diff --git a/drizzle/0014_fixed_sunfire.sql b/drizzle/0014_fixed_sunfire.sql new file mode 100644 index 0000000..2688777 --- /dev/null +++ b/drizzle/0014_fixed_sunfire.sql @@ -0,0 +1,37 @@ +CREATE TABLE `contract_rooms` ( + `contract_id` text NOT NULL, + `room_id` text NOT NULL, + PRIMARY KEY(`contract_id`, `room_id`), + FOREIGN KEY (`contract_id`) REFERENCES `contracts`(`id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`room_id`) REFERENCES `rooms`(`id`) ON UPDATE no action ON DELETE restrict +); +--> statement-breakpoint +CREATE INDEX `contract_rooms_room_id_idx` ON `contract_rooms` (`room_id`);--> statement-breakpoint +INSERT INTO `contract_rooms`("contract_id", "room_id") SELECT "id", "room_id" FROM `contracts` WHERE "room_id" IS NOT NULL;--> statement-breakpoint +PRAGMA foreign_keys=OFF;--> statement-breakpoint +CREATE TABLE `__new_contracts` ( + `id` text PRIMARY KEY NOT NULL, + `organization_id` text NOT NULL, + `client_id` text NOT NULL, + `service_id` text, + `license_fee_gbp` real DEFAULT 0 NOT NULL, + `deposit_gbp` real DEFAULT 0 NOT NULL, + `start_date` text NOT NULL, + `end_date` text NOT NULL, + `billed_to` text, + `status` text DEFAULT 'draft' NOT NULL, + `created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL, + `updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL, + `archived_at` integer, + FOREIGN KEY (`organization_id`) REFERENCES `organizations`(`id`) ON UPDATE no action ON DELETE restrict, + FOREIGN KEY (`client_id`) REFERENCES `clients`(`id`) ON UPDATE no action ON DELETE restrict, + FOREIGN KEY (`service_id`) REFERENCES `services`(`id`) ON UPDATE no action ON DELETE restrict +); +--> statement-breakpoint +INSERT INTO `__new_contracts`("id", "organization_id", "client_id", "service_id", "license_fee_gbp", "deposit_gbp", "start_date", "end_date", "billed_to", "status", "created_at", "updated_at", "archived_at") SELECT "id", "organization_id", "client_id", "service_id", "license_fee_gbp", "deposit_gbp", "start_date", "end_date", "billed_to", "status", "created_at", "updated_at", "archived_at" FROM `contracts`;--> statement-breakpoint +DROP TABLE `contracts`;--> statement-breakpoint +ALTER TABLE `__new_contracts` RENAME TO `contracts`;--> statement-breakpoint +PRAGMA foreign_keys=ON;--> statement-breakpoint +CREATE INDEX `contracts_organization_id_idx` ON `contracts` (`organization_id`);--> statement-breakpoint +CREATE INDEX `contracts_client_id_idx` ON `contracts` (`client_id`);--> statement-breakpoint +CREATE INDEX `contracts_service_id_idx` ON `contracts` (`service_id`); diff --git a/drizzle/0015_fuzzy_vampiro.sql b/drizzle/0015_fuzzy_vampiro.sql new file mode 100644 index 0000000..1f3878d --- /dev/null +++ b/drizzle/0015_fuzzy_vampiro.sql @@ -0,0 +1 @@ +ALTER TABLE `invoices` DROP COLUMN `status`; \ No newline at end of file diff --git a/drizzle/meta/0014_snapshot.json b/drizzle/meta/0014_snapshot.json new file mode 100644 index 0000000..0d65b18 --- /dev/null +++ b/drizzle/meta/0014_snapshot.json @@ -0,0 +1,2085 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "82df9fa7-5d45-4dda-bf9a-d28c9508aae3", + "prevId": "9b273e1a-b074-44d2-b90f-7aa8a6605a47", + "tables": { + "task": { + "name": "task", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "clients": { + "name": "clients", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "industry": { + "name": "industry", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "website": { + "name": "website", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "clients_organization_id_organizations_id_fk": { + "name": "clients_organization_id_organizations_id_fk", + "tableFrom": "clients", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organizations": { + "name": "organizations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "address_line_1": { + "name": "address_line_1", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "address_line_2": { + "name": "address_line_2", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_line_3": { + "name": "address_line_3", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "city": { + "name": "city", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "postcode": { + "name": "postcode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "country": { + "name": "country", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'United Kingdom'" + }, + "bank_name": { + "name": "bank_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bank_account_name": { + "name": "bank_account_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bank_account_number": { + "name": "bank_account_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bank_sort_code": { + "name": "bank_sort_code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bank_iban": { + "name": "bank_iban", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bank_swift": { + "name": "bank_swift", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organization_memberships": { + "name": "organization_memberships", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'user'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "organization_memberships_organization_user_unique": { + "name": "organization_memberships_organization_user_unique", + "columns": [ + "organization_id", + "user_id" + ], + "isUnique": true + }, + "organization_memberships_organization_id_idx": { + "name": "organization_memberships_organization_id_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "organization_memberships_user_id_idx": { + "name": "organization_memberships_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "organization_memberships_organization_id_organizations_id_fk": { + "name": "organization_memberships_organization_id_organizations_id_fk", + "tableFrom": "organization_memberships", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_memberships_user_id_user_id_fk": { + "name": "organization_memberships_user_id_user_id_fk", + "tableFrom": "organization_memberships", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "addresses": { + "name": "addresses", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'primary'" + }, + "line_1": { + "name": "line_1", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "line_2": { + "name": "line_2", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "line_3": { + "name": "line_3", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "city": { + "name": "city", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "postcode": { + "name": "postcode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "country": { + "name": "country", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'United Kingdom'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "addresses_organization_id_idx": { + "name": "addresses_organization_id_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "addresses_client_id_idx": { + "name": "addresses_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "addresses_organization_id_organizations_id_fk": { + "name": "addresses_organization_id_organizations_id_fk", + "tableFrom": "addresses", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "addresses_client_id_clients_id_fk": { + "name": "addresses_client_id_clients_id_fk", + "tableFrom": "addresses", + "tableTo": "clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "contacts": { + "name": "contacts", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_primary": { + "name": "is_primary", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "receives_invoices": { + "name": "receives_invoices", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "receives_contracts": { + "name": "receives_contracts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "contacts_organization_id_idx": { + "name": "contacts_organization_id_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "contacts_client_id_idx": { + "name": "contacts_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "contacts_organization_id_organizations_id_fk": { + "name": "contacts_organization_id_organizations_id_fk", + "tableFrom": "contacts", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "contacts_client_id_clients_id_fk": { + "name": "contacts_client_id_clients_id_fk", + "tableFrom": "contacts", + "tableTo": "clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "rooms": { + "name": "rooms", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'meeting_room'" + }, + "sq_ft": { + "name": "sq_ft", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "internal_price_per_hour_gbp": { + "name": "internal_price_per_hour_gbp", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_price_per_hour_gbp": { + "name": "external_price_per_hour_gbp", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "internal_price_per_day_gbp": { + "name": "internal_price_per_day_gbp", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_price_per_day_gbp": { + "name": "external_price_per_day_gbp", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "max_attendees": { + "name": "max_attendees", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "workstations": { + "name": "workstations", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "price_per_month_gbp": { + "name": "price_per_month_gbp", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "rooms_organization_id_organizations_id_fk": { + "name": "rooms_organization_id_organizations_id_fk", + "tableFrom": "rooms", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "services": { + "name": "services", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'each'" + }, + "price_gbp": { + "name": "price_gbp", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "services_organization_id_organizations_id_fk": { + "name": "services_organization_id_organizations_id_fk", + "tableFrom": "services", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "bookings": { + "name": "bookings", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "room_id": { + "name": "room_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_id": { + "name": "service_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "starts_at": { + "name": "starts_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ends_at": { + "name": "ends_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'booked'" + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "bookings_organization_id_idx": { + "name": "bookings_organization_id_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "bookings_client_id_idx": { + "name": "bookings_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "bookings_room_id_idx": { + "name": "bookings_room_id_idx", + "columns": [ + "room_id" + ], + "isUnique": false + }, + "bookings_service_id_idx": { + "name": "bookings_service_id_idx", + "columns": [ + "service_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "bookings_organization_id_organizations_id_fk": { + "name": "bookings_organization_id_organizations_id_fk", + "tableFrom": "bookings", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "bookings_client_id_clients_id_fk": { + "name": "bookings_client_id_clients_id_fk", + "tableFrom": "bookings", + "tableTo": "clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "bookings_room_id_rooms_id_fk": { + "name": "bookings_room_id_rooms_id_fk", + "tableFrom": "bookings", + "tableTo": "rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "bookings_service_id_services_id_fk": { + "name": "bookings_service_id_services_id_fk", + "tableFrom": "bookings", + "tableTo": "services", + "columnsFrom": [ + "service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "invoice_lines": { + "name": "invoice_lines", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "invoice_id": { + "name": "invoice_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "period_start": { + "name": "period_start", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "period_end": { + "name": "period_end", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "quantity": { + "name": "quantity", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "unit_price_gbp": { + "name": "unit_price_gbp", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "total_gbp": { + "name": "total_gbp", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "invoice_lines_organization_id_idx": { + "name": "invoice_lines_organization_id_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "invoice_lines_invoice_id_idx": { + "name": "invoice_lines_invoice_id_idx", + "columns": [ + "invoice_id" + ], + "isUnique": false + }, + "invoice_lines_client_id_idx": { + "name": "invoice_lines_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "invoice_lines_source_idx": { + "name": "invoice_lines_source_idx", + "columns": [ + "source_type", + "source_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "invoice_lines_organization_id_organizations_id_fk": { + "name": "invoice_lines_organization_id_organizations_id_fk", + "tableFrom": "invoice_lines", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "invoice_lines_invoice_id_invoices_id_fk": { + "name": "invoice_lines_invoice_id_invoices_id_fk", + "tableFrom": "invoice_lines", + "tableTo": "invoices", + "columnsFrom": [ + "invoice_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invoice_lines_client_id_clients_id_fk": { + "name": "invoice_lines_client_id_clients_id_fk", + "tableFrom": "invoice_lines", + "tableTo": "clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "invoices": { + "name": "invoices", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "invoice_number": { + "name": "invoice_number", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "issue_date": { + "name": "issue_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "due_date": { + "name": "due_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "subtotal_gbp": { + "name": "subtotal_gbp", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "tax_gbp": { + "name": "tax_gbp", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "total_gbp": { + "name": "total_gbp", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "invoices_invoice_number_unique": { + "name": "invoices_invoice_number_unique", + "columns": [ + "invoice_number" + ], + "isUnique": true + }, + "invoices_organization_id_idx": { + "name": "invoices_organization_id_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "invoices_client_id_idx": { + "name": "invoices_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "invoices_organization_id_organizations_id_fk": { + "name": "invoices_organization_id_organizations_id_fk", + "tableFrom": "invoices", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "invoices_client_id_clients_id_fk": { + "name": "invoices_client_id_clients_id_fk", + "tableFrom": "invoices", + "tableTo": "clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "contract_rooms": { + "name": "contract_rooms", + "columns": { + "contract_id": { + "name": "contract_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "room_id": { + "name": "room_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "contract_rooms_room_id_idx": { + "name": "contract_rooms_room_id_idx", + "columns": [ + "room_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "contract_rooms_contract_id_contracts_id_fk": { + "name": "contract_rooms_contract_id_contracts_id_fk", + "tableFrom": "contract_rooms", + "tableTo": "contracts", + "columnsFrom": [ + "contract_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "contract_rooms_room_id_rooms_id_fk": { + "name": "contract_rooms_room_id_rooms_id_fk", + "tableFrom": "contract_rooms", + "tableTo": "rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "contract_rooms_contract_id_room_id_pk": { + "columns": [ + "contract_id", + "room_id" + ], + "name": "contract_rooms_contract_id_room_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "contracts": { + "name": "contracts", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_id": { + "name": "service_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "license_fee_gbp": { + "name": "license_fee_gbp", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "deposit_gbp": { + "name": "deposit_gbp", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "start_date": { + "name": "start_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "end_date": { + "name": "end_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "billed_to": { + "name": "billed_to", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "contracts_organization_id_idx": { + "name": "contracts_organization_id_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "contracts_client_id_idx": { + "name": "contracts_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "contracts_service_id_idx": { + "name": "contracts_service_id_idx", + "columns": [ + "service_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "contracts_organization_id_organizations_id_fk": { + "name": "contracts_organization_id_organizations_id_fk", + "tableFrom": "contracts", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "contracts_client_id_clients_id_fk": { + "name": "contracts_client_id_clients_id_fk", + "tableFrom": "contracts", + "tableTo": "clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "contracts_service_id_services_id_fk": { + "name": "contracts_service_id_services_id_fk", + "tableFrom": "contracts", + "tableTo": "services", + "columnsFrom": [ + "service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "account": { + "name": "account", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "session_token_unique": { + "name": "session_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_verified": { + "name": "email_verified", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "banned": { + "name": "banned", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verification": { + "name": "verification", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + "identifier" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0015_snapshot.json b/drizzle/meta/0015_snapshot.json new file mode 100644 index 0000000..508c139 --- /dev/null +++ b/drizzle/meta/0015_snapshot.json @@ -0,0 +1,2077 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "945d719e-4e40-434b-b9db-e63e5fbf4301", + "prevId": "82df9fa7-5d45-4dda-bf9a-d28c9508aae3", + "tables": { + "task": { + "name": "task", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "clients": { + "name": "clients", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "industry": { + "name": "industry", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "website": { + "name": "website", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "clients_organization_id_organizations_id_fk": { + "name": "clients_organization_id_organizations_id_fk", + "tableFrom": "clients", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organizations": { + "name": "organizations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "address_line_1": { + "name": "address_line_1", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "address_line_2": { + "name": "address_line_2", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_line_3": { + "name": "address_line_3", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "city": { + "name": "city", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "postcode": { + "name": "postcode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "country": { + "name": "country", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'United Kingdom'" + }, + "bank_name": { + "name": "bank_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bank_account_name": { + "name": "bank_account_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bank_account_number": { + "name": "bank_account_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bank_sort_code": { + "name": "bank_sort_code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bank_iban": { + "name": "bank_iban", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bank_swift": { + "name": "bank_swift", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organization_memberships": { + "name": "organization_memberships", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'user'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "organization_memberships_organization_user_unique": { + "name": "organization_memberships_organization_user_unique", + "columns": [ + "organization_id", + "user_id" + ], + "isUnique": true + }, + "organization_memberships_organization_id_idx": { + "name": "organization_memberships_organization_id_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "organization_memberships_user_id_idx": { + "name": "organization_memberships_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "organization_memberships_organization_id_organizations_id_fk": { + "name": "organization_memberships_organization_id_organizations_id_fk", + "tableFrom": "organization_memberships", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_memberships_user_id_user_id_fk": { + "name": "organization_memberships_user_id_user_id_fk", + "tableFrom": "organization_memberships", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "addresses": { + "name": "addresses", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'primary'" + }, + "line_1": { + "name": "line_1", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "line_2": { + "name": "line_2", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "line_3": { + "name": "line_3", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "city": { + "name": "city", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "postcode": { + "name": "postcode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "country": { + "name": "country", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'United Kingdom'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "addresses_organization_id_idx": { + "name": "addresses_organization_id_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "addresses_client_id_idx": { + "name": "addresses_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "addresses_organization_id_organizations_id_fk": { + "name": "addresses_organization_id_organizations_id_fk", + "tableFrom": "addresses", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "addresses_client_id_clients_id_fk": { + "name": "addresses_client_id_clients_id_fk", + "tableFrom": "addresses", + "tableTo": "clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "contacts": { + "name": "contacts", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_primary": { + "name": "is_primary", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "receives_invoices": { + "name": "receives_invoices", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "receives_contracts": { + "name": "receives_contracts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "contacts_organization_id_idx": { + "name": "contacts_organization_id_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "contacts_client_id_idx": { + "name": "contacts_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "contacts_organization_id_organizations_id_fk": { + "name": "contacts_organization_id_organizations_id_fk", + "tableFrom": "contacts", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "contacts_client_id_clients_id_fk": { + "name": "contacts_client_id_clients_id_fk", + "tableFrom": "contacts", + "tableTo": "clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "rooms": { + "name": "rooms", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'meeting_room'" + }, + "sq_ft": { + "name": "sq_ft", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "internal_price_per_hour_gbp": { + "name": "internal_price_per_hour_gbp", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_price_per_hour_gbp": { + "name": "external_price_per_hour_gbp", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "internal_price_per_day_gbp": { + "name": "internal_price_per_day_gbp", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_price_per_day_gbp": { + "name": "external_price_per_day_gbp", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "max_attendees": { + "name": "max_attendees", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "workstations": { + "name": "workstations", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "price_per_month_gbp": { + "name": "price_per_month_gbp", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "rooms_organization_id_organizations_id_fk": { + "name": "rooms_organization_id_organizations_id_fk", + "tableFrom": "rooms", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "services": { + "name": "services", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'each'" + }, + "price_gbp": { + "name": "price_gbp", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "services_organization_id_organizations_id_fk": { + "name": "services_organization_id_organizations_id_fk", + "tableFrom": "services", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "bookings": { + "name": "bookings", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "room_id": { + "name": "room_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_id": { + "name": "service_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "starts_at": { + "name": "starts_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ends_at": { + "name": "ends_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'booked'" + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "bookings_organization_id_idx": { + "name": "bookings_organization_id_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "bookings_client_id_idx": { + "name": "bookings_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "bookings_room_id_idx": { + "name": "bookings_room_id_idx", + "columns": [ + "room_id" + ], + "isUnique": false + }, + "bookings_service_id_idx": { + "name": "bookings_service_id_idx", + "columns": [ + "service_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "bookings_organization_id_organizations_id_fk": { + "name": "bookings_organization_id_organizations_id_fk", + "tableFrom": "bookings", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "bookings_client_id_clients_id_fk": { + "name": "bookings_client_id_clients_id_fk", + "tableFrom": "bookings", + "tableTo": "clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "bookings_room_id_rooms_id_fk": { + "name": "bookings_room_id_rooms_id_fk", + "tableFrom": "bookings", + "tableTo": "rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "bookings_service_id_services_id_fk": { + "name": "bookings_service_id_services_id_fk", + "tableFrom": "bookings", + "tableTo": "services", + "columnsFrom": [ + "service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "invoice_lines": { + "name": "invoice_lines", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "invoice_id": { + "name": "invoice_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "period_start": { + "name": "period_start", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "period_end": { + "name": "period_end", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "quantity": { + "name": "quantity", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "unit_price_gbp": { + "name": "unit_price_gbp", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "total_gbp": { + "name": "total_gbp", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "invoice_lines_organization_id_idx": { + "name": "invoice_lines_organization_id_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "invoice_lines_invoice_id_idx": { + "name": "invoice_lines_invoice_id_idx", + "columns": [ + "invoice_id" + ], + "isUnique": false + }, + "invoice_lines_client_id_idx": { + "name": "invoice_lines_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "invoice_lines_source_idx": { + "name": "invoice_lines_source_idx", + "columns": [ + "source_type", + "source_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "invoice_lines_organization_id_organizations_id_fk": { + "name": "invoice_lines_organization_id_organizations_id_fk", + "tableFrom": "invoice_lines", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "invoice_lines_invoice_id_invoices_id_fk": { + "name": "invoice_lines_invoice_id_invoices_id_fk", + "tableFrom": "invoice_lines", + "tableTo": "invoices", + "columnsFrom": [ + "invoice_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invoice_lines_client_id_clients_id_fk": { + "name": "invoice_lines_client_id_clients_id_fk", + "tableFrom": "invoice_lines", + "tableTo": "clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "invoices": { + "name": "invoices", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "invoice_number": { + "name": "invoice_number", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "issue_date": { + "name": "issue_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "due_date": { + "name": "due_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subtotal_gbp": { + "name": "subtotal_gbp", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "tax_gbp": { + "name": "tax_gbp", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "total_gbp": { + "name": "total_gbp", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "invoices_invoice_number_unique": { + "name": "invoices_invoice_number_unique", + "columns": [ + "invoice_number" + ], + "isUnique": true + }, + "invoices_organization_id_idx": { + "name": "invoices_organization_id_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "invoices_client_id_idx": { + "name": "invoices_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "invoices_organization_id_organizations_id_fk": { + "name": "invoices_organization_id_organizations_id_fk", + "tableFrom": "invoices", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "invoices_client_id_clients_id_fk": { + "name": "invoices_client_id_clients_id_fk", + "tableFrom": "invoices", + "tableTo": "clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "contract_rooms": { + "name": "contract_rooms", + "columns": { + "contract_id": { + "name": "contract_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "room_id": { + "name": "room_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "contract_rooms_room_id_idx": { + "name": "contract_rooms_room_id_idx", + "columns": [ + "room_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "contract_rooms_contract_id_contracts_id_fk": { + "name": "contract_rooms_contract_id_contracts_id_fk", + "tableFrom": "contract_rooms", + "tableTo": "contracts", + "columnsFrom": [ + "contract_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "contract_rooms_room_id_rooms_id_fk": { + "name": "contract_rooms_room_id_rooms_id_fk", + "tableFrom": "contract_rooms", + "tableTo": "rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "contract_rooms_contract_id_room_id_pk": { + "columns": [ + "contract_id", + "room_id" + ], + "name": "contract_rooms_contract_id_room_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "contracts": { + "name": "contracts", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_id": { + "name": "service_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "license_fee_gbp": { + "name": "license_fee_gbp", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "deposit_gbp": { + "name": "deposit_gbp", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "start_date": { + "name": "start_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "end_date": { + "name": "end_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "billed_to": { + "name": "billed_to", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "contracts_organization_id_idx": { + "name": "contracts_organization_id_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "contracts_client_id_idx": { + "name": "contracts_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "contracts_service_id_idx": { + "name": "contracts_service_id_idx", + "columns": [ + "service_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "contracts_organization_id_organizations_id_fk": { + "name": "contracts_organization_id_organizations_id_fk", + "tableFrom": "contracts", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "contracts_client_id_clients_id_fk": { + "name": "contracts_client_id_clients_id_fk", + "tableFrom": "contracts", + "tableTo": "clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "contracts_service_id_services_id_fk": { + "name": "contracts_service_id_services_id_fk", + "tableFrom": "contracts", + "tableTo": "services", + "columnsFrom": [ + "service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "account": { + "name": "account", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "session_token_unique": { + "name": "session_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_verified": { + "name": "email_verified", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "banned": { + "name": "banned", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verification": { + "name": "verification", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + "identifier" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 13c8940..d461497 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -99,6 +99,20 @@ "when": 1782313131621, "tag": "0013_flippant_jamie_braddock", "breakpoints": true + }, + { + "idx": 14, + "version": "6", + "when": 1782337886282, + "tag": "0014_fixed_sunfire", + "breakpoints": true + }, + { + "idx": 15, + "version": "6", + "when": 1782338642144, + "tag": "0015_fuzzy_vampiro", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/lib/schemas/contacts.schema.ts b/src/lib/schemas/contacts.schema.ts index 015dab5..9ac47de 100644 --- a/src/lib/schemas/contacts.schema.ts +++ b/src/lib/schemas/contacts.schema.ts @@ -13,7 +13,6 @@ export const contactCreateSchema = z.object({ role: optionalText(120), email: optionalEmail, phone: optionalText(80), - isPrimary: booleanString, receivesInvoices: booleanString, receivesContracts: booleanString }); diff --git a/src/lib/schemas/contracts.schema.ts b/src/lib/schemas/contracts.schema.ts index a3ff756..1260bcd 100644 --- a/src/lib/schemas/contracts.schema.ts +++ b/src/lib/schemas/contracts.schema.ts @@ -6,19 +6,18 @@ export const contractStatuses = ['draft', 'active', 'expired', 'void'] as const; export const contractCreateSchema = z .object({ clientId: requiredText('Client'), - roomId: optionalText(80), + roomIds: z.array(z.string().uuid()).default([]), serviceId: optionalText(80), licenseFeeGbp: moneyGbp, depositGbp: moneyGbp, startDate: requiredText('Start date', 40), - endDate: requiredText('End date', 40), - notes: optionalText(1000) + endDate: requiredText('End date', 40) }) .superRefine((data, ctx) => { - if (!data.roomId && !data.serviceId) { + if (data.roomIds.length === 0 && !data.serviceId) { ctx.addIssue({ code: 'custom', - path: ['roomId'], + path: ['roomIds'], message: 'Select a room or service.' }); } diff --git a/src/lib/schemas/invoices.schema.ts b/src/lib/schemas/invoices.schema.ts index 50c407a..ee0fdbd 100644 --- a/src/lib/schemas/invoices.schema.ts +++ b/src/lib/schemas/invoices.schema.ts @@ -1,16 +1,13 @@ import { z } from 'zod'; -import { idSchema, moneyGbp, optionalText, requiredText } from './shared.schema'; +import { moneyGbp, optionalText, requiredText } from './shared.schema'; export const invoiceCreateSchema = z.object({ clientId: requiredText('Client'), invoiceNumber: requiredText('Invoice number', 80), issueDate: requiredText('Issue date', 40), dueDate: requiredText('Due date', 40), - status: z.enum(['draft', 'sent', 'paid', 'overdue', 'void']).default('draft'), subtotalGbp: moneyGbp, taxGbp: moneyGbp, totalGbp: moneyGbp, notes: optionalText(1000) }); - -export const invoiceEditSchema = invoiceCreateSchema.extend(idSchema.shape); diff --git a/src/lib/server/db/contracts.schema.ts b/src/lib/server/db/contracts.schema.ts index e3fd157..e3eb4f6 100644 --- a/src/lib/server/db/contracts.schema.ts +++ b/src/lib/server/db/contracts.schema.ts @@ -1,4 +1,4 @@ -import { index, real, sqliteTable, text } from 'drizzle-orm/sqlite-core'; +import { index, primaryKey, real, sqliteTable, text } from 'drizzle-orm/sqlite-core'; import { clients } from './clients.schema'; import { organizations } from './organizations.schema'; import { rooms } from './rooms.schema'; @@ -17,7 +17,6 @@ export const contracts = sqliteTable( clientId: text('client_id') .notNull() .references(() => clients.id, { onDelete: 'restrict' }), - roomId: text('room_id').references(() => rooms.id, { onDelete: 'restrict' }), serviceId: text('service_id').references(() => services.id, { onDelete: 'restrict' }), licenseFeeGbp: real('license_fee_gbp').notNull().default(0), depositGbp: real('deposit_gbp').notNull().default(0), @@ -25,13 +24,27 @@ export const contracts = sqliteTable( endDate: text('end_date').notNull(), billedTo: text('billed_to'), status: text('status').notNull().default('draft'), - notes: text('notes'), ...timestamps }, (table) => [ index('contracts_organization_id_idx').on(table.organizationId), index('contracts_client_id_idx').on(table.clientId), - index('contracts_room_id_idx').on(table.roomId), index('contracts_service_id_idx').on(table.serviceId) ] ); + +export const contractRooms = sqliteTable( + 'contract_rooms', + { + contractId: text('contract_id') + .notNull() + .references(() => contracts.id, { onDelete: 'cascade' }), + roomId: text('room_id') + .notNull() + .references(() => rooms.id, { onDelete: 'restrict' }) + }, + (table) => [ + primaryKey({ columns: [table.contractId, table.roomId] }), + index('contract_rooms_room_id_idx').on(table.roomId) + ] +); diff --git a/src/lib/server/db/invoices.schema.ts b/src/lib/server/db/invoices.schema.ts index 040dc0d..263e982 100644 --- a/src/lib/server/db/invoices.schema.ts +++ b/src/lib/server/db/invoices.schema.ts @@ -18,7 +18,6 @@ export const invoices = sqliteTable( invoiceNumber: text('invoice_number').notNull().unique(), issueDate: text('issue_date').notNull(), dueDate: text('due_date').notNull(), - status: text('status').notNull().default('draft'), subtotalGbp: real('subtotal_gbp').notNull().default(0), taxGbp: real('tax_gbp').notNull().default(0), totalGbp: real('total_gbp').notNull().default(0), diff --git a/src/routes/dashboard/billing/+page.server.ts b/src/routes/dashboard/billing/+page.server.ts index f32965b..3818af5 100644 --- a/src/routes/dashboard/billing/+page.server.ts +++ b/src/routes/dashboard/billing/+page.server.ts @@ -1,20 +1,18 @@ import { fail } from '@sveltejs/kit'; -import { and, asc, desc, eq, gte, isNull, like, lte, ne } from 'drizzle-orm'; +import { and, asc, desc, eq, gte, inArray, isNull, like, lte, ne } from 'drizzle-orm'; import { createSearchParamsSchema, validateSearchParams } from 'runed/kit'; -import { message, superValidate } from 'sveltekit-superforms/server'; -import { zod4 } from 'sveltekit-superforms/adapters'; import { db } from '$lib/server/db'; import { bookings, clients, contracts, + contractRooms, invoiceLines, invoices, rooms, services } from '$lib/server/db/schema'; import { loadOrganizationContext } from '$lib/server/organizations'; -import { archiveSchema } from '$lib/schemas/shared.schema'; import type { Actions, PageServerLoad } from './$types'; type BillingLine = { @@ -301,7 +299,6 @@ async function buildBillingPreview(organizationId: string, invoiceDate: string) id: contracts.id, clientId: contracts.clientId, clientName: clients.name, - roomName: rooms.name, serviceName: services.name, licenseFeeGbp: contracts.licenseFeeGbp, startDate: contracts.startDate, @@ -309,7 +306,6 @@ async function buildBillingPreview(organizationId: string, invoiceDate: string) }) .from(contracts) .innerJoin(clients, eq(contracts.clientId, clients.id)) - .leftJoin(rooms, eq(contracts.roomId, rooms.id)) .leftJoin(services, eq(contracts.serviceId, services.id)) .where( and( @@ -321,6 +317,25 @@ async function buildBillingPreview(organizationId: string, invoiceDate: string) ) ) .orderBy(asc(contracts.startDate)); + const contractIds = contractRows.map((contract) => contract.id); + const contractRoomLinks = + contractIds.length > 0 + ? await db + .select({ + contractId: contractRooms.contractId, + roomName: rooms.name + }) + .from(contractRooms) + .innerJoin(rooms, eq(contractRooms.roomId, rooms.id)) + .where(inArray(contractRooms.contractId, contractIds)) + .orderBy(asc(rooms.name)) + : []; + const roomNamesByContract = new Map(); + for (const link of contractRoomLinks) { + const existing = roomNamesByContract.get(link.contractId) ?? []; + existing.push(link.roomName); + roomNamesByContract.set(link.contractId, existing); + } const contractCutoff = parseDateOnly(cutoffs.contractsCutoff); @@ -343,7 +358,10 @@ async function buildBillingPreview(organizationId: string, invoiceDate: string) const monthDays = daysInclusive(startOfMonth(month), endOfMonth(month)); const billedDays = daysInclusive(periodStartDate, periodEndDate); const quantity = roundQuantity(billedDays / monthDays); - const labelParts = [contract.roomName, contract.serviceName].filter(Boolean); + const labelParts = [ + ...(roomNamesByContract.get(contract.id) ?? []), + contract.serviceName + ].filter(Boolean); const line = { id: '', clientId: contract.clientId, @@ -407,11 +425,7 @@ export const load: PageServerLoad = async ({ locals, url }) => { return { records, options: await loadOptions(activeOrganizationId), - billingPreview: await buildBillingPreview(activeOrganizationId, invoiceDate), - archiveForm: await superValidate(zod4(archiveSchema), { - id: 'invoices-archive', - errors: false - }) + billingPreview: await buildBillingPreview(activeOrganizationId, invoiceDate) }; }; @@ -458,7 +472,6 @@ export const actions: Actions = { invoiceNumber, issueDate, dueDate, - status: 'draft', subtotalGbp: client.totalGbp, taxGbp: 0, totalGbp: client.totalGbp, @@ -508,19 +521,5 @@ export const actions: Actions = { return { message: `Billing run complete. Created ${groupedClients.length} invoice${groupedClients.length === 1 ? '' : 's'}.` }; - }, - - archive: async (event) => { - const { activeOrganizationId } = await loadOrganizationContext(event.locals); - const form = await superValidate(event, zod4(archiveSchema), { id: 'invoices-archive' }); - - if (!form.valid) return message(form, 'Invoice id is required.', { status: 400 }); - - await db - .update(invoices) - .set({ archivedAt: new Date(), updatedAt: new Date() }) - .where(and(eq(invoices.id, form.data.id), eq(invoices.organizationId, activeOrganizationId))); - - return message(form, 'Invoice archived.'); } }; diff --git a/src/routes/dashboard/billing/+page.svelte b/src/routes/dashboard/billing/+page.svelte index 905fe14..3a623ee 100644 --- a/src/routes/dashboard/billing/+page.svelte +++ b/src/routes/dashboard/billing/+page.svelte @@ -1,51 +1,9 @@ @@ -108,21 +49,5 @@ /> - + - - (archivingId = null)} -/> diff --git a/src/routes/dashboard/billing/archive-invoice-dialog.svelte b/src/routes/dashboard/billing/archive-invoice-dialog.svelte deleted file mode 100644 index 4eb0f86..0000000 --- a/src/routes/dashboard/billing/archive-invoice-dialog.svelte +++ /dev/null @@ -1,40 +0,0 @@ - - - !open && onClose()}> - - - Archive invoice? - This will hide {recordName(archivingRecord)} from active invoices. The record will remain in - the database. - - - Cancel - {#if archivingRecord} -
- - Archive invoice -
- {/if} -
-
-
diff --git a/src/routes/dashboard/billing/invoices-table.svelte b/src/routes/dashboard/billing/invoices-table.svelte index 0bfa8ae..5cc5e84 100644 --- a/src/routes/dashboard/billing/invoices-table.svelte +++ b/src/routes/dashboard/billing/invoices-table.svelte @@ -1,26 +1,18 @@ @@ -33,9 +25,7 @@ Client Issued Due - Status Total - Actions @@ -45,28 +35,7 @@ {relationLabel(record.clientId, 'clients')} {formatDate(record.issueDate)} {formatDate(record.dueDate)} - {formatValue(record.status)} {formatMoney(record.totalGbp)} - - - {#snippet child({ props })}{/snippet} - - openArchive(record)} - >Archive - - - {/each} diff --git a/src/routes/dashboard/clients/[id]/contacts/+page.server.ts b/src/routes/dashboard/clients/[id]/contacts/+page.server.ts index 7f5ffd0..5f81c45 100644 --- a/src/routes/dashboard/clients/[id]/contacts/+page.server.ts +++ b/src/routes/dashboard/clients/[id]/contacts/+page.server.ts @@ -44,7 +44,6 @@ export const load: PageServerLoad = async ({ locals, params }) => { role: record.role ?? '', email: record.email ?? '', phone: record.phone ?? '', - isPrimary: record.isPrimary ? 'true' : 'false', receivesInvoices: record.receivesInvoices ? 'true' : 'false', receivesContracts: record.receivesContracts ? 'true' : 'false' } @@ -85,7 +84,7 @@ export const actions: Actions = { role: form.data.role || null, email: form.data.email || null, phone: form.data.phone || null, - isPrimary: form.data.isPrimary === 'true', + isPrimary: false, receivesInvoices: form.data.receivesInvoices === 'true', receivesContracts: form.data.receivesContracts === 'true', updatedAt: new Date(), @@ -115,7 +114,6 @@ export const actions: Actions = { role: form.data.role || null, email: form.data.email || null, phone: form.data.phone || null, - isPrimary: form.data.isPrimary === 'true', receivesInvoices: form.data.receivesInvoices === 'true', receivesContracts: form.data.receivesContracts === 'true', updatedAt: new Date() @@ -134,6 +132,60 @@ export const actions: Actions = { return message(form, 'Contact updated.'); }, + makePrimary: async ({ locals, params, request }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); + const form = await superValidate(await request.formData(), zod4(archiveSchema), { + id: 'contacts-primary' + }); + + if (!form.valid) return message(form, 'Contact id is required.', { status: 400 }); + + const [contact] = await db + .select({ id: contacts.id, isPrimary: contacts.isPrimary }) + .from(contacts) + .where( + and( + eq(contacts.id, form.data.id), + eq(contacts.clientId, params.id), + eq(contacts.organizationId, activeOrganizationId), + isNull(contacts.archivedAt) + ) + ) + .limit(1); + + if (!contact) return message(form, 'Choose a valid contact.', { status: 400 }); + if (contact.isPrimary) return message(form, 'Contact is already primary.'); + + const now = new Date(); + await db.transaction(async (tx) => { + await tx + .update(contacts) + .set({ isPrimary: false, updatedAt: now }) + .where( + and( + eq(contacts.clientId, params.id), + eq(contacts.organizationId, activeOrganizationId), + eq(contacts.isPrimary, true), + isNull(contacts.archivedAt) + ) + ); + + await tx + .update(contacts) + .set({ isPrimary: true, updatedAt: now }) + .where( + and( + eq(contacts.id, form.data.id), + eq(contacts.clientId, params.id), + eq(contacts.organizationId, activeOrganizationId), + isNull(contacts.archivedAt) + ) + ); + }); + + return message(form, 'Primary contact updated.'); + }, + archive: async ({ locals, params, request }) => { const { activeOrganizationId } = await loadOrganizationContext(locals); const form = await superValidate(await request.formData(), zod4(archiveSchema), { diff --git a/src/routes/dashboard/clients/[id]/contacts/contacts-table.svelte b/src/routes/dashboard/clients/[id]/contacts/contacts-table.svelte index 30db88b..3d1a9d7 100644 --- a/src/routes/dashboard/clients/[id]/contacts/contacts-table.svelte +++ b/src/routes/dashboard/clients/[id]/contacts/contacts-table.svelte @@ -4,6 +4,7 @@ import { Button } from '$lib/components/ui/button/index.js'; import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index.js'; import * as Table from '$lib/components/ui/table/index.js'; + import { cn } from '$lib/utils.js'; let { data, openEdit, @@ -54,8 +55,21 @@ {...props}>{/snippet} - + openEdit(record)}>Edit + {#if !record.isPrimary} +
+ +
+ {/if} openArchive(record)} >Archive diff --git a/src/routes/dashboard/clients/[id]/contacts/create-contact-dialog.svelte b/src/routes/dashboard/clients/[id]/contacts/create-contact-dialog.svelte index e1075ed..254536e 100644 --- a/src/routes/dashboard/clients/[id]/contacts/create-contact-dialog.svelte +++ b/src/routes/dashboard/clients/[id]/contacts/create-contact-dialog.svelte @@ -2,7 +2,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import PlusIcon from '@lucide/svelte/icons/plus'; import { Field as FormField, Control, FieldErrors } from 'formsnap'; - import FormSelect from '$lib/components/form-select.svelte'; import FormCheckbox from '$lib/components/form-checkbox.svelte'; import * as Field from '$lib/components/ui/field/index.js'; import { Button } from '$lib/components/ui/button/index.js'; @@ -77,26 +76,6 @@ - - - - {#snippet children({ props })} - Primary contact - - {/snippet} - - - -
/* eslint-disable @typescript-eslint/no-explicit-any */ import { Field as FormField, Control, FieldErrors } from 'formsnap'; - import FormSelect from '$lib/components/form-select.svelte'; import FormCheckbox from '$lib/components/form-checkbox.svelte'; import * as Field from '$lib/components/ui/field/index.js'; import { Button } from '$lib/components/ui/button/index.js'; @@ -75,26 +74,6 @@ - - - - {#snippet children({ props })} - Primary contact - - {/snippet} - - - -
({ label: `${room.name} ยท ${room.type === 'private_office' ? 'Private office' : 'Coworking desk'}`, value: room.id, @@ -63,11 +62,9 @@ export const load: PageServerLoad = async ({ locals, params }) => { const records = await db .select({ contract: contracts, - roomName: rooms.name, serviceName: services.name }) .from(contracts) - .leftJoin(rooms, eq(contracts.roomId, rooms.id)) .leftJoin(services, eq(contracts.serviceId, services.id)) .where( and( @@ -77,29 +74,57 @@ export const load: PageServerLoad = async ({ locals, params }) => { ) ) .orderBy(asc(contracts.startDate)); + const contractIds = records.map((record) => record.contract.id); + const roomLinks = + contractIds.length > 0 + ? await db + .select({ + contractId: contractRooms.contractId, + roomId: rooms.id, + roomName: rooms.name + }) + .from(contractRooms) + .innerJoin(rooms, eq(contractRooms.roomId, rooms.id)) + .where(inArray(contractRooms.contractId, contractIds)) + .orderBy(asc(rooms.name)) + : []; + const roomsByContract = new Map(); + for (const link of roomLinks) { + const existing = roomsByContract.get(link.contractId) ?? []; + existing.push({ id: link.roomId, name: link.roomName }); + roomsByContract.set(link.contractId, existing); + } return { - records: records.map((record) => ({ - ...record.contract, - roomName: record.roomName, - serviceName: record.serviceName, - formValues: { - id: record.contract.id, - clientId: record.contract.clientId ?? '', - roomId: record.contract.roomId ?? '', - serviceId: record.contract.serviceId ?? '', - licenseFeeGbp: record.contract.licenseFeeGbp, - depositGbp: record.contract.depositGbp, - startDate: record.contract.startDate ?? '', - endDate: record.contract.endDate ?? '', - notes: record.contract.notes ?? '' - } - })), - options: await loadOptions(activeOrganizationId), - createForm: await superValidate({ clientId: params.id }, zod4(contractCreateSchema), { - id: 'contracts-create', - errors: false + records: records.map((record) => { + const linkedRooms = roomsByContract.get(record.contract.id) ?? []; + return { + ...record.contract, + roomName: linkedRooms.map((room) => room.name).join(', '), + roomNames: linkedRooms.map((room) => room.name), + roomIds: linkedRooms.map((room) => room.id), + serviceName: record.serviceName, + formValues: { + id: record.contract.id, + clientId: record.contract.clientId ?? '', + roomIds: linkedRooms.map((room) => room.id), + serviceId: record.contract.serviceId ?? '', + licenseFeeGbp: record.contract.licenseFeeGbp, + depositGbp: record.contract.depositGbp, + startDate: record.contract.startDate ?? '', + endDate: record.contract.endDate ?? '' + } + }; }), + options: await loadOptions(activeOrganizationId), + createForm: await superValidate( + { clientId: params.id, roomIds: [] }, + zod4(contractCreateSchema), + { + id: 'contracts-create', + errors: false + } + ), editForm: await superValidate(zod4(contractEditSchema), { id: 'contracts-edit', errors: false @@ -117,26 +142,26 @@ export const load: PageServerLoad = async ({ locals, params }) => { async function selectionError( organizationId: string, - roomId: string, + roomIds: string[], serviceId: string -): Promise<{ field: 'roomId' | 'serviceId'; message: string } | null> { - if (roomId) { - const [room] = await db +): Promise<{ field: 'roomIds' | 'serviceId'; message: string } | null> { + if (roomIds.length > 0) { + const uniqueRoomIds = [...new Set(roomIds)]; + const validRooms = await db .select({ id: rooms.id }) .from(rooms) .where( and( - eq(rooms.id, roomId), + inArray(rooms.id, uniqueRoomIds), eq(rooms.organizationId, organizationId), inArray(rooms.type, ['private_office', 'coworking_desk']), isNull(rooms.archivedAt) ) - ) - .limit(1); + ); - if (!room) { + if (validRooms.length !== uniqueRoomIds.length) { return { - field: 'roomId', + field: 'roomIds', message: 'Select a valid private office or coworking desk.' }; } @@ -173,26 +198,42 @@ export const actions: Actions = { if (!form.valid) return message(form, 'Check the highlighted fields.', { status: 400 }); const invalidSelection = await selectionError( activeOrganizationId, - form.data.roomId, + form.data.roomIds, form.data.serviceId ); - if (invalidSelection) return setError(form, invalidSelection.field, invalidSelection.message); + if (invalidSelection) { + if (invalidSelection.field === 'serviceId') { + return setError(form, invalidSelection.field, invalidSelection.message); + } + return message(form, invalidSelection.message, { status: 400 }); + } try { - await db.insert(contracts).values({ - id: crypto.randomUUID(), - organizationId: activeOrganizationId, - clientId: form.data.clientId, - roomId: form.data.roomId || null, - serviceId: form.data.serviceId || null, - licenseFeeGbp: form.data.licenseFeeGbp, - depositGbp: form.data.depositGbp, - startDate: form.data.startDate, - endDate: form.data.endDate, - status: 'draft', - notes: form.data.notes || null, - updatedAt: new Date(), - createdAt: new Date() + const contractId = crypto.randomUUID(); + await db.transaction(async (tx) => { + await tx.insert(contracts).values({ + id: contractId, + organizationId: activeOrganizationId, + clientId: form.data.clientId, + serviceId: form.data.serviceId || null, + licenseFeeGbp: form.data.licenseFeeGbp, + depositGbp: form.data.depositGbp, + startDate: form.data.startDate, + endDate: form.data.endDate, + status: 'draft', + updatedAt: new Date(), + createdAt: new Date() + }); + + const uniqueRoomIds = [...new Set(form.data.roomIds)]; + if (uniqueRoomIds.length > 0) { + await tx.insert(contractRooms).values( + uniqueRoomIds.map((roomId) => ({ + contractId, + roomId + })) + ); + } }); } catch { return message(form, 'Unable to create contract.', { status: 400 }); @@ -212,32 +253,48 @@ export const actions: Actions = { if (!form.valid) return message(form, 'Check the highlighted fields.', { status: 400 }); const invalidSelection = await selectionError( activeOrganizationId, - form.data.roomId, + form.data.roomIds, form.data.serviceId ); - if (invalidSelection) return setError(form, invalidSelection.field, invalidSelection.message); + if (invalidSelection) { + if (invalidSelection.field === 'serviceId') { + return setError(form, invalidSelection.field, invalidSelection.message); + } + return message(form, invalidSelection.message, { status: 400 }); + } try { - await db - .update(contracts) - .set({ - clientId: form.data.clientId, - roomId: form.data.roomId || null, - serviceId: form.data.serviceId || null, - licenseFeeGbp: form.data.licenseFeeGbp, - depositGbp: form.data.depositGbp, - startDate: form.data.startDate, - endDate: form.data.endDate, - notes: form.data.notes || null, - updatedAt: new Date() - }) - .where( - and( - eq(contracts.id, form.data.id), - eq(contracts.clientId, params.id), - eq(contracts.organizationId, activeOrganizationId) - ) - ); + await db.transaction(async (tx) => { + await tx + .update(contracts) + .set({ + clientId: form.data.clientId, + serviceId: form.data.serviceId || null, + licenseFeeGbp: form.data.licenseFeeGbp, + depositGbp: form.data.depositGbp, + startDate: form.data.startDate, + endDate: form.data.endDate, + updatedAt: new Date() + }) + .where( + and( + eq(contracts.id, form.data.id), + eq(contracts.clientId, params.id), + eq(contracts.organizationId, activeOrganizationId) + ) + ); + + await tx.delete(contractRooms).where(eq(contractRooms.contractId, form.data.id)); + const uniqueRoomIds = [...new Set(form.data.roomIds)]; + if (uniqueRoomIds.length > 0) { + await tx.insert(contractRooms).values( + uniqueRoomIds.map((roomId) => ({ + contractId: form.data.id, + roomId + })) + ); + } + }); } catch { return message(form, 'Unable to update contract.', { status: 400 }); } diff --git a/src/routes/dashboard/clients/[id]/contracts/contract-form-fields.svelte b/src/routes/dashboard/clients/[id]/contracts/contract-form-fields.svelte index 43170f2..45d73d2 100644 --- a/src/routes/dashboard/clients/[id]/contracts/contract-form-fields.svelte +++ b/src/routes/dashboard/clients/[id]/contracts/contract-form-fields.svelte @@ -1,11 +1,16 @@ - + - + {#snippet children({ props })} - Room - Rooms + updatePricing(roomId, $data.serviceId)} + onValueChange={(roomIds) => updatePricing(roomIds, $data.serviceId)} /> {/snippet} @@ -74,7 +120,7 @@ searchPlaceholder="Search services..." emptyMessage="No matching services." triggerProps={props} - onValueChange={(serviceId) => updatePricing($data.roomId, serviceId)} + onValueChange={(serviceId) => updatePricing($data.roomIds, serviceId)} /> {/snippet} @@ -89,7 +135,7 @@ name="licenseFeeGbp" label="License fee" id={`${prefix}-licenseFeeGbp`} - description="Private office monthly price plus the selected service. You can overwrite this amount." + description="Selected room monthly prices plus the selected service. You can overwrite this amount." /> {#snippet children({ props })} Start date - + + + {#snippet child({ props: triggerProps })} + + {/snippet} + + + + + + {/snippet} @@ -118,22 +190,36 @@ {#snippet children({ props })} End date - + + + {#snippet child({ props: triggerProps })} + + {/snippet} + + + + + + {/snippet}
- - - - - {#snippet children({ props })} - Notes -