@saasicat/adapter-prisma 1.0.0-rc.4 → 1.0.0-rc.6
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.
- package/dist/.build-stamp +1 -1
- package/dist/index.cjs +118 -22
- package/dist/index.d.cts +17 -2
- package/dist/index.d.ts +17 -2
- package/dist/index.js +118 -22
- package/package.json +4 -4
package/dist/.build-stamp
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
529e89589dea5797242ccc83833f15705b7fbf7c056430280539f88b62f3d037
|
package/dist/index.cjs
CHANGED
|
@@ -4073,9 +4073,14 @@ var PrismaSubscriptionContractRepository = class {
|
|
|
4073
4073
|
where: {
|
|
4074
4074
|
id: contractId
|
|
4075
4075
|
},
|
|
4076
|
+
// A null status leaves the column alone: the contract keeps whatever
|
|
4077
|
+
// it had — `active`, usually — and its new `effectiveUntil` is what
|
|
4078
|
+
// takes it out of the active lookup once that moment arrives.
|
|
4076
4079
|
data: {
|
|
4077
4080
|
effectiveUntil: data.effectiveUntil,
|
|
4078
|
-
status:
|
|
4081
|
+
...data.status === null ? {} : {
|
|
4082
|
+
status: data.status
|
|
4083
|
+
}
|
|
4079
4084
|
},
|
|
4080
4085
|
include: {
|
|
4081
4086
|
lineItems: true
|
|
@@ -4242,10 +4247,36 @@ var PrismaSubscriptionRepository = class {
|
|
|
4242
4247
|
async countActiveByPlanKey(projectKey) {
|
|
4243
4248
|
const db = this.db();
|
|
4244
4249
|
const subscriptions = await this.subscriptions(db).findMany({
|
|
4250
|
+
// Status alone answers the wrong question. A cancellation that has
|
|
4251
|
+
// taken effect leaves the column at ACTIVE — nothing transitions it
|
|
4252
|
+
// — so a plan's tenant count in the SuperAdmin UI would carry every
|
|
4253
|
+
// customer who ever left.
|
|
4254
|
+
//
|
|
4255
|
+
// Three branches, because the effective date lives in two places.
|
|
4256
|
+
// On a row written before the fields separated it is `canceledAt`
|
|
4257
|
+
// and the second column is null; reading only the second excludes
|
|
4258
|
+
// that customer on the day they DECLARE, months before they leave,
|
|
4259
|
+
// and the migration guide promises no backfill is needed.
|
|
4245
4260
|
where: {
|
|
4246
4261
|
status: {
|
|
4247
4262
|
in: ACTIVE_STATUSES
|
|
4248
|
-
}
|
|
4263
|
+
},
|
|
4264
|
+
OR: [
|
|
4265
|
+
{
|
|
4266
|
+
canceledAt: null
|
|
4267
|
+
},
|
|
4268
|
+
{
|
|
4269
|
+
canceledEffectiveAt: {
|
|
4270
|
+
gt: /* @__PURE__ */ new Date()
|
|
4271
|
+
}
|
|
4272
|
+
},
|
|
4273
|
+
{
|
|
4274
|
+
canceledEffectiveAt: null,
|
|
4275
|
+
canceledAt: {
|
|
4276
|
+
gt: /* @__PURE__ */ new Date()
|
|
4277
|
+
}
|
|
4278
|
+
}
|
|
4279
|
+
]
|
|
4249
4280
|
},
|
|
4250
4281
|
select: {
|
|
4251
4282
|
planVersionId: true
|
|
@@ -4334,6 +4365,12 @@ var PrismaSubscriptionRepository = class {
|
|
|
4334
4365
|
pendingPlan: row.pendingPlan,
|
|
4335
4366
|
pendingEffectiveAt: row.pendingEffectiveAt,
|
|
4336
4367
|
customLimits: row.customLimits ?? null,
|
|
4368
|
+
// Entitlement resolution ends a subscription by reading these. An
|
|
4369
|
+
// adapter that leaves them out grants a subscription that ended
|
|
4370
|
+
// last January everything it had — which is why the port requires
|
|
4371
|
+
// them rather than defaulting them to null here.
|
|
4372
|
+
canceledAt: row.canceledAt ?? null,
|
|
4373
|
+
canceledEffectiveAt: row.canceledEffectiveAt ?? null,
|
|
4337
4374
|
planVersionId: row.planVersionId,
|
|
4338
4375
|
planVersion: {
|
|
4339
4376
|
planId: planKey,
|
|
@@ -4479,6 +4516,13 @@ var PrismaSubscriptionUsageAdapter = class {
|
|
|
4479
4516
|
startedAt: subscription.startedAt,
|
|
4480
4517
|
currentPeriodStart: subscription.currentPeriodStart ?? null,
|
|
4481
4518
|
currentPeriodEnd: subscription.currentPeriodEnd ?? null,
|
|
4519
|
+
// Without these three the cancellation rules read a term that is
|
|
4520
|
+
// always null and the tenant page shows a cancelled subscription as
|
|
4521
|
+
// running: the fields exist on the record, the shipped adapter did
|
|
4522
|
+
// not map them, and every consumer using it got the defaults.
|
|
4523
|
+
minimumTermUntil: subscription.minimumTermUntil ?? null,
|
|
4524
|
+
canceledAt: subscription.canceledAt ?? null,
|
|
4525
|
+
canceledEffectiveAt: subscription.canceledEffectiveAt ?? null,
|
|
4482
4526
|
pendingPlan: subscription.pendingPlan,
|
|
4483
4527
|
pendingBillingCycle: subscription.pendingBillingCycle ?? null,
|
|
4484
4528
|
pendingEffectiveAt: subscription.pendingEffectiveAt,
|
|
@@ -4670,35 +4714,46 @@ var PrismaTenantSubscriptionWriteAdapter = class {
|
|
|
4670
4714
|
} : {}
|
|
4671
4715
|
};
|
|
4672
4716
|
if (this.schema.tenantSubscription.synchronizePlanVersion) {
|
|
4673
|
-
const
|
|
4717
|
+
const current2 = await subscription.findUnique({
|
|
4674
4718
|
where: {
|
|
4675
4719
|
tenantId
|
|
4676
4720
|
}
|
|
4677
4721
|
});
|
|
4678
|
-
if (!
|
|
4722
|
+
if (!current2) {
|
|
4679
4723
|
throw new Error(`No subscription for tenant ${tenantId}.`);
|
|
4680
4724
|
}
|
|
4681
4725
|
const storagePlanId = await this.planBinding.toStoragePlanId(client, input.planId);
|
|
4682
4726
|
data.planVersionId = await this.findTargetPlanVersionId(client, storagePlanId, input.periodStart ?? /* @__PURE__ */ new Date());
|
|
4683
|
-
if (await this.pendingVersionBelongsToAnotherPlan(client,
|
|
4727
|
+
if (await this.pendingVersionBelongsToAnotherPlan(client, current2.pendingPlanVersionId, storagePlanId)) {
|
|
4684
4728
|
Object.assign(data, clearedPendingVersionData());
|
|
4685
4729
|
}
|
|
4686
4730
|
}
|
|
4687
|
-
const
|
|
4731
|
+
const claim = await subscription.updateMany({
|
|
4688
4732
|
where: {
|
|
4689
|
-
tenantId
|
|
4733
|
+
tenantId,
|
|
4734
|
+
canceledAt: input.expectedCanceledAt
|
|
4690
4735
|
},
|
|
4691
4736
|
data
|
|
4692
4737
|
});
|
|
4738
|
+
const current = await subscription.findUnique({
|
|
4739
|
+
where: {
|
|
4740
|
+
tenantId
|
|
4741
|
+
}
|
|
4742
|
+
});
|
|
4743
|
+
if (!current) {
|
|
4744
|
+
throw new Error(`No subscription for tenant ${tenantId}.`);
|
|
4745
|
+
}
|
|
4693
4746
|
return {
|
|
4694
|
-
plan:
|
|
4695
|
-
billingCycle:
|
|
4747
|
+
plan: current.plan,
|
|
4748
|
+
billingCycle: current.billingCycle,
|
|
4749
|
+
claimed: claim.count > 0
|
|
4696
4750
|
};
|
|
4697
4751
|
}
|
|
4698
4752
|
async schedulePlanChange(tenantId, input) {
|
|
4699
|
-
await this.subscription(this.prisma).
|
|
4753
|
+
const claim = await this.subscription(this.prisma).updateMany({
|
|
4700
4754
|
where: {
|
|
4701
|
-
tenantId
|
|
4755
|
+
tenantId,
|
|
4756
|
+
canceledAt: input.expectedCanceledAt
|
|
4702
4757
|
},
|
|
4703
4758
|
data: {
|
|
4704
4759
|
pendingPlan: input.pendingPlan,
|
|
@@ -4706,6 +4761,9 @@ var PrismaTenantSubscriptionWriteAdapter = class {
|
|
|
4706
4761
|
pendingEffectiveAt: input.pendingEffectiveAt
|
|
4707
4762
|
}
|
|
4708
4763
|
});
|
|
4764
|
+
return {
|
|
4765
|
+
claimed: claim.count > 0
|
|
4766
|
+
};
|
|
4709
4767
|
}
|
|
4710
4768
|
async acceptPendingPlanVersion(tenantId, userId, now) {
|
|
4711
4769
|
const subscription = this.subscription(this.prisma);
|
|
@@ -4772,12 +4830,30 @@ var PrismaTenantSubscriptionWriteAdapter = class {
|
|
|
4772
4830
|
const storagePlanId = await this.planBinding.toStoragePlanId(tx, input.planId);
|
|
4773
4831
|
data.planVersionId = await this.findTargetPlanVersionId(tx, storagePlanId, input.periodStart ?? /* @__PURE__ */ new Date());
|
|
4774
4832
|
}
|
|
4775
|
-
const
|
|
4833
|
+
const claim = await this.subscription(tx).updateMany({
|
|
4776
4834
|
where: {
|
|
4777
|
-
tenantId
|
|
4835
|
+
tenantId,
|
|
4836
|
+
canceledAt: input.expectedCanceledAt
|
|
4778
4837
|
},
|
|
4779
4838
|
data
|
|
4780
4839
|
});
|
|
4840
|
+
const updated = await this.subscription(tx).findUnique({
|
|
4841
|
+
where: {
|
|
4842
|
+
tenantId
|
|
4843
|
+
}
|
|
4844
|
+
});
|
|
4845
|
+
if (!updated) {
|
|
4846
|
+
throw new Error(`No subscription for tenant ${tenantId}.`);
|
|
4847
|
+
}
|
|
4848
|
+
if (claim.count === 0) {
|
|
4849
|
+
return {
|
|
4850
|
+
plan: updated.plan,
|
|
4851
|
+
billingCycle: updated.billingCycle,
|
|
4852
|
+
subscriptionId: updated.id,
|
|
4853
|
+
promoRedemption: null,
|
|
4854
|
+
claimed: false
|
|
4855
|
+
};
|
|
4856
|
+
}
|
|
4781
4857
|
let promoRedemption = null;
|
|
4782
4858
|
if (redeemPromo) {
|
|
4783
4859
|
promoRedemption = await redeemPromo(tx, updated.id);
|
|
@@ -4786,11 +4862,12 @@ var PrismaTenantSubscriptionWriteAdapter = class {
|
|
|
4786
4862
|
plan: updated.plan,
|
|
4787
4863
|
billingCycle: updated.billingCycle,
|
|
4788
4864
|
subscriptionId: updated.id,
|
|
4789
|
-
promoRedemption
|
|
4865
|
+
promoRedemption,
|
|
4866
|
+
claimed: true
|
|
4790
4867
|
};
|
|
4791
4868
|
});
|
|
4792
4869
|
}
|
|
4793
|
-
async cancelSubscription(tenantId,
|
|
4870
|
+
async cancelSubscription(tenantId, input) {
|
|
4794
4871
|
const subscription = this.subscription(this.prisma);
|
|
4795
4872
|
const sub = await subscription.findUnique({
|
|
4796
4873
|
where: {
|
|
@@ -4800,19 +4877,38 @@ var PrismaTenantSubscriptionWriteAdapter = class {
|
|
|
4800
4877
|
if (!sub) {
|
|
4801
4878
|
throw new Error(`No subscription for tenant ${tenantId}.`);
|
|
4802
4879
|
}
|
|
4803
|
-
const
|
|
4804
|
-
const updated = await subscription.update({
|
|
4880
|
+
const claimed = await subscription.updateMany({
|
|
4805
4881
|
where: {
|
|
4806
|
-
tenantId
|
|
4882
|
+
tenantId,
|
|
4883
|
+
canceledAt: null,
|
|
4884
|
+
canceledEffectiveAt: null
|
|
4807
4885
|
},
|
|
4808
4886
|
data: {
|
|
4809
|
-
canceledAt,
|
|
4810
|
-
|
|
4887
|
+
canceledAt: input.canceledAt,
|
|
4888
|
+
canceledEffectiveAt: input.effectiveAt,
|
|
4889
|
+
// Absent means unchanged rather than null: an ordinary
|
|
4890
|
+
// cancellation does not touch the commitment it was measured
|
|
4891
|
+
// against, and writing the field on every call would erase a
|
|
4892
|
+
// term that is still running.
|
|
4893
|
+
...input.minimumTermUntil ? {
|
|
4894
|
+
minimumTermUntil: input.minimumTermUntil
|
|
4895
|
+
} : {},
|
|
4896
|
+
status: input.terminateNow ? "CANCELED" : sub.status
|
|
4811
4897
|
}
|
|
4812
4898
|
});
|
|
4899
|
+
const current = await subscription.findUnique({
|
|
4900
|
+
where: {
|
|
4901
|
+
tenantId
|
|
4902
|
+
}
|
|
4903
|
+
});
|
|
4904
|
+
if (!current) {
|
|
4905
|
+
throw new Error(`No subscription for tenant ${tenantId}.`);
|
|
4906
|
+
}
|
|
4813
4907
|
return {
|
|
4814
|
-
canceledAt:
|
|
4815
|
-
|
|
4908
|
+
canceledAt: current.canceledAt ?? null,
|
|
4909
|
+
canceledEffectiveAt: current.canceledEffectiveAt ?? null,
|
|
4910
|
+
status: current.status,
|
|
4911
|
+
alreadyCanceled: claimed.count === 0
|
|
4816
4912
|
};
|
|
4817
4913
|
}
|
|
4818
4914
|
subscription(client) {
|
package/dist/index.d.cts
CHANGED
|
@@ -24,6 +24,11 @@ interface SubscriptionRowLike {
|
|
|
24
24
|
pilotEndsAt?: Date | null;
|
|
25
25
|
currentPeriodStart?: Date | null;
|
|
26
26
|
currentPeriodEnd?: Date | null;
|
|
27
|
+
/** End of what was committed to. Null on a trial or a pre-existing row. */
|
|
28
|
+
minimumTermUntil?: Date | null;
|
|
29
|
+
/** When a cancellation was declared, and when it lands. */
|
|
30
|
+
canceledAt?: Date | null;
|
|
31
|
+
canceledEffectiveAt?: Date | null;
|
|
27
32
|
pendingBillingCycle?: string | null;
|
|
28
33
|
pendingPlanVersionEffectiveAt?: Date | null;
|
|
29
34
|
pendingPlanVersionAccepted?: boolean;
|
|
@@ -1190,9 +1195,12 @@ declare class PrismaTenantSubscriptionWriteAdapter implements TenantSubscription
|
|
|
1190
1195
|
changePlanImmediate(tenantId: string, input: ImmediatePlanChangeInput): Promise<{
|
|
1191
1196
|
plan: string;
|
|
1192
1197
|
billingCycle: string;
|
|
1198
|
+
claimed: boolean;
|
|
1193
1199
|
}>;
|
|
1194
1200
|
private changePlanImmediateInClient;
|
|
1195
|
-
schedulePlanChange(tenantId: string, input: ScheduledPlanChangeInput): Promise<
|
|
1201
|
+
schedulePlanChange(tenantId: string, input: ScheduledPlanChangeInput): Promise<{
|
|
1202
|
+
claimed: boolean;
|
|
1203
|
+
}>;
|
|
1196
1204
|
acceptPendingPlanVersion(tenantId: string, userId: string, now: Date): Promise<{
|
|
1197
1205
|
accepted: boolean;
|
|
1198
1206
|
acceptedAt: Date | null;
|
|
@@ -1200,9 +1208,16 @@ declare class PrismaTenantSubscriptionWriteAdapter implements TenantSubscription
|
|
|
1200
1208
|
alreadyAccepted: boolean;
|
|
1201
1209
|
}>;
|
|
1202
1210
|
private applyOnboardingSelectionAtomic;
|
|
1203
|
-
cancelSubscription(tenantId: string,
|
|
1211
|
+
cancelSubscription(tenantId: string, input: {
|
|
1212
|
+
canceledAt: Date;
|
|
1213
|
+
effectiveAt: Date;
|
|
1214
|
+
terminateNow: boolean;
|
|
1215
|
+
minimumTermUntil?: Date;
|
|
1216
|
+
}): Promise<{
|
|
1204
1217
|
canceledAt: Date | null;
|
|
1218
|
+
canceledEffectiveAt: Date | null;
|
|
1205
1219
|
status: string;
|
|
1220
|
+
alreadyCanceled: boolean;
|
|
1206
1221
|
}>;
|
|
1207
1222
|
private subscription;
|
|
1208
1223
|
private planVersions;
|
package/dist/index.d.ts
CHANGED
|
@@ -24,6 +24,11 @@ interface SubscriptionRowLike {
|
|
|
24
24
|
pilotEndsAt?: Date | null;
|
|
25
25
|
currentPeriodStart?: Date | null;
|
|
26
26
|
currentPeriodEnd?: Date | null;
|
|
27
|
+
/** End of what was committed to. Null on a trial or a pre-existing row. */
|
|
28
|
+
minimumTermUntil?: Date | null;
|
|
29
|
+
/** When a cancellation was declared, and when it lands. */
|
|
30
|
+
canceledAt?: Date | null;
|
|
31
|
+
canceledEffectiveAt?: Date | null;
|
|
27
32
|
pendingBillingCycle?: string | null;
|
|
28
33
|
pendingPlanVersionEffectiveAt?: Date | null;
|
|
29
34
|
pendingPlanVersionAccepted?: boolean;
|
|
@@ -1190,9 +1195,12 @@ declare class PrismaTenantSubscriptionWriteAdapter implements TenantSubscription
|
|
|
1190
1195
|
changePlanImmediate(tenantId: string, input: ImmediatePlanChangeInput): Promise<{
|
|
1191
1196
|
plan: string;
|
|
1192
1197
|
billingCycle: string;
|
|
1198
|
+
claimed: boolean;
|
|
1193
1199
|
}>;
|
|
1194
1200
|
private changePlanImmediateInClient;
|
|
1195
|
-
schedulePlanChange(tenantId: string, input: ScheduledPlanChangeInput): Promise<
|
|
1201
|
+
schedulePlanChange(tenantId: string, input: ScheduledPlanChangeInput): Promise<{
|
|
1202
|
+
claimed: boolean;
|
|
1203
|
+
}>;
|
|
1196
1204
|
acceptPendingPlanVersion(tenantId: string, userId: string, now: Date): Promise<{
|
|
1197
1205
|
accepted: boolean;
|
|
1198
1206
|
acceptedAt: Date | null;
|
|
@@ -1200,9 +1208,16 @@ declare class PrismaTenantSubscriptionWriteAdapter implements TenantSubscription
|
|
|
1200
1208
|
alreadyAccepted: boolean;
|
|
1201
1209
|
}>;
|
|
1202
1210
|
private applyOnboardingSelectionAtomic;
|
|
1203
|
-
cancelSubscription(tenantId: string,
|
|
1211
|
+
cancelSubscription(tenantId: string, input: {
|
|
1212
|
+
canceledAt: Date;
|
|
1213
|
+
effectiveAt: Date;
|
|
1214
|
+
terminateNow: boolean;
|
|
1215
|
+
minimumTermUntil?: Date;
|
|
1216
|
+
}): Promise<{
|
|
1204
1217
|
canceledAt: Date | null;
|
|
1218
|
+
canceledEffectiveAt: Date | null;
|
|
1205
1219
|
status: string;
|
|
1220
|
+
alreadyCanceled: boolean;
|
|
1206
1221
|
}>;
|
|
1207
1222
|
private subscription;
|
|
1208
1223
|
private planVersions;
|
package/dist/index.js
CHANGED
|
@@ -4012,9 +4012,14 @@ var PrismaSubscriptionContractRepository = class {
|
|
|
4012
4012
|
where: {
|
|
4013
4013
|
id: contractId
|
|
4014
4014
|
},
|
|
4015
|
+
// A null status leaves the column alone: the contract keeps whatever
|
|
4016
|
+
// it had — `active`, usually — and its new `effectiveUntil` is what
|
|
4017
|
+
// takes it out of the active lookup once that moment arrives.
|
|
4015
4018
|
data: {
|
|
4016
4019
|
effectiveUntil: data.effectiveUntil,
|
|
4017
|
-
status:
|
|
4020
|
+
...data.status === null ? {} : {
|
|
4021
|
+
status: data.status
|
|
4022
|
+
}
|
|
4018
4023
|
},
|
|
4019
4024
|
include: {
|
|
4020
4025
|
lineItems: true
|
|
@@ -4181,10 +4186,36 @@ var PrismaSubscriptionRepository = class {
|
|
|
4181
4186
|
async countActiveByPlanKey(projectKey) {
|
|
4182
4187
|
const db = this.db();
|
|
4183
4188
|
const subscriptions = await this.subscriptions(db).findMany({
|
|
4189
|
+
// Status alone answers the wrong question. A cancellation that has
|
|
4190
|
+
// taken effect leaves the column at ACTIVE — nothing transitions it
|
|
4191
|
+
// — so a plan's tenant count in the SuperAdmin UI would carry every
|
|
4192
|
+
// customer who ever left.
|
|
4193
|
+
//
|
|
4194
|
+
// Three branches, because the effective date lives in two places.
|
|
4195
|
+
// On a row written before the fields separated it is `canceledAt`
|
|
4196
|
+
// and the second column is null; reading only the second excludes
|
|
4197
|
+
// that customer on the day they DECLARE, months before they leave,
|
|
4198
|
+
// and the migration guide promises no backfill is needed.
|
|
4184
4199
|
where: {
|
|
4185
4200
|
status: {
|
|
4186
4201
|
in: ACTIVE_STATUSES
|
|
4187
|
-
}
|
|
4202
|
+
},
|
|
4203
|
+
OR: [
|
|
4204
|
+
{
|
|
4205
|
+
canceledAt: null
|
|
4206
|
+
},
|
|
4207
|
+
{
|
|
4208
|
+
canceledEffectiveAt: {
|
|
4209
|
+
gt: /* @__PURE__ */ new Date()
|
|
4210
|
+
}
|
|
4211
|
+
},
|
|
4212
|
+
{
|
|
4213
|
+
canceledEffectiveAt: null,
|
|
4214
|
+
canceledAt: {
|
|
4215
|
+
gt: /* @__PURE__ */ new Date()
|
|
4216
|
+
}
|
|
4217
|
+
}
|
|
4218
|
+
]
|
|
4188
4219
|
},
|
|
4189
4220
|
select: {
|
|
4190
4221
|
planVersionId: true
|
|
@@ -4273,6 +4304,12 @@ var PrismaSubscriptionRepository = class {
|
|
|
4273
4304
|
pendingPlan: row.pendingPlan,
|
|
4274
4305
|
pendingEffectiveAt: row.pendingEffectiveAt,
|
|
4275
4306
|
customLimits: row.customLimits ?? null,
|
|
4307
|
+
// Entitlement resolution ends a subscription by reading these. An
|
|
4308
|
+
// adapter that leaves them out grants a subscription that ended
|
|
4309
|
+
// last January everything it had — which is why the port requires
|
|
4310
|
+
// them rather than defaulting them to null here.
|
|
4311
|
+
canceledAt: row.canceledAt ?? null,
|
|
4312
|
+
canceledEffectiveAt: row.canceledEffectiveAt ?? null,
|
|
4276
4313
|
planVersionId: row.planVersionId,
|
|
4277
4314
|
planVersion: {
|
|
4278
4315
|
planId: planKey,
|
|
@@ -4418,6 +4455,13 @@ var PrismaSubscriptionUsageAdapter = class {
|
|
|
4418
4455
|
startedAt: subscription.startedAt,
|
|
4419
4456
|
currentPeriodStart: subscription.currentPeriodStart ?? null,
|
|
4420
4457
|
currentPeriodEnd: subscription.currentPeriodEnd ?? null,
|
|
4458
|
+
// Without these three the cancellation rules read a term that is
|
|
4459
|
+
// always null and the tenant page shows a cancelled subscription as
|
|
4460
|
+
// running: the fields exist on the record, the shipped adapter did
|
|
4461
|
+
// not map them, and every consumer using it got the defaults.
|
|
4462
|
+
minimumTermUntil: subscription.minimumTermUntil ?? null,
|
|
4463
|
+
canceledAt: subscription.canceledAt ?? null,
|
|
4464
|
+
canceledEffectiveAt: subscription.canceledEffectiveAt ?? null,
|
|
4421
4465
|
pendingPlan: subscription.pendingPlan,
|
|
4422
4466
|
pendingBillingCycle: subscription.pendingBillingCycle ?? null,
|
|
4423
4467
|
pendingEffectiveAt: subscription.pendingEffectiveAt,
|
|
@@ -4609,35 +4653,46 @@ var PrismaTenantSubscriptionWriteAdapter = class {
|
|
|
4609
4653
|
} : {}
|
|
4610
4654
|
};
|
|
4611
4655
|
if (this.schema.tenantSubscription.synchronizePlanVersion) {
|
|
4612
|
-
const
|
|
4656
|
+
const current2 = await subscription.findUnique({
|
|
4613
4657
|
where: {
|
|
4614
4658
|
tenantId
|
|
4615
4659
|
}
|
|
4616
4660
|
});
|
|
4617
|
-
if (!
|
|
4661
|
+
if (!current2) {
|
|
4618
4662
|
throw new Error(`No subscription for tenant ${tenantId}.`);
|
|
4619
4663
|
}
|
|
4620
4664
|
const storagePlanId = await this.planBinding.toStoragePlanId(client, input.planId);
|
|
4621
4665
|
data.planVersionId = await this.findTargetPlanVersionId(client, storagePlanId, input.periodStart ?? /* @__PURE__ */ new Date());
|
|
4622
|
-
if (await this.pendingVersionBelongsToAnotherPlan(client,
|
|
4666
|
+
if (await this.pendingVersionBelongsToAnotherPlan(client, current2.pendingPlanVersionId, storagePlanId)) {
|
|
4623
4667
|
Object.assign(data, clearedPendingVersionData());
|
|
4624
4668
|
}
|
|
4625
4669
|
}
|
|
4626
|
-
const
|
|
4670
|
+
const claim = await subscription.updateMany({
|
|
4627
4671
|
where: {
|
|
4628
|
-
tenantId
|
|
4672
|
+
tenantId,
|
|
4673
|
+
canceledAt: input.expectedCanceledAt
|
|
4629
4674
|
},
|
|
4630
4675
|
data
|
|
4631
4676
|
});
|
|
4677
|
+
const current = await subscription.findUnique({
|
|
4678
|
+
where: {
|
|
4679
|
+
tenantId
|
|
4680
|
+
}
|
|
4681
|
+
});
|
|
4682
|
+
if (!current) {
|
|
4683
|
+
throw new Error(`No subscription for tenant ${tenantId}.`);
|
|
4684
|
+
}
|
|
4632
4685
|
return {
|
|
4633
|
-
plan:
|
|
4634
|
-
billingCycle:
|
|
4686
|
+
plan: current.plan,
|
|
4687
|
+
billingCycle: current.billingCycle,
|
|
4688
|
+
claimed: claim.count > 0
|
|
4635
4689
|
};
|
|
4636
4690
|
}
|
|
4637
4691
|
async schedulePlanChange(tenantId, input) {
|
|
4638
|
-
await this.subscription(this.prisma).
|
|
4692
|
+
const claim = await this.subscription(this.prisma).updateMany({
|
|
4639
4693
|
where: {
|
|
4640
|
-
tenantId
|
|
4694
|
+
tenantId,
|
|
4695
|
+
canceledAt: input.expectedCanceledAt
|
|
4641
4696
|
},
|
|
4642
4697
|
data: {
|
|
4643
4698
|
pendingPlan: input.pendingPlan,
|
|
@@ -4645,6 +4700,9 @@ var PrismaTenantSubscriptionWriteAdapter = class {
|
|
|
4645
4700
|
pendingEffectiveAt: input.pendingEffectiveAt
|
|
4646
4701
|
}
|
|
4647
4702
|
});
|
|
4703
|
+
return {
|
|
4704
|
+
claimed: claim.count > 0
|
|
4705
|
+
};
|
|
4648
4706
|
}
|
|
4649
4707
|
async acceptPendingPlanVersion(tenantId, userId, now) {
|
|
4650
4708
|
const subscription = this.subscription(this.prisma);
|
|
@@ -4711,12 +4769,30 @@ var PrismaTenantSubscriptionWriteAdapter = class {
|
|
|
4711
4769
|
const storagePlanId = await this.planBinding.toStoragePlanId(tx, input.planId);
|
|
4712
4770
|
data.planVersionId = await this.findTargetPlanVersionId(tx, storagePlanId, input.periodStart ?? /* @__PURE__ */ new Date());
|
|
4713
4771
|
}
|
|
4714
|
-
const
|
|
4772
|
+
const claim = await this.subscription(tx).updateMany({
|
|
4715
4773
|
where: {
|
|
4716
|
-
tenantId
|
|
4774
|
+
tenantId,
|
|
4775
|
+
canceledAt: input.expectedCanceledAt
|
|
4717
4776
|
},
|
|
4718
4777
|
data
|
|
4719
4778
|
});
|
|
4779
|
+
const updated = await this.subscription(tx).findUnique({
|
|
4780
|
+
where: {
|
|
4781
|
+
tenantId
|
|
4782
|
+
}
|
|
4783
|
+
});
|
|
4784
|
+
if (!updated) {
|
|
4785
|
+
throw new Error(`No subscription for tenant ${tenantId}.`);
|
|
4786
|
+
}
|
|
4787
|
+
if (claim.count === 0) {
|
|
4788
|
+
return {
|
|
4789
|
+
plan: updated.plan,
|
|
4790
|
+
billingCycle: updated.billingCycle,
|
|
4791
|
+
subscriptionId: updated.id,
|
|
4792
|
+
promoRedemption: null,
|
|
4793
|
+
claimed: false
|
|
4794
|
+
};
|
|
4795
|
+
}
|
|
4720
4796
|
let promoRedemption = null;
|
|
4721
4797
|
if (redeemPromo) {
|
|
4722
4798
|
promoRedemption = await redeemPromo(tx, updated.id);
|
|
@@ -4725,11 +4801,12 @@ var PrismaTenantSubscriptionWriteAdapter = class {
|
|
|
4725
4801
|
plan: updated.plan,
|
|
4726
4802
|
billingCycle: updated.billingCycle,
|
|
4727
4803
|
subscriptionId: updated.id,
|
|
4728
|
-
promoRedemption
|
|
4804
|
+
promoRedemption,
|
|
4805
|
+
claimed: true
|
|
4729
4806
|
};
|
|
4730
4807
|
});
|
|
4731
4808
|
}
|
|
4732
|
-
async cancelSubscription(tenantId,
|
|
4809
|
+
async cancelSubscription(tenantId, input) {
|
|
4733
4810
|
const subscription = this.subscription(this.prisma);
|
|
4734
4811
|
const sub = await subscription.findUnique({
|
|
4735
4812
|
where: {
|
|
@@ -4739,19 +4816,38 @@ var PrismaTenantSubscriptionWriteAdapter = class {
|
|
|
4739
4816
|
if (!sub) {
|
|
4740
4817
|
throw new Error(`No subscription for tenant ${tenantId}.`);
|
|
4741
4818
|
}
|
|
4742
|
-
const
|
|
4743
|
-
const updated = await subscription.update({
|
|
4819
|
+
const claimed = await subscription.updateMany({
|
|
4744
4820
|
where: {
|
|
4745
|
-
tenantId
|
|
4821
|
+
tenantId,
|
|
4822
|
+
canceledAt: null,
|
|
4823
|
+
canceledEffectiveAt: null
|
|
4746
4824
|
},
|
|
4747
4825
|
data: {
|
|
4748
|
-
canceledAt,
|
|
4749
|
-
|
|
4826
|
+
canceledAt: input.canceledAt,
|
|
4827
|
+
canceledEffectiveAt: input.effectiveAt,
|
|
4828
|
+
// Absent means unchanged rather than null: an ordinary
|
|
4829
|
+
// cancellation does not touch the commitment it was measured
|
|
4830
|
+
// against, and writing the field on every call would erase a
|
|
4831
|
+
// term that is still running.
|
|
4832
|
+
...input.minimumTermUntil ? {
|
|
4833
|
+
minimumTermUntil: input.minimumTermUntil
|
|
4834
|
+
} : {},
|
|
4835
|
+
status: input.terminateNow ? "CANCELED" : sub.status
|
|
4750
4836
|
}
|
|
4751
4837
|
});
|
|
4838
|
+
const current = await subscription.findUnique({
|
|
4839
|
+
where: {
|
|
4840
|
+
tenantId
|
|
4841
|
+
}
|
|
4842
|
+
});
|
|
4843
|
+
if (!current) {
|
|
4844
|
+
throw new Error(`No subscription for tenant ${tenantId}.`);
|
|
4845
|
+
}
|
|
4752
4846
|
return {
|
|
4753
|
-
canceledAt:
|
|
4754
|
-
|
|
4847
|
+
canceledAt: current.canceledAt ?? null,
|
|
4848
|
+
canceledEffectiveAt: current.canceledEffectiveAt ?? null,
|
|
4849
|
+
status: current.status,
|
|
4850
|
+
alreadyCanceled: claimed.count === 0
|
|
4755
4851
|
};
|
|
4756
4852
|
}
|
|
4757
4853
|
subscription(client) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saasicat/adapter-prisma",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.6",
|
|
4
4
|
"description": "Prisma + PostgreSQL persistence for SaaSiCat: one standard bundle for core, catalog, entitlement, tenant billing, Admin resources and promo, plus individual adapters for custom compositions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"dist"
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@saasicat/core": "^1.0.0-rc.
|
|
26
|
+
"@saasicat/core": "^1.0.0-rc.6"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
29
|
"@nestjs/common": "^11.0.0"
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"@prisma/client": "^6.19.0",
|
|
36
36
|
"tsup": "^8.0.0",
|
|
37
37
|
"typescript": "^6.0.0",
|
|
38
|
-
"@saasicat/persistence-testing": "^1.0.0-rc.
|
|
39
|
-
"@saasicat/spec": "^1.0.0-rc.
|
|
38
|
+
"@saasicat/persistence-testing": "^1.0.0-rc.6",
|
|
39
|
+
"@saasicat/spec": "^1.0.0-rc.6"
|
|
40
40
|
},
|
|
41
41
|
"license": "PolyForm-Shield-1.0.0",
|
|
42
42
|
"author": "Taci Uelker",
|