抓取生产工单,赚取发料异常

This commit is contained in:
hjq
2026-06-11 15:58:56 +08:00
parent 66eecd0daa
commit 5b19790037
40 changed files with 4942 additions and 54 deletions

View File

@@ -6,6 +6,7 @@ from config import OUTPUT_DIR, DB_PATH
RECEIPT_JSON = OUTPUT_DIR / "receipt_details_full_clean.json"
BOM_JSON = OUTPUT_DIR / "bom_cost_full_tree_final.json"
ISSUE_JSON = OUTPUT_DIR / "issue_receipt_details_full.json"
def init_db():
"""初始化数据库并创建表"""
@@ -40,6 +41,68 @@ def init_db():
cursor.execute('CREATE INDEX IF NOT EXISTS idx_receipt_supplier_name ON receipt_details(supplier_name)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_receipt_time ON receipt_details(receipt_time)')
# 创建发料明细表
cursor.execute('''
CREATE TABLE IF NOT EXISTS issue_receipt_details (
id INTEGER PRIMARY KEY AUTOINCREMENT,
production_order_no TEXT,
product_material_code TEXT,
product_material_name TEXT,
product_material_specification TEXT,
work_orders_number TEXT,
status INTEGER,
material_specification TEXT,
material_name TEXT,
material_code TEXT,
issue_number REAL,
has_issue_number REAL,
amount REAL,
cost_price REAL,
issue_amount REAL,
production_order_remark TEXT,
detailed_remark TEXT,
unit_name TEXT,
warehouse_name TEXT,
line_number INTEGER,
work_orders_remark TEXT,
executor_user_name TEXT,
material_model TEXT,
execution_time TEXT,
materials_user_name TEXT,
product_material_model TEXT,
custom_field TEXT,
department_information_code TEXT,
department_information_name TEXT,
image_file TEXT,
issue_amount_total REAL,
material_group_code TEXT,
material_group_name TEXT,
numnber_of_reserved_digits INTEGER,
place_ment_strategy INTEGER,
price REAL,
sales_order_code TEXT
)
''')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_issue_material_code ON issue_receipt_details(material_code)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_issue_execution_time ON issue_receipt_details(execution_time)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_issue_work_orders_number ON issue_receipt_details(work_orders_number)')
# 删除历史可能存在的重复数据,确保唯一索引能够成功创建
cursor.execute('''
DELETE FROM issue_receipt_details
WHERE id NOT IN (
SELECT MIN(id)
FROM issue_receipt_details
GROUP BY work_orders_number, line_number, material_code
)
''')
# 为发料明细表创建唯一索引,用于 UPSERT 冲突检测
cursor.execute('''
CREATE UNIQUE INDEX IF NOT EXISTS idx_issue_unique
ON issue_receipt_details(work_orders_number, line_number, material_code)
''')
# 注意:为了在打包部署时不丢失用户已抓取的数据,改为 IF NOT EXISTS
cursor.execute('''
CREATE TABLE IF NOT EXISTS bom_parent (
@@ -66,6 +129,81 @@ def init_db():
cursor.execute('CREATE INDEX IF NOT EXISTS idx_bom_child_parent_code ON bom_child(parent_material_code)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_bom_child_node_code ON bom_child(node_material_code)')
# 创建生产工单表
cursor.execute('''
CREATE TABLE IF NOT EXISTS work_orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
work_orders_number TEXT,
line_number INTEGER,
material_code TEXT,
material_name TEXT,
material_specification TEXT,
status INTEGER,
unit_name TEXT,
cost_price REAL,
issue_number REAL,
total_issue_number REAL,
issue_amount REAL,
issue_amount_total REAL,
executor_user_name TEXT,
execution_time TEXT,
production_order_no TEXT,
warehouse_name TEXT,
materials_user_name TEXT,
work_orders_remark TEXT
)
''')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_work_orders_number ON work_orders(work_orders_number)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_work_orders_material_code ON work_orders(material_code)')
# 删除可能存在的重复数据
cursor.execute('''
DELETE FROM work_orders
WHERE id NOT IN (
SELECT MIN(id)
FROM work_orders
GROUP BY work_orders_number, line_number, material_code
)
''')
# 唯一索引
cursor.execute('''
CREATE UNIQUE INDEX IF NOT EXISTS idx_work_orders_unique
ON work_orders(work_orders_number, line_number, material_code)
''')
# 创建发料异常报表表
cursor.execute('''
CREATE TABLE IF NOT EXISTS abnormal_report (
id INTEGER PRIMARY KEY AUTOINCREMENT,
work_orders_number TEXT,
product_code TEXT,
product_name TEXT,
status TEXT,
completed_qty REAL,
order_date TEXT,
workshop TEXT,
material_code TEXT,
material_name TEXT,
material_specification TEXT,
unit_qty REAL,
total_demand_qty REAL,
warehouse_issue_qty REAL,
theoretical_issue_qty REAL,
issue_method TEXT,
issue_status TEXT
)
''')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_abnormal_work_orders_number ON abnormal_report(work_orders_number)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_abnormal_material_code ON abnormal_report(material_code)')
cursor.execute('''
CREATE UNIQUE INDEX IF NOT EXISTS idx_abnormal_unique
ON abnormal_report(work_orders_number, material_code)
''')
conn.commit()
return conn
@@ -80,68 +218,74 @@ def import_receipt_details(conn):
data = json.load(f)
cursor = conn.cursor()
# 清空旧数据(如果需要重复运行),并且我们现在要更新表结构
cursor.execute('DROP TABLE IF EXISTS receipt_details')
# 删除历史可能存在的重复数据,确保唯一索引能够成功创建
cursor.execute('''
CREATE TABLE receipt_details (
id INTEGER PRIMARY KEY AUTOINCREMENT,
purchase_order_code TEXT,
row_no INTEGER,
material_code TEXT,
material_name TEXT,
material_specification TEXT,
warehouse_code TEXT,
warehouse_name TEXT,
supplier_code TEXT,
supplier_name TEXT,
unit_name TEXT,
conversion_unit TEXT,
receive_price REAL,
receipt_time TEXT,
purchase_qty REAL,
receive_qty REAL,
total_amount REAL
)
DELETE FROM receipt_details
WHERE id NOT IN (
SELECT MIN(id)
FROM receipt_details
GROUP BY purchase_order_code, row_no, material_code
)
''')
# 为了避免没有唯一索引导致 UPSERT 报错,这里显式创建一次
cursor.execute('''
CREATE UNIQUE INDEX IF NOT EXISTS idx_receipt_unique
ON receipt_details(purchase_order_code, row_no, material_code)
''')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_receipt_material_code ON receipt_details(material_code)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_receipt_supplier_name ON receipt_details(supplier_name)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_receipt_time ON receipt_details(receipt_time)')
count = 0
count_inserted = 0
for item in data:
p_qty = item.get("进货数量")
r_qty = item.get("收货数量")
po_code = item.get("采购订单号")
row_no = item.get("行号")
mat_code = item.get("物料代码")
cursor.execute('''
INSERT INTO receipt_details (
purchase_order_code, row_no, material_code, material_name,
material_specification, warehouse_code, warehouse_name,
supplier_code, supplier_name, unit_name, conversion_unit,
receive_price, receipt_time,
purchase_qty, receive_qty, total_amount
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (
item.get("采购订单号"),
item.get("行号"),
item.get("物料代码"),
item.get("物料名称"),
item.get("物料规格"),
item.get("仓库代码"),
item.get("仓库名称"),
item.get("供应商代码"),
item.get("供应商名称"),
item.get("单位名称"),
item.get("转换单位"),
item.get("收货单价"),
item.get("收货时间"),
p_qty,
r_qty,
item.get("收货总金额")
))
count += 1
# 容错:如果关键字段为空则跳过
if not po_code or not row_no or not mat_code:
continue
try:
cursor.execute('''
INSERT INTO receipt_details (
purchase_order_code, row_no, material_code, material_name,
material_specification, warehouse_code, warehouse_name,
supplier_code, supplier_name, unit_name, conversion_unit,
receive_price, receipt_time,
purchase_qty, receive_qty, total_amount
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(purchase_order_code, row_no, material_code) DO UPDATE SET
receive_price=excluded.receive_price,
receive_qty=excluded.receive_qty,
total_amount=excluded.total_amount,
receipt_time=excluded.receipt_time
''', (
po_code,
row_no,
mat_code,
item.get("物料名称"),
item.get("物料规格"),
item.get("仓库代码"),
item.get("仓库名称"),
item.get("供应商代码"),
item.get("供应商名称"),
item.get("单位名称"),
item.get("转换单位"),
item.get("收货单价"),
item.get("收货时间"),
p_qty,
r_qty,
item.get("收货总金额")
))
count_inserted += 1
except sqlite3.Error as e:
print(f"入库报错 收货单:{po_code} 行号:{row_no} 错误:{e}")
conn.commit()
print(f"成功导入 {count} 条收货明细数据!")
print(f"成功处理(新增或更新) {count_inserted} 条收货明细数据!")
def _insert_bom_tree(cursor, parent_material_code, tree_nodes, parent_node_id=None):
"""递归插入 BOM 树节点"""
@@ -213,6 +357,128 @@ def import_bom_data(conn):
child_count = cursor.fetchone()[0]
print(f"成功导入 {parent_count} 个 BOM 父件,包含 {child_count} 个子件节点!")
def import_issue_receipt_details(conn):
"""导入发料明细数据"""
if not ISSUE_JSON.exists():
print(f"找不到发料明细文件: {ISSUE_JSON}")
return
print("开始导入发料明细数据...")
with open(ISSUE_JSON, 'r', encoding='utf-8') as f:
data = json.load(f)
cursor = conn.cursor()
# 为了避免没有唯一索引导致 UPSERT 报错,这里显式创建一次
cursor.execute('''
CREATE UNIQUE INDEX IF NOT EXISTS idx_issue_unique
ON issue_receipt_details(work_orders_number, line_number, material_code)
''')
count_inserted = 0
count_updated = 0
for item in data:
# 提取关键字段
wo_number = item.get("发料单号")
line_no = item.get("行号")
mat_code = item.get("物料代码")
# 容错:如果关键字段为空则跳过
if not wo_number or not line_no or not mat_code:
continue
try:
cursor.execute('''
INSERT INTO issue_receipt_details (
production_order_no, product_material_code, product_material_name, product_material_specification,
work_orders_number, status, material_specification, material_name, material_code,
issue_number, has_issue_number, amount, cost_price, issue_amount,
production_order_remark, detailed_remark, unit_name, warehouse_name, line_number,
work_orders_remark, executor_user_name, material_model, execution_time, materials_user_name,
product_material_model, custom_field, department_information_code, department_information_name,
image_file, issue_amount_total, material_group_code, material_group_name,
numnber_of_reserved_digits, place_ment_strategy, price, sales_order_code
) VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
ON CONFLICT(work_orders_number, line_number, material_code) DO UPDATE SET
status=excluded.status,
issue_number=excluded.issue_number,
has_issue_number=excluded.has_issue_number,
amount=excluded.amount,
cost_price=excluded.cost_price,
issue_amount=excluded.issue_amount,
warehouse_name=excluded.warehouse_name,
executor_user_name=excluded.executor_user_name,
execution_time=excluded.execution_time,
materials_user_name=excluded.materials_user_name,
issue_amount_total=excluded.issue_amount_total
''', (
item.get("生产任务单号"), item.get("生产物料代码"), item.get("生产物料名称"), item.get("生产物料规格"),
wo_number, item.get("状态"), item.get("物料规格"), item.get("物料名称"), mat_code,
item.get("发料数量"), item.get("已发料数量"), item.get("金额"), item.get("成本价"), item.get("发料金额"),
item.get("生产订单备注"), item.get("明细备注"), item.get("单位名称"), item.get("仓库名称"), line_no,
item.get("发料单备注"), item.get("执行人名称"), item.get("物料型号"), item.get("执行时间"), item.get("领料人"),
item.get("生产物料型号"), item.get("自定义字段"), item.get("部门代码"), item.get("部门名称"),
item.get("图片文件"), item.get("汇总金额"), item.get("物料组代码"), item.get("物料组名称"),
item.get("单价小数位数"), item.get("单价进位策略"), item.get("单价"), item.get("销售订单号")
))
# 由于 sqlite3 在 UPSERT 时,如果发生了 UPDATErowcount 也是 1
# 但我们可以通过比较变化前后的总数来粗略估计,或者统一提示处理成功
count_inserted += 1
except sqlite3.Error as e:
print(f"入库报错 发料单:{wo_number} 行号:{line_no} 错误:{e}")
conn.commit()
print(f"成功处理(新增或更新) {count_inserted} 条发料明细数据!")
def import_abnormal_report_data(items):
"""直接将 API 获取到的异常报表 items 数组存入数据库"""
conn = init_db()
cursor = conn.cursor()
count_inserted = 0
for item in items:
wo_number = item.get("生产工单号")
mat_code = item.get("需求物料代码")
if not wo_number or not mat_code:
continue
try:
cursor.execute('''
INSERT INTO abnormal_report (
work_orders_number, product_code, product_name, status, completed_qty,
order_date, workshop, material_code, material_name, material_specification,
unit_qty, total_demand_qty, warehouse_issue_qty, theoretical_issue_qty,
issue_method, issue_status
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(work_orders_number, material_code) DO UPDATE SET
status=excluded.status,
completed_qty=excluded.completed_qty,
warehouse_issue_qty=excluded.warehouse_issue_qty,
theoretical_issue_qty=excluded.theoretical_issue_qty,
issue_status=excluded.issue_status
''', (
wo_number, item.get("产品代码"), item.get("产品名称"), item.get("工单状态"),
item.get("已完工数量"), item.get("下单日期"), item.get("生产车间"),
mat_code, item.get("需求物料名称"), item.get("需求物料规格"),
item.get("单机用量"), item.get("需求总量"), item.get("仓库发放数量"),
item.get("理论仓库出料数量"), item.get("发料方式"), item.get("发料情况")
))
count_inserted += 1
except sqlite3.Error as e:
print(f"异常报表入库报错 工单:{wo_number} 物料:{mat_code} 错误:{e}")
conn.commit()
conn.close()
return count_inserted
if __name__ == "__main__":
import sys
print(f"数据库文件将保存在: {DB_PATH}")
@@ -225,10 +491,13 @@ if __name__ == "__main__":
import_bom_data(conn)
elif "--receipt-only" in args:
import_receipt_details(conn)
elif "--issue-only" in args:
import_issue_receipt_details(conn)
else:
# 默认全量导入
import_receipt_details(conn)
import_bom_data(conn)
import_issue_receipt_details(conn)
conn.close()
print("全部导入完成!你可以使用 SQLite 客户端连接 erp_data.db 查看数据。")