@goweekdays/core 2.11.4 → 2.11.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @goweekdays/core
2
2
 
3
+ ## 2.11.6
4
+
5
+ ### Patch Changes
6
+
7
+ - 8736673: Add user count check for org member invites
8
+
9
+ ## 2.11.5
10
+
11
+ ### Patch Changes
12
+
13
+ - cb8aa36: Enhance promo usage tracking and seat limit logic
14
+
3
15
  ## 2.11.4
4
16
 
5
17
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -344,6 +344,7 @@ declare function useMemberRepo(): {
344
344
  updateStatusById: (_id: string | ObjectId, status: string, session?: ClientSession) => Promise<string>;
345
345
  deleteById: (_id: string | ObjectId, session?: ClientSession) => Promise<string>;
346
346
  updateStatusByOrg: (org: string | ObjectId, status: string, session?: ClientSession) => Promise<string>;
347
+ countUserByOrg: (org: string | ObjectId) => Promise<any>;
347
348
  };
348
349
 
349
350
  declare function useMemberController(): {
@@ -470,6 +471,33 @@ declare function usePromoController(): {
470
471
  deleteById: (req: Request, res: Response, next: NextFunction) => Promise<void>;
471
472
  };
472
473
 
474
+ type TPromoUsage = {
475
+ _id?: ObjectId;
476
+ promo: ObjectId;
477
+ org: ObjectId | string;
478
+ usedBy: string;
479
+ status?: "active" | "inactive";
480
+ createdAt?: Date | string;
481
+ updatedAt?: Date | string;
482
+ };
483
+
484
+ declare function usePromoUsageRepo(): {
485
+ createIndexes: () => Promise<void>;
486
+ add: (value: TPromoUsage, session?: ClientSession) => Promise<string>;
487
+ getAll: ({ page, limit, promo, org, }?: {
488
+ page?: number | undefined;
489
+ limit?: number | undefined;
490
+ promo?: string | undefined;
491
+ org?: string | undefined;
492
+ }) => Promise<TPaginate<TPromoUsage>>;
493
+ getByPromo: (promo: string | ObjectId) => Promise<TPromoUsage | null>;
494
+ getById: (_id: string | ObjectId) => Promise<TPromoUsage | null>;
495
+ getByOrgId: (orgId: string | ObjectId) => Promise<TPromoUsage[]>;
496
+ countByPromoId: (promoId: string | ObjectId) => Promise<number>;
497
+ updateStatusByOrgId: (orgId: string | ObjectId, status: "active" | "inactive", session?: ClientSession) => Promise<string>;
498
+ deleteById: (_id: string | ObjectId) => Promise<string>;
499
+ };
500
+
473
501
  type TRole = {
474
502
  _id?: ObjectId;
475
503
  id?: string | ObjectId;
@@ -1255,4 +1283,4 @@ declare const XENDIT_BASE_URL: string;
1255
1283
  declare const DOMAIN: string;
1256
1284
  declare const APP_ORG: string;
1257
1285
 
1258
- export { ACCESS_TOKEN_EXPIRY, ACCESS_TOKEN_SECRET, APP_ACCOUNT, APP_MAIN, APP_ORG, DEFAULT_USER_EMAIL, DEFAULT_USER_FIRST_NAME, DEFAULT_USER_LAST_NAME, DEFAULT_USER_PASSWORD, DOMAIN, MAILER_EMAIL, MAILER_PASSWORD, MAILER_TRANSPORT_HOST, MAILER_TRANSPORT_PORT, MAILER_TRANSPORT_SECURE, MBuilding, MBuildingUnit, MFile, MONGO_DB, MONGO_URI, PAYPAL_API_URL, PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET, PAYPAL_WEBHOOK_ID, PORT, PaypalWebhookHeaders, REDIS_HOST, REDIS_PASSWORD, REDIS_PORT, REFRESH_TOKEN_EXPIRY, REFRESH_TOKEN_SECRET, SECRET_KEY, SPACES_ACCESS_KEY, SPACES_BUCKET, SPACES_ENDPOINT, SPACES_REGION, SPACES_SECRET_KEY, TApp, TBuilding, TBuildingUnit, TCounter, TFile, TJobPost, TLedgerBill, TMember, TOrg, TPermission, TPermissionGroup, TPlan, TPromo, TRole, TSubscribe, TSubscription, TSubscriptionTransaction, TUser, TVerification, TVerificationMetadata, VERIFICATION_FORGET_PASSWORD_DURATION, VERIFICATION_USER_INVITE_DURATION, XENDIT_BASE_URL, XENDIT_SECRET_KEY, currencies, isDev, ledgerBillStatuses, ledgerBillTypes, modelApp, modelJobPost, modelLedgerBill, modelMember, modelOrg, modelPermission, modelPermissionGroup, modelPlan, modelPromo, modelRole, modelSubscription, modelSubscriptionTransaction, modelUser, modelVerification, schemaApp, schemaAppUpdate, schemaBuilding, schemaBuildingUnit, schemaInviteMember, schemaJobPost, schemaJobPostUpdate, schemaLedgerBill, schemaLedgerBillingSummary, schemaMember, schemaMemberRole, schemaMemberStatus, schemaOrg, schemaOrgAdd, schemaOrgUpdate, schemaPermission, schemaPermissionGroup, schemaPermissionGroupUpdate, schemaPermissionUpdate, schemaPlan, schemaPromo, schemaRole, schemaRoleUpdate, schemaSubscribe, schemaSubscription, schemaSubscriptionCompute, schemaSubscriptionPromoCode, schemaSubscriptionSeats, schemaSubscriptionTransaction, schemaSubscriptionUpdate, schemaUpdateOptions, schemaUser, schemaVerification, transactionSchema, useAppController, useAppRepo, useAppService, useAuthController, useAuthService, useBuildingController, useBuildingRepo, useBuildingService, useBuildingUnitController, useBuildingUnitRepo, useBuildingUnitService, useCounterModel, useCounterRepo, useFileController, useFileRepo, useFileService, useGitHubService, useJobPostController, useJobPostRepo, useJobPostService, useLedgerBillingController, useLedgerBillingRepo, useMemberController, useMemberRepo, useOrgController, useOrgRepo, useOrgService, usePaypalService, usePermissionController, usePermissionGroupController, usePermissionGroupRepo, usePermissionGroupService, usePermissionRepo, usePermissionService, usePlanController, usePlanRepo, usePlanService, usePromoController, usePromoRepo, useRoleController, useRoleRepo, useRoleService, useSubscriptionController, useSubscriptionRepo, useSubscriptionService, useSubscriptionTransactionController, useSubscriptionTransactionRepo, useUserController, useUserRepo, useUserService, useUtilController, useVerificationController, useVerificationRepo, useVerificationService };
1286
+ export { ACCESS_TOKEN_EXPIRY, ACCESS_TOKEN_SECRET, APP_ACCOUNT, APP_MAIN, APP_ORG, DEFAULT_USER_EMAIL, DEFAULT_USER_FIRST_NAME, DEFAULT_USER_LAST_NAME, DEFAULT_USER_PASSWORD, DOMAIN, MAILER_EMAIL, MAILER_PASSWORD, MAILER_TRANSPORT_HOST, MAILER_TRANSPORT_PORT, MAILER_TRANSPORT_SECURE, MBuilding, MBuildingUnit, MFile, MONGO_DB, MONGO_URI, PAYPAL_API_URL, PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET, PAYPAL_WEBHOOK_ID, PORT, PaypalWebhookHeaders, REDIS_HOST, REDIS_PASSWORD, REDIS_PORT, REFRESH_TOKEN_EXPIRY, REFRESH_TOKEN_SECRET, SECRET_KEY, SPACES_ACCESS_KEY, SPACES_BUCKET, SPACES_ENDPOINT, SPACES_REGION, SPACES_SECRET_KEY, TApp, TBuilding, TBuildingUnit, TCounter, TFile, TJobPost, TLedgerBill, TMember, TOrg, TPermission, TPermissionGroup, TPlan, TPromo, TRole, TSubscribe, TSubscription, TSubscriptionTransaction, TUser, TVerification, TVerificationMetadata, VERIFICATION_FORGET_PASSWORD_DURATION, VERIFICATION_USER_INVITE_DURATION, XENDIT_BASE_URL, XENDIT_SECRET_KEY, currencies, isDev, ledgerBillStatuses, ledgerBillTypes, modelApp, modelJobPost, modelLedgerBill, modelMember, modelOrg, modelPermission, modelPermissionGroup, modelPlan, modelPromo, modelRole, modelSubscription, modelSubscriptionTransaction, modelUser, modelVerification, schemaApp, schemaAppUpdate, schemaBuilding, schemaBuildingUnit, schemaInviteMember, schemaJobPost, schemaJobPostUpdate, schemaLedgerBill, schemaLedgerBillingSummary, schemaMember, schemaMemberRole, schemaMemberStatus, schemaOrg, schemaOrgAdd, schemaOrgUpdate, schemaPermission, schemaPermissionGroup, schemaPermissionGroupUpdate, schemaPermissionUpdate, schemaPlan, schemaPromo, schemaRole, schemaRoleUpdate, schemaSubscribe, schemaSubscription, schemaSubscriptionCompute, schemaSubscriptionPromoCode, schemaSubscriptionSeats, schemaSubscriptionTransaction, schemaSubscriptionUpdate, schemaUpdateOptions, schemaUser, schemaVerification, transactionSchema, useAppController, useAppRepo, useAppService, useAuthController, useAuthService, useBuildingController, useBuildingRepo, useBuildingService, useBuildingUnitController, useBuildingUnitRepo, useBuildingUnitService, useCounterModel, useCounterRepo, useFileController, useFileRepo, useFileService, useGitHubService, useJobPostController, useJobPostRepo, useJobPostService, useLedgerBillingController, useLedgerBillingRepo, useMemberController, useMemberRepo, useOrgController, useOrgRepo, useOrgService, usePaypalService, usePermissionController, usePermissionGroupController, usePermissionGroupRepo, usePermissionGroupService, usePermissionRepo, usePermissionService, usePlanController, usePlanRepo, usePlanService, usePromoController, usePromoRepo, usePromoUsageRepo, useRoleController, useRoleRepo, useRoleService, useSubscriptionController, useSubscriptionRepo, useSubscriptionService, useSubscriptionTransactionController, useSubscriptionTransactionRepo, useUserController, useUserRepo, useUserService, useUtilController, useVerificationController, useVerificationRepo, useVerificationService };
package/dist/index.js CHANGED
@@ -161,6 +161,7 @@ __export(src_exports, {
161
161
  usePlanService: () => usePlanService,
162
162
  usePromoController: () => usePromoController,
163
163
  usePromoRepo: () => usePromoRepo,
164
+ usePromoUsageRepo: () => usePromoUsageRepo,
164
165
  useRoleController: () => useRoleController,
165
166
  useRoleRepo: () => useRoleRepo,
166
167
  useRoleService: () => useRoleService,
@@ -1269,6 +1270,7 @@ function useMemberRepo() {
1269
1270
  try {
1270
1271
  await collection.createIndexes([
1271
1272
  { key: { name: 1 } },
1273
+ { key: { user: 1 } },
1272
1274
  { key: { status: 1 } },
1273
1275
  { key: { org: 1 } },
1274
1276
  { key: { name: "text", orgName: "text" }, name: "text_index" },
@@ -2008,6 +2010,55 @@ function useMemberRepo() {
2008
2010
  );
2009
2011
  }
2010
2012
  }
2013
+ async function countUserByOrg(org) {
2014
+ try {
2015
+ org = new import_mongodb7.ObjectId(org);
2016
+ } catch (error) {
2017
+ throw new import_utils7.BadRequestError("Invalid organization ID.");
2018
+ }
2019
+ try {
2020
+ const cacheKey = (0, import_utils7.makeCacheKey)(namespace_collection, {
2021
+ org: String(org),
2022
+ tag: "countUserByOrg"
2023
+ });
2024
+ const cached = await getCache(cacheKey);
2025
+ if (cached) {
2026
+ import_utils7.logger.log({
2027
+ level: "info",
2028
+ message: `Cache hit for countUserByOrg members: ${cacheKey}`
2029
+ });
2030
+ return cached;
2031
+ }
2032
+ const data = await collection.aggregate([
2033
+ { $match: { org, status: "active" } },
2034
+ {
2035
+ $group: {
2036
+ _id: "$user"
2037
+ }
2038
+ },
2039
+ {
2040
+ $count: "memberCount"
2041
+ }
2042
+ ]).toArray();
2043
+ const count = data[0]?.memberCount ?? 0;
2044
+ setCache(cacheKey, count, 300).then(() => {
2045
+ import_utils7.logger.log({
2046
+ level: "info",
2047
+ message: `Cache set for countUserByOrg members: ${cacheKey}`
2048
+ });
2049
+ }).catch((err) => {
2050
+ import_utils7.logger.log({
2051
+ level: "error",
2052
+ message: `Failed to set cache for countUserByOrg members: ${err.message}`
2053
+ });
2054
+ });
2055
+ return count;
2056
+ } catch (error) {
2057
+ throw new import_utils7.InternalServerError(
2058
+ "Internal server error, failed to count users."
2059
+ );
2060
+ }
2061
+ }
2011
2062
  return {
2012
2063
  createIndexes,
2013
2064
  add,
@@ -2026,7 +2077,8 @@ function useMemberRepo() {
2026
2077
  updateRoleById,
2027
2078
  updateStatusById,
2028
2079
  deleteById,
2029
- updateStatusByOrg
2080
+ updateStatusByOrg,
2081
+ countUserByOrg
2030
2082
  };
2031
2083
  }
2032
2084
 
@@ -5810,7 +5862,7 @@ function useSubscriptionRepo() {
5810
5862
  const validation = import_joi21.default.object({
5811
5863
  seats: import_joi21.default.number().integer().min(1).optional().allow("", null),
5812
5864
  paidSeats: import_joi21.default.number().integer().min(0).optional(),
5813
- amount: import_joi21.default.number().positive().optional(),
5865
+ amount: import_joi21.default.number().positive().optional().allow(0),
5814
5866
  promoCode: import_joi21.default.string().max(50).optional().allow("", null),
5815
5867
  status: import_joi21.default.string().valid("active", "due", "overdue", "suspended").optional().allow("", null),
5816
5868
  nextBillingDate: import_joi21.default.date().optional().allow("", null)
@@ -7803,7 +7855,8 @@ var import_mongodb23 = require("mongodb");
7803
7855
  var schemaPromoUsage = import_joi34.default.object({
7804
7856
  promo: import_joi34.default.string().hex().length(24).required(),
7805
7857
  org: import_joi34.default.string().hex().length(24).required(),
7806
- usedBy: import_joi34.default.string().email().required()
7858
+ usedBy: import_joi34.default.string().email().required(),
7859
+ status: import_joi34.default.string().valid("active", "inactive").optional()
7807
7860
  });
7808
7861
  function modelPromoUsage(value) {
7809
7862
  const { error } = schemaPromoUsage.validate(value);
@@ -7817,14 +7870,14 @@ function modelPromoUsage(value) {
7817
7870
  throw new import_utils40.BadRequestError("Invalid Promo Usage _id");
7818
7871
  }
7819
7872
  }
7820
- if (typeof value.promo === "string") {
7873
+ if (value.promo && typeof value.promo === "string") {
7821
7874
  try {
7822
7875
  value.promo = new import_mongodb23.ObjectId(value.promo);
7823
7876
  } catch (error2) {
7824
7877
  throw new import_utils40.BadRequestError("Invalid Promo Usage promo");
7825
7878
  }
7826
7879
  }
7827
- if (typeof value.org === "string") {
7880
+ if (value.org && typeof value.org === "string") {
7828
7881
  try {
7829
7882
  value.org = new import_mongodb23.ObjectId(value.org);
7830
7883
  } catch (error2) {
@@ -7836,6 +7889,7 @@ function modelPromoUsage(value) {
7836
7889
  promo: value.promo,
7837
7890
  org: value.org,
7838
7891
  usedBy: value.usedBy,
7892
+ status: value.status ?? "active",
7839
7893
  createdAt: value.createdAt ? new Date(value.createdAt) : /* @__PURE__ */ new Date(),
7840
7894
  updatedAt: value.updatedAt ?? ""
7841
7895
  };
@@ -7868,6 +7922,9 @@ function usePromoUsageRepo() {
7868
7922
  async function createIndexes() {
7869
7923
  try {
7870
7924
  await collection.createIndexes([
7925
+ {
7926
+ key: { status: 1 }
7927
+ },
7871
7928
  {
7872
7929
  key: { promo: 1, org: 1 },
7873
7930
  name: "promo_org_index"
@@ -7880,10 +7937,10 @@ function usePromoUsageRepo() {
7880
7937
  } catch (error) {
7881
7938
  }
7882
7939
  }
7883
- async function add(value) {
7940
+ async function add(value, session) {
7884
7941
  try {
7885
7942
  value = modelPromoUsage(value);
7886
- await collection.insertOne(value);
7943
+ await collection.insertOne(value, { session });
7887
7944
  delCachedData();
7888
7945
  return "Successfully added promo usage.";
7889
7946
  } catch (error) {
@@ -8075,7 +8132,10 @@ function usePromoUsageRepo() {
8075
8132
  if (cachedData !== null && cachedData !== void 0) {
8076
8133
  return cachedData;
8077
8134
  }
8078
- const count = await collection.countDocuments({ promo: promoId });
8135
+ const count = await collection.countDocuments({
8136
+ promo: promoId,
8137
+ status: "active"
8138
+ });
8079
8139
  setCache(cacheKey, count).then(() => {
8080
8140
  import_utils41.logger.log({
8081
8141
  level: "info",
@@ -8092,6 +8152,28 @@ function usePromoUsageRepo() {
8092
8152
  throw new import_utils41.InternalServerError("Failed to count promo usages.");
8093
8153
  }
8094
8154
  }
8155
+ async function updateStatusByOrgId(orgId, status, session) {
8156
+ const { error } = import_joi35.default.string().hex().length(24).required().validate(orgId);
8157
+ if (error) {
8158
+ throw new import_utils41.BadRequestError(`Invalid org ID: ${error.message}`);
8159
+ }
8160
+ try {
8161
+ orgId = new import_mongodb24.ObjectId(orgId);
8162
+ } catch (error2) {
8163
+ throw new import_utils41.BadRequestError("Invalid org ID.");
8164
+ }
8165
+ try {
8166
+ await collection.updateMany(
8167
+ { org: orgId, status: "active" },
8168
+ { $set: { status, updatedAt: /* @__PURE__ */ new Date() } },
8169
+ { session }
8170
+ );
8171
+ delCachedData();
8172
+ return "Successfully updated promo usage status.";
8173
+ } catch (error2) {
8174
+ throw new import_utils41.InternalServerError("Failed to update promo usage status.");
8175
+ }
8176
+ }
8095
8177
  async function deleteById(_id) {
8096
8178
  const { error } = import_joi35.default.string().hex().length(24).required().validate(_id);
8097
8179
  if (error) {
@@ -8124,6 +8206,7 @@ function usePromoUsageRepo() {
8124
8206
  getById,
8125
8207
  getByOrgId,
8126
8208
  countByPromoId,
8209
+ updateStatusByOrgId,
8127
8210
  deleteById
8128
8211
  };
8129
8212
  }
@@ -8300,6 +8383,11 @@ function useSubscriptionService() {
8300
8383
  const { getDefault: getDefaultPlan, getById: getPlanById } = usePlanRepo();
8301
8384
  const { getById: getOrgById, updateStatusById: updateOrgStatusById } = useOrgRepo();
8302
8385
  const { getByCode: getPromoByCode } = usePromoRepo();
8386
+ const {
8387
+ countByPromoId,
8388
+ add: addPromoUsage,
8389
+ updateStatusByOrgId: updatePromoUsageStatusByOrgId
8390
+ } = usePromoUsageRepo();
8303
8391
  const { getByApp: getMembershipByApp } = useMemberRepo();
8304
8392
  function calculateVolumeTierAmount(tiers, startSeat, seatCount, fallbackRate) {
8305
8393
  const sortedTiers = [...tiers].sort((a, b) => a.minSeats - b.minSeats);
@@ -8350,16 +8438,33 @@ function useSubscriptionService() {
8350
8438
  if (!promo) {
8351
8439
  throw new import_utils44.BadRequestError("Promo code not found.");
8352
8440
  }
8441
+ if (promo.usage && promo.usage > 0) {
8442
+ const currentUsageCount = await countByPromoId(
8443
+ promo._id?.toString() ?? ""
8444
+ );
8445
+ const isAlreadyUsingPromo = existingSubscription?.promoCode === promoCode;
8446
+ if (!isAlreadyUsingPromo && currentUsageCount >= promo.usage) {
8447
+ throw new import_utils44.BadRequestError(
8448
+ "Promo code has reached its maximum usage limit."
8449
+ );
8450
+ }
8451
+ }
8353
8452
  }
8354
8453
  let monthlyAmount = plan.price * value.seats;
8355
8454
  if (promo) {
8455
+ const promoSeatLimit = promo.seats && promo.seats > 0 ? promo.seats : Infinity;
8356
8456
  switch (promo.type) {
8357
- case "fixed":
8358
- monthlyAmount = Math.max(promo.fixedRate ?? 0, 0) * value.seats;
8457
+ case "fixed": {
8458
+ const promoSeats = Math.min(value.seats, promoSeatLimit);
8459
+ const standardSeats = Math.max(value.seats - promoSeatLimit, 0);
8460
+ monthlyAmount = Math.max(promo.fixedRate ?? 0, 0) * promoSeats + plan.price * standardSeats;
8359
8461
  break;
8360
- case "flat":
8361
- monthlyAmount = Math.max((promo.flatRate ?? 0) / value.seats, 0) * value.seats;
8462
+ }
8463
+ case "flat": {
8464
+ const standardSeats = Math.max(value.seats - promoSeatLimit, 0);
8465
+ monthlyAmount = (promo.flatRate ?? 0) + plan.price * standardSeats;
8362
8466
  break;
8467
+ }
8363
8468
  case "volume": {
8364
8469
  if (promo.tiers && promo.tiers.length > 0) {
8365
8470
  monthlyAmount = calculateVolumeTierAmount(
@@ -8402,17 +8507,32 @@ function useSubscriptionService() {
8402
8507
  plan.price
8403
8508
  );
8404
8509
  } else if (promo?.type === "fixed") {
8405
- const discountedPricePerSeat = Math.max(
8406
- plan.price - (promo.fixedRate ?? 0),
8407
- 0
8510
+ const promoSeatLimit = promo.seats && promo.seats > 0 ? promo.seats : Infinity;
8511
+ const startSeat = existingSubscription.paidSeats + 1;
8512
+ const endSeat = value.seats;
8513
+ const promoSeatsInRange = Math.max(
8514
+ 0,
8515
+ Math.min(promoSeatLimit, endSeat) - startSeat + 1
8408
8516
  );
8409
- additionalSeatsAmount = discountedPricePerSeat * additionalSeats;
8410
- } else if (promo?.type === "flat") {
8411
- const effectivePricePerSeat = Math.max(
8412
- plan.price - (promo.flatRate ?? 0) / value.seats,
8413
- 0
8517
+ const standardSeatsInRange = Math.max(
8518
+ 0,
8519
+ additionalSeats - promoSeatsInRange
8414
8520
  );
8415
- additionalSeatsAmount = effectivePricePerSeat * additionalSeats;
8521
+ additionalSeatsAmount = (promo.fixedRate ?? 0) * promoSeatsInRange + plan.price * standardSeatsInRange;
8522
+ } else if (promo?.type === "flat") {
8523
+ const promoSeatLimit = promo.seats && promo.seats > 0 ? promo.seats : Infinity;
8524
+ const startSeat = existingSubscription.paidSeats + 1;
8525
+ const endSeat = value.seats;
8526
+ if (startSeat > promoSeatLimit) {
8527
+ additionalSeatsAmount = plan.price * additionalSeats;
8528
+ } else {
8529
+ const seatsStillCoveredByFlat = Math.max(
8530
+ 0,
8531
+ Math.min(promoSeatLimit, endSeat) - startSeat + 1
8532
+ );
8533
+ const seatsAtPlanPrice = additionalSeats - seatsStillCoveredByFlat;
8534
+ additionalSeatsAmount = plan.price * seatsAtPlanPrice;
8535
+ }
8416
8536
  } else {
8417
8537
  additionalSeatsAmount = plan.price * additionalSeats;
8418
8538
  }
@@ -8420,8 +8540,6 @@ function useSubscriptionService() {
8420
8540
  proratedAmount = dailyRate * daysRemaining;
8421
8541
  }
8422
8542
  }
8423
- const isDecrease = existingSubscription && value.seats < existingSubscription.paidSeats;
8424
- const isNoChange = existingSubscription && value.seats === existingSubscription.paidSeats;
8425
8543
  const isIncrease = existingSubscription && value.seats > existingSubscription.paidSeats;
8426
8544
  let subscriptionAmount;
8427
8545
  if (!existingSubscription) {
@@ -8486,6 +8604,13 @@ function useSubscriptionService() {
8486
8604
  org: value.org,
8487
8605
  plan: value.plan
8488
8606
  });
8607
+ let promo = null;
8608
+ if (value.promoCode) {
8609
+ promo = await getPromoByCode(value.promoCode);
8610
+ if (!promo) {
8611
+ throw new import_utils44.BadRequestError("Promo code not found.");
8612
+ }
8613
+ }
8489
8614
  const subId = await _add(
8490
8615
  {
8491
8616
  org: value.org,
@@ -8501,6 +8626,16 @@ function useSubscriptionService() {
8501
8626
  },
8502
8627
  session
8503
8628
  );
8629
+ if (promo && promo._id) {
8630
+ await addPromoUsage(
8631
+ {
8632
+ promo: promo._id,
8633
+ org: value.org,
8634
+ usedBy: userData.email
8635
+ },
8636
+ session
8637
+ );
8638
+ }
8504
8639
  await addTransaction(
8505
8640
  {
8506
8641
  type: "initiate",
@@ -8657,6 +8792,19 @@ function useSubscriptionService() {
8657
8792
  { promoCode: value.promoCode ?? "", amount: subscriptionAmount },
8658
8793
  session
8659
8794
  );
8795
+ if (subscription.promoCode) {
8796
+ await updatePromoUsageStatusByOrgId(value.org, "inactive", session);
8797
+ }
8798
+ if (promo && promo._id) {
8799
+ await addPromoUsage(
8800
+ {
8801
+ promo: promo._id,
8802
+ org: value.org,
8803
+ usedBy: userData.email
8804
+ },
8805
+ session
8806
+ );
8807
+ }
8660
8808
  await addTransaction(
8661
8809
  {
8662
8810
  type: "promo-updated",
@@ -10435,7 +10583,7 @@ function useVerificationService() {
10435
10583
  getVerifications: _getVerifications
10436
10584
  } = useVerificationRepo();
10437
10585
  const { getUserByEmail } = useUserRepo();
10438
- const { add: addMember } = useMemberRepo();
10586
+ const { add: addMember, countUserByOrg } = useMemberRepo();
10439
10587
  const { getById: getOrgById } = useOrgRepo();
10440
10588
  const { getById: getRoleById } = useRoleRepo();
10441
10589
  async function createUserInvite({
@@ -10734,6 +10882,7 @@ function useVerificationService() {
10734
10882
  }
10735
10883
  }
10736
10884
  const { getByOrg } = useSubscriptionRepo();
10885
+ const { getByCode: getPromoByCode } = usePromoRepo();
10737
10886
  async function inviteMember(value) {
10738
10887
  const { error } = schemaInviteMember.validate(value);
10739
10888
  if (error) {
@@ -10755,6 +10904,23 @@ function useVerificationService() {
10755
10904
  "Organization does not have an active subscription."
10756
10905
  );
10757
10906
  }
10907
+ if (subscription.promoCode) {
10908
+ const promo = await getPromoByCode(subscription.promoCode);
10909
+ if (!promo) {
10910
+ throw new import_utils53.BadRequestError("Promo code not found.");
10911
+ }
10912
+ if (promo.apps && promo.apps.length && !promo.apps.includes(value.app)) {
10913
+ throw new import_utils53.BadRequestError(
10914
+ "Promo code is not valid for the specified app."
10915
+ );
10916
+ }
10917
+ }
10918
+ const memberCount = await countUserByOrg(String(value.org));
10919
+ if (subscription.seats <= memberCount) {
10920
+ throw new import_utils53.BadRequestError(
10921
+ "Organization has reached the maximum number of members for its subscription plan."
10922
+ );
10923
+ }
10758
10924
  }
10759
10925
  let verificationData = {
10760
10926
  type: "user-invite",
@@ -13540,6 +13706,7 @@ function useJobPostController() {
13540
13706
  usePlanService,
13541
13707
  usePromoController,
13542
13708
  usePromoRepo,
13709
+ usePromoUsageRepo,
13543
13710
  useRoleController,
13544
13711
  useRoleRepo,
13545
13712
  useRoleService,