docs: bring CLAUDE.md, README, Docs and CHANGELOG up to date with current product
Reflects this iteration's domain/feature changes across the docs set: - Cost centre = Vessel only (labelled 'Cost Centre'); costCentreRef/Site removed - Companies (multi-company invoicing) on POs and exports - 3-level 6-digit accounting-code hierarchy; leaf-only PO selection - Structured PO numbers COMPANY/VESSEL/ID/FY (ids from 9000) - Compulsory payment date; editable poDate; export date = approval date - Submitter vendor creation (unverified until proven); verifyVendor - Import PO -> CLOSED with auto vendor/product creation - Inventory flag; inventory added at approval; partial pay/receipt states - Microsoft Entra SSO (nullable passwordHash); profile reachable by all roles - README: roles, domain concepts, db:seed:prod, migrate-before-serve callout - CHANGELOG: Added/Changed/Fixed for the above Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
859be8c8d0
commit
58a5a00594
6 changed files with 164 additions and 25 deletions
|
|
@ -49,17 +49,20 @@ Internal purchase order management system for a maritime company. Full-stack Nex
|
|||
|
||||
```
|
||||
DRAFT → SUBMITTED → MGR_REVIEW → MGR_APPROVED → SENT_FOR_PAYMENT → PAID_DELIVERED → CLOSED
|
||||
↓↑
|
||||
EDITS_REQUESTED / REJECTED / VENDOR_ID_PENDING
|
||||
↓↑ ↕ ↕
|
||||
EDITS_REQUESTED / REJECTED PARTIALLY_PAID PARTIALLY_CLOSED
|
||||
/ VENDOR_ID_PENDING
|
||||
```
|
||||
|
||||
Every status change is validated against the state machine and recorded as a `POAction` row (audit trail).
|
||||
Partial payments (`PARTIALLY_PAID`) and partial receipts (`PARTIALLY_CLOSED`) loop until the full amount/quantity is settled. Imported POs are created directly in `CLOSED`. Every status change is validated against the state machine and recorded as a `POAction` row (audit trail).
|
||||
|
||||
### Role-Based Permissions
|
||||
|
||||
`lib/permissions.ts` defines `hasPermission(role, permission)` and `requirePermission(role, permission)`. Roles: `TECHNICAL`, `MANNING`, `ACCOUNTS`, `MANAGER`, `SUPERUSER`, `AUDITOR`, `ADMIN`.
|
||||
`lib/permissions.ts` defines `hasPermission(role, permission)` and `requirePermission(role, permission)`. Roles: `TECHNICAL`, `MANNING`, `ACCOUNTS`, `MANAGER`, `SUPERUSER`, `AUDITOR`, `ADMIN`. Permissions include (non-exhaustive): `create_po`, `approve_po`, `process_payment`, `confirm_receipt`, `create_vendor`, `manage_vendors`, `manage_products`, `manage_sites`, `manage_vessels_accounts`, `manage_users`. `create_vendor` is held by submitters too; `manage_*` by Manager/Admin.
|
||||
|
||||
**Pattern:** Server Actions call `requirePermission()` at the top before any DB write.
|
||||
**Pattern:** Server Actions call `requirePermission()` (or `hasPermission()`) at the top before any DB write.
|
||||
|
||||
**Auth:** NextAuth v5 with a Microsoft Entra SSO provider **and** a credentials provider. SSO-only users have no `passwordHash` (it is nullable) — the profile page lets them optionally set one, and is reachable by every role. Only approvers (`approve_po`) can upload a signature.
|
||||
|
||||
### Key Directories
|
||||
|
||||
|
|
@ -74,15 +77,46 @@ Every status change is validated against the state machine and recorded as a `PO
|
|||
|
||||
### Cost Centre Model
|
||||
|
||||
A PO's "cost centre" is either a **Vessel** or a **Site**. `PurchaseOrder` has both `vesselId String?` (nullable) and `siteId String?` — exactly one is set.
|
||||
A PO's **cost centre is a Vessel** (the `Vessel` model). `PurchaseOrder.vesselId` is **required**. POs no longer reference a Site as a cost centre — that earlier dual Vessel-or-Site design was removed.
|
||||
|
||||
**Form encoding:** All PO creation/edit forms use a `costCentreRef` field with values `v:<vesselId>` (vessel) or `s:<siteId>` (site). Server actions parse this to set the correct FK.
|
||||
**Form field:** PO create/edit/import forms use a plain `vesselId` select (no more `costCentreRef` encoding).
|
||||
|
||||
**Display pattern:** `po.vessel?.name ?? po.site?.name ?? "—"` everywhere a cost centre name is shown.
|
||||
**Display pattern:** `po.vessel?.name ?? "—"`.
|
||||
|
||||
**URL pre-select:** `/po/new?costCentreRef=v:<id>` or `?costCentreRef=s:<id>`.
|
||||
**URL pre-select:** `/po/new?vesselId=<id>`.
|
||||
|
||||
**Terminology:** Admin pages use the real entity names (Vessel Management, Sites). PO-facing pages use "Cost Centre" for the combined concept. Budget heads are labelled "Accounting Code" (not "Account").
|
||||
**Terminology:** "Vessel" is surfaced as **"Cost Centre"** everywhere in the UI, including the admin page (`/admin/vessels` → "Cost Centre Management"). `Site` still exists as a separate construct (used for vendor-distance and inventory), but is not a PO cost centre. Budget heads are labelled "Accounting Code" (not "Account").
|
||||
|
||||
### Accounting Code Hierarchy
|
||||
|
||||
`Account` is a self-referential 3-level tree via `parentId` (`AccountHierarchy` relation): **Top Category (6-digit, e.g. `100000`) → Sub-Category (`100100`) → Leaf Item (`100101`)**. Codes are 6-digit numeric strings. Seed data lives in `prisma/accounting-codes-data.ts`.
|
||||
|
||||
- **Only leaf items** (accounts with no children) are selectable on a PO.
|
||||
- PO forms group leaf codes by their sub-category in a searchable dropdown (`components/ui/searchable-select.tsx`, a portal-rendered combobox used in the line-items editor and the main accounting-code field).
|
||||
|
||||
### Companies (multi-company invoicing)
|
||||
|
||||
`Company` represents the sister company a PO is billed under (`PurchaseOrder.companyId`, optional). Fields: `name`, `code` (unique short code, e.g. `PMS`), `gstNumber`, `address`, `telephone`, `mobile`, `email`, `invoiceEmail`, `invoiceAddress`. Managed at `/admin/companies`. The selected company's details populate the **exported PO header / invoice block** (falling back to hardcoded Pelagia defaults when no company is linked).
|
||||
|
||||
### PO Numbering (`lib/po-number.ts`)
|
||||
|
||||
Structured format: **`COMPANY/VESSEL/PO_ID/FY`** — e.g. `PMS/HNR1/9000/2024-25`. The financial year is Indian (Apr–Mar) rendered `YYYY-YY`. System-generated `PO_ID` starts at **9000** to avoid clashing with historical numbers. **Imported POs keep their original PO number** verbatim; `parsePoNumber()` extracts the company/vessel/id parts on import.
|
||||
|
||||
### Payments
|
||||
|
||||
When Accounts records a payment, a **compulsory payment date** is captured (`PurchaseOrder.paymentDate`) — the input defaults to today and rejects future dates (validated in `processPaymentSchema` and `markPaid`). There is also an editable **`poDate`** field; the exported PO "Date" shows `poDate ?? approvedAt ?? createdAt` (i.e. the approval date once approved, not creation).
|
||||
|
||||
### Vendors
|
||||
|
||||
`Vendor` carries `isVerified`, `gstin`, `pincode` + `latitude`/`longitude` (geocoded for vendor-distance sorting from a Site), and a `VendorContact[]` list. **Submitters can create vendors** (permission `create_vendor`) but they are created **unverified**; a vendor becomes verified when a PO is closed/paid with it, on import, or when a Manager/Accounts/Admin runs `verifyVendor`. Only `manage_vendors` holders may assign a `vendorId` (the formal verified code).
|
||||
|
||||
### Inventory (feature-flagged)
|
||||
|
||||
Inventory (`ItemInventory`, keyed by `productId` + `siteId`) is **incremented at PO approval** — not on close — for the ordered quantities, when the PO has a `siteId`. The whole inventory surface (site stock, consumption) is gated by `NEXT_PUBLIC_INVENTORY_ENABLED` (see `lib/feature-flags.ts`); the vendor/product catalogue used for PO creation stays available regardless.
|
||||
|
||||
### Import → Closed
|
||||
|
||||
`/po/import` parses a Pelagia-format Excel PO and saves it **directly as `CLOSED`** (historical record, bypasses approval). It auto-detects the company (by header/code), auto-matches the vessel by code, **auto-creates the vendor and any unknown products**, and upserts per-vendor prices.
|
||||
|
||||
### GST Calculation
|
||||
|
||||
|
|
|
|||
|
|
@ -133,6 +133,8 @@ FORGEJO_TOKEN=<forgejo access token>
|
|||
pnpm db:migrate:deploy # runs prisma migrate deploy (safe for production)
|
||||
```
|
||||
|
||||
> **Always run migrations before the new build serves traffic.** `pnpm build` only runs `prisma generate` (which updates the TypeScript client) — it does **not** apply migrations. Deploying new code whose client expects a column the DB doesn't have yet produces `P2022 … column does not exist` errors at runtime. The release workflow (`.forgejo/workflows/deploy.yml`) runs `migrate deploy` as part of the deploy; for manual deploys, run it (and restart) before/with the swap.
|
||||
|
||||
### 3. Build and start
|
||||
|
||||
```bash
|
||||
|
|
@ -172,7 +174,8 @@ and `staging-tunnel.cmd` (Windows tunnel launcher).
|
|||
| `pnpm db:migrate` | Create and run a new migration (dev only) |
|
||||
| `pnpm db:migrate:deploy` | Apply pending migrations without prompting (CI/production) |
|
||||
| `pnpm db:push` | Push schema changes without a migration file (prototyping only) |
|
||||
| `pnpm db:seed` | Seed sample data |
|
||||
| `pnpm db:seed` | Seed sample/demo data (dev) |
|
||||
| `pnpm db:seed:prod` | Seed real production reference data — users, companies, cost centres, sites, and the full accounting-code hierarchy (idempotent) |
|
||||
| `pnpm db:studio` | Open Prisma Studio GUI |
|
||||
| `pnpm db:reset` | Drop and recreate the database, then re-seed (dev only) |
|
||||
|
||||
|
|
@ -235,12 +238,21 @@ pelagia-portal/
|
|||
|
||||
| Role | Description |
|
||||
|---|---|
|
||||
| Technical | Deck/engine crew — create and submit POs, confirm receipt |
|
||||
| Technical | Deck/engine crew — create and submit POs, confirm receipt, add (unverified) vendors |
|
||||
| Manning | Crew-management staff — same as Technical |
|
||||
| Manager | Review, approve, reject, request edits |
|
||||
| Accounts | Process payment for approved POs |
|
||||
| Manager | Review, approve, reject, request edits; manage cost centres, items, vendors |
|
||||
| Accounts | Process payment for approved POs (records payment reference + date); manage vendors |
|
||||
| SuperUser | Combined Technical + Manning + Manager authority |
|
||||
| Auditor | Read-only access to all records and reports |
|
||||
| Admin | Manage users, vessels, accounts, and vendors |
|
||||
| Admin | Manage users, companies, accounting codes, cost centres, sites, items, and vendors |
|
||||
|
||||
User accounts are provisioned by an Admin; there is no self-registration.
|
||||
User accounts are provisioned by an Admin (or via Microsoft Entra SSO); there is no self-registration. SSO-only users have no password and may optionally set one from their profile.
|
||||
|
||||
## Domain Concepts
|
||||
|
||||
- **Cost Centre** — a PO is raised against a **Vessel** (surfaced as "Cost Centre" in the UI). Required on every PO.
|
||||
- **Company** — the sister company a PO is billed under (e.g. PMS, HNR, DEI). Its GST/address details appear on the exported PO.
|
||||
- **Accounting Code** — a 3-level hierarchy of 6-digit codes (Top Category → Sub-Category → Leaf). Only leaf codes are selectable on a PO.
|
||||
- **PO Number** — auto-formatted `COMPANY/VESSEL/ID/FY` (e.g. `PMS/HNR1/9000/2024-25`); imported POs keep their original number.
|
||||
- **Vendors** — submitters can add vendors; they stay *unverified* until a PO closes with them or a Manager/Accounts/Admin verifies them.
|
||||
- **Import PO** (Manager/SuperUser) — uploads a Pelagia-format Excel PO straight into `CLOSED`, auto-creating the vendor and any new items.
|
||||
|
|
|
|||
22
CHANGELOG.md
22
CHANGELOG.md
|
|
@ -4,7 +4,29 @@
|
|||
|
||||
### Added
|
||||
|
||||
- **Companies (multi-company invoicing)** — new `Company` model and `/admin/companies` CRUD. A PO is billed under a selected company (name, short `code`, GST number, address, phone/mobile, contact + invoice email, invoice address). The company's details populate the exported PO header / invoice block.
|
||||
- **Structured PO numbers** (`lib/po-number.ts`) — `COMPANY/VESSEL/ID/FY` (e.g. `PMS/HNR1/9000/2024-25`); Indian financial year; system-generated IDs start at 9000. Imported POs keep their original number.
|
||||
- **3-level accounting-code hierarchy** — `Account.parentId` self-relation (Top Category → Sub-Category → Leaf), 6-digit numeric codes seeded from `prisma/accounting-codes-data.ts`. Only leaf codes are PO-selectable, via a searchable, portal-rendered combobox.
|
||||
- **Compulsory payment date** — `PurchaseOrder.paymentDate` captured when Accounts records a payment; defaults to today, rejects future dates. Backfilled for existing POs from `paidAt` / the first payment action.
|
||||
- **Editable PO date (`poDate`)** — the exported PO "Date" now shows `poDate ?? approvedAt ?? createdAt` (approval date once approved, not creation).
|
||||
- **Submitter vendor creation** — `create_vendor` permission lets Technical/Manning add vendors; they are created **unverified** and become verified when a PO closes/pays with them, on import, or via Manager/Accounts/Admin (`verifyVendor`).
|
||||
- **Import PO → Closed** — `/po/import` saves a parsed Excel PO directly as `CLOSED`, auto-detecting the company, matching the vessel by code, and auto-creating the vendor, products, and per-vendor prices.
|
||||
- **Inventory feature flag** (`NEXT_PUBLIC_INVENTORY_ENABLED`) — site stock/consumption surfaces are gated; the vendor/item catalogue for PO creation stays available. Inventory is incremented at **PO approval** (not on close).
|
||||
- **Dashboards** — Accounts gains a "Payments Completed This Month" card.
|
||||
- **Automated issue-to-deploy pipeline** — end-to-end flow from a user-reported bug to a production fix without manual intervention on the developer's part:
|
||||
- **Report Issue button** (`App/components/layout/report-issue-button.tsx`) — any signed-in user can file a bug from the portal header; the server action (`report-issue-actions.ts`) calls the Forgejo API and attaches `portal` + `claude-queue` labels.
|
||||
- **Claude issue watcher** (`automation/claude-issue-watcher.ps1`) — a Windows Scheduled Task (`PelagiaClaudeIssueWatcher`) polls Forgejo every 10 minutes, picks up `claude-queue` issues, and runs Claude Code headlessly to implement and verify a fix. On success the watcher pushes a `claude/issue-N` branch and opens a PR; on failure it posts a comment and labels the issue `claude-failed`.
|
||||
- **Tag-triggered deploy workflow** (`.forgejo/workflows/deploy.yml`) — pushing a `v*` semver tag triggers the `host` Forgejo runner on pms1, which checks out the tag, runs `pnpm install`, builds the app, applies Prisma migrations, and restarts the pm2 process `ppms`.
|
||||
|
||||
### Changed
|
||||
|
||||
- **Cost centre is now a Vessel only.** The earlier Vessel-or-Site cost-centre model was removed: `PurchaseOrder.vesselId` is required, the `costCentreRef` encoding is gone, and `Vessel` no longer links to a `Site`. Vessels are surfaced as **"Cost Centre"** throughout the UI (`/admin/vessels` → "Cost Centre Management").
|
||||
- **Closed Purchase Orders** list: submitters see only their own `CLOSED` POs; Managers/SuperUsers see all `CLOSED` POs.
|
||||
- **Sidebar** reorganised into **Purchasing** and **Administration** sections (role-aware); "Inventory" renamed to "Purchasing".
|
||||
- **Items**: `/admin/products` is the editable catalogue; `/inventory/items` is read-only; both link to a shared item detail page.
|
||||
- **Profile** page is reachable by every role (incl. SSO-only / no-password users, with an email fallback lookup); only approvers can upload an approval signature.
|
||||
- **Manager dashboard** "Approved This Month" now counts by `approvedAt` (no longer undercounts once a PO progresses past `MGR_APPROVED`).
|
||||
|
||||
### Fixed
|
||||
|
||||
- Production `P2022 … column does not exist` after deploy — caused by shipping code whose Prisma client expected a column before `migrate deploy` had run. Migrations must be applied before the new build serves traffic (now documented in the README).
|
||||
|
|
|
|||
|
|
@ -1,5 +1,18 @@
|
|||
# Pelagia Portal — Design Document
|
||||
|
||||
> **Note — this is the original design spec.** The shipped product has evolved; where they
|
||||
> differ, the code, `App/CLAUDE.md`, `Docs/02-architecture.md`, and the `CHANGELOG.md` are
|
||||
> authoritative. Key amendments since this document was written:
|
||||
> - **Cost centre is a Vessel only** (Vessels are labelled "Cost Centre" in the UI). "Account
|
||||
> Management / cost centres" below refers to budget **Accounting Codes**, now a 3-level
|
||||
> hierarchy of 6-digit codes — not cost centres.
|
||||
> - **Companies** were added (a PO is billed under a sister company; details appear on the
|
||||
> exported PO).
|
||||
> - **Submitters can create vendors** (created unverified until approved or proven by a closed PO).
|
||||
> - **Accounts capture a compulsory payment date**; PO numbers are auto-formatted
|
||||
> `COMPANY/VESSEL/ID/FY`; POs can be **imported** straight to `CLOSED`.
|
||||
> - Auth supports **Microsoft Entra SSO** (passwordless users); partial payment/receipt states exist.
|
||||
|
||||
## 1. Overview
|
||||
|
||||
Pelagia Portal is an internal purchase order (PO) management web application for a maritime / vessel-operations company. It digitises the entire PO lifecycle — from a crew member raising a requisition, through manager approval and vendor validation, to accounts payment processing and final receipt confirmation — replacing ad-hoc email chains and spreadsheets with a single, auditable system.
|
||||
|
|
|
|||
|
|
@ -127,6 +127,24 @@ pelagia-portal/
|
|||
|
||||
## 4. Data Model
|
||||
|
||||
> **Source of truth:** `App/prisma/schema.prisma`. The excerpt below is an illustrative
|
||||
> overview and may lag the schema. Notable evolutions since the original design:
|
||||
> - **Cost centre is a Vessel only** — the PO `vesselId` is required; the earlier
|
||||
> Vessel-or-Site cost-centre design was dropped. `Vessel` no longer links to a `Site`.
|
||||
> In the UI a Vessel is labelled **"Cost Centre"**.
|
||||
> - **`Account` is a 3-level hierarchy** (self-relation via `parentId`): Top Category →
|
||||
> Sub-Category → Leaf item, with 6-digit numeric codes; only leaf codes are PO-selectable.
|
||||
> - **`Company`** added — the sister company a PO is billed under (`PurchaseOrder.companyId`).
|
||||
> - **`PurchaseOrder`** gained `companyId`, `siteId?`, `paidAmount`, `paymentDate`, `poDate`,
|
||||
> and the quotation / requisition / terms-and-conditions fields; `currency` defaults to `INR`.
|
||||
> - **`Vendor`** gained `gstin`, `address`, `pincode`, `latitude`/`longitude` (geocoded for
|
||||
> distance) and a `VendorContact[]` list. Submitters can create vendors (created
|
||||
> *unverified*); a vendor is verified when a PO closes/pays with it, on import, or by a
|
||||
> Manager/Accounts/Admin.
|
||||
> - Inventory/catalogue models added: `Site`, `ItemInventory`, `ItemConsumption`,
|
||||
> `ProductVendorPrice`. Auth: `User.passwordHash` is **nullable** (SSO users) and `User`
|
||||
> has a `signatureKey`. New statuses `PARTIALLY_PAID` / `PARTIALLY_CLOSED`.
|
||||
|
||||
### 4.1 Entity Relationship (Prisma Schema)
|
||||
|
||||
```prisma
|
||||
|
|
@ -151,7 +169,9 @@ enum POStatus {
|
|||
REJECTED
|
||||
MGR_APPROVED
|
||||
SENT_FOR_PAYMENT
|
||||
PARTIALLY_PAID
|
||||
PAID_DELIVERED
|
||||
PARTIALLY_CLOSED
|
||||
CLOSED
|
||||
}
|
||||
|
||||
|
|
@ -187,23 +207,45 @@ model User {
|
|||
notifications Notification[]
|
||||
}
|
||||
|
||||
// Cost centre. Surfaced as "Cost Centre" in the UI.
|
||||
model Vessel {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
code String @unique
|
||||
isActive Boolean @default(true)
|
||||
|
||||
siteId String?
|
||||
site Site? @relation(fields: [siteId], references: [id])
|
||||
|
||||
purchaseOrders PurchaseOrder[]
|
||||
}
|
||||
|
||||
// 3-level hierarchy via self-relation: Top Category → Sub-Category → Leaf item.
|
||||
// 6-digit numeric codes; only leaf accounts (no children) are selectable on a PO.
|
||||
model Account {
|
||||
id String @id @default(cuid())
|
||||
code String @unique
|
||||
id String @id @default(cuid())
|
||||
code String @unique // e.g. 100000 / 100100 / 100101
|
||||
name String
|
||||
description String?
|
||||
isActive Boolean @default(true)
|
||||
isActive Boolean @default(true)
|
||||
|
||||
parentId String?
|
||||
parent Account? @relation("AccountHierarchy", fields: [parentId], references: [id])
|
||||
children Account[] @relation("AccountHierarchy")
|
||||
|
||||
purchaseOrders PurchaseOrder[]
|
||||
}
|
||||
|
||||
// Sister company a PO is billed under; its details populate the exported PO header.
|
||||
model Company {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
code String? @unique // short code used in PO numbers, e.g. PMS
|
||||
gstNumber String?
|
||||
address String?
|
||||
telephone String?
|
||||
mobile String?
|
||||
email String?
|
||||
invoiceEmail String?
|
||||
invoiceAddress String?
|
||||
isActive Boolean @default(true)
|
||||
|
||||
purchaseOrders PurchaseOrder[]
|
||||
}
|
||||
|
|
@ -239,15 +281,19 @@ model Product {
|
|||
|
||||
model PurchaseOrder {
|
||||
id String @id @default(cuid())
|
||||
poNumber String @unique @default(cuid()) // formatted in app layer
|
||||
poNumber String @unique // COMPANY/VESSEL/ID/FY, formatted in lib/po-number.ts
|
||||
title String
|
||||
status POStatus @default(DRAFT)
|
||||
totalAmount Decimal @db.Decimal(12, 2)
|
||||
currency String @default("USD")
|
||||
paidAmount Decimal? @db.Decimal(12, 2) // accumulates across partial payments
|
||||
currency String @default("INR")
|
||||
poDate DateTime? // editable PO date (export "Date" = poDate ?? approvedAt ?? createdAt)
|
||||
dateRequired DateTime?
|
||||
projectCode String?
|
||||
managerNote String?
|
||||
paymentRef String?
|
||||
paymentDate DateTime? // compulsory when Accounts records a payment; no future dates
|
||||
// + piQuotationNo/Date, requisitionNo/Date, placeOfDelivery, tc* (terms & conditions) fields
|
||||
submittedAt DateTime?
|
||||
approvedAt DateTime?
|
||||
paidAt DateTime?
|
||||
|
|
@ -257,10 +303,14 @@ model PurchaseOrder {
|
|||
|
||||
submitterId String
|
||||
submitter User @relation("Submitter", fields: [submitterId], references: [id])
|
||||
vesselId String
|
||||
vesselId String // cost centre (required)
|
||||
vessel Vessel @relation(fields: [vesselId], references: [id])
|
||||
siteId String? // optional delivery site (drives inventory)
|
||||
site Site? @relation(fields: [siteId], references: [id])
|
||||
accountId String
|
||||
account Account @relation(fields: [accountId], references: [id])
|
||||
companyId String?
|
||||
company Company? @relation(fields: [companyId], references: [id])
|
||||
vendorId String?
|
||||
vendor Vendor? @relation(fields: [vendorId], references: [id])
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,14 @@
|
|||
Internal purchase-order management system for a maritime company.
|
||||
This document describes every feature, page, workflow, and user story to guide UI/UX design.
|
||||
|
||||
> **Note — original UI/UX spec; the shipped product has evolved.** For current behaviour see
|
||||
> `App/CLAUDE.md`, `Docs/02-architecture.md`, and `CHANGELOG.md`. Notably: a PO's cost centre
|
||||
> is a **Vessel** (labelled "Cost Centre"); a PO is billed under a **Company** (shown on the
|
||||
> exported PO); **Accounting Codes** are a 3-level 6-digit hierarchy; PO numbers are
|
||||
> `COMPANY/VESSEL/ID/FY`; submitters can add (unverified) vendors; Accounts capture a
|
||||
> compulsory **payment date**; POs can be **imported** directly to `CLOSED`; and login
|
||||
> supports **Microsoft Entra SSO**.
|
||||
|
||||
---
|
||||
|
||||
## 1. Purpose
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue