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)
|
// there is no per-entry divisor, so /100 (all Etsy currencies are 2-decimal)
|
||||||
const ledgerAmount = (entry: any): number => (entry.amount || 0) / 100;
|
const ledgerAmount = (entry: any): number => (entry.amount || 0) / 100;
|
||||||
|
|
||||||
// Map a debit ledger entry to an expense category, or null to skip it.
|
// Classify a ledger entry:
|
||||||
// Conservative: only genuine business costs become expenses. Credits (sales),
|
// - a category string → import as an expense
|
||||||
// disbursements to bank, and refunds (revenue reversals) are never expenses.
|
// - 'skip' → deliberately not an expense (credit, disbursement, refund)
|
||||||
export const classifyLedgerEntry = (entry: any): string | null => {
|
// - null → an unrecognised debit, reported so rules can be refined
|
||||||
if ((entry.amount || 0) >= 0) return null; // credits: sale payments, misc credits
|
// 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();
|
const text = `${entry.description || ''} ${entry.ledger_type || ''} ${entry.reference_type || ''}`.toLowerCase();
|
||||||
|
|
||||||
// Transfers and revenue reversals are not expenses
|
// Transfers to bank and revenue reversals are intentionally not expenses
|
||||||
if (/disburse|deposit|withdraw|payout/.test(text)) return null;
|
if (/disburse|deposit|withdraw|payout/.test(text)) return 'skip';
|
||||||
if (/refund|reversal|return/.test(text)) return null;
|
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 (/listing/.test(text)) return 'Listing Fees';
|
||||||
if (/transaction/.test(text)) return 'Transaction Fees';
|
if (/transaction/.test(text)) return 'Transaction Fees';
|
||||||
if (/processing|payment fee/.test(text)) return 'Payment Processing 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 (/shipping|postage|label/.test(text)) return 'Shipping & Postage';
|
||||||
if (/vat|gst|\btax\b/.test(text)) return 'Taxes & GST';
|
if (/vat|gst|\btax\b/.test(text)) return 'Taxes & GST';
|
||||||
if (/subscription|etsy plus/.test(text)) return 'Subscriptions';
|
if (/subscription|etsy plus/.test(text)) return 'Subscriptions';
|
||||||
|
|
@ -400,8 +403,9 @@ export const syncLedgerEntries = async (
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
result.entriesSeen++;
|
result.entriesSeen++;
|
||||||
const category = classifyLedgerEntry(entry);
|
const category = classifyLedgerEntry(entry);
|
||||||
if (!category) {
|
if (category === 'skip') continue; // deliberately not an expense
|
||||||
// Collect unclassified debits for reporting (capped)
|
if (category === null) {
|
||||||
|
// Genuinely unrecognised debit — report (capped) so rules can be refined
|
||||||
if ((entry.amount || 0) < 0 && result.unknownDebits.length < 25) {
|
if ((entry.amount || 0) < 0 && result.unknownDebits.length < 25) {
|
||||||
const desc = String(entry.description || entry.ledger_type || 'unknown').trim();
|
const desc = String(entry.description || entry.ledger_type || 'unknown').trim();
|
||||||
if (!result.unknownDebits.includes(desc)) result.unknownDebits.push(desc);
|
if (!result.unknownDebits.includes(desc)) result.unknownDebits.push(desc);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue