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: