shoc-frontend-new/src/domain/vendors/mappers/vendor-mapper.ts
2026-09-14 13:07:00 -03:00

362 lines
13 KiB
TypeScript

import type {
Vendor,
VendorDeactivationImpact,
VendorDirectoryTechnician,
VendorFacetCompany,
VendorFacetJobBucket,
VendorFacetLocation,
VendorFacets,
VendorListItem,
VendorOpenWorkOrderSummary,
VendorPortalToken,
VendorPreferredContact,
} from "@/domain/vendors/types/vendor";
const PREFERRED_CONTACT_VALUES = new Set<VendorPreferredContact>(["Phone", "Email", "Text"]);
function asRecord(raw: unknown): Record<string, unknown> {
return typeof raw === "object" && raw !== null ? (raw as Record<string, unknown>) : {};
}
function readString(record: Record<string, unknown>, ...keys: string[]): string {
for (const key of keys) {
const value = record[key];
if (typeof value === "string") return value;
if (typeof value === "number") return String(value);
}
return "";
}
function readNumber(record: Record<string, unknown>, ...keys: string[]): number {
for (const key of keys) {
const value = record[key];
if (typeof value === "number" && Number.isFinite(value)) return value;
if (typeof value === "string") {
const parsed = Number(value);
if (Number.isFinite(parsed)) return parsed;
}
}
return 0;
}
function readId(record: Record<string, unknown>): string | number | null {
const value = record.id ?? record.Id;
if (typeof value === "string") return value.trim() === "" ? null : value;
if (typeof value === "number" && Number.isFinite(value)) return value;
return null;
}
function readCompanyId(record: Record<string, unknown>): string | number | null {
const value = record.companyId ?? record.CompanyId;
if (typeof value === "string") return value.trim() === "" ? null : value;
if (typeof value === "number" && Number.isFinite(value)) return value;
return null;
}
function readNumericCompanyId(record: Record<string, unknown>): number | null {
const value = readCompanyId(record);
if (typeof value === "number") return value;
if (typeof value !== "string" || value.trim() === "") return null;
const parsed = Number(value);
return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
}
function readPreferredContact(
record: Record<string, unknown>,
...keys: string[]
): VendorPreferredContact {
for (const key of keys) {
const value = record[key];
if (typeof value === "string") {
const normalized = value.trim();
if (PREFERRED_CONTACT_VALUES.has(normalized as VendorPreferredContact)) {
return normalized as VendorPreferredContact;
}
const lowered = normalized.toLowerCase();
if (lowered === "phone" || lowered === "call") return "Phone";
if (lowered === "email" || lowered === "e-mail") return "Email";
if (lowered === "text" || lowered === "sms") return "Text";
}
}
return "Phone";
}
function readPreferredContactOptional(
record: Record<string, unknown>,
): VendorPreferredContact | undefined {
const raw = record.preferredContact ?? record.PreferredContact;
if (typeof raw !== "string" || raw.trim() === "") return undefined;
return readPreferredContact(record, "preferredContact", "PreferredContact");
}
export function toCanonicalPhone(value: unknown): string {
const original = String(value ?? "");
let digits = original.replace(/\D+/g, "");
if (digits.length === 11 && digits.startsWith("1")) {
digits = digits.slice(1);
}
if (digits.length !== 10) return original;
return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`;
}
function readBool(record: Record<string, unknown>, ...keys: string[]): boolean {
for (const key of keys) {
const value = record[key];
if (typeof value === "boolean") return value;
}
return true;
}
export function mapVendorListItem(raw: unknown): VendorListItem {
const item = asRecord(raw);
const mapped = {
id: readId(item),
companyId: readCompanyId(item),
companyName: readString(item, "companyName", "CompanyName"),
contactName: readString(item, "contactName", "ContactName"),
email: readString(item, "email", "Email"),
phone: readString(item, "phone", "Phone"),
companyPhone: readString(item, "companyPhone", "CompanyPhone"),
googleMapsUrl: readString(item, "googleMapsUrl", "GoogleMapsUrl"),
notes: readString(item, "notes", "Notes"),
totalJobs: readNumber(item, "totalJobs", "TotalJobs"),
address: readString(item, "address", "Address"),
city: readString(item, "city", "City"),
state: readString(item, "state", "State"),
tradeSpecialties: readString(item, "tradeSpecialties", "TradeSpecialties"),
isActive: readBool(item, "isActive", "IsActive"),
preferredContact: readPreferredContact(item, "preferredContact", "PreferredContact"),
};
const rawTechnicians = item.technicians ?? item.Technicians;
const technicians = Array.isArray(rawTechnicians)
? rawTechnicians.map(mapVendorDirectoryTechnician)
: mapped.id == null
? []
: [
{
id: mapped.id,
contactName: mapped.contactName,
email: mapped.email,
phone: mapped.phone,
preferredContact: mapped.preferredContact,
tradeSpecialties: mapped.tradeSpecialties,
isActive: mapped.isActive,
totalJobs: mapped.totalJobs,
},
];
return { ...mapped, technicians };
}
function mapVendorDirectoryTechnician(raw: unknown): VendorDirectoryTechnician {
const item = asRecord(raw);
const id = readId(item);
return {
id: id ?? "",
contactName: readString(item, "contactName", "ContactName"),
email: readString(item, "email", "Email"),
phone: readString(item, "phone", "Phone"),
preferredContact: readPreferredContact(item, "preferredContact", "PreferredContact"),
tradeSpecialties: readString(item, "tradeSpecialties", "TradeSpecialties"),
isActive: readBool(item, "isActive", "IsActive"),
totalJobs: readNumber(item, "totalJobs", "TotalJobs"),
};
}
export function mapVendorListItems(rawItems: unknown[]): VendorListItem[] {
return rawItems.map(mapVendorListItem);
}
export function mapVendor(raw: unknown): Vendor {
const item = asRecord(raw);
return {
id: readId(item) || undefined,
companyId: readCompanyId(item),
companyName: readString(item, "companyName", "CompanyName"),
contactName: readString(item, "contactName", "ContactName"),
email: readString(item, "email", "Email"),
phone: readString(item, "phone", "Phone"),
companyPhone: readString(item, "companyPhone", "CompanyPhone"),
googleMapsUrl: readString(item, "googleMapsUrl", "GoogleMapsUrl"),
notes: readString(item, "notes", "Notes"),
totalJobs: readNumber(item, "totalJobs", "TotalJobs"),
address: readString(item, "address", "Address"),
city: readString(item, "city", "City"),
state: readString(item, "state", "State"),
zip: readString(item, "zip", "Zip", "zipCode", "ZipCode"),
tradeSpecialties: readString(item, "tradeSpecialties", "TradeSpecialties"),
isActive: readBool(item, "isActive", "IsActive"),
preferredContact: readPreferredContact(item, "preferredContact", "PreferredContact"),
};
}
export function mapVendorToBackend(vendor: unknown): Record<string, unknown> {
const item = asRecord(vendor);
const companyName = readString(item, "companyName", "CompanyName");
const payload: Record<string, unknown> = {
companyId: readNumericCompanyId(item),
companyName,
contactName: readString(item, "contactName", "ContactName"),
email: readString(item, "email", "Email"),
phone: toCanonicalPhone(item.phone ?? item.Phone),
companyPhone: toCanonicalPhone(item.companyPhone ?? item.CompanyPhone),
googleMapsUrl: readString(item, "googleMapsUrl", "GoogleMapsUrl"),
notes: readString(item, "notes", "Notes"),
address: readString(item, "address", "Address"),
city: readString(item, "city", "City"),
state: readString(item, "state", "State"),
zip: readString(item, "zip", "Zip", "zipCode", "ZipCode"),
tradeSpecialties: readString(item, "tradeSpecialties", "TradeSpecialties"),
isActive: readBool(item, "isActive", "IsActive"),
};
const preferredContact = readPreferredContactOptional(item);
if (preferredContact) {
payload.preferredContact = preferredContact;
}
const id = item.id ?? item.Id;
if (typeof id === "string" || typeof id === "number") {
payload.id = id;
}
return payload;
}
export function mapVendorUpdateToBackend(vendor: unknown): Record<string, unknown> {
const item = asRecord(vendor);
const payload: Record<string, unknown> = {};
const fieldMappings = [
["companyName", "CompanyName"],
["contactName", "ContactName"],
["email", "Email"],
["googleMapsUrl", "GoogleMapsUrl"],
["notes", "Notes"],
["address", "Address"],
["city", "City"],
["state", "State"],
["zip", "Zip"],
["tradeSpecialties", "TradeSpecialties"],
["isActive", "IsActive"],
["preferredContact", "PreferredContact"],
] as const;
fieldMappings.forEach(([target, source]) => {
if (Object.prototype.hasOwnProperty.call(item, target)) payload[target] = item[target];
if (Object.prototype.hasOwnProperty.call(item, source)) payload[target] = item[source];
});
if (
Object.prototype.hasOwnProperty.call(item, "companyId") ||
Object.prototype.hasOwnProperty.call(item, "CompanyId")
) {
payload.companyId = readNumericCompanyId(item);
}
if (
Object.prototype.hasOwnProperty.call(item, "phone") ||
Object.prototype.hasOwnProperty.call(item, "Phone")
) {
payload.phone = toCanonicalPhone(item.phone ?? item.Phone);
}
if (
Object.prototype.hasOwnProperty.call(item, "companyPhone") ||
Object.prototype.hasOwnProperty.call(item, "CompanyPhone")
) {
payload.companyPhone = toCanonicalPhone(item.companyPhone ?? item.CompanyPhone);
}
return payload;
}
function mapFacetCompany(raw: unknown): VendorFacetCompany {
const item = asRecord(raw);
return {
companyId: readCompanyId(item),
name: readString(item, "name", "Name"),
companyPhone: readString(item, "companyPhone", "CompanyPhone"),
email: readString(item, "email", "Email"),
address: readString(item, "address", "Address"),
city: readString(item, "city", "City"),
state: readString(item, "state", "State"),
zip: readString(item, "zip", "Zip", "zipCode", "ZipCode"),
googleMapsUrl: readString(item, "googleMapsUrl", "GoogleMapsUrl"),
};
}
function mapFacetLocation(raw: unknown): VendorFacetLocation {
const item = asRecord(raw);
const city = readString(item, "city", "City");
const state = readString(item, "state", "State");
const label = readString(item, "label", "Label");
return {
city,
state,
label: label || [city, state].filter(Boolean).join(", "),
};
}
function mapFacetJobBucket(raw: unknown): VendorFacetJobBucket {
const item = asRecord(raw);
return {
id: readString(item, "id", "Id"),
label: readString(item, "label", "Label"),
};
}
export function mapVendorFacets(raw: unknown): VendorFacets {
const item = asRecord(raw);
const companiesRaw = item.companies ?? item.Companies;
const tradesRaw = item.trades ?? item.Trades;
const locationsRaw = item.locations ?? item.Locations;
const jobBucketsRaw = item.jobBuckets ?? item.JobBuckets;
return {
companies: Array.isArray(companiesRaw) ? companiesRaw.map(mapFacetCompany) : [],
trades: Array.isArray(tradesRaw)
? tradesRaw
.map((entry) => {
if (typeof entry === "string") return entry;
return readString(asRecord(entry), "name", "Name", "value", "Value", "label", "Label");
})
.filter(Boolean)
: [],
locations: Array.isArray(locationsRaw) ? locationsRaw.map(mapFacetLocation) : [],
jobBuckets: Array.isArray(jobBucketsRaw) ? jobBucketsRaw.map(mapFacetJobBucket) : [],
};
}
export function mapVendorPortalToken(raw: unknown): VendorPortalToken | null {
const item = asRecord(raw);
const portalUrl = readString(item, "portalUrl", "PortalUrl");
if (!portalUrl) return null;
return {
portalUrl,
issuedAt: readString(item, "issuedAt", "IssuedAt"),
expiresAt: readString(item, "expiresAt", "ExpiresAt"),
lastUsedAt: readString(item, "lastUsedAt", "LastUsedAt") || undefined,
};
}
function mapOpenWorkOrder(raw: unknown): VendorOpenWorkOrderSummary {
const item = asRecord(raw);
const workOrderId = item.workOrderId ?? item.WorkOrderId;
const dispatchId = item.dispatchId ?? item.DispatchId;
return {
workOrderId:
typeof workOrderId === "string" || typeof workOrderId === "number" ? workOrderId : "",
workOrderNumber: readString(item, "workOrderNumber", "WorkOrderNumber"),
workOrderTitle: readString(item, "workOrderTitle", "WorkOrderTitle"),
status: readString(item, "status", "Status"),
scheduledDate: readString(item, "scheduledDate", "ScheduledDate") || undefined,
dispatchId:
typeof dispatchId === "string" || typeof dispatchId === "number" ? dispatchId : undefined,
};
}
export function mapVendorDeactivationImpact(raw: unknown): VendorDeactivationImpact {
const item = asRecord(raw);
const openWorkOrdersRaw = item.openWorkOrders ?? item.OpenWorkOrders;
const rawId = item.vendorId ?? item.VendorId ?? item.id ?? item.Id;
const vendorId: string | number =
typeof rawId === "string" || typeof rawId === "number" ? rawId : "";
return {
vendorId,
canDeactivate: readBool(item, "canDeactivate", "CanDeactivate"),
openWorkOrders: Array.isArray(openWorkOrdersRaw) ? openWorkOrdersRaw.map(mapOpenWorkOrder) : [],
};
}