feat(pl): driver public feedback by product and brand qty
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
0667e8d77b
commit
2a215b2e1f
4 files changed
+436
-72
No files matched your search
@@ -15,7 +15,16 @@ from app.core.config import settings
|
||||
from app.core.database import get_db
|
||||
from app.core.response import error_response, success_response
|
||||
from app.crud import oss as oss_svc
|
||||
from app.models.pl_models import PlPlan, PlPlanProduct, PlPlanTransport, PlTransport
|
||||
from app.models.crm_models import CrmProductBrand
|
||||
from app.models.pl_models import (
|
||||
PlPlan,
|
||||
PlPlanProduct,
|
||||
PlPlanTransport,
|
||||
PlTransport,
|
||||
PlTransportProductBrandQty,
|
||||
PlTransportProductFeedback,
|
||||
)
|
||||
from app.schemas.pl.io_order import BrandOption, BrandQtyItem
|
||||
from app.schemas.pl.resource_snapshot import parse_resource_snapshot
|
||||
from app.schemas.pl.transport import (
|
||||
PublicTransportFeedback,
|
||||
@@ -23,6 +32,7 @@ from app.schemas.pl.transport import (
|
||||
PublicTransportPersonnelRead,
|
||||
PublicTransportPlanProductRead,
|
||||
)
|
||||
from app.services.pl_feedback_brand_qty import persist_transport_product_feedback
|
||||
from app.utils.excel_export import load_dict_label_maps
|
||||
|
||||
router = APIRouter(prefix="/public/pl/transport", tags=["运输计划-司机公共"])
|
||||
@@ -128,19 +138,86 @@ def _build_public_page(db: Session, transport: PlTransport) -> PublicTransportPa
|
||||
.all()
|
||||
)
|
||||
|
||||
crm_product_ids = {(p.product_id or "").strip() for p in products if (p.product_id or "").strip()}
|
||||
brand_options_by_product: dict[str, list[BrandOption]] = {}
|
||||
if crm_product_ids:
|
||||
brand_rows = (
|
||||
db.query(CrmProductBrand)
|
||||
.filter(
|
||||
CrmProductBrand.product_id.in_(list(crm_product_ids)),
|
||||
CrmProductBrand.tenant_no == transport.tenant_no,
|
||||
CrmProductBrand.status == "on",
|
||||
CrmProductBrand.del_flag == 0,
|
||||
)
|
||||
.order_by(CrmProductBrand.order_num.asc(), CrmProductBrand.create_time.asc())
|
||||
.all()
|
||||
)
|
||||
for row in brand_rows:
|
||||
brand_options_by_product.setdefault(row.product_id, []).append(
|
||||
BrandOption(brand_id=row.id, brand_name=row.brand_name or "")
|
||||
)
|
||||
|
||||
feedback_by_plan_product: dict[str, PlTransportProductFeedback] = {}
|
||||
brand_qtys_by_plan_product: dict[str, list[BrandQtyItem]] = {}
|
||||
feedback_rows = (
|
||||
db.query(PlTransportProductFeedback)
|
||||
.filter(
|
||||
PlTransportProductFeedback.transport_id == transport.id,
|
||||
PlTransportProductFeedback.tenant_no == transport.tenant_no,
|
||||
PlTransportProductFeedback.del_flag == 0,
|
||||
)
|
||||
.order_by(PlTransportProductFeedback.order_num.asc())
|
||||
.all()
|
||||
)
|
||||
for fb in feedback_rows:
|
||||
feedback_by_plan_product[fb.plan_product_id] = fb
|
||||
|
||||
if feedback_rows:
|
||||
feedback_ids = [fb.id for fb in feedback_rows]
|
||||
brand_qty_rows = (
|
||||
db.query(PlTransportProductBrandQty)
|
||||
.filter(
|
||||
PlTransportProductBrandQty.transport_id == transport.id,
|
||||
PlTransportProductBrandQty.tenant_no == transport.tenant_no,
|
||||
PlTransportProductBrandQty.del_flag == 0,
|
||||
PlTransportProductBrandQty.feedback_id.in_(feedback_ids),
|
||||
)
|
||||
.order_by(PlTransportProductBrandQty.order_num.asc())
|
||||
.all()
|
||||
)
|
||||
for row in brand_qty_rows:
|
||||
brand_qtys_by_plan_product.setdefault(row.plan_product_id, []).append(
|
||||
BrandQtyItem(
|
||||
brand_id=row.brand_id or "",
|
||||
brand_name=row.brand_name,
|
||||
qty=row.qty or 0,
|
||||
)
|
||||
)
|
||||
|
||||
product_reads = []
|
||||
unit_map = load_dict_label_maps(
|
||||
db, [CRM_PRODUCT_UNIT], tenant_no=transport.tenant_no
|
||||
).get(CRM_PRODUCT_UNIT, {})
|
||||
for prod in products:
|
||||
product_id = (prod.product_id or "").strip()
|
||||
options = brand_options_by_product.get(product_id) or None
|
||||
fb = feedback_by_plan_product.get(prod.id)
|
||||
brand_qtys = brand_qtys_by_plan_product.get(prod.id) or None
|
||||
product_reads.append(
|
||||
PublicTransportPlanProductRead(
|
||||
plan_product_id=prod.id,
|
||||
product_name=prod.product_name or "",
|
||||
model=prod.model,
|
||||
package_spec=prod.package_spec,
|
||||
unit=_resolve_unit_label(unit_map, prod.unit),
|
||||
quantity=prod.quantity or prod.inbound_estimate_qty or prod.outbound_estimate_qty,
|
||||
remark=(prod.remark or "").strip() or None,
|
||||
brand_mode=prod.brand_mode,
|
||||
brand_id=prod.brand_id,
|
||||
brand_name=prod.brand_name,
|
||||
brand_options=options,
|
||||
actual_qty=fb.actual_qty if fb else None,
|
||||
brand_qtys=brand_qtys,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -203,7 +280,39 @@ def save_public_feedback(
|
||||
if payload.feedback_remark is not None:
|
||||
transport.feedback_remark = payload.feedback_remark
|
||||
|
||||
if payload.feedback_qty is not None:
|
||||
if payload.products:
|
||||
link = (
|
||||
db.query(PlPlanTransport)
|
||||
.filter(
|
||||
PlPlanTransport.transport_id == transport.id,
|
||||
PlPlanTransport.del_flag == 0,
|
||||
)
|
||||
.order_by(PlPlanTransport.create_time.desc())
|
||||
.first()
|
||||
)
|
||||
if not link or not link.plan_id:
|
||||
return error_response(400, "未找到关联计划,无法保存产品回传")
|
||||
products_payload = [
|
||||
{
|
||||
"planProductId": item.plan_product_id,
|
||||
"actualQty": item.actual_qty,
|
||||
"brandQtys": [b.model_dump(by_alias=True) for b in (item.brand_qtys or [])],
|
||||
}
|
||||
for item in payload.products
|
||||
]
|
||||
try:
|
||||
persist_transport_product_feedback(
|
||||
db,
|
||||
tenant_no=transport.tenant_no,
|
||||
transport=transport,
|
||||
plan_id=link.plan_id,
|
||||
products=products_payload,
|
||||
user_id=None,
|
||||
enforce_brand=True,
|
||||
)
|
||||
except ValueError as exc:
|
||||
return error_response(400, str(exc))
|
||||
elif payload.feedback_qty is not None:
|
||||
transport.feedback_qty = payload.feedback_qty
|
||||
|
||||
transport.feedback_time = datetime.now()
|
||||
|
||||
@@ -6,6 +6,7 @@ from typing import Optional
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.schemas.pl.datetime_parse import OptionalPlanDateTime
|
||||
from app.schemas.pl.io_order import BrandOption, BrandQtyItem
|
||||
from app.schemas.pl.resource_snapshot import PlanResourceSnapshotMixin
|
||||
|
||||
|
||||
@@ -188,20 +189,35 @@ class TransportFeedback(BaseModel):
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class PublicTransportProductFeedbackItem(BaseModel):
|
||||
plan_product_id: str = Field(..., alias="planProductId")
|
||||
actual_qty: Decimal = Field(..., alias="actualQty", ge=0)
|
||||
brand_qtys: Optional[list[BrandQtyItem]] = Field(default=None, alias="brandQtys")
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class PublicTransportFeedback(BaseModel):
|
||||
feedback_images: Optional[list[str]] = Field(default=None, alias="feedbackImages")
|
||||
feedback_remark: Optional[str] = Field(default=None, alias="feedbackRemark")
|
||||
feedback_qty: Optional[Decimal] = Field(default=None, alias="feedbackQty", ge=0, description="回传数量")
|
||||
products: Optional[list[PublicTransportProductFeedbackItem]] = None
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class PublicTransportPlanProductRead(BaseModel):
|
||||
plan_product_id: str = Field(alias="planProductId")
|
||||
product_name: str = Field(alias="productName")
|
||||
model: Optional[str] = None
|
||||
package_spec: Optional[str] = Field(default=None, alias="packageSpec")
|
||||
unit: Optional[str] = None
|
||||
quantity: Optional[Decimal] = None
|
||||
remark: Optional[str] = Field(default=None, alias="remark", description="运输计划产品备注")
|
||||
brand_mode: Optional[str] = Field(default=None, alias="brandMode")
|
||||
brand_id: Optional[str] = Field(default=None, alias="brandId")
|
||||
brand_name: Optional[str] = Field(default=None, alias="brandName")
|
||||
brand_options: Optional[list[BrandOption]] = Field(default=None, alias="brandOptions")
|
||||
actual_qty: Optional[Decimal] = Field(default=None, alias="actualQty")
|
||||
brand_qtys: Optional[list[BrandQtyItem]] = Field(default=None, alias="brandQtys")
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user