@sgpfldev/ac
v1.2.113
Published
SGPFL Service Wrappers Bundle
Downloads
23
Readme
SGPFL AC Services
This package is a bundle of all of SGPFL's packages that are API connecters or wrappers to API connececters with added functionality.
These packages include:
This README is a WIP. Please contact me with any questions not explained here!
Installation
npm i @sgpfldev/ac --save
Finale
Finale Inventory API(docs) bindings and abstractions.
Authentication
This module exports a Finale object which is configured by passing the following credntials object:
import 'dotenv/config'
import Finale from '@sgpfldev/ac/dist/finale'
const { FINALE_USER, FINALE_PASS, FINALE_COMPANY } = process.env
const finale = new Finale({
company: FINALE_COMPANY, //Required - i.e. stovegrillpartsforless
credentials: {
username: FINALE_USER, //Required
password: FINALE_PASS //Required
}
})
Call Limits
Finale restricts users to no more than 120 updates (POST request) per minute, 120 single item fetches (GET specific entity) per minute, and no more than 300 collection requests or reports per hour.
Data Caching
To adhere to the API limits this package uses a configurable caching system for data that is unlikely to update before its expiration for increased performance and scalability. The cache lifespan can be configured during initialization of the Finale module by setting the cacheConfig property like so
const finale = new Finale({
company: FINALE_COMPANY, //Required - i.e. stovegrillpartsforless
credentials: {
username: FINALE_USER, //Required
password: FINALE_PASS //Required
},
cacheConfig: {
allProducts: {
lifespan: 6
},
inventory: {
lifespan: 60
},
BoM: {
lifespan: 6
},
productLookups: {
lifespan: 6
},
suppliers: {
lifespan: 6
},
salesHistory30: {
lifespan: 600
},
salesHistory90: {
lifespan: 600
},
salesHistory365: {
lifespan: 600
}
}
})
Full Example
import 'dotenv/config'
import Finale from '@sgpfldev/ac/dist/finale'
(async () => {
const { FINALE_USER, FINALE_PASS, FINALE_COMPANY } = process.env
const finale = new Finale({
company: FINALE_COMPANY, //Required - the company to use i.e. stovegrillpartsforless
credentials: {
username: FINALE_USER, //Required - the user login for the finale company account
password: FINALE_PASS //Required - the user password for the finale company account
}
})
let productRes = await finale.getProduct("00-0005-0003")
console.log(productRes)
})()
Methods
async getProduct(productId: string)
Gets a single product in real-time. Parses incoming data to be easier to work with including custom user fields, price list, and suppliers.
const product: FinaleProduct = await finale.getProduct(testProductId)
const { productId, prices, suppliers, userFields } = product
console.log(`ID/SKU`, productId)
console.log(`Prices`, prices)
console.log(`Suppliers`, suppliers)
console.log('Custom Fields', userFields)
async updateProduct(productId: string, updates: object)
Updates a single product in real-time. Returns result. Note: When updating custom user fields all incoming fields are merged with existing fields to avoid overwriting important integration information.
See finale product fields for a full list of fields that can be updated.
const updatedUserFields = [
{ attrName: "user_10001", attrValue: "JaNiToR's TeSt mOp" },
];
const updatedPriceList = [
{
productPriceTypeId: '##user3', // Sale Price
price: 9.99,
currencyUomId: 'USD'
},
{
productPriceTypeId: 'LIST_PRICE', // Retail Price
price: 19.99,
currencyUomId: 'USD'
}
];
const updates = {
internalName: "Janitor's TEST-MOP",
userFieldDataList: updatedUserFields,
priceList: updatedPriceList
};
const result = await finale.updateProduct("TEST-MOP", updates);
console.log("Updated product:", result);
async getSalesHistory(productId: string, timeframes?: number[])
Gets 30, 90, and 365 day sales history for a productId. the timeframes param can be used to specify custom timeframes to run the reports for. Favors use of any cached data first, and falls back to refreshing the cache at the cost of a slower response time. This is based on Finale ID sales, NOT Shopify sales. See Shopify section for shopify varaint sales data.
const testProductId = "00-0005-0003"
const salesHistory = await finale.getSalesHistory(testProductId)
console.log(`getSalesHistory("${testProductId}")`, salesHistory)
const salesHistoryCustom = await finale.getSalesHistory(testProductId, [7,14,21,28])
console.log(`getSalesHistory("${testProductId}", [7,14,21,28])`, salesHistoryCustom)
async getProductBoM(productId: string)
Gets product bill of materials. Includes things such as total cost, total qty, total retail price, and more. Favors use of any cached data first, and falls back to refreshing the cache at the cost of a slower response time.
This example below use lodash to calculate totals
const productBoM = await finale.getProductBoM(testProductId)
console.log(`getProductBoM("${testProductId}")`, productBoM.length, 'components found!')
console.log(`getProductBoM("${testProductId}")`, _.sumBy(productBoM, o => o.componentQuantity), 'items in total!')
console.log(`getProductBoM("${testProductId}")`, _.sumBy(productBoM, o => o.componentRetailPrice), '= retail price of all components!')
console.log(`getProductBoM("${testProductId}")`, _.sumBy(productBoM, o => o.componentSupplierPrice || o.componentSupplier2Price || o.componentSupplier3Price), '= cost of all components!')
async getInventoryItem(productId: string)
Gets an inventory object from Finale. Favors use of any cached data first, and falls back to refreshing the cache at the cost of a slower response time.
const testProductId = "00-0005-0003"
const inventoryItem = await finale.getInventoryItem(testProductId)
console.log(`getInventoryForItem("${testProductId}")`, inventoryItem)
async getProductLookups(productId?: string)
Gets all product lookups, can be filtered by product ID. Favors use of any cached data first, and falls back to refreshing the cache at the cost of a slower response time.
//All Lookups
const allLookups = await finale.getProductLookups()
console.log('getProductLookups()', allLookups.length, 'lookups loaded!')
//All Lookups for a given ID (Product ID or SKU).
const allLookupsForID = await finale.getProductLookups("00-0005-0003")
console.log('getProductLookups("00-0005-0003")', allLookupsForID.length, 'lookups found!')
console.log('getProductLookups("00-0005-0003")[0]', allLookupsForID[0])
async getReport(reportUrl: string)
Downloads a finale report given the report URL. Report URL's can be base64 decoded, and updated, to change report filters on the fly for things such as dynamic time frames, or reducing report sizes by constraining results to the scope of your code.
let startTimestamp = Date.now()
//get report as JSON. Returns an array, get first index.
const report: FinaleReport = (await finale.getReport(reportUrl))[0]
//run the response through a parser to make your data usable client-side
const parsedReportData = await ParseInventoryReport(report)
console.log(LOG_PREFIX, `Successfully loaded ${parsedReportData.length} report items in ${Date.now() - startTimestamp}ms!`)
Firebase
firebase abstraction layer for auth and database reading/writing.
Authentication
This module exports an initialized firebase app which is configured by setting the following credntials in your .env file in the root of your project:
[email protected]
FIREBASE_PASS=abc123
FIREBASE_KEY=key
FIREBASE_AUTH_DOMAIN=appname.firebaseapp.com
FIREBASE_PROJECT_ID=project_id
FIREBASE_STORAGE_BUCKET=appname.appspot.com
FIREBASE_MSG_SENDER_ID=1234567890
FIREBASE_APP_ID=appid
FIREBASE_MEASUREMENT_ID=measurementid
Methods
async signIntoFirebase()
Signs into firebase using the account set in .env. Resolves to "true" if the sign in was successful. IMPORTANT: This needs to be called before your database requests. This package does not automate authentication at this time.
const isSignedIn = await signIntoFirebase()
console.log(`Signed In: ${isSignedIn}`)
async getFirestoreDocument(collectionId: string, documentId: string)
Gets a specified document by ID from the specified collection by ID.
const COLLECTION_ID = "bigDataCache"
const DOCUMENT_ID = "testCache"
/** Load doc from DB */
const shopifySalesCache = await getFirestoreDocument(COLLECTION_ID, DOCUMENT_ID);
if (shopifySalesCache) {
console.log("Loaded testCache data succesfully! Found", shopifySalesCache)
} else {
throw (new Error("Unable to load testCache document"))
}
async setFirestoreDocument(collectionId: string, documentId: string, data: object | JSON)
Sets the data of a specified document by ID from the specified collection by ID.
const COLLECTION_ID = "bigDataCache"
const DOCUMENT_ID = "testCache"
/** Update Document */
const updateData = { "AMP123": [10, 40, 201], "AMP456": [100, 400, 2001] }
const didUpdateWork = await setFirestoreDocument(COLLECTION_ID, DOCUMENT_ID, updateData);
if (didUpdateWork)
console.log("Updated testCache data succesfully!")
else
throw (new Error("Unable to update testCache document"))
Shopify
shopify-api-node extension with added abstractions. Inherits all of the methods of shopify-api-node. Uses Firestore if configured to cache salesHistory in a lite format to increase shopify sales history read speeds for reports and more.
Authentication
This module exports an initialized shopify connecter object which is configured by setting the following credntials in your .env file in the root of your project:
SHOPIFY_SHOP_NAME=store-name.myshopify.com
SHOPIFY_ACCESS_TOKEN=shpat_e123abc1234abc1234abc123
Methods
This modules inherits the shopify-api-node package and gets access to all the same functions it has for interfacing with the Shopify API. See Here for a full list of methods this package inherits.
async loadResource(options)
This is the main functionality of the shopify module. This persistantly loads in all of a given resource type from shopify with configuration for including metafields and other linkable resource types.
//Load Resource Options
const options: {
resourceType: string //product, custom_collection, page, etc...
params?: object //Used to filter the load resource request using Shopify filters
includeMetafields?: boolean //for use on any resource
includeInventoryItems?: boolean //for use on product resource
includeCollects?: boolean //for use on custom and smart collection resources
onResourceLoad?: Function //async, called once per loaded resource
onPageLoad?: Function //async, called once per loaded page of resources (250)
limit?: number //max resources PER REQUEST to load in. Does NOT change all resources getting loaded.
logging?: boolean
}
Load all active products
/** Products */
const productsCount = await shopify.product.count({ status: "active" })
console.log(`Found ${productsCount} products!`)
const products: IProduct[] = await shopify.loadResource({
resourceType: "product",
params: { status: "active" },
includeMetafields: false,//true,
includeInventoryItems: false,
logging: false,
onPageLoad: async (loadedResources) => {
//On every page load (single request for 250 products)...
//manually load metafields, same as setting "includeMetafields" to true
//await shopify.loadResourceMetafields("product", ...loadedResources)
//manually load inventory items, same as setting "includeInventoryItems" to true
//await shopify.loadInventoryItems(loadedResources)
}
})
console.log(`Loaded ${products.length} / ${productsCount} products!`)
Pages
/** Pages */
const pageCount = await shopify.page.count()
console.log(`Found ${pageCount} pages!`)
const pages: IPage[] = await shopify.loadResource({ resourceType: "page" })
console.log(`Loaded ${pages.length} / ${pageCount} pages!`)
Custom Collections
/** Custom Collections */
const customCollectionCount = await shopify.customCollection.count()
console.log(`Found ${customCollectionCount} custom collections!`)
const customCollections: ICustomCollection[] = await shopify.loadResource({
resourceType: "customCollection",
includeMetafields: false,
includeCollects: false
})
console.log(`Found ${customCollections.filter(cc => cc.sort_order === 'best-selling').length} manually sorted custom collections!`)
console.log(`Loaded ${customCollections.length} / ${customCollectionCount} custom collections!`)
async getSalesHistory(sku: string)
Gets sales data based on shopify sku sales history using a google sheet cache.
/** Sales History */
const sku = "BAC409-AMP"
const testSalesHistory = await shopify.getSalesHistory(sku)
console.log("Found sales history for", sku, testSalesHistory)
GSHEET_SHOPIFY_SALES_HISTORY_ID="17aehghh31licmJoZ61_5bAH7Vxsfghjndgfhmndg"
GSHEET_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMZ/YtQ==\n-----END PRIVATE KEY-----\n"
GSHEET_PRIVATE_KEY_ID="58c19d12345678901234567890"
GSHEET_PROJECT_ID="ac"
GSHEET_CLIENT_EMAIL="[email protected]"
GSHEET_CLIENT_ID="12345678901234567890"
GSHEET_CLIENT_CERT_URL="https://www.googleapis.com/robot/v1/metadata/x509/ac.gserviceaccount.com"
Amazon SP
The amazon seller partner module allows us to read in listings from seller central.
Authentication
We will need to set up the following crednetials in our ENV file. See Amazon's authentication docs for how to get these keys and secrets.
const credentials = {
SELLING_PARTNER_APP_CLIENT_ID: process.env.SELLING_PARTNER_APP_CLIENT_ID,
SELLING_PARTNER_APP_CLIENT_SECRET: process.env.SELLING_PARTNER_APP_CLIENT_SECRET,
AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID,
AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY,
AWS_SELLING_PARTNER_ROLE: process.env.AWS_SELLING_PARTNER_ROLE
}
const optionsSPFL: sellerPartnerOptions = {
logging: false,
credentials,
token: process.env.SELLING_PARTNER_REFRESH_TOKEN_SPFL,
title: 'SPFL',
//Optional
finaleInstance: finale //If you pass in a Finale instance, the Finale Product ID is automatically added to the listing object when loaded.
}
const amazonSPFL: SellerPartnerAPIClient = new SellerPartnerAPIClient(optionsSPFL)
getAllListings()
Gets an all listings report using amazon-sp-api.
const amazonSPFL: SellerPartnerAPIClient = new SellerPartnerAPIClient(optionsSPFL)
const allSPFLListings = await amazonSPFL.getAllListings()
console.log(`Loaded ${allSPFLListings.length} Amazon SPFL Listings`, allSPFLListings[0])
Listing Example
{
"item-name": "PB Upper Cooking Rack for Pro Series 1100, PB1100PS1-003",
"item-description": "Replacement Porcelain-Coated Steel Upper Cooking Rack for Pit Boss 1100 Pro Series.",
"listing-id": "0813ZPZ5UQ0",
"seller-sku": "1100PS1-1",
"price": "49.95",
"quantity": "13",
"open-date": "2022-08-12 17:37:55 PDT",
"image-url": "",
"item-is-marketplace": "y",
"product-id-type": "3",
"zshop-shipping-fee": "",
"item-note": "",
"item-condition": "11",
"zshop-category1": "",
"zshop-browse-path": "",
"zshop-storefront-feature": "",
"asin1": "B0B9J12JTD",
"asin2": "",
"asin3": "",
"will-ship-internationally": "",
"expedited-shipping": "",
"zshop-boldface": "",
"product-id": "746622411137",
"bid-for-featured-placement": "",
"add-delete": "",
"pending-quantity": "0",
"fulfillment-channel": "DEFAULT",
"merchant-shipping-group": "USE THIS ONE FOR LARGE ITEMS",
"status": "Active"
}
Tasks
Tasks are special functions that do not fit into the above packages, but are still shared resources across multiple repos.
cacheShopifySalesHistory()
Gets all sales history from Shopify and saves it to a googlesheet for later use. Requires GSHEET_SERVICE_ACC_BASE64 to be set in .env.
/** Cache Sales History */
await cacheShopifySalesHistory()
GSHEET_SHOPIFY_SALES_HISTORY_ID="17aehghh31licmJoZ61_5bAH7Vxsfghjndgfhmndg"
GSHEET_SERVICE_ACC_BASE64="ewogICAgInR5cGUiOiAic2VydmljZV9hY2NvdW50IiwKICAgICJwcm9qZWN0X2lkIjogImFjLWphbml0b3IiLAogICAgInByaXZhdGVfa2V5X2lkIjogIjU4YzE5ZDUwNzc0ZDA2N2I2NTE0MTVmNDY4YTU0MmU1M2MzMWZkY2QiLAogICAgInByaXZhdGVfa2V5IjogIi0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuTUlJRXZ3SUJBREFOQmdrcWhraUc5dzBCQVFFRkFBU0NCS2t3Z2dTbEFnRUFBb0lCQVFEeTIxUHRvRktRWnpueVxucXVGZlhoOTc5ckZUOEsrd0JJbHNkYlVaVUY0SnVmL0pOUHZpbnJzeXVUUG9jNHhLWXpsRFZXZTRCY25MdnMyc1xuTnRRZ0dVOVFpN0d6blI1dm1ubHUvVHFwYWpTR01uN2VFUi91NWZmRFF1VEp1MUZ0K1VKc0hZblhWT0doQ2pvNVxubmYxTU42blhOUzV="
.env File
The following is an exhaustive list of all supported env variables.
FIREBASE_USER="[email protected]"
FIREBASE_PASS="12345"
FIREBASE_KEY="ABC123"
FIREBASE_AUTH_DOMAIN="app.firebaseapp.com"
FIREBASE_PROJECT_ID="my-project"
FIREBASE_STORAGE_BUCKET="app.appspot.com"
FIREBASE_MSG_SENDER_ID="123456789"
FIREBASE_APP_ID="1:23456789:web:12345678"
FIREBASE_MEASUREMENT_ID="G-12345678"
FINALE_COMPANY="mycompanyname"
FINALE_USER="user"
FINALE_PASS="pass"
FINALE_INVENTORY_REPORT_URL="https://app.finaleinventory.com/mycompanyname/doc/report/pivotTableStream/1234567/Report.json?format=json&data=product"
FINALE_ALL_PRODUCTS_REPORT_URL=""https://app.finaleinventory.com/mycompanyname/doc/report/pivotTableStream/1234567/Report.json?format=json&data=product"
FINALE_SALES_HISTORY_REPORT_URL="https://app.finaleinventory.com/mycompanyname/doc/report/pivotTableStream/1234567/Report.json?format=json&data=product"
SHOPIFY_SHOP_NAME="shop.myshopify.com"
SHOPIFY_ACCESS_TOKEN="shpat_abc1234567890"
SELLING_PARTNER_REFRESH_TOKEN_SPFL="Atzr|abc123456"
SELLING_PARTNER_REFRESH_TOKEN_GPFL="Atzr|abc123456"
SELLING_PARTNER_APP_CLIENT_SECRET="abc1234567"
AWS_ACCESS_KEY_ID='abc12345676'
AWS_SECRET_ACCESS_KEY='abc1234567'
AWS_SELLING_PARTNER_ROLE='arn:aws:iam::12345678:role/my-role'
GSHEET_SHOPIFY_SALES_HISTORY_ID="17aehghh31licmJoZ61_5bAH7Vxsfghjndgfhmndg"
GSHEET_SERVICE_ACC_BASE64="ewogICAgInR5cGUiOiAic2VydmljZV9hY2NvdW50IiwKICAgICJwcm9qZWN0X2lkIjogImFjLWphbml0b3IiLAogICAgInByaXZhdGVfa2V5X2lkIjogIjU4YzE5ZDUwNzc0ZDA2N2I2NTE0MTVmNDY4YTU0MmU1M2MzMWZkY2QiLAogICAgInByaXZhdGVfa2V5IjogIi0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuTUlJRXZ3SUJBREFOQmdrcWhraUc5dzBCQVFFRkFBU0NCS2t3Z2dTbEFnRUFBb0lCQVFEeTIxUHRvRktRWnpueVxucXVGZlhoOTc5ckZUOEsrd0JJbHNkYlVaVUY0SnVmL0pOUHZpbnJzeXVUUG9jNHhLWXpsRFZXZTRCY25MdnMyc1xuTnRRZ0dVOVFpN0d6blI1dm1ubHUvVHFwYWpTR01uN2VFUi91NWZmRFF1VEp1MUZ0K1VKc0hZblhWT0doQ2pvNVxubmYxTU42blhOUzV="