@@ -23,6 +23,32 @@ import SCREENS from '@src/SCREENS';
2323import type { Errors } from '@src/types/onyx/OnyxCommon' ;
2424import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue' ;
2525
26+ /**
27+ * Parses a CSV cell value for receipt requirement columns.
28+ * Mirrors the OD import logic: "default" → null, "required"/"always_required" → 0,
29+ * "not_required" → DISABLED_MAX_EXPENSE_VALUE, numeric string → number.
30+ */
31+ function parseCsvReceiptValue ( raw : string | undefined ) : number | null | undefined {
32+ if ( raw === undefined ) {
33+ return undefined ;
34+ }
35+ const trimmed = raw . trim ( ) . toLowerCase ( ) ;
36+ if ( ! trimmed || trimmed === 'default' ) {
37+ return null ;
38+ }
39+ if ( trimmed === 'required' || trimmed === 'always_required' ) {
40+ return 0 ;
41+ }
42+ if ( trimmed === 'not_required' ) {
43+ return CONST . DISABLED_MAX_EXPENSE_VALUE ;
44+ }
45+ const num = Number ( trimmed ) ;
46+ if ( Number . isFinite ( num ) && num >= 0 ) {
47+ return num ;
48+ }
49+ return undefined ;
50+ }
51+
2652type ImportedCategoriesPageProps = {
2753 route : RouteProp < SettingsNavigatorParamList , typeof SCREENS . WORKSPACE . DYNAMIC_CATEGORIES_IMPORTED | typeof SCREENS . SETTINGS_CATEGORIES . SETTINGS_CATEGORIES_IMPORTED > ;
2854} ;
@@ -52,7 +78,11 @@ function ImportedCategoriesPage({route}: ImportedCategoriesPageProps) {
5278 ) ;
5379
5480 if ( isControlPolicy ( policy ) ) {
55- roles . push ( { text : translate ( 'workspace.categories.glCode' ) , value : CONST . CSV_IMPORT_COLUMNS . GL_CODE } ) ;
81+ roles . push (
82+ { text : translate ( 'workspace.categories.glCode' ) , value : CONST . CSV_IMPORT_COLUMNS . GL_CODE } ,
83+ { text : translate ( 'workspace.rules.categoryRules.requireReceiptsOver' ) , value : CONST . CSV_IMPORT_COLUMNS . MAX_AMOUNT_NO_RECEIPT } ,
84+ { text : translate ( 'workspace.rules.categoryRules.requireItemizedReceiptsOver' ) , value : CONST . CSV_IMPORT_COLUMNS . MAX_AMOUNT_NO_ITEMIZED_RECEIPT } ,
85+ ) ;
5686 }
5787
5888 return roles ;
@@ -99,17 +129,39 @@ function ImportedCategoriesPage({route}: ImportedCategoriesPageProps) {
99129 const categoriesNamesColumn = columns . findIndex ( ( column ) => column === CONST . CSV_IMPORT_COLUMNS . NAME ) ;
100130 const categoriesGLCodeColumn = columns . findIndex ( ( column ) => column === CONST . CSV_IMPORT_COLUMNS . GL_CODE ) ;
101131 const categoriesEnabledColumn = columns . findIndex ( ( column ) => column === CONST . CSV_IMPORT_COLUMNS . ENABLED ) ;
132+ const categoriesMaxAmountNoReceiptColumn = columns . findIndex ( ( column ) => column === CONST . CSV_IMPORT_COLUMNS . MAX_AMOUNT_NO_RECEIPT ) ;
133+ const categoriesMaxAmountNoItemizedReceiptColumn = columns . findIndex ( ( column ) => column === CONST . CSV_IMPORT_COLUMNS . MAX_AMOUNT_NO_ITEMIZED_RECEIPT ) ;
102134 const categoriesNames = spreadsheet ?. data [ categoriesNamesColumn ] . map ( ( name ) => name ) ;
103135 const categoriesEnabled = categoriesEnabledColumn !== - 1 ? spreadsheet ?. data [ categoriesEnabledColumn ] . map ( ( enabled ) => enabled ) : [ ] ;
104136 const categoriesGLCode = categoriesGLCodeColumn !== - 1 ? spreadsheet ?. data [ categoriesGLCodeColumn ] . map ( ( glCode ) => glCode ) : [ ] ;
137+ const categoriesMaxAmountNoReceipt = categoriesMaxAmountNoReceiptColumn !== - 1 ? spreadsheet ?. data [ categoriesMaxAmountNoReceiptColumn ] : [ ] ;
138+ const categoriesMaxAmountNoItemizedReceipt = categoriesMaxAmountNoItemizedReceiptColumn !== - 1 ? spreadsheet ?. data [ categoriesMaxAmountNoItemizedReceiptColumn ] : [ ] ;
105139 const categories = categoriesNames ?. slice ( containsHeader ? 1 : 0 ) . map ( ( name , index ) => {
106140 const categoryAlreadyExists = policyCategories ?. [ name ] ;
107141 const existingGLCodeOrDefault = categoryAlreadyExists ?. [ 'GL Code' ] ?? '' ;
142+ const dataIndex = containsHeader ? index + 1 : index ;
143+
144+ const parsedMaxAmountNoReceipt = categoriesMaxAmountNoReceiptColumn !== - 1 ? parseCsvReceiptValue ( categoriesMaxAmountNoReceipt ?. [ dataIndex ] ?. toString ( ) ) : undefined ;
145+ const parsedMaxAmountNoItemizedReceipt =
146+ categoriesMaxAmountNoItemizedReceiptColumn !== - 1 ? parseCsvReceiptValue ( categoriesMaxAmountNoItemizedReceipt ?. [ dataIndex ] ?. toString ( ) ) : undefined ;
147+
148+ // Apply normalization: if itemized receipts required but receipts not required, force both to required
149+ let normalizedMaxAmountNoReceipt = parsedMaxAmountNoReceipt ;
150+ let normalizedMaxAmountNoItemizedReceipt = parsedMaxAmountNoItemizedReceipt ;
151+ if ( normalizedMaxAmountNoReceipt === CONST . DISABLED_MAX_EXPENSE_VALUE && normalizedMaxAmountNoItemizedReceipt !== undefined ) {
152+ normalizedMaxAmountNoItemizedReceipt = CONST . DISABLED_MAX_EXPENSE_VALUE ;
153+ }
154+ if ( normalizedMaxAmountNoItemizedReceipt === 0 && normalizedMaxAmountNoReceipt !== undefined ) {
155+ normalizedMaxAmountNoReceipt = 0 ;
156+ }
157+
108158 return {
109159 name,
110- enabled : categoriesEnabledColumn !== - 1 ? [ 'true' , 'yes' ] . includes ( categoriesEnabled ?. [ containsHeader ? index + 1 : index ] ?. toString ( ) . toLowerCase ( ) ?? '' ) : true ,
160+ enabled : categoriesEnabledColumn !== - 1 ? [ 'true' , 'yes' ] . includes ( categoriesEnabled ?. [ dataIndex ] ?. toString ( ) . toLowerCase ( ) ?? '' ) : true ,
111161 // eslint-disable-next-line @typescript-eslint/naming-convention
112- 'GL Code' : categoriesGLCodeColumn !== - 1 ? ( categoriesGLCode ?. [ containsHeader ? index + 1 : index ] ?? '' ) : existingGLCodeOrDefault ,
162+ 'GL Code' : categoriesGLCodeColumn !== - 1 ? ( categoriesGLCode ?. [ dataIndex ] ?? '' ) : existingGLCodeOrDefault ,
163+ ...( normalizedMaxAmountNoReceipt !== undefined && { maxAmountNoReceipt : normalizedMaxAmountNoReceipt } ) ,
164+ ...( normalizedMaxAmountNoItemizedReceipt !== undefined && { maxAmountNoItemizedReceipt : normalizedMaxAmountNoItemizedReceipt } ) ,
113165 } ;
114166 } ) ;
115167
0 commit comments