diff --git a/apps/bff/src/core/config/auth-dev.config.ts b/apps/bff/src/core/config/auth-dev.config.ts index 953ae9dc..c0f9200a 100644 --- a/apps/bff/src/core/config/auth-dev.config.ts +++ b/apps/bff/src/core/config/auth-dev.config.ts @@ -12,7 +12,12 @@ export interface DevAuthConfig { skipOtp: boolean; } -export const createDevAuthConfig = (): DevAuthConfig => { +/** + * Get the current dev auth configuration. + * This function reads environment variables at call time, not at module load time. + * This is important because .env files are loaded by NestJS ConfigModule after modules are imported. + */ +export const getDevAuthConfig = (): DevAuthConfig => { const isDevelopment = process.env["NODE_ENV"] !== "production"; return { @@ -36,4 +41,7 @@ export const createDevAuthConfig = (): DevAuthConfig => { }; }; -export const devAuthConfig = createDevAuthConfig(); +/** + * @deprecated Use getDevAuthConfig() instead to ensure env vars are read after ConfigModule loads + */ +export const devAuthConfig = getDevAuthConfig(); diff --git a/apps/bff/src/core/security/middleware/csrf.middleware.ts b/apps/bff/src/core/security/middleware/csrf.middleware.ts index 68880173..a8408ebb 100644 --- a/apps/bff/src/core/security/middleware/csrf.middleware.ts +++ b/apps/bff/src/core/security/middleware/csrf.middleware.ts @@ -4,7 +4,7 @@ import { ConfigService } from "@nestjs/config"; import { Logger } from "nestjs-pino"; import type { Request, Response, NextFunction } from "express"; import { CsrfService } from "../services/csrf.service.js"; -import { devAuthConfig } from "../../config/auth-dev.config.js"; +import { getDevAuthConfig } from "../../config/auth-dev.config.js"; interface CsrfRequestBody { _csrf?: string | string[]; @@ -61,8 +61,9 @@ export class CsrfMiddleware implements NestMiddleware { use(req: CsrfRequest, res: Response, next: NextFunction): void { // Skip CSRF protection entirely in development if disabled - if (devAuthConfig.disableCsrf) { - if (devAuthConfig.enableDebugLogs) { + const devConfig = getDevAuthConfig(); + if (devConfig.disableCsrf) { + if (devConfig.enableDebugLogs) { this.logger.debug("CSRF protection disabled in development", { method: req.method, path: req.path, diff --git a/apps/bff/src/integrations/salesforce/services/opportunity/opportunity-mutation.service.ts b/apps/bff/src/integrations/salesforce/services/opportunity/opportunity-mutation.service.ts index a7074393..a5651edd 100644 --- a/apps/bff/src/integrations/salesforce/services/opportunity/opportunity-mutation.service.ts +++ b/apps/bff/src/integrations/salesforce/services/opportunity/opportunity-mutation.service.ts @@ -180,6 +180,7 @@ export class OpportunityMutationService { const payload: Record = { Id: safeOppId, [OPPORTUNITY_FIELD_MAP.whmcsServiceId]: whmcsServiceId, + [OPPORTUNITY_FIELD_MAP.whmcsRegistrationUrl]: `productselect=${whmcsServiceId}`, }; try { diff --git a/apps/bff/src/integrations/salesforce/services/salesforce-sim-inventory.service.ts b/apps/bff/src/integrations/salesforce/services/salesforce-sim-inventory.service.ts index 7596d72b..89836db0 100644 --- a/apps/bff/src/integrations/salesforce/services/salesforce-sim-inventory.service.ts +++ b/apps/bff/src/integrations/salesforce/services/salesforce-sim-inventory.service.ts @@ -28,6 +28,18 @@ export const SIM_INVENTORY_STATUS = { export type SimInventoryStatus = (typeof SIM_INVENTORY_STATUS)[keyof typeof SIM_INVENTORY_STATUS]; +/** + * Optional assignment details when marking a SIM as assigned + */ +export interface SimAssignmentDetails { + /** Salesforce Account ID to assign the SIM to */ + accountId?: string; + /** Salesforce Order ID that assigned the SIM */ + orderId?: string; + /** SIM Type (eSIM or Physical SIM) */ + simType?: string; +} + /** * SIM Inventory record from Salesforce */ @@ -165,19 +177,46 @@ export class SalesforceSIMInventoryService { /** * Update SIM Inventory status to "Assigned" after successful activation + * + * @param simInventoryId - Salesforce ID of the SIM_Inventory__c record + * @param details - Optional assignment details (account, order, SIM type) */ - async markAsAssigned(simInventoryId: string): Promise { + async markAsAssigned(simInventoryId: string, details?: SimAssignmentDetails): Promise { const safeId = assertSalesforceId(simInventoryId, "simInventoryId"); - this.logger.log("Marking SIM Inventory as Assigned", { simInventoryId: safeId }); + this.logger.log("Marking SIM Inventory as Assigned", { + simInventoryId: safeId, + hasAssignmentDetails: !!details, + accountId: details?.accountId, + orderId: details?.orderId, + simType: details?.simType, + }); try { - await this.sf.sobject("SIM_Inventory__c").update?.({ + const updatePayload: Record = { Id: safeId, Status__c: SIM_INVENTORY_STATUS.ASSIGNED, - }); + }; - this.logger.log("SIM Inventory marked as Assigned", { simInventoryId: safeId }); + // Add optional assignment fields if provided + if (details?.accountId) { + updatePayload["Assigned_Account__c"] = details.accountId; + } + if (details?.orderId) { + updatePayload["Assigned_Order__c"] = details.orderId; + } + if (details?.simType) { + updatePayload["SIM_Type__c"] = details.simType; + } + + await this.sf.sobject("SIM_Inventory__c").update?.(updatePayload as { Id: string }); + + this.logger.log("SIM Inventory marked as Assigned", { + simInventoryId: safeId, + assignedAccount: details?.accountId, + assignedOrder: details?.orderId, + simType: details?.simType, + }); } catch (error: unknown) { this.logger.error("Failed to update SIM Inventory status", { simInventoryId: safeId, diff --git a/apps/bff/src/integrations/whmcs/services/whmcs-order.service.ts b/apps/bff/src/integrations/whmcs/services/whmcs-order.service.ts index 8e2973e4..7583bc90 100644 --- a/apps/bff/src/integrations/whmcs/services/whmcs-order.service.ts +++ b/apps/bff/src/integrations/whmcs/services/whmcs-order.service.ts @@ -184,6 +184,67 @@ export class WhmcsOrderService { } } + /** + * Update service custom fields after order is accepted + * Uses UpdateClientProduct API which supports field names (not just IDs) + * + * @param serviceId - The WHMCS service ID to update + * @param customFields - Key-value map of custom field names to values + */ + async updateServiceCustomFields( + serviceId: number, + customFields: Record + ): Promise { + if (!customFields || Object.keys(customFields).length === 0) { + return; + } + + this.logger.log("Updating WHMCS service custom fields", { + serviceId, + fieldCount: Object.keys(customFields).length, + fieldNames: Object.keys(customFields), + }); + + try { + // Serialize custom fields to WHMCS format (base64 encoded PHP serialized array) + const serialized = this.serializeCustomFields(customFields); + + await this.connection.makeRequest("UpdateClientProduct", { + serviceid: serviceId, + customfields: serialized, + }); + + this.logger.log("WHMCS service custom fields updated", { + serviceId, + fields: Object.keys(customFields), + }); + } catch (error) { + this.logger.error("Failed to update WHMCS service custom fields", { + error: extractErrorMessage(error), + serviceId, + fieldNames: Object.keys(customFields), + }); + throw error; + } + } + + /** + * Serialize custom fields to WHMCS format (base64 encoded PHP serialized array) + */ + private serializeCustomFields(data: Record): string { + const entries = Object.entries(data).filter(([k, v]) => k && v); + if (entries.length === 0) return ""; + + const parts = entries.map(([key, value]) => { + const keyBytes = Buffer.byteLength(key, "utf8"); + const valueBytes = Buffer.byteLength(value, "utf8"); + return `s:${keyBytes}:"${key}";s:${valueBytes}:"${value}";`; + }); + + const serialized = `a:${entries.length}:{${parts.join("")}}`; + return Buffer.from(serialized).toString("base64"); + } + /** * Get order details from WHMCS */ diff --git a/apps/bff/src/modules/auth/presentation/http/auth.controller.ts b/apps/bff/src/modules/auth/presentation/http/auth.controller.ts index 5407b2bd..5cbf49c7 100644 --- a/apps/bff/src/modules/auth/presentation/http/auth.controller.ts +++ b/apps/bff/src/modules/auth/presentation/http/auth.controller.ts @@ -41,7 +41,7 @@ import { clearTrustedDeviceCookie, getTrustedDeviceToken, } from "./utils/trusted-device-cookie.util.js"; -import { devAuthConfig } from "@bff/core/config/auth-dev.config.js"; +import { getDevAuthConfig } from "@bff/core/config/auth-dev.config.js"; import { TrustedDeviceService } from "../../infra/trusted-device/trusted-device.service.js"; // Import Zod schemas from domain @@ -143,7 +143,7 @@ export class AuthController { this.applyAuthRateLimitHeaders(req, res); // In dev mode with SKIP_OTP=true, skip OTP and complete login directly - if (devAuthConfig.skipOtp) { + if (getDevAuthConfig().skipOtp) { const loginResult = await this.authOrchestrator.completeLogin( { id: req.user.id, email: req.user.email, role: req.user.role ?? "USER" }, req diff --git a/apps/bff/src/modules/me-status/me-status.aggregator.ts b/apps/bff/src/modules/me-status/me-status.aggregator.ts index 9214c55a..b157fb72 100644 --- a/apps/bff/src/modules/me-status/me-status.aggregator.ts +++ b/apps/bff/src/modules/me-status/me-status.aggregator.ts @@ -217,7 +217,7 @@ export class MeStatusAggregator { o => o.status === "Draft" || o.status === "Pending" || - (o.status === "Activated" && o.activationStatus !== "Completed") + (o.status === "Processed" && o.activationStatus !== "Completed") ); if (!pendingOrder) { diff --git a/apps/bff/src/modules/orders/services/fulfillment-step-executors.service.ts b/apps/bff/src/modules/orders/services/fulfillment-step-executors.service.ts index a3c1efb7..cf96da1f 100644 --- a/apps/bff/src/modules/orders/services/fulfillment-step-executors.service.ts +++ b/apps/bff/src/modules/orders/services/fulfillment-step-executors.service.ts @@ -100,12 +100,28 @@ export class FulfillmentStepExecutors { hasContactIdentity: !!contactIdentity, }); + // Build assignment details for SIM Inventory record (only include defined properties) + const assignmentDetails: { + accountId?: string; + orderId?: string; + simType?: string; + } = { + orderId: ctx.sfOrderId, + }; + if (ctx.validation?.sfOrder?.AccountId) { + assignmentDetails.accountId = ctx.validation.sfOrder.AccountId; + } + if (ctx.validation?.sfOrder?.SIM_Type__c) { + assignmentDetails.simType = ctx.validation.sfOrder.SIM_Type__c; + } + // Build request with only defined optional properties const request: Parameters[0] = { orderDetails: ctx.orderDetails, configurations, voiceMailEnabled, callWaitingEnabled, + assignmentDetails, }; if (assignedPhysicalSimId) { request.assignedPhysicalSimId = assignedPhysicalSimId; @@ -120,7 +136,8 @@ export class FulfillmentStepExecutors { } /** - * Update Salesforce order status to "Activated" after SIM fulfillment + * Update Salesforce Activation_Status__c after SIM fulfillment + * Note: Status is NOT changed here to avoid locking the Order before WHMCS setup */ async executeSfActivatedUpdate( ctx: OrderFulfillmentContext, @@ -130,13 +147,14 @@ export class FulfillmentStepExecutors { return { skipped: true }; } + // Only update Activation_Status__c - Status will be set in sf_registration_complete + // to avoid locking the Order before WHMCS info can be written const result = await this.salesforceFacade.updateOrder({ Id: ctx.sfOrderId, - Status: "Activated", Activation_Status__c: "Activated", }); this.sideEffects.publishStatusUpdate(ctx.sfOrderId, { - status: "Activated", + status: ctx.validation?.sfOrder?.Status ?? "Pending", activationStatus: "Activated", stage: "in_progress", source: "fulfillment", @@ -166,11 +184,15 @@ export class FulfillmentStepExecutors { // Set phone number as domain (shows in WHMCS Domain field) item.domain = simFulfillmentResult.phoneNumber!; // Also add to custom fields for SIM Number field + // Note: Field names must match exactly as configured in WHMCS (with spaces) item.customFields = { ...item.customFields, - SimNumber: simFulfillmentResult.phoneNumber, + "SIM Number": simFulfillmentResult.phoneNumber, ...(simFulfillmentResult.serialNumber && { - SerialNumber: simFulfillmentResult.serialNumber, + "Serial Number": simFulfillmentResult.serialNumber, + }), + ...(simFulfillmentResult.eid && { + EID: simFulfillmentResult.eid, }), }; } @@ -256,33 +278,78 @@ export class FulfillmentStepExecutors { return { orderId, serviceIds }; } + /** + * Update WHMCS service custom fields after order is accepted + * This is necessary because AddOrder expects field IDs, but we use field names + */ + async executeWhmcsCustomFieldsUpdate( + ctx: OrderFulfillmentContext, + whmcsCreateResult?: WhmcsOrderResult, + simFulfillmentResult?: SimFulfillmentResult + ): Promise<{ updated: boolean; serviceId?: number }> { + const serviceId = whmcsCreateResult?.serviceIds?.[0]; + + if (!serviceId) { + this.logger.debug("No WHMCS service ID available for custom fields update", { + sfOrderId: ctx.sfOrderId, + }); + return { updated: false }; + } + + // Build custom fields from SIM fulfillment result + const customFields: Record = {}; + + if (simFulfillmentResult?.phoneNumber) { + customFields["SIM Number"] = simFulfillmentResult.phoneNumber; + } + if (simFulfillmentResult?.serialNumber) { + customFields["Serial Number"] = simFulfillmentResult.serialNumber; + } + if (simFulfillmentResult?.eid) { + customFields["EID"] = simFulfillmentResult.eid; + } + + if (Object.keys(customFields).length === 0) { + this.logger.debug("No custom fields to update for WHMCS service", { + sfOrderId: ctx.sfOrderId, + serviceId, + }); + return { updated: false, serviceId }; + } + + this.logger.log("Updating WHMCS service custom fields", { + sfOrderId: ctx.sfOrderId, + serviceId, + fieldNames: Object.keys(customFields), + }); + + await this.whmcsOrderService.updateServiceCustomFields(serviceId, customFields); + + return { updated: true, serviceId }; + } + /** * Update Salesforce with WHMCS registration info + * This is the final step that sets Status to "Processed" */ async executeSfRegistrationComplete( ctx: OrderFulfillmentContext, whmcsCreateResult?: WhmcsOrderResult, - simFulfillmentResult?: SimFulfillmentResult + // eslint-disable-next-line @typescript-eslint/no-unused-vars -- Parameter kept for interface consistency + _simFulfillmentResult?: SimFulfillmentResult ): Promise { - // For SIM orders that are already "Activated", don't change Status - // Only update WHMCS info. For non-SIM orders, set Status to "Activated" - const isSIMOrder = ctx.orderDetails?.orderType === "SIM"; - const isAlreadyActivated = simFulfillmentResult?.activated === true; - + // Set Status to "Processed" and update WHMCS info + // Status is set here (not earlier) to avoid locking the Order before WHMCS data is written const updatePayload: { Id: string; [key: string]: unknown } = { Id: ctx.sfOrderId, + Status: "Processed", Activation_Status__c: "Activated", WHMCS_Order_ID__c: whmcsCreateResult?.orderId?.toString(), }; - // Only set Status if not already activated (non-SIM orders) - if (!isSIMOrder || !isAlreadyActivated) { - updatePayload["Status"] = "Activated"; - } - const result = await this.salesforceFacade.updateOrder(updatePayload); this.sideEffects.publishStatusUpdate(ctx.sfOrderId, { - status: "Activated", + status: "Processed", activationStatus: "Activated", stage: "completed", source: "fulfillment", @@ -290,7 +357,7 @@ export class FulfillmentStepExecutors { payload: { whmcsOrderId: whmcsCreateResult?.orderId, whmcsServiceIds: whmcsCreateResult?.serviceIds, - simPhoneNumber: simFulfillmentResult?.phoneNumber, + simPhoneNumber: ctx.simFulfillmentResult?.phoneNumber, }, }); await this.sideEffects.notifyOrderActivated(ctx.sfOrderId, ctx.validation?.sfOrder?.AccountId); diff --git a/apps/bff/src/modules/orders/services/fulfillment-step-factory.service.ts b/apps/bff/src/modules/orders/services/fulfillment-step-factory.service.ts index c1d5ea5a..8cde253f 100644 --- a/apps/bff/src/modules/orders/services/fulfillment-step-factory.service.ts +++ b/apps/bff/src/modules/orders/services/fulfillment-step-factory.service.ts @@ -68,10 +68,11 @@ export class FulfillmentStepFactory { steps.push(this.createSfActivatedUpdateStep(context, state)); } - // Steps 5-9: WHMCS and completion + // Steps 5-10: WHMCS and completion steps.push(this.createMappingStep(context, state)); steps.push(this.createWhmcsCreateStep(context, state)); steps.push(this.createWhmcsAcceptStep(context, state)); + steps.push(this.createWhmcsCustomFieldsUpdateStep(context, state)); steps.push(this.createSfRegistrationCompleteStep(context, state)); steps.push(this.createOpportunityUpdateStep(context, state)); @@ -130,7 +131,7 @@ export class FulfillmentStepFactory { ): DistributedStep { return { id: "sf_activated_update", - description: "Update Salesforce order status to Activated", + description: "Update Salesforce Activation_Status to Activated", execute: this.createTrackedStep(ctx, "sf_activated_update", async () => { return this.executors.executeSfActivatedUpdate(ctx, state.simFulfillmentResult); }), @@ -201,6 +202,24 @@ export class FulfillmentStepFactory { }; } + private createWhmcsCustomFieldsUpdateStep( + ctx: OrderFulfillmentContext, + state: StepState + ): DistributedStep { + return { + id: "whmcs_custom_fields", + description: "Update WHMCS service custom fields (SIM Number, Serial Number, EID)", + execute: this.createTrackedStep(ctx, "whmcs_custom_fields", async () => { + return this.executors.executeWhmcsCustomFieldsUpdate( + ctx, + state.whmcsCreateResult, + state.simFulfillmentResult + ); + }), + critical: false, // Custom fields update failure shouldn't rollback fulfillment + }; + } + private createSfRegistrationCompleteStep( ctx: OrderFulfillmentContext, state: StepState diff --git a/apps/bff/src/modules/orders/services/order-fulfillment-orchestrator.service.ts b/apps/bff/src/modules/orders/services/order-fulfillment-orchestrator.service.ts index 588a54e4..d8483928 100644 --- a/apps/bff/src/modules/orders/services/order-fulfillment-orchestrator.service.ts +++ b/apps/bff/src/modules/orders/services/order-fulfillment-orchestrator.service.ts @@ -230,6 +230,7 @@ export class OrderFulfillmentOrchestrator { { step: "mapping", status: "pending" }, { step: "whmcs_create", status: "pending" }, { step: "whmcs_accept", status: "pending" }, + { step: "whmcs_custom_fields", status: "pending" }, { step: "sf_registration_complete", status: "pending" }, { step: "opportunity_update", status: "pending" } ); diff --git a/apps/bff/src/modules/orders/services/sim-fulfillment.service.ts b/apps/bff/src/modules/orders/services/sim-fulfillment.service.ts index e0ca48ab..1e7a7cec 100644 --- a/apps/bff/src/modules/orders/services/sim-fulfillment.service.ts +++ b/apps/bff/src/modules/orders/services/sim-fulfillment.service.ts @@ -22,6 +22,18 @@ export interface ContactIdentityData { birthday: string; // YYYYMMDD format } +/** + * Assignment details for Physical SIM inventory + */ +export interface SimAssignmentDetails { + /** Salesforce Account ID to assign the SIM to */ + accountId?: string; + /** Salesforce Order ID that assigned the SIM */ + orderId?: string; + /** SIM Type (eSIM or Physical SIM) */ + simType?: string; +} + export interface SimFulfillmentRequest { orderDetails: OrderDetails; configurations: Record; @@ -33,6 +45,8 @@ export interface SimFulfillmentRequest { callWaitingEnabled?: boolean; /** Contact identity data for PA05-05 */ contactIdentity?: ContactIdentityData; + /** Assignment details for SIM Inventory record (Physical SIM only) */ + assignmentDetails?: SimAssignmentDetails; } /** @@ -49,6 +63,8 @@ export interface SimFulfillmentResult { serialNumber?: string; /** Salesforce SIM Inventory ID */ simInventoryId?: string; + /** EID for eSIM (for WHMCS custom fields) */ + eid?: string; } @Injectable() @@ -67,6 +83,7 @@ export class SimFulfillmentService { voiceMailEnabled = false, callWaitingEnabled = false, contactIdentity, + assignmentDetails, } = request; const simType = this.readEnum(configurations["simType"], ["eSIM", "Physical SIM"]); @@ -154,6 +171,7 @@ export class SimFulfillmentService { activated: true, simType: "eSIM", phoneNumber, + eid, }; } else { // Physical SIM activation flow (PA05-18 + PA02-01 + PA05-05) @@ -172,6 +190,7 @@ export class SimFulfillmentService { voiceMailEnabled, callWaitingEnabled, contactIdentity, + assignmentDetails, }); this.logger.log("Physical SIM fulfillment completed successfully", { @@ -259,19 +278,15 @@ export class SimFulfillmentService { } /** - * Activate Physical SIM via Freebit PA05-18 + PA02-01 + PA05-05 APIs + * Activate Physical SIM (Black SIM) via Freebit PA02-01 + PA05-05 APIs * - * Flow for Physical SIMs: + * Flow for Physical SIMs (Black SIMs): * 1. Fetch SIM Inventory details from Salesforce * 2. Validate SIM status is "Available" * 3. Map product SKU to Freebit plan code - * 4. Call Freebit PA05-18 (Semi-Black Registration) - MUST be called first! - * 5. Call Freebit PA02-01 (Account Registration) with createType="add" - * 6. Call Freebit PA05-05 (Voice Options) to configure voice features - * 7. Update SIM Inventory status to "Assigned" - * - * Note: PA05-18 must be called before PA02-01, otherwise PA02-01 will fail - * with error 210 "アカウント不在エラー" (Account not found error). + * 4. Call Freebit PA02-01 (Account Registration) with createType="new" + * 5. Call Freebit PA05-05 (Voice Options) to configure voice features + * 6. Update SIM Inventory status to "Assigned" */ private async activatePhysicalSim(params: { orderId: string; @@ -281,6 +296,7 @@ export class SimFulfillmentService { voiceMailEnabled: boolean; callWaitingEnabled: boolean; contactIdentity?: ContactIdentityData | undefined; + assignmentDetails?: SimAssignmentDetails | undefined; }): Promise<{ phoneNumber: string; serialNumber: string }> { const { orderId, @@ -290,9 +306,10 @@ export class SimFulfillmentService { voiceMailEnabled, callWaitingEnabled, contactIdentity, + assignmentDetails, } = params; - this.logger.log("Starting Physical SIM activation (PA05-18 + PA02-01 + PA05-05)", { + this.logger.log("Starting Physical SIM activation (PA02-01 + PA05-05)", { orderId, simInventoryId, planSku, @@ -315,50 +332,27 @@ export class SimFulfillmentService { // Use phone number from SIM inventory const accountPhoneNumber = simRecord.phoneNumber; - // PT Number is the productNumber required for PA05-18 - const productNumber = simRecord.ptNumber; this.logger.log("Physical SIM inventory validated", { orderId, simInventoryId, accountPhoneNumber, - productNumber, planCode, }); try { - // Step 4: Call Freebit PA05-18 (Semi-Black Registration) - MUST be first! - this.logger.log("Calling PA05-18 Semi-Black Registration", { - orderId, - account: accountPhoneNumber, - productNumber, - planCode, - }); - - await this.freebitFacade.registerSemiBlackAccount({ - account: accountPhoneNumber, - productNumber, - planCode, - }); - - this.logger.log("PA05-18 Semi-Black Registration successful", { - orderId, - account: accountPhoneNumber, - }); - - // Step 5: Call Freebit PA02-01 (Account Registration) with createType="add" - // Note: After PA05-18, we use createType="add" (not "new") + // Step 4: Call Freebit PA02-01 (Account Registration) for Black SIM this.logger.log("Calling PA02-01 Account Registration", { orderId, account: accountPhoneNumber, planCode, - createType: "add", + createType: "new", }); await this.freebitFacade.registerAccount({ account: accountPhoneNumber, planCode, - createType: "add", + createType: "new", }); this.logger.log("PA02-01 Account Registration successful", { @@ -366,7 +360,7 @@ export class SimFulfillmentService { account: accountPhoneNumber, }); - // Step 6: Call Freebit PA05-05 (Voice Options Registration) + // Step 5: Call Freebit PA05-05 (Voice Options Registration) // Only call if we have contact identity data if (contactIdentity) { this.logger.log("Calling PA05-05 Voice Options Registration", { @@ -401,8 +395,8 @@ export class SimFulfillmentService { }); } - // Step 7: Update SIM Inventory status to "Assigned" - await this.simInventory.markAsAssigned(simInventoryId); + // Step 6: Update SIM Inventory status to "Assigned" with assignment details + await this.simInventory.markAsAssigned(simInventoryId, assignmentDetails); this.logger.log("Physical SIM activated successfully", { orderId, diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 28069cd8..c618e14e 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -6,3 +6,14 @@ catalog: "@types/node": ^25.2.0 typescript: ^5.9.3 zod: ^4.3.6 +onlyBuiltDependencies: + - "@nestjs/core" + - "@prisma/engines" + - "@swc/core" + - argon2 + - esbuild + - prisma + - protobufjs + - sharp + - ssh2 + - unrs-resolver