Full Working static multi tenant
This commit is contained in:
@@ -1,105 +1,12 @@
|
||||
import uuid
|
||||
from fastapi import Depends, HTTPException, File, UploadFile
|
||||
import shutil
|
||||
from fastapi import APIRouter
|
||||
|
||||
from ..core.routes import get_crud_router
|
||||
from .routes_contract import contract_router as contract_subrouter
|
||||
from .routes_signature import signature_router
|
||||
from .routes_draft import draft_router
|
||||
from .print import print_router
|
||||
|
||||
from .models import Contract, ContractDraft, ContractDraftStatus, Party, replace_variables_in_value
|
||||
from .schemas import ContractCreate, ContractRead, ContractUpdate
|
||||
|
||||
from ..entity.models import Entity
|
||||
from ..template.models import ProvisionTemplate
|
||||
from ..user.manager import get_current_user, get_current_superuser
|
||||
|
||||
contract_router = get_crud_router(Contract, ContractCreate, ContractRead, ContractUpdate)
|
||||
del(contract_router.routes[0])
|
||||
del(contract_router.routes[2])
|
||||
del(contract_router.routes[2])
|
||||
|
||||
contract_router = APIRouter()
|
||||
contract_router.include_router(draft_router, prefix="/draft", )
|
||||
contract_router.include_router(contract_subrouter, )
|
||||
contract_router.include_router(print_router, prefix="/print", )
|
||||
|
||||
|
||||
@contract_router.post("/", response_description="Contract Successfully created")
|
||||
async def create(item: ContractCreate, user=Depends(get_current_user)) -> dict:
|
||||
await item.validate_foreign_key()
|
||||
|
||||
draft = await ContractDraft.get(item.draft_id)
|
||||
|
||||
for v in draft.variables:
|
||||
if not v.key or not v.value:
|
||||
raise HTTPException(status_code=400, detail="Variable {} is invalid".format(v))
|
||||
|
||||
contract_dict = item.dict()
|
||||
del(contract_dict['draft_id'])
|
||||
|
||||
contract_dict['lawyer'] = await Entity.get(user.entity_id)
|
||||
|
||||
contract_dict['name'] = draft.name
|
||||
contract_dict['title'] = draft.title
|
||||
parties = []
|
||||
for p in draft.parties:
|
||||
parties.append({
|
||||
'entity': await Entity.get(p.entity_id),
|
||||
'part': p.part,
|
||||
'representative': await Entity.get(p.representative_id) if p.representative_id else None,
|
||||
'signature_uuid': str(uuid.uuid4())
|
||||
})
|
||||
|
||||
contract_dict['parties'] = parties
|
||||
|
||||
provisions = []
|
||||
for p in draft.provisions:
|
||||
p = p.provision
|
||||
provision = await ProvisionTemplate.get(p.provision_template_id) if p.type == 'template' \
|
||||
else p
|
||||
|
||||
provisions.append({
|
||||
'title': replace_variables_in_value(draft.variables, provision.title),
|
||||
'body': replace_variables_in_value(draft.variables, provision.body)
|
||||
})
|
||||
|
||||
contract_dict['provisions'] = provisions
|
||||
|
||||
o = await Contract(**contract_dict).create()
|
||||
|
||||
await draft.update({"$set": {"status": ContractDraftStatus.published}})
|
||||
return {"message": "Contract Successfully created", "id": o.id}
|
||||
|
||||
|
||||
@contract_router.put("/{id}", response_description="")
|
||||
async def update(id: str, contract_form: ContractUpdate, user=Depends(get_current_superuser)) -> ContractRead:
|
||||
raise HTTPException(status_code=400, detail="No modification on contract")
|
||||
|
||||
|
||||
@contract_router.get("/signature/{signature_id}", response_description="")
|
||||
async def get_signature(signature_id: str) -> Party:
|
||||
contract = await Contract.find_by_signature_id(signature_id)
|
||||
signature = contract.get_signature(signature_id)
|
||||
return signature
|
||||
|
||||
|
||||
@contract_router.post("/signature/{signature_id}", response_description="")
|
||||
async def affix_signature(signature_id: str, signature_file: UploadFile = File(...)) -> bool:
|
||||
contract = await Contract.find_by_signature_id(signature_id)
|
||||
|
||||
signature_index = contract.get_signature_index(signature_id)
|
||||
signature = contract.parties[signature_index]
|
||||
|
||||
if signature.signature_affixed:
|
||||
raise HTTPException(status_code=400, detail="Signature already affixed")
|
||||
|
||||
with open(f'media/signatures/{signature_id}.png', "wb") as buffer:
|
||||
shutil.copyfileobj(signature_file.file, buffer)
|
||||
|
||||
update_query = {"$set": {
|
||||
f'parties.{signature_index}.signature_affixed': True
|
||||
}}
|
||||
signature.signature_affixed = True
|
||||
if contract.is_signed():
|
||||
update_query["$set"]['status'] = 'signed'
|
||||
await contract.update(update_query)
|
||||
|
||||
return True
|
||||
contract_router.include_router(signature_router, prefix="/signature", )
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import datetime
|
||||
from typing import List, Literal, Optional
|
||||
from enum import Enum
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field, validator
|
||||
from beanie.operators import ElemMatch
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from ..core.models import CrudDocument, RichtextSingleline, RichtextMultiline, DictionaryEntry
|
||||
from ..core.filter import Filter
|
||||
from ..entity.models import Entity
|
||||
|
||||
|
||||
@@ -57,9 +58,12 @@ class Party(BaseModel):
|
||||
signature_affixed: bool = False
|
||||
signature_png: Optional[str] = None
|
||||
|
||||
class ContractProvisionType(Enum):
|
||||
genuine = 'genuine'
|
||||
template = 'template'
|
||||
|
||||
class ProvisionGenuine(BaseModel):
|
||||
type: Literal['genuine'] = 'genuine'
|
||||
type: Literal['genuine'] = ContractProvisionType.genuine
|
||||
title: str = RichtextSingleline(props={"parametrized": True}, default="", title="Titre")
|
||||
body: str = RichtextMultiline(props={"parametrized": True}, default="", title="Corps")
|
||||
|
||||
@@ -68,7 +72,7 @@ class ProvisionGenuine(BaseModel):
|
||||
|
||||
|
||||
class ContractProvisionTemplateReference(BaseModel):
|
||||
type: Literal['template'] = 'template'
|
||||
type: Literal['template'] = ContractProvisionType.template
|
||||
provision_template_id: str = Field(
|
||||
foreignKey={
|
||||
"reference": {
|
||||
@@ -97,6 +101,9 @@ class Provision(BaseModel):
|
||||
title: str = RichtextSingleline(title="Titre")
|
||||
body: str = RichtextMultiline(title="Corps")
|
||||
|
||||
class ContractDraftUpdateStatus(BaseModel):
|
||||
status: str = Field()
|
||||
todo: List[str] = Field(default=[])
|
||||
|
||||
class ContractDraft(CrudDocument):
|
||||
"""
|
||||
@@ -130,7 +137,7 @@ class ContractDraft(CrudDocument):
|
||||
class Config:
|
||||
title = 'Brouillon de contrat'
|
||||
|
||||
async def check_is_ready(self):
|
||||
async def check_is_ready(self, db):
|
||||
if self.status == ContractDraftStatus.published:
|
||||
return
|
||||
|
||||
@@ -152,18 +159,18 @@ class ContractDraft(CrudDocument):
|
||||
|
||||
for v in self.variables:
|
||||
if not (v.key and v.value):
|
||||
self.todo.append('Empty variable')
|
||||
self.todo.append(f'Empty variable: {v.key}')
|
||||
|
||||
if self.todo:
|
||||
self.status = ContractDraftStatus.in_progress
|
||||
else:
|
||||
self.status = ContractDraftStatus.ready
|
||||
|
||||
await self.update({"$set": {
|
||||
"status": self.status,
|
||||
"todo": self.todo
|
||||
}})
|
||||
await self.update(db, self, ContractDraftUpdateStatus(status=self.status, todo=self.todo))
|
||||
|
||||
async def update_status(self, db, status):
|
||||
update = ContractDraftUpdateStatus(status=status)
|
||||
await self.update(db, self, update)
|
||||
|
||||
class Contract(CrudDocument):
|
||||
"""
|
||||
@@ -181,19 +188,14 @@ class Contract(CrudDocument):
|
||||
lawyer: Entity = Field(title="Avocat en charge")
|
||||
location: str = Field(title="Lieu")
|
||||
date: datetime.date = Field(title="Date")
|
||||
label: Optional[str] = None
|
||||
|
||||
@validator("label", always=True)
|
||||
def generate_label(cls, v, values, **kwargs):
|
||||
if not v:
|
||||
contract_label = values['title']
|
||||
for p in values['parties']:
|
||||
contract_label = contract_label + f" - {p.entity.label}"
|
||||
def compute_label(self) -> str:
|
||||
contract_label = self.title
|
||||
for p in self.parties:
|
||||
contract_label = f"{contract_label} - {p.entity.label}"
|
||||
|
||||
contract_label = contract_label + f" - {values['date'].strftime('%m/%d/%Y')}"
|
||||
return contract_label
|
||||
|
||||
return v
|
||||
contract_label = f"{contract_label} - {self.date.strftime('%m/%d/%Y')}"
|
||||
return contract_label
|
||||
|
||||
class Settings(CrudDocument.Settings):
|
||||
fulltext_search = ['name', 'title']
|
||||
@@ -205,18 +207,19 @@ class Contract(CrudDocument):
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def find_by_signature_id(cls, signature_id: str):
|
||||
crit = ElemMatch(cls.parties, {"signature_uuid": signature_id})
|
||||
return cls.find_one(crit)
|
||||
async def find_by_signature_id(cls, db, signature_id: UUID):
|
||||
request = {'parties': {"$elemMatch": {"signature_uuid": str(signature_id) }}}
|
||||
value = await cls._get_collection(db).find_one(request)
|
||||
return cls.model_validate(value) if value else None
|
||||
|
||||
def get_signature(self, signature_id: str):
|
||||
for p in self.parties:
|
||||
if p.signature_uuid == signature_id:
|
||||
if p.signature_uuid == str(signature_id):
|
||||
return p
|
||||
|
||||
def get_signature_index(self, signature_id: str):
|
||||
for i, p in enumerate(self.parties):
|
||||
if p.signature_uuid == signature_id:
|
||||
if p.signature_uuid == str(signature_id):
|
||||
return i
|
||||
|
||||
def is_signed(self):
|
||||
@@ -225,9 +228,39 @@ class Contract(CrudDocument):
|
||||
return False
|
||||
return True
|
||||
|
||||
async def affix_signature(self, db, signature_index):
|
||||
update_query = {"$set": {
|
||||
f'parties.{signature_index}.signature_affixed': True
|
||||
}}
|
||||
|
||||
self.parties[signature_index].signature_affixed = True
|
||||
if self.is_signed():
|
||||
update_query["$set"]['status'] = 'signed'
|
||||
|
||||
await self._get_collection(db).update_one({"_id": self.id}, update_query)
|
||||
return await self.get(db, self.id)
|
||||
|
||||
|
||||
def replace_variables_in_value(variables, value: str):
|
||||
for v in variables:
|
||||
if v.value:
|
||||
value = value.replace('%{}%'.format(v.key), v.value)
|
||||
return value
|
||||
|
||||
class ContractDraftFilters(Filter):
|
||||
name__like: Optional[str] = None
|
||||
|
||||
order_by: Optional[list[str]] = None
|
||||
|
||||
class Constants(Filter.Constants):
|
||||
model = ContractDraft
|
||||
search_model_fields = ["name"]
|
||||
|
||||
class ContractFilters(Filter):
|
||||
name__like: Optional[str] = None
|
||||
|
||||
order_by: Optional[list[str]] = None
|
||||
|
||||
class Constants(Filter.Constants):
|
||||
model = Contract
|
||||
search_model_fields = ["name"]
|
||||
@@ -1,16 +1,19 @@
|
||||
import datetime
|
||||
import os
|
||||
import base64
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Request
|
||||
from fastapi import APIRouter, HTTPException, Request, Depends
|
||||
from fastapi.responses import HTMLResponse, FileResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from pydantic import BaseModel
|
||||
|
||||
from weasyprint import HTML, CSS
|
||||
from weasyprint.text.fonts import FontConfiguration
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from app.core.routes import get_tenant_db_cursor
|
||||
from app.entity.models import Entity
|
||||
from app.template.models import ProvisionTemplate
|
||||
from ..models import ContractDraft, Contract, ContractStatus, replace_variables_in_value
|
||||
@@ -77,16 +80,24 @@ async def render_css(root_url, contract):
|
||||
})
|
||||
|
||||
|
||||
def retrieve_signature_png(filepath):
|
||||
with open(filepath, "rb") as f:
|
||||
b_content = f.read()
|
||||
base64_utf8_str = base64.b64encode(b_content).decode('utf-8')
|
||||
ext = filepath.split('.')[-1]
|
||||
return f'data:image/{ext};base64,{base64_utf8_str}'
|
||||
|
||||
|
||||
@print_router.get("/preview/draft/{draft_id}", response_class=HTMLResponse)
|
||||
async def preview_draft(draft_id: str, request: Request) -> str:
|
||||
draft = await build_model(await ContractDraft.get(draft_id))
|
||||
async def preview_draft(draft_id: str, db=Depends(get_tenant_db_cursor)) -> str:
|
||||
draft = await build_model(await ContractDraft.get(db, draft_id))
|
||||
|
||||
return await render_print('', draft)
|
||||
|
||||
|
||||
@print_router.get("/preview/signature/{signature_id}", response_class=HTMLResponse)
|
||||
async def preview_contract_by_signature(signature_id: str, request: Request) -> str:
|
||||
contract = await Contract.find_by_signature_id(signature_id)
|
||||
async def preview_contract_by_signature(signature_id: UUID, db=Depends(get_tenant_db_cursor)) -> str:
|
||||
contract = await Contract.find_by_signature_id(db, signature_id)
|
||||
for p in contract.parties:
|
||||
if p.signature_affixed:
|
||||
p.signature_png = retrieve_signature_png(f'media/signatures/{p.signature_uuid}.png')
|
||||
@@ -95,8 +106,8 @@ async def preview_contract_by_signature(signature_id: str, request: Request) ->
|
||||
|
||||
|
||||
@print_router.get("/preview/{contract_id}", response_class=HTMLResponse)
|
||||
async def preview_contract(contract_id: str, request: Request) -> str:
|
||||
contract = await Contract.get(contract_id)
|
||||
async def preview_contract(contract_id: str, db=Depends(get_tenant_db_cursor)) -> str:
|
||||
contract = await Contract.get(db, contract_id)
|
||||
for p in contract.parties:
|
||||
if p.signature_affixed:
|
||||
p.signature_png = retrieve_signature_png(f'media/signatures/{p.signature_uuid}.png')
|
||||
@@ -105,8 +116,8 @@ async def preview_contract(contract_id: str, request: Request) -> str:
|
||||
|
||||
|
||||
@print_router.get("/pdf/{contract_id}", response_class=FileResponse)
|
||||
async def create_pdf(contract_id: str) -> str:
|
||||
contract = await Contract.get(contract_id)
|
||||
async def create_pdf(contract_id: str, db=Depends(get_tenant_db_cursor)) -> str:
|
||||
contract = await Contract.get(db, contract_id)
|
||||
contract_path = "media/contracts/{}.pdf".format(contract_id)
|
||||
if not os.path.isfile(contract_path):
|
||||
if contract.status != ContractStatus.signed:
|
||||
@@ -122,10 +133,8 @@ async def create_pdf(contract_id: str) -> str:
|
||||
css = CSS(string=await render_css('http://nginx', contract), font_config=font_config)
|
||||
|
||||
html.write_pdf(contract_path, stylesheets=[css], font_config=font_config)
|
||||
update_query = {"$set": {
|
||||
'status': 'printed'
|
||||
}}
|
||||
await contract.update(update_query)
|
||||
|
||||
await contract.update_status(db, 'printed')
|
||||
|
||||
return FileResponse(
|
||||
contract_path,
|
||||
@@ -133,17 +142,9 @@ async def create_pdf(contract_id: str) -> str:
|
||||
filename=contract.label)
|
||||
|
||||
|
||||
def retrieve_signature_png(filepath):
|
||||
with open(filepath, "rb") as f:
|
||||
b_content = f.read()
|
||||
base64_utf8_str = base64.b64encode(b_content).decode('utf-8')
|
||||
ext = filepath.split('.')[-1]
|
||||
return f'data:image/{ext};base64,{base64_utf8_str}'
|
||||
|
||||
|
||||
@print_router.get("/opengraph/{signature_id}", response_class=HTMLResponse)
|
||||
async def get_signature_opengraph(signature_id: str, request: Request) -> str:
|
||||
contract = await Contract.find_by_signature_id(signature_id)
|
||||
async def get_signature_opengraph(signature_id: str, request: Request, db=Depends(get_tenant_db_cursor)) -> str:
|
||||
contract = await Contract.find_by_signature_id(db, signature_id)
|
||||
signature = contract.get_signature(signature_id)
|
||||
template = templates.get_template("opengraph.html")
|
||||
|
||||
|
||||
70
back/app/contract/routes_contract.py
Normal file
70
back/app/contract/routes_contract.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import uuid
|
||||
from fastapi import Depends, HTTPException
|
||||
|
||||
from ..core.routes import get_crud_router, get_logged_tenant_db_cursor
|
||||
|
||||
from .models import Contract, ContractDraft, ContractDraftStatus, replace_variables_in_value, ContractFilters
|
||||
from .schemas import ContractCreate, ContractRead, ContractUpdate, ContractInit
|
||||
|
||||
from ..entity.models import Entity
|
||||
from ..template.models import ProvisionTemplate
|
||||
from ..user.manager import get_current_user
|
||||
|
||||
|
||||
contract_router = get_crud_router(Contract, ContractCreate, ContractRead, ContractUpdate, ContractFilters)
|
||||
del(contract_router.routes[4]) #delete
|
||||
del(contract_router.routes[3]) #update
|
||||
del(contract_router.routes[1]) #create
|
||||
|
||||
@contract_router.post("/", response_description="Contract Successfully created")
|
||||
async def create(schema: ContractCreate, db=Depends(get_logged_tenant_db_cursor), user=Depends(get_current_user)) -> ContractRead:
|
||||
await schema.validate_foreign_key(db)
|
||||
|
||||
draft = await ContractDraft.get(db, schema.draft_id)
|
||||
if not draft:
|
||||
raise HTTPException(status_code=404, detail=f"Contract draft not found!")
|
||||
|
||||
for v in draft.variables:
|
||||
if not v.key or not v.value:
|
||||
raise HTTPException(status_code=400, detail="Variable {} is invalid".format(v))
|
||||
|
||||
contract_dict = schema.model_dump()
|
||||
del(contract_dict['draft_id'])
|
||||
|
||||
lawyer = await Entity.get(db, user.entity_id)
|
||||
contract_dict['lawyer'] = lawyer.model_dump()
|
||||
|
||||
contract_dict['name'] = draft.name
|
||||
contract_dict['title'] = draft.title
|
||||
parties = []
|
||||
for p in draft.parties:
|
||||
parties.append({
|
||||
'entity': await Entity.get(db, p.entity_id),
|
||||
'part': p.part,
|
||||
'representative': await Entity.get(db, p.representative_id) if p.representative_id else None,
|
||||
'signature_uuid': str(uuid.uuid4())
|
||||
})
|
||||
|
||||
contract_dict['parties'] = parties
|
||||
|
||||
provisions = []
|
||||
for p in draft.provisions:
|
||||
p = p.provision
|
||||
provision = await ProvisionTemplate.get(db, p.provision_template_id) if p.type == "template" \
|
||||
else p
|
||||
|
||||
provisions.append({
|
||||
'title': replace_variables_in_value(draft.variables, provision.title),
|
||||
'body': replace_variables_in_value(draft.variables, provision.body)
|
||||
})
|
||||
|
||||
contract_dict['provisions'] = provisions
|
||||
|
||||
record = await Contract.create(db, ContractInit(**contract_dict))
|
||||
await draft.update_status(db, ContractDraftStatus.published)
|
||||
|
||||
return ContractRead.from_model(record)
|
||||
|
||||
@contract_router.put("/{record_id}", response_description="")
|
||||
async def update(record_id: str, contract_form: ContractUpdate, db=Depends(get_logged_tenant_db_cursor)) -> ContractRead:
|
||||
raise HTTPException(status_code=400, detail="No modification on contract")
|
||||
@@ -1,47 +1,42 @@
|
||||
from beanie import PydanticObjectId
|
||||
from fastapi import HTTPException, Depends
|
||||
|
||||
from ..core.routes import get_crud_router
|
||||
from ..core.routes import get_crud_router, get_logged_tenant_db_cursor
|
||||
from ..user.manager import get_current_user
|
||||
|
||||
from .models import ContractDraft, ContractDraftStatus
|
||||
from .models import ContractDraft, ContractDraftStatus, ContractDraftFilters
|
||||
from .schemas import ContractDraftCreate, ContractDraftRead, ContractDraftUpdate
|
||||
|
||||
draft_router = get_crud_router(ContractDraft, ContractDraftCreate, ContractDraftRead, ContractDraftUpdate)
|
||||
draft_router = get_crud_router(ContractDraft, ContractDraftCreate, ContractDraftRead, ContractDraftUpdate, ContractDraftFilters)
|
||||
|
||||
del(draft_router.routes[0])
|
||||
del(draft_router.routes[2])
|
||||
del(draft_router.routes[3]) #update route
|
||||
del(draft_router.routes[1]) #post route
|
||||
|
||||
|
||||
@draft_router.post("/", response_description="Contract Draft added to the database")
|
||||
async def create(item: ContractDraftCreate, user=Depends(get_current_user)) -> dict:
|
||||
await item.validate_foreign_key()
|
||||
o = await ContractDraft(**item.dict()).create()
|
||||
await o.check_is_ready()
|
||||
async def create(schema: ContractDraftCreate, db=Depends(get_logged_tenant_db_cursor)) -> ContractDraftRead:
|
||||
await schema.validate_foreign_key(db)
|
||||
record = await ContractDraft.create(db, schema)
|
||||
await record.check_is_ready(db)
|
||||
|
||||
return {"message": "Contract Draft added successfully", "id": o.id}
|
||||
return ContractDraftRead.from_model(record)
|
||||
|
||||
|
||||
@draft_router.put("/{id}", response_description="Contract Draft record updated")
|
||||
async def update(id: PydanticObjectId, req: ContractDraftUpdate, user=Depends(get_current_user)) -> ContractDraftRead:
|
||||
req = {k: v for k, v in req.dict().items() if v is not None}
|
||||
update_query = {"$set": {
|
||||
field: value for field, value in req.items()
|
||||
}}
|
||||
|
||||
item = await ContractDraft.get(id)
|
||||
if not item:
|
||||
@draft_router.put("/{record_id}", response_description="Contract Draft record updated")
|
||||
async def update(record_id: PydanticObjectId, schema: ContractDraftUpdate, db=Depends(get_logged_tenant_db_cursor)) -> ContractDraftRead:
|
||||
record = await ContractDraft.get(db, record_id)
|
||||
if not record:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="Contract Draft record not found!"
|
||||
)
|
||||
if item.status == ContractDraftStatus.published:
|
||||
if record.status == ContractDraftStatus.published:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Contract Draft has already been published"
|
||||
)
|
||||
|
||||
await item.update(update_query)
|
||||
await item.check_is_ready()
|
||||
record = await ContractDraft.update(db, record, schema)
|
||||
await record.check_is_ready(db)
|
||||
|
||||
return ContractDraftRead(**item.dict())
|
||||
return ContractDraftRead.from_model(record)
|
||||
|
||||
35
back/app/contract/routes_signature.py
Normal file
35
back/app/contract/routes_signature.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from fastapi import Depends, HTTPException, File, UploadFile, APIRouter
|
||||
import shutil
|
||||
|
||||
from uuid import UUID
|
||||
|
||||
from .models import Contract, Party
|
||||
from ..core.routes import get_tenant_db_cursor
|
||||
|
||||
|
||||
signature_router = APIRouter()
|
||||
|
||||
@signature_router.get("/{signature_id}", response_description="")
|
||||
async def get_signature(signature_id: UUID, db=Depends(get_tenant_db_cursor)) -> Party:
|
||||
contract = await Contract.find_by_signature_id(db, signature_id)
|
||||
signature = contract.get_signature(signature_id)
|
||||
return signature
|
||||
|
||||
@signature_router.post("/{signature_id}", response_description="")
|
||||
async def affix_signature(signature_id: UUID, signature_file: UploadFile = File(...), db=Depends(get_tenant_db_cursor)) -> bool:
|
||||
contract = await Contract.find_by_signature_id(db, signature_id)
|
||||
|
||||
if not contract:
|
||||
raise HTTPException(status_code=404, detail="Contract record not found!")
|
||||
|
||||
signature_index = contract.get_signature_index(signature_id)
|
||||
signature = contract.parties[signature_index]
|
||||
|
||||
if signature.signature_affixed:
|
||||
raise HTTPException(status_code=400, detail="Signature already affixed")
|
||||
|
||||
with open(f'media/signatures/{signature_id}.png', "wb") as buffer:
|
||||
shutil.copyfileobj(signature_file.file, buffer)
|
||||
|
||||
await contract.affix_signature(db, signature_index)
|
||||
return True
|
||||
@@ -6,11 +6,11 @@ from pydantic import BaseModel, Field
|
||||
from .models import ContractDraft, DraftProvision, DraftParty, Contract
|
||||
|
||||
from ..entity.models import Entity
|
||||
from ..core.schemas import Writer
|
||||
from ..core.schemas import Writer, Reader
|
||||
from ..core.models import DictionaryEntry
|
||||
|
||||
|
||||
class ContractDraftRead(ContractDraft):
|
||||
class ContractDraftRead(Reader, ContractDraft):
|
||||
pass
|
||||
|
||||
|
||||
@@ -28,12 +28,12 @@ class ContractDraftCreate(Writer):
|
||||
title='Variables'
|
||||
)
|
||||
|
||||
async def validate_foreign_key(self):
|
||||
return
|
||||
async def validate_foreign_key(self, db):
|
||||
for p in self.parties:
|
||||
p.entity = await Entity.get(p.entity)
|
||||
if p.entity is None:
|
||||
raise ValueError
|
||||
if p.entity_id:
|
||||
p.entity = await Entity.get(db, p.entity_id)
|
||||
if p.entity is None:
|
||||
raise ValueError
|
||||
|
||||
|
||||
class ContractDraftUpdate(ContractDraftCreate):
|
||||
@@ -57,7 +57,7 @@ class PartyRead(BaseModel):
|
||||
title = "Partie"
|
||||
|
||||
|
||||
class ContractRead(Contract):
|
||||
class ContractRead(Reader, Contract):
|
||||
parties: List[PartyRead]
|
||||
lawyer: ForeignEntityRead
|
||||
|
||||
@@ -70,6 +70,14 @@ class ContractCreate(Writer):
|
||||
location: str
|
||||
draft_id: str
|
||||
|
||||
class ContractInit(BaseModel):
|
||||
date: datetime.date
|
||||
location: str
|
||||
lawyer: dict
|
||||
name: str
|
||||
title: str
|
||||
parties: List[dict]
|
||||
provisions: List[dict]
|
||||
|
||||
class ContractUpdate(BaseModel):
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user