u
This commit is contained in:
2
API.md
2
API.md
@@ -209,7 +209,7 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
||||
- [order](API.order.md)
|
||||
- `create(googleAuthId: number, body: {number: string, date: string, entityId: number, externalNumber?: string, lines?: Array<{sku?: string, name?: string, pu?: number, amount: number, net: number, rebate?: number, taxrate: number, comment?: string|null, shippable?: boolean}>}, tx?: object): Promise<{id: number, number: string, type: string, entityId: number, externalNumber: string|null, date: string, lines: Array<{id: number, sku: string|null, name: string|null, pu: number, amount: number, net: number, rebate: number, taxrate: number, comment: string|null}>}>`
|
||||
- `createFromQuote(googleAuthId: number, quoteId: number, body?: {number?: string, date?: string, createdAt?: string, entityId?: number, externalNumber?: string, lines?: Array<{sku?: string, name?: string, pu?: number, amount: number, net: number, rebate?: number, taxrate: number, comment?: string|null, shippable?: boolean}>}, tx?: object): Promise<{id: number, number: string, type: string, entityId: number|null, externalNumber: string|null, date: string, lines: Array<{id: number, sku: string|null, name: string|null, pu: number, amount: number, net: number, rebate: number, taxrate: number, comment: string|null}>}>`
|
||||
- `createShipment(googleAuthId: number, orderId: number, input: {lines: Array<{sku: string, amount?: number, name?: string, pu?: number, net?: number, rebate?: number, taxrate?: number, comment?: string}>, date?: string|Date, externalNumber?: string}, tx?: object): Promise<object>`
|
||||
- `createShipment(googleAuthId: number, orderId: number, input: {lines: Array<{sku: string, amount?: number, name?: string, pu?: number, net?: number, rebate?: number, taxrate?: number, comment?: string}>, date?: string|Date, externalNumber?: string, shipping: object}, tx?: object): Promise<object>`
|
||||
- `delete(googleAuthId: number, id: number, tx?: object): Promise<void>`
|
||||
- `getById(googleAuthId: number, id: number, tx?: object): Promise<{id: number, number: string, type: string, entityId: number|null, externalNumber: string|null, date: string, lines: Array<{id: number, sku: string|null, name: string|null, pu: number, amount: number, net: number, rebate: number, taxrate: number, comment: string|null}>}>`
|
||||
- `list(googleAuthId: number, opts?: {offset?: number, limit?: number, search?: string, onlyUnpaid?: boolean, onlyUndelivered?: boolean, seekMonths?: number}, tx?: object): Promise<{orders: Array<{id: number, number: string, type: string, entityId: number|null, externalNumber: string|null, date: string, createdAt: string|null, lines: Array<{id: number, sku: string|null, name: string|null, pu: number, amount: number, net: number, rebate: number, taxrate: number, comment: string|null}>}>, total: number, unfilteredTotal: number, offset: number, limit: number}|Array<object>>`
|
||||
|
||||
@@ -88,6 +88,7 @@ Creates a new shipment manifest for an order. Can contain specified items from t
|
||||
| `input.lines` | `Array<{sku: string, amount?: number, name?: string, pu?: number, net?: number, rebate?: number, taxrate?: number, comment?: string}>` | Positions to ship. |
|
||||
| `[input.date]` | `string|Date` | Optional shipment date. |
|
||||
| `[input.externalNumber]` | `string` | Optional external delivery reference. |
|
||||
| `input.shipping` | `object` | Required shipping ({carrierId, type, state, trackingCode?, warehouseId?}); see shipment.create. |
|
||||
| `[tx]` | `object` | Optional transaction. |
|
||||
|
||||
**Returns**
|
||||
@@ -97,7 +98,7 @@ Creates a new shipment manifest for an order. Can contain specified items from t
|
||||
**Example**
|
||||
|
||||
```js
|
||||
const shipment = await store.order.createShipment(googleAuthId, 10, { lines: [{ sku: 'ITEM-A', amount: 5 }] });
|
||||
const shipment = await store.order.createShipment(googleAuthId, 10, { lines: [{ sku: 'ITEM-A', amount: 5 }], shipping: { carrierId: 1, type: 'parcel', state: 'draft' } });
|
||||
```
|
||||
|
||||
## `order.delete`
|
||||
|
||||
@@ -269,8 +269,12 @@ async function run() {
|
||||
],
|
||||
});
|
||||
|
||||
const testCarrier = await store.carrier.create(googleAuthId, { name: 'Test Carrier', hasPackages: true });
|
||||
const testShipping = { carrierId: testCarrier.id, type: 'parcel', state: 'draft' };
|
||||
|
||||
const shipment = await store.shipment.create(googleAuthId, {
|
||||
orderId: order2.id,
|
||||
shipping: testShipping,
|
||||
lines: [{ sku: 'SKU-ABC', amount: 3 }],
|
||||
});
|
||||
|
||||
@@ -358,6 +362,7 @@ async function run() {
|
||||
|
||||
const shipment3 = await store.shipment.create(googleAuthId, {
|
||||
orderId: order3.id,
|
||||
shipping: testShipping,
|
||||
lines: [
|
||||
{ sku: 'SKU-ABC', amount: 4 },
|
||||
{ sku: 'SKU-ABC', amount: -1 },
|
||||
@@ -419,6 +424,7 @@ async function run() {
|
||||
await assert.rejects(
|
||||
async () => store.shipment.create(googleAuthId, {
|
||||
orderId: guardOrder.id,
|
||||
shipping: testShipping,
|
||||
warehouseId: wh1.id,
|
||||
lines: [{ sku: article.sku, amount: curStock + 10 }],
|
||||
}),
|
||||
|
||||
@@ -95,14 +95,11 @@ async function run() {
|
||||
entityId: customerBob.id,
|
||||
lines: [{ sku: art.sku, name: 'Widget', amount: 2, net: 100, taxrate: 19 }],
|
||||
});
|
||||
const testCarrier = await store.carrier.create(googleAuthId, { name: 'Test Carrier', hasPackages: true });
|
||||
const shipment2 = await store.shipment.create(googleAuthId, {
|
||||
orderId: order2.id,
|
||||
lines: [{ sku: art.sku, amount: 2 }],
|
||||
});
|
||||
await store.shipping.create(googleAuthId, {
|
||||
manifestId: shipment2.id,
|
||||
type: 'parcel',
|
||||
state: 'shipped',
|
||||
shipping: { carrierId: testCarrier.id, type: 'parcel', state: 'shipped' },
|
||||
});
|
||||
|
||||
// Order 3: Alice, O-1003, €59.50, Date: today (2026-10-13)
|
||||
@@ -128,14 +125,10 @@ async function run() {
|
||||
type: 'bank_transfer',
|
||||
amount: 150, // Overpaid!
|
||||
});
|
||||
const shipment4 = await store.shipment.create(googleAuthId, {
|
||||
await store.shipment.create(googleAuthId, {
|
||||
orderId: order4.id,
|
||||
lines: [{ sku: art.sku, amount: 1 }],
|
||||
});
|
||||
await store.shipping.create(googleAuthId, {
|
||||
manifestId: shipment4.id,
|
||||
type: 'parcel',
|
||||
state: 'shipped',
|
||||
shipping: { carrierId: testCarrier.id, type: 'parcel', state: 'shipped' },
|
||||
});
|
||||
|
||||
console.log('Orders created successfully.');
|
||||
|
||||
@@ -118,13 +118,9 @@ async function run() {
|
||||
{ sku: trackedArt.sku, amount: 3 },
|
||||
{ sku: untrackedArt.sku, amount: 2 },
|
||||
],
|
||||
shipping: { carrierId: carrier.id, type: 'parcel', state: 'open' },
|
||||
});
|
||||
const shippingOpen = await store.shipping.create(googleAuthId, {
|
||||
manifestId: shipment.id,
|
||||
carrierId: carrier.id,
|
||||
type: 'parcel',
|
||||
state: 'open',
|
||||
});
|
||||
const shippingOpen = shipment.shippings[0];
|
||||
assert.equal(shippingOpen.state, 'open');
|
||||
|
||||
// Assert stock is still 10
|
||||
@@ -191,6 +187,7 @@ async function run() {
|
||||
number: 'O-HIST-001',
|
||||
date: historicalDate,
|
||||
createdAt: historicalDate,
|
||||
entityId: customer.id,
|
||||
lines: [{ sku: trackedArt.sku, amount: 1, net: 10, taxrate: 19 }],
|
||||
});
|
||||
const histWithOld = await store.article.getHistoryBySku(googleAuthId, trackedArt.sku);
|
||||
@@ -253,14 +250,9 @@ async function run() {
|
||||
const shipment2 = await store.shipment.create(googleAuthId, {
|
||||
orderId: order2.id,
|
||||
lines: [{ sku: trackedArt.sku, amount: 2 }],
|
||||
shipping: { carrierId: carrier.id, type: 'parcel', state: 'shipped' },
|
||||
});
|
||||
|
||||
const directShipped = await store.shipping.create(googleAuthId, {
|
||||
manifestId: shipment2.id,
|
||||
carrierId: carrier.id,
|
||||
type: 'parcel',
|
||||
state: 'shipped',
|
||||
});
|
||||
const directShipped = shipment2.shippings[0];
|
||||
assert.equal(directShipped.state, 'shipped');
|
||||
|
||||
const artFinal = await store.article.getBySku(googleAuthId, trackedArt.sku);
|
||||
|
||||
@@ -84,8 +84,26 @@ async function run() {
|
||||
assert.equal(order.number, 'AU-1001');
|
||||
console.log('Order created:', order.number);
|
||||
|
||||
const carrier = await store.carrier.create(googleAuthId, {
|
||||
name: 'DHL',
|
||||
hasPackages: true,
|
||||
isReverse: false,
|
||||
});
|
||||
assert.equal(carrier.name, 'DHL');
|
||||
assert.equal(carrier.hasPackages, true);
|
||||
assert.equal(carrier.isReverse, false);
|
||||
const shipping = { carrierId: carrier.id, type: 'parcel', state: 'draft' };
|
||||
|
||||
// 0. Shipment without shipping is rejected
|
||||
await assert.rejects(
|
||||
() => store.shipment.create(googleAuthId, { orderId: order.id, lines: [{ sku: 'ITEM-A', amount: 1 }] }),
|
||||
{ message: 'Shipping is required' },
|
||||
);
|
||||
console.log('PASS: Shipment without shipping rejected');
|
||||
|
||||
// 1. Create Shipment 1: 6x Item A, 3x Item B -> AU-1001-L-1
|
||||
const shipment1 = await store.shipment.create(googleAuthId, {
|
||||
shipping,
|
||||
orderId: order.id,
|
||||
lines: [
|
||||
{ sku: 'ITEM-A', amount: 6 },
|
||||
@@ -100,6 +118,7 @@ async function run() {
|
||||
let ceilingErrorThrown = false;
|
||||
try {
|
||||
await store.shipment.create(googleAuthId, {
|
||||
shipping,
|
||||
orderId: order.id,
|
||||
lines: [{ sku: 'ITEM-A', amount: 5 }],
|
||||
});
|
||||
@@ -114,6 +133,7 @@ async function run() {
|
||||
let foreignArticleErrorThrown = false;
|
||||
try {
|
||||
await store.shipment.create(googleAuthId, {
|
||||
shipping,
|
||||
orderId: order.id,
|
||||
lines: [{ sku: 'ITEM-UNKNOWN', amount: 1 }],
|
||||
});
|
||||
@@ -126,6 +146,7 @@ async function run() {
|
||||
|
||||
// 3. Create Shipment 2: 4x Item A, 2x Item B -> AU-1001-L-2
|
||||
const shipment2 = await store.shipment.create(googleAuthId, {
|
||||
shipping,
|
||||
orderId: order.id,
|
||||
lines: [
|
||||
{ sku: 'ITEM-A', amount: 4 },
|
||||
@@ -140,6 +161,7 @@ async function run() {
|
||||
let exhaustedErrorThrown = false;
|
||||
try {
|
||||
await store.shipment.create(googleAuthId, {
|
||||
shipping,
|
||||
orderId: order.id,
|
||||
lines: [{ sku: 'ITEM-A', amount: 1 }],
|
||||
});
|
||||
@@ -150,16 +172,7 @@ async function run() {
|
||||
}
|
||||
assert.ok(exhaustedErrorThrown, 'Expected exhausted error to be thrown');
|
||||
|
||||
// 5. Add packages to Shipment 1 with tracking codes and carrier
|
||||
const carrier = await store.carrier.create(googleAuthId, {
|
||||
name: 'DHL',
|
||||
hasPackages: true,
|
||||
isReverse: false,
|
||||
});
|
||||
assert.equal(carrier.name, 'DHL');
|
||||
assert.equal(carrier.hasPackages, true);
|
||||
assert.equal(carrier.isReverse, false);
|
||||
|
||||
// 5. Add further packages to Shipment 1 with tracking codes
|
||||
const pkg1 = await store.shipping.create(googleAuthId, {
|
||||
manifestId: shipment1.id,
|
||||
carrierId: carrier.id,
|
||||
@@ -194,13 +207,13 @@ async function run() {
|
||||
// Check listByOrderId
|
||||
const orderShipments = await store.shipment.listByOrderId(googleAuthId, order.id);
|
||||
assert.equal(orderShipments.length, 2);
|
||||
assert.equal(orderShipments[0].shippings.length, 2);
|
||||
assert.equal(orderShipments[0].shippings.length, 3);
|
||||
console.log('PASS: store.shipment.listByOrderId returned both shipments');
|
||||
|
||||
// Check getById
|
||||
const fetchedShipment1 = await store.shipment.getById(googleAuthId, shipment1.id);
|
||||
assert.equal(fetchedShipment1.id, shipment1.id);
|
||||
assert.equal(fetchedShipment1.shippings.length, 2);
|
||||
assert.equal(fetchedShipment1.shippings.length, 3);
|
||||
console.log('PASS: store.shipment.getById returned shipment with shippings');
|
||||
|
||||
// 6. Transition Package 1 to shipped (should deduct ledger stock for Shipment 1 lines: 6 of Item A, 3 of Item B)
|
||||
@@ -253,6 +266,7 @@ async function run() {
|
||||
|
||||
// 9a. Ship ONE item (3x ITEM-A) via store.order.createShipment
|
||||
const shOne = await store.order.createShipment(googleAuthId, order2.id, {
|
||||
shipping,
|
||||
lines: [{ sku: 'ITEM-A', amount: 3 }],
|
||||
});
|
||||
assert.equal(shOne.number, 'AU-1002-L-1');
|
||||
@@ -268,6 +282,7 @@ async function run() {
|
||||
|
||||
// 9b. Ship SOME items with omitted amount (e.g. ITEM-A with no amount -> defaults to remaining 7)
|
||||
const shSome = await store.order.createShipment(googleAuthId, order2.id, {
|
||||
shipping,
|
||||
lines: [{ sku: 'ITEM-A' }],
|
||||
});
|
||||
assert.equal(shSome.number, 'AU-1002-L-2');
|
||||
@@ -282,7 +297,7 @@ async function run() {
|
||||
console.log('PASS: 9b. Shipped SOME items with omitted amount; ordersummary updated with shipped_quantity=10, is_shipped=0');
|
||||
|
||||
// 9c. Ship remaining items with explicit lines
|
||||
const shAll = await store.order.createShipment(googleAuthId, order2.id, { lines: [{ sku: 'ITEM-B', amount: 5 }] });
|
||||
const shAll = await store.order.createShipment(googleAuthId, order2.id, { shipping, lines: [{ sku: 'ITEM-B', amount: 5 }] });
|
||||
assert.equal(shAll.number, 'AU-1002-L-3');
|
||||
assert.equal(shAll.lines.length, 1);
|
||||
assert.equal(shAll.lines[0].sku, 'ITEM-B');
|
||||
|
||||
@@ -13,11 +13,12 @@ export default function createShipment(provider) {
|
||||
* @param {Array<{sku: string, amount?: number, name?: string, pu?: number, net?: number, rebate?: number, taxrate?: number, comment?: string}>} input.lines - Positions to ship.
|
||||
* @param {string|Date} [input.date] - Optional shipment date.
|
||||
* @param {string} [input.externalNumber] - Optional external delivery reference.
|
||||
* @param {object} input.shipping - Required shipping ({carrierId, type, state, trackingCode?, warehouseId?}); see shipment.create.
|
||||
* @param {object} [tx] - Optional transaction.
|
||||
* @returns {Promise<object>} The created shipment.
|
||||
*
|
||||
* @example
|
||||
* const shipment = await store.order.createShipment(googleAuthId, 10, { lines: [{ sku: 'ITEM-A', amount: 5 }] });
|
||||
* const shipment = await store.order.createShipment(googleAuthId, 10, { lines: [{ sku: 'ITEM-A', amount: 5 }], shipping: { carrierId: 1, type: 'parcel', state: 'draft' } });
|
||||
*/
|
||||
return async (googleAuthId, orderId, input = {}, tx) => {
|
||||
return create(googleAuthId, { ...input, orderId }, tx);
|
||||
|
||||
@@ -3,6 +3,7 @@ import taxEnsure from '../tax/ensure.js';
|
||||
import { insertLines, parseDate } from '../manifestLines.js';
|
||||
import { assertUniqueManifestNumber } from '../manifestDoc/helpers.js';
|
||||
import makeRunner from '../runner.js';
|
||||
import shippingCreate from '../shipping/create.js';
|
||||
import { loadShipment, queriesFor } from './helpers.js';
|
||||
import { syncOrderSummary } from '../order/syncSummary.js';
|
||||
import { syncShipmentSummary } from './syncSummary.js';
|
||||
@@ -24,6 +25,7 @@ function requireId(val, name) {
|
||||
export default function create(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const ensureTax = taxEnsure(provider);
|
||||
const createShipping = shippingCreate(provider);
|
||||
const q = queriesFor(provider);
|
||||
|
||||
/**
|
||||
@@ -37,14 +39,21 @@ export default function create(provider) {
|
||||
* @param {string|Date} [input.date] - Optional shipment date (defaults to current date).
|
||||
* @param {'new'|'delivered'|'returned'} [input.state='new'] - Optional delivery state (defaults to 'new').
|
||||
* @param {string} [input.externalNumber] - Optional external delivery note number (defaults to order.externalNumber or order.number).
|
||||
* @param {object} input.shipping - Required shipping (package) created atomically with the shipment, so a shipment never exists without a carrier.
|
||||
* @param {number} input.shipping.carrierId - The carrier id.
|
||||
* @param {string} input.shipping.type - Shipping type.
|
||||
* @param {string} input.shipping.state - Shipping state ('shipped' or other).
|
||||
* @param {string} [input.shipping.trackingCode] - Carrier tracking code.
|
||||
* @param {number} [input.shipping.warehouseId] - Optional warehouse ID for stock deduction when state is 'shipped'.
|
||||
* @param {object} [tx] - Optional transaction.
|
||||
* @returns {Promise<object>} The created shipment manifest.
|
||||
* @throws {Error} If order not found, invalid lines, or article ceiling invariant exceeded.
|
||||
* @throws {Error} If order not found, invalid lines, shipping/carrier missing or invalid, or article ceiling invariant exceeded.
|
||||
*
|
||||
* @example
|
||||
* const shipment = await store.shipment.create(googleAuthId, {
|
||||
* orderId: 10,
|
||||
* lines: [{ sku: 'ITEM-A', amount: 5 }]
|
||||
* lines: [{ sku: 'ITEM-A', amount: 5 }],
|
||||
* shipping: { carrierId: 1, type: 'parcel', state: 'draft' }
|
||||
* });
|
||||
*/
|
||||
return async (googleAuthId, input = {}, tx) => {
|
||||
@@ -54,6 +63,11 @@ export default function create(provider) {
|
||||
throw new Error('Invalid lines');
|
||||
}
|
||||
|
||||
if (!input.shipping || typeof input.shipping !== 'object') {
|
||||
throw new Error('Shipping is required');
|
||||
}
|
||||
requireId(input.shipping.carrierId, 'Carrier');
|
||||
|
||||
const shipmentDate = parseDate(input.date) || new Date();
|
||||
|
||||
const run = async (runner, innerTx) => {
|
||||
@@ -289,6 +303,9 @@ export default function create(provider) {
|
||||
state: validState,
|
||||
});
|
||||
|
||||
// 10. Create the shipping (carrier) atomically with the shipment
|
||||
await createShipping(googleAuthId, { ...input.shipping, manifestId }, innerTx);
|
||||
|
||||
return loadShipment(runner, q, companyId, manifestId);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user