feat: 多工厂/厂间调拨/替代料/流水台账折叠 + 重写 README
- 多工厂(factory 表) + 顶栏工厂下拉过滤, 库存按厂聚合 - 厂间调拨(transfer): 源厂减+目标厂加, 总库存不变, 双向流水对账 - 替代料关系(substitute): 出库/BOM领料/生产领料缺料时提示可替代料 - 流水台账(ledger): 入出按日期合并折叠, 按日期导出同一 Excel - 生产订单增强: 编辑/删除(级联清关联流水)/调入数量/确认调入=厂间调拨 - 入库新增送货日期字段; 出库物料编码扫码定位 - 重写 README 补全全部模块; .gitignore 排除 dist_pkg/build_pkg/*.db - 停止跟踪 instance/app.db(运行时自动生成)
This commit is contained in:
+45
-3
@@ -118,7 +118,7 @@ def read_materials(path):
|
||||
return out
|
||||
|
||||
|
||||
def import_materials(rows, operator="导入"):
|
||||
def import_materials(rows, operator="导入", factory_id=None):
|
||||
"""upsert 物料; opening_stock>0 写入一条"期初"入库流水。返回 (新增, 更新)"""
|
||||
added = 0
|
||||
updated = 0
|
||||
@@ -138,11 +138,12 @@ def import_materials(rows, operator="导入"):
|
||||
op = d.get("opening_stock", 0) or 0
|
||||
if op > 0:
|
||||
exists = db.session.scalar(
|
||||
db.select(Inbound).where(Inbound.material_id == m.id, Inbound.source == "期初"))
|
||||
db.select(Inbound).where(Inbound.material_id == m.id, Inbound.source == "期初",
|
||||
Inbound.factory_id == factory_id))
|
||||
if exists:
|
||||
exists.qty = op
|
||||
else:
|
||||
db.session.add(Inbound(material_id=m.id, qty=op, source="期初",
|
||||
db.session.add(Inbound(material_id=m.id, factory_id=factory_id, qty=op, source="期初",
|
||||
occur_date=date.today(), operator=operator))
|
||||
db.session.commit()
|
||||
return added, updated
|
||||
@@ -256,6 +257,47 @@ def build_material_template(path):
|
||||
return path
|
||||
|
||||
|
||||
def export_ledger(target, rows):
|
||||
"""导出流水台账。target 可为路径或文件对象(如 BytesIO)。
|
||||
|
||||
rows: _build_ledger_rows() 返回的流水行列表, 每项含
|
||||
date/type/cat/m/qty/fac/who/ref/op 字段。
|
||||
"""
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = "流水台账"
|
||||
head = ["日期", "类型", "业务类型", "物料编号", "型号/规格", "单位",
|
||||
"数量", "工厂", "来源/用途", "调拨/关联单号", "经手人"]
|
||||
ws.append(head)
|
||||
for c in ws[1]:
|
||||
c.font = Font(bold=True)
|
||||
c.fill = PatternFill("solid", fgColor="DDEBF7")
|
||||
for r in rows:
|
||||
m = r["m"]
|
||||
ws.append([
|
||||
r["date"].isoformat() if r["date"] else "",
|
||||
r["type"],
|
||||
r["cat"],
|
||||
m.part_number if m else "",
|
||||
m.manufacturer if m else "",
|
||||
m.unit if m else "",
|
||||
r["qty"],
|
||||
r["fac"].code if r["fac"] else "",
|
||||
r["who"] or "",
|
||||
r["ref"] or "",
|
||||
r["op"] or "",
|
||||
])
|
||||
# 出入着色: 入(绿) / 出(红)
|
||||
for i in range(2, ws.max_row + 1):
|
||||
tval = ws.cell(row=i, column=2).value
|
||||
if tval == "入":
|
||||
ws.cell(row=i, column=2).fill = PatternFill("solid", fgColor="C6EFCE")
|
||||
elif tval == "出":
|
||||
ws.cell(row=i, column=2).fill = PatternFill("solid", fgColor="FFC7CE")
|
||||
ws.freeze_panes = "A2"
|
||||
wb.save(target)
|
||||
|
||||
|
||||
def build_bom_template(path):
|
||||
"""生成与真实 BOM 模板一致的格式(Altium/嘉立创导出):
|
||||
每个 worksheet 一个 BOM, sheet 名即 BOM 型号; 列头 Item/Part Number/
|
||||
|
||||
Reference in New Issue
Block a user