export const meta = { name: 'erp-p1-build', description: 'P1核心能力阻断:财务内核/MRP-BOM/合同闭环/存疑MET正名 4工作流并行(各只动自有文件,共享文件回传片段中央合并)', phases: [{ title: 'P1并行建设' }], } const COMMON = `黄金模板与约定(照抄风格):实体 domain/Financing.java、控制器 web/FinancingController.java(CRUD+业务动作+@Transactional级联)、前端 oa/pages/payment/financing.vue(MasterDataPage接真API)与 repayment.vue(自定义联动页)。 硬约束:①金额一律 BigDecimal + com.kaidi.oa.common.Money.of/add/sub/nz(无裸double算钱;乘法用BigDecimal.multiply再Money.of);②日期 String(YYYY-MM-DD),算天/折旧用 java.time.LocalDate/ChronoUnit;③控制器 @RestController @RequestMapping("/api/oa/<资源复数>"),返回 com.kaidi.oa.common.ApiResp.ok(),请求体 record,找不到抛 NotFoundException,校验抛 ApiException(400/409,中文),状态迁移/级联写 @Transactional;④仓库 extends JpaRepository 派生查询;⑤前端真实页接你的真后端(import { http } from 该页相对路径的 api/http;http.get/post/patch/del 已解包ApiResp.data;token键 oa.token),列表CRUD套 pages/masterdata/MasterDataPage.vue,工作流/联动页用 el-table+el-dialog;⑥无emoji(图标@element-plus/icons-vue)、无渐变(纯色蓝主调)、排版间距承载形态慎重(美观+简单易用);⑦类名/表名加域前缀,先 grep 确认与现有~94控制器/实体零撞名。 **关键纪律(避免并行冲突)**: - 你**只能新建文件**或编辑**你工作流明确拥有**的现有文件; - 凡需改动**共享文件**(config/AuthInterceptor.java、data/oaModules.ts、service/TriggerRuleEngine.java、service/WorkflowService.java、web/WorkOrderController.java、web/ContractController.java、web/CustomerController.java、web/OpportunityController.java),**一律不要自己改**,把"目标文件+精确改法(插入位置锚点+代码片段)"放进 sharedFileSnippets 字段,由主代理中央串行合并; - **不要运行 gradle/npm**(并行争用),逐文件仔细自审语法。` const RESULT = { type: 'object', additionalProperties: false, properties: { workstream: { type: 'string' }, backendFiles: { type: 'array', items: { type: 'string' } }, frontendFiles: { type: 'array', items: { type: 'string' } }, authFinancePrefixes: { 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', description: '要插入/替换处的唯一锚点文本(现有代码原文)' }, change: { type: 'string', description: '具体改法:在锚点后插入/替换为...的完整代码片段' }, why: { type: 'string' }, }, required: ['file', 'anchor', 'change', 'why'] }, }, endpoints: { type: 'array', items: { type: 'string' } }, summary: { type: 'string' }, }, required: ['workstream', 'backendFiles', 'frontendFiles', 'authFinancePrefixes', 'authSensitiveRead', 'navEntries', 'sharedFileSnippets', 'endpoints', 'summary'], } const WS = [ { key: 'fin', label: '财务内核', prompt: `工作流【财务内核】。审计:凭证无状态机/红冲、应收应付不自动生成核销、报销无预算校验、税务整模块缺、资产无折旧计提。 你拥有并可编辑:web/VoucherController.java + domain/Voucher.java(加凭证状态机:草稿→已审核→已过账 + 红冲冲销动作)。 新建(新文件):①税务 domain/TaxFiling.java(税种/所属期/计税依据/税率/应纳税额/状态)+仓库+web/TaxFilingController.java;②固定资产 domain/FixedAsset.java(资产编号/名称/原值/累计折旧/净值/折旧方法 年限平均/月折旧额/状态)+仓库+web/FixedAssetController.java(含 /{id}/depreciate 按月计提:本月折旧=原值/(年限*12),累计折旧累加,净值=原值-累计,折旧达原值即提完);③应收应付 domain/ArApItem.java(类型 应收/应付/关联合同或发票/金额/已核销/未核销/状态)+仓库+web/ArApController.java(含 /{id}/writeoff 核销:核销额累加,全核销置已结清)。 报销预算校验:做 web/ExpenseClaimController.java 或在新建的报销实体里,提交报销时查对应 Budget 剩余(读 BudgetRepository),超预算 ApiException(409,中文)。前端挂 finance 模块(凭证/税务/固定资产/应收应付/报销 真实页)。 共享文件(回传片段勿自改):若要让付款办结自动生成应付/凭证,给 service/TriggerRuleEngine.java 的片段;AuthInterceptor 财务前缀;oaModules finance 模块导航。`, }, { key: 'mfg', label: 'MRP/BOM', prompt: `工作流【MRP/BOM】。审计:MRP净需求(读库存/在途/在制)缺、BOM差异的actualCost/actualQty手填未从工单/领料自动回填。 你拥有并可编辑:web/BomAnalysisController.java(把差异核算的实际量/实际成本改为可从工单领料自动回填的端点)。 新建(新文件):①web/MrpController.java + service/MrpService.java:MRP净需求运算——给一个生产需求(产品+数量),按 BomItem 多级展开毛需求,减去 InventoryItem 现有库存、在途(采购在途,可先建字段或读现有)、在制,得净需求与建议采购/生产量;返回逐物料 {物料,毛需求,现有,在途,在制,净需求,建议}。②领料/工单回填:domain/MaterialIssue.java(工单领料:workOrderId/物料/数量/单价/金额)+仓库+web/MaterialIssueController.java,领料写入后可被 BOM差异核算读取做 actualQty/actualCost 自动回填。 前端挂 mfg 模块(MRP运算/领料 真实页)与 budget/contract 无关。 共享文件(回传片段勿自改):若需 WorkOrderController 暴露在制量,给片段;AuthInterceptor;oaModules mfg 模块导航。`, }, { key: 'contract', label: '合同闭环', prompt: `工作流【合同闭环】。审计(LOGIC_GAP):模板bodyTemplate无页面消费、自动化条款比对不存在、电子签/外部盖章未集成。 你拥有并可编辑:web/ContractTemplateController.java(确保能取模板正文 bodyTemplate)。 新建(新文件):①web/ClauseCompareController.java + service/ClauseCompareService.java:自动化条款比对——给一份合同草稿正文与模板编号,逐条比对(按句/关键条款:金额/工期/质保/违约/付款方式),输出偏差清单 {条款,模板值/草稿值,偏差类型 缺失/修改/新增,风险级别}。可用文本规则(关键词+数值抽取)实现,不需LLM。②电子签/外部盖章:web/EsignController.java——/contracts/{id}/esign 发起电子签(置状态待签→已签,记录签署人/时间/外部签章流水号),外部盖章系统对接标注为对接项留接口。 前端:在 contract 模块新建"模板起草"(选模板→bodyTemplate填充→生成草稿)、"条款比对"(贴草稿→比对→偏差表)、"电子签"页,接真后端。 共享文件(回传片段勿自改):若合同起草要写 Contract,给 web/ContractController.java 片段(新增"从模板起草"端点);AuthInterceptor;oaModules contract 模块导航。`, }, { key: 'met', label: '存疑MET正名', prompt: `工作流【存疑MET正名】。审计:①分公司/办事处无机构级目录与数据隔离(只是opportunity一个自由文本orgUnit);②生物质 autoInventory 联动是控制器私有方法,不走TriggerRuleEngine、无ruleKey/AutomationLog/幂等。 新建(新文件):①机构目录 domain/OrgBranch.java(机构编号/名称/类型 总部/分公司/办事处/上级/地区/负责人/状态)+仓库+web/OrgBranchController.java(CRUD),让分公司/办事处成为真实机构主数据;前端在 crm 或 masterdata 模块建"机构目录(分公司/办事处)"页。②前端建"分支机构经营看板"页:按 OrgBranch 维度聚合商机/客户(调现有 /opportunities /customers 带 orgUnit 过滤),体现机构级视图。 共享文件(回传片段勿自改,关键): - service/TriggerRuleEngine.java + web/WorkOrderController.java:把"已入库工单→自动生成成品库存"从 WorkOrderController 私有方法改为经 TriggerRuleEngine 正规联动(ruleKey 如 workorder.done.inventory + AutomationLog 留痕 + existsByInstanceIdAndRuleKey 幂等),与其余7条正规链同源。给出精确锚点+片段。 - 若 CustomerController/OpportunityController 需加 orgUnit/branchId 过滤,给片段。 AuthInterceptor(org-branches 写口收口);oaModules 导航。`, }, ] phase('P1并行建设') const results = await parallel(WS.map((w) => () => agent( `你是资深全栈工程师,为凯迪 ERP 平台做 P1 深度增强。${w.prompt}\n\n${COMMON}\n\n返回结构化结果:新建文件、你已编辑的自有文件、需中央合并的共享文件片段(sharedFileSnippets:file+anchor原文+change改法+why)、鉴权前缀、导航条目、关键端点、summary。`, { label: `P1:${w.key}`, phase: 'P1并行建设', schema: RESULT } ) )).then((a) => a.filter(Boolean)) return { built: results.length, results }