This commit is contained in:
@@ -105,6 +105,46 @@ async def create_contract(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@router.get('/{form_id}/base')
|
||||||
|
async def get_base_contract_template(
|
||||||
|
form_id: int,
|
||||||
|
session: Session = Depends(get_session),
|
||||||
|
):
|
||||||
|
form = form_service.get_one(session, form_id)
|
||||||
|
recurrents = [pr for pr in form.productor.products if pr.type == models.ProductType.RECCURENT]
|
||||||
|
occasionals = [{
|
||||||
|
'shipment': sh,
|
||||||
|
'price': None,
|
||||||
|
'products': [{'product': pr, 'quantity': None} for pr in sh.products]
|
||||||
|
} for sh in form.shipments]
|
||||||
|
empty_contract = models.Contract(
|
||||||
|
firstname="",
|
||||||
|
form=form,
|
||||||
|
lastname="",
|
||||||
|
email="",
|
||||||
|
phone="",
|
||||||
|
payment_method="cheque"
|
||||||
|
)
|
||||||
|
cheques = [{"name": None, "value": None}, {"name": None, "value": None}, {"name": None, "value": None}]
|
||||||
|
try:
|
||||||
|
pdf_bytes = generate_html_contract(
|
||||||
|
empty_contract,
|
||||||
|
cheques,
|
||||||
|
occasionals,
|
||||||
|
recurrents,
|
||||||
|
)
|
||||||
|
pdf_file = io.BytesIO(pdf_bytes)
|
||||||
|
contract_id = f'{empty_contract.form.productor.type}_{empty_contract.form.season}'
|
||||||
|
except Exception:
|
||||||
|
raise HTTPException(status_code=400, detail=messages.pdferror)
|
||||||
|
return StreamingResponse(
|
||||||
|
pdf_file,
|
||||||
|
media_type='application/pdf',
|
||||||
|
headers={
|
||||||
|
'Content-Disposition': f'attachment; filename=contract_{contract_id}.pdf'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
@router.get('', response_model=list[models.ContractPublic])
|
@router.get('', response_model=list[models.ContractPublic])
|
||||||
def get_contracts(
|
def get_contracts(
|
||||||
forms: list[str] = Query([]),
|
forms: list[str] = Query([]),
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ def generate_html_contract(
|
|||||||
cheques: list[dict],
|
cheques: list[dict],
|
||||||
occasionals: list[dict],
|
occasionals: list[dict],
|
||||||
reccurents: list[dict],
|
reccurents: list[dict],
|
||||||
recurrent_price: float,
|
recurrent_price: float | None = None,
|
||||||
total_price: float
|
total_price: float | None = None
|
||||||
):
|
):
|
||||||
template_dir = pathlib.Path("./src/contracts/templates").resolve()
|
template_dir = pathlib.Path("./src/contracts/templates").resolve()
|
||||||
template_loader = jinja2.FileSystemLoader(searchpath=template_dir)
|
template_loader = jinja2.FileSystemLoader(searchpath=template_dir)
|
||||||
|
|||||||
@@ -281,7 +281,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row" colspan="4">Total</th>
|
<th scope="row" colspan="4">Total</th>
|
||||||
<td>{{recurrent_price}}€</td>
|
<td>{{recurrent_price if recurrent_price else ""}}€</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -317,14 +317,15 @@
|
|||||||
product.product.quantity_unit != None else ""}}
|
product.product.quantity_unit != None else ""}}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{product.quantity}}{{"g" if product.product.unit == "1" else
|
{{product.product.quantity if product.product.quantity != None
|
||||||
|
else ""}}{{"g" if product.product.unit == "1" else
|
||||||
"kg" if product.product.unit == "2" else "p" }}
|
"kg" if product.product.unit == "2" else "p" }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor%}
|
{% endfor%}
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row" colspan="4">Total</th>
|
<th scope="row" colspan="4">Total</th>
|
||||||
<td>{{occasional.price}}€</td>
|
<td>{{occasional.price if occasional.price else ""}}€</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -333,7 +334,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="total-box">
|
<div class="total-box">
|
||||||
<div class="total-label">Prix Total :</div>
|
<div class="total-label">Prix Total :</div>
|
||||||
<div class="total-price">{{total_price}}€</div>
|
<div class="total-price">{{total_price if total_price else ""}}€</div>
|
||||||
</div>
|
</div>
|
||||||
<h4>Paiement par {{contract_payment_method}}</h4>
|
<h4>Paiement par {{contract_payment_method}}</h4>
|
||||||
{% if contract_payment_method == "chèque" %}
|
{% if contract_payment_method == "chèque" %}
|
||||||
@@ -342,14 +343,14 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{% for cheque in cheques %}
|
{% for cheque in cheques %}
|
||||||
<th>Cheque n°{{cheque.name}}</th>
|
<th>Cheque n°{{cheque.name if cheque.name else ""}}</th>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
{% for cheque in cheques %}
|
{% for cheque in cheques %}
|
||||||
<td>{{cheque.value}}€</td>
|
<td>{{cheque.value if cheque.value else ""}}€</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -75,6 +75,8 @@
|
|||||||
"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.",
|
||||||
"export contracts": "export contracts",
|
"export contracts": "export contracts",
|
||||||
"download recap": "download recap",
|
"download recap": "download recap",
|
||||||
|
"fill contract online": "fill contract online",
|
||||||
|
"download base template to print": "download base template to print",
|
||||||
"to export contracts submissions before sending to the productor go to the contracts section": "to export contracts submissions before sending to the productor go to the contracts section.",
|
"to export contracts submissions before sending to the productor go to the contracts section": "to export contracts submissions before sending to the productor go to the contracts section.",
|
||||||
"in this page you can view all contracts submissions, you can remove duplicates submission or download a specific contract": "in this page you can view all contracts submissions, you can remove duplicates submission or download a specific contract",
|
"in this page you can view all contracts submissions, you can remove duplicates submission or download a specific contract": "in this page you can view all contracts submissions, you can remove duplicates submission or download a specific contract",
|
||||||
"you can download all contracts for your form using the export all": "you can download all contracts for your form using the export all",
|
"you can download all contracts for your form using the export all": "you can download all contracts for your form using the export all",
|
||||||
|
|||||||
@@ -89,6 +89,8 @@
|
|||||||
"all contracts": "tous les contrats",
|
"all contracts": "tous les contrats",
|
||||||
"remove contract": "supprimer le contrat",
|
"remove contract": "supprimer le contrat",
|
||||||
"download contract": "télécharger le contrat",
|
"download contract": "télécharger le contrat",
|
||||||
|
"fill contract online": "remplir le contrat en ligne",
|
||||||
|
"download base template to print": "télécharger le contrat à remplir sur papier",
|
||||||
"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",
|
||||||
|
|||||||
@@ -1,17 +1,53 @@
|
|||||||
import { Badge, Box, Group, Paper, Text, Title } from "@mantine/core";
|
import { ActionIcon, Badge, Box, Group, Paper, Text, Title, Tooltip } from "@mantine/core";
|
||||||
import { Link } from "react-router";
|
import { Link } from "react-router";
|
||||||
import type { Form } from "@/services/resources/forms";
|
import type { Form } from "@/services/resources/forms";
|
||||||
|
import { IconDownload, IconExternalLink, IconLink } from "@tabler/icons-react";
|
||||||
|
import { t } from "@/config/i18n";
|
||||||
|
import { useGetContractFileTemplate } from "@/services/api";
|
||||||
|
|
||||||
export type FormCardProps = {
|
export type FormCardProps = {
|
||||||
form: Form;
|
form: Form;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function FormCard({ form }: FormCardProps) {
|
export function FormCard({ form }: FormCardProps) {
|
||||||
|
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">
|
||||||
|
<Tooltip
|
||||||
|
label={t("download base template to print")}
|
||||||
|
>
|
||||||
|
<ActionIcon
|
||||||
|
variant={"outline"}
|
||||||
|
aria-label={t("download base template to print")}
|
||||||
|
onClick={async () => {
|
||||||
|
await contractBaseTemplate.mutateAsync(form.id)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconDownload/>
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip
|
||||||
|
label={t("fill contract online")}
|
||||||
|
>
|
||||||
|
<ActionIcon
|
||||||
|
variant={"outline"}
|
||||||
|
aria-label={t("fill contract online")}
|
||||||
|
component={Link}
|
||||||
|
to={`/form/${form.id}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<IconExternalLink/>
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
component={Link}
|
component={Link}
|
||||||
to={`/form/${form.id}`}
|
to={`/form/${form.id}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
style={{ textDecoration: "none", color: "black" }}
|
style={{ textDecoration: "none", color: "black" }}
|
||||||
>
|
>
|
||||||
<Group justify="space-between" wrap="nowrap">
|
<Group justify="space-between" wrap="nowrap">
|
||||||
|
|||||||
@@ -711,6 +711,34 @@ export function useGetContractFile() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function useGetContractFileTemplate() {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (form_id: number) => {
|
||||||
|
const res = await fetchWithAuth(`${Config.backend_uri}/contracts/${form_id}/base`)
|
||||||
|
.then((res) => res);
|
||||||
|
|
||||||
|
if (!res.ok) throw new Error();
|
||||||
|
const blob = await res.blob();
|
||||||
|
const disposition = res.headers.get("Content-Disposition");
|
||||||
|
const filename =
|
||||||
|
disposition && disposition?.includes("filename=")
|
||||||
|
? disposition.split("filename=")[1].replace(/"/g, "")
|
||||||
|
: `contract_${form_id}.pdf`;
|
||||||
|
return { blob, filename };
|
||||||
|
},
|
||||||
|
onSuccess: ({ blob, filename }) => {
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = url;
|
||||||
|
link.download = filename;
|
||||||
|
link.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export function useGetRecap() {
|
export function useGetRecap() {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (form_id: number) => {
|
mutationFn: async (form_id: number) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user