fix: 加强对账归属查询一致性

This commit is contained in:
souplearn 2026-07-25 05:16:59 +08:00
parent 2de4b9009a
commit 9674554b98
2 changed files with 216 additions and 2 deletions

View File

@ -1,7 +1,7 @@
from datetime import date from datetime import date
from fastapi import APIRouter, Depends, HTTPException, Query from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import extract, func, select from sqlalchemy import and_, extract, func, select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.database import get_db from app.database import get_db
@ -101,7 +101,13 @@ def _reported_good_quantities(
func.sum(ProductionReportAllocation.good_qty).label("good_qty"), func.sum(ProductionReportAllocation.good_qty).label("good_qty"),
) )
.select_from(ProductionReportAllocation) .select_from(ProductionReportAllocation)
.join(ProductionReportItem, ProductionReportItem.id == ProductionReportAllocation.report_item_id) .join(
ProductionReportItem,
and_(
ProductionReportItem.id == ProductionReportAllocation.report_item_id,
ProductionReportItem.report_id == ProductionReportAllocation.report_id,
),
)
.join(ProductionReport, ProductionReport.id == ProductionReportAllocation.report_id) .join(ProductionReport, ProductionReport.id == ProductionReportAllocation.report_id)
.where( .where(
ProductionReport.status == ReportStatus.approved, ProductionReport.status == ReportStatus.approved,

View File

@ -1,6 +1,113 @@
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 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(): def test_fold_reported_good_quantity_rows_uses_allocation_month_for_latest_process_only():
rows = [ rows = [
("嘉恒", "产品A", "1序", 7, 999), ("嘉恒", "产品A", "1序", 7, 999),
@ -13,3 +120,104 @@ def test_fold_reported_good_quantity_rows_uses_allocation_month_for_latest_proce
assert totals[("嘉恒", "产品A", 7)] == 350 assert totals[("嘉恒", "产品A", 7)] == 350
assert totals[("嘉恒", "产品A", 8)] == 300 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()