printer-core 0.0.11__tar.gz → 0.0.12__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. {printer_core-0.0.11 → printer_core-0.0.12}/PKG-INFO +1 -1
  2. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/algorithms/products_modifiers_multiprinter.py +8 -5
  3. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/algorithms/products_modifiers_multiprinter_family.py +1 -1
  4. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/builders/client_ticket_builder.py +11 -11
  5. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/entities/print_context.py +4 -3
  6. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/entities/print_models.py +4 -3
  7. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/services/flows/client.py +10 -7
  8. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/services/flows/kitchen.py +20 -20
  9. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/services/multi_printer_family_service.py +20 -9
  10. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/services/multi_printer_service.py +3 -2
  11. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/tests/unitaries/test_client_ticket_builder.py +40 -40
  12. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/tests/unitaries/test_multi_printer_family_service.py +21 -3
  13. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/tests/unitaries/test_order_print_service.py +104 -104
  14. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/tests/unitaries/test_products_modifiers.py +14 -1
  15. {printer_core-0.0.11 → printer_core-0.0.12}/pyproject.toml +1 -1
  16. {printer_core-0.0.11 → printer_core-0.0.12}/.gitignore +0 -0
  17. {printer_core-0.0.11 → printer_core-0.0.12}/README.md +0 -0
  18. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/__init__.py +0 -0
  19. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/algorithms/__init__.py +0 -0
  20. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/algorithms/products_modifiers_unique_ticket.py +0 -0
  21. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/builders/__init__.py +0 -0
  22. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/builders/header_builder.py +0 -0
  23. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/builders/kitchen_ticket_payload_builder.py +0 -0
  24. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/builders/printer_products_grouper.py +0 -0
  25. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/clock.py +0 -0
  26. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/context/__init__.py +0 -0
  27. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/context/ticket_context_builder.py +0 -0
  28. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/dispatchers/__init__.py +0 -0
  29. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/dispatchers/kitchen_ticket_dispatcher.py +0 -0
  30. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/dispatchers/payload_dispatcher.py +0 -0
  31. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/entities/__init__.py +0 -0
  32. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/formatters/__init__.py +0 -0
  33. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/formatters/discount_formatter.py +0 -0
  34. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/ports.py +0 -0
  35. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/selectors/__init__.py +0 -0
  36. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/selectors/family_product_selector.py +0 -0
  37. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/services/__init__.py +0 -0
  38. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/services/flows/__init__.py +0 -0
  39. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/services/flows/base.py +0 -0
  40. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/services/march_printer_service.py +0 -0
  41. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/services/order_print_service.py +0 -0
  42. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/services/print_policy.py +0 -0
  43. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/services/unique_ticket_service.py +0 -0
  44. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/tests/__init__.py +0 -0
  45. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/tests/unitaries/__init__.py +0 -0
  46. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/tests/unitaries/helpers.py +0 -0
  47. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/tests/unitaries/test_cases_output.py +0 -0
  48. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/tests/unitaries/test_discount_formatter.py +0 -0
  49. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/tests/unitaries/test_family_product_selector.py +0 -0
  50. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/tests/unitaries/test_flows.py +0 -0
  51. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/tests/unitaries/test_header_builder_foodeo_printers.py +0 -0
  52. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/tests/unitaries/test_kitchen_ticket_dispatcher.py +0 -0
  53. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/tests/unitaries/test_kitchen_ticket_payload_builder.py +0 -0
  54. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/tests/unitaries/test_march_printer_service.py +0 -0
  55. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/tests/unitaries/test_multi_printer_service.py +0 -0
  56. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/tests/unitaries/test_payload_dispatcher.py +0 -0
  57. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/tests/unitaries/test_print_policy.py +0 -0
  58. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/tests/unitaries/test_printer_helpers.py +0 -0
  59. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/tests/unitaries/test_printer_products_grouper.py +0 -0
  60. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/tests/unitaries/test_ticket_context_builder.py +0 -0
  61. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/tests/unitaries/test_unique_ticket_service.py +0 -0
  62. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/utils/__init__.py +0 -0
  63. {printer_core-0.0.11 → printer_core-0.0.12}/printer_core/utils/printer_helpers.py +0 -0
  64. {printer_core-0.0.11 → printer_core-0.0.12}/scripts/link_backend_env.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: printer-core
3
- Version: 0.0.11
3
+ Version: 0.0.12
4
4
  Summary: Foodeo Printer Core Domain.
5
5
  Project-URL: Homepage, https://github.com/Foodeo/printer-core
6
6
  Project-URL: Repository, https://github.com/Foodeo/printer-core
@@ -13,14 +13,17 @@ class ProductsModifiersMultiPrinter:
13
13
  request: PrintRequestData,
14
14
  products: list[PrintProductData],
15
15
  kitchen_ticket_settings: Any,
16
- main_printer: PrinterData | dict[str, Any],
16
+ main_printer: PrinterData | dict[str, Any] | None,
17
17
  ) -> list[KitchenProductLine]:
18
18
  list_products_own_printer: list[KitchenProductLine] = []
19
- fallback_printer = self._resolve_printer(main_printer)
20
19
  for product in products:
21
- printers = list(product.printer or [])
22
- if not printers:
23
- printers = [fallback_printer]
20
+ if product.printer == []:
21
+ continue
22
+ printers = product.printer
23
+ if printers is None:
24
+ if main_printer is None:
25
+ raise ValueError("main_printer is required for unresolved kitchen products")
26
+ printers = [self._resolve_printer(main_printer)]
24
27
  for actual_printer in printers:
25
28
  printer_payload = self._resolve_printer(actual_printer)
26
29
  name = product.product_name
@@ -14,7 +14,7 @@ class ProductsModifiersMultiprinterFamily:
14
14
  request: PrintRequestData,
15
15
  kitchen_ticket_settings: Any,
16
16
  ) -> None:
17
- for actual_printer in product.printer:
17
+ for actual_printer in product.printer or []:
18
18
  printer_payload = self._resolve_printer(actual_printer)
19
19
  name_with_discount: str = product.product_name
20
20
  if not request.all_discounted:
@@ -1,12 +1,12 @@
1
+ from datetime import datetime
1
2
  from decimal import Decimal
2
3
  from typing import Any, Callable
3
- from datetime import datetime
4
4
 
5
5
  from printer_core.clock import SystemClock
6
- from printer_core.entities import (
7
- ClientTicketData,
8
- ClientTicketPayload,
9
- ClientTicketProfile,
6
+ from printer_core.entities import (
7
+ ClientTicketData,
8
+ ClientTicketPayload,
9
+ ClientTicketProfile,
10
10
  ClientTicketProduct,
11
11
  PrintProductData,
12
12
  PrintRequestData,
@@ -50,10 +50,10 @@ class ClientTicketBuilder:
50
50
  client_request = client_request.model_copy(update={"created_at": self._resolve_created_at(request)})
51
51
 
52
52
  printer_name, printer_width = self._resolve_printer(printer)
53
- payload = ClientTicketPayload(
53
+ payload = ClientTicketPayload(
54
54
  data=client_request,
55
55
  currency=client_request.currency or "",
56
- model=3 if request.client_profile == ClientTicketProfile.COMMAND else 2,
56
+ model=3 if request.client_profile == ClientTicketProfile.COMMAND else 2,
57
57
  company_key=tpv_key,
58
58
  printer=printer_name,
59
59
  printer_qty=qty_client_ticket,
@@ -112,10 +112,10 @@ class ClientTicketBuilder:
112
112
  return printer["name"], printer["printer_width"]
113
113
  return printer.name, printer.printer_width
114
114
 
115
- def _resolve_created_at(self, request: PrintRequestData) -> str:
116
- if request.client_profile == ClientTicketProfile.COMMAND:
117
- return request.created_at or self._clock.now_iso()
118
- if request.type in ["domicilio", "recoger"] and request.shift_time and request.delivery_date:
115
+ def _resolve_created_at(self, request: PrintRequestData) -> str:
116
+ if request.client_profile == ClientTicketProfile.COMMAND:
117
+ return request.created_at or self._clock.now_iso()
118
+ if request.type in ["domicilio", "recoger"] and request.shift_time and request.delivery_date:
119
119
  shift_time = datetime.strptime(request.shift_time, "%H:%M").time()
120
120
  delivery_date = datetime.strptime(request.delivery_date, "%Y-%m-%d").date()
121
121
  return datetime.combine(delivery_date, shift_time).strftime("%Y-%m-%d %H:%M")
@@ -7,9 +7,10 @@ from printer_core.entities.print_models import PrinterData, WorkerData
7
7
 
8
8
 
9
9
  @dataclass(frozen=True)
10
- class PrintContext:
10
+ class PrintContext:
11
11
  ticket_settings: Any
12
12
  kitchen_settings: Any
13
13
  global_settings: Any
14
- main_printer: PrinterData
15
- worker: WorkerData | None = None
14
+ main_printer: PrinterData | None
15
+ worker: WorkerData | None = None
16
+ client_printer: PrinterData | None = None
@@ -81,7 +81,7 @@ class PrinterData(BaseModel):
81
81
  tpv: str | None = None
82
82
 
83
83
 
84
- class PrintProductData(BaseModel):
84
+ class PrintProductData(BaseModel):
85
85
  model_config = ConfigDict(extra="allow")
86
86
 
87
87
  product_name: str
@@ -95,8 +95,9 @@ class PrintProductData(BaseModel):
95
95
  details: str | None = None
96
96
  order: str | None = None
97
97
  order_readable: str | None = None
98
- printer: list[PrinterData] = Field(default_factory=list)
99
- category: int | None = None
98
+ printer: list[PrinterData] | None = None
99
+ category: int | None = None
100
+ request_item_id: int | None = Field(default=None, exclude=True)
100
101
 
101
102
 
102
103
  class ClientTicketProduct(BaseModel):
@@ -63,13 +63,16 @@ class ClientPrintFlow:
63
63
  "headerData": header_data,
64
64
  })
65
65
  client_request = self._apply_client_request_overrides(client_request, channel)
66
- data = self._build_client_ticket(
67
- client_request,
68
- products,
69
- context.ticket_settings,
70
- context.main_printer,
71
- tpv_key,
72
- )
66
+ client_printer = getattr(context, "client_printer", None) or context.main_printer
67
+ if client_printer is None:
68
+ raise ValueError("client_printer is required for client printing")
69
+ data = self._build_client_ticket(
70
+ client_request,
71
+ products,
72
+ context.ticket_settings,
73
+ client_printer,
74
+ tpv_key,
75
+ )
73
76
  payload = [self._serialize_client_payload(item) for item in data]
74
77
  self._gateway.send_data(tpv_key, payload)
75
78
  return data
@@ -3,14 +3,14 @@ from __future__ import annotations
3
3
  from typing import Any, Callable
4
4
 
5
5
  from printer_core.context.ticket_context_builder import TicketContextBuilder
6
- from printer_core.entities import (
7
- KitchenProductLine,
8
- KitchenTicketPayload,
9
- PrintChannel,
10
- PrintProductData,
11
- PrintRequestData,
12
- PrinterData,
13
- )
6
+ from printer_core.entities import (
7
+ KitchenProductLine,
8
+ KitchenTicketPayload,
9
+ PrintChannel,
10
+ PrintProductData,
11
+ PrintRequestData,
12
+ PrinterData,
13
+ )
14
14
  from printer_core.ports import FamilyCategoryProvider, HeaderProvider, PrinterGateway, PrintSettingsBuilder
15
15
  from printer_core.services.multi_printer_family_service import MultiPrinterFamilyService
16
16
  from printer_core.services.multi_printer_service import MultiPrinterService
@@ -66,14 +66,14 @@ class KitchenPrintFlow:
66
66
  products: list[PrintProductData],
67
67
  ticket_settings: Any,
68
68
  kitchen_settings: Any,
69
- main_printer: PrinterData,
69
+ main_printer: PrinterData | None,
70
70
  ) -> list[KitchenTicketPayload]:
71
71
  if getattr(kitchen_settings, "print_by", "categories") == "categories":
72
- service = MultiPrinterService(
73
- gateway=self._gateway,
74
- currency_symbol_resolver=self._currency_symbol_resolver,
75
- order_by_func=self._noop_order_by,
76
- )
72
+ service = MultiPrinterService(
73
+ gateway=self._gateway,
74
+ currency_symbol_resolver=self._currency_symbol_resolver,
75
+ order_by_func=self._noop_order_by,
76
+ )
77
77
  return service.build_and_dispatch(
78
78
  request=request,
79
79
  products=products,
@@ -83,10 +83,10 @@ class KitchenPrintFlow:
83
83
  )
84
84
  service = MultiPrinterFamilyService(
85
85
  gateway=self._gateway,
86
- family_provider=self._family_provider,
87
- currency_symbol_resolver=self._currency_symbol_resolver,
88
- order_by_func=self._noop_order_by,
89
- )
86
+ family_provider=self._family_provider,
87
+ currency_symbol_resolver=self._currency_symbol_resolver,
88
+ order_by_func=self._noop_order_by,
89
+ )
90
90
  return service.build_and_dispatch(
91
91
  request=request,
92
92
  products=products,
@@ -103,5 +103,5 @@ class KitchenPrintFlow:
103
103
  def _include_print_by(self, channel: PrintChannel) -> bool:
104
104
  return channel not in (PrintChannel.BARRA, PrintChannel.KIOSKO)
105
105
 
106
- def _noop_order_by(self, products: list[KitchenProductLine]) -> list[KitchenProductLine]:
107
- return products
106
+ def _noop_order_by(self, products: list[KitchenProductLine]) -> list[KitchenProductLine]:
107
+ return products
@@ -38,7 +38,7 @@ class MultiPrinterFamilyService:
38
38
  self,
39
39
  request: PrintRequestData,
40
40
  products: list[PrintProductData],
41
- main_printer: PrinterData,
41
+ main_printer: PrinterData | None,
42
42
  ticket_settings: Any,
43
43
  kitchen_ticket_settings: Any,
44
44
  ) -> list[KitchenTicketPayload]:
@@ -54,10 +54,9 @@ class MultiPrinterFamilyService:
54
54
  list_products_own_printer.clear()
55
55
  family_products = products_by_family.get(family.id, [])
56
56
  for product in family_products:
57
- if product.printer:
58
- product_with_printer = product
59
- else:
60
- product_with_printer = product.model_copy(update={"printer": [main_printer]})
57
+ product_with_printer = self._resolve_product_route(product, main_printer)
58
+ if product_with_printer is None:
59
+ continue
61
60
  family_builder.append_product(
62
61
  list_products_own_printer, product_with_printer, request, kitchen_ticket_settings
63
62
  )
@@ -67,10 +66,9 @@ class MultiPrinterFamilyService:
67
66
 
68
67
  list_products_own_printer.clear()
69
68
  for product in products_without_family:
70
- if product.printer:
71
- product_with_printer = product
72
- else:
73
- product_with_printer = product.model_copy(update={"printer": [main_printer]})
69
+ product_with_printer = self._resolve_product_route(product, main_printer)
70
+ if product_with_printer is None:
71
+ continue
74
72
  family_builder.append_product(
75
73
  list_products_own_printer, product_with_printer, request, kitchen_ticket_settings
76
74
  )
@@ -80,6 +78,19 @@ class MultiPrinterFamilyService:
80
78
  ))
81
79
  return dispatched
82
80
 
81
+ def _resolve_product_route(
82
+ self,
83
+ product: PrintProductData,
84
+ main_printer: PrinterData | None,
85
+ ) -> PrintProductData | None:
86
+ if product.printer == []:
87
+ return None
88
+ if product.printer is not None:
89
+ return product
90
+ if main_printer is None:
91
+ raise ValueError("main_printer is required for unresolved kitchen products")
92
+ return product.model_copy(update={"printer": [main_printer]})
93
+
83
94
  def _dispatch_products(
84
95
  self,
85
96
  list_products_own_printer: list[KitchenProductLine],
@@ -3,7 +3,8 @@ from typing import Any, Callable
3
3
  from printer_core.algorithms.products_modifiers_multiprinter import ProductsModifiersMultiPrinter
4
4
  from printer_core.builders.printer_products_grouper import PrinterProductsGrouper
5
5
  from printer_core.dispatchers.kitchen_ticket_dispatcher import KitchenTicketDispatcher
6
- from printer_core.entities import KitchenProductLine, KitchenTicketPayload, PrintProductData, PrintRequestData, PrinterData, ProductsGroup
6
+ from printer_core.entities import KitchenProductLine, KitchenTicketPayload, PrintProductData, PrintRequestData, \
7
+ PrinterData, ProductsGroup
7
8
  from printer_core.formatters.discount_formatter import DiscountFormatter
8
9
  from printer_core.ports import PrinterGateway
9
10
  from printer_core.utils.printer_helpers import group_products_by_orders, order_products_by_name
@@ -26,7 +27,7 @@ class MultiPrinterService:
26
27
  self,
27
28
  request: PrintRequestData,
28
29
  products: list[PrintProductData],
29
- main_printer: PrinterData,
30
+ main_printer: PrinterData | None,
30
31
  ticket_settings: Any,
31
32
  kitchen_ticket_settings: Any,
32
33
  ) -> list[KitchenTicketPayload]:
@@ -1,7 +1,7 @@
1
1
  import unittest
2
- from printer_core.builders.client_ticket_builder import ClientTicketBuilder
3
- from printer_core.entities import ClientTicketProfile, DiscountData
4
2
 
3
+ from printer_core.builders.client_ticket_builder import ClientTicketBuilder
4
+ from printer_core.entities import ClientTicketProfile, DiscountData
5
5
  from printer_core.tests.unitaries.helpers import make_printer, make_product, make_request, make_ticket_settings
6
6
 
7
7
 
@@ -109,7 +109,7 @@ class ClientTicketBuilderTest(unittest.TestCase):
109
109
 
110
110
  self.assertEqual(len(payloads[0].data.products), 1)
111
111
 
112
- def test_build_does_not_serialize_rectified_flag_when_request_is_rectified(self):
112
+ def test_build_does_not_serialize_rectified_flag_when_request_is_rectified(self):
113
113
  builder = ClientTicketBuilder()
114
114
  request = make_request(is_rectified=True)
115
115
  products = [make_product()]
@@ -122,40 +122,40 @@ class ClientTicketBuilderTest(unittest.TestCase):
122
122
  tpv_key="TPV1",
123
123
  )
124
124
 
125
- self.assertTrue(request.is_rectified)
126
- self.assertNotIn("is_rectified", payloads[0].data.model_dump())
127
-
128
- def test_command_profile_uses_model_three_and_preserves_created_at_and_discount(self):
129
- builder = ClientTicketBuilder()
130
- request = make_request(
131
- request_type="local",
132
- created_at="2026-07-30T14:25:00+00:00",
133
- discount=DiscountData(value="10", type="%", total="2.00"),
134
- client_profile=ClientTicketProfile.COMMAND,
135
- )
136
-
137
- payload = builder.build(
138
- request=request,
139
- products=[make_product()],
140
- printer=make_printer(),
141
- ticket_settings=make_ticket_settings(),
142
- tpv_key="TPV1",
143
- )[0]
144
-
145
- self.assertEqual(payload.model, 3)
146
- self.assertEqual(payload.data.created_at, "2026-07-30T14:25:00+00:00")
147
- self.assertEqual(payload.data.discount.total, "2.00")
148
-
149
- def test_client_profile_is_not_serialized_in_request_or_payload(self):
150
- request = make_request(client_profile=ClientTicketProfile.COMMAND)
151
-
152
- self.assertNotIn("client_profile", request.model_dump())
153
-
154
- payload = ClientTicketBuilder().build(
155
- request=request,
156
- products=[make_product()],
157
- printer=make_printer(),
158
- ticket_settings=make_ticket_settings(),
159
- tpv_key="TPV1",
160
- )[0]
161
- self.assertNotIn("client_profile", payload.model_dump()["data"])
125
+ self.assertTrue(request.is_rectified)
126
+ self.assertNotIn("is_rectified", payloads[0].data.model_dump())
127
+
128
+ def test_command_profile_uses_model_three_and_preserves_created_at_and_discount(self):
129
+ builder = ClientTicketBuilder()
130
+ request = make_request(
131
+ request_type="local",
132
+ created_at="2026-07-30T14:25:00+00:00",
133
+ discount=DiscountData(value="10", type="%", total="2.00"),
134
+ client_profile=ClientTicketProfile.COMMAND,
135
+ )
136
+
137
+ payload = builder.build(
138
+ request=request,
139
+ products=[make_product()],
140
+ printer=make_printer(),
141
+ ticket_settings=make_ticket_settings(),
142
+ tpv_key="TPV1",
143
+ )[0]
144
+
145
+ self.assertEqual(payload.model, 3)
146
+ self.assertEqual(payload.data.created_at, "2026-07-30T14:25:00+00:00")
147
+ self.assertEqual(payload.data.discount.total, "2.00")
148
+
149
+ def test_client_profile_is_not_serialized_in_request_or_payload(self):
150
+ request = make_request(client_profile=ClientTicketProfile.COMMAND)
151
+
152
+ self.assertNotIn("client_profile", request.model_dump())
153
+
154
+ payload = ClientTicketBuilder().build(
155
+ request=request,
156
+ products=[make_product()],
157
+ printer=make_printer(),
158
+ ticket_settings=make_ticket_settings(),
159
+ tpv_key="TPV1",
160
+ )[0]
161
+ self.assertNotIn("client_profile", payload.model_dump()["data"])
@@ -1,10 +1,10 @@
1
+ import unittest
1
2
  from unittest.mock import patch
2
3
 
3
- import unittest
4
4
  from printer_core.entities import FamilyData
5
5
  from printer_core.services.multi_printer_family_service import MultiPrinterFamilyService
6
-
7
- from printer_core.tests.unitaries.helpers import FakeFamilyProvider, make_kitchen_settings, make_printer, make_product, make_request, make_ticket_settings
6
+ from printer_core.tests.unitaries.helpers import FakeFamilyProvider, make_kitchen_settings, make_printer, make_product, \
7
+ make_request, make_ticket_settings
8
8
 
9
9
 
10
10
  class MultiPrinterFamilyServiceTest(unittest.TestCase):
@@ -51,3 +51,21 @@ class MultiPrinterFamilyServiceTest(unittest.TestCase):
51
51
 
52
52
  self.assertEqual(len(dispatch_calls), 3)
53
53
  self.assertTrue(all(count >= 1 for count in dispatch_calls))
54
+
55
+ def test_explicit_empty_route_is_not_replaced_by_fallback(self):
56
+ provider = FakeFamilyProvider(families=[], family_by_category={})
57
+ service = MultiPrinterFamilyService(
58
+ gateway=object(),
59
+ family_provider=provider,
60
+ currency_symbol_resolver=lambda _: "",
61
+ )
62
+
63
+ result = service.build_and_dispatch(
64
+ request=make_request(),
65
+ products=[make_product(printer=[])],
66
+ main_printer=make_printer(name="Main"),
67
+ ticket_settings=make_ticket_settings(),
68
+ kitchen_ticket_settings=make_kitchen_settings(show_price_per_unit=False),
69
+ )
70
+
71
+ self.assertEqual(result, [])
@@ -1,8 +1,8 @@
1
- import unittest
2
- from printer_core.entities import ClientTicketProfile, PrintChannel, PrintJob
3
- from printer_core.services.order_print_service import OrderPrintService
4
- from printer_core.services.print_policy import FlowTarget, PrintPlan
1
+ import unittest
5
2
 
3
+ from printer_core.entities import ClientTicketProfile, PrintChannel, PrintJob
4
+ from printer_core.services.order_print_service import OrderPrintService
5
+ from printer_core.services.print_policy import FlowTarget, PrintPlan
6
6
  from printer_core.tests.unitaries.helpers import (
7
7
  FakeClock,
8
8
  make_discount,
@@ -104,7 +104,7 @@ class OrderPrintServiceTest(unittest.TestCase):
104
104
  enriched = kitchen_flow.calls[0]["base_request"]
105
105
  self.assertEqual(enriched.created_at, "2024-05-01 08:00")
106
106
 
107
- def test_print_job_skips_flows_when_disabled(self):
107
+ def test_print_job_skips_flows_when_disabled(self):
108
108
  kitchen_flow = FakeFlow(FlowTarget.KITCHEN, return_value=[])
109
109
  service = OrderPrintService(
110
110
  flows=[kitchen_flow],
@@ -119,102 +119,102 @@ class OrderPrintServiceTest(unittest.TestCase):
119
119
 
120
120
  result = service.print_job(job, context, PrintChannel.LOCAL)
121
121
 
122
- self.assertEqual(kitchen_flow.calls, [])
123
- self.assertIsNone(result.multi_printer)
124
- self.assertIsNone(result.ticket_client)
125
-
126
- def test_explicit_plan_runs_client_when_automatic_setting_is_disabled(self):
127
- client_flow = FakeFlow(FlowTarget.CLIENT, return_value=[{"client": True}])
128
- service = OrderPrintService(flows=[client_flow])
129
- job = PrintJob(
130
- request=make_request(request_type="local"),
131
- products=[make_product()],
132
- tpv_key="TPV1",
133
- )
134
- context = type("Ctx", (), {
135
- "ticket_settings": make_ticket_settings(ticket_kitchen_local=False, ticket_client_local=False),
136
- "kitchen_settings": make_kitchen_settings(print_by="categories"),
137
- "global_settings": make_global_settings(currency="EUR", iva=21),
138
- })()
139
-
140
- result = service.print_job(
141
- job,
142
- context,
143
- PrintChannel.LOCAL,
144
- plan=PrintPlan(enabled_flows={FlowTarget.CLIENT}),
145
- )
146
-
147
- self.assertEqual(result.ticket_client, [{"client": True}])
148
- self.assertEqual(len(client_flow.calls), 1)
149
-
150
- def test_explicit_plan_rejects_target_without_configured_flow(self):
151
- service = OrderPrintService(flows=[])
152
- job = PrintJob(
153
- request=make_request(request_type="local"),
154
- products=[make_product()],
155
- tpv_key="TPV1",
156
- )
157
- context = type("Ctx", (), {
158
- "ticket_settings": make_ticket_settings(ticket_kitchen_local=False, ticket_client_local=False),
159
- "kitchen_settings": make_kitchen_settings(print_by="categories"),
160
- "global_settings": make_global_settings(currency="EUR", iva=21),
161
- })()
162
-
163
- with self.assertRaisesRegex(ValueError, "client"):
164
- service.print_job(
165
- job,
166
- context,
167
- PrintChannel.LOCAL,
168
- plan=PrintPlan(enabled_flows={FlowTarget.CLIENT}),
169
- )
170
-
171
- def test_automatic_plan_ignores_enabled_flow_not_exposed_by_caller(self):
172
- kitchen_flow = FakeFlow(FlowTarget.KITCHEN, return_value=[{"kitchen": True}])
173
- service = OrderPrintService(flows=[kitchen_flow])
174
- job = PrintJob(
175
- request=make_request(request_type="barra"),
176
- products=[make_product()],
177
- tpv_key="TPV1",
178
- )
179
- context = type("Ctx", (), {
180
- "ticket_settings": make_ticket_settings(
181
- ticket_kitchen_barra=True,
182
- ticket_client_barra=True,
183
- ),
184
- "kitchen_settings": make_kitchen_settings(print_by="categories"),
185
- "global_settings": make_global_settings(currency="EUR", iva=21),
186
- })()
187
-
188
- result = service.print_job(job, context, PrintChannel.BARRA)
189
-
190
- self.assertEqual(result.multi_printer, [{"kitchen": True}])
191
- self.assertIsNone(result.ticket_client)
192
- self.assertEqual(len(kitchen_flow.calls), 1)
193
-
194
- def test_command_profile_preserves_local_header_discount(self):
195
- client_flow = FakeFlow(FlowTarget.CLIENT, return_value=[])
196
- service = OrderPrintService(flows=[client_flow])
197
- discount = make_discount(total="2.00")
198
- job = PrintJob(
199
- request=make_request(
200
- request_type="local",
201
- discount=discount,
202
- client_profile=ClientTicketProfile.COMMAND,
203
- ),
204
- products=[make_product()],
205
- tpv_key="TPV1",
206
- )
207
- context = type("Ctx", (), {
208
- "ticket_settings": make_ticket_settings(ticket_client_local=False),
209
- "kitchen_settings": make_kitchen_settings(print_by="categories"),
210
- "global_settings": make_global_settings(currency="EUR", iva=21),
211
- })()
212
-
213
- service.print_job(
214
- job,
215
- context,
216
- PrintChannel.LOCAL,
217
- plan=PrintPlan(enabled_flows={FlowTarget.CLIENT}),
218
- )
219
-
220
- self.assertEqual(client_flow.calls[0]["base_request"].discount, discount)
122
+ self.assertEqual(kitchen_flow.calls, [])
123
+ self.assertIsNone(result.multi_printer)
124
+ self.assertIsNone(result.ticket_client)
125
+
126
+ def test_explicit_plan_runs_client_when_automatic_setting_is_disabled(self):
127
+ client_flow = FakeFlow(FlowTarget.CLIENT, return_value=[{"client": True}])
128
+ service = OrderPrintService(flows=[client_flow])
129
+ job = PrintJob(
130
+ request=make_request(request_type="local"),
131
+ products=[make_product()],
132
+ tpv_key="TPV1",
133
+ )
134
+ context = type("Ctx", (), {
135
+ "ticket_settings": make_ticket_settings(ticket_kitchen_local=False, ticket_client_local=False),
136
+ "kitchen_settings": make_kitchen_settings(print_by="categories"),
137
+ "global_settings": make_global_settings(currency="EUR", iva=21),
138
+ })()
139
+
140
+ result = service.print_job(
141
+ job,
142
+ context,
143
+ PrintChannel.LOCAL,
144
+ plan=PrintPlan(enabled_flows={FlowTarget.CLIENT}),
145
+ )
146
+
147
+ self.assertEqual(result.ticket_client, [{"client": True}])
148
+ self.assertEqual(len(client_flow.calls), 1)
149
+
150
+ def test_explicit_plan_rejects_target_without_configured_flow(self):
151
+ service = OrderPrintService(flows=[])
152
+ job = PrintJob(
153
+ request=make_request(request_type="local"),
154
+ products=[make_product()],
155
+ tpv_key="TPV1",
156
+ )
157
+ context = type("Ctx", (), {
158
+ "ticket_settings": make_ticket_settings(ticket_kitchen_local=False, ticket_client_local=False),
159
+ "kitchen_settings": make_kitchen_settings(print_by="categories"),
160
+ "global_settings": make_global_settings(currency="EUR", iva=21),
161
+ })()
162
+
163
+ with self.assertRaisesRegex(ValueError, "client"):
164
+ service.print_job(
165
+ job,
166
+ context,
167
+ PrintChannel.LOCAL,
168
+ plan=PrintPlan(enabled_flows={FlowTarget.CLIENT}),
169
+ )
170
+
171
+ def test_automatic_plan_ignores_enabled_flow_not_exposed_by_caller(self):
172
+ kitchen_flow = FakeFlow(FlowTarget.KITCHEN, return_value=[{"kitchen": True}])
173
+ service = OrderPrintService(flows=[kitchen_flow])
174
+ job = PrintJob(
175
+ request=make_request(request_type="barra"),
176
+ products=[make_product()],
177
+ tpv_key="TPV1",
178
+ )
179
+ context = type("Ctx", (), {
180
+ "ticket_settings": make_ticket_settings(
181
+ ticket_kitchen_barra=True,
182
+ ticket_client_barra=True,
183
+ ),
184
+ "kitchen_settings": make_kitchen_settings(print_by="categories"),
185
+ "global_settings": make_global_settings(currency="EUR", iva=21),
186
+ })()
187
+
188
+ result = service.print_job(job, context, PrintChannel.BARRA)
189
+
190
+ self.assertEqual(result.multi_printer, [{"kitchen": True}])
191
+ self.assertIsNone(result.ticket_client)
192
+ self.assertEqual(len(kitchen_flow.calls), 1)
193
+
194
+ def test_command_profile_preserves_local_header_discount(self):
195
+ client_flow = FakeFlow(FlowTarget.CLIENT, return_value=[])
196
+ service = OrderPrintService(flows=[client_flow])
197
+ discount = make_discount(total="2.00")
198
+ job = PrintJob(
199
+ request=make_request(
200
+ request_type="local",
201
+ discount=discount,
202
+ client_profile=ClientTicketProfile.COMMAND,
203
+ ),
204
+ products=[make_product()],
205
+ tpv_key="TPV1",
206
+ )
207
+ context = type("Ctx", (), {
208
+ "ticket_settings": make_ticket_settings(ticket_client_local=False),
209
+ "kitchen_settings": make_kitchen_settings(print_by="categories"),
210
+ "global_settings": make_global_settings(currency="EUR", iva=21),
211
+ })()
212
+
213
+ service.print_job(
214
+ job,
215
+ context,
216
+ PrintChannel.LOCAL,
217
+ plan=PrintPlan(enabled_flows={FlowTarget.CLIENT}),
218
+ )
219
+
220
+ self.assertEqual(client_flow.calls[0]["base_request"].discount, discount)
@@ -1,9 +1,9 @@
1
1
  import unittest
2
+
2
3
  from printer_core.algorithms.products_modifiers_multiprinter import ProductsModifiersMultiPrinter
3
4
  from printer_core.algorithms.products_modifiers_multiprinter_family import ProductsModifiersMultiprinterFamily
4
5
  from printer_core.algorithms.products_modifiers_unique_ticket import ProductsModifiersUniqueTicket
5
6
  from printer_core.formatters.discount_formatter import DiscountFormatter
6
-
7
7
  from printer_core.tests.unitaries.helpers import make_kitchen_settings, make_printer, make_product, make_request
8
8
 
9
9
 
@@ -34,6 +34,19 @@ class ProductsModifiersMultiPrinterTest(unittest.TestCase):
34
34
  self.assertEqual(len(lines), 2)
35
35
  self.assertEqual({line.printer_r for line in lines}, {"P1", "P2"})
36
36
 
37
+ def test_build_skips_product_with_explicit_empty_route(self):
38
+ service = ProductsModifiersMultiPrinter(DiscountFormatter())
39
+ product = make_product(printer=[])
40
+
41
+ lines = service.build(
42
+ make_request(),
43
+ [product],
44
+ make_kitchen_settings(show_price_per_unit=False),
45
+ None,
46
+ )
47
+
48
+ self.assertEqual(lines, [])
49
+
37
50
  def test_build_sets_prices_when_enabled(self):
38
51
  formatter = DiscountFormatter()
39
52
  service = ProductsModifiersMultiPrinter(formatter)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "printer-core"
7
- version = "0.0.11"
7
+ version = "0.0.12"
8
8
  description = "Foodeo Printer Core Domain."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
File without changes
File without changes