improvement: newly created organization's subscriptions flow improved.

This commit is contained in:
2026-04-30 18:15:40 +03:30
parent 9fa406e35e
commit 64215f07e6
15 changed files with 264 additions and 92 deletions

View File

@@ -32,6 +32,16 @@ const ALL_PERMISSIONS = [
'TAB_REPORTS_EDIT',
];
const READ_ONLY_PERMISSIONS = [
'TAB_TODAY_READ',
'TAB_PATIENTS_READ',
'TAB_APPOINTMENTS_READ',
'TAB_STAFF_READ',
'TAB_LAB_READ',
'TAB_BILLING_READ',
'TAB_REPORTS_READ',
];
@Injectable()
export class AuthService {
constructor(
@@ -141,9 +151,7 @@ export class AuthService {
name: membership.organization.name,
type: membership.organization.type.name, // 'CLINIC' or 'LAB'
isOwner: membership.isOwner,
permissions: membership.isOwner
? ALL_PERMISSIONS
: membership.permissions?.map(p => p.permission.name) || [],
permissions: this.getMembershipPermissions(membership),
plan: membership.organization.plan
? {
name: membership.organization.plan.name,
@@ -253,16 +261,13 @@ export class AuthService {
async createOrganization(userId: string, dto: CreateOrganizationDto) {
const owner = await this.prisma.user.findUnique({
where: { id: userId },
select: { id: true, trialUsedAt: true },
select: { id: true },
});
if (!owner) {
throw new UnauthorizedException('User not found');
}
const planName = dto.planName?.trim() || 'Small';
const effectivePlanName = owner.trialUsedAt ? planName : 'trial';
const organization = await this.prisma.$transaction(async (tx) => {
const createdOrganization = await tx.organization.create({
data: {
@@ -271,9 +276,6 @@ export class AuthService {
owner: {
connect: { id: userId },
},
plan: {
connect: { name: effectivePlanName },
},
type: {
connect: { name: dto.organizationType },
},
@@ -287,14 +289,6 @@ export class AuthService {
isOwner: true,
},
});
if (!owner.trialUsedAt) {
await tx.user.update({
where: { id: userId },
data: { trialUsedAt: new Date() },
});
}
return createdOrganization;
});
@@ -350,9 +344,7 @@ export class AuthService {
name: membership.organization.name,
type: membership.organization.type.name,
isOwner: membership.isOwner,
permissions: membership.isOwner
? ALL_PERMISSIONS
: membership.permissions?.map(p => p.permission.name) || [],
permissions: this.getMembershipPermissions(membership),
plan: membership.organization.plan
? {
name: membership.organization.plan.name,
@@ -471,9 +463,7 @@ export class AuthService {
name: membership.organization.name,
type: membership.organization.type.name,
isOwner: membership.isOwner,
permissions: membership.isOwner
? ALL_PERMISSIONS
: membership.permissions?.map(p => p.permission.name) || [],
permissions: this.getMembershipPermissions(membership),
plan: membership.organization.plan
? {
name: membership.organization.plan.name,
@@ -678,9 +668,7 @@ export class AuthService {
name: membership.organization.name,
type: membership.organization.type.name,
isOwner: membership.isOwner,
permissions: membership.isOwner
? ALL_PERMISSIONS
: membership.permissions?.map(p => p.permission.name) || [],
permissions: this.getMembershipPermissions(membership),
plan: membership.organization.plan
? {
name: membership.organization.plan.name,
@@ -747,9 +735,7 @@ export class AuthService {
});
// 4. Format permissions
const permissions = membership.isOwner
? ALL_PERMISSIONS
: membership.permissions.map(p => p.permission.name);
const permissions = this.getMembershipPermissions(membership);
return {
success: true,
@@ -789,6 +775,19 @@ export class AuthService {
return memberships.filter((m) => m.isOwner || m.isActive);
}
private getMembershipPermissions(membership: {
isOwner: boolean;
organization: {
plan?: { name: string; maxUsers: number; price: number } | null;
};
permissions?: Array<{ permission: { name: string } }>;
}): string[] {
if (membership.isOwner) {
return membership.organization.plan ? ALL_PERMISSIONS : READ_ONLY_PERMISSIONS;
}
return membership.permissions?.map((p) => p.permission.name) || [];
}
/**
* Owner-only subscription / seat alerts for the current org (from JWT).
* Used for a subtle warning indicator in the app shell (not staff-facing banners).
@@ -799,6 +798,7 @@ export class AuthService {
success: true,
data: {
showWarning: false,
noActiveSubscription: false,
seatsLow: false,
trialEndingSoon: false,
trialExpired: false,
@@ -822,6 +822,7 @@ export class AuthService {
success: true,
data: {
showWarning: false,
noActiveSubscription: false,
seatsLow: false,
trialEndingSoon: false,
trialExpired: false,
@@ -833,6 +834,24 @@ export class AuthService {
const org = membership.organization;
const plan = org.plan;
if (!plan) {
return {
success: true,
data: {
showWarning: true,
noActiveSubscription: true,
seatsLow: false,
trialEndingSoon: false,
trialExpired: false,
seatsUsed: 0,
seatsLimit: null,
daysUntilTrialEnd: null,
trialEndsAt: null,
daysUntilPlanEnd: null,
planEndsAt: null,
},
};
}
const maxUsers = plan.maxUsers;
const seatsUsed = await this.prisma.membership.count({
where: {
@@ -863,6 +882,7 @@ export class AuthService {
success: true,
data: {
showWarning,
noActiveSubscription: false,
seatsLow,
trialEndingSoon,
trialExpired,