diff --git a/README.md b/README.md index 512d40b..74aedc7 100644 --- a/README.md +++ b/README.md @@ -345,6 +345,8 @@ export interface LocationData extends Record { locationName?: string; address?: string[]; countryCode?: string; + organisationId?: string; + userId?: string; } export interface Location extends LocationData { @@ -458,6 +460,8 @@ export interface Organisation extends OrganisationData { approvedByUserId?: string; } +export type PartialOrganisation = OrganisationData & Partial; + export interface PartnerData extends Record { partnerName: string; countryCode?: string; @@ -535,6 +539,7 @@ export interface Product extends ProductData { export type ShipmentStatus = "pending" | "processing" | "sent" | "delivered"; export interface ShipmentLocation { + organisationId?: string; // Optional fixed organisation locationId?: string; // Optional fixed location inventoryId?: string; // Optional fixed inventory set inventoryProductId?: string; // Optional fixed inventory set diff --git a/src/data/inventory-product/list-inventory-products.ts b/src/data/inventory-product/list-inventory-products.ts index 6dff475..4ada72f 100644 --- a/src/data/inventory-product/list-inventory-products.ts +++ b/src/data/inventory-product/list-inventory-products.ts @@ -13,7 +13,7 @@ export async function listInventoryProducts(options: ListInventoryProductsInput) > { // TODO make this not all in memory // Its okay for now :) - const { inventoryId} = options; + const { inventoryId } = options; if (!inventoryId) { const inventory = await listInventory(options); const values = await Promise.all( @@ -27,7 +27,6 @@ export async function listInventoryProducts(options: ListInventoryProductsInput) return values.flatMap(value => value); } const { productId, status } = options; - const store = getInventoryProductStore(inventoryId); let values = await store.values(); if (productId) { diff --git a/src/data/inventory/list-inventory.ts b/src/data/inventory/list-inventory.ts index a124f7d..74036da 100644 --- a/src/data/inventory/list-inventory.ts +++ b/src/data/inventory/list-inventory.ts @@ -2,14 +2,22 @@ import {Inventory, InventoryType} from "./types"; import { getInventoryStore } from "./store"; export interface ListInventoryInput { - type?: InventoryType + type?: InventoryType; + organisationId?: string; + userId?: string; } -export async function listInventory({ type = "inventory" }: ListInventoryInput = {}): Promise< +export async function listInventory({ type = "inventory", organisationId, userId }: ListInventoryInput = {}): Promise< Inventory[] > { const store = getInventoryStore(); let values = await store.values(); + if (organisationId) { + values = values.filter(value => value.organisationId === organisationId) + } + if (userId) { + values = values.filter(value => value.userId === userId) + } if (type) { values = values.filter(value => value.type === type); } diff --git a/src/data/inventory/types.ts b/src/data/inventory/types.ts index af02dd4..3f422ad 100644 --- a/src/data/inventory/types.ts +++ b/src/data/inventory/types.ts @@ -8,6 +8,8 @@ export type InventoryType = export interface InventoryData { type: InventoryType + userId?: string; + organisationId?: string; locationId?: string; products?: (InventoryProductIdentifierData & Partial)[]; } diff --git a/src/data/location/types.ts b/src/data/location/types.ts index 34b62f0..5c4f507 100644 --- a/src/data/location/types.ts +++ b/src/data/location/types.ts @@ -9,6 +9,8 @@ export interface LocationData extends Record { locationName?: string; address?: string[]; countryCode?: string; + organisationId?: string; + userId?: string; } export interface Location extends LocationData { diff --git a/src/data/offer/list-offers.ts b/src/data/offer/list-offers.ts index ac46b55..2be8937 100644 --- a/src/data/offer/list-offers.ts +++ b/src/data/offer/list-offers.ts @@ -4,6 +4,8 @@ import { getOfferStore } from "./store"; export interface ListOffersInput { // Only return public offers, regardless if the user is anonymous public?: boolean; + productId?: string; + organisationId?: string; } export async function listOffers

(options: ListOffersInput = {}): Promise< @@ -11,9 +13,15 @@ export async function listOffers

(options: ListOffersInp > { const store = getOfferStore

(); let offers = await store.values(); + if (options.organisationId) { + offers = offers.filter(value => value.organisationId); + } if (options.public) { // Force public only offers = offers.filter(value => value.public); } + if (options.productId) { + offers = offers.filter(value => value.items.find(item => item.type === "product" ? item.productId === options.productId : false)) + } return offers; } diff --git a/src/data/order/list-orders.ts b/src/data/order/list-orders.ts index 1288060..bc165a2 100644 --- a/src/data/order/list-orders.ts +++ b/src/data/order/list-orders.ts @@ -1,17 +1,70 @@ import {Order, OrderStatus} from "./types"; import { getOrderStore } from "./store"; +import {ShipmentFrom, ShipmentLocation, ShipmentTo} from "../shipment"; export interface ListOrdersInput { status?: OrderStatus; + from?: ShipmentFrom; + to?: ShipmentTo; } -export async function listOrders({ status }: ListOrdersInput = {}): Promise< +export async function listOrders({ status, from, to }: ListOrdersInput = {}): Promise< Order[] > { const store = getOrderStore(); let values = await store.values(); + if (from) { + values = values.filter(value => { + if (!value.from) return false; + return isShipmentLocationMatch(from, value.from); + }); + } + if (to) { + values = values.filter(value => { + if (!value.to) return false; + return isShipmentLocationMatch(to, value.to); + }); + } if (status) { values = values.filter(value => value.status === status); } return values.sort((a, b) => new Date(a.createdAt).getTime() < new Date(b.createdAt).getTime() ? -1 : 1) + + } + +function isShipmentLocationMatch(base: ShipmentLocation, match: ShipmentLocation) { + return ( + ( + !base.organisationId || + base.organisationId === match.organisationId + ) && + ( + !base.locationId || + base.locationId === match.locationId + ) && + ( + !base.inventoryId || + base.inventoryId === match.inventoryId + ) && + ( + !base.inventoryProductId || + base.inventoryProductId === match.inventoryProductId + ) && + ( + !base.countryCode || + base.countryCode === match.countryCode + ) && + ( + !base.address || + ( + match.address && + base.address.length === match.address.length && + ( + !base.address.length || + base.address.every((value, index) => match.address[index] === value) + ) + ) + ) + ) +} \ No newline at end of file diff --git a/src/data/organisation/add-organisation.ts b/src/data/organisation/add-organisation.ts index fe0a097..88950a5 100644 --- a/src/data/organisation/add-organisation.ts +++ b/src/data/organisation/add-organisation.ts @@ -1,24 +1,13 @@ import { v4 } from "uuid"; import { OrganisationData, Organisation } from "./types"; -import { getOrganisationStore } from "./store"; +import { setOrganisation } from "./set-organisation"; -export interface AddOrganisationInput extends OrganisationData {} - -export async function addOrganisation( - data: AddOrganisationInput -): Promise { - const store = getOrganisationStore(); - const organisationId = v4(); - const createdAt = new Date().toISOString(); - const organisation: Organisation = { +export async function addOrganisation(data: OrganisationData): Promise { + return setOrganisation({ ...data, - organisationId, + organisationId: v4(), approved: false, approvedAt: undefined, - approvedByUserId: undefined, - createdAt, - updatedAt: createdAt, - }; - await store.set(organisationId, organisation); - return organisation; + approvedByUserId: undefined + }); } diff --git a/src/data/organisation/index.ts b/src/data/organisation/index.ts index 862df43..53db077 100644 --- a/src/data/organisation/index.ts +++ b/src/data/organisation/index.ts @@ -3,4 +3,5 @@ export * from "./types"; export * from "./get-organisation"; export * from "./list-organisations"; export * from "./add-organisation"; -export * as organisationSchema from "./schema"; +export * from "./set-organisation"; +export * as organisationSchema from "./schema"; \ No newline at end of file diff --git a/src/data/organisation/set-organisation.ts b/src/data/organisation/set-organisation.ts new file mode 100644 index 0000000..26b65f4 --- /dev/null +++ b/src/data/organisation/set-organisation.ts @@ -0,0 +1,23 @@ +import { v4 } from "uuid"; +import { Organisation, PartialOrganisation } from "./types"; +import { getOrganisationStore } from "./store"; + +export async function setOrganisation( + data: PartialOrganisation +): Promise { + const store = getOrganisationStore(); + const organisationId = data.organisationId || v4(); + const updatedAt = new Date().toISOString(); + const createdAt = data.createdAt || updatedAt; + const organisation: Organisation = { + approved: false, + approvedAt: undefined, + approvedByUserId: undefined, + ...data, + organisationId, + createdAt, + updatedAt, + }; + await store.set(organisationId, organisation); + return organisation; +} diff --git a/src/data/organisation/types.ts b/src/data/organisation/types.ts index 41aa3fc..cfd35d1 100644 --- a/src/data/organisation/types.ts +++ b/src/data/organisation/types.ts @@ -23,3 +23,5 @@ export interface Organisation extends OrganisationData { updatedAt: string; approvedByUserId?: string; } + +export type PartialOrganisation = OrganisationData & Partial; \ No newline at end of file diff --git a/src/data/shipment/types.ts b/src/data/shipment/types.ts index 3bab9d0..fabc120 100644 --- a/src/data/shipment/types.ts +++ b/src/data/shipment/types.ts @@ -3,6 +3,7 @@ import {Identifier} from "../identifier"; export type ShipmentStatus = "pending" | "processing" | "sent" | "delivered"; export interface ShipmentLocation { + organisationId?: string; // Optional fixed organisation locationId?: string; // Optional fixed location inventoryId?: string; // Optional fixed inventory set inventoryProductId?: string; // Optional fixed inventory set diff --git a/src/package.readonly.ts b/src/package.readonly.ts index a27c9d4..18c7d59 100644 --- a/src/package.readonly.ts +++ b/src/package.readonly.ts @@ -1,15 +1,15 @@ // File generated by scripts/pre-build.js -export const commit = "c55f5e8843ad844cbe27bb2b6bb5043cb6256444"; -export const commitShort = "c55f5e8"; +export const commit = "1183f730ea35ee842cbc4d1be6a5fef5c515d156"; +export const commitShort = "1183f73"; export const commitAuthor = "Fabian Cook"; export const commitEmail = "hello@fabiancook.dev"; export const commitMessage = "Public offers and products, #21, #1"; export const commitAt = "2023-06-18T04:56:58.000Z"; -export const secondsBetweenCommitAndBuild = 107.2; -export const minutesBetweenCommitAndBuild = 1.79; -export const timeBetweenCommitAndBuild = "1 minutes and 47 seconds"; +export const secondsBetweenCommitAndBuild = 17513.54; +export const minutesBetweenCommitAndBuild = 291.89; +export const timeBetweenCommitAndBuild = "291 minutes and 53 seconds"; // Variables to be replaced after tests -export const secondsBetweenCommitAndTestCompletion = ""; -export const minutesBetweenCommitAndTestCompletion = ""; -export const timeBetweenCommitAndTestCompletion = ""; \ No newline at end of file +export const secondsBetweenCommitAndTestCompletion = "17547.92"; +export const minutesBetweenCommitAndTestCompletion = "292.47"; +export const timeBetweenCommitAndTestCompletion = "292 minutes and 27 seconds"; \ No newline at end of file diff --git a/src/tests/scenarios/ordered-products.ts b/src/tests/scenarios/ordered-products.ts index 685d79a..637a260 100644 --- a/src/tests/scenarios/ordered-products.ts +++ b/src/tests/scenarios/ordered-products.ts @@ -1,38 +1,71 @@ import { addInventory, - addInventoryProduct, + addInventoryProduct, addOffer, addOrder, - addOrderProduct, - addShipment, + addOrderProduct, addOrganisation, + addShipment, getOrganisation, Identifier, - listInventoryProducts, + listInventoryProducts, listOffers, listOrderProducts, listOrders, listProducts, setInventoryProduct, - setOrder, + setOrder, setOrganisation, ShipmentFrom, ShipmentTo } from "../../data"; import {ok} from "../../is"; -import {v4} from "uuid"; +import {v4, v5} from "uuid"; import {Chance} from "chance"; import {addLocation} from "../../data/location"; const chance = new Chance(); -{ +const namespace = "737f2826-b4c3-40ba-b1f5-1f7766439c9d"; +const organisationId = v5("organisationId", namespace); +{ let products = await listProducts(); + await setOrganisation({ + organisationName: chance.company(), + ...await getOrganisation(organisationId), + organisationId, + }); + + ok(products.length >= 3, "Some products should be already seeded"); + { + for (const { productId } of products) { + const offers = await listOffers({ + productId, + organisationId + }); + + // If there aren't any offers yet, make some available! + if (!offers.length) { + await addOffer({ + status: "available", + items: [ + { + type: "product", + productId + } + ], + organisationId + }); + } + } + } + // A third party reference that can be recognised // In other software, this should be human-readable const reference = v4(); const { locationId } = await addLocation({ - type: "place" + type: "place", + organisationId }); { @@ -45,14 +78,14 @@ const chance = new Chance(); }; const from: ShipmentFrom = { - locationId + locationId, + organisationId, }; let order = await addOrder({ status: "pending", // Pending cart order, products: [], reference, - to, from }); const { orderId } = order; @@ -73,16 +106,20 @@ const chance = new Chance(); quantity: chance.integer({ min: 1, max: 20 }) }); - ok(order.to, "Expected shipping address before submitting"); order = await setOrder({ ...order, + to, status: "submitted" }); + ok(order.to, "Expected shipping address after submitting"); } { const submittedOrders = await listOrders({ - status: "submitted" + status: "submitted", + from: { + organisationId + } }); let mostRecent = submittedOrders.at(-1); @@ -104,7 +141,8 @@ const chance = new Chance(); // Inventory shelf just for this order when it comes in // Extra priority! const { inventoryId } = await addInventory({ - type: "inventory" + type: "inventory", + organisationId }) // Ensure we have everything in stock @@ -127,7 +165,8 @@ const chance = new Chance(); from: partner, to: { locationId, - inventoryId + inventoryId, + organisationId }, products: orderedProducts .map(({ productId, quantity }) => ({ productId, quantity })) @@ -142,7 +181,8 @@ const chance = new Chance(); from: partner, to: { locationId, - inventoryId + inventoryId, + organisationId }, inventoryId, productId, @@ -161,12 +201,14 @@ const chance = new Chance(); // Pick the order ready for shipping const picking = await addLocation({ - type: "picking" + type: "picking", + organisationId }); const pickingInventory = await addInventory({ type: "picking", - locationId: picking.locationId + locationId: picking.locationId, + organisationId }); {