add(remaining): add a way to generate contracts only with remaining shipments
All checks were successful
Deploy Amap / deploy (push) Successful in 41s

This commit is contained in:
Julien Aldon
2026-04-17 13:33:07 +02:00
parent 4b57b29b34
commit 0c4f499878
5 changed files with 57 additions and 8 deletions

View File

@@ -1,9 +1,11 @@
"""Router for contract resource""" """Router for contract resource"""
import datetime
import io import io
import zipfile import zipfile
import src.contracts.service as service import src.contracts.service as service
import src.forms.service as form_service import src.forms.service as form_service
import src.shipments.service as shipment_service
from fastapi import APIRouter, Depends, HTTPException, Query from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from sqlmodel import Session 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( prices = service.generate_products_prices(
occasionals, occasionals,
recurrents, recurrents,
new_contract.form.shipments remaining_shipments
) )
recurrent_price = prices['recurrent'] recurrent_price = prices['recurrent']
total_price = prices['total'] total_price = prices['total']
@@ -111,7 +118,7 @@ async def get_base_contract_template(
cheque_payment_method = list(filter( cheque_payment_method = list(filter(
lambda x: x.name == "cheque", form.productor.payment_methods)) lambda x: x.name == "cheque", form.productor.payment_methods))
cheque_number = cheque_payment_method[0].max if len( 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) print(cheque_number, cheque_payment_method)
empty_contract = models.ContractPublic( empty_contract = models.ContractPublic(
firstname='', firstname='',

View File

@@ -49,6 +49,19 @@ def get_one(session: Session, shipment_id: int) -> models.ShipmentPublic:
return session.get(models.Shipment, shipment_id) 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( def create_one(
session: Session, session: Session,
shipment: models.ShipmentCreate) -> models.ShipmentPublic: shipment: models.ShipmentCreate) -> models.ShipmentPublic:

View File

@@ -94,7 +94,7 @@
"filter by productor": "filter by producer", "filter by productor": "filter by producer",
"filter by season": "filter by season", "filter by season": "filter by season",
"filter by type": "filter by type", "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.", "for transfer method contact your referer or productor": "for bank transfer, contact your referent or producer.",
"form": "contract form", "form": "contract form",
"form name": "contract form name", "form name": "contract form name",
@@ -184,6 +184,8 @@
"shipment products": "shipment products", "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).", "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", "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.", "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": "start",
"start date": "start date", "start date": "start date",

View File

@@ -94,7 +94,7 @@
"filter by productor": "filtrer par producteur·trice", "filter by productor": "filtrer par producteur·trice",
"filter by season": "filtrer par saisons", "filter by season": "filtrer par saisons",
"filter by type": "filtrer par type", "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.", "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": "formulaire de contrat",
"form name": "nom du formulaire de contrat", "form name": "nom du formulaire de contrat",
@@ -184,6 +184,8 @@
"shipment products": "produits pour la livraison", "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).", "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", "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é sil ne sapplique pas à votre contrat.", "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é sil ne sapplique pas à votre contrat.",
"start": "début", "start": "début",
"start date": "date de début", "start date": "date de début",

View File

@@ -24,6 +24,7 @@ import { useParams } from "react-router";
import { computePrices } from "./price"; import { computePrices } from "./price";
import { ContractCheque } from "@/components/PaymentMethods/Cheque"; import { ContractCheque } from "@/components/PaymentMethods/Cheque";
import { tranformProducts, type ContractInputs } from "@/services/resources/contracts"; import { tranformProducts, type ContractInputs } from "@/services/resources/contracts";
import type { Shipment } from "@/services/resources/shipments";
export function Contract() { export function Contract() {
const { id } = useParams(); const { id } = useParams();
@@ -71,12 +72,22 @@ export function Contract() {
return form?.productor?.products; return form?.productor?.products;
}, [form]); }, [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(() => { const price = useMemo(() => {
if (!allProducts) { if (!allProducts) {
return 0; return 0;
} }
const productValues = Object.entries(inputForm.getValues().products); const productValues = Object.entries(inputForm.getValues().products);
return computePrices(productValues, allProducts, form?.shipments.length); return computePrices(productValues, allProducts, remainingShipments?.length);
}, [inputForm, allProducts, form?.shipments]); }, [inputForm, allProducts, form?.shipments]);
const inputRefs = useRef<Record<string, HTMLInputElement | null>>({ const inputRefs = useRef<Record<string, HTMLInputElement | null>>({
@@ -229,7 +240,7 @@ export function Contract() {
/> />
</Group> </Group>
<Title order={3}>{t("shipments", { capfirst: true })}</Title> <Title order={3}>{t("shipments", { capfirst: true })}</Title>
<Text>{`${t("there is", { capfirst: true })} ${shipments.length} ${shipments.length > 1 ? t("shipments") : t("shipment")} ${t("for this contract")}`}</Text> <Text>{`${t("there is", { capfirst: true })} ${shipments.length} ${shipments.length > 1 ? t("shipments") : t("shipment")} ${t("for this contract")} :`}</Text>
<List> <List>
{shipments.map((shipment) => ( {shipments.map((shipment) => (
<List.Item key={shipment.id}> <List.Item key={shipment.id}>
@@ -243,6 +254,20 @@ export function Contract() {
</List.Item> </List.Item>
))} ))}
</List> </List>
<Text>{`${t("there is", { capfirst: true })} ${remainingShipments?.length} ${t("remaining_shipments", {count: remainingShipments?.length})} :`}</Text>
<List>
{remainingShipments?.map((shipment) => (
<List.Item key={shipment.id}>
{`${shipment.name} :
${new Date(shipment.date).toLocaleDateString("fr-FR", {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
})}`}
</List.Item>
))}
</List>
{productsRecurent.length > 0 ? ( {productsRecurent.length > 0 ? (
<> <>
<Title order={3}>{t("recurrent products", { capfirst: true })}</Title> <Title order={3}>{t("recurrent products", { capfirst: true })}</Title>
@@ -256,12 +281,12 @@ export function Contract() {
))} ))}
</> </>
) : null} ) : null}
{shipments.some((shipment) => shipment.products.length > 0) ? ( {remainingShipments?.some((shipment) => shipment.products.length > 0) ? (
<> <>
<Title order={3}>{t("occasional products", { capfirst: true })}</Title> <Title order={3}>{t("occasional products", { capfirst: true })}</Title>
<Text>{t("select products per shipment", { capfirst: true })}</Text> <Text>{t("select products per shipment", { capfirst: true })}</Text>
<Accordion defaultValue={"0"}> <Accordion defaultValue={"0"}>
{shipments.map((shipment, index) => ( {remainingShipments?.map((shipment, index) => (
<ShipmentForm <ShipmentForm
minimumPrice={form.minimum_shipment_value} minimumPrice={form.minimum_shipment_value}
shipment={shipment} shipment={shipment}