Compare commits

...

2 Commits

Author SHA1 Message Date
Julien Aldon
cfb8d435a8 Merge branch 'main' of gitea.aldon.fr:Mop/amap
All checks were successful
Deploy Amap / deploy (push) Successful in 35s
2026-02-23 15:38:45 +01:00
Julien Aldon
124b0700da add visible field to form 2026-02-23 15:38:29 +01:00
19 changed files with 119 additions and 15 deletions

View File

@@ -21,6 +21,8 @@
- check products
- Publish
## Only show productors / products / forms for referent of type
## Footer
### Legal

View File

@@ -0,0 +1,33 @@
"""message
Revision ID: e777ed5729ce
Revises: 7854064278ce
Create Date: 2026-02-23 13:53:09.999893
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import sqlmodel.sql.sqltypes
# revision identifiers, used by Alembic.
revision: str = 'e777ed5729ce'
down_revision: Union[str, Sequence[str], None] = '7854064278ce'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('form', sa.Column('visible', sa.Boolean(), nullable=False, default=False, server_default="False"))
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('form', 'visible')
# ### end Alembic commands ###

View File

@@ -13,10 +13,20 @@ async def get_forms(
seasons: list[str] = Query([]),
productors: list[str] = Query([]),
current_season: bool = False,
session: Session = Depends(get_session)
session: Session = Depends(get_session),
):
return service.get_all(session, seasons, productors, current_season)
@router.get('/referents', response_model=list[models.FormPublic])
async def get_forms_filtered(
seasons: list[str] = Query([]),
productors: list[str] = Query([]),
current_season: bool = False,
session: Session = Depends(get_session),
user: models.User = Depends(get_current_user)
):
return service.get_all(session, seasons, productors, current_season, user)
@router.get('/{id}', response_model=models.FormPublic)
async def get_form(id: int, session: Session = Depends(get_session)):
result = service.get_one(session, id)

View File

@@ -7,12 +7,20 @@ def get_all(
seasons: list[str],
productors: list[str],
current_season: bool,
user: models.User = None
) -> list[models.FormPublic]:
statement = select(models.Form)
if user:
statement = statement\
.join(models.Productor, models.Form.productor_id == models.Productor.id)\
.where(models.Productor.type.in_([r.name for r in user.roles]))\
.distinct()
if len(seasons) > 0:
statement = statement.where(models.Form.season.in_(seasons))
if len(productors) > 0:
statement = statement.join(models.Productor).where(models.Productor.name.in_(productors))
if not user:
statement = statement.where(models.Form.visible == True)
if current_season:
subquery = (
select(
@@ -29,6 +37,8 @@ def get_all(
(models.Productor.type == subquery.c.type) &
(models.Form.start == subquery.c.max_start)
)
if not user:
statement = statement.where(models.Form.visible == True)
return session.exec(statement.order_by(models.Form.name)).all()
return session.exec(statement.order_by(models.Form.name)).all()

View File

@@ -136,6 +136,7 @@ class FormBase(SQLModel):
start: datetime.date
end: datetime.date
minimum_shipment_value: float | None
visible: bool
class FormPublic(FormBase):
id: int
@@ -167,6 +168,7 @@ class FormUpdate(SQLModel):
start: datetime.date | None
end: datetime.date | None
minimum_shipment_value: float | None
visible: bool | None
class FormCreate(FormBase):
pass

View File

@@ -15,7 +15,7 @@ def get_productors(
user: models.User = Depends(get_current_user),
session: Session = Depends(get_session)
):
return service.get_all(session, names, types)
return service.get_all(session, user, names, types)
@router.get('/{id}', response_model=models.ProductorPublic)
def get_productor(

View File

@@ -3,10 +3,13 @@ import src.models as models
def get_all(
session: Session,
user: models.User,
names: list[str],
types: list[str]
) -> list[models.ProductorPublic]:
statement = select(models.Productor)
statement = select(models.Productor)\
.where(models.Productor.type.in_([r.name for r in user.roles]))\
.distinct()
if len(names) > 0:
statement = statement.where(models.Productor.name.in_(names))
if len(types) > 0:

View File

@@ -17,6 +17,7 @@ def get_products(
):
return service.get_all(
session,
user,
names,
productors,
types,

View File

@@ -3,11 +3,15 @@ import src.models as models
def get_all(
session: Session,
user: models.User,
names: list[str],
productors: list[str],
types: list[str],
) -> list[models.ProductPublic]:
statement = select(models.Product)
statement = select(models.Product)\
.join(models.Productor, models.Product.productor_id == models.Productor.id)\
.where(models.Productor.type.in_([r.name for r in user.roles]))\
.distinct()
if len(names) > 0:
statement = statement.where(models.Product.name.in_(names))
if len(productors) > 0:

View File

@@ -3,11 +3,16 @@ import src.models as models
def get_all(
session: Session,
user: models.User,
names: list[str],
dates: list[str],
forms: list[int]
) -> list[models.ShipmentPublic]:
statement = select(models.Shipment)
statement = select(models.Shipment)\
.join(models.Form, models.Shipment.form_id == models.Form.id)\
.join(models.Productor, models.Form.productor_id == models.Productor.id)\
.where(models.Productor.type.in_([r.name for r in user.roles]))\
.distinct()
if len(names) > 0:
statement = statement.where(models.Shipment.name.in_(names))
if len(dates) > 0:

View File

@@ -11,12 +11,14 @@ router = APIRouter(prefix='/shipments')
@router.get('', response_model=list[models.ShipmentPublic], )
def get_shipments(
session: Session = Depends(get_session),
user: models.User = Depends(get_current_user),
names: list[str] = Query([]),
dates: list[str] = Query([]),
forms: list[str] = Query([]),
):
return service.get_all(
session,
user,
names,
dates,
forms,

View File

@@ -82,7 +82,10 @@
"you can download all contracts for your form using the export all": "you can download all contracts for your form using the export all",
"in the same corner you can download a recap by clicking on the button": "in the same corner you can download a recap by clicking on the",
"once all contracts downloaded, you can delete the form (to avoid new submissions) and hide it from the home page": "once all contracts downloaded, you can delete the form (to avoid new submissions) and hide it from the home page",
"by checking this option the form will be accessible publicly on the home page, only check it if everything is fine with your form": "by checking this option the form will be accessible publicly on the home page, only check it if everything is fine with your form",
"contracts": "contracts",
"hidden": "hidden",
"visible": "visible",
"minimum price for this shipment should be at least": "minimum price for this shipment should be at least",
"there is": "there is",
"for this contract": "for this contract.",

View File

@@ -73,7 +73,10 @@
"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).",
"recurrent product is for all shipments, occasional product is for a specific shipment (see shipment form)": "les produits récurrents sont pour toutes les livraisons, les produits occasionnels sont pour une livraison particulière (voir formulaire de création de livraison).",
"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.",
"by checking this option the form will be accessible publicly on the home page, only check it if everything is fine with your form": "en cochant cette option le formulaire sera accessible publiquement sur la page d'accueil, cochez cette option uniquement si tout est prêt avec votre formulaire.",
"contracts": "contrats",
"hidden": "caché",
"visible": "visible",
"minimum price for this shipment should be at least": "le prix minimum d'une livraison doit être au moins de",
"there is": "il y a",
"for this contract": "pour ce contrat.",
@@ -166,7 +169,7 @@
"with cheque and transfer": "avec chèques et virements configuré pour le producteur",
"mililiter": "mililitres (ml)",
"this field is optionnal a product can have a quantity if configured inside the product it will be shown inside the form": "ce champ est optionnel dans la configuration d'un produit, il représente la quantité d'un produit (poids d'une tranche de foie, poids d'un panier, taille d'un bocal...). Si ce champs est renseigné il sera affiché dans le formulaire à destination des amapiens.",
"this field is also optionnal if a product have a quantity you can select the correct unit (metric system). It will be shown next to product quantity inside the form": "ce champs est optionnel dans la configuation d'un produit, il représente l'unité de mesure associé à la quantité d'un produit (g, kg, ml, L). Si ce champs est renseigné il sera affiché dans le formulaire à destination des amapiens à coté de la quantité du produit.",
"this field is also optionnal if a product have a quantity you can select the correct unit (metric system). It will be shown next to product quantity inside the form": "ce champs est optionnel dans la configuation d'un produit, il représente l'unité de mesure associée à la quantité d'un produit (g, kg, ml, L). Si ce champs est renseigné il sera affiché dans le formulaire à destination des amapiens à coté de la quantité du produit.",
"with 150 set as quantity and g as quantity unit in product": "avec \"150\" en quantité de produit et \"grammes\" selectionné dans l'unité de quantité du produit",
"all shipments should be recreated for each form creation": "les livraisons étant liées à un formulaire elles doivent être recréés pour chaque nouveau formulaire.",
"a productor can be edited if its informations change, it should not be recreated for each contracts": "un(e) producteur·trice peut être édité si ses informations changent, il/elle ne doit pas être recréé pour chaque nouveau contrat.",

View File

@@ -1,5 +1,6 @@
import {
Button,
Checkbox,
Group,
Modal,
NumberInput,
@@ -33,6 +34,7 @@ export default function FormModal({ opened, onClose, currentForm, handleSubmit }
productor_id: currentForm?.productor?.id.toString() ?? "",
referer_id: currentForm?.referer?.id.toString() ?? "",
minimum_shipment_value: currentForm?.minimum_shipment_value ?? null,
visible: currentForm?.visible ?? false
},
validate: {
name: (value) =>
@@ -136,6 +138,11 @@ export default function FormModal({ opened, onClose, currentForm, handleSubmit }
radius="sm"
{...form.getInputProps("minimum_shipment_value")}
/>
<Checkbox mt="lg"
label={t("visible", {capfirst: true})}
description={t("by checking this option the form will be accessible publicly on the home page, only check it if everything is fine with your form", {capfirst: true})}
{...form.getInputProps("visible", {type: "checkbox"})}
/>
<Group mt="sm" justify="space-between">
<Button
variant="filled"

View File

@@ -1,4 +1,4 @@
import { ActionIcon, Table, Tooltip } from "@mantine/core";
import { ActionIcon, Badge, Table, Tooltip } from "@mantine/core";
import { useNavigate, useSearchParams } from "react-router";
import { useDeleteForm } from "@/services/api";
import { IconEdit, IconX } from "@tabler/icons-react";
@@ -16,6 +16,12 @@ export default function FormRow({ form }: FormRowProps) {
return (
<Table.Tr key={form.id}>
<Table.Td>
{form.visible ?
<Badge color="green">{t("visible", {capfirst: true})}</Badge> :
<Badge color="red">{t("hidden", {capfirst: true})}</Badge>
}
</Table.Td>
<Table.Td>{form.name}</Table.Td>
<Table.Td>{form.season}</Table.Td>
<Table.Td>{form.start}</Table.Td>

View File

@@ -13,7 +13,7 @@ import { IconCancel, IconEdit, IconPlus } from "@tabler/icons-react";
import { useForm } from "@mantine/form";
import { useMemo } from "react";
import { type Shipment, type ShipmentInputs } from "@/services/resources/shipments";
import { useGetForms, useGetProductors, useGetProducts } from "@/services/api";
import { useGetReferentForms, useGetProductors, useGetProducts } from "@/services/api";
export type ShipmentModalProps = ModalBaseProps & {
currentShipment?: Shipment;
@@ -43,7 +43,7 @@ export default function ShipmentModal({
},
});
const { data: allForms } = useGetForms();
const { data: allForms } = useGetReferentForms();
const { data: allProducts } = useGetProducts(new URLSearchParams("types=1"));
const { data: allProductors } = useGetProductors();

View File

@@ -1,5 +1,5 @@
import { Stack, Loader, Title, Group, ActionIcon, Tooltip, Table, ScrollArea } from "@mantine/core";
import { useCreateForm, useEditForm, useGetForm, useGetForms } from "@/services/api";
import { useCreateForm, useEditForm, useGetForm, useGetReferentForms } from "@/services/api";
import { t } from "@/config/i18n";
import { useLocation, useNavigate, useSearchParams } from "react-router";
import { IconPlus } from "@tabler/icons-react";
@@ -28,12 +28,12 @@ export function Forms() {
navigate(`/dashboard/forms${searchParams ? `?${searchParams.toString()}` : ""}`);
}, [navigate, searchParams]);
const { isPending, data } = useGetForms(searchParams);
const { isPending, data } = useGetReferentForms(searchParams);
const { data: currentForm } = useGetForm(Number(editId), {
enabled: !!editId,
});
const { data: allForms } = useGetForms();
const { data: allForms } = useGetReferentForms();
const seasons = useMemo(() => {
return allForms
@@ -148,6 +148,7 @@ export function Forms() {
<Table striped>
<Table.Thead>
<Table.Tr>
<Table.Th>{t("visible", { capfirst: true })}</Table.Th>
<Table.Th>{t("name", { capfirst: true })}</Table.Th>
<Table.Th>{t("type", { capfirst: true })}</Table.Th>
<Table.Th>{t("start", { capfirst: true })}</Table.Th>

View File

@@ -330,6 +330,15 @@ export function useGetForms(filters?: URLSearchParams): UseQueryResult<Form[], E
});
}
export function useGetReferentForms(filters?: URLSearchParams): UseQueryResult<Form[], Error> {
const queryString = filters?.toString();
return useQuery<Form[]>({
queryKey: ["forms", queryString],
queryFn: () =>
fetchWithAuth(`${Config.backend_uri}/forms/referents${filters ? `?${queryString}` : ""}`).then((res) => res.json()),
});
}
export function useCreateForm() {
const queryClient = useQueryClient();
@@ -711,7 +720,6 @@ export function useGetContractFile() {
});
}
export function useGetContractFileTemplate() {
return useMutation({
mutationFn: async (form_id: number) => {

View File

@@ -12,6 +12,7 @@ export type Form = {
referer: User;
shipments: Shipment[];
minimum_shipment_value: number | null;
visible: boolean;
};
export type FormCreate = {
@@ -22,6 +23,7 @@ export type FormCreate = {
productor_id: number;
referer_id: number;
minimum_shipment_value: number | null;
visible: boolean;
};
export type FormEdit = {
@@ -32,6 +34,7 @@ export type FormEdit = {
productor_id?: number | null;
referer_id?: number | null;
minimum_shipment_value: number | null;
visible: boolean;
};
export type FormEditPayload = {
@@ -47,4 +50,5 @@ export type FormInputs = {
productor_id: string;
referer_id: string;
minimum_shipment_value: number | string | null;
visible: boolean;
};