feat(pl): 出入库 API 接入 enrichment 并扩展 schema
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
4f69a9b904
commit
f5a0931471
4 files changed
+592
-1
No files matched your search
@@ -0,0 +1,323 @@
|
||||
"""运输计划 · 入库单 / 出库单管理端 API"""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy import or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.db_auth import DbUserContext, get_db_current_user
|
||||
from app.core.tenant_context import get_tenant_no
|
||||
from app.models.pl_models import (
|
||||
PlInboundOrder,
|
||||
PlInboundOrderItem,
|
||||
PlOutboundOrder,
|
||||
PlOutboundOrderItem,
|
||||
)
|
||||
from app.schemas.pl.io_order import IoOrderListResponse, IoOrderPatch, IoOrderRead
|
||||
from app.services.pl_io_order_service import (
|
||||
dump_feedback_images,
|
||||
order_header_to_dict,
|
||||
)
|
||||
|
||||
inbound_router = APIRouter(prefix="/inbound", tags=["运输计划-入库单"])
|
||||
outbound_router = APIRouter(prefix="/outbound", tags=["运输计划-出库单"])
|
||||
|
||||
_STATUS_OK = {"in_progress", "completed", "voided"}
|
||||
|
||||
|
||||
def _models(kind: str):
|
||||
if kind == "inbound":
|
||||
return PlInboundOrder, PlInboundOrderItem
|
||||
return PlOutboundOrder, PlOutboundOrderItem
|
||||
|
||||
|
||||
def _get_order(db: Session, tenant_no: str, kind: str, order_id: str):
|
||||
header_m, item_m = _models(kind)
|
||||
header = (
|
||||
db.query(header_m)
|
||||
.filter(header_m.id == order_id, header_m.tenant_no == tenant_no, header_m.del_flag == 0)
|
||||
.first()
|
||||
)
|
||||
if not header:
|
||||
raise HTTPException(status_code=404, detail="单据不存在")
|
||||
items = (
|
||||
db.query(item_m)
|
||||
.filter(item_m.order_id == header.id, item_m.del_flag == 0)
|
||||
.order_by(item_m.order_num.asc(), item_m.create_time.asc())
|
||||
.all()
|
||||
)
|
||||
return header, items
|
||||
|
||||
|
||||
def _to_read(kind: str, header, items, db: Session) -> IoOrderRead:
|
||||
return IoOrderRead.model_validate(
|
||||
order_header_to_dict(kind, header, items, db=db)
|
||||
)
|
||||
|
||||
|
||||
def _list_orders(
|
||||
*,
|
||||
kind: str,
|
||||
order_no: Optional[str],
|
||||
plan_no: Optional[str],
|
||||
status: Optional[str],
|
||||
partner_company: Optional[str],
|
||||
vehicle_no: Optional[str],
|
||||
q: Optional[str],
|
||||
page: int,
|
||||
page_size: int,
|
||||
db: Session,
|
||||
user: DbUserContext,
|
||||
) -> IoOrderListResponse:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
header_m, item_m = _models(kind)
|
||||
query = db.query(header_m).filter(header_m.tenant_no == tenant_no, header_m.del_flag == 0)
|
||||
if order_no:
|
||||
query = query.filter(header_m.order_no.contains(order_no))
|
||||
if plan_no:
|
||||
query = query.filter(header_m.plan_no.contains(plan_no))
|
||||
if status:
|
||||
query = query.filter(header_m.status == status)
|
||||
if partner_company:
|
||||
query = query.filter(header_m.partner_company.contains(partner_company))
|
||||
if vehicle_no:
|
||||
query = query.filter(header_m.vehicle_no.contains(vehicle_no))
|
||||
if q:
|
||||
query = query.filter(
|
||||
or_(
|
||||
header_m.order_no.contains(q),
|
||||
header_m.plan_no.contains(q),
|
||||
header_m.partner_company.contains(q),
|
||||
header_m.vehicle_no.contains(q),
|
||||
)
|
||||
)
|
||||
total = query.count()
|
||||
rows = (
|
||||
query.order_by(header_m.create_time.desc())
|
||||
.offset((page - 1) * page_size)
|
||||
.limit(page_size)
|
||||
.all()
|
||||
)
|
||||
result: list[IoOrderRead] = []
|
||||
for row in rows:
|
||||
items = (
|
||||
db.query(item_m)
|
||||
.filter(item_m.order_id == row.id, item_m.del_flag == 0)
|
||||
.order_by(item_m.order_num.asc())
|
||||
.all()
|
||||
)
|
||||
result.append(_to_read(kind, row, items, db))
|
||||
return IoOrderListResponse(total=total, list=result)
|
||||
|
||||
|
||||
def _patch_order(
|
||||
*,
|
||||
kind: str,
|
||||
order_id: str,
|
||||
payload: IoOrderPatch,
|
||||
db: Session,
|
||||
user: DbUserContext,
|
||||
) -> IoOrderRead:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
header, items = _get_order(db, tenant_no, kind, order_id)
|
||||
if payload.status is not None:
|
||||
if payload.status not in _STATUS_OK:
|
||||
raise HTTPException(status_code=400, detail="无效状态")
|
||||
header.status = payload.status
|
||||
if payload.site_feedback_remark is not None:
|
||||
header.site_feedback_remark = (payload.site_feedback_remark or "").strip() or None
|
||||
if payload.site_feedback_images is not None:
|
||||
header.site_feedback_images = dump_feedback_images(payload.site_feedback_images)
|
||||
header.site_feedback_time = datetime.now()
|
||||
if payload.remark is not None:
|
||||
header.remark = payload.remark
|
||||
if payload.items:
|
||||
by_id = {it.id: it for it in items}
|
||||
for patch in payload.items:
|
||||
row = by_id.get(patch.id)
|
||||
if not row:
|
||||
continue
|
||||
row.actual_qty = Decimal(str(patch.actual_qty))
|
||||
row.qty_source = "manual"
|
||||
row.update_by = user.id
|
||||
header.update_by = user.id
|
||||
db.commit()
|
||||
header, items = _get_order(db, tenant_no, kind, order_id)
|
||||
return _to_read(kind, header, items, db)
|
||||
|
||||
|
||||
def _get_by_plan(db: Session, tenant_no: str, kind: str, plan_id: str):
|
||||
"""按计划查找入库/出库单;未落单时返回 (None, []),与运费 by-transport 一致。"""
|
||||
header_m, item_m = _models(kind)
|
||||
header = (
|
||||
db.query(header_m)
|
||||
.filter(
|
||||
header_m.tenant_no == tenant_no,
|
||||
header_m.plan_id == plan_id,
|
||||
header_m.del_flag == 0,
|
||||
)
|
||||
.order_by(header_m.create_time.desc())
|
||||
.first()
|
||||
)
|
||||
if not header:
|
||||
return None, []
|
||||
items = (
|
||||
db.query(item_m)
|
||||
.filter(item_m.order_id == header.id, item_m.del_flag == 0)
|
||||
.order_by(item_m.order_num.asc(), item_m.create_time.asc())
|
||||
.all()
|
||||
)
|
||||
return header, items
|
||||
|
||||
|
||||
@inbound_router.get("/by-plan/{plan_id}", response_model=Optional[IoOrderRead])
|
||||
def get_inbound_by_plan(
|
||||
plan_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> Optional[IoOrderRead]:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
header, items = _get_by_plan(db, tenant_no, "inbound", plan_id)
|
||||
if not header:
|
||||
return None
|
||||
return _to_read("inbound", header, items, db)
|
||||
|
||||
|
||||
@inbound_router.get("/", response_model=IoOrderListResponse)
|
||||
def list_inbound(
|
||||
order_no: Optional[str] = Query(default=None, alias="orderNo"),
|
||||
plan_no: Optional[str] = Query(default=None, alias="planNo"),
|
||||
status: Optional[str] = Query(default=None),
|
||||
partner_company: Optional[str] = Query(default=None, alias="partnerCompany"),
|
||||
vehicle_no: Optional[str] = Query(default=None, alias="vehicleNo"),
|
||||
q: Optional[str] = Query(default=None),
|
||||
page: int = Query(default=1, ge=1),
|
||||
page_size: int = Query(default=20, ge=1, le=100, alias="pageSize"),
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> IoOrderListResponse:
|
||||
return _list_orders(
|
||||
kind="inbound",
|
||||
order_no=order_no,
|
||||
plan_no=plan_no,
|
||||
status=status,
|
||||
partner_company=partner_company,
|
||||
vehicle_no=vehicle_no,
|
||||
q=q,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
db=db,
|
||||
user=user,
|
||||
)
|
||||
|
||||
|
||||
@inbound_router.get("/{id}", response_model=IoOrderRead)
|
||||
def get_inbound(
|
||||
id: str,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> IoOrderRead:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
header, items = _get_order(db, tenant_no, "inbound", id)
|
||||
return _to_read("inbound", header, items, db)
|
||||
|
||||
|
||||
@inbound_router.patch("/{id}", response_model=IoOrderRead)
|
||||
def patch_inbound(
|
||||
id: str,
|
||||
payload: IoOrderPatch,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> IoOrderRead:
|
||||
return _patch_order(kind="inbound", order_id=id, payload=payload, db=db, user=user)
|
||||
|
||||
|
||||
@inbound_router.get("/{id}/site-url")
|
||||
def inbound_site_url(
|
||||
id: str,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> dict:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
header, _ = _get_order(db, tenant_no, "inbound", id)
|
||||
data = order_header_to_dict("inbound", header, None)
|
||||
return {"siteUrl": data.get("siteUrl"), "siteAccessToken": data.get("siteAccessToken")}
|
||||
|
||||
|
||||
@outbound_router.get("/by-plan/{plan_id}", response_model=Optional[IoOrderRead])
|
||||
def get_outbound_by_plan(
|
||||
plan_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> Optional[IoOrderRead]:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
header, items = _get_by_plan(db, tenant_no, "outbound", plan_id)
|
||||
if not header:
|
||||
return None
|
||||
return _to_read("outbound", header, items, db)
|
||||
|
||||
|
||||
@outbound_router.get("/", response_model=IoOrderListResponse)
|
||||
def list_outbound(
|
||||
order_no: Optional[str] = Query(default=None, alias="orderNo"),
|
||||
plan_no: Optional[str] = Query(default=None, alias="planNo"),
|
||||
status: Optional[str] = Query(default=None),
|
||||
partner_company: Optional[str] = Query(default=None, alias="partnerCompany"),
|
||||
vehicle_no: Optional[str] = Query(default=None, alias="vehicleNo"),
|
||||
q: Optional[str] = Query(default=None),
|
||||
page: int = Query(default=1, ge=1),
|
||||
page_size: int = Query(default=20, ge=1, le=100, alias="pageSize"),
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> IoOrderListResponse:
|
||||
return _list_orders(
|
||||
kind="outbound",
|
||||
order_no=order_no,
|
||||
plan_no=plan_no,
|
||||
status=status,
|
||||
partner_company=partner_company,
|
||||
vehicle_no=vehicle_no,
|
||||
q=q,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
db=db,
|
||||
user=user,
|
||||
)
|
||||
|
||||
|
||||
@outbound_router.get("/{id}", response_model=IoOrderRead)
|
||||
def get_outbound(
|
||||
id: str,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> IoOrderRead:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
header, items = _get_order(db, tenant_no, "outbound", id)
|
||||
return _to_read("outbound", header, items, db)
|
||||
|
||||
|
||||
@outbound_router.patch("/{id}", response_model=IoOrderRead)
|
||||
def patch_outbound(
|
||||
id: str,
|
||||
payload: IoOrderPatch,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> IoOrderRead:
|
||||
return _patch_order(kind="outbound", order_id=id, payload=payload, db=db, user=user)
|
||||
|
||||
|
||||
@outbound_router.get("/{id}/site-url")
|
||||
def outbound_site_url(
|
||||
id: str,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
) -> dict:
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
header, _ = _get_order(db, tenant_no, "outbound", id)
|
||||
data = order_header_to_dict("outbound", header, None)
|
||||
return {"siteUrl": data.get("siteUrl"), "siteAccessToken": data.get("siteAccessToken")}
|
||||
@@ -0,0 +1,165 @@
|
||||
"""运输计划 · 入库/出库单现场免登录公共 API"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from decimal import Decimal
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.attachment_biz import resolve_upload_folder
|
||||
from app.core.config import settings
|
||||
from app.core.database import get_db
|
||||
from app.core.response import error_response, success_response
|
||||
from app.core.storage import storage
|
||||
from app.crud import oss as oss_svc
|
||||
from app.models.pl_models import (
|
||||
PlInboundOrder,
|
||||
PlInboundOrderItem,
|
||||
PlOutboundOrder,
|
||||
PlOutboundOrderItem,
|
||||
PlPlan,
|
||||
)
|
||||
from app.schemas.pl.io_order import PublicIoOrderFeedback
|
||||
from app.services.pl_io_order_service import apply_site_feedback, order_header_to_dict, parse_feedback_images
|
||||
|
||||
inbound_public_router = APIRouter(prefix="/public/pl/inbound", tags=["运输计划-入库单公共"])
|
||||
outbound_public_router = APIRouter(prefix="/public/pl/outbound", tags=["运输计划-出库单公共"])
|
||||
|
||||
|
||||
def _models(kind: str):
|
||||
if kind == "inbound":
|
||||
return PlInboundOrder, PlInboundOrderItem
|
||||
return PlOutboundOrder, PlOutboundOrderItem
|
||||
|
||||
|
||||
def _get_by_token(db: Session, kind: str, token: str):
|
||||
header_m, item_m = _models(kind)
|
||||
header = (
|
||||
db.query(header_m)
|
||||
.filter(header_m.site_access_token == token, header_m.del_flag == 0)
|
||||
.first()
|
||||
)
|
||||
if not header:
|
||||
raise HTTPException(status_code=404, detail="链接无效或已失效")
|
||||
items = (
|
||||
db.query(item_m)
|
||||
.filter(item_m.order_id == header.id, item_m.del_flag == 0)
|
||||
.order_by(item_m.order_num.asc())
|
||||
.all()
|
||||
)
|
||||
return header, items
|
||||
|
||||
|
||||
def _build_page(db: Session, kind: str, header, items) -> dict:
|
||||
plan = db.query(PlPlan).filter(PlPlan.id == header.plan_id, PlPlan.del_flag == 0).first()
|
||||
voided = header.status == "voided" or bool(plan and plan.follow_status == "cancelled")
|
||||
data = order_header_to_dict(
|
||||
kind, header, items, db=db, public_unit_as_label=True
|
||||
)
|
||||
data["voided"] = voided
|
||||
data["title"] = "入库单" if kind == "inbound" else "出库单"
|
||||
return data
|
||||
|
||||
|
||||
def _format_file_size(size_bytes: int) -> str:
|
||||
if size_bytes < 1024:
|
||||
return f"{size_bytes} B"
|
||||
if size_bytes < 1024 * 1024:
|
||||
return f"{size_bytes / 1024:.1f} KB"
|
||||
return f"{size_bytes / (1024 * 1024):.1f} MB"
|
||||
|
||||
|
||||
def _make_handlers(kind: str):
|
||||
def get_page(token: str, db: Session = Depends(get_db)):
|
||||
header, items = _get_by_token(db, kind, token)
|
||||
return success_response(_build_page(db, kind, header, items))
|
||||
|
||||
def save_feedback(token: str, payload: PublicIoOrderFeedback, db: Session = Depends(get_db)):
|
||||
header, items = _get_by_token(db, kind, token)
|
||||
page = _build_page(db, kind, header, items)
|
||||
if page["voided"]:
|
||||
return error_response(400, "单据已作废,无法提交")
|
||||
qty_map = {x.id: Decimal(str(x.actual_qty)) for x in payload.items}
|
||||
images = payload.images
|
||||
if not images:
|
||||
images = parse_feedback_images(header.site_feedback_images)
|
||||
try:
|
||||
apply_site_feedback(
|
||||
db,
|
||||
header=header,
|
||||
items=items,
|
||||
item_qty=qty_map,
|
||||
remark=payload.remark,
|
||||
images=images,
|
||||
)
|
||||
except ValueError as exc:
|
||||
return error_response(400, str(exc))
|
||||
db.commit()
|
||||
return success_response({"success": True, "message": "保存成功"})
|
||||
|
||||
def upload_attachment(
|
||||
token: str,
|
||||
file: UploadFile = File(...),
|
||||
folder: Optional[str] = Form(""),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
header, _items = _get_by_token(db, kind, token)
|
||||
page = _build_page(db, kind, header, _items)
|
||||
if page["voided"]:
|
||||
return error_response(400, "单据已作废,无法上传附件")
|
||||
if not file or not file.filename:
|
||||
return error_response(400, "请选择文件")
|
||||
try:
|
||||
effective_folder = resolve_upload_folder(
|
||||
folder=folder or "", biz_type="pl_io_order_site_feedback"
|
||||
)
|
||||
file_content = file.file.read()
|
||||
full_url = storage.upload_file(file_content, file.filename, effective_folder)
|
||||
base_url = settings.MINIO_BASE_URL.rstrip("/") if settings.MINIO_BASE_URL else ""
|
||||
if base_url and full_url.startswith(base_url + "/"):
|
||||
file_url = full_url[len(base_url) + 1 :]
|
||||
else:
|
||||
file_url = full_url
|
||||
record = oss_svc.create_oss(
|
||||
db,
|
||||
tenant_no=header.tenant_no,
|
||||
url=file_url,
|
||||
original_name=file.filename,
|
||||
file_suffix=os.path.splitext(file.filename)[1].lstrip(".") if file.filename else "",
|
||||
file_size=len(file_content),
|
||||
file_type=file.content_type or "",
|
||||
biz_type="pl_io_order_site_feedback",
|
||||
user_id=None,
|
||||
ext1=effective_folder or None,
|
||||
)
|
||||
db.commit()
|
||||
return success_response(
|
||||
{
|
||||
"id": record.id,
|
||||
"fileName": record.original_name,
|
||||
"fileUrl": record.url,
|
||||
"fileSize": record.file_size,
|
||||
"fileSizeDisplay": _format_file_size(record.file_size or 0),
|
||||
"fileType": record.file_type,
|
||||
}
|
||||
)
|
||||
except Exception as exc:
|
||||
db.rollback()
|
||||
return error_response(500, f"上传失败: {str(exc)}")
|
||||
|
||||
get_page.__name__ = f"get_public_{kind}_order"
|
||||
save_feedback.__name__ = f"save_public_{kind}_feedback"
|
||||
upload_attachment.__name__ = f"upload_public_{kind}_attachment"
|
||||
return get_page, save_feedback, upload_attachment
|
||||
|
||||
|
||||
_in_get, _in_fb, _in_up = _make_handlers("inbound")
|
||||
_out_get, _out_fb, _out_up = _make_handlers("outbound")
|
||||
inbound_public_router.get("/{token}")(_in_get)
|
||||
inbound_public_router.post("/{token}/feedback")(_in_fb)
|
||||
inbound_public_router.post("/{token}/attachments")(_in_up)
|
||||
outbound_public_router.get("/{token}")(_out_get)
|
||||
outbound_public_router.post("/{token}/feedback")(_out_fb)
|
||||
outbound_public_router.post("/{token}/attachments")(_out_up)
|
||||
@@ -0,0 +1,98 @@
|
||||
"""入库单 / 出库单 Pydantic schemas"""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class IoOrderItemRead(BaseModel):
|
||||
id: str
|
||||
order_id: str = Field(alias="orderId")
|
||||
plan_product_id: str = Field(alias="planProductId")
|
||||
product_id: Optional[str] = Field(default=None, alias="productId")
|
||||
product_name: Optional[str] = Field(default=None, alias="productName")
|
||||
specification: Optional[str] = None
|
||||
unit: Optional[str] = None
|
||||
plan_qty: Optional[Decimal] = Field(default=None, alias="planQty")
|
||||
actual_qty: Optional[Decimal] = Field(default=None, alias="actualQty")
|
||||
qty_source: Optional[str] = Field(default=None, alias="qtySource")
|
||||
product_no: Optional[str] = Field(default=None, alias="productNo")
|
||||
package_spec: Optional[str] = Field(default=None, alias="packageSpec")
|
||||
remark: Optional[str] = None
|
||||
unit_label: Optional[str] = Field(default=None, alias="unitLabel")
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class IoOrderItemPatch(BaseModel):
|
||||
id: str
|
||||
actual_qty: Decimal = Field(alias="actualQty")
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class IoOrderPatch(BaseModel):
|
||||
status: Optional[str] = None
|
||||
site_feedback_remark: Optional[str] = Field(default=None, alias="siteFeedbackRemark")
|
||||
site_feedback_images: Optional[list[str]] = Field(default=None, alias="siteFeedbackImages")
|
||||
remark: Optional[str] = None
|
||||
items: Optional[list[IoOrderItemPatch]] = None
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class IoOrderRead(BaseModel):
|
||||
id: str
|
||||
order_no: str = Field(alias="orderNo")
|
||||
plan_id: str = Field(alias="planId")
|
||||
transport_id: str = Field(alias="transportId")
|
||||
status: str
|
||||
partner_company: Optional[str] = Field(default=None, alias="partnerCompany")
|
||||
vehicle_no: Optional[str] = Field(default=None, alias="vehicleNo")
|
||||
plan_no: Optional[str] = Field(default=None, alias="planNo")
|
||||
plan_date: Optional[date] = Field(default=None, alias="planDate")
|
||||
site_access_token: Optional[str] = Field(default=None, alias="siteAccessToken")
|
||||
site_url: Optional[str] = Field(default=None, alias="siteUrl")
|
||||
site_feedback_remark: Optional[str] = Field(default=None, alias="siteFeedbackRemark")
|
||||
site_feedback_images: list[str] = Field(default_factory=list, alias="siteFeedbackImages")
|
||||
site_feedback_time: Optional[datetime] = Field(default=None, alias="siteFeedbackTime")
|
||||
site_first_submitted_at: Optional[datetime] = Field(default=None, alias="siteFirstSubmittedAt")
|
||||
remark: Optional[str] = None
|
||||
product_summary: Optional[str] = Field(default=None, alias="productSummary")
|
||||
actual_qty_total: Optional[float] = Field(default=None, alias="actualQtyTotal")
|
||||
kind: str
|
||||
items: list[IoOrderItemRead] = Field(default_factory=list)
|
||||
business_type: Optional[str] = Field(default=None, alias="businessType")
|
||||
plan_remark: Optional[str] = Field(default=None, alias="planRemark")
|
||||
pickup_address: Optional[str] = Field(default=None, alias="pickupAddress")
|
||||
pickup_lng: Optional[float] = Field(default=None, alias="pickupLng")
|
||||
pickup_lat: Optional[float] = Field(default=None, alias="pickupLat")
|
||||
pickup_time: Optional[datetime] = Field(default=None, alias="pickupTime")
|
||||
pickup_notes: Optional[str] = Field(default=None, alias="pickupNotes")
|
||||
destination_name: Optional[str] = Field(default=None, alias="destinationName")
|
||||
destination_address: Optional[str] = Field(default=None, alias="destinationAddress")
|
||||
destination_lng: Optional[float] = Field(default=None, alias="destinationLng")
|
||||
destination_lat: Optional[float] = Field(default=None, alias="destinationLat")
|
||||
delivery_time: Optional[datetime] = Field(default=None, alias="deliveryTime")
|
||||
destination_notes: Optional[str] = Field(default=None, alias="destinationNotes")
|
||||
create_time: Optional[datetime] = Field(default=None, alias="createTime")
|
||||
update_time: Optional[datetime] = Field(default=None, alias="updateTime")
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class IoOrderListResponse(BaseModel):
|
||||
total: int
|
||||
list: list[IoOrderRead]
|
||||
|
||||
|
||||
class PublicIoOrderItemFeedback(BaseModel):
|
||||
id: str
|
||||
actual_qty: Decimal = Field(alias="actualQty")
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
class PublicIoOrderFeedback(BaseModel):
|
||||
remark: Optional[str] = None
|
||||
images: list[str] = Field(default_factory=list)
|
||||
items: list[PublicIoOrderItemFeedback] = Field(default_factory=list)
|
||||
model_config = {"populate_by_name": True}
|
||||
@@ -417,7 +417,11 @@ def enrich_io_order_dict(
|
||||
if plan_id:
|
||||
plan = (
|
||||
db.query(PlPlan)
|
||||
.filter(PlPlan.id == plan_id, PlPlan.del_flag == 0)
|
||||
.filter(
|
||||
PlPlan.id == plan_id,
|
||||
PlPlan.tenant_no == tenant_no,
|
||||
PlPlan.del_flag == 0,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
merge_plan_logistics_into_order(data, plan)
|
||||
@@ -441,6 +445,7 @@ def enrich_io_order_dict(
|
||||
db.query(PlPlanProduct)
|
||||
.filter(
|
||||
PlPlanProduct.id.in_(product_ids),
|
||||
PlPlanProduct.tenant_no == tenant_no,
|
||||
PlPlanProduct.del_flag == 0,
|
||||
)
|
||||
.all()
|
||||
|
||||
Reference in new issue
Block a user