fix: preserve reviewer risk notice after standard adjustment
This commit is contained in:
@@ -93,7 +93,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
buildCurrentStandardAdjustmentMap,
|
buildCurrentStandardAdjustmentMap,
|
||||||
buildStandardAdjustmentPayload as buildStandardAdjustmentPayloadModel,
|
buildStandardAdjustmentPayload as buildStandardAdjustmentPayloadModel,
|
||||||
filterSubmitterStandardAdjustedRiskCards as filterSubmitterStandardAdjustedRiskCardsModel,
|
filterSubmitterResolvedRiskCards as filterSubmitterResolvedRiskCardsModel,
|
||||||
isRiskCardMissingExpenseNote as isRiskCardMissingExpenseNoteModel,
|
isRiskCardMissingExpenseNote as isRiskCardMissingExpenseNoteModel,
|
||||||
resolveExpenseItemForRiskCard as resolveExpenseItemForRiskCardModel
|
resolveExpenseItemForRiskCard as resolveExpenseItemForRiskCardModel
|
||||||
} from './travelRequestDetailStandardAdjustment.js'
|
} from './travelRequestDetailStandardAdjustment.js'
|
||||||
@@ -1179,8 +1179,8 @@ export default {
|
|||||||
return resolveExpenseItemForRiskCardModel(card, expenseItems.value)
|
return resolveExpenseItemForRiskCardModel(card, expenseItems.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterSubmitterStandardAdjustedRiskCards(cards, businessStage) {
|
function filterSubmitterResolvedRiskCards(cards, businessStage) {
|
||||||
return filterSubmitterStandardAdjustedRiskCardsModel({
|
return filterSubmitterResolvedRiskCardsModel({
|
||||||
cards,
|
cards,
|
||||||
businessStage,
|
businessStage,
|
||||||
isCurrentApplicant: isCurrentApplicant.value,
|
isCurrentApplicant: isCurrentApplicant.value,
|
||||||
@@ -1597,7 +1597,7 @@ export default {
|
|||||||
: []
|
: []
|
||||||
const scopedRiskCards = [
|
const scopedRiskCards = [
|
||||||
...(hasActionableRiskCards ? [] : summaryRiskCards),
|
...(hasActionableRiskCards ? [] : summaryRiskCards),
|
||||||
...filterSubmitterStandardAdjustedRiskCards(directRiskCards, currentBusinessStage)
|
...filterSubmitterResolvedRiskCards(directRiskCards, currentBusinessStage)
|
||||||
]
|
]
|
||||||
const riskCards = filterRiskCardsForVisibility(scopedRiskCards, riskViewerContext.value)
|
const riskCards = filterRiskCardsForVisibility(scopedRiskCards, riskViewerContext.value)
|
||||||
|
|
||||||
|
|||||||
@@ -31,14 +31,6 @@ export function resolveExpenseItemForRiskCard(card, expenseItems = []) {
|
|||||||
|| null
|
|| 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 = []) {
|
export function isRiskCardMissingExpenseNote(card, expenseItems = []) {
|
||||||
const item = resolveExpenseItemForRiskCard(card, expenseItems)
|
const item = resolveExpenseItemForRiskCard(card, expenseItems)
|
||||||
if (!item) {
|
if (!item) {
|
||||||
@@ -47,7 +39,15 @@ export function isRiskCardMissingExpenseNote(card, expenseItems = []) {
|
|||||||
return !normalizeText(item.itemNote)
|
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) {
|
if (normalizeText(card?.source) === STANDARD_ADJUSTMENT_RISK_SOURCE) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -57,7 +57,7 @@ function isRiskCardCoveredByStandardAdjustment(card, expenseItems, standardAdjus
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function filterSubmitterStandardAdjustedRiskCards({
|
export function filterSubmitterResolvedRiskCards({
|
||||||
cards = [],
|
cards = [],
|
||||||
businessStage = 'reimbursement',
|
businessStage = 'reimbursement',
|
||||||
isCurrentApplicant = false,
|
isCurrentApplicant = false,
|
||||||
@@ -67,7 +67,7 @@ export function filterSubmitterStandardAdjustedRiskCards({
|
|||||||
if (businessStage !== 'reimbursement' || !isCurrentApplicant) {
|
if (businessStage !== 'reimbursement' || !isCurrentApplicant) {
|
||||||
return cards
|
return cards
|
||||||
}
|
}
|
||||||
return cards.filter((card) => !isRiskCardCoveredByStandardAdjustment(card, expenseItems, standardAdjustmentMap))
|
return cards.filter((card) => !isRiskCardResolvedForSubmitter(card, expenseItems, standardAdjustmentMap))
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractRiskCardMoneyValues(card) {
|
function extractRiskCardMoneyValues(card) {
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ import {
|
|||||||
buildEmployeeProfileAdviceItems,
|
buildEmployeeProfileAdviceItems,
|
||||||
buildTravelReceiptMaterialPrompts
|
buildTravelReceiptMaterialPrompts
|
||||||
} from '../src/views/scripts/travelRequestDetailAdviceModel.js'
|
} from '../src/views/scripts/travelRequestDetailAdviceModel.js'
|
||||||
|
import {
|
||||||
|
filterSubmitterResolvedRiskCards
|
||||||
|
} from '../src/views/scripts/travelRequestDetailStandardAdjustment.js'
|
||||||
|
|
||||||
const detailViewTemplate = readFileSync(
|
const detailViewTemplate = readFileSync(
|
||||||
fileURLToPath(new URL('../src/views/TravelRequestDetailView.vue', import.meta.url)),
|
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(detailViewTemplate, /submitConfirmAmountDisplay/)
|
||||||
assert.match(detailViewStyle, /\.expense-original-amount[\s\S]*text-decoration-line: line-through/)
|
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, /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(detailViewScript, /acceptExpenseClaimStandardAdjustment/)
|
||||||
assert.match(detailExpenseModelScript, /STANDARD_ADJUSTMENT_RISK_SOURCE = 'reimbursement_standard_adjustment'/)
|
assert.match(detailExpenseModelScript, /STANDARD_ADJUSTMENT_RISK_SOURCE = 'reimbursement_standard_adjustment'/)
|
||||||
assert.match(requestsComposableScript, /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)
|
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', () => {
|
test('expense item upload remains limited to one receipt per detail row', () => {
|
||||||
assert.match(detailViewTemplate, /ref="expenseUploadInput"[\s\S]*type="file"/)
|
assert.match(detailViewTemplate, /ref="expenseUploadInput"[\s\S]*type="file"/)
|
||||||
assert.doesNotMatch(
|
assert.doesNotMatch(
|
||||||
|
|||||||
Reference in New Issue
Block a user