Files
Apothecary/web/src/api.ts
T
josh e9e66ab1cb
Build and push image / build (push) Successful in 1m2s
Add purchase date to bulk edit modal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-07 22:34:12 -04:00

208 lines
5.8 KiB
TypeScript

import type { Bootstrap, AuditMode } from "./types.js";
export type BatchOp =
| { action: "update"; id: string; fields: Partial<{ shopId: string | null; binId: string | null; price: number; thc: number; cbd: number; totalCannabinoids: number; purchaseDate: string }> }
| { action: "checkout"; id: string; date: string }
| { action: "checkin"; id: string; date: string; binId: string }
| { action: "finish"; id: string; date: string; rating?: number; notes?: string }
| { action: "gone"; id: string; date: string; reason: string; notes?: string };
async function request<T>(path: string, init?: RequestInit): Promise<T> {
const res = await fetch(`/api${path}`, {
...init,
headers: { "Content-Type": "application/json", ...(init?.headers ?? {}) },
});
if (!res.ok) {
const text = await res.text();
throw new Error(`${res.status} ${res.statusText}: ${text}`);
}
return res.json() as Promise<T>;
}
export const api = {
bootstrap: () => request<Bootstrap>("/bootstrap"),
// Catalog: products
createProduct: (body: {
sku: string;
type: string;
kind: "bulk" | "discrete";
strainId?: string | null;
strainName?: string;
brandId?: string | null;
defaultThc?: number;
defaultCbd?: number;
defaultTotalCannabinoids?: number;
}) => request<{ id: string }>("/products", { method: "POST", body: JSON.stringify(body) }),
updateProduct: (
id: string,
body: Partial<{
type: string;
kind: "bulk" | "discrete";
strainId: string | null;
strainName: string;
brandId: string | null;
}>,
) =>
request<{ ok: true }>(`/products/${id}`, {
method: "PATCH",
body: JSON.stringify(body),
}),
deleteProduct: (id: string) =>
request<{ ok: true }>(`/products/${id}`, { method: "DELETE" }),
updateStrain: (
id: string,
body: Partial<{
name: string;
defaultThc: number | null;
defaultCbd: number | null;
defaultTotalCannabinoids: number | null;
notes: string | null;
}>,
) =>
request<{ ok: true }>(`/strains/${id}`, {
method: "PATCH",
body: JSON.stringify(body),
}),
// Inventory items (instances)
createInventoryItem: (body: {
assetId: string;
productId: string;
shopId?: string | null;
binId?: string | null;
price: number;
thc?: number;
cbd?: number;
totalCannabinoids?: number;
weight?: number;
countOriginal?: number;
unitWeight?: number;
purchaseDate: string;
}) =>
request<{ id: string; assetId: string }>("/inventory", {
method: "POST",
body: JSON.stringify(body),
}),
updateInventoryItem: (
id: string,
body: Partial<{
shopId: string | null;
binId: string | null;
price: number;
thc: number;
cbd: number;
totalCannabinoids: number;
weight: number;
countOriginal: number;
unitWeight: number;
purchaseDate: string;
}>,
) =>
request<{ ok: true }>(`/inventory/${id}`, {
method: "PATCH",
body: JSON.stringify(body),
}),
finishInventoryItem: (
id: string,
body: { date: string; rating?: number; notes?: string },
) =>
request<{ ok: true }>(`/inventory/${id}/finish`, {
method: "POST",
body: JSON.stringify(body),
}),
markInventoryItemGone: (
id: string,
body: { date: string; reason: string; notes?: string },
) =>
request<{ ok: true }>(`/inventory/${id}/gone`, {
method: "POST",
body: JSON.stringify(body),
}),
checkoutInventoryItem: (
id: string,
body: { date: string },
) =>
request<{ ok: true }>(`/inventory/${id}/checkout`, {
method: "POST",
body: JSON.stringify(body),
}),
checkinInventoryItem: (
id: string,
body: { date: string; binId: string; remainingWeight?: number },
) =>
request<{ ok: true }>(`/inventory/${id}/checkin`, {
method: "POST",
body: JSON.stringify(body),
}),
auditInventoryItem: (
id: string,
body: { date: string; mode: AuditMode; value: number; confirmedBy?: string },
) =>
request<{ ok: true }>(`/inventory/${id}/audit`, {
method: "POST",
body: JSON.stringify(body),
}),
batchInventory: (ops: BatchOp[]) =>
request<{ ok: true; count: number }>("/inventory/batch", {
method: "POST",
body: JSON.stringify({ ops }),
}),
// Catalog tables (brand/shop/bin) — unchanged
createBrand: (name: string) =>
request<{ id: string; name: string }>("/brands", {
method: "POST",
body: JSON.stringify({ name }),
}),
updateBrand: (id: string, body: { name: string }) =>
request<{ id: string; name: string }>(`/brands/${id}`, {
method: "PATCH",
body: JSON.stringify(body),
}),
deleteBrand: (id: string) =>
request<{ ok: true }>(`/brands/${id}`, { method: "DELETE" }),
createShop: (body: { name: string; location?: string }) =>
request<{ id: string; name: string; location: string | null }>("/shops", {
method: "POST",
body: JSON.stringify(body),
}),
updateShop: (id: string, body: { name?: string; location?: string | null }) =>
request<{ id: string; name: string; location: string | null }>(`/shops/${id}`, {
method: "PATCH",
body: JSON.stringify(body),
}),
deleteShop: (id: string) =>
request<{ ok: true }>(`/shops/${id}`, { method: "DELETE" }),
createBin: (body: { name: string; capacity?: number }) =>
request<{ id: string; name: string; capacity: number }>("/bins", {
method: "POST",
body: JSON.stringify(body),
}),
updateBin: (id: string, body: { name?: string; capacity?: number }) =>
request<{ id: string; name: string; capacity: number }>(`/bins/${id}`, {
method: "PATCH",
body: JSON.stringify(body),
}),
deleteBin: (id: string) =>
request<{ ok: true }>(`/bins/${id}`, { method: "DELETE" }),
};