UNPKG

@accounter/server

Version:
282 lines • 11.8 kB
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