mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-05 21:12:07 +00:00
362 lines
13 KiB
TypeScript
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) : [],
|
|
};
|
|
}
|