feat: implement sysadmin subscription management and plan usage cycle recalculation tools

This commit is contained in:
2026-07-18 21:32:47 -03:00
parent 1c8c47cf76
commit 4f3b56784e
36 changed files with 2146 additions and 21 deletions
+2
View File
@@ -5,6 +5,7 @@ import wapRoutes from "./routes/wap.routes";
import usersRoutes from "./routes/users.routes";
import companiesRoutes from "./routes/companies.routes";
import servicesRoutes from "./routes/services.routes";
import plansRoutes from "./routes/plans.routes";
const app = express();
@@ -16,5 +17,6 @@ app.use("/wap", wapRoutes);
app.use("/users", usersRoutes);
app.use("/companies", companiesRoutes);
app.use("/services", servicesRoutes);
app.use("/plans", plansRoutes);
export default app;
@@ -0,0 +1,28 @@
import { Request, Response } from "express";
import { PlansService } from "../services/plans.service";
import { SysAdminUpdatePlanParams } from "../models/Plans.Model";
const plansService = new PlansService();
export class PlansController {
public listPlans = async (_req: Request, res: Response): Promise<void> => {
try {
const plans = await plansService.listPlans();
res.json(plans);
} catch (error: any) {
console.error("Error listing plans:", error?.response?.data || error);
res.status(500).json(error?.response?.data || { success: false, message: "Internal Server Error" });
}
};
public updatePlan = async (req: Request, res: Response): Promise<void> => {
try {
const data: SysAdminUpdatePlanParams = req.body;
const result = await plansService.updatePlan(data);
res.json(result);
} catch (error: any) {
console.error("Error updating plan:", error?.response?.data || error);
res.status(500).json(error?.response?.data || { success: false, message: "Internal Server Error" });
}
};
}
@@ -6,6 +6,9 @@ import {
SysAdminSetVerifiedParams,
SysAdminDeleteUserParams,
SysAdminOrganizationsStatusParams,
SysAdminRecalculatePlanUsageCycleParams,
SysAdminExtendUserSubscriptionParams,
SysAdminUserSubscriptionDetailsParams,
} from "../models/Users.Model";
const usersService = new UsersService();
@@ -65,4 +68,37 @@ export class UsersController {
res.status(500).json(error?.response?.data || { success: false, message: "Internal Server Error" });
}
};
public recalculatePlanUsageCycle = async (req: Request, res: Response): Promise<void> => {
try {
const data: SysAdminRecalculatePlanUsageCycleParams = req.body;
const result = await usersService.recalculatePlanUsageCycle(data);
res.json(result);
} catch (error: any) {
console.error("Error recalculating plan usage cycle:", error?.response?.data || error);
res.status(500).json(error?.response?.data || { success: false, message: "Internal Server Error" });
}
};
public subscriptionDetails = async (req: Request, res: Response): Promise<void> => {
try {
const data: SysAdminUserSubscriptionDetailsParams = req.body;
const result = await usersService.getSubscriptionDetails(data);
res.json(result);
} catch (error: any) {
console.error("Error fetching subscription details:", error?.response?.data || error);
res.status(500).json(error?.response?.data || { success: false, message: "Internal Server Error" });
}
};
public extendSubscription = async (req: Request, res: Response): Promise<void> => {
try {
const data: SysAdminExtendUserSubscriptionParams = req.body;
const result = await usersService.extendSubscription(data);
res.json(result);
} catch (error: any) {
console.error("Error extending subscription:", error?.response?.data || error);
res.status(500).json(error?.response?.data || { success: false, message: "Internal Server Error" });
}
};
}
+31
View File
@@ -0,0 +1,31 @@
export type SysAdminPlan = {
id?: string;
_id?: string;
name: string;
description: string;
features: string[];
code: string;
price: number;
annualPrice: number;
limitOrganizations: number;
limitEmployees: number;
limitServices: number;
limitAppointments: number;
limitClients: number;
limitRepeats: number;
mailNotifications: boolean;
smsNotifications: boolean;
wapNotifications: boolean;
payments: boolean;
bot: boolean;
active: boolean;
dateLimit: boolean;
discount3Months?: number;
discount6Months?: number;
discount12Months?: number;
featured?: boolean;
};
export type SysAdminUpdatePlanParams = Partial<Omit<SysAdminPlan, "id" | "_id">> & {
planId: string;
};
+64
View File
@@ -52,3 +52,67 @@ export type SysAdminUserOrganizationsResult = {
clientIn: { companyId: string; companyName: string }[];
collaboratorIn: { companyId: string; companyName: string }[];
};
export type SysAdminRecalculatePlanUsageCycleParams = {
userId: string;
};
export type SysAdminRecalculatePlanUsageCycleResult = {
userId: string;
subscriptionId: string;
planId: string;
cycleStart: string;
cycleEnd: string;
appointmentsCount: number;
};
export type SysAdminUserSubscriptionDetailsParams = {
userId: string;
};
export type SysAdminExtendUserSubscriptionParams = {
userId: string;
mode: "add_months" | "set_end_date";
months?: 1 | 2;
endDate?: string;
reason?: string;
};
export type SysAdminExtendUserSubscriptionResult = {
userId: string;
subscriptionId: string;
previousEndDate: string;
newEndDate: string;
paymentId: string;
transactionId?: string;
};
export type SysAdminUserSubscriptionDetailsResult = {
userId: string;
currentSubscription: {
id: string;
plan: {
id: string;
name: string;
code: string;
price: number;
} | null;
startDate: string;
endDate: string;
isActive: boolean;
mpStatus?: string;
billingMonths?: number;
pendingPaymentType?: "extension" | "upgrade";
pendingPaymentPreferenceId?: string;
lastPaymentStatus?: string;
} | null;
payments: {
id: string;
subscriptionId: string;
amount: number;
paymentDate: string;
paymentMethod?: string;
status: string;
transactionId?: string;
}[];
};
+10
View File
@@ -0,0 +1,10 @@
import { Router } from "express";
import { PlansController } from "../controllers/plans.controller";
const router = Router();
const plansController = new PlansController();
router.post("/list", plansController.listPlans);
router.post("/update", plansController.updatePlan);
export default router;
+3
View File
@@ -8,6 +8,9 @@ router.post("/paginate", usersController.paginateUsers);
router.post("/update", usersController.updateProfile);
router.post("/set-verified", usersController.setVerifiedStatus);
router.post("/organizations-status", usersController.organizationsStatus);
router.post("/recalculate-plan-usage-cycle", usersController.recalculatePlanUsageCycle);
router.post("/subscription-details", usersController.subscriptionDetails);
router.post("/extend-subscription", usersController.extendSubscription);
router.post("/delete", usersController.deleteUser);
export default router;
+31
View File
@@ -0,0 +1,31 @@
import axios from "axios";
import { getSysAdminNonce } from "../helpers/GetSysAdminNonce";
import { getApiHost } from "../helpers/GetApiHost";
import { getPayload } from "../helpers/GetPayload";
import { SysAdminPlan, SysAdminUpdatePlanParams } from "../models/Plans.Model";
export class PlansService {
public async listPlans(): Promise<SysAdminPlan[]> {
const nonce = await getSysAdminNonce();
const postData = { payload: getPayload(nonce) };
const response = await axios.post(`${getApiHost()}/sysadmin/plans/list`, postData, {
headers: { "Content-Type": "application/json" },
timeout: 5000,
});
return response.data;
}
public async updatePlan(data: SysAdminUpdatePlanParams): Promise<any> {
const nonce = await getSysAdminNonce();
const postData = { ...data, payload: getPayload(nonce) };
const response = await axios.post(`${getApiHost()}/sysadmin/plans/update`, postData, {
headers: { "Content-Type": "application/json" },
timeout: 5000,
});
return response.data;
}
}
+48
View File
@@ -10,6 +10,12 @@ import {
SysAdminDeleteUserParams,
SysAdminOrganizationsStatusParams,
SysAdminUserOrganizationsResult,
SysAdminRecalculatePlanUsageCycleParams,
SysAdminRecalculatePlanUsageCycleResult,
SysAdminExtendUserSubscriptionParams,
SysAdminExtendUserSubscriptionResult,
SysAdminUserSubscriptionDetailsParams,
SysAdminUserSubscriptionDetailsResult,
} from "../models/Users.Model";
export class UsersService {
@@ -72,4 +78,46 @@ export class UsersService {
return response.data;
}
public async recalculatePlanUsageCycle(
data: SysAdminRecalculatePlanUsageCycleParams
): Promise<SysAdminRecalculatePlanUsageCycleResult> {
const nonce = await getSysAdminNonce();
const postData = { ...data, ...{ payload: getPayload(nonce) } };
const response = await axios.post(`${getApiHost()}/sysadmin/plan-usage-cycle/recalculate-user`, postData, {
headers: { "Content-Type": "application/json" },
timeout: 10000,
});
return response.data;
}
public async getSubscriptionDetails(
data: SysAdminUserSubscriptionDetailsParams
): Promise<SysAdminUserSubscriptionDetailsResult> {
const nonce = await getSysAdminNonce();
const postData = { ...data, ...{ payload: getPayload(nonce) } };
const response = await axios.post(`${getApiHost()}/sysadmin/subscriptions/user-details`, postData, {
headers: { "Content-Type": "application/json" },
timeout: 10000,
});
return response.data;
}
public async extendSubscription(
data: SysAdminExtendUserSubscriptionParams
): Promise<SysAdminExtendUserSubscriptionResult> {
const nonce = await getSysAdminNonce();
const postData = { ...data, ...{ payload: getPayload(nonce) } };
const response = await axios.post(`${getApiHost()}/sysadmin/subscriptions/extend-user`, postData, {
headers: { "Content-Type": "application/json" },
timeout: 10000,
});
return response.data;
}
}