@@ -0,0 +1,66 @@
|
||||
import { Prisma } from '../prisma/prisma.service';
|
||||
|
||||
/** The fields of a product that the dashboard and the terminals change; stock changes through its own rules. */
|
||||
export const PRODUCT_FIELDS = [
|
||||
'name', 'category', 'priceInCents', 'buyPriceInCents', 'lowStockThreshold', 'desiredStock', 'archived', 'imageId',
|
||||
] as const;
|
||||
export type ProductField = (typeof PRODUCT_FIELDS)[number];
|
||||
|
||||
export type ProductChanges = {
|
||||
name?: string;
|
||||
category?: string | null;
|
||||
priceInCents?: number;
|
||||
buyPriceInCents?: number | null;
|
||||
lowStockThreshold?: number | null;
|
||||
desiredStock?: number | null;
|
||||
archived?: boolean;
|
||||
imageId?: string | null;
|
||||
};
|
||||
|
||||
type FieldTimes = Partial<Record<ProductField, string>>;
|
||||
|
||||
/**
|
||||
* Merges [changes] made at [at] into a product whose fields were last changed at [times]: per field, the latest
|
||||
* change wins. A price changed on the dashboard and a name changed on an offline terminal both survive; a change
|
||||
* that arrives late (a terminal coming back online) loses to a newer change of the same field.
|
||||
*
|
||||
* Returns the Prisma data to write (empty when nothing wins) and the product's new field times.
|
||||
*/
|
||||
export function mergeProductChanges(times: unknown, changes: ProductChanges, at: Date, current?: { archivedAt: Date | null }) {
|
||||
const previous = (times && typeof times === 'object' ? times : {}) as FieldTimes;
|
||||
const next: FieldTimes = { ...previous };
|
||||
const data: Prisma.ProductUncheckedUpdateInput = {};
|
||||
|
||||
for (const field of PRODUCT_FIELDS) {
|
||||
const value = changes[field];
|
||||
if (value === undefined) continue;
|
||||
const last = previous[field];
|
||||
if (last && new Date(last) > at) continue;
|
||||
|
||||
next[field] = at.toISOString();
|
||||
if (field === 'archived') {
|
||||
// Archiving again keeps the original archive time
|
||||
data.archivedAt = value ? (current?.archivedAt ?? at) : null;
|
||||
} else {
|
||||
(data as Record<string, unknown>)[field] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return { data, fieldUpdatedAt: next as Prisma.InputJsonObject, changed: Object.keys(data).length > 0 };
|
||||
}
|
||||
|
||||
/** Field times for a product created at [at]: every field it was created with. */
|
||||
export function createdFieldTimes(changes: ProductChanges, at: Date): Prisma.InputJsonObject {
|
||||
return Object.fromEntries(PRODUCT_FIELDS.filter((f) => changes[f] !== undefined).map((f) => [f, at.toISOString()]));
|
||||
}
|
||||
|
||||
/**
|
||||
* When a change made now on the dashboard happened: now, or just after the latest field time when a terminal whose
|
||||
* clock runs a little ahead stamped one later. A change on the dashboard is made last, so it wins.
|
||||
*/
|
||||
export function dashboardChangeTime(now: Date, times: unknown): Date {
|
||||
const stamps = Object.values((times && typeof times === 'object' ? times : {}) as Record<string, string>)
|
||||
.map((t) => new Date(t).getTime())
|
||||
.filter((t) => !Number.isNaN(t));
|
||||
return new Date(Math.max(now.getTime(), ...stamps));
|
||||
}
|
||||
Reference in New Issue
Block a user