فهرست منبع

fix(billing): a failed dispatch no longer leaves its cost centre for the next print

maziggy 6 روز پیش
والد
کامیت
b34ae7ca7b
3فایلهای تغییر یافته به همراه10 افزوده شده و 4 حذف شده
  1. 1 0
      CHANGELOG.md
  2. 4 3
      backend/app/main.py
  3. 5 1
      backend/tests/unit/test_expected_print_rollback.py

+ 1 - 0
CHANGELOG.md

@@ -164,6 +164,7 @@ All notable changes to Bambuddy will be documented in this file.
 - **The frontend build no longer warns about `path` and `crypto` being externalized for the STEP previewer (#2976)** — `occt-import-js`, the Emscripten build behind STEP previews, requires both modules, but only inside its `ENVIRONMENT_IS_NODE` branches; in the browser it loads its `.wasm` from the URL the preview worker passes and draws randomness from `crypto.getRandomValues`. Vite still externalized both and printed two warnings on every build. `vite.config.ts` now drops exactly those two warnings for that one package through `build.rolldownOptions.onLog`, so an externalization anywhere else, or of any other module, still shows.
 
 ### Fixed
+- **A print whose dispatch failed no longer leaves its cost centre behind** — the next print of the same archive without a cost centre of its own was charged to it, instead of the user or the archive's cost centre.
 - **Which plate of a multi-plate file printed** —
   - Print History logs the filament and cost of the plate that printed. A reprint of another plate of the same file used to log the first plate's estimate.
   - Filament usage after a Bambuddy restart mid-print is charged for the plate the queue job printed, not for the plate of the archive's first print.

+ 4 - 3
backend/app/main.py

@@ -1209,9 +1209,9 @@ def unregister_expected_print(printer_id: int, filename: str, archive_id: int) -
     ``ams_mapping`` / ``plate_id`` with it. Hence the explicit inverse.
 
     Mirrors the sweep's rules, including the one that is easy to get wrong:
-    ``_print_ams_mappings`` / ``_print_plate_ids`` are keyed by archive, not by
-    file, so they may only be dropped once no live key still points at that
-    archive.
+    ``_print_ams_mappings`` / ``_print_plate_ids`` / ``_print_cost_center_ids``
+    are keyed by archive, not by file, so they may only be dropped once no
+    live key still points at that archive.
     """
     keys = [(printer_id, filename)]
     if filename.endswith(".3mf"):
@@ -1228,6 +1228,7 @@ def unregister_expected_print(printer_id: int, filename: str, archive_id: int) -
 
     if archive_id not in set(_expected_prints.values()):
         _print_ams_mappings.pop(archive_id, None)
+        _print_cost_center_ids.pop(archive_id, None)
         _print_plate_ids.pop(archive_id, None)
 
     if removed:

+ 5 - 1
backend/tests/unit/test_expected_print_rollback.py

@@ -29,6 +29,7 @@ def expected_print_tables():
         "_expected_print_registered_at",
         "_print_ams_mappings",
         "_print_plate_ids",
+        "_print_cost_center_ids",
     )
     saved = {n: dict(getattr(main, n)) for n in names}
     for n in names:
@@ -48,7 +49,9 @@ def test_unregister_leaves_every_registry_as_it_found_them(expected_print_tables
     """The strongest form: register then unregister is a round trip to empty."""
     main = expected_print_tables
 
-    main.register_expected_print(1, "widget.3mf", 298, ams_mapping=[3, 6], created_by_id=7, plate_id=1)
+    main.register_expected_print(
+        1, "widget.3mf", 298, ams_mapping=[3, 6], created_by_id=7, plate_id=1, cost_center_id=5
+    )
     assert main._expected_prints, "nothing registered — the test proves nothing"
 
     main.unregister_expected_print(1, "widget.3mf", 298)
@@ -58,6 +61,7 @@ def test_unregister_leaves_every_registry_as_it_found_them(expected_print_tables
     assert main._expected_print_registered_at == {}
     assert main._print_ams_mappings == {}
     assert main._print_plate_ids == {}
+    assert main._print_cost_center_ids == {}
 
 
 def test_unregister_clears_the_filename_variants_too(expected_print_tables):