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,221 @@
package com.kaidi.oa.seed;
import com.kaidi.oa.domain.QualCert;
import com.kaidi.oa.domain.QualDeclTask;
import com.kaidi.oa.domain.QualDeclaration;
import com.kaidi.oa.domain.StaffCredential;
import com.kaidi.oa.domain.StaffDossier;
import com.kaidi.oa.domain.StaffDossierItem;
import com.kaidi.oa.repository.QualCertRepository;
import com.kaidi.oa.repository.QualDeclTaskRepository;
import com.kaidi.oa.repository.QualDeclarationRepository;
import com.kaidi.oa.repository.StaffCredentialRepository;
import com.kaidi.oa.repository.StaffDossierItemRepository;
import com.kaidi.oa.repository.StaffDossierRepository;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
/**
* 行政·资质管理办 / 综合部——资质台账、资质申报、人员证书、员工电子档案的演示种子。
*
* 幂等 + 非破坏:以各表是否已有数据为哨兵,仅在空表时灌入少量演示数据,让深水页面首屏不空。
* 日期以"当前日期 +/- 偏移"动态生成,使到期分级预警在任何时间运行都有 即将到期/逾期 样本。
*/
@Component
@Order(6)
public class AdminQualSeeder implements CommandLineRunner {
private final QualCertRepository certRepo;
private final QualDeclarationRepository declRepo;
private final QualDeclTaskRepository taskRepo;
private final StaffCredentialRepository credRepo;
private final StaffDossierRepository dossierRepo;
private final StaffDossierItemRepository itemRepo;
public AdminQualSeeder(QualCertRepository certRepo, QualDeclarationRepository declRepo,
QualDeclTaskRepository taskRepo, StaffCredentialRepository credRepo,
StaffDossierRepository dossierRepo, StaffDossierItemRepository itemRepo) {
this.certRepo = certRepo;
this.declRepo = declRepo;
this.taskRepo = taskRepo;
this.credRepo = credRepo;
this.dossierRepo = dossierRepo;
this.itemRepo = itemRepo;
}
@Override
public void run(String... args) {
Instant now = Instant.now();
LocalDate today = LocalDate.now();
if (certRepo.count() == 0) {
certRepo.save(cert("ZZ-1", "建筑工程施工总承包", "企业资质", "施工总承包", "JZ-2021-0356",
"凯迪建设工程有限公司", "住房和城乡建设部", "一级", "房屋建筑工程施工总承包",
today.minusYears(3).toString(), today.plusDays(40).toString(), "年检", "即将到期", now));
certRepo.save(cert("ZZ-2", "安全生产许可证", "企业资质", "安全生产许可证", "JZ-AQ-2019-1182",
"凯迪建设工程有限公司", "省住房和城乡建设厅", "不分级", "施工安全生产",
today.minusYears(4).toString(), today.plusDays(8).toString(), "年检", "即将到期", now));
certRepo.save(cert("ZZ-3", "市政公用工程施工总承包", "企业资质", "施工总承包", "JZ-2018-2210",
"凯迪建设工程有限公司", "省住房和城乡建设厅", "二级", "市政公用工程",
today.minusYears(5).toString(), today.minusDays(20).toString(), "年检", "已过期", now));
certRepo.save(cert("ZZ-4", "高新技术企业证书", "企业资质", "高新技术企业", "GR202144000123",
"凯迪科技有限公司", "省科技厅", "不分级", "技术研发",
today.minusYears(2).toString(), today.plusDays(150).toString(), "三年复评", "有效", now));
}
if (declRepo.count() == 0) {
QualDeclaration d1 = decl("ZSB-1", "市政公用工程施工总承包", "升级", "企业资质", "一级",
"资质管理办", "李工", "省住房和城乡建设厅", today.minusDays(60).toString(),
today.plusDays(30).toString(), "专家评审", 380000.0, now);
declRepo.save(d1);
taskRepo.save(task(d1.getId(), "注册人员配备", "人员", "王人事", today.plusDays(5).toString(), "进行中", now));
taskRepo.save(task(d1.getId(), "业绩材料整理", "业绩", "李工", today.plusDays(10).toString(), "待办", now));
taskRepo.save(task(d1.getId(), "网上填报", "填报", "张专员", today.plusDays(15).toString(), "待办", now));
declRepo.save(decl("ZSB-2", "ISO9001质量管理体系", "延续", "企业资质", "不分级",
"资质管理办", "陈工", "认证中心", today.minusDays(15).toString(),
today.plusDays(45).toString(), "内审中", 60000.0, now));
}
if (credRepo.count() == 0) {
credRepo.save(cred("张伟", "工程部", "一级建造师", "建筑工程", "JZ-2018-44521",
"省住建厅", today.minusYears(5).toString(), today.plusDays(120).toString(), "已占用", "凯迪科技园项目", now));
credRepo.save(cred("刘强", "工程部", "一级建造师", "市政公用", "JZ-2019-55102",
"省住建厅", today.minusYears(4).toString(), today.plusDays(25).toString(), "空闲", null, now));
credRepo.save(cred("赵敏", "技术部", "注册结构工程师", "结构", "JG-2017-33421",
"省人事厅", today.minusYears(6).toString(), today.minusDays(10).toString(), "空闲", null, now));
credRepo.save(cred("孙磊", "安全部", "注册安全工程师", "安全", "AQ-2020-12009",
"应急管理厅", today.minusYears(2).toString(), today.plusDays(300).toString(), "空闲", null, now));
}
if (dossierRepo.count() == 0) {
StaffDossier s1 = dossier("KD0001", "张伟", "工程部", "项目经理", today.minusYears(5).toString(),
"410101199001011234", today.plusDays(60).toString(), today.plusDays(20).toString(), now);
dossierRepo.save(s1);
itemRepo.save(item(s1.getId(), "基本信息", "入职登记表.pdf", "入职", now));
itemRepo.save(item(s1.getId(), "合同信息", "劳动合同(2021).pdf", "入职", now));
itemRepo.save(item(s1.getId(), "资质证书", "一级建造师证.jpg", "手工上传", now));
// 缺「培训记录」→ 完整性 75%
StaffDossier s2 = dossier("KD0002", "刘强", "工程部", "技术负责人", today.minusYears(3).toString(),
"410101199203054321", today.plusYears(2).toString(), today.plusDays(200).toString(), now);
dossierRepo.save(s2);
itemRepo.save(item(s2.getId(), "基本信息", "入职登记表.pdf", "入职", now));
}
}
private static QualCert cert(String code, String name, String category, String certType, String certNo,
String holder, String issuer, String level, String scope, String issueDate,
String expireDate, String reviewCycle, String status, Instant now) {
QualCert c = new QualCert();
c.setCode(code);
c.setName(name);
c.setCategory(category);
c.setCertType(certType);
c.setCertNo(certNo);
c.setHolder(holder);
c.setIssuer(issuer);
c.setLevel(level);
c.setScope(scope);
c.setIssueDate(issueDate);
c.setExpireDate(expireDate);
c.setReviewCycle(reviewCycle);
c.setStatus(status);
c.setCreatedAt(now);
return c;
}
private static QualDeclaration decl(String code, String qualName, String declType, String category,
String targetLevel, String dept, String owner, String externalOrg,
String planStart, String planDeadline, String status,
double budget, Instant now) {
QualDeclaration d = new QualDeclaration();
d.setCode(code);
d.setQualName(qualName);
d.setDeclType(declType);
d.setCategory(category);
d.setTargetLevel(targetLevel);
d.setDept(dept);
d.setOwner(owner);
d.setExternalOrg(externalOrg);
d.setPlanStart(planStart);
d.setPlanDeadline(planDeadline);
d.setStatus(status);
d.setBudget(BigDecimal.valueOf(budget).setScale(2, java.math.RoundingMode.HALF_UP));
d.setFeeSpent(BigDecimal.ZERO.setScale(2, java.math.RoundingMode.HALF_UP));
d.setCreatedAt(now);
return d;
}
private static QualDeclTask task(Long declId, String name, String taskType, String assignee,
String dueDate, String status, Instant now) {
QualDeclTask t = new QualDeclTask();
t.setDeclarationId(declId);
t.setName(name);
t.setTaskType(taskType);
t.setAssignee(assignee);
t.setDueDate(dueDate);
t.setStatus(status);
t.setCreatedAt(now);
return t;
}
private static StaffCredential cred(String personName, String dept, String credType, String major,
String credNo, String issuer, String issueDate, String expireDate,
String lockState, String lockedProject, Instant now) {
StaffCredential c = new StaffCredential();
c.setPersonName(personName);
c.setDept(dept);
c.setCredType(credType);
c.setMajor(major);
c.setCredNo(credNo);
c.setIssuer(issuer);
c.setIssueDate(issueDate);
c.setExpireDate(expireDate);
c.setLockState(lockState);
c.setLockedProject(lockedProject);
if ("已占用".equals(lockState)) {
c.setLockedDate(LocalDate.now().minusDays(15).toString());
}
c.setBorrowState("在库");
c.setCreatedAt(now);
return c;
}
private static StaffDossier dossier(String employeeNo, String name, String dept, String position,
String hireDate, String idCardNo, String idCardExpire,
String contractExpire, Instant now) {
StaffDossier d = new StaffDossier();
d.setEmployeeNo(employeeNo);
d.setName(name);
d.setDept(dept);
d.setPosition(position);
d.setHireDate(hireDate);
d.setIdCardNo(idCardNo);
d.setIdCardExpire(idCardExpire);
d.setContractExpire(contractExpire);
d.setDossierStatus("在职");
d.setCompleteness(0);
d.setCreatedAt(now);
d.setUpdatedAt(now);
return d;
}
private static StaffDossierItem item(Long dossierId, String category, String title,
String sourceEvent, Instant now) {
StaffDossierItem it = new StaffDossierItem();
it.setDossierId(dossierId);
it.setCategory(category);
it.setTitle(title);
it.setArchiveDate(LocalDate.now().toString());
it.setSourceEvent(sourceEvent);
it.setAutoArchived(Boolean.FALSE);
it.setCreatedAt(now);
return it;
}
}