From 2de4b9009a057b684808b7d48bafa5a43397c5ee Mon Sep 17 00:00:00 2001 From: souplearn Date: Sat, 25 Jul 2026 05:08:43 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AF=B9=E8=B4=A6=E5=B0=8F=E8=B4=A6?= =?UTF-8?q?=E6=9C=AC=E6=8C=89=E5=BD=92=E5=B1=9E=E6=97=A5=E6=9C=9F=E7=BB=9F?= =?UTF-8?q?=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/routers/reconciliation.py | 50 +++++++++++++++++------- tests/test_reconciliation_allocations.py | 15 +++++++ 2 files changed, 50 insertions(+), 15 deletions(-) create mode 100644 tests/test_reconciliation_allocations.py diff --git a/app/routers/reconciliation.py b/app/routers/reconciliation.py index 0ce6f19..af41bf5 100644 --- a/app/routers/reconciliation.py +++ b/app/routers/reconciliation.py @@ -6,7 +6,16 @@ from sqlalchemy.orm import Session from app.database import get_db from app.deps import require_roles -from app.models import Personnel, Product, ProductionReport, ProductionReportItem, ReconciliationLedgerEntry, ReportStatus, Role +from app.models import ( + Personnel, + Product, + ProductionReport, + ProductionReportAllocation, + ProductionReportItem, + ReconciliationLedgerEntry, + ReportStatus, + Role, +) from app.schemas import ( ReconciliationEntryUpdate, ReconciliationLedgerOut, @@ -58,6 +67,20 @@ def _month_range(year: int, month: int) -> tuple[date, date]: return start, end +def _fold_reported_good_quantity_rows( + rows, + latest_processes: dict[tuple[str, str], set[str]], +) -> dict[tuple[str, str, int], float]: + totals: dict[tuple[str, str, int], float] = {} + for point_name, product_name, process_name, month, good_qty in rows: + product_key = (point_name, product_name) + if str(process_name or "").strip() not in latest_processes.get(product_key, set()): + continue + key = (point_name, product_name, int(month)) + totals[key] = round2(totals.get(key, 0) + as_float(good_qty)) + return totals + + def _reported_good_quantities( db: Session, *, @@ -74,14 +97,16 @@ def _reported_good_quantities( ProductionReportItem.attendance_point_name, ProductionReportItem.product_name, ProductionReportItem.process_name, - extract("month", ProductionReport.report_date).label("month"), - func.sum(ProductionReportItem.good_qty).label("good_qty"), + extract("month", ProductionReportAllocation.allocation_date).label("month"), + func.sum(ProductionReportAllocation.good_qty).label("good_qty"), ) - .join(ProductionReport, ProductionReport.id == ProductionReportItem.report_id) + .select_from(ProductionReportAllocation) + .join(ProductionReportItem, ProductionReportItem.id == ProductionReportAllocation.report_item_id) + .join(ProductionReport, ProductionReport.id == ProductionReportAllocation.report_id) .where( ProductionReport.status == ReportStatus.approved, ProductionReport.is_voided.is_(False), - extract("year", ProductionReport.report_date) == year, + extract("year", ProductionReportAllocation.allocation_date) == year, ProductionReportItem.attendance_point_name.in_(point_names), ProductionReportItem.product_name.in_(product_names), ) @@ -89,17 +114,10 @@ def _reported_good_quantities( ProductionReportItem.attendance_point_name, ProductionReportItem.product_name, ProductionReportItem.process_name, - extract("month", ProductionReport.report_date), + extract("month", ProductionReportAllocation.allocation_date), ) ).all() - totals: dict[tuple[str, str, int], float] = {} - for point_name, product_name, process_name, month, good_qty in rows: - product_key = (point_name, product_name) - if str(process_name or "").strip() not in latest_processes.get(product_key, set()): - continue - key = (point_name, product_name, int(month)) - totals[key] = round2(totals.get(key, 0) + as_float(good_qty)) - return totals + return _fold_reported_good_quantity_rows(rows, latest_processes) def _ledger_quantities(db: Session, *, year: int) -> dict[tuple[str, str, int], dict[str, float]]: @@ -125,7 +143,9 @@ def list_reconciliation_years( current_year = today().year years = {current_year} report_years = db.execute( - select(extract("year", ProductionReport.report_date)) + select(extract("year", ProductionReportAllocation.allocation_date)) + .select_from(ProductionReportAllocation) + .join(ProductionReport, ProductionReport.id == ProductionReportAllocation.report_id) .where( ProductionReport.status == ReportStatus.approved, ProductionReport.is_voided.is_(False), diff --git a/tests/test_reconciliation_allocations.py b/tests/test_reconciliation_allocations.py new file mode 100644 index 0000000..6f7deb4 --- /dev/null +++ b/tests/test_reconciliation_allocations.py @@ -0,0 +1,15 @@ +from app.routers import reconciliation + + +def test_fold_reported_good_quantity_rows_uses_allocation_month_for_latest_process_only(): + rows = [ + ("嘉恒", "产品A", "1序", 7, 999), + ("嘉恒", "产品A", "2序", 7, 350), + ("嘉恒", "产品A", "2序", 8, 300), + ] + latest_processes = {("嘉恒", "产品A"): {"2序"}} + + totals = reconciliation._fold_reported_good_quantity_rows(rows, latest_processes) + + assert totals[("嘉恒", "产品A", 7)] == 350 + assert totals[("嘉恒", "产品A", 8)] == 300