118 lines
8.9 KiB
SQL
118 lines
8.9 KiB
SQL
-- 应收账单毛利汇总快照升级 v1,MySQL 5.7+。
|
|
-- 前置:执行 20261105_ar_profit_report.sql;停止所有旧版后端(包括异步任务),在独立连接执行本文件,
|
|
-- 核对末尾统计后再部署新版本。执行工具须支持 DELIMITER 和存储过程。
|
|
-- DDL 自动提交,历史回填每批独立提交;异常修复后可重跑完整文件,已成功的快照不会覆盖。
|
|
-- 历史客户区域沿用账单已有快照;NULL 表示未计算,异常账单不填零。
|
|
-- READY 有效;NO_DETAIL 无明细;MISSING_AREA 区域缺失或不支持;MISSING_PRICE 缺价格;
|
|
-- CURRENCY_MISMATCH 币种异常;AMOUNT_MISMATCH 金额不一致;PRICE_OVERFLOW 超出字段范围。
|
|
SET NAMES utf8mb4;
|
|
DROP PROCEDURE IF EXISTS bms_ar_bill_profit_snapshot_20261111_v1;
|
|
DELIMITER $$
|
|
CREATE PROCEDURE bms_ar_bill_profit_snapshot_20261111_v1()
|
|
BEGIN
|
|
DECLARE n BIGINT DEFAULT 0;
|
|
DECLARE first_id BIGINT DEFAULT NULL;
|
|
DECLARE last_id BIGINT DEFAULT 0;
|
|
DECLARE EXIT HANDLER FOR SQLEXCEPTION
|
|
BEGIN
|
|
ROLLBACK;
|
|
RESIGNAL;
|
|
END;
|
|
|
|
SELECT COUNT(*) INTO n FROM information_schema.tables
|
|
WHERE table_schema = DATABASE() AND table_name IN ('ar_bill', 'ar_bill_detail', 'ar_bill_detail_fee');
|
|
IF n <> 3 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '毛利快照升级前置表缺失'; END IF;
|
|
SELECT COUNT(*) INTO n FROM information_schema.columns
|
|
WHERE table_schema = DATABASE() AND ((table_name = 'ar_bill' AND column_name IN ('ar_bill_id','profit_customer_area','total_amount','currency','generation_status'))
|
|
OR (table_name = 'ar_bill_detail' AND column_name IN ('detail_id','ar_bill_id','total_amount','currency'))
|
|
OR (table_name = 'ar_bill_detail_fee' AND column_name IN ('detail_id','amount','currency','product_amount','redline_amount')));
|
|
IF n <> 14 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '请先完成应收毛利及费用价格快照升级'; END IF;
|
|
SELECT COUNT(*) INTO n FROM information_schema.columns
|
|
WHERE table_schema = DATABASE() AND table_name = 'ar_bill'
|
|
AND column_name IN ('product_amount', 'redline_amount', 'profit_calc_status')
|
|
AND NOT (is_nullable = 'YES' AND ((column_name IN ('product_amount', 'redline_amount')
|
|
AND data_type = 'decimal' AND numeric_precision = 18 AND numeric_scale = 4)
|
|
OR (column_name = 'profit_calc_status' AND data_type = 'varchar' AND character_maximum_length = 24)));
|
|
IF n > 0 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '已有账单毛利快照字段结构冲突'; END IF;
|
|
|
|
SELECT COUNT(*) INTO n FROM information_schema.columns
|
|
WHERE table_schema = DATABASE() AND table_name = 'ar_bill' AND column_name = 'product_amount';
|
|
IF n = 0 THEN ALTER TABLE ar_bill ADD COLUMN product_amount DECIMAL(18,4) NULL COMMENT '账单产品价合计快照' AFTER total_amount; END IF;
|
|
SELECT COUNT(*) INTO n FROM information_schema.columns
|
|
WHERE table_schema = DATABASE() AND table_name = 'ar_bill' AND column_name = 'redline_amount';
|
|
IF n = 0 THEN ALTER TABLE ar_bill ADD COLUMN redline_amount DECIMAL(18,4) NULL COMMENT '账单红线价合计快照' AFTER product_amount; END IF;
|
|
SELECT COUNT(*) INTO n FROM information_schema.columns
|
|
WHERE table_schema = DATABASE() AND table_name = 'ar_bill' AND column_name = 'profit_calc_status';
|
|
IF n = 0 THEN ALTER TABLE ar_bill ADD COLUMN profit_calc_status VARCHAR(24) NULL COMMENT '毛利快照校验状态,NULL表示未计算' AFTER redline_amount; END IF;
|
|
SELECT COUNT(*) INTO n FROM information_schema.columns
|
|
WHERE table_schema = DATABASE() AND table_name = 'ar_bill' AND is_nullable = 'YES'
|
|
AND ((column_name IN ('product_amount', 'redline_amount') AND data_type = 'decimal' AND numeric_precision = 18 AND numeric_scale = 4)
|
|
OR (column_name = 'profit_calc_status' AND data_type = 'varchar' AND character_maximum_length = 24));
|
|
IF n <> 3 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '账单毛利快照字段结构冲突,请核对后重试'; END IF;
|
|
SELECT COUNT(*) INTO n FROM ar_bill WHERE profit_calc_status IS NULL
|
|
AND (product_amount IS NOT NULL OR redline_amount IS NOT NULL);
|
|
IF n > 0 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '存在无状态的已填价格快照,请人工核对'; END IF;
|
|
SELECT COUNT(*) INTO n FROM ar_bill WHERE profit_calc_status IS NOT NULL
|
|
AND profit_calc_status NOT IN ('READY','NO_DETAIL','MISSING_AREA','CURRENCY_MISMATCH','AMOUNT_MISMATCH','MISSING_PRICE','PRICE_OVERFLOW');
|
|
IF n > 0 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '存在未知毛利快照状态,请人工核对'; END IF;
|
|
|
|
backfill: LOOP
|
|
SELECT MIN(ar_bill_id) INTO first_id FROM ar_bill
|
|
WHERE generation_status = 'READY' AND (profit_calc_status IS NULL OR profit_calc_status != 'READY') AND ar_bill_id > last_id;
|
|
IF first_id IS NULL THEN LEAVE backfill; END IF;
|
|
SELECT MAX(batch.ar_bill_id) INTO last_id FROM (
|
|
SELECT ar_bill_id FROM ar_bill WHERE generation_status = 'READY' AND (profit_calc_status IS NULL OR profit_calc_status != 'READY')
|
|
AND ar_bill_id >= first_id ORDER BY ar_bill_id LIMIT 200
|
|
) batch;
|
|
START TRANSACTION;
|
|
-- 与 ArBillMapper.updateProfitSnapshot 的汇总和校验保持一致。
|
|
UPDATE ar_bill b
|
|
LEFT JOIN (
|
|
SELECT d.ar_bill_id, COUNT(*) AS detail_count, SUM(d.total_amount) AS detail_total,
|
|
COUNT(DISTINCT d.currency) AS detail_currency_count, MIN(d.currency) AS detail_currency,
|
|
SUM(CASE WHEN d.currency IS NOT NULL AND f.currency_count = 1 AND f.fee_currency = d.currency
|
|
THEN 0 ELSE 1 END) AS currency_errors,
|
|
SUM(CASE WHEN d.total_amount IS NOT NULL AND f.sales_amount IS NOT NULL
|
|
AND f.sales_amount = d.total_amount THEN 0 ELSE 1 END) AS amount_errors,
|
|
CASE WHEN COUNT(f.product_amount) = COUNT(*) THEN SUM(f.product_amount) ELSE NULL END AS product_total,
|
|
CASE WHEN COUNT(f.redline_amount) = COUNT(*) THEN SUM(f.redline_amount) ELSE NULL END AS redline_total
|
|
FROM ar_bill_detail d
|
|
LEFT JOIN (
|
|
SELECT fee.detail_id,
|
|
CASE WHEN COUNT(fee.amount) = COUNT(*) THEN SUM(fee.amount) ELSE NULL END AS sales_amount,
|
|
CASE WHEN COUNT(fee.currency) = COUNT(*) THEN COUNT(DISTINCT fee.currency) ELSE 0 END AS currency_count,
|
|
MIN(fee.currency) AS fee_currency,
|
|
CASE WHEN COUNT(fee.product_amount) = COUNT(*) THEN SUM(fee.product_amount) ELSE NULL END AS product_amount,
|
|
CASE WHEN COUNT(fee.redline_amount) = COUNT(*) THEN SUM(fee.redline_amount) ELSE NULL END AS redline_amount
|
|
FROM ar_bill_detail_fee fee
|
|
JOIN ar_bill_detail fd ON fd.detail_id = fee.detail_id
|
|
WHERE fd.ar_bill_id BETWEEN first_id AND last_id
|
|
GROUP BY fee.detail_id
|
|
) f ON f.detail_id = d.detail_id
|
|
WHERE d.ar_bill_id BETWEEN first_id AND last_id
|
|
GROUP BY d.ar_bill_id
|
|
) a ON a.ar_bill_id = b.ar_bill_id
|
|
SET b.product_amount = CASE WHEN ABS(a.product_total) <= 99999999999999.9999 THEN a.product_total ELSE NULL END,
|
|
b.redline_amount = CASE WHEN ABS(a.redline_total) <= 99999999999999.9999 THEN a.redline_total ELSE NULL END,
|
|
b.profit_calc_status = CASE
|
|
WHEN a.detail_count IS NULL OR a.detail_count = 0 THEN 'NO_DETAIL'
|
|
WHEN b.profit_customer_area IS NULL OR b.profit_customer_area NOT IN ('C', 'GZ') THEN 'MISSING_AREA'
|
|
WHEN b.currency IS NULL OR a.currency_errors > 0 OR a.detail_currency_count != 1 OR a.detail_currency != b.currency THEN 'CURRENCY_MISMATCH'
|
|
WHEN b.total_amount IS NULL OR a.amount_errors > 0 OR a.detail_total != b.total_amount THEN 'AMOUNT_MISMATCH'
|
|
WHEN (b.profit_customer_area = 'C' AND a.product_total IS NULL)
|
|
OR (b.profit_customer_area = 'GZ' AND a.redline_total IS NULL) THEN 'MISSING_PRICE'
|
|
WHEN (b.profit_customer_area = 'C' AND ABS(a.product_total) > 99999999999999.9999)
|
|
OR (b.profit_customer_area = 'GZ' AND ABS(a.redline_total) > 99999999999999.9999) THEN 'PRICE_OVERFLOW'
|
|
ELSE 'READY' END
|
|
WHERE b.ar_bill_id BETWEEN first_id AND last_id AND b.generation_status = 'READY'
|
|
AND (b.profit_calc_status IS NULL OR b.profit_calc_status != 'READY');
|
|
COMMIT;
|
|
END LOOP;
|
|
SELECT COALESCE(profit_calc_status, 'NOT_CALCULATED') AS profit_calc_status, COUNT(*) AS bill_count
|
|
FROM ar_bill WHERE generation_status = 'READY' GROUP BY profit_calc_status ORDER BY profit_calc_status;
|
|
SELECT COUNT(*) AS ready_unprocessed FROM ar_bill WHERE generation_status = 'READY' AND profit_calc_status IS NULL;
|
|
END$$
|
|
DELIMITER ;
|
|
CALL bms_ar_bill_profit_snapshot_20261111_v1();
|
|
DROP PROCEDURE IF EXISTS bms_ar_bill_profit_snapshot_20261111_v1;
|