Schema: - Site model (name, code, address, lat/lng, isActive) - ItemInventory model (quantity per product per site, unique[productId,siteId]) - ItemConsumption model (daily usage per product per site) - Vendor: add latitude, longitude - Vessel: add siteId (home port) - PurchaseOrder: add siteId (delivery site) Permissions: add manage_sites to MANAGER and ADMIN Sidebar: Inventory section (Vendors, Items, Vessels, Sites, Cart) for MANAGER and ADMIN; old admin items reorganised Lib: - lib/geo.ts: Haversine distance + Nominatim pincode geocoding - lib/gst-lookup.ts: AbhiAPI GSTIN lookup (ABHIAPI_KEY env var) - lib/cart.ts: localStorage cart (add/remove/clear + cart-updated event) API: GET /api/gst?gstin= — validates GSTIN, fetches via AbhiAPI, geocodes pincode via Nominatim, returns name/address/lat/lng Vendor form: GSTIN "Look up" button auto-fills name, address, and lat/lng; lat/lng fields editable as override Sites: full CRUD at /admin/sites; detail page with inventory table, consumption recording form, BarChart (stock) + LineChart (30-day consumption), linked vessels, recent POs Vessels: detail page at /admin/vessels/[id] with "Create PO" button, PO history and spend summary; accessible to MANAGER Items detail: price comparison BarChart; site distance filter (dropdown → re-render sorted by Haversine distance); "Add to Cart" per vendor row; stock-by-site section Cart: /inventory/cart — localStorage CartView; qty edit, remove, clear; "Create PO →" encodes cart into /po/new?cart=... Receipt/delivery: confirmReceipt now upserts ItemInventory (increment qty) for each linked line item, using PO siteId or vessel home site as the delivery location Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
84 lines
2.1 KiB
TypeScript
84 lines
2.1 KiB
TypeScript
import type { Role } from "@prisma/client";
|
|
|
|
export type Permission =
|
|
| "create_po"
|
|
| "submit_po"
|
|
| "edit_own_draft_po"
|
|
| "view_own_pos"
|
|
| "view_all_pos"
|
|
| "approve_po"
|
|
| "reject_po"
|
|
| "request_edits"
|
|
| "request_vendor_id"
|
|
| "process_payment"
|
|
| "confirm_receipt"
|
|
| "view_analytics"
|
|
| "export_reports"
|
|
| "manage_users"
|
|
| "manage_vendors"
|
|
| "manage_vessels_accounts"
|
|
| "manage_products"
|
|
| "manage_sites";
|
|
|
|
const ROLE_PERMISSIONS: Record<Role, Permission[]> = {
|
|
TECHNICAL: ["create_po", "submit_po", "edit_own_draft_po", "view_own_pos", "confirm_receipt"],
|
|
MANNING: ["create_po", "submit_po", "edit_own_draft_po", "view_own_pos", "confirm_receipt"],
|
|
ACCOUNTS: ["view_all_pos", "process_payment", "manage_vendors"],
|
|
MANAGER: [
|
|
"create_po",
|
|
"submit_po",
|
|
"edit_own_draft_po",
|
|
"view_own_pos",
|
|
"view_all_pos",
|
|
"approve_po",
|
|
"reject_po",
|
|
"request_edits",
|
|
"request_vendor_id",
|
|
"view_analytics",
|
|
"export_reports",
|
|
"manage_vendors",
|
|
"manage_products",
|
|
"manage_sites",
|
|
],
|
|
SUPERUSER: [
|
|
"create_po",
|
|
"submit_po",
|
|
"edit_own_draft_po",
|
|
"view_own_pos",
|
|
"view_all_pos",
|
|
"approve_po",
|
|
"reject_po",
|
|
"request_edits",
|
|
"request_vendor_id",
|
|
"process_payment",
|
|
"confirm_receipt",
|
|
"view_analytics",
|
|
"export_reports",
|
|
],
|
|
AUDITOR: ["view_own_pos", "view_all_pos", "view_analytics", "export_reports"],
|
|
ADMIN: [
|
|
"view_own_pos",
|
|
"view_all_pos",
|
|
"view_analytics",
|
|
"export_reports",
|
|
"manage_users",
|
|
"manage_vendors",
|
|
"manage_vessels_accounts",
|
|
"manage_products",
|
|
"manage_sites",
|
|
],
|
|
};
|
|
|
|
export function hasPermission(role: Role, permission: Permission): boolean {
|
|
return ROLE_PERMISSIONS[role]?.includes(permission) ?? false;
|
|
}
|
|
|
|
export function requirePermission(role: Role, permission: Permission): void {
|
|
if (!hasPermission(role, permission)) {
|
|
throw new Error(`Forbidden: role ${role} lacks permission ${permission}`);
|
|
}
|
|
}
|
|
|
|
export function getPermissions(role: Role): Permission[] {
|
|
return ROLE_PERMISSIONS[role] ?? [];
|
|
}
|