Dockerfile 部署

This commit is contained in:
hjq
2026-06-22 14:34:23 +08:00
parent 2b461c2aea
commit e751d53362
4 changed files with 299 additions and 73 deletions

View File

@@ -146,31 +146,36 @@ class MaterialReconciliationService:
conn.close()
return [dict(r) for r in rows]
def step3_bom_reconciliation(self, start_date=None, end_date=None):
def step3_bom_reconciliation(self, start_date=None, end_date=None, match_type='official'):
"""
第三步BOM 校准物料比对发料与BOM差异
返回按工单分组的差异列表
返回按工单分组的树形结构
"""
conn = self.get_conn()
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
where_actual = "WHERE inferred_production_order_no != '' AND inferred_production_order_no IS NOT NULL"
if match_type == 'official':
where_actual = "WHERE production_order_no != '' AND production_order_no IS NOT NULL"
sfc_field = "production_order_no"
else:
where_actual = "WHERE (production_order_no = '' OR production_order_no IS NULL) AND inferred_production_order_no != '' AND inferred_production_order_no IS NOT NULL"
sfc_field = "inferred_production_order_no"
params_actual = []
if start_date and end_date:
where_actual += " AND execution_time >= ? AND execution_time <= ?"
params_actual.extend([f"{start_date} 00:00:00", f"{end_date} 23:59:59"])
sql_actual = f"""
SELECT
inferred_production_order_no as sfc,
{sfc_field} as sfc,
material_code,
material_name,
SUM(issue_number) as total_actual_issue
FROM issue_receipt_details
{where_actual}
GROUP BY inferred_production_order_no, material_code
GROUP BY {sfc_field}, material_code
"""
where_bom = ""
@@ -182,6 +187,8 @@ class MaterialReconciliationService:
sql_bom = f"""
SELECT
work_orders_number as sfc,
product_code,
product_name,
material_code,
material_name,
theoretical_issue_qty as bom_qty
@@ -195,45 +202,186 @@ class MaterialReconciliationService:
cursor.execute(sql_bom, params_bom)
boms = cursor.fetchall()
conn.close()
reconciliation = {}
sfc_to_product = {}
sfc_bom_dict = {} # {sfc: {material_code: {qty, name}}}
for r in boms:
key = (r['sfc'], r['material_code'])
reconciliation[key] = {
'sfc': r['sfc'],
'material_code': r['material_code'],
'material_name': r['material_name'],
sfc = r['sfc']
mat_code = r['material_code']
if sfc not in sfc_bom_dict:
sfc_bom_dict[sfc] = {}
sfc_bom_dict[sfc][mat_code] = {
'bom_qty': r['bom_qty'] or 0,
'actual_qty': 0,
'diff_qty': 0 - (r['bom_qty'] or 0),
'status': '未发料'
'material_name': r['material_name']
}
if r['product_code']:
sfc_to_product[sfc] = {
'product_code': r['product_code'],
'product_name': r['product_name']
}
sfc_actual_dict = {} # {sfc: {material_code: {qty, name}}}
for r in actuals:
sfc = r['sfc']
mat_code = r['material_code']
if sfc not in sfc_actual_dict:
sfc_actual_dict[sfc] = {}
sfc_actual_dict[sfc][mat_code] = {
'actual_qty': r['total_actual_issue'] or 0,
'material_name': r['material_name']
}
for r in actuals:
key = (r['sfc'], r['material_code'])
if key not in reconciliation:
reconciliation[key] = {
'sfc': r['sfc'],
'material_code': r['material_code'],
'material_name': r['material_name'],
'bom_qty': 0,
'actual_qty': 0,
'diff_qty': 0,
'status': 'BOM外发料'
}
reconciliation[key]['actual_qty'] += r['total_actual_issue']
reconciliation[key]['diff_qty'] = round(reconciliation[key]['actual_qty'] - reconciliation[key]['bom_qty'], 4)
if match_type == 'official':
all_sfcs = set(sfc_bom_dict.keys()).union(set(sfc_actual_dict.keys()))
else:
all_sfcs = set(sfc_actual_dict.keys())
# 预先获取所有的 product_code 的 bom_child 树结构
product_codes = [p['product_code'] for p in sfc_to_product.values() if p['product_code']]
bom_trees = {} # {product_code: list of root children}
if product_codes:
unique_p_codes = list(set(product_codes))
placeholders = ','.join(['?'] * len(unique_p_codes))
cursor.execute(f"""
SELECT id, parent_material_code, node_material_code, node_material_name, bom_level, parent_node_id, usage_qty
FROM bom_child
WHERE parent_material_code IN ({placeholders})
""", unique_p_codes)
all_bom_children = cursor.fetchall()
if reconciliation[key]['diff_qty'] > 0 and reconciliation[key]['bom_qty'] > 0:
reconciliation[key]['status'] = '超领发料'
elif reconciliation[key]['diff_qty'] < 0:
reconciliation[key]['status'] = '少领发料'
elif reconciliation[key]['diff_qty'] == 0 and reconciliation[key]['bom_qty'] > 0:
reconciliation[key]['status'] = '发料正常'
from collections import defaultdict
child_by_product = defaultdict(list)
for row in all_bom_children:
child_by_product[row['parent_material_code']].append(dict(row))
return list(reconciliation.values())
for p_code, children in child_by_product.items():
nodes_map = {}
for row in children:
nodes_map[row['id']] = {
'id': f"node_{row['id']}",
'material_code': row['node_material_code'],
'material_name': row['node_material_name'],
'bom_level': row['bom_level'],
'parent_node_id': row['parent_node_id'],
'bom_qty': 0,
'actual_qty': 0,
'diff_qty': 0,
'status': '',
'children': []
}
root_children = []
for row in children:
node_id = row['id']
parent_id = row['parent_node_id']
if parent_id is None:
root_children.append(nodes_map[node_id])
else:
if parent_id in nodes_map:
nodes_map[parent_id]['children'].append(nodes_map[node_id])
bom_trees[p_code] = root_children
conn.close()
result = []
for sfc in all_sfcs:
prod_info = sfc_to_product.get(sfc, {})
p_code = prod_info.get('product_code', '')
p_name = prod_info.get('product_name', '无工单关联产品')
sfc_node = {
'id': f"sfc_{sfc}",
'sfc': sfc,
'material_code': p_code,
'material_name': f"【成品】{p_name}",
'bom_qty': '',
'actual_qty': '',
'diff_qty': '',
'status': '',
'children': []
}
boms_for_sfc = sfc_bom_dict.get(sfc, {})
actuals_for_sfc = sfc_actual_dict.get(sfc, {})
processed_materials = set()
import copy
if p_code in bom_trees:
tree_clone = copy.deepcopy(bom_trees[p_code])
def fill_tree_qty(nodes, sfc_id):
for idx, node in enumerate(nodes):
node['id'] = f"{sfc_id}_{node['id']}_{idx}" # 确保前端树节点的唯一性
m_code = node['material_code']
bom_info = boms_for_sfc.get(m_code, {})
act_info = actuals_for_sfc.get(m_code, {})
node['bom_qty'] = bom_info.get('bom_qty', 0)
node['actual_qty'] = act_info.get('actual_qty', 0)
node['diff_qty'] = round(node['actual_qty'] - node['bom_qty'], 4)
if node['diff_qty'] > 0 and node['bom_qty'] > 0:
node['status'] = '超领发料'
elif node['diff_qty'] < 0:
node['status'] = '少领发料'
elif node['diff_qty'] == 0 and node['bom_qty'] > 0:
node['status'] = '发料正常'
elif node['bom_qty'] == 0 and node['actual_qty'] > 0:
node['status'] = 'BOM外发料'
elif node['bom_qty'] > 0 and node['actual_qty'] == 0:
node['status'] = '未发料'
else:
node['status'] = '无发料任务'
processed_materials.add(m_code)
fill_tree_qty(node['children'], sfc_id)
fill_tree_qty(tree_clone, sfc)
sfc_node['children'].extend(tree_clone)
all_m_codes_for_sfc = set(boms_for_sfc.keys()).union(set(actuals_for_sfc.keys()))
extra_materials = all_m_codes_for_sfc - processed_materials
# 将多出的(或者没有树结构的)物料直接作为工单的子节点
for i, m_code in enumerate(extra_materials):
bom_info = boms_for_sfc.get(m_code, {})
act_info = actuals_for_sfc.get(m_code, {})
bom_q = bom_info.get('bom_qty', 0)
act_q = act_info.get('actual_qty', 0)
m_name = bom_info.get('material_name') or act_info.get('material_name') or '未知名称'
diff_q = round(act_q - bom_q, 4)
status = ''
if diff_q > 0 and bom_q > 0:
status = '超领发料'
elif diff_q < 0:
status = '少领发料'
elif diff_q == 0 and bom_q > 0:
status = '发料正常'
elif bom_q == 0 and act_q > 0:
status = 'BOM外发料'
elif bom_q > 0 and act_q == 0:
status = '未发料'
sfc_node['children'].append({
'id': f"extra_{sfc}_{m_code}_{i}",
'sfc': '',
'material_code': m_code,
'material_name': m_name,
'bom_qty': bom_q,
'actual_qty': act_q,
'diff_qty': diff_q,
'status': status,
'children': []
})
result.append(sfc_node)
# 根据工单号倒序排列
result.sort(key=lambda x: x['sfc'], reverse=True)
return result
if __name__ == '__main__':
service = MaterialReconciliationService()