@pelygo/janus 0.2.0 → 0.4.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 CHANGED
@@ -1,263 +1,718 @@
1
1
  # @pelygo/janus
2
2
 
3
- TypeScript API client for JANUS (VCOMSUITE backend) endpoints with full type safety.
3
+ TypeScript API client for JANUS (VCOMSUITE warehouse management backend). Provides typed access to orders, returns, products, stock, invoices, ASNs, dispatches, and more.
4
4
 
5
- ## Installation
5
+ ## Setup
6
+
7
+ ### Install
6
8
 
7
9
  ```bash
8
10
  npm install @pelygo/janus @pelygo/auth
9
11
  ```
10
12
 
11
- ## Quick Start
13
+ `@pelygo/auth` is a required peer dependency. It handles token storage and refresh automatically.
14
+
15
+ ### Configure
16
+
17
+ The JANUS API server and the auth server are **different hosts**. You must provide both URLs.
12
18
 
13
19
  ```typescript
14
20
  import { createJanusApi } from '@pelygo/janus';
15
21
 
16
- // Create API instance
17
22
  const janus = createJanusApi({
18
- baseUrl: 'https://api.janus.pelygo.com',
23
+ baseUrl: 'https://api.janus.pelygo.com', // JANUS API server
24
+ authUrl: 'https://auth.pelygo.com', // Auth server (different host)
19
25
  onUnauthorized: () => {
20
- // Handle session expiry (redirect to login, etc.)
26
+ // IMPORTANT: use setTimeout in Base44/iframe environments
27
+ setTimeout(() => window.location.href = '/login', 0);
21
28
  },
22
29
  });
23
-
24
- // Authenticate
25
- await janus.auth.login('username', 'password');
26
-
27
- // Use typed resources
28
- const clients = await janus.clients.getAll();
29
- const returns = await janus.returns.query({ clientId: 5, pageSize: 50 });
30
30
  ```
31
31
 
32
- ## API Reference
33
-
34
- ### Creating the Client
32
+ **Dev environment:**
35
33
 
36
34
  ```typescript
37
35
  const janus = createJanusApi({
38
- baseUrl: string; // JANUS API base URL
39
- onUnauthorized?: () => void; // Called on 401 responses
36
+ baseUrl: 'https://api.janus.dev.pelygo.com',
37
+ authUrl: 'https://auth.dev.pelygo.com',
40
38
  });
41
39
  ```
42
40
 
43
- ### Authentication
41
+ ### Read-only mode (default)
44
42
 
45
- The client wraps `@pelygo/auth` for authentication:
43
+ By default the client is **read-only** any attempt to create, update, or delete data will throw an error. This prevents accidental writes from AI-generated code or dashboard apps that should only display data.
46
44
 
47
45
  ```typescript
48
- // Login
49
- await janus.auth.login(username, password);
46
+ // Read-only (default) — getAll, query, getById work; create, update, delete throw
47
+ const janus = createJanusApi({ baseUrl: '...' });
50
48
 
51
- // Get current user
52
- const user = await janus.auth.getUser();
49
+ // Full access — all operations allowed
50
+ const janus = createJanusApi({ baseUrl: '...', allowWrites: true });
51
+ ```
53
52
 
54
- // Logout
55
- await janus.auth.logout();
53
+ Read-only mode allows:
54
+ - All `GET` requests (list, getById, stats, reports, filter options)
55
+ - Query-style `POST` requests (`returns.queryPost`, `legacy.queryProducts`)
56
+ - Authentication (`janus.auth.login`, `janus.auth.verify`)
56
57
 
57
- // Check if authenticated
58
- const isLoggedIn = janus.auth.isAuthenticated();
58
+ Read-only mode blocks:
59
+ - `POST` create operations (e.g. `products.create`, `orders.create`)
60
+ - `PUT` / `PATCH` update operations (e.g. `clients.update`, `returns.update`)
61
+ - `DELETE` operations (e.g. `products.delete`, `users.delete`)
62
+
63
+ When a blocked operation is attempted, it throws:
64
+ ```
65
+ @pelygo/janus: Write operation blocked (POST /products).
66
+ The client is in read-only mode. To enable writes, set allowWrites: true in createJanusApi options.
59
67
  ```
60
68
 
61
- ### Clients Resource
69
+ ### Authenticate
62
70
 
63
71
  ```typescript
64
- // Get all clients
65
- const clients = await janus.clients.getAll();
72
+ await janus.auth.login('username', 'password');
73
+ const user = await janus.auth.getUser();
74
+ const isLoggedIn = janus.auth.isAuthenticated();
75
+ await janus.auth.logout();
76
+ ```
66
77
 
67
- // Get single client
68
- const client = await janus.clients.getById(5);
78
+ ---
79
+
80
+ ## All Available Resources
81
+
82
+ | Resource | Accessor | Auth | Description |
83
+ |----------|----------|------|-------------|
84
+ | Clients | `janus.clients` | Yes | Client account CRUD, courier services per client |
85
+ | Returns | `janus.returns` | Yes | Full returns management: CRUD, items, comments, statuses, reasons, categories, sources |
86
+ | Orders | `janus.orders` | Yes | Query orders, stats, filter options, create/update |
87
+ | Products | `janus.products` | Yes | Product CRUD (preferred over `legacy.queryProducts`) |
88
+ | Product Categories | `janus.productCategories` | Yes | Product category CRUD |
89
+ | Stocks | `janus.stocks` | Yes | Stock record CRUD |
90
+ | Stock Transactions | `janus.stockTransactions` | Yes | Stock transaction history |
91
+ | Stock Allocations | `janus.stockAllocations` | Yes | Stock allocation records |
92
+ | Locations | `janus.locations` | Yes | Warehouse location CRUD |
93
+ | Dispatches | `janus.dispatches` | Yes | Dispatch CRUD |
94
+ | Consignments | `janus.consignments` | Yes | Consignment records |
95
+ | Invoices | `janus.invoices` | Yes | Invoice CRUD with line items |
96
+ | ASNs | `janus.asns` | Yes | Advance Shipping Notices with lines and receipts |
97
+ | Couriers | `janus.couriers` | Yes | Courier and service management |
98
+ | Reports | `janus.reports` | Yes | Returns summary, reasons, overview reports (JSON + CSV) |
99
+ | Integrations | `janus.integrations` | Yes | Integration management |
100
+ | Users | `janus.users` | Yes | User management |
101
+ | Logs | `janus.logs` | Yes | Activity and order issue logs |
102
+ | Tasks | `janus.tasks` | Yes | Task history |
103
+ | Audits | `janus.audits` | Yes | Audit trail (read-only) |
104
+ | Legacy | `janus.legacy` | Yes | Legacy couriers, post services, products |
105
+ | Helpers | `janus.helpers` | No | Public endpoints: return portal form submission, notification settings |
106
+ | Customer | `janus.customer` | No | Public customer tracking (order lookup) |
107
+ | Auth | `janus.auth` | -- | Underlying AuthApi instance for raw requests |
108
+
109
+ ---
110
+
111
+ ## Entity Field Reference
112
+
113
+ ### Return
114
+
115
+ | Field | Type | Description |
116
+ |-------|------|-------------|
117
+ | `id` | `number` | Unique ID |
118
+ | `return_reference` | `string?` | Human-readable return reference |
119
+ | `wms_order_ref` | `string?` | Original order reference |
120
+ | `client_id` | `number?` | Client ID |
121
+ | `status_id` | `number?` | Current status ID |
122
+ | `reason_id` | `number?` | Return reason ID |
123
+ | `customername` | `string?` | Full customer name |
124
+ | `forename` | `string?` | First name |
125
+ | `surname` | `string?` | Last name |
126
+ | `email` | `string?` | Customer email |
127
+ | `tracking_number` | `string?` | Courier tracking number |
128
+ | `tracking_url` | `string?` | Tracking page URL |
129
+ | `approval_status` | `'pending' \| 'approved' \| 'rejected'?` | Approval state |
130
+ | `approved` | `boolean?` | Whether approved |
131
+ | `approved_ts` | `string?` | Approval timestamp |
132
+ | `line_1` | `string?` | Address line 1 |
133
+ | `line_2` | `string?` | Address line 2 |
134
+ | `line_3` | `string?` | Address line 3 |
135
+ | `city` | `string?` | City |
136
+ | `county` | `string?` | County/state |
137
+ | `country` | `string?` | Country name |
138
+ | `country_code` | `string?` | ISO country code (e.g. `"GB"`) |
139
+ | `postcode` | `string?` | Postal code |
140
+ | `company` | `string?` | Company name |
141
+ | `wms_service_cost` | `number?` | WMS service cost |
142
+ | `courier_service_cost` | `number?` | Courier service cost |
143
+ | `courier_service_id` | `number?` | Assigned courier service ID |
144
+ | `items` | `ReturnItem[]?` | Line items (when included) |
145
+ | `statuses` | `ReturnStatusHistory[]?` | Status history (when included) |
146
+ | `comments` | `ReturnComment[]?` | Comments (when included) |
147
+ | `latestStatus` | `ReturnStatusHistory?` | Most recent status |
148
+ | `created_ts` | `string?` | ISO timestamp |
149
+ | `updated_ts` | `string?` | ISO timestamp |
150
+
151
+ ### LegacyOrder
152
+
153
+ | Field | Type | Description |
154
+ |-------|------|-------------|
155
+ | `id` | `number` | Primary key |
156
+ | `customerId` | `number?` | FK to customer |
157
+ | `reference` | `string?` | Order reference string |
158
+ | `source` | `number?` | Source ID |
159
+ | `orderDate` | `string?` | ISO date the order was placed |
160
+ | `dispatchCompletedDate` | `string?` | ISO date all dispatches completed |
161
+ | `orderStatus` | `number?` | Numeric status code |
162
+ | `postServiceId` | `number?` | FK to shipping service |
163
+ | `courierId` | `number?` | FK to courier |
164
+ | `actualWeight` | `number?` | Measured weight |
165
+ | `notes` | `string?` | Internal notes |
166
+ | `hold` | `unknown?` | Whether order is on hold |
167
+ | `releaseDate` | `string?` | ISO date when hold was released |
168
+ | `draft` | `unknown?` | Whether order is a draft |
169
+ | `giftMessage` | `string?` | Gift message text |
170
+ | `deliveryInstructions` | `string?` | Delivery instructions |
171
+ | `orderIssue` | `number?` | Issue flag |
172
+ | `deliveryTerms` | `string?` | Incoterms code (e.g. `"DAP"`) |
173
+ | `transaction_type` | `'b2c' \| 'b2b' \| 'internal_transfer' \| 'damage' \| 'expiry'` | **Required.** Transaction classification |
174
+ | `external_order_id` | `string?` | External system order ID |
175
+ | `channel_order_id` | `string?` | Sales channel order ID |
176
+ | `channel` | `string?` | Sales channel name |
177
+ | `created` | `string?` | ISO created timestamp |
178
+ | `modified` | `string?` | ISO modified timestamp |
179
+ | `status` | `number?` | Record status (0 = inactive, 1 = active) |
180
+ | `orderItems` | `LegacyOrderItem[]` | Line items |
181
+ | `orderMeta` | `LegacyOrderMeta[]` | Metadata key-value pairs |
182
+ | `shopperDeliveryAddress` | `LegacyShopperAddress` | Delivery address |
183
+ | `shopperBillingAddress` | `LegacyShopperAddress` | Billing address |
184
+ | `dispatches` | `LegacyDispatch[]` | All dispatches for this order |
185
+ | `dispatch` | `LegacyDispatch` | Primary dispatch |
186
+ | `orderSource` | `Source` | Source reference data |
187
+
188
+ ### LegacyDispatch
189
+
190
+ | Field | Type | Description |
191
+ |-------|------|-------------|
192
+ | `id` | `number` | Primary key |
193
+ | `orderId` | `number?` | FK to parent order |
194
+ | `reference` | `string?` | Client-facing reference code |
195
+ | `dispatchDate` | `string?` | ISO date shipped |
196
+ | `dispatchStatus` | `number?` | 0 = pending, 1 = dispatched, 2 = cancelled |
197
+ | `trackingNumber` | `string?` | Carrier tracking number |
198
+ | `actualWeight` | `number?` | Measured weight |
199
+ | `courierId` | `number?` | FK to courier |
200
+ | `postServiceId` | `number?` | FK to shipping service |
201
+ | `notes` | `string?` | Internal notes |
202
+ | `hold` | `unknown?` | 0 = not held, 1 = on hold |
203
+ | `draft` | `unknown?` | 0 = finalised, 1 = draft |
204
+ | `deliveryInstructions` | `string?` | Special delivery instructions |
205
+ | `dispatchActionId` | `number?` | Current action (pick, pack, ship) |
206
+ | `dispatchActionStatus` | `number?` | 0 = not started, 1 = in progress, 2 = completed |
207
+ | `stockStatus` | `number?` | 0 = unallocated, 1 = partial, 2 = fully allocated |
208
+ | `dispatchIssue` | `number?` | 0 = no issue, 1 = issue flagged |
209
+ | `priority` | `number?` | Higher = higher priority |
210
+ | `complete` | `number?` | 0 = incomplete, 1 = complete |
211
+ | `complete_date` | `string?` | ISO date marked complete |
212
+ | `deliveryTerms` | `string?` | Incoterms code |
213
+ | `created` | `string?` | ISO created timestamp |
214
+ | `modified` | `string?` | ISO modified timestamp |
215
+ | `status` | `number?` | 0 = inactive/deleted, 1 = active |
216
+ | `dispatchItems` | `LegacyDispatchItem[]` | Line items in this dispatch |
217
+ | `shopperDeliveryAddress` | `LegacyShopperAddress` | Delivery address |
218
+ | `consignment` | `Consignment` | Consignment record |
219
+ | `postService` | `LegacyPostService` | Shipping service details |
220
+
221
+ ### FormattedProduct
222
+
223
+ Returned by `janus.products.getAll()`. Field names are snake_case (different from the raw `LegacyProduct` entity).
224
+
225
+ | Field | Type | Description |
226
+ |-------|------|-------------|
227
+ | `product_id` | `number` | Primary key |
228
+ | `client_id` | `number` | FK to owning client |
229
+ | `sku_code` | `string?` | SKU -- unique product identifier within the client |
230
+ | `product_name` | `string?` | Human-readable name |
231
+ | `description` | `string?` | Extended description |
232
+ | `additional_skus` | `string?` | Comma-separated alternative SKUs |
233
+ | `asins` | `string?` | Amazon Standard Identification Numbers |
234
+ | `custom_fields` | `Record<string, unknown>?` | Client-defined key-value pairs |
235
+ | `hs_code` | `string?` | Harmonized System code for customs |
236
+ | `unit_of_measure` | `string?` | e.g. `"each"`, `"kg"`, `"litre"` |
237
+ | `eaches_per_pack` | `number?` | Units per case pack |
238
+ | `case_pack_sku_code` | `string?` | SKU of the case pack |
239
+ | `weight_grams` | `number?` | Unit weight in grams |
240
+ | `barcode` | `string?` | Primary EAN/UPC barcode |
241
+ | `case_pack_barcode` | `string?` | Case pack barcode |
242
+ | `product_category_id` | `number?` | FK to product category |
243
+ | `product_type` | `string?` | e.g. `"physical"`, `"digital"`, `"service"` |
244
+ | `sellable_format` | `string?` | e.g. `"single"`, `"pack"`, `"case"` |
245
+ | `commodity_description` | `string?` | Customs commodity description |
246
+ | `price` | `number?` | Unit sale price |
247
+ | `currency` | `string?` | ISO 4217 code (e.g. `"GBP"`) |
248
+ | `country_of_origin` | `string?` | ISO 3166-1 alpha-2 (e.g. `"CN"`) |
249
+ | `primary_material` | `string?` | Primary material |
250
+ | `secondary_material` | `string?` | Secondary material |
251
+ | `dangerous_goods` | `string?` | DG classification / UN number |
252
+ | `image_url` | `string?` | Product image URL |
253
+ | `low_stock_threshold` | `number?` | Low-stock alert threshold |
254
+ | `requires_batch_tracking` | `number?` | 0 = no, 1 = yes |
255
+ | `requires_serial_tracking` | `number?` | 0 = no, 1 = yes |
256
+ | `requires_bbe_tracking` | `number?` | 0 = no, 1 = yes (best-before/expiry) |
257
+ | `is_bundle` | `number?` | 0 = standalone, 1 = bundle |
258
+ | `bundle_components` | `string?` | Component SKUs and quantities |
259
+ | `default_stock_status` | `string?` | Status assigned on receipt |
260
+ | `product_status` | `string?` | e.g. `"active"`, `"discontinued"` |
261
+ | `status` | `number?` | 0 = inactive/deleted, 1 = active |
262
+ | `created_date` | `string?` | ISO created timestamp |
263
+ | `updated_date` | `string?` | ISO updated timestamp |
264
+
265
+ ### Stock
266
+
267
+ Extends `BaseEntity` (adds `id`, `created_by`, `updated_by`, `created_ts`, `updated_ts`, `partition_id`).
268
+
269
+ | Field | Type | Description |
270
+ |-------|------|-------------|
271
+ | `id` | `number` | Primary key (from BaseEntity) |
272
+ | `clientId` | `number?` | FK to owning client |
273
+ | `sku_code` | `string?` | SKU identifying the product |
274
+ | `location` | `string?` | Location code (e.g. `"A-01-03-B"`) |
275
+ | `quantity` | `number?` | Units at this location |
276
+ | `batch_number` | `string?` | Batch/lot number |
277
+ | `serial_number` | `string?` | Serial number |
278
+ | `best_before_date` | `string?` | ISO expiry date (FEFO rotation) |
279
+ | `received_date` | `string?` | ISO date received into warehouse |
280
+ | `stock_status` | `'available' \| 'quarantined' \| 'expired' \| 'damaged' \| 'reserved'` | **Required.** Current status |
281
+ | `fulfilment_location_id` | `number?` | FK to fulfilment centre |
282
+ | `fulfilment_location_name` | `string?` | Fulfilment centre name |
283
+ | `fulfilment_location_country` | `string?` | ISO country code |
284
+ | `notes` | `string?` | Free-text notes |
285
+ | `created_ts` | `string?` | ISO created timestamp (from BaseEntity) |
286
+ | `updated_ts` | `string?` | ISO updated timestamp (from BaseEntity) |
287
+
288
+ ### Location
289
+
290
+ Extends `BaseEntity`.
291
+
292
+ | Field | Type | Description |
293
+ |-------|------|-------------|
294
+ | `id` | `number` | Primary key |
295
+ | `clientId` | `number?` | FK to owning client |
296
+ | `location_code` | `string?` | Unique code (e.g. `"A-01-03-B"`) |
297
+ | `location_type` | `'pick' \| 'bulk' \| 'staging' \| 'receiving' \| 'shipping' \| 'quarantine' \| 'returns'` | **Required.** Location role |
298
+ | `zone` | `string?` | Warehouse zone (e.g. `"A"`) |
299
+ | `aisle` | `string?` | Aisle identifier |
300
+ | `bay` | `string?` | Bay/rack identifier |
301
+ | `level` | `string?` | Vertical level/shelf |
302
+ | `position` | `string?` | Horizontal position |
303
+ | `fulfilment_location_id` | `number?` | FK to fulfilment centre |
304
+ | `fulfilment_location_name` | `string?` | Fulfilment centre name |
305
+ | `current_units` | `number?` | Stock units currently held |
306
+ | `max_capacity_units` | `number?` | Maximum unit capacity |
307
+ | `max_capacity_weight_kg` | `number?` | Maximum weight (kg) |
308
+ | `is_mixed_sku_allowed` | `number?` | 0 = single SKU only, 1 = mixed |
309
+ | `is_active` | `number?` | 0 = decommissioned, 1 = in use |
310
+ | `is_pickable` | `number?` | 0 = no, 1 = yes |
311
+ | `is_receivable` | `number?` | 0 = no, 1 = yes |
312
+ | `temperature_zone` | `'ambient' \| 'chilled' \| 'frozen'` | **Required.** Temperature classification |
313
+ | `notes` | `string?` | Free-text notes |
314
+
315
+ ### Invoice
316
+
317
+ Extends `BaseEntity`.
318
+
319
+ | Field | Type | Description |
320
+ |-------|------|-------------|
321
+ | `id` | `number` | Primary key |
322
+ | `clientId` | `number?` | FK to client |
323
+ | `invoice_date` | `string` | **Required.** ISO invoice date |
324
+ | `xero_reference` | `string?` | Xero accounting reference |
325
+ | `total` | `number?` | Total invoice amount |
326
+ | `emailed` | `number?` | 0 = not emailed, 1 = emailed |
327
+ | `paid` | `number?` | 0 = unpaid, 1 = paid |
328
+ | `fuel_mode` | `number?` | 0 = flat rate, 1 = percentage |
329
+ | `status` | `number?` | 0 = inactive/deleted, 1 = active |
330
+ | `invoiceLines` | `InvoiceLine[]?` | Line items |
331
+
332
+ ### InvoiceLine
333
+
334
+ Extends `BaseEntity`.
335
+
336
+ | Field | Type | Description |
337
+ |-------|------|-------------|
338
+ | `id` | `number` | Primary key |
339
+ | `invoice_id` | `number?` | FK to parent invoice |
340
+ | `class_id` | `number?` | Charge classification (storage, fulfilment, shipping) |
341
+ | `object_id` | `number?` | FK to billable object (dispatch ID, etc.) |
342
+ | `reference` | `string` | **Required.** Client-facing reference |
343
+ | `description` | `string?` | Charge description |
344
+ | `dispatch_date` | `string?` | ISO dispatch date |
345
+ | `service_name` | `string?` | Shipping service name |
346
+ | `price` | `number?` | Unit price |
347
+ | `discount_rate` | `number?` | Discount percentage |
348
+ | `postcode` | `string?` | Destination postcode |
349
+ | `country_code` | `string?` | ISO destination country |
350
+ | `tax_class` | `number?` | 0 = zero-rated, 1 = standard, 2 = exempt |
351
+ | `consignment_price` | `number?` | Carrier cost before margin |
352
+ | `fuel` | `number?` | Fuel surcharge |
353
+ | `packaging_price` | `number?` | Packaging material cost |
354
+ | `discount` | `number?` | Calculated discount amount |
355
+ | `lines` | `number?` | Number of order lines |
356
+ | `picks` | `number?` | Number of picks |
357
+ | `weight` | `number?` | Total weight (kg) |
358
+ | `margin_charged` | `number?` | Margin on carrier cost |
359
+ | `total_override` | `number?` | Manual total override |
360
+ | `status` | `number?` | 0 = inactive/deleted, 1 = active |
361
+
362
+ ### ASN (Advance Shipping Notice)
363
+
364
+ Extends `BaseEntity`.
365
+
366
+ | Field | Type | Description |
367
+ |-------|------|-------------|
368
+ | `id` | `number` | Primary key |
369
+ | `clientId` | `number` | **Required.** FK to client |
370
+ | `asn_status` | `string` | **Required.** One of: `draft`, `awaiting_arrival`, `arrived`, `processing`, `receipted`, `receipted_with_discrepancies`, `temporarily_receipted`, `quarantined`, `cancelled` |
371
+ | `expected_arrival_date` | `string?` | ISO expected arrival date |
372
+ | `total_expected_units` | `number?` | Total expected units |
373
+ | `asnLines` | `AsnLine[]` | Line items |
374
+
375
+ ### AsnLine
376
+
377
+ Extends `BaseEntity`.
378
+
379
+ | Field | Type | Description |
380
+ |-------|------|-------------|
381
+ | `id` | `number` | Primary key |
382
+ | `asn_id` | `number` | **Required.** FK to parent ASN |
383
+ | `product_id` | `number?` | FK to product |
384
+ | `sku_code` | `string` | **Required.** SKU code |
385
+ | `expected_quantity` | `number` | **Required.** Expected unit count |
386
+ | `received_quantity` | `number?` | Actual received count |
387
+ | `asnReceipts` | `AsnReceipt[]` | Receipt records |
388
+
389
+ ### AsnReceipt
390
+
391
+ Extends `BaseEntity`.
392
+
393
+ | Field | Type | Description |
394
+ |-------|------|-------------|
395
+ | `id` | `number` | Primary key |
396
+ | `asn_id` | `number` | **Required.** FK to ASN |
397
+ | `asn_line_id` | `number` | **Required.** FK to ASN line |
398
+ | `received_quantity` | `number` | **Required.** Quantity received |
399
+ | `stock_status` | `'available' \| 'quarantined' \| 'expired' \| 'damaged' \| 'reserved'` | **Required.** Status of received stock |
400
+
401
+ ---
402
+
403
+ ## Query Filter Reference
404
+
405
+ ### PaginatedQueryFilters (v2 endpoints: products, stocks, locations, invoices, ASNs, dispatches)
406
+
407
+ | Param | Type | Required | Description |
408
+ |-------|------|----------|-------------|
409
+ | `clientId` | `number` | **Yes** | Client ID from `useClient()` |
410
+ | `pageSize` | `number` | No | Results per page (default: 25) |
411
+ | `pageNumber` | `number` | No | Page number, 1-based (default: 1) |
412
+ | `orderColumn` | `string` | No | Column to sort by (e.g. `'id'`, `'created'`) |
413
+ | `orderDirection` | `'ASC' \| 'DESC'` | No | Sort direction (default: DESC) |
414
+ | `query` | `string` | No | Free-text search across key fields |
415
+
416
+ ### ReturnsQueryFilters (janus.returns.query)
417
+
418
+ | Param | Type | Required | Description |
419
+ |-------|------|----------|-------------|
420
+ | `clientId` | `number` | **Yes** | Client ID |
421
+ | `page` | `number` | No | Page number |
422
+ | `pageSize` | `number` | No | Results per page |
423
+ | `created_at` | `string` | No | Start date (ISO, e.g. `'2025-01-01'`) |
424
+ | `created_to` | `string` | No | End date (ISO) |
425
+ | `last_status_ids` | `string` | No | Comma-separated status IDs to match current status |
426
+ | `include_status_ids` | `string` | No | Include returns with any of these status IDs |
427
+ | `exclude_status_ids` | `string` | No | Exclude returns with these status IDs |
428
+ | `processed_at` | `string` | No | Processed start date (ISO) |
429
+ | `processed_to` | `string` | No | Processed end date (ISO) |
430
+ | `include_associations` | `string` | No | Nested data: `'return_items'`, `'return_comments'`, or comma-separated |
431
+ | `csv` | `boolean` | No | Export as CSV instead of JSON |
432
+ | `group_by` | `string` | No | Group results by field |
433
+
434
+ ### OrdersQueryFilters (janus.orders.getAll)
435
+
436
+ | Param | Type | Required | Description |
437
+ |-------|------|----------|-------------|
438
+ | `clientId` | `number` | **Yes** | Client ID |
439
+ | `page` | `number` | No | Page number |
440
+ | `pageSize` | `number` | No | Results per page |
441
+ | `order_column` | `string` | No | Sort column |
442
+ | `order_direction` | `'ASC' \| 'DESC'` | No | Sort direction |
443
+ | `created_at` | `string` | No | Start date (ISO) |
444
+ | `created_to` | `string` | No | End date (ISO) |
445
+ | `channel` | `string` | No | Filter by sales channel |
446
+ | `destination` | `string` | No | Filter by destination |
447
+ | `courier` | `string` | No | Filter by courier name |
448
+ | `service` | `string` | No | Filter by courier service |
449
+ | `status` | `string` | No | Filter by order status |
450
+ | `query` | `string` | No | Free-text search |
451
+ | `include_all_stages` | `boolean` | No | Include all order stages |
452
+
453
+ ### ReturnsSummaryFilters (janus.reports.returnsSummary / returnsSummaryCsv)
454
+
455
+ | Param | Type | Required | Description |
456
+ |-------|------|----------|-------------|
457
+ | `clientId` | `number` | **Yes** | Client ID |
458
+ | `createdFrom` | `string` | No | Start date (ISO) |
459
+ | `createdTo` | `string` | No | End date (ISO) |
460
+ | `approvedFrom` | `string` | No | Approval start date |
461
+ | `approvedTo` | `string` | No | Approval end date |
462
+ | `courierServiceId` | `number` | No | Filter by courier service |
463
+ | `approved` | `boolean` | No | Only approved returns |
464
+ | `isClientsReport` | `boolean` | No | Format as client-level report |
465
+ | `page` | `number` | No | Page number |
466
+ | `pageSize` | `number` | No | Results per page |
467
+
468
+ ### ReturnsReasonsFilters (janus.reports.returnsReasons / returnsReasonsCsv)
469
+
470
+ | Param | Type | Required | Description |
471
+ |-------|------|----------|-------------|
472
+ | `clientId` | `number` | **Yes** | Client ID |
473
+ | `createdFrom` | `string` | No | Start date (ISO) |
474
+ | `createdTo` | `string` | No | End date (ISO) |
475
+
476
+ ### OrdersStatsFilters (janus.orders.getStats)
477
+
478
+ | Param | Type | Required | Description |
479
+ |-------|------|----------|-------------|
480
+ | `clientId` | `number` | **Yes** | Client ID |
481
+ | `created_at` | `string` | No | Start date (ISO) |
482
+ | `created_to` | `string` | No | End date (ISO) |
483
+ | `dashboard` | `boolean` | No | Return dashboard-formatted stats (recommended: `true`) |
484
+
485
+ ### Resource-specific extra filters
486
+
487
+ | Resource method | Extra param | Type | Description |
488
+ |----------------|-----------|------|-------------|
489
+ | `janus.stockTransactions.getAll` | `stockId` | `number` | Filter by stock record |
490
+ | `janus.stockAllocations.getAll` | `orderId` | `number` | Filter by order |
491
+ | `janus.dispatches.getAll` | `orderId` | `number` | Filter by order |
492
+ | `janus.audits.getAll` | `filter` | `string` | Additional filter |
493
+ | `janus.users.getAll` | `filterRole` | `string` | Filter by user role |
494
+ | `janus.users.getAll` | `filterType` | `string` | Filter by user type |
495
+ | `janus.users.getAll` | `filterClientId` | `number` | Filter by assigned client |
496
+
497
+ ---
498
+
499
+ ## Common Patterns
500
+
501
+ ### Querying with Filters and Pagination
502
+
503
+ **v2 endpoints** (products, stocks, locations, invoices, ASNs, dispatches) use `ClientScopedPaginatedQueryFilters`:
69
504
 
70
- // Update client
71
- const updated = await janus.clients.update(5, { name: 'New Name' });
505
+ ```typescript
506
+ const { data, meta } = await janus.products.getAll({
507
+ clientId: 5, // REQUIRED
508
+ pageSize: 50,
509
+ pageNumber: 1,
510
+ orderColumn: 'sku_code',
511
+ orderDirection: 'ASC',
512
+ query: 'widget', // free-text search
513
+ });
72
514
 
73
- // Get courier services for client
74
- const services = await janus.clients.getCourierServices(5);
75
- const service = await janus.clients.getCourierServiceById(5, serviceId);
76
- await janus.clients.updateCourierService(5, serviceId, { active: true });
515
+ // meta = { total_items, total_pages, current_page, page_size }
77
516
  ```
78
517
 
79
- ### Returns Resource
518
+ **Legacy endpoints** (orders, returns) use their own filter shapes:
80
519
 
81
520
  ```typescript
82
- // Query returns with filters
83
521
  const returns = await janus.returns.query({
84
522
  clientId: 5,
85
523
  last_status_ids: '1,2,3',
86
524
  created_at: '2024-01-01',
87
525
  created_to: '2024-12-31',
88
526
  pageSize: 100,
527
+ csv: false,
89
528
  });
90
529
 
91
- // Get single return with items
92
- const ret = await janus.returns.getById(123, { includeAssociations: true });
93
-
94
- // Create a return
95
- const newReturn = await janus.returns.create({
96
- client_id: 5,
97
- customer_name: 'John Doe',
98
- // ...
530
+ const orders = await janus.orders.getAll({
531
+ clientId: 5,
532
+ created_at: '2024-01-01',
533
+ created_to: '2024-12-31',
534
+ status: 'dispatched',
535
+ order_column: 'created',
536
+ order_direction: 'DESC',
99
537
  });
538
+ ```
100
539
 
101
- // Update a return
102
- await janus.returns.update(123, { customer_name: 'Jane Doe' });
103
-
104
- // Delete a return
105
- await janus.returns.delete(123);
106
-
107
- // Manage statuses
108
- await janus.returns.addStatus(123, statusId);
109
- await janus.returns.removeStatus(123, statusId);
110
- const statuses = await janus.returns.getStatuses(123);
111
-
112
- // Manage comments
113
- await janus.returns.addComment(123, 'Comment text');
114
- await janus.returns.removeComment(123, commentId);
115
- const comments = await janus.returns.getComments(123);
116
-
117
- // Manage items
118
- const items = await janus.returns.getItems(123);
119
- await janus.returns.addItem(123, { wms_product_sku: 'SKU123', qty: 2 });
120
- await janus.returns.updateItem(123, itemId, { qty: 3 });
540
+ ### CSV Export
121
541
 
122
- // Approval workflow
123
- await janus.returns.setApproval(123, true); // Approve
124
- await janus.returns.setApproval(123, false); // Reject
542
+ Reports have dedicated CSV methods:
125
543
 
126
- // Reference data
127
- const reasons = await janus.returns.getReasons();
128
- const allStatuses = await janus.returns.getAllStatuses();
129
- const filterOptions = await janus.returns.getFilterOptions(5);
544
+ ```typescript
545
+ const csvString = await janus.reports.returnsSummaryCsv({ clientId: 5 });
546
+ const reasonsCsv = await janus.reports.returnsReasonsCsv({ clientId: 5 });
547
+ const overviewCsv = await janus.reports.returnsOverviewCsv({ clientId: 5 });
130
548
  ```
131
549
 
132
- ### Reports Resource
550
+ Returns can also export as CSV via the query filter:
133
551
 
134
552
  ```typescript
135
- // Returns summary report
136
- const summary = await janus.reports.returnsSummary({
137
- clientId: 5,
138
- created_at: '2024-01-01',
139
- created_to: '2024-12-31',
140
- groupBy: 'month',
141
- });
142
-
143
- // CSV export
144
- const csvData = await janus.reports.returnsSummaryCsv({ clientId: 5 });
553
+ const csv = await janus.returns.query({ clientId: 5, csv: true });
554
+ ```
145
555
 
146
- // Returns by reason
147
- const reasonReport = await janus.reports.returnsReasons({ clientId: 5 });
148
- const reasonCsv = await janus.reports.returnsReasonsCsv({ clientId: 5 });
556
+ ### CRUD on Sub-Resources
149
557
 
150
- // Returns overview
151
- const overview = await janus.reports.returnsOverview({ clientId: 5 });
152
- const overviewCsv = await janus.reports.returnsOverviewCsv({ clientId: 5 });
558
+ Invoices, ASNs, and returns have nested sub-resources:
153
559
 
154
- // Statistics
155
- const stats = await janus.reports.returnsStats(5);
560
+ ```typescript
561
+ // Invoice lines
562
+ const { data: lines } = await janus.invoices.getLines(invoiceId, { clientId: 5 });
563
+ await janus.invoices.createLine(invoiceId, lineData);
564
+ await janus.invoices.updateLine(invoiceId, lineId, updatedData);
565
+
566
+ // ASN lines and receipts
567
+ const { data: asnLines } = await janus.asns.getLines(asnId, { clientId: 5 });
568
+ await janus.asns.createReceipt(asnId, lineId, receiptData);
569
+
570
+ // Return items, comments, statuses
571
+ const items = await janus.returns.getItems(returnId);
572
+ await janus.returns.addComment(returnId, 'Inspected, item damaged');
573
+ await janus.returns.addStatus(returnId, statusId);
156
574
  ```
157
575
 
158
- ### Helpers Resource (Public Endpoints)
576
+ ### Paginated Response Shape
159
577
 
160
- ```typescript
161
- // Submit return portal form (no auth required)
162
- const result = await janus.helpers.submitReturnPortalForm(formData);
578
+ v2 endpoints return:
163
579
 
164
- // Get notification settings
165
- const settings = await janus.helpers.getNotificationSettings(clientId);
580
+ ```typescript
581
+ {
582
+ data: T[],
583
+ meta: {
584
+ total_items: number,
585
+ total_pages: number,
586
+ current_page: number,
587
+ page_size: number,
588
+ }
589
+ }
166
590
  ```
167
591
 
168
- ## Types
592
+ ### Raw API Access
169
593
 
170
- All entity types are exported and can be imported:
594
+ For endpoints not yet wrapped:
171
595
 
172
596
  ```typescript
173
- import type {
174
- Client,
175
- Return,
176
- ReturnItem,
177
- ReturnStatus,
178
- ReturnReason,
179
- User,
180
- // ... more types
181
- } from '@pelygo/janus';
597
+ const result = await janus.auth.get('/some/endpoint?clientId=5');
598
+ const result = await janus.auth.post('/some/endpoint', { body: 'data' });
182
599
  ```
183
600
 
184
- ### Filter Types
601
+ ---
185
602
 
186
- ```typescript
187
- import type { ReturnsQueryParams, ReportFilters } from '@pelygo/janus';
603
+ ## Important Rules
188
604
 
189
- const filters: ReturnsQueryParams = {
190
- clientId: 5,
191
- last_status_ids: '1,2',
192
- pageSize: 50,
193
- };
194
- ```
605
+ 1. **Always pass `clientId`.**
606
+ Get it from `useClient()` or equivalent. Never omit it.
607
+ - New v2 endpoints (`products`, `stocks`, `locations`, `invoices`, `asns`, `dispatches`) **reject with 400** if `clientId` is missing.
608
+ - Older endpoints (`orders`, `returns`) silently return **all clients' data** if omitted -- this is a data leak, not a feature.
195
609
 
196
- ## Development
610
+ 2. **Use the exact field names from this README.**
611
+ Field names differ between entities (e.g. `sku_code` not `sku`, `product_name` not `name`, `price` not `value`). Do not guess -- refer to the tables above.
197
612
 
198
- ```bash
199
- # Install dependencies
200
- npm install
613
+ 3. **Navigation in Base44/iframe environments must use `setTimeout`.**
614
+ Direct `window.location` or router calls inside callbacks (like `onUnauthorized`) will fail silently in iframed environments:
615
+ ```typescript
616
+ onUnauthorized: () => {
617
+ setTimeout(() => window.location.href = '/login', 0);
618
+ },
619
+ ```
201
620
 
202
- # Run tests
203
- npm run test
621
+ 4. **`baseUrl` and `authUrl` are different servers.**
622
+ Do not pass the same URL for both. The JANUS API server handles data; the auth server handles tokens.
204
623
 
205
- # Run tests once
206
- npm run test:run
624
+ 5. **Use `janus.products` instead of `janus.legacy.queryProducts`.**
625
+ The `products` resource uses the newer v2 endpoint with proper pagination and typed responses.
207
626
 
208
- # Type check
209
- npm run typecheck
627
+ ---
210
628
 
211
- # Build
212
- npm run build
629
+ ## API Behaviour Notes
213
630
 
214
- # Generate entity types from v2 TypeORM entities
215
- npm run generate-types
631
+ ### Error responses
632
+ ```json
633
+ { "statusCode": 400, "message": "Error description" }
216
634
  ```
635
+ Common codes: 400 (bad request/validation), 401 (unauthorized), 404 (not found).
217
636
 
218
- ### Integration Tests
637
+ ### Date formats
638
+ - **Query params:** ISO 8601 format (`'2025-01-01'` or `'2025-01-01 12:00:00'`)
639
+ - **Response fields:** ISO 8601 timestamps (e.g. `created_ts`, `orderDate`, `created`)
640
+ - **CSV exports:** formatted as `DD-MM-YYYY HH:mm`
219
641
 
220
- To run integration tests against a real JANUS API, set environment variables:
642
+ ### Pagination defaults
643
+ - Default `pageSize`: 50 (v2 endpoints)
644
+ - Default `pageNumber`: 1 (1-based indexing)
645
+ - Default sort: DESC (newest first)
646
+ - Max sensible page size: 1,000
221
647
 
222
- ```bash
223
- export JANUS_API_URL="https://api.janus.dev.pelygo.com"
224
- export JANUS_TEST_USERNAME="testuser"
225
- export JANUS_TEST_PASSWORD="testpass"
226
- export JANUS_TEST_CLIENT_ID="5"
648
+ ### Search behaviour (`query` param)
649
+ Free-text search is case-insensitive LIKE matching across key text columns:
650
+ - **Products:** `sku`, `name`, `description`
651
+ - **Returns:** order references, tracking numbers
652
+ - **Clients:** client names
653
+ - **Orders:** order references, channel IDs
227
654
 
228
- npm run test:integration
229
- ```
655
+ ### Returns-specific behaviour
656
+ - **Archived returns** (status ID 10) are excluded by default — pass `include_archived: true` to include
657
+ - **Return types:** `0` = regular return, `1` = exchange — filter with `return_types: '0'` or `'1'`
658
+ - **Associations:** `include_associations: 'return_items'` loads line items; `'return_items,return_comments'` loads both
659
+ - **Approval statuses:** `'pending'`, `'approved'`, `'rejected'`
230
660
 
231
- ## Architecture
661
+ ### Stats responses
662
+ `getStats()` endpoints return flat objects with numeric values. **Keys vary by client data.** Always use `Object.entries(stats)` to iterate — never hardcode expected keys.
232
663
 
233
- ```
234
- src/
235
- ├── index.ts # Main exports
236
- ├── client.ts # createJanusApi() factory
237
- ├── types/
238
- │ ├── entities/ # Generated entity interfaces
239
- │ ├── filters.ts # Query filter types
240
- │ └── responses.ts # API response types
241
- ├── resources/
242
- │ ├── clients.ts # Clients API
243
- │ ├── returns.ts # Returns API
244
- │ ├── reports.ts # Reports API
245
- │ └── helpers.ts # Public endpoints
246
- └── utils/
247
- └── query-builder.ts # URL query string builder
664
+ ---
665
+
666
+ ## Type Imports
667
+
668
+ ```typescript
669
+ import type {
670
+ // Entities
671
+ Return, ReturnItem, ReturnStatus, ReturnReason, ReturnComment,
672
+ LegacyOrder, LegacyDispatch, LegacyOrderItem, LegacyDispatchItem,
673
+ LegacyShopperAddress,
674
+ FormattedProduct,
675
+ Stock,
676
+ Location,
677
+ Invoice, InvoiceLine,
678
+ Asn, AsnLine, AsnReceipt,
679
+ Client,
680
+
681
+ // Filters
682
+ ReturnsQueryFilters,
683
+ OrdersQueryFilters,
684
+ ClientScopedPaginatedQueryFilters,
685
+ PaginatedQueryFilters,
686
+ ReturnsSummaryFilters,
687
+
688
+ // Responses
689
+ PaginatedListResponse,
690
+ PaginatedResponse,
691
+ PaginationMeta,
692
+ ReturnsStatsResponse,
693
+ FilterOptionsResponse,
694
+
695
+ // API types
696
+ JanusApi,
697
+ JanusApiOptions,
698
+ } from '@pelygo/janus';
248
699
  ```
249
700
 
250
- ## Base44 Integration
701
+ ---
251
702
 
252
- For Base44 applications, see the **[Base44 Integration Guide](docs/BASE44_GUIDE.md)** which includes:
703
+ ## Development
253
704
 
254
- - Installation and setup for Base44
255
- - AI prompt examples for generating pages
256
- - Common patterns and code snippets
705
+ ```bash
706
+ npm run build # Build with Vite
707
+ npm run typecheck # TypeScript check
708
+ npm run test # Run tests with Vitest
709
+ npm run test:run # Run tests once
710
+ npm run generate-types # Generate entity types from v2 TypeORM entities
711
+ ```
257
712
 
258
713
  ## Related Packages
259
714
 
260
- - [@pelygo/auth](https://www.npmjs.com/package/@pelygo/auth) - Authentication client (peer dependency)
715
+ - [@pelygo/auth](https://www.npmjs.com/package/@pelygo/auth) -- Authentication client (peer dependency)
261
716
 
262
717
  ## License
263
718