from datetime import date, datetime from sqlalchemy import BigInteger, create_engine from sqlalchemy.ext.compiler import compiles from sqlalchemy.orm import sessionmaker from app.database import Base from app.models import ( ProductionReport, ProductionReportAllocation, ProductionReportItem, ReportStatus, ) from app.routers import reconciliation @compiles(BigInteger, "sqlite") def _compile_big_integer_for_sqlite(type_, compiler, **kw): return "INTEGER" def _sqlite_db(): engine = create_engine("sqlite+pysqlite:///:memory:", future=True) Base.metadata.create_all(engine) SessionLocal = sessionmaker(bind=engine, future=True) return SessionLocal() def _report( *, id: int, status: ReportStatus = ReportStatus.approved, is_voided: bool = False, ): return ProductionReport( id=id, session_id=id, attendance_point_name="嘉恒", employee_phone="13800000000", report_date=date(2026, 7, 31), start_at=datetime(2026, 7, 31, 20, 0), end_at=datetime(2026, 8, 1, 2, 0), duration_minutes=360, break_minutes=0, effective_minutes=360, total_good_qty=650, total_output_qty=650, actual_beat=1, standard_beat=1, expected_workload=0, pace_rate=0, workload_rate=0, status=status, is_voided=is_voided, submitted_at=datetime(2026, 8, 1, 2, 1), ) def _item( *, id: int, report_id: int, process_name: str = "2序", ): return ProductionReportItem( id=id, report_id=report_id, attendance_point_name="嘉恒", device_no="A-01", project_no="P1", product_name="产品A", process_name=process_name, operator_count=1, process_unit_price_yuan=1, standard_beat=1, standard_workload=0, good_qty=650, defect_qty=0, scrap_qty=0, allocated_minutes=360, ) def _allocation( *, id: int, report_id: int, report_item_id: int, allocation_date: date, good_qty: float, ): return ProductionReportAllocation( id=id, report_id=report_id, report_item_id=report_item_id, attendance_point_name="嘉恒", employee_phone="13800000000", allocation_date=allocation_date, day_minutes=0, overtime_minutes=0, night_minutes=0, effective_minutes=0, good_qty=good_qty, defect_qty=0, scrap_qty=0, changeover_count=0, reference_wage=0, ) 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 def test_reported_good_quantities_groups_by_allocation_month_for_approved_unvoided_latest_process(): db = _sqlite_db() try: approved = _report(id=1) pending = _report(id=2, status=ReportStatus.pending) voided = _report(id=3, is_voided=True) latest_item = _item(id=1, report_id=approved.id, process_name="2序") earlier_item = _item(id=2, report_id=approved.id, process_name="1序") pending_item = _item(id=3, report_id=pending.id, process_name="2序") voided_item = _item(id=4, report_id=voided.id, process_name="2序") db.add_all([ approved, pending, voided, latest_item, earlier_item, pending_item, voided_item, _allocation( id=1, report_id=approved.id, report_item_id=latest_item.id, allocation_date=date(2026, 7, 31), good_qty=350, ), _allocation( id=2, report_id=approved.id, report_item_id=latest_item.id, allocation_date=date(2026, 8, 1), good_qty=300, ), _allocation( id=3, report_id=approved.id, report_item_id=earlier_item.id, allocation_date=date(2026, 7, 31), good_qty=999, ), _allocation( id=4, report_id=pending.id, report_item_id=pending_item.id, allocation_date=date(2026, 7, 31), good_qty=111, ), _allocation( id=5, report_id=voided.id, report_item_id=voided_item.id, allocation_date=date(2026, 8, 1), good_qty=222, ), ]) db.commit() totals = reconciliation._reported_good_quantities( db, year=2026, product_keys=[("嘉恒", "产品A")], latest_processes={("嘉恒", "产品A"): {"2序"}}, ) assert totals[("嘉恒", "产品A", 7)] == 350 assert totals[("嘉恒", "产品A", 8)] == 300 finally: db.close() def test_reported_good_quantities_ignores_mismatched_allocation_report_item_rows(): db = _sqlite_db() try: approved = _report(id=1) other_report = _report(id=2, status=ReportStatus.pending) other_item = _item(id=2, report_id=other_report.id, process_name="2序") db.add_all([ approved, other_report, other_item, _allocation( id=1, report_id=approved.id, report_item_id=other_item.id, allocation_date=date(2026, 7, 31), good_qty=123, ), ]) db.commit() totals = reconciliation._reported_good_quantities( db, year=2026, product_keys=[("嘉恒", "产品A")], latest_processes={("嘉恒", "产品A"): {"2序"}}, ) assert totals == {} finally: db.close()