fix: invoice integrity — atomic numbering, safe delete, single create path
- Invoice numbers now come from DB function next_invoice_number() (max+1 per year under advisory lock) with a unique index; the old row-count method reused numbers after deletes and raced concurrent creates, which broke public pay links - Remove dead standalone TeamCreateInvoice page; the TeamInvoices modal is the single create path (page had already drifted) - Invoice delete now asks for confirmation and only un-bills tasks/ submissions not billed on another invoice - Created invoice shows correct "sent" status in list without reload - Invoice/due dates computed at save time, not module load - Reopening a paid invoice clears stale stripe_fee - Stripe webhook markPaid is idempotent (no double receipts) - subcontractor_invoice_items.version_number column stores billing version explicitly; description parsing kept as legacy fallback - Drop unused buildInvoiceStatusByKey/deriveVersionStatus Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+2
-3
@@ -44,7 +44,6 @@ const CompaniesPage = lazy(() => import('./pages/Companies'));
|
||||
const CompanyDetail = lazy(() => import('./pages/CompanyDetail'));
|
||||
const TeamInvoices = lazy(() => import('./pages/team/TeamInvoices'));
|
||||
const TaskDetail = lazy(() => import('./pages/TaskDetail'));
|
||||
const TeamCreateInvoice = lazy(() => import('./pages/team/TeamCreateInvoice'));
|
||||
const TeamCreateSubcontractorPO = lazy(() => import('./pages/team/TeamCreateSubcontractorPO'));
|
||||
const TeamInvoiceDetail = lazy(() => import('./pages/team/TeamInvoiceDetail'));
|
||||
const TeamSubcontractorPODetail = lazy(() => import('./pages/team/TeamSubcontractorPODetail'));
|
||||
@@ -111,13 +110,13 @@ export default function App() {
|
||||
<Route path="/requests" element={<Navigate to="/tasks" replace />} />
|
||||
<Route path="/team-projects" element={<Navigate to="/tasks" replace />} />
|
||||
<Route path="/finances" element={<ProtectedRoute role="team"><TeamInvoices /></ProtectedRoute>} />
|
||||
<Route path="/finances/new" element={<ProtectedRoute role="team"><TeamCreateInvoice /></ProtectedRoute>} />
|
||||
<Route path="/finances/new" element={<Navigate to="/finances" replace />} />
|
||||
<Route path="/subcontractor-pos/new" element={<ProtectedRoute role="team"><TeamCreateSubcontractorPO /></ProtectedRoute>} />
|
||||
<Route path="/finances/:id" element={<ProtectedRoute role="team"><TeamInvoiceDetail /></ProtectedRoute>} />
|
||||
<Route path="/subcontractor-pos/:id" element={<ProtectedRoute role="team"><TeamSubcontractorPODetail /></ProtectedRoute>} />
|
||||
<Route path="/sub-invoices/:id" element={<ProtectedRoute role="team"><TeamSubInvoiceDetail /></ProtectedRoute>} />
|
||||
<Route path="/invoices" element={<Navigate to="/finances" replace />} />
|
||||
<Route path="/invoices/new" element={<Navigate to="/finances/new" replace />} />
|
||||
<Route path="/invoices/new" element={<Navigate to="/finances" replace />} />
|
||||
<Route path="/invoices/:id" element={<RedirectTeamInvoiceDetail />} />
|
||||
<Route path="/survey-maker" element={<ProtectedRoute role={['team', 'external']}><SurveyMaker /></ProtectedRoute>} />
|
||||
<Route path="/brand-book" element={<ProtectedRoute role={['team', 'external']}><BrandBook /></ProtectedRoute>} />
|
||||
|
||||
Reference in New Issue
Block a user