|
2 | 2 | import json
|
3 | 3 | import os
|
4 | 4 | import textwrap
|
| 5 | +from dataclasses import dataclass |
5 | 6 | from datetime import date, datetime
|
| 7 | +from typing import List |
6 | 8 |
|
7 | 9 | import labels # package pylabels
|
8 | 10 | from django.conf import settings
|
@@ -1113,12 +1115,20 @@ def get(self, request, *args, **kwargs):
|
1113 | 1115 |
|
1114 | 1116 | component_lines = kcr_make_component_lines(kitchen_list, delivery_date)
|
1115 | 1117 | meal_lines = kcr_make_meal_lines(kitchen_list)
|
| 1118 | + preperation_lines_with_incompatible_ingr = kcr_make_preparation_lines( |
| 1119 | + kitchen_list, "only_clients_with_incompatible_ingredients" |
| 1120 | + ) |
| 1121 | + preperation_lines_without_incompatible_ingr = kcr_make_preparation_lines( |
| 1122 | + kitchen_list, "only_clients_without_incompatible_ingredients" |
| 1123 | + ) |
1116 | 1124 | if component_lines:
|
1117 | 1125 | # we have orders on that date
|
1118 | 1126 | num_pages = kcr_make_pages( # kitchen count as PDF
|
1119 | 1127 | delivery_date,
|
1120 | 1128 | component_lines,
|
1121 | 1129 | meal_lines, # summary
|
| 1130 | + preperation_lines_with_incompatible_ingr, |
| 1131 | + preperation_lines_without_incompatible_ingr, |
1122 | 1132 | ) # detail
|
1123 | 1133 | num_labels = kcr_make_labels( # meal labels as PDF
|
1124 | 1134 | delivery_date,
|
@@ -1249,7 +1259,7 @@ def kcr_make_meal_lines(kitchen_list):
|
1249 | 1259 | [
|
1250 | 1260 | (client_id, kitchen_item)
|
1251 | 1261 | for client_id, kitchen_item in kitchen_list.items()
|
1252 |
| - if kitchen_item.incompatible_ingredients or kitchen_item.preparation |
| 1262 | + if kitchen_item.incompatible_ingredients |
1253 | 1263 | ],
|
1254 | 1264 | key=lambda item: item[1].incompatible_ingredients,
|
1255 | 1265 | )
|
@@ -1388,17 +1398,74 @@ def format_client_name(firstname, lastname):
|
1388 | 1398 | return f"{lastname}, {firstname[:2]}."
|
1389 | 1399 |
|
1390 | 1400 |
|
1391 |
| -def kcr_make_pages(kcr_date, component_lines, meal_lines): |
| 1401 | +@dataclass |
| 1402 | +class PreparationLine: |
| 1403 | + preparation_method: str |
| 1404 | + quantity: int |
| 1405 | + client_names: List[str] |
| 1406 | + |
| 1407 | + |
| 1408 | +def kcr_make_preparation_lines(kitchen_list, client_filter): |
| 1409 | + """Get food preparation method for clients not having clashing (incompatible) |
| 1410 | + ingredients.""" |
| 1411 | + if client_filter == "only_clients_with_incompatible_ingredients": |
| 1412 | + |
| 1413 | + def select_item(item): |
| 1414 | + return bool(item.incompatible_ingredients) |
| 1415 | + elif client_filter == "only_clients_without_incompatible_ingredients": |
| 1416 | + |
| 1417 | + def select_item(item): |
| 1418 | + return not item.incompatible_ingredients |
| 1419 | + else: |
| 1420 | + raise ValueError(f"Incompatible client_filter: {client_filter}") |
| 1421 | + |
| 1422 | + items = [item for item in kitchen_list.values() if select_item(item)] |
| 1423 | + |
| 1424 | + # Group KitchenItem per preparation method |
| 1425 | + items_per_preparation = collections.defaultdict(list) |
| 1426 | + for item in items: |
| 1427 | + for prep in item.preparation: |
| 1428 | + items_per_preparation[prep].append(item) |
| 1429 | + |
| 1430 | + preparation_lines = [] |
| 1431 | + for prep in sorted(items_per_preparation.keys()): |
| 1432 | + client_names = sorted( |
| 1433 | + format_client_name(item.firstname, item.lastname) |
| 1434 | + + (f" (x {item.meal_qty})" if item.meal_qty > 1 else "") |
| 1435 | + for item in items_per_preparation[prep] |
| 1436 | + ) |
| 1437 | + quantity = sum(item.meal_qty for item in items_per_preparation[prep]) |
| 1438 | + |
| 1439 | + preparation_lines.append( |
| 1440 | + PreparationLine( |
| 1441 | + preparation_method=prep, |
| 1442 | + quantity=quantity, |
| 1443 | + client_names=client_names, |
| 1444 | + ) |
| 1445 | + ) |
| 1446 | + |
| 1447 | + return preparation_lines |
| 1448 | + |
| 1449 | + |
| 1450 | +def kcr_make_pages( |
| 1451 | + kcr_date, |
| 1452 | + component_lines, |
| 1453 | + meal_lines, |
| 1454 | + preperation_lines_with_incompatible_ingr, |
| 1455 | + preperation_lines_without_incompatible_ingr, |
| 1456 | +): |
1392 | 1457 | """Generate the kitchen count report pages as a PDF file.
|
1393 | 1458 |
|
1394 | 1459 | Uses ReportLab see http://www.reportlab.com/documentation/faq/
|
1395 | 1460 |
|
1396 | 1461 | Args:
|
1397 |
| - kcr_date : The delivery date of the meals. |
1398 |
| - component_lines : A list of ComponentLine objects, the summary of |
| 1462 | + kcr_date: The delivery date of the meals. |
| 1463 | + component_lines: A list of ComponentLine objects, the summary of |
1399 | 1464 | component quantities and sizes for the date's meal.
|
1400 |
| - meal_lines : A list of MealLine objects, the details of the clients |
| 1465 | + meal_lines: A list of MealLine objects, the details of the clients |
1401 | 1466 | for the date that have ingredients clashing with those in main dish.
|
| 1467 | + preperation_lines_with_incompatible_ingr: list[PreperationLine] |
| 1468 | + preperation_lines_without_incompatible_ingr: list[PreperationLine] |
1402 | 1469 |
|
1403 | 1470 | Returns:
|
1404 | 1471 | An integer : The number of pages generated.
|
@@ -1459,6 +1526,43 @@ def myLaterPages(canvas, doc):
|
1459 | 1526 | drawHeader(canvas, doc)
|
1460 | 1527 | canvas.restoreState()
|
1461 | 1528 |
|
| 1529 | + def get_food_preperation_table(preperation_lines, heading_suffix): |
| 1530 | + rows = [] |
| 1531 | + line = 0 |
| 1532 | + tab_style = RLTableStyle([("VALIGN", (0, 0), (-1, -1), "TOP")]) |
| 1533 | + rows.append( |
| 1534 | + [ |
| 1535 | + RLParagraph("Food Preparation", styles["NormalLeft"]), |
| 1536 | + RLParagraph("Quantity", styles["NormalRight"]), |
| 1537 | + "", |
| 1538 | + [ |
| 1539 | + RLParagraph("Clients", styles["NormalLeft"]), |
| 1540 | + RLParagraph(f"({heading_suffix})", styles["NormalLeftBold"]), |
| 1541 | + ], |
| 1542 | + ] |
| 1543 | + ) |
| 1544 | + tab_style.add("LINEABOVE", (0, line), (-1, line), 1, rl_colors.black) |
| 1545 | + tab_style.add("LINEBELOW", (0, line), (-1, line), 1, rl_colors.black) |
| 1546 | + tab_style.add("LINEBEFORE", (0, line), (0, line), 1, rl_colors.black) |
| 1547 | + tab_style.add("LINEAFTER", (-1, line), (-1, line), 1, rl_colors.black) |
| 1548 | + line += 1 |
| 1549 | + |
| 1550 | + for prepline in preperation_lines: |
| 1551 | + rows.append( |
| 1552 | + [ |
| 1553 | + RLParagraph(prepline.preparation_method, styles["LargeBoldLeft"]), |
| 1554 | + RLParagraph(str(prepline.quantity), styles["NormalRightBold"]), |
| 1555 | + "", |
| 1556 | + RLParagraph( |
| 1557 | + "; ".join(prepline.client_names), |
| 1558 | + styles["NormalLeft"], |
| 1559 | + ), |
| 1560 | + ] |
| 1561 | + ) |
| 1562 | + tab = RLTable(rows, colWidths=(150, 50, 10, 310), repeatRows=1) |
| 1563 | + tab.setStyle(tab_style) |
| 1564 | + return tab |
| 1565 | + |
1462 | 1566 | def go():
|
1463 | 1567 | """Generate the pages.
|
1464 | 1568 |
|
@@ -1611,6 +1715,21 @@ def go():
|
1611 | 1715 | story.append(RLSpacer(1, 1 * rl_inch))
|
1612 | 1716 | # end Detail section
|
1613 | 1717 |
|
| 1718 | + story.append(RLPageBreak()) |
| 1719 | + story.append( |
| 1720 | + get_food_preperation_table( |
| 1721 | + preperation_lines_with_incompatible_ingr, "with restrictions" |
| 1722 | + ) |
| 1723 | + ) |
| 1724 | + story.append(RLSpacer(1, 1 * rl_inch)) |
| 1725 | + |
| 1726 | + story.append( |
| 1727 | + get_food_preperation_table( |
| 1728 | + preperation_lines_without_incompatible_ingr, "without restrictions" |
| 1729 | + ) |
| 1730 | + ) |
| 1731 | + story.append(RLSpacer(1, 1 * rl_inch)) |
| 1732 | + |
1614 | 1733 | # build full document
|
1615 | 1734 | doc.build(story, onFirstPage=myFirstPage, onLaterPages=myLaterPages)
|
1616 | 1735 | return doc.page
|
|
0 commit comments