diff --git a/api/rpk-api/firm/__init__.py b/api/rpk-api/firm/__init__.py index 4ffc770..b54deb6 100644 --- a/api/rpk-api/firm/__init__.py +++ b/api/rpk-api/firm/__init__.py @@ -3,10 +3,12 @@ from fastapi import APIRouter from firm.entity import entity_router from firm.template import template_router from firm.contract import contract_router +from firm.current_firm.routes import current_firm_router firm_router = APIRouter(prefix="/{instance}/{firm}") +firm_router.include_router(current_firm_router, tags=["Current Firm"]) firm_router.include_router(entity_router, prefix="/entities", tags=["Entity"], ) firm_router.include_router(template_router, prefix="/templates", tags=["Template"], ) firm_router.include_router(contract_router, prefix="/contracts", ) diff --git a/api/rpk-api/firm/contract/print/__init__.py b/api/rpk-api/firm/contract/print/__init__.py index 8aa435d..3268c77 100644 --- a/api/rpk-api/firm/contract/print/__init__.py +++ b/api/rpk-api/firm/contract/print/__init__.py @@ -12,7 +12,7 @@ from weasyprint.text.fonts import FontConfiguration from pathlib import Path -from firm.core.routes import get_tenant_db_cursor +from firm.core.depends import get_tenant_db_cursor from firm.entity.models import Entity from firm.template.models import ProvisionTemplate from firm.contract.models import ContractDraft, Contract, ContractStatus, replace_variables_in_value diff --git a/api/rpk-api/firm/contract/routes_contract.py b/api/rpk-api/firm/contract/routes_contract.py index 24a20d0..6864d5c 100644 --- a/api/rpk-api/firm/contract/routes_contract.py +++ b/api/rpk-api/firm/contract/routes_contract.py @@ -1,7 +1,8 @@ import uuid from fastapi import Depends, HTTPException -from firm.core.routes import get_crud_router, get_logged_tenant_db_cursor +from firm.core.routes import get_crud_router +from firm.core.depends import get_logged_tenant_db_cursor from firm.contract.models import Contract, ContractDraft, ContractDraftStatus, replace_variables_in_value, ContractFilters from firm.contract.schemas import ContractCreate, ContractRead, ContractUpdate, ContractInit diff --git a/api/rpk-api/firm/contract/routes_draft.py b/api/rpk-api/firm/contract/routes_draft.py index a9be126..620f990 100644 --- a/api/rpk-api/firm/contract/routes_draft.py +++ b/api/rpk-api/firm/contract/routes_draft.py @@ -1,7 +1,8 @@ from beanie import PydanticObjectId from fastapi import HTTPException, Depends -from firm.core.routes import get_crud_router, get_logged_tenant_db_cursor +from firm.core.routes import get_crud_router +from firm.core.depends import get_logged_tenant_db_cursor from firm.contract.models import ContractDraft, ContractDraftStatus, ContractDraftFilters from firm.contract.schemas import ContractDraftCreate, ContractDraftRead, ContractDraftUpdate diff --git a/api/rpk-api/firm/contract/routes_signature.py b/api/rpk-api/firm/contract/routes_signature.py index 46447d8..140ae08 100644 --- a/api/rpk-api/firm/contract/routes_signature.py +++ b/api/rpk-api/firm/contract/routes_signature.py @@ -4,7 +4,7 @@ import shutil from uuid import UUID from firm.contract.models import Contract, Party -from firm.core.routes import get_tenant_db_cursor +from firm.core.depends import get_tenant_db_cursor signature_router = APIRouter() diff --git a/api/rpk-api/firm/core/depends.py b/api/rpk-api/firm/core/depends.py new file mode 100644 index 0000000..28c191f --- /dev/null +++ b/api/rpk-api/firm/core/depends.py @@ -0,0 +1,34 @@ +from fastapi import HTTPException, Depends +from hub.auth import get_current_user + +from firm.db import get_db_client +from firm.current_firm import CurrentFirmModel + +async def get_tenant_db_cursor(instance: str, firm: str, db_client=Depends(get_db_client)): + db_cursor = db_client[f"tenant_{instance}_{firm}"] + current_firm = await CurrentFirmModel.get(db_cursor) + if current_firm is None: + raise HTTPException(status_code=405, detail=f"Firm needs to be instantiated first") + db_cursor.firm = current_firm + return db_cursor + +def get_logged_tenant_db_cursor(db_cursor=Depends(get_tenant_db_cursor), user=Depends(get_current_user)): + for firm in user.firms: + if firm == db_cursor.firm: + db_cursor.user = user + return db_cursor + + raise HTTPException(status_code=404, detail="This firm doesn't exist or you are not allowed to access it.") + +async def get_uninitialized_tenant_db_cursor(instance: str, firm: str, db_client=Depends(get_db_client), user=Depends(get_current_user)): + db_cursor = db_client[f"tenant_{instance}_{firm}"] + current_firm = await CurrentFirmModel.get(db_cursor) + if current_firm is not None: + HTTPException(status_code=409, detail="Firm configuration already exists") + + for firm in user.firms: + if firm == db_cursor.firm: + db_cursor.user = user + return db_cursor + + raise HTTPException(status_code=404, detail="This firm doesn't exist or you are not allowed to access it.") diff --git a/api/rpk-api/firm/core/models.py b/api/rpk-api/firm/core/models.py index ae718a5..e9c0bec 100644 --- a/api/rpk-api/firm/core/models.py +++ b/api/rpk-api/firm/core/models.py @@ -9,9 +9,9 @@ from pydantic import BaseModel, Field, computed_field class CrudDocument(BaseModel): id: Optional[PydanticObjectId] = Field(default=None) created_at: datetime = Field(default=datetime.now(UTC), nullable=False, title="Créé le") -# created_by: str - updated_at: datetime = Field(default_factory=datetime.utcnow, nullable=False, title="Modifié le") -# updated_by: str + created_by: str = Field(nullable=False, title="Créé par") + updated_at: datetime = Field(default_factory=lambda: datetime.now(UTC), nullable=False, title="Modifié le") + updated_by: str = Field(nullable=False, title="Modifié par") @property def _id(self): @@ -37,8 +37,9 @@ class CrudDocument(BaseModel): @classmethod async def create(cls, db, create_schema): - values = cls.model_validate(create_schema.model_dump()).model_dump(mode="json") - result = await cls._get_collection(db).insert_one(values) + model_dict = create_schema.model_dump() | {"created_by": db.user, "updated_by":db.user} + document = cls.model_validate(model_dict).model_dump(mode="json") + result = await cls._get_collection(db).insert_one(document) return await cls.get(db, result.inserted_id) @@ -56,12 +57,12 @@ class CrudDocument(BaseModel): @classmethod async def get(cls, db, model_id): - value = await cls._get_collection(db).find_one({"_id": model_id}) - if not value: + document = await cls._get_collection(db).find_one({"_id": model_id}) + if not document: return None - value["id"] = value.pop("_id") - return cls.model_validate(value) + document["id"] = document.pop("_id") + return cls.model_validate(document) @classmethod async def update(cls, db, model, update_schema): diff --git a/api/rpk-api/firm/core/routes.py b/api/rpk-api/firm/core/routes.py index 9aa5462..b0d0e0e 100644 --- a/api/rpk-api/firm/core/routes.py +++ b/api/rpk-api/firm/core/routes.py @@ -5,26 +5,9 @@ from fastapi_filter import FilterDepends from fastapi_pagination import Page, add_pagination from fastapi_pagination.ext.motor import paginate -from hub.auth import get_current_user +from firm.core.depends import get_logged_tenant_db_cursor from firm.core.models import CrudDocument from firm.core.schemas import Writer, Reader -from firm.db import get_db_client - - - -#instance: str="westside", firm: str="cht", -def get_tenant_db_cursor(db_client=Depends(get_db_client)): - instance = "westside" - firm = "cht" - return db_client[f"tenant_{instance}_{firm}"] - -#instance: str="westside", firm: str="cht", -def get_logged_tenant_db_cursor(db_client=Depends(get_db_client), user=Depends(get_current_user)): - instance = "westside" - firm = "cht" - db_cursor = db_client[f"tenant_{instance}_{firm}"] - db_cursor.user = user - return db_cursor def get_crud_router(model: CrudDocument, model_create: Writer, model_read: Reader, model_update: Writer, model_filter): model_name = model.__name__ diff --git a/api/rpk-api/firm/current_firm/__init__.py b/api/rpk-api/firm/current_firm/__init__.py new file mode 100644 index 0000000..bcd41ca --- /dev/null +++ b/api/rpk-api/firm/current_firm/__init__.py @@ -0,0 +1,45 @@ +from typing import Any + +from pydantic import Field + +from firm.core.models import CrudDocument +from firm.core.schemas import Writer, Reader +from firm.entity.schemas import EntityIndividualCreate, EntityCorporationCreate + + +class CurrentFirmModel(CrudDocument): + instance: str + firm: str + name: str = Field(nullable=False) + + # primary_color: str = Field() + # secondary_color: str = Field() + + def __eq__(self, other: Any) -> bool: + if isinstance(other, dict): + return self.instance == other["instance"] and self.firm == other["firm"] + return super().__eq__(other) + + def compute_label(self) -> str: + return self.name + + @classmethod + async def get(cls, db): + document = await cls._get_collection(db).find_one({}) + if not document: + return None + + document["id"] = document.pop("_id") + return cls.model_validate(document) + + +class CurrentFirmSchemaRead(Reader): + pass + +class CurrentFirmSchemaCreate(Writer): + corporation: EntityCorporationCreate = Field(title="Informations sur la firme") + owner: EntityIndividualCreate = Field(title="Informations sur le dirigeant") + + +class CurrentFirmSchemaUpdate(Writer): + pass diff --git a/api/rpk-api/firm/current_firm/routes.py b/api/rpk-api/firm/current_firm/routes.py new file mode 100644 index 0000000..1107fa1 --- /dev/null +++ b/api/rpk-api/firm/current_firm/routes.py @@ -0,0 +1,22 @@ +from fastapi import APIRouter, Depends + +from firm.core.depends import get_logged_tenant_db_cursor, get_uninitialized_tenant_db_cursor +from firm.current_firm import CurrentFirmModel, CurrentFirmSchemaRead, CurrentFirmSchemaCreate, CurrentFirmSchemaUpdate + +current_firm_router = APIRouter() + +@current_firm_router.get("/", response_model=CurrentFirmSchemaRead, response_description=f"Current Firm records retrieved") +async def read(db=Depends(get_logged_tenant_db_cursor)) -> CurrentFirmSchemaRead: + return CurrentFirmSchemaRead.from_model(**CurrentFirmModel.get(db)) + +@current_firm_router.post("/", response_description=f"Current Firm added to the database") +async def create(schema: CurrentFirmSchemaCreate, db=Depends(get_uninitialized_tenant_db_cursor)) -> CurrentFirmSchemaRead: + await schema.validate_foreign_key(db) + record = await CurrentFirmModel.create(db, schema) + return CurrentFirmSchemaRead.from_model(record) + +@current_firm_router.put("/", response_description=f"Current Firm record updated") +async def update(schema: CurrentFirmSchemaUpdate, db=Depends(get_logged_tenant_db_cursor)) -> CurrentFirmSchemaRead: + record = await CurrentFirmModel.get(db) + record = await CurrentFirmModel.update(db, record, schema) + return CurrentFirmSchemaRead.from_model(record) diff --git a/api/rpk-api/firm/entity/schemas.py b/api/rpk-api/firm/entity/schemas.py index 237710a..f465f72 100644 --- a/api/rpk-api/firm/entity/schemas.py +++ b/api/rpk-api/firm/entity/schemas.py @@ -13,5 +13,11 @@ class EntityCreate(Writer): class Config: title = "Création d'un client" +class EntityIndividualCreate(EntityCreate): + entity_data: Individual + +class EntityCorporationCreate(EntityCreate): + entity_data: Corporation + class EntityUpdate(EntityCreate): pass diff --git a/gui/rpk-gui/src/App.tsx b/gui/rpk-gui/src/App.tsx index 7b76f75..bd41d72 100644 --- a/gui/rpk-gui/src/App.tsx +++ b/gui/rpk-gui/src/App.tsx @@ -55,7 +55,7 @@ function App() { queries: { retry: (failureCount, error) => { // @ts-ignore - if (error.status >= 400 && error.status <= 499) { + if (error.statusCode >= 400 && error.statusCode <= 499) { return false } return failureCount < 4 diff --git a/gui/rpk-gui/src/pages/firm/index.tsx b/gui/rpk-gui/src/pages/firm/index.tsx index 9aa1d78..ac1876b 100644 --- a/gui/rpk-gui/src/pages/firm/index.tsx +++ b/gui/rpk-gui/src/pages/firm/index.tsx @@ -2,6 +2,9 @@ import {Route, Routes} from "react-router"; import React, { useContext } from "react"; import { FirmContext, FirmContextProvider } from "../../contexts/FirmContext"; import { Header } from "../../components"; +import { useOne } from "@refinedev/core"; +import { CrudForm } from "../../lib/crud/components/crud-form"; +import { IFirm } from "../../interfaces"; export const FirmRoutes = () => { return ( @@ -22,7 +25,31 @@ export const FirmRoutes = () => { const FirmHome = () => { const { currentFirm } = useContext(FirmContext); + const { data: firm, isError, error, isLoading } = useOne({resource: 'firm', id: `${currentFirm.instance}/${currentFirm.firm}`, errorNotification: false}) + + if (isLoading) { + return

Loading...

+ } + + if (isError && error?.statusCode == 405) { + return + } + return (

This is la firme {currentFirm.instance} / {currentFirm.firm}

); } + +type FirmInitFormPros = { + currentFirm: IFirm +} + +const FirmInitForm = (props: FirmInitFormPros) => { + const { currentFirm } = props; + return ( + <> +

Initialization of {`${currentFirm.instance} / ${currentFirm.firm}`}

+ + + ) +} \ No newline at end of file diff --git a/gui/rpk-gui/src/providers/data-provider.tsx b/gui/rpk-gui/src/providers/data-provider.tsx index 604bc80..4259c30 100644 --- a/gui/rpk-gui/src/providers/data-provider.tsx +++ b/gui/rpk-gui/src/providers/data-provider.tsx @@ -1,11 +1,20 @@ -import type { DataProvider } from "@refinedev/core"; +import type { DataProvider, HttpError } from "@refinedev/core"; const API_URL = "/api/v1"; export const dataProvider: DataProvider = { getOne: async ({ resource, id, meta }) => { const response = id !== "" ? await fetch(`${API_URL}/${resource}/${id}`) : await fetch(`${API_URL}/${resource}`); - if (response.status < 200 || response.status > 299) throw response; + if (response.status < 200 || response.status > 299) { + if (response.status == 405) { + const error: HttpError = { + message: "Resource is not ready", + statusCode: 405, + }; + return Promise.reject(error); + } + throw response; + } const data = await response.json(); @@ -22,8 +31,16 @@ export const dataProvider: DataProvider = { }, }); - if (response.status < 200 || response.status > 299) throw response; - + if (response.status < 200 || response.status > 299) { + if (response.status == 405) { + const error: HttpError = { + message: "Resource is not ready", + statusCode: 405, + }; + return Promise.reject(error); + } + throw response; + } const data = await response.json(); return { data }; @@ -51,7 +68,16 @@ export const dataProvider: DataProvider = { const response = await fetch(`${API_URL}/${resource}?${params.toString()}`); - if (response.status < 200 || response.status > 299) throw response; + if (response.status < 200 || response.status > 299) { + if (response.status == 405) { + const error: HttpError = { + message: "Resource is not ready", + statusCode: 405, + }; + return Promise.reject(error); + } + throw response; + } const data = await response.json(); @@ -75,7 +101,16 @@ export const dataProvider: DataProvider = { }, }); - if (response.status < 200 || response.status > 299) throw response; + if (response.status < 200 || response.status > 299) { + if (response.status == 405) { + const error: HttpError = { + message: "Resource is not ready", + statusCode: 405, + }; + return Promise.reject(error); + } + throw response; + } const data = await response.json(); @@ -86,7 +121,16 @@ export const dataProvider: DataProvider = { method: "DELETE", }); - if (response.status < 200 || response.status > 299) throw response; + if (response.status < 200 || response.status > 299) { + if (response.status == 405) { + const error: HttpError = { + message: "Resource is not ready", + statusCode: 405, + }; + return Promise.reject(error); + } + throw response; + } const data = await response.json();