提交
This commit is contained in:
1 parent
833544def3
commit
16be6fa22d
204 files changed
+455089
-46491
No files matched your search
@@ -1,10 +1,10 @@
|
||||
from typing import Optional
|
||||
from typing import List, Optional
|
||||
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy import func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@@ -38,6 +38,13 @@ from app.services.db.product_helpers import (
|
||||
resolve_crm_product,
|
||||
)
|
||||
from app.services.db_inventory_site_token import build_site_inventory_url, ensure_site_access_token
|
||||
from app.utils.excel_export import (
|
||||
EXPORT_MAX_ROWS,
|
||||
ExportColumn,
|
||||
ExportValidationError,
|
||||
export_rows_to_xlsx_response,
|
||||
filter_export_columns,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/inventory", tags=["inventory"])
|
||||
|
||||
@@ -272,6 +279,94 @@ def list_inventory(
|
||||
return result_list
|
||||
|
||||
|
||||
INVENTORY_EXPORT_COLUMNS = [
|
||||
ExportColumn(title="ID", field="id"),
|
||||
ExportColumn(title="计划号", field="planNo"),
|
||||
ExportColumn(title="出入库单号", field="batchNo"),
|
||||
ExportColumn(title="日期", field="date"),
|
||||
ExportColumn(title="往来单位", field="partnerCompany"),
|
||||
ExportColumn(title="业务类型", field="businessType"),
|
||||
ExportColumn(title="状态", field="status"),
|
||||
ExportColumn(title="产品", field="product"),
|
||||
ExportColumn(title="规格", field="specification"),
|
||||
ExportColumn(title="车牌号", field="vehicleNo"),
|
||||
ExportColumn(title="数量(吨)", field="quantity"),
|
||||
ExportColumn(title="净重(吨)", field="netWeight"),
|
||||
ExportColumn(title="毛重(吨)", field="grossWeight"),
|
||||
ExportColumn(title="毛重时间", field="grossWeightTime"),
|
||||
ExportColumn(title="皮重(吨)", field="tareWeight"),
|
||||
ExportColumn(title="皮重时间", field="tareWeightTime"),
|
||||
ExportColumn(title="扣杂(吨)", field="deduction"),
|
||||
ExportColumn(title="备注", field="remarks"),
|
||||
ExportColumn(title="创建人", field="createByName"),
|
||||
]
|
||||
|
||||
|
||||
class InventoryExportBody(BaseModel):
|
||||
q: Optional[str] = None
|
||||
plan_no: Optional[str] = Field(default=None, alias="planNo")
|
||||
batch_no: Optional[str] = Field(default=None, alias="batchNo")
|
||||
partner_company: Optional[str] = Field(default=None, alias="partnerCompany")
|
||||
product: Optional[str] = None
|
||||
vehicle_no: Optional[str] = Field(default=None, alias="vehicleNo")
|
||||
date_from: Optional[str] = Field(default=None, alias="dateFrom")
|
||||
date_to: Optional[str] = Field(default=None, alias="dateTo")
|
||||
status: Optional[str] = None
|
||||
business_type: Optional[str] = Field(default=None, alias="businessType")
|
||||
ids: Optional[List[str]] = None
|
||||
columns: Optional[List[str]] = None
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
@router.post("/export")
|
||||
def export_inventory(
|
||||
body: InventoryExportBody,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
):
|
||||
"""导出出入库列表(当前筛选全量,或 ids 已选)。"""
|
||||
try:
|
||||
items = list_inventory(
|
||||
q=body.q,
|
||||
plan_no=body.plan_no,
|
||||
batch_no=body.batch_no,
|
||||
partner_company=body.partner_company,
|
||||
product=body.product,
|
||||
vehicle_no=body.vehicle_no,
|
||||
date_from=body.date_from,
|
||||
date_to=body.date_to,
|
||||
status_filter=body.status,
|
||||
business_type=body.business_type,
|
||||
db=db,
|
||||
user=user,
|
||||
)
|
||||
if body.ids:
|
||||
id_set = set(body.ids)
|
||||
items = [x for x in items if x.id in id_set]
|
||||
if len(items) > EXPORT_MAX_ROWS:
|
||||
raise ExportValidationError(f"导出超过 {EXPORT_MAX_ROWS} 条,请缩小筛选范围")
|
||||
rows = []
|
||||
for item in items:
|
||||
data = item.model_dump(by_alias=True)
|
||||
if not data.get("createByName"):
|
||||
data["createByName"] = data.get("createBy") or ""
|
||||
rows.append(data)
|
||||
cols = filter_export_columns(INVENTORY_EXPORT_COLUMNS, body.columns)
|
||||
date_str = datetime.now().strftime("%Y%m%d")
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
return export_rows_to_xlsx_response(
|
||||
db,
|
||||
tenant_no=tenant_no,
|
||||
sheet_title="出入库列表",
|
||||
columns=cols,
|
||||
rows=rows,
|
||||
filename_stem=f"出入库列表_{date_str}",
|
||||
)
|
||||
except ExportValidationError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e)) from e
|
||||
|
||||
|
||||
@router.post("/", response_model=InboundOutboundRead, status_code=status.HTTP_201_CREATED)
|
||||
def create_inventory(
|
||||
payload: InboundOutboundCreate,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from typing import Optional
|
||||
from typing import List, Optional
|
||||
|
||||
from datetime import date, datetime
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy import func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@@ -35,6 +35,13 @@ from app.services.db.product_helpers import (
|
||||
apply_product_snapshot_to_plan,
|
||||
resolve_crm_product,
|
||||
)
|
||||
from app.utils.excel_export import (
|
||||
EXPORT_MAX_ROWS,
|
||||
ExportColumn,
|
||||
ExportValidationError,
|
||||
export_rows_to_xlsx_response,
|
||||
filter_export_columns,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/plans", tags=["plans"])
|
||||
|
||||
@@ -199,6 +206,96 @@ def list_plans(
|
||||
return result_list
|
||||
|
||||
|
||||
PLAN_EXPORT_COLUMNS = [
|
||||
ExportColumn(title="计划号", field="planNo"),
|
||||
ExportColumn(title="业务类型", field="businessType"),
|
||||
ExportColumn(title="往来单位", field="customerName"),
|
||||
ExportColumn(title="计划日期", field="planDate"),
|
||||
ExportColumn(title="起始日期", field="startDate"),
|
||||
ExportColumn(title="截止日期", field="endDate"),
|
||||
ExportColumn(title="产品", field="product"),
|
||||
ExportColumn(title="规格", field="specification"),
|
||||
ExportColumn(title="车牌号", field="vehicleNo"),
|
||||
ExportColumn(title="计划量(吨)", field="plannedQuantity"),
|
||||
ExportColumn(title="已执行量(吨)", field="executedQuantity"),
|
||||
ExportColumn(title="执行状态", field="executionStatus"),
|
||||
ExportColumn(title="备注", field="remarks"),
|
||||
ExportColumn(title="自动生成", field="autoGenerate"),
|
||||
ExportColumn(title="创建人", field="createByName"),
|
||||
]
|
||||
|
||||
|
||||
class PlanExportBody(BaseModel):
|
||||
q: Optional[str] = None
|
||||
plan_no: Optional[str] = Field(default=None, alias="planNo")
|
||||
customer_name: Optional[str] = Field(default=None, alias="customerName")
|
||||
product: Optional[str] = None
|
||||
business_type: Optional[str] = Field(default=None, alias="businessType")
|
||||
execution_status: Optional[str] = Field(default=None, alias="executionStatus")
|
||||
created_from: Optional[str] = Field(default=None, alias="createdFrom")
|
||||
created_to: Optional[str] = Field(default=None, alias="createdTo")
|
||||
start_date_from: Optional[str] = Field(default=None, alias="startDateFrom")
|
||||
start_date_to: Optional[str] = Field(default=None, alias="startDateTo")
|
||||
ids: Optional[List[str]] = None
|
||||
columns: Optional[List[str]] = None
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
@router.post("/export")
|
||||
def export_plans(
|
||||
body: PlanExportBody,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
):
|
||||
"""导出计划列表(当前筛选全量,或 ids 已选)。"""
|
||||
try:
|
||||
items = list_plans(
|
||||
q=body.q,
|
||||
plan_no=body.plan_no,
|
||||
customer_name=body.customer_name,
|
||||
product=body.product,
|
||||
business_type=body.business_type,
|
||||
execution_status=body.execution_status,
|
||||
created_from=body.created_from,
|
||||
created_to=body.created_to,
|
||||
start_date_from=body.start_date_from,
|
||||
start_date_to=body.start_date_to,
|
||||
db=db,
|
||||
user=user,
|
||||
)
|
||||
if body.ids:
|
||||
id_set = set(body.ids)
|
||||
items = [x for x in items if x.id in id_set]
|
||||
if len(items) > EXPORT_MAX_ROWS:
|
||||
raise ExportValidationError(f"导出超过 {EXPORT_MAX_ROWS} 条,请缩小筛选范围")
|
||||
rows = []
|
||||
for item in items:
|
||||
data = item.model_dump(by_alias=True)
|
||||
data["autoGenerate"] = "是" if data.get("autoGenerateEnabled") else "否"
|
||||
eq = data.get("executedQuantity") or 0
|
||||
try:
|
||||
data["executedQuantity"] = round(float(eq) / 1000, 2)
|
||||
except (TypeError, ValueError):
|
||||
data["executedQuantity"] = 0
|
||||
if not data.get("createByName"):
|
||||
data["createByName"] = data.get("createBy") or ""
|
||||
rows.append(data)
|
||||
cols = filter_export_columns(PLAN_EXPORT_COLUMNS, body.columns)
|
||||
date_str = datetime.now().strftime("%Y%m%d")
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
return export_rows_to_xlsx_response(
|
||||
db,
|
||||
tenant_no=tenant_no,
|
||||
sheet_title="计划列表",
|
||||
columns=cols,
|
||||
rows=rows,
|
||||
filename_stem=f"计划列表_{date_str}",
|
||||
)
|
||||
except ExportValidationError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e)) from e
|
||||
|
||||
|
||||
def _generate_plan_no() -> str:
|
||||
from app.services.db.plan_helpers import generate_plan_no
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from typing import Optional
|
||||
from typing import List, Optional
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy import func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@@ -32,6 +32,13 @@ from app.services.db.product_helpers import (
|
||||
apply_product_snapshot_to_weighing,
|
||||
resolve_crm_product,
|
||||
)
|
||||
from app.utils.excel_export import (
|
||||
EXPORT_MAX_ROWS,
|
||||
ExportColumn,
|
||||
ExportValidationError,
|
||||
export_rows_to_xlsx_response,
|
||||
filter_export_columns,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/weighing", tags=["weighing"])
|
||||
|
||||
@@ -195,6 +202,106 @@ def list_weighing(
|
||||
]
|
||||
|
||||
|
||||
WEIGHING_EXPORT_COLUMNS = [
|
||||
ExportColumn(title="日期", field="inventoryDate"),
|
||||
ExportColumn(title="计划单号", field="planNo"),
|
||||
ExportColumn(title="出入库单号", field="batchNo"),
|
||||
ExportColumn(title="车牌号", field="vehicleNo"),
|
||||
ExportColumn(title="货物名称", field="cargoName"),
|
||||
ExportColumn(title="规格", field="specification"),
|
||||
ExportColumn(title="业务类型", field="businessType"),
|
||||
ExportColumn(title="单据状态", field="docStatus"),
|
||||
ExportColumn(title="供货单位", field="supplier"),
|
||||
ExportColumn(title="收货单位", field="consignee"),
|
||||
ExportColumn(title="本次量(吨)", field="currentQuantity"),
|
||||
ExportColumn(title="净重(吨)", field="netWeight"),
|
||||
ExportColumn(title="毛重(kg)", field="grossWeight"),
|
||||
ExportColumn(title="毛重时间", field="grossWeightTime"),
|
||||
ExportColumn(title="皮重(kg)", field="tareWeight"),
|
||||
ExportColumn(title="皮重时间", field="tareWeightTime"),
|
||||
ExportColumn(title="扣杂(kg)", field="deduction"),
|
||||
ExportColumn(title="创建人", field="createByName"),
|
||||
]
|
||||
|
||||
|
||||
class WeighingExportBody(BaseModel):
|
||||
q: Optional[str] = None
|
||||
date_from: Optional[str] = Field(default=None, alias="dateFrom")
|
||||
date_to: Optional[str] = Field(default=None, alias="dateTo")
|
||||
plan_no: Optional[str] = Field(default=None, alias="planNo")
|
||||
batch_no: Optional[str] = Field(default=None, alias="batchNo")
|
||||
vehicle_no: Optional[str] = Field(default=None, alias="vehicleNo")
|
||||
cargo_name: Optional[str] = Field(default=None, alias="cargoName")
|
||||
supplier: Optional[str] = None
|
||||
consignee: Optional[str] = None
|
||||
business_type: Optional[str] = Field(default=None, alias="businessType")
|
||||
ids: Optional[List[str]] = None
|
||||
columns: Optional[List[str]] = None
|
||||
|
||||
model_config = {"populate_by_name": True}
|
||||
|
||||
|
||||
def _kg_to_ton(value) -> float:
|
||||
try:
|
||||
return round(float(value or 0) / 1000, 2)
|
||||
except (TypeError, ValueError):
|
||||
return 0.0
|
||||
|
||||
|
||||
@router.post("/export")
|
||||
def export_weighing(
|
||||
body: WeighingExportBody,
|
||||
db: Session = Depends(get_db),
|
||||
user: DbUserContext = Depends(get_db_current_user),
|
||||
):
|
||||
"""导出地磅单列表(当前筛选全量,或 ids 已选)。"""
|
||||
try:
|
||||
items = list_weighing(
|
||||
q=body.q,
|
||||
date_from=body.date_from,
|
||||
date_to=body.date_to,
|
||||
plan_no=body.plan_no,
|
||||
batch_no=body.batch_no,
|
||||
vehicle_no=body.vehicle_no,
|
||||
cargo_name=body.cargo_name,
|
||||
supplier=body.supplier,
|
||||
consignee=body.consignee,
|
||||
business_type=body.business_type,
|
||||
db=db,
|
||||
user=user,
|
||||
)
|
||||
if body.ids:
|
||||
id_set = set(body.ids)
|
||||
items = [x for x in items if x.id in id_set]
|
||||
if len(items) > EXPORT_MAX_ROWS:
|
||||
raise ExportValidationError(f"导出超过 {EXPORT_MAX_ROWS} 条,请缩小筛选范围")
|
||||
rows = []
|
||||
for item in items:
|
||||
data = item.model_dump(by_alias=True)
|
||||
data["docStatus"] = "已作废" if data.get("isVoided") == 1 else "正常"
|
||||
data["currentQuantity"] = _kg_to_ton(data.get("currentQuantity"))
|
||||
data["netWeight"] = _kg_to_ton(data.get("netWeight"))
|
||||
if not data.get("inventoryDate"):
|
||||
gwt = data.get("grossWeightTime") or ""
|
||||
data["inventoryDate"] = str(gwt)[:10] if gwt else ""
|
||||
if not data.get("createByName"):
|
||||
data["createByName"] = data.get("createBy") or ""
|
||||
rows.append(data)
|
||||
cols = filter_export_columns(WEIGHING_EXPORT_COLUMNS, body.columns)
|
||||
date_str = datetime.now().strftime("%Y%m%d")
|
||||
tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
|
||||
return export_rows_to_xlsx_response(
|
||||
db,
|
||||
tenant_no=tenant_no,
|
||||
sheet_title="地磅单列表",
|
||||
columns=cols,
|
||||
rows=rows,
|
||||
filename_stem=f"地磅单列表_{date_str}",
|
||||
)
|
||||
except ExportValidationError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e)) from e
|
||||
|
||||
|
||||
@router.post("/", response_model=WeighingSlipRead, status_code=status.HTTP_201_CREATED)
|
||||
def create_weighing(
|
||||
payload: WeighingSlipCreate,
|
||||
|
||||
Reference in new issue
Block a user