UNPKG

@accounter/server

Version:

272 lines • 11.6 kB
import { GraphQLError } from 'graphql'; import { GreenInvoiceClientProvider } from '../../app-providers/green-invoice-client.js'; import { ChargesProvider } from '../../charges/providers/charges.provider.js'; import { IssuedDocumentsProvider } from '../../documents/providers/issued-documents.provider.js'; import { FinancialAccountsProvider } from '../../financial-accounts/providers/financial-accounts.provider.js'; import { BusinessesProvider } from '../../financial-entities/providers/businesses.provider.js'; import { ClientsProvider } from '../../financial-entities/providers/clients.provider.js'; import { DocumentType } from '../../../shared/enums.js'; import { dateToTimelessDateString } from '../../../shared/helpers/index.js'; import { convertDocumentInputIntoGreenInvoiceInput, getVatTypeFromGreenInvoiceDocument, insertNewDocumentFromGreenInvoice, normalizeDocumentType, } from './green-invoice.helper.js'; export async function getPaymentsFromTransactions(injector, transactions) { const payments = await Promise.all(transactions.map(async (transaction) => { // get account const account = await injector .get(FinancialAccountsProvider) .getFinancialAccountByAccountIDLoader.load(transaction.account_id); if (!account) { throw new Error(`Account with ID ${transaction.account_id} not found`); } // get account type let type = 'OTHER'; switch (account.type) { case 'BANK_ACCOUNT': type = 'WIRE_TRANSFER'; break; case 'CREDIT_CARD': type = 'CREDIT_CARD'; break; } // get further fields let paymentTypeSpecificAttributes = {}; switch (type) { case 'CHEQUE': paymentTypeSpecificAttributes = { // chequeNum: _____, }; break; case 'CREDIT_CARD': paymentTypeSpecificAttributes = { cardType: 'MASTERCARD', // TODO: add logic to support other card types cardNum: account.account_number, dealType: 'STANDARD', // TODO: add logic to support other deal types numPayments: 1, firstPayment: transaction.event_date.getTime() / 1000, // assuming first payment is the transaction date }; break; case 'WIRE_TRANSFER': paymentTypeSpecificAttributes = { bankName: account.bank_number?.toString(), bankBranch: account.branch_number?.toString(), bankAccount: account.account_number?.toString(), }; break; case 'PAYMENT_APP': paymentTypeSpecificAttributes = { // appType: _____, // accountId: _____, // transactionId: _____, }; break; case 'OTHER': paymentTypeSpecificAttributes = { // subType: _____, // accountId: _____, // transactionId: _____, }; break; case 'PAYPAL': paymentTypeSpecificAttributes = { // accountId: _____, // transactionId: _____, }; break; case 'CASH': case 'TAX_DEDUCTION': case 'OTHER_DEDUCTION': break; } const payment = { currency: transaction.currency, currencyRate: undefined, date: dateToTimelessDateString(transaction.debit_date ?? transaction.event_date), price: Number(transaction.amount), type, transactionId: transaction.id, accountId: account.id, ...paymentTypeSpecificAttributes, }; return payment; })); return payments; } export function getIncomeFromDocuments(documents) { const incomes = documents .map(({ greenInvoiceDocument }) => { return greenInvoiceDocument.income.filter(Boolean).map(originIncome => { if (!originIncome?.currency) { throw new Error('Income currency is missing'); } const income = { description: originIncome.description, quantity: originIncome.quantity, price: originIncome.price, currency: originIncome.currency, currencyRate: undefined, vatRate: originIncome.vatRate, itemId: originIncome.itemId, vatType: getVatTypeFromGreenInvoiceDocument(greenInvoiceDocument.vatType), }; return income; }); }) .flat(); return incomes; } export function getTypeFromDocumentsAndTransactions(greenInvoiceDocuments, transactions) { if (!greenInvoiceDocuments.length) { if (transactions.length) { return DocumentType.InvoiceReceipt; } return DocumentType.Proforma; } const documentsTypes = new Set(greenInvoiceDocuments.map(doc => normalizeDocumentType(doc.type))); if (documentsTypes.size === 1) { switch (Array.from(documentsTypes)[0]) { case DocumentType.Invoice: return DocumentType.Receipt; case DocumentType.Proforma: return DocumentType.InvoiceReceipt; } } return DocumentType.Proforma; } export function filterAndHandleSwiftTransactions(originTransactions, swiftBusinessId) { const swiftTransactions = []; const incomeTransactions = []; for (const transaction of originTransactions) { if (swiftBusinessId && transaction.business_id === swiftBusinessId) { swiftTransactions.push(transaction); } else if (Number(transaction.amount) > 0) { incomeTransactions.push(transaction); } } if (swiftTransactions.length === 0) { return incomeTransactions; } if (swiftTransactions.length === 1 && incomeTransactions.length === 1) { const incomeTransaction = incomeTransactions[0]; return [ { ...incomeTransaction, amount: (Number(incomeTransaction.amount) - Number(swiftTransactions[0].amount)).toFixed(2), }, ]; } throw new GraphQLError("Unable to process transactions. Couldn't match swift fees to transactions"); } export function getLinkedDocumentsAttributes(issuedDocuments) { const linkedDocumentIds = issuedDocuments.map(doc => doc.external_id); return { linkedDocumentIds, linkType: linkedDocumentIds.length ? 'LINK' : undefined, }; } export function getDocumentDateOutOfTransactions(transactions) { const debitDates = transactions.map(tx => tx.debit_date).filter(Boolean); // if no debit dates, use current date if (!debitDates.length) return dateToTimelessDateString(new Date()); // Sort dates and take the first one const sortedDates = [...debitDates].sort((a, b) => b.getTime() - a.getTime()); const firstDate = sortedDates[0]; // Return the date in the required format return dateToTimelessDateString(firstDate); } export async function getClientFromGreenInvoiceClient(injector, businessId, useGreenInvoiceId = false) { const client = await injector.get(ClientsProvider).getClientByIdLoader.load(businessId); if (!client) { return useGreenInvoiceId ? undefined : { id: businessId }; } const greenInvoiceClient = await injector .get(GreenInvoiceClientProvider) .clientLoader.load(client.green_invoice_id); if (!greenInvoiceClient) { return useGreenInvoiceId ? undefined : { id: businessId }; } return { id: useGreenInvoiceId && greenInvoiceClient.id ? greenInvoiceClient.id : businessId, country: greenInvoiceClient.country, emails: [ ...(greenInvoiceClient.emails?.filter(Boolean) ?? []), 'ap@the-guild.dev', ], name: greenInvoiceClient.name, phone: greenInvoiceClient.phone, taxId: greenInvoiceClient.taxId, address: greenInvoiceClient.address, city: greenInvoiceClient.city, zip: greenInvoiceClient.zip, fax: greenInvoiceClient.fax, mobile: greenInvoiceClient.mobile, }; } export async function deduceVatTypeFromBusiness(injector, businessId) { if (!businessId) return 'DEFAULT'; const business = await injector.get(BusinessesProvider).getBusinessByIdLoader.load(businessId); if (!business) return 'DEFAULT'; // Deduce VAT type based on business information if (business.country === 'Israel') { return 'MIXED'; } return 'EXEMPT'; } export async function executeDocumentIssue(injector, adminBusinessId, initialInput, emailContent, attachment = true, chargeId, sendEmail = false) { try { const coreInput = await convertDocumentInputIntoGreenInvoiceInput(initialInput, injector); const input = { ...coreInput, client: !coreInput.client || sendEmail ? coreInput.client : { ...coreInput.client, emails: [], // if client exists, and sendEmail is false, we don't want to send emails }, emailContent, attachment, }; const document = await injector.get(GreenInvoiceClientProvider).addDocuments({ input }); if (!document) { throw new GraphQLError('Failed to issue new document'); } if ('id' in document && document.id) { const greenInvoiceDocument = await injector .get(GreenInvoiceClientProvider) .documentLoader.load(document.id); if (!greenInvoiceDocument) { console.error('Failed to fetch issued document from Green Invoice', document); throw new GraphQLError('Failed to issue new document'); } // Insert new issued document to DB const newDocument = await insertNewDocumentFromGreenInvoice(injector, greenInvoiceDocument, adminBusinessId, chargeId); if (!newDocument.charge_id) { console.error('New document does not have a charge ID', newDocument); throw new GraphQLError('Failed to issue new document'); } // Close linked documents if (coreInput.linkedDocumentIds?.length) { await Promise.all(coreInput.linkedDocumentIds.map(async (id) => { if (id) { await injector .get(IssuedDocumentsProvider) .updateIssuedDocumentByExternalId({ externalId: id, status: 'CLOSED' }) .catch(e => { throw new Error(`Failed to close linked document with ID ${id}: ${e.message}`); }); } })); } return injector.get(ChargesProvider).getChargeByIdLoader.load(newDocument.charge_id); } console.error('Document issue failed', document); throw new GraphQLError('Document issue failed'); } catch (error) { console.error('Error issuing document:', error); throw new GraphQLError('Error issuing document'); } } //# sourceMappingURL=issue-document.helper.js.map