From ff19448991c102283b214ad0fb249e66c425da60 Mon Sep 17 00:00:00 2001 From: Julien Aldon Date: Thu, 5 Mar 2026 17:17:23 +0100 Subject: [PATCH] add functionnal recap ready for tests --- backend/src/contracts/contracts.py | 97 +----- backend/src/contracts/generate_contract.py | 329 ++++++++++++++++----- backend/src/contracts/service.py | 100 +++++++ backend/test.pdf | Bin 27071 -> 0 bytes frontend/src/pages/Contracts/index.tsx | 2 + 5 files changed, 359 insertions(+), 169 deletions(-) delete mode 100644 backend/test.pdf diff --git a/backend/src/contracts/contracts.py b/backend/src/contracts/contracts.py index f2b13e5..3eb18e6 100644 --- a/backend/src/contracts/contracts.py +++ b/backend/src/contracts/contracts.py @@ -17,88 +17,6 @@ from src.database import get_session router = APIRouter(prefix='/contracts') -def compute_recurrent_prices( - products_quantities: list[dict], - nb_shipment: int -): - """Compute price for recurrent products""" - result = 0 - for product_quantity in products_quantities: - product = product_quantity['product'] - quantity = product_quantity['quantity'] - result += compute_product_price(product, quantity, nb_shipment) - return result - - -def compute_occasional_prices(occasionals: list[dict]): - """Compute prices for occassional products""" - result = 0 - for occasional in occasionals: - result += occasional['price'] - return result - - -def compute_product_price( - product: models.Product, - quantity: int, - nb_shipment: int = 1 -): - """Compute price for a product""" - product_quantity_unit = ( - 1 if product.unit == models.Unit.KILO else 1000 - ) - final_quantity = ( - quantity if product.price else quantity / product_quantity_unit - ) - final_price = ( - product.price if product.price else product.price_kg - ) - return final_price * final_quantity * nb_shipment - - -def find_dict_in_list(lst, key, value): - """Find the index of a dictionnary in a list of dictionnaries given a key - and a value. - """ - for i, dic in enumerate(lst): - if dic[key].id == value: - return i - return -1 - - -def create_occasional_dict(contract_products: list[models.ContractProduct]): - """Create a dictionnary of occasional products""" - result = [] - for contract_product in contract_products: - existing_id = find_dict_in_list( - result, - 'shipment', - contract_product.shipment.id - ) - if existing_id < 0: - result.append({ - 'shipment': contract_product.shipment, - 'price': compute_product_price( - contract_product.product, - contract_product.quantity - ), - 'products': [{ - 'product': contract_product.product, - 'quantity': contract_product.quantity - }] - }) - else: - result[existing_id]['products'].append({ - 'product': contract_product.product, - 'quantity': contract_product.quantity - }) - result[existing_id]['price'] += compute_product_price( - contract_product.product, - contract_product.quantity - ) - return result - - @router.post('') async def create_contract( contract: models.ContractCreate, @@ -114,7 +32,7 @@ async def create_contract( new_contract.products ) ) - occasionals = create_occasional_dict(occasional_contract_products) + occasionals = service.create_occasional_dict(occasional_contract_products) recurrents = list( map( lambda x: {'product': x.product, 'quantity': x.quantity}, @@ -127,11 +45,13 @@ async def create_contract( ) ) ) - recurrent_price = compute_recurrent_prices( + prices = service.generate_products_prices( + occasionals, recurrents, - len(new_contract.form.shipments) + new_contract.form.shipments ) - price = recurrent_price + compute_occasional_prices(occasionals) + recurrent_price = prices['recurrent'] + total_price = prices['total'] cheques = list( map( lambda x: {'name': x.name, 'value': x.value}, @@ -145,7 +65,7 @@ async def create_contract( occasionals, recurrents, '{:10.2f}'.format(recurrent_price), - '{:10.2f}'.format(price) + '{:10.2f}'.format(total_price) ) pdf_file = io.BytesIO(pdf_bytes) contract_id = ( @@ -154,7 +74,8 @@ async def create_contract( f'{new_contract.form.productor.type}_' f'{new_contract.form.season}' ) - service.add_contract_file(session, new_contract.id, pdf_bytes, price) + service.add_contract_file( + session, new_contract.id, pdf_bytes, total_price) except Exception as error: raise HTTPException( status_code=400, diff --git a/backend/src/contracts/generate_contract.py b/backend/src/contracts/generate_contract.py index bfd8e1f..e1d7b2c 100644 --- a/backend/src/contracts/generate_contract.py +++ b/backend/src/contracts/generate_contract.py @@ -1,13 +1,16 @@ 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 @@ -96,60 +99,97 @@ def create_row_style_height(size: str) -> odfdo.Style: ) -def create_center_cell_style(name: str = "centered-cell") -> odfdo.Style: +def create_cell_style( + name: str = "centered-cell", + font_size: str = '10pt', + bold: bool = False, + background_color: str = '#FFFFFF', + color: str = '#000000' +) -> odfdo.Style: + bold_attr = """ + fo:font-weight="bold" + style:font-weight-asian="bold" + style:font-weight-complex="bold" + """ if bold else '' return odfdo.Element.from_tag( - f'' - '' - '' - '' + f""" + + + + """ ) -def create_cell_style_with_font(name: str = "font", font_size="14pt", bold: bool = False) -> odfdo.Style: - return odfdo.Element.from_tag( - f'' - '' - f'' - '' - ) - - -def apply_center_cell_style(document: odfdo.Document, row: odfdo.Row): - style = document.insert_style( - create_center_cell_style() - ) - for cell in row.get_cells(): - cell.style = style - - -def apply_column_height_style(document: odfdo.Document, row: odfdo.Row, height: str): - style = document.insert_style( - style=create_row_style_height(height), name=height, automatic=True - ) - row.style = style - - -def apply_font_style(document: odfdo.Document, table: odfdo.Table, size: str = "14pt"): - style_header = document.insert_style( - style=create_cell_style_with_font( - 'header_font', font_size=size, bold=True +def apply_cell_style(document: odfdo.Document, table: odfdo.Table): + header_style = document.insert_style( + create_cell_style( + name="header-cells", + bold=True, + font_size='12pt', + background_color="#3480eb", + color="#FFF" ) ) - style_body = document.insert_style( - style=create_cell_style_with_font( - 'body_font', font_size=size, bold=False + body_style_even = document.insert_style( + create_cell_style( + name="body-style-even", + bold=False, + background_color="#e8eaed", + color="#000000" ) ) - for position in range(table.height): - row = table.get_row(position) + body_style_odd = document.insert_style( + create_cell_style( + name="body-style-odd", + bold=False, + background_color="#FFFFFF", + color="#000000" + ) + ) + + footer_style = document.insert_style( + create_cell_style( + name="footer-cells", + bold=True, + font_size='12pt', + ) + ) + + for index, row in enumerate(table.get_rows()): + style = body_style_even + if index == 0 or index == 1: + style = header_style + elif index % 2 == 0: + style = body_style_even + elif index == len(table.get_rows()) - 1: + style = footer_style + else: + style = body_style_odd for cell in row.get_cells(): - cell.style = style_header if position == 0 or position == 1 else style_body - for paragraph in cell.get_paragraphs(): - paragraph.style = cell.style + cell.style = style + + +def apply_column_height_style(document: odfdo.Document, table: odfdo.Table): + header_style = document.insert_style( + style=create_row_style_height('1.60cm'), name='1.60cm', automatic=True + ) + body_style = document.insert_style( + style=create_row_style_height('0.90cm'), name='0.90cm', automatic=True + ) + for index, row in enumerate(table.get_rows()): + if index == 1: + row.style = header_style + else: + row.style = body_style def apply_column_width_style(document: odfdo.Document, table: odfdo.Table, widths: list[str]): @@ -162,7 +202,8 @@ def apply_column_width_style(document: odfdo.Document, table: odfdo.Table, width styles = [] for w in widths: styles.append(document.insert_style( - style=create_column_style_width(w), name=w, automatic=True)) + style=create_column_style_width(w), name=w, automatic=True) + ) for position in range(table.width): col = table.get_column(position) @@ -170,75 +211,201 @@ def apply_column_width_style(document: odfdo.Document, table: odfdo.Table, width table.set_column(position, col) +def generate_ods_letters(n: int): + letters = string.ascii_lowercase + result = [] + for i in range(n): + if i > len(letters) - 1: + letter = f'{letters[int(i / len(letters)) - 1]}' + letter += f'{letters[i % len(letters)]}' + result.append(letter) + continue + letter = letters[i] + result.append(letters[i]) + return result + + +def compute_contract_prices(contract: models.Contract) -> dict: + occasional_contract_products = list( + filter( + lambda contract_product: ( + contract_product.product.type == models.ProductType.OCCASIONAL + ), + contract.products + ) + ) + occasionals_dict = service.create_occasional_dict( + occasional_contract_products) + recurrents_dict = list( + map( + lambda x: {'product': x.product, 'quantity': x.quantity}, + filter( + lambda contract_product: ( + contract_product.product.type == + models.ProductType.RECCURENT + ), + contract.products + ) + ) + ) + prices = service.generate_products_prices( + occasionals_dict, + recurrents_dict, + contract.form.shipments + ) + return prices + + def generate_recap( contracts: list[models.Contract], form: models.Form, ): - recurrents = [pr.name for pr in form.productor.products if pr.type == - models.ProductType.RECCURENT] + product_unit_map = { + '1': 'g', + '2': 'Kg', + '3': 'Piece' + } + recurrents = [ + f'{pr.name}({product_unit_map[pr.unit]})' + for pr in form.productor.products + if pr.type == models.ProductType.RECCURENT + ] recurrents.sort() - occasionnals = [pr.name for pr in form.productor.products if pr.type == - models.ProductType.OCCASIONAL] + occasionnals = [ + f'{pr.name}({product_unit_map[pr.unit]})' + for pr in form.productor.products + if pr.type == models.ProductType.OCCASIONAL + ] occasionnals.sort() shipments = form.shipments occasionnals_header = [ - occ for shipment in shipments for occ in occasionnals] - shipment_header = flatten( - [[f'{shipment.name} - {shipment.date.strftime('%Y-%m-%d')}'] + ["" * len(occasionnals)] for shipment in shipments]) - product_unit_map = { - "1": "g", - "2": "kg", - "3": "p" - } + occ for shipment in shipments for occ in occasionnals + ] - header = ( - ["Nom", "Email"] + - ["Tarif panier", "Total Paniers", "Total à payer"] + - ["Cheque 1", "Cheque 2", "Cheque 3"] + - [f"Total {len(shipments)} livraisons + produits occasionnels"] + + info_header: list[str] = ['', 'Nom', 'Email'] + cheque_header: list[str] = ['Cheque 1', 'Cheque 2', 'Cheque 3'] + payment_header = ( + cheque_header + + [f'Total {len(shipments)} livraisons + produits occasionnels'] + ) + prefix_header: list[str] = ( + info_header + + payment_header + ) + + suffix_header: list[str] = [ + 'Total produits occasionnels', + 'Remarques', + 'Nom' + ] + + shipment_header = flatten([ + [f'{shipment.name} - {shipment.date.strftime('%Y-%m-%d')}'] + + ['' * len(occasionnals)] for shipment in shipments] + + [''] * len(suffix_header) + ) + + header: list[str] = ( + prefix_header + recurrents + + ['Total produits récurrents'] + occasionnals_header + - ["Remarques", "Nom"] + suffix_header + ) + + letters = generate_ods_letters(len(header)) + payment_formula_letters = letters[ + len(info_header):len(info_header) + len(payment_header) + ] + recurent_formula_letters = letters[ + len(info_header)+len(payment_formula_letters): + len(info_header)+len(payment_formula_letters)+len(recurrents) + 1 + ] + occasionnals_formula_letters = letters[ + len(info_header)+len(payment_formula_letters)+len(recurent_formula_letters): + 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', ''] + + [f'=SUM({letter}3:{letter}{2+len(contracts)})' + for letter in payment_formula_letters] + + [f'=SUM({letter}3:{letter}{2+len(contracts)})' + for letter in recurent_formula_letters] + + [f'=SUM({letter}3:{letter}{2+len(contracts)})' + for letter in occasionnals_formula_letters] ) data = [ - [""] * (9 + len(recurrents)) + shipment_header, + [''] * (len(prefix_header) + len(recurrents) + 1) + shipment_header, header, *[ [ + f'{index + 1}', f'{contract.firstname} {contract.lastname}', f'{contract.email}', - *[f'{pr.quantity} {product_unit_map[pr.product.unit]}' for pr in sorted( - contract.products, key=lambda x: x.product.name) if pr.product.type == models.ProductType.RECCURENT], - *[f'{pr.quantity} {product_unit_map[pr.product.unit]}' for pr in sorted( - contract.products, key=lambda x: x.product.name) if pr.product.type == models.ProductType.OCCASIONAL], - "", + *[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 contract in contracts - ] + ] for index, contract in enumerate(contracts) + ], + footer ] - doc = odfdo.Document("spreadsheet") + doc = odfdo.Document('spreadsheet') sheet = doc.body.get_sheet(0) sheet.name = 'Recap' sheet.set_values(data) - apply_column_width_style(doc, doc.body.get_table(0), ["4cm"] * len(header)) - apply_column_height_style( - doc, - doc.body.get_table(0).get_rows((1, 1))[0], - "1.20cm" - ) - apply_center_cell_style(doc, doc.body.get_table(0).get_rows((1, 1))[0]) - apply_font_style(doc, doc.body.get_table(0)) - index = 9 + len(recurrents) + + 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) + 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 + + apply_column_width_style( + doc, + doc.body.get_table(0), + ['2cm'] + + ['4cm'] * len(info_header) + + ['2.40cm'] * (len(payment_header) - 1) + + ['4cm'] * len(recurrents) + + ['4cm'] + + ['4cm'] * (len(occasionnals_header) + 1) + + ['4cm', '8cm', '4cm'] + ) + apply_column_height_style( + doc, + doc.body.get_table(0), + ) + apply_cell_style(doc, doc.body.get_table(0)) doc.body.append(sheet) buffer = io.BytesIO() - doc.save('test.ods') + doc.save(buffer) + # doc.save('test.ods') return buffer.getvalue() diff --git a/backend/src/contracts/service.py b/backend/src/contracts/service.py index 79cb063..969eafa 100644 --- a/backend/src/contracts/service.py +++ b/backend/src/contracts/service.py @@ -166,3 +166,103 @@ def is_allowed( .distinct() ) return len(session.exec(statement).all()) > 0 + + +def compute_recurrent_prices( + products_quantities: list[dict], + nb_shipment: int +): + """Compute price for recurrent products""" + result = 0 + for product_quantity in products_quantities: + product = product_quantity['product'] + quantity = product_quantity['quantity'] + result += compute_product_price(product, quantity, nb_shipment) + return result + + +def compute_occasional_prices(occasionals: list[dict]): + """Compute prices for occassional products""" + result = 0 + for occasional in occasionals: + result += occasional['price'] + return result + + +def compute_product_price( + product: models.Product, + quantity: int, + nb_shipment: int = 1 +): + """Compute price for a product""" + product_quantity_unit = ( + 1 if product.unit == models.Unit.KILO else 1000 + ) + final_quantity = ( + quantity if product.price else quantity / product_quantity_unit + ) + final_price = ( + product.price if product.price else product.price_kg + ) + return final_price * final_quantity * nb_shipment + + +def find_dict_in_list(lst, key, value): + """Find the index of a dictionnary in a list of dictionnaries given a key + and a value. + """ + for i, dic in enumerate(lst): + if dic[key].id == value: + return i + return -1 + + +def create_occasional_dict(contract_products: list[models.ContractProduct]): + """Create a dictionnary of occasional products""" + result = [] + for contract_product in contract_products: + existing_id = find_dict_in_list( + result, + 'shipment', + contract_product.shipment.id + ) + if existing_id < 0: + result.append({ + 'shipment': contract_product.shipment, + 'price': compute_product_price( + contract_product.product, + contract_product.quantity + ), + 'products': [{ + 'product': contract_product.product, + 'quantity': contract_product.quantity + }] + }) + else: + result[existing_id]['products'].append({ + 'product': contract_product.product, + 'quantity': contract_product.quantity + }) + result[existing_id]['price'] += compute_product_price( + contract_product.product, + contract_product.quantity + ) + return result + + +def generate_products_prices( + occasionals: list[dict], + recurrents: list[dict], + shipments: list[models.ShipmentPublic] +): + recurrent_price = compute_recurrent_prices( + recurrents, + len(shipments) + ) + occasional_price = compute_occasional_prices(occasionals) + price = recurrent_price + occasional_price + return { + 'total': price, + 'recurrent': recurrent_price, + 'occasionnal': occasional_price + } diff --git a/backend/test.pdf b/backend/test.pdf deleted file mode 100644 index 554d27aaa2dadf963c0733b219bb696f3fd83d8a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27071 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 { + if (!allContracts) + return []; return allContracts ?.map((contract: Contract) => contract.form.name) .filter((contract, index, array) => array.indexOf(contract) === index);