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;