Classify prolist as ads; stop reporting intentionally-skipped ledger types
- 'prolist' (Etsy Promoted Listings / onsite ads) now maps to Marketing & Advertising instead of being skipped - classifyLedgerEntry distinguishes 'skip' (credits, disbursements, refunds — deliberately not expenses) from null (unrecognised debit). Only null entries are reported, so disbursements/refunds no longer appear in the 'unknown charge types' list. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
7a626257cd
commit
d5742940ec
1 changed files with 15 additions and 11 deletions
|
|
@ -337,22 +337,25 @@ export interface LedgerSyncResult {
|
|||
// there is no per-entry divisor, so /100 (all Etsy currencies are 2-decimal)
|
||||
const ledgerAmount = (entry: any): number => (entry.amount || 0) / 100;
|
||||
|
||||
// Map a debit ledger entry to an expense category, or null to skip it.
|
||||
// Conservative: only genuine business costs become expenses. Credits (sales),
|
||||
// disbursements to bank, and refunds (revenue reversals) are never expenses.
|
||||
export const classifyLedgerEntry = (entry: any): string | null => {
|
||||
if ((entry.amount || 0) >= 0) return null; // credits: sale payments, misc credits
|
||||
// Classify a ledger entry:
|
||||
// - a category string → import as an expense
|
||||
// - 'skip' → deliberately not an expense (credit, disbursement, refund)
|
||||
// - null → an unrecognised debit, reported so rules can be refined
|
||||
// Conservative: only genuine business costs become expenses.
|
||||
export const classifyLedgerEntry = (entry: any): string | 'skip' | null => {
|
||||
if ((entry.amount || 0) >= 0) return 'skip'; // credits: sale payments, misc credits
|
||||
|
||||
const text = `${entry.description || ''} ${entry.ledger_type || ''} ${entry.reference_type || ''}`.toLowerCase();
|
||||
|
||||
// Transfers and revenue reversals are not expenses
|
||||
if (/disburse|deposit|withdraw|payout/.test(text)) return null;
|
||||
if (/refund|reversal|return/.test(text)) return null;
|
||||
// Transfers to bank and revenue reversals are intentionally not expenses
|
||||
if (/disburse|deposit|withdraw|payout/.test(text)) return 'skip';
|
||||
if (/refund|reversal|return/.test(text)) return 'skip';
|
||||
|
||||
// Etsy's onsite ads are logged as "prolist" (Promoted Listings)
|
||||
if (/prolist|promoted|ads|advertis|marketing|offsite/.test(text)) return 'Marketing & Advertising';
|
||||
if (/listing/.test(text)) return 'Listing Fees';
|
||||
if (/transaction/.test(text)) return 'Transaction Fees';
|
||||
if (/processing|payment fee/.test(text)) return 'Payment Processing Fees';
|
||||
if (/ads|advertis|marketing|offsite/.test(text)) return 'Marketing & Advertising';
|
||||
if (/shipping|postage|label/.test(text)) return 'Shipping & Postage';
|
||||
if (/vat|gst|\btax\b/.test(text)) return 'Taxes & GST';
|
||||
if (/subscription|etsy plus/.test(text)) return 'Subscriptions';
|
||||
|
|
@ -400,8 +403,9 @@ export const syncLedgerEntries = async (
|
|||
for (const entry of entries) {
|
||||
result.entriesSeen++;
|
||||
const category = classifyLedgerEntry(entry);
|
||||
if (!category) {
|
||||
// Collect unclassified debits for reporting (capped)
|
||||
if (category === 'skip') continue; // deliberately not an expense
|
||||
if (category === null) {
|
||||
// Genuinely unrecognised debit — report (capped) so rules can be refined
|
||||
if ((entry.amount || 0) < 0 && result.unknownDebits.length < 25) {
|
||||
const desc = String(entry.description || entry.ledger_type || 'unknown').trim();
|
||||
if (!result.unknownDebits.includes(desc)) result.unknownDebits.push(desc);
|
||||
|
|
|
|||
Loading…
Reference in a new issue