@kwiz/common
Version:
KWIZ common utilities and helpers for M365 platform
198 lines • 9.29 kB
JavaScript
import { nsReadOnlyFieldTypesForSublist, nsReadOnlyFieldTypes } from "../../types/ns/ns.common.types";
import { nsCountriesByCode } from "../../types/ns/ns.countries.rest";
import { nsCountriesByEnum } from "../../types/ns/ns.countries.restlet";
import { firstOrNull } from "../collections.base";
import { isNotEmptyArray, isNotEmptyString, isNullOrEmptyString } from "../typecheckers";
import { isnsSuiteTalkRestErrorData } from "./type-checkers";
const nsEndpointHosts = {
suitetalk: '.suitetalk.api.netsuite.com',
restlets: '.restlets.api.netsuite.com',
ui: '.app.netsuite.com'
};
export function getNSAccuntUrlPrefix(accountId) {
return accountId.replace('_', '-').toLowerCase();
}
export function getNsHost(accountId, host) {
return `https://${getNSAccuntUrlPrefix(accountId)}${nsEndpointHosts[host]}`;
}
export function getBundlePage(info) {
if (info.type === "update")
return `${getNsHost(info.accountId, "ui")}/app/bundler/previewbundleupdate.nl?fromcompid=${info.orgId}&domain=PRODUCTION&id=${info.id}`;
else
return `${getNsHost(info.accountId, "ui")}/app/bundler/bundledetails.nl?sourcecompanyid=${info.orgId}&domain=PRODUCTION&config=F&id=${info.id}`;
}
/** send in either an AxiosError that has a response.data object, or just the data object itself. */
export function nsGetErrorDetails(error) {
var _a;
const data = isnsSuiteTalkRestErrorData(error)
? error
: isnsSuiteTalkRestErrorData((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.data)
? error.response.data
: null;
return data;
}
const ignoreSuffixIfDuplicate = ["WithHierarchy", "Copy", "Display"];
function nsFieldEditableCheck(field, allFields) {
if (isNullOrEmptyString(field.title)
|| field.readOnly
|| ['id', 'createdDate', 'lastModifiedDate', 'externalId', 'refName'].includes(field.name))
return false;
if (isNotEmptyArray(allFields)) {
const ending = firstOrNull(ignoreSuffixIfDuplicate, suffix => field.name.endsWith(suffix));
if (isNotEmptyString(ending) && allFields.includes(field.name.slice(0, -1 * ending.length))) //got the field without the ending
return false;
}
return true;
}
export function nsObjectFilter(obj) {
return isNotEmptyString(obj);
}
export function nsIsFeaturedObject(obj) {
return ["account", "lead", "customer"].includes(obj);
}
/** returns the title of the country or empty string if none found */
export function NSCountryToText(country) {
var _a, _b;
return ((_a = nsCountriesByCode[country]) === null || _a === void 0 ? void 0 : _a.title)
|| ((_b = nsCountriesByEnum[country]) === null || _b === void 0 ? void 0 : _b.title)
|| "";
}
/** returns the code and enum of the best matching country or null if not found */
export function TextToNSCountry(country) {
if (isNullOrEmptyString(country))
return null;
let lowerWords = country.toLowerCase().split(" ");
//try to find the best match, until we are left with one option
let allOptions = Object.keys(nsCountriesByCode);
let validOptions = allOptions;
for (let i = 0; i < lowerWords.length && validOptions.length > 1; i++) {
let word = lowerWords[i];
let newOptions = validOptions.filter(o => nsCountriesByCode[o].title.toLowerCase().split(' ').includes(word));
if (newOptions.length > 0) //not empty - use this list
validOptions = newOptions;
}
//done my loop. if I have more than 1 option - pick the best one.
if (validOptions.length === 0 || validOptions.length === allOptions.length)
return null; //none found
else if (validOptions.length === 1)
return { code: validOptions[0], enum: nsCountriesByCode[validOptions[0]].enum };
else {
//if user typed "atlantis" and we have "republic of atlantis" and "atlantis", he would get "atlantis" unless he mentiones "republic" as well
let option = validOptions[0];
let optionTitleSplit = nsCountriesByCode[option].title.split(' ');
validOptions.forEach(o => {
const oTitleSplit = nsCountriesByCode[o].title.split(' ');
if (oTitleSplit.length < optionTitleSplit.length) {
option = o;
optionTitleSplit = oTitleSplit;
}
});
return { code: option, enum: nsCountriesByCode[option].enum };
}
}
export function nsFormatDate(date, fieldType) {
if (!date || isNaN(date.getTime()))
return "";
const pad = (n) => n.toString().padStart(2, '0');
const yyyy = date.getUTCFullYear();
const mm = pad(date.getUTCMonth() + 1);
const dd = pad(date.getUTCDate());
const hh = pad(date.getUTCHours());
const min = pad(date.getUTCMinutes());
const ss = pad(date.getUTCSeconds());
switch (fieldType) {
case 'date':
// YYYY-MM-DD
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
case 'datetime':
case 'datetimetz':
// YYYY-MM-DDTHH:mm:ssZ
// Note: .toISOString() includes milliseconds, which is usually fine,
// but this manual string ensures exact compliance.
return `${yyyy}-${mm}-${dd}T${hh}:${min}:${ss}Z`;
case 'time':
case 'timeofday':
// HH:mm:ss (NetSuite often accepts HH:mm for timeofday)
return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
case 'mmyydate':
// MM/YYYY
return `${pad(date.getMonth() + 1)}/${date.getFullYear()}`;
default:
return date.toISOString();
}
}
const ghostFieldPrefixes = ['_', 'nsapi'];
const systemReadOnlyFields = [
//body fields
'id', 'internalid', 'createddate', 'lastmodifieddate',
'owner', 'status', 'total', 'subtotal', 'tranid',
'wfinstances', 'entryformquerystring',
//sublist fields
'line', 'linenumber', 'quantityonhand', 'quantityavailable', 'taxrate'
];
function tnsrFieldEditableCheck(field, info = {}) {
return field.isVisible !== false && !systemReadOnlyFields.includes(field.id.toLowerCase())
&& !(info.inSubList ? nsReadOnlyFieldTypesForSublist : nsReadOnlyFieldTypes).includes(field.type)
//also remove system / ghost fields by prefix
&& !ghostFieldPrefixes.some(pre => field.id.startsWith(pre));
}
/** get the REST api fields, and restlet api fields and return a full field exanded info */
export function nsExpandFields(restFields, restletFields) {
const expandedFields = {};
const restFieldsLower = {};
const allRestFields = Object.keys(restFields);
allRestFields.map(f => restFieldsLower[f.toLowerCase()] = { ...restFields[f], name: f });
restletFields.bodyFields.forEach(bodyField => {
const restField = restFieldsLower[bodyField.id];
if (restField) {
delete restFieldsLower[bodyField.id]; //remove it from extra fields loop
}
let readOnly = !tnsrFieldEditableCheck(bodyField);
expandedFields[bodyField.id] = {
restId: restField === null || restField === void 0 ? void 0 : restField.name,
restType: restField === null || restField === void 0 ? void 0 : restField.type,
description: restField === null || restField === void 0 ? void 0 : restField.description,
id: bodyField.id,
label: bodyField.label || (restField === null || restField === void 0 ? void 0 : restField.title) || bodyField.id,
type: bodyField.type,
defaultValue: bodyField.defaultValue,
options: bodyField.options,
required: bodyField.isMandatory === true,
readOnly
};
});
if (restletFields.sublists)
Object.keys(restletFields.sublists).forEach(sublist => {
delete restFieldsLower[sublist]; //sublists will show up as rest fields
});
Object.keys(restFieldsLower).forEach(f => {
const restField = restFieldsLower[f];
switch (restField.type) {
case "string":
case "number":
case "boolean":
case "integer":
expandedFields[f] = {
id: f,
required: restField.nullable !== true,
label: restField.title,
description: restField.description,
type: restField.type === "string"
? "text"
: restField.type === "boolean"
? "checkbox"
: restField.type === "number"
? restField.format === "double" ? "currency" : restField.format === "float" ? "float" : "integer"
: restField.type === "integer"
? "integer"
: "text",
restId: restField.name,
restType: restField.type,
readOnly: !nsFieldEditableCheck(restField, allRestFields)
};
break;
}
});
return expandedFields;
}
//# sourceMappingURL=utilities.js.map