feat(P25-1/P25-5/P26-1/P26-3/P24-1/P29-4): 落地内部OA够用版后端与试运行能力

关联 Issue: P25-1, P25-2, P25-3, P25-4, P25-5, P26-1, P26-3, P24-1, P24-2, P29-1, P29-2, P29-3, P29-4
这个提交包含在:
2026-03-21 18:03:20 +08:00
父节点 9b7cd818f7
当前提交 e39e41287e
共修改 45 个文件,包含 4571 行新增和 328 行删除
+110
查看文件
@@ -233,6 +233,70 @@ const DEFAULT_FEATURE_FLAGS = [
department_ids: [],
note: "bootstrap default: force_off",
},
{
flag_key: "budget_precheck_v1",
override_mode: "force_off",
rollout_type: "global",
department_ids: [],
note: "bootstrap default: force_off",
},
{
flag_key: "payment_batch_v1",
override_mode: "force_off",
rollout_type: "global",
department_ids: [],
note: "bootstrap default: force_off",
},
{
flag_key: "portal_home_v1",
override_mode: "force_off",
rollout_type: "global",
department_ids: [],
note: "bootstrap default: force_off",
},
{
flag_key: "global_search_v1",
override_mode: "force_off",
rollout_type: "global",
department_ids: [],
note: "bootstrap default: force_off",
},
{
flag_key: "announcements_v1",
override_mode: "force_off",
rollout_type: "global",
department_ids: [],
note: "bootstrap default: force_off",
},
{
flag_key: "workflow_definition_admin_v1",
override_mode: "force_off",
rollout_type: "global",
department_ids: [],
note: "bootstrap default: force_off",
},
{
flag_key: "cutover_console_v1",
override_mode: "force_off",
rollout_type: "global",
department_ids: [],
note: "bootstrap default: force_off",
},
];
const DEFAULT_BUDGET_CONTROL_RULES = [
{
name: "默认全局预算预警",
entity_type: "all",
department_id: null,
legal_entity_id: null,
cost_center_id: null,
expense_type_id: null,
control_mode: "warn",
priority: 100,
enabled: true,
notes: "bootstrap default global warn rule",
},
];
const DEFAULT_LEAVE_TYPES = [
@@ -411,6 +475,51 @@ async function seedFeatureFlags(prisma) {
}
}
async function seedBudgetControlRules(prisma) {
const now = new Date();
for (const item of DEFAULT_BUDGET_CONTROL_RULES) {
const existing = await prisma.budgetControlRule.findFirst({
where: {
name: item.name,
entity_type: item.entity_type,
archived_at: null,
},
select: { id: true },
});
if (existing) {
await prisma.budgetControlRule.update({
where: { id: existing.id },
data: {
department_id: item.department_id,
legal_entity_id: item.legal_entity_id,
cost_center_id: item.cost_center_id,
expense_type_id: item.expense_type_id,
control_mode: item.control_mode,
priority: item.priority,
enabled: item.enabled,
notes: item.notes,
archived_at: null,
updated_at: now,
updated_by: null,
},
});
continue;
}
await prisma.budgetControlRule.create({
data: {
...item,
created_at: now,
created_by: null,
updated_at: now,
updated_by: null,
},
});
}
}
async function seedLeaveTypes(prisma) {
const now = new Date();
@@ -608,6 +717,7 @@ async function main() {
await seedWorkflowDefinition(prisma);
await seedLeaveTypes(prisma);
await seedFeatureFlags(prisma);
await seedBudgetControlRules(prisma);
// eslint-disable-next-line no-console
console.log("Seeded config data:");
+68 -2
查看文件
@@ -109,6 +109,46 @@ async function ensureCostCenter(prisma, payload) {
});
}
async function ensureBudgetPool(prisma, payload) {
const now = new Date();
const existing = await prisma.budgetPool.findUnique({
where: { code: payload.code },
select: { id: true },
});
const data = {
name: payload.name,
department_id: payload.departmentId ?? null,
legal_entity_id: payload.legalEntityId ?? null,
cost_center_id: payload.costCenterId ?? null,
status: "active",
period_start: payload.periodStart,
period_end: payload.periodEnd,
allocated_amount: payload.allocatedAmount,
warning_threshold_percent: payload.warningThresholdPercent ?? 80,
archived_at: null,
updated_at: now,
updated_by: null,
};
if (existing) {
return prisma.budgetPool.update({
where: { id: existing.id },
data,
});
}
return prisma.budgetPool.create({
data: {
code: payload.code,
used_amount: payload.usedAmount ?? "0.00",
created_at: now,
created_by: null,
...data,
},
});
}
async function ensureSupplier(prisma, payload) {
const now = new Date();
const existing = await prisma.supplier.findUnique({
@@ -303,6 +343,9 @@ async function main() {
await prisma.$connect();
const hireDate = new Date("2020-01-01");
const currentYear = new Date().getUTCFullYear();
const fiscalPeriodStart = new Date(`${currentYear}-01-01T00:00:00.000Z`);
const fiscalPeriodEnd = new Date(`${currentYear}-12-31T00:00:00.000Z`);
try {
const rootDepartment = await ensureDepartment(prisma, { name: "公司" });
@@ -460,7 +503,7 @@ async function main() {
],
});
await ensureCostCenter(prisma, {
const managementCostCenter = await ensureCostCenter(prisma, {
legalEntityId: legalEntity.id,
code: "MGMT",
name: "管理中心",
@@ -478,7 +521,7 @@ async function main() {
name: "人力中心",
ownerEmployeeId: hr.id,
});
await ensureCostCenter(prisma, {
const rdCostCenter = await ensureCostCenter(prisma, {
legalEntityId: legalEntity.id,
code: "RD",
name: "研发中心",
@@ -496,6 +539,27 @@ async function main() {
legalEntityId: legalEntity.id,
});
const managementBudgetPool = await ensureBudgetPool(prisma, {
code: `DEV_BUDGET_MGMT_${currentYear}`,
name: `${currentYear} 管理中心预算池`,
legalEntityId: legalEntity.id,
costCenterId: managementCostCenter.id,
periodStart: fiscalPeriodStart,
periodEnd: fiscalPeriodEnd,
allocatedAmount: "500000.00",
warningThresholdPercent: 80,
});
const rdBudgetPool = await ensureBudgetPool(prisma, {
code: `DEV_BUDGET_RD_${currentYear}`,
name: `${currentYear} 研发部预算池`,
departmentId: rdDepartment.id,
periodStart: fiscalPeriodStart,
periodEnd: fiscalPeriodEnd,
allocatedAmount: "800000.00",
warningThresholdPercent: 75,
});
// eslint-disable-next-line no-console
console.log("Seeded dev users:");
// eslint-disable-next-line no-console
@@ -520,6 +584,8 @@ async function main() {
console.log(`- HolidayCalendar: ${holidayCalendar.code} / ${holidayCalendar.name}`);
// eslint-disable-next-line no-console
console.log(`- Supplier: ${supplier.code} / ${supplier.name}`);
// eslint-disable-next-line no-console
console.log(`- Budget Pools: ${managementBudgetPool.code}, ${rdBudgetPool.code}`);
} finally {
await prisma.$disconnect();
await pool.end();