From 7e42fbe106e71085e2ba983307caefe8ae002957 Mon Sep 17 00:00:00 2001 From: Julien Aldon Date: Fri, 13 Feb 2026 17:46:24 +0100 Subject: [PATCH] add contract page with dynamic form elements --- backend/src/auth/auth.py | 1 - backend/src/forms/service.py | 2 +- backend/src/models.py | 35 ++- backend/src/productors/service.py | 2 +- backend/src/products/products.py | 3 +- backend/src/products/service.py | 22 +- backend/src/shipments/service.py | 2 +- backend/src/templates/service.py | 2 +- backend/src/users/service.py | 2 +- frontend/locales/en.json | 28 ++- frontend/locales/fr.json | 31 ++- frontend/src/components/Forms/Modal/index.tsx | 35 +-- frontend/src/components/Forms/Row/index.tsx | 5 +- frontend/src/components/Navbar/index.tsx | 2 +- .../components/Productors/Filter/index.tsx | 2 + .../src/components/Productors/Modal/index.tsx | 4 +- .../src/components/Productors/Row/index.tsx | 5 +- .../src/components/Products/Filter/index.tsx | 2 + .../src/components/Products/Form/index.tsx | 44 ++++ .../src/components/Products/Modal/index.tsx | 130 +++++++----- .../src/components/Products/Row/index.tsx | 17 +- .../src/components/Shipments/Form/index.tsx | 101 +++++---- .../src/components/Shipments/Modal/index.tsx | 24 ++- .../src/components/Shipments/Row/index.tsx | 5 +- frontend/src/components/Users/Modal/index.tsx | 4 +- frontend/src/components/Users/Row/index.tsx | 5 +- frontend/src/config/i18n.tsx | 3 +- frontend/src/pages/Contract/index.tsx | 199 +++++++++++++++--- frontend/src/pages/Forms/index.tsx | 4 +- frontend/src/pages/Productors/index.tsx | 6 +- frontend/src/pages/Products/index.tsx | 11 +- frontend/src/pages/Shipments/index.tsx | 4 +- frontend/src/pages/Users/index.tsx | 4 +- frontend/src/services/resources/products.ts | 57 +++-- 34 files changed, 540 insertions(+), 263 deletions(-) create mode 100644 frontend/src/components/Products/Form/index.tsx diff --git a/backend/src/auth/auth.py b/backend/src/auth/auth.py index f9732d8..801d496 100644 --- a/backend/src/auth/auth.py +++ b/backend/src/auth/auth.py @@ -58,7 +58,6 @@ def callback(code: str, session: Session = Depends(get_session)): email=decoded_token.get("email"), name=decoded_token.get("preferred_username") ) - print(user_create) user = service.get_or_create_user(session, user_create) return { "access_token": token_data["access_token"], diff --git a/backend/src/forms/service.py b/backend/src/forms/service.py index 4371e7d..7465abd 100644 --- a/backend/src/forms/service.py +++ b/backend/src/forms/service.py @@ -11,7 +11,7 @@ def get_all( statement = statement.where(models.Form.season.in_(seasons)) if len(productors) > 0: statement = statement.join(models.Productor).where(models.Productor.name.in_(productors)) - return session.exec(statement).all() + return session.exec(statement.order_by(models.Form.name)).all() def get_one(session: Session, form_id: int) -> models.FormPublic: return session.get(models.Form, form_id) diff --git a/backend/src/models.py b/backend/src/models.py index d7e7155..66e8650 100644 --- a/backend/src/models.py +++ b/backend/src/models.py @@ -33,7 +33,12 @@ class ProductorPublic(ProductorBase): class Productor(ProductorBase, table=True): id: int | None = Field(default=None, primary_key=True) - products: list["Product"] = Relationship(back_populates='productor') + products: list["Product"] = Relationship( + back_populates='productor', + sa_relationship_kwargs={ + "order_by": "Product.name" + }, + ) class ProductorUpdate(SQLModel): name: str | None @@ -60,9 +65,10 @@ class ShipmentProductLink(SQLModel, table=True): class ProductBase(SQLModel): name: str unit: Unit - price: float + price: float | None price_kg: float | None - weight: float | None + quantity: float | None + quantity_unit: str | None type: ProductType productor_id: int | None = Field(default=None, foreign_key="productor.id") @@ -81,12 +87,13 @@ class ProductUpdate(SQLModel): unit: Unit | None price: float | None price_kg: float | None - weight: float | None + quantity: float | None + quantity_unit: str | None productor_id: int | None - shipment_ids: list[int] | None = [] + type: ProductType | None class ProductCreate(ProductBase): - shipment_ids: list[int] | None = [] + pass class FormBase(SQLModel): name: str @@ -106,7 +113,13 @@ class Form(FormBase, table=True): id: int | None = Field(default=None, primary_key=True) productor: Optional['Productor'] = Relationship() referer: Optional['User'] = Relationship() - shipments: list["Shipment"] = Relationship(back_populates="form", cascade_delete=True) + shipments: list["Shipment"] = Relationship( + back_populates="form", + cascade_delete=True, + sa_relationship_kwargs={ + "order_by": "Shipment.name" + }, + ) class FormUpdate(SQLModel): name: str | None @@ -161,7 +174,13 @@ class ShipmentPublic(ShipmentBase): class Shipment(ShipmentBase, table=True): id: int | None = Field(default=None, primary_key=True) - products: list[Product] = Relationship(back_populates="shipments", link_model=ShipmentProductLink) + products: list[Product] = Relationship( + back_populates="shipments", + link_model=ShipmentProductLink, + sa_relationship_kwargs={ + "order_by": "Product.name" + }, + ) form: Optional[Form] = Relationship(back_populates="shipments") class ShipmentUpdate(SQLModel): diff --git a/backend/src/productors/service.py b/backend/src/productors/service.py index 0d3aa55..a6d9e80 100644 --- a/backend/src/productors/service.py +++ b/backend/src/productors/service.py @@ -7,7 +7,7 @@ def get_all(session: Session, names: list[str], types: list[str]) -> list[models statement = statement.where(models.Productor.name.in_(names)) if len(types) > 0: statement = statement.where(models.Productor.type.in_(types)) - return session.exec(statement).all() + return session.exec(statement.order_by(models.Productor.name)).all() def get_one(session: Session, productor_id: int) -> models.ProductorPublic: return session.get(models.Productor, productor_id) diff --git a/backend/src/products/products.py b/backend/src/products/products.py index 902af0f..0bdf100 100644 --- a/backend/src/products/products.py +++ b/backend/src/products/products.py @@ -11,9 +11,10 @@ router = APIRouter(prefix='/products') def get_products( session: Session = Depends(get_session), names: list[str] = Query([]), + types: list[str] = Query([]), productors: list[str] = Query([]), ): - return service.get_all(session, names, productors) + return service.get_all(session, names, productors, types) @router.get('/{id}', response_model=models.ProductPublic) def get_product(id: int, session: Session = Depends(get_session)): diff --git a/backend/src/products/service.py b/backend/src/products/service.py index 4470c10..951a992 100644 --- a/backend/src/products/service.py +++ b/backend/src/products/service.py @@ -4,23 +4,25 @@ import src.models as models def get_all( session: Session, names: list[str], - productors: list[str] + productors: list[str], + types: list[str], + ) -> list[models.ProductPublic]: statement = select(models.Product) if len(names) > 0: statement = statement.where(models.Product.name.in_(names)) if len(productors) > 0: statement = statement.join(models.Productor).where(models.Productor.name.in_(productors)) - return session.exec(statement).all() + if len(types) > 0: + statement = statement.where(models.Product.type.in_(types)) + return session.exec(statement.order_by(models.Product.name)).all() def get_one(session: Session, product_id: int) -> models.ProductPublic: return session.get(models.Product, product_id) def create_one(session: Session, product: models.ProductCreate) -> models.ProductPublic: - shipments = session.exec(select(models.Shipment).where(models.Shipment.id.in_(product.shipment_ids))).all() - - product_create = product.model_dump(exclude_unset=True, exclude={'shipment_ids'}) - new_product = models.Product(**product_create, shipments=shipments) + product_create = product.model_dump(exclude_unset=True) + new_product = models.Product(**product_create) session.add(new_product) session.commit() session.refresh(new_product) @@ -32,13 +34,7 @@ def update_one(session: Session, id: int, product: models.ProductUpdate) -> mode new_product = result.first() if not new_product: return None - - shipments_to_add = session.exec(select(models.Shipment).where(models.Shipment.id.in_(product.shipment_ids))).all() - new_product.shipments.clear() - for add in shipments_to_add: - new_product.shipments.append(add) - - product_updates = product.model_dump(exclude_unset=True, exclude={"shipment_ids"}) + product_updates = product.model_dump(exclude_unset=True) for key, value in product_updates.items(): setattr(new_product, key, value) diff --git a/backend/src/shipments/service.py b/backend/src/shipments/service.py index 80b9e2c..c2c0071 100644 --- a/backend/src/shipments/service.py +++ b/backend/src/shipments/service.py @@ -3,7 +3,7 @@ import src.models as models def get_all(session: Session) -> list[models.ShipmentPublic]: statement = select(models.Shipment) - return session.exec(statement).all() + return session.exec(statement.order_by(models.Shipment.name)).all() def get_one(session: Session, shipment_id: int) -> models.ShipmentPublic: return session.get(models.Shipment, shipment_id) diff --git a/backend/src/templates/service.py b/backend/src/templates/service.py index 1d28256..ea0e37f 100644 --- a/backend/src/templates/service.py +++ b/backend/src/templates/service.py @@ -3,7 +3,7 @@ import src.models as models def get_all(session: Session) -> list[models.TemplatePublic]: statement = select(models.Template) - return session.exec(statement).all() + return session.exec(statement.order_by(models.Template.name)).all() def get_one(session: Session, template_id: int) -> models.TemplatePublic: return session.get(models.Template, template_id) diff --git a/backend/src/users/service.py b/backend/src/users/service.py index 3d9e7ac..825f48a 100644 --- a/backend/src/users/service.py +++ b/backend/src/users/service.py @@ -3,7 +3,7 @@ import src.models as models def get_all(session: Session) -> list[models.UserPublic]: statement = select(models.User) - return session.exec(statement).all() + return session.exec(statement.order_by(models.User.name)).all() def get_one(session: Session, user_id: int) -> models.UserPublic: return session.get(models.User, user_id) diff --git a/frontend/locales/en.json b/frontend/locales/en.json index f4a46a4..72eefe5 100644 --- a/frontend/locales/en.json +++ b/frontend/locales/en.json @@ -1,10 +1,15 @@ { "product name": "product name", "product price": "product price", - "product weight": "product weight", + "product quantity": "product quantity", + "product quantity unit": "product quantity unit", "product type": "product type", "planned": "planned", - "reccurent": "reccurent", + "planned products": "planned products", + "select products per shipment": "select products per shipment.", + "recurrent products": "recurrent products", + "your selection in this category will apply for all shipments": "your selection in this category will apply for all shipments.", + "recurrent": "recurrent", "product price kg": "product price kg", "product unit": "product unit", "grams": "grams", @@ -19,7 +24,8 @@ "referer": "referer", "edit form": "edit form", "form name": "form name", - "contact season": "contact season", + "contract season": "contract season", + "contract season recommandation": "recommandation : - (example: Winter-2025)", "start date": "start date", "end date": "end date", "nothing found": "nothing found", @@ -41,13 +47,19 @@ "productor address": "productor address", "productor payment": "productor payment", "priceKg": "priceKg", - "weight": "weight", + "quantity": "quantity", + "quantity unit": "quantity unit", + "unit": "sell unit", "price": "price", "create product": "create product", "informations": "informations", "remove product": "remove product", "edit product": "edit product", "shipment name": "shipment name", + "shipments": "shipments", + "shipment": "shipment", + "there is": "there is", + "for this contract": "for this contact.", "shipment date": "shipment date", "remove shipment": "remove shipment", "productors": "productors", @@ -65,5 +77,11 @@ "a start date": "a start date", "a end date": "a end date", "a productor": "a productor", - "a referer": "a referer" + "a referer": "a referer", + "a phone": "a phone", + "a fistname": "a fistname", + "a lastname": "a lastname", + "a email": "a email", + "submit contract": "submit contract", + "all theses informations are for contract generation, no informations is stored outside of contracts": "all theses informations are for contract generation, no informations is stored outside of contracts." } \ No newline at end of file diff --git a/frontend/locales/fr.json b/frontend/locales/fr.json index ad4a481..089d81c 100644 --- a/frontend/locales/fr.json +++ b/frontend/locales/fr.json @@ -1,15 +1,21 @@ { "product name": "nom du produit", "product price": "prix du produit", - "product weight": "poids du produit", + "product quantity": "quantité du produit", "product type": "type de produit", "planned": "planifié", - "reccurent": "récurrent", + "planned products": "Produits planifiés par livraison", + "select products per shipment": "Selectionnez les produits pour chaque livraison.", + "recurrent": "récurrent", + "recurrent products": "Produits récurents", + "your selection in this category will apply for all shipments": "votre selection sera appliquée pour chaque livraisons (Exemple: 6 livraisons, le produits sera comptés 6 fois : une fois par livraison).", "product price kg": "prix du produit au Kilo", "product unit": "unité de vente du produit", "grams": "grammes", "kilo": "kilo", "piece": "pièce", + "in": "en", + "enter quantity": "entrez la quantitée", "filter by season": "filtrer par saisons", "name": "nom", "season": "saison", @@ -19,7 +25,8 @@ "referer": "référent·e", "edit form": "modifier le formulaire de contrat", "form name": "nom du formulaire de contrat", - "contact season": "saison du contrat", + "contract season": "saison du contrat", + "contract season recommandation": "recommandation : - (Exemple: Hiver-2025)", "start date": "date de début", "end date": "date de fin", "nothing found": "rien à afficher", @@ -35,13 +42,15 @@ "address": "adresse", "payment": "ordre du chèque", "type": "type", - "create productor": "créer le producteur·troce", + "create productor": "créer le producteur·trice", "productor name": "nom du producteur·trice", "productor type": "type du producteur·trice", "productor address": "adresse du producteur·trice", "productor payment": "ordre du chèque du producteur·trice", "priceKg": "prix au kilo", - "weight": "poids", + "quantity": "quantité", + "quantity unit": "unité de quantité", + "unit": "Unité de vente", "price": "prix", "create product": "créer le produit", "informations": "informations", @@ -49,6 +58,10 @@ "edit product": "modifier le produit", "shipment name": "nom de la livraison", "shipment date": "date de la livraison", + "shipments": "livraisons", + "shipment": "livraison", + "there is": "il y a", + "for this contract": "pour ce contrat.", "remove shipment": "supprimer la livraison", "productors": "producteur·trices", "products": "produits", @@ -66,5 +79,11 @@ "a start date": "une date de début", "a end date": "une date de fin", "a productor": "un(e) producteur·trice", - "a referer": "un référent·e" + "a referer": "un référent·e", + "a phone": "un numéro de téléphone", + "a fistname": "un prénom", + "a lastname": "un nom", + "a email": "une adresse email", + "submit contract": "Envoyer le contrat", + "all theses informations are for contract generation, no informations is stored outside of contracts": "ces informations sont nécéssaires pour la génération de contrat, aucune information personnelle n'est gardée ailleurs que dans les contrats générés." } \ No newline at end of file diff --git a/frontend/src/components/Forms/Modal/index.tsx b/frontend/src/components/Forms/Modal/index.tsx index 76f228a..527342e 100644 --- a/frontend/src/components/Forms/Modal/index.tsx +++ b/frontend/src/components/Forms/Modal/index.tsx @@ -67,7 +67,7 @@ export default function FormModal({ return ( - - + + + + - + + + ({value: key, label: t(value)}))} + {...form.getInputProps('unit')} + /> + + + + + + +