Files
NetForge/.planning/phases/04-inventory-expenses-and-financial-reports/04-04-PLAN.md
kevin-asprec b5f2f3946b docs(04): create phase plan — Inventory, Expenses, and Financial Reports
Phase 04: 5 plans in 2 waves
- Wave 1: 04-01 (inventory event-ledger), 04-03 (expense tracking), 04-05 (financial reports) — parallel
- Wave 2: 04-02 (asset management), 04-04 (expense reports + audit trail) — sequential
- Ready for execution

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 10:00:55 +08:00

163 lines
8.0 KiB
Markdown

---
phase: 04-inventory-expenses-and-financial-reports
plan: 04
type: execute
wave: 2
depends_on: ["04-03"]
files_modified:
- src/lib/services/expense-report-service.ts
- src/lib/services/audit-trail-service.ts
- src/app/api/reports/expenses/route.ts
- src/app/api/reports/expenses/by-vendor/route.ts
- src/app/api/accounting/journal-entries/[id]/audit/route.ts
- src/lib/__tests__/expense-report-service.test.ts
autonomous: true
must_haves:
truths:
- "Admin can generate expense reports filtered by category, vendor, and date range"
- "Expense report shows totals per category and per vendor for the period"
- "Every journal entry has a full audit trail: who created it, when, what source transaction"
- "Audit trail shows referenceType and referenceId linking JE back to source (Invoice, Payment, Expense, StockMovement, Collection, Remittance)"
artifacts:
- path: "src/lib/services/expense-report-service.ts"
provides: "getExpensesByCategory, getExpensesByVendor, getExpenseSummary"
exports: ["ExpenseReportService"]
- path: "src/lib/services/audit-trail-service.ts"
provides: "getJournalEntryAudit, getAuditTrailForEntity"
exports: ["AuditTrailService"]
- path: "src/lib/__tests__/expense-report-service.test.ts"
provides: "Tests for expense reports and audit trail"
min_lines: 60
key_links:
- from: "src/lib/services/expense-report-service.ts"
to: "prisma.expense"
via: "Aggregation queries on expense records"
pattern: "expense\\.(groupBy|findMany|aggregate)"
- from: "src/lib/services/audit-trail-service.ts"
to: "prisma.journalEntry"
via: "Query JE with createdBy, approvedBy, referenceType"
pattern: "journalEntry\\.find"
---
<objective>
Build expense reporting (by category, vendor, period) and the journal entry audit trail that links every accounting entry back to its source transaction.
Purpose: Gives ISP owners spending visibility (where money goes) and auditors the ability to trace any ledger entry to its origin. ACCT-08 (audit trail) applies to ALL journal entries, not just expense ones.
Output: ExpenseReportService, AuditTrailService, report API routes, tests.
</objective>
<execution_context>
@C:\Users\KevinAsprec\.claude/get-shit-done/workflows/execute-plan.md
@C:\Users\KevinAsprec\.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/04-inventory-expenses-and-financial-reports/04-CONTEXT.md
@.planning/phases/04-inventory-expenses-and-financial-reports/04-03-SUMMARY.md
@prisma/schema.prisma
@src/lib/accounting/journal-entry-service.ts
@src/lib/services/expense-service.ts
</context>
<tasks>
<task type="auto">
<name>Task 1: ExpenseReportService and AuditTrailService</name>
<files>
src/lib/services/expense-report-service.ts
src/lib/services/audit-trail-service.ts
src/app/api/reports/expenses/route.ts
src/app/api/reports/expenses/by-vendor/route.ts
src/app/api/accounting/journal-entries/[id]/audit/route.ts
</files>
<action>
**ExpenseReportService** (`src/lib/services/expense-report-service.ts`):
Static class:
- `getExpensesByCategory(tenantPrisma, { startDate, endDate })`:
- Query POSTED expenses grouped by categoryId within date range
- Return: array of { categoryId, categoryName, totalAmount (Decimal), expenseCount (number) }
- Order by totalAmount DESC
- `getExpensesByVendor(tenantPrisma, { startDate, endDate, vendorId? })`:
- Query POSTED expenses grouped by vendorId within date range
- Optional vendorId filter for single-vendor detail
- Return: array of { vendorId, vendorName, totalAmount, expenseCount }
- Include a "No Vendor" bucket for expenses without vendorId
- Order by totalAmount DESC
- `getExpenseSummary(tenantPrisma, { startDate, endDate })`:
- Return combined report: { totalExpenses (Decimal), byCategory: [...], byVendor: [...], expenseCount }
- Calls the two methods above and computes grand total
**AuditTrailService** (`src/lib/services/audit-trail-service.ts`):
Static class:
- `getJournalEntryAudit(tenantPrisma, entryId)`:
- Fetch JE with include: createdBy (select id, email, firstName, lastName), approvedBy, lines with account details
- Return: { entryNumber, date, description, source, status, referenceType, referenceId, createdBy: {name, email}, createdAt, approvedBy?: {name, email}, approvedAt?, lines: [{account code+name, debit, credit}] }
- This provides ACCT-08: who created it, when, what source transaction
- `getAuditTrailForEntity(tenantPrisma, { referenceType, referenceId })`:
- Fetch all JEs where referenceType and referenceId match
- Returns array of JE audit records — enables "show all journal entries for this invoice" or "for this payment"
- Includes reversing entries (where reversesEntryId links to matching JEs)
**API Routes:**
- `GET /api/reports/expenses` — expense summary by category. Query: startDate, endDate. ADMIN only.
- `GET /api/reports/expenses/by-vendor` — expense summary by vendor. Query: startDate, endDate, vendorId?. ADMIN only.
- `GET /api/accounting/journal-entries/[id]/audit` — full audit trail for a JE. ADMIN, OFFICE_STAFF.
All routes use withPermission() HOF.
</action>
<verify>API route files exist and export correct HTTP methods</verify>
<done>ExpenseReportService provides category and vendor expense reports; AuditTrailService links every JE to its creator and source transaction</done>
</task>
<task type="auto">
<name>Task 2: Tests for expense reports and audit trail</name>
<files>src/lib/__tests__/expense-report-service.test.ts</files>
<action>
**Setup:** createTenant, create admin user, create 2 vendors, create expenses across 3 categories (use 2 different expense categories from the 9 seeded defaults + 1 custom category). Create expenses linked to different vendors. Ensure expenses are POSTED (so JEs exist).
**Test cases:**
1. Expense report by category — returns correct totals per category for date range
2. Expense report by category excludes DRAFT/VOIDED expenses (only POSTED counted)
3. Expense report by vendor — returns correct totals per vendor
4. Expense report by vendor includes "No Vendor" bucket for unlinked expenses
5. Expense summary — combines category and vendor views with grand total
6. Date range filtering works (expenses outside range excluded)
7. JE audit trail — fetch audit for a posted expense's JE: shows createdBy user, source=SYSTEM, referenceType="Expense", referenceId=expense.id
8. Audit trail for entity — fetch all JEs for an expense (original + void reversal if voided)
9. JE audit trail on a payment JE (from Phase 2) — verify referenceType="Payment" is correctly returned (proves ACCT-08 works across all JE sources, not just expenses)
Cleanup order: expenses -> vendors -> expenseCategories (custom only) -> journalEntryLines -> null reversesEntryId -> journalEntries -> invoiceLines -> invoices -> subscribers -> servicePlans -> accountingPeriods -> accounts -> ticketCategories -> expenseCategories (system) -> users -> tenant
</action>
<verify>npx jest expense-report-service --verbose passes all tests</verify>
<done>Expense reports correctly aggregate by category/vendor with date filtering. Audit trail shows creator, timestamp, and source reference for any JE. All tests pass.</done>
</task>
</tasks>
<verification>
- `npx jest expense-report-service --verbose` — all tests pass
- Expense report by category shows correct totals
- Expense report by vendor shows correct totals
- JE audit trail includes who/when/source for all JE types (not just expenses)
</verification>
<success_criteria>
- Admin can generate expense reports by category and vendor for any date range
- Reports show totals and counts, excluding non-POSTED expenses
- Every journal entry (Invoice, Payment, Expense, Collection, Remittance, StockMovement) has traceable audit trail
- All tests pass
</success_criteria>
<output>
After completion, create `.planning/phases/04-inventory-expenses-and-financial-reports/04-04-SUMMARY.md`
</output>