Files
shanxia2.0/docs/sql/migrations/2026-11-04-transport-cert-purchase-link-backfill.sql
T
wuzilongandCursor 8e9536332a feat: 保存运输证件、报销付款和开发流程的当前实现。
补齐证书齐备、证件折算、移动用证和合同绑定,并带上报销批量发放、付款备注以及个人开发流程升级,便于继续讨论已安排计划的调整规则。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-04 21:42:33 +08:00

217 lines
10 KiB
SQL

-- 已建档运输证回填购买证关联,并补上该购买证的合同和合同产品。
-- MySQL 5.7。执行前备份 pl_transport_cert_purchase_ref、pl_contract_cert_link、pl_contract_cert_product。
-- 临时表使用 utf8mb4_general_ci,与业务表一致;库默认 unicode_ci 时直接比较会报 1267。
-- 可重复执行。只补空缺,不改已经选过的购买证,不改、不删运输证上已有合同。
--
-- 规则与证件建档识别后的自动关联一致:
-- 1. 引用证号与购买证证号去掉首尾空格后按二进制完全一致,且同一租户只命中一张未删除购买证,才写入购买证。
-- 命中 0 张或 2 张及以上保持为空。已经填写的购买证不覆盖。
-- 2. 仅针对本次新关联上的购买证,把它尚未出现在该运输证上的合同和产品复制过去。
-- 新记录使用新主键,不沿用购买证那边的关联 ID、产品范围 ID。
-- 合同已在运输证上存在(含已软删除)则整份跳过,不合并、不恢复。
-- 购买证上没有仍有效的合同产品的合同不复制。合同或产品行已删除的不复制。
-- 3. 作废、已核销的运输证只补购买证引用,不新增合同范围。
--
-- 只读核对想看结果、先不落库时,把文末 COMMIT 改成 ROLLBACK。
SET NAMES utf8mb4;
SET SESSION group_concat_max_len = 1048576;
DROP TEMPORARY TABLE IF EXISTS tmp_transport_purchase_match;
DROP TEMPORARY TABLE IF EXISTS tmp_transport_contract_copy;
CREATE TEMPORARY TABLE tmp_transport_purchase_match (
ref_id varchar(64) NOT NULL,
tenant_no varchar(64) NOT NULL,
transport_cert_id varchar(64) NOT NULL,
transport_cert_no varchar(100) NOT NULL,
transport_status varchar(20) NOT NULL,
create_dept_id varchar(64) NOT NULL,
purchase_cert_id varchar(64) NOT NULL,
purchase_cert_no varchar(100) NOT NULL,
reference_no varchar(100) NOT NULL,
PRIMARY KEY (ref_id),
KEY idx_match_transport (tenant_no, transport_cert_id),
KEY idx_match_purchase (tenant_no, purchase_cert_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TEMPORARY TABLE tmp_transport_contract_copy (
new_link_id varchar(64) NOT NULL,
source_link_id varchar(64) NOT NULL,
tenant_no varchar(64) NOT NULL,
transport_cert_id varchar(64) NOT NULL,
contract_id varchar(64) NOT NULL,
contract_no varchar(100) NOT NULL,
link_status varchar(16) NOT NULL,
order_num int DEFAULT NULL,
create_dept_id varchar(64) NOT NULL,
PRIMARY KEY (new_link_id),
UNIQUE KEY uk_transport_contract (tenant_no, transport_cert_id, contract_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
START TRANSACTION;
INSERT INTO tmp_transport_purchase_match (
ref_id, tenant_no, transport_cert_id, transport_cert_no, transport_status, create_dept_id,
purchase_cert_id, purchase_cert_no, reference_no
)
SELECT ref.id, ref.tenant_no, ref.transport_cert_id, transport.cert_no, transport.status,
transport.create_dept_id, MIN(purchase.id), MIN(purchase.cert_no), ref.reference_no
FROM pl_transport_cert_purchase_ref ref
JOIN pl_transport_cert transport
ON transport.id = ref.transport_cert_id
AND transport.tenant_no = ref.tenant_no
AND transport.del_flag = 0
JOIN pl_purchase_cert purchase
ON purchase.tenant_no = ref.tenant_no
AND purchase.del_flag = 0
AND CHAR_LENGTH(TRIM(purchase.cert_no)) > 0
AND BINARY TRIM(purchase.cert_no) = BINARY TRIM(ref.reference_no)
WHERE ref.del_flag = 0
AND CHAR_LENGTH(TRIM(IFNULL(ref.purchase_cert_id, ''))) = 0
AND CHAR_LENGTH(TRIM(ref.reference_no)) > 0
GROUP BY ref.id, ref.tenant_no, ref.transport_cert_id, transport.cert_no, transport.status,
transport.create_dept_id, ref.reference_no
HAVING COUNT(*) = 1;
-- 引用证号命中多张购买证:不自动关联,留给人工核对。
SELECT ref.tenant_no, transport.cert_no AS transport_cert_no, ref.reference_no,
COUNT(*) AS purchase_hit_count, GROUP_CONCAT(purchase.cert_no ORDER BY purchase.cert_no SEPARATOR '、') AS purchase_cert_nos
FROM pl_transport_cert_purchase_ref ref
JOIN pl_transport_cert transport
ON transport.id = ref.transport_cert_id
AND transport.tenant_no = ref.tenant_no
AND transport.del_flag = 0
JOIN pl_purchase_cert purchase
ON purchase.tenant_no = ref.tenant_no
AND purchase.del_flag = 0
AND CHAR_LENGTH(TRIM(purchase.cert_no)) > 0
AND BINARY TRIM(purchase.cert_no) = BINARY TRIM(ref.reference_no)
WHERE ref.del_flag = 0
AND CHAR_LENGTH(TRIM(IFNULL(ref.purchase_cert_id, ''))) = 0
AND CHAR_LENGTH(TRIM(ref.reference_no)) > 0
GROUP BY ref.tenant_no, transport.cert_no, ref.reference_no
HAVING COUNT(*) > 1;
INSERT INTO tmp_transport_contract_copy (
new_link_id, source_link_id, tenant_no, transport_cert_id, contract_id, contract_no,
link_status, order_num, create_dept_id
)
SELECT UPPER(REPLACE(UUID(), '-', '')), source_link.id, picked.tenant_no, picked.transport_cert_id,
picked.contract_id, IFNULL(contract.contract_no, ''),
IF(source_link.link_status = 'inactive', 'inactive', 'active'),
source_link.order_num, picked.create_dept_id
FROM (
SELECT matched.tenant_no, matched.transport_cert_id, source_link.contract_id, matched.create_dept_id,
SUBSTRING_INDEX(GROUP_CONCAT(source_link.id ORDER BY matched.ref_id, source_link.id SEPARATOR ','), ',', 1) AS source_link_id
FROM tmp_transport_purchase_match matched
JOIN pl_contract_cert_link source_link
ON source_link.tenant_no = matched.tenant_no
AND source_link.cert_type = 'purchase'
AND source_link.cert_id = matched.purchase_cert_id
AND source_link.del_flag = 0
JOIN crm_contract contract
ON contract.id = source_link.contract_id
AND contract.tenant_no = source_link.tenant_no
AND contract.del_flag = 0
WHERE matched.transport_status NOT IN ('void', 'closed')
AND EXISTS (
-- MySQL 5.7 不能在子查询 JOIN 的 ON 里引用外层 source_link,合同条件放在 WHERE。
SELECT 1
FROM pl_contract_cert_product source_product
JOIN crm_contract_product contract_product
ON contract_product.id = source_product.contract_product_id
AND contract_product.tenant_no = source_product.tenant_no
AND contract_product.del_flag = 0
WHERE source_product.contract_cert_link_id = source_link.id
AND source_product.tenant_no = source_link.tenant_no
AND source_product.del_flag = 0
AND CHAR_LENGTH(TRIM(source_product.contract_product_id)) > 0
AND contract_product.contract_id = source_link.contract_id
)
AND NOT EXISTS (
SELECT 1
FROM pl_contract_cert_link existing
WHERE existing.tenant_no = matched.tenant_no
AND existing.contract_id = source_link.contract_id
AND existing.cert_type = 'transport'
AND existing.cert_id = matched.transport_cert_id
)
GROUP BY matched.tenant_no, matched.transport_cert_id, source_link.contract_id, matched.create_dept_id
) picked
JOIN pl_contract_cert_link source_link ON source_link.id = picked.source_link_id
JOIN crm_contract contract
ON contract.id = picked.contract_id
AND contract.tenant_no = picked.tenant_no
AND contract.del_flag = 0;
-- 本次将补上的购买证引用。
SELECT tenant_no, transport_cert_no, reference_no, purchase_cert_no, transport_status
FROM tmp_transport_purchase_match
ORDER BY tenant_no, transport_cert_no, reference_no;
-- 本次将补上的合同。作废、已核销不在其中。
SELECT copy_row.tenant_no, transport.cert_no AS transport_cert_no, copy_row.contract_no,
copy_row.link_status
FROM tmp_transport_contract_copy copy_row
JOIN pl_transport_cert transport
ON transport.id = copy_row.transport_cert_id
AND transport.tenant_no = copy_row.tenant_no
ORDER BY copy_row.tenant_no, transport.cert_no, copy_row.contract_no;
UPDATE pl_transport_cert_purchase_ref ref_row
JOIN tmp_transport_purchase_match matched ON matched.ref_id = ref_row.id
SET ref_row.purchase_cert_id = matched.purchase_cert_id,
ref_row.update_by = 'system',
ref_row.update_time = NOW()
WHERE ref_row.del_flag = 0
AND CHAR_LENGTH(TRIM(IFNULL(ref_row.purchase_cert_id, ''))) = 0;
INSERT INTO pl_contract_cert_link (
id, contract_id, contract_no_snapshot, cert_type, cert_id, link_status, scope_revision,
tenant_no, order_num, remark, create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT new_link_id, contract_id, contract_no, 'transport', transport_cert_id, link_status, 1,
tenant_no, order_num, NULL, 'system', create_dept_id, NOW(), 'system', NOW(), 0
FROM tmp_transport_contract_copy;
INSERT INTO pl_contract_cert_product (
id, contract_cert_link_id, contract_product_id, scope_status,
tenant_no, order_num, remark, create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT UPPER(REPLACE(UUID(), '-', '')), copy_row.new_link_id, source_product.contract_product_id,
IF(source_product.scope_status = 'inactive', 'inactive', 'active'),
copy_row.tenant_no, source_product.order_num, NULL, 'system', copy_row.create_dept_id, NOW(), 'system', NOW(), 0
FROM tmp_transport_contract_copy copy_row
JOIN pl_contract_cert_product source_product
ON source_product.contract_cert_link_id = copy_row.source_link_id
AND source_product.tenant_no = copy_row.tenant_no
AND source_product.del_flag = 0
AND CHAR_LENGTH(TRIM(source_product.contract_product_id)) > 0
JOIN crm_contract_product contract_product
ON contract_product.id = source_product.contract_product_id
AND contract_product.contract_id = copy_row.contract_id
AND contract_product.tenant_no = source_product.tenant_no
AND contract_product.del_flag = 0;
-- MySQL 5.7 同一条语句不能重复打开临时表,汇总拆开查询。
SELECT COUNT(*) AS linked_purchase_refs FROM tmp_transport_purchase_match;
SELECT COUNT(*) AS copied_contracts FROM tmp_transport_contract_copy;
SELECT COUNT(*) AS copied_products
FROM tmp_transport_contract_copy copy_row
JOIN pl_contract_cert_product source_product
ON source_product.contract_cert_link_id = copy_row.source_link_id
AND source_product.tenant_no = copy_row.tenant_no
AND source_product.del_flag = 0
JOIN crm_contract_product contract_product
ON contract_product.id = source_product.contract_product_id
AND contract_product.contract_id = copy_row.contract_id
AND contract_product.tenant_no = source_product.tenant_no
AND contract_product.del_flag = 0;
COMMIT;
DROP TEMPORARY TABLE IF EXISTS tmp_transport_purchase_match;
DROP TEMPORARY TABLE IF EXISTS tmp_transport_contract_copy;