feat(G1,P16A,P16B): 收口主数据核心与节假日日历增强
这个提交包含在:
@@ -106,6 +106,83 @@ const DEFAULT_LEAVE_WORKFLOW_DEFINITION = {
|
||||
};
|
||||
|
||||
|
||||
const DEFAULT_DOCUMENT_CATEGORIES = [
|
||||
{
|
||||
code: "reimbursement_general",
|
||||
name: "通用报销单",
|
||||
entity_type: "reimbursement",
|
||||
sequence_prefix: "BX",
|
||||
number_length: 4,
|
||||
reset_period: "yearly",
|
||||
default_workflow_key: null,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
code: "leave_request",
|
||||
name: "请假申请单",
|
||||
entity_type: "leave",
|
||||
sequence_prefix: "JQ",
|
||||
number_length: 4,
|
||||
reset_period: "yearly",
|
||||
default_workflow_key: null,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
code: "purchase_general",
|
||||
name: "日常采购申请",
|
||||
entity_type: "purchase_request",
|
||||
sequence_prefix: "CG",
|
||||
number_length: 4,
|
||||
reset_period: "yearly",
|
||||
default_workflow_key: null,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
code: "asset_it",
|
||||
name: "IT 固资台账",
|
||||
entity_type: "asset_card",
|
||||
sequence_prefix: "ZC",
|
||||
number_length: 4,
|
||||
reset_period: "yearly",
|
||||
default_workflow_key: null,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
code: "general_record",
|
||||
name: "通用业务单据",
|
||||
entity_type: "general",
|
||||
sequence_prefix: "GEN",
|
||||
number_length: 4,
|
||||
reset_period: "yearly",
|
||||
default_workflow_key: null,
|
||||
enabled: true,
|
||||
},
|
||||
];
|
||||
|
||||
const DEFAULT_FEATURE_FLAGS = [
|
||||
{
|
||||
flag_key: "erp_sync",
|
||||
override_mode: "force_off",
|
||||
rollout_type: "global",
|
||||
department_ids: [],
|
||||
note: "bootstrap default: force_off",
|
||||
},
|
||||
{
|
||||
flag_key: "invoice_verification",
|
||||
override_mode: "force_off",
|
||||
rollout_type: "global",
|
||||
department_ids: [],
|
||||
note: "bootstrap default: force_off",
|
||||
},
|
||||
{
|
||||
flag_key: "leave_holiday_calendar",
|
||||
override_mode: "force_off",
|
||||
rollout_type: "global",
|
||||
department_ids: [],
|
||||
note: "bootstrap default: force_off",
|
||||
},
|
||||
];
|
||||
|
||||
const DEFAULT_LEAVE_TYPES = [
|
||||
{
|
||||
code: "annual_leave",
|
||||
@@ -216,6 +293,72 @@ async function seedDocumentSequence(prisma) {
|
||||
});
|
||||
}
|
||||
|
||||
async function seedDocumentCategories(prisma) {
|
||||
const now = new Date();
|
||||
|
||||
for (const item of DEFAULT_DOCUMENT_CATEGORIES) {
|
||||
await prisma.documentCategory.upsert({
|
||||
where: { code: item.code },
|
||||
update: {
|
||||
name: item.name,
|
||||
entity_type: item.entity_type,
|
||||
sequence_prefix: item.sequence_prefix,
|
||||
number_length: item.number_length,
|
||||
reset_period: item.reset_period,
|
||||
default_workflow_key: item.default_workflow_key,
|
||||
enabled: item.enabled,
|
||||
archived_at: null,
|
||||
updated_at: now,
|
||||
updated_by: null,
|
||||
},
|
||||
create: {
|
||||
code: item.code,
|
||||
name: item.name,
|
||||
entity_type: item.entity_type,
|
||||
sequence_prefix: item.sequence_prefix,
|
||||
number_length: item.number_length,
|
||||
reset_period: item.reset_period,
|
||||
default_workflow_key: item.default_workflow_key,
|
||||
enabled: item.enabled,
|
||||
archived_at: null,
|
||||
created_at: now,
|
||||
created_by: null,
|
||||
updated_at: now,
|
||||
updated_by: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function seedFeatureFlags(prisma) {
|
||||
const now = new Date();
|
||||
|
||||
for (const item of DEFAULT_FEATURE_FLAGS) {
|
||||
await prisma.featureFlag.upsert({
|
||||
where: { flag_key: item.flag_key },
|
||||
update: {
|
||||
override_mode: item.override_mode,
|
||||
rollout_type: item.rollout_type,
|
||||
department_ids: item.department_ids,
|
||||
note: item.note,
|
||||
updated_at: now,
|
||||
updated_by: null,
|
||||
},
|
||||
create: {
|
||||
flag_key: item.flag_key,
|
||||
override_mode: item.override_mode,
|
||||
rollout_type: item.rollout_type,
|
||||
department_ids: item.department_ids,
|
||||
note: item.note,
|
||||
created_at: now,
|
||||
created_by: null,
|
||||
updated_at: now,
|
||||
updated_by: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function seedLeaveTypes(prisma) {
|
||||
const now = new Date();
|
||||
|
||||
@@ -345,8 +488,10 @@ async function main() {
|
||||
await seedExpenseTypes(prisma);
|
||||
await seedApprovalRules(prisma);
|
||||
await seedDocumentSequence(prisma);
|
||||
await seedDocumentCategories(prisma);
|
||||
await seedWorkflowDefinition(prisma);
|
||||
await seedLeaveTypes(prisma);
|
||||
await seedFeatureFlags(prisma);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("Seeded config data:");
|
||||
@@ -357,11 +502,15 @@ async function main() {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("- document sequence: reimbursement(reset_period=none)");
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`- document categories: ${DEFAULT_DOCUMENT_CATEGORIES.length} seeded`);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("- workflow definition: reimbursement v1 (1-step role=ceo)");
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("- workflow definition: leave v1 (1-step department_manager)");
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`- leave types: ${DEFAULT_LEAVE_TYPES.length} seeded with default policies`);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`- feature flags: ${DEFAULT_FEATURE_FLAGS.length} seeded`);
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
await pool.end();
|
||||
|
||||
@@ -33,6 +33,147 @@ async function ensureDepartment(prisma, payload) {
|
||||
});
|
||||
}
|
||||
|
||||
async function ensureLegalEntity(prisma, payload) {
|
||||
const now = new Date();
|
||||
const existing = await prisma.legalEntity.findUnique({
|
||||
where: { code: payload.code },
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
return prisma.legalEntity.update({
|
||||
where: { id: existing.id },
|
||||
data: {
|
||||
name: payload.name,
|
||||
short_name: payload.shortName ?? null,
|
||||
currency_code: payload.currencyCode,
|
||||
tax_no: payload.taxNo ?? null,
|
||||
enabled: true,
|
||||
archived_at: null,
|
||||
updated_at: now,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return prisma.legalEntity.create({
|
||||
data: {
|
||||
code: payload.code,
|
||||
name: payload.name,
|
||||
short_name: payload.shortName ?? null,
|
||||
currency_code: payload.currencyCode,
|
||||
tax_no: payload.taxNo ?? null,
|
||||
enabled: true,
|
||||
archived_at: null,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function ensureCostCenter(prisma, payload) {
|
||||
const now = new Date();
|
||||
const existing = await prisma.costCenter.findFirst({
|
||||
where: {
|
||||
legal_entity_id: payload.legalEntityId,
|
||||
code: payload.code,
|
||||
},
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
return prisma.costCenter.update({
|
||||
where: { id: existing.id },
|
||||
data: {
|
||||
name: payload.name,
|
||||
parent_id: payload.parentId ?? null,
|
||||
owner_employee_id: payload.ownerEmployeeId ?? null,
|
||||
enabled: true,
|
||||
archived_at: null,
|
||||
updated_at: now,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return prisma.costCenter.create({
|
||||
data: {
|
||||
legal_entity_id: payload.legalEntityId,
|
||||
code: payload.code,
|
||||
name: payload.name,
|
||||
parent_id: payload.parentId ?? null,
|
||||
owner_employee_id: payload.ownerEmployeeId ?? null,
|
||||
enabled: true,
|
||||
archived_at: null,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function ensureHolidayCalendar(prisma, payload) {
|
||||
const now = new Date();
|
||||
const existing = await prisma.holidayCalendar.findUnique({
|
||||
where: { code: payload.code },
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
const calendar = existing
|
||||
? await prisma.holidayCalendar.update({
|
||||
where: { id: existing.id },
|
||||
data: {
|
||||
name: payload.name,
|
||||
timezone: payload.timezone ?? "Asia/Shanghai",
|
||||
legal_entity_id: payload.legalEntityId ?? null,
|
||||
enabled: true,
|
||||
archived_at: null,
|
||||
updated_at: now,
|
||||
},
|
||||
})
|
||||
: await prisma.holidayCalendar.create({
|
||||
data: {
|
||||
code: payload.code,
|
||||
name: payload.name,
|
||||
timezone: payload.timezone ?? "Asia/Shanghai",
|
||||
legal_entity_id: payload.legalEntityId ?? null,
|
||||
enabled: true,
|
||||
archived_at: null,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.holidayCalendarDay.deleteMany({
|
||||
where: { calendar_id: calendar.id },
|
||||
});
|
||||
|
||||
if (payload.days?.length) {
|
||||
await prisma.holidayCalendarDay.createMany({
|
||||
data: payload.days.map((item) => ({
|
||||
calendar_id: calendar.id,
|
||||
date: new Date(`${item.date}T00:00:00.000Z`),
|
||||
day_type: item.dayType,
|
||||
name: item.name,
|
||||
note: item.note ?? null,
|
||||
created_at: now,
|
||||
created_by: null,
|
||||
updated_at: now,
|
||||
updated_by: null,
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
if (payload.setAsDefault && payload.legalEntityId) {
|
||||
await prisma.legalEntity.update({
|
||||
where: { id: payload.legalEntityId },
|
||||
data: {
|
||||
default_holiday_calendar_id: calendar.id,
|
||||
updated_at: now,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return calendar;
|
||||
}
|
||||
|
||||
async function ensureEmployee(prisma, payload) {
|
||||
const now = new Date();
|
||||
const passwordHash = await hashPassword(payload.password);
|
||||
@@ -50,6 +191,7 @@ async function ensureEmployee(prisma, payload) {
|
||||
name: payload.name,
|
||||
phone: payload.phone,
|
||||
department_id: payload.departmentId,
|
||||
legal_entity_id: payload.legalEntityId ?? null,
|
||||
title: payload.title,
|
||||
direct_manager_id: payload.directManagerId,
|
||||
hire_date: payload.hireDate,
|
||||
@@ -86,6 +228,7 @@ async function ensureEmployee(prisma, payload) {
|
||||
phone: payload.phone,
|
||||
email: payload.email,
|
||||
department_id: payload.departmentId,
|
||||
legal_entity_id: payload.legalEntityId ?? null,
|
||||
title: payload.title,
|
||||
direct_manager_id: payload.directManagerId,
|
||||
hire_date: payload.hireDate,
|
||||
@@ -141,6 +284,14 @@ async function main() {
|
||||
parent_id: rootDepartment.id,
|
||||
});
|
||||
|
||||
const legalEntity = await ensureLegalEntity(prisma, {
|
||||
code: "HYB_CN",
|
||||
name: "海音达科技有限公司",
|
||||
shortName: "海音达",
|
||||
currencyCode: "CNY",
|
||||
taxNo: null,
|
||||
});
|
||||
|
||||
const ceo = await ensureEmployee(prisma, {
|
||||
employeeNo: process.env.SEED_CEO_EMPLOYEE_NO ?? "E0001",
|
||||
name: "CEO",
|
||||
@@ -148,6 +299,7 @@ async function main() {
|
||||
email: process.env.SEED_CEO_EMAIL ?? "[email protected]",
|
||||
password: process.env.SEED_CEO_PASSWORD ?? "CeoPassword1234",
|
||||
departmentId: rootDepartment.id,
|
||||
legalEntityId: legalEntity.id,
|
||||
title: "总经理",
|
||||
directManagerId: null,
|
||||
roles: ["ceo", "employee"],
|
||||
@@ -161,6 +313,7 @@ async function main() {
|
||||
email: process.env.SEED_ADMIN_EMAIL ?? "[email protected]",
|
||||
password: process.env.SEED_ADMIN_PASSWORD ?? "AdminPassword1234",
|
||||
departmentId: managementDepartment.id,
|
||||
legalEntityId: legalEntity.id,
|
||||
title: "系统管理员",
|
||||
directManagerId: ceo.id,
|
||||
roles: ["admin", "employee"],
|
||||
@@ -174,6 +327,7 @@ async function main() {
|
||||
email: process.env.SEED_MANAGER_EMAIL ?? "[email protected]",
|
||||
password: process.env.SEED_MANAGER_PASSWORD ?? "ManagerPassword1234",
|
||||
departmentId: rdDepartment.id,
|
||||
legalEntityId: legalEntity.id,
|
||||
title: "研发经理",
|
||||
directManagerId: ceo.id,
|
||||
roles: ["manager", "employee"],
|
||||
@@ -187,6 +341,7 @@ async function main() {
|
||||
email: process.env.SEED_EMPLOYEE_EMAIL ?? "[email protected]",
|
||||
password: process.env.SEED_EMPLOYEE_PASSWORD ?? "EmployeePassword1234",
|
||||
departmentId: rdDepartment.id,
|
||||
legalEntityId: legalEntity.id,
|
||||
title: "研发工程师",
|
||||
directManagerId: manager.id,
|
||||
roles: ["employee"],
|
||||
@@ -200,6 +355,7 @@ async function main() {
|
||||
email: process.env.SEED_FINANCE_EMAIL ?? "[email protected]",
|
||||
password: process.env.SEED_FINANCE_PASSWORD ?? "FinancePassword1234",
|
||||
departmentId: financeDepartment.id,
|
||||
legalEntityId: legalEntity.id,
|
||||
title: "财务专员",
|
||||
directManagerId: ceo.id,
|
||||
roles: ["finance", "employee"],
|
||||
@@ -213,6 +369,7 @@ async function main() {
|
||||
email: process.env.SEED_FINANCE_MGR_EMAIL ?? "[email protected]",
|
||||
password: process.env.SEED_FINANCE_MGR_PASSWORD ?? "FinanceMgrPassword1234",
|
||||
departmentId: financeDepartment.id,
|
||||
legalEntityId: legalEntity.id,
|
||||
title: "财务主管",
|
||||
directManagerId: ceo.id,
|
||||
roles: ["finance", "finance_mgr", "employee"],
|
||||
@@ -226,6 +383,7 @@ async function main() {
|
||||
email: process.env.SEED_HR_EMAIL ?? "[email protected]",
|
||||
password: process.env.SEED_HR_PASSWORD ?? "HrPassword1234",
|
||||
departmentId: hrDepartment.id,
|
||||
legalEntityId: legalEntity.id,
|
||||
title: "人事专员",
|
||||
directManagerId: ceo.id,
|
||||
roles: ["hr"],
|
||||
@@ -239,12 +397,51 @@ async function main() {
|
||||
email: process.env.SEED_ADMINISTRATION_EMAIL ?? "[email protected]",
|
||||
password: process.env.SEED_ADMINISTRATION_PASSWORD ?? "AdministrationPassword1234",
|
||||
departmentId: administrationDepartment.id,
|
||||
legalEntityId: legalEntity.id,
|
||||
title: "行政专员",
|
||||
directManagerId: manager.id,
|
||||
roles: ["administration"],
|
||||
hireDate,
|
||||
});
|
||||
|
||||
const holidayCalendar = await ensureHolidayCalendar(prisma, {
|
||||
code: "HYB_CN_STD",
|
||||
name: "海音达默认日历",
|
||||
timezone: "Asia/Shanghai",
|
||||
legalEntityId: legalEntity.id,
|
||||
setAsDefault: true,
|
||||
days: [
|
||||
{ date: "2026-05-04", dayType: "holiday", name: "公司调休假" },
|
||||
{ date: "2026-05-05", dayType: "holiday", name: "公司调休假" },
|
||||
{ date: "2026-05-09", dayType: "adjusted_workday", name: "周末补班" },
|
||||
],
|
||||
});
|
||||
|
||||
await ensureCostCenter(prisma, {
|
||||
legalEntityId: legalEntity.id,
|
||||
code: "MGMT",
|
||||
name: "管理中心",
|
||||
ownerEmployeeId: admin.id,
|
||||
});
|
||||
await ensureCostCenter(prisma, {
|
||||
legalEntityId: legalEntity.id,
|
||||
code: "FIN",
|
||||
name: "财务中心",
|
||||
ownerEmployeeId: financeMgr.id,
|
||||
});
|
||||
await ensureCostCenter(prisma, {
|
||||
legalEntityId: legalEntity.id,
|
||||
code: "HR",
|
||||
name: "人力中心",
|
||||
ownerEmployeeId: hr.id,
|
||||
});
|
||||
await ensureCostCenter(prisma, {
|
||||
legalEntityId: legalEntity.id,
|
||||
code: "RD",
|
||||
name: "研发中心",
|
||||
ownerEmployeeId: manager.id,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("Seeded dev users:");
|
||||
// eslint-disable-next-line no-console
|
||||
@@ -263,6 +460,10 @@ async function main() {
|
||||
console.log(`- HR: ${hr.email} / ${process.env.SEED_HR_PASSWORD ?? "HrPassword1234"}`);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`- Administration: ${administration.email} / ${process.env.SEED_ADMINISTRATION_PASSWORD ?? "AdministrationPassword1234"}`);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`- Legal Entity: ${legalEntity.code} / ${legalEntity.name}`);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`- HolidayCalendar: ${holidayCalendar.code} / ${holidayCalendar.name}`);
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
await pool.end();
|
||||
|
||||
在新工单中引用
屏蔽一个用户