108 lines
3.9 KiB
TypeScript
108 lines
3.9 KiB
TypeScript
import {
|
||
boolean,
|
||
date,
|
||
doublePrecision,
|
||
index,
|
||
jsonb,
|
||
pgTable,
|
||
text,
|
||
timestamp,
|
||
uuid,
|
||
integer,
|
||
} from "drizzle-orm/pg-core";
|
||
import type { NutritionDeclaration } from "@app/shared-types";
|
||
import {
|
||
createdAt,
|
||
dateKindEnum,
|
||
expiryStatusEnum,
|
||
inventorySourceEnum,
|
||
inventoryTransactionTypeEnum,
|
||
unitEnum,
|
||
updatedAt,
|
||
} from "./_shared.js";
|
||
import { households, storageLocations } from "./households.js";
|
||
import { canonicalIngredients } from "./ingredients.js";
|
||
import { products } from "./products.js";
|
||
import { users } from "./users.js";
|
||
|
||
/**
|
||
* Food Twin (spec §8): lagerposter med fullständig spårbarhet.
|
||
* `quantity` är ett cachat saldo – sanningen är inventory_transactions.
|
||
*/
|
||
export const inventoryItems = pgTable(
|
||
"inventory_items",
|
||
{
|
||
id: uuid("id").primaryKey().defaultRandom(),
|
||
householdId: uuid("household_id")
|
||
.notNull()
|
||
.references(() => households.id, { onDelete: "cascade" }),
|
||
canonicalIngredientId: text("canonical_ingredient_id").references(
|
||
() => canonicalIngredients.id,
|
||
),
|
||
productId: uuid("product_id").references(() => products.id),
|
||
displayName: text("display_name").notNull(),
|
||
brand: text("brand"),
|
||
quantity: doublePrecision("quantity").notNull().default(0),
|
||
unit: unitEnum("unit").notNull(),
|
||
storageLocationId: uuid("storage_location_id")
|
||
.notNull()
|
||
.references(() => storageLocations.id),
|
||
sublocation: text("sublocation"),
|
||
purchasedAt: date("purchased_at"),
|
||
openedAt: date("opened_at"),
|
||
bestBeforeDate: date("best_before_date"),
|
||
useByDate: date("use_by_date"),
|
||
dateKind: dateKindEnum("date_kind"),
|
||
frozenAt: date("frozen_at"),
|
||
thawedAt: date("thawed_at"),
|
||
priceMinor: integer("price_minor"),
|
||
nutritionPer100: jsonb("nutrition_per_100").$type<NutritionDeclaration>(),
|
||
source: inventorySourceEnum("source").notNull(),
|
||
confidence: doublePrecision("confidence").notNull().default(1),
|
||
verifiedByUser: boolean("verified_by_user").notNull().default(false),
|
||
lastVerifiedAt: timestamp("last_verified_at", { withTimezone: true }),
|
||
expiryStatus: expiryStatusEnum("expiry_status").notNull().default("unknown"),
|
||
/** Sätts när saldot nått 0 och posten arkiverats. */
|
||
depletedAt: timestamp("depleted_at", { withTimezone: true }),
|
||
modelVersion: text("model_version"),
|
||
promptVersion: text("prompt_version"),
|
||
createdAt: createdAt(),
|
||
updatedAt: updatedAt(),
|
||
},
|
||
(t) => [
|
||
index("inventory_items_household_idx").on(t.householdId),
|
||
index("inventory_items_household_bb_idx").on(t.householdId, t.bestBeforeDate),
|
||
index("inventory_items_location_idx").on(t.storageLocationId),
|
||
index("inventory_items_canonical_idx").on(t.canonicalIngredientId),
|
||
],
|
||
);
|
||
|
||
/** Transaktionsloggen är källan till sanning (spec §8): + inköp, − använt, − kasserat … */
|
||
export const inventoryTransactions = pgTable(
|
||
"inventory_transactions",
|
||
{
|
||
id: uuid("id").primaryKey().defaultRandom(),
|
||
householdId: uuid("household_id")
|
||
.notNull()
|
||
.references(() => households.id, { onDelete: "cascade" }),
|
||
inventoryItemId: uuid("inventory_item_id")
|
||
.notNull()
|
||
.references(() => inventoryItems.id, { onDelete: "cascade" }),
|
||
type: inventoryTransactionTypeEnum("type").notNull(),
|
||
quantityDelta: doublePrecision("quantity_delta").notNull(),
|
||
unit: unitEnum("unit").notNull(),
|
||
refType: text("ref_type"),
|
||
refId: uuid("ref_id"),
|
||
actorUserId: uuid("actor_user_id").references(() => users.id, { onDelete: "set null" }),
|
||
note: text("note"),
|
||
/** Värde i SEK för matsvinnsberäkning vid discard (spec §12, §26). */
|
||
valueMinor: integer("value_minor"),
|
||
createdAt: createdAt(),
|
||
},
|
||
(t) => [
|
||
index("inventory_tx_item_idx").on(t.inventoryItemId),
|
||
index("inventory_tx_household_time_idx").on(t.householdId, t.createdAt),
|
||
index("inventory_tx_type_idx").on(t.type),
|
||
],
|
||
);
|