503 lines
19 KiB
Python
503 lines
19 KiB
Python
import sqlite3
|
||
import json
|
||
from pathlib import Path
|
||
import os
|
||
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():
|
||
"""初始化数据库并创建表"""
|
||
conn = sqlite3.connect(DB_PATH)
|
||
cursor = conn.cursor()
|
||
|
||
# 创建收货明细表
|
||
cursor.execute('''
|
||
CREATE TABLE IF NOT EXISTS 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
|
||
)
|
||
''')
|
||
|
||
# 为收货明细表创建索引以加速查询
|
||
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)')
|
||
|
||
# 创建发料明细表
|
||
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 (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
parent_material_code TEXT UNIQUE,
|
||
parent_material_name TEXT
|
||
)
|
||
''')
|
||
|
||
cursor.execute('''
|
||
CREATE TABLE IF NOT EXISTS bom_child (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
parent_material_code TEXT, -- 归属的最顶层父件
|
||
node_material_code TEXT,
|
||
node_material_name TEXT,
|
||
bom_level INTEGER,
|
||
parent_node_id INTEGER, -- 指向上一级子件的 id,如果是一级子件则为空
|
||
usage_qty REAL DEFAULT 1.0,
|
||
FOREIGN KEY(parent_material_code) REFERENCES bom_parent(parent_material_code),
|
||
FOREIGN KEY(parent_node_id) REFERENCES bom_child(id)
|
||
)
|
||
''')
|
||
|
||
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
|
||
|
||
def import_receipt_details(conn):
|
||
"""导入收货明细数据"""
|
||
if not RECEIPT_JSON.exists():
|
||
print(f"找不到收货明细文件: {RECEIPT_JSON}")
|
||
return
|
||
|
||
print("开始导入收货明细数据...")
|
||
with open(RECEIPT_JSON, 'r', encoding='utf-8') as f:
|
||
data = json.load(f)
|
||
|
||
cursor = conn.cursor()
|
||
|
||
# 删除历史可能存在的重复数据,确保唯一索引能够成功创建
|
||
cursor.execute('''
|
||
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)
|
||
''')
|
||
|
||
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("物料代码")
|
||
|
||
# 容错:如果关键字段为空则跳过
|
||
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_inserted} 条收货明细数据!")
|
||
|
||
def _insert_bom_tree(cursor, parent_material_code, tree_nodes, parent_node_id=None):
|
||
"""递归插入 BOM 树节点"""
|
||
for node in tree_nodes:
|
||
# 提取当前节点信息
|
||
node_code = node.get("childMaterialCode")
|
||
node_name = node.get("childMaterialName")
|
||
bom_level = node.get("bomLevel")
|
||
usage_qty = float(node.get("usageQty") or 1.0)
|
||
|
||
# 插入当前节点
|
||
cursor.execute('''
|
||
INSERT INTO bom_child (
|
||
parent_material_code, node_material_code, node_material_name, bom_level, parent_node_id, usage_qty
|
||
) VALUES (?, ?, ?, ?, ?, ?)
|
||
''', (parent_material_code, node_code, node_name, bom_level, parent_node_id, usage_qty))
|
||
|
||
# 获取刚插入的节点 ID,作为其子节点的 parent_node_id
|
||
current_node_id = cursor.lastrowid
|
||
|
||
# 如果有子节点,递归插入
|
||
sub_items = node.get("sub_items", [])
|
||
if sub_items:
|
||
_insert_bom_tree(cursor, parent_material_code, sub_items, current_node_id)
|
||
|
||
def import_bom_data(conn):
|
||
"""导入 BOM 成本树状数据"""
|
||
if not BOM_JSON.exists():
|
||
print(f"找不到 BOM 成本文件: {BOM_JSON}")
|
||
return
|
||
|
||
print("开始导入 BOM 成本数据...")
|
||
with open(BOM_JSON, 'r', encoding='utf-8') as f:
|
||
data = json.load(f)
|
||
|
||
cursor = conn.cursor()
|
||
# 清空旧数据
|
||
cursor.execute('DELETE FROM bom_child')
|
||
cursor.execute('DELETE FROM bom_parent')
|
||
|
||
parent_count = 0
|
||
for parent in data:
|
||
parent_code = parent.get("parentMaterialCode")
|
||
parent_name = parent.get("parentMaterialName")
|
||
|
||
# 忽略空父件
|
||
if not parent_code:
|
||
continue
|
||
|
||
try:
|
||
cursor.execute('''
|
||
INSERT INTO bom_parent (parent_material_code, parent_material_name)
|
||
VALUES (?, ?)
|
||
''', (parent_code, parent_name))
|
||
parent_count += 1
|
||
|
||
# 递归处理这棵树
|
||
tree = parent.get("bom_cost_tree", [])
|
||
if tree:
|
||
_insert_bom_tree(cursor, parent_code, tree, parent_node_id=None)
|
||
|
||
except sqlite3.IntegrityError:
|
||
print(f"警告: 父件重复 {parent_code},跳过")
|
||
|
||
conn.commit()
|
||
|
||
# 统计插入的子件数量
|
||
cursor.execute('SELECT COUNT(*) FROM bom_child')
|
||
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 时,如果发生了 UPDATE,rowcount 也是 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}")
|
||
conn = init_db()
|
||
|
||
# 允许通过命令行参数单独导入某一部分数据
|
||
args = sys.argv[1:]
|
||
|
||
if "--bom-only" in args:
|
||
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 查看数据。") |