Skip to content

feat: add Rent Invoices + Payments modules (issues/012-014) - #139

Open
HadiSaaibi wants to merge 5 commits into
hadisaiibi-mouhannadfrom
feat/rent-invoices
Open

feat: add Rent Invoices + Payments modules (issues/012-014)#139
HadiSaaibi wants to merge 5 commits into
hadisaiibi-mouhannadfrom
feat/rent-invoices

Conversation

@HadiSaaibi

@HadiSaaibi HadiSaaibi commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Invoices read path: Invoice/InvoiceLineItem schema, computeInvoiceSummary deep module (derives total/paid/status, never stored), new invoices permission area, filtered list page (issues/012)
  • Invoices write path: create/update/remove with wholesale line-item replacement, LeasesService.remove guarded against referenced invoices, New/Edit Invoice dialog (issues/013)
  • Invoice Payments: InvoicePayment schema + service (create/findAll/findOne/remove, no update), building-scoped via parent Invoice, InvoicesService.remove guarded against referenced payments, Invoice detail page with line items + payment history + Record Payment action (issues/014)

Test plan

  • npm run test
  • npm run typecheck
  • Manually verify: create an invoice with multiple line items, confirm computed total/status, record partial + full payments, confirm status transitions to paid, confirm delete guards on Lease/Invoice with dependents
  • Confirm supervisor role only sees invoices/payments for assigned buildings; maintenance/tenant blocked from the page

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added invoices management with list/detail views, create/edit/delete flows, line items, due dates, notes, and computed status.
    • Added invoice payment recording, viewing, and deletion, with payment method support and automatic invoice summary updates.
    • Added new invoices dashboard navigation and role-based access (including web/API routing and permissions).
  • Bug Fixes
    • Prevented deleting leases when invoices reference them.
    • Prevented deleting invoices when related payments exist.
  • Tests
    • Added unit/integration coverage for invoice summary calculations, permissions, validation, payment workflows, and deletion safeguards.

HadiSaaibi and others added 3 commits July 14, 2026 21:35
Invoice/InvoiceLineItem schema, computeInvoiceSummary deep module,
read-only InvoicesService/Controller, invoices permission area, and
the filtered list page. Write path lands in issues/013.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
InvoicesService.create/update/remove with wholesale line-item
replacement on update, LeasesService.remove guarded against
referenced invoices, and the New/Edit Invoice dialog (cascading
lease picker, repeatable line-item rows) plus delete confirmation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
InvoicePayment schema + InvoicePaymentsService (create/findAll/
findOne/remove), building-scoped via the parent Invoice, returning
recomputed totalAmount/paidAmount/status on create/remove.
InvoicesService.remove now also rejects when a payment references
the invoice. Adds the Invoice detail page showing line items and
payment history together, with Record Payment and delete actions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@HadiSaaibi, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 50 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 583c432c-a8d7-41bf-b205-6ec37fdad6f7

📥 Commits

Reviewing files that changed from the base of the PR and between 51518ae and 06bfa01.

📒 Files selected for processing (4)
  • apps/api/src/common/invoice-summary/compute-invoice-summary.ts
  • apps/api/src/modules/invoice-payments/invoice-payments.service.ts
  • apps/web/src/components/dashboard/invoice-detail-page.tsx
  • apps/web/src/components/dashboard/invoices-page.tsx
📝 Walkthrough

Walkthrough

Adds invoice and invoice-payment database models, contracts, secured NestJS APIs, computed status summaries, RTK Query integration, authorization rules, and dashboard pages for invoice and payment management.

Changes

Invoice Management

Layer / File(s) Summary
Invoice data contracts and persistence
packages/contracts/src/index.ts, packages/database/prisma/*, apps/api/src/common/invoice-summary/*
Defines invoice and payment types, Prisma models, migrations, and deterministic invoice total/status computation with tests.
Invoice service and CRUD API
apps/api/src/modules/invoices/*, apps/api/src/modules/leases/*, apps/api/src/app.module.ts
Adds validated invoice CRUD operations, organization/building scoping, timeline events, payment-reference deletion guards, module wiring, and service tests.
Invoice payment API
apps/api/src/modules/invoice-payments/*
Adds payment validation, retrieval, creation, deletion, summary recomputation, authorization, timeline events, and tests.
Web API integration and access wiring
apps/web/src/app/api/invoices/*, apps/web/src/app/api/invoice-payments/*, apps/web/src/store/api/*, apps/web/src/auth/*, apps/web/src/components/layout/*, apps/web/src/i18n/dictionaries/*
Adds proxy routes, RTK Query endpoints and cache tags, invoice permissions, sidebar navigation, and translations.
Invoice dashboard workflows
apps/web/src/app/[lang]/dashboard/invoices/*, apps/web/src/components/dashboard/invoices-page.tsx, apps/web/src/components/dashboard/invoice-detail-page.tsx
Adds invoice listing and detail pages with filtering, validated create/edit forms, payment recording, deletion dialogs, and role-gated actions.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant InvoicesPage
  participant invoicesApi
  participant NextAPI
  participant InvoicesController
  participant InvoicesService
  participant PrismaService
  User->>InvoicesPage: create or update invoice
  InvoicesPage->>invoicesApi: submit invoice mutation
  invoicesApi->>NextAPI: POST or PATCH /api/invoices
  NextAPI->>InvoicesController: forward request
  InvoicesController->>InvoicesService: create or update scoped invoice
  InvoicesService->>PrismaService: persist invoice and line items
  PrismaService-->>InvoicesService: invoice record
  InvoicesService-->>InvoicesPage: formatted invoice response
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is detailed, but it misses the template's Link to issue or ticket and Screenshots sections and doesn't use the required headings. Add the required Description, Link to issue or ticket, and Screenshots sections, and align the headings with the repository template.
Docstring Coverage ⚠️ Warning Docstring coverage is 15.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding invoice and payment modules.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/rent-invoices

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

🟠 Major comments (18)
apps/api/src/modules/invoice-payments/invoice-payments.service.ts-32-38 (1)

32-38: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use the mandated service infrastructure.

Inject DatabaseService instead of PrismaService, and add the required NestJS Logger field.

As per coding guidelines, API services must access Prisma through DatabaseService and declare a NestJS Logger.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/modules/invoice-payments/invoice-payments.service.ts` around
lines 32 - 38, Update InvoicePaymentsService to inject DatabaseService in place
of PrismaService, and replace the corresponding field type and usage references
accordingly. Add a NestJS Logger field to the service, following the existing
service logging convention.

Source: Coding guidelines

apps/api/src/modules/invoice-payments/invoice-payments.service.ts-165-185 (1)

165-185: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Avoid partially completed payment operations.

  • apps/api/src/modules/invoice-payments/invoice-payments.service.ts#L165-L185: atomically persist the payment and timeline event, or make creation idempotent.
  • apps/api/src/modules/invoice-payments/invoice-payments.service.ts#L203-L214: atomically delete the payment and record its timeline event.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/modules/invoice-payments/invoice-payments.service.ts` around
lines 165 - 185, The invoice payment create and delete flows can leave database
state and timeline events inconsistent. Update the relevant create flow around
prisma.invoicePayment.create and the delete flow at the specified sibling site
to use a shared Prisma transaction that atomically persists/deletes the payment
and records the corresponding timeline event; preserve the existing event data
and return behavior.
apps/api/src/modules/invoice-payments/invoice-payments.service.ts-63-73 (1)

63-73: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Apply organization scope to every invoice-payment query.

  • apps/api/src/modules/invoice-payments/invoice-payments.service.ts#L63-L73: accept orgId and query the invoice with { id: invoiceId, orgId }.
  • apps/api/src/modules/invoice-payments/invoice-payments.service.ts#L203-L203: perform the deletion with both id and orgId.
  • apps/api/src/modules/invoice-payments/invoice-payments.service.spec.ts#L250-L252: assert the scoped deletion contract.

As per coding guidelines, every Prisma query on tenant-scoped models must filter by organization ID.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/modules/invoice-payments/invoice-payments.service.ts` around
lines 63 - 73, Scope all invoice Prisma operations by organization ID: update
summarizeInvoice to accept orgId and include it with invoiceId in the invoice
lookup, update the deletion query to filter by both id and orgId, and extend the
corresponding service spec assertion to verify the scoped deletion contract.
Apply changes at
apps/api/src/modules/invoice-payments/invoice-payments.service.ts lines 63-73
and 203, and
apps/api/src/modules/invoice-payments/invoice-payments.service.spec.ts lines
250-252.

Source: Coding guidelines

apps/api/src/modules/invoice-payments/dto/create-invoice-payment.dto.ts-1-36 (1)

1-36: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Replace the inline DTO with the shared Zod contract.

Define this request in packages/contracts, export its inferred type, and validate it in the controller using ZodValidationPipe. Importing InvoicePaymentMethod from @repo/db also couples the public API contract to persistence.

As per coding guidelines, wire schemas must come from @repo/contracts and API request inputs must use ZodValidationPipe.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/modules/invoice-payments/dto/create-invoice-payment.dto.ts`
around lines 1 - 36, Replace CreateInvoicePaymentDto’s class-validator
decorators with a shared Zod request schema defined and exported from
packages/contracts, including the inferred request type and an API-safe
payment-method definition rather than importing InvoicePaymentMethod from
`@repo/db`. Update the corresponding controller endpoint to accept this contract
through ZodValidationPipe and remove the inline DTO usage.

Source: Coding guidelines

apps/web/src/components/dashboard/invoice-detail-page.tsx-154-155 (1)

154-155: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Handle payment-list failures instead of showing an empty history.

When the query fails, payments is undefined, so the page incorrectly renders “No payments recorded yet.” Capture isError and render an explicit retry/error state.

Also applies to: 345-375

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/dashboard/invoice-detail-page.tsx` around lines 154 -
155, Update the useListInvoicePaymentsQuery call to capture its isError state,
then adjust the payment history rendering to show an explicit payment-loading
error and retry action when the query fails instead of treating undefined
payments as an empty history. Preserve the existing empty-state rendering for
successful queries with no payments.
apps/web/src/components/dashboard/invoices-page.tsx-210-251 (1)

210-251: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Add accessible labels to every line-item control.

The category, description, and amount controls have no associated labels. Add unique Label elements or descriptive aria-label attributes so screen-reader users can complete the required form.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/dashboard/invoices-page.tsx` around lines 210 - 251,
Add accessible labeling to the category Select, description Input, and amount
Input within the line-item rendering block. Use unique per-row identifiers based
on idPrefix and index, associate visible Label elements through matching
htmlFor/id attributes or provide descriptive aria-labels, and preserve the
existing form registration and validation behavior.
apps/web/src/components/dashboard/invoice-detail-page.tsx-70-75 (1)

70-75: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Replace raw status palette colors with design tokens.

  • apps/web/src/components/dashboard/invoice-detail-page.tsx#L70-L75: use semantic tokens from globals.css.
  • apps/web/src/components/dashboard/invoices-page.tsx#L84-L89: share the same token-based status styles.

As per coding guidelines, Tailwind components must use project design tokens instead of arbitrary color values.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/dashboard/invoice-detail-page.tsx` around lines 70 -
75, Replace the raw Tailwind color classes in STATUS_STYLES within
apps/web/src/components/dashboard/invoice-detail-page.tsx at lines 70-75 with
the semantic design tokens defined in globals.css. Apply the same token-based
status styling to the corresponding STATUS_STYLES mapping in
apps/web/src/components/dashboard/invoices-page.tsx at lines 84-89, keeping
status-specific variants consistent between both components.

Source: Coding guidelines

apps/web/src/components/dashboard/invoice-detail-page.tsx-54-59 (1)

54-59: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Import wire contract types directly from @repo/contracts.

Both components consume API response enums and shapes through @/types/api, allowing the web definitions to drift from the shared contract.

  • apps/web/src/components/dashboard/invoice-detail-page.tsx#L54-L59: import invoice and payment response types from @repo/contracts.
  • apps/web/src/components/dashboard/invoices-page.tsx#L67-L71: import invoice response types and enums from @repo/contracts.

As per coding guidelines, “Both the API and web app must import from @repo/contracts.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/dashboard/invoice-detail-page.tsx` around lines 54 -
59, Replace the invoice and payment API type imports in invoice-detail-page.tsx
with their corresponding exports from `@repo/contracts`, preserving the existing
type usage. Apply the same migration in invoices-page.tsx for its invoice
response types and enums; both components must consume shared wire-contract
definitions directly rather than importing from `@/types/api`.

Source: Coding guidelines

apps/web/src/components/dashboard/invoice-detail-page.tsx-185-210 (1)

185-210: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve actionable ApiError messages in mutation handlers.

The generic catches hide validation, authorization, and conflict details—especially the invoice deletion guard for recorded payments.

  • apps/web/src/components/dashboard/invoice-detail-page.tsx#L185-L210: catch ApiError and toast its message for payment mutations.
  • apps/web/src/components/dashboard/invoices-page.tsx#L496-L560: do the same for create, edit, and delete mutations.

As per coding guidelines, submit handlers must catch ApiError and display it with toast.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/dashboard/invoice-detail-page.tsx` around lines 185 -
210, Update onRecordSubmit and handleDelete in
apps/web/src/components/dashboard/invoice-detail-page.tsx (lines 185-210) to
catch ApiError and display its message via toast, while retaining the generic
fallback for unknown errors. Apply the same ApiError-aware handling to the
create, edit, and delete mutation handlers in
apps/web/src/components/dashboard/invoices-page.tsx (lines 496-560), ensuring
actionable validation, authorization, and conflict messages are shown.

Source: Coding guidelines

apps/web/src/components/dashboard/invoice-detail-page.tsx-63-110 (1)

63-110: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Use the locale dictionaries for invoice labels and remaining UI copy.

The Arabic route currently renders English status, category, payment-method, and dialog text.

  • apps/web/src/components/dashboard/invoice-detail-page.tsx#L63-L110: derive labels and rendered copy from the active dictionary.
  • apps/web/src/components/dashboard/invoices-page.tsx#L77-L117: localize labels and the remaining JSX literals through the same mechanism.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/dashboard/invoice-detail-page.tsx` around lines 63 -
110, Replace the hard-coded invoice labels and UI copy with values from the
active locale dictionary. In
apps/web/src/components/dashboard/invoice-detail-page.tsx#L63-L110, update
STATUS_LABELS, LINE_ITEM_CATEGORY_LABELS, PAYMENT_METHOD_LABELS, and related
dialog text to use the dictionary; in
apps/web/src/components/dashboard/invoices-page.tsx#L77-L117, localize its label
constants and remaining JSX literals through the same mechanism, preserving the
existing rendering behavior for both locales.
apps/web/src/components/dashboard/invoice-detail-page.tsx-265-269 (1)

265-269: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Format date-only due dates as local dates
new Date(invoice.dueDate).toLocaleDateString() will show the previous calendar day for users west of UTC. Parse the YYYY-MM-DD parts as a local date before formatting in both apps/web/src/components/dashboard/invoice-detail-page.tsx and apps/web/src/components/dashboard/invoices-page.tsx.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/dashboard/invoice-detail-page.tsx` around lines 265 -
269, Format date-only due dates as local calendar dates by splitting the
YYYY-MM-DD value into numeric parts and constructing a local Date before calling
toLocaleDateString. Apply this change at the due-date rendering in
apps/web/src/components/dashboard/invoice-detail-page.tsx (265-269) and
apps/web/src/components/dashboard/invoices-page.tsx (754-756), preserving the
existing display behavior aside from preventing UTC day shifts.
apps/web/src/components/dashboard/invoices-page.tsx-734-745 (1)

734-745: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Prevent nested action controls from triggering row navigation. The actions menu sits inside a keyboard-activatable row, so Space/Enter on the trigger can bubble into the row’s onKeyDown and navigate instead of just opening the menu. Guard the row handler with e.target === e.currentTarget, or stop propagation on the trigger.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/dashboard/invoices-page.tsx` around lines 734 - 745,
Prevent nested action controls from triggering navigation in the TableRow
keyboard handler: update the row’s onKeyDown around goToInvoice so it only
handles Enter or Space when e.target equals e.currentTarget, preserving
navigation for direct row activation while allowing the actions-menu trigger to
handle its own keyboard events.
packages/database/prisma/schema.prisma-344-360 (1)

344-360: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Specify the schema and use UUIDs for primary keys.

Multiple models are missing the @@schema("public") declaration and use cuid() instead of uuid() for the primary key. As per coding guidelines, every model must declare @@schema(...) and use UUID primary keys (@id @default(uuid())).

  • packages/database/prisma/schema.prisma#L344-L360: In the Invoice model, change cuid() to uuid() and append @@schema("public").
  • packages/database/prisma/schema.prisma#L362-L373: In the InvoiceLineItem model, change cuid() to uuid() and append @@schema("public").
  • packages/database/prisma/schema.prisma#L375-L389: In the InvoicePayment model, change cuid() to uuid() and append @@schema("public").
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/database/prisma/schema.prisma` around lines 344 - 360, Update the
Invoice, InvoiceLineItem, and InvoicePayment models in
packages/database/prisma/schema.prisma (lines 344-360, 362-373, and 375-389) to
use `@default`(uuid()) for their primary key IDs and append @@schema("public") to
each model.

Source: Coding guidelines

packages/contracts/src/index.ts-646-779 (1)

646-779: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Move schema definitions to separate files.

The invoice and payment schemas are defined directly within index.ts. As per coding guidelines, you must create one schema per file in packages/contracts, naming them according to the pattern <resource>-<operation>.{request,response}.ts (e.g., invoice-create.request.ts), and export them from the folder index.ts and the main index.ts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/contracts/src/index.ts` around lines 646 - 779, Move the invoice and
invoice-payment schema definitions currently in packages/contracts/src/index.ts
into separate resource/operation files following the
<resource>-<operation>.request.ts or .response.ts naming convention. Update the
folder index and main index exports so all existing invoice symbols remain
publicly available, including invoiceLineItemCategorySchema,
invoiceStatusSchema, invoicePaymentMethodSchema, and the related
request/response types.

Source: Coding guidelines

apps/api/src/common/invoice-summary/compute-invoice-summary.ts-24-29 (1)

24-29: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Avoid floating-point precision errors in monetary comparisons.

JavaScript floating-point arithmetic can produce rounding errors (e.g., 0.1 + 0.2 = 0.30000000000000004). If totalAmount suffers from this precision loss while paidAmount does not (or vice versa), paidAmount >= totalAmount might evaluate to false for a fully paid invoice, leaving it stuck in partially_paid or overdue status.

Consider rounding both amounts to the nearest cent before comparison, or use integer cents internally.

💻 Proposed fix
   const totalAmount = lineItems.reduce((sum, item) => sum + item.amount, 0);
   const paidAmount = payments.reduce((sum, p) => sum + p.amount, 0);
 
   let status: InvoiceStatus;
-  if (paidAmount >= totalAmount) {
+  // Round sums to 2 decimal places to avoid floating-point comparison errors
+  const roundedTotal = Math.round(totalAmount * 100) / 100;
+  const roundedPaid = Math.round(paidAmount * 100) / 100;
+
+  if (roundedPaid >= roundedTotal) {
     status = 'paid';
   } else if (dueDate < now) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/common/invoice-summary/compute-invoice-summary.ts` around lines
24 - 29, Update the amount calculation and comparison in the invoice summary
logic so totalAmount and paidAmount are normalized to integer cents (or rounded
to the nearest cent) before the paid status check. Ensure the existing paid,
partially_paid, and overdue status flow uses these normalized values and
correctly treats fully paid invoices as paid.
apps/api/src/modules/invoice-payments/invoice-payments.controller.ts-29-32 (1)

29-32: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Validate the query and request body with contract-backed Zod pipes.

Both invoiceId and the creation payload currently bypass ZodValidationPipe. Apply schemas imported from @repo/contracts; otherwise malformed query values and bodies can reach service/Prisma logic.

As per coding guidelines, every controller request body and query parameter must use ZodValidationPipe, with schemas from @repo/contracts.

Also applies to: 54-57

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/modules/invoice-payments/invoice-payments.controller.ts` around
lines 29 - 32, Update getInvoicePayments and the creation endpoint to apply
ZodValidationPipe to the invoiceId query parameter and request body, using the
corresponding schemas imported from `@repo/contracts`. Ensure both query values
and creation payloads are validated before reaching the service layer, while
preserving the existing controller behavior after validation.

Source: Coding guidelines

apps/api/src/modules/invoices/invoices.service.ts-285-294 (1)

285-294: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make reference checks and deletions atomic and tenant-scoped.

Both deletion safeguards can race with creation of a new dependent record.

  • apps/api/src/modules/invoices/invoices.service.ts#L285-L294: atomically guard invoice deletion against payments, include orgId, and translate the FK conflict.
  • apps/api/src/modules/leases/leases.service.ts#L333-L342: atomically guard lease deletion against invoices, include orgId, and translate the FK conflict.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/modules/invoices/invoices.service.ts` around lines 285 - 294,
Update invoice deletion in invoices.service.ts around the payment check and
prisma.invoice.delete to use an atomic, tenant-scoped deletion guard including
orgId; remove the race-prone pre-count or incorporate the dependency condition
into the delete transaction, and translate any resulting foreign-key conflict
into the existing ConflictException behavior. Apply the same atomic
invoice-reference guard, orgId scoping, and FK-conflict translation to lease
deletion in leases.service.ts around its dependent-invoice check and lease
delete.

Source: Coding guidelines

apps/api/src/modules/invoices/invoices.service.ts-236-257 (1)

236-257: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Keep replacement mutations tenant-scoped.

The scoped lookup does not satisfy the tenant predicate requirement for the subsequent deleteMany and update. Include orgId directly—or a relation filter for line items—in both transaction queries.

Proposed scope changes
- await tx.invoiceLineItem.deleteMany({ where: { invoiceId } });
+ await tx.invoiceLineItem.deleteMany({
+   where: { invoiceId, invoice: { orgId } },
+ });

  return tx.invoice.update({
-   where: { id: invoiceId },
+   where: { id: invoiceId, orgId },

As per coding guidelines, every Prisma query on a tenant-scoped model must include the tenant identifier.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/modules/invoices/invoices.service.ts` around lines 236 - 257,
Update the transaction queries in the invoice update flow to include the current
tenant’s orgId directly in both the invoiceLineItem.deleteMany and
invoice.update predicates. Preserve the existing invoiceId filtering and
replacement behavior, ensuring every mutation on the tenant-scoped models is
tenant-scoped.

Source: Coding guidelines

🟡 Minor comments (3)
apps/web/src/i18n/dictionaries/ar.json-21-21 (1)

21-21: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Differentiate between "Billing" and "Invoices" in Arabic.

Currently, both nav.billing (line 10) and nav.invoices are translated as "الفواتير" (Invoices). Since both items appear in the navigation sidebar, having identical labels will confuse users.

Consider renaming nav.billing to "الفوترة" (Billing) or "الاشتراكات" (Subscriptions), depending on the platform's context, to distinguish it from "الفواتير" (Invoices).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/i18n/dictionaries/ar.json` at line 21, Update the Arabic
nav.billing translation to a distinct Billing or Subscriptions label, such as
“الفوترة” or “الاشتراكات”, while preserving nav.invoices as “الفواتير”.
apps/web/src/app/[lang]/dashboard/invoices/page.tsx-1-1 (1)

1-1: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Run Prettier on the new invoice workflow files.

The formatting check currently fails for all three files.

  • apps/web/src/app/[lang]/dashboard/invoices/page.tsx#L1-L1: apply Prettier.
  • apps/web/src/components/dashboard/invoice-detail-page.tsx#L1-L1: apply Prettier.
  • apps/web/src/components/dashboard/invoices-page.tsx#L1-L1: apply Prettier.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/`[lang]/dashboard/invoices/page.tsx at line 1, Run the
repository’s Prettier formatter on
apps/web/src/app/[lang]/dashboard/invoices/page.tsx (anchor),
apps/web/src/components/dashboard/invoice-detail-page.tsx, and
apps/web/src/components/dashboard/invoices-page.tsx, preserving their behavior
while applying the formatter’s output.

Source: Pipeline failures

apps/api/src/modules/invoices/invoices.service.spec.ts-1-1 (1)

1-1: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Run Prettier on this test file.

The current Prettier --check CI step fails for this file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/modules/invoices/invoices.service.spec.ts` at line 1, Format the
test file with Prettier so it passes the repository’s Prettier --check step,
preserving the existing test behavior and content.

Source: Pipeline failures

🧹 Nitpick comments (2)
apps/web/src/store/api/endpoints/invoice-payments.api.ts (1)

10-14: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract duplicated unwrap utility.

This unwrap function is duplicated in invoices.api.ts and potentially other API endpoints. Consider extracting it to a shared utilities file (e.g., src/store/api/utils.ts or exporting it from base-api.ts) to adhere to the DRY principle.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/store/api/endpoints/invoice-payments.api.ts` around lines 10 -
14, Extract the duplicated unwrap utility into a shared API utility module or
export it from base-api.ts, then update invoices.api.ts and
invoice-payments.api.ts to import and reuse that single implementation. Preserve
the existing generic behavior and envelope detection logic.
apps/api/src/modules/invoices/invoices.service.ts (1)

54-60: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add the required NestJS Logger.

InvoicesService has no Logger instance. Add and use private readonly logger = new Logger(InvoicesService.name) for unexpected operational failures.

As per coding guidelines, “Every service in apps/api/src/ must use NestJS's Logger class.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/modules/invoices/invoices.service.ts` around lines 54 - 60, Add
NestJS Logger usage to InvoicesService by importing Logger and defining private
readonly logger = new Logger(InvoicesService.name). Use this logger for
unexpected operational failures within the service.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/modules/invoices/dto/create-invoice.dto.ts`:
- Around line 1-54: Replace the inline class-validator DTOs with Zod contracts
under packages/contracts/src/<resource>/: remove create-invoice.dto.ts and
update-invoice.dto.ts, define schemas with inferred request types, and update
invoices.controller.ts lines 47-50 and 57-61 to apply ZodValidationPipe in
`@Body`() and use those inferred types. Affected sites:
apps/api/src/modules/invoices/dto/create-invoice.dto.ts lines 1-54 and
update-invoice.dto.ts lines 1-31 require removal; both controller ranges require
the pipe and contract-derived payload types.

In `@apps/api/src/modules/invoices/dto/update-invoice.dto.ts`:
- Around line 13-31: Replace the inline class-validator definition in
UpdateInvoiceDto with the corresponding invoice update request Zod schema from
`@repo/contracts`. Define or extend the schema under the contracts invoice
resource for dueDate, nullable notes, and nested lineItems, then import and use
that shared schema in the API while preserving the existing validation and
optionality.

In `@apps/api/src/modules/invoices/invoices.controller.ts`:
- Around line 49-60: Update the invoice controller’s create and update body
parameters to use ZodValidationPipe with the corresponding invoiceCreateSchema
and invoiceUpdateSchema from `@repo/contracts`, replacing class-validator DTO
types with the matching request types. Import the pipe and schemas, while
preserving the existing service calls and authorization flow.

In `@packages/contracts/src/index.ts`:
- Around line 711-727: Define and export Zod schemas for CreateInvoiceBody and
PatchInvoiceBody in the contracts module, matching every field’s requiredness,
nullability, and types from the existing TypeScript definitions. Derive or align
the exported TypeScript types with these schemas where appropriate, and add
corresponding schemas for the other request/response wire shapes in this module
that currently have only type definitions.

---

Major comments:
In `@apps/api/src/common/invoice-summary/compute-invoice-summary.ts`:
- Around line 24-29: Update the amount calculation and comparison in the invoice
summary logic so totalAmount and paidAmount are normalized to integer cents (or
rounded to the nearest cent) before the paid status check. Ensure the existing
paid, partially_paid, and overdue status flow uses these normalized values and
correctly treats fully paid invoices as paid.

In `@apps/api/src/modules/invoice-payments/dto/create-invoice-payment.dto.ts`:
- Around line 1-36: Replace CreateInvoicePaymentDto’s class-validator decorators
with a shared Zod request schema defined and exported from packages/contracts,
including the inferred request type and an API-safe payment-method definition
rather than importing InvoicePaymentMethod from `@repo/db`. Update the
corresponding controller endpoint to accept this contract through
ZodValidationPipe and remove the inline DTO usage.

In `@apps/api/src/modules/invoice-payments/invoice-payments.controller.ts`:
- Around line 29-32: Update getInvoicePayments and the creation endpoint to
apply ZodValidationPipe to the invoiceId query parameter and request body, using
the corresponding schemas imported from `@repo/contracts`. Ensure both query
values and creation payloads are validated before reaching the service layer,
while preserving the existing controller behavior after validation.

In `@apps/api/src/modules/invoice-payments/invoice-payments.service.ts`:
- Around line 32-38: Update InvoicePaymentsService to inject DatabaseService in
place of PrismaService, and replace the corresponding field type and usage
references accordingly. Add a NestJS Logger field to the service, following the
existing service logging convention.
- Around line 165-185: The invoice payment create and delete flows can leave
database state and timeline events inconsistent. Update the relevant create flow
around prisma.invoicePayment.create and the delete flow at the specified sibling
site to use a shared Prisma transaction that atomically persists/deletes the
payment and records the corresponding timeline event; preserve the existing
event data and return behavior.
- Around line 63-73: Scope all invoice Prisma operations by organization ID:
update summarizeInvoice to accept orgId and include it with invoiceId in the
invoice lookup, update the deletion query to filter by both id and orgId, and
extend the corresponding service spec assertion to verify the scoped deletion
contract. Apply changes at
apps/api/src/modules/invoice-payments/invoice-payments.service.ts lines 63-73
and 203, and
apps/api/src/modules/invoice-payments/invoice-payments.service.spec.ts lines
250-252.

In `@apps/api/src/modules/invoices/invoices.service.ts`:
- Around line 285-294: Update invoice deletion in invoices.service.ts around the
payment check and prisma.invoice.delete to use an atomic, tenant-scoped deletion
guard including orgId; remove the race-prone pre-count or incorporate the
dependency condition into the delete transaction, and translate any resulting
foreign-key conflict into the existing ConflictException behavior. Apply the
same atomic invoice-reference guard, orgId scoping, and FK-conflict translation
to lease deletion in leases.service.ts around its dependent-invoice check and
lease delete.
- Around line 236-257: Update the transaction queries in the invoice update flow
to include the current tenant’s orgId directly in both the
invoiceLineItem.deleteMany and invoice.update predicates. Preserve the existing
invoiceId filtering and replacement behavior, ensuring every mutation on the
tenant-scoped models is tenant-scoped.

In `@apps/web/src/components/dashboard/invoice-detail-page.tsx`:
- Around line 154-155: Update the useListInvoicePaymentsQuery call to capture
its isError state, then adjust the payment history rendering to show an explicit
payment-loading error and retry action when the query fails instead of treating
undefined payments as an empty history. Preserve the existing empty-state
rendering for successful queries with no payments.
- Around line 70-75: Replace the raw Tailwind color classes in STATUS_STYLES
within apps/web/src/components/dashboard/invoice-detail-page.tsx at lines 70-75
with the semantic design tokens defined in globals.css. Apply the same
token-based status styling to the corresponding STATUS_STYLES mapping in
apps/web/src/components/dashboard/invoices-page.tsx at lines 84-89, keeping
status-specific variants consistent between both components.
- Around line 54-59: Replace the invoice and payment API type imports in
invoice-detail-page.tsx with their corresponding exports from `@repo/contracts`,
preserving the existing type usage. Apply the same migration in
invoices-page.tsx for its invoice response types and enums; both components must
consume shared wire-contract definitions directly rather than importing from
`@/types/api`.
- Around line 185-210: Update onRecordSubmit and handleDelete in
apps/web/src/components/dashboard/invoice-detail-page.tsx (lines 185-210) to
catch ApiError and display its message via toast, while retaining the generic
fallback for unknown errors. Apply the same ApiError-aware handling to the
create, edit, and delete mutation handlers in
apps/web/src/components/dashboard/invoices-page.tsx (lines 496-560), ensuring
actionable validation, authorization, and conflict messages are shown.
- Around line 63-110: Replace the hard-coded invoice labels and UI copy with
values from the active locale dictionary. In
apps/web/src/components/dashboard/invoice-detail-page.tsx#L63-L110, update
STATUS_LABELS, LINE_ITEM_CATEGORY_LABELS, PAYMENT_METHOD_LABELS, and related
dialog text to use the dictionary; in
apps/web/src/components/dashboard/invoices-page.tsx#L77-L117, localize its label
constants and remaining JSX literals through the same mechanism, preserving the
existing rendering behavior for both locales.
- Around line 265-269: Format date-only due dates as local calendar dates by
splitting the YYYY-MM-DD value into numeric parts and constructing a local Date
before calling toLocaleDateString. Apply this change at the due-date rendering
in apps/web/src/components/dashboard/invoice-detail-page.tsx (265-269) and
apps/web/src/components/dashboard/invoices-page.tsx (754-756), preserving the
existing display behavior aside from preventing UTC day shifts.

In `@apps/web/src/components/dashboard/invoices-page.tsx`:
- Around line 210-251: Add accessible labeling to the category Select,
description Input, and amount Input within the line-item rendering block. Use
unique per-row identifiers based on idPrefix and index, associate visible Label
elements through matching htmlFor/id attributes or provide descriptive
aria-labels, and preserve the existing form registration and validation
behavior.
- Around line 734-745: Prevent nested action controls from triggering navigation
in the TableRow keyboard handler: update the row’s onKeyDown around goToInvoice
so it only handles Enter or Space when e.target equals e.currentTarget,
preserving navigation for direct row activation while allowing the actions-menu
trigger to handle its own keyboard events.

In `@packages/contracts/src/index.ts`:
- Around line 646-779: Move the invoice and invoice-payment schema definitions
currently in packages/contracts/src/index.ts into separate resource/operation
files following the <resource>-<operation>.request.ts or .response.ts naming
convention. Update the folder index and main index exports so all existing
invoice symbols remain publicly available, including
invoiceLineItemCategorySchema, invoiceStatusSchema, invoicePaymentMethodSchema,
and the related request/response types.

In `@packages/database/prisma/schema.prisma`:
- Around line 344-360: Update the Invoice, InvoiceLineItem, and InvoicePayment
models in packages/database/prisma/schema.prisma (lines 344-360, 362-373, and
375-389) to use `@default`(uuid()) for their primary key IDs and append
@@schema("public") to each model.

---

Minor comments:
In `@apps/api/src/modules/invoices/invoices.service.spec.ts`:
- Line 1: Format the test file with Prettier so it passes the repository’s
Prettier --check step, preserving the existing test behavior and content.

In `@apps/web/src/app/`[lang]/dashboard/invoices/page.tsx:
- Line 1: Run the repository’s Prettier formatter on
apps/web/src/app/[lang]/dashboard/invoices/page.tsx (anchor),
apps/web/src/components/dashboard/invoice-detail-page.tsx, and
apps/web/src/components/dashboard/invoices-page.tsx, preserving their behavior
while applying the formatter’s output.

In `@apps/web/src/i18n/dictionaries/ar.json`:
- Line 21: Update the Arabic nav.billing translation to a distinct Billing or
Subscriptions label, such as “الفوترة” or “الاشتراكات”, while preserving
nav.invoices as “الفواتير”.

---

Nitpick comments:
In `@apps/api/src/modules/invoices/invoices.service.ts`:
- Around line 54-60: Add NestJS Logger usage to InvoicesService by importing
Logger and defining private readonly logger = new Logger(InvoicesService.name).
Use this logger for unexpected operational failures within the service.

In `@apps/web/src/store/api/endpoints/invoice-payments.api.ts`:
- Around line 10-14: Extract the duplicated unwrap utility into a shared API
utility module or export it from base-api.ts, then update invoices.api.ts and
invoice-payments.api.ts to import and reuse that single implementation. Preserve
the existing generic behavior and envelope detection logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 90c47391-4671-437c-a8c3-2a75db79066d

📥 Commits

Reviewing files that changed from the base of the PR and between e462d2b and 9368d3c.

📒 Files selected for processing (35)
  • apps/api/src/app.module.ts
  • apps/api/src/common/invoice-summary/compute-invoice-summary.spec.ts
  • apps/api/src/common/invoice-summary/compute-invoice-summary.ts
  • apps/api/src/modules/invoice-payments/dto/create-invoice-payment.dto.ts
  • apps/api/src/modules/invoice-payments/invoice-payments.controller.ts
  • apps/api/src/modules/invoice-payments/invoice-payments.module.ts
  • apps/api/src/modules/invoice-payments/invoice-payments.service.spec.ts
  • apps/api/src/modules/invoice-payments/invoice-payments.service.ts
  • apps/api/src/modules/invoices/dto/create-invoice.dto.ts
  • apps/api/src/modules/invoices/dto/update-invoice.dto.ts
  • apps/api/src/modules/invoices/invoices.controller.ts
  • apps/api/src/modules/invoices/invoices.module.ts
  • apps/api/src/modules/invoices/invoices.service.spec.ts
  • apps/api/src/modules/invoices/invoices.service.ts
  • apps/api/src/modules/leases/leases.service.spec.ts
  • apps/api/src/modules/leases/leases.service.ts
  • apps/web/src/app/[lang]/dashboard/invoices/[id]/page.tsx
  • apps/web/src/app/[lang]/dashboard/invoices/page.tsx
  • apps/web/src/app/api/invoice-payments/[id]/route.ts
  • apps/web/src/app/api/invoice-payments/route.ts
  • apps/web/src/app/api/invoices/[id]/route.ts
  • apps/web/src/app/api/invoices/route.ts
  • apps/web/src/auth/permissions.ts
  • apps/web/src/components/dashboard/invoice-detail-page.tsx
  • apps/web/src/components/dashboard/invoices-page.tsx
  • apps/web/src/components/layout/dashboard-sidebar.tsx
  • apps/web/src/i18n/dictionaries/ar.json
  • apps/web/src/i18n/dictionaries/en.json
  • apps/web/src/store/api/endpoints/invoice-payments.api.ts
  • apps/web/src/store/api/endpoints/invoices.api.ts
  • apps/web/src/store/api/tag-types.ts
  • packages/contracts/src/index.ts
  • packages/database/prisma/migrations/20260714175807_add_invoice_model/migration.sql
  • packages/database/prisma/migrations/20260714201131_add_invoice_payment_model/migration.sql
  • packages/database/prisma/schema.prisma

Comment on lines +1 to +54
import {
ArrayMinSize,
IsArray,
IsDateString,
IsEnum,
IsNotEmpty,
IsNumber,
IsOptional,
IsString,
Min,
ValidateNested,
} from 'class-validator';
import { Type } from 'class-transformer';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { InvoiceLineItemCategory } from '@repo/db';

export class InvoiceLineItemInputDto {
@ApiProperty({ enum: InvoiceLineItemCategory })
@IsEnum(InvoiceLineItemCategory)
category: InvoiceLineItemCategory;

@ApiPropertyOptional()
@IsOptional()
@IsString()
description?: string;

@ApiProperty()
@IsNumber({ maxDecimalPlaces: 2 })
@Min(0)
amount: number;
}

export class CreateInvoiceDto {
@ApiProperty()
@IsString()
@IsNotEmpty()
leaseId: string;

@ApiProperty()
@IsDateString()
dueDate: string;

@ApiPropertyOptional()
@IsOptional()
@IsString()
notes?: string;

@ApiProperty({ type: [InvoiceLineItemInputDto] })
@IsArray()
@ArrayMinSize(1)
@ValidateNested({ each: true })
@Type(() => InvoiceLineItemInputDto)
lineItems: InvoiceLineItemInputDto[];
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔴 Critical | 🏗️ Heavy lift

Use Zod schemas from @repo/contracts instead of inline class-validator DTOs.

As per coding guidelines, you must define every request/response shape on the wire as a Zod schema in packages/contracts/src/<resource>/ and validate request bodies in the controller using ZodValidationPipe. Never define DTOs inline in apps/api.

  • apps/api/src/modules/invoices/dto/create-invoice.dto.ts#L1-L54: Remove this file and define the create-invoice schema in packages/contracts.
  • apps/api/src/modules/invoices/dto/update-invoice.dto.ts#L1-L31: Remove this file and define the update-invoice schema in packages/contracts.
  • apps/api/src/modules/invoices/invoices.controller.ts#L47-L50: Inject ZodValidationPipe in the @Body() decorator and type the payload using the inferred request type from your new contract.
  • apps/api/src/modules/invoices/invoices.controller.ts#L57-L61: Inject ZodValidationPipe in the @Body() decorator and type the payload using the inferred request type from your new contract.
📍 Affects 3 files
  • apps/api/src/modules/invoices/dto/create-invoice.dto.ts#L1-L54 (this comment)
  • apps/api/src/modules/invoices/dto/update-invoice.dto.ts#L1-L31
  • apps/api/src/modules/invoices/invoices.controller.ts#L47-L50
  • apps/api/src/modules/invoices/invoices.controller.ts#L57-L61
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/modules/invoices/dto/create-invoice.dto.ts` around lines 1 - 54,
Replace the inline class-validator DTOs with Zod contracts under
packages/contracts/src/<resource>/: remove create-invoice.dto.ts and
update-invoice.dto.ts, define schemas with inferred request types, and update
invoices.controller.ts lines 47-50 and 57-61 to apply ZodValidationPipe in
`@Body`() and use those inferred types. Affected sites:
apps/api/src/modules/invoices/dto/create-invoice.dto.ts lines 1-54 and
update-invoice.dto.ts lines 1-31 require removal; both controller ranges require
the pipe and contract-derived payload types.

Source: Coding guidelines

Comment on lines +13 to +31
export class UpdateInvoiceDto {
@ApiPropertyOptional()
@IsOptional()
@IsDateString()
dueDate?: string;

@ApiPropertyOptional({ nullable: true })
@IsOptional()
@IsString()
notes?: string | null;

@ApiPropertyOptional({ type: [InvoiceLineItemInputDto] })
@IsOptional()
@IsArray()
@ArrayMinSize(1)
@ValidateNested({ each: true })
@Type(() => InvoiceLineItemInputDto)
lineItems?: InvoiceLineItemInputDto[];
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔴 Critical | 🏗️ Heavy lift

Use Zod schemas from @repo/contracts instead of inline DTOs.

As per coding guidelines, DTOs should never be defined inline in apps/api using class-validator. Instead, define every request/response shape on the wire as a Zod schema in packages/contracts/src/<resource>/ and import it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/modules/invoices/dto/update-invoice.dto.ts` around lines 13 -
31, Replace the inline class-validator definition in UpdateInvoiceDto with the
corresponding invoice update request Zod schema from `@repo/contracts`. Define or
extend the schema under the contracts invoice resource for dueDate, nullable
notes, and nested lineItems, then import and use that shared schema in the API
while preserving the existing validation and optionality.

Source: Coding guidelines

Comment on lines +49 to +60
@Body() dto: CreateInvoiceDto,
) {
const { orgId, role } = await this.orgScope.resolveForCaller(user);
return this.invoicesService.create(orgId, user.sub, role, dto);
}

@Roles(Role.ORG_ADMIN, Role.FINANCE)
@Patch(':id')
async updateInvoice(
@CurrentUser() user: AuthenticatedUser,
@Param('id') id: string,
@Body() dto: UpdateInvoiceDto,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔴 Critical | 🏗️ Heavy lift

Validate request bodies using ZodValidationPipe.

As per coding guidelines, every request body must be validated with ZodValidationPipe from src/common/pipes/ using Zod schemas from @repo/contracts, rather than using class-validator DTOs.

♻️ Proposed structure
  `@Post`()
  async createInvoice(
    `@CurrentUser`() user: AuthenticatedUser,
    `@Body`(new ZodValidationPipe(invoiceCreateSchema)) dto: InvoiceCreateRequest,
  ) {
    // ...
  }

  `@Patch`(':id')
  async updateInvoice(
    `@CurrentUser`() user: AuthenticatedUser,
    `@Param`('id') id: string,
    `@Body`(new ZodValidationPipe(invoiceUpdateSchema)) dto: InvoiceUpdateRequest,
  ) {
    // ...
  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/modules/invoices/invoices.controller.ts` around lines 49 - 60,
Update the invoice controller’s create and update body parameters to use
ZodValidationPipe with the corresponding invoiceCreateSchema and
invoiceUpdateSchema from `@repo/contracts`, replacing class-validator DTO types
with the matching request types. Import the pipe and schemas, while preserving
the existing service calls and authorization flow.

Source: Coding guidelines

Comment on lines +711 to +727
export type CreateInvoiceBody = {
leaseId: string;
dueDate: string;
notes?: string;
lineItems: InvoiceLineItemInput[];
};

/**
* When lineItems is provided, the full desired set is replaced wholesale
* (delete-then-recreate) — not diffed individually. Omit it to patch
* dueDate/notes only.
*/
export type PatchInvoiceBody = {
dueDate?: string;
notes?: string | null;
lineItems?: InvoiceLineItemInput[];
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

Define Zod schemas for request/response bodies.

You have only exported TypeScript types for the request bodies (CreateInvoiceBody, PatchInvoiceBody, etc.) without defining their corresponding Zod schemas. As per coding guidelines, every request/response shape on the wire must be defined as a Zod schema in @repo/contracts so the API can use them for validation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/contracts/src/index.ts` around lines 711 - 727, Define and export
Zod schemas for CreateInvoiceBody and PatchInvoiceBody in the contracts module,
matching every field’s requiredness, nullability, and types from the existing
TypeScript definitions. Derive or align the exported TypeScript types with these
schemas where appropriate, and add corresponding schemas for the other
request/response wire shapes in this module that currently have only type
definitions.

Source: Coding guidelines

HadiSaaibi and others added 2 commits July 15, 2026 11:21
CI format:check failed on 7 files added by the Invoices/Payments
PR (issues/012-014) that weren't run through prettier before commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixed the findings that were genuine bugs or matched real project
convention (AGENTS.md, existing service patterns):
- computeInvoiceSummary: compare paid/total in integer cents, not
  raw floats, so a fully-paid invoice can't get stuck partially_paid
  due to float rounding (e.g. 0.1 + 0.2).
- InvoicePaymentsService.summarizeInvoice: scope the Invoice lookup
  by orgId (defense-in-depth; both call sites already passed a
  pre-validated invoiceId, but this method returns entity data,
  unlike the id-only reference-count guards elsewhere).
- Invoice detail page: surface a payment-list fetch failure instead
  of silently rendering "No payments recorded yet."
- Invoices page: add aria-labels to the line-item category/
  description/amount controls; guard the row's onKeyDown so
  Enter/Space on the nested actions-menu trigger doesn't also
  navigate the row (keydown bubbles past the click stopPropagation
  guard).

Skipped findings that contradict this repo's actual conventions
(verified against ExpensesService/expenses-page.tsx/AGENTS.md):
DatabaseService+Logger (no DatabaseService exists anywhere; every
service uses PrismaService per AGENTS.md), UUID+@@Schema primary
keys (all 17 existing models use cuid(), none declare @@Schema),
per-file contracts (packages/contracts/src is a single index.ts),
ZodValidationPipe on API controllers (backend uses class-validator
DTOs; Zod is frontend-only per AGENTS.md), importing wire types from
@repo/contracts in the browser (existing pages import from
@/types/api), ApiError-specific toast messages and design-token
status colors (existing pages use generic toasts and raw Tailwind
colors identically), and wrapping writes+timeline emits in a
transaction (no service in the codebase does this — verified in
ExpensesService). Also verified the flagged delete-guard "atomicity"
concerns in InvoicesService/LeasesService already match the
identical id-only reference-count pattern used in
VendorsService/WorkOrdersService.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant