2025-08-22 17:02:49 +09:00
|
|
|
import {
|
|
|
|
|
Controller,
|
|
|
|
|
Get,
|
2025-09-04 18:34:28 +09:00
|
|
|
Post,
|
2025-08-22 17:02:49 +09:00
|
|
|
Param,
|
|
|
|
|
Query,
|
2025-09-04 18:34:28 +09:00
|
|
|
Body,
|
2025-08-20 18:02:50 +09:00
|
|
|
Request,
|
|
|
|
|
ParseIntPipe,
|
|
|
|
|
BadRequestException,
|
2025-09-24 18:00:49 +09:00
|
|
|
UsePipes,
|
2025-10-29 13:29:28 +09:00
|
|
|
Header,
|
2025-12-25 19:01:00 +09:00
|
|
|
UseGuards,
|
2025-08-22 17:02:49 +09:00
|
|
|
} from "@nestjs/common";
|
2025-12-10 16:08:34 +09:00
|
|
|
import { Public } from "@bff/modules/auth/decorators/public.decorator.js";
|
|
|
|
|
import { SubscriptionsService } from "./subscriptions.service.js";
|
|
|
|
|
import { SimManagementService } from "./sim-management.service.js";
|
|
|
|
|
import { SimTopUpPricingService } from "./sim-management/services/sim-topup-pricing.service.js";
|
2025-08-28 16:57:57 +09:00
|
|
|
|
2025-12-10 16:08:34 +09:00
|
|
|
import { subscriptionQuerySchema } from "@customer-portal/domain/subscriptions";
|
|
|
|
|
import type {
|
2025-09-25 11:44:10 +09:00
|
|
|
Subscription,
|
|
|
|
|
SubscriptionList,
|
2025-10-08 16:31:42 +09:00
|
|
|
SubscriptionStats,
|
|
|
|
|
SimActionResponse,
|
|
|
|
|
SimPlanChangeResult,
|
2025-12-10 16:08:34 +09:00
|
|
|
SubscriptionQuery,
|
2025-10-03 16:37:52 +09:00
|
|
|
} from "@customer-portal/domain/subscriptions";
|
2025-12-10 16:08:34 +09:00
|
|
|
import type { InvoiceList } from "@customer-portal/domain/billing";
|
2025-10-21 17:52:23 +09:00
|
|
|
import { createPaginationSchema } from "@customer-portal/domain/toolkit/validation/helpers";
|
|
|
|
|
import type { z } from "zod";
|
2025-10-03 16:37:52 +09:00
|
|
|
import {
|
2025-09-19 16:34:10 +09:00
|
|
|
simTopupRequestSchema,
|
|
|
|
|
simChangePlanRequestSchema,
|
|
|
|
|
simCancelRequestSchema,
|
|
|
|
|
simFeaturesRequestSchema,
|
2025-11-29 16:42:08 +09:00
|
|
|
simCancelFullRequestSchema,
|
|
|
|
|
simChangePlanFullRequestSchema,
|
2025-09-19 16:34:10 +09:00
|
|
|
type SimTopupRequest,
|
|
|
|
|
type SimChangePlanRequest,
|
|
|
|
|
type SimCancelRequest,
|
2025-09-25 11:44:10 +09:00
|
|
|
type SimFeaturesRequest,
|
2025-11-29 16:42:08 +09:00
|
|
|
type SimCancelFullRequest,
|
|
|
|
|
type SimChangePlanFullRequest,
|
2025-10-03 16:37:52 +09:00
|
|
|
} from "@customer-portal/domain/sim";
|
2025-12-02 11:06:54 +09:00
|
|
|
import { ZodValidationPipe } from "nestjs-zod";
|
2025-12-10 16:08:34 +09:00
|
|
|
import type { RequestWithUser } from "@bff/modules/auth/auth.types.js";
|
|
|
|
|
import { SimPlanService } from "./sim-management/services/sim-plan.service.js";
|
|
|
|
|
import { SimCancellationService } from "./sim-management/services/sim-cancellation.service.js";
|
2025-12-25 19:01:00 +09:00
|
|
|
import { AdminGuard } from "@bff/core/security/guards/admin.guard.js";
|
2025-12-10 16:08:34 +09:00
|
|
|
import {
|
|
|
|
|
EsimManagementService,
|
|
|
|
|
type ReissueSimRequest,
|
|
|
|
|
} from "./sim-management/services/esim-management.service.js";
|
|
|
|
|
import { SimCallHistoryService } from "./sim-management/services/sim-call-history.service.js";
|
2025-12-23 15:19:20 +09:00
|
|
|
import { InternetCancellationService } from "./internet-management/services/internet-cancellation.service.js";
|
|
|
|
|
import {
|
|
|
|
|
internetCancelRequestSchema,
|
|
|
|
|
type InternetCancelRequest,
|
|
|
|
|
type SimActionResponse as SubscriptionActionResponse,
|
|
|
|
|
} from "@customer-portal/domain/subscriptions";
|
2025-08-20 18:02:50 +09:00
|
|
|
|
2025-10-21 17:52:23 +09:00
|
|
|
const subscriptionInvoiceQuerySchema = createPaginationSchema({
|
|
|
|
|
defaultLimit: 10,
|
|
|
|
|
maxLimit: 100,
|
|
|
|
|
minLimit: 1,
|
|
|
|
|
});
|
|
|
|
|
type SubscriptionInvoiceQuery = z.infer<typeof subscriptionInvoiceQuerySchema>;
|
|
|
|
|
|
2025-08-22 17:02:49 +09:00
|
|
|
@Controller("subscriptions")
|
2025-08-20 18:02:50 +09:00
|
|
|
export class SubscriptionsController {
|
2025-09-04 18:34:28 +09:00
|
|
|
constructor(
|
|
|
|
|
private readonly subscriptionsService: SubscriptionsService,
|
2025-11-18 11:14:05 +09:00
|
|
|
private readonly simManagementService: SimManagementService,
|
2025-11-29 16:42:08 +09:00
|
|
|
private readonly simTopUpPricingService: SimTopUpPricingService,
|
|
|
|
|
private readonly simPlanService: SimPlanService,
|
|
|
|
|
private readonly simCancellationService: SimCancellationService,
|
2025-11-29 17:52:57 +09:00
|
|
|
private readonly esimManagementService: EsimManagementService,
|
2025-12-23 15:19:20 +09:00
|
|
|
private readonly simCallHistoryService: SimCallHistoryService,
|
|
|
|
|
private readonly internetCancellationService: InternetCancellationService
|
2025-09-04 18:34:28 +09:00
|
|
|
) {}
|
2025-08-20 18:02:50 +09:00
|
|
|
|
|
|
|
|
@Get()
|
2025-10-29 13:29:28 +09:00
|
|
|
@Header("Cache-Control", "private, max-age=300") // 5 minutes, user-specific
|
2025-10-02 16:33:25 +09:00
|
|
|
@UsePipes(new ZodValidationPipe(subscriptionQuerySchema))
|
2025-08-20 18:02:50 +09:00
|
|
|
async getSubscriptions(
|
2025-08-23 17:24:37 +09:00
|
|
|
@Request() req: RequestWithUser,
|
2025-10-02 16:33:25 +09:00
|
|
|
@Query() query: SubscriptionQuery
|
2025-10-21 11:44:06 +09:00
|
|
|
): Promise<SubscriptionList> {
|
|
|
|
|
const { status } = query;
|
|
|
|
|
return this.subscriptionsService.getSubscriptions(req.user.id, { status });
|
2025-08-20 18:02:50 +09:00
|
|
|
}
|
|
|
|
|
|
2025-08-22 17:02:49 +09:00
|
|
|
@Get("active")
|
2025-10-29 13:29:28 +09:00
|
|
|
@Header("Cache-Control", "private, max-age=300") // 5 minutes, user-specific
|
2025-08-23 17:24:37 +09:00
|
|
|
async getActiveSubscriptions(@Request() req: RequestWithUser): Promise<Subscription[]> {
|
2025-08-27 10:54:05 +09:00
|
|
|
return this.subscriptionsService.getActiveSubscriptions(req.user.id);
|
2025-08-20 18:02:50 +09:00
|
|
|
}
|
|
|
|
|
|
2025-08-22 17:02:49 +09:00
|
|
|
@Get("stats")
|
2025-10-29 13:29:28 +09:00
|
|
|
@Header("Cache-Control", "private, max-age=300") // 5 minutes, user-specific
|
2025-10-08 16:31:42 +09:00
|
|
|
async getSubscriptionStats(@Request() req: RequestWithUser): Promise<SubscriptionStats> {
|
2025-08-27 10:54:05 +09:00
|
|
|
return this.subscriptionsService.getSubscriptionStats(req.user.id);
|
2025-08-20 18:02:50 +09:00
|
|
|
}
|
|
|
|
|
|
2025-11-29 17:52:57 +09:00
|
|
|
// ==================== Static SIM Routes (must be before :id routes) ====================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get available months for call/SMS history
|
|
|
|
|
*/
|
|
|
|
|
@Public()
|
|
|
|
|
@Get("sim/call-history/available-months")
|
|
|
|
|
@Header("Cache-Control", "public, max-age=3600")
|
|
|
|
|
async getAvailableMonths() {
|
|
|
|
|
const months = await this.simCallHistoryService.getAvailableMonths();
|
|
|
|
|
return { success: true, data: months };
|
2025-08-20 18:02:50 +09:00
|
|
|
}
|
2025-11-29 17:52:57 +09:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* List available files on SFTP for debugging
|
|
|
|
|
*/
|
2025-12-25 19:01:00 +09:00
|
|
|
@UseGuards(AdminGuard)
|
2025-11-29 17:52:57 +09:00
|
|
|
@Get("sim/call-history/sftp-files")
|
|
|
|
|
async listSftpFiles(@Query("path") path: string = "/home/PASI") {
|
2025-12-25 19:01:00 +09:00
|
|
|
if (!path.startsWith("/home/PASI")) {
|
|
|
|
|
throw new BadRequestException("Invalid path");
|
|
|
|
|
}
|
2025-11-29 17:52:57 +09:00
|
|
|
const files = await this.simCallHistoryService.listSftpFiles(path);
|
|
|
|
|
return { success: true, data: files, path };
|
2025-08-20 18:02:50 +09:00
|
|
|
}
|
2025-09-04 18:34:28 +09:00
|
|
|
|
2025-11-29 17:52:57 +09:00
|
|
|
/**
|
|
|
|
|
* Trigger manual import of call history (admin only)
|
|
|
|
|
*/
|
2025-12-25 19:01:00 +09:00
|
|
|
@UseGuards(AdminGuard)
|
2025-11-29 17:52:57 +09:00
|
|
|
@Post("sim/call-history/import")
|
|
|
|
|
async importCallHistory(@Query("month") yearMonth: string) {
|
|
|
|
|
if (!yearMonth || !/^\d{6}$/.test(yearMonth)) {
|
|
|
|
|
throw new BadRequestException("Invalid month format (expected YYYYMM)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const result = await this.simCallHistoryService.importCallHistory(yearMonth);
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
message: `Imported ${result.domestic} domestic calls, ${result.international} international calls, ${result.sms} SMS`,
|
|
|
|
|
data: result,
|
|
|
|
|
};
|
|
|
|
|
}
|
2025-09-04 18:34:28 +09:00
|
|
|
|
2025-11-18 11:14:05 +09:00
|
|
|
@Get("sim/top-up/pricing")
|
|
|
|
|
@Header("Cache-Control", "public, max-age=3600") // 1 hour, pricing is relatively static
|
|
|
|
|
async getSimTopUpPricing() {
|
|
|
|
|
const pricing = await this.simTopUpPricingService.getTopUpPricing();
|
|
|
|
|
return { success: true, data: pricing };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Get("sim/top-up/pricing/preview")
|
|
|
|
|
@Header("Cache-Control", "public, max-age=3600") // 1 hour, pricing calculation is deterministic
|
|
|
|
|
async previewSimTopUpPricing(@Query("quotaMb") quotaMb: string) {
|
|
|
|
|
const quotaMbNum = parseInt(quotaMb, 10);
|
|
|
|
|
if (isNaN(quotaMbNum) || quotaMbNum <= 0) {
|
|
|
|
|
throw new BadRequestException("Invalid quotaMb parameter");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const preview = await this.simTopUpPricingService.calculatePricingPreview(quotaMbNum);
|
|
|
|
|
return { success: true, data: preview };
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-21 18:41:14 +09:00
|
|
|
@Get("debug/sim-details/:account")
|
2025-12-25 19:01:00 +09:00
|
|
|
@UseGuards(AdminGuard)
|
2025-11-21 18:41:14 +09:00
|
|
|
async debugSimDetails(@Param("account") account: string) {
|
|
|
|
|
return await this.simManagementService.getSimDetailsDebug(account);
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-29 17:52:57 +09:00
|
|
|
// ==================== Dynamic :id Routes ====================
|
|
|
|
|
|
|
|
|
|
@Get(":id")
|
|
|
|
|
@Header("Cache-Control", "private, max-age=300") // 5 minutes, user-specific
|
|
|
|
|
async getSubscriptionById(
|
|
|
|
|
@Request() req: RequestWithUser,
|
|
|
|
|
@Param("id", ParseIntPipe) subscriptionId: number
|
|
|
|
|
): Promise<Subscription> {
|
|
|
|
|
return this.subscriptionsService.getSubscriptionById(req.user.id, subscriptionId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Get(":id/invoices")
|
|
|
|
|
@Header("Cache-Control", "private, max-age=60") // 1 minute, may update with payments
|
|
|
|
|
async getSubscriptionInvoices(
|
|
|
|
|
@Request() req: RequestWithUser,
|
|
|
|
|
@Param("id", ParseIntPipe) subscriptionId: number,
|
|
|
|
|
@Query(new ZodValidationPipe(subscriptionInvoiceQuerySchema)) query: SubscriptionInvoiceQuery
|
|
|
|
|
): Promise<InvoiceList> {
|
|
|
|
|
return this.subscriptionsService.getSubscriptionInvoices(req.user.id, subscriptionId, query);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== SIM Management Endpoints (subscription-specific) ====================
|
|
|
|
|
|
2025-09-04 18:34:28 +09:00
|
|
|
@Get(":id/sim/debug")
|
2025-12-25 19:01:00 +09:00
|
|
|
@UseGuards(AdminGuard)
|
2025-09-04 18:34:28 +09:00
|
|
|
async debugSimSubscription(
|
|
|
|
|
@Request() req: RequestWithUser,
|
|
|
|
|
@Param("id", ParseIntPipe) subscriptionId: number
|
2025-09-09 15:59:30 +09:00
|
|
|
): Promise<Record<string, unknown>> {
|
2025-09-04 18:34:28 +09:00
|
|
|
return this.simManagementService.debugSimSubscription(req.user.id, subscriptionId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Get(":id/sim")
|
|
|
|
|
async getSimInfo(
|
|
|
|
|
@Request() req: RequestWithUser,
|
|
|
|
|
@Param("id", ParseIntPipe) subscriptionId: number
|
2025-11-21 18:41:14 +09:00
|
|
|
) {
|
2025-09-04 18:34:28 +09:00
|
|
|
return this.simManagementService.getSimInfo(req.user.id, subscriptionId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Get(":id/sim/details")
|
|
|
|
|
async getSimDetails(
|
|
|
|
|
@Request() req: RequestWithUser,
|
|
|
|
|
@Param("id", ParseIntPipe) subscriptionId: number
|
|
|
|
|
) {
|
|
|
|
|
return this.simManagementService.getSimDetails(req.user.id, subscriptionId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Get(":id/sim/usage")
|
|
|
|
|
async getSimUsage(
|
|
|
|
|
@Request() req: RequestWithUser,
|
|
|
|
|
@Param("id", ParseIntPipe) subscriptionId: number
|
|
|
|
|
) {
|
|
|
|
|
return this.simManagementService.getSimUsage(req.user.id, subscriptionId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Get(":id/sim/top-up-history")
|
|
|
|
|
async getSimTopUpHistory(
|
|
|
|
|
@Request() req: RequestWithUser,
|
|
|
|
|
@Param("id", ParseIntPipe) subscriptionId: number,
|
|
|
|
|
@Query("fromDate") fromDate: string,
|
|
|
|
|
@Query("toDate") toDate: string
|
|
|
|
|
) {
|
|
|
|
|
if (!fromDate || !toDate) {
|
|
|
|
|
throw new BadRequestException("fromDate and toDate are required");
|
|
|
|
|
}
|
2025-09-09 15:45:03 +09:00
|
|
|
|
2025-09-04 18:34:28 +09:00
|
|
|
return this.simManagementService.getSimTopUpHistory(req.user.id, subscriptionId, {
|
|
|
|
|
fromDate,
|
|
|
|
|
toDate,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Post(":id/sim/top-up")
|
2025-09-24 18:00:49 +09:00
|
|
|
@UsePipes(new ZodValidationPipe(simTopupRequestSchema))
|
2025-09-04 18:34:28 +09:00
|
|
|
async topUpSim(
|
|
|
|
|
@Request() req: RequestWithUser,
|
|
|
|
|
@Param("id", ParseIntPipe) subscriptionId: number,
|
2025-09-24 18:00:49 +09:00
|
|
|
@Body() body: SimTopupRequest
|
2025-10-08 16:31:42 +09:00
|
|
|
): Promise<SimActionResponse> {
|
2025-09-04 18:34:28 +09:00
|
|
|
await this.simManagementService.topUpSim(req.user.id, subscriptionId, body);
|
|
|
|
|
return { success: true, message: "SIM top-up completed successfully" };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Post(":id/sim/change-plan")
|
2025-09-24 18:00:49 +09:00
|
|
|
@UsePipes(new ZodValidationPipe(simChangePlanRequestSchema))
|
2025-09-04 18:34:28 +09:00
|
|
|
async changeSimPlan(
|
|
|
|
|
@Request() req: RequestWithUser,
|
|
|
|
|
@Param("id", ParseIntPipe) subscriptionId: number,
|
2025-09-24 18:00:49 +09:00
|
|
|
@Body() body: SimChangePlanRequest
|
2025-10-08 16:31:42 +09:00
|
|
|
): Promise<SimPlanChangeResult> {
|
2025-09-04 18:34:28 +09:00
|
|
|
const result = await this.simManagementService.changeSimPlan(req.user.id, subscriptionId, body);
|
2025-09-09 15:45:03 +09:00
|
|
|
return {
|
|
|
|
|
success: true,
|
2025-09-04 18:34:28 +09:00
|
|
|
message: "SIM plan change completed successfully",
|
2025-09-09 15:45:03 +09:00
|
|
|
...result,
|
2025-09-04 18:34:28 +09:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Post(":id/sim/cancel")
|
2025-09-24 18:00:49 +09:00
|
|
|
@UsePipes(new ZodValidationPipe(simCancelRequestSchema))
|
2025-09-04 18:34:28 +09:00
|
|
|
async cancelSim(
|
|
|
|
|
@Request() req: RequestWithUser,
|
|
|
|
|
@Param("id", ParseIntPipe) subscriptionId: number,
|
2025-09-24 18:00:49 +09:00
|
|
|
@Body() body: SimCancelRequest
|
2025-10-08 16:31:42 +09:00
|
|
|
): Promise<SimActionResponse> {
|
2025-09-04 18:34:28 +09:00
|
|
|
await this.simManagementService.cancelSim(req.user.id, subscriptionId, body);
|
|
|
|
|
return { success: true, message: "SIM cancellation completed successfully" };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Post(":id/sim/reissue-esim")
|
|
|
|
|
async reissueEsimProfile(
|
|
|
|
|
@Request() req: RequestWithUser,
|
2025-09-09 18:50:31 +09:00
|
|
|
@Param("id", ParseIntPipe) subscriptionId: number,
|
2025-11-21 18:41:14 +09:00
|
|
|
@Body() body: { newEid?: string } = {}
|
2025-10-08 16:31:42 +09:00
|
|
|
): Promise<SimActionResponse> {
|
2025-11-21 18:41:14 +09:00
|
|
|
await this.simManagementService.reissueEsimProfile(req.user.id, subscriptionId, body.newEid);
|
2025-09-04 18:34:28 +09:00
|
|
|
return { success: true, message: "eSIM profile reissue completed successfully" };
|
|
|
|
|
}
|
2025-09-05 15:39:43 +09:00
|
|
|
|
|
|
|
|
@Post(":id/sim/features")
|
2025-09-24 18:00:49 +09:00
|
|
|
@UsePipes(new ZodValidationPipe(simFeaturesRequestSchema))
|
2025-09-05 15:39:43 +09:00
|
|
|
async updateSimFeatures(
|
|
|
|
|
@Request() req: RequestWithUser,
|
|
|
|
|
@Param("id", ParseIntPipe) subscriptionId: number,
|
2025-09-24 18:00:49 +09:00
|
|
|
@Body() body: SimFeaturesRequest
|
2025-10-08 16:31:42 +09:00
|
|
|
): Promise<SimActionResponse> {
|
2025-09-05 15:39:43 +09:00
|
|
|
await this.simManagementService.updateSimFeatures(req.user.id, subscriptionId, body);
|
|
|
|
|
return { success: true, message: "SIM features updated successfully" };
|
|
|
|
|
}
|
2025-11-29 16:42:08 +09:00
|
|
|
|
|
|
|
|
// ==================== Enhanced SIM Management Endpoints ====================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get available plans for plan change (filtered by current plan type)
|
|
|
|
|
*/
|
|
|
|
|
@Get(":id/sim/available-plans")
|
|
|
|
|
@Header("Cache-Control", "private, max-age=300")
|
|
|
|
|
async getAvailablePlans(
|
|
|
|
|
@Request() req: RequestWithUser,
|
|
|
|
|
@Param("id", ParseIntPipe) subscriptionId: number
|
|
|
|
|
) {
|
|
|
|
|
const plans = await this.simPlanService.getAvailablePlans(req.user.id, subscriptionId);
|
|
|
|
|
return { success: true, data: plans };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Change SIM plan with enhanced flow (Salesforce SKU mapping + email notifications)
|
|
|
|
|
*/
|
|
|
|
|
@Post(":id/sim/change-plan-full")
|
|
|
|
|
@UsePipes(new ZodValidationPipe(simChangePlanFullRequestSchema))
|
|
|
|
|
async changeSimPlanFull(
|
|
|
|
|
@Request() req: RequestWithUser,
|
|
|
|
|
@Param("id", ParseIntPipe) subscriptionId: number,
|
|
|
|
|
@Body() body: SimChangePlanFullRequest
|
|
|
|
|
): Promise<SimPlanChangeResult> {
|
|
|
|
|
const result = await this.simPlanService.changeSimPlanFull(req.user.id, subscriptionId, body);
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
message: `SIM plan change scheduled for ${result.scheduledAt}`,
|
|
|
|
|
...result,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get cancellation preview (available months, customer info, minimum contract term)
|
|
|
|
|
*/
|
|
|
|
|
@Get(":id/sim/cancellation-preview")
|
|
|
|
|
@Header("Cache-Control", "private, max-age=60")
|
|
|
|
|
async getCancellationPreview(
|
|
|
|
|
@Request() req: RequestWithUser,
|
|
|
|
|
@Param("id", ParseIntPipe) subscriptionId: number
|
|
|
|
|
) {
|
|
|
|
|
const preview = await this.simCancellationService.getCancellationPreview(
|
|
|
|
|
req.user.id,
|
|
|
|
|
subscriptionId
|
|
|
|
|
);
|
|
|
|
|
return { success: true, data: preview };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Cancel SIM with full flow (PA02-04 + email notifications)
|
|
|
|
|
*/
|
|
|
|
|
@Post(":id/sim/cancel-full")
|
|
|
|
|
@UsePipes(new ZodValidationPipe(simCancelFullRequestSchema))
|
|
|
|
|
async cancelSimFull(
|
|
|
|
|
@Request() req: RequestWithUser,
|
|
|
|
|
@Param("id", ParseIntPipe) subscriptionId: number,
|
|
|
|
|
@Body() body: SimCancelFullRequest
|
|
|
|
|
): Promise<SimActionResponse> {
|
|
|
|
|
await this.simCancellationService.cancelSimFull(req.user.id, subscriptionId, body);
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
message: `SIM cancellation scheduled for end of ${body.cancellationMonth}`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Reissue SIM (both eSIM and physical SIM)
|
|
|
|
|
*/
|
|
|
|
|
@Post(":id/sim/reissue")
|
|
|
|
|
async reissueSim(
|
|
|
|
|
@Request() req: RequestWithUser,
|
|
|
|
|
@Param("id", ParseIntPipe) subscriptionId: number,
|
|
|
|
|
@Body() body: ReissueSimRequest
|
|
|
|
|
): Promise<SimActionResponse> {
|
|
|
|
|
await this.esimManagementService.reissueSim(req.user.id, subscriptionId, body);
|
2025-12-10 16:08:34 +09:00
|
|
|
|
2025-11-29 16:42:08 +09:00
|
|
|
if (body.simType === "esim") {
|
|
|
|
|
return { success: true, message: "eSIM profile reissue request submitted" };
|
|
|
|
|
} else {
|
2025-12-10 16:08:34 +09:00
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
message: "Physical SIM reissue request submitted. You will be contacted shortly.",
|
|
|
|
|
};
|
2025-11-29 16:42:08 +09:00
|
|
|
}
|
|
|
|
|
}
|
2025-11-29 17:52:57 +09:00
|
|
|
|
2025-12-23 15:19:20 +09:00
|
|
|
// ==================== Internet Management Endpoints ====================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get Internet cancellation preview (available months, service details)
|
|
|
|
|
*/
|
|
|
|
|
@Get(":id/internet/cancellation-preview")
|
|
|
|
|
@Header("Cache-Control", "private, max-age=60")
|
|
|
|
|
async getInternetCancellationPreview(
|
|
|
|
|
@Request() req: RequestWithUser,
|
|
|
|
|
@Param("id", ParseIntPipe) subscriptionId: number
|
|
|
|
|
) {
|
|
|
|
|
const preview = await this.internetCancellationService.getCancellationPreview(
|
|
|
|
|
req.user.id,
|
|
|
|
|
subscriptionId
|
|
|
|
|
);
|
|
|
|
|
return { success: true, data: preview };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Submit Internet cancellation request
|
|
|
|
|
*/
|
|
|
|
|
@Post(":id/internet/cancel")
|
|
|
|
|
@UsePipes(new ZodValidationPipe(internetCancelRequestSchema))
|
|
|
|
|
async cancelInternet(
|
|
|
|
|
@Request() req: RequestWithUser,
|
|
|
|
|
@Param("id", ParseIntPipe) subscriptionId: number,
|
|
|
|
|
@Body() body: InternetCancelRequest
|
|
|
|
|
): Promise<SubscriptionActionResponse> {
|
|
|
|
|
await this.internetCancellationService.submitCancellation(req.user.id, subscriptionId, body);
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
message: `Internet cancellation scheduled for end of ${body.cancellationMonth}`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-29 17:52:57 +09:00
|
|
|
// ==================== Call/SMS History Endpoints ====================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get domestic call history
|
|
|
|
|
*/
|
|
|
|
|
@Get(":id/sim/call-history/domestic")
|
|
|
|
|
@Header("Cache-Control", "private, max-age=300")
|
|
|
|
|
async getDomesticCallHistory(
|
|
|
|
|
@Request() req: RequestWithUser,
|
|
|
|
|
@Param("id", ParseIntPipe) subscriptionId: number,
|
|
|
|
|
@Query("month") month?: string,
|
|
|
|
|
@Query("page") page?: string,
|
|
|
|
|
@Query("limit") limit?: string
|
|
|
|
|
) {
|
|
|
|
|
const pageNum = parseInt(page || "1", 10);
|
|
|
|
|
const limitNum = parseInt(limit || "50", 10);
|
|
|
|
|
|
|
|
|
|
if (isNaN(pageNum) || pageNum < 1) {
|
|
|
|
|
throw new BadRequestException("Invalid page number");
|
|
|
|
|
}
|
|
|
|
|
if (isNaN(limitNum) || limitNum < 1 || limitNum > 100) {
|
|
|
|
|
throw new BadRequestException("Invalid limit (must be 1-100)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const result = await this.simCallHistoryService.getDomesticCallHistory(
|
|
|
|
|
req.user.id,
|
|
|
|
|
subscriptionId,
|
|
|
|
|
month,
|
|
|
|
|
pageNum,
|
|
|
|
|
limitNum
|
|
|
|
|
);
|
|
|
|
|
return { success: true, data: result };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get international call history
|
|
|
|
|
*/
|
|
|
|
|
@Get(":id/sim/call-history/international")
|
|
|
|
|
@Header("Cache-Control", "private, max-age=300")
|
|
|
|
|
async getInternationalCallHistory(
|
|
|
|
|
@Request() req: RequestWithUser,
|
|
|
|
|
@Param("id", ParseIntPipe) subscriptionId: number,
|
|
|
|
|
@Query("month") month?: string,
|
|
|
|
|
@Query("page") page?: string,
|
|
|
|
|
@Query("limit") limit?: string
|
|
|
|
|
) {
|
|
|
|
|
const pageNum = parseInt(page || "1", 10);
|
|
|
|
|
const limitNum = parseInt(limit || "50", 10);
|
|
|
|
|
|
|
|
|
|
if (isNaN(pageNum) || pageNum < 1) {
|
|
|
|
|
throw new BadRequestException("Invalid page number");
|
|
|
|
|
}
|
|
|
|
|
if (isNaN(limitNum) || limitNum < 1 || limitNum > 100) {
|
|
|
|
|
throw new BadRequestException("Invalid limit (must be 1-100)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const result = await this.simCallHistoryService.getInternationalCallHistory(
|
|
|
|
|
req.user.id,
|
|
|
|
|
subscriptionId,
|
|
|
|
|
month,
|
|
|
|
|
pageNum,
|
|
|
|
|
limitNum
|
|
|
|
|
);
|
|
|
|
|
return { success: true, data: result };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get SMS history
|
|
|
|
|
*/
|
|
|
|
|
@Get(":id/sim/sms-history")
|
|
|
|
|
@Header("Cache-Control", "private, max-age=300")
|
|
|
|
|
async getSmsHistory(
|
|
|
|
|
@Request() req: RequestWithUser,
|
|
|
|
|
@Param("id", ParseIntPipe) subscriptionId: number,
|
|
|
|
|
@Query("month") month?: string,
|
|
|
|
|
@Query("page") page?: string,
|
|
|
|
|
@Query("limit") limit?: string
|
|
|
|
|
) {
|
|
|
|
|
const pageNum = parseInt(page || "1", 10);
|
|
|
|
|
const limitNum = parseInt(limit || "50", 10);
|
|
|
|
|
|
|
|
|
|
if (isNaN(pageNum) || pageNum < 1) {
|
|
|
|
|
throw new BadRequestException("Invalid page number");
|
|
|
|
|
}
|
|
|
|
|
if (isNaN(limitNum) || limitNum < 1 || limitNum > 100) {
|
|
|
|
|
throw new BadRequestException("Invalid limit (must be 1-100)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const result = await this.simCallHistoryService.getSmsHistory(
|
|
|
|
|
req.user.id,
|
|
|
|
|
subscriptionId,
|
|
|
|
|
month,
|
|
|
|
|
pageNum,
|
|
|
|
|
limitNum
|
|
|
|
|
);
|
|
|
|
|
return { success: true, data: result };
|
|
|
|
|
}
|
2025-08-20 18:02:50 +09:00
|
|
|
}
|