diff --git a/backend/db/migrations/0003_woozy_adam_destine.sql b/backend/db/migrations/0003_woozy_adam_destine.sql new file mode 100644 index 0000000..1f0451f --- /dev/null +++ b/backend/db/migrations/0003_woozy_adam_destine.sql @@ -0,0 +1,2 @@ +-- No-op migration: Datei war im Journal referenziert, aber fehlte im Repository. +SELECT 1; diff --git a/backend/db/migrations/0004_stormy_onslaught.sql b/backend/db/migrations/0004_stormy_onslaught.sql new file mode 100644 index 0000000..1f0451f --- /dev/null +++ b/backend/db/migrations/0004_stormy_onslaught.sql @@ -0,0 +1,2 @@ +-- No-op migration: Datei war im Journal referenziert, aber fehlte im Repository. +SELECT 1; diff --git a/backend/db/migrations/0005_green_shinobi_shaw.sql b/backend/db/migrations/0005_green_shinobi_shaw.sql new file mode 100644 index 0000000..190e7ad --- /dev/null +++ b/backend/db/migrations/0005_green_shinobi_shaw.sql @@ -0,0 +1,123 @@ +CREATE TABLE "m2m_api_keys" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + "tenant_id" bigint NOT NULL, + "user_id" uuid NOT NULL, + "created_by" uuid, + "name" text NOT NULL, + "key_prefix" text NOT NULL, + "key_hash" text NOT NULL, + "active" boolean DEFAULT true NOT NULL, + "last_used_at" timestamp with time zone, + "expires_at" timestamp with time zone, + CONSTRAINT "m2m_api_keys_key_hash_unique" UNIQUE("key_hash") +); +--> statement-breakpoint +CREATE TABLE "staff_time_events" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "tenant_id" bigint NOT NULL, + "user_id" uuid NOT NULL, + "actor_type" text NOT NULL, + "actor_user_id" uuid, + "event_time" timestamp with time zone NOT NULL, + "event_type" text NOT NULL, + "source" text NOT NULL, + "invalidates_event_id" uuid, + "related_event_id" uuid, + "metadata" jsonb, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "time_events_actor_user_check" CHECK ( + (actor_type = 'system' AND actor_user_id IS NULL) + OR + (actor_type = 'user' AND actor_user_id IS NOT NULL) + ) +); +--> statement-breakpoint +CREATE TABLE "serialtypes" ( + "id" bigint PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (sequence name "serialtypes_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1), + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "name" text NOT NULL, + "intervall" text, + "icon" text, + "tenant" bigint NOT NULL, + "archived" boolean DEFAULT false NOT NULL, + "updated_at" timestamp with time zone, + "updated_by" uuid +); +--> statement-breakpoint +CREATE TABLE "serial_executions" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "tenant" bigint NOT NULL, + "execution_date" timestamp NOT NULL, + "status" text DEFAULT 'draft', + "created_by" text, + "created_at" timestamp DEFAULT now(), + "summary" text +); +--> statement-breakpoint +CREATE TABLE "public_links" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "token" text NOT NULL, + "tenant" integer NOT NULL, + "default_profile" uuid, + "is_protected" boolean DEFAULT false NOT NULL, + "pin_hash" text, + "config" jsonb DEFAULT '{}'::jsonb, + "name" text NOT NULL, + "description" text, + "active" boolean DEFAULT true NOT NULL, + "created_at" timestamp DEFAULT now(), + "updated_at" timestamp DEFAULT now(), + CONSTRAINT "public_links_token_unique" UNIQUE("token") +); +--> statement-breakpoint +CREATE TABLE "wiki_pages" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "tenant_id" bigint NOT NULL, + "parent_id" uuid, + "title" text NOT NULL, + "content" jsonb, + "is_folder" boolean DEFAULT false NOT NULL, + "sort_order" integer DEFAULT 0 NOT NULL, + "entity_type" text, + "entity_id" bigint, + "entity_uuid" uuid, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone, + "created_by" uuid, + "updated_by" uuid +); +--> statement-breakpoint +ALTER TABLE "time_events" DISABLE ROW LEVEL SECURITY;--> statement-breakpoint +DROP TABLE "time_events" CASCADE;--> statement-breakpoint +ALTER TABLE "projects" ALTER COLUMN "active_phase" SET DEFAULT 'Erstkontakt';--> statement-breakpoint +ALTER TABLE "createddocuments" ADD COLUMN "serialexecution" uuid;--> statement-breakpoint +ALTER TABLE "devices" ADD COLUMN "last_seen" timestamp with time zone;--> statement-breakpoint +ALTER TABLE "devices" ADD COLUMN "last_debug_info" jsonb;--> statement-breakpoint +ALTER TABLE "files" ADD COLUMN "size" bigint;--> statement-breakpoint +ALTER TABLE "m2m_api_keys" ADD CONSTRAINT "m2m_api_keys_tenant_id_tenants_id_fk" FOREIGN KEY ("tenant_id") REFERENCES "public"."tenants"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "m2m_api_keys" ADD CONSTRAINT "m2m_api_keys_user_id_auth_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."auth_users"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "m2m_api_keys" ADD CONSTRAINT "m2m_api_keys_created_by_auth_users_id_fk" FOREIGN KEY ("created_by") REFERENCES "public"."auth_users"("id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "staff_time_events" ADD CONSTRAINT "staff_time_events_tenant_id_tenants_id_fk" FOREIGN KEY ("tenant_id") REFERENCES "public"."tenants"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "staff_time_events" ADD CONSTRAINT "staff_time_events_user_id_auth_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."auth_users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "staff_time_events" ADD CONSTRAINT "staff_time_events_actor_user_id_auth_users_id_fk" FOREIGN KEY ("actor_user_id") REFERENCES "public"."auth_users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "staff_time_events" ADD CONSTRAINT "staff_time_events_invalidates_event_id_staff_time_events_id_fk" FOREIGN KEY ("invalidates_event_id") REFERENCES "public"."staff_time_events"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "staff_time_events" ADD CONSTRAINT "staff_time_events_related_event_id_staff_time_events_id_fk" FOREIGN KEY ("related_event_id") REFERENCES "public"."staff_time_events"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "serialtypes" ADD CONSTRAINT "serialtypes_tenant_tenants_id_fk" FOREIGN KEY ("tenant") REFERENCES "public"."tenants"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "serialtypes" ADD CONSTRAINT "serialtypes_updated_by_auth_users_id_fk" FOREIGN KEY ("updated_by") REFERENCES "public"."auth_users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "serial_executions" ADD CONSTRAINT "serial_executions_tenant_tenants_id_fk" FOREIGN KEY ("tenant") REFERENCES "public"."tenants"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "public_links" ADD CONSTRAINT "public_links_tenant_tenants_id_fk" FOREIGN KEY ("tenant") REFERENCES "public"."tenants"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "public_links" ADD CONSTRAINT "public_links_default_profile_auth_profiles_id_fk" FOREIGN KEY ("default_profile") REFERENCES "public"."auth_profiles"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "wiki_pages" ADD CONSTRAINT "wiki_pages_tenant_id_tenants_id_fk" FOREIGN KEY ("tenant_id") REFERENCES "public"."tenants"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "wiki_pages" ADD CONSTRAINT "wiki_pages_parent_id_wiki_pages_id_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."wiki_pages"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "wiki_pages" ADD CONSTRAINT "wiki_pages_created_by_auth_users_id_fk" FOREIGN KEY ("created_by") REFERENCES "public"."auth_users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "wiki_pages" ADD CONSTRAINT "wiki_pages_updated_by_auth_users_id_fk" FOREIGN KEY ("updated_by") REFERENCES "public"."auth_users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "idx_time_events_tenant_user_time" ON "staff_time_events" USING btree ("tenant_id","user_id","event_time");--> statement-breakpoint +CREATE INDEX "idx_time_events_created_at" ON "staff_time_events" USING btree ("created_at");--> statement-breakpoint +CREATE INDEX "idx_time_events_invalidates" ON "staff_time_events" USING btree ("invalidates_event_id");--> statement-breakpoint +CREATE INDEX "wiki_pages_tenant_idx" ON "wiki_pages" USING btree ("tenant_id");--> statement-breakpoint +CREATE INDEX "wiki_pages_parent_idx" ON "wiki_pages" USING btree ("parent_id");--> statement-breakpoint +CREATE INDEX "wiki_pages_entity_int_idx" ON "wiki_pages" USING btree ("tenant_id","entity_type","entity_id");--> statement-breakpoint +CREATE INDEX "wiki_pages_entity_uuid_idx" ON "wiki_pages" USING btree ("tenant_id","entity_type","entity_uuid");--> statement-breakpoint +ALTER TABLE "createddocuments" ADD CONSTRAINT "createddocuments_serialexecution_serial_executions_id_fk" FOREIGN KEY ("serialexecution") REFERENCES "public"."serial_executions"("id") ON DELETE no action ON UPDATE no action; \ No newline at end of file diff --git a/backend/db/migrations/0006_nifty_price_lock.sql b/backend/db/migrations/0006_nifty_price_lock.sql new file mode 100644 index 0000000..9942445 --- /dev/null +++ b/backend/db/migrations/0006_nifty_price_lock.sql @@ -0,0 +1 @@ +ALTER TABLE "services" ADD COLUMN "priceUpdateLocked" boolean DEFAULT false NOT NULL; diff --git a/backend/db/schema/services.ts b/backend/db/schema/services.ts index 0dcdd4b..c9491af 100644 --- a/backend/db/schema/services.ts +++ b/backend/db/schema/services.ts @@ -54,6 +54,7 @@ export const services = pgTable("services", { materialComposition: jsonb("materialComposition").notNull().default([]), personalComposition: jsonb("personalComposition").notNull().default([]), + priceUpdateLocked: boolean("priceUpdateLocked").notNull().default(false), updatedAt: timestamp("updated_at", { withTimezone: true }), updatedBy: uuid("updated_by").references(() => authUsers.id), diff --git a/backend/src/routes/resources/main.ts b/backend/src/routes/resources/main.ts index 17bc928..d0177c8 100644 --- a/backend/src/routes/resources/main.ts +++ b/backend/src/routes/resources/main.ts @@ -12,6 +12,7 @@ import { import { resourceConfig } from "../../utils/resource.config"; import { useNextNumberRangeNumber } from "../../utils/functions"; +import { recalculateServicePricesForTenant } from "../../modules/service-price-recalculation.service"; // ------------------------------------------------------------- // SQL Suche auf mehreren Feldern (Haupttabelle + Relationen) @@ -343,6 +344,11 @@ export default async function resourceRoutes(server: FastifyInstance) { }) const [created] = await server.db.insert(table).values(createData).returning() + + if (["products", "services", "hourrates"].includes(resource)) { + await recalculateServicePricesForTenant(server, req.user.tenant_id, req.user?.user_id || null); + } + return created; } catch (error) { console.error(error); @@ -374,9 +380,14 @@ export default async function resourceRoutes(server: FastifyInstance) { }) const [updated] = await server.db.update(table).set(data).where(and(eq(table.id, id), eq(table.tenant, tenantId))).returning() + + if (["products", "services", "hourrates"].includes(resource)) { + await recalculateServicePricesForTenant(server, tenantId, userId); + } + return updated } catch (err) { console.error(err) } }) -} \ No newline at end of file +} diff --git a/frontend/stores/data.js b/frontend/stores/data.js index 1206ac1..b04fdb0 100644 --- a/frontend/stores/data.js +++ b/frontend/stores/data.js @@ -1970,6 +1970,11 @@ export const useDataStore = defineStore('data', () => { label: "Verkaufspreis", component: sellingPrice, }, + { + key: "priceUpdateLocked", + label: "Preis-Update sperren", + inputType: "bool", + }, { key: "taxPercentage", label: "Umsatzsteuer",