UNPKG

@accounter/server

Version:

668 lines 23.4 kB
import { GraphQLError } from 'graphql'; import { CloudinaryProvider } from '../../app-providers/cloudinary.js'; import { GreenInvoiceClientProvider } from '../../app-providers/green-invoice-client.js'; import { ChargesProvider } from '../../charges/providers/charges.provider.js'; import { DocumentsProvider } from '../../documents/providers/documents.provider.js'; import { IssuedDocumentsProvider } from '../../documents/providers/issued-documents.provider.js'; import { ClientsProvider } from '../../financial-entities/providers/clients.provider.js'; import { Currency, DocumentType, } from '../../../__generated__/types.js'; import { formatCurrency } from '../../../shared/helpers/index.js'; export function normalizeDocumentType(rawType) { if (!rawType) { return DocumentType.Unprocessed; } if (typeof rawType === 'string' && rawType.startsWith('_')) { const int = parseInt(rawType.replace('_', '')); if (Number.isInteger(int)) { rawType = int; } } switch (rawType) { case 20: // חשבון / אישור תשלום return DocumentType.Invoice; case 300: // חשבונית עסקה return DocumentType.Proforma; case 305: // חשבונית מס return DocumentType.Invoice; case 320: // חשבונית מס\קבלה return DocumentType.InvoiceReceipt; case 330: // חשבונית זיכוי return DocumentType.CreditInvoice; case 400: // קבלה return DocumentType.Receipt; case 405: // קבלה על תרומה return DocumentType.Unprocessed; default: console.log(`Got a new document type from Green Invoice: ${rawType}`); return DocumentType.Unprocessed; } } export function getGreenInvoiceDocumentType(documentType) { switch (documentType) { case DocumentType.Invoice: return '_305'; case DocumentType.Proforma: return '_300'; case DocumentType.InvoiceReceipt: return '_320'; case DocumentType.CreditInvoice: return '_330'; case DocumentType.Receipt: return '_400'; default: throw new Error(`Unsupported document type: ${documentType}`); } } export function getTypeFromGreenInvoiceDocument(documentType) { switch (documentType) { case '_305': return DocumentType.Invoice; case '_300': return DocumentType.Proforma; case '_320': return DocumentType.InvoiceReceipt; case '_330': return DocumentType.CreditInvoice; case '_400': return DocumentType.Receipt; default: throw new Error(`Unsupported document type: ${documentType}`); } } export function getGreenInvoiceDocumentNameFromType(documentType) { switch (documentType) { case DocumentType.Invoice: case '_305': return 'Tax Invoice'; case DocumentType.Proforma: case '_300': return 'Proforma Invoice'; case DocumentType.InvoiceReceipt: case '_320': return 'Invoice / Receipt'; case DocumentType.CreditInvoice: case '_330': return 'Credit Note'; case DocumentType.Receipt: case '_400': return 'Receipt'; default: throw new Error(`Unsupported document type: ${documentType}`); } } export function getGreenInvoiceDocumentLanguage(lang) { switch (lang) { case 'HEBREW': return 'he'; case 'ENGLISH': return 'en'; default: throw new Error(`Unsupported document language: ${lang}`); } } export function getLanguageFromGreenInvoiceDocument(lang) { switch (lang) { case 'he': return 'HEBREW'; case 'en': return 'ENGLISH'; default: throw new Error(`Unsupported document language: ${lang}`); } } export function getGreenInvoiceDocumentVatType(vatType) { switch (vatType) { case 'DEFAULT': return '_0'; case 'EXEMPT': return '_1'; case 'MIXED': return '_2'; default: throw new Error(`Unsupported VAT type: ${vatType}`); } } export function getVatTypeFromGreenInvoiceDocument(vatType) { switch (vatType) { case '_0': return 'DEFAULT'; case '_1': return 'EXEMPT'; case '_2': return 'MIXED'; default: throw new Error(`Unsupported VAT type: ${vatType}`); } } export function getGreenInvoiceDocumentDiscountType(discountType) { switch (discountType) { case 'PERCENTAGE': return 'percentage'; case 'SUM': return 'sum'; default: throw new Error(`Unsupported discount type: ${discountType}`); } } export function getGreenInvoiceDocumentPaymentType(type) { switch (type) { case 'TAX_DEDUCTION': return 'NEGATIVE_1'; case 'CASH': return '_1'; case 'CHEQUE': return '_2'; case 'CREDIT_CARD': return '_3'; case 'WIRE_TRANSFER': return '_4'; case 'PAYPAL': return '_5'; case 'OTHER_DEDUCTION': return '_9'; case 'PAYMENT_APP': return '_10'; case 'OTHER': return '_11'; default: throw new Error(`Unsupported payment type: ${type}`); } } export function getTypeFromGreenInvoiceDocumentPayment(type) { switch (type) { case 'NEGATIVE_1': return 'TAX_DEDUCTION'; case '_1': return 'CASH'; case '_2': return 'CHEQUE'; case '_3': return 'CREDIT_CARD'; case '_4': return 'WIRE_TRANSFER'; case '_5': return 'PAYPAL'; case '_9': return 'OTHER_DEDUCTION'; case '_10': return 'PAYMENT_APP'; case '_11': return 'OTHER'; default: throw new Error(`Unsupported payment type: ${type}`); } } export function getGreenInvoiceDocumentPaymentSubType(subType) { switch (subType) { case 'BITCOIN': return '_1'; case 'BUYME_VOUCHER': return '_7'; case 'ETHEREUM': return '_6'; case 'GIFT_CARD': return '_4'; case 'MONEY_EQUAL': return '_2'; case 'NII_EMPLOYEE_DEDUCTION': return '_5'; case 'PAYONEER': return '_8'; case 'V_CHECK': return '_3'; default: throw new Error(`Unsupported payment sub-type: ${subType}`); } } export function getSubTypeFromGreenInvoiceDocumentPayment(subType) { switch (subType) { case '_1': return 'BITCOIN'; case '_7': return 'BUYME_VOUCHER'; case '_6': return 'ETHEREUM'; case '_4': return 'GIFT_CARD'; case '_2': return 'MONEY_EQUAL'; case '_5': return 'NII_EMPLOYEE_DEDUCTION'; case '_8': return 'PAYONEER'; case '_3': return 'V_CHECK'; default: throw new Error(`Unsupported payment sub-type: ${subType}`); } } export function getGreenInvoiceDocumentPaymentAppType(appType) { switch (appType) { case 'APPLE_PAY': return '_6'; case 'BIT': return '_1'; case 'CULO': return '_4'; case 'GOOGLE_PAY': return '_5'; case 'PAYBOX': return '_3'; case 'PAY_BY_PEPPER': return '_2'; default: throw new Error(`Unsupported payment app type: ${appType}`); } } export function getPaymentAppTypeFromGreenInvoiceDocument(appType) { switch (appType) { case '_6': return 'APPLE_PAY'; case '_1': return 'BIT'; case '_4': return 'CULO'; case '_5': return 'GOOGLE_PAY'; case '_3': return 'PAYBOX'; case '_2': return 'PAY_BY_PEPPER'; default: throw new Error(`Unsupported payment app type: ${appType}`); } } export function getGreenInvoiceDocumentPaymentCardType(cardType) { switch (cardType) { case 'AMERICAN_EXPRESS': return '_4'; case 'DINERS': return '_5'; case 'ISRACARD': return '_1'; case 'MASTERCARD': return '_3'; // case 'UNKNOWN': // return '_0'; // TODO: why is this not supported? case 'VISA': return '_2'; default: throw new Error(`Unsupported payment card type: ${cardType}`); } } export function getCardTypeFromGreenInvoiceDocumentPayment(cardType) { switch (cardType) { case '_4': return 'AMERICAN_EXPRESS'; case '_5': return 'DINERS'; case '_1': return 'ISRACARD'; case '_3': return 'MASTERCARD'; // case '_0': // TODO: why is this not supported? // return 'UNKNOWN'; case '_2': return 'VISA'; default: throw new Error(`Unsupported payment card type: ${cardType}`); } } export function getGreenInvoiceDocumentPaymentDealType(dealType) { switch (dealType) { case 'CREDIT': return '_3'; case 'DEFERRED': return '_4'; case 'OTHER': return '_5'; case 'PAYMENTS': return '_2'; case 'RECURRING': return '_6'; case 'STANDARD': return '_1'; default: throw new Error(`Unsupported payment deal type: ${dealType}`); } } export function getDealTypeFromGreenInvoiceDocumentPayment(dealType) { switch (dealType) { case '_3': return 'CREDIT'; case '_4': return 'DEFERRED'; case '_5': return 'OTHER'; case '_2': return 'PAYMENTS'; case '_6': return 'RECURRING'; case '_1': return 'STANDARD'; default: throw new Error(`Unsupported payment deal type: ${dealType}`); } } export function getGreenInvoiceDocumentLinkType(linkType) { switch (linkType) { case 'CANCEL': return 'CANCEL'; case 'LINK': return 'LINK'; default: throw new Error(`Unsupported link type: ${linkType}`); } } export function convertCurrencyToGreenInvoice(currency) { switch (currency) { case Currency.Aud: return 'AUD'; case Currency.Cad: return 'CAD'; case Currency.Eur: return 'EUR'; case Currency.Gbp: return 'GBP'; case Currency.Ils: return 'ILS'; case Currency.Jpy: return 'JPY'; case Currency.Sek: return 'SEK'; case Currency.Usd: return 'USD'; case Currency.Eth: case Currency.Grt: case Currency.Usdc: throw new Error(`Crypto currency (${currency}) is not supported`); default: throw new Error(`Unsupported currency: ${currency}`); } } export function greenInvoiceToDocumentStatus(greenInvoiceStatus) { switch (greenInvoiceStatus) { case 0: return 'OPEN'; case 1: return 'CLOSED'; case 2: return 'MANUALLY_CLOSED'; case 3: return 'CANCELLED_BY_OTHER_DOC'; case 4: return 'CANCELLED'; default: throw new Error(`Unsupported Green Invoice document status: ${greenInvoiceStatus}`); } } export async function getLinkedDocuments(injector, externalDocumentId) { const greenInvoiceDocument = await injector .get(GreenInvoiceClientProvider) .documentLoader.load(externalDocumentId); if (!greenInvoiceDocument?.linkedDocuments) { return null; } const linkedDocuments = greenInvoiceDocument.linkedDocuments.filter(Boolean); if (!linkedDocuments.length) { return null; } const linkedDocumentsIds = []; await Promise.all(linkedDocuments.map(async (linkedDocument) => { const issuedDocument = await injector .get(IssuedDocumentsProvider) .getIssuedDocumentsByExternalIdLoader.load(linkedDocument.id); if (!issuedDocument?.id) { throw new GraphQLError(`Linked document with ID ${linkedDocument.id} not found in issued documents`); } linkedDocumentsIds.push(issuedDocument.id); })); return linkedDocumentsIds; } export async function insertNewDocumentFromGreenInvoice(injector, greenInvoiceDoc, ownerId, preDictatedChargeId) { const documentType = normalizeDocumentType(greenInvoiceDoc.type); const isOwnerCreditor = greenInvoiceDoc.amount > 0 && documentType !== DocumentType.CreditInvoice; try { // generate preview image via cloudinary const imagePromise = injector .get(CloudinaryProvider) .uploadInvoiceToCloudinary(greenInvoiceDoc.url.origin); // Get matching business const clientPromise = injector .get(ClientsProvider) .getClientByGreenInvoiceIdLoader.load(greenInvoiceDoc.client.id); const linkedDocumentsPromise = getLinkedDocuments(injector, greenInvoiceDoc.id); const [{ imageUrl }, client, linkedDocumentIds] = await Promise.all([ imagePromise, clientPromise, linkedDocumentsPromise, ]); let chargeId = preDictatedChargeId || null; // if linked documents exist, use the first one to get the charge ID if (!chargeId && linkedDocumentIds?.length) { const linkedDocId = linkedDocumentIds[0]; const document = await injector .get(DocumentsProvider) .getDocumentsByIdLoader.load(linkedDocId); if (document?.charge_id) { chargeId = document.charge_id; } } if (!chargeId) { // Generate new parent charge let userDescription = 'Green Invoice generated charge'; const income = greenInvoiceDoc.income; if (income && income.length > 0 && income[0].description && income[0].description !== '') { userDescription = income .filter(item => item?.description) .map(item => item.description) .join(', '); } else if (greenInvoiceDoc.description && greenInvoiceDoc.description !== '') { userDescription = greenInvoiceDoc.description; } const [charge] = await injector.get(ChargesProvider).generateCharge({ ownerId, userDescription, }); if (!charge) { throw new Error('Failed to generate charge'); } chargeId = charge.id; } const counterpartyId = client?.business_id ?? null; // insert document const rawDocument = { image: imageUrl, file: greenInvoiceDoc.url.origin, documentType, serialNumber: greenInvoiceDoc.number, date: greenInvoiceDoc.documentDate, amount: greenInvoiceDoc.amount, currencyCode: formatCurrency(greenInvoiceDoc.currency), vat: greenInvoiceDoc.vat, chargeId, vatReportDateOverride: null, noVatAmount: null, creditorId: isOwnerCreditor ? ownerId : counterpartyId, debtorId: isOwnerCreditor ? counterpartyId : ownerId, allocationNumber: null, // TODO: add allocation number from GreenInvoice API exchangeRateOverride: null, }; const newDocumentResponse = await injector .get(DocumentsProvider) .insertDocuments({ document: [rawDocument] }); if (!newDocumentResponse || newDocumentResponse.length === 0) { throw new Error('Failed to insert document'); } const newDocument = newDocumentResponse[0]; // insert issued document await injector .get(IssuedDocumentsProvider) .insertIssuedDocuments({ issuedDocuments: [ { external_id: greenInvoiceDoc.id, id: newDocument.id, status: greenInvoiceToDocumentStatus(greenInvoiceDoc.status), linked_document_ids: linkedDocumentIds, }, ], }) .catch(e => { console.error('Failed to insert issued document', e); throw new GraphQLError(`Failed to insert issued document for Green Invoice ID: ${greenInvoiceDoc.id}`); }); return newDocument; } catch (e) { throw new GraphQLError(`Error adding Green Invoice document: ${e}\n\n${JSON.stringify(greenInvoiceDoc, null, 2)}`); } } export async function getGreenInvoiceDocuments(injector, recursive = false) { const documents = []; async function getDocuments(page = 1) { const data = await injector.get(GreenInvoiceClientProvider).searchDocuments({ input: { pageSize: 100, sort: 'creationDate', page }, }); if (!data?.items) { if (!recursive) { throw new GraphQLError('Failed to fetch documents'); } return; } documents.push(...data.items.filter(item => item !== null)); if (data.items.length < 100) { return; } if (recursive) { await getDocuments(page + 1); } } await getDocuments(); return documents; } export async function convertDocumentInputIntoGreenInvoiceInput(initialInput, injector) { let client = undefined; if (initialInput.client) { const clientInfo = await injector .get(ClientsProvider) .getClientByIdLoader.load(initialInput.client.id); if (!clientInfo) { throw new GraphQLError(`Client with ID ${initialInput.client.id} not found in Green Invoice`); } const greenInvoiceClient = await injector .get(GreenInvoiceClientProvider) .clientLoader.load(clientInfo.green_invoice_id); if (!greenInvoiceClient) { throw new GraphQLError(`Green Invoice client with ID ${clientInfo.green_invoice_id} not found`); } const emails = ['ap@the-guild.dev']; const inputEmails = initialInput.client?.emails?.filter(Boolean) ?? []; if (inputEmails.length) { emails.push(...inputEmails); } else { emails.push(...(greenInvoiceClient.emails ?? [])); } client = { id: clientInfo.green_invoice_id, country: greenInvoiceClient.country, name: greenInvoiceClient.name, phone: greenInvoiceClient.phone, taxId: greenInvoiceClient.taxId, self: false, address: greenInvoiceClient.address, city: greenInvoiceClient.city, zip: greenInvoiceClient.zip, fax: greenInvoiceClient.fax, mobile: greenInvoiceClient.mobile, emails, }; } return { ...initialInput, currency: convertCurrencyToGreenInvoice(initialInput.currency), type: getGreenInvoiceDocumentType(initialInput.type), lang: getGreenInvoiceDocumentLanguage(initialInput.lang), vatType: getGreenInvoiceDocumentVatType(initialInput.vatType ?? 'DEFAULT'), discount: initialInput.discount ? { ...initialInput.discount, type: getGreenInvoiceDocumentDiscountType(initialInput.discount.type), } : undefined, client, income: initialInput.income?.map(income => ({ ...income, currency: convertCurrencyToGreenInvoice(income.currency), vatType: getGreenInvoiceDocumentVatType(income.vatType ?? 'DEFAULT'), })) ?? [], payment: initialInput.payment?.map(payment => ({ ...payment, type: getGreenInvoiceDocumentPaymentType(payment.type), subType: payment.subType ? getGreenInvoiceDocumentPaymentSubType(payment.subType) : undefined, appType: payment.appType ? getGreenInvoiceDocumentPaymentAppType(payment.appType) : undefined, cardType: payment.cardType ? getGreenInvoiceDocumentPaymentCardType(payment.cardType) : undefined, dealType: payment.dealType ? getGreenInvoiceDocumentPaymentDealType(payment.dealType) : undefined, currency: convertCurrencyToGreenInvoice(payment.currency), })), linkedDocumentIds: initialInput.linkedDocumentIds?.length ? [...initialInput.linkedDocumentIds] : undefined, linkType: initialInput.linkType ? getGreenInvoiceDocumentLinkType(initialInput.linkType) : undefined, }; } export function convertGreenInvoiceDocumentToLocalDocumentInfo(greenInvoiceDocument) { return { ...greenInvoiceDocument, client: greenInvoiceDocument.client ? { ...greenInvoiceDocument.client, emails: greenInvoiceDocument.client.emails ? greenInvoiceDocument.client.emails.filter(Boolean) : [], } : undefined, currency: greenInvoiceDocument.currency, income: greenInvoiceDocument.income?.filter(Boolean).map(income => ({ ...income, currency: income.currency, vatType: getVatTypeFromGreenInvoiceDocument(income.vatType), })), lang: getLanguageFromGreenInvoiceDocument(greenInvoiceDocument.lang), payment: greenInvoiceDocument.payment?.filter(Boolean).map(payment => ({ ...payment, appType: payment?.appType ? getPaymentAppTypeFromGreenInvoiceDocument(payment.appType) : undefined, cardType: payment?.cardType ? getCardTypeFromGreenInvoiceDocumentPayment(payment.cardType) : undefined, currency: payment.currency, dealType: payment?.dealType ? getDealTypeFromGreenInvoiceDocumentPayment(payment.dealType) : undefined, subType: payment?.subType ? getSubTypeFromGreenInvoiceDocumentPayment(payment.subType) : undefined, type: getTypeFromGreenInvoiceDocumentPayment(payment.type), })), type: getTypeFromGreenInvoiceDocument(greenInvoiceDocument.type), vatType: getVatTypeFromGreenInvoiceDocument(greenInvoiceDocument.vatType), }; } //# sourceMappingURL=green-invoice.helper.js.map