@saasicat/spec 1.0.0-rc.2 → 1.0.0-rc.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +30 -0
  2. package/acceptance/README.md +3 -3
  3. package/acceptance/mfa/totp-verify-good-and-bad-code.yaml +6 -6
  4. package/admin-api.openapi.yaml +328 -77
  5. package/cli-conventions.md +19 -19
  6. package/index.cjs +3 -0
  7. package/index.d.cts +3 -1
  8. package/index.d.ts +2 -0
  9. package/index.js +9 -1
  10. package/package.json +4 -2
  11. package/prisma-fragments/01-subscription.prisma +34 -47
  12. package/prisma-fragments/03-plan-versions.prisma +3 -4
  13. package/prisma-fragments/05-bundle.prisma +4 -5
  14. package/prisma-fragments/06-catalog-entries.prisma +24 -24
  15. package/prisma-fragments/07-promotion.prisma +2 -3
  16. package/prisma-fragments/08-subscription-contract.prisma +42 -5
  17. package/prisma-fragments/09-pending-registration.prisma +32 -27
  18. package/prisma-fragments/11-subscription-bundle.prisma +17 -0
  19. package/prisma-fragments/12-applied-settings.prisma +55 -0
  20. package/prisma-fragments/13-subscriber.prisma +94 -0
  21. package/prisma-fragments/14-payments.prisma +100 -0
  22. package/prisma-fragments/README.md +40 -22
  23. package/schemas/admin-manifest.schema.json +9 -2
  24. package/schemas/plan-catalog.schema.json +239 -18
  25. package/schemas/tenant-ledger.schema.json +236 -0
  26. package/sql/1.0-a-contract-names-its-subscriber.postgres.sql +383 -0
  27. package/sql/1.0-a-payment-method-is-a-gateway-reference.postgres.sql +177 -0
  28. package/sql/1.0-a-settings-change-carries-its-order.postgres.sql +73 -0
  29. package/sql/1.0-line-items-record-their-money.postgres.sql +134 -0
  30. package/sql/1.0-remove-project-key.postgres.sql +203 -0
  31. package/sql/1.0-the-applied-settings-are-recorded.postgres.sql +67 -0
  32. package/sql/constraints.postgres.sql +76 -0
  33. package/sql/reference-schema.postgres.sql +299 -77
@@ -14,6 +14,9 @@
14
14
  -- prisma-fragments/09-pending-registration.prisma
15
15
  -- prisma-fragments/10-super-admin.prisma
16
16
  -- prisma-fragments/11-subscription-bundle.prisma
17
+ -- prisma-fragments/12-applied-settings.prisma
18
+ -- prisma-fragments/13-subscriber.prisma
19
+ -- prisma-fragments/14-payments.prisma
17
20
  -- plus the normative constraints from sql/constraints.postgres.sql.
18
21
  -- Do not edit by hand — change the fragments/constraints and regenerate.
19
22
 
@@ -26,9 +29,6 @@ CREATE TYPE "BillingCycle" AS ENUM ('MONTHLY', 'YEARLY');
26
29
  -- CreateEnum
27
30
  CREATE TYPE "SubscriptionStatus" AS ENUM ('TRIAL', 'ACTIVE', 'PAST_DUE', 'CANCELED', 'PENDING_SALES');
28
31
 
29
- -- CreateEnum
30
- CREATE TYPE "SubscriptionPaymentType" AS ENUM ('CARD', 'SEPA', 'PAYPAL', 'KLARNA', 'INVOICE');
31
-
32
32
  -- CreateEnum
33
33
  CREATE TYPE "PromoCodeValueType" AS ENUM ('PERCENT', 'ABSOLUTE');
34
34
 
@@ -60,8 +60,11 @@ CREATE TABLE "subscriptions" (
60
60
  "trialEndsAt" TIMESTAMP(3),
61
61
  "startedAt" TIMESTAMP(3),
62
62
  "canceledAt" TIMESTAMP(3),
63
+ "canceledEffectiveAt" TIMESTAMP(3),
63
64
  "currentPeriodStart" TIMESTAMP(3),
64
65
  "currentPeriodEnd" TIMESTAMP(3),
66
+ "minimumTermUntil" TIMESTAMP(3),
67
+ "billingAnchorDay" INTEGER,
65
68
  "planVersionId" TEXT NOT NULL,
66
69
  "pendingPlanVersionId" TEXT,
67
70
  "pendingPlanVersionEffectiveAt" TIMESTAMP(3),
@@ -89,29 +92,9 @@ CREATE TABLE "subscriptions" (
89
92
  CONSTRAINT "subscriptions_pkey" PRIMARY KEY ("id")
90
93
  );
91
94
 
92
- -- CreateTable
93
- CREATE TABLE "subscription_payment_methods" (
94
- "id" TEXT NOT NULL,
95
- "subscriptionId" TEXT NOT NULL,
96
- "type" "SubscriptionPaymentType" NOT NULL,
97
- "cardName" TEXT,
98
- "cardBrand" TEXT,
99
- "cardLast4" TEXT,
100
- "cardExp" TEXT,
101
- "ibanLast4" TEXT,
102
- "ibanName" TEXT,
103
- "paypalEmail" TEXT,
104
- "klarnaPlan" TEXT,
105
- "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
106
- "updatedAt" TIMESTAMP(3) NOT NULL,
107
-
108
- CONSTRAINT "subscription_payment_methods_pkey" PRIMARY KEY ("id")
109
- );
110
-
111
95
  -- CreateTable
112
96
  CREATE TABLE "checkout_offers" (
113
97
  "id" TEXT NOT NULL,
114
- "projectKey" TEXT NOT NULL,
115
98
  "planKey" TEXT NOT NULL,
116
99
  "planVersionId" TEXT,
117
100
  "billingCycle" TEXT NOT NULL,
@@ -197,7 +180,6 @@ CREATE TABLE "promo_code_validation_logs" (
197
180
  -- CreateTable
198
181
  CREATE TABLE "plans" (
199
182
  "id" TEXT NOT NULL,
200
- "projectKey" TEXT NOT NULL,
201
183
  "planKey" TEXT NOT NULL,
202
184
  "label" TEXT NOT NULL,
203
185
  "description" TEXT,
@@ -257,7 +239,6 @@ CREATE TABLE "audit_logs" (
257
239
  -- CreateTable
258
240
  CREATE TABLE "bundles" (
259
241
  "id" TEXT NOT NULL,
260
- "projectKey" TEXT NOT NULL,
261
242
  "bundleKey" TEXT NOT NULL,
262
243
  "label" TEXT NOT NULL,
263
244
  "description" TEXT,
@@ -302,7 +283,6 @@ CREATE TABLE "bundle_versions" (
302
283
  -- CreateTable
303
284
  CREATE TABLE "capability_catalog_entries" (
304
285
  "id" TEXT NOT NULL,
305
- "projectKey" TEXT NOT NULL,
306
286
  "capabilityKey" TEXT NOT NULL,
307
287
  "label" TEXT NOT NULL,
308
288
  "description" TEXT,
@@ -327,7 +307,6 @@ CREATE TABLE "capability_catalog_entries" (
327
307
  -- CreateTable
328
308
  CREATE TABLE "feature_catalog_entries" (
329
309
  "id" TEXT NOT NULL,
330
- "projectKey" TEXT NOT NULL,
331
310
  "featureKey" TEXT NOT NULL,
332
311
  "label" TEXT NOT NULL,
333
312
  "description" TEXT,
@@ -356,7 +335,6 @@ CREATE TABLE "feature_catalog_entries" (
356
335
  -- CreateTable
357
336
  CREATE TABLE "quota_catalog_entries" (
358
337
  "id" TEXT NOT NULL,
359
- "projectKey" TEXT NOT NULL,
360
338
  "quotaKey" TEXT NOT NULL,
361
339
  "label" TEXT NOT NULL,
362
340
  "description" TEXT,
@@ -382,7 +360,6 @@ CREATE TABLE "quota_catalog_entries" (
382
360
  -- CreateTable
383
361
  CREATE TABLE "marketing_projections" (
384
362
  "id" TEXT NOT NULL,
385
- "projectKey" TEXT NOT NULL,
386
363
  "targetType" TEXT NOT NULL,
387
364
  "targetVersionId" TEXT NOT NULL,
388
365
  "locale" TEXT NOT NULL DEFAULT 'de',
@@ -405,8 +382,7 @@ CREATE TABLE "marketing_projections" (
405
382
 
406
383
  -- CreateTable
407
384
  CREATE TABLE "marketing_settings" (
408
- "id" TEXT NOT NULL,
409
- "projectKey" TEXT NOT NULL,
385
+ "id" TEXT NOT NULL DEFAULT 'marketing-settings',
410
386
  "activeLocales" JSONB NOT NULL DEFAULT '[]',
411
387
  "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
412
388
  "updatedAt" TIMESTAMP(3) NOT NULL,
@@ -417,7 +393,6 @@ CREATE TABLE "marketing_settings" (
417
393
  -- CreateTable
418
394
  CREATE TABLE "promotions" (
419
395
  "id" TEXT NOT NULL,
420
- "projectKey" TEXT NOT NULL,
421
396
  "internalLabel" TEXT NOT NULL,
422
397
  "type" TEXT NOT NULL,
423
398
  "value" JSONB NOT NULL,
@@ -441,8 +416,11 @@ CREATE TABLE "promotions" (
441
416
  -- CreateTable
442
417
  CREATE TABLE "subscription_contracts" (
443
418
  "id" TEXT NOT NULL,
444
- "projectKey" TEXT NOT NULL,
445
419
  "tenantId" TEXT NOT NULL,
420
+ "subscriberId" TEXT NOT NULL,
421
+ "subscriberSnapshot" JSONB NOT NULL,
422
+ "issuerSnapshot" JSONB,
423
+ "partiesMigrated" BOOLEAN NOT NULL DEFAULT false,
446
424
  "status" "SubscriptionContractStatus" NOT NULL DEFAULT 'active',
447
425
  "effectiveFrom" TIMESTAMP(3) NOT NULL,
448
426
  "effectiveUntil" TIMESTAMP(3),
@@ -474,6 +452,9 @@ CREATE TABLE "contract_line_items" (
474
452
  "priceNet" DECIMAL(10,2) NOT NULL,
475
453
  "priceGross" DECIMAL(10,2) NOT NULL,
476
454
  "billingCycle" TEXT NOT NULL,
455
+ "currency" TEXT NOT NULL,
456
+ "taxRate" DECIMAL(5,2) NOT NULL,
457
+ "taxAmount" DECIMAL(10,2) NOT NULL,
477
458
  "minimumTermUntil" TIMESTAMP(3),
478
459
  "featuresSnapshot" JSONB NOT NULL DEFAULT '[]',
479
460
  "quotaEffectsSnapshot" JSONB NOT NULL DEFAULT '{}',
@@ -506,7 +487,16 @@ CREATE TABLE "PendingRegistration" (
506
487
  "configJson" JSONB,
507
488
  "billingCycle" TEXT,
508
489
  "appliedPromoCode" TEXT,
490
+ "addressLine1" TEXT,
491
+ "addressLine2" TEXT,
492
+ "postalCode" TEXT,
493
+ "city" TEXT,
494
+ "country" TEXT,
495
+ "vatId" TEXT,
496
+ "taxNumber" TEXT,
509
497
  "checkoutSessionId" TEXT,
498
+ "checkoutGatewayAccount" TEXT,
499
+ "gatewayCustomerRef" TEXT,
510
500
  "checkoutStartedAt" TIMESTAMP(3),
511
501
  "expiresAt" TIMESTAMP(3) NOT NULL,
512
502
  "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
@@ -515,19 +505,6 @@ CREATE TABLE "PendingRegistration" (
515
505
  CONSTRAINT "PendingRegistration_pkey" PRIMARY KEY ("id")
516
506
  );
517
507
 
518
- -- CreateTable
519
- CREATE TABLE "PaymentEventLog" (
520
- "id" TEXT NOT NULL,
521
- "eventId" TEXT NOT NULL,
522
- "provider" TEXT NOT NULL,
523
- "sessionId" TEXT,
524
- "status" TEXT NOT NULL,
525
- "payload" JSONB,
526
- "processedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
527
-
528
- CONSTRAINT "PaymentEventLog_pkey" PRIMARY KEY ("id")
529
- );
530
-
531
508
  -- CreateTable
532
509
  CREATE TABLE "super_admin_users" (
533
510
  "id" TEXT NOT NULL,
@@ -562,6 +539,9 @@ CREATE TABLE "subscription_bundles" (
562
539
  "bundleVersionId" TEXT NOT NULL,
563
540
  "startedAt" TIMESTAMP(3) NOT NULL,
564
541
  "minimumTermEndsAt" TIMESTAMP(3),
542
+ "billingCycle" TEXT,
543
+ "currentPeriodStart" TIMESTAMP(3),
544
+ "currentPeriodEnd" TIMESTAMP(3),
565
545
  "canceledAt" TIMESTAMP(3),
566
546
  "canceledEffectiveAt" TIMESTAMP(3),
567
547
  "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
@@ -570,6 +550,127 @@ CREATE TABLE "subscription_bundles" (
570
550
  CONSTRAINT "subscription_bundles_pkey" PRIMARY KEY ("id")
571
551
  );
572
552
 
553
+ -- CreateTable
554
+ CREATE TABLE "applied_settings" (
555
+ "id" TEXT NOT NULL DEFAULT 'installation',
556
+ "fingerprint" TEXT NOT NULL,
557
+ "settings" JSONB NOT NULL,
558
+ "source" TEXT NOT NULL,
559
+ "appliedAt" TIMESTAMP(3) NOT NULL,
560
+
561
+ CONSTRAINT "applied_settings_pkey" PRIMARY KEY ("id")
562
+ );
563
+
564
+ -- CreateTable
565
+ CREATE TABLE "settings_changes" (
566
+ "id" TEXT NOT NULL,
567
+ "seq" SERIAL NOT NULL,
568
+ "noticedAt" TIMESTAMP(3) NOT NULL,
569
+ "source" TEXT NOT NULL,
570
+ "previous" JSONB NOT NULL,
571
+ "current" JSONB NOT NULL,
572
+ "acknowledgedAt" TIMESTAMP(3),
573
+ "acknowledgedBy" TEXT,
574
+
575
+ CONSTRAINT "settings_changes_pkey" PRIMARY KEY ("id")
576
+ );
577
+
578
+ -- CreateTable
579
+ CREATE TABLE "subscribers" (
580
+ "id" TEXT NOT NULL,
581
+ "customerSequence" SERIAL NOT NULL,
582
+ "customerNumberPrefix" TEXT NOT NULL DEFAULT '',
583
+ "legalName" TEXT NOT NULL,
584
+ "vatId" TEXT,
585
+ "taxNumber" TEXT,
586
+ "addressLine1" TEXT,
587
+ "addressLine2" TEXT,
588
+ "postalCode" TEXT,
589
+ "city" TEXT,
590
+ "country" TEXT,
591
+ "invoiceEmail" TEXT,
592
+ "migrated" BOOLEAN NOT NULL DEFAULT false,
593
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
594
+ "updatedAt" TIMESTAMP(3) NOT NULL,
595
+
596
+ CONSTRAINT "subscribers_pkey" PRIMARY KEY ("id")
597
+ );
598
+
599
+ -- CreateTable
600
+ CREATE TABLE "subscriber_tenants" (
601
+ "id" TEXT NOT NULL,
602
+ "subscriberId" TEXT NOT NULL,
603
+ "tenantId" TEXT NOT NULL,
604
+ "linkedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
605
+ "unlinkedAt" TIMESTAMP(3),
606
+
607
+ CONSTRAINT "subscriber_tenants_pkey" PRIMARY KEY ("id")
608
+ );
609
+
610
+ -- CreateTable
611
+ CREATE TABLE "subscriber_corrections" (
612
+ "id" TEXT NOT NULL,
613
+ "subscriberId" TEXT NOT NULL,
614
+ "previous" JSONB NOT NULL,
615
+ "corrected" JSONB NOT NULL,
616
+ "reason" TEXT NOT NULL,
617
+ "correctedBy" TEXT NOT NULL,
618
+ "correctedAt" TIMESTAMP(3) NOT NULL,
619
+
620
+ CONSTRAINT "subscriber_corrections_pkey" PRIMARY KEY ("id")
621
+ );
622
+
623
+ -- CreateTable
624
+ CREATE TABLE "subscriber_payment_methods" (
625
+ "id" TEXT NOT NULL,
626
+ "subscriberId" TEXT NOT NULL,
627
+ "gatewayAccount" TEXT NOT NULL,
628
+ "provider" TEXT NOT NULL,
629
+ "customerRef" TEXT NOT NULL,
630
+ "paymentMethodRef" TEXT NOT NULL,
631
+ "type" TEXT NOT NULL,
632
+ "brand" TEXT,
633
+ "last4" TEXT NOT NULL,
634
+ "expiryMonth" INTEGER,
635
+ "expiryYear" INTEGER,
636
+ "country" TEXT,
637
+ "bankCode" TEXT,
638
+ "mandateReference" TEXT,
639
+ "status" TEXT NOT NULL DEFAULT 'ACTIVE',
640
+ "confirmedAt" TIMESTAMP(3) NOT NULL,
641
+ "replacedAt" TIMESTAMP(3),
642
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
643
+
644
+ CONSTRAINT "subscriber_payment_methods_pkey" PRIMARY KEY ("id")
645
+ );
646
+
647
+ -- CreateTable
648
+ CREATE TABLE "subscriber_payment_method_setups" (
649
+ "id" TEXT NOT NULL,
650
+ "subscriberId" TEXT NOT NULL,
651
+ "gatewayAccount" TEXT NOT NULL,
652
+ "sessionRef" TEXT NOT NULL,
653
+ "customerRef" TEXT NOT NULL,
654
+ "startedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
655
+ "completedAt" TIMESTAMP(3),
656
+
657
+ CONSTRAINT "subscriber_payment_method_setups_pkey" PRIMARY KEY ("id")
658
+ );
659
+
660
+ -- CreateTable
661
+ CREATE TABLE "PaymentEventLog" (
662
+ "id" TEXT NOT NULL,
663
+ "gatewayAccount" TEXT NOT NULL,
664
+ "eventId" TEXT NOT NULL,
665
+ "provider" TEXT NOT NULL,
666
+ "sessionId" TEXT,
667
+ "status" TEXT NOT NULL,
668
+ "payload" JSONB,
669
+ "processedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
670
+
671
+ CONSTRAINT "PaymentEventLog_pkey" PRIMARY KEY ("id")
672
+ );
673
+
573
674
  -- CreateIndex
574
675
  CREATE UNIQUE INDEX "subscriptions_tenantId_key" ON "subscriptions"("tenantId");
575
676
 
@@ -586,10 +687,7 @@ CREATE INDEX "subscriptions_pendingPlanVersionId_idx" ON "subscriptions"("pendin
586
687
  CREATE INDEX "subscriptions_currentPeriodEnd_idx" ON "subscriptions"("currentPeriodEnd");
587
688
 
588
689
  -- CreateIndex
589
- CREATE UNIQUE INDEX "subscription_payment_methods_subscriptionId_key" ON "subscription_payment_methods"("subscriptionId");
590
-
591
- -- CreateIndex
592
- CREATE INDEX "checkout_offers_projectKey_status_idx" ON "checkout_offers"("projectKey", "status");
690
+ CREATE INDEX "checkout_offers_status_idx" ON "checkout_offers"("status");
593
691
 
594
692
  -- CreateIndex
595
693
  CREATE UNIQUE INDEX "promo_codes_code_key" ON "promo_codes"("code");
@@ -622,10 +720,10 @@ CREATE INDEX "promo_code_validation_logs_ipHash_createdAt_idx" ON "promo_code_va
622
720
  CREATE INDEX "promo_code_validation_logs_sessionId_createdAt_idx" ON "promo_code_validation_logs"("sessionId", "createdAt");
623
721
 
624
722
  -- CreateIndex
625
- CREATE INDEX "plans_projectKey_deletedAt_idx" ON "plans"("projectKey", "deletedAt");
723
+ CREATE INDEX "plans_deletedAt_idx" ON "plans"("deletedAt");
626
724
 
627
725
  -- CreateIndex
628
- CREATE UNIQUE INDEX "plans_projectKey_planKey_key" ON "plans"("projectKey", "planKey");
726
+ CREATE UNIQUE INDEX "plans_planKey_key" ON "plans"("planKey");
629
727
 
630
728
  -- CreateIndex
631
729
  CREATE INDEX "plan_versions_planId_supersededAt_idx" ON "plan_versions"("planId", "supersededAt");
@@ -652,10 +750,10 @@ CREATE INDEX "audit_logs_entity_action_createdAt_idx" ON "audit_logs"("entity",
652
750
  CREATE INDEX "audit_logs_actorTag_createdAt_idx" ON "audit_logs"("actorTag", "createdAt");
653
751
 
654
752
  -- CreateIndex
655
- CREATE INDEX "bundles_projectKey_deletedAt_idx" ON "bundles"("projectKey", "deletedAt");
753
+ CREATE INDEX "bundles_deletedAt_idx" ON "bundles"("deletedAt");
656
754
 
657
755
  -- CreateIndex
658
- CREATE UNIQUE INDEX "bundles_projectKey_bundleKey_key" ON "bundles"("projectKey", "bundleKey");
756
+ CREATE UNIQUE INDEX "bundles_bundleKey_key" ON "bundles"("bundleKey");
659
757
 
660
758
  -- CreateIndex
661
759
  CREATE INDEX "bundle_versions_bundleId_supersededAt_idx" ON "bundle_versions"("bundleId", "supersededAt");
@@ -670,46 +768,46 @@ CREATE INDEX "bundle_versions_bundleId_validFrom_idx" ON "bundle_versions"("bund
670
768
  CREATE UNIQUE INDEX "bundle_versions_bundleId_version_key" ON "bundle_versions"("bundleId", "version");
671
769
 
672
770
  -- CreateIndex
673
- CREATE INDEX "capability_catalog_entries_projectKey_codeStatus_idx" ON "capability_catalog_entries"("projectKey", "codeStatus");
771
+ CREATE INDEX "capability_catalog_entries_codeStatus_idx" ON "capability_catalog_entries"("codeStatus");
674
772
 
675
773
  -- CreateIndex
676
- CREATE INDEX "capability_catalog_entries_projectKey_featureKey_idx" ON "capability_catalog_entries"("projectKey", "featureKey");
774
+ CREATE INDEX "capability_catalog_entries_featureKey_idx" ON "capability_catalog_entries"("featureKey");
677
775
 
678
776
  -- CreateIndex
679
- CREATE UNIQUE INDEX "capability_catalog_entries_projectKey_capabilityKey_key" ON "capability_catalog_entries"("projectKey", "capabilityKey");
777
+ CREATE UNIQUE INDEX "capability_catalog_entries_capabilityKey_key" ON "capability_catalog_entries"("capabilityKey");
680
778
 
681
779
  -- CreateIndex
682
- CREATE INDEX "feature_catalog_entries_projectKey_discoveryStatus_idx" ON "feature_catalog_entries"("projectKey", "discoveryStatus");
780
+ CREATE INDEX "feature_catalog_entries_discoveryStatus_idx" ON "feature_catalog_entries"("discoveryStatus");
683
781
 
684
782
  -- CreateIndex
685
- CREATE INDEX "feature_catalog_entries_projectKey_plannedOnly_idx" ON "feature_catalog_entries"("projectKey", "plannedOnly");
783
+ CREATE INDEX "feature_catalog_entries_plannedOnly_idx" ON "feature_catalog_entries"("plannedOnly");
686
784
 
687
785
  -- CreateIndex
688
- CREATE UNIQUE INDEX "feature_catalog_entries_projectKey_featureKey_key" ON "feature_catalog_entries"("projectKey", "featureKey");
786
+ CREATE UNIQUE INDEX "feature_catalog_entries_featureKey_key" ON "feature_catalog_entries"("featureKey");
689
787
 
690
788
  -- CreateIndex
691
- CREATE INDEX "quota_catalog_entries_projectKey_discoveryStatus_idx" ON "quota_catalog_entries"("projectKey", "discoveryStatus");
789
+ CREATE INDEX "quota_catalog_entries_discoveryStatus_idx" ON "quota_catalog_entries"("discoveryStatus");
692
790
 
693
791
  -- CreateIndex
694
- CREATE UNIQUE INDEX "quota_catalog_entries_projectKey_quotaKey_key" ON "quota_catalog_entries"("projectKey", "quotaKey");
792
+ CREATE UNIQUE INDEX "quota_catalog_entries_quotaKey_key" ON "quota_catalog_entries"("quotaKey");
695
793
 
696
794
  -- CreateIndex
697
- CREATE INDEX "marketing_projections_projectKey_targetType_locale_priority_idx" ON "marketing_projections"("projectKey", "targetType", "locale", "priority");
795
+ CREATE INDEX "marketing_projections_targetType_locale_priority_idx" ON "marketing_projections"("targetType", "locale", "priority");
698
796
 
699
797
  -- CreateIndex
700
798
  CREATE UNIQUE INDEX "marketing_projections_targetType_targetVersionId_locale_key" ON "marketing_projections"("targetType", "targetVersionId", "locale");
701
799
 
702
800
  -- CreateIndex
703
- CREATE UNIQUE INDEX "marketing_settings_projectKey_key" ON "marketing_settings"("projectKey");
801
+ CREATE INDEX "promotions_targetType_validFrom_validTo_idx" ON "promotions"("targetType", "validFrom", "validTo");
704
802
 
705
803
  -- CreateIndex
706
- CREATE INDEX "promotions_projectKey_targetType_validFrom_validTo_idx" ON "promotions"("projectKey", "targetType", "validFrom", "validTo");
804
+ CREATE INDEX "subscription_contracts_tenantId_status_effectiveFrom_idx" ON "subscription_contracts"("tenantId", "status", "effectiveFrom");
707
805
 
708
806
  -- CreateIndex
709
- CREATE INDEX "subscription_contracts_tenantId_status_effectiveFrom_idx" ON "subscription_contracts"("tenantId", "status", "effectiveFrom");
807
+ CREATE INDEX "subscription_contracts_subscriberId_idx" ON "subscription_contracts"("subscriberId");
710
808
 
711
809
  -- CreateIndex
712
- CREATE INDEX "subscription_contracts_projectKey_status_idx" ON "subscription_contracts"("projectKey", "status");
810
+ CREATE INDEX "subscription_contracts_status_idx" ON "subscription_contracts"("status");
713
811
 
714
812
  -- CreateIndex
715
813
  CREATE INDEX "subscription_contracts_originalOfferId_idx" ON "subscription_contracts"("originalOfferId");
@@ -730,13 +828,7 @@ CREATE INDEX "PendingRegistration_status_expiresAt_idx" ON "PendingRegistration"
730
828
  CREATE INDEX "PendingRegistration_tenantSlug_idx" ON "PendingRegistration"("tenantSlug");
731
829
 
732
830
  -- CreateIndex
733
- CREATE UNIQUE INDEX "PaymentEventLog_eventId_key" ON "PaymentEventLog"("eventId");
734
-
735
- -- CreateIndex
736
- CREATE INDEX "PaymentEventLog_sessionId_idx" ON "PaymentEventLog"("sessionId");
737
-
738
- -- CreateIndex
739
- CREATE INDEX "PaymentEventLog_status_processedAt_idx" ON "PaymentEventLog"("status", "processedAt");
831
+ CREATE INDEX "PendingRegistration_checkoutGatewayAccount_checkoutSessionI_idx" ON "PendingRegistration"("checkoutGatewayAccount", "checkoutSessionId");
740
832
 
741
833
  -- CreateIndex
742
834
  CREATE UNIQUE INDEX "super_admin_users_email_key" ON "super_admin_users"("email");
@@ -753,15 +845,54 @@ CREATE INDEX "subscription_bundles_bundleVersionId_idx" ON "subscription_bundles
753
845
  -- CreateIndex
754
846
  CREATE INDEX "subscription_bundles_canceledEffectiveAt_idx" ON "subscription_bundles"("canceledEffectiveAt");
755
847
 
848
+ -- CreateIndex
849
+ CREATE UNIQUE INDEX "settings_changes_seq_key" ON "settings_changes"("seq");
850
+
851
+ -- CreateIndex
852
+ CREATE INDEX "settings_changes_acknowledgedAt_noticedAt_idx" ON "settings_changes"("acknowledgedAt", "noticedAt");
853
+
854
+ -- CreateIndex
855
+ CREATE UNIQUE INDEX "subscribers_customerSequence_key" ON "subscribers"("customerSequence");
856
+
857
+ -- CreateIndex
858
+ CREATE INDEX "subscriber_tenants_tenantId_idx" ON "subscriber_tenants"("tenantId");
859
+
860
+ -- CreateIndex
861
+ CREATE INDEX "subscriber_tenants_subscriberId_idx" ON "subscriber_tenants"("subscriberId");
862
+
863
+ -- CreateIndex
864
+ CREATE INDEX "subscriber_corrections_subscriberId_correctedAt_idx" ON "subscriber_corrections"("subscriberId", "correctedAt");
865
+
866
+ -- CreateIndex
867
+ CREATE INDEX "subscriber_payment_methods_subscriberId_status_idx" ON "subscriber_payment_methods"("subscriberId", "status");
868
+
869
+ -- CreateIndex
870
+ CREATE INDEX "subscriber_payment_methods_gatewayAccount_status_idx" ON "subscriber_payment_methods"("gatewayAccount", "status");
871
+
872
+ -- CreateIndex
873
+ CREATE UNIQUE INDEX "subscriber_payment_methods_gatewayAccount_paymentMethodRef_key" ON "subscriber_payment_methods"("gatewayAccount", "paymentMethodRef");
874
+
875
+ -- CreateIndex
876
+ CREATE INDEX "subscriber_payment_method_setups_subscriberId_idx" ON "subscriber_payment_method_setups"("subscriberId");
877
+
878
+ -- CreateIndex
879
+ CREATE UNIQUE INDEX "subscriber_payment_method_setups_gatewayAccount_sessionRef_key" ON "subscriber_payment_method_setups"("gatewayAccount", "sessionRef");
880
+
881
+ -- CreateIndex
882
+ CREATE INDEX "PaymentEventLog_sessionId_idx" ON "PaymentEventLog"("sessionId");
883
+
884
+ -- CreateIndex
885
+ CREATE INDEX "PaymentEventLog_status_processedAt_idx" ON "PaymentEventLog"("status", "processedAt");
886
+
887
+ -- CreateIndex
888
+ CREATE UNIQUE INDEX "PaymentEventLog_gatewayAccount_eventId_key" ON "PaymentEventLog"("gatewayAccount", "eventId");
889
+
756
890
  -- AddForeignKey
757
891
  ALTER TABLE "subscriptions" ADD CONSTRAINT "subscriptions_planVersionId_fkey" FOREIGN KEY ("planVersionId") REFERENCES "plan_versions"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
758
892
 
759
893
  -- AddForeignKey
760
894
  ALTER TABLE "subscriptions" ADD CONSTRAINT "subscriptions_pendingPlanVersionId_fkey" FOREIGN KEY ("pendingPlanVersionId") REFERENCES "plan_versions"("id") ON DELETE SET NULL ON UPDATE CASCADE;
761
895
 
762
- -- AddForeignKey
763
- ALTER TABLE "subscription_payment_methods" ADD CONSTRAINT "subscription_payment_methods_subscriptionId_fkey" FOREIGN KEY ("subscriptionId") REFERENCES "subscriptions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
764
-
765
896
  -- AddForeignKey
766
897
  ALTER TABLE "promo_code_redemptions" ADD CONSTRAINT "promo_code_redemptions_promoCodeId_fkey" FOREIGN KEY ("promoCodeId") REFERENCES "promo_codes"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
767
898
 
@@ -780,6 +911,9 @@ ALTER TABLE "bundle_versions" ADD CONSTRAINT "bundle_versions_bundleId_fkey" FOR
780
911
  -- AddForeignKey
781
912
  ALTER TABLE "bundle_versions" ADD CONSTRAINT "bundle_versions_baseVersionId_fkey" FOREIGN KEY ("baseVersionId") REFERENCES "bundle_versions"("id") ON DELETE SET NULL ON UPDATE CASCADE;
782
913
 
914
+ -- AddForeignKey
915
+ ALTER TABLE "subscription_contracts" ADD CONSTRAINT "subscription_contracts_subscriberId_fkey" FOREIGN KEY ("subscriberId") REFERENCES "subscribers"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
916
+
783
917
  -- AddForeignKey
784
918
  ALTER TABLE "contract_line_items" ADD CONSTRAINT "contract_line_items_contractId_fkey" FOREIGN KEY ("contractId") REFERENCES "subscription_contracts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
785
919
 
@@ -789,6 +923,18 @@ ALTER TABLE "subscription_bundles" ADD CONSTRAINT "subscription_bundles_subscrip
789
923
  -- AddForeignKey
790
924
  ALTER TABLE "subscription_bundles" ADD CONSTRAINT "subscription_bundles_bundleVersionId_fkey" FOREIGN KEY ("bundleVersionId") REFERENCES "bundle_versions"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
791
925
 
926
+ -- AddForeignKey
927
+ ALTER TABLE "subscriber_tenants" ADD CONSTRAINT "subscriber_tenants_subscriberId_fkey" FOREIGN KEY ("subscriberId") REFERENCES "subscribers"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
928
+
929
+ -- AddForeignKey
930
+ ALTER TABLE "subscriber_corrections" ADD CONSTRAINT "subscriber_corrections_subscriberId_fkey" FOREIGN KEY ("subscriberId") REFERENCES "subscribers"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
931
+
932
+ -- AddForeignKey
933
+ ALTER TABLE "subscriber_payment_methods" ADD CONSTRAINT "subscriber_payment_methods_subscriberId_fkey" FOREIGN KEY ("subscriberId") REFERENCES "subscribers"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
934
+
935
+ -- AddForeignKey
936
+ ALTER TABLE "subscriber_payment_method_setups" ADD CONSTRAINT "subscriber_payment_method_setups_subscriberId_fkey" FOREIGN KEY ("subscriberId") REFERENCES "subscribers"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
937
+
792
938
  -- =============================================================================
793
939
  -- SaaSiCat — normative PostgreSQL constraints the Prisma DSL cannot express.
794
940
  -- =============================================================================
@@ -808,3 +954,79 @@ CREATE UNIQUE INDEX IF NOT EXISTS plan_versions_draft_per_plan
808
954
 
809
955
  CREATE UNIQUE INDEX IF NOT EXISTS bundle_versions_draft_per_bundle
810
956
  ON bundle_versions ("bundleId") WHERE "publishedAt" IS NULL;
957
+
958
+ -- `marketing_settings` holds at most ONE row.
959
+ --
960
+ -- The Prisma DSL cannot say this, and the constant default on `id` does not: a
961
+ -- default applies only where the caller omits the value, and a primary key
962
+ -- accepts every distinct one. A consumer writing the row directly could make as
963
+ -- many as it liked, and the repository would then read one and ignore the rest.
964
+ --
965
+ -- `ADD CONSTRAINT` has no `IF NOT EXISTS`, and this file is applied again on
966
+ -- every deployment — so it is dropped first. Two plain statements rather than a
967
+ -- dollar-quoted `DO` block on purpose: every consumer of this file splits it at
968
+ -- the statement separator, and a dollar-quoted body would need each of those
969
+ -- splitters to become a SQL lexer. For the same reason no comment in this file
970
+ -- may contain that separator — one here did, and three splitters cut the
971
+ -- sentence in half.
972
+ ALTER TABLE marketing_settings
973
+ DROP CONSTRAINT IF EXISTS marketing_settings_is_a_singleton;
974
+ ALTER TABLE marketing_settings
975
+ ADD CONSTRAINT marketing_settings_is_a_singleton CHECK ("id" = 'marketing-settings');
976
+
977
+ -- `applied_settings` holds at most ONE row: the installation's.
978
+ --
979
+ -- Same reasoning as above, and the same two plain statements. The row mirrors
980
+ -- the settings the installation applied at its last start, and an installation
981
+ -- serves one application, so there is exactly one configuration to record.
982
+ ALTER TABLE applied_settings
983
+ DROP CONSTRAINT IF EXISTS applied_settings_is_a_singleton;
984
+ ALTER TABLE applied_settings
985
+ ADD CONSTRAINT applied_settings_is_a_singleton CHECK ("id" = 'installation');
986
+
987
+ -- A subscriber is live for at most ONE tenant, and a tenant has at most ONE
988
+ -- live subscriber. A link that ended keeps its row with `unlinkedAt` set, so the
989
+ -- tenants a subscriber had before stay in its history. Two partial unique
990
+ -- indexes, because a link that is over must not count against the next one.
991
+ CREATE UNIQUE INDEX IF NOT EXISTS subscriber_tenants_live_per_tenant
992
+ ON subscriber_tenants ("tenantId") WHERE "unlinkedAt" IS NULL;
993
+
994
+ CREATE UNIQUE INDEX IF NOT EXISTS subscriber_tenants_live_per_subscriber
995
+ ON subscriber_tenants ("subscriberId") WHERE "unlinkedAt" IS NULL;
996
+
997
+ -- A subscriber has at most ONE payment method in use. The one a newer payment
998
+ -- method replaced keeps its row as `REPLACED`, so it does not count here.
999
+ CREATE UNIQUE INDEX IF NOT EXISTS subscriber_payment_methods_active_per_subscriber
1000
+ ON subscriber_payment_methods ("subscriberId") WHERE "status" = 'ACTIVE';
1001
+
1002
+ -- A gateway session is confirmed ONCE.
1003
+ --
1004
+ -- A gateway delivers every event at least once, and it may report one session
1005
+ -- through more than one event — a completed form and the payment method behind
1006
+ -- it can arrive as two. `eventId` tells those apart, so both would be claimed,
1007
+ -- and both would record the session's payment method: for a sign-up that means
1008
+ -- a second tenant for one registration.
1009
+ --
1010
+ -- Both adapters claim with `ON CONFLICT DO NOTHING` and no conflict target, so
1011
+ -- this index turns the second confirmation into no row rather than an error,
1012
+ -- and the caller reads it as the duplicate it is. Partial on the kind, because
1013
+ -- a failed setup and an event SaaSiCat does not act on say nothing about the
1014
+ -- session being confirmed. A claim carrying no session does not collide:
1015
+ -- PostgreSQL counts nulls as distinct in a unique index.
1016
+ CREATE UNIQUE INDEX IF NOT EXISTS payment_event_log_confirmation_per_session
1017
+ ON "PaymentEventLog" ("gatewayAccount", "sessionId")
1018
+ WHERE "status" = 'payment-method-confirmed';
1019
+
1020
+ -- Customer numbers count from 10001, so a number has five digits up to 99999
1021
+ -- and none reads as a count of subscribers. Two statements: the first makes
1022
+ -- 10001 where the sequence starts over, which a restart of its identity reads,
1023
+ -- and the second moves a sequence that has never handed out a number there. A
1024
+ -- second run finds the start already set and the sequence used, and an
1025
+ -- installation without the subscriber tables has no such sequence.
1026
+ ALTER SEQUENCE IF EXISTS "subscribers_customerSequence_seq" START WITH 10001;
1027
+
1028
+ SELECT setval(format('%I.%I', schemaname, sequencename)::regclass, 10001, false)
1029
+ FROM pg_sequences
1030
+ WHERE schemaname = current_schema()
1031
+ AND sequencename = 'subscribers_customerSequence_seq'
1032
+ AND last_value IS NULL;