diff --git a/web/src/views/scripts/TravelRequestDetailView.js b/web/src/views/scripts/TravelRequestDetailView.js index 0a7527b..e7766b4 100644 --- a/web/src/views/scripts/TravelRequestDetailView.js +++ b/web/src/views/scripts/TravelRequestDetailView.js @@ -93,7 +93,7 @@ import { import { buildCurrentStandardAdjustmentMap, buildStandardAdjustmentPayload as buildStandardAdjustmentPayloadModel, - filterSubmitterStandardAdjustedRiskCards as filterSubmitterStandardAdjustedRiskCardsModel, + filterSubmitterResolvedRiskCards as filterSubmitterResolvedRiskCardsModel, isRiskCardMissingExpenseNote as isRiskCardMissingExpenseNoteModel, resolveExpenseItemForRiskCard as resolveExpenseItemForRiskCardModel } from './travelRequestDetailStandardAdjustment.js' @@ -1179,8 +1179,8 @@ export default { return resolveExpenseItemForRiskCardModel(card, expenseItems.value) } - function filterSubmitterStandardAdjustedRiskCards(cards, businessStage) { - return filterSubmitterStandardAdjustedRiskCardsModel({ + function filterSubmitterResolvedRiskCards(cards, businessStage) { + return filterSubmitterResolvedRiskCardsModel({ cards, businessStage, isCurrentApplicant: isCurrentApplicant.value, @@ -1597,7 +1597,7 @@ export default { : [] const scopedRiskCards = [ ...(hasActionableRiskCards ? [] : summaryRiskCards), - ...filterSubmitterStandardAdjustedRiskCards(directRiskCards, currentBusinessStage) + ...filterSubmitterResolvedRiskCards(directRiskCards, currentBusinessStage) ] const riskCards = filterRiskCardsForVisibility(scopedRiskCards, riskViewerContext.value) diff --git a/web/src/views/scripts/travelRequestDetailStandardAdjustment.js b/web/src/views/scripts/travelRequestDetailStandardAdjustment.js index 96b9c3b..b0be2e4 100644 --- a/web/src/views/scripts/travelRequestDetailStandardAdjustment.js +++ b/web/src/views/scripts/travelRequestDetailStandardAdjustment.js @@ -31,14 +31,6 @@ export function resolveExpenseItemForRiskCard(card, expenseItems = []) { || null } -export function hasStandardAdjustmentForItem(item, standardAdjustmentMap = new Map()) { - const itemId = normalizeText(item?.id) - if (!itemId) { - return false - } - return Boolean(item?.standardAdjustmentAccepted || standardAdjustmentMap.has(itemId)) -} - export function isRiskCardMissingExpenseNote(card, expenseItems = []) { const item = resolveExpenseItemForRiskCard(card, expenseItems) if (!item) { @@ -47,7 +39,15 @@ export function isRiskCardMissingExpenseNote(card, expenseItems = []) { return !normalizeText(item.itemNote) } -function isRiskCardCoveredByStandardAdjustment(card, expenseItems, standardAdjustmentMap) { +export function hasStandardAdjustmentForItem(item, standardAdjustmentMap = new Map()) { + const itemId = normalizeText(item?.id) + if (!itemId) { + return false + } + return Boolean(item?.standardAdjustmentAccepted || standardAdjustmentMap.has(itemId)) +} + +function isRiskCardResolvedForSubmitter(card, expenseItems, standardAdjustmentMap) { if (normalizeText(card?.source) === STANDARD_ADJUSTMENT_RISK_SOURCE) { return true } @@ -57,7 +57,7 @@ function isRiskCardCoveredByStandardAdjustment(card, expenseItems, standardAdjus ) } -export function filterSubmitterStandardAdjustedRiskCards({ +export function filterSubmitterResolvedRiskCards({ cards = [], businessStage = 'reimbursement', isCurrentApplicant = false, @@ -67,7 +67,7 @@ export function filterSubmitterStandardAdjustedRiskCards({ if (businessStage !== 'reimbursement' || !isCurrentApplicant) { return cards } - return cards.filter((card) => !isRiskCardCoveredByStandardAdjustment(card, expenseItems, standardAdjustmentMap)) + return cards.filter((card) => !isRiskCardResolvedForSubmitter(card, expenseItems, standardAdjustmentMap)) } function extractRiskCardMoneyValues(card) { diff --git a/web/tests/travel-request-detail-risk-advice.test.mjs b/web/tests/travel-request-detail-risk-advice.test.mjs index a2ca316..eb4e53a 100644 --- a/web/tests/travel-request-detail-risk-advice.test.mjs +++ b/web/tests/travel-request-detail-risk-advice.test.mjs @@ -24,6 +24,9 @@ import { buildEmployeeProfileAdviceItems, buildTravelReceiptMaterialPrompts } from '../src/views/scripts/travelRequestDetailAdviceModel.js' +import { + filterSubmitterResolvedRiskCards +} from '../src/views/scripts/travelRequestDetailStandardAdjustment.js' const detailViewTemplate = readFileSync( fileURLToPath(new URL('../src/views/TravelRequestDetailView.vue', import.meta.url)), @@ -708,7 +711,8 @@ test('expense detail shows standard-adjusted reimbursable amount separately from assert.match(detailViewTemplate, /submitConfirmAmountDisplay/) assert.match(detailViewStyle, /\.expense-original-amount[\s\S]*text-decoration-line: line-through/) assert.match(detailViewScript, /const expenseTotal = computed\(\(\) => \{[\s\S]*item\.reimbursableAmount/) - assert.match(detailViewScript, /filterSubmitterStandardAdjustedRiskCards/) + assert.match(detailViewScript, /filterSubmitterResolvedRiskCards/) + assert.doesNotMatch(detailViewScript, /filterSubmitterStandardAdjustedRiskCards/) assert.match(detailViewScript, /acceptExpenseClaimStandardAdjustment/) assert.match(detailExpenseModelScript, /STANDARD_ADJUSTMENT_RISK_SOURCE = 'reimbursement_standard_adjustment'/) assert.match(requestsComposableScript, /STANDARD_ADJUSTMENT_RISK_SOURCE = 'reimbursement_standard_adjustment'/) @@ -744,6 +748,66 @@ test('expense detail shows standard-adjusted reimbursable amount separately from assert.equal(item.hasStandardAdjustment, true) }) +test('standard adjustment resolves submitter risk prompt only after accepted while reviewer still sees notice', () => { + const originalRiskCard = { + id: 'risk-hotel-1', + source: 'attachment_analysis', + itemId: 'expense-item-1', + tone: 'high', + risk: '住宿票据金额超过职级标准。' + } + const reviewerNoticeCard = { + id: 'standard-adjustment-1', + source: 'reimbursement_standard_adjustment', + itemId: 'expense-item-1', + tone: 'medium', + risk: '提交人已选择按职级标准报销,超出部分由员工自担。' + } + const expenseItems = [{ id: 'expense-item-1' }] + const standardAdjustmentMap = buildStandardAdjustmentMap({ + riskFlags: [ + { + source: 'reimbursement_standard_adjustment', + item_id: 'expense-item-1', + original_amount: '880.00', + reimbursable_amount: '600.00', + employee_absorbed_amount: '280.00' + } + ] + }) + + assert.deepEqual( + filterSubmitterResolvedRiskCards({ + cards: [originalRiskCard], + businessStage: 'reimbursement', + isCurrentApplicant: true, + expenseItems, + standardAdjustmentMap: new Map() + }), + [originalRiskCard] + ) + assert.deepEqual( + filterSubmitterResolvedRiskCards({ + cards: [originalRiskCard], + businessStage: 'reimbursement', + isCurrentApplicant: true, + expenseItems, + standardAdjustmentMap + }), + [] + ) + assert.deepEqual( + filterSubmitterResolvedRiskCards({ + cards: [originalRiskCard, reviewerNoticeCard], + businessStage: 'reimbursement', + isCurrentApplicant: false, + expenseItems, + standardAdjustmentMap + }), + [originalRiskCard, reviewerNoticeCard] + ) +}) + test('expense item upload remains limited to one receipt per detail row', () => { assert.match(detailViewTemplate, /ref="expenseUploadInput"[\s\S]*type="file"/) assert.doesNotMatch(