Suppliers Module
Overview
The Suppliers module manages vendor/business partner records for purchasing and accounts payable workflows. Every supplier is scoped to a businessId for multi-tenant isolation.
Suppliers are referenced by:
- Purchase Orders (
supplierIdFK) - Goods Received Notes (
supplierIdFK) - Accounts Payable Bills (
supplierIdFK) - Replenishment Settings (
supplierIdFK) - Contractor Assignments (
supplierIdFK)
Architecture
suppliers/
├── suppliers.module.ts # NestJS module
├── domain/
│ └── suppliers-repository.domain.ts # Repository port (interface)
├── application/
│ └── suppliers.service.ts # Use cases
├── infrastructure/
│ └── suppliers.repository.ts # Kysely adapter
└── interfaces/
├── suppliers.controller.ts # HTTP routes
├── dtos/
│ ├── create-supplier.dto.ts # Create validation + Swagger
│ └── update-supplier.dto.ts # Partial update validation
└── query/
└── paginate-suppliers.query.ts # Pagination + sort + search
Layer responsibilities
| Layer | Responsibility |
|---|---|
| Domain | ISuppliersRepository interface — no framework dependencies |
| Application | SuppliersService orchestrates CRUD + pagination |
| Infrastructure | SuppliersRepository implements Kysely queries with multi-tenant scoping |
| Interfaces | Controller maps HTTP requests; DTOs validate input |
Database Schema
Table: supplier
| Column | Type | Required | Description |
|---|---|---|---|
id | UUID | auto | Primary key |
businessId | UUID | yes | Multi-tenant scope (FK → business) |
name | VARCHAR | no | Display name |
taxId | VARCHAR | no | Tax identification number |
taxName | VARCHAR | no | Legal/tax registered name |
taxAddress | VARCHAR | no | Tax-registered address |
contact | JSON | no | Contact person details |
email | VARCHAR | no | Email address |
phone | VARCHAR | no | Phone number |
address | VARCHAR | no | Physical address |
city | VARCHAR | no | City |
postalCode | VARCHAR | no | Postal/ZIP code |
supplierCode | VARCHAR | no | Unique supplier code (import upsert key) |
countryId | VARCHAR(3) | default GT | ISO country code |
stateName | VARCHAR | no | State/region |
departmentName | VARCHAR | no | Department |
municipalityName | VARCHAR | no | Municipality |
taxpayerType | VARCHAR | no | Tax classification |
isActive | BOOLEAN | default true | Active flag |
createdAt | TIMESTAMPTZ | auto | Creation timestamp |
createdBy | UUID | yes | FK → user |
updatedAt | TIMESTAMPTZ | no | Last update timestamp |
updatedBy | UUID | no | FK → user |
API Endpoints
All endpoints require Bearer token authentication and Supplier resource permissions.
POST /suppliers
Create a new supplier. createdBy is resolved from the authenticated Firebase user.
GET /suppliers?businessId=<uuid>
Paginated list with search across 9 fields (name, email, phone, taxId, taxName, taxAddress, address, city, postalCode). Supports orderBy, order, page, size query params.
GET /suppliers/:id?businessId=<uuid>
Get a single supplier by ID, scoped by business.
PATCH /suppliers/:id
Partial update. updatedBy is resolved from the authenticated Firebase user. Body must include businessId.
DELETE /suppliers/:id?businessId=<uuid>
Permanently delete a supplier.
Data Import
Suppliers can be bulk-imported via the Data Import module. The import handler:
- Requires
name+ eithersupplierCodeoremailfor upsert matching - Resolves country names/aliases to ISO codes (US, GT, SV, HN, MX, CA)
- Auto-detects create vs update based on existing records
Handler: apps/backend/src/data-import/application/handlers/supplier-import.handler.ts
Design Decisions
- No custom enums — Suppliers use plain string fields (taxpayerType, supplierCode) rather than PostgreSQL enums, allowing flexibility across different tax jurisdictions.
- Upsert via supplierCode or email — The import system uses these as natural keys for matching, avoiding duplicate suppliers during bulk imports.
- Country defaults to GT — The system defaults to Guatemala (
countryId = 'GT') reflecting the primary market. - Hard delete — Suppliers are permanently deleted (not soft-deleted). Consider deactivating (
isActive = false) before deleting if the supplier has related purchase orders or AP bills.