475 lines
16 KiB
Markdown

# Parcel/Package Object
This object is supplied a plugin registered with `registerRateEndpoint` when PostalPoint requests
shipping rates from the plugin.
```javascript
export class Package {
constructor(isPrepaid = false) {
this.prepaid = isPrepaid;
this.packaging = {
type: "Parcel",
service: "",
carrier: "",
length: 999999,
width: 999999,
height: 999999,
weightOz: 999999,
nonmachinable: false,
additionalHandling: false,
internalid: 100,
oversizeFlag: false
};
this.extraServices = {
certifiedMail: false,
barcode3800: "",
registeredMail: false,
registeredMailAmount: false, // can be a number in USD
returnReceipt: false,
returnReceiptElectronic: false,
insurance: false, // can be a number in USD
signature: false, // can be false, "SIGNATURE", or "SIGNATURE_RESTRICTED"
hazmat: false,
perishable: false,
crematedRemains: false,
liveAnimal: false, // BEES, DAY_OLD_POULTRY, ADULT_BIRDS, OTHER_LIVES
cod: false, // Collect on Delivery
codAmount: false,
endorsement: "", // ADDRESS_SERVICE_REQUESTED, CHANGE_SERVICE_REQUESTED, FORWARDING_SERVICE_REQUESTED, LEAVE_IF_NO_RESPONSE, RETURN_SERVICE_REQUESTED
carrier_billing_account: {// Bill a third party's account number for the label
type: "", // "" (ignores this entire option), "SENDER" (EasyPost default), "THIRD_PARTY", "RECEIVER", "COLLECT"
carrier: "", // Carrier ID (should be used to filter rates)
account_number: "", // Carrier account number to bill
country: "", // Country account is based in
postal_code: "" // Postal code of account
},
dryIce: false,
dryIceWeight: 0,
dryIceMedical: false
};
this.specialRateEligibility = false;
this.customs = {
contents: "",
contentsExplanation: "", // needed if contents is "other"
signature: "",
restriction: "",
restrictionComments: "", // needed if restriction is "other"
nonDelivery: "return", // "return" or "abandon",
eel_pfc: "",
items: [] // {index: 0, description: "", qty: "", lbs: "", oz: "", value: "", hscode: "", origin: US"}
};
this.toAddress = new Address();
this.returnAddress = new Address();
this.originAddress = new Address();
this.trackingNumber = "";
}
/**
* Format as EasyPost shipment object
* @returns {Package.toEasyPostShipment.shipment}
*/
async toEasyPostShipment() {
// Not relevant to plugins
}
/**
* Format as Endicia shipment object
* @returns {Package.toSERAShipment.shipment}
*/
async toSERAShipment() {
// Not relevant to plugins
}
/**
* Get a human-readable summary of size and options.
* Does not include address data.
* @returns {String}
*/
async toString() {
let summary = [];
let packaging = await getPackagingByID(this.packaging.internalid);
let weight = ozToLbsOz(this.packaging.weightOz);
let weightStr = this.packaging.weightOz >= 16 ? `${weight[0]} lbs ${weight[1]} oz` : `${weight[1]} oz`;
if (packaging != false) {
if (packaging.irregular) {
if (packaging.weight === false) {
summary.push("Parcel");
} else {
summary.push(`${weightStr} Parcel`);
}
summary.push("Additional Handling");
} else {
if (packaging.weight === false) {
summary.push(packaging.name);
} else {
summary.push(`${weightStr} ${packaging.name}`);
}
}
} else {
summary.push(weightStr);
}
if (this.extraServices.hazmat) {
summary.push("HAZMAT");
}
if (this.extraServices.liveAnimal === true) {
summary.push("Live Animals");
} else if (typeof this.extraServices.liveAnimal == "string") {
switch (this.extraServices.liveAnimal) {
case "BEES":
summary.push("Live Bees");
break;
case "DAY_OLD_POULTRY":
summary.push("Day-old Poultry");
break;
case "ADULT_BIRDS":
summary.push("Live Adult Birds");
break;
case "OTHER_LIVES":
default:
summary.push("Live Animals");
break;
}
}
if (this.extraServices.perishable) {
summary.push("Perishable");
}
if (this.extraServices.crematedRemains) {
summary.push("Cremated Remains");
}
if (this.extraServices.certifiedMail) {
summary.push("Certified Mail");
} else if (this.extraServices.registeredMail) {
summary.push("Registered Mail");
summary.push("Registered for $" + (this.extraServices.registeredMailAmount * 1.0).toFixed(2));
} else if (this.extraServices.signature == "SIGNATURE") {
summary.push("Signature Required");
}
if (this.extraServices.signature == "ADULT_SIGNATURE") {
summary.push("Adult Signature Required");
}
if (this.extraServices.signature == "SIGNATURE_RESTRICTED") {
summary.push("Restricted Delivery");
}
if (this.extraServices.returnReceiptElectronic) {
summary.push("Return Receipt Electronic");
}
if (this.extraServices.returnReceipt) {
summary.push("Return Receipt");
}
if (this.extraServices.insurance) {
summary.push("Insured for $" + (this.extraServices.insurance * 1.0).toFixed(2));
}
if (this.extraServices.cod) {
summary.push("Collect on Delivery: $" + (this.extraServices.codAmount * 1.0).toFixed(2));
}
if (this.extraServices.dryIce && this.extraServices.dryIceWeight > 0) {
summary.push("Dry Ice: " + (this.extraServices.dryIceWeight * 1).toFixed(0) + " oz");
}
if (this.extraServices.carrier_billing_account?.type) {
if (this.extraServices.carrier_billing_account.type != "") {
var accountNumber = this.extraServices.carrier_billing_account.account_number;
var accountNumberCensored = accountNumber.substring(accountNumber.length - 4).padStart(accountNumber.length, "X");
var carrierName = this.extraServices.carrier_billing_account.carrier;
switch (this.extraServices.carrier_billing_account.type) {
case "SENDER":
summary.push(`Bill to sender ${carrierName} account #${accountNumberCensored}`);
break;
case "THIRD_PARTY":
summary.push(`Bill to third party ${carrierName} account #${accountNumberCensored}`);
break;
case "RECEIVER":
summary.push(`Bill to receiver ${carrierName} account #${accountNumberCensored}`);
break;
case "COLLECT":
if (accountNumber.length > 0) {
summary.push(`Bill collect ${carrierName} account #${accountNumberCensored}`);
} else {
summary.push(`Bill collect`);
}
break;
}
}
}
return summary.join("\n");
}
async needsHAZMATPrompt() {
try {
let packagingInfo = await getPackagingByID(this.packaging.internalid);
if (packagingInfo.hazmat) {
return true;
}
if (this.packaging.weight > 10) {
return true;
}
if (packagingInfo.l >= -1 && Math.max(this.packaging.length, this.packaging.width, this.packaging.height) > 0.5) {
return true;
}
switch (packagingInfo.type) {
case "Letter":
case "Card":
return false;
}
return true;
} catch (ex) {
return true;
}
}
get isPrepaid() {
return this.prepaid == true;
}
setCustomsInfo(contents, contentsExplanation, signature, restriction, restrictionComments, nonDelivery) {
let items = this.customs.items; // Save this and copy it back in so we don't overwrite it
this.customs = {
contents: contents,
contentsExplanation: contentsExplanation, // needed if contents is "other"
signature: signature,
restriction: restriction,
restrictionComments: restrictionComments, // needed if restriction is "other"
nonDelivery: nonDelivery, // "return" or "abandon",
items: items
};
}
/**
* Get the customs items, ignoring any that are blank.
* @returns {Array}
*/
getCustomsItems() {
let items = [];
for (let i = 0; i < this.customs.items.length; i++) {
let item = this.customs.items[i];
if (item.description == "" && (item.qty == "" || item.qty == 0) && (item.weight == "" || item.weight == 0) && (item.value == "" || item.value == 0)) {
continue;
}
items.push(item);
}
return items;
}
setCustomsItems(items) {
this.customs.items = items;
}
getCustoms() {
this.customs.items = this.getCustomsItems();
return this.customs;
}
/**
* Attempt to automatically fix simple issues like overweight letters.
* @returns {undefined}
*/
async fixIssues() {
if (this.packaging.type == "Letter" && this.packaging.weightOz > 3.5) {
if (this.packaging.nonmachinable) {
return; // Has to be a parcel, can't fix without dimensions
}
this.packaging.type = "Flat";
this.packaging.internalid = 104;
}
}
/**
* Do some basic checks to see if this package is even remotely shippable
* @param {boolean} kioskMode If true, returned strings are suitable for display in kiosk mode.
* @returns {boolean|string} true if okay, human-readable error message and instructions if not okay
*/
async isValid(kioskMode = false) {
// Removed from docs for brevity. Just a bunch of if statements to catch problems.
}
/**
* Set package characteristics
* @param {string} type "Parcel", "Letter", "Flat", "Card"
* @param {type} service
* @param {type} carrier
* @param {type} length
* @param {type} width
* @param {type} height
* @param {type} weightOz
* @returns {undefined}
*/
setPackaging(type, service, carrier, length, width, height, weightOz, nonmachinable) {
if (typeof nonmachinable == "undefined") {
nonmachinable = false;
}
if (type == "Card") {
// Postcards
weightOz = 1;
this.packaging.internalid = 105;
} else if (type == "Flat") {
this.packaging.internalid = 104;
} else if (type == "Letter") {
this.packaging.internalid = 102;
if (nonmachinable) {
this.packaging.internalid = 103;
}
}
this.packaging.type = type;
this.packaging.service = service;
this.packaging.carrier = carrier;
this.packaging.weightOz = weightOz;
this.packaging.nonmachinable = nonmachinable;
// Enforce Length > Width > Height
let size = [length, width, height];
size.sort(function (a, b) {
return b - a;
});
this.packaging.length = size[0];
this.packaging.width = size[1];
this.packaging.height = size[2];
}
/**
* Set an extra service
* @param {string} id Service ID
* @param {boolean} enabled Turn it on or off
* @param {string} value Service value, if needed (some are not just a boolean)
* @returns {undefined}
*/
setExtraService(id, enabled, value) {
if (typeof value != "undefined" && enabled) {
this.extraServices[id] = value;
} else {
this.extraServices[id] = enabled == true;
}
}
getExtraServices() {
return this.extraServices;
}
/**
* Set to "MEDIA_MAIL", "LIBRARY_MAIL", or false
* @param {type} rate
* @returns {undefined}
*/
set specialRate(rate) {
if (rate == "MEDIA") {
rate = "MEDIA_MAIL";
} else if (rate == "LIBRARY") {
rate = "LIBRARY_MAIL";
}
if (rate != "MEDIA_MAIL" && rate != "LIBRARY_MAIL") {
rate = false;
}
this.specialRateEligibility = rate;
}
get specialRate() {
return this.specialRateEligibility;
}
/**
* Save an address to this package.
* @param {string} type "to", "return", or "origin"
* @param {string} name
* @param {string} company
* @param {string} street1
* @param {string} street2
* @param {string} city
* @param {string} state
* @param {string} zip
* @param {string} country ISO 2-char country code
* @param {string} phone
* @param {string} email
* @returns {undefined}
*/
setAddress(type, name, company, street1, street2, city, state, zip, country, phone, email) {
let address = Address.fromObject({
name: name,
company: company,
street1: street1,
street2: street2,
city: city,
state: state,
zip: zip,
country: country,
phone: phone,
email: email
});
switch (type) {
case "to":
this.toAddress = address;
break;
case "return":
this.returnAddress = address;
break;
case "origin":
this.originAddress = address;
break;
}
}
/**
* Set an address using an object that matches the internal form (see setAddress())
* @param {string} type
* @param {object} data
* @returns {undefined}
*/
setAddressWhole(type, address) {
switch (type) {
case "to":
this.toAddress = Address.fromObject(address);
break;
case "return":
this.returnAddress = Address.fromObject(address);
break;
case "origin":
this.originAddress = Address.fromObject(address);
break;
}
}
get tracking() {
return this.trackingNumber;
}
set tracking(n) {
this.trackingNumber = n;
}
/**
* Get the "from" address that will be shown,
* using the return address or origin address as needed
* @returns {address}
*/
getReturnAddress() {
var a = null;
if (typeof this.returnAddress == "object") {
a = Address.fromObject(this.returnAddress);
} else {
a = Address.fromObject(this.originAddress);
}
if (a.country == "") {
a.country = defaultCountryCode();
}
return a;
}
getToAddress() {
var a = Address.fromObject(this.toAddress);
if (a.country == "") {
a.country = defaultCountryCode();
}
return a;
}
getFromAddress() {
var a = null;
if (typeof this.originAddress == "object") {
a = Address.fromObject(this.originAddress);
} else {
a = Address.fromObject(this.returnAddress);
}
if (a.country == "") {
a.country = defaultCountryCode();
}
return a;
}
}
```