@porulle/plugin-procurement 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/routes/procurement.d.ts +11 -0
- package/dist/routes/procurement.d.ts.map +1 -0
- package/dist/routes/procurement.js +126 -0
- package/dist/schema.d.ts +1392 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +109 -0
- package/dist/services/grn-service.d.ts +34 -0
- package/dist/services/grn-service.d.ts.map +1 -0
- package/dist/services/grn-service.js +74 -0
- package/dist/services/po-service.d.ts +31 -0
- package/dist/services/po-service.d.ts.map +1 -0
- package/dist/services/po-service.js +77 -0
- package/dist/services/supplier-service.d.ts +30 -0
- package/dist/services/supplier-service.d.ts.map +1 -0
- package/dist/services/supplier-service.js +32 -0
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +58 -0
- package/src/index.ts +29 -0
- package/src/routes/procurement.ts +135 -0
- package/src/schema.ts +115 -0
- package/src/services/grn-service.ts +91 -0
- package/src/services/po-service.ts +85 -0
- package/src/services/supplier-service.ts +40 -0
- package/src/types.ts +9 -0
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# @porulle/plugin-procurement
|
|
2
|
+
|
|
3
|
+
Suppliers with catalog links, purchase orders through approval, and goods-received notes for inbound receiving.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @porulle/plugin-procurement
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Add to `commerce.config.ts`:
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { procurementPlugin } from "@porulle/plugin-procurement";
|
|
15
|
+
|
|
16
|
+
export default defineConfig({
|
|
17
|
+
plugins: [procurementPlugin()],
|
|
18
|
+
});
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Add to `drizzle.config.ts`:
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
schema: [
|
|
25
|
+
"./node_modules/@porulle/plugin-procurement/src/schema.ts",
|
|
26
|
+
// ...
|
|
27
|
+
],
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## What it does
|
|
31
|
+
|
|
32
|
+
Manages supplier master data, links sellable items to suppliers, creates and approves POs, records GRNs, and accepts GRNs into inventory context via services (see procurement routes for permissions).
|
|
33
|
+
|
|
34
|
+
## Routes exposed
|
|
35
|
+
|
|
36
|
+
**`/procurement`** — `POST/GET /suppliers`, `GET /suppliers/{id}`, `POST /suppliers/{id}/items`, `POST/GET /purchase-orders`, `GET /purchase-orders/{id}`, `POST .../submit|approve|cancel`, `POST/GET /grn`, `GET /grn/{id}`, `POST /grn/{id}/accept`
|
|
37
|
+
|
|
38
|
+
## Hooks
|
|
39
|
+
|
|
40
|
+
**Emitted:** none.
|
|
41
|
+
|
|
42
|
+
**Consumed:** none.
|
|
43
|
+
|
|
44
|
+
## MCP tools
|
|
45
|
+
|
|
46
|
+
**`procurement`** — `list_suppliers`, `get_supplier`, `list_po`, `get_po`, `approve_po`, `cancel_po`, `list_grn`, `get_grn`, `accept_grn`
|
|
47
|
+
|
|
48
|
+
## Configuration options
|
|
49
|
+
|
|
50
|
+
None (`procurementPlugin()` takes no options).
|
|
51
|
+
|
|
52
|
+
## License
|
|
53
|
+
|
|
54
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { Db } from "./types.js";
|
|
2
|
+
export { SupplierService } from "./services/supplier-service.js";
|
|
3
|
+
export { PurchaseOrderService } from "./services/po-service.js";
|
|
4
|
+
export { GRNService } from "./services/grn-service.js";
|
|
5
|
+
export declare function procurementPlugin(): import("@porulle/core").CommercePlugin;
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,YAAY,EAAE,EAAE,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAEvD,wBAAgB,iBAAiB,2CAiBhC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { defineCommercePlugin } from "@porulle/core";
|
|
2
|
+
import { suppliers, supplierItems, purchaseOrders, purchaseOrderItems, goodsReceivedNotes, grnItems } from "./schema.js";
|
|
3
|
+
import { SupplierService } from "./services/supplier-service.js";
|
|
4
|
+
import { PurchaseOrderService } from "./services/po-service.js";
|
|
5
|
+
import { GRNService } from "./services/grn-service.js";
|
|
6
|
+
import { buildProcurementRoutes } from "./routes/procurement.js";
|
|
7
|
+
export { SupplierService } from "./services/supplier-service.js";
|
|
8
|
+
export { PurchaseOrderService } from "./services/po-service.js";
|
|
9
|
+
export { GRNService } from "./services/grn-service.js";
|
|
10
|
+
export function procurementPlugin() {
|
|
11
|
+
return defineCommercePlugin({
|
|
12
|
+
id: "procurement",
|
|
13
|
+
version: "1.0.0",
|
|
14
|
+
permissions: [
|
|
15
|
+
{ scope: "procurement:admin", description: "Approve POs, manage suppliers, accept GRNs." },
|
|
16
|
+
{ scope: "procurement:create", description: "Create POs, create GRNs." },
|
|
17
|
+
{ scope: "procurement:read", description: "View POs, GRNs, suppliers." },
|
|
18
|
+
],
|
|
19
|
+
schema: () => ({ suppliers, supplierItems, purchaseOrders, purchaseOrderItems, goodsReceivedNotes, grnItems }),
|
|
20
|
+
hooks: () => [],
|
|
21
|
+
routes: (ctx) => {
|
|
22
|
+
const db = ctx.database.db;
|
|
23
|
+
if (!db)
|
|
24
|
+
return [];
|
|
25
|
+
return buildProcurementRoutes(new SupplierService(db), new PurchaseOrderService(db), new GRNService(db), ctx);
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SupplierService } from "../services/supplier-service.js";
|
|
2
|
+
import type { PurchaseOrderService } from "../services/po-service.js";
|
|
3
|
+
import type { GRNService } from "../services/grn-service.js";
|
|
4
|
+
import type { PluginRouteRegistration } from "@porulle/core";
|
|
5
|
+
export declare function buildProcurementRoutes(supplierSvc: SupplierService, poSvc: PurchaseOrderService, grnSvc: GRNService, ctx: {
|
|
6
|
+
services?: Record<string, unknown>;
|
|
7
|
+
database?: {
|
|
8
|
+
db: unknown;
|
|
9
|
+
};
|
|
10
|
+
}): PluginRouteRegistration[];
|
|
11
|
+
//# sourceMappingURL=procurement.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"procurement.d.ts","sourceRoot":"","sources":["../../src/routes/procurement.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAE7D,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,eAAe,EAAE,KAAK,EAAE,oBAAoB,EAAE,MAAM,EAAE,UAAU,EAC7E,GAAG,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,GACtE,uBAAuB,EAAE,CA4H3B"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { router } from "@porulle/core";
|
|
2
|
+
import { z } from "@hono/zod-openapi";
|
|
3
|
+
export function buildProcurementRoutes(supplierSvc, poSvc, grnSvc, ctx) {
|
|
4
|
+
const r = router("Procurement", "/procurement", ctx);
|
|
5
|
+
// Suppliers
|
|
6
|
+
r.post("/suppliers").summary("Register supplier").permission("procurement:admin")
|
|
7
|
+
.input(z.object({ name: z.string().min(1), code: z.string().min(1), contactName: z.string().optional(), contactEmail: z.string().email().optional(), contactPhone: z.string().optional(), paymentTermsDays: z.number().int().optional(), currency: z.string().optional() }))
|
|
8
|
+
.handler(async ({ input, orgId }) => {
|
|
9
|
+
const result = await supplierSvc.create(orgId, input);
|
|
10
|
+
if (!result.ok)
|
|
11
|
+
throw new Error(result.error);
|
|
12
|
+
return result.value;
|
|
13
|
+
});
|
|
14
|
+
r.get("/suppliers").summary("List suppliers").permission("procurement:read")
|
|
15
|
+
.handler(async ({ orgId }) => {
|
|
16
|
+
const result = await supplierSvc.list(orgId);
|
|
17
|
+
if (!result.ok)
|
|
18
|
+
throw new Error(result.error);
|
|
19
|
+
return result.value;
|
|
20
|
+
});
|
|
21
|
+
r.get("/suppliers/{id}").summary("Get supplier with items").permission("procurement:read")
|
|
22
|
+
.handler(async ({ params, orgId }) => {
|
|
23
|
+
const result = await supplierSvc.getById(orgId, params.id);
|
|
24
|
+
if (!result.ok)
|
|
25
|
+
throw new Error(result.error);
|
|
26
|
+
return result.value;
|
|
27
|
+
});
|
|
28
|
+
r.post("/suppliers/{id}/items").summary("Link item to supplier").permission("procurement:admin")
|
|
29
|
+
.input(z.object({ entityId: z.string().uuid(), variantId: z.string().uuid().optional(), supplierSku: z.string().optional(), unitCost: z.number().int().positive(), minOrderQuantity: z.number().int().optional(), leadTimeDays: z.number().int().optional(), isPreferred: z.boolean().optional() }))
|
|
30
|
+
.handler(async ({ params, input }) => {
|
|
31
|
+
const result = await supplierSvc.addItem(params.id, input);
|
|
32
|
+
if (!result.ok)
|
|
33
|
+
throw new Error(result.error);
|
|
34
|
+
return result.value;
|
|
35
|
+
});
|
|
36
|
+
// Purchase Orders
|
|
37
|
+
r.post("/purchase-orders").summary("Create purchase order").permission("procurement:create")
|
|
38
|
+
.input(z.object({
|
|
39
|
+
supplierId: z.string().uuid(), warehouseId: z.string().uuid(), notes: z.string().optional(),
|
|
40
|
+
expectedDelivery: z.string().optional(),
|
|
41
|
+
items: z.array(z.object({ entityId: z.string().uuid(), variantId: z.string().uuid().optional(), itemName: z.string(), quantityOrdered: z.number().int().positive(), unitCost: z.number().int().positive() })).min(1),
|
|
42
|
+
}))
|
|
43
|
+
.handler(async ({ input, actor, orgId }) => {
|
|
44
|
+
const body = input;
|
|
45
|
+
const result = await poSvc.create(orgId, { ...body, requestedBy: actor.userId });
|
|
46
|
+
if (!result.ok)
|
|
47
|
+
throw new Error(result.error);
|
|
48
|
+
return result.value;
|
|
49
|
+
});
|
|
50
|
+
r.get("/purchase-orders").summary("List purchase orders").permission("procurement:read")
|
|
51
|
+
.query(z.object({ status: z.string().optional() }))
|
|
52
|
+
.handler(async ({ query, orgId }) => {
|
|
53
|
+
const result = await poSvc.list(orgId, query.status);
|
|
54
|
+
if (!result.ok)
|
|
55
|
+
throw new Error(result.error);
|
|
56
|
+
return result.value;
|
|
57
|
+
});
|
|
58
|
+
r.get("/purchase-orders/{id}").summary("Get PO with items").permission("procurement:read")
|
|
59
|
+
.handler(async ({ params, orgId }) => {
|
|
60
|
+
const result = await poSvc.getById(orgId, params.id);
|
|
61
|
+
if (!result.ok)
|
|
62
|
+
throw new Error(result.error);
|
|
63
|
+
return result.value;
|
|
64
|
+
});
|
|
65
|
+
r.post("/purchase-orders/{id}/submit").summary("Submit PO for approval").permission("procurement:create")
|
|
66
|
+
.handler(async ({ params, orgId }) => {
|
|
67
|
+
const result = await poSvc.submit(orgId, params.id);
|
|
68
|
+
if (!result.ok)
|
|
69
|
+
throw new Error(result.error);
|
|
70
|
+
return result.value;
|
|
71
|
+
});
|
|
72
|
+
r.post("/purchase-orders/{id}/approve").summary("Approve PO").permission("procurement:admin")
|
|
73
|
+
.handler(async ({ params, actor, orgId }) => {
|
|
74
|
+
const result = await poSvc.approve(orgId, params.id, actor.userId);
|
|
75
|
+
if (!result.ok)
|
|
76
|
+
throw new Error(result.error);
|
|
77
|
+
return result.value;
|
|
78
|
+
});
|
|
79
|
+
r.post("/purchase-orders/{id}/cancel").summary("Cancel PO").permission("procurement:admin")
|
|
80
|
+
.handler(async ({ params, orgId }) => {
|
|
81
|
+
const result = await poSvc.cancel(orgId, params.id);
|
|
82
|
+
if (!result.ok)
|
|
83
|
+
throw new Error(result.error);
|
|
84
|
+
return result.value;
|
|
85
|
+
});
|
|
86
|
+
// GRN
|
|
87
|
+
r.post("/grn").summary("Create GRN").permission("procurement:create")
|
|
88
|
+
.input(z.object({
|
|
89
|
+
poId: z.string().uuid(), supplierId: z.string().uuid(), warehouseId: z.string().uuid(), notes: z.string().optional(),
|
|
90
|
+
items: z.array(z.object({
|
|
91
|
+
poItemId: z.string().uuid(), entityId: z.string().uuid(), variantId: z.string().uuid().optional(),
|
|
92
|
+
quantityOrdered: z.number().int(), quantityReceived: z.number().int(), quantityAccepted: z.number().int(),
|
|
93
|
+
quantityRejected: z.number().int().optional(), rejectionReason: z.string().optional(),
|
|
94
|
+
batchNumber: z.string().optional(), expiryDate: z.string().optional(), unitCost: z.number().int(),
|
|
95
|
+
})).min(1),
|
|
96
|
+
}))
|
|
97
|
+
.handler(async ({ input, actor, orgId }) => {
|
|
98
|
+
const body = input;
|
|
99
|
+
const result = await grnSvc.create(orgId, { ...body, receivedBy: actor.userId });
|
|
100
|
+
if (!result.ok)
|
|
101
|
+
throw new Error(result.error);
|
|
102
|
+
return result.value;
|
|
103
|
+
});
|
|
104
|
+
r.get("/grn").summary("List GRNs").permission("procurement:read")
|
|
105
|
+
.handler(async ({ orgId }) => {
|
|
106
|
+
const result = await grnSvc.list(orgId);
|
|
107
|
+
if (!result.ok)
|
|
108
|
+
throw new Error(result.error);
|
|
109
|
+
return result.value;
|
|
110
|
+
});
|
|
111
|
+
r.get("/grn/{id}").summary("Get GRN with items").permission("procurement:read")
|
|
112
|
+
.handler(async ({ params, orgId }) => {
|
|
113
|
+
const result = await grnSvc.getById(orgId, params.id);
|
|
114
|
+
if (!result.ok)
|
|
115
|
+
throw new Error(result.error);
|
|
116
|
+
return result.value;
|
|
117
|
+
});
|
|
118
|
+
r.post("/grn/{id}/accept").summary("Accept GRN").permission("procurement:admin")
|
|
119
|
+
.handler(async ({ params, orgId }) => {
|
|
120
|
+
const result = await grnSvc.accept(orgId, params.id);
|
|
121
|
+
if (!result.ok)
|
|
122
|
+
throw new Error(result.error);
|
|
123
|
+
return result.value;
|
|
124
|
+
});
|
|
125
|
+
return r.routes();
|
|
126
|
+
}
|