From 079ef5021199af181b1c71d9475c94b4659c9e6d Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 19 Apr 2026 17:14:22 +0000 Subject: [PATCH 1/7] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/settings.import.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/routes/settings.import.tsx b/src/routes/settings.import.tsx index 62cf14a..04627f4 100644 --- a/src/routes/settings.import.tsx +++ b/src/routes/settings.import.tsx @@ -425,8 +425,9 @@ const IMPORTERS: ImporterConfig[] = [ r.user_id = uid || ctx.userId; delete r._username; - // Invoice number is informational — we don't link it (would require invoice lookup). - delete r._invoice; + // Invoice number — keep as hint; async pre-insert step will look up or + // create an invoice with that number and link this row to it. + // (do NOT delete _invoice here) if (r.hours == null || r.hours <= 0) return null; if (!r.description) return null; From 14fdd4f9522a07e5027f37a2fa351a655f3423a4 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 19 Apr 2026 17:14:49 +0000 Subject: [PATCH 2/7] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/settings.import.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/routes/settings.import.tsx b/src/routes/settings.import.tsx index 04627f4..f44ced7 100644 --- a/src/routes/settings.import.tsx +++ b/src/routes/settings.import.tsx @@ -450,6 +450,7 @@ const IMPORTERS: ImporterConfig[] = [ amount: "amount", cost: "amount", billable: "billable", date: "expense_date", expensedate: "expense_date", + invoice: "_invoice", invoicenumber: "_invoice", }, numeric: ["amount"], boolCols: ["billable"], From 8ca92e38d9665b59f15d7e56b00edbb37c1eb3cb Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 19 Apr 2026 17:14:58 +0000 Subject: [PATCH 3/7] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/settings.import.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/settings.import.tsx b/src/routes/settings.import.tsx index f44ced7..6a3030a 100644 --- a/src/routes/settings.import.tsx +++ b/src/routes/settings.import.tsx @@ -342,7 +342,7 @@ const IMPORTERS: ImporterConfig[] = [ { key: "time_entries", label: "Time entries → Time", - description: "Expected columns: Date, Case, Time, Rate, Flat rate, Total, Description, User, Case Name, Invoice, Nonbillable. Missing clients/cases are auto-created as archived; rows are marked already-invoiced.", + description: "Expected columns: Date, Case, Time, Rate, Flat rate, Total, Description, User, Case Name, Invoice, Nonbillable. Missing clients/cases are auto-created as archived. Rows with an Invoice number are linked to (or create) an invoice with that number; rows without an Invoice number stay unbilled.", table: "time_entries", conflict: "external_id", required: ["case_id", "description", "hours"], From 6ddf94d5d2008c2bbe5b419ec48f4eb04d548de0 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 19 Apr 2026 17:15:04 +0000 Subject: [PATCH 4/7] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/settings.import.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/settings.import.tsx b/src/routes/settings.import.tsx index 6a3030a..11dad04 100644 --- a/src/routes/settings.import.tsx +++ b/src/routes/settings.import.tsx @@ -438,7 +438,7 @@ const IMPORTERS: ImporterConfig[] = [ { key: "expenses", label: "Expenses → Expenses", - description: "Case-related expenses. Missing clients/cases are auto-created as archived; rows are marked already-invoiced.", + description: "Case-related expenses. Missing clients/cases are auto-created as archived. Rows with an Invoice number are linked to (or create) an invoice with that number; rows without an Invoice number stay unbilled.", table: "expenses", conflict: "external_id", required: ["case_id", "description", "amount"], From 172400600e5ce00f2950128b1fff2a49b8cf5c63 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 19 Apr 2026 17:15:27 +0000 Subject: [PATCH 5/7] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/settings.import.tsx | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/routes/settings.import.tsx b/src/routes/settings.import.tsx index 11dad04..2a40b3b 100644 --- a/src/routes/settings.import.tsx +++ b/src/routes/settings.import.tsx @@ -884,31 +884,49 @@ function ImportPage() { if (extId) ctx.caseByExt.set(String(extId), data.id); return data.id; }; - const ensurePreInvoicedInvoice = async (clientId: string, caseId: string): Promise => { - const cached = placeholderInvoiceByClient.get(clientId); + // Look up an existing invoice by number for this client, or create a + // new "sent" invoice with that exact number to bucket imported items. + const invoiceByClientAndNumber = new Map(); // `${clientId}::${number}` -> invoiceId + const ensureNamedInvoice = async ( + clientId: string, + caseId: string, + invoiceNumber: string, + ): Promise => { + const cacheKey = `${clientId}::${invoiceNumber}`; + const cached = invoiceByClientAndNumber.get(cacheKey); if (cached) return cached; - const invNumber = `IMPORT-PREBILLED-${Date.now()}-${Math.floor(Math.random() * 10000)}`; + // First try existing invoice with this number for this client + const { data: existing } = await supabase + .from("invoices") + .select("id") + .eq("client_id", clientId) + .eq("invoice_number", invoiceNumber) + .maybeSingle(); + if (existing?.id) { + invoiceByClientAndNumber.set(cacheKey, existing.id); + return existing.id; + } const { data, error } = await supabase .from("invoices") .insert({ client_id: clientId, case_id: caseId, - invoice_number: invNumber, - status: "void", + invoice_number: invoiceNumber, + status: "sent", issue_date: new Date().toISOString().slice(0, 10), subtotal: 0, tax: 0, total: 0, - notes: "Placeholder invoice for items imported as already invoiced.", + notes: "Auto-created from import to hold items linked to this invoice number.", created_by: user.id, } as any) .select("id") .single(); if (error || !data) { - errors.push(`Auto-create placeholder invoice: ${error?.message ?? "unknown"}`); + errors.push(`Auto-create invoice "${invoiceNumber}": ${error?.message ?? "unknown"}`); return null; } - placeholderInvoiceByClient.set(clientId, data.id); + invoiceByClientAndNumber.set(cacheKey, data.id); return data.id; }; From 6463081ba758b426de32e1fdba70d30a6f46fbb1 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 19 Apr 2026 17:15:35 +0000 Subject: [PATCH 6/7] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/settings.import.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/settings.import.tsx b/src/routes/settings.import.tsx index 2a40b3b..5873da6 100644 --- a/src/routes/settings.import.tsx +++ b/src/routes/settings.import.tsx @@ -825,7 +825,7 @@ function ImportPage() { // archived client + archived case from the row hints, then attach a // placeholder voided invoice so the row imports as "already invoiced". if (cfg.table === "time_entries" || cfg.table === "expenses") { - const placeholderInvoiceByClient = new Map(); + // Cache of (clientId,invoiceNumber) -> invoiceId is defined below const ensureArchivedClient = async (name: string, extId?: string | null): Promise => { const key = name.toLowerCase().trim(); const existing = (extId && ctx.clientByExt.get(String(extId))) || ctx.clientByName.get(key); From 333bcf633a69073ff907254fc2b0e333fa6e55bd Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 19 Apr 2026 17:15:49 +0000 Subject: [PATCH 7/7] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/settings.import.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/routes/settings.import.tsx b/src/routes/settings.import.tsx index 5873da6..bf92579 100644 --- a/src/routes/settings.import.tsx +++ b/src/routes/settings.import.tsx @@ -969,9 +969,13 @@ function ImportPage() { } } - // Mark as already invoiced via placeholder voided invoice (per client) - if (clientId && !row.invoice_id) { - const invId = await ensurePreInvoicedInvoice(clientId, caseId); + // Link to a real invoice ONLY when the source row provided an + // invoice number. Otherwise leave invoice_id null so the user can + // bill these items normally later. + const invNumber = row._invoice ? String(row._invoice).trim() : ""; + delete row._invoice; + if (invNumber && clientId && !row.invoice_id) { + const invId = await ensureNamedInvoice(clientId, caseId, invNumber); if (invId) row.invoice_id = invId; }