文件
hyb-oa/server/src/services/budgetPool.js
T

176 行
4.5 KiB
JavaScript

const { AppError } = require("../utils/AppError");
const BUDGET_SCOPE_TYPES = Object.freeze({
global: "global",
departmentLegacy: "department_legacy",
costCenterScoped: "cost_center_scoped",
});
const BUDGET_SCOPE_PRIORITY = [
BUDGET_SCOPE_TYPES.costCenterScoped,
BUDGET_SCOPE_TYPES.departmentLegacy,
BUDGET_SCOPE_TYPES.global,
];
function buildValidationError(details, statusCode = 400) {
return new AppError({
code: "VALID_001",
message: "参数校验失败",
statusCode,
details,
});
}
function resolveBudgetScope({ departmentId = null, legalEntityId = null, costCenterId = null } = {}) {
const hasDepartment = Boolean(departmentId);
const hasLegalEntity = Boolean(legalEntityId);
const hasCostCenter = Boolean(costCenterId);
if (hasCostCenter && !hasLegalEntity) {
throw buildValidationError({ costCenterId: "legalEntityId_required" });
}
if (hasDepartment && (hasLegalEntity || hasCostCenter)) {
throw buildValidationError({ scope: "mixed_dimensions_not_allowed" }, 409);
}
if (hasLegalEntity && !hasCostCenter) {
throw buildValidationError({ costCenterId: "required_with_legalEntityId" });
}
if (hasLegalEntity && hasCostCenter) {
return {
scopeType: BUDGET_SCOPE_TYPES.costCenterScoped,
departmentId: null,
legalEntityId,
costCenterId,
};
}
if (hasDepartment) {
return {
scopeType: BUDGET_SCOPE_TYPES.departmentLegacy,
departmentId,
legalEntityId: null,
costCenterId: null,
};
}
return {
scopeType: BUDGET_SCOPE_TYPES.global,
departmentId: null,
legalEntityId: null,
costCenterId: null,
};
}
function resolveBudgetScopeType(record) {
const hasDepartment = Boolean(record?.department_id);
const hasLegalEntity = Boolean(record?.legal_entity_id);
const hasCostCenter = Boolean(record?.cost_center_id);
if (hasLegalEntity && hasCostCenter && !hasDepartment) {
return BUDGET_SCOPE_TYPES.costCenterScoped;
}
if (hasDepartment && !hasLegalEntity && !hasCostCenter) {
return BUDGET_SCOPE_TYPES.departmentLegacy;
}
if (!hasDepartment && !hasLegalEntity && !hasCostCenter) {
return BUDGET_SCOPE_TYPES.global;
}
return "invalid";
}
function matchesBudgetPoolContext(record, context) {
const scopeType = resolveBudgetScopeType(record);
if (scopeType === BUDGET_SCOPE_TYPES.costCenterScoped) {
return (
Boolean(context.legalEntityId) &&
Boolean(context.costCenterId) &&
record.legal_entity_id === context.legalEntityId &&
record.cost_center_id === context.costCenterId
);
}
if (scopeType === BUDGET_SCOPE_TYPES.departmentLegacy) {
return Boolean(context.departmentId) && record.department_id === context.departmentId;
}
if (scopeType === BUDGET_SCOPE_TYPES.global) {
return true;
}
return false;
}
function normalizeBudgetContext({ departmentId = null, legalEntityId = null, costCenterId = null } = {}) {
if (costCenterId && !legalEntityId) {
throw buildValidationError({ costCenterId: "legalEntityId_required" });
}
return {
departmentId,
legalEntityId,
costCenterId,
};
}
function selectBudgetPoolCandidate(records, context = {}) {
const normalizedContext = normalizeBudgetContext(context);
for (const scopeType of BUDGET_SCOPE_PRIORITY) {
const matches = (records ?? []).filter(
(record) => resolveBudgetScopeType(record) === scopeType && matchesBudgetPoolContext(record, normalizedContext),
);
if (matches.length === 1) {
return matches[0];
}
if (matches.length > 1) {
if (scopeType === BUDGET_SCOPE_TYPES.costCenterScoped) {
throw buildValidationError({ budget: "budget_pool_ambiguous", scopeType }, 409);
}
return matches[0];
}
}
return null;
}
async function resolveApplicableBudgetPoolByContext(
tx,
{ departmentId = null, legalEntityId = null, costCenterId = null, effectiveDate },
) {
if (!(effectiveDate instanceof Date) || Number.isNaN(effectiveDate.getTime())) {
throw buildValidationError({ effectiveDate: "invalid_date" });
}
const records = await tx.budgetPool.findMany({
where: {
status: "active",
archived_at: null,
period_start: { lte: effectiveDate },
period_end: { gte: effectiveDate },
},
orderBy: [{ created_at: "asc" }, { code: "asc" }],
});
return selectBudgetPoolCandidate(records, {
departmentId,
legalEntityId,
costCenterId,
});
}
module.exports = {
BUDGET_SCOPE_TYPES,
resolveBudgetScope,
resolveBudgetScopeType,
selectBudgetPoolCandidate,
resolveApplicableBudgetPoolByContext,
};