diff --git a/backend/src/contracts/contracts.py b/backend/src/contracts/contracts.py index c88e813..430c459 100644 --- a/backend/src/contracts/contracts.py +++ b/backend/src/contracts/contracts.py @@ -1,9 +1,11 @@ """Router for contract resource""" +import datetime import io import zipfile import src.contracts.service as service import src.forms.service as form_service +import src.shipments.service as shipment_service from fastapi import APIRouter, Depends, HTTPException, Query from fastapi.responses import StreamingResponse from sqlmodel import Session @@ -44,10 +46,15 @@ async def create_contract( ) ) ) + remaining_shipments = shipment_service.get_remaining_shipments( + session, + new_contract.form.id, + datetime.datetime.today() + ) prices = service.generate_products_prices( occasionals, recurrents, - new_contract.form.shipments + remaining_shipments ) recurrent_price = prices['recurrent'] total_price = prices['total'] @@ -111,7 +118,7 @@ async def get_base_contract_template( cheque_payment_method = list(filter( lambda x: x.name == "cheque", form.productor.payment_methods)) cheque_number = cheque_payment_method[0].max if len( - cheque_payment_method) > 0 else 3 + cheque_payment_method) > 0 and cheque_payment_method[0].max is not None else 3 print(cheque_number, cheque_payment_method) empty_contract = models.ContractPublic( firstname='', diff --git a/backend/src/shipments/service.py b/backend/src/shipments/service.py index c49ef85..8d37474 100644 --- a/backend/src/shipments/service.py +++ b/backend/src/shipments/service.py @@ -49,6 +49,19 @@ def get_one(session: Session, shipment_id: int) -> models.ShipmentPublic: return session.get(models.Shipment, shipment_id) +def get_remaining_shipments(session: Session, form_id: int, date: datetime.date) -> list[models.ShipmentPublic]: + statement = ( + select(models.Shipment) + .join( + models.Form, + models.Shipment.form_id == models.Form.id + ) + .where(models.Form.id == form_id) + .where(models.Shipment.date >= date) + ) + return session.exec(statement.order_by(models.Shipment.name)).all() + + def create_one( session: Session, shipment: models.ShipmentCreate) -> models.ShipmentPublic: diff --git a/frontend/locales/en.json b/frontend/locales/en.json index 93d050f..6fc8dc5 100644 --- a/frontend/locales/en.json +++ b/frontend/locales/en.json @@ -94,7 +94,7 @@ "filter by productor": "filter by producer", "filter by season": "filter by season", "filter by type": "filter by type", - "for this contract": "for this contract.", + "for this contract": "for this contract", "for transfer method contact your referer or productor": "for bank transfer, contact your referent or producer.", "form": "contract form", "form name": "contract form name", @@ -184,6 +184,8 @@ "shipment products": "shipment products", "shipment products is necessary only for occasional products (if all products are recurrent leave empty)": "shipment products configuration is only necessary for occasional products (leave empty if all products are recurrent).", "shipments": "shipments", + "remaining_shipments_one": "remaining shipment", + "remaining_shipments_other": "remaining shipments", "some contracts require a minimum value per shipment, ignore this field if it's not the case": "some contracts require a minimum value per shipment. Ignore this field if it does not apply to your contract.", "start": "start", "start date": "start date", diff --git a/frontend/locales/fr.json b/frontend/locales/fr.json index 5cc437a..f94a6e0 100644 --- a/frontend/locales/fr.json +++ b/frontend/locales/fr.json @@ -94,7 +94,7 @@ "filter by productor": "filtrer par producteur·trice", "filter by season": "filtrer par saisons", "filter by type": "filtrer par type", - "for this contract": "pour ce contrat.", + "for this contract": "pour ce contrat", "for transfer method contact your referer or productor": "pour mettre en place le virement automatique, contactez votre référent ou le producteur.", "form": "formulaire de contrat", "form name": "nom du formulaire de contrat", @@ -184,6 +184,8 @@ "shipment products": "produits pour la livraison", "shipment products is necessary only for occasional products (if all products are recurrent leave empty)": "il est nécessaire de configurer les produits pour la livraison uniquement si il y a des produits occasionnels (laisser vide si tous les produits sont récurents).", "shipments": "livraisons", + "remaining_shipments_one": "livraison restante", + "remaining_shipments_other": "livraisons restantes", "some contracts require a minimum value per shipment, ignore this field if it's not the case": "certains contrats nécessitent une valeur minimum par livraison. Ce champ peut être ignoré s’il ne s’applique pas à votre contrat.", "start": "début", "start date": "date de début", diff --git a/frontend/src/pages/Contract/index.tsx b/frontend/src/pages/Contract/index.tsx index 458a5a1..bf68907 100644 --- a/frontend/src/pages/Contract/index.tsx +++ b/frontend/src/pages/Contract/index.tsx @@ -24,6 +24,7 @@ import { useParams } from "react-router"; import { computePrices } from "./price"; import { ContractCheque } from "@/components/PaymentMethods/Cheque"; import { tranformProducts, type ContractInputs } from "@/services/resources/contracts"; +import type { Shipment } from "@/services/resources/shipments"; export function Contract() { const { id } = useParams(); @@ -71,12 +72,22 @@ export function Contract() { return form?.productor?.products; }, [form]); + const remainingShipments = useMemo(() => { + const currentDate = new Date(); + return form?.shipments.reduce((acc, shipment) => { + if (new Date(shipment.date) > currentDate) { + return [...acc, shipment]; + } + return acc; + }, [] as Shipment[]) + }, [form]) + const price = useMemo(() => { if (!allProducts) { return 0; } const productValues = Object.entries(inputForm.getValues().products); - return computePrices(productValues, allProducts, form?.shipments.length); + return computePrices(productValues, allProducts, remainingShipments?.length); }, [inputForm, allProducts, form?.shipments]); const inputRefs = useRef>({ @@ -229,7 +240,7 @@ export function Contract() { /> {t("shipments", { capfirst: true })} - {`${t("there is", { capfirst: true })} ${shipments.length} ${shipments.length > 1 ? t("shipments") : t("shipment")} ${t("for this contract")}`} + {`${t("there is", { capfirst: true })} ${shipments.length} ${shipments.length > 1 ? t("shipments") : t("shipment")} ${t("for this contract")} :`} {shipments.map((shipment) => ( @@ -243,6 +254,20 @@ export function Contract() { ))} + {`${t("there is", { capfirst: true })} ${remainingShipments?.length} ${t("remaining_shipments", {count: remainingShipments?.length})} :`} + + {remainingShipments?.map((shipment) => ( + + {`${shipment.name} : + ${new Date(shipment.date).toLocaleDateString("fr-FR", { + weekday: "long", + year: "numeric", + month: "long", + day: "numeric", + })}`} + + ))} + {productsRecurent.length > 0 ? ( <> {t("recurrent products", { capfirst: true })} @@ -256,12 +281,12 @@ export function Contract() { ))} ) : null} - {shipments.some((shipment) => shipment.products.length > 0) ? ( + {remainingShipments?.some((shipment) => shipment.products.length > 0) ? ( <> {t("occasional products", { capfirst: true })} {t("select products per shipment", { capfirst: true })} - {shipments.map((shipment, index) => ( + {remainingShipments?.map((shipment, index) => (