ForgeFlow-ERP/backend/sql/add_material_driven_flow_patch.sql
2026-06-12 16:00:56 +08:00

336 lines
14 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

SET @po_receipt_item_merge_column_exists := (
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'po_receipt_item'
AND COLUMN_NAME = 'merge_to_lot_id'
);
SET @po_receipt_item_merge_sql := IF(
@po_receipt_item_merge_column_exists = 0,
'ALTER TABLE po_receipt_item ADD COLUMN merge_to_lot_id BIGINT NULL COMMENT ''合并入库目标库存批次ID为空表示新建独立批次'' AFTER lot_no',
'SELECT ''po_receipt_item.merge_to_lot_id already exists'''
);
PREPARE po_receipt_item_merge_stmt FROM @po_receipt_item_merge_sql;
EXECUTE po_receipt_item_merge_stmt;
DEALLOCATE PREPARE po_receipt_item_merge_stmt;
SET @stock_lot_parent_column_exists := (
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'wh_stock_lot'
AND COLUMN_NAME = 'parent_lot_id'
);
SET @stock_lot_parent_sql := IF(
@stock_lot_parent_column_exists = 0,
'ALTER TABLE wh_stock_lot ADD COLUMN parent_lot_id BIGINT NULL COMMENT ''父批次ID用于合并批次、退货批次或成品来源追溯'' AFTER lot_no',
'SELECT ''wh_stock_lot.parent_lot_id already exists'''
);
PREPARE stock_lot_parent_stmt FROM @stock_lot_parent_sql;
EXECUTE stock_lot_parent_stmt;
DEALLOCATE PREPARE stock_lot_parent_stmt;
SET @stock_lot_role_column_exists := (
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'wh_stock_lot'
AND COLUMN_NAME = 'lot_role'
);
SET @stock_lot_role_sql := IF(
@stock_lot_role_column_exists = 0,
'ALTER TABLE wh_stock_lot ADD COLUMN lot_role VARCHAR(32) NOT NULL DEFAULT ''INVENTORY'' COMMENT ''批次角色INBOUND_RAW/MERGED_RAW/FINISHED_FROM_RAW_BATCH/RETURNED_FINISHED/INVENTORY'' AFTER parent_lot_id',
'SELECT ''wh_stock_lot.lot_role already exists'''
);
PREPARE stock_lot_role_stmt FROM @stock_lot_role_sql;
EXECUTE stock_lot_role_stmt;
DEALLOCATE PREPARE stock_lot_role_stmt;
SET @stock_lot_sub_batch_column_exists := (
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'wh_stock_lot'
AND COLUMN_NAME = 'material_sub_batch_no'
);
SET @stock_lot_sub_batch_sql := IF(
@stock_lot_sub_batch_column_exists = 0,
'ALTER TABLE wh_stock_lot ADD COLUMN material_sub_batch_no VARCHAR(80) NULL COMMENT ''原材料生产分批号格式JH_材料号_0001'' AFTER lot_role',
'SELECT ''wh_stock_lot.material_sub_batch_no already exists'''
);
PREPARE stock_lot_sub_batch_stmt FROM @stock_lot_sub_batch_sql;
EXECUTE stock_lot_sub_batch_stmt;
DEALLOCATE PREPARE stock_lot_sub_batch_stmt;
SET @stock_lot_source_material_lot_column_exists := (
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'wh_stock_lot'
AND COLUMN_NAME = 'source_material_lot_id'
);
SET @stock_lot_source_material_lot_sql := IF(
@stock_lot_source_material_lot_column_exists = 0,
'ALTER TABLE wh_stock_lot ADD COLUMN source_material_lot_id BIGINT NULL COMMENT ''成品批次来源原材料库存批次ID'' AFTER source_line_id',
'SELECT ''wh_stock_lot.source_material_lot_id already exists'''
);
PREPARE stock_lot_source_material_lot_stmt FROM @stock_lot_source_material_lot_sql;
EXECUTE stock_lot_source_material_lot_stmt;
DEALLOCATE PREPARE stock_lot_source_material_lot_stmt;
SET @stock_lot_source_material_sub_column_exists := (
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'wh_stock_lot'
AND COLUMN_NAME = 'source_material_sub_batch_no'
);
SET @stock_lot_source_material_sub_sql := IF(
@stock_lot_source_material_sub_column_exists = 0,
'ALTER TABLE wh_stock_lot ADD COLUMN source_material_sub_batch_no VARCHAR(80) NULL COMMENT ''成品批次来源原材料生产分批号'' AFTER source_material_lot_id',
'SELECT ''wh_stock_lot.source_material_sub_batch_no already exists'''
);
PREPARE stock_lot_source_material_sub_stmt FROM @stock_lot_source_material_sub_sql;
EXECUTE stock_lot_source_material_sub_stmt;
DEALLOCATE PREPARE stock_lot_source_material_sub_stmt;
SET @stock_lot_source_material_summary_column_exists := (
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'wh_stock_lot'
AND COLUMN_NAME = 'source_material_summary'
);
SET @stock_lot_source_material_summary_sql := IF(
@stock_lot_source_material_summary_column_exists = 0,
'ALTER TABLE wh_stock_lot ADD COLUMN source_material_summary VARCHAR(1000) NULL COMMENT ''成品批次来源原材料分批摘要'' AFTER source_material_sub_batch_no',
'SELECT ''wh_stock_lot.source_material_summary already exists'''
);
PREPARE stock_lot_source_material_summary_stmt FROM @stock_lot_source_material_summary_sql;
EXECUTE stock_lot_source_material_summary_stmt;
DEALLOCATE PREPARE stock_lot_source_material_summary_stmt;
SET @completion_source_material_lot_column_exists := (
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'pp_completion_receipt_item'
AND COLUMN_NAME = 'source_material_lot_id'
);
SET @completion_source_material_lot_sql := IF(
@completion_source_material_lot_column_exists = 0,
'ALTER TABLE pp_completion_receipt_item ADD COLUMN source_material_lot_id BIGINT NULL COMMENT ''成品来源原材料库存批次ID'' AFTER lot_no',
'SELECT ''pp_completion_receipt_item.source_material_lot_id already exists'''
);
PREPARE completion_source_material_lot_stmt FROM @completion_source_material_lot_sql;
EXECUTE completion_source_material_lot_stmt;
DEALLOCATE PREPARE completion_source_material_lot_stmt;
SET @completion_source_material_sub_column_exists := (
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'pp_completion_receipt_item'
AND COLUMN_NAME = 'source_material_sub_batch_no'
);
SET @completion_source_material_sub_sql := IF(
@completion_source_material_sub_column_exists = 0,
'ALTER TABLE pp_completion_receipt_item ADD COLUMN source_material_sub_batch_no VARCHAR(80) NULL COMMENT ''成品来源原材料生产分批号'' AFTER source_material_lot_id',
'SELECT ''pp_completion_receipt_item.source_material_sub_batch_no already exists'''
);
PREPARE completion_source_material_sub_stmt FROM @completion_source_material_sub_sql;
EXECUTE completion_source_material_sub_stmt;
DEALLOCATE PREPARE completion_source_material_sub_stmt;
SET @completion_source_material_summary_column_exists := (
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'pp_completion_receipt_item'
AND COLUMN_NAME = 'source_material_summary'
);
SET @completion_source_material_summary_sql := IF(
@completion_source_material_summary_column_exists = 0,
'ALTER TABLE pp_completion_receipt_item ADD COLUMN source_material_summary VARCHAR(1000) NULL COMMENT ''成品来源原材料分批摘要'' AFTER source_material_sub_batch_no',
'SELECT ''pp_completion_receipt_item.source_material_summary already exists'''
);
PREPARE completion_source_material_summary_stmt FROM @completion_source_material_summary_sql;
EXECUTE completion_source_material_summary_stmt;
DEALLOCATE PREPARE completion_source_material_summary_stmt;
CREATE TABLE IF NOT EXISTS pp_work_order_material_issue (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
work_order_material_id BIGINT NOT NULL COMMENT '工单用料ID',
work_order_id BIGINT NOT NULL COMMENT '生产工单ID',
material_item_id BIGINT NOT NULL COMMENT '原材料物料ID',
source_lot_id BIGINT NOT NULL COMMENT '来源原材料库存批次ID',
material_sub_batch_no VARCHAR(80) NOT NULL COMMENT '兼容字段:来源库存批次号',
issued_qty DECIMAL(18,6) NOT NULL DEFAULT 0 COMMENT '领用数量',
issued_weight_kg DECIMAL(18,6) NOT NULL DEFAULT 0 COMMENT '领用重量(kg)',
unit_cost DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '领用单价',
amount DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '领用金额',
issue_time DATETIME NOT NULL COMMENT '领用时间',
operator_user_id BIGINT NULL COMMENT '操作人用户ID',
remark VARCHAR(255) NULL COMMENT '备注',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_material_issue_work_order (work_order_id),
INDEX idx_material_issue_lot (source_lot_id),
INDEX idx_material_issue_material (material_item_id),
FOREIGN KEY (work_order_material_id) REFERENCES pp_work_order_material(id),
FOREIGN KEY (work_order_id) REFERENCES pp_work_order(id),
FOREIGN KEY (material_item_id) REFERENCES md_item(id),
FOREIGN KEY (source_lot_id) REFERENCES wh_stock_lot(id)
) ENGINE=InnoDB COMMENT='工单原材料领用分批记录';
SET @idx_po_receipt_item_merge_exists := (
SELECT COUNT(*)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'po_receipt_item'
AND INDEX_NAME = 'idx_receipt_item_merge_to_lot'
);
SET @idx_po_receipt_item_merge_sql := IF(
@idx_po_receipt_item_merge_exists = 0,
'ALTER TABLE po_receipt_item ADD INDEX idx_receipt_item_merge_to_lot (merge_to_lot_id)',
'SELECT ''idx_receipt_item_merge_to_lot already exists'''
);
PREPARE idx_po_receipt_item_merge_stmt FROM @idx_po_receipt_item_merge_sql;
EXECUTE idx_po_receipt_item_merge_stmt;
DEALLOCATE PREPARE idx_po_receipt_item_merge_stmt;
SET @idx_stock_lot_source_material_exists := (
SELECT COUNT(*)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'wh_stock_lot'
AND INDEX_NAME = 'idx_stock_lot_source_material'
);
SET @idx_stock_lot_source_material_sql := IF(
@idx_stock_lot_source_material_exists = 0,
'ALTER TABLE wh_stock_lot ADD INDEX idx_stock_lot_source_material (source_material_lot_id, source_material_sub_batch_no)',
'SELECT ''idx_stock_lot_source_material already exists'''
);
PREPARE idx_stock_lot_source_material_stmt FROM @idx_stock_lot_source_material_sql;
EXECUTE idx_stock_lot_source_material_stmt;
DEALLOCATE PREPARE idx_stock_lot_source_material_stmt;
SET @idx_completion_source_material_exists := (
SELECT COUNT(*)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'pp_completion_receipt_item'
AND INDEX_NAME = 'idx_completion_source_material'
);
SET @idx_completion_source_material_sql := IF(
@idx_completion_source_material_exists = 0,
'ALTER TABLE pp_completion_receipt_item ADD INDEX idx_completion_source_material (source_material_lot_id, source_material_sub_batch_no)',
'SELECT ''idx_completion_source_material already exists'''
);
PREPARE idx_completion_source_material_stmt FROM @idx_completion_source_material_sql;
EXECUTE idx_completion_source_material_stmt;
DEALLOCATE PREPARE idx_completion_source_material_stmt;
UPDATE wh_stock_lot lot
JOIN md_item item ON item.id = lot.item_id
SET lot.lot_role = CASE
WHEN item.item_type = 'RAW_MATERIAL' AND lot.source_doc_type = 'PURCHASE_RECEIPT' THEN 'INBOUND_RAW'
WHEN item.item_type = 'FINISHED_GOOD' AND lot.source_doc_type = 'FG_RECEIPT' THEN 'FINISHED_GOOD'
ELSE lot.lot_role
END
WHERE lot.lot_role = 'INVENTORY';
INSERT INTO pp_work_order_material_issue (
work_order_material_id,
work_order_id,
material_item_id,
source_lot_id,
material_sub_batch_no,
issued_qty,
issued_weight_kg,
unit_cost,
amount,
issue_time,
operator_user_id,
remark,
created_at,
updated_at
)
SELECT
source_rows.work_order_material_id,
source_rows.work_order_id,
source_rows.material_item_id,
source_rows.source_lot_id,
CONCAT('JH_', source_rows.material_code, '_', LPAD(source_rows.seq_no, 4, '0')) AS material_sub_batch_no,
source_rows.issued_qty,
source_rows.issued_weight_kg,
source_rows.unit_cost,
source_rows.amount,
source_rows.issue_time,
source_rows.operator_user_id,
source_rows.remark,
NOW(),
NOW()
FROM (
SELECT
wom.id AS work_order_material_id,
wom.work_order_id AS work_order_id,
txn.item_id AS material_item_id,
txn.lot_id AS source_lot_id,
item.item_code AS material_code,
ROW_NUMBER() OVER (PARTITION BY txn.item_id ORDER BY txn.biz_time, txn.id) AS seq_no,
ABS(txn.qty_change) AS issued_qty,
ABS(txn.weight_change_kg) AS issued_weight_kg,
txn.unit_cost AS unit_cost,
ABS(txn.amount) AS amount,
txn.biz_time AS issue_time,
txn.operator_user_id AS operator_user_id,
txn.remark AS remark
FROM wh_inventory_txn txn
JOIN pp_work_order_material wom
ON wom.id = txn.source_line_id
AND wom.work_order_id = txn.source_doc_id
JOIN md_item item ON item.id = txn.item_id
WHERE txn.txn_type = 'MATERIAL_ISSUE'
AND txn.source_doc_type = 'WORK_ORDER'
AND txn.lot_id IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM pp_work_order_material_issue)
) source_rows;
UPDATE pp_completion_receipt_item item
JOIN pp_completion_receipt receipt ON receipt.id = item.completion_receipt_id
JOIN (
SELECT
issue.work_order_id,
MIN(issue.source_lot_id) AS source_material_lot_id,
SUBSTRING_INDEX(GROUP_CONCAT(issue.material_sub_batch_no ORDER BY issue.id SEPARATOR ','), ',', 1) AS source_material_sub_batch_no,
LEFT(
GROUP_CONCAT(
CONCAT(issue.material_sub_batch_no, ' / ', material.item_name, ' / ', issue.issued_weight_kg, 'kg / ¥', issue.amount)
ORDER BY issue.id
SEPARATOR ''
),
1000
) AS source_material_summary
FROM pp_work_order_material_issue issue
JOIN md_item material ON material.id = issue.material_item_id
GROUP BY issue.work_order_id
) source_map ON source_map.work_order_id = receipt.work_order_id
SET
item.source_material_lot_id = COALESCE(item.source_material_lot_id, source_map.source_material_lot_id),
item.source_material_sub_batch_no = COALESCE(item.source_material_sub_batch_no, source_map.source_material_sub_batch_no),
item.source_material_summary = COALESCE(item.source_material_summary, source_map.source_material_summary)
WHERE item.source_material_lot_id IS NULL;
UPDATE wh_stock_lot lot
JOIN pp_completion_receipt_item item
ON lot.source_doc_type = 'FG_RECEIPT'
AND lot.source_line_id = item.id
SET
lot.parent_lot_id = COALESCE(lot.parent_lot_id, item.source_material_lot_id),
lot.lot_role = 'FINISHED_FROM_RAW_BATCH',
lot.source_material_lot_id = COALESCE(lot.source_material_lot_id, item.source_material_lot_id),
lot.source_material_sub_batch_no = COALESCE(lot.source_material_sub_batch_no, item.source_material_sub_batch_no),
lot.source_material_summary = COALESCE(lot.source_material_summary, item.source_material_summary)
WHERE item.source_material_lot_id IS NOT NULL;