@cityssm/dynamics-gp
Version:
Read only inquiries into Microsoft Dynamics GP using a SQL Server connection.
283 lines (231 loc) • 8.57 kB
text/typescript
import { NodeCache } from '@cacheable/node-cache'
import { minutesToSeconds, secondsToMillis } from '@cityssm/to-millis'
import type { config as MSSQLConfig } from 'mssql'
import _extendGpInvoice from './diamond/extendGpInvoice.js'
import _getCashReceiptByDocumentNumber from './diamond/getCashReceiptByDocumentNumber.js'
import type {
DiamondCashReceipt,
DiamondExtendedGPInvoice
} from './diamond/types.js'
import _getAccountByAccountIndex from './gp/getAccountByAccountIndex.js'
import _getCustomerByCustomerNumber from './gp/getCustomerByCustomerNumber.js'
import _getInvoiceByInvoiceNumber from './gp/getInvoiceByInvoiceNumber.js'
import _getInvoiceDocumentTypes from './gp/getInvoiceDocumentTypes.js'
import _getItemByItemNumber from './gp/getItemByItemNumber.js'
import _getItemsByLocationCodes from './gp/getItemsByLocationCodes.js'
import _getVendors, { type GetVendorsFilters } from './gp/getVendors.js'
import type {
GPAccount,
GPCustomer,
GPInvoice,
GPInvoiceDocumentType,
GPItemWithQuantities,
GPItemWithQuantity,
GPVendor
} from './gp/types.js'
export interface DynamicsGPOptions {
/**
* Time in seconds until cached lookup records expire
*/
cacheTTL: number
/**
* Time in seconds until cached document records expire
*/
documentCacheTTL: number
}
const defaultOptions: DynamicsGPOptions = {
cacheTTL: minutesToSeconds(3),
documentCacheTTL: minutesToSeconds(1)
}
function getInvoiceCacheKey(
invoiceNumber: string,
invoiceDocumentTypeOrAbbreviationOrName?: number | string
): string {
return `${(
invoiceDocumentTypeOrAbbreviationOrName ?? ''
).toString()}::${invoiceNumber}`
}
export class DynamicsGP {
readonly #mssqlConfig: MSSQLConfig
readonly #options: DynamicsGPOptions
readonly #accountCache: NodeCache<GPAccount | undefined>
readonly #customerCache: NodeCache<GPCustomer | undefined>
readonly #invoiceCache: NodeCache<GPInvoice | undefined>
readonly #itemCache: NodeCache<GPItemWithQuantities | undefined>
readonly #vendorCache: NodeCache<GPVendor | undefined>
#invoiceDocumentTypesCache: GPInvoiceDocumentType[] = []
#invoiceDocumentTypesCacheExpiryMillis = 0
readonly #diamondCashReceiptCache: NodeCache<DiamondCashReceipt | undefined>
readonly #diamondInvoiceCache: NodeCache<DiamondExtendedGPInvoice | undefined>
constructor(mssqlConfig: MSSQLConfig, options?: Partial<DynamicsGPOptions>) {
this.#mssqlConfig = mssqlConfig
this.#options = { ...defaultOptions, ...options }
this.#accountCache = new NodeCache({ stdTTL: this.#options.cacheTTL })
this.#customerCache = new NodeCache({ stdTTL: this.#options.cacheTTL })
this.#itemCache = new NodeCache({ stdTTL: this.#options.cacheTTL })
this.#vendorCache = new NodeCache({ stdTTL: this.#options.cacheTTL })
this.#invoiceCache = new NodeCache({
stdTTL: this.#options.documentCacheTTL
})
this.#diamondCashReceiptCache = new NodeCache({
stdTTL: this.#options.documentCacheTTL
})
this.#diamondInvoiceCache = new NodeCache({
stdTTL: this.#options.documentCacheTTL
})
}
clearCaches(): void {
this.#accountCache.flushAll()
this.#customerCache.flushAll()
this.#invoiceCache.flushAll()
this.#itemCache.flushAll()
this.#vendorCache.flushAll()
this.#vendorCache.flushAll()
this.#invoiceDocumentTypesCache = []
this.#invoiceDocumentTypesCacheExpiryMillis = 0
this.#diamondCashReceiptCache.flushAll()
this.#diamondInvoiceCache.flushAll()
}
async getAccountByAccountIndex(
accountIndex: number | string
): Promise<GPAccount | undefined> {
let account = this.#accountCache.get(accountIndex) ?? undefined
if (account === undefined) {
account = await _getAccountByAccountIndex(this.#mssqlConfig, accountIndex)
this.#accountCache.set(accountIndex, account)
}
return account
}
async getCustomerByCustomerNumber(
customerNumber: string
): Promise<GPCustomer | undefined> {
let customer = this.#customerCache.get(customerNumber) ?? undefined
if (customer === undefined) {
customer = await _getCustomerByCustomerNumber(
this.#mssqlConfig,
customerNumber
)
this.#customerCache.set(customerNumber, customer)
}
return customer
}
async #getInvoiceByInvoiceNumber(
invoiceNumber: string,
invoiceDocumentTypeOrAbbreviationOrName?: number | string,
skipCache = false
): Promise<GPInvoice | undefined> {
const cacheKey = getInvoiceCacheKey(
invoiceNumber,
invoiceDocumentTypeOrAbbreviationOrName
)
let invoice = this.#invoiceCache.get(cacheKey) ?? undefined
if (invoice === undefined || skipCache) {
invoice = await _getInvoiceByInvoiceNumber(
this.#mssqlConfig,
invoiceNumber,
invoiceDocumentTypeOrAbbreviationOrName
)
if (!skipCache) {
this.#invoiceCache.set(cacheKey, invoice)
}
}
return invoice
}
async getInvoiceByInvoiceNumber(
invoiceNumber: string,
invoiceDocumentTypeOrAbbreviationOrName?: number | string
): Promise<GPInvoice | undefined> {
return await this.#getInvoiceByInvoiceNumber(
invoiceNumber,
invoiceDocumentTypeOrAbbreviationOrName,
false
)
}
async getInvoiceDocumentTypes(): Promise<GPInvoiceDocumentType[]> {
if (this.#invoiceDocumentTypesCacheExpiryMillis < Date.now()) {
this.#invoiceDocumentTypesCache = await _getInvoiceDocumentTypes(
this.#mssqlConfig
)
this.#invoiceDocumentTypesCacheExpiryMillis =
Date.now() + secondsToMillis(this.#options.cacheTTL)
}
return this.#invoiceDocumentTypesCache
}
async getItemByItemNumber(
itemNumber: string
): Promise<GPItemWithQuantities | undefined> {
let item = this.#itemCache.get(itemNumber) ?? undefined
if (item === undefined) {
item = await _getItemByItemNumber(this.#mssqlConfig, itemNumber)
this.#itemCache.set(itemNumber, item)
}
return item
}
async getItemsByLocationCodes(
locationCodes: string[] = ['']
): Promise<GPItemWithQuantity[]> {
return await _getItemsByLocationCodes(this.#mssqlConfig, locationCodes)
}
async getVendorByVendorId(vendorId: string): Promise<GPVendor | undefined> {
let vendor = this.#vendorCache.get(vendorId) ?? undefined
if (vendor === undefined) {
const vendors = await this.getVendors({ vendorId })
vendor = vendors.length > 0 ? vendors[0] : undefined
this.#vendorCache.set(vendorId, vendor)
}
return vendor
}
async getVendors(
vendorFilters?: Partial<GetVendorsFilters>
): Promise<GPVendor[]> {
return await _getVendors(this.#mssqlConfig, vendorFilters ?? {})
}
async getDiamondCashReceiptByDocumentNumber(
documentNumber: number | string
): Promise<DiamondCashReceipt | undefined> {
let receipt = this.#diamondCashReceiptCache.get(documentNumber)
if (receipt === undefined) {
receipt = await _getCashReceiptByDocumentNumber(
this.#mssqlConfig,
documentNumber
)
this.#diamondCashReceiptCache.set(documentNumber, receipt)
}
return receipt
}
async getDiamondExtendedInvoiceByInvoiceNumber(
invoiceNumber: string,
invoiceDocumentTypeOrAbbreviationOrName?: number | string
): Promise<DiamondExtendedGPInvoice | undefined> {
const cacheKey = getInvoiceCacheKey(
invoiceNumber,
invoiceDocumentTypeOrAbbreviationOrName
)
let diamondInvoice = this.#diamondInvoiceCache.get(cacheKey) ?? undefined
if (diamondInvoice === undefined) {
const gpInvoice = await this.#getInvoiceByInvoiceNumber(
invoiceNumber,
invoiceDocumentTypeOrAbbreviationOrName
)
if (gpInvoice !== undefined) {
diamondInvoice = await _extendGpInvoice(this.#mssqlConfig, gpInvoice)
this.#diamondInvoiceCache.set(cacheKey, diamondInvoice)
}
}
return diamondInvoice
}
}
export type {
GPAccount,
GPCustomer,
GPInvoice,
GPInvoiceDocumentType,
GPItemWithQuantities,
GPItemWithQuantity,
GPVendor
} from './gp/types.js'
export type {
DiamondCashReceipt,
DiamondExtendedGPInvoice
} from './diamond/types.js'
export type { GetVendorsFilters } from './gp/getVendors.js'