diff --git a/backend/.pre-commit-config.yaml b/backend/.pre-commit-config.yaml
deleted file mode 100644
index 9028aa1..0000000
--- a/backend/.pre-commit-config.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
-default_language_version:
- python: python3.13
-
-repos:
- - repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v6.0.0
- hooks:
- - id: check-added-large-files
- - id: trailing-whitespace
- - id: check-ast
- - id: check-builtin-literals
- - id: check-docstring-first
- - id: check-yaml
- - id: check-toml
- - id: mixed-line-ending
- - id: end-of-file-fixer
- - repo: local
- hooks:
- - id: check-pylint
- name: check-pylint
- entry: pylint -d R0801,R0903,W0511,W0603,C0103,R0902
- language: system
- types: [python]
- pass_filenames: false
- args:
- - backend
diff --git a/backend/pyproject.toml b/backend/pyproject.toml
index 48506bb..333dab7 100644
--- a/backend/pyproject.toml
+++ b/backend/pyproject.toml
@@ -34,8 +34,6 @@ dependencies = [
"pytest",
"pytest-cov",
"pytest-mock",
- "autopep8",
- "prek",
"pylint",
]
diff --git a/backend/src/contracts/contracts.py b/backend/src/contracts/contracts.py
index 3eb18e6..12f029c 100644
--- a/backend/src/contracts/contracts.py
+++ b/backend/src/contracts/contracts.py
@@ -250,12 +250,13 @@ def get_contract_recap(
)
form = form_service.get_one(session, form_id=form_id)
contracts = service.get_all(session, user, forms=[form.name])
+ filename = f'{form.name}_recapitulatif_contrats.ods'
return StreamingResponse(
io.BytesIO(generate_recap(contracts, form)),
- media_type='application/zip',
+ media_type='application/vnd.oasis.opendocument.spreadsheet',
headers={
'Content-Disposition': (
- 'attachment; filename=filename.ods'
+ f'attachment; filename={filename}'
)
}
)
diff --git a/backend/src/contracts/generate_contract.py b/backend/src/contracts/generate_contract.py
index 6ab7d99..2dbf7cb 100644
--- a/backend/src/contracts/generate_contract.py
+++ b/backend/src/contracts/generate_contract.py
@@ -1,14 +1,11 @@
import html
import io
-import math
import pathlib
import string
import jinja2
import odfdo
-# from odfdo import Cell, Document, Row, Style, Table
-from odfdo.element import Element
from src import models
from src.contracts import service
from weasyprint import HTML
@@ -99,20 +96,34 @@ def create_row_style_height(size: str) -> odfdo.Style:
)
+def create_currency_style(name:str = 'currency-euro'):
+ return odfdo.Element.from_tag(
+ f"""
+
+
+ €
+ """
+ )
+
def create_cell_style(
name: str = "centered-cell",
font_size: str = '10pt',
bold: bool = False,
background_color: str = '#FFFFFF',
- color: str = '#000000'
+ color: str = '#000000',
+ currency: bool = False,
) -> odfdo.Style:
bold_attr = """
fo:font-weight="bold"
style:font-weight-asian="bold"
style:font-weight-complex="bold"
""" if bold else ''
+ currency_attr = """
+ style:data-style-name="currency-euro">
+ """ if currency else ''
return odfdo.Element.from_tag(
- f"""
+ f"""
dict:
return prices
+def transform_formula_cells(sheet: odfdo.Spreadsheet):
+ for row in sheet.get_rows():
+ for cell in row.get_cells():
+ if not cell.value or cell.get_attribute("office:value-type") == "float":
+ continue
+ if '=' in cell.value:
+ formula = cell.value
+ cell.clear()
+ cell.formula = formula
+
+
+def merge_shipment_cells(
+ sheet: odfdo.Spreadsheet,
+ prefix_header: list[str],
+ recurrents: list[str],
+ occasionnals: list[str],
+ shipments: list[models.Shipment]
+ ):
+ index = len(prefix_header) + len(recurrents) + 1
+ for _ in enumerate(shipments):
+ startcol = index
+ endcol = index+len(occasionnals) - 1
+ sheet.set_span((startcol, 0, endcol, 0), merge=True)
+ index += len(occasionnals)
+
def generate_recap(
contracts: list[models.Contract],
form: models.Form,
@@ -266,13 +347,13 @@ def generate_recap(
'3': 'Piece'
}
recurrents = [
- f'{pr.name}({product_unit_map[pr.unit]})'
+ f'{pr.name}{f' - {pr.quantity}{pr.quantity_unit}' if pr.quantity else ''} ({product_unit_map[pr.unit]})'
for pr in form.productor.products
if pr.type == models.ProductType.RECCURENT
]
recurrents.sort()
occasionnals = [
- f'{pr.name}({product_unit_map[pr.unit]})'
+ f'{pr.name}{f' - {pr.quantity}{pr.quantity_unit}' if pr.quantity else ''} ({product_unit_map[pr.unit]})'
for pr in form.productor.products
if pr.type == models.ProductType.OCCASIONAL
]
@@ -292,7 +373,6 @@ def generate_recap(
info_header +
payment_header
)
-
suffix_header: list[str] = [
'Total produits occasionnels',
'Remarques',
@@ -326,9 +406,6 @@ def generate_recap(
len(info_header)+len(payment_formula_letters) +
len(recurent_formula_letters)+len(occasionnals_header) + 1
]
- print(payment_formula_letters)
- print(recurent_formula_letters)
- print(occasionnals_formula_letters)
footer = (
['', 'Total contrats', ''] +
@@ -340,29 +417,39 @@ def generate_recap(
for letter in occasionnals_formula_letters]
)
+ main_data = []
+ for index, contract in enumerate(contracts):
+ prices = compute_contract_prices(contract)
+ occasionnal_sorted = sorted(
+ [product for product in contract.products if product.product.type == models.ProductType.OCCASIONAL],
+ key=lambda x: (x.shipment.name, x.product.name)
+ )
+ recurrent_sorted = sorted(
+ [product for product in contract.products if product.product.type == models.ProductType.RECCURENT],
+ key=lambda x: x.product.name
+ )
+
+ main_data.append([
+ f'{index + 1}',
+ f'{contract.firstname} {contract.lastname}',
+ f'{contract.email}',
+ *[float(contract.cheques[i].value)
+ if len(contract.cheques) > i
+ else ''
+ for i in range(3)],
+ prices['total'],
+ *[pr.quantity for pr in recurrent_sorted],
+ prices['recurrent'],
+ *[pr.quantity for pr in occasionnal_sorted],
+ prices['occasionnal'],
+ '',
+ f'{contract.firstname} {contract.lastname}',
+ ])
+
data = [
[''] * (len(prefix_header) + len(recurrents) + 1) + shipment_header,
header,
- *[
- [
- f'{index + 1}',
- f'{contract.firstname} {contract.lastname}',
- f'{contract.email}',
- *[float(contract.cheques[i].value) if len(
- contract.cheques) > i else '' for i in range(3)],
- compute_contract_prices(contract)['total'],
- *[pr.quantity for pr in sorted(
- contract.products, key=lambda x: x.product.name)
- if pr.product.type == models.ProductType.RECCURENT],
- compute_contract_prices(contract)['recurrent'],
- *[pr.quantity for pr in sorted(
- contract.products, key=lambda x: x.product.name)
- if pr.product.type == models.ProductType.OCCASIONAL],
- compute_contract_prices(contract)['occasionnal'],
- '',
- f'{contract.firstname} {contract.lastname}',
- ] for index, contract in enumerate(contracts)
- ],
+ *main_data,
footer
]
@@ -371,41 +458,45 @@ def generate_recap(
sheet.name = 'Recap'
sheet.set_values(data)
- index = len(prefix_header) + len(recurrents) + 1
- for _ in enumerate(shipments):
- startcol = index
- endcol = index+len(occasionnals) - 1
- sheet.set_span((startcol, 0, endcol, 0), merge=True)
- index += len(occasionnals)
+ if len(occasionnals) > 0:
+ merge_shipment_cells(
+ sheet,
+ prefix_header,
+ recurrents,
+ occasionnals,
+ shipments
+ )
- for row in sheet.get_rows():
- for cell in row.get_cells():
- if not cell.value or cell.get_attribute("office:value-type") == "float":
- continue
- if '=' in cell.value:
- formula = cell.value
- cell.clear()
- cell.formula = formula
+ transform_formula_cells(sheet)
apply_column_width_style(
doc,
doc.body.get_table(0),
['2cm'] +
- ['4cm'] * 2 +
+ ['6cm'] * 2 +
['2.40cm'] * (len(payment_header) - 1) +
['4cm'] * len(recurrents) +
['4cm'] +
['4cm'] * (len(occasionnals_header) + 1) +
- ['4cm', '8cm', '4cm']
+ ['4cm', '8cm', '6cm']
)
apply_column_height_style(
doc,
doc.body.get_table(0),
)
- apply_cell_style(doc, doc.body.get_table(0))
+ apply_cell_style(
+ doc,
+ doc.body.get_table(0),
+ [
+ 3,
+ 4,
+ 5,
+ 6,
+ len(info_header) + len(payment_header),
+ len(info_header) + len(payment_header) + 1 + len(occasionnals),
+ ]
+ )
doc.body.append(sheet)
-
buffer = io.BytesIO()
doc.save(buffer)
- # doc.save('test.ods')
return buffer.getvalue()
diff --git a/backend/src/productors/service.py b/backend/src/productors/service.py
index 73248ca..3febfc6 100644
--- a/backend/src/productors/service.py
+++ b/backend/src/productors/service.py
@@ -81,8 +81,8 @@ def update_one(
return new_productor
-def delete_one(session: Session, id: int) -> models.ProductorPublic:
- statement = select(models.Productor).where(models.Productor.id == id)
+def delete_one(session: Session, _id: int) -> models.ProductorPublic:
+ statement = select(models.Productor).where(models.Productor.id == _id)
result = session.exec(statement)
productor = result.first()
if not productor: