Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7152,6 +7152,11 @@ const CONST = {
reportView: true,
},
TAG: {column: this.TABLE_COLUMNS.TAG, search: true, reportView: true},
VIOLATIONS: {
column: this.TABLE_COLUMNS.VIOLATIONS,
search: true,
reportView: false,
},
TAG_GL_CODE: {
column: this.TABLE_COLUMNS.TAG_GL_CODE,
search: true,
Expand Down Expand Up @@ -7471,6 +7476,7 @@ const CONST = {
TO: 'to',
CATEGORY: 'category',
TAG: 'tag',
VIOLATIONS: 'violations',
ORIGINAL_AMOUNT: 'originalamount',
REIMBURSABLE: 'reimbursable',
BILLABLE: 'billable',
Expand Down Expand Up @@ -7712,6 +7718,7 @@ const CONST = {
[this.TABLE_COLUMNS.TO]: 'to',
[this.TABLE_COLUMNS.CATEGORY]: 'category',
[this.TABLE_COLUMNS.TAG]: 'tag',
[this.TABLE_COLUMNS.VIOLATIONS]: 'violations',
[this.TABLE_COLUMNS.ORIGINAL_AMOUNT]: 'purchase-amount',
[this.TABLE_COLUMNS.REIMBURSABLE]: 'reimbursable',
[this.TABLE_COLUMNS.BILLABLE]: 'billable',
Expand Down Expand Up @@ -7806,6 +7813,7 @@ const CONST = {
TOP_CATEGORIES: 'topCategories',
TOP_MERCHANTS: 'topMerchants',
SPEND_OVER_TIME: 'spendOverTime',
VIOLATIONS_BY_SUBMITTER: 'violationsBySubmitter',
},
SAVED_SEARCH_PREFIX: 'savedSearch_',
GROUP_PREFIX: 'group_',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ function TransactionGroupListExpandedImpl({
)}
{visibleTransactions.map((transaction, index) => {
const shouldShowBottomBorder = !isLastTransaction(index);
const exportedReportActions = Object.values(transactionsSnapshot?.data?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transaction?.reportID}`] ?? {});
const reportActions = Object.values(transactionsSnapshot?.data?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transaction?.reportID}`] ?? {});
const isDeletedOrPendingDelete = isDeletedTransaction(transaction) || isTransactionPendingDelete(transaction);

return (
Expand Down Expand Up @@ -384,7 +384,7 @@ function TransactionGroupListExpandedImpl({
shouldShowBottomBorder={shouldShowBottomBorder}
onArrowRightPress={isDeletedOrPendingDelete ? undefined : (event) => openReportInRHP(transaction, event)}
shouldShowArrowRightOnNarrowLayout
reportActions={exportedReportActions}
reportActions={reportActions}
nonPersonalAndWorkspaceCards={nonPersonalAndWorkspaceCards}
isActionColumnWide={isActionColumnWide}
isHover={hovered}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ function TransactionListItemNarrow<TItem extends ListItem>({
handleActionButtonPress,
shouldDisableActionPointerEvents,
transactionPreviewData,
exportedReportActions,
reportActions,
nonPersonalAndWorkspaceCards,
isAttendeesEnabledForMovingPolicy,
}: TransactionListItemNarrowProps<TItem>) {
Expand Down Expand Up @@ -170,7 +170,7 @@ function TransactionListItemNarrow<TItem extends ListItem>({
onArrowRightPress={isDeletedTransaction ? undefined : (event) => onSelectRow(item, transactionPreviewData, event)}
isHover={false}
nonPersonalAndWorkspaceCards={nonPersonalAndWorkspaceCards}
reportActions={exportedReportActions}
reportActions={reportActions}
isAttendeesEnabledForMovingPolicy={isAttendeesEnabledForMovingPolicy}
/>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function TransactionListItemWide<TItem extends ListItem>({
handleActionButtonPress,
shouldDisableActionPointerEvents,
transactionPreviewData,
exportedReportActions,
reportActions,
policyCategories,
policyTagLists,
rowPolicy,
Expand Down Expand Up @@ -211,7 +211,7 @@ function TransactionListItemWide<TItem extends ListItem>({
onArrowRightPress={isDeletedTransaction ? undefined : (event) => onSelectRow(item, transactionPreviewData, event)}
isHover={hovered}
nonPersonalAndWorkspaceCards={nonPersonalAndWorkspaceCards}
reportActions={exportedReportActions}
reportActions={reportActions}
isAttendeesEnabledForMovingPolicy={isAttendeesEnabledForMovingPolicy}
onEditDate={onEditDate}
onEditMerchant={onEditMerchant}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ function TransactionListItemInner<TItem extends ListItem>({
const snapshotPolicy = (currentSearchResults?.data?.[`${ONYXKEYS.COLLECTION.POLICY}${transactionItem.policyID}`] ?? {}) as Policy;

const actionsData = currentSearchResults?.data?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionItem.reportID}`];
const exportedReportActions = actionsData ? Object.values(actionsData) : [];
const reportActions = actionsData ? Object.values(actionsData) : [];

// Fetch policy categories directly from Onyx since they are not included in the search snapshot
const [policyCategories] = originalUseOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${getNonEmptyStringOnyxID(policyID)}`);
Expand All @@ -138,7 +138,7 @@ function TransactionListItemInner<TItem extends ListItem>({
const allViolations: OnyxCollection<TransactionViolations> = {[transactionViolationsKey]: transactionViolationsForRow};
const parentReportActionID = transactionItem?.reportAction?.reportActionID;
const [parentReportAction] = originalUseOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(transactionItem.reportID)}`, {
selector: (reportActions: OnyxEntry<ReportActions>): OnyxEntry<ReportAction> => reportActions?.[`${parentReportActionID}`],
selector: (actions: OnyxEntry<ReportActions>): OnyxEntry<ReportAction> => actions?.[`${parentReportActionID}`],
});
const currentUserDetails = useCurrentUserPersonalDetails();
const chatReportID = snapshotReport?.chatReportID ?? snapshotReport?.parentReportID;
Expand All @@ -159,7 +159,7 @@ function TransactionListItemInner<TItem extends ListItem>({
reportID: transactionItem.reportID,
itemKey: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionItem.transactionID}`,
snapshotData,
snapshotActions: exportedReportActions,
snapshotActions: reportActions,
enabled: !!snapshotData,
});
const transactionPreviewData: TransactionPreviewData = {
Expand Down Expand Up @@ -273,7 +273,7 @@ function TransactionListItemInner<TItem extends ListItem>({
handleActionButtonPress,
shouldDisableActionPointerEvents: shouldDisableSearchSubmitPress,
transactionPreviewData,
exportedReportActions,
reportActions,
policyCategories,
policyTagLists,
rowPolicy,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type TransactionListItemSharedProps<TItem extends ListItem> = {
handleActionButtonPress: (event?: ModifiedMouseEvent) => void;
shouldDisableActionPointerEvents?: boolean;
transactionPreviewData: TransactionPreviewData;
exportedReportActions: ReportAction[];
reportActions: ReportAction[];
policyCategories?: PolicyCategories;
policyTagLists?: PolicyTagLists;
rowPolicy?: Policy;
Expand Down
5 changes: 5 additions & 0 deletions src/components/Search/SearchTableHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ const getExpenseHeaders = (groupBy?: SearchGroupBy): SearchColumnConfig[] => [
translationKey: 'common.tag',
canEdit: true,
},
{
columnName: CONST.SEARCH.TABLE_COLUMNS.VIOLATIONS,
translationKey: 'common.violations',
isColumnSortable: false,
},
{
columnName: CONST.SEARCH.TABLE_COLUMNS.TAG_GL_CODE,
translationKey: 'common.tagGLCode',
Expand Down
10 changes: 10 additions & 0 deletions src/components/TransactionItemRow/TransactionItemRowWide.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import getBase62ReportID from '@libs/getBase62ReportID';
import {isTaxCodeCustomized, getTagGLCode} from '@libs/PolicyUtils';
import {getReportName} from '@libs/ReportNameUtils';
import {getReimbursableTotal, isExpenseReport} from '@libs/ReportUtils';
import {getSubmittedViolationsForTransaction} from '@libs/SearchUIUtils';
import {getShiftKeyFromEvent} from '@libs/shiftRangeSelection';
import {
getAmount,
Expand Down Expand Up @@ -208,6 +209,15 @@ function TransactionItemRowWide({
/>
</View>
);
case CONST.SEARCH.TABLE_COLUMNS.VIOLATIONS:
return (
<View
key={column}
style={[StyleUtils.getReportTableColumnStyles(CONST.SEARCH.TABLE_COLUMNS.VIOLATIONS)]}
>
<TextCell text={getSubmittedViolationsForTransaction(reportActions, transactionItem.transactionID, translate)} />
</View>
);
case CONST.SEARCH.TABLE_COLUMNS.TAG_GL_CODE:
return (
<View
Expand Down
25 changes: 23 additions & 2 deletions src/hooks/useSearchBulkActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,25 @@ import {
isSelfDM,
shouldShowMarkAsDone,
} from '@libs/ReportUtils';
import {buildSearchQueryJSON, buildSearchQueryString, getFilterFromQuery, isDefaultExpensesQuery, serializeQueryJSONForBackend} from '@libs/SearchQueryUtils';
import {
buildSearchQueryJSON,
buildSearchQueryString,
getFilterFromQuery,
isDefaultExpensesQuery,
queryHasSubmittedViolationFilter,
serializeQueryJSONForBackend,
} from '@libs/SearchQueryUtils';
import refreshSearchAfterReportAction from '@libs/SearchRefreshUtils';
import {getColumnsToShow, getSearchColumnTranslationKey, getSelectedGroupFilterEntry, getValidGroupBy, isGroupEntry, navigateToSearchRHP, shouldShowDeleteOption} from '@libs/SearchUIUtils';
import {
getColumnsToShow,
getSearchColumnTranslationKey,
getSelectedGroupFilterEntry,
getValidGroupBy,
insertColumnBeforeTotalAmount,
isGroupEntry,
navigateToSearchRHP,
shouldShowDeleteOption,
} from '@libs/SearchUIUtils';
import showConfirmModalAfterMoreMenuDismiss from '@libs/showConfirmModalAfterMoreMenuDismiss';
import playSound, {SOUNDS} from '@libs/Sound';
import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils';
Expand Down Expand Up @@ -907,6 +923,11 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) {
const expenseColumns: SearchColumnType[] = (visibleColumns ?? []).filter((column) => expensePermittedColumns.includes(column));

columnsToExport = [CONST.SEARCH.TABLE_COLUMNS.TYPE, ...(expenseColumns.length > 0 ? expenseColumns : Object.values(CONST.SEARCH.TYPE_DEFAULT_COLUMNS.EXPENSE))];
// Grouped export skips getColumnsToShow(), so inject Violations when the query asks for it
// (e.g. Violations by submitter, which has groupBy but no saved columns).
if (queryHasSubmittedViolationFilter(queryJSON)) {
insertColumnBeforeTotalAmount(columnsToExport, CONST.SEARCH.TABLE_COLUMNS.VIOLATIONS);
}
} else {
columnsToExport = getColumnsToShow({
currentAccountID: accountID,
Expand Down
1 change: 1 addition & 0 deletions src/hooks/useSearchShouldCalculateTotals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ function useSearchShouldCalculateTotals(searchKey: SearchKey | undefined, search
CONST.SEARCH.SEARCH_KEYS.TOP_MERCHANTS,
CONST.SEARCH.SEARCH_KEYS.TOP_SPENDERS,
CONST.SEARCH.SEARCH_KEYS.SPEND_OVER_TIME,
CONST.SEARCH.SEARCH_KEYS.VIOLATIONS_BY_SUBMITTER,
];

const isSuggestedSearchWithTotals = eligibleSearchKeys.includes(searchKey);
Expand Down
7 changes: 7 additions & 0 deletions src/hooks/useSearchTypeMenuSections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const policyMapper = (policy: OnyxEntry<Policy>): OnyxEntry<Policy> =>
achAccount: policy.achAccount,
areCategoriesEnabled: policy.areCategoriesEnabled,
areWorkflowsEnabled: policy.areWorkflowsEnabled,
areRulesEnabled: policy.areRulesEnabled,
};

const currentUserLoginAndAccountIDSelector = (session: OnyxEntry<Session>) => ({
Expand Down Expand Up @@ -76,6 +77,10 @@ const useSearchTypeMenuSections = (queryParams?: UseSearchTypeMenuSectionsParams
const [savedSearches] = useOnyx(ONYXKEYS.SAVED_SEARCHES);
const [draftTransactionIDs] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {selector: validTransactionDraftIDsSelector});
const [isTrackIntentUser] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {selector: isTrackIntentUserSelector});
// Migrated Control workspaces leave `areRulesEnabled` undefined; Violations by submitter then depends on
// Classic category rules stored on POLICY_CATEGORIES. No selector: mapping this collection would still be
// large, and shallowEqual on the raw references is cheaper than deepEqual of a transformed copy.
const [allPolicyCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CATEGORIES);

// A report awaiting the current user's approval makes the "Needs approval" suggested search relevant even when they
// are not part of the policy's approval workflow (e.g. an approver chosen manually on a single report).
Expand Down Expand Up @@ -125,6 +130,7 @@ const useSearchTypeMenuSections = (queryParams?: UseSearchTypeMenuSectionsParams
draftTransactionIDs,
isTrackIntentUser: isTrackIntentUser ?? false,
hasReportAwaitingApproval,
policyCategories: allPolicyCategories,
}),
[
currentUserLoginAndAccountID?.email,
Expand All @@ -139,6 +145,7 @@ const useSearchTypeMenuSections = (queryParams?: UseSearchTypeMenuSectionsParams
draftTransactionIDs,
isTrackIntentUser,
hasReportAwaitingApproval,
allPolicyCategories,
],
);

Expand Down
49 changes: 49 additions & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ const translations: TranslationDeepObject<typeof en> = {
billable: 'Abrechenbar',
nonBillable: 'Nicht abrechenbar',
tag: 'Tag',
violations: 'Verstöße',
receipt: 'Beleg',
verified: 'Verifiziert',
replace: 'Ersetzen',
Expand Down Expand Up @@ -9487,6 +9488,7 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc
topSpenders: 'Höchste Ausgaben',
topCategories: 'Topkategorien',
topMerchants: 'Top-Händler',
violationsBySubmitter: 'Verstöße durch Einreichende',
},
},
genericErrorPage: {
Expand Down Expand Up @@ -10190,6 +10192,53 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc
customUnitRateOutOfDateRangeStartOnly: ({startDate}: {startDate: string}) => `Der Satz gilt erst ab dem ${startDate}`,
customUnitRateOutOfDateRangeEndOnly: ({endDate}: {endDate: string}) => `Satz gilt nur bis ${endDate}`,
cannotMergeDuplicates: 'Sie können Ausgaben nur in Entwürfen oder offenen Berichten zusammenführen. Ziehen Sie den Bericht zurück und versuchen Sie es erneut.',
shortName: {
allTagLevelsRequired: 'Alle Tags erforderlich',
autoReportedRejectedExpense: 'Ausgabe abgelehnt',
billableExpense: 'Verrechenbar nicht mehr gültig',
cashExpenseWithNoReceipt: 'Beleg erforderlich',
categoryOutOfPolicy: 'Kategorie nicht mehr gültig',
companyCardRequired: 'Firmenkarte erforderlich',
conversionSurcharge: 'Umrechnungszuschlag angewendet',
customUnitOutOfPolicy: 'Satz für Arbeitsbereich ungültig',
customUnitRateOutOfDateRange: 'Kurs außerhalb der gültigen Daten',
duplicatedTransaction: 'Möglicher Duplikat',
fieldRequired: 'Berichtsfeld erforderlich',
futureDate: 'Zukünftiges Datum nicht zulässig',
hold: 'Ausgabe angehalten',
inactiveVendor: 'Lieferant nicht mehr gültig',
increasedDistance: 'Entfernung überschreitet Route',
invoiceMarkup: 'Rechnung mit Aufschlag versehen',
itemizedReceiptRequired: 'Aufgeschlüsselter Beleg erforderlich',
maxAge: 'Datum ist älter als das maximale Spesenalter',
missingAttendees: 'Teilnehmende erforderlich',
missingCategory: 'Fehlende Kategorie',
missingComment: 'Beschreibung erforderlich',
missingTag: 'Fehlendes Tag',
modifiedAmount: 'Betrag geändert',
modifiedDate: 'Änderungsdatum',
noRoute: 'Keine gültige Route',
nonExpensiworksExpense: 'Ausgabe außerhalb von Expensiworks',
overAutoApprovalLimit: 'Über dem Auto-Genehmigungslimit',
overCategoryLimit: 'Über Kategorienlimit',
overLimit: 'Über Limit',
overTripLimit: 'Über dem Reisekostenlimit',
perDayLimit: 'Über Tageslimit',
prohibitedExpense: 'Unzulässige Ausgabe',
receiptGeneratedWithAI: 'Mögliche KI-generierte Quittung',
receiptNotSmartScanned: 'Beleg manuell hinzugefügt',
receiptRequired: 'Beleg erforderlich',
rter: 'Kartenzuordnung ausstehend',
smartscanFailed: 'Belegscan fehlgeschlagen',
someTagLevelsRequired: 'Tag erforderlich',
tagOutOfPolicy: 'Tag nicht mehr gültig',
overLimitAttendee: 'Über Personenlimit',
customRules: 'Verstoß gegen benutzerdefinierte Regel',
taxAmountChanged: 'Steuerbetrag geändert',
taxOutOfPolicy: 'Steuersatz nicht mehr gültig',
taxRateChanged: 'Steuersatz geändert',
taxRequired: 'Fehlender Steuersatz',
},
},
reportViolations: {
[CONST.REPORT_VIOLATIONS.FIELD_REQUIRED]: (fieldName: string) => `${fieldName} ist erforderlich`,
Expand Down
Loading
Loading