恢复点(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>
742 lines
40 KiB
Java
742 lines
40 KiB
Java
package com.kaidi.oa.seed;
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
|
import com.kaidi.oa.domain.FormTemplate;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* Builds the built-in form template catalog, porting the frontend
|
|
* src/oa/engine/templates/*.ts catalog (11 templates). Each template's form and
|
|
* flow schemas are serialized to JSON TEXT with the exact same structure the
|
|
* frontend produces (FormSchema{title,rows,subTables}, FlowSchema{nodes,edges,
|
|
* parallels,branches}).
|
|
*
|
|
* Order mirrors the frontend catalog array:
|
|
* baohan-payment, expense-reimburse, leave-apply, seal-apply, general-collab,
|
|
* supplier-access, business-trip, purchase-contract, payment-apply,
|
|
* overtime-apply, expense-cond.
|
|
*/
|
|
public final class TemplateSeedData {
|
|
|
|
private final ObjectMapper om;
|
|
|
|
public TemplateSeedData(ObjectMapper om) {
|
|
this.om = om;
|
|
}
|
|
|
|
// ---- small schema-builder DSL mirroring the frontend types ----
|
|
|
|
private ObjectNode field(String id, String label, String type) {
|
|
ObjectNode f = om.createObjectNode();
|
|
f.put("id", id);
|
|
f.put("label", label);
|
|
f.put("type", type);
|
|
return f;
|
|
}
|
|
|
|
private ObjectNode req(ObjectNode f) {
|
|
f.put("required", true);
|
|
return f;
|
|
}
|
|
|
|
private ObjectNode full(ObjectNode f) {
|
|
f.put("fullWidth", true);
|
|
return f;
|
|
}
|
|
|
|
private ObjectNode opts(ObjectNode f, String... options) {
|
|
ArrayNode arr = f.putArray("options");
|
|
for (String o : options) {
|
|
arr.add(o);
|
|
}
|
|
return f;
|
|
}
|
|
|
|
private ObjectNode col(String id, String label, String type, Integer width) {
|
|
ObjectNode c = om.createObjectNode();
|
|
c.put("id", id);
|
|
c.put("label", label);
|
|
c.put("type", type);
|
|
if (width != null) {
|
|
c.put("width", width);
|
|
}
|
|
return c;
|
|
}
|
|
|
|
private ObjectNode subTable(String id, String title, ObjectNode... columns) {
|
|
ObjectNode st = om.createObjectNode();
|
|
st.put("id", id);
|
|
st.put("title", title);
|
|
ArrayNode cols = st.putArray("columns");
|
|
for (ObjectNode c : columns) {
|
|
cols.add(c);
|
|
}
|
|
return st;
|
|
}
|
|
|
|
private ObjectNode node(String id, String label, String type) {
|
|
ObjectNode n = om.createObjectNode();
|
|
n.put("id", id);
|
|
n.put("label", label);
|
|
n.put("type", type);
|
|
return n;
|
|
}
|
|
|
|
private ArrayNode edge(String from, String to) {
|
|
ArrayNode e = om.createArrayNode();
|
|
e.add(from);
|
|
e.add(to);
|
|
return e;
|
|
}
|
|
|
|
/** Assemble a form schema node. rows is a list of row arrays of fields. */
|
|
private String formJson(String title, List<ObjectNode[]> rows, ObjectNode... subTables) {
|
|
ObjectNode form = om.createObjectNode();
|
|
form.put("title", title);
|
|
ArrayNode rowsArr = form.putArray("rows");
|
|
for (ObjectNode[] row : rows) {
|
|
ArrayNode r = rowsArr.addArray();
|
|
for (ObjectNode f : row) {
|
|
r.add(f);
|
|
}
|
|
}
|
|
if (subTables.length > 0) {
|
|
ArrayNode sts = form.putArray("subTables");
|
|
for (ObjectNode st : subTables) {
|
|
sts.add(st);
|
|
}
|
|
}
|
|
return form.toString();
|
|
}
|
|
|
|
private FormTemplate tpl(String id, String name, String category, String org,
|
|
String publishedAt, String instructions,
|
|
String formJson, String flowJson) {
|
|
FormTemplate t = new FormTemplate();
|
|
t.setId(id);
|
|
t.setName(name);
|
|
t.setCategory(category);
|
|
t.setOrg(org);
|
|
t.setPublishedAt(publishedAt);
|
|
t.setInstructions(instructions);
|
|
t.setFormSchemaJson(formJson);
|
|
t.setFlowSchemaJson(flowJson);
|
|
t.setBuiltin(true);
|
|
return t;
|
|
}
|
|
|
|
private List<ObjectNode[]> rows(ObjectNode[]... rows) {
|
|
return List.of(rows);
|
|
}
|
|
|
|
private ObjectNode[] row(ObjectNode... fields) {
|
|
return fields;
|
|
}
|
|
|
|
/** Build all 14 built-in templates (含申报计划审批模板 + 合同法务审批模板). */
|
|
public List<FormTemplate> all() {
|
|
List<FormTemplate> list = new ArrayList<>();
|
|
list.add(baohan());
|
|
list.add(reimburse());
|
|
list.add(leave());
|
|
list.add(seal());
|
|
list.add(general());
|
|
list.add(supplier());
|
|
list.add(businessTrip());
|
|
list.add(purchaseContract());
|
|
list.add(paymentApply());
|
|
list.add(overtime());
|
|
list.add(expenseCond());
|
|
list.add(financingApply());
|
|
list.add(rdDeclPlanApprove());
|
|
list.add(legalContractReview());
|
|
return list;
|
|
}
|
|
|
|
// ===================== individual templates =====================
|
|
|
|
private FormTemplate baohan() {
|
|
String form = formJson("保函付款申请单", rows(
|
|
row(req(field("applicant", "申请人", "person")), req(field("applyDate", "申请时间", "date"))),
|
|
row(req(field("applyDept", "申请部门", "dept")), req(field("deadline", "截止付款时间", "date"))),
|
|
row(full(req(opts(field("payType", "付款类型", "radio"), "投标保函", "预付款保函", "履约保函", "代理费")))),
|
|
row(full(req(field("payUnit", "付款单位(保函开具单位)", "text")))),
|
|
row(full(req(field("payAccount", "付款银行账号", "text")))),
|
|
row(full(field("office", "办事处", "text"))),
|
|
row(full(req(field("projectName", "项目名称", "text")))),
|
|
row(full(field("payReason", "付款事由", "textarea")))
|
|
), subTable("payeeDetails", "收款明细",
|
|
col("payeeName", "收款人户名", "text", null),
|
|
col("payeeAccount", "收款人账号", "text", null),
|
|
col("payeeBank", "收款人开户行", "text", null),
|
|
col("amount", "金额", "money", 140),
|
|
col("voucher", "凭证附件", "attachment", 100)));
|
|
|
|
ObjectNode flow = om.createObjectNode();
|
|
ArrayNode nodes = flow.putArray("nodes");
|
|
nodes.add(node("start", "发起者", "start"));
|
|
nodes.add(node("deptHead", "发起者部门主管", "审批"));
|
|
nodes.add(node("cashierMaker", "制单出纳", "知会"));
|
|
nodes.add(node("cashierReview", "复核出纳", "知会"));
|
|
nodes.add(node("settleAccount", "结算会计", "审批"));
|
|
nodes.add(node("financeHead", "财务部负责人", "审批"));
|
|
nodes.add(node("chairman", "董事长", "审批"));
|
|
nodes.add(node("firstCashier", "第一出纳", "协同"));
|
|
nodes.add(node("settleNotify", "结算会计", "知会"));
|
|
nodes.add(node("reviewApprove", "复核出纳", "审批"));
|
|
nodes.add(node("end", "结束", "end"));
|
|
ArrayNode edges = flow.putArray("edges");
|
|
edges.add(edge("start", "deptHead"));
|
|
edges.add(edge("deptHead", "cashierMaker"));
|
|
edges.add(edge("deptHead", "cashierReview"));
|
|
edges.add(edge("cashierMaker", "settleAccount"));
|
|
edges.add(edge("cashierReview", "settleAccount"));
|
|
edges.add(edge("settleAccount", "financeHead"));
|
|
edges.add(edge("financeHead", "chairman"));
|
|
edges.add(edge("chairman", "firstCashier"));
|
|
edges.add(edge("firstCashier", "settleNotify"));
|
|
edges.add(edge("settleNotify", "reviewApprove"));
|
|
edges.add(edge("reviewApprove", "end"));
|
|
ArrayNode parallels = flow.putArray("parallels");
|
|
ArrayNode p = parallels.addArray();
|
|
p.add("cashierMaker");
|
|
p.add("cashierReview");
|
|
|
|
String instructions =
|
|
"一、适用范围:本流程适用于投标保函、预付款保函、履约保函及代理费的对外付款申请。\n" +
|
|
"二、填报要求:\n" +
|
|
" 1. 申请人需完整填写付款单位、付款银行账号、办事处及项目名称;\n" +
|
|
" 2. 收款明细子表须逐行登记收款人户名、账号、开户行、金额,并上传凭证附件;\n" +
|
|
" 3. 付款事由应说明保函用途与付款依据。\n" +
|
|
"三、流程节点说明:\n" +
|
|
" 发起者提交后先经发起者部门主管审批;随后由制单出纳、复核出纳并行知会;\n" +
|
|
" 经结算会计、财务部负责人、董事长逐级审批;最后由第一出纳协同制单、\n" +
|
|
" 结算会计知会、复核出纳审批后办结归档。\n" +
|
|
"四、注意事项:金额较大或跨境付款的,应附合同及保函正本扫描件。";
|
|
return tpl("baohan-payment", "保函付款申请单", "财务审批", "信息中心", "2024-09-12",
|
|
instructions, form, flow.toString());
|
|
}
|
|
|
|
private FormTemplate reimburse() {
|
|
String form = formJson("费用报销单", rows(
|
|
row(req(field("applicant", "申请人", "person")), req(field("reimburseDept", "报销部门", "dept"))),
|
|
row(req(opts(field("expenseType", "费用类型", "select"), "差旅费", "招待费", "办公费", "培训费", "其他")),
|
|
req(field("amount", "金额", "money")))
|
|
), subTable("expenseItems", "报销明细",
|
|
col("item", "费用项", "text", null),
|
|
col("itemAmount", "金额", "money", 140),
|
|
col("remark", "备注", "text", null)));
|
|
|
|
ObjectNode flow = simpleFlow(new String[][]{
|
|
{"start", "发起者", "start"},
|
|
{"deptHead", "部门主管", "审批"},
|
|
{"financeAudit", "财务审核", "审批"},
|
|
{"gm", "总经理", "审批"},
|
|
{"end", "结束", "end"}
|
|
});
|
|
String instructions =
|
|
"费用报销流程:申请人填写报销部门、费用类型、金额及报销明细后提交,\n" +
|
|
"经部门主管审批、财务审核、总经理审批后办结。明细金额合计须与申请金额一致,\n" +
|
|
"并附合规发票。";
|
|
return tpl("expense-reimburse", "费用报销", "财务审批", "财务部", "2024-07-21",
|
|
instructions, form, flow.toString());
|
|
}
|
|
|
|
private FormTemplate leave() {
|
|
String form = formJson("请假申请", rows(
|
|
row(req(field("applicant", "申请人", "person"))),
|
|
row(full(req(opts(field("leaveType", "请假类型", "radio"), "事假", "病假", "年假")))),
|
|
row(req(field("startTime", "开始时间", "datetime")), req(field("endTime", "结束时间", "datetime"))),
|
|
row(full(req(field("reason", "请假事由", "textarea"))))
|
|
));
|
|
ObjectNode flow = simpleFlow(new String[][]{
|
|
{"start", "发起者", "start"},
|
|
{"deptHead", "部门主管", "审批"},
|
|
{"hr", "人力资源部", "审批"},
|
|
{"end", "结束", "end"}
|
|
});
|
|
String instructions =
|
|
"请假申请流程:员工填写请假类型、起止时间与事由后提交,经部门主管审批、\n" +
|
|
"人力资源部审批后生效。事假/病假需提前一个工作日提交,病假应附就诊证明。";
|
|
return tpl("leave-apply", "请假申请", "人力资源", "行政部", "2024-06-03",
|
|
instructions, form, flow.toString());
|
|
}
|
|
|
|
private FormTemplate seal() {
|
|
String form = formJson("用印申请单", rows(
|
|
row(req(field("applicant", "申请人", "person")), req(field("applyDate", "申请时间", "date"))),
|
|
row(req(opts(field("sealType", "印章类型", "select"), "公章", "合同章", "财务章", "法人章")),
|
|
req(field("copies", "份数", "number"))),
|
|
row(full(req(field("docName", "文件名称", "text")))),
|
|
row(full(req(field("reason", "用印事由", "textarea"))))
|
|
));
|
|
ObjectNode flow = simpleFlow(new String[][]{
|
|
{"start", "发起者", "start"},
|
|
{"deptHead", "部门主管", "审批"},
|
|
{"adminHead", "行政负责人", "审批"},
|
|
{"end", "结束", "end"}
|
|
});
|
|
String instructions =
|
|
"用印申请:填写用印事由、印章类型、文件名称及份数,经部门主管、行政负责人审批后用印。";
|
|
return tpl("seal-apply", "用印申请", "行政部", "行政部", "2024-05-16",
|
|
instructions, form, flow.toString());
|
|
}
|
|
|
|
private FormTemplate general() {
|
|
String form = formJson("通用事项", rows(
|
|
row(full(req(field("title", "标题", "text")))),
|
|
row(req(field("applicant", "发起人", "person"))),
|
|
row(full(req(field("content", "正文", "textarea"))))
|
|
));
|
|
ObjectNode flow = simpleFlow(new String[][]{
|
|
{"start", "发起者", "start"},
|
|
{"deptHead", "部门主管", "审批"},
|
|
{"end", "结束", "end"}
|
|
});
|
|
String instructions =
|
|
"通用协同事项:填写标题与正文后提交,经部门主管审批后办结,适用于无固定表单的一般性事项。";
|
|
return tpl("general-collab", "通用事项", "公用模板", "信息中心", "2024-01-08",
|
|
instructions, form, flow.toString());
|
|
}
|
|
|
|
private FormTemplate supplier() {
|
|
String form = formJson("供应商准入申请单", rows(
|
|
row(req(field("applicant", "申请人", "person")), req(field("applyDate", "申请时间", "date"))),
|
|
row(full(req(field("supplierName", "供应商名称", "text")))),
|
|
row(req(field("creditCode", "统一社会信用代码", "text")), field("regCapital", "注册资本", "money")),
|
|
row(req(field("contact", "联系人", "text")), req(field("phone", "联系电话", "text"))),
|
|
row(full(req(opts(field("supplyCategory", "供应类别", "checkbox"),
|
|
"原材料", "设备", "工程服务", "办公用品", "其他")))),
|
|
row(full(req(opts(field("cooperation", "合作意向", "radio"), "长期合作", "单次采购", "战略合作")))),
|
|
row(full(req(field("reason", "准入理由", "textarea"))))
|
|
), subTable("qualifications", "资质证照明细",
|
|
col("certName", "证照名称", "text", null),
|
|
col("certNo", "证照编号", "text", null),
|
|
col("validUntil", "有效期至", "date", 140),
|
|
col("attach", "扫描件", "attachment", 100)));
|
|
|
|
ObjectNode flow = om.createObjectNode();
|
|
ArrayNode nodes = flow.putArray("nodes");
|
|
nodes.add(node("start", "发起者", "start"));
|
|
nodes.add(node("purchaseLead", "采购主管", "审批"));
|
|
nodes.add(node("qualityDept", "质量部", "知会"));
|
|
nodes.add(node("financeDept", "财务部", "知会"));
|
|
nodes.add(node("purchaseDirector", "采购总监", "审批"));
|
|
nodes.add(node("vp", "分管副总", "审批"));
|
|
nodes.add(node("end", "结束", "end"));
|
|
ArrayNode edges = flow.putArray("edges");
|
|
edges.add(edge("start", "purchaseLead"));
|
|
edges.add(edge("purchaseLead", "qualityDept"));
|
|
edges.add(edge("purchaseLead", "financeDept"));
|
|
edges.add(edge("qualityDept", "purchaseDirector"));
|
|
edges.add(edge("financeDept", "purchaseDirector"));
|
|
edges.add(edge("purchaseDirector", "vp"));
|
|
edges.add(edge("vp", "end"));
|
|
ArrayNode parallels = flow.putArray("parallels");
|
|
ArrayNode p = parallels.addArray();
|
|
p.add("qualityDept");
|
|
p.add("financeDept");
|
|
|
|
String instructions =
|
|
"一、适用范围:本流程适用于新增供应商的准入审核及合格供应商名录登记。\n" +
|
|
"二、填报要求:\n" +
|
|
" 1. 申请人需完整填写供应商名称、统一社会信用代码、联系人及联系电话;\n" +
|
|
" 2. 资质证照子表须逐行登记证照名称、证照编号、有效期,并上传扫描件附件;\n" +
|
|
" 3. 供应类别与合作意向应如实勾选,准入理由须说明引入背景。\n" +
|
|
"三、流程节点说明:\n" +
|
|
" 发起者提交后先经采购主管审批;随后由质量部、财务部并行知会进行资质与\n" +
|
|
" 信用初核;经采购总监、分管副总逐级审批后完成准入登记。\n" +
|
|
"四、注意事项:证照缺失或在有效期外的,不予准入。";
|
|
return tpl("supplier-access", "供应商准入申请", "采购管理", "采购部", "2024-10-18",
|
|
instructions, form, flow.toString());
|
|
}
|
|
|
|
private FormTemplate businessTrip() {
|
|
String form = formJson("出差申请单", rows(
|
|
row(req(field("applicant", "出差人", "person")), req(field("applyDept", "所属部门", "dept"))),
|
|
row(req(field("destination", "出差目的地", "text")),
|
|
req(opts(field("transport", "主要交通工具", "select"), "飞机", "高铁", "汽车", "其他"))),
|
|
row(req(field("startTime", "开始时间", "datetime")), req(field("endTime", "结束时间", "datetime"))),
|
|
row(full(field("companions", "同行人员", "person"))),
|
|
row(req(field("budget", "预算金额", "money"))),
|
|
row(full(req(field("reason", "出差事由", "textarea"))))
|
|
), subTable("itinerary", "行程明细",
|
|
col("date", "日期", "date", 140),
|
|
col("fromTo", "行程(出发-到达)", "text", null),
|
|
col("wayTransport", "交通方式", "text", 120),
|
|
col("estCost", "预计费用", "money", 140)));
|
|
|
|
ObjectNode flow = simpleFlow(new String[][]{
|
|
{"start", "发起者", "start"},
|
|
{"deptHead", "部门主管", "审批"},
|
|
{"adminDept", "行政部", "知会"},
|
|
{"gm", "总经理", "审批"},
|
|
{"end", "结束", "end"}
|
|
});
|
|
String instructions =
|
|
"出差申请流程:申请人填写出差事由、目的地、起止时间、同行人员及预算后提交,\n" +
|
|
"经部门主管审批、行政部知会安排行程后,由总经理审批办结。行程明细须逐段登记\n" +
|
|
"交通方式与预计费用,单次预算超过 5000 元的应附说明。";
|
|
return tpl("business-trip", "出差申请", "行政部", "行政部", "2024-08-09",
|
|
instructions, form, flow.toString());
|
|
}
|
|
|
|
private FormTemplate purchaseContract() {
|
|
String form = formJson("采购合同会签单", rows(
|
|
row(req(field("applicant", "申请人", "person")), req(field("applyDept", "申请部门", "dept"))),
|
|
row(full(req(field("contractName", "合同名称", "text")))),
|
|
row(full(req(field("counterparty", "对方单位", "text")))),
|
|
row(req(field("amount", "合同金额", "money")), req(field("signDate", "签订日期", "date"))),
|
|
row(req(opts(field("payMethod", "付款方式", "select"), "预付款", "货到付款", "分期付款", "验收后付款")),
|
|
opts(field("contractType", "合同类型", "radio"), "框架协议", "一次性采购", "长期供货")),
|
|
row(full(field("remark", "会签说明", "textarea")))
|
|
), subTable("purchaseItems", "采购明细",
|
|
col("material", "物料名称", "text", null),
|
|
col("spec", "规格型号", "text", null),
|
|
col("qty", "数量", "number", 100),
|
|
col("unitPrice", "单价", "money", 120),
|
|
col("itemAmount", "金额", "money", 140)));
|
|
|
|
ObjectNode flow = om.createObjectNode();
|
|
ArrayNode nodes = flow.putArray("nodes");
|
|
nodes.add(node("start", "发起者", "start"));
|
|
nodes.add(node("purchaseLead", "采购主管", "审批"));
|
|
nodes.add(node("legalDept", "法务部", "协同"));
|
|
nodes.add(node("financeDept", "财务部", "知会"));
|
|
nodes.add(node("purchaseDirector", "采购总监", "审批"));
|
|
nodes.add(node("vp", "分管副总", "审批"));
|
|
nodes.add(node("gm", "总经理", "审批"));
|
|
nodes.add(node("end", "结束", "end"));
|
|
ArrayNode edges = flow.putArray("edges");
|
|
edges.add(edge("start", "purchaseLead"));
|
|
edges.add(edge("purchaseLead", "legalDept"));
|
|
edges.add(edge("purchaseLead", "financeDept"));
|
|
edges.add(edge("legalDept", "purchaseDirector"));
|
|
edges.add(edge("financeDept", "purchaseDirector"));
|
|
edges.add(edge("purchaseDirector", "vp"));
|
|
edges.add(edge("vp", "gm"));
|
|
edges.add(edge("gm", "end"));
|
|
ArrayNode parallels = flow.putArray("parallels");
|
|
ArrayNode p = parallels.addArray();
|
|
p.add("legalDept");
|
|
p.add("financeDept");
|
|
|
|
String instructions =
|
|
"一、适用范围:本流程适用于采购合同签订前的多部门会签与用印审批。\n" +
|
|
"二、填报要求:\n" +
|
|
" 1. 申请人需完整填写合同名称、对方单位、合同金额、付款方式及签订日期;\n" +
|
|
" 2. 采购明细子表须逐行登记物料名称、规格、数量、单价、金额;\n" +
|
|
" 3. 合同正本扫描件应作为附件随单上传。\n" +
|
|
"三、流程节点说明:\n" +
|
|
" 发起者提交后先经采购主管审批;随后由法务部协同审查条款、财务部并行知会\n" +
|
|
" 核对资金;经采购总监、分管副总、总经理逐级审批后用印签订。\n" +
|
|
"四、注意事项:合同金额与采购明细合计须一致,重大合同应附立项依据。";
|
|
return tpl("purchase-contract", "采购合同会签", "采购管理", "采购部", "2024-11-05",
|
|
instructions, form, flow.toString());
|
|
}
|
|
|
|
private FormTemplate paymentApply() {
|
|
String form = formJson("付款申请单", rows(
|
|
row(req(field("applicant", "申请人", "person")), req(field("applyDept", "申请部门", "dept"))),
|
|
row(full(req(field("payee", "收款单位", "text")))),
|
|
row(req(field("amount", "付款金额", "money")), req(field("planDate", "计划付款日期", "date"))),
|
|
row(req(opts(field("payMethod", "付款方式", "select"), "银行转账", "电汇", "承兑汇票", "现金")),
|
|
field("contractNo", "关联合同号", "text")),
|
|
row(full(req(opts(field("payCategory", "付款类别", "radio"),
|
|
"货款", "服务费", "保证金", "预付款", "其他")))),
|
|
row(full(req(field("purpose", "付款用途", "textarea"))))
|
|
), subTable("payDetails", "付款明细",
|
|
col("payeeName", "收款户名", "text", null),
|
|
col("payeeBank", "开户行", "text", null),
|
|
col("payeeAccount", "账号", "text", null),
|
|
col("detailAmount", "金额", "money", 140),
|
|
col("invoice", "发票附件", "attachment", 100)));
|
|
|
|
ObjectNode flow = om.createObjectNode();
|
|
ArrayNode nodes = flow.putArray("nodes");
|
|
nodes.add(node("start", "发起者", "start"));
|
|
nodes.add(node("deptHead", "部门主管", "审批"));
|
|
nodes.add(node("financeAudit", "财务审核", "审批"));
|
|
nodes.add(node("cashierMaker", "制单出纳", "知会"));
|
|
nodes.add(node("cashierReview", "复核出纳", "知会"));
|
|
nodes.add(node("financeHead", "财务负责人", "审批"));
|
|
nodes.add(node("gm", "总经理", "审批"));
|
|
nodes.add(node("cashierPay", "出纳付款", "协同"));
|
|
nodes.add(node("end", "结束", "end"));
|
|
ArrayNode edges = flow.putArray("edges");
|
|
edges.add(edge("start", "deptHead"));
|
|
edges.add(edge("deptHead", "financeAudit"));
|
|
edges.add(edge("financeAudit", "cashierMaker"));
|
|
edges.add(edge("financeAudit", "cashierReview"));
|
|
edges.add(edge("cashierMaker", "financeHead"));
|
|
edges.add(edge("cashierReview", "financeHead"));
|
|
edges.add(edge("financeHead", "gm"));
|
|
edges.add(edge("gm", "cashierPay"));
|
|
edges.add(edge("cashierPay", "end"));
|
|
ArrayNode parallels = flow.putArray("parallels");
|
|
ArrayNode p = parallels.addArray();
|
|
p.add("cashierMaker");
|
|
p.add("cashierReview");
|
|
|
|
String instructions =
|
|
"一、适用范围:本流程适用于供应商货款、服务费、保证金等对外付款申请。\n" +
|
|
"二、填报要求:\n" +
|
|
" 1. 申请人需完整填写收款单位、付款用途、付款方式及计划付款日期;\n" +
|
|
" 2. 付款明细子表须逐行登记收款户名、开户行、账号、金额,并上传发票附件;\n" +
|
|
" 3. 关联合同号应如实填写,以便财务核对。\n" +
|
|
"三、流程节点说明:\n" +
|
|
" 发起者提交后经部门主管、财务审核逐级审批;随后由制单出纳、复核出纳并行\n" +
|
|
" 知会;经财务负责人、总经理审批后,由出纳协同完成付款。\n" +
|
|
"四、注意事项:付款金额与明细合计须一致,无合同支撑的付款不予受理。";
|
|
return tpl("payment-apply", "付款申请", "财务审批", "财务部", "2024-09-28",
|
|
instructions, form, flow.toString());
|
|
}
|
|
|
|
private FormTemplate overtime() {
|
|
String form = formJson("加班申请单", rows(
|
|
row(req(field("applicant", "申请人", "person")), req(field("applyDept", "所属部门", "dept"))),
|
|
row(full(req(opts(field("overtimeType", "加班类型", "radio"),
|
|
"工作日加班", "休息日加班", "节假日加班")))),
|
|
row(req(field("startTime", "开始时间", "datetime")), req(field("endTime", "结束时间", "datetime"))),
|
|
row(req(opts(field("compensation", "补偿方式", "select"), "调休", "加班费")),
|
|
req(field("totalHours", "合计时长(小时)", "number"))),
|
|
row(full(req(field("reason", "加班事由", "textarea"))))
|
|
), subTable("overtimeItems", "加班明细",
|
|
col("date", "加班日期", "date", 140),
|
|
col("fromTime", "开始时间", "datetime", 180),
|
|
col("toTime", "结束时间", "datetime", 180),
|
|
col("hours", "时长(小时)", "number", 120),
|
|
col("workContent", "工作内容", "text", null)));
|
|
|
|
ObjectNode flow = simpleFlow(new String[][]{
|
|
{"start", "发起者", "start"},
|
|
{"deptHead", "部门主管", "审批"},
|
|
{"hr", "人力资源部", "知会"},
|
|
{"leader", "分管领导", "审批"},
|
|
{"end", "结束", "end"}
|
|
});
|
|
String instructions =
|
|
"加班申请流程:申请人填写加班类型、起止时间、加班事由及补偿方式后提交,\n" +
|
|
"经部门主管审批、人力资源部知会登记后,由分管领导审批生效。加班明细须逐日\n" +
|
|
"登记起止时间与时长,调休与加班费不可同时申报。";
|
|
return tpl("overtime-apply", "加班申请", "人力资源", "人力资源部", "2024-07-30",
|
|
instructions, form, flow.toString());
|
|
}
|
|
|
|
private FormTemplate expenseCond() {
|
|
String form = formJson("费用审批单", rows(
|
|
row(req(field("applicant", "申请人", "person")), req(field("amount", "金额", "money"))),
|
|
row(req(opts(field("expenseType", "费用类型", "select"),
|
|
"差旅费", "招待费", "办公费", "培训费", "其他"))),
|
|
row(full(req(field("reason", "费用说明", "textarea"))))
|
|
));
|
|
|
|
ObjectNode flow = om.createObjectNode();
|
|
ArrayNode nodes = flow.putArray("nodes");
|
|
nodes.add(node("start", "发起者", "start"));
|
|
nodes.add(node("deptHead", "部门主管", "审批"));
|
|
nodes.add(node("amountGate", "金额判断", "审批"));
|
|
nodes.add(node("gm", "总经理", "审批"));
|
|
nodes.add(node("directPass", "直接通过", "知会"));
|
|
nodes.add(node("financeReview", "财务复核", "审批"));
|
|
nodes.add(node("end", "结束", "end"));
|
|
ArrayNode edges = flow.putArray("edges");
|
|
edges.add(edge("start", "deptHead"));
|
|
edges.add(edge("deptHead", "amountGate"));
|
|
edges.add(edge("amountGate", "gm"));
|
|
edges.add(edge("amountGate", "directPass"));
|
|
edges.add(edge("gm", "financeReview"));
|
|
edges.add(edge("directPass", "financeReview"));
|
|
edges.add(edge("financeReview", "end"));
|
|
ArrayNode branches = flow.putArray("branches");
|
|
ObjectNode br = branches.addObject();
|
|
br.put("fromId", "amountGate");
|
|
ArrayNode cases = br.putArray("cases");
|
|
ObjectNode c1 = cases.addObject();
|
|
c1.put("label", "金额 ≥ 1万元");
|
|
c1.put("toId", "gm");
|
|
ObjectNode c2 = cases.addObject();
|
|
c2.put("label", "金额 < 1万元");
|
|
c2.put("toId", "directPass");
|
|
|
|
String instructions =
|
|
"费用审批(条件分支演示):部门主管审批后,系统按报销金额自动分流——\n" +
|
|
"金额 ≥ 1万元 的走「总经理审批」分支,< 1万元 的直接进入财务复核,\n" +
|
|
"两条分支最终汇合到财务复核节点后办结。";
|
|
return tpl("expense-cond", "费用审批(条件分支)", "财务审批", "财务部", "2025-03-18",
|
|
instructions, form, flow.toString());
|
|
}
|
|
|
|
/**
|
|
* 融资申请审批单:金融办初审 → 财务部 → 风控负责人 → 高管/董事长。
|
|
* templateId = "financing-apply",供 FinancingController.submitApproval() 调用。
|
|
*/
|
|
private FormTemplate financingApply() {
|
|
String form = formJson("融资申请审批单", rows(
|
|
row(req(field("applicant", "经办人", "person")),
|
|
req(field("companySubject", "融资主体", "text"))),
|
|
row(req(field("financingCode", "融资编号", "text")),
|
|
req(field("financingType", "融资品种", "text"))),
|
|
row(req(field("lender", "融资机构", "text")),
|
|
req(field("amount", "融资金额(元)", "money"))),
|
|
row(req(field("rate", "年化利率(%)", "number")),
|
|
req(field("termMonths", "期限(月)", "number"))),
|
|
row(req(field("repayMethod", "还款方式", "text")),
|
|
field("purpose", "资金用途", "text")),
|
|
row(full(field("schemeCompareNote", "方案比选说明", "textarea"))),
|
|
row(full(field("creditCheckNote", "授信充足性自查", "textarea")))
|
|
));
|
|
|
|
ObjectNode flow = om.createObjectNode();
|
|
ArrayNode nodes = flow.putArray("nodes");
|
|
nodes.add(node("start", "发起者(金融办)", "start"));
|
|
nodes.add(node("financeOfficeReview", "金融办初审", "审批"));
|
|
nodes.add(node("financeDept", "财务部审批", "审批"));
|
|
nodes.add(node("riskControl", "风控负责人会签", "会签"));
|
|
nodes.add(node("gm", "总经理审批", "审批"));
|
|
nodes.add(node("chairman", "董事长审批", "审批"));
|
|
nodes.add(node("financeNotify", "财务部知会", "知会"));
|
|
nodes.add(node("end", "结束", "end"));
|
|
ArrayNode edges = flow.putArray("edges");
|
|
edges.add(edge("start", "financeOfficeReview"));
|
|
edges.add(edge("financeOfficeReview", "financeDept"));
|
|
edges.add(edge("financeDept", "riskControl"));
|
|
edges.add(edge("riskControl", "gm"));
|
|
edges.add(edge("gm", "chairman"));
|
|
edges.add(edge("chairman", "financeNotify"));
|
|
edges.add(edge("financeNotify", "end"));
|
|
|
|
String instructions =
|
|
"融资申请审批流程:\n" +
|
|
"一、适用范围:本流程适用于各类融资申请(银行贷款/融资租赁/债券/票据等)。\n" +
|
|
"二、填报要求:\n" +
|
|
" 1. 经办人须完整填写融资编号、融资机构、融资金额、年化利率及期限;\n" +
|
|
" 2. 方案比选说明一栏需附多机构比选依据(综合成本率对比);\n" +
|
|
" 3. 授信充足性自查一栏须确认该机构授信额度剩余足以覆盖本次融资。\n" +
|
|
"三、审批节点:\n" +
|
|
" 金融办初审 → 财务部审批 → 风控负责人会签 → 总经理 → 董事长 → 财务部知会 → 办结。\n" +
|
|
"四、注意事项:\n" +
|
|
" 审批通过后,融资台账状态自动由「审批中」推进到「已放款」(到账确认后手动更新)。";
|
|
return tpl("financing-apply", "融资申请审批单", "财务审批", "金融办", "2025-01-15",
|
|
instructions, form, flow.toString());
|
|
}
|
|
|
|
/**
|
|
* 申报服务部 — 年度/季度申报计划审批模板(需求 §1 年度/季度申报计划,Gap 1 [low])。
|
|
* RdDeclPlanController 固定引用 templateId="rd-decl-plan-approve";本模板确保种子注册。
|
|
*/
|
|
private FormTemplate rdDeclPlanApprove() {
|
|
String form = formJson("申报计划审批单", rows(
|
|
row(req(field("applicant", "经办人", "person")),
|
|
req(field("planName", "计划名称", "text"))),
|
|
row(req(opts(field("periodType", "计划周期", "radio"), "年度", "季度")),
|
|
req(field("planYear", "计划年度", "number"))),
|
|
row(req(opts(field("policyCategory", "政策类别", "select"),
|
|
"资质类", "资金类", "荣誉类", "人才类")),
|
|
field("programName", "申报项目名称", "text")),
|
|
row(field("budget", "申报预算(元)", "money"),
|
|
field("expectedBenefit", "预期获批金额(元)", "money")),
|
|
row(field("planApplyDate", "计划申报日期", "date"),
|
|
field("planEndDate", "计划结束日期", "date")),
|
|
row(full(field("owner", "负责人", "person"))),
|
|
row(full(field("remark", "备注说明", "textarea")))
|
|
));
|
|
|
|
ObjectNode flow = simpleFlow(new String[][]{
|
|
{"start", "申报专员(发起)", "start"},
|
|
{"deptHead", "申报服务部负责人", "审批"},
|
|
{"rdDirector", "创新研发中心主任", "审批"},
|
|
{"gm", "分管副总", "审批"},
|
|
{"end", "结束", "end"}
|
|
});
|
|
|
|
String instructions =
|
|
"申报计划审批流程:\n" +
|
|
"一、适用范围:本流程适用于创新研发中心·申报服务部编制的年度/季度政策申报计划。\n" +
|
|
"二、填报要求:\n" +
|
|
" 1. 计划名称须明确年度(季度)和申报项目,如「2026年高新技术企业认定申报计划」;\n" +
|
|
" 2. 申报预算应包含咨询费、审计费、材料费合计;\n" +
|
|
" 3. 预期获批金额填写政策奖励或补贴估算值(不含税收减免);\n" +
|
|
" 4. 计划申报日期与计划结束日期须在政策申报截止日之前。\n" +
|
|
"三、审批节点:\n" +
|
|
" 申报专员提交 → 申报服务部负责人审批 → 创新研发中心主任审批 → 分管副总审批 → 办结。\n" +
|
|
"四、注意事项:\n" +
|
|
" 审批通过后,计划状态自动变为「已批准」,系统同步在政策申报台账创建对应申报记录。";
|
|
|
|
return tpl("rd-decl-plan-approve", "申报计划审批单", "研发申报", "申报服务部", "2026-01-01",
|
|
instructions, form, flow.toString());
|
|
}
|
|
|
|
/**
|
|
* 合同法务审批模板(templateId = "legal-contract-review")。
|
|
* 由 LegalContractFlowController.submitReview() 引用,补齐审批引擎对接缺口。
|
|
* 审批链:业务负责人 → 法务审核 → 财务审核(并行)→ 合规审核 → 高管审批 → 办结。
|
|
*/
|
|
private FormTemplate legalContractReview() {
|
|
String form = formJson("合同法务审批单", rows(
|
|
row(req(field("applicant", "发起人", "person")), req(field("applyDept", "发起部门", "dept"))),
|
|
row(full(req(field("contractName", "合同名称", "text")))),
|
|
row(full(req(field("counterparty", "对方单位", "text")))),
|
|
row(req(field("amount", "合同金额", "money")), req(field("signDate", "签订日期", "date"))),
|
|
row(req(opts(field("contractType", "合同类型", "select"),
|
|
"采购合同", "销售合同", "技术合同", "劳务合同", "保密协议", "NDA", "其他")),
|
|
req(opts(field("riskLevel", "风险等级", "radio"), "高", "中", "低"))),
|
|
row(full(field("riskPoints", "主要风险点说明", "textarea"))),
|
|
row(full(field("remark", "备注", "textarea")))
|
|
));
|
|
|
|
ObjectNode flow = om.createObjectNode();
|
|
ArrayNode nodes = flow.putArray("nodes");
|
|
nodes.add(node("start", "发起者", "start"));
|
|
nodes.add(node("bizLead", "业务负责人", "审批"));
|
|
nodes.add(node("legal", "法务审核", "审批"));
|
|
nodes.add(node("finance", "财务审核", "知会"));
|
|
nodes.add(node("compliance", "合规审核", "审批"));
|
|
nodes.add(node("gm", "高管审批", "审批"));
|
|
nodes.add(node("end", "结束", "end"));
|
|
ArrayNode edges = flow.putArray("edges");
|
|
edges.add(edge("start", "bizLead"));
|
|
edges.add(edge("bizLead", "legal"));
|
|
edges.add(edge("bizLead", "finance"));
|
|
edges.add(edge("legal", "compliance"));
|
|
edges.add(edge("finance", "compliance"));
|
|
edges.add(edge("compliance", "gm"));
|
|
edges.add(edge("gm", "end"));
|
|
ArrayNode parallels = flow.putArray("parallels");
|
|
ArrayNode par = parallels.addArray();
|
|
par.add("legal");
|
|
par.add("finance");
|
|
|
|
String instructions =
|
|
"合同法务审批流程:\n" +
|
|
"一、适用范围:适用于各类型合同(采购/销售/技术/劳务/NDA)签订前的法务合规审批。\n" +
|
|
"二、填报要求:\n" +
|
|
" 1. 合同名称、对方单位、合同金额、签订日期必填;\n" +
|
|
" 2. 合同类型须从下拉列表选择,风险等级(高/中/低)须如实填写;\n" +
|
|
" 3. 主要风险点说明(如付款条款、违约责任、适用法律等)须简要描述。\n" +
|
|
"三、审批节点:\n" +
|
|
" 业务负责人确认 → 法务审核(条款)+ 财务知会(金额)并行 → 合规审核 → 高管审批 → 办结。\n" +
|
|
"四、注意事项:高风险合同建议额外附法律意见书;合同正本扫描件须随单上传。";
|
|
return tpl("legal-contract-review", "合同法务审批单", "合同管理", "法务风险部", "2026-01-01",
|
|
instructions, form, flow.toString());
|
|
}
|
|
|
|
/** Linear flow builder: nodes connected start -> ... -> end in order. */
|
|
private ObjectNode simpleFlow(String[][] nodeDefs) {
|
|
ObjectNode flow = om.createObjectNode();
|
|
ArrayNode nodes = flow.putArray("nodes");
|
|
for (String[] nd : nodeDefs) {
|
|
nodes.add(node(nd[0], nd[1], nd[2]));
|
|
}
|
|
ArrayNode edges = flow.putArray("edges");
|
|
for (int i = 0; i < nodeDefs.length - 1; i++) {
|
|
edges.add(edge(nodeDefs[i][0], nodeDefs[i + 1][0]));
|
|
}
|
|
return flow;
|
|
}
|
|
}
|