88 lines
3.3 KiB
TypeScript
88 lines
3.3 KiB
TypeScript
import {
|
||
doublePrecision,
|
||
index,
|
||
integer,
|
||
pgTable,
|
||
primaryKey,
|
||
text,
|
||
timestamp,
|
||
varchar,
|
||
uniqueIndex,
|
||
uuid,
|
||
} from "drizzle-orm/pg-core";
|
||
import { createdAt, householdRoleEnum, storageLocationTypeEnum, updatedAt } from "./_shared.js";
|
||
import { users } from "./users.js";
|
||
|
||
/** Hushåll delar lager, inköpslista, plan, matlådor och budget (spec §7). */
|
||
export const households = pgTable(
|
||
"households",
|
||
{
|
||
id: uuid("id").primaryKey().defaultRandom(),
|
||
name: text("name").notNull(),
|
||
inviteCode: text("invite_code").notNull(),
|
||
weeklyBudgetMinor: integer("weekly_budget_minor"),
|
||
/** Fas 1b: aktiveringsmått (spec §4.2) */
|
||
firstScanCompletedAt: timestamp("first_scan_completed_at", { withTimezone: true }),
|
||
fifthItemConfirmedAt: timestamp("fifth_item_confirmed_at", { withTimezone: true }),
|
||
firstRecipeRecommendationViewedAt: timestamp(
|
||
"first_recipe_recommendation_viewed_at",
|
||
{ withTimezone: true },
|
||
),
|
||
firstRecipeSavedOrStartedAt: timestamp("first_recipe_saved_or_started_at", {
|
||
withTimezone: true,
|
||
}),
|
||
activatedAt: timestamp("activated_at", { withTimezone: true }),
|
||
firstCookingSessionCompletedAt: timestamp("first_cooking_session_completed_at", {
|
||
withTimezone: true,
|
||
}),
|
||
inventoryUpdatedAfterCookingAt: timestamp("inventory_updated_after_cooking_at", {
|
||
withTimezone: true,
|
||
}),
|
||
valueConfirmedAt: timestamp("value_confirmed_at", { withTimezone: true }),
|
||
/** Antal personer i hushållet – anges tidigt i onboarding för att styra portionsant och budget. */
|
||
size: integer("size"),
|
||
/** ISO 4217. Pengagränsen går vid hushållet (i18n-spec §20) – alla belopp i hushållet tolkas i denna valuta. */
|
||
currencyCode: varchar("currency_code", { length: 3 }).notNull().default("SEK"),
|
||
createdAt: createdAt(),
|
||
updatedAt: updatedAt(),
|
||
},
|
||
(t) => [uniqueIndex("households_invite_code_unique").on(t.inviteCode)],
|
||
);
|
||
|
||
export const householdMembers = pgTable(
|
||
"household_members",
|
||
{
|
||
householdId: uuid("household_id")
|
||
.notNull()
|
||
.references(() => households.id, { onDelete: "cascade" }),
|
||
userId: uuid("user_id")
|
||
.notNull()
|
||
.references(() => users.id, { onDelete: "cascade" }),
|
||
role: householdRoleEnum("role").notNull().default("member"),
|
||
/** Individuell portionsfaktor (spec §7: samma rätt, anpassad per person). */
|
||
portionFactor: doublePrecision("portion_factor").notNull().default(1),
|
||
joinedAt: timestamp("joined_at", { withTimezone: true }).notNull().defaultNow(),
|
||
},
|
||
(t) => [
|
||
primaryKey({ columns: [t.householdId, t.userId] }),
|
||
index("household_members_user_idx").on(t.userId),
|
||
],
|
||
);
|
||
|
||
/** Kyl, frys, skafferi, garagefrys, vinkyl, matkällare, matlådor, egna platser (spec §8). */
|
||
export const storageLocations = pgTable(
|
||
"storage_locations",
|
||
{
|
||
id: uuid("id").primaryKey().defaultRandom(),
|
||
householdId: uuid("household_id")
|
||
.notNull()
|
||
.references(() => households.id, { onDelete: "cascade" }),
|
||
type: storageLocationTypeEnum("type").notNull(),
|
||
name: text("name").notNull(),
|
||
sublocations: text("sublocations").array().notNull().default([]),
|
||
sortOrder: integer("sort_order").notNull().default(0),
|
||
createdAt: createdAt(),
|
||
},
|
||
(t) => [index("storage_locations_household_idx").on(t.householdId)],
|
||
);
|