add server to monorepo.

This commit is contained in:
a.bouhuolia
2023-02-03 11:57:50 +02:00
parent 28e309981b
commit 80b97b5fdc
1303 changed files with 137049 additions and 0 deletions

View File

@@ -0,0 +1,203 @@
import * as R from 'ramda';
import {
ISaleInvoice,
IItemEntry,
ILedgerEntry,
AccountNormal,
ILedger,
} from '@/interfaces';
import { Knex } from 'knex';
import { Service, Inject } from 'typedi';
import Ledger from '@/services/Accounting/Ledger';
import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export class SaleInvoiceGLEntries {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private ledegrRepository: LedgerStorageService;
/**
* Writes a sale invoice GL entries.
* @param {number} tenantId
* @param {number} saleInvoiceId
* @param {Knex.Transaction} trx
*/
public writeInvoiceGLEntries = async (
tenantId: number,
saleInvoiceId: number,
trx?: Knex.Transaction
) => {
const { SaleInvoice } = this.tenancy.models(tenantId);
const { accountRepository } = this.tenancy.repositories(tenantId);
const saleInvoice = await SaleInvoice.query(trx)
.findById(saleInvoiceId)
.withGraphFetched('entries.item');
// Find or create the A/R account.
const ARAccount = await accountRepository.findOrCreateAccountReceivable(
saleInvoice.currencyCode
);
// Retrieves the ledger of the invoice.
const ledger = this.getInvoiceGLedger(saleInvoice, ARAccount.id);
// Commits the ledger entries to the storage as UOW.
await this.ledegrRepository.commit(tenantId, ledger, trx);
};
/**
* Rewrites the given invoice GL entries.
* @param {number} tenantId
* @param {number} saleInvoiceId
* @param {Knex.Transaction} trx
*/
public rewritesInvoiceGLEntries = async (
tenantId: number,
saleInvoiceId: number,
trx?: Knex.Transaction
) => {
// Reverts the invoice GL entries.
await this.revertInvoiceGLEntries(tenantId, saleInvoiceId, trx);
// Writes the invoice GL entries.
await this.writeInvoiceGLEntries(tenantId, saleInvoiceId, trx);
};
/**
* Reverts the given invoice GL entries.
* @param {number} tenantId
* @param {number} saleInvoiceId
* @param {Knex.Transaction} trx
*/
public revertInvoiceGLEntries = async (
tenantId: number,
saleInvoiceId: number,
trx?: Knex.Transaction
) => {
await this.ledegrRepository.deleteByReference(
tenantId,
saleInvoiceId,
'SaleInvoice',
trx
);
};
/**
* Retrieves the given invoice ledger.
* @param {ISaleInvoice} saleInvoice
* @param {number} ARAccountId
* @returns {ILedger}
*/
public getInvoiceGLedger = (
saleInvoice: ISaleInvoice,
ARAccountId: number
): ILedger => {
const entries = this.getInvoiceGLEntries(saleInvoice, ARAccountId);
return new Ledger(entries);
};
/**
* Retrieves the invoice GL common entry.
* @param {ISaleInvoice} saleInvoice
* @returns {Partial<ILedgerEntry>}
*/
private getInvoiceGLCommonEntry = (
saleInvoice: ISaleInvoice
): Partial<ILedgerEntry> => ({
credit: 0,
debit: 0,
currencyCode: saleInvoice.currencyCode,
exchangeRate: saleInvoice.exchangeRate,
transactionType: 'SaleInvoice',
transactionId: saleInvoice.id,
date: saleInvoice.invoiceDate,
userId: saleInvoice.userId,
transactionNumber: saleInvoice.invoiceNo,
referenceNumber: saleInvoice.referenceNo,
createdAt: saleInvoice.createdAt,
indexGroup: 10,
branchId: saleInvoice.branchId,
});
/**
* Retrieve receivable entry of the given invoice.
* @param {ISaleInvoice} saleInvoice
* @param {number} ARAccountId
* @returns {ILedgerEntry}
*/
private getInvoiceReceivableEntry = (
saleInvoice: ISaleInvoice,
ARAccountId: number
): ILedgerEntry => {
const commonEntry = this.getInvoiceGLCommonEntry(saleInvoice);
return {
...commonEntry,
debit: saleInvoice.localAmount,
accountId: ARAccountId,
contactId: saleInvoice.customerId,
accountNormal: AccountNormal.DEBIT,
index: 1,
} as ILedgerEntry;
};
/**
* Retrieve item income entry of the given invoice.
* @param {ISaleInvoice} saleInvoice -
* @param {IItemEntry} entry -
* @param {number} index -
* @returns {ILedgerEntry}
*/
private getInvoiceItemEntry = R.curry(
(
saleInvoice: ISaleInvoice,
entry: IItemEntry,
index: number
): ILedgerEntry => {
const commonEntry = this.getInvoiceGLCommonEntry(saleInvoice);
const localAmount = entry.amount * saleInvoice.exchangeRate;
return {
...commonEntry,
credit: localAmount,
accountId: entry.sellAccountId,
note: entry.description,
index: index + 2,
itemId: entry.itemId,
itemQuantity: entry.quantity,
accountNormal: AccountNormal.CREDIT,
projectId: entry.projectId || saleInvoice.projectId
};
}
);
/**
* Retrieves the invoice GL entries.
* @param {ISaleInvoice} saleInvoice
* @param {number} ARAccountId
* @returns {ILedgerEntry[]}
*/
public getInvoiceGLEntries = (
saleInvoice: ISaleInvoice,
ARAccountId: number
): ILedgerEntry[] => {
const receivableEntry = this.getInvoiceReceivableEntry(
saleInvoice,
ARAccountId
);
const transformItemEntry = this.getInvoiceItemEntry(saleInvoice);
const creditEntries = saleInvoice.entries.map(transformItemEntry);
return [receivableEntry, ...creditEntries];
};
}

View File

@@ -0,0 +1,56 @@
import { Transformer } from '@/lib/Transformer/Transformer';
import { formatNumber } from 'utils';
export class InvoicePaymentTransactionTransformer extends Transformer {
/**
* Include these attributes to sale credit note object.
* @returns {Array}
*/
public includeAttributes = (): string[] => {
return ['formattedPaymentAmount', 'formattedPaymentDate'];
};
/**
* Retrieve formatted invoice amount.
* @param {ICreditNote} credit
* @returns {string}
*/
protected formattedPaymentAmount = (entry): string => {
return formatNumber(entry.paymentAmount, {
currencyCode: entry.payment.currencyCode,
});
};
protected formattedPaymentDate = (entry): string => {
return this.formatDate(entry.payment.paymentDate);
};
/**
*
* @param entry
* @returns
*/
public transform = (entry) => {
return {
invoiceId: entry.invoiceId,
paymentReceiveId: entry.paymentReceiveId,
paymentDate: entry.payment.paymentDate,
formattedPaymentDate: entry.formattedPaymentDate,
paymentAmount: entry.paymentAmount,
formattedPaymentAmount: entry.formattedPaymentAmount,
currencyCode: entry.payment.currencyCode,
paymentNumber: entry.payment.paymentReceiveNo,
paymentReferenceNo: entry.payment.referenceNo,
invoiceNumber: entry.invoice.invoiceNo,
invoiceReferenceNo: entry.invoice.referenceNo,
depositAccountId: entry.payment.depositAccountId,
depositAccountName: entry.payment.depositAccount.name,
depositAccountSlug: entry.payment.depositAccount.slug,
};
};
}

View File

@@ -0,0 +1,76 @@
import { Knex } from 'knex';
import async from 'async';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { Inject, Service } from 'typedi';
import { PaymentReceiveGLEntries } from '../PaymentReceives/PaymentReceiveGLEntries';
@Service()
export class InvoicePaymentsGLEntriesRewrite {
@Inject()
public tenancy: HasTenancyService;
@Inject()
public paymentGLEntries: PaymentReceiveGLEntries;
/**
* Rewrites the payment GL entries task.
* @param {{ tenantId: number, paymentId: number, trx: Knex?.Transaction }}
* @returns {Promise<void>}
*/
public rewritePaymentsGLEntriesTask = async ({
tenantId,
paymentId,
trx,
}) => {
await this.paymentGLEntries.rewritePaymentGLEntries(
tenantId,
paymentId,
trx
);
};
/**
* Rewrites the payment GL entries of the given payments ids.
* @param {number} tenantId
* @param {number[]} paymentsIds
* @param {Knex.Transaction} trx
*/
public rewritePaymentsGLEntriesQueue = async (
tenantId: number,
paymentsIds: number[],
trx?: Knex.Transaction
) => {
// Initiate a new queue for accounts balance mutation.
const rewritePaymentGL = async.queue(this.rewritePaymentsGLEntriesTask, 10);
paymentsIds.forEach((paymentId: number) => {
rewritePaymentGL.push({ paymentId, trx, tenantId });
});
if (paymentsIds.length > 0) {
await rewritePaymentGL.drain();
}
};
/**
* Rewrites the payments GL entries that associated to the given invoice.
* @param {number} tenantId
* @param {number} invoiceId
* @param {Knex.Transaction} trx
* @returns {Promise<void>}
*/
public invoicePaymentsGLEntriesRewrite = async (
tenantId: number,
invoiceId: number,
trx?: Knex.Transaction
) => {
const { PaymentReceiveEntry } = this.tenancy.models(tenantId);
const invoicePaymentEntries = await PaymentReceiveEntry.query().where(
'invoiceId',
invoiceId
);
const paymentsIds = invoicePaymentEntries.map((e) => e.paymentReceiveId);
await this.rewritePaymentsGLEntriesQueue(tenantId, paymentsIds, trx);
};
}

View File

@@ -0,0 +1,34 @@
import { Service, Inject } from 'typedi';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { InvoicePaymentTransactionTransformer } from './InvoicePaymentTransactionTransformer';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
@Service()
export default class InvoicePaymentsService {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private transformer: TransformerInjectable;
/**
* Retrieve the invoice assocaited payments transactions.
* @param {number} tenantId - Tenant id.
* @param {number} invoiceId - Invoice id.
*/
public getInvoicePayments = async (tenantId: number, invoiceId: number) => {
const { PaymentReceiveEntry } = this.tenancy.models(tenantId);
const paymentsEntries = await PaymentReceiveEntry.query()
.where('invoiceId', invoiceId)
.withGraphJoined('payment.depositAccount')
.withGraphJoined('invoice')
.orderBy('payment:paymentDate', 'ASC');
return this.transformer.transform(
tenantId,
paymentsEntries,
new InvoicePaymentTransactionTransformer()
);
};
}

View File

@@ -0,0 +1,146 @@
import { Service, Inject } from 'typedi';
import * as R from 'ramda';
import { Knex } from 'knex';
import { AccountNormal, IInventoryLotCost, ILedgerEntry } from '@/interfaces';
import { increment } from 'utils';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import Ledger from '@/services/Accounting/Ledger';
import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
import { groupInventoryTransactionsByTypeId } from '../../Inventory/utils';
@Service()
export class SaleInvoiceCostGLEntries {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private ledgerStorage: LedgerStorageService;
/**
* Writes journal entries from sales invoices.
* @param {number} tenantId - The tenant id.
* @param {Date} startingDate - Starting date.
* @param {boolean} override
*/
public writeInventoryCostJournalEntries = async (
tenantId: number,
startingDate: Date,
trx?: Knex.Transaction
): Promise<void> => {
const { InventoryCostLotTracker } = this.tenancy.models(tenantId);
const inventoryCostLotTrans = await InventoryCostLotTracker.query()
.where('direction', 'OUT')
.where('transaction_type', 'SaleInvoice')
.where('cost', '>', 0)
.modify('filterDateRange', startingDate)
.orderBy('date', 'ASC')
.withGraphFetched('invoice')
.withGraphFetched('item');
const ledger = this.getInventoryCostLotsLedger(inventoryCostLotTrans);
// Commit the ledger to the storage.
await this.ledgerStorage.commit(tenantId, ledger, trx);
};
/**
* Retrieves the inventory cost lots ledger.
* @param {IInventoryLotCost[]} inventoryCostLots
* @returns {Ledger}
*/
private getInventoryCostLotsLedger = (
inventoryCostLots: IInventoryLotCost[]
) => {
// Groups the inventory cost lots transactions.
const inventoryTransactions =
groupInventoryTransactionsByTypeId(inventoryCostLots);
const entries = inventoryTransactions
.map(this.getSaleInvoiceCostGLEntries)
.flat();
return new Ledger(entries);
};
/**
*
* @param {IInventoryLotCost} inventoryCostLot
* @returns {}
*/
private getInvoiceCostGLCommonEntry = (
inventoryCostLot: IInventoryLotCost
) => {
return {
currencyCode: inventoryCostLot.invoice.currencyCode,
exchangeRate: inventoryCostLot.invoice.exchangeRate,
transactionType: inventoryCostLot.transactionType,
transactionId: inventoryCostLot.transactionId,
date: inventoryCostLot.date,
indexGroup: 20,
costable: true,
createdAt: inventoryCostLot.createdAt,
debit: 0,
credit: 0,
branchId: inventoryCostLot.invoice.branchId,
};
};
/**
* Retrieves the inventory cost GL entry.
* @param {IInventoryLotCost} inventoryLotCost
* @returns {ILedgerEntry[]}
*/
private getInventoryCostGLEntry = R.curry(
(
getIndexIncrement,
inventoryCostLot: IInventoryLotCost
): ILedgerEntry[] => {
const commonEntry = this.getInvoiceCostGLCommonEntry(inventoryCostLot);
const costAccountId =
inventoryCostLot.costAccountId || inventoryCostLot.item.costAccountId;
// XXX Debit - Cost account.
const costEntry = {
...commonEntry,
debit: inventoryCostLot.cost,
accountId: costAccountId,
accountNormal: AccountNormal.DEBIT,
itemId: inventoryCostLot.itemId,
index: getIndexIncrement(),
};
// XXX Credit - Inventory account.
const inventoryEntry = {
...commonEntry,
credit: inventoryCostLot.cost,
accountId: inventoryCostLot.item.inventoryAccountId,
accountNormal: AccountNormal.DEBIT,
itemId: inventoryCostLot.itemId,
index: getIndexIncrement(),
};
return [costEntry, inventoryEntry];
}
);
/**
* Writes journal entries for given sale invoice.
* -------
* - Cost of goods sold -> Debit -> YYYY
* - Inventory assets -> Credit -> YYYY
* --------
* @param {ISaleInvoice} saleInvoice
* @param {JournalPoster} journal
*/
public getSaleInvoiceCostGLEntries = (
inventoryCostLots: IInventoryLotCost[]
): ILedgerEntry[] => {
const getIndexIncrement = increment(0);
const getInventoryLotEntry =
this.getInventoryCostGLEntry(getIndexIncrement);
return inventoryCostLots.map(getInventoryLotEntry).flat();
};
}

View File

@@ -0,0 +1,36 @@
import { Inject, Service } from 'typedi';
import events from '@/subscribers/events';
import { IInventoryCostLotsGLEntriesWriteEvent } from '@/interfaces';
import { SaleInvoiceCostGLEntries } from '../SaleInvoiceCostGLEntries';
@Service()
export class InvoiceCostGLEntriesSubscriber {
@Inject()
invoiceCostEntries: SaleInvoiceCostGLEntries;
/**
* Attaches events.
*/
public attach(bus) {
bus.subscribe(
events.inventory.onCostLotsGLEntriesWrite,
this.writeInvoicesCostEntriesOnCostLotsWritten
);
}
/**
* Writes the invoices cost GL entries once the inventory cost lots be written.
* @param {IInventoryCostLotsGLEntriesWriteEvent}
*/
private writeInvoicesCostEntriesOnCostLotsWritten = async ({
trx,
startingDate,
tenantId,
}: IInventoryCostLotsGLEntriesWriteEvent) => {
await this.invoiceCostEntries.writeInventoryCostJournalEntries(
tenantId,
startingDate,
trx
);
};
}

View File

@@ -0,0 +1,37 @@
import { Service, Inject } from 'typedi';
import events from '@/subscribers/events';
import { ISaleInvoiceEditingPayload } from '@/interfaces';
import { InvoicePaymentsGLEntriesRewrite } from '../InvoicePaymentsGLRewrite';
@Service()
export class InvoicePaymentGLRewriteSubscriber {
@Inject()
private invoicePaymentsRewriteGLEntries: InvoicePaymentsGLEntriesRewrite;
/**
* Attaches events with handlers.
*/
public attach = (bus) => {
bus.subscribe(
events.saleInvoice.onEdited,
this.paymentGLEntriesRewriteOnPaymentEdit
);
return bus;
};
/**
* Writes associated invoiceso of payment receive once edit.
* @param {ISaleInvoiceEditingPayload} -
*/
private paymentGLEntriesRewriteOnPaymentEdit = async ({
tenantId,
oldSaleInvoice,
trx,
}: ISaleInvoiceEditingPayload) => {
await this.invoicePaymentsRewriteGLEntries.invoicePaymentsGLEntriesRewrite(
tenantId,
oldSaleInvoice.id,
trx
);
};
}