// Filter model + predicate for the settlement grids (Amazon / eBay), modelled on // PayJoe's transaction filter: free-text search, booking-date range with quick // presets, Klassifikation checkboxes, match status and "matchrelevant" (only rows // that need a Beleg — sales and refunds). Applied through AG Grid's external // filter API so it composes with the column filters and row grouping. export interface SettlementGridFilter { search: string dateFrom: string // yyyy-mm-dd or '' dateTo: string // yyyy-mm-dd or '' buckets: string[] // empty = all classifications match: 'all' | 'matched' | 'unmatched' matchRelevant: boolean // Lexoffice sync state (Amazon page). All three only ever look at rows that // reference an order — account fees / payout rows are neither "with" nor // "without" an invoice, so they drop out as soon as one of these is set. invoice: 'all' | 'with' | 'without' // internal invoice exists? lexoffice: 'all' | 'uploaded' | 'notUploaded' // invoice uploaded to lexoffice? (invoice rows only) csv: 'all' | 'exported' | 'notExported' // order marked as exported via settlement CSV? (matched rows only) } export const defaultSettlementGridFilter = (): SettlementGridFilter => ({ search: '', dateFrom: '', dateTo: '', buckets: [], match: 'all', matchRelevant: false, invoice: 'all', lexoffice: 'all', csv: 'all' }) export const settlementGridFilterActive = (f: SettlementGridFilter | null | undefined): boolean => { if (!f) return false return !!( f.dateFrom || f.dateTo || (f.buckets && f.buckets.length) || (f.match && f.match !== 'all') || f.matchRelevant || (f.invoice && f.invoice !== 'all') || (f.lexoffice && f.lexoffice !== 'all') || (f.csv && f.csv !== 'all') ) } const MATCH_RELEVANT_BUCKETS = new Set(['sale', 'refund']) const isoDay = (value?: string) => { if (!value) return '' const d = new Date(value) if (isNaN(d.getTime())) return '' // Local calendar day — the date inputs are local too. const y = d.getFullYear() const m = String(d.getMonth() + 1).padStart(2, '0') const dd = String(d.getDate()).padStart(2, '0') return `${y}-${m}-${dd}` } // `classify` maps a row to its bucket key ('sale' | 'refund' | 'orderFee' | …), // i.e. classifyAmazonRow / classifyEbayRow. export const settlementRowPassesFilter = ( row: any, f: SettlementGridFilter, classify: (row: any) => string ): boolean => { if (!row) return true if (f.dateFrom || f.dateTo) { const day = isoDay(row.postedDate) if (f.dateFrom && (!day || day < f.dateFrom)) return false if (f.dateTo && (!day || day > f.dateTo)) return false } const bucket = classify(row) if (f.buckets && f.buckets.length && !f.buckets.includes(bucket)) return false if (f.matchRelevant && !MATCH_RELEVANT_BUCKETS.has(bucket)) return false if (f.match && f.match !== 'all') { // Only rows that reference an order can be matched at all; account-level // fees and the payout row are neither "zugeordnet" nor "nicht zugeordnet". if (!row.orderId) return false if (f.match === 'matched' && !row.matched) return false if (f.match === 'unmatched' && row.matched) return false } if (f.invoice && f.invoice !== 'all') { if (!row.orderId) return false if (f.invoice === 'with' && !row.invoiceExists) return false if (f.invoice === 'without' && row.invoiceExists) return false } if (f.lexoffice && f.lexoffice !== 'all') { // Only rows WITH an invoice can be uploaded / not yet uploaded. if (!row.invoiceExists) return false if (f.lexoffice === 'uploaded' && !row.invoiceLexofficeUploaded) return false if (f.lexoffice === 'notUploaded' && row.invoiceLexofficeUploaded) return false } if (f.csv && f.csv !== 'all') { // The CSV marker lives on the internal order, so only matched rows qualify. if (!row.matched) return false if (f.csv === 'exported' && !row.csvExported) return false if (f.csv === 'notExported' && row.csvExported) return false } return true } export type SettlementDatePreset = 'thisWeek' | 'lastWeek' | 'last3Weeks' | 'thisMonth' | 'lastMonth' | 'last3Months' export const SETTLEMENT_DATE_PRESETS: { key: SettlementDatePreset; label: string }[] = [ { key: 'thisWeek', label: 'diese Woche' }, { key: 'lastWeek', label: 'letzte Woche' }, { key: 'last3Weeks', label: 'letzte 3 Wochen' }, { key: 'thisMonth', label: 'dieser Monat' }, { key: 'lastMonth', label: 'letzter Monat' }, { key: 'last3Months', label: 'letzte 3 Monate' } ] const fmt = (d: Date) => isoDay(d.toISOString()) const startOfWeek = (d: Date) => { const r = new Date(d.getFullYear(), d.getMonth(), d.getDate()) const dow = (r.getDay() + 6) % 7 // Monday = 0 r.setDate(r.getDate() - dow) return r } const addDays = (d: Date, n: number) => { const r = new Date(d); r.setDate(r.getDate() + n); return r } export const settlementDatePreset = (key: SettlementDatePreset, now = new Date()): { dateFrom: string; dateTo: string } => { const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()) switch (key) { case 'thisWeek': { const s = startOfWeek(today) return { dateFrom: fmt(s), dateTo: fmt(addDays(s, 6)) } } case 'lastWeek': { const s = addDays(startOfWeek(today), -7) return { dateFrom: fmt(s), dateTo: fmt(addDays(s, 6)) } } case 'last3Weeks': { const s = addDays(startOfWeek(today), -14) return { dateFrom: fmt(s), dateTo: fmt(today) } } case 'thisMonth': { const s = new Date(today.getFullYear(), today.getMonth(), 1) const e = new Date(today.getFullYear(), today.getMonth() + 1, 0) return { dateFrom: fmt(s), dateTo: fmt(e) } } case 'lastMonth': { const s = new Date(today.getFullYear(), today.getMonth() - 1, 1) const e = new Date(today.getFullYear(), today.getMonth(), 0) return { dateFrom: fmt(s), dateTo: fmt(e) } } case 'last3Months': { const s = new Date(today.getFullYear(), today.getMonth() - 2, 1) return { dateFrom: fmt(s), dateTo: fmt(today) } } } }