This commit is contained in:
zlwu committed 2026-08-09 14:51:41 +08:00
1 parent ca6aca2b14
commit f70dc26206
63 files changed
+589912 -9127

No files matched your search

@@ -0,0 +1,22 @@
-- backend/scripts/migrate_20260808_pl_plan_contract.sql
-- 运输计划:供方/需方分侧多合同关联
CREATE TABLE IF NOT EXISTS `pl_plan_contract` (
`id` varchar(64) NOT NULL COMMENT '主键ID',
`plan_id` varchar(64) NOT NULL COMMENT '运输计划ID',
`side` varchar(20) NOT NULL COMMENT 'supplier-供方 demander-需方',
`contract_id` varchar(64) NOT NULL COMMENT 'CRM合同ID',
`contract_no` varchar(50) DEFAULT NULL COMMENT '合同编号快照',
`tenant_no` varchar(64) NOT NULL DEFAULT 'shanxia2.0' COMMENT '租户编号',
`order_num` int DEFAULT NULL COMMENT '排序号',
`remark` varchar(512) DEFAULT NULL COMMENT '备注',
`create_by` varchar(64) DEFAULT NULL COMMENT '创建人id',
`create_dept_id` varchar(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT NULL COMMENT '更新人id',
`update_time` datetime NOT NULL COMMENT '更新时间',
`del_flag` smallint NOT NULL DEFAULT 0 COMMENT '删除标记:0-正常 1-已删除',
PRIMARY KEY (`id`),
KEY `idx_pl_plan_contract_plan` (`plan_id`, `del_flag`),
KEY `idx_pl_plan_contract_side` (`plan_id`, `side`, `del_flag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='运输计划-合同关联(分侧多选)';
@@ -0,0 +1,158 @@
#!/usr/bin/env python3
"""将历史运输计划的单一合同/客户,按单据类型落到供方或需方。
规则(与产品确认一致):
- 出库单 outbound / 直销单 direct / 其它或空:旧合同 + 客户 → 需方
- 入库单 inbound:旧合同 + 客户 → 供方(customer_* 清空,写入 supplier_*)
处理范围:
1) 仍有 pl_plan.contract_id,且尚无 pl_plan_contract 有效关联 → 写入分侧合同
2) 无合同但有客户:按单据类型把客户落到供方/需方(便于用户继续完善)
用法(在 backend 目录、已激活 venv):
ENV_FILE=.env.production python scripts/migrate_20260808_pl_plan_legacy_party_contracts.py --dry-run
ENV_FILE=.env.production python scripts/migrate_20260808_pl_plan_legacy_party_contracts.py
"""
from __future__ import annotations
import argparse
import sys
from datetime import datetime
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from app.core.database import SessionLocal # noqa: E402
from app.core.utils import generate_uuid # noqa: E402
from app.models.pl_models import PlPlan, PlPlanContract # noqa: E402
def _resolve_side(plan_type: str) -> str:
return "supplier" if (plan_type or "").strip() == "inbound" else "demander"
def _has_text(value) -> bool:
return bool(str(value or "").strip())
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--dry-run", action="store_true", help="只统计不落库")
args = parser.parse_args()
db = SessionLocal()
now = datetime.now()
linked = 0
party_updated = 0
skipped = 0
try:
plans = db.query(PlPlan).filter(PlPlan.del_flag == 0).all()
for plan in plans:
side = _resolve_side(plan.plan_type or "")
has_contract = _has_text(plan.contract_id)
has_customer = _has_text(plan.customer_id) or _has_text(plan.customer_name)
has_supplier = _has_text(plan.supplier_id) or _has_text(plan.supplier_name)
exists_link = (
db.query(PlPlanContract.id)
.filter(
PlPlanContract.plan_id == plan.id,
PlPlanContract.del_flag == 0,
)
.first()
)
changed = False
# 1) 合同落到分侧关联表
if has_contract and not exists_link:
if not args.dry_run:
db.add(
PlPlanContract(
id=generate_uuid(),
plan_id=plan.id,
side=side,
contract_id=plan.contract_id,
contract_no=plan.contract_no,
tenant_no=plan.tenant_no,
order_num=1,
create_by=plan.update_by or plan.create_by,
create_dept_id=plan.create_dept_id or "",
create_time=now,
update_by=plan.update_by or plan.create_by,
update_time=now,
del_flag=0,
)
)
linked += 1
changed = True
# 2) 客户落到供方/需方
if side == "supplier":
# 入库:客户 → 供方;清空需方客户
# - 供方为空:整段迁移
# - 供方与客户同名:清掉重复的需方客户
# - 供方与客户不同:保留两边,留给人工确认(不覆盖供方)
if has_customer and not has_supplier:
if not args.dry_run:
plan.supplier_id = plan.customer_id
plan.supplier_name = plan.customer_name
plan.customer_id = None
plan.customer_name = None
plan.destination_id = None
plan.destination_name = None
plan.update_time = now
party_updated += 1
changed = True
elif (
has_customer
and has_supplier
and str(plan.customer_name or "").strip()
== str(plan.supplier_name or "").strip()
):
if not args.dry_run:
plan.customer_id = None
plan.customer_name = None
plan.destination_id = None
plan.destination_name = None
plan.update_time = now
party_updated += 1
changed = True
else:
# 出库/直销:客户保留在需方;补齐 destination
if has_customer and not _has_text(plan.destination_name):
if not args.dry_run:
plan.destination_id = plan.customer_id
plan.destination_name = plan.customer_name
plan.update_time = now
party_updated += 1
changed = True
if not changed:
skipped += 1
if args.dry_run:
db.rollback()
print(
f"[dry-run] would link contracts={linked}, "
f"update party fields={party_updated}, skipped={skipped}"
)
else:
db.commit()
print(
f"ok linked contracts={linked}, "
f"updated party fields={party_updated}, skipped={skipped}"
)
return 0
except Exception as exc:
db.rollback()
print(f"ERR {type(exc).__name__}: {exc}")
return 1
finally:
db.close()
if __name__ == "__main__":
raise SystemExit(main())