@memberjunction/actions-bizapps-accounting
Version:
Accounting system integration actions for MemberJunction
174 lines • 7.23 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { BaseAction } from '@memberjunction/actions';
import { RegisterClass, UUIDsEqual } from '@memberjunction/global';
import { Metadata, RunView } from '@memberjunction/core';
/**
* Base class for all accounting-related actions.
* Provides common functionality and patterns for interacting with accounting systems.
*/
let BaseAccountingAction = class BaseAccountingAction extends BaseAction {
constructor() {
super(...arguments);
/**
* Cached company integration for the current execution
*/
this._companyIntegration = null;
}
/**
* Helper to get a parameter value from the params array
*/
getParamValue(params, name) {
const param = params.find(p => p.Name === name);
return param?.Value;
}
/**
* Common accounting parameters that many actions will need
*/
getCommonAccountingParams() {
return [
{
Name: 'CompanyID',
Type: 'Input',
Value: null
},
{
Name: 'FiscalYear',
Type: 'Input',
Value: null
},
{
Name: 'AccountingPeriod',
Type: 'Input',
Value: null
}
];
}
/**
* Gets the company integration record for the specified company and accounting system
*/
async getCompanyIntegration(companyId, contextUser) {
// Check cache first
if (this._companyIntegration && UUIDsEqual(this._companyIntegration.CompanyID, companyId)) {
return this._companyIntegration;
}
const rv = new RunView();
const result = await rv.RunView({
EntityName: 'MJ: Company Integrations',
ExtraFilter: `CompanyID = '${companyId}' AND Integration.Name = '${this.integrationName}'`,
ResultType: 'entity_object'
}, contextUser);
if (!result.Success) {
throw new Error(`Failed to retrieve company integration: ${result.ErrorMessage}`);
}
if (!result.Results || result.Results.length === 0) {
throw new Error(`No ${this.integrationName} integration found for company ${companyId}. Please configure the integration first.`);
}
this._companyIntegration = result.Results[0];
return this._companyIntegration;
}
/**
* Gets credentials from environment variables
* Format: BIZAPPS_{PROVIDER}_{COMPANY_ID}_{CREDENTIAL_TYPE}
* Example: BIZAPPS_QUICKBOOKS_12345_ACCESS_TOKEN
*/
getCredentialFromEnv(companyId, credentialType) {
const envKey = `BIZAPPS_${this.accountingProvider.toUpperCase().replace(/\s+/g, '_')}_${companyId}_${credentialType.toUpperCase()}`;
return process.env[envKey];
}
/**
* Gets OAuth tokens - first tries environment variables, then falls back to database
*/
async getOAuthTokens(integration) {
const companyId = integration.CompanyID;
// Try environment variables first
const envAccessToken = this.getCredentialFromEnv(companyId, 'ACCESS_TOKEN');
const envRefreshToken = this.getCredentialFromEnv(companyId, 'REFRESH_TOKEN');
if (envAccessToken) {
return {
accessToken: envAccessToken,
refreshToken: envRefreshToken
};
}
// Fall back to database (for backwards compatibility)
if (!integration.AccessToken) {
throw new Error(`No access token found for ${this.integrationName} integration. Please set environment variable BIZAPPS_${this.accountingProvider.toUpperCase().replace(/\s+/g, '_')}_${companyId}_ACCESS_TOKEN or configure in database.`);
}
// Check if token is expired
if (integration.TokenExpirationDate && new Date(integration.TokenExpirationDate) < new Date()) {
throw new Error(`Access token for ${this.integrationName} has expired. Please re-authenticate.`);
}
return {
accessToken: integration.AccessToken,
refreshToken: integration.RefreshToken || undefined
};
}
/**
* Gets the base URL for API calls from the integration
*/
async getAPIBaseURL(contextUser, provider) {
const md = provider ?? new Metadata();
const integration = await md.GetEntityObject('MJ: Integrations', contextUser);
const rv = new RunView();
const result = await rv.RunView({
EntityName: 'MJ: Integrations',
ExtraFilter: `Name = '${this.integrationName}'`,
ResultType: 'entity_object'
}, contextUser);
if (!result.Success || !result.Results || result.Results.length === 0) {
throw new Error(`Integration configuration not found for ${this.integrationName}`);
}
return result.Results[0].NavigationBaseURL || '';
}
/**
* Validates common accounting data formats
*/
validateAccountNumber(accountNumber) {
// Basic validation - can be overridden by specific providers
return /^[0-9\-\.]+$/.test(accountNumber);
}
/**
* Validates journal entry balance (debits must equal credits)
*/
validateJournalEntryBalance(lines) {
const totalDebits = lines.reduce((sum, line) => sum + (line.debit || 0), 0);
const totalCredits = lines.reduce((sum, line) => sum + (line.credit || 0), 0);
return Math.abs(totalDebits - totalCredits) < 0.01; // Allow for minor rounding differences
}
/**
* Formats currency values consistently
*/
formatCurrency(amount, currencyCode = 'USD') {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currencyCode,
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(amount);
}
/**
* Standard date format for accounting systems (ISO 8601)
*/
formatAccountingDate(date) {
return date.toISOString().split('T')[0];
}
/**
* Helper to build consistent error messages for accounting operations
*/
buildAccountingErrorMessage(operation, details, systemError) {
let message = `Accounting operation failed: ${operation}. ${details}`;
if (systemError) {
message += ` System error: ${systemError.message || systemError}`;
}
return message;
}
};
BaseAccountingAction = __decorate([
RegisterClass(BaseAction, 'BaseAccountingAction')
], BaseAccountingAction);
export { BaseAccountingAction };
//# sourceMappingURL=base-accounting-action.js.map