Compare commits
2 Commits
e970bb683a
...
e69ddf3749
| Author | SHA1 | Date | |
|---|---|---|---|
| e69ddf3749 | |||
| 768fdb8d97 |
@@ -0,0 +1,33 @@
|
|||||||
|
"""add minimum for payment method cheque
|
||||||
|
|
||||||
|
Revision ID: 31071e10af15
|
||||||
|
Revises: e777ed5729ce
|
||||||
|
Create Date: 2026-09-07 18:48:08.498571
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
import sqlmodel.sql.sqltypes
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = '31071e10af15'
|
||||||
|
down_revision: Union[str, Sequence[str], None] = 'e777ed5729ce'
|
||||||
|
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('paymentmethod', sa.Column('min', sa.Integer(), nullable=True))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Downgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('paymentmethod', 'min')
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -42,7 +42,6 @@ def get_one(
|
|||||||
"""Get one contract"""
|
"""Get one contract"""
|
||||||
return session.get(models.Contract, contract_id)
|
return session.get(models.Contract, contract_id)
|
||||||
|
|
||||||
|
|
||||||
def create_one(
|
def create_one(
|
||||||
session: Session,
|
session: Session,
|
||||||
contract: models.ContractCreate
|
contract: models.ContractCreate
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ class PaymentMethodBase(SQLModel):
|
|||||||
name: str
|
name: str
|
||||||
details: str
|
details: str
|
||||||
max: int | None
|
max: int | None
|
||||||
|
min: int | None
|
||||||
|
|
||||||
|
|
||||||
class PaymentMethod(PaymentMethodBase, table=True):
|
class PaymentMethod(PaymentMethodBase, table=True):
|
||||||
|
|||||||
@@ -63,12 +63,14 @@ def update_one(
|
|||||||
if 'payment_methods' in productor_updates:
|
if 'payment_methods' in productor_updates:
|
||||||
new_productor.payment_methods.clear()
|
new_productor.payment_methods.clear()
|
||||||
for pm in productor_updates['payment_methods']:
|
for pm in productor_updates['payment_methods']:
|
||||||
|
print(pm)
|
||||||
new_productor.payment_methods.append(
|
new_productor.payment_methods.append(
|
||||||
models.PaymentMethod(
|
models.PaymentMethod(
|
||||||
name=pm['name'],
|
name=pm['name'],
|
||||||
details=pm['details'],
|
details=pm['details'],
|
||||||
productor_id=id,
|
productor_id=id,
|
||||||
max=pm['max']
|
max=pm['max'],
|
||||||
|
min=pm['min']
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
del productor_updates['payment_methods']
|
del productor_updates['payment_methods']
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ class TestUsersService:
|
|||||||
role_names=['test role']
|
role_names=['test role']
|
||||||
)
|
)
|
||||||
user_id = users[0].id
|
user_id = users[0].id
|
||||||
|
print(user_id)
|
||||||
result = users_service.update_one(session, user_id, user_update)
|
result = users_service.update_one(session, user_id, user_update)
|
||||||
|
|
||||||
assert result.id == user_id
|
assert result.id == user_id
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
"minimum shipment value": "minimum shipment value (€)",
|
"minimum shipment value": "minimum shipment value (€)",
|
||||||
"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).",
|
||||||
"recurrent product is for all shipments, occasional product is for a specific shipment (see shipment form)": "recurrent products are for all shipments, occasional products are for a specific shipment (see shipment form).",
|
"recurrent product is for all shipments, occasional product is for a specific shipment (see shipment form)": "recurrent products are for all shipments, occasional products are for a specific shipment (see shipment form).",
|
||||||
"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. This field is only for occasional products, ignore this field if it does not apply to your contract.",
|
||||||
"export contracts": "export contracts",
|
"export contracts": "export contracts",
|
||||||
"download recap": "download recap",
|
"download recap": "download recap",
|
||||||
"fill contract online": "fill contract online",
|
"fill contract online": "fill contract online",
|
||||||
@@ -211,5 +211,10 @@
|
|||||||
"your keycloak user has no roles, please contact your administrator": "your keycloak user has no roles, please contact your administrator",
|
"your keycloak user has no roles, please contact your administrator": "your keycloak user has no roles, please contact your administrator",
|
||||||
"choose payment method": "choose your payment method (you do not need to pay now).",
|
"choose payment method": "choose your payment method (you do not need to pay now).",
|
||||||
"the product unit will be assigned to the quantity requested in the form": "the product unit defines the unit used in the contract form.",
|
"the product unit will be assigned to the quantity requested in the form": "the product unit defines the unit used in the contract form.",
|
||||||
"all theses informations are for contract generation": "all this information is required for contract generation."
|
"all theses informations are for contract generation": "all this information is required for contract generation.",
|
||||||
|
"i have read and accept the mutual agreement": "i have read and accept the mutual agreement.",
|
||||||
|
"here": "here",
|
||||||
|
"your contract has been submited": "your contract has been submited.",
|
||||||
|
"show contract": "show contract",
|
||||||
|
"you must accept terms and conditions": "you must accept terms and conditions."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
"minimum shipment value": "valeur minimum d'une livraison (€)",
|
"minimum shipment value": "valeur minimum d'une 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).",
|
||||||
"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).",
|
"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é s’il ne s’applique 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 s'applique UNIQUEMENT pour les produits occasonnels et peut être ignoré s’il ne s’applique 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.",
|
"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",
|
"contracts": "contrats",
|
||||||
"hidden": "caché",
|
"hidden": "caché",
|
||||||
@@ -97,8 +97,10 @@
|
|||||||
"by selecting a form here you can download all contracts of your form": "en selectionnant un formulaire, vous téléchargez tous les contrats pour un formulaire donné.",
|
"by selecting a form here you can download all contracts of your form": "en selectionnant un formulaire, vous téléchargez tous les contrats pour un formulaire donné.",
|
||||||
"edit user": "modifier l'utilisateur·trice",
|
"edit user": "modifier l'utilisateur·trice",
|
||||||
"remove user": "supprimer l'utilisateur·trice",
|
"remove user": "supprimer l'utilisateur·trice",
|
||||||
"max cheque number": "numbre maximum de cheques possible",
|
"max cheque number": "nombre maximum de cheques possible",
|
||||||
|
"min cheque number": "nombre minimum de cheques possible",
|
||||||
"can be empty default to 3": "optionnel, la valeur par défaut est à 3 cheques",
|
"can be empty default to 3": "optionnel, la valeur par défaut est à 3 cheques",
|
||||||
|
"can be empty default to 1": "optionnel, la valeur par défaut est à 1 cheque",
|
||||||
"all forms": "tous les formulaires de contrat",
|
"all forms": "tous les formulaires de contrat",
|
||||||
"create new form": "créer un nouveau formulaire de contrat",
|
"create new form": "créer un nouveau formulaire de contrat",
|
||||||
"actions": "actions",
|
"actions": "actions",
|
||||||
@@ -202,7 +204,7 @@
|
|||||||
"cheque value": "valeur du chèque",
|
"cheque value": "valeur du chèque",
|
||||||
"enter cheque value": "entrez la valeur du chèque",
|
"enter cheque value": "entrez la valeur du chèque",
|
||||||
"enter payment method": "sélectionnez votre méthode de paiement",
|
"enter payment method": "sélectionnez votre méthode de paiement",
|
||||||
"number of cheques between 1 and 3 cheques also enter your cheques identifiers, value is calculated automatically": "nombre de chèques entre 1 et 3, entrez également les identifiants des chèques utilisés.",
|
"number of cheques between 1 and 3 cheques also enter your cheques identifiers, value is calculated automatically": "nombre de chèques entre {{min}} et {{max}}, entrez également les identifiants des chèques utilisés.",
|
||||||
"payment method": "méthode de paiement",
|
"payment method": "méthode de paiement",
|
||||||
"your session has expired please log in again": "votre session a expiré veuillez vous reconnecter.",
|
"your session has expired please log in again": "votre session a expiré veuillez vous reconnecter.",
|
||||||
"session expired": "session expirée",
|
"session expired": "session expirée",
|
||||||
@@ -211,5 +213,10 @@
|
|||||||
"your keycloak user has no roles, please contact your administrator": "votre utilisateur keycloak n'a pas de roles configurés, contactez votre administrateur.",
|
"your keycloak user has no roles, please contact your administrator": "votre utilisateur keycloak n'a pas de roles configurés, contactez votre administrateur.",
|
||||||
"choose payment method": "choisissez votre méthode de paiement (vous n'avez pas à payer tout de suite, uniquement renseigner comment vous souhaitez régler votre commande).",
|
"choose payment method": "choisissez votre méthode de paiement (vous n'avez pas à payer tout de suite, uniquement renseigner comment vous souhaitez régler votre commande).",
|
||||||
"the product unit will be assigned to the quantity requested in the form": "l'unité de vente du produit définit l'unité associée à la quantité demandée dans le formulaire des amapiens.",
|
"the product unit will be assigned to the quantity requested in the form": "l'unité de vente du produit définit l'unité associée à la quantité demandée dans le formulaire des amapiens.",
|
||||||
"all theses informations are for contract generation": "ces informations sont nécessaires pour la génération de contrat."
|
"all theses informations are for contract generation": "ces informations sont nécessaires pour la génération de contrat.",
|
||||||
|
"i have read and accept the mutual agreement": "j'ai lu et j'accepte le contrat d'engagement réciproque.",
|
||||||
|
"here": "ici",
|
||||||
|
"your contract has been submited": "votre contrat a été envoyé au référent, si vous avez fait une erreur dans la saisie de votre contrat, contactez votre référent·e.",
|
||||||
|
"show contract": "voir le contrat",
|
||||||
|
"you must accept terms and conditions": "vous devez accepter les termes du contrat d'engagement réciproque."
|
||||||
}
|
}
|
||||||
|
|||||||
1137
frontend/package-lock.json
generated
1137
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -10,37 +10,33 @@ export type FormCardProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function FormCard({ form }: FormCardProps) {
|
export function FormCard({ form }: FormCardProps) {
|
||||||
const contractBaseTemplate = useGetContractFileTemplate()
|
const contractBaseTemplate = useGetContractFileTemplate();
|
||||||
return (
|
return (
|
||||||
<Paper shadow="xl" p="xl" miw={{ base: "100vw", md: "25vw", lg: "20vw" }}>
|
<Paper shadow="xl" p="xl" miw={{ base: "100vw", md: "25vw", lg: "20vw" }}>
|
||||||
<Group justify="start" mb="md">
|
<Group justify="start" mb="md">
|
||||||
<Tooltip
|
<Tooltip label={t("download base template to print", { capfirst: true })}>
|
||||||
label={t("download base template to print")}
|
<ActionIcon
|
||||||
>
|
variant={"outline"}
|
||||||
<ActionIcon
|
aria-label={t("download base template to print", { capfirst: true })}
|
||||||
variant={"outline"}
|
onClick={async () => {
|
||||||
aria-label={t("download base template to print")}
|
await contractBaseTemplate.mutateAsync(form.id);
|
||||||
onClick={async () => {
|
}}
|
||||||
await contractBaseTemplate.mutateAsync(form.id)
|
>
|
||||||
}}
|
<IconDownload />
|
||||||
>
|
</ActionIcon>
|
||||||
<IconDownload/>
|
</Tooltip>
|
||||||
</ActionIcon>
|
<Tooltip label={t("fill contract online", { capfirst: true })}>
|
||||||
</Tooltip>
|
<ActionIcon
|
||||||
<Tooltip
|
variant={"outline"}
|
||||||
label={t("fill contract online")}
|
aria-label={t("fill contract online", { capfirst: true })}
|
||||||
>
|
component={Link}
|
||||||
<ActionIcon
|
to={`/form/${form.id}`}
|
||||||
variant={"outline"}
|
target="_blank"
|
||||||
aria-label={t("fill contract online")}
|
rel="noopener noreferrer"
|
||||||
component={Link}
|
>
|
||||||
to={`/form/${form.id}`}
|
<IconExternalLink />
|
||||||
target="_blank"
|
</ActionIcon>
|
||||||
rel="noopener noreferrer"
|
</Tooltip>
|
||||||
>
|
|
||||||
<IconExternalLink/>
|
|
||||||
</ActionIcon>
|
|
||||||
</Tooltip>
|
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
|
|||||||
@@ -43,21 +43,27 @@ export function ContractCheque({ inputForm, price, productor }: ContractChequePr
|
|||||||
}, [inputForm.values.cheque_quantity, price, inputForm.values.cheques]);
|
}, [inputForm.values.cheque_quantity, price, inputForm.values.cheques]);
|
||||||
|
|
||||||
const paymentMethod = useMemo(() => {
|
const paymentMethod = useMemo(() => {
|
||||||
return productor?.payment_methods.find((el) => el.name === "cheque")
|
return productor?.payment_methods.find((el) => el.name === "cheque");
|
||||||
}, [productor]);
|
}, [productor]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
<Title order={4}>{`${t("order name")} : ${paymentMethod?.details}`}</Title>
|
<Title
|
||||||
|
order={4}
|
||||||
|
>{`${t("order name", { capfirst: true })} : ${paymentMethod?.details}`}</Title>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
label={t("cheque quantity", { capfirst: true })}
|
label={t("cheque quantity", { capfirst: true })}
|
||||||
placeholder={t("enter cheque quantity", { capfirst: true })}
|
placeholder={t("enter cheque quantity", { capfirst: true })}
|
||||||
description={t(
|
description={t(
|
||||||
"number of cheques between 1 and 3 cheques also enter your cheques identifiers, value is calculated automatically",
|
"number of cheques between 1 and 3 cheques also enter your cheques identifiers, value is calculated automatically",
|
||||||
{ capfirst: true },
|
{ capfirst: true, min: paymentMethod?.min || 1, max: paymentMethod?.max || 3 },
|
||||||
)}
|
)}
|
||||||
min={1}
|
min={
|
||||||
max={paymentMethod?.max && paymentMethod?.max !== "" ? Number(paymentMethod?.max) : 3}
|
paymentMethod?.min && paymentMethod?.min !== "" ? Number(paymentMethod?.min) : 1
|
||||||
|
}
|
||||||
|
max={
|
||||||
|
paymentMethod?.max && paymentMethod?.max !== "" ? Number(paymentMethod?.max) : 3
|
||||||
|
}
|
||||||
{...inputForm.getInputProps(`cheque_quantity`)}
|
{...inputForm.getInputProps(`cheque_quantity`)}
|
||||||
/>
|
/>
|
||||||
<Group grow>
|
<Group grow>
|
||||||
@@ -67,12 +73,8 @@ export function ContractCheque({ inputForm, price, productor }: ContractChequePr
|
|||||||
label={t("cheque id", { capfirst: true })}
|
label={t("cheque id", { capfirst: true })}
|
||||||
placeholder={t("cheque id", { capfirst: true })}
|
placeholder={t("cheque id", { capfirst: true })}
|
||||||
{...inputForm.getInputProps(`cheques.${index}.name`)}
|
{...inputForm.getInputProps(`cheques.${index}.name`)}
|
||||||
error={
|
error={cheque.name == "" ? inputForm?.errors.cheques : null}
|
||||||
cheque.name == "" ?
|
/>
|
||||||
inputForm?.errors.cheques :
|
|
||||||
null
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<NumberInput
|
<NumberInput
|
||||||
readOnly
|
readOnly
|
||||||
label={t("cheque value", { capfirst: true })}
|
label={t("cheque value", { capfirst: true })}
|
||||||
|
|||||||
17
frontend/src/components/PdfViewer/index.tsx
Normal file
17
frontend/src/components/PdfViewer/index.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
export type PdfViewerProps = {
|
||||||
|
file: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function PdfViewer({ file }: PdfViewerProps) {
|
||||||
|
return (
|
||||||
|
<div onContextMenu={(e) => e.preventDefault()}>
|
||||||
|
<iframe
|
||||||
|
src={`${file.toString()}#toolbar=0&navpanes=0`}
|
||||||
|
title="Contract PDF"
|
||||||
|
width="100%"
|
||||||
|
height="800px"
|
||||||
|
style={{ border: 0 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -48,17 +48,19 @@ export function ProductorModal({
|
|||||||
!value ? `${t("address", { capfirst: true })} ${t("is required")}` : null,
|
!value ? `${t("address", { capfirst: true })} ${t("is required")}` : null,
|
||||||
type: (value) =>
|
type: (value) =>
|
||||||
!value ? `${t("type", { capfirst: true })} ${t("is required")}` : null,
|
!value ? `${t("type", { capfirst: true })} ${t("is required")}` : null,
|
||||||
payment_methods: (value) =>
|
payment_methods: (value) =>
|
||||||
value.length === 0 || value.some(
|
value.length === 0 ||
|
||||||
(payment) =>
|
value.some((payment) => payment.name === "cheque" && payment.details === "")
|
||||||
payment.name === "cheque" &&
|
? `${t("a payment method", { capfirst: true })} ${t("is required")}`
|
||||||
payment.details === "") ?
|
: null,
|
||||||
`${t("a payment method", { capfirst: true })} ${t("is required")}` : null,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const roleSelect = useMemo(() => {
|
const roleSelect = useMemo(() => {
|
||||||
return loggedUser?.user?.roles?.map((role) => ({ value: String(role.name), label: role.name }));
|
return loggedUser?.user?.roles?.map((role) => ({
|
||||||
|
value: String(role.name),
|
||||||
|
label: role.name,
|
||||||
|
}));
|
||||||
}, [loggedUser?.user?.roles]);
|
}, [loggedUser?.user?.roles]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -110,6 +112,7 @@ export function ProductorModal({
|
|||||||
name,
|
name,
|
||||||
details: "",
|
details: "",
|
||||||
max: "",
|
max: "",
|
||||||
|
min: "",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@@ -124,10 +127,16 @@ export function ProductorModal({
|
|||||||
placeholder={t("order name", { capfirst: true })}
|
placeholder={t("order name", { capfirst: true })}
|
||||||
{...form.getInputProps(`payment_methods.${index}.details`)}
|
{...form.getInputProps(`payment_methods.${index}.details`)}
|
||||||
/>
|
/>
|
||||||
|
<NumberInput
|
||||||
|
label={t("min cheque number", { capfirst: true })}
|
||||||
|
placeholder={t("min cheque number", { capfirst: true })}
|
||||||
|
description={t("can be empty default to 1", { capfirst: true })}
|
||||||
|
{...form.getInputProps(`payment_methods.${index}.min`)}
|
||||||
|
/>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
label={t("max cheque number", { capfirst: true })}
|
label={t("max cheque number", { capfirst: true })}
|
||||||
placeholder={t("max cheque number", { capfirst: true })}
|
placeholder={t("max cheque number", { capfirst: true })}
|
||||||
description={t("can be empty default to 3", {capfirst: true})}
|
description={t("can be empty default to 3", { capfirst: true })}
|
||||||
{...form.getInputProps(`payment_methods.${index}.max`)}
|
{...form.getInputProps(`payment_methods.${index}.max`)}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -19,15 +19,23 @@ import {
|
|||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { useForm } from "@mantine/form";
|
import { useForm } from "@mantine/form";
|
||||||
import { IconDownload, IconMail, IconPhone, IconUser } from "@tabler/icons-react";
|
import { IconDownload, IconMail, IconPhone, IconUser } from "@tabler/icons-react";
|
||||||
import { useCallback, useMemo, useRef } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useParams } from "react-router";
|
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 { useDisclosure } from "@mantine/hooks";
|
||||||
|
import { ContractRecap } from "./recap";
|
||||||
|
|
||||||
export function Contract() {
|
export function Contract() {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
const [pdfUrl, setPdfUrl] = useState<string | undefined>();
|
||||||
const { data: form } = useGetForm(Number(id), { enabled: !!id });
|
const { data: form } = useGetForm(Number(id), { enabled: !!id });
|
||||||
|
const [isRecapModalOpened, { open: openRecapModal, close: closeRecapModal }] =
|
||||||
|
useDisclosure(false);
|
||||||
|
const minCheques = useMemo(() => {
|
||||||
|
return form?.productor?.payment_methods.find((pm) => pm.name === "cheque")?.min;
|
||||||
|
}, [form]);
|
||||||
const inputForm = useForm<ContractInputs>({
|
const inputForm = useForm<ContractInputs>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
firstname: "",
|
firstname: "",
|
||||||
@@ -50,11 +58,19 @@ export function Contract() {
|
|||||||
!value ? `${t("a phone", { capfirst: true })} ${t("is required")}` : null,
|
!value ? `${t("a phone", { capfirst: true })} ${t("is required")}` : null,
|
||||||
payment_method: (value) =>
|
payment_method: (value) =>
|
||||||
!value ? `${t("a payment method", { capfirst: true })} ${t("is required")}` : null,
|
!value ? `${t("a payment method", { capfirst: true })} ${t("is required")}` : null,
|
||||||
cheques: (value, values) =>
|
cheques: (value, values) =>
|
||||||
values.payment_method === "cheque" && value.some((val) => val.name == "") ? `${t("cheque id", {capfirst: true})} ${t("is required")}` : null,
|
values.payment_method === "cheque" && value.some((val) => val.name == "")
|
||||||
|
? `${t("cheque id", { capfirst: true })} ${t("is required")}`
|
||||||
|
: null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!form) return;
|
||||||
|
console.log("aze");
|
||||||
|
inputForm.setFieldValue("cheque_quantity", Number(minCheques) || 1);
|
||||||
|
}, [form]);
|
||||||
|
|
||||||
const createContractMutation = useCreateContract();
|
const createContractMutation = useCreateContract();
|
||||||
|
|
||||||
const productsRecurent = useMemo(() => {
|
const productsRecurent = useMemo(() => {
|
||||||
@@ -137,12 +153,15 @@ export function Contract() {
|
|||||||
const formValues = inputForm.getValues();
|
const formValues = inputForm.getValues();
|
||||||
const contract = {
|
const contract = {
|
||||||
...formValues,
|
...formValues,
|
||||||
cheque_quantity: formValues.payment_method === "cheque" ? formValues.cheque_quantity : 0,
|
cheque_quantity:
|
||||||
|
formValues.payment_method === "cheque" ? formValues.cheque_quantity : 0,
|
||||||
form_id: form.id,
|
form_id: form.id,
|
||||||
products: tranformProducts(withDefaultValues(formValues.products)),
|
products: tranformProducts(withDefaultValues(formValues.products)),
|
||||||
};
|
};
|
||||||
await createContractMutation.mutateAsync(contract);
|
const { blob } = await createContractMutation.mutateAsync(contract);
|
||||||
window.location.href = '/';
|
const url = URL.createObjectURL(blob);
|
||||||
|
setPdfUrl(url);
|
||||||
|
openRecapModal();
|
||||||
} else {
|
} else {
|
||||||
const firstErrorField = Object.keys(errors.errors)[0];
|
const firstErrorField = Object.keys(errors.errors)[0];
|
||||||
const ref = inputRefs.current[firstErrorField];
|
const ref = inputRefs.current[firstErrorField];
|
||||||
@@ -155,6 +174,7 @@ export function Contract() {
|
|||||||
form,
|
form,
|
||||||
createContractMutation,
|
createContractMutation,
|
||||||
withDefaultValues,
|
withDefaultValues,
|
||||||
|
setPdfUrl,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!form || !shipments || !productsRecurent)
|
if (!form || !shipments || !productsRecurent)
|
||||||
@@ -165,7 +185,7 @@ export function Contract() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack w={{ base: "100%", md: "80%", lg: "50%" }} p={{base: 'xs'}}>
|
<Stack w={{ base: "100%", md: "80%", lg: "50%" }} p={{ base: "xs" }}>
|
||||||
<Title order={2}>{form.name}</Title>
|
<Title order={2}>{form.name}</Title>
|
||||||
<Title order={3}>{t("informations", { capfirst: true })}</Title>
|
<Title order={3}>{t("informations", { capfirst: true })}</Title>
|
||||||
<Text size="sm">
|
<Text size="sm">
|
||||||
@@ -283,23 +303,20 @@ export function Contract() {
|
|||||||
ref={(el) => {
|
ref={(el) => {
|
||||||
inputRefs.current.payment_method = el;
|
inputRefs.current.payment_method = el;
|
||||||
}}
|
}}
|
||||||
comboboxProps={{
|
comboboxProps={{
|
||||||
withinPortal: false,
|
withinPortal: false,
|
||||||
position: "bottom-start",
|
position: "bottom-start",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{inputForm.values.payment_method === "cheque" ? (
|
{inputForm.values.payment_method === "cheque" ? (
|
||||||
<ContractCheque
|
<ContractCheque productor={form?.productor} price={price} inputForm={inputForm} />
|
||||||
productor={form?.productor}
|
|
||||||
price={price}
|
|
||||||
inputForm={inputForm}
|
|
||||||
/>
|
|
||||||
) : null}
|
) : null}
|
||||||
{inputForm.values.payment_method === "transfer" ? (
|
{inputForm.values.payment_method === "transfer" ? (
|
||||||
<Text>
|
<Text>
|
||||||
{t("for transfer method contact your referer or productor", { capfirst: true })}
|
{t("for transfer method contact your referer or productor", { capfirst: true })}
|
||||||
</Text>
|
</Text>
|
||||||
) : null}
|
) : null}
|
||||||
|
<ContractRecap opened={isRecapModalOpened} close={closeRecapModal} pdfUrl={pdfUrl} />
|
||||||
<Space h="15vh"></Space>
|
<Space h="15vh"></Space>
|
||||||
<Overlay
|
<Overlay
|
||||||
bg={"lightGray"}
|
bg={"lightGray"}
|
||||||
@@ -320,10 +337,12 @@ export function Contract() {
|
|||||||
currency: "EUR",
|
currency: "EUR",
|
||||||
}).format(price)}
|
}).format(price)}
|
||||||
</Text>
|
</Text>
|
||||||
<Button
|
<Button
|
||||||
leftSection={<IconDownload/>}
|
leftSection={<IconDownload />}
|
||||||
aria-label={t("submit contracts")} onClick={handleSubmit}>
|
aria-label={t("submit contracts")}
|
||||||
{t("submit", {capfirst: true})}
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
{t("submit contract", { capfirst: true })}
|
||||||
</Button>
|
</Button>
|
||||||
</Overlay>
|
</Overlay>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
70
frontend/src/pages/Contract/recap.tsx
Normal file
70
frontend/src/pages/Contract/recap.tsx
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import { PdfViewer } from "@/components/PdfViewer";
|
||||||
|
import { t } from "@/config/i18n";
|
||||||
|
import { Button, Checkbox, Loader, Modal, Stack, Text } from "@mantine/core";
|
||||||
|
import { isNotEmpty, useForm } from "@mantine/form";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
export type ContractRecapProps = {
|
||||||
|
opened: boolean;
|
||||||
|
close: () => void;
|
||||||
|
pdfUrl?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ContractRecap({ opened, close, pdfUrl }: ContractRecapProps) {
|
||||||
|
const [viewPdf, setViewPdf] = useState<boolean>(false);
|
||||||
|
const form = useForm({
|
||||||
|
mode: "uncontrolled",
|
||||||
|
initialValues: { terms: false },
|
||||||
|
validate: {
|
||||||
|
terms: isNotEmpty(t("you must accept terms and conditions", { capfirst: true })),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!pdfUrl)
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
opened={opened}
|
||||||
|
onClose={close}
|
||||||
|
size="100%"
|
||||||
|
title={t("contract recap", { capfirst: true })}
|
||||||
|
>
|
||||||
|
<Loader></Loader>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
opened={opened}
|
||||||
|
onClose={close}
|
||||||
|
size={"55rem"}
|
||||||
|
title={t("contract recap", { capfirst: true })}
|
||||||
|
>
|
||||||
|
<Stack>
|
||||||
|
<Text>{t("your contract has been submited", { capfirst: true })}</Text>
|
||||||
|
<Button onClick={() => setViewPdf(!viewPdf)}>
|
||||||
|
{t("show contract", { capfirst: true })}
|
||||||
|
</Button>
|
||||||
|
{viewPdf ? <PdfViewer file={pdfUrl} /> : null}
|
||||||
|
<form
|
||||||
|
onSubmit={form.onSubmit(() => {
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = pdfUrl;
|
||||||
|
link.target = "_blank";
|
||||||
|
link.click();
|
||||||
|
URL.revokeObjectURL(pdfUrl);
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Stack>
|
||||||
|
<Checkbox
|
||||||
|
label={t("i have read and accept the mutual agreement", {
|
||||||
|
capfirst: true,
|
||||||
|
})}
|
||||||
|
key={form.key("terms")}
|
||||||
|
{...form.getInputProps("terms", { type: "checkbox" })}
|
||||||
|
/>
|
||||||
|
<Button type="submit">{t("download contract", { capfirst: true })}</Button>
|
||||||
|
</Stack>
|
||||||
|
</form>
|
||||||
|
</Stack>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ export default function Productors() {
|
|||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { data: productors, isPending } = useGetProductors(searchParams);
|
const { data: productors, isPending } = useGetProductors(searchParams);
|
||||||
|
|
||||||
const { data: allProductors } = useGetProductors();
|
const { data: allProductors } = useGetProductors();
|
||||||
const isCreate = location.pathname === "/dashboard/productors/create";
|
const isCreate = location.pathname === "/dashboard/productors/create";
|
||||||
const isEdit = location.pathname.includes("/edit");
|
const isEdit = location.pathname.includes("/edit");
|
||||||
@@ -62,11 +62,12 @@ export default function Productors() {
|
|||||||
async (productor: ProductorInputs) => {
|
async (productor: ProductorInputs) => {
|
||||||
await createProductorMutation.mutateAsync({
|
await createProductorMutation.mutateAsync({
|
||||||
...productor,
|
...productor,
|
||||||
payment_methods: productor.payment_methods.map((payment) =>( {
|
payment_methods: productor.payment_methods.map((payment) => ({
|
||||||
name: payment.name,
|
name: payment.name,
|
||||||
details: payment.details,
|
details: payment.details,
|
||||||
max: payment.max === "" ? null : payment.max
|
max: payment.max === "" ? null : payment.max,
|
||||||
}))
|
min: payment.min === "" ? null : payment.min,
|
||||||
|
})),
|
||||||
});
|
});
|
||||||
closeModal();
|
closeModal();
|
||||||
},
|
},
|
||||||
@@ -80,11 +81,12 @@ export default function Productors() {
|
|||||||
id: id,
|
id: id,
|
||||||
productor: {
|
productor: {
|
||||||
...productor,
|
...productor,
|
||||||
payment_methods: productor.payment_methods.map((payment) =>( {
|
payment_methods: productor.payment_methods.map((payment) => ({
|
||||||
name: payment.name,
|
name: payment.name,
|
||||||
details: payment.details,
|
details: payment.details,
|
||||||
max: payment.max === "" ? null : payment.max
|
max: payment.max === "" ? null : payment.max,
|
||||||
}))
|
min: payment.min === "" ? null : payment.min,
|
||||||
|
})),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
closeModal();
|
closeModal();
|
||||||
|
|||||||
@@ -26,10 +26,17 @@ import { notifications } from "@mantine/notifications";
|
|||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
|
|
||||||
export async function refreshToken() {
|
export async function refreshToken() {
|
||||||
return await fetch(`${Config.backend_uri}/auth/refresh`, {method: "POST", credentials: "include"});
|
return await fetch(`${Config.backend_uri}/auth/refresh`, {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchWithAuth(input: RequestInfo, options?: RequestInit, redirect: boolean = true) {
|
export async function fetchWithAuth(
|
||||||
|
input: RequestInfo,
|
||||||
|
options?: RequestInit,
|
||||||
|
redirect: boolean = true,
|
||||||
|
) {
|
||||||
const res = await fetch(input, {
|
const res = await fetch(input, {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
...options,
|
...options,
|
||||||
@@ -38,11 +45,10 @@ export async function fetchWithAuth(input: RequestInfo, options?: RequestInit, r
|
|||||||
if (res.status === 401) {
|
if (res.status === 401) {
|
||||||
const refresh = await refreshToken();
|
const refresh = await refreshToken();
|
||||||
if (refresh.status == 400 || refresh.status == 401) {
|
if (refresh.status == 400 || refresh.status == 401) {
|
||||||
if (redirect)
|
if (redirect) window.location.href = `/?sessionExpired=True`;
|
||||||
window.location.href = `/?sessionExpired=True`;
|
|
||||||
|
|
||||||
const error = new Error("Unauthorized");
|
const error = new Error("Unauthorized");
|
||||||
error.cause = 401
|
error.cause = 401;
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
const newRes = await fetch(input, {
|
const newRes = await fetch(input, {
|
||||||
@@ -188,9 +194,7 @@ export function useGetProductors(filters?: URLSearchParams): UseQueryResult<Prod
|
|||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
fetchWithAuth(`${Config.backend_uri}/productors${filters ? `?${queryString}` : ""}`, {
|
fetchWithAuth(`${Config.backend_uri}/productors${filters ? `?${queryString}` : ""}`, {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
})
|
}).then((res) => res.json()),
|
||||||
.then((res) => res.json()
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,7 +330,9 @@ export function useGetForms(filters?: URLSearchParams): UseQueryResult<Form[], E
|
|||||||
return useQuery<Form[]>({
|
return useQuery<Form[]>({
|
||||||
queryKey: ["forms", queryString],
|
queryKey: ["forms", queryString],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
fetch(`${Config.backend_uri}/forms${filters ? `?${queryString}` : ""}`).then((res) => res.json()),
|
fetch(`${Config.backend_uri}/forms${filters ? `?${queryString}` : ""}`).then((res) =>
|
||||||
|
res.json(),
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,7 +341,9 @@ export function useGetReferentForms(filters?: URLSearchParams): UseQueryResult<F
|
|||||||
return useQuery<Form[]>({
|
return useQuery<Form[]>({
|
||||||
queryKey: ["forms", queryString],
|
queryKey: ["forms", queryString],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
fetchWithAuth(`${Config.backend_uri}/forms/referents${filters ? `?${queryString}` : ""}`).then((res) => res.json()),
|
fetchWithAuth(
|
||||||
|
`${Config.backend_uri}/forms/referents${filters ? `?${queryString}` : ""}`,
|
||||||
|
).then((res) => res.json()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -723,8 +731,9 @@ export function useGetContractFile() {
|
|||||||
export function useGetContractFileTemplate() {
|
export function useGetContractFileTemplate() {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (form_id: number) => {
|
mutationFn: async (form_id: number) => {
|
||||||
const res = await fetchWithAuth(`${Config.backend_uri}/contracts/${form_id}/base`)
|
const res = await fetchWithAuth(`${Config.backend_uri}/contracts/${form_id}/base`).then(
|
||||||
.then((res) => res);
|
(res) => res,
|
||||||
|
);
|
||||||
|
|
||||||
if (!res.ok) throw new Error();
|
if (!res.ok) throw new Error();
|
||||||
const blob = await res.blob();
|
const blob = await res.blob();
|
||||||
@@ -746,7 +755,6 @@ export function useGetContractFileTemplate() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function useGetRecap() {
|
export function useGetRecap() {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (form_id: number) => {
|
mutationFn: async (form_id: number) => {
|
||||||
@@ -822,13 +830,7 @@ export function useCreateContract() {
|
|||||||
: `contract_${newContract.form_id}.pdf`;
|
: `contract_${newContract.form_id}.pdf`;
|
||||||
return { blob, filename };
|
return { blob, filename };
|
||||||
},
|
},
|
||||||
onSuccess: async ({ blob, filename }) => {
|
onSuccess: async () => {
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
const link = document.createElement("a");
|
|
||||||
link.href = url;
|
|
||||||
link.download = filename;
|
|
||||||
link.click();
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
await queryClient.invalidateQueries({ queryKey: ["contracts"] });
|
await queryClient.invalidateQueries({ queryKey: ["contracts"] });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -880,10 +882,14 @@ export function useCurrentUser() {
|
|||||||
return useQuery<UserLogged>({
|
return useQuery<UserLogged>({
|
||||||
queryKey: ["currentUser"],
|
queryKey: ["currentUser"],
|
||||||
queryFn: () => {
|
queryFn: () => {
|
||||||
return fetchWithAuth(`${Config.backend_uri}/auth/user/me`, {
|
return fetchWithAuth(
|
||||||
credentials: "include",
|
`${Config.backend_uri}/auth/user/me`,
|
||||||
}, false).then((res) => res.json());
|
{
|
||||||
|
credentials: "include",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
).then((res) => res.json());
|
||||||
},
|
},
|
||||||
retry: false,
|
retry: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export type PaymentMethod = {
|
|||||||
name: string;
|
name: string;
|
||||||
details: string;
|
details: string;
|
||||||
max: number | string | null;
|
max: number | string | null;
|
||||||
|
min: number | string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Productor = {
|
export type Productor = {
|
||||||
|
|||||||
Reference in New Issue
Block a user