feat: 输出报工归属摘要
This commit is contained in:
parent
ebb490590f
commit
b0cb217ce3
@ -74,7 +74,8 @@ def _cleaning_device_nos(item: CleaningReportSubmitItem) -> list[str]:
|
|||||||
|
|
||||||
def _report_query():
|
def _report_query():
|
||||||
return select(ProductionReport).options(
|
return select(ProductionReport).options(
|
||||||
selectinload(ProductionReport.items),
|
selectinload(ProductionReport.allocations),
|
||||||
|
selectinload(ProductionReport.items).selectinload(ProductionReportItem.allocations),
|
||||||
selectinload(ProductionReport.audit_logs),
|
selectinload(ProductionReport.audit_logs),
|
||||||
selectinload(ProductionReport.employee),
|
selectinload(ProductionReport.employee),
|
||||||
selectinload(ProductionReport.reviewer),
|
selectinload(ProductionReport.reviewer),
|
||||||
|
|||||||
@ -117,7 +117,8 @@ def _validate_normal_report_devices(db: Session, report: ProductionReport) -> No
|
|||||||
|
|
||||||
def _report_query():
|
def _report_query():
|
||||||
return select(ProductionReport).options(
|
return select(ProductionReport).options(
|
||||||
selectinload(ProductionReport.items),
|
selectinload(ProductionReport.allocations),
|
||||||
|
selectinload(ProductionReport.items).selectinload(ProductionReportItem.allocations),
|
||||||
selectinload(ProductionReport.audit_logs),
|
selectinload(ProductionReport.audit_logs),
|
||||||
selectinload(ProductionReport.employee),
|
selectinload(ProductionReport.employee),
|
||||||
selectinload(ProductionReport.reviewer),
|
selectinload(ProductionReport.reviewer),
|
||||||
|
|||||||
@ -453,6 +453,19 @@ class ReportMetrics(BaseModel):
|
|||||||
workload_rate: float
|
workload_rate: float
|
||||||
|
|
||||||
|
|
||||||
|
class ReportAllocationOut(BaseModel):
|
||||||
|
allocation_date: date
|
||||||
|
day_minutes: float = 0
|
||||||
|
overtime_minutes: float = 0
|
||||||
|
night_minutes: float = 0
|
||||||
|
effective_minutes: float = 0
|
||||||
|
good_qty: float = 0
|
||||||
|
defect_qty: float = 0
|
||||||
|
scrap_qty: float = 0
|
||||||
|
changeover_count: float = 0
|
||||||
|
reference_wage: float = 0
|
||||||
|
|
||||||
|
|
||||||
class ReportItemOut(BaseModel):
|
class ReportItemOut(BaseModel):
|
||||||
id: int | None = None
|
id: int | None = None
|
||||||
attendance_point_name: str = ""
|
attendance_point_name: str = ""
|
||||||
@ -478,6 +491,7 @@ 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
|
||||||
|
allocations: list[ReportAllocationOut] = Field(default_factory=list)
|
||||||
corrections: dict[str, Any] = Field(default_factory=dict)
|
corrections: dict[str, Any] = Field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
@ -531,6 +545,7 @@ class ReportOut(BaseModel):
|
|||||||
items: list[ReportItemOut]
|
items: list[ReportItemOut]
|
||||||
metrics: ReportMetrics
|
metrics: ReportMetrics
|
||||||
result_text: str
|
result_text: str
|
||||||
|
allocation_summary_text: str = ""
|
||||||
corrections: dict[str, Any] = Field(default_factory=dict)
|
corrections: dict[str, Any] = Field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
@ -587,6 +602,8 @@ class DashboardRow(BaseModel):
|
|||||||
id: str
|
id: str
|
||||||
attendance_point_name: str = ""
|
attendance_point_name: str = ""
|
||||||
report_date: date
|
report_date: date
|
||||||
|
source_report_date: date | None = None
|
||||||
|
source_report_dates: list[date] = Field(default_factory=list)
|
||||||
employee_phone: str
|
employee_phone: str
|
||||||
employee_name: str
|
employee_name: str
|
||||||
project_no: str
|
project_no: str
|
||||||
|
|||||||
@ -23,6 +23,7 @@ from app.schemas import (
|
|||||||
PersonnelOut,
|
PersonnelOut,
|
||||||
ProductOption,
|
ProductOption,
|
||||||
ProductOut,
|
ProductOut,
|
||||||
|
ReportAllocationOut,
|
||||||
ReportDeviceSegmentOut,
|
ReportDeviceSegmentOut,
|
||||||
ReportItemOut,
|
ReportItemOut,
|
||||||
ReportMetrics,
|
ReportMetrics,
|
||||||
@ -36,6 +37,7 @@ from app.services.metrics import build_result_text, calculate_report_metrics
|
|||||||
from app.services.misc_work import is_misc_item, is_misc_product
|
from app.services.misc_work import is_misc_item, is_misc_product
|
||||||
from app.services.multi_person import is_multi_person_item, is_multi_person_product
|
from app.services.multi_person import is_multi_person_item, is_multi_person_product
|
||||||
from app.services.process_names import export_process_name
|
from app.services.process_names import export_process_name
|
||||||
|
from app.services.report_allocations import allocation_summary_text
|
||||||
from app.services.report_lifecycle import can_edit_report, can_unvoid_report
|
from app.services.report_lifecycle import can_edit_report, can_unvoid_report
|
||||||
from app.services.work_schedule import WorkScheduleConfig
|
from app.services.work_schedule import WorkScheduleConfig
|
||||||
from app.timezone import now
|
from app.timezone import now
|
||||||
@ -341,6 +343,21 @@ def report_device_segment_out(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def report_allocation_out(row) -> ReportAllocationOut:
|
||||||
|
return ReportAllocationOut(
|
||||||
|
allocation_date=row.allocation_date,
|
||||||
|
day_minutes=as_float(row.day_minutes),
|
||||||
|
overtime_minutes=as_float(row.overtime_minutes),
|
||||||
|
night_minutes=as_float(row.night_minutes),
|
||||||
|
effective_minutes=as_float(row.effective_minutes),
|
||||||
|
good_qty=as_float(row.good_qty),
|
||||||
|
defect_qty=as_float(row.defect_qty),
|
||||||
|
scrap_qty=as_float(row.scrap_qty),
|
||||||
|
changeover_count=as_float(row.changeover_count),
|
||||||
|
reference_wage=as_float(row.reference_wage),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def report_item_out(item: ProductionReportItem, corrections: dict | None = None) -> ReportItemOut:
|
def report_item_out(item: ProductionReportItem, corrections: dict | None = None) -> ReportItemOut:
|
||||||
return ReportItemOut(
|
return ReportItemOut(
|
||||||
id=item.id,
|
id=item.id,
|
||||||
@ -367,6 +384,7 @@ 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,
|
||||||
|
allocations=[report_allocation_out(row) for row in getattr(item, "allocations", []) or []],
|
||||||
corrections=corrections or {},
|
corrections=corrections or {},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -486,5 +504,6 @@ def report_out(report: ProductionReport, schedule: WorkScheduleConfig | None = N
|
|||||||
if is_cleaning_report
|
if is_cleaning_report
|
||||||
else ("处理杂活已提交,等待管理员审核" if _report_is_misc_only(report) else build_result_text(metrics))
|
else ("处理杂活已提交,等待管理员审核" if _report_is_misc_only(report) else build_result_text(metrics))
|
||||||
),
|
),
|
||||||
|
allocation_summary_text=allocation_summary_text(list(getattr(report, "allocations", []) or [])),
|
||||||
corrections=report_corrections,
|
corrections=report_corrections,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
|
from decimal import Decimal
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
|
||||||
from sqlalchemy import BigInteger, create_engine, select
|
from sqlalchemy import BigInteger, create_engine, select
|
||||||
@ -9,6 +10,7 @@ from app.database import Base
|
|||||||
from app.models import (
|
from app.models import (
|
||||||
AttendancePoint,
|
AttendancePoint,
|
||||||
Equipment,
|
Equipment,
|
||||||
|
Personnel,
|
||||||
Product,
|
Product,
|
||||||
ProductionReport,
|
ProductionReport,
|
||||||
ProductionReportAllocation,
|
ProductionReportAllocation,
|
||||||
@ -38,6 +40,7 @@ from app.services.report_allocations import (
|
|||||||
build_report_allocation_drafts,
|
build_report_allocation_drafts,
|
||||||
refresh_report_allocations,
|
refresh_report_allocations,
|
||||||
)
|
)
|
||||||
|
from app.services.serializers import report_out
|
||||||
from app.services.work_schedule import WorkScheduleConfig
|
from app.services.work_schedule import WorkScheduleConfig
|
||||||
|
|
||||||
|
|
||||||
@ -453,6 +456,131 @@ def test_allocation_summary_text_groups_dates_and_shift_kinds():
|
|||||||
assert allocation_summary_text(rows) == "2026-07-24 夜班5.83小时;2026-07-25 白班3小时、加班2小时"
|
assert allocation_summary_text(rows) == "2026-07-24 夜班5.83小时;2026-07-25 白班3小时、加班2小时"
|
||||||
|
|
||||||
|
|
||||||
|
def test_report_out_exposes_allocation_summary_and_item_allocations():
|
||||||
|
report = ProductionReport(
|
||||||
|
id=1,
|
||||||
|
session_id=1,
|
||||||
|
attendance_point_name="总厂",
|
||||||
|
employee_phone="13800000000",
|
||||||
|
report_date=date(2026, 7, 25),
|
||||||
|
start_at=datetime(2026, 7, 25, 8, 0),
|
||||||
|
end_at=datetime(2026, 7, 25, 18, 30),
|
||||||
|
break_minutes=60,
|
||||||
|
status=ReportStatus.pending,
|
||||||
|
submitted_at=datetime(2026, 7, 25, 18, 31),
|
||||||
|
)
|
||||||
|
report.employee = Personnel(phone="13800000000", name="张三")
|
||||||
|
report.session = WorkSession(
|
||||||
|
id=1,
|
||||||
|
employee_phone="13800000000",
|
||||||
|
attendance_point_name="总厂",
|
||||||
|
start_at=datetime(2026, 7, 25, 8, 0),
|
||||||
|
status=SessionStatus.submitted,
|
||||||
|
)
|
||||||
|
report.session.devices = []
|
||||||
|
item = ProductionReportItem(
|
||||||
|
id=1,
|
||||||
|
report_id=1,
|
||||||
|
attendance_point_name="总厂",
|
||||||
|
device_no="23#",
|
||||||
|
project_no="P1",
|
||||||
|
product_name="23#",
|
||||||
|
process_name="冲压",
|
||||||
|
process_unit_price_yuan=Decimal("2.50"),
|
||||||
|
good_qty=Decimal("100.50"),
|
||||||
|
defect_qty=Decimal("2.25"),
|
||||||
|
scrap_qty=Decimal("1.25"),
|
||||||
|
changeover_count=Decimal("1.50"),
|
||||||
|
standard_beat=Decimal("3.00"),
|
||||||
|
standard_workload=Decimal("0.00"),
|
||||||
|
)
|
||||||
|
allocation = ProductionReportAllocation(
|
||||||
|
id=1,
|
||||||
|
report_id=1,
|
||||||
|
report_item_id=1,
|
||||||
|
attendance_point_name="总厂",
|
||||||
|
employee_phone="13800000000",
|
||||||
|
allocation_date=date(2026, 7, 25),
|
||||||
|
day_minutes=Decimal("120.50"),
|
||||||
|
overtime_minutes=Decimal("30.25"),
|
||||||
|
night_minutes=Decimal("0.00"),
|
||||||
|
effective_minutes=Decimal("150.75"),
|
||||||
|
good_qty=Decimal("100.50"),
|
||||||
|
defect_qty=Decimal("2.25"),
|
||||||
|
scrap_qty=Decimal("1.25"),
|
||||||
|
changeover_count=Decimal("1.50"),
|
||||||
|
reference_wage=Decimal("251.25"),
|
||||||
|
)
|
||||||
|
item.allocations = [allocation]
|
||||||
|
report.items = [item]
|
||||||
|
report.allocations = [allocation]
|
||||||
|
report.audit_logs = []
|
||||||
|
|
||||||
|
output = report_out(report)
|
||||||
|
|
||||||
|
assert output.allocation_summary_text == "2026-07-25 白班2.01小时、加班0.5小时"
|
||||||
|
assert len(output.items[0].allocations) == 1
|
||||||
|
item_allocation = output.items[0].allocations[0]
|
||||||
|
assert item_allocation.allocation_date == date(2026, 7, 25)
|
||||||
|
assert item_allocation.day_minutes == 120.5
|
||||||
|
assert item_allocation.overtime_minutes == 30.25
|
||||||
|
assert item_allocation.effective_minutes == 150.75
|
||||||
|
assert item_allocation.good_qty == 100.5
|
||||||
|
assert item_allocation.defect_qty == 2.25
|
||||||
|
assert item_allocation.scrap_qty == 1.25
|
||||||
|
assert item_allocation.changeover_count == 1.5
|
||||||
|
assert item_allocation.reference_wage == 251.25
|
||||||
|
|
||||||
|
|
||||||
|
def test_report_out_handles_empty_allocations():
|
||||||
|
report = ProductionReport(
|
||||||
|
id=1,
|
||||||
|
session_id=1,
|
||||||
|
attendance_point_name="总厂",
|
||||||
|
employee_phone="13800000000",
|
||||||
|
report_date=date(2026, 7, 25),
|
||||||
|
start_at=datetime(2026, 7, 25, 8, 0),
|
||||||
|
end_at=datetime(2026, 7, 25, 9, 0),
|
||||||
|
break_minutes=0,
|
||||||
|
status=ReportStatus.pending,
|
||||||
|
submitted_at=datetime(2026, 7, 25, 9, 1),
|
||||||
|
)
|
||||||
|
report.employee = Personnel(phone="13800000000", name="张三")
|
||||||
|
report.session = WorkSession(
|
||||||
|
id=1,
|
||||||
|
employee_phone="13800000000",
|
||||||
|
attendance_point_name="总厂",
|
||||||
|
start_at=datetime(2026, 7, 25, 8, 0),
|
||||||
|
status=SessionStatus.submitted,
|
||||||
|
)
|
||||||
|
report.session.devices = []
|
||||||
|
item = ProductionReportItem(
|
||||||
|
id=1,
|
||||||
|
report_id=1,
|
||||||
|
attendance_point_name="总厂",
|
||||||
|
device_no="23#",
|
||||||
|
project_no="P1",
|
||||||
|
product_name="23#",
|
||||||
|
process_name="冲压",
|
||||||
|
process_unit_price_yuan=0,
|
||||||
|
good_qty=0,
|
||||||
|
defect_qty=0,
|
||||||
|
scrap_qty=0,
|
||||||
|
changeover_count=0,
|
||||||
|
standard_beat=1,
|
||||||
|
standard_workload=0,
|
||||||
|
)
|
||||||
|
item.allocations = []
|
||||||
|
report.items = [item]
|
||||||
|
report.allocations = []
|
||||||
|
report.audit_logs = []
|
||||||
|
|
||||||
|
output = report_out(report)
|
||||||
|
|
||||||
|
assert output.allocation_summary_text == ""
|
||||||
|
assert output.items[0].allocations == []
|
||||||
|
|
||||||
|
|
||||||
def test_refresh_report_allocations_replaces_existing_rows_and_flushes():
|
def test_refresh_report_allocations_replaces_existing_rows_and_flushes():
|
||||||
engine = create_engine("sqlite+pysqlite:///:memory:", future=True)
|
engine = create_engine("sqlite+pysqlite:///:memory:", future=True)
|
||||||
Base.metadata.create_all(engine)
|
Base.metadata.create_all(engine)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user