提交
This commit is contained in:
1 parent
4611dbc2b2
commit
ebf006eeff
46 files changed
+36802
-20974
No files matched your search
+61
-108
@@ -63,10 +63,15 @@ from app.schemas.pl.plan import (
|
||||
PlanRead,
|
||||
PlanSideContractsUpdateBody,
|
||||
PlanSiteFeedbackUpdateBody,
|
||||
PlanStopContractsUpdateBody,
|
||||
PlanUpdate,
|
||||
PlanWithProducts,
|
||||
)
|
||||
from app.services.pl_plan_contract_gap import list_plan_contract_gaps, update_plan_side_contracts
|
||||
from app.services.pl_plan_contract_gap import (
|
||||
list_plan_contract_gaps,
|
||||
update_plan_side_contracts,
|
||||
update_plan_stop_contracts,
|
||||
)
|
||||
from app.services.pl_brand_archive import load_brand_qtys_by_plan_product_ids
|
||||
from app.services.pl_product_display import (
|
||||
load_crm_product_alias_maps,
|
||||
@@ -93,6 +98,7 @@ from app.services.pl_plan_archive_stop_service import (
|
||||
sum_delivery_outbound_qty,
|
||||
sync_plan_products_from_stop_archive,
|
||||
)
|
||||
from app.services.pl_plan_archive_export import build_plan_archive_export_rows
|
||||
from app.services.pl_plan_list_attachments import load_plan_document_attachments_by_plan_ids
|
||||
from app.services.pl_plan_print_service import get_delivery_slip_print_context
|
||||
from app.services.pl_plan_stop_service import (
|
||||
@@ -345,6 +351,11 @@ def list_plans(
|
||||
alias="certUploadStatus",
|
||||
description="incomplete=至少一张必需危化证书未上传",
|
||||
),
|
||||
hazard_cert_ready: Optional[int] = Query(
|
||||
default=None,
|
||||
alias="hazardCertReady",
|
||||
description="0=未确认齐备 1=已确认齐备",
|
||||
),
|
||||
page: int = Query(default=1, ge=1),
|
||||
page_size: int = Query(default=20, ge=1, le=500, alias="pageSize"),
|
||||
db: Session = Depends(get_db),
|
||||
@@ -411,6 +422,9 @@ def list_plans(
|
||||
if (cert_upload_status or "").strip().lower() == "incomplete":
|
||||
query = query.filter(plan_has_incomplete_hazard_cert_exists(tenant_no))
|
||||
|
||||
if hazard_cert_ready is not None:
|
||||
query = query.filter(PlPlan.hazard_cert_ready == int(hazard_cert_ready))
|
||||
|
||||
query = apply_pl_scope(query, PlPlan, user, db, tenant_no)
|
||||
|
||||
# 统计总数
|
||||
@@ -537,37 +551,33 @@ PLAN_EXPORT_COLUMNS = [
|
||||
|
||||
PLAN_ARCHIVE_EXPORT_COLUMNS = [
|
||||
ExportColumn(title="序号", field="rowNo"),
|
||||
ExportColumn(title="到货日期", field="deliveryDate"),
|
||||
ExportColumn(title="供方名称", field="supplierName"),
|
||||
ExportColumn(title="需方名称", field="customerName"),
|
||||
ExportColumn(title="计划编号", field="planNo"),
|
||||
ExportColumn(title="计划日期", field="planDate"),
|
||||
ExportColumn(title="归档状态", field="archiveStatus"),
|
||||
ExportColumn(title="跟进状态", field="followStatus"),
|
||||
ExportColumn(title="单据类型", field="planType"),
|
||||
ExportColumn(title="段次", field="stopKind"),
|
||||
ExportColumn(title="客户", field="stopCustomer"),
|
||||
ExportColumn(title="地址", field="stopAddress"),
|
||||
ExportColumn(title="段次时间", field="stopTime"),
|
||||
ExportColumn(title="产品名称", field="productName"),
|
||||
ExportColumn(title="型号/规格", field="model"),
|
||||
ExportColumn(title="包装", field="packageSpec"),
|
||||
ExportColumn(title="单位", field="unit"),
|
||||
ExportColumn(title="入库数", field="inboundQty"),
|
||||
ExportColumn(title="入库价", field="inboundPrice"),
|
||||
ExportColumn(title="入库金额", field="inboundAmount"),
|
||||
ExportColumn(title="出库数", field="outboundQty"),
|
||||
ExportColumn(title="出库价", field="outboundPrice"),
|
||||
ExportColumn(title="出库金额", field="outboundAmount"),
|
||||
ExportColumn(title="计划数量", field="planQty"),
|
||||
ExportColumn(title="实际数量", field="actualQty"),
|
||||
ExportColumn(title="入库数量", field="inboundQty"),
|
||||
ExportColumn(title="出库数量", field="outboundQty"),
|
||||
ExportColumn(title="单价(元)", field="unitPrice"),
|
||||
ExportColumn(title="金额(元)", field="amount"),
|
||||
ExportColumn(title="车牌号", field="licensePlate"),
|
||||
ExportColumn(title="运费单位", field="freightType"),
|
||||
ExportColumn(title="运费单价", field="freightPrice"),
|
||||
ExportColumn(title="运费金额", field="freightAmount"),
|
||||
ExportColumn(title="ERP", field="erpEntered"),
|
||||
ExportColumn(title="备注", field="remark"),
|
||||
]
|
||||
|
||||
PLAN_ARCHIVE_FREIGHT_TYPE_LABELS = {
|
||||
"by_weight": "按重量",
|
||||
"by_trip": "按趟",
|
||||
}
|
||||
|
||||
|
||||
def _format_plan_delivery_date_for_export(plan: PlPlan) -> str:
|
||||
if plan.delivery_time is None:
|
||||
return ""
|
||||
return str(plan.delivery_time)[:10]
|
||||
|
||||
|
||||
def _load_transports_by_id(db: Session, tenant_no: str, transport_ids: list[str]) -> dict[str, PlTransport]:
|
||||
if not transport_ids:
|
||||
@@ -584,89 +594,6 @@ def _load_transports_by_id(db: Session, tenant_no: str, transport_ids: list[str]
|
||||
return {row.id: row for row in rows}
|
||||
|
||||
|
||||
def _load_products_by_plan_id(db: Session, tenant_no: str, plan_ids: list[str]) -> dict[str, list[PlPlanProduct]]:
|
||||
if not plan_ids:
|
||||
return {}
|
||||
products = (
|
||||
db.query(PlPlanProduct)
|
||||
.filter(
|
||||
PlPlanProduct.tenant_no == tenant_no,
|
||||
PlPlanProduct.del_flag == 0,
|
||||
PlPlanProduct.plan_id.in_(plan_ids),
|
||||
)
|
||||
.order_by(PlPlanProduct.order_num)
|
||||
.all()
|
||||
)
|
||||
grouped: dict[str, list[PlPlanProduct]] = {}
|
||||
for product in products:
|
||||
grouped.setdefault(product.plan_id, []).append(product)
|
||||
return grouped
|
||||
|
||||
|
||||
def _build_plan_archive_export_rows(
|
||||
plans: list[PlPlan],
|
||||
products_by_plan: dict[str, list[PlPlanProduct]],
|
||||
transports_by_id: dict[str, PlTransport],
|
||||
) -> list[dict]:
|
||||
rows: list[dict] = []
|
||||
row_no = 0
|
||||
for plan in plans:
|
||||
products = products_by_plan.get(plan.id, [])
|
||||
transport = transports_by_id.get(plan.transport_id) if plan.transport_id else None
|
||||
freight_type_raw = transport.freight_type if transport else "by_weight"
|
||||
freight_type_label = PLAN_ARCHIVE_FREIGHT_TYPE_LABELS.get(
|
||||
freight_type_raw, freight_type_raw or ""
|
||||
)
|
||||
freight_price = transport.freight_price if transport else None
|
||||
freight_amount = plan.freight_amount
|
||||
if freight_amount is None and transport is not None:
|
||||
freight_amount = transport.freight_amount
|
||||
plan_common = {
|
||||
"deliveryDate": _format_plan_delivery_date_for_export(plan),
|
||||
"supplierName": plan.supplier_name or "",
|
||||
"customerName": plan.customer_name or "",
|
||||
"licensePlate": plan.license_plate or "",
|
||||
"freightType": freight_type_label,
|
||||
"freightPrice": freight_price,
|
||||
"freightAmount": freight_amount,
|
||||
"remark": "",
|
||||
}
|
||||
product_rows = products or [None]
|
||||
for product in product_rows:
|
||||
row_no += 1
|
||||
row = {
|
||||
**plan_common,
|
||||
"rowNo": row_no,
|
||||
"productName": "",
|
||||
"model": "",
|
||||
"packageSpec": "",
|
||||
"unit": "",
|
||||
"inboundQty": None,
|
||||
"inboundPrice": None,
|
||||
"inboundAmount": None,
|
||||
"outboundQty": None,
|
||||
"outboundPrice": None,
|
||||
"outboundAmount": None,
|
||||
}
|
||||
if product is not None:
|
||||
row.update(
|
||||
{
|
||||
"productName": product.product_name or "",
|
||||
"model": product.model or "",
|
||||
"packageSpec": product.package_spec or "",
|
||||
"unit": product.unit or "",
|
||||
"inboundQty": product.inbound_qty,
|
||||
"inboundPrice": product.inbound_price,
|
||||
"inboundAmount": product.inbound_amount,
|
||||
"outboundQty": product.outbound_qty,
|
||||
"outboundPrice": product.outbound_price,
|
||||
"outboundAmount": product.outbound_amount,
|
||||
}
|
||||
)
|
||||
rows.append(row)
|
||||
return rows
|
||||
|
||||
|
||||
class PlanExportBody(BaseModel):
|
||||
q: Optional[str] = None
|
||||
plan_no: Optional[str] = Field(default=None, alias="planNo")
|
||||
@@ -681,6 +608,7 @@ class PlanExportBody(BaseModel):
|
||||
plan_date_to: Optional[date] = Field(default=None, alias="planDateTo")
|
||||
has_hazard_product: Optional[int] = Field(default=None, alias="hasHazardProduct")
|
||||
cert_upload_status: Optional[str] = Field(default=None, alias="certUploadStatus")
|
||||
hazard_cert_ready: Optional[int] = Field(default=None, alias="hazardCertReady")
|
||||
ids: Optional[List[str]] = None
|
||||
columns: Optional[List[str]] = None
|
||||
export_format: Optional[str] = Field(default=None, alias="exportFormat", description="archive=运输计划归档专用导出")
|
||||
@@ -743,6 +671,8 @@ def export_plans(
|
||||
query = query.filter(plan_has_hazard_product_exists(tenant_no))
|
||||
if (body.cert_upload_status or "").strip().lower() == "incomplete":
|
||||
query = query.filter(plan_has_incomplete_hazard_cert_exists(tenant_no))
|
||||
if body.hazard_cert_ready is not None:
|
||||
query = query.filter(PlPlan.hazard_cert_ready == int(body.hazard_cert_ready))
|
||||
if body.ids:
|
||||
query = query.filter(PlPlan.id.in_(body.ids))
|
||||
query = apply_pl_scope(query, PlPlan, user, db, tenant_no)
|
||||
@@ -750,12 +680,10 @@ def export_plans(
|
||||
date_str = datetime.now().strftime("%Y%m%d")
|
||||
is_archive_export = (body.export_format or "").strip().lower() == "archive"
|
||||
if is_archive_export:
|
||||
plan_ids = [plan.id for plan in plans]
|
||||
products_by_plan = _load_products_by_plan_id(db, tenant_no, plan_ids)
|
||||
transport_ids = [plan.transport_id for plan in plans if plan.transport_id]
|
||||
transports_by_id = _load_transports_by_id(db, tenant_no, transport_ids)
|
||||
items = _build_plan_archive_export_rows(plans, products_by_plan, transports_by_id)
|
||||
cols = PLAN_ARCHIVE_EXPORT_COLUMNS
|
||||
items = build_plan_archive_export_rows(db, tenant_no, plans, transports_by_id)
|
||||
cols = filter_export_columns(PLAN_ARCHIVE_EXPORT_COLUMNS, body.columns)
|
||||
sheet_title = "计划归档"
|
||||
filename_stem = f"计划归档_{date_str}"
|
||||
else:
|
||||
@@ -948,6 +876,31 @@ def export_contract_gaps(
|
||||
raise HTTPException(status_code=400, detail=str(e)) from e
|
||||
|
||||
|
||||
@router.put("/{id}/stop-contracts", response_model=PlanContractGapRead)
|
||||
def put_plan_stop_contracts(
|
||||
id: str,
|
||||
payload: PlanStopContractsUpdateBody,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> PlanContractGapRead:
|
||||
"""保存某装货/送货段合同关联(合同补全)。"""
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
plan = _get_plan(db, tenant_no, id, user)
|
||||
result = update_plan_stop_contracts(
|
||||
db,
|
||||
tenant_no=tenant_no,
|
||||
plan=plan,
|
||||
stop_type=payload.stop_type,
|
||||
stop_id=payload.stop_id,
|
||||
contracts=payload.contracts,
|
||||
user_id=user.id,
|
||||
dept_id=user.dept_id,
|
||||
)
|
||||
plan.update_by = user.id
|
||||
db.commit()
|
||||
return result
|
||||
|
||||
|
||||
@router.put("/{id}/contracts/{side}", response_model=PlanContractGapRead)
|
||||
def put_plan_side_contracts(
|
||||
id: str,
|
||||
|
||||
Reference in new issue
Block a user