feat: 输出报工超额快照字段
This commit is contained in:
parent
b203b782de
commit
58d8272448
@ -491,6 +491,18 @@ class ReportItemOut(BaseModel):
|
|||||||
is_continuous_die: bool = False
|
is_continuous_die: bool = False
|
||||||
is_multi_person: bool = False
|
is_multi_person: bool = False
|
||||||
remark: str | None = None
|
remark: str | None = None
|
||||||
|
over_limit_status: str = "not_checked"
|
||||||
|
over_limit_type: str | None = None
|
||||||
|
over_limit_reason: str | None = None
|
||||||
|
over_limit_checked_at: datetime | None = None
|
||||||
|
over_limit_check_source: str | None = None
|
||||||
|
over_limit_batch_no: str | None = None
|
||||||
|
over_limit_product_gross_weight_kg: float | None = None
|
||||||
|
over_limit_issued_weight_kg: float | None = None
|
||||||
|
over_limit_max_reportable_qty: float | None = None
|
||||||
|
over_limit_previous_good_qty: float | None = None
|
||||||
|
over_limit_current_cumulative_qty: float | None = None
|
||||||
|
over_limit_current_report_qty: float | None = None
|
||||||
allocations: list[ReportAllocationOut] = Field(default_factory=list)
|
allocations: list[ReportAllocationOut] = Field(default_factory=list)
|
||||||
corrections: dict[str, Any] = Field(default_factory=dict)
|
corrections: dict[str, Any] = Field(default_factory=dict)
|
||||||
|
|
||||||
@ -530,6 +542,8 @@ class ReportOut(BaseModel):
|
|||||||
submitted_at: datetime
|
submitted_at: datetime
|
||||||
is_system_auto_submitted: bool = False
|
is_system_auto_submitted: bool = False
|
||||||
auto_submit_reason: str | None = None
|
auto_submit_reason: str | None = None
|
||||||
|
has_over_limit: bool = False
|
||||||
|
over_limit_summary: str | None = None
|
||||||
is_multi_person_assistant: bool = False
|
is_multi_person_assistant: bool = False
|
||||||
multi_person_source_report_id: int | None = None
|
multi_person_source_report_id: int | None = None
|
||||||
is_voided: bool = False
|
is_voided: bool = False
|
||||||
|
|||||||
@ -52,6 +52,10 @@ def temporary_expired(person: Personnel) -> bool:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _optional_float(value) -> float | None:
|
||||||
|
return None if value is None else as_float(value)
|
||||||
|
|
||||||
|
|
||||||
def attendance_point_out(point: AttendancePoint) -> AttendancePointOut:
|
def attendance_point_out(point: AttendancePoint) -> AttendancePointOut:
|
||||||
return AttendancePointOut(
|
return AttendancePointOut(
|
||||||
name=point.name,
|
name=point.name,
|
||||||
@ -123,7 +127,6 @@ def personnel_summary_out(person: Personnel) -> dict:
|
|||||||
|
|
||||||
|
|
||||||
def product_out(product: Product) -> ProductOut:
|
def product_out(product: Product) -> ProductOut:
|
||||||
optional_float = lambda value: None if value is None else as_float(value)
|
|
||||||
return ProductOut(
|
return ProductOut(
|
||||||
attendance_point_name=product.attendance_point_name,
|
attendance_point_name=product.attendance_point_name,
|
||||||
project_no=product.project_no,
|
project_no=product.project_no,
|
||||||
@ -132,10 +135,10 @@ def product_out(product: Product) -> ProductOut:
|
|||||||
material_code=product.material_code,
|
material_code=product.material_code,
|
||||||
material_name=product.material_name,
|
material_name=product.material_name,
|
||||||
supplier=product.supplier,
|
supplier=product.supplier,
|
||||||
product_net_weight_kg=optional_float(product.product_net_weight_kg),
|
product_net_weight_kg=_optional_float(product.product_net_weight_kg),
|
||||||
product_gross_weight_kg=optional_float(product.product_gross_weight_kg),
|
product_gross_weight_kg=_optional_float(product.product_gross_weight_kg),
|
||||||
scrap_loss_rate=optional_float(product.scrap_loss_rate),
|
scrap_loss_rate=_optional_float(product.scrap_loss_rate),
|
||||||
waste_price_yuan_per_kg=optional_float(product.waste_price_yuan_per_kg),
|
waste_price_yuan_per_kg=_optional_float(product.waste_price_yuan_per_kg),
|
||||||
device_no=product.device_no,
|
device_no=product.device_no,
|
||||||
process_name=product.process_name,
|
process_name=product.process_name,
|
||||||
stamping_method=product.stamping_method,
|
stamping_method=product.stamping_method,
|
||||||
@ -410,6 +413,18 @@ def report_item_out(item: ProductionReportItem, corrections: dict | None = None)
|
|||||||
is_continuous_die=is_continuous_die_item(item),
|
is_continuous_die=is_continuous_die_item(item),
|
||||||
is_multi_person=is_multi_person_item(item),
|
is_multi_person=is_multi_person_item(item),
|
||||||
remark=item.remark,
|
remark=item.remark,
|
||||||
|
over_limit_status=item.over_limit_status or "not_checked",
|
||||||
|
over_limit_type=item.over_limit_type,
|
||||||
|
over_limit_reason=item.over_limit_reason,
|
||||||
|
over_limit_checked_at=item.over_limit_checked_at,
|
||||||
|
over_limit_check_source=item.over_limit_check_source,
|
||||||
|
over_limit_batch_no=item.over_limit_batch_no,
|
||||||
|
over_limit_product_gross_weight_kg=_optional_float(item.over_limit_product_gross_weight_kg),
|
||||||
|
over_limit_issued_weight_kg=_optional_float(item.over_limit_issued_weight_kg),
|
||||||
|
over_limit_max_reportable_qty=_optional_float(item.over_limit_max_reportable_qty),
|
||||||
|
over_limit_previous_good_qty=_optional_float(item.over_limit_previous_good_qty),
|
||||||
|
over_limit_current_cumulative_qty=_optional_float(item.over_limit_current_cumulative_qty),
|
||||||
|
over_limit_current_report_qty=_optional_float(item.over_limit_current_report_qty),
|
||||||
allocations=[report_allocation_out(row) for row in allocation_rows],
|
allocations=[report_allocation_out(row) for row in allocation_rows],
|
||||||
corrections=corrections or {},
|
corrections=corrections or {},
|
||||||
)
|
)
|
||||||
@ -505,6 +520,8 @@ def report_out(report: ProductionReport, schedule: WorkScheduleConfig | None = N
|
|||||||
submitted_at=report.submitted_at,
|
submitted_at=report.submitted_at,
|
||||||
is_system_auto_submitted=bool(report.is_system_auto_submitted),
|
is_system_auto_submitted=bool(report.is_system_auto_submitted),
|
||||||
auto_submit_reason=report.auto_submit_reason,
|
auto_submit_reason=report.auto_submit_reason,
|
||||||
|
has_over_limit=bool(report.has_over_limit),
|
||||||
|
over_limit_summary=report.over_limit_summary,
|
||||||
is_multi_person_assistant=bool(report.is_multi_person_assistant),
|
is_multi_person_assistant=bool(report.is_multi_person_assistant),
|
||||||
multi_person_source_report_id=report.multi_person_source_report_id,
|
multi_person_source_report_id=report.multi_person_source_report_id,
|
||||||
is_voided=bool(report.is_voided),
|
is_voided=bool(report.is_voided),
|
||||||
|
|||||||
@ -26,6 +26,7 @@ from app.services.report_over_limit import (
|
|||||||
parse_process_order,
|
parse_process_order,
|
||||||
refresh_report_over_limit_snapshot,
|
refresh_report_over_limit_snapshot,
|
||||||
)
|
)
|
||||||
|
from app.services.serializers import report_out
|
||||||
from app.timezone import now
|
from app.timezone import now
|
||||||
|
|
||||||
|
|
||||||
@ -810,3 +811,56 @@ def test_approve_report_refreshes_over_limit_snapshot_after_correction(monkeypat
|
|||||||
assert saved.items[0].over_limit_current_report_qty == Decimal("60.00")
|
assert saved.items[0].over_limit_current_report_qty == Decimal("60.00")
|
||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
def test_report_out_exposes_over_limit_snapshot_fields():
|
||||||
|
report = ProductionReport(
|
||||||
|
id=1,
|
||||||
|
session_id=1,
|
||||||
|
attendance_point_name="总厂",
|
||||||
|
employee_phone="13800000000",
|
||||||
|
report_date=datetime(2026, 7, 25).date(),
|
||||||
|
start_at=datetime(2026, 7, 25, 8),
|
||||||
|
end_at=datetime(2026, 7, 25, 10),
|
||||||
|
status=ReportStatus.pending,
|
||||||
|
submitted_at=datetime(2026, 7, 25, 10),
|
||||||
|
has_over_limit=True,
|
||||||
|
over_limit_summary="1项超额",
|
||||||
|
)
|
||||||
|
item = ProductionReportItem(
|
||||||
|
id=1,
|
||||||
|
report=report,
|
||||||
|
attendance_point_name="总厂",
|
||||||
|
device_no="A",
|
||||||
|
project_no="P1",
|
||||||
|
product_name="产品A",
|
||||||
|
raw_material_batch_no="LOT-1",
|
||||||
|
process_name="1",
|
||||||
|
good_qty=60,
|
||||||
|
defect_qty=0,
|
||||||
|
scrap_qty=0,
|
||||||
|
standard_beat=1,
|
||||||
|
over_limit_status=OVER_LIMIT_STATUS_EXCEEDED,
|
||||||
|
over_limit_type="first_process_material",
|
||||||
|
over_limit_reason="1序累计报工数量超过本批次材料可支撑数量",
|
||||||
|
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_previous_good_qty=None,
|
||||||
|
over_limit_current_cumulative_qty=Decimal("60.00"),
|
||||||
|
over_limit_current_report_qty=Decimal("60.00"),
|
||||||
|
)
|
||||||
|
report.items = [item]
|
||||||
|
report.allocations = []
|
||||||
|
report.audit_logs = []
|
||||||
|
|
||||||
|
output = report_out(report)
|
||||||
|
|
||||||
|
assert output.has_over_limit is True
|
||||||
|
assert output.over_limit_summary == "1项超额"
|
||||||
|
assert output.items[0].over_limit_status == OVER_LIMIT_STATUS_EXCEEDED
|
||||||
|
assert output.items[0].over_limit_reason == "1序累计报工数量超过本批次材料可支撑数量"
|
||||||
|
assert output.items[0].over_limit_max_reportable_qty == 50
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user