add functionnal recap ready for tests
This commit is contained in:
@@ -17,88 +17,6 @@ from src.database import get_session
|
|||||||
router = APIRouter(prefix='/contracts')
|
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('')
|
@router.post('')
|
||||||
async def create_contract(
|
async def create_contract(
|
||||||
contract: models.ContractCreate,
|
contract: models.ContractCreate,
|
||||||
@@ -114,7 +32,7 @@ async def create_contract(
|
|||||||
new_contract.products
|
new_contract.products
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
occasionals = create_occasional_dict(occasional_contract_products)
|
occasionals = service.create_occasional_dict(occasional_contract_products)
|
||||||
recurrents = list(
|
recurrents = list(
|
||||||
map(
|
map(
|
||||||
lambda x: {'product': x.product, 'quantity': x.quantity},
|
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,
|
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(
|
cheques = list(
|
||||||
map(
|
map(
|
||||||
lambda x: {'name': x.name, 'value': x.value},
|
lambda x: {'name': x.name, 'value': x.value},
|
||||||
@@ -145,7 +65,7 @@ async def create_contract(
|
|||||||
occasionals,
|
occasionals,
|
||||||
recurrents,
|
recurrents,
|
||||||
'{:10.2f}'.format(recurrent_price),
|
'{:10.2f}'.format(recurrent_price),
|
||||||
'{:10.2f}'.format(price)
|
'{:10.2f}'.format(total_price)
|
||||||
)
|
)
|
||||||
pdf_file = io.BytesIO(pdf_bytes)
|
pdf_file = io.BytesIO(pdf_bytes)
|
||||||
contract_id = (
|
contract_id = (
|
||||||
@@ -154,7 +74,8 @@ async def create_contract(
|
|||||||
f'{new_contract.form.productor.type}_'
|
f'{new_contract.form.productor.type}_'
|
||||||
f'{new_contract.form.season}'
|
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:
|
except Exception as error:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
|
|
||||||
import html
|
import html
|
||||||
import io
|
import io
|
||||||
|
import math
|
||||||
import pathlib
|
import pathlib
|
||||||
|
import string
|
||||||
|
|
||||||
import jinja2
|
import jinja2
|
||||||
import odfdo
|
import odfdo
|
||||||
# from odfdo import Cell, Document, Row, Style, Table
|
# from odfdo import Cell, Document, Row, Style, Table
|
||||||
from odfdo.element import Element
|
from odfdo.element import Element
|
||||||
from src import models
|
from src import models
|
||||||
|
from src.contracts import service
|
||||||
from weasyprint import HTML
|
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(
|
return odfdo.Element.from_tag(
|
||||||
f'<style:style style:name="{name}" style:family="table-cell">'
|
f"""<style:style style:name="{name}" style:family="table-cell">
|
||||||
'<style:table-cell-properties style:vertical-align="middle" fo:wrap-option="wrap"/>'
|
<style:table-cell-properties
|
||||||
'<style:paragraph-properties fo:text-align="center"/>'
|
fo:border="0.75pt solid #000000"
|
||||||
'</style:style>'
|
style:vertical-align="middle"
|
||||||
|
fo:wrap-option="wrap"
|
||||||
|
fo:background-color="{background_color}"/>
|
||||||
|
<style:paragraph-properties fo:text-align="center"/>
|
||||||
|
<style:text-properties
|
||||||
|
{bold_attr}
|
||||||
|
fo:font-size="{font_size}"
|
||||||
|
fo:color="{color}"/>
|
||||||
|
</style:style>"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def create_cell_style_with_font(name: str = "font", font_size="14pt", bold: bool = False) -> odfdo.Style:
|
def apply_cell_style(document: odfdo.Document, table: odfdo.Table):
|
||||||
return odfdo.Element.from_tag(
|
header_style = document.insert_style(
|
||||||
f'<style:style style:name="{name}" style:family="table-cell" '
|
create_cell_style(
|
||||||
f'xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0">'
|
name="header-cells",
|
||||||
'<style:table-cell-properties style:vertical-align="middle" fo:wrap-option="wrap"/>'
|
bold=True,
|
||||||
f'<style:paragraph-properties fo:text-align="center" fo:font-size="{font_size}" '
|
font_size='12pt',
|
||||||
f'{"fo:font-weight=\"bold\"" if bold else ""}/>'
|
background_color="#3480eb",
|
||||||
'</style:style>'
|
color="#FFF"
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
style_body = document.insert_style(
|
body_style_even = document.insert_style(
|
||||||
style=create_cell_style_with_font(
|
create_cell_style(
|
||||||
'body_font', font_size=size, bold=False
|
name="body-style-even",
|
||||||
|
bold=False,
|
||||||
|
background_color="#e8eaed",
|
||||||
|
color="#000000"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
for position in range(table.height):
|
body_style_odd = document.insert_style(
|
||||||
row = table.get_row(position)
|
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():
|
for cell in row.get_cells():
|
||||||
cell.style = style_header if position == 0 or position == 1 else style_body
|
cell.style = style
|
||||||
for paragraph in cell.get_paragraphs():
|
|
||||||
paragraph.style = cell.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]):
|
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 = []
|
styles = []
|
||||||
for w in widths:
|
for w in widths:
|
||||||
styles.append(document.insert_style(
|
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):
|
for position in range(table.width):
|
||||||
col = table.get_column(position)
|
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)
|
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(
|
def generate_recap(
|
||||||
contracts: list[models.Contract],
|
contracts: list[models.Contract],
|
||||||
form: models.Form,
|
form: models.Form,
|
||||||
):
|
):
|
||||||
recurrents = [pr.name for pr in form.productor.products if pr.type ==
|
product_unit_map = {
|
||||||
models.ProductType.RECCURENT]
|
'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()
|
recurrents.sort()
|
||||||
occasionnals = [pr.name for pr in form.productor.products if pr.type ==
|
occasionnals = [
|
||||||
models.ProductType.OCCASIONAL]
|
f'{pr.name}({product_unit_map[pr.unit]})'
|
||||||
|
for pr in form.productor.products
|
||||||
|
if pr.type == models.ProductType.OCCASIONAL
|
||||||
|
]
|
||||||
occasionnals.sort()
|
occasionnals.sort()
|
||||||
shipments = form.shipments
|
shipments = form.shipments
|
||||||
occasionnals_header = [
|
occasionnals_header = [
|
||||||
occ for shipment in shipments for occ in occasionnals]
|
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"
|
|
||||||
}
|
|
||||||
|
|
||||||
header = (
|
info_header: list[str] = ['', 'Nom', 'Email']
|
||||||
["Nom", "Email"] +
|
cheque_header: list[str] = ['Cheque 1', 'Cheque 2', 'Cheque 3']
|
||||||
["Tarif panier", "Total Paniers", "Total à payer"] +
|
payment_header = (
|
||||||
["Cheque 1", "Cheque 2", "Cheque 3"] +
|
cheque_header +
|
||||||
[f"Total {len(shipments)} livraisons + produits occasionnels"] +
|
[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 +
|
recurrents +
|
||||||
|
['Total produits récurrents'] +
|
||||||
occasionnals_header +
|
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 = [
|
data = [
|
||||||
[""] * (9 + len(recurrents)) + shipment_header,
|
[''] * (len(prefix_header) + len(recurrents) + 1) + shipment_header,
|
||||||
header,
|
header,
|
||||||
*[
|
*[
|
||||||
[
|
[
|
||||||
|
f'{index + 1}',
|
||||||
f'{contract.firstname} {contract.lastname}',
|
f'{contract.firstname} {contract.lastname}',
|
||||||
f'{contract.email}',
|
f'{contract.email}',
|
||||||
*[f'{pr.quantity} {product_unit_map[pr.product.unit]}' for pr in sorted(
|
*[float(contract.cheques[i].value) if len(
|
||||||
contract.products, key=lambda x: x.product.name) if pr.product.type == models.ProductType.RECCURENT],
|
contract.cheques) > i else '' for i in range(3)],
|
||||||
*[f'{pr.quantity} {product_unit_map[pr.product.unit]}' for pr in sorted(
|
compute_contract_prices(contract)['total'],
|
||||||
contract.products, key=lambda x: x.product.name) if pr.product.type == models.ProductType.OCCASIONAL],
|
*[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}',
|
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 = doc.body.get_sheet(0)
|
||||||
sheet.name = 'Recap'
|
sheet.name = 'Recap'
|
||||||
sheet.set_values(data)
|
sheet.set_values(data)
|
||||||
apply_column_width_style(doc, doc.body.get_table(0), ["4cm"] * len(header))
|
|
||||||
apply_column_height_style(
|
index = len(prefix_header) + len(recurrents) + 1
|
||||||
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)
|
|
||||||
for _ in enumerate(shipments):
|
for _ in enumerate(shipments):
|
||||||
startcol = index
|
startcol = index
|
||||||
endcol = index+len(occasionnals) - 1
|
endcol = index+len(occasionnals) - 1
|
||||||
sheet.set_span((startcol, 0, endcol, 0), merge=True)
|
sheet.set_span((startcol, 0, endcol, 0), merge=True)
|
||||||
index += len(occasionnals)
|
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)
|
doc.body.append(sheet)
|
||||||
|
|
||||||
buffer = io.BytesIO()
|
buffer = io.BytesIO()
|
||||||
doc.save('test.ods')
|
doc.save(buffer)
|
||||||
|
# doc.save('test.ods')
|
||||||
return buffer.getvalue()
|
return buffer.getvalue()
|
||||||
|
|||||||
@@ -166,3 +166,103 @@ def is_allowed(
|
|||||||
.distinct()
|
.distinct()
|
||||||
)
|
)
|
||||||
return len(session.exec(statement).all()) > 0
|
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
|
||||||
|
}
|
||||||
|
|||||||
BIN
backend/test.pdf
BIN
backend/test.pdf
Binary file not shown.
@@ -27,6 +27,8 @@ export default function Contracts() {
|
|||||||
const { data: allContracts } = useGetContracts();
|
const { data: allContracts } = useGetContracts();
|
||||||
|
|
||||||
const forms = useMemo(() => {
|
const forms = useMemo(() => {
|
||||||
|
if (!allContracts)
|
||||||
|
return [];
|
||||||
return allContracts
|
return allContracts
|
||||||
?.map((contract: Contract) => contract.form.name)
|
?.map((contract: Contract) => contract.form.name)
|
||||||
.filter((contract, index, array) => array.indexOf(contract) === index);
|
.filter((contract, index, array) => array.indexOf(contract) === index);
|
||||||
|
|||||||
Reference in New Issue
Block a user