Dockerfile 部署
This commit is contained in:
@@ -146,31 +146,36 @@ class MaterialReconciliationService:
|
|||||||
conn.close()
|
conn.close()
|
||||||
return [dict(r) for r in rows]
|
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差异
|
第三步:BOM 校准物料,比对发料与BOM差异
|
||||||
返回按工单分组的差异列表。
|
返回按工单分组的树形结构。
|
||||||
"""
|
"""
|
||||||
conn = self.get_conn()
|
conn = self.get_conn()
|
||||||
conn.row_factory = sqlite3.Row
|
conn.row_factory = sqlite3.Row
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
where_actual = "WHERE inferred_production_order_no != '' AND inferred_production_order_no IS NOT NULL"
|
if match_type == 'official':
|
||||||
params_actual = []
|
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:
|
if start_date and end_date:
|
||||||
where_actual += " AND execution_time >= ? AND execution_time <= ?"
|
where_actual += " AND execution_time >= ? AND execution_time <= ?"
|
||||||
params_actual.extend([f"{start_date} 00:00:00", f"{end_date} 23:59:59"])
|
params_actual.extend([f"{start_date} 00:00:00", f"{end_date} 23:59:59"])
|
||||||
|
|
||||||
sql_actual = f"""
|
sql_actual = f"""
|
||||||
SELECT
|
SELECT
|
||||||
inferred_production_order_no as sfc,
|
{sfc_field} as sfc,
|
||||||
material_code,
|
material_code,
|
||||||
material_name,
|
material_name,
|
||||||
SUM(issue_number) as total_actual_issue
|
SUM(issue_number) as total_actual_issue
|
||||||
FROM issue_receipt_details
|
FROM issue_receipt_details
|
||||||
{where_actual}
|
{where_actual}
|
||||||
GROUP BY inferred_production_order_no, material_code
|
GROUP BY {sfc_field}, material_code
|
||||||
"""
|
"""
|
||||||
|
|
||||||
where_bom = ""
|
where_bom = ""
|
||||||
@@ -182,6 +187,8 @@ class MaterialReconciliationService:
|
|||||||
sql_bom = f"""
|
sql_bom = f"""
|
||||||
SELECT
|
SELECT
|
||||||
work_orders_number as sfc,
|
work_orders_number as sfc,
|
||||||
|
product_code,
|
||||||
|
product_name,
|
||||||
material_code,
|
material_code,
|
||||||
material_name,
|
material_name,
|
||||||
theoretical_issue_qty as bom_qty
|
theoretical_issue_qty as bom_qty
|
||||||
@@ -195,45 +202,186 @@ class MaterialReconciliationService:
|
|||||||
cursor.execute(sql_bom, params_bom)
|
cursor.execute(sql_bom, params_bom)
|
||||||
boms = cursor.fetchall()
|
boms = cursor.fetchall()
|
||||||
|
|
||||||
conn.close()
|
sfc_to_product = {}
|
||||||
|
sfc_bom_dict = {} # {sfc: {material_code: {qty, name}}}
|
||||||
reconciliation = {}
|
|
||||||
|
|
||||||
for r in boms:
|
for r in boms:
|
||||||
key = (r['sfc'], r['material_code'])
|
sfc = r['sfc']
|
||||||
reconciliation[key] = {
|
mat_code = r['material_code']
|
||||||
'sfc': r['sfc'],
|
if sfc not in sfc_bom_dict:
|
||||||
'material_code': r['material_code'],
|
sfc_bom_dict[sfc] = {}
|
||||||
'material_name': r['material_name'],
|
sfc_bom_dict[sfc][mat_code] = {
|
||||||
'bom_qty': r['bom_qty'] or 0,
|
'bom_qty': r['bom_qty'] or 0,
|
||||||
'actual_qty': 0,
|
'material_name': r['material_name']
|
||||||
'diff_qty': 0 - (r['bom_qty'] or 0),
|
}
|
||||||
'status': '未发料'
|
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:
|
if match_type == 'official':
|
||||||
key = (r['sfc'], r['material_code'])
|
all_sfcs = set(sfc_bom_dict.keys()).union(set(sfc_actual_dict.keys()))
|
||||||
if key not in reconciliation:
|
else:
|
||||||
reconciliation[key] = {
|
all_sfcs = set(sfc_actual_dict.keys())
|
||||||
'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 reconciliation[key]['diff_qty'] > 0 and reconciliation[key]['bom_qty'] > 0:
|
# 预先获取所有的 product_code 的 bom_child 树结构
|
||||||
reconciliation[key]['status'] = '超领发料'
|
product_codes = [p['product_code'] for p in sfc_to_product.values() if p['product_code']]
|
||||||
elif reconciliation[key]['diff_qty'] < 0:
|
bom_trees = {} # {product_code: list of root children}
|
||||||
reconciliation[key]['status'] = '少领发料'
|
if product_codes:
|
||||||
elif reconciliation[key]['diff_qty'] == 0 and reconciliation[key]['bom_qty'] > 0:
|
unique_p_codes = list(set(product_codes))
|
||||||
reconciliation[key]['status'] = '发料正常'
|
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()
|
||||||
|
|
||||||
return list(reconciliation.values())
|
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))
|
||||||
|
|
||||||
|
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__':
|
if __name__ == '__main__':
|
||||||
service = MaterialReconciliationService()
|
service = MaterialReconciliationService()
|
||||||
|
|||||||
57
update_html.py
Normal file
57
update_html.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
with open('web_ui/templates/reconciliation.html', 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Replace activeTab
|
||||||
|
content = content.replace("activeTab: 'unmatched'", "activeTab: 'official'")
|
||||||
|
|
||||||
|
# Replace the HTML for Tabs
|
||||||
|
tabs_start = '<el-tabs v-model="activeTab" type="border-card" @tab-click="handleTabClick">'
|
||||||
|
tabs_end = '</el-tabs>'
|
||||||
|
|
||||||
|
start_idx = content.find(tabs_start)
|
||||||
|
end_idx = content.find(tabs_end, start_idx) + len(tabs_end)
|
||||||
|
|
||||||
|
if start_idx != -1 and end_idx != -1:
|
||||||
|
new_tabs = """<el-tabs v-model="activeTab" type="border-card" @tab-click="handleTabClick">
|
||||||
|
<!-- Tab 1: 工单发料明细 -->
|
||||||
|
<el-tab-pane label="工单发料明细" name="official">
|
||||||
|
<span slot="label"><i class="el-icon-document"></i> 工单发料明细</span>
|
||||||
|
|
||||||
|
<div class="filter-row">
|
||||||
|
<el-input v-modimport re
|
||||||
|
|
||||||
|
with open('web_ui/templates/reconcil??with op/? content = f.read()
|
||||||
|
|
||||||
|
# Replace activeTab
|
||||||
|
content = content.replace("activeTab: 'unmatched' <
|
||||||
|
# Replace activeTab
|
||||||
|
ficcontent = content.la
|
||||||
|
# Replace the HTML for Tabs
|
||||||
|
tabs_start = '<el-tabs v-model="activeTab" type="border-caiontabs_sta全部" value=""></etabs_end = '</el-tabs>'
|
||||||
|
|
||||||
|
start_idx = content.find(tabs_start)
|
||||||
|
end_idx = content.find(tabs_ti
|
||||||
|
start_idx = content.find(tabs <end_idx = content.find(tabs_end, ste=
|
||||||
|
if start_idx != -1 and end_idx != -1:
|
||||||
|
new_tabs = """<l-o new_tabs = """<el-tabs v-model="?? <!-- Tab 1: 工单发料明细 -->
|
||||||
|
<el-tab-pane label="工单? <el-tab-pane label="工单发料?t <span slot="label"><i class="el-icon-document"></i>
|
||||||
|
<div class="filter-row">
|
||||||
|
<el-in @ al <el-input v-modimpo;"
|
||||||
|
with open('web_ui/templates/reconcil??wit/di
|
||||||
|
# Replace activeTab
|
||||||
|
content = content.replace("activeTab: 'unmatcheddincontent = content.=# Replace activeTab
|
||||||
|
ficcontent = content.la
|
||||||
|
# Replace the n:ficcontent = conte# Replace the HTML forthtabs_start = '<el-tabs"
|
||||||
|
start_idx = content.find(tabs_start)
|
||||||
|
end_idx = content.find(tabs_ti
|
||||||
|
start_idx = content.find(tabs <end_idx = content.find"sfend_idx = content.find(tabs_ti
|
||||||
|
star50start_idx = content.find(tabs
|
||||||
|
if start_idx != -1 and end_idx != -1:
|
||||||
|
new_tabs = """<l-o new"? new_tabs = """<l-o new_tabs =ol <el-tab-pane label="工单? <el-tab-pane label="工单发料?t ov <div class="filter-row">
|
||||||
|
<el-in @ al <el-input v-modimpo;"
|
||||||
|
with open('web_ui/templates/reconcil??"bom_qty" label="BOM 应发量" width="110" aliwith open('web_ui/templates/reconcil??wit/di
|
||||||
|
# Replace activeTab
|
||||||
|
content = content.replace(" # Replace activeTab
|
||||||
|
content = content.replace("a"content = content.beficcontent = content.la
|
||||||
@@ -375,15 +375,11 @@ def get_bom_reconciliation():
|
|||||||
try:
|
try:
|
||||||
start_date = request.args.get('start')
|
start_date = request.args.get('start')
|
||||||
end_date = request.args.get('end')
|
end_date = request.args.get('end')
|
||||||
|
match_type = request.args.get('match_type', 'official')
|
||||||
sys.path.insert(0, str(BROWSER_LOGIN_DIR))
|
sys.path.insert(0, str(BROWSER_LOGIN_DIR))
|
||||||
from analysis_service import MaterialReconciliationService
|
from analysis_service import MaterialReconciliationService
|
||||||
service = MaterialReconciliationService()
|
service = MaterialReconciliationService()
|
||||||
data = service.step3_bom_reconciliation(start_date, end_date)
|
data = service.step3_bom_reconciliation(start_date, end_date, match_type)
|
||||||
|
|
||||||
# 支持前端筛选
|
|
||||||
status_filter = request.args.get('status', '')
|
|
||||||
if status_filter:
|
|
||||||
data = [r for r in data if r['status'] == status_filter]
|
|
||||||
|
|
||||||
return jsonify({"total": len(data), "rows": data})
|
return jsonify({"total": len(data), "rows": data})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -123,8 +123,7 @@
|
|||||||
<el-button type="success" icon="el-icon-download" @click="exportReconData" size="small" style="margin-left: auto;">导出 Excel</el-button>
|
<el-button type="success" icon="el-icon-download" @click="exportReconData" size="small" style="margin-left: auto;">导出 Excel</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-table :data="pagedReconData" v-loading="loadingRecon" border style="width: 100%" stripe size="small" :header-cell-style="{background:'#f5f7fa',color:'#606266'}">
|
<el-table :data="pagedReconData" v-loading="loadingRecon" row-key="id" :tree-props="{children: 'children', hasChildren: 'hasChildren'}" border style="width: 100%" stripe size="small" :header-cell-style="{background:'#f5f7fa',color:'#606266'}">
|
||||||
<el-table-column type="index" label="序号" width="60" align="center"></el-table-column>
|
|
||||||
<el-table-column prop="sfc" label="工单号 (SFC)" width="150" sortable></el-table-column>
|
<el-table-column prop="sfc" label="工单号 (SFC)" width="150" sortable></el-table-column>
|
||||||
<el-table-column prop="material_code" label="物料代码" width="120"></el-table-column>
|
<el-table-column prop="material_code" label="物料代码" width="120"></el-table-column>
|
||||||
<el-table-column prop="material_name" label="物料名称" min-width="180" show-overflow-tooltip></el-table-column>
|
<el-table-column prop="material_name" label="物料名称" min-width="180" show-overflow-tooltip></el-table-column>
|
||||||
@@ -132,17 +131,17 @@
|
|||||||
<el-table-column label="发料对比" align="center">
|
<el-table-column label="发料对比" align="center">
|
||||||
<el-table-column prop="bom_qty" label="BOM 应发量" width="110" align="right">
|
<el-table-column prop="bom_qty" label="BOM 应发量" width="110" align="right">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<span v-text="Number(scope.row.bom_qty).toFixed(4)"></span>
|
<span v-if="scope.row.bom_qty !== ''" v-text="Number(scope.row.bom_qty).toFixed(4)"></span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="actual_qty" label="实际发料量" width="110" align="right">
|
<el-table-column prop="actual_qty" label="实际发料量" width="110" align="right">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<span style="font-weight: bold;" v-text="Number(scope.row.actual_qty).toFixed(4)"></span>
|
<span v-if="scope.row.actual_qty !== ''" style="font-weight: bold;" v-text="Number(scope.row.actual_qty).toFixed(4)"></span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="diff_qty" label="差异数量 (实-应)" width="130" align="right">
|
<el-table-column prop="diff_qty" label="差异数量 (实-应)" width="130" align="right">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<span :style="{
|
<span v-if="scope.row.diff_qty !== ''" :style="{
|
||||||
color: scope.row.diff_qty > 0 ? '#F56C6C' : (scope.row.diff_qty < 0 ? '#E6A23C' : '#67C23A'),
|
color: scope.row.diff_qty > 0 ? '#F56C6C' : (scope.row.diff_qty < 0 ? '#E6A23C' : '#67C23A'),
|
||||||
fontWeight: 'bold'
|
fontWeight: 'bold'
|
||||||
}" v-text="(scope.row.diff_qty > 0 ? '+' : '') + Number(scope.row.diff_qty).toFixed(4)">
|
}" v-text="(scope.row.diff_qty > 0 ? '+' : '') + Number(scope.row.diff_qty).toFixed(4)">
|
||||||
@@ -153,7 +152,7 @@
|
|||||||
|
|
||||||
<el-table-column prop="status" label="状态" width="120" align="center" sortable>
|
<el-table-column prop="status" label="状态" width="120" align="center" sortable>
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-tag :type="getStatusType(scope.row.status)" effect="dark" size="small" v-text="scope.row.status"></el-tag>
|
<el-tag v-if="scope.row.status && scope.row.status !== '-'" :type="getStatusType(scope.row.status)" effect="dark" size="small" v-text="scope.row.status"></el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -181,7 +180,7 @@
|
|||||||
el: '#app',
|
el: '#app',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
activeTab: 'unmatched',
|
activeTab: 'official',
|
||||||
matching: false,
|
matching: false,
|
||||||
dateRange: { start: '-', end: '-' },
|
dateRange: { start: '-', end: '-' },
|
||||||
dateRangeSelect: [],
|
dateRangeSelect: [],
|
||||||
@@ -224,16 +223,34 @@
|
|||||||
|
|
||||||
filteredReconData() {
|
filteredReconData() {
|
||||||
let data = this.reconData;
|
let data = this.reconData;
|
||||||
if (this.reconStatusFilter) {
|
|
||||||
data = data.filter(item => item.status === this.reconStatusFilter);
|
if (this.reconSearch || this.reconStatusFilter) {
|
||||||
}
|
const keyword = this.reconSearch ? this.reconSearch.toLowerCase() : '';
|
||||||
if (this.reconSearch) {
|
const statusFilter = this.reconStatusFilter;
|
||||||
const keyword = this.reconSearch.toLowerCase();
|
|
||||||
data = data.filter(item =>
|
// 辅助函数:递归检查节点及其子节点是否匹配条件
|
||||||
(item.sfc && item.sfc.toLowerCase().includes(keyword)) ||
|
const checkNode = (node) => {
|
||||||
(item.material_name && item.material_name.toLowerCase().includes(keyword)) ||
|
let match = true;
|
||||||
(item.material_code && item.material_code.toLowerCase().includes(keyword))
|
if (keyword) {
|
||||||
);
|
match = match && (
|
||||||
|
(node.sfc && node.sfc.toLowerCase().includes(keyword)) ||
|
||||||
|
(node.material_code && node.material_code.toLowerCase().includes(keyword)) ||
|
||||||
|
(node.material_name && node.material_name.toLowerCase().includes(keyword))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (statusFilter) {
|
||||||
|
match = match && (node.status === statusFilter);
|
||||||
|
}
|
||||||
|
if (match) return true;
|
||||||
|
|
||||||
|
// 如果当前节点不匹配,检查是否有子节点匹配
|
||||||
|
if (node.children && node.children.length > 0) {
|
||||||
|
return node.children.some(child => checkNode(child));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
data = data.filter(row => checkNode(row));
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
@@ -382,22 +399,30 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
exportReconData() {
|
exportReconData() {
|
||||||
// 简单的前端 CSV 导出
|
// 简单的前端 CSV 导出(支持树形结构展开)
|
||||||
const headers = ['工单号(SFC)', '物料代码', '物料名称', 'BOM应发量', '实际发料量', '差异数量', '状态'];
|
const headers = ['层级', '工单号(SFC)', '物料代码', '物料名称', 'BOM应发量', '实际发料量', '差异数量', '状态'];
|
||||||
let csvContent = "data:text/csv;charset=utf-8,\uFEFF" + headers.join(",") + "\n";
|
let csvContent = "data:text/csv;charset=utf-8,\uFEFF" + headers.join(",") + "\n";
|
||||||
|
|
||||||
this.filteredReconData.forEach(row => {
|
const processRow = (node, level = 0) => {
|
||||||
|
const indent = " ".repeat(level);
|
||||||
const rowData = [
|
const rowData = [
|
||||||
row.sfc,
|
level,
|
||||||
row.material_code,
|
node.sfc || '',
|
||||||
`"${row.material_name || ''}"`,
|
node.material_code || '',
|
||||||
row.bom_qty,
|
`"${indent}${node.material_name || ''}"`,
|
||||||
row.actual_qty,
|
node.bom_qty !== '' ? node.bom_qty : '',
|
||||||
row.diff_qty,
|
node.actual_qty !== '' ? node.actual_qty : '',
|
||||||
row.status
|
node.diff_qty !== '' ? node.diff_qty : '',
|
||||||
|
node.status || ''
|
||||||
];
|
];
|
||||||
csvContent += rowData.join(",") + "\n";
|
csvContent += rowData.join(",") + "\n";
|
||||||
});
|
|
||||||
|
if (node.children && node.children.length > 0) {
|
||||||
|
node.children.forEach(child => processRow(child, level + 1));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.filteredReconData.forEach(row => processRow(row, 0));
|
||||||
|
|
||||||
const encodedUri = encodeURI(csvContent);
|
const encodedUri = encodeURI(csvContent);
|
||||||
const link = document.createElement("a");
|
const link = document.createElement("a");
|
||||||
|
|||||||
Reference in New Issue
Block a user