feat: P0-6 Excel导入导出全套(客户/合同/库存/统计, 自动表头探测)
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
"""仓库库存路由:材料 CRUD + 出入库。"""
|
||||
from fastapi import APIRouter
|
||||
"""仓库库存路由:材料 CRUD + 出入库 + Excel 导入导出。"""
|
||||
from fastapi import APIRouter, UploadFile
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy import desc, select
|
||||
|
||||
@@ -69,6 +69,80 @@ async def create_item(body: ItemBody, user: CurrentUser, db: DbSession):
|
||||
return ok(data={"id": item.id}, message="创建成功")
|
||||
|
||||
|
||||
# ---------------- Excel 导入导出 ----------------
|
||||
|
||||
@router.get("/excel-out")
|
||||
async def excel_out(user: CurrentUser, db: DbSession):
|
||||
"""库存全量导出(对齐目标站 warehouse_excel_out)。"""
|
||||
from fastapi.responses import Response
|
||||
from huamenchuang.services.excel_service import simple_table_workbook
|
||||
items = list((await db.execute(
|
||||
select(WarehouseItem).where(WarehouseItem.company_id == user.company_id)
|
||||
.order_by(WarehouseItem.id))).scalars())
|
||||
mat_label = {"profile": "型材", "glass": "玻璃", "hardware": "五金", "addon": "配件"}
|
||||
data = [[i.bom_code, i.name, mat_label.get(i.material, i.material), i.spec, i.color,
|
||||
float(i.price), float(i.cost), float(i.count), i.unit, i.length, float(i.warning)]
|
||||
for i in items]
|
||||
content = simple_table_workbook("库存列表",
|
||||
["编码", "名称", "类别", "规格", "颜色", "售价", "成本",
|
||||
"库存数量", "单位", "单根长度mm", "预警线"],
|
||||
data, widths=[12, 20, 8, 14, 10, 10, 10, 10, 8, 12, 10])
|
||||
return Response(content, media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
headers={"Content-Disposition": "attachment; filename=warehouse.xlsx"})
|
||||
|
||||
|
||||
@router.post("/excel-in")
|
||||
async def excel_in(file: UploadFile, user: CurrentUser, db: DbSession):
|
||||
"""材料批量导入(对齐目标站 materials_config_excel:同名材料按名称匹配更新数量,否则新增)。"""
|
||||
if not file.filename or not file.filename.lower().endswith((".xlsx", ".xls")):
|
||||
return fail("请上传 xlsx 文件")
|
||||
from huamenchuang.services.excel_service import pick, read_rows
|
||||
try:
|
||||
rows = read_rows(await file.read())
|
||||
except Exception:
|
||||
return fail("文件解析失败")
|
||||
if not rows:
|
||||
return fail("文件中没有数据行")
|
||||
mat_map = {"型材": "profile", "玻璃": "glass", "五金": "hardware", "配件": "addon",
|
||||
"profile": "profile", "glass": "glass", "hardware": "hardware", "addon": "addon"}
|
||||
existing = {i.name: i for i in (await db.execute(
|
||||
select(WarehouseItem).where(WarehouseItem.company_id == user.company_id))).scalars()}
|
||||
created, updated, skipped = 0, 0, 0
|
||||
for i, row in enumerate(rows, 2):
|
||||
name = pick(row, "名称", "材料名称", "name")
|
||||
if not name:
|
||||
skipped += 1
|
||||
continue
|
||||
name = str(name).strip()
|
||||
mat = mat_map.get(str(pick(row, "类别", "材料", "material", default="型材") or "型材"), "profile")
|
||||
values = dict(
|
||||
bom_code=str(pick(row, "编码", "bom_code", default="") or ""),
|
||||
material=mat, spec=str(pick(row, "规格", "spec", default="") or ""),
|
||||
color=str(pick(row, "颜色", "color", default="") or ""),
|
||||
price=float(pick(row, "售价", "price", default=0) or 0),
|
||||
cost=float(pick(row, "成本", "cost", default=0) or 0),
|
||||
count=float(pick(row, "库存数量", "数量", "count", default=0) or 0),
|
||||
unit=str(pick(row, "单位", "unit", default="根") or "根"),
|
||||
length=int(float(pick(row, "单根长度mm", "长度", "length", default=0) or 0)),
|
||||
warning=float(pick(row, "预警线", "warning", default=0) or 0))
|
||||
if name in existing: # 同名更新(数量累加)
|
||||
item = existing[name]
|
||||
for k, v in values.items():
|
||||
if k == "count":
|
||||
continue
|
||||
setattr(item, k, v)
|
||||
item.count = float(item.count) + values["count"]
|
||||
updated += 1
|
||||
else:
|
||||
item = WarehouseItem(company_id=user.company_id, name=name, **values)
|
||||
db.add(item)
|
||||
existing[name] = item
|
||||
created += 1
|
||||
await db.flush()
|
||||
return ok(data={"created": created, "updated": updated, "skipped": skipped},
|
||||
message=f"导入完成:新增 {created} 条,更新 {updated} 条,跳过 {skipped} 条")
|
||||
|
||||
|
||||
@router.put("/{item_id}")
|
||||
async def update_item(item_id: int, body: ItemBody, user: CurrentUser, db: DbSession):
|
||||
"""编辑材料。"""
|
||||
|
||||
Reference in New Issue
Block a user