SNAPSHOT W7 已部署稳定态 — 凯迪ERP+OA一体化平台 (MET 73.3%)

恢复点(restore point)。别人改崩后可 git reset --hard 回到此提交。

== 此快照内容 ==
- 后端 oa-backend: 734 控制器 / 711 实体 (Spring Boot 3.2.5 + SQLite, 端口8091)
- 前端 modern-ui/app: Vue3+Vite, 约700页 (构建产物已在 oa-backend/src/main/resources/static)
- 数据库 oa-backend/data/oa.db: 含全部演示数据 (强制入库, 6.6MB)
- 交接文档 go.md + go-code-reference/endpoints/entities/database.md
- 多代理建设脚本 .claude/wf-*.js

== 状态 ==
- 对 凯迪科技ERP_20260507.xlsx 合规 MET ~73.3% (PARTIAL 75: 34可建+6种子/bug+35外部硬天花板)
- 安全: 5轮红队+5轮复检, default-deny分级鉴权, 连续零可利用
- W3~W7 累计补完436缺口; W8末轮(40缺口)为半成品(源码树可编译但未集成)
- 运行: cd oa-backend; java -jar build/libs/oa-backend-0.1.0.jar --server.port=8091; admin/123456

== 排除(gitignore, 可再生) ==
node_modules / oa-backend/build / .jdks / *.log / Backup-ERP-* / 弃用的OFBiz核心(只保留modern-ui)
完整文件夹备份见同目录 Backup-ERP-20260615-191517/ (含上述全部, 仅缺 node_modules)

时间戳: 20260615-191517

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Qiufeng
2026-06-15 19:19:15 +08:00
co-authored by Claude Opus 4.8
commit 5e51dc3f56
10584 changed files with 2501339 additions and 0 deletions
@@ -0,0 +1,225 @@
package com.kaidi.oa.seed;
import com.kaidi.oa.common.Money;
import com.kaidi.oa.domain.ArApItem;
import com.kaidi.oa.domain.OrgBranch;
import com.kaidi.oa.repository.ArApItemRepository;
import com.kaidi.oa.repository.OrgBranchRepository;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
/**
* 分公司财务报表演示数据种子(经营数据分析 §8 缺口补完)。
*
* 审计缺口:
* 1. OrgBranch 无种子数据 → /api/oa/branch-fin-statements/branches 返回空列表,
* 整个分公司独立财务报表功能无法演示。
* 2. ArApItem 无种子数据 + companySubject 未设 → 现金流量表直接法用 Payment.subject
* 模糊匹配作过渡;资产负债表 AR/AP 科目为零。
*
* 本种子补全:
* - 5 条 OrgBranch(总部 1 + 分公司 3 + 办事处 1),覆盖岳阳分公司/华东分公司/华南分公司。
* - 20 条 ArApItem(应收 12 + 应付 8),按 companySubject 与 OrgBranch.name 一致,
* 使分公司维度的应收/应付/已核销/未核销在报表中实际显示。
*
* 幂等:OrgBranch 存在则跳过(guard on count > 0)。
*
* @Order(15) 在 DataSeeder(@Order 1) 之后独立运行。
*/
@Component
@Order(15)
public class BranchFinDemoSeeder implements ApplicationRunner {
private final OrgBranchRepository orgBranchRepo;
private final ArApItemRepository arApRepo;
public BranchFinDemoSeeder(OrgBranchRepository orgBranchRepo,
ArApItemRepository arApRepo) {
this.orgBranchRepo = orgBranchRepo;
this.arApRepo = arApRepo;
}
@Override
public void run(ApplicationArguments args) {
seedOrgBranches();
seedArApItems();
}
// ============================================================
// 1. OrgBranch 机构目录
// ============================================================
private void seedOrgBranches() {
if (orgBranchRepo.count() > 0) {
return;
}
List<OrgBranch> seeds = new ArrayList<>();
seeds.add(branch("HQ001", "湖南凯迪工程科技有限公司", "总部", null,
"湖南长沙", "张伟", "0731-88889999", "长沙市岳麓区科技中路88号", "运营中",
"凯迪集团总部,负责全国市场统筹与管理"));
seeds.add(branch("YY001", "湖南凯迪工程科技有限公司岳阳分公司", "分公司", "HQ001",
"湖南岳阳", "陈静", "0730-22223333", "岳阳市云溪区长炼路66号", "运营中",
"负责岳阳及周边区域工程项目承接与实施"));
seeds.add(branch("HD001", "凯迪(华东)工程技术有限公司", "分公司", "HQ001",
"华东地区", "李明", "021-66668888", "上海市浦东新区世纪大道1368号", "运营中",
"负责华东区(沪苏浙皖闽赣)市场开拓与项目执行"));
seeds.add(branch("HN001", "凯迪(华南)工程服务有限公司", "分公司", "HQ001",
"华南地区", "赵云", "020-33337777", "广州市天河区珠江新城华夏路1号", "运营中",
"负责华南区(粤桂琼)市场开拓与EPC工程管理"));
seeds.add(branch("CS001", "凯迪长沙办公室", "办事处", "HQ001",
"湖南长沙", "王芳", "0731-55556666", "长沙市雨花区劳动中路158号", "运营中",
"长沙本地业务联络与客户服务办事处"));
orgBranchRepo.saveAll(seeds);
}
private OrgBranch branch(String code, String name, String type, String parentCode,
String region, String manager, String phone, String address,
String status, String remark) {
OrgBranch b = new OrgBranch();
b.setCode(code);
b.setName(name);
b.setType(type);
b.setParentCode(parentCode);
b.setRegion(region);
b.setManager(manager);
b.setPhone(phone);
b.setAddress(address);
b.setStatus(status);
b.setRemark(remark);
b.setCreatedAt(Instant.now());
return b;
}
// ============================================================
// 2. ArApItem 应收/应付 演示数据(含 companySubject 精准关联分公司)
// ============================================================
private void seedArApItems() {
if (arApRepo.count() > 0) {
return;
}
List<ArApItem> seeds = new ArrayList<>();
// --- 岳阳分公司 应收账款 ---
seeds.add(arApItem("AR-YY-2026-001", "应收", "岳阳市政工程集团有限公司",
"HT-YY-2026-001", "湖南凯迪工程科技有限公司岳阳分公司",
"3800000.00", "2280000.00", "1520000.00", "部分核销", "2026-09-30"));
seeds.add(arApItem("AR-YY-2026-002", "应收", "岳阳县水务局",
"HT-YY-2026-002", "湖南凯迪工程科技有限公司岳阳分公司",
"1650000.00", "990000.00", "660000.00", "部分核销", "2026-08-31"));
seeds.add(arApItem("AR-YY-2026-003", "应收", "湖南云溪工业园管委会",
"HT-YY-2026-003", "湖南凯迪工程科技有限公司岳阳分公司",
"920000.00", "0.00", "920000.00", "未核销", "2026-12-31"));
seeds.add(arApItem("AR-YY-2026-004", "应收", "岳阳华能电厂",
"HT-YY-2026-004", "湖南凯迪工程科技有限公司岳阳分公司",
"560000.00", "560000.00", "0.00", "已结清", "2026-06-30"));
// --- 岳阳分公司 应付账款 ---
seeds.add(arApItem("AP-YY-2026-001", "应付", "岳阳通达设备租赁有限公司",
"HT-YY-2026-AP01", "湖南凯迪工程科技有限公司岳阳分公司",
"386000.00", "193000.00", "193000.00", "部分核销", "2026-07-31"));
seeds.add(arApItem("AP-YY-2026-002", "应付", "长沙华信网络科技有限公司",
"HT-YY-2026-AP02", "湖南凯迪工程科技有限公司岳阳分公司",
"128000.00", "0.00", "128000.00", "未核销", "2026-09-30"));
// --- 华东分公司 应收账款 ---
seeds.add(arApItem("AR-HD-2026-001", "应收", "上海浦东建设集团有限公司",
"HT-HD-2026-001", "凯迪(华东)工程技术有限公司",
"5200000.00", "3120000.00", "2080000.00", "部分核销", "2026-10-31"));
seeds.add(arApItem("AR-HD-2026-002", "应收", "江苏南京城市基建有限公司",
"HT-HD-2026-002", "凯迪(华东)工程技术有限公司",
"3100000.00", "1860000.00", "1240000.00", "部分核销", "2026-09-30"));
seeds.add(arApItem("AR-HD-2026-003", "应收", "浙江绍兴市污水处理有限公司",
"HT-HD-2026-003", "凯迪(华东)工程技术有限公司",
"1880000.00", "1128000.00", "752000.00", "部分核销", "2026-11-30"));
seeds.add(arApItem("AR-HD-2026-004", "应收", "安徽合肥市水务集团",
"HT-HD-2026-004", "凯迪(华东)工程技术有限公司",
"2650000.00", "0.00", "2650000.00", "未核销", "2026-12-31"));
// --- 华东分公司 应付账款 ---
seeds.add(arApItem("AP-HD-2026-001", "应付", "上海华申建材有限公司",
"HT-HD-2026-AP01", "凯迪(华东)工程技术有限公司",
"680000.00", "340000.00", "340000.00", "部分核销", "2026-08-31"));
seeds.add(arApItem("AP-HD-2026-002", "应付", "南京精工机械设备有限公司",
"HT-HD-2026-AP02", "凯迪(华东)工程技术有限公司",
"420000.00", "0.00", "420000.00", "未核销", "2026-10-31"));
seeds.add(arApItem("AP-HD-2026-003", "应付", "苏州翔宇临时用工服务有限公司",
"HT-HD-2026-AP03", "凯迪(华东)工程技术有限公司",
"185000.00", "92500.00", "92500.00", "部分核销", "2026-09-15"));
// --- 华南分公司 应收账款 ---
seeds.add(arApItem("AR-HN-2026-001", "应收", "广州市城市建设开发有限公司",
"HT-HN-2026-001", "凯迪(华南)工程服务有限公司",
"4800000.00", "2400000.00", "2400000.00", "部分核销", "2026-11-30"));
seeds.add(arApItem("AR-HN-2026-002", "应收", "深圳市水务集团有限公司",
"HT-HN-2026-002", "凯迪(华南)工程服务有限公司",
"3200000.00", "3200000.00", "0.00", "已结清", "2026-06-30"));
seeds.add(arApItem("AR-HN-2026-003", "应收", "广西南宁城投集团有限公司",
"HT-HN-2026-003", "凯迪(华南)工程服务有限公司",
"1960000.00", "980000.00", "980000.00", "部分核销", "2026-10-31"));
seeds.add(arApItem("AR-HN-2026-004", "应收", "海南省生态环境工程有限公司",
"HT-HN-2026-004", "凯迪(华南)工程服务有限公司",
"1100000.00", "0.00", "1100000.00", "未核销", "2027-03-31"));
// --- 华南分公司 应付账款 ---
seeds.add(arApItem("AP-HN-2026-001", "应付", "广州宏基建材有限公司",
"HT-HN-2026-AP01", "凯迪(华南)工程服务有限公司",
"540000.00", "270000.00", "270000.00", "部分核销", "2026-09-30"));
seeds.add(arApItem("AP-HN-2026-002", "应付", "深圳福田劳务派遣有限公司",
"HT-HN-2026-AP02", "凯迪(华南)工程服务有限公司",
"168000.00", "84000.00", "84000.00", "部分核销", "2026-08-31"));
// --- 总部 应收账款 ---
seeds.add(arApItem("AR-HQ-2026-001", "应收", "靖州供水项目业主方",
"HT-HQ-2026-001", "湖南凯迪工程科技有限公司",
"8600000.00", "5160000.00", "3440000.00", "部分核销", "2026-12-31"));
arApRepo.saveAll(seeds);
}
private ArApItem arApItem(String code, String arApType, String partyName,
String relatedRef, String companySubject,
String amount, String writtenOff, String unwrittenOff,
String status, String dueDate) {
ArApItem a = new ArApItem();
a.setCode(code);
a.setArApType(arApType);
a.setPartyName(partyName);
a.setRelatedRef(relatedRef);
a.setCompanySubject(companySubject);
a.setAmount(Money.of(Double.parseDouble(amount)));
a.setWrittenOff(Money.of(Double.parseDouble(writtenOff)));
a.setUnwrittenOff(Money.of(Double.parseDouble(unwrittenOff)));
a.setStatus(status);
a.setDueDate(dueDate);
a.setCreatedAt(Instant.now());
return a;
}
}