Add business-scoped invoices billed to users.

Introduce invoices.user_id, business template/invoice APIs, and tenant-domain public URLs so business dashboards can issue invoices without platform-scope template mismatches.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-11 15:17:03 +03:30
co-authored by Cursor
parent e59db25814
commit 5734a73c9c
8 changed files with 899 additions and 235 deletions
+32 -18
View File
@@ -1,7 +1,7 @@
# Meshkee CMS API — Project Context
> Living reference for developers and AI assistants working on this codebase.
> Last updated: August 9, 2026
> Last updated: August 11, 2026
## What This Project Is
@@ -160,11 +160,13 @@ Example super admin: `+989121111111` / `password`
| `038_invoice_templates.sql` | Full invoice templates + key points/accounts on invoices |
| `039_invoice_account_holder.sql` | `account_holder_name` on invoice / template accounts |
| `040_invoice_public_id.sql` | Opaque `public_id` for unguessable public invoice links |
| `041_invoice_status_approved.sql` | Invoice status `approved` |
| `049_user_name_en.sql` | Optional `users.first_name_en` / `last_name_en` for EN display names |
| `052_user_products.sql` | Customer stock listings (`user_products`) + technical values; `cities.level` adds `district`; `media_entity_type` adds `user_product` |
| `053_cities_country_optional_province.sql` | City may hang under country (province optional); seed Iraq/Turkey/UAE + major cities |
| `054_seed_iran_provinces_cities.sql` | Seed Iran provinces + cities when missing (004 seed may never have run) |
| `055_user_products_listing_fields.sql` | User product listing fields: `price_currency`, `delivery_note`, `condition` (`user_product_condition`), `technical_notes` |
| `058_invoice_user_id.sql` | `invoices.user_id` billed user (backfill from business owner) |
Docker mounts `./database/migrations` into Postgres init — migrations run automatically only on **first** volume creation. Use `migrate.sh` for subsequent migrations.
@@ -217,9 +219,11 @@ Business 1──* Cart (per customer) 1──* CartItem → ProductVariant
Business 1──* Order 1──* OrderItem → ProductVariant (snapshot on order)
User 1──* Cart, Order (as customer)
Business 1──* Invoice (billed party) 1──* InvoiceItem
InvoiceItemTemplate (platform or per-business predefined lines)
Invoice.issuedBy → User
Business 1──* Invoice (tenant context) 1──* InvoiceItem
User 1──* Invoice (billed party via user_id)
InvoiceItemTemplate / InvoiceTemplate (platform or per-business)
Invoice.issuedBy → User (issuer)
Invoice.issuerBusinessId → Business (when owner_scope=business)
Media 1──* MediaAttachment (polymorphic: entityType + entityId)
```
@@ -624,9 +628,9 @@ See `.env.example` for the full list. Key groups:
---
## Invoices (platform / super-admin)
## Invoices (platform + business)
Super admins issue invoices **to** a business. Schema is ready for future business-scoped issuing (`owner_scope=business`).
Invoices bill a **User** (`user_id`). `business_id` is tenant/context. Super-admin issues platform invoices (`owner_scope=platform`, default billed user = business owner). Business admins issue business-scoped invoices (`owner_scope=business`, `issuer_business_id`, required `userId` who is a customer or team member).
### Tables
@@ -635,10 +639,10 @@ Super admins issue invoices **to** a business. Schema is ready for future busine
| `invoice_item_templates` | Predefined line items (`owner_scope` platform \| business) |
| `invoice_templates` | Full blueprints: name, top_text |
| `invoice_template_items` / `_key_points` / `_accounts` | Nested template content |
| `invoices` | Invoice header (`business_id` = billed party, optional `name`, `top_text`, `notes`, `invoice_template_id`, `status`) |
| `invoices` | Header: `user_id` (billed), `business_id` (context), optional `name`, `top_text`, `notes`, `invoice_template_id`, `status`, `public_id` |
| `invoice_items` / `invoice_key_points` / `invoice_accounts` | Issued invoice nested content |
### API (super_admin only today)
### API — platform templates (super_admin)
| Method | Path |
|--------|------|
@@ -646,20 +650,30 @@ Super admins issue invoices **to** a business. Schema is ready for future busine
| PATCH/DELETE | `/invoice-item-templates/:templateId` |
| GET/POST | `/invoice-templates` |
| GET/PATCH/DELETE | `/invoice-templates/:templateId` |
| GET/POST | `/businesses/:businessId/invoices` |
| GET/PUT/PATCH/DELETE | `/businesses/:businessId/invoices/:invoiceId` (PUT = content; PATCH = status; content locked when `approved`) |
| GET | `/public/invoices/:publicId` (no auth; issued/approved/paid; opaque 12-digit id) |
| POST | `/public/invoices/:publicId/approve` (no auth; `issued``approved`) |
Auth (admin routes): `JwtAuthGuard` + service `assertSuperAdmin`.
### API — business-scoped (`BusinessPermissionGuard`)
Serialized platform invoices include `publicId` + `publicUrl`: `https://{INVOICE_PUBLIC_DOMAIN}/invoices/{publicId}` (default `meshkee.com`), or `{INVOICE_PUBLIC_BASE_URL}/invoices/{publicId}` when set. Accounts include optional `accountHolderName`.
| Method | Path | Permission |
|--------|------|------------|
| GET/POST | `/businesses/:businessId/invoice-item-templates` | `invoice_templates.read` / `.manage` |
| PATCH/DELETE | `.../invoice-item-templates/:templateId` | `invoice_templates.manage` |
| GET/POST | `/businesses/:businessId/invoice-templates` | `invoice_templates.read` / `.manage` |
| GET/PATCH/DELETE | `.../invoice-templates/:templateId` | read / manage |
| GET/POST | `/businesses/:businessId/invoices` | `invoices.read` / `.create` (`?userId=` filter) |
| GET/PUT/PATCH/DELETE | `.../invoices/:invoiceId` | read / update / delete |
Public HTML viewer lives in the dashboards super-admin SPA (`/invoices/:publicId`); API serves JSON via `/public/invoices/:publicId` (sequential PK is not accepted).
Super-admin calling `/businesses/:id/invoices*` still operates on **platform** invoices for that business (service branches on `isSuperAdmin`).
Permissions seeded for future business dashboard: `invoices.*`, `invoice_templates.*`.
### Public
Module: `src/invoices/` · Migrations: `036``041`
| Method | Path |
|--------|------|
| GET | `/public/invoices/:publicId` (issued/approved/paid; platform or business) |
| POST | `/public/invoices/:publicId/approve` (`issued``approved`) |
Serialized invoices include `userId`, `user`, `publicId`, `publicUrl` (both scopes). Business-scoped `publicUrl` uses the business primary domain (`https://{host}/invoices/{publicId}`); platform uses `INVOICE_PUBLIC_DOMAIN` (default `meshkee.com`).
Module: `src/invoices/` · Migrations: `036``041`, `058_invoice_user_id.sql`
---
@@ -707,7 +721,7 @@ Follow the pattern in `CategoryVariationsService` / `CategoryTechnicalFormServic
| Prisma schema | `prisma/schema.prisma` |
| Env template | `.env.example` (`INVOICE_PUBLIC_DOMAIN` / optional `INVOICE_PUBLIC_BASE_URL`) |
| Invoices module | `src/invoices/` |
| Invoice migrations | `database/migrations/036_invoices.sql``040_invoice_public_id.sql` |
| Invoice migrations | `database/migrations/036_invoices.sql``041_invoice_status_approved.sql`, `058_invoice_user_id.sql` |
| Docker services | `docker-compose.yml` |
| Dev seed data | `database/seeds/001_sample_data.sql` |
| Postman | `postman/Meshkee-CMS-Auth.postman_collection.json` |