恢复点(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>
107 lines
11 KiB
JavaScript
107 lines
11 KiB
JavaScript
export const meta = {
|
||
name: 'erp-w7-buildable',
|
||
description: 'W7:29机构并行攻剩余45个可建+种子/小bug缺口(补种子数据让空表功能可演示+修小bug+建真缺功能;跳过AI/LLM/外部)',
|
||
phases: [{ title: '补可建缺口W7' }],
|
||
}
|
||
|
||
const COMMON = `黄金模板:web/FinancingController.java、web/DevProjectController.java(状态机+审批自动展开)、聚合 web/FundPoolController.java、调度 @Scheduled、前端 oa/pages/payment/repayment.vue。
|
||
硬约束:①金额BigDecimal+com.kaidi.oa.common.Money(无裸double);②日期String+LocalDate/ChronoUnit;③控制器@RestController @RequestMapping("/api/oa/<资源>"),ApiResp.ok(),record请求体,NotFoundException/ApiException(400/409中文),状态迁移/级联@Transactional;④仓库extends JpaRepository派生查询;⑤前端真页接真后端(import { http },已解包ApiResp.data,token键oa.token)。
|
||
|
||
**⚠️W3量产5大崩溃坑——本轮绝不再犯(逐条自查)**:
|
||
[后端]①**Map.of()最多10对键值**(20参)——超过就编译失败"找不到合适的方法";多字段Map一律用 new java.util.LinkedHashMap<>() 逐.put()。
|
||
②**SQLite保留字绝不做列名/字段名**:references/primary/order/group/key/values/index/table/column/check/default/constraint/references/desc/asc等;若业务字段必须叫这名,Java字段可留但**必须**加@Column(name="安全别名")映射物理列(并import jakarta.persistence.Column),否则建表DDL崩"near xxx: syntax error"。
|
||
③**派生查询禁用ContainingIgnoreCase**(Hibernate6社区SQLite方言upper()类型校验崩,启动即挂)——一律用 Containing(SQLite的LIKE对ASCII本就大小写不敏感,中文无大小写,功能等价)。
|
||
[前端]④**el-table-column插槽**:写 #default="{ row }" 或 #default="{ row }: { row: any }";**绝不写 typeof X[0] / typeof X.value[0] 注解**(vue-tsc过但vite编译/类型崩),绝不套外层括号。
|
||
⑤**v-model必须是可写成员表达式**:绝不 v-model="!!expr" 或 v-model="fn()"(vite编译崩"must be a valid JavaScript member expression");需要派生可见性用 :model-value="!!x" @update:model-value="(v:boolean)=>{ if(!v) x=null }"。
|
||
⑥**图标必须真存在于@element-plus/icons-vue**(MobilePhone不存在→用Iphone;不确定就用 Document/List/Money/DataLine 等常见图标);http删除用 http.del() 不是 http.delete()。
|
||
⑦**具名插槽「<template #footer>」/「#header」/「#default」必须是组件(el-dialog/el-card/el-table-column)的直接子节点,绝不能嵌在「<div>」等HTML元素里**(否则vite vue编译器崩"Cannot read properties of undefined reading type",整个build失败)——dialog里要按状态切换footer时,footer提到dialog直属用「<template v-if>」切换,别把footer放进「<div v-if>」。
|
||
⑧**http参数形态**:「http.get<T>(url, query)」第2参直接就是query对象(「Record<string,string|number|boolean|null|undefined>」),**不是**「{ params: {...} }」;「http.post<T>(url, body, opts)」的opts用「query」键不是「params」;声明query变量用「const params: Record<string, any> = {}」(别用unknown)。模板里「Promise」/「window」不在Vue全局白名单,要用就在「<script setup>」定义函数包装再调。
|
||
⑨无emoji、无渐变(纯色蓝#2f6fed);类名表名加域前缀,grep确认与现有~623控制器/614实体零撞名。**优先新建实体;若必须给既有实体加字段,在summary注明"既有实体X加字段Y"(SQLite ddl-update不可靠加列,集成时统一schema-sync)**。
|
||
**关键纪律**:①只新建文件或改你机构自己的页;共享文件(AuthInterceptor/oaModules/TriggerRuleEngine/WorkflowService)不自改,放sharedFileSnippets;②不运行gradle/npm;③加法式不破坏现有链;④逐文件自审Java语法(import齐全[@PathVariable/@RequestBody/@Column/domain类]、ApiResp用record访问器.data()非.getData()、括号配平、record字段一致、实体setter存在、Map.of≤10对)与Vue语法(插槽不套外括号无typeof、v-model成员表达式、http.del、图标存在)。`
|
||
|
||
const RESULT = {
|
||
type: 'object', additionalProperties: false,
|
||
properties: {
|
||
unit: { type: 'string' },
|
||
backendFiles: { type: 'array', items: { type: 'string' } },
|
||
frontendFiles: { type: 'array', items: { type: 'string' } },
|
||
authSensitiveRead: { type: 'array', items: { type: 'string' } },
|
||
navEntries: { type: 'array', items: {
|
||
type: 'object', additionalProperties: false,
|
||
properties: { moduleId: { type: 'string' }, items: { type: 'array', items: {
|
||
type: 'object', additionalProperties: false,
|
||
properties: { key: { type: 'string' }, label: { type: 'string' }, kind: { type: 'string' }, path: { type: 'string' } },
|
||
required: ['key', 'label', 'kind', 'path'] } } },
|
||
required: ['moduleId', 'items'] } },
|
||
sharedFileSnippets: { type: 'array', items: {
|
||
type: 'object', additionalProperties: false,
|
||
properties: { file: { type: 'string' }, anchor: { type: 'string' }, change: { type: 'string' }, why: { type: 'string' } },
|
||
required: ['file', 'anchor', 'change', 'why'] } },
|
||
closedGaps: { type: 'array', items: { type: 'string' } },
|
||
skippedExternal: { type: 'array', items: { type: 'string' }, description: '本轮判定需外部系统而跳过的缺口' },
|
||
summary: { type: 'string' },
|
||
},
|
||
required: ['unit', 'backendFiles', 'frontendFiles', 'authSensitiveRead', 'navEntries', 'sharedFileSnippets', 'closedGaps', 'skippedExternal', 'summary'],
|
||
}
|
||
|
||
const GAPS = '/Users/qiu/Desktop/ERP/requirements/_xlsx_audit_w6post.json'
|
||
const SLICE = '/Users/qiu/Desktop/ERP/requirements/_req_slices'
|
||
const UNITS = [
|
||
['创新研发中心','产品开发部','rd','01_创新研发中心__产品开发部'],
|
||
['创新研发中心','申报服务部','rd','02_创新研发中心__申报服务部'],
|
||
['创新研发中心','知识产权部','rd','03_创新研发中心__知识产权部'],
|
||
['创新研发中心','实验室','lab','04_创新研发中心__实验室'],
|
||
['设计研究中心','咨询可研院','design','05_设计研究中心__咨询可研院'],
|
||
['设计研究中心','规划设计部','design','06_设计研究中心__规划设计部'],
|
||
['设计研究中心','工程监理部','supervision','07_设计研究中心__工程监理部'],
|
||
['制造管理中心','环保设备制造中心','mfg','08_制造管理中心__环保设备制造中心'],
|
||
['制造管理中心','生物质肥料制造中心','mfg','09_制造管理中心__生物质肥料制造中心'],
|
||
['工程管理中心','质安部','ehs','10_工程管理中心__质安部'],
|
||
['工程管理中心','资料室','archive','11_工程管理中心__资料室'],
|
||
['工程管理中心','专利工法办','rd','12_工程管理中心__专利工法办'],
|
||
['工程管理中心','成本控制部','budget','13_工程管理中心__成本控制部'],
|
||
['运营管理中心','城镇污水运营中心','ops','14_运营管理中心__城镇污水运营中心'],
|
||
['运营管理中心','工业废水运营中心','ops','15_运营管理中心__工业废水运营中心'],
|
||
['市场部','经营部','crm','16_市场部__经营部'],
|
||
['市场部','办事处','crm','17_市场部__办事处'],
|
||
['市场部','分公司','crm','18_市场部__分公司'],
|
||
['支付中心','结算中心','payment','19_支付中心__结算中心'],
|
||
['支付中心','财务部','finance','20_支付中心__财务部'],
|
||
['支付中心','金融办','payment','21_支付中心__金融办'],
|
||
['内控部','法务风险部','legal','22_内控部__法务风险部'],
|
||
['内控部','审计监察部','audit','23_内控部__审计监察部'],
|
||
['品牌推广部','宣传部','culture','24_品牌推广部__宣传部'],
|
||
['品牌推广部','项目文化','culture','25_品牌推广部__项目文化'],
|
||
['行政','综合部','hr','26_行政__综合部'],
|
||
['行政','信息部','itasset','27_行政__信息部'],
|
||
['行政','办公室','admin','28_行政__办公室'],
|
||
['行政','后勤','admin','29_行政__后勤'],
|
||
]
|
||
|
||
log(`W4:${UNITS.length} 机构补94个可建PARTIAL缺口`)
|
||
phase('补可建缺口W7')
|
||
const results = await parallel(UNITS.map(([dept, sub, mod, fn]) => () =>
|
||
agent(
|
||
`你是资深全栈工程师,为凯迪 ERP【把这个机构剩余的"可建PARTIAL缺口"推向真正MET】:${dept} / ${sub}。
|
||
第一步 运行 python3 取本机构的剩余可建缺口:python3 -c "import json;d=json.load(open('${GAPS}'));[print('['+g['severity']+']',g['module'],':',g['gap']) for g in d['buildable'] if '${sub}' in g['area']]"
|
||
若上面输出为空,说明本机构无可建缺口,直接返回空结果(closedGaps=[])。
|
||
第二步 Read 需求切片 ${SLICE}/${fn}.txt 对照理解。
|
||
第三步 grep/读码确认现状(本机构已有很多控制器/实体),针对每条可建缺口补完真实现:缺的结构化实体(如测试计划TestPlan)/状态机端点/甘特或日历聚合API/自动计算逻辑(如标准成本从BOM+工艺路线展开)/跨模块联动写入(如发布BOM到生产)/规则引擎自动触发钩子/调度Job(@Scheduled自动下架/生成)/统计分析端点(上市周期/成功率/三维投入)等。新建实体+仓库+控制器(+服务)与真实前端页,把PARTIAL真正补全到可用。
|
||
**本轮特别注意三类廉价高值修复**:①**缺口提到"表空/0条/无种子数据/活体返回null,功能在但无数据演示"**→在对应的 seed/ 目录 Seeder(@Order)或新建Seeder补**真实演示数据**(金额BigDecimal,日期合理,跨状态多条),让该功能活体可见;②**缺口提到"路径错误/字段需调试/返回400/正确路径为X"等小bug**→直接读码定位并修正(改控制器路径/补@RequestParam/修字段名/补@PathVariable),这类一行修复最划算;③**缺口提到"前端缺可视化页/无看板/无录入入口/页面调错接口"**→新建或修正前端.vue页接对后端真接口。
|
||
**优先 high>med>low**。**凡缺口本质需要外部系统的**(真CA电子签/数字证书/区块链存证、BIM碰撞、CAD/Revit解析、AI语义/知识图谱/向量、JPush/APNs推送网关、语音录入、IoT/SCADA、银企直连/税务直连)——放入skippedExternal数组并简述,本轮不做。
|
||
前端页挂模块 '${mod}'。
|
||
|
||
${COMMON}
|
||
|
||
返回结构化结果(closedGaps列出真正补完到MET的缺口模块名;skippedExternal列出跳过的;导航 moduleId='${mod}')。`,
|
||
{ label: `W7:${sub}`, phase: '补可建缺口W7', schema: RESULT, model: 'sonnet' }
|
||
)
|
||
)).then((a) => a.filter(Boolean))
|
||
|
||
const closed = results.reduce((s, r) => s + (r.closedGaps || []).length, 0)
|
||
const skipped = results.reduce((s, r) => s + (r.skippedExternal || []).length, 0)
|
||
const bf = results.reduce((s, r) => s + (r.backendFiles || []).length, 0)
|
||
const ff = results.reduce((s, r) => s + (r.frontendFiles || []).length, 0)
|
||
log(`W7完成: 补完${closed}缺口 跳过外部${skipped}; 后端文件${bf} 前端页${ff}`)
|
||
return { built: results.length, total: UNITS.length, closed, skipped, backendFiles: bf, frontendFiles: ff, results }
|