fix: 审核未修正时保留超额提交快照
This commit is contained in:
parent
7cdb528b1c
commit
00dd4f93b5
@ -116,6 +116,14 @@ def _validate_normal_report_devices(db: Session, report: ProductionReport) -> No
|
|||||||
raise HTTPException(status_code=400, detail=f"设备 {device_no} 不是该考勤点的冲压设备")
|
raise HTTPException(status_code=400, detail=f"设备 {device_no} 不是该考勤点的冲压设备")
|
||||||
|
|
||||||
|
|
||||||
|
def _missing_over_limit_snapshot(report: ProductionReport) -> bool:
|
||||||
|
return any(
|
||||||
|
item.over_limit_checked_at is None and not item.over_limit_check_source
|
||||||
|
for item in report.items
|
||||||
|
if not is_cleaning_item(item)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _report_query():
|
def _report_query():
|
||||||
return select(ProductionReport).options(
|
return select(ProductionReport).options(
|
||||||
selectinload(ProductionReport.allocations),
|
selectinload(ProductionReport.allocations),
|
||||||
@ -398,6 +406,7 @@ def approve_report(
|
|||||||
schedule = get_work_schedule_config(db, report.attendance_point_name)
|
schedule = get_work_schedule_config(db, report.attendance_point_name)
|
||||||
item_map = {item.id: item for item in report.items}
|
item_map = {item.id: item for item in report.items}
|
||||||
has_correction = False
|
has_correction = False
|
||||||
|
over_limit_input_changed = False
|
||||||
|
|
||||||
next_start_at = _effective_review_time(payload.start_at, report.start_at)
|
next_start_at = _effective_review_time(payload.start_at, report.start_at)
|
||||||
next_end_at = _effective_review_time(payload.end_at, report.end_at)
|
next_end_at = _effective_review_time(payload.end_at, report.end_at)
|
||||||
@ -443,11 +452,14 @@ def approve_report(
|
|||||||
if correction.raw_material_batch_no is not None
|
if correction.raw_material_batch_no is not None
|
||||||
else item.raw_material_batch_no
|
else item.raw_material_batch_no
|
||||||
)
|
)
|
||||||
if (
|
product_identity_changed = (
|
||||||
next_device_no != item.device_no
|
next_project_no != item.project_no
|
||||||
or next_project_no != item.project_no
|
|
||||||
or next_product_name != item.product_name
|
or next_product_name != item.product_name
|
||||||
or next_process_name != item.process_name
|
or next_process_name != item.process_name
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
next_device_no != item.device_no
|
||||||
|
or product_identity_changed
|
||||||
):
|
):
|
||||||
product = db.scalar(
|
product = db.scalar(
|
||||||
select(Product).where(
|
select(Product).where(
|
||||||
@ -481,10 +493,13 @@ def approve_report(
|
|||||||
item.standard_beat = as_float(product.standard_beat)
|
item.standard_beat = as_float(product.standard_beat)
|
||||||
item.standard_workload = 0
|
item.standard_workload = 0
|
||||||
has_correction = True
|
has_correction = True
|
||||||
|
if product_identity_changed:
|
||||||
|
over_limit_input_changed = True
|
||||||
|
|
||||||
if next_raw_material_batch_no != item.raw_material_batch_no:
|
if next_raw_material_batch_no != item.raw_material_batch_no:
|
||||||
item.raw_material_batch_no = next_raw_material_batch_no
|
item.raw_material_batch_no = next_raw_material_batch_no
|
||||||
has_correction = True
|
has_correction = True
|
||||||
|
over_limit_input_changed = True
|
||||||
|
|
||||||
if correction.changeover_count is not None:
|
if correction.changeover_count is not None:
|
||||||
next_changeover_count = as_float(correction.changeover_count)
|
next_changeover_count = as_float(correction.changeover_count)
|
||||||
@ -499,12 +514,15 @@ def approve_report(
|
|||||||
if correction.good_qty is not None and as_float(correction.good_qty) != as_float(item.good_qty):
|
if correction.good_qty is not None and as_float(correction.good_qty) != as_float(item.good_qty):
|
||||||
item.good_qty = correction.good_qty
|
item.good_qty = correction.good_qty
|
||||||
has_correction = True
|
has_correction = True
|
||||||
|
over_limit_input_changed = True
|
||||||
if correction.defect_qty is not None and as_float(correction.defect_qty) != as_float(item.defect_qty):
|
if correction.defect_qty is not None and as_float(correction.defect_qty) != as_float(item.defect_qty):
|
||||||
item.defect_qty = correction.defect_qty
|
item.defect_qty = correction.defect_qty
|
||||||
has_correction = True
|
has_correction = True
|
||||||
|
over_limit_input_changed = True
|
||||||
if correction.scrap_qty is not None and as_float(correction.scrap_qty) != as_float(item.scrap_qty):
|
if correction.scrap_qty is not None and as_float(correction.scrap_qty) != as_float(item.scrap_qty):
|
||||||
item.scrap_qty = correction.scrap_qty
|
item.scrap_qty = correction.scrap_qty
|
||||||
has_correction = True
|
has_correction = True
|
||||||
|
over_limit_input_changed = True
|
||||||
if item_is_misc and correction.remark is not None:
|
if item_is_misc and correction.remark is not None:
|
||||||
next_remark = str(correction.remark or "").strip() or None
|
next_remark = str(correction.remark or "").strip() or None
|
||||||
if next_remark != item.remark:
|
if next_remark != item.remark:
|
||||||
@ -533,7 +551,8 @@ def approve_report(
|
|||||||
_apply_metrics(report, schedule)
|
_apply_metrics(report, schedule)
|
||||||
refresh_report_allocations(db, report, schedule=schedule, commit=False)
|
refresh_report_allocations(db, report, schedule=schedule, commit=False)
|
||||||
reviewed_at = now()
|
reviewed_at = now()
|
||||||
refresh_report_over_limit_snapshot(db, report, source=CHECK_SOURCE_REVIEW, checked_at=reviewed_at)
|
if not is_cleaning and (over_limit_input_changed or _missing_over_limit_snapshot(report)):
|
||||||
|
refresh_report_over_limit_snapshot(db, report, source=CHECK_SOURCE_REVIEW, checked_at=reviewed_at)
|
||||||
was_pending = report.status == ReportStatus.pending
|
was_pending = report.status == ReportStatus.pending
|
||||||
report.status = ReportStatus.approved
|
report.status = ReportStatus.approved
|
||||||
report.reviewer_phone = user.phone
|
report.reviewer_phone = user.phone
|
||||||
|
|||||||
@ -813,6 +813,85 @@ def test_approve_report_refreshes_over_limit_snapshot_after_correction(monkeypat
|
|||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
def test_approve_report_without_over_limit_input_changes_preserves_submit_snapshot(monkeypatch):
|
||||||
|
db = _sqlite_db()
|
||||||
|
try:
|
||||||
|
user = SimpleNamespace(phone="13900000000", role=Role.admin)
|
||||||
|
session = _numeric_session(status=SessionStatus.submitted)
|
||||||
|
report = ProductionReport(
|
||||||
|
attendance_point_name="总厂",
|
||||||
|
session=session,
|
||||||
|
employee_phone="13800000000",
|
||||||
|
report_date=date(2026, 7, 25),
|
||||||
|
start_at=session.start_at,
|
||||||
|
end_at=session.end_at,
|
||||||
|
duration_minutes=120,
|
||||||
|
effective_minutes=120,
|
||||||
|
total_good_qty=60,
|
||||||
|
total_output_qty=60,
|
||||||
|
actual_beat=120,
|
||||||
|
standard_beat=1,
|
||||||
|
expected_workload=0,
|
||||||
|
pace_rate=0,
|
||||||
|
workload_rate=0,
|
||||||
|
status=ReportStatus.pending,
|
||||||
|
submitted_at=session.end_at,
|
||||||
|
has_over_limit=True,
|
||||||
|
over_limit_summary="1项超额",
|
||||||
|
items=[
|
||||||
|
ProductionReportItem(
|
||||||
|
attendance_point_name="总厂",
|
||||||
|
device_no="E1",
|
||||||
|
project_no="P1",
|
||||||
|
product_name="产品A",
|
||||||
|
raw_material_batch_no="LOT-1",
|
||||||
|
process_name="1",
|
||||||
|
standard_beat=1,
|
||||||
|
standard_workload=0,
|
||||||
|
good_qty=60,
|
||||||
|
defect_qty=0,
|
||||||
|
scrap_qty=0,
|
||||||
|
over_limit_status=OVER_LIMIT_STATUS_EXCEEDED,
|
||||||
|
over_limit_type="first_process_material",
|
||||||
|
over_limit_reason="提交时已超额",
|
||||||
|
over_limit_checked_at=datetime(2026, 7, 25, 10),
|
||||||
|
over_limit_check_source="submit",
|
||||||
|
over_limit_batch_no="LOT-1",
|
||||||
|
over_limit_product_gross_weight_kg=Decimal("2.0000"),
|
||||||
|
over_limit_issued_weight_kg=Decimal("100.000000"),
|
||||||
|
over_limit_max_reportable_qty=Decimal("50.00"),
|
||||||
|
over_limit_current_cumulative_qty=Decimal("60.00"),
|
||||||
|
over_limit_current_report_qty=Decimal("60.00"),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
db.add_all([_press_equipment(), _numeric_product(), report])
|
||||||
|
db.commit()
|
||||||
|
monkeypatch.setattr(reviews_router, "accessible_point_names", lambda _db, _user: ["总厂"])
|
||||||
|
monkeypatch.setattr(reviews_router, "report_out", lambda report, schedule=None: report)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"app.services.report_over_limit.read_batch_issued_weight_kg",
|
||||||
|
lambda db, batch_no, project_no, product_name: Decimal("200"),
|
||||||
|
)
|
||||||
|
|
||||||
|
saved = reviews_router.approve_report(
|
||||||
|
report.id,
|
||||||
|
ApproveReportRequest(),
|
||||||
|
user=user,
|
||||||
|
db=db,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert saved.has_over_limit is True
|
||||||
|
assert saved.over_limit_summary == "1项超额"
|
||||||
|
assert saved.items[0].over_limit_status == OVER_LIMIT_STATUS_EXCEEDED
|
||||||
|
assert saved.items[0].over_limit_check_source == "submit"
|
||||||
|
assert saved.items[0].over_limit_reason == "提交时已超额"
|
||||||
|
assert saved.items[0].over_limit_issued_weight_kg == Decimal("100.000000")
|
||||||
|
assert saved.items[0].over_limit_max_reportable_qty == Decimal("50.00")
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
def test_report_out_exposes_over_limit_snapshot_fields():
|
def test_report_out_exposes_over_limit_snapshot_fields():
|
||||||
report = ProductionReport(
|
report = ProductionReport(
|
||||||
id=1,
|
id=1,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user