fix: restrict application linking for reimbursement drafts
This commit is contained in:
@@ -13,7 +13,10 @@ const APPLICATION_TYPE_ALIASES = {
|
||||
}
|
||||
|
||||
const GENERIC_APPLICATION_TYPES = new Set(['application', 'expense_application'])
|
||||
const BLOCKED_APPLICATION_STATUSES = new Set(['draft', 'returned', 'rejected', 'cancelled', 'canceled', 'deleted'])
|
||||
const APPROVED_APPLICATION_STATUSES = new Set(['approved', 'completed'])
|
||||
const APPROVED_APPLICATION_APPROVAL_KEYS = new Set(['completed'])
|
||||
const BLOCKED_APPLICATION_LINK_STATUSES = new Set(['draft', 'returned', 'rejected', 'archived', 'cancelled', 'canceled', 'deleted'])
|
||||
const INACTIVE_REIMBURSEMENT_LINK_STATUSES = new Set(['cancelled', 'canceled', 'deleted'])
|
||||
|
||||
const STATUS_LABELS = {
|
||||
submitted: '审批中',
|
||||
@@ -53,6 +56,10 @@ function normalizeApplicationStatus(claim) {
|
||||
return normalizeLower(claim?.status || claim?.state || claim?.approval_status || claim?.approvalStatus)
|
||||
}
|
||||
|
||||
function normalizeApprovalKey(claim) {
|
||||
return normalizeLower(claim?.approvalKey || claim?.approval_key)
|
||||
}
|
||||
|
||||
function normalizeDocumentType(claim) {
|
||||
return normalizeLower(
|
||||
claim?.document_type_code
|
||||
@@ -141,6 +148,99 @@ function resolveApplicationDetailPayload(claim) {
|
||||
return detail && typeof detail === 'object' ? detail : {}
|
||||
}
|
||||
|
||||
function resolveRiskFlags(claim) {
|
||||
const flags = claim?.risk_flags_json || claim?.riskFlags || []
|
||||
return Array.isArray(flags) ? flags : []
|
||||
}
|
||||
|
||||
function createReferenceIndex() {
|
||||
return {
|
||||
ids: new Set(),
|
||||
claimNos: new Set()
|
||||
}
|
||||
}
|
||||
|
||||
function addApplicationReference(index, idValue, claimNoValue) {
|
||||
const id = normalizeText(idValue)
|
||||
if (id) {
|
||||
index.ids.add(id)
|
||||
}
|
||||
const claimNo = normalizeText(claimNoValue).toUpperCase()
|
||||
if (claimNo) {
|
||||
index.claimNos.add(claimNo)
|
||||
}
|
||||
}
|
||||
|
||||
function addApplicationReferencesFromPayload(index, payload) {
|
||||
if (!payload || typeof payload !== 'object') {
|
||||
return
|
||||
}
|
||||
|
||||
addApplicationReference(
|
||||
index,
|
||||
payload.application_claim_id || payload.applicationClaimId || payload.id,
|
||||
payload.application_claim_no || payload.applicationClaimNo || payload.claim_no || payload.claimNo
|
||||
)
|
||||
}
|
||||
|
||||
function collectLinkedApplicationReferences(claim) {
|
||||
const index = createReferenceIndex()
|
||||
addApplicationReferencesFromPayload(index, claim?.relatedApplication)
|
||||
addApplicationReferencesFromPayload(index, claim?.related_application)
|
||||
resolveRiskFlags(claim).forEach((flag) => {
|
||||
if (!flag || typeof flag !== 'object') {
|
||||
return
|
||||
}
|
||||
addApplicationReferencesFromPayload(index, flag)
|
||||
addApplicationReferencesFromPayload(index, flag.application_detail || flag.applicationDetail)
|
||||
addApplicationReferencesFromPayload(index, flag.review_form_values || flag.reviewFormValues)
|
||||
addApplicationReferencesFromPayload(index, flag.expense_scene_selection || flag.expenseSceneSelection)
|
||||
})
|
||||
return index
|
||||
}
|
||||
|
||||
function hasAnyApplicationReference(index) {
|
||||
return Boolean(index?.ids?.size || index?.claimNos?.size)
|
||||
}
|
||||
|
||||
function buildLinkedApplicationReferenceIndex(claims) {
|
||||
const index = createReferenceIndex()
|
||||
;(Array.isArray(claims) ? claims : []).forEach((claim) => {
|
||||
if (isExpenseApplicationClaim(claim)) {
|
||||
return
|
||||
}
|
||||
|
||||
const status = normalizeApplicationStatus(claim)
|
||||
if (INACTIVE_REIMBURSEMENT_LINK_STATUSES.has(status)) {
|
||||
return
|
||||
}
|
||||
|
||||
const claimReferences = collectLinkedApplicationReferences(claim)
|
||||
claimReferences.ids.forEach((id) => index.ids.add(id))
|
||||
claimReferences.claimNos.forEach((claimNo) => index.claimNos.add(claimNo))
|
||||
})
|
||||
return index
|
||||
}
|
||||
|
||||
function isApplicationAlreadyLinked(claim, linkedApplicationReferences) {
|
||||
if (!linkedApplicationReferences) {
|
||||
return false
|
||||
}
|
||||
|
||||
const ownReferences = createReferenceIndex()
|
||||
addApplicationReference(
|
||||
ownReferences,
|
||||
claim?.id || claim?.claim_id || claim?.claimId,
|
||||
claim?.claim_no || claim?.claimNo
|
||||
)
|
||||
if (!hasAnyApplicationReference(ownReferences)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return Array.from(ownReferences.ids).some((id) => linkedApplicationReferences.ids.has(id))
|
||||
|| Array.from(ownReferences.claimNos).some((claimNo) => linkedApplicationReferences.claimNos.has(claimNo))
|
||||
}
|
||||
|
||||
function toTimestamp(value) {
|
||||
const date = new Date(value)
|
||||
return Number.isNaN(date.getTime()) ? 0 : date.getTime()
|
||||
@@ -265,9 +365,14 @@ export function isClaimOwnedByCurrentUser(claim, currentUser = {}) {
|
||||
return true
|
||||
}
|
||||
|
||||
export function isUsableRequiredApplicationClaim(claim) {
|
||||
export function isUsableRequiredApplicationClaim(claim, linkedApplicationReferences = null) {
|
||||
const status = normalizeApplicationStatus(claim)
|
||||
return !BLOCKED_APPLICATION_STATUSES.has(status)
|
||||
const approvalKey = normalizeApprovalKey(claim)
|
||||
if (BLOCKED_APPLICATION_LINK_STATUSES.has(status)) {
|
||||
return false
|
||||
}
|
||||
return (APPROVED_APPLICATION_STATUSES.has(status) || APPROVED_APPLICATION_APPROVAL_KEYS.has(approvalKey))
|
||||
&& !isApplicationAlreadyLinked(claim, linkedApplicationReferences)
|
||||
}
|
||||
|
||||
export function normalizeRequiredApplicationCandidate(claim) {
|
||||
@@ -331,10 +436,12 @@ export function filterRequiredApplicationCandidates(claimsPayload, expenseType,
|
||||
? claimsPayload.claims
|
||||
: []
|
||||
|
||||
const linkedApplicationReferences = buildLinkedApplicationReferenceIndex(claims)
|
||||
|
||||
return claims
|
||||
.filter((claim) => (
|
||||
isExpenseApplicationClaim(claim)
|
||||
&& isUsableRequiredApplicationClaim(claim)
|
||||
&& isUsableRequiredApplicationClaim(claim, linkedApplicationReferences)
|
||||
&& isClaimOwnedByCurrentUser(claim, currentUser)
|
||||
&& matchesRequiredApplicationExpenseType(claim, expenseType)
|
||||
))
|
||||
|
||||
Reference in New Issue
Block a user