@accounter/server
Version:
Accounter GraphQL server
282 lines • 11.8 kB
JavaScript
import { __decorate, __metadata, __param } from "tslib";
import DataLoader from 'dataloader';
import { GraphQLError } from 'graphql';
import { Inject, Injectable, Scope } from 'graphql-modules';
import { init, } from '../../../../green-invoice-graphql/src/index.js';
import { dateToTimelessDateString } from '../../shared/helpers/index.js';
import { ENVIRONMENT } from '../../shared/tokens.js';
import { AdminContextProvider } from '../admin-context/providers/admin-context.provider.js';
import { ProviderCredentialsProvider } from '../provider-credentials/providers/provider-credentials.provider.js';
let GreenInvoiceClientProvider = class GreenInvoiceClientProvider {
adminContextProvider;
env;
credentialsProvider;
initPromise = null;
constructor(adminContextProvider, env, credentialsProvider) {
this.adminContextProvider = adminContextProvider;
this.env = env;
this.credentialsProvider = credentialsProvider;
}
authToken = null;
init() {
this.initPromise ??= this._doInit();
return this.initPromise;
}
async _doInit() {
const fromDb = await this.credentialsProvider.getGreenInvoiceCredentials();
const creds = fromDb ?? null;
if (!creds) {
throw new GraphQLError('Green Invoice credentials not configured for this tenant', {
extensions: { code: 'PROVIDER_NOT_CONFIGURED' },
});
}
return init(creds.id, creds.secret);
}
async getSDK() {
try {
const { sdk, authToken } = await this.init();
this.authToken = authToken;
return sdk;
}
catch (e) {
const message = 'Green Invoice SDK error';
console.error(`${message}: ${e}`);
throw new Error(message, { cause: e });
}
}
/**
* Expenses
* */
async addExpenseDraftByFile(file) {
try {
const { defaultLocalCurrency } = await this.adminContextProvider.getVerifiedAdminContext();
// convert what-wg file to nodejs file
const newFile = new File([Buffer.from(await file.arrayBuffer())], 'name' in file ? file.name : 'new-file', {
type: file.type,
});
// get file upload url + params
const sdk = await this.getSDK();
const { getFileUploadUrl: fileUploadParams } = await sdk.getFileUploadUrl_query({
context: 'expense',
data: { source: 5 },
});
if (!fileUploadParams) {
throw new Error('No file upload params returned');
}
const form = new FormData();
for (const [field, value] of Object.entries(fileUploadParams.fields)) {
if (value) {
// replace field names underscores with dashes (originally dashes, Mesh converted to underscores to match GraphQL spec)
form.append(field.replaceAll('_', '-'), value);
}
}
form.append('file', newFile, newFile.name);
const uploadTime = new Date().getTime();
// upload the file
await fetch(fileUploadParams.url, {
method: 'POST',
body: form,
}).then(async (res) => {
if (![200, 201, 204].includes(res.status)) {
throw new Error('Failed to upload file');
}
return;
});
// TODO: next section is temporary fix until decent webhook will be set up
let retryCounter = 15; // 30 seconds
while (retryCounter > 0) {
await new Promise(resolve => setTimeout(resolve, 2000)); // 2 second delay to allow Green Invoice to process the file.
const res = await sdk.searchExpenseDrafts_query({
input: {
fromDate: dateToTimelessDateString(new Date()),
toDate: dateToTimelessDateString(new Date()),
},
});
if (res.searchExpenseDrafts &&
'items' in res.searchExpenseDrafts &&
res.searchExpenseDrafts.items.length) {
const items = res.searchExpenseDrafts.items.filter(item => {
return !!item && item?.creationDate > uploadTime / 1000;
});
if (items.length > 1) {
// currently we cannot differentiate between drafts
throw new Error('Multiple drafts found');
}
if (items.length === 1) {
const { expense } = items[0];
// check if any actual OCR fields exists
if (expense.amount !== null ||
expense.currency !== defaultLocalCurrency ||
expense.date !== null ||
expense.documentType !== '_20' ||
expense.number !== null ||
expense.vat !== 0) {
console.log(`Finally, it took only ${(15 - retryCounter) * 2} seconds...`);
return items[0];
}
}
}
retryCounter -= 1;
}
console.log('Failed to fetch OCR data, returning empty draft');
const emptyDraft = {
expense: {
documentType: '_405', // "receipt on donation" which is not supported, resulting in unprocessed document
number: undefined,
date: undefined,
amount: 0,
currency: defaultLocalCurrency,
vat: 0,
},
};
return emptyDraft;
}
catch (e) {
const message = 'Green Invoice Add Expense Draft error';
console.error(`${message}: ${e}`);
throw new Error(message, { cause: e });
}
}
/**
* Documents
* */
async searchDocuments(...params) {
const sdk = await this.getSDK();
return sdk.searchDocuments_query(...params).then(res => {
for (const doc of res.searchDocuments?.items ?? []) {
if (doc)
this.documentLoader.prime(doc.id, doc);
}
return res.searchDocuments;
});
}
async addDocuments(...params) {
const sdk = await this.getSDK();
return sdk.addDocument_mutation(...params).then(res => res.addDocument);
}
async previewDocuments(...params) {
const sdk = await this.getSDK();
return sdk.previewDocument_query(...params).then(res => res.previewDocument);
}
async getLinkedDocuments(documentId) {
const sdk = await this.getSDK();
return sdk.getLinkedDocuments_query({ id: documentId }).then(res => res.getLinkedDocuments);
}
async getDocumentLinks(documentId) {
const sdk = await this.getSDK();
return sdk
.getDocumentsDownloadLinks_query({ id: documentId })
.then(res => res.getDocumentsDownloadLinks);
}
async _batchLoadByIds(ids, fetcher, primingFunction) {
const sdk = await this.getSDK();
const uniqueIds = Array.from(new Set(ids));
const items = await Promise.all(uniqueIds.map(async (id) => fetcher(sdk, id).then(item => {
if (item?.id) {
primingFunction(item);
}
return item ?? undefined;
})));
const itemsMap = new Map(items.filter((i) => !!i).map(i => [i.id, i]));
return ids.map(id => itemsMap.get(id) ?? null);
}
async batchDocumentsByIds(ids) {
return this._batchLoadByIds(ids, (sdk, id) => sdk.getDocument_query({ id }).then(res => res.getDocument), item => this.documentLoader.prime(item.id, item));
}
documentLoader = new DataLoader((ids) => this.batchDocumentsByIds(ids));
async closeDocument(documentId) {
this.invalidateDocument(documentId);
const sdk = await this.getSDK();
return sdk.closeDocument_mutation({ id: documentId }).then(res => res.closeDocument);
}
invalidateDocument(documentId) {
this.documentLoader.clear(documentId);
}
/**
* Clients
* */
async batchClientsByIds(ids) {
return this._batchLoadByIds(ids, (sdk, id) => sdk.getClient_query({ id }).then(res => res.getClient), item => this.clientLoader.prime(item.id, item));
}
clientLoader = new DataLoader((ids) => this.batchClientsByIds(ids));
async createClient(input) {
try {
const sdk = await this.getSDK();
const client = await sdk.addClient_mutation({ input }).then(res => res.addClient);
if (!client) {
throw new Error('Failed to create Green Invoice client');
}
if ('errorMessage' in client) {
throw new Error(`Failed to create Green Invoice client: ${client.errorMessage}`);
}
const { id } = client;
if (id) {
this.clientLoader.prime(id, client);
}
return client;
}
catch (error) {
const message = 'Green Invoice Create Client error';
console.error(`${message}: ${error}`);
throw new Error(message, { cause: error });
}
}
async updateClient(args) {
try {
const sdk = await this.getSDK();
const client = await sdk.updateClient_mutation(args).then(res => res.updateClient);
if (!client) {
throw new Error('Failed to update Green Invoice client');
}
if ('errorMessage' in client) {
throw new Error(`Failed to update Green Invoice client: ${client.errorMessage}`);
}
const { id } = client;
if (id) {
this.clientLoader.prime(id, client);
}
return client;
}
catch (error) {
this.invalidateClient(args.id);
const message = 'Green Invoice Update Client error';
console.error(`${message}: ${error}`);
throw new Error(message, { cause: error });
}
}
async deleteClient(clientId) {
try {
const sdk = await this.getSDK();
const client = await sdk
.deleteClient_mutation({ id: clientId })
.then(res => res.deleteClient);
if (!client) {
throw new Error('Failed to delete Green Invoice client');
}
if ('errorMessage' in client) {
throw new Error(`Failed to delete Green Invoice client: ${client.errorMessage}`);
}
this.invalidateClient(clientId);
return client;
}
catch (error) {
this.invalidateClient(clientId);
const message = 'Green Invoice Delete Client error';
console.error(`${message}: ${error}`);
throw new Error(message, { cause: error });
}
}
invalidateClient(clientId) {
this.clientLoader.clear(clientId);
}
};
GreenInvoiceClientProvider = __decorate([
Injectable({
scope: Scope.Operation,
global: true,
}),
__param(1, Inject(ENVIRONMENT)),
__metadata("design:paramtypes", [AdminContextProvider, Object, ProviderCredentialsProvider])
], GreenInvoiceClientProvider);
export { GreenInvoiceClientProvider };
//# sourceMappingURL=green-invoice-client.js.map