238 lines
8.4 KiB
TypeScript

/**
* Orders Domain - Salesforce Provider Mapper
*
* Transforms Salesforce Order and OrderItem records into domain contracts.
*/
import type {
OrderDetails,
OrderItemDetails,
OrderItemSummary,
OrderSummary,
} from "../../contract.js";
import { normalizeBillingCycle } from "../../helpers.js";
import { orderDetailsSchema, orderSummarySchema, orderItemDetailsSchema } from "../../schema.js";
import type {
SalesforceProduct2WithPricebookEntries,
SalesforcePricebookEntryRecord,
} from "#internal/services/providers/salesforce";
import { defaultSalesforceOrderFieldMap, type SalesforceOrderFieldMap } from "./field-map.js";
import type { SalesforceOrderItemRecord, SalesforceOrderRecord } from "./raw.types.js";
/**
* Helper function to get sort priority for item class
*/
function getItemClassSortPriority(itemClass?: string): number {
if (!itemClass) return 4;
const normalized = itemClass.toLowerCase();
if (normalized === "service") return 1;
if (normalized === "installation" || normalized === "activation") return 2;
if (normalized === "add-on") return 3;
return 4;
}
/**
* Extract product info from Salesforce product record using field map
*/
function extractProductInfo(
product: SalesforceProduct2WithPricebookEntries,
productFields: SalesforceOrderFieldMap["product"]
): OrderItemDetails["product"] {
return {
id: ensureString(product.Id),
name: ensureString(product.Name),
sku: ensureString(product[productFields.sku]) ?? undefined,
itemClass: ensureString(product[productFields.itemClass]) ?? undefined,
whmcsProductId: resolveWhmcsProductId(product[productFields.whmcsProductId]),
internetOfferingType: ensureString(product[productFields.internetOfferingType]) ?? undefined,
internetPlanTier: ensureString(product[productFields.internetPlanTier]) ?? undefined,
vpnRegion: ensureString(product[productFields.vpnRegion]) ?? undefined,
isBundledAddon: coerceBoolean(product[productFields.isBundledAddon]),
bundledAddonId: ensureString(product[productFields.bundledAddon]) ?? undefined,
};
}
/**
* Build order item summary from details
*/
function buildOrderItemSummary(details: OrderItemDetails): OrderItemSummary {
return {
productName: details.product?.name,
name: details.product?.name,
sku: details.product?.sku,
productId: details.product?.id,
status: undefined,
billingCycle: details.billingCycle,
itemClass: details.product?.itemClass,
quantity: details.quantity,
unitPrice: details.unitPrice,
totalPrice: details.totalPrice,
isBundledAddon: details.product?.isBundledAddon,
bundledAddonId: details.product?.bundledAddonId,
};
}
/**
* Transform a Salesforce OrderItem record into domain details + summary.
*/
export function transformSalesforceOrderItem(
record: SalesforceOrderItemRecord,
fieldMap: SalesforceOrderFieldMap = defaultSalesforceOrderFieldMap
): { details: OrderItemDetails; summary: OrderItemSummary } {
const pricebookEntry = (record.PricebookEntry ?? null) as SalesforcePricebookEntryRecord | null;
const product = pricebookEntry?.Product2 as SalesforceProduct2WithPricebookEntries | undefined;
const billingCycleRaw =
record[fieldMap.orderItem.billingCycle] ??
(product ? (product[fieldMap.product.billingCycle] as unknown) : undefined);
const billingCycle =
billingCycleRaw !== undefined && billingCycleRaw !== null
? normalizeBillingCycle(billingCycleRaw)
: undefined;
const details = orderItemDetailsSchema.parse({
id: record.Id,
orderId: ensureString(record.OrderId) ?? "",
quantity: normalizeQuantity(record.Quantity),
unitPrice: coerceNumber(record.UnitPrice),
totalPrice: coerceNumber(record.TotalPrice),
billingCycle,
product: product ? extractProductInfo(product, fieldMap.product) : undefined,
});
return { details, summary: buildOrderItemSummary(details) };
}
/**
* Transform a Salesforce Order record (with associated OrderItems) into domain OrderDetails.
*/
export function transformSalesforceOrderDetails(
order: SalesforceOrderRecord,
itemRecords: SalesforceOrderItemRecord[],
fieldMap: SalesforceOrderFieldMap = defaultSalesforceOrderFieldMap
): OrderDetails {
const transformedItems = itemRecords.map(record =>
transformSalesforceOrderItem(record, fieldMap)
);
// Sort items by item class priority (Service -> Installation/Activation -> Add-on -> Others)
transformedItems.sort((a, b) => {
const priorityA = getItemClassSortPriority(a.summary.itemClass);
const priorityB = getItemClassSortPriority(b.summary.itemClass);
return priorityA - priorityB;
});
const items = transformedItems.map(item => item.details);
const itemsSummary = transformedItems.map(item => item.summary);
const summary = buildOrderSummary(order, itemsSummary, fieldMap);
const orderFields = fieldMap.order;
return orderDetailsSchema.parse({
...summary,
accountId: ensureString(order.AccountId),
accountName: ensureString(order.Account?.Name),
pricebook2Id: ensureString(order.Pricebook2Id),
opportunityId: ensureString(order.OpportunityId), // Linked Opportunity for lifecycle tracking
activationType: ensureString(order[orderFields.activationType]),
activationStatus: summary.activationStatus,
activationScheduledAt: ensureString(order[orderFields.activationScheduledAt]),
activationErrorCode: ensureString(order[orderFields.activationErrorCode]),
activationErrorMessage: ensureString(order[orderFields.activationErrorMessage]),
activatedDate: ensureString(order.ActivatedDate),
items,
});
}
/**
* Transform a Salesforce Order record (with optional OrderItems) into domain OrderSummary.
*/
export function transformSalesforceOrderSummary(
order: SalesforceOrderRecord,
itemRecords: SalesforceOrderItemRecord[],
fieldMap: SalesforceOrderFieldMap = defaultSalesforceOrderFieldMap
): OrderSummary {
const transformedItems = itemRecords.map(record =>
transformSalesforceOrderItem(record, fieldMap)
);
// Sort items by item class priority (Service -> Installation/Activation -> Add-on -> Others)
transformedItems.sort((a, b) => {
const priorityA = getItemClassSortPriority(a.summary.itemClass);
const priorityB = getItemClassSortPriority(b.summary.itemClass);
return priorityA - priorityB;
});
const itemsSummary = transformedItems.map(item => item.summary);
return buildOrderSummary(order, itemsSummary, fieldMap);
}
function buildOrderSummary(
order: SalesforceOrderRecord,
itemsSummary: OrderItemSummary[],
fieldMap: SalesforceOrderFieldMap
): OrderSummary {
const orderFields = fieldMap.order;
const effectiveDate =
ensureString(order.EffectiveDate) ??
ensureString(order.CreatedDate) ??
new Date().toISOString();
const createdDate = ensureString(order.CreatedDate) ?? effectiveDate;
const lastModifiedDate = ensureString(order.LastModifiedDate) ?? createdDate;
const totalAmount = coerceNumber(order.TotalAmount);
return orderSummarySchema.parse({
id: order.Id,
orderNumber: ensureString(order.OrderNumber) ?? order.Id,
status: ensureString(order.Status) ?? "Unknown",
orderType: ensureString(order[orderFields.type]) ?? undefined,
effectiveDate,
totalAmount: typeof totalAmount === "number" ? totalAmount : undefined,
createdDate,
lastModifiedDate,
whmcsOrderId: ensureString(order[orderFields.whmcsOrderId]) ?? undefined,
activationStatus: ensureString(order[orderFields.activationStatus]) ?? undefined,
itemsSummary,
});
}
function ensureString(value: unknown): string | undefined {
return typeof value === "string" ? value : undefined;
}
function coerceNumber(value: unknown): number | undefined {
if (typeof value === "number") return Number.isFinite(value) ? value : undefined;
if (typeof value === "string") {
const parsed = Number.parseFloat(value);
return Number.isFinite(parsed) ? parsed : undefined;
}
return undefined;
}
function normalizeQuantity(value: unknown): number {
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
return Math.trunc(value);
}
return 1;
}
function resolveWhmcsProductId(value: unknown): string | undefined {
if (value === null || value === undefined) {
return undefined;
}
if (typeof value === "number") {
return Number.isFinite(value) ? String(value) : undefined;
}
if (typeof value === "string") {
return value;
}
return undefined;
}
function coerceBoolean(value: unknown): boolean | undefined {
if (typeof value === "boolean") {
return value;
}
return undefined;
}