fix: refactoring invoice calc cost service.

This commit is contained in:
a.bouhuolia
2021-01-05 17:06:42 +02:00
parent d25360d279
commit df85c9b295
41 changed files with 1684 additions and 1085 deletions

View File

@@ -97,7 +97,6 @@ function VendorForm({
}), }),
[defaultInitialValues], [defaultInitialValues],
); );
console.log(isNewMode, 'Val');
useEffect(() => { useEffect(() => {
!isNewMode !isNewMode
? changePageTitle(formatMessage({ id: 'edit_vendor' })) ? changePageTitle(formatMessage({ id: 'edit_vendor' }))

View File

@@ -1,4 +1,4 @@
import { Router, Request, Response } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import { query, ValidationChain } from 'express-validator'; import { query, ValidationChain } from 'express-validator';
import asyncMiddleware from 'api/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
import BaseController from '../BaseController'; import BaseController from '../BaseController';
@@ -48,7 +48,7 @@ export default class GeneralLedgerReportController extends BaseController{
* @param {Request} req - * @param {Request} req -
* @param {Response} res - * @param {Response} res -
*/ */
async generalLedger(req: Request, res: Response) { async generalLedger(req: Request, res: Response, next: NextFunction) {
const { tenantId, settings } = req; const { tenantId, settings } = req;
const filter = this.matchedQueryData(req); const filter = this.matchedQueryData(req);
@@ -68,7 +68,7 @@ export default class GeneralLedgerReportController extends BaseController{
query: this.transfromToResponse(query), query: this.transfromToResponse(query),
}); });
} catch (error) { } catch (error) {
console.log(error); next(error);
} }
} }
} }

View File

@@ -1,5 +1,5 @@
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import { Request, Response, Router } from 'express'; import { Request, Response, Router, NextFunction } from 'express';
import { castArray } from 'lodash'; import { castArray } from 'lodash';
import { query, oneOf } from 'express-validator'; import { query, oneOf } from 'express-validator';
import JournalSheetService from 'services/FinancialStatements/JournalSheet/JournalSheetService'; import JournalSheetService from 'services/FinancialStatements/JournalSheet/JournalSheetService';
@@ -55,7 +55,7 @@ export default class JournalSheetController extends BaseController {
* @param {Request} req - * @param {Request} req -
* @param {Response} res - * @param {Response} res -
*/ */
async journal(req: Request, res: Response) { async journal(req: Request, res: Response, next: NextFunction) {
const { tenantId, settings } = req; const { tenantId, settings } = req;
let filter = this.matchedQueryData(req); let filter = this.matchedQueryData(req);
@@ -76,7 +76,7 @@ export default class JournalSheetController extends BaseController {
query: this.transfromToResponse(query), query: this.transfromToResponse(query),
}); });
} catch (error) { } catch (error) {
console.log(error); next(error);
} }
} }
} }

View File

@@ -73,7 +73,7 @@ export default class ProfitLossSheetController extends BaseController {
query: this.transfromToResponse(query), query: this.transfromToResponse(query),
}); });
} catch (error) { } catch (error) {
console.log(error); next(error);
} }
} }
} }

View File

@@ -140,7 +140,6 @@ export default class BillsPayments extends BaseController {
message: 'Payment made has been created successfully.', message: 'Payment made has been created successfully.',
}); });
} catch (error) { } catch (error) {
console.log(error);
next(error); next(error);
} }
} }

View File

@@ -248,12 +248,13 @@ export default class SaleInvoicesController extends BaseController {
*/ */
async getSaleInvoice(req: Request, res: Response, next: NextFunction) { async getSaleInvoice(req: Request, res: Response, next: NextFunction) {
const { id: saleInvoiceId } = req.params; const { id: saleInvoiceId } = req.params;
const { tenantId } = req; const { tenantId, user } = req;
try { try {
const saleInvoice = await this.saleInvoiceService.getSaleInvoice( const saleInvoice = await this.saleInvoiceService.getSaleInvoice(
tenantId, tenantId,
saleInvoiceId saleInvoiceId,
user
); );
return res.status(200).send({ sale_invoice: saleInvoice }); return res.status(200).send({ sale_invoice: saleInvoice });
} catch (error) { } catch (error) {

View File

@@ -11,7 +11,6 @@ exports.up = function(knex) {
table.string('contact_type').nullable().index(); table.string('contact_type').nullable().index();
table.integer('contact_id').unsigned().nullable().index(); table.integer('contact_id').unsigned().nullable().index();
table.string('note'); table.string('note');
table.boolean('draft').defaultTo(false);
table.integer('user_id').unsigned().index(); table.integer('user_id').unsigned().index();
table.integer('index').unsigned(); table.integer('index').unsigned();
table.date('date').index(); table.date('date').index();

View File

@@ -1,9 +1,10 @@
export type TInventoryTransactionDirection = 'IN' | 'OUT';
export interface IInventoryTransaction { export interface IInventoryTransaction {
id?: number, id?: number,
date: Date, date: Date,
direction: string, direction: TInventoryTransactionDirection,
itemId: number, itemId: number,
quantity: number, quantity: number,
rate: number, rate: number,

View File

@@ -1,4 +1,5 @@
export type IItemEntryTransactionType = 'SaleInvoice' | 'Bill' | 'SaleReceipt';
export interface IItemEntry { export interface IItemEntry {
id?: number, id?: number,

View File

@@ -1,40 +1,50 @@
import { ISalesInvoicesFilter } from "./SaleInvoice"; import { ISalesInvoicesFilter } from './SaleInvoice';
export interface ISaleReceipt { export interface ISaleReceipt {
id?: number, id?: number;
customerId: number, customerId: number;
depositAccountId: number, depositAccountId: number;
receiptDate: Date, receiptDate: Date;
sendToEmail: string, sendToEmail: string;
referenceNo: string, referenceNo: string;
receiptMessage: string, receiptMessage: string;
receiptNumber: string, receiptNumber: string;
statement: string, amount: number;
closedAt: Date|string, statement: string;
entries: any[], closedAt: Date | string;
}; entries: any[];
}
export interface ISalesReceiptsFilter { export interface ISalesReceiptsFilter {}
};
export interface ISaleReceiptDTO { export interface ISaleReceiptDTO {
customerId: number, customerId: number;
depositAccountId: number, depositAccountId: number;
receiptDate: Date, receiptDate: Date;
sendToEmail: string, sendToEmail: string;
referenceNo: string, referenceNo: string;
receiptMessage: string, receiptMessage: string;
statement: string, statement: string;
closed: boolean, closed: boolean;
entries: any[], entries: any[];
}; }
export interface ISalesReceiptService { export interface ISalesReceiptService {
createSaleReceipt(tenantId: number, saleReceiptDTO: ISaleReceiptDTO): Promise<void>; createSaleReceipt(
tenantId: number,
saleReceiptDTO: ISaleReceiptDTO
): Promise<void>;
editSaleReceipt(tenantId: number, saleReceiptId: number): Promise<void>; editSaleReceipt(tenantId: number, saleReceiptId: number): Promise<void>;
deleteSaleReceipt(tenantId: number, saleReceiptId: number): Promise<void>; deleteSaleReceipt(tenantId: number, saleReceiptId: number): Promise<void>;
salesReceiptsList(tennatid: number, salesReceiptsFilter: ISalesReceiptsFilter): Promise<{ salesReceipts: ISaleReceipt[], pagination: IPaginationMeta, filterMeta: IFilterMeta }>;
}; salesReceiptsList(
tennatid: number,
salesReceiptsFilter: ISalesReceiptsFilter
): Promise<{
salesReceipts: ISaleReceipt[];
pagination: IPaginationMeta;
filterMeta: IFilterMeta;
}>;
}

View File

@@ -29,7 +29,6 @@ export default class ComputeItemCostJob {
/** /**
* The job handler. * The job handler.
* @param {} -
*/ */
public async handler(job, done: Function): Promise<void> { public async handler(job, done: Function): Promise<void> {
const Logger = Container.get('logger'); const Logger = Container.get('logger');
@@ -51,7 +50,6 @@ export default class ComputeItemCostJob {
/** /**
* Handle the job started. * Handle the job started.
* @param {Job} job - .
*/ */
async onJobStart(job) { async onJobStart(job) {
const { startingDate, itemId, tenantId } = job.attrs.data; const { startingDate, itemId, tenantId } = job.attrs.data;

View File

@@ -12,15 +12,22 @@ export default class MailNotificationSubscribeEnd {
const subscriptionService = Container.get(SubscriptionService); const subscriptionService = Container.get(SubscriptionService);
const Logger = Container.get('logger'); const Logger = Container.get('logger');
Logger.info(`Send mail notification subscription end soon - started: ${job.attrs.data}`); Logger.info(
`Send mail notification subscription end soon - started: ${job.attrs.data}`
);
try { try {
subscriptionService.mailMessages.sendRemainingTrialPeriod( subscriptionService.mailMessages.sendRemainingTrialPeriod(
phoneNumber, remainingDays, phoneNumber,
remainingDays
);
Logger.info(
`Send mail notification subscription end soon - finished: ${job.attrs.data}`
); );
Logger.info(`Send mail notification subscription end soon - finished: ${job.attrs.data}`);
} catch (error) { } catch (error) {
Logger.info(`Send mail notification subscription end soon - failed: ${job.attrs.data}, error: ${e}`); Logger.info(
`Send mail notification subscription end soon - failed: ${job.attrs.data}, error: ${e}`
);
done(e); done(e);
} }
} }

View File

@@ -12,15 +12,22 @@ export default class MailNotificationTrialEnd {
const subscriptionService = Container.get(SubscriptionService); const subscriptionService = Container.get(SubscriptionService);
const Logger = Container.get('logger'); const Logger = Container.get('logger');
Logger.info(`Send mail notification subscription end soon - started: ${job.attrs.data}`); Logger.info(
`Send mail notification subscription end soon - started: ${job.attrs.data}`
);
try { try {
subscriptionService.mailMessages.sendRemainingTrialPeriod( subscriptionService.mailMessages.sendRemainingTrialPeriod(
phoneNumber, remainingDays, phoneNumber,
remainingDays
);
Logger.info(
`Send mail notification subscription end soon - finished: ${job.attrs.data}`
); );
Logger.info(`Send mail notification subscription end soon - finished: ${job.attrs.data}`);
} catch (error) { } catch (error) {
Logger.info(`Send mail notification subscription end soon - failed: ${job.attrs.data}, error: ${e}`); Logger.info(
`Send mail notification subscription end soon - failed: ${job.attrs.data}, error: ${e}`
);
done(e); done(e);
} }
} }

View File

@@ -5,13 +5,28 @@ import 'subscribers/organization';
import 'subscribers/inviteUser'; import 'subscribers/inviteUser';
import 'subscribers/manualJournals'; import 'subscribers/manualJournals';
import 'subscribers/expenses'; import 'subscribers/expenses';
import 'subscribers/bills';
import 'subscribers/saleInvoices'; import 'subscribers/Bills';
import 'subscribers/Bills/SyncItemsQuantity';
import 'subscribers/Bills/SyncVendorsBalances';
import 'subscribers/Bills/WriteJournalEntries';
import 'subscribers/Bills/WriteInventoryTransactions';
import 'subscribers/SaleInvoices';
import 'subscribers/SaleInvoices/SyncCustomersBalance';
import 'subscribers/SaleInvoices/SyncItemsQuantity';
import 'subscribers/SaleInvoices/WriteInventoryTransactions';
import 'subscribers/SaleInvoices/WriteJournalEntries';
import 'subscribers/SaleReceipt';
import 'subscribers/SaleReceipt/SyncItemsQuantity';
import 'subscribers/SaleReceipt/WriteInventoryTransactions';
import 'subscribers/SaleReceipt/WriteJournalEntries';
import 'subscribers/customers'; import 'subscribers/customers';
import 'subscribers/vendors'; import 'subscribers/vendors';
import 'subscribers/paymentMades'; import 'subscribers/paymentMades';
import 'subscribers/paymentReceives'; import 'subscribers/paymentReceives';
import 'subscribers/saleEstimates'; import 'subscribers/saleEstimates';
import 'subscribers/saleReceipts';
import 'subscribers/inventory'; import 'subscribers/inventory';
import 'subscribers/items'; import 'subscribers/items';

View File

@@ -129,8 +129,6 @@ export default class Vendor extends TenantModel {
{ key: 'unpaid', label: 'Unpaid' }, { key: 'unpaid', label: 'Unpaid' },
], ],
query: (query, role) => { query: (query, role) => {
console.log(role);
switch(role.value) { switch(role.value) {
case 'active': case 'active':
query.modify('active'); query.modify('active');

View File

@@ -1,6 +1,6 @@
import { sumBy, chain } from 'lodash'; import { sumBy, chain } from 'lodash';
import moment, { LongDateFormatKey } from 'moment'; import moment, { LongDateFormatKey } from 'moment';
import { IBill, IManualJournalEntry, ISystemUser } from 'interfaces'; import { IBill, IManualJournalEntry, ISaleReceipt, ISystemUser } from 'interfaces';
import JournalPoster from './JournalPoster'; import JournalPoster from './JournalPoster';
import JournalEntry from './JournalEntry'; import JournalEntry from './JournalEntry';
import { AccountTransaction } from 'models'; import { AccountTransaction } from 'models';
@@ -425,4 +425,51 @@ export default class JournalCommands {
} }
); );
} }
/**
* Writes the sale invoice income journal entries.
* -----
* - Deposit account -> Debit -> XXXX
* - Income -> Credit -> XXXX
*
* @param {ISaleInvoice} saleInvoice
* @param {number} receivableAccountsId
* @param {number} authorizedUserId
*/
async saleReceiptIncomeEntries(
saleReceipt: ISaleReceipt & {
entries: IItemEntry & { item: IItem };
},
): Promise<void> {
const commonEntry = {
referenceType: 'SaleReceipt',
referenceId: saleReceipt.id,
date: saleReceipt.receiptDate,
userId: saleReceipt.userId,
};
// XXX Debit - Deposit account.
const depositEntry = new JournalEntry({
...commonEntry,
debit: saleReceipt.amount,
account: saleReceipt.depositAccountId,
index: 1,
});
this.journal.debit(depositEntry);
saleReceipt.entries.forEach(
(entry: IItemEntry & { item: IItem }, index: number) => {
const income: number = entry.quantity * entry.rate;
// XXX Credit - Income account.
const incomeEntry = new JournalEntry({
...commonEntry,
credit: income,
account: entry.item.sellAccountId,
note: entry.description,
index: index + 2,
});
this.journal.credit(incomeEntry);
}
);
}
} }

View File

@@ -8,13 +8,15 @@ import {
import { import {
IInventoryLotCost, IInventoryLotCost,
IInventoryTransaction, IInventoryTransaction,
IItem, TInventoryTransactionDirection,
IItemEntry, IItemEntry,
IItemEntryTransactionType,
} from 'interfaces'; } from 'interfaces';
import InventoryAverageCost from 'services/Inventory/InventoryAverageCost'; import InventoryAverageCost from 'services/Inventory/InventoryAverageCost';
import InventoryCostLotTracker from 'services/Inventory/InventoryCostLotTracker'; import InventoryCostLotTracker from 'services/Inventory/InventoryCostLotTracker';
import TenancyService from 'services/Tenancy/TenancyService'; import TenancyService from 'services/Tenancy/TenancyService';
import events from 'subscribers/events'; import events from 'subscribers/events';
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
type TCostMethod = 'FIFO' | 'LIFO' | 'AVG'; type TCostMethod = 'FIFO' | 'LIFO' | 'AVG';
@@ -26,22 +28,23 @@ export default class InventoryService {
@EventDispatcher() @EventDispatcher()
eventDispatcher: EventDispatcherInterface; eventDispatcher: EventDispatcherInterface;
@Inject()
itemsEntriesService: ItemsEntriesService;
/** /**
* Transforms the items entries to inventory transactions. * Transforms the items entries to inventory transactions.
*/ */
transformItemEntriesToInventory( transformItemEntriesToInventory(
itemEntries: IItemEntry[], itemEntries: IItemEntry[],
transactionType: string, direction: TInventoryTransactionDirection,
transactionId: number,
direction: 'IN' | 'OUT',
date: Date | string, date: Date | string,
lotNumber: number lotNumber: number
) { ) {
return itemEntries.map((entry: IItemEntry) => ({ return itemEntries.map((entry: IItemEntry) => ({
...pick(entry, ['itemId', 'quantity', 'rate']), ...pick(entry, ['itemId', 'quantity', 'rate']),
lotNumber, lotNumber,
transactionType, transactionType: entry.referenceType,
transactionId, transactionId: entry.referenceId,
direction, direction,
date, date,
entryId: entry.id, entryId: entry.id,
@@ -130,7 +133,6 @@ export default class InventoryService {
tenantId, tenantId,
} }
); );
// Triggers `onComputeItemCostJobScheduled` event. // Triggers `onComputeItemCostJobScheduled` event.
await this.eventDispatcher.dispatch( await this.eventDispatcher.dispatch(
events.inventory.onComputeItemCostJobScheduled, events.inventory.onComputeItemCostJobScheduled,
@@ -157,10 +159,12 @@ export default class InventoryService {
} }
/** /**
* Writes the inventory transactiosn on the storage from the given
* inventory transactions entries.
* *
* @param {number} tenantId * @param {number} tenantId -
* @param {IInventoryTransaction} inventoryEntry * @param {IInventoryTransaction} inventoryEntry -
* @param {boolean} deleteOld * @param {boolean} deleteOld -
*/ */
async recordInventoryTransaction( async recordInventoryTransaction(
tenantId: number, tenantId: number,
@@ -182,24 +186,104 @@ export default class InventoryService {
}); });
} }
/**
* Records the inventory transactions from items entries that have (inventory) type.
*
* @param {number} tenantId
* @param {number} transactionId
* @param {string} transactionType
* @param {Date|string} transactionDate
* @param {boolean} override
*/
async recordInventoryTransactionsFromItemsEntries(
tenantId: number,
transactionId: number,
transactionType: IItemEntryTransactionType,
transactionDate: Date | string,
transactionDirection: TInventoryTransactionDirection,
override: boolean = false
): Promise<void> {
// Gets the next inventory lot number.
const lotNumber = this.getNextLotNumber(tenantId);
// Loads the inventory items entries of the given sale invoice.
const inventoryEntries = await this.itemsEntriesService.getInventoryEntries(
tenantId,
transactionType,
transactionId
);
// Can't continue if there is no entries has inventory items in the invoice.
if (inventoryEntries.length <= 0) {
return;
}
// Inventory transactions.
const inventoryTranscations = this.transformItemEntriesToInventory(
inventoryEntries,
transactionDirection,
transactionDate,
lotNumber
);
// Records the inventory transactions of the given sale invoice.
await this.recordInventoryTransactions(
tenantId,
inventoryTranscations,
override
);
// Increment and save the next lot number settings.
await this.incrementNextLotNumber(tenantId);
// Triggers `onInventoryTransactionsCreated` event.
this.eventDispatcher.dispatch(
events.inventory.onInventoryTransactionsCreated,
{
tenantId,
inventoryEntries,
transactionId,
transactionType,
transactionDate,
transactionDirection,
override,
}
);
}
/** /**
* Deletes the given inventory transactions. * Deletes the given inventory transactions.
* @param {number} tenantId - Tenant id. * @param {number} tenantId - Tenant id.
* @param {string} transactionType * @param {string} transactionType
* @param {number} transactionId * @param {number} transactionId
* @return {Promise} * @return {Promise<{
* oldInventoryTransactions: IInventoryTransaction[]
* }>}
*/ */
async deleteInventoryTransactions( async deleteInventoryTransactions(
tenantId: number, tenantId: number,
transactionId: number, transactionId: number,
transactionType: string transactionType: string
): Promise<void> { ): Promise<{ oldInventoryTransactions: IInventoryTransaction[] }> {
const { InventoryTransaction } = this.tenancy.models(tenantId); const { inventoryTransactionRepository } = this.tenancy.repositories(tenantId);
await InventoryTransaction.query() // Retrieve the inventory transactions of the given sale invoice.
.where('transaction_type', transactionType) const oldInventoryTransactions = await inventoryTransactionRepository.find({
.where('transaction_id', transactionId) transactionId,
.delete(); transactionType,
});
// Deletes the inventory transactions by the given transaction type and id.
await inventoryTransactionRepository.deleteBy({
transactionType,
transactionId,
});
// Triggers `onInventoryTransactionsDeleted` event.
this.eventDispatcher.dispatch(
events.inventory.onInventoryTransactionsDeleted,
{
tenantId,
oldInventoryTransactions,
transactionId,
transactionType,
}
);
return { oldInventoryTransactions };
} }
/** /**

View File

@@ -1,4 +1,4 @@
import { omit, sumBy, pick, map } from 'lodash'; import { omit, sumBy } from 'lodash';
import moment from 'moment'; import moment from 'moment';
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import { import {
@@ -16,19 +16,17 @@ import { formatDateFields } from 'utils';
import { import {
IBillDTO, IBillDTO,
IBill, IBill,
IItem,
ISystemUser, ISystemUser,
IBillEditDTO, IBillEditDTO,
IPaginationMeta, IPaginationMeta,
IFilterMeta, IFilterMeta,
IBillsFilter, IBillsFilter,
IItemEntry,
IInventoryTransaction,
} from 'interfaces'; } from 'interfaces';
import { ServiceError } from 'exceptions'; import { ServiceError } from 'exceptions';
import ItemsService from 'services/Items/ItemsService'; import ItemsService from 'services/Items/ItemsService';
import ItemsEntriesService from 'services/Items/ItemsEntriesService'; import ItemsEntriesService from 'services/Items/ItemsEntriesService';
import JournalCommands from 'services/Accounting/JournalCommands'; import JournalCommands from 'services/Accounting/JournalCommands';
import JournalPosterService from 'services/Sales/JournalPosterService';
const ERRORS = { const ERRORS = {
BILL_NOT_FOUND: 'BILL_NOT_FOUND', BILL_NOT_FOUND: 'BILL_NOT_FOUND',
@@ -71,6 +69,9 @@ export default class BillsService extends SalesInvoicesCost {
@Inject() @Inject()
itemsEntriesService: ItemsEntriesService; itemsEntriesService: ItemsEntriesService;
@Inject()
journalPosterService: JournalPosterService;
/** /**
* Validates whether the vendor is exist. * Validates whether the vendor is exist.
* @async * @async
@@ -362,106 +363,6 @@ export default class BillsService extends SalesInvoicesCost {
}); });
} }
/**
* Records the inventory transactions from the given bill input.
* @param {Bill} bill - Bill model object.
* @param {number} billId - Bill id.
* @return {Promise<void>}
*/
public async recordInventoryTransactions(
tenantId: number,
billId: number,
billDate: Date,
override?: boolean
): Promise<void> {
// Retrieve the next inventory lot number.
const lotNumber = this.inventoryService.getNextLotNumber(tenantId);
const inventoryEntries = await this.itemsEntriesService.getInventoryEntries(
tenantId,
'Bill',
billId
);
// Can't continue if there is no entries has inventory items in the bill.
if (inventoryEntries.length <= 0) return;
// Inventory transactions.
const inventoryTranscations = this.inventoryService.transformItemEntriesToInventory(
inventoryEntries,
'Bill',
billId,
'IN',
billDate,
lotNumber
);
// Records the inventory transactions.
await this.inventoryService.recordInventoryTransactions(
tenantId,
inventoryTranscations,
override
);
// Save the next lot number settings.
await this.inventoryService.incrementNextLotNumber(tenantId);
// Triggers `onInventoryTransactionsCreated` event.
this.eventDispatcher.dispatch(events.bill.onInventoryTransactionsCreated, {
tenantId,
billId,
billDate,
});
}
/**
* Reverts the inventory transactions of the given bill id.
* @param {number} tenantId - Tenant id.
* @param {number} billId - Bill id.
* @return {Promise<void>}
*/
public async revertInventoryTransactions(tenantId: number, billId: number) {
const { inventoryTransactionRepository } = this.tenancy.repositories(
tenantId
);
// Retrieve the inventory transactions of the given sale invoice.
const oldInventoryTransactions = await inventoryTransactionRepository.find({
transactionId: billId,
transactionType: 'Bill',
});
await this.inventoryService.deleteInventoryTransactions(
tenantId,
billId,
'Bill'
);
// Triggers 'onInventoryTransactionsDeleted' event.
this.eventDispatcher.dispatch(
events.bill.onInventoryTransactionsDeleted,
{ tenantId, billId, oldInventoryTransactions }
);
}
/**
* Records the bill journal transactions.
* @async
* @param {IBill} bill
* @param {Integer} billId
*/
public async recordJournalTransactions(
tenantId: number,
bill: IBill,
override: boolean = false
) {
const journal = new JournalPoster(tenantId);
const journalCommands = new JournalCommands(journal);
await journalCommands.bill(bill, override);
return Promise.all([
journal.deleteEntries(),
journal.saveEntries(),
journal.saveBalance(),
]);
}
/** /**
* Retrieve bills data table list. * Retrieve bills data table list.
* @param {number} tenantId - * @param {number} tenantId -
@@ -544,36 +445,6 @@ export default class BillsService extends SalesInvoicesCost {
return bill; return bill;
} }
/**
* Schedules compute bill items cost based on each item cost method.
* @param {number} tenantId -
* @param {IBill} bill -
* @return {Promise}
*/
public async scheduleComputeBillItemsCost(tenantId: number, billId: number) {
const { Item, Bill } = this.tenancy.models(tenantId);
// Retrieve the bill with associated entries.
const bill = await Bill.query()
.findById(billId)
.withGraphFetched('entries');
// Retrieves inventory items only.
const inventoryItems = await Item.query()
.whereIn('id', map(bill.entries, 'itemId'))
.where('type', 'inventory');
const inventoryItemsIds = map(inventoryItems, 'id');
if (inventoryItemsIds.length > 0) {
await this.scheduleComputeItemsCost(
tenantId,
inventoryItemsIds,
bill.billDate
);
}
}
/** /**
* Mark the bill as open. * Mark the bill as open.
* @param {number} tenantId * @param {number} tenantId
@@ -588,10 +459,89 @@ export default class BillsService extends SalesInvoicesCost {
if (oldBill.isOpen) { if (oldBill.isOpen) {
throw new ServiceError(ERRORS.BILL_ALREADY_OPEN); throw new ServiceError(ERRORS.BILL_ALREADY_OPEN);
} }
// Record the bill opened at on the storage. // Record the bill opened at on the storage.
await Bill.query().findById(billId).patch({ await Bill.query().findById(billId).patch({
openedAt: moment().toMySqlDateTime(), openedAt: moment().toMySqlDateTime(),
}); });
} }
/**
* Records the inventory transactions from the given bill input.
* @param {Bill} bill - Bill model object.
* @param {number} billId - Bill id.
* @return {Promise<void>}
*/
public async recordInventoryTransactions(
tenantId: number,
bill: IBill,
override?: boolean
): Promise<void> {
await this.inventoryService.recordInventoryTransactionsFromItemsEntries(
tenantId,
bill.id,
'Bill',
bill.billDate,
'IN',
override
);
// Triggers `onInventoryTransactionsCreated` event.
this.eventDispatcher.dispatch(events.bill.onInventoryTransactionsCreated, {
tenantId,
bill,
});
}
/**
* Reverts the inventory transactions of the given bill id.
* @param {number} tenantId - Tenant id.
* @param {number} billId - Bill id.
* @return {Promise<void>}
*/
public async revertInventoryTransactions(tenantId: number, billId: number) {
// Deletes the inventory transactions by the given reference id and type.
await this.inventoryService.deleteInventoryTransactions(
tenantId,
billId,
'Bill'
);
}
/**
* Records the bill journal transactions.
* @async
* @param {IBill} bill
* @param {Integer} billId
*/
public async recordJournalTransactions(
tenantId: number,
bill: IBill,
override: boolean = false
) {
const journal = new JournalPoster(tenantId);
const journalCommands = new JournalCommands(journal);
await journalCommands.bill(bill, override);
return Promise.all([
journal.deleteEntries(),
journal.saveEntries(),
journal.saveBalance(),
]);
}
/**
* Reverts the bill journal entries.
* @param {number} tenantId
* @param {number} billId
*/
public async revertJournalEntries(
tenantId: number,
billId: number
): Promise<void> {
await this.journalPosterService.revertJournalTransactions(
tenantId,
billId,
'Bill'
);
}
} }

View File

@@ -1,6 +1,12 @@
import { omit, sumBy } from 'lodash'; import { omit, sumBy } from 'lodash';
import { Service, Inject } from 'typedi'; import { Service, Inject } from 'typedi';
import { IEstimatesFilter, IFilterMeta, IPaginationMeta, ISaleEstimate, ISaleEstimateDTO } from 'interfaces'; import {
IEstimatesFilter,
IFilterMeta,
IPaginationMeta,
ISaleEstimate,
ISaleEstimateDTO,
} from 'interfaces';
import { import {
EventDispatcher, EventDispatcher,
EventDispatcherInterface, EventDispatcherInterface,
@@ -14,7 +20,6 @@ import { ServiceError } from 'exceptions';
import CustomersService from 'services/Contacts/CustomersService'; import CustomersService from 'services/Contacts/CustomersService';
import moment from 'moment'; import moment from 'moment';
const ERRORS = { const ERRORS = {
SALE_ESTIMATE_NOT_FOUND: 'SALE_ESTIMATE_NOT_FOUND', SALE_ESTIMATE_NOT_FOUND: 'SALE_ESTIMATE_NOT_FOUND',
CUSTOMER_NOT_FOUND: 'CUSTOMER_NOT_FOUND', CUSTOMER_NOT_FOUND: 'CUSTOMER_NOT_FOUND',
@@ -24,8 +29,9 @@ const ERRORS = {
SALE_ESTIMATE_CONVERTED_TO_INVOICE: 'SALE_ESTIMATE_CONVERTED_TO_INVOICE', SALE_ESTIMATE_CONVERTED_TO_INVOICE: 'SALE_ESTIMATE_CONVERTED_TO_INVOICE',
SALE_ESTIMATE_ALREADY_REJECTED: 'SALE_ESTIMATE_ALREADY_REJECTED', SALE_ESTIMATE_ALREADY_REJECTED: 'SALE_ESTIMATE_ALREADY_REJECTED',
SALE_ESTIMATE_ALREADY_APPROVED: 'SALE_ESTIMATE_ALREADY_APPROVED', SALE_ESTIMATE_ALREADY_APPROVED: 'SALE_ESTIMATE_ALREADY_APPROVED',
SALE_ESTIMATE_NOT_DELIVERED: 'SALE_ESTIMATE_NOT_DELIVERED' SALE_ESTIMATE_NOT_DELIVERED: 'SALE_ESTIMATE_NOT_DELIVERED',
}; };
/** /**
* Sale estimate service. * Sale estimate service.
* @Service * @Service
@@ -57,7 +63,9 @@ export default class SaleEstimateService {
*/ */
async getSaleEstimateOrThrowError(tenantId: number, saleEstimateId: number) { async getSaleEstimateOrThrowError(tenantId: number, saleEstimateId: number) {
const { SaleEstimate } = this.tenancy.models(tenantId); const { SaleEstimate } = this.tenancy.models(tenantId);
const foundSaleEstimate = await SaleEstimate.query().findById(saleEstimateId); const foundSaleEstimate = await SaleEstimate.query().findById(
saleEstimateId
);
if (!foundSaleEstimate) { if (!foundSaleEstimate) {
throw new ServiceError(ERRORS.SALE_ESTIMATE_NOT_FOUND); throw new ServiceError(ERRORS.SALE_ESTIMATE_NOT_FOUND);
@@ -71,7 +79,11 @@ export default class SaleEstimateService {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
async validateEstimateNumberExistance(tenantId: number, estimateNumber: string, notEstimateId?: number) { async validateEstimateNumberExistance(
tenantId: number,
estimateNumber: string,
notEstimateId?: number
) {
const { SaleEstimate } = this.tenancy.models(tenantId); const { SaleEstimate } = this.tenancy.models(tenantId);
const foundSaleEstimate = await SaleEstimate.query() const foundSaleEstimate = await SaleEstimate.query()
@@ -96,24 +108,25 @@ export default class SaleEstimateService {
transformDTOToModel( transformDTOToModel(
tenantId: number, tenantId: number,
estimateDTO: ISaleEstimateDTO, estimateDTO: ISaleEstimateDTO,
oldSaleEstimate?: ISaleEstimate, oldSaleEstimate?: ISaleEstimate
): ISaleEstimate { ): ISaleEstimate {
const { ItemEntry } = this.tenancy.models(tenantId); const { ItemEntry } = this.tenancy.models(tenantId);
const amount = sumBy(estimateDTO.entries, e => ItemEntry.calcAmount(e)); const amount = sumBy(estimateDTO.entries, (e) => ItemEntry.calcAmount(e));
return { return {
amount, amount,
...formatDateFields( ...formatDateFields(omit(estimateDTO, ['delivered', 'entries']), [
omit(estimateDTO, ['delivered', 'entries']), 'estimateDate',
['estimateDate', 'expirationDate'] 'expirationDate',
), ]),
entries: estimateDTO.entries.map((entry) => ({ entries: estimateDTO.entries.map((entry) => ({
reference_type: 'SaleEstimate', reference_type: 'SaleEstimate',
...omit(entry, ['total', 'amount', 'id']), ...omit(entry, ['total', 'amount', 'id']),
})), })),
// Avoid rewrite the deliver date in edit mode when already published. // Avoid rewrite the deliver date in edit mode when already published.
...(estimateDTO.delivered && (!oldSaleEstimate?.deliveredAt)) && ({ ...(estimateDTO.delivered &&
!oldSaleEstimate?.deliveredAt && {
deliveredAt: moment().toMySqlDateTime(), deliveredAt: moment().toMySqlDateTime(),
}), }),
}; };
@@ -139,23 +152,35 @@ export default class SaleEstimateService {
// Validate estimate number uniquiness on the storage. // Validate estimate number uniquiness on the storage.
if (estimateDTO.estimateNumber) { if (estimateDTO.estimateNumber) {
await this.validateEstimateNumberExistance(tenantId, estimateDTO.estimateNumber); await this.validateEstimateNumberExistance(
tenantId,
estimateDTO.estimateNumber
);
} }
// Retrieve the given customer or throw not found service error. // Retrieve the given customer or throw not found service error.
await this.customersService.getCustomer(tenantId, estimateDTO.customerId); await this.customersService.getCustomer(tenantId, estimateDTO.customerId);
// Validate items IDs existance on the storage. // Validate items IDs existance on the storage.
await this.itemsEntriesService.validateItemsIdsExistance(tenantId, estimateDTO.entries); await this.itemsEntriesService.validateItemsIdsExistance(
tenantId,
estimateDTO.entries
);
// Validate non-sellable items. // Validate non-sellable items.
await this.itemsEntriesService.validateNonSellableEntriesItems(tenantId, estimateDTO.entries); await this.itemsEntriesService.validateNonSellableEntriesItems(
tenantId,
estimateDTO.entries
);
const saleEstimate = await SaleEstimate.query() const saleEstimate = await SaleEstimate.query().upsertGraphAndFetch({
.upsertGraphAndFetch({ ...estimateObj }); ...estimateObj,
});
this.logger.info('[sale_estimate] insert sale estimated success.'); this.logger.info('[sale_estimate] insert sale estimated success.');
await this.eventDispatcher.dispatch(events.saleEstimate.onCreated, { await this.eventDispatcher.dispatch(events.saleEstimate.onCreated, {
tenantId, saleEstimate, saleEstimateId: saleEstimate.id, tenantId,
saleEstimate,
saleEstimateId: saleEstimate.id,
}); });
return saleEstimate; return saleEstimate;
@@ -175,38 +200,61 @@ export default class SaleEstimateService {
estimateDTO: ISaleEstimateDTO estimateDTO: ISaleEstimateDTO
): Promise<ISaleEstimate> { ): Promise<ISaleEstimate> {
const { SaleEstimate } = this.tenancy.models(tenantId); const { SaleEstimate } = this.tenancy.models(tenantId);
const oldSaleEstimate = await this.getSaleEstimateOrThrowError(tenantId, estimateId); const oldSaleEstimate = await this.getSaleEstimateOrThrowError(
tenantId,
estimateId
);
// Transform DTO object ot model object. // Transform DTO object ot model object.
const estimateObj = this.transformDTOToModel(tenantId, estimateDTO, oldSaleEstimate); const estimateObj = this.transformDTOToModel(
tenantId,
estimateDTO,
oldSaleEstimate
);
// Validate estimate number uniquiness on the storage. // Validate estimate number uniquiness on the storage.
if (estimateDTO.estimateNumber) { if (estimateDTO.estimateNumber) {
await this.validateEstimateNumberExistance(tenantId, estimateDTO.estimateNumber, estimateId); await this.validateEstimateNumberExistance(
tenantId,
estimateDTO.estimateNumber,
estimateId
);
} }
// Retrieve the given customer or throw not found service error. // Retrieve the given customer or throw not found service error.
await this.customersService.getCustomer(tenantId, estimateDTO.customerId); await this.customersService.getCustomer(tenantId, estimateDTO.customerId);
// Validate sale estimate entries existance. // Validate sale estimate entries existance.
await this.itemsEntriesService.validateEntriesIdsExistance(tenantId, estimateId, 'SaleEstiamte', estimateDTO.entries); await this.itemsEntriesService.validateEntriesIdsExistance(
tenantId,
estimateId,
'SaleEstiamte',
estimateDTO.entries
);
// Validate items IDs existance on the storage. // Validate items IDs existance on the storage.
await this.itemsEntriesService.validateItemsIdsExistance(tenantId, estimateDTO.entries); await this.itemsEntriesService.validateItemsIdsExistance(
tenantId,
estimateDTO.entries
);
// Validate non-sellable items. // Validate non-sellable items.
await this.itemsEntriesService.validateNonSellableEntriesItems(tenantId, estimateDTO.entries); await this.itemsEntriesService.validateNonSellableEntriesItems(
tenantId,
estimateDTO.entries
);
this.logger.info('[sale_estimate] editing sale estimate on the storage.'); this.logger.info('[sale_estimate] editing sale estimate on the storage.');
const saleEstimate = await SaleEstimate.query() const saleEstimate = await SaleEstimate.query().upsertGraphAndFetch({
.upsertGraphAndFetch({
id: estimateId, id: estimateId,
...estimateObj ...estimateObj,
}); });
await this.eventDispatcher.dispatch(events.saleEstimate.onEdited, { await this.eventDispatcher.dispatch(events.saleEstimate.onEdited, {
tenantId, estimateId, saleEstimate, oldSaleEstimate, tenantId,
estimateId,
saleEstimate,
oldSaleEstimate,
});
this.logger.info('[sale_estiamte] edited successfully', {
tenantId,
estimateId,
}); });
this.logger.info('[sale_estiamte] edited successfully', { tenantId, estimateId });
return saleEstimate; return saleEstimate;
} }
@@ -218,28 +266,41 @@ export default class SaleEstimateService {
* @param {IEstimate} estimateId * @param {IEstimate} estimateId
* @return {void} * @return {void}
*/ */
public async deleteEstimate(tenantId: number, estimateId: number): Promise<void> { public async deleteEstimate(
tenantId: number,
estimateId: number
): Promise<void> {
const { SaleEstimate, ItemEntry } = this.tenancy.models(tenantId); const { SaleEstimate, ItemEntry } = this.tenancy.models(tenantId);
// Retrieve sale estimate or throw not found service error. // Retrieve sale estimate or throw not found service error.
const oldSaleEstimate = await this.getSaleEstimateOrThrowError(tenantId, estimateId); const oldSaleEstimate = await this.getSaleEstimateOrThrowError(
tenantId,
estimateId
);
// Throw error if the sale estimate converted to sale invoice. // Throw error if the sale estimate converted to sale invoice.
if (oldSaleEstimate.convertedToInvoiceId) { if (oldSaleEstimate.convertedToInvoiceId) {
throw new ServiceError(ERRORS.SALE_ESTIMATE_CONVERTED_TO_INVOICE); throw new ServiceError(ERRORS.SALE_ESTIMATE_CONVERTED_TO_INVOICE);
} }
this.logger.info('[sale_estimate] delete sale estimate and associated entries from the storage.'); this.logger.info(
'[sale_estimate] delete sale estimate and associated entries from the storage.'
);
await ItemEntry.query() await ItemEntry.query()
.where('reference_id', estimateId) .where('reference_id', estimateId)
.where('reference_type', 'SaleEstimate') .where('reference_type', 'SaleEstimate')
.delete(); .delete();
await SaleEstimate.query().where('id', estimateId).delete(); await SaleEstimate.query().where('id', estimateId).delete();
this.logger.info('[sale_estimate] deleted successfully.', { tenantId, estimateId }); this.logger.info('[sale_estimate] deleted successfully.', {
tenantId,
estimateId,
});
await this.eventDispatcher.dispatch(events.saleEstimate.onDeleted, { await this.eventDispatcher.dispatch(events.saleEstimate.onDeleted, {
tenantId, saleEstimateId: estimateId, oldSaleEstimate, tenantId,
saleEstimateId: estimateId,
oldSaleEstimate,
}); });
} }
@@ -270,18 +331,25 @@ export default class SaleEstimateService {
public async estimatesList( public async estimatesList(
tenantId: number, tenantId: number,
estimatesFilter: IEstimatesFilter estimatesFilter: IEstimatesFilter
): Promise<{ salesEstimates: ISaleEstimate[], pagination: IPaginationMeta, filterMeta: IFilterMeta }> { ): Promise<{
salesEstimates: ISaleEstimate[];
pagination: IPaginationMeta;
filterMeta: IFilterMeta;
}> {
const { SaleEstimate } = this.tenancy.models(tenantId); const { SaleEstimate } = this.tenancy.models(tenantId);
const dynamicFilter = await this.dynamicListService.dynamicList(tenantId, SaleEstimate, estimatesFilter); const dynamicFilter = await this.dynamicListService.dynamicList(
tenantId,
SaleEstimate,
estimatesFilter
);
const { results, pagination } = await SaleEstimate.query().onBuild(builder => { const { results, pagination } = await SaleEstimate.query()
.onBuild((builder) => {
builder.withGraphFetched('customer'); builder.withGraphFetched('customer');
builder.withGraphFetched('entries'); builder.withGraphFetched('entries');
dynamicFilter.buildQuery()(builder); dynamicFilter.buildQuery()(builder);
}).pagination( })
estimatesFilter.page - 1, .pagination(estimatesFilter.page - 1, estimatesFilter.pageSize);
estimatesFilter.pageSize,
);
return { return {
salesEstimates: results, salesEstimates: results,
@@ -299,12 +367,15 @@ export default class SaleEstimateService {
async convertEstimateToInvoice( async convertEstimateToInvoice(
tenantId: number, tenantId: number,
estimateId: number, estimateId: number,
invoiceId: number, invoiceId: number
): Promise<void> { ): Promise<void> {
const { SaleEstimate } = this.tenancy.models(tenantId); const { SaleEstimate } = this.tenancy.models(tenantId);
// Retrieve details of the given sale estimate. // Retrieve details of the given sale estimate.
const saleEstimate = await this.getSaleEstimateOrThrowError(tenantId, estimateId); const saleEstimate = await this.getSaleEstimateOrThrowError(
tenantId,
estimateId
);
await SaleEstimate.query().where('id', estimateId).patch({ await SaleEstimate.query().where('id', estimateId).patch({
convertedToInvoiceId: invoiceId, convertedToInvoiceId: invoiceId,
@@ -320,13 +391,15 @@ export default class SaleEstimateService {
*/ */
async unlinkConvertedEstimateFromInvoice( async unlinkConvertedEstimateFromInvoice(
tenantId: number, tenantId: number,
invoiceId: number, invoiceId: number
): Promise<void> { ): Promise<void> {
const { SaleEstimate } = this.tenancy.models(tenantId); const { SaleEstimate } = this.tenancy.models(tenantId);
await SaleEstimate.query().where({ await SaleEstimate.query()
.where({
convertedToInvoiceId: invoiceId, convertedToInvoiceId: invoiceId,
}).patch({ })
.patch({
convertedToInvoiceId: null, convertedToInvoiceId: null,
convertedToInvoiceAt: null, convertedToInvoiceAt: null,
}); });
@@ -339,12 +412,15 @@ export default class SaleEstimateService {
*/ */
public async deliverSaleEstimate( public async deliverSaleEstimate(
tenantId: number, tenantId: number,
saleEstimateId: number, saleEstimateId: number
): Promise<void> { ): Promise<void> {
const { SaleEstimate } = this.tenancy.models(tenantId); const { SaleEstimate } = this.tenancy.models(tenantId);
// Retrieve details of the given sale estimate id. // Retrieve details of the given sale estimate id.
const saleEstimate = await this.getSaleEstimateOrThrowError(tenantId, saleEstimateId); const saleEstimate = await this.getSaleEstimateOrThrowError(
tenantId,
saleEstimateId
);
// Throws error in case the sale estimate already published. // Throws error in case the sale estimate already published.
if (saleEstimate.isDelivered) { if (saleEstimate.isDelivered) {
@@ -352,7 +428,7 @@ export default class SaleEstimateService {
} }
// Record the delivered at on the storage. // Record the delivered at on the storage.
await SaleEstimate.query().where('id', saleEstimateId).patch({ await SaleEstimate.query().where('id', saleEstimateId).patch({
deliveredAt: moment().toMySqlDateTime() deliveredAt: moment().toMySqlDateTime(),
}); });
} }
@@ -363,12 +439,15 @@ export default class SaleEstimateService {
*/ */
public async approveSaleEstimate( public async approveSaleEstimate(
tenantId: number, tenantId: number,
saleEstimateId: number, saleEstimateId: number
): Promise<void> { ): Promise<void> {
const { SaleEstimate } = this.tenancy.models(tenantId); const { SaleEstimate } = this.tenancy.models(tenantId);
// Retrieve details of the given sale estimate id. // Retrieve details of the given sale estimate id.
const saleEstimate = await this.getSaleEstimateOrThrowError(tenantId, saleEstimateId); const saleEstimate = await this.getSaleEstimateOrThrowError(
tenantId,
saleEstimateId
);
// Throws error in case the sale estimate still not delivered to customer. // Throws error in case the sale estimate still not delivered to customer.
if (!saleEstimate.isDelivered) { if (!saleEstimate.isDelivered) {
@@ -391,12 +470,15 @@ export default class SaleEstimateService {
*/ */
public async rejectSaleEstimate( public async rejectSaleEstimate(
tenantId: number, tenantId: number,
saleEstimateId: number, saleEstimateId: number
): Promise<void> { ): Promise<void> {
const { SaleEstimate } = this.tenancy.models(tenantId); const { SaleEstimate } = this.tenancy.models(tenantId);
// Retrieve details of the given sale estimate id. // Retrieve details of the given sale estimate id.
const saleEstimate = await this.getSaleEstimateOrThrowError(tenantId, saleEstimateId); const saleEstimate = await this.getSaleEstimateOrThrowError(
tenantId,
saleEstimateId
);
// Throws error in case the sale estimate still not delivered to customer. // Throws error in case the sale estimate still not delivered to customer.
if (!saleEstimate.isDelivered) { if (!saleEstimate.isDelivered) {

View File

@@ -341,7 +341,7 @@ export default class SaleInvoicesService {
} }
// Record the delivered at on the storage. // Record the delivered at on the storage.
await saleInvoiceRepository.update( await saleInvoiceRepository.update(
{ deliveredAt: moment().toMySqlDateTime(), }, { deliveredAt: moment().toMySqlDateTime() },
{ id: saleInvoiceId } { id: saleInvoiceId }
); );
// Triggers `onSaleInvoiceDelivered` event. // Triggers `onSaleInvoiceDelivered` event.
@@ -416,48 +416,17 @@ export default class SaleInvoicesService {
*/ */
public async recordInventoryTranscactions( public async recordInventoryTranscactions(
tenantId: number, tenantId: number,
saleInvoiceId: number, saleInvoice: ISaleInvoice,
saleInvoiceDate: Date,
override?: boolean override?: boolean
): Promise<void> { ): Promise<void> {
// Gets the next inventory lot number. await this.inventoryService.recordInventoryTransactionsFromItemsEntries(
const lotNumber = this.inventoryService.getNextLotNumber(tenantId);
// Loads the inventory items entries of the given sale invoice.
const inventoryEntries = await this.itemsEntriesService.getInventoryEntries(
tenantId, tenantId,
saleInvoice.id,
'SaleInvoice', 'SaleInvoice',
saleInvoiceId saleInvoice.invoiceDate,
);
// Can't continue if there is no entries has inventory items in the invoice.
if (inventoryEntries.length <= 0) return;
// Inventory transactions.
const inventoryTranscations = this.inventoryService.transformItemEntriesToInventory(
inventoryEntries,
'SaleInvoice',
saleInvoiceId,
'OUT', 'OUT',
saleInvoiceDate,
lotNumber
);
// Records the inventory transactions of the given sale invoice.
await this.inventoryService.recordInventoryTransactions(
tenantId,
inventoryTranscations,
override override
); );
// Increment and save the next lot number settings.
await this.inventoryService.incrementNextLotNumber(tenantId);
// Triggers `onInventoryTransactionsCreated` event.
await this.eventDispatcher.dispatch(
events.saleInvoice.onInventoryTransactionsCreated,
{
tenantId,
saleInvoiceId,
}
);
} }
/** /**
@@ -491,7 +460,7 @@ export default class SaleInvoicesService {
await Promise.all([ await Promise.all([
journal.deleteEntries(), journal.deleteEntries(),
journal.saveBalance(), journal.saveBalance(),
journal.saveEntries() journal.saveEntries(),
]); ]);
} }
@@ -505,25 +474,14 @@ export default class SaleInvoicesService {
tenantId: number, tenantId: number,
saleInvoiceId: number saleInvoiceId: number
): Promise<void> { ): Promise<void> {
const { inventoryTransactionRepository } = this.tenancy.repositories(
tenantId
);
// Retrieve the inventory transactions of the given sale invoice.
const oldInventoryTransactions = await inventoryTransactionRepository.find({
transactionId: saleInvoiceId,
transactionType: 'SaleInvoice',
});
// Delete the inventory transaction of the given sale invoice. // Delete the inventory transaction of the given sale invoice.
await this.inventoryService.deleteInventoryTransactions( const {
oldInventoryTransactions,
} = await this.inventoryService.deleteInventoryTransactions(
tenantId, tenantId,
saleInvoiceId, saleInvoiceId,
'SaleInvoice' 'SaleInvoice'
); );
// Triggers 'onInventoryTransactionsDeleted' event.
this.eventDispatcher.dispatch(
events.saleInvoice.onInventoryTransactionsDeleted,
{ tenantId, saleInvoiceId, oldInventoryTransactions }
);
} }
/** /**
@@ -545,8 +503,9 @@ export default class SaleInvoicesService {
/** /**
* Retrieve sale invoice with associated entries. * Retrieve sale invoice with associated entries.
* @async * @param {Number} saleInvoiceId -
* @param {Number} saleInvoiceId * @param {ISystemUser} authorizedUser -
* @return {Promise<ISaleInvoice>}
*/ */
public async getSaleInvoice( public async getSaleInvoice(
tenantId: number, tenantId: number,

View File

@@ -1,9 +1,8 @@
import { Container, Service, Inject } from 'typedi'; import { Container, Service, Inject } from 'typedi';
import { map } from 'lodash';
import JournalPoster from 'services/Accounting/JournalPoster'; import JournalPoster from 'services/Accounting/JournalPoster';
import InventoryService from 'services/Inventory/Inventory'; import InventoryService from 'services/Inventory/Inventory';
import TenancyService from 'services/Tenancy/TenancyService'; import TenancyService from 'services/Tenancy/TenancyService';
import { ISaleInvoice, IItemEntry, IInventoryLotCost, IItem } from 'interfaces'; import { IInventoryLotCost, IItem } from 'interfaces';
import JournalCommands from 'services/Accounting/JournalCommands'; import JournalCommands from 'services/Accounting/JournalCommands';
@Service() @Service()
@@ -39,86 +38,6 @@ export default class SaleInvoicesCost {
return Promise.all([...asyncOpers]); return Promise.all([...asyncOpers]);
} }
/**
* Schedules compute sale invoice items cost based on each item
* cost method.
* @param {number} tenantId - Tenant id.
* @param {ISaleInvoice} saleInvoiceId - Sale invoice id.
* @param {boolean} override - Allow to override old computes in edit mode.
* @return {Promise}
*/
async scheduleComputeCostByInvoiceId(
tenantId: number,
saleInvoiceId: number
) {
const { SaleInvoice } = this.tenancy.models(tenantId);
// Retrieve the sale invoice with associated entries.
const saleInvoice: ISaleInvoice = await SaleInvoice.query()
.findById(saleInvoiceId)
.withGraphFetched('entries');
// Schedule compute inventory items cost by the given invoice model object.
return this.scheduleComputeCostByEntries(
tenantId,
saleInvoice.entries,
saleInvoice.invoiceDate
);
}
/**
* Schedules the compute inventory items cost by the given bill id.
* @param {number} tenantId - Tenant id.
* @param {number} billId - Bill id.
* @return {Promise<void>}
*/
async scheduleComputeCostByBillId(
tenantId: number,
billId: number
): Promise<void> {
const { Bill } = this.tenancy.models(tenantId);
// Retrieve the bill with associated entries.
const bill = await Bill.query()
.findById(billId)
.withGraphFetched('entries');
return this.scheduleComputeCostByEntries(
tenantId,
bill.entries,
bill.billDate
);
}
/**
* Schedules the compute inventory items by the given invoice.
* @param {number} tenantId
* @param {ISaleInvoice & { entries: IItemEntry[] }} saleInvoice
* @param {boolean} override
*/
async scheduleComputeCostByEntries(
tenantId: number,
entries: IItemEntry[],
startingDate: Date
) {
const { Item } = this.tenancy.models(tenantId);
// Retrieve the inventory items that associated to the sale invoice entries.
const inventoryItems = await Item.query()
.whereIn('id', map(entries, 'itemId'))
.where('type', 'inventory');
const inventoryItemsIds = map(inventoryItems, 'id');
if (inventoryItemsIds.length > 0) {
await this.scheduleComputeCostByItemsIds(
tenantId,
inventoryItemsIds,
startingDate
);
}
}
/** /**
* Schedule writing journal entries. * Schedule writing journal entries.
* @param {Date} startingDate * @param {Date} startingDate

View File

@@ -6,7 +6,9 @@ import {
EventDispatcherInterface, EventDispatcherInterface,
} from 'decorators/eventDispatcher'; } from 'decorators/eventDispatcher';
import events from 'subscribers/events'; import events from 'subscribers/events';
import { ISaleReceipt, ISaleReceiptDTO } from 'interfaces'; import JournalPoster from 'services/Accounting/JournalPoster';
import JournalCommands from 'services/Accounting/JournalCommands';
import { ISaleReceipt, ISaleReceiptDTO, IItemEntry, IItem } from 'interfaces';
import JournalPosterService from 'services/Sales/JournalPosterService'; import JournalPosterService from 'services/Sales/JournalPosterService';
import TenancyService from 'services/Tenancy/TenancyService'; import TenancyService from 'services/Tenancy/TenancyService';
import { formatDateFields } from 'utils'; import { formatDateFields } from 'utils';
@@ -15,15 +17,16 @@ import DynamicListingService from 'services/DynamicListing/DynamicListService';
import { ServiceError } from 'exceptions'; import { ServiceError } from 'exceptions';
import ItemsEntriesService from 'services/Items/ItemsEntriesService'; import ItemsEntriesService from 'services/Items/ItemsEntriesService';
import { ItemEntry } from 'models'; import { ItemEntry } from 'models';
import InventoryService from 'services/Inventory/Inventory';
const ERRORS = { const ERRORS = {
SALE_RECEIPT_NOT_FOUND: 'SALE_RECEIPT_NOT_FOUND', SALE_RECEIPT_NOT_FOUND: 'SALE_RECEIPT_NOT_FOUND',
DEPOSIT_ACCOUNT_NOT_FOUND: 'DEPOSIT_ACCOUNT_NOT_FOUND', DEPOSIT_ACCOUNT_NOT_FOUND: 'DEPOSIT_ACCOUNT_NOT_FOUND',
DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET: 'DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET', DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET: 'DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET',
SALE_RECEIPT_NUMBER_NOT_UNIQUE: 'SALE_RECEIPT_NUMBER_NOT_UNIQUE', SALE_RECEIPT_NUMBER_NOT_UNIQUE: 'SALE_RECEIPT_NUMBER_NOT_UNIQUE',
SALE_RECEIPT_IS_ALREADY_CLOSED: 'SALE_RECEIPT_IS_ALREADY_CLOSED' SALE_RECEIPT_IS_ALREADY_CLOSED: 'SALE_RECEIPT_IS_ALREADY_CLOSED',
}; };
@Service() @Service()
export default class SalesReceiptService { export default class SalesReceiptService {
@Inject() @Inject()
@@ -38,6 +41,9 @@ export default class SalesReceiptService {
@Inject() @Inject()
itemsEntriesService: ItemsEntriesService; itemsEntriesService: ItemsEntriesService;
@Inject()
inventoryService: InventoryService;
@EventDispatcher() @EventDispatcher()
eventDispatcher: EventDispatcherInterface; eventDispatcher: EventDispatcherInterface;
@@ -52,11 +58,19 @@ export default class SalesReceiptService {
async getSaleReceiptOrThrowError(tenantId: number, saleReceiptId: number) { async getSaleReceiptOrThrowError(tenantId: number, saleReceiptId: number) {
const { SaleReceipt } = this.tenancy.models(tenantId); const { SaleReceipt } = this.tenancy.models(tenantId);
this.logger.info('[sale_receipt] trying to validate existance.', { tenantId, saleReceiptId }); this.logger.info('[sale_receipt] trying to validate existance.', {
const foundSaleReceipt = await SaleReceipt.query().findById(saleReceiptId); tenantId,
saleReceiptId,
});
const foundSaleReceipt = await SaleReceipt.query()
.findById(saleReceiptId)
.withGraphFetched('entries');
if (!foundSaleReceipt) { if (!foundSaleReceipt) {
this.logger.info('[sale_receipt] not found on the storage.', { tenantId, saleReceiptId }); this.logger.info('[sale_receipt] not found on the storage.', {
tenantId,
saleReceiptId,
});
throw new ServiceError(ERRORS.SALE_RECEIPT_NOT_FOUND); throw new ServiceError(ERRORS.SALE_RECEIPT_NOT_FOUND);
} }
return foundSaleReceipt; return foundSaleReceipt;
@@ -67,16 +81,27 @@ export default class SalesReceiptService {
* @param {number} tenantId - * @param {number} tenantId -
* @param {number} accountId - * @param {number} accountId -
*/ */
async validateReceiptDepositAccountExistance(tenantId: number, accountId: number) { async validateReceiptDepositAccountExistance(
const { accountRepository, accountTypeRepository } = this.tenancy.repositories(tenantId); tenantId: number,
accountId: number
) {
const {
accountRepository,
accountTypeRepository,
} = this.tenancy.repositories(tenantId);
const depositAccount = await accountRepository.findOneById(accountId); const depositAccount = await accountRepository.findOneById(accountId);
if (!depositAccount) { if (!depositAccount) {
throw new ServiceError(ERRORS.DEPOSIT_ACCOUNT_NOT_FOUND); throw new ServiceError(ERRORS.DEPOSIT_ACCOUNT_NOT_FOUND);
} }
const depositAccountType = await accountTypeRepository.findOneById(depositAccount.accountTypeId); const depositAccountType = await accountTypeRepository.findOneById(
depositAccount.accountTypeId
);
if (!depositAccountType || depositAccountType.childRoot === 'current_asset') { if (
!depositAccountType ||
depositAccountType.childRoot === 'current_asset'
) {
throw new ServiceError(ERRORS.DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET); throw new ServiceError(ERRORS.DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET);
} }
} }
@@ -87,10 +112,17 @@ export default class SalesReceiptService {
* @param {string} receiptNumber - * @param {string} receiptNumber -
* @param {number} notReceiptId - * @param {number} notReceiptId -
*/ */
async validateReceiptNumberUnique(tenantId: number, receiptNumber: string, notReceiptId?: number) { async validateReceiptNumberUnique(
tenantId: number,
receiptNumber: string,
notReceiptId?: number
) {
const { SaleReceipt } = this.tenancy.models(tenantId); const { SaleReceipt } = this.tenancy.models(tenantId);
this.logger.info('[sale_receipt] validate receipt number uniquiness.', { tenantId, receiptNumber }); this.logger.info('[sale_receipt] validate receipt number uniquiness.', {
tenantId,
receiptNumber,
});
const saleReceipt = await SaleReceipt.query() const saleReceipt = await SaleReceipt.query()
.findOne('receipt_number', receiptNumber) .findOne('receipt_number', receiptNumber)
.onBuild((builder) => { .onBuild((builder) => {
@@ -100,7 +132,9 @@ export default class SalesReceiptService {
}); });
if (saleReceipt) { if (saleReceipt) {
this.logger.info('[sale_receipt] sale receipt number not unique.', { tenantId }); this.logger.info('[sale_receipt] sale receipt number not unique.', {
tenantId,
});
throw new ServiceError(ERRORS.SALE_RECEIPT_NUMBER_NOT_UNIQUE); throw new ServiceError(ERRORS.SALE_RECEIPT_NUMBER_NOT_UNIQUE);
} }
} }
@@ -115,16 +149,18 @@ export default class SalesReceiptService {
saleReceiptDTO: ISaleReceiptDTO, saleReceiptDTO: ISaleReceiptDTO,
oldSaleReceipt?: ISaleReceipt oldSaleReceipt?: ISaleReceipt
): ISaleReceipt { ): ISaleReceipt {
const amount = sumBy(saleReceiptDTO.entries, e => ItemEntry.calcAmount(e)); const amount = sumBy(saleReceiptDTO.entries, (e) =>
ItemEntry.calcAmount(e)
);
return { return {
amount, amount,
...formatDateFields( ...formatDateFields(omit(saleReceiptDTO, ['closed', 'entries']), [
omit(saleReceiptDTO, ['closed', 'entries']), 'receiptDate',
['receiptDate'] ]),
),
// Avoid rewrite the deliver date in edit mode when already published. // Avoid rewrite the deliver date in edit mode when already published.
...(saleReceiptDTO.closed && (!oldSaleReceipt?.closedAt)) && ({ ...(saleReceiptDTO.closed &&
!oldSaleReceipt?.closedAt && {
closedAt: moment().toMySqlDateTime(), closedAt: moment().toMySqlDateTime(),
}), }),
entries: saleReceiptDTO.entries.map((entry) => ({ entries: saleReceiptDTO.entries.map((entry) => ({
@@ -140,31 +176,56 @@ export default class SalesReceiptService {
* @param {ISaleReceipt} saleReceipt * @param {ISaleReceipt} saleReceipt
* @return {Object} * @return {Object}
*/ */
public async createSaleReceipt(tenantId: number, saleReceiptDTO: any): Promise<ISaleReceipt> { public async createSaleReceipt(
tenantId: number,
saleReceiptDTO: any
): Promise<ISaleReceipt> {
const { SaleReceipt } = this.tenancy.models(tenantId); const { SaleReceipt } = this.tenancy.models(tenantId);
// Transform sale receipt DTO to model. // Transform sale receipt DTO to model.
const saleReceiptObj = this.transformObjectDTOToModel(saleReceiptDTO); const saleReceiptObj = this.transformObjectDTOToModel(saleReceiptDTO);
// Validate receipt deposit account existance and type. // Validate receipt deposit account existance and type.
await this.validateReceiptDepositAccountExistance(tenantId, saleReceiptDTO.depositAccountId); await this.validateReceiptDepositAccountExistance(
tenantId,
saleReceiptDTO.depositAccountId
);
// Validate items IDs existance on the storage. // Validate items IDs existance on the storage.
await this.itemsEntriesService.validateItemsIdsExistance(tenantId, saleReceiptDTO.entries); await this.itemsEntriesService.validateItemsIdsExistance(
tenantId,
saleReceiptDTO.entries
);
// Validate the sellable items. // Validate the sellable items.
await this.itemsEntriesService.validateNonSellableEntriesItems(tenantId, saleReceiptDTO.entries); await this.itemsEntriesService.validateNonSellableEntriesItems(
tenantId,
saleReceiptDTO.entries
);
// Validate sale receipt number uniuqiness. // Validate sale receipt number uniuqiness.
if (saleReceiptDTO.receiptNumber) { if (saleReceiptDTO.receiptNumber) {
await this.validateReceiptNumberUnique(tenantId, saleReceiptDTO.receiptNumber); await this.validateReceiptNumberUnique(
tenantId,
saleReceiptDTO.receiptNumber
);
} }
this.logger.info('[sale_receipt] trying to insert sale receipt graph.', { tenantId, saleReceiptDTO }); this.logger.info('[sale_receipt] trying to insert sale receipt graph.', {
const saleReceipt = await SaleReceipt.query().insertGraphAndFetch({ ...saleReceiptObj }); tenantId,
saleReceiptDTO,
await this.eventDispatcher.dispatch(events.saleReceipt.onCreated, { tenantId, saleReceipt }); });
const saleReceipt = await SaleReceipt.query().upsertGraph({
this.logger.info('[sale_receipt] sale receipt inserted successfully.', { tenantId }); ...saleReceiptObj,
});
// Triggers `onSaleReceiptCreated` event.
await this.eventDispatcher.dispatch(events.saleReceipt.onCreated, {
tenantId,
saleReceipt,
saleReceiptId: saleReceipt.id,
});
this.logger.info('[sale_receipt] sale receipt inserted successfully.', {
tenantId,
});
return saleReceipt; return saleReceipt;
} }
@@ -175,37 +236,61 @@ export default class SalesReceiptService {
* @param {ISaleReceipt} saleReceipt * @param {ISaleReceipt} saleReceipt
* @return {void} * @return {void}
*/ */
public async editSaleReceipt(tenantId: number, saleReceiptId: number, saleReceiptDTO: any) { public async editSaleReceipt(
const { SaleReceipt, ItemEntry } = this.tenancy.models(tenantId); tenantId: number,
saleReceiptId: number,
saleReceiptDTO: any
) {
const { SaleReceipt } = this.tenancy.models(tenantId);
// Retrieve sale receipt or throw not found service error. // Retrieve sale receipt or throw not found service error.
const oldSaleReceipt = await this.getSaleReceiptOrThrowError(tenantId, saleReceiptId); const oldSaleReceipt = await this.getSaleReceiptOrThrowError(
tenantId,
saleReceiptId
);
// Transform sale receipt DTO to model. // Transform sale receipt DTO to model.
const saleReceiptObj = this.transformObjectDTOToModel(saleReceiptDTO, oldSaleReceipt); const saleReceiptObj = this.transformObjectDTOToModel(
saleReceiptDTO,
oldSaleReceipt
);
// Validate receipt deposit account existance and type. // Validate receipt deposit account existance and type.
await this.validateReceiptDepositAccountExistance(tenantId, saleReceiptDTO.depositAccountId); await this.validateReceiptDepositAccountExistance(
tenantId,
saleReceiptDTO.depositAccountId
);
// Validate items IDs existance on the storage. // Validate items IDs existance on the storage.
await this.itemsEntriesService.validateItemsIdsExistance(tenantId, saleReceiptDTO.entries); await this.itemsEntriesService.validateItemsIdsExistance(
tenantId,
saleReceiptDTO.entries
);
// Validate the sellable items. // Validate the sellable items.
await this.itemsEntriesService.validateNonSellableEntriesItems(tenantId, saleReceiptDTO.entries); await this.itemsEntriesService.validateNonSellableEntriesItems(
tenantId,
saleReceiptDTO.entries
);
// Validate sale receipt number uniuqiness. // Validate sale receipt number uniuqiness.
if (saleReceiptDTO.receiptNumber) { if (saleReceiptDTO.receiptNumber) {
await this.validateReceiptNumberUnique(tenantId, saleReceiptDTO.receiptNumber, saleReceiptId); await this.validateReceiptNumberUnique(
tenantId,
saleReceiptDTO.receiptNumber,
saleReceiptId
);
} }
const saleReceipt = await SaleReceipt.query().upsertGraphAndFetch({ const saleReceipt = await SaleReceipt.query().upsertGraphAndFetch({
id: saleReceiptId, id: saleReceiptId,
...saleReceiptObj ...saleReceiptObj,
}); });
this.logger.info('[sale_receipt] edited successfully.', { tenantId, saleReceiptId }); this.logger.info('[sale_receipt] edited successfully.', {
tenantId,
saleReceiptId,
});
// Triggers `onSaleReceiptEdited` event.
await this.eventDispatcher.dispatch(events.saleReceipt.onEdited, { await this.eventDispatcher.dispatch(events.saleReceipt.onEdited, {
oldSaleReceipt, tenantId, saleReceiptId, saleReceipt, tenantId,
oldSaleReceipt,
saleReceipt,
saleReceiptId,
}); });
return saleReceipt; return saleReceipt;
} }
@@ -218,6 +303,10 @@ export default class SalesReceiptService {
public async deleteSaleReceipt(tenantId: number, saleReceiptId: number) { public async deleteSaleReceipt(tenantId: number, saleReceiptId: number) {
const { SaleReceipt, ItemEntry } = this.tenancy.models(tenantId); const { SaleReceipt, ItemEntry } = this.tenancy.models(tenantId);
const oldSaleReceipt = await this.getSaleReceiptOrThrowError(
tenantId,
saleReceiptId
);
await ItemEntry.query() await ItemEntry.query()
.where('reference_id', saleReceiptId) .where('reference_id', saleReceiptId)
.where('reference_type', 'SaleReceipt') .where('reference_type', 'SaleReceipt')
@@ -225,8 +314,15 @@ export default class SalesReceiptService {
await SaleReceipt.query().where('id', saleReceiptId).delete(); await SaleReceipt.query().where('id', saleReceiptId).delete();
this.logger.info('[sale_receipt] deleted successfully.', { tenantId, saleReceiptId }); this.logger.info('[sale_receipt] deleted successfully.', {
await this.eventDispatcher.dispatch(events.saleReceipt.onDeleted); tenantId,
saleReceiptId,
});
await this.eventDispatcher.dispatch(events.saleReceipt.onDeleted, {
tenantId,
saleReceiptId,
oldSaleReceipt
});
} }
/** /**
@@ -256,22 +352,31 @@ export default class SalesReceiptService {
*/ */
public async salesReceiptsList( public async salesReceiptsList(
tenantId: number, tenantId: number,
salesReceiptsFilter: ISaleReceiptFilter, salesReceiptsFilter: ISaleReceiptFilter
): Promise<{ salesReceipts: ISaleReceipt[], pagination: IPaginationMeta, filterMeta: IFilterMeta }> { ): Promise<{
salesReceipts: ISaleReceipt[];
pagination: IPaginationMeta;
filterMeta: IFilterMeta;
}> {
const { SaleReceipt } = this.tenancy.models(tenantId); const { SaleReceipt } = this.tenancy.models(tenantId);
const dynamicFilter = await this.dynamicListService.dynamicList(tenantId, SaleReceipt, salesReceiptsFilter); const dynamicFilter = await this.dynamicListService.dynamicList(
tenantId,
SaleReceipt,
salesReceiptsFilter
);
this.logger.info('[sale_receipt] try to get sales receipts list.', { tenantId }); this.logger.info('[sale_receipt] try to get sales receipts list.', {
const { results, pagination } = await SaleReceipt.query().onBuild((builder) => { tenantId,
});
const { results, pagination } = await SaleReceipt.query()
.onBuild((builder) => {
builder.withGraphFetched('depositAccount'); builder.withGraphFetched('depositAccount');
builder.withGraphFetched('customer'); builder.withGraphFetched('customer');
builder.withGraphFetched('entries'); builder.withGraphFetched('entries');
dynamicFilter.buildQuery()(builder); dynamicFilter.buildQuery()(builder);
}).pagination( })
salesReceiptsFilter.page - 1, .pagination(salesReceiptsFilter.page - 1, salesReceiptsFilter.pageSize);
salesReceiptsFilter.pageSize,
);
return { return {
salesReceipts: results, salesReceipts: results,
@@ -293,7 +398,10 @@ export default class SalesReceiptService {
const { SaleReceipt } = this.tenancy.models(tenantId); const { SaleReceipt } = this.tenancy.models(tenantId);
// Retrieve sale receipt or throw not found service error. // Retrieve sale receipt or throw not found service error.
const oldSaleReceipt = await this.getSaleReceiptOrThrowError(tenantId, saleReceiptId); const oldSaleReceipt = await this.getSaleReceiptOrThrowError(
tenantId,
saleReceiptId
);
// Throw service error if the sale receipt already closed. // Throw service error if the sale receipt already closed.
if (oldSaleReceipt.isClosed) { if (oldSaleReceipt.isClosed) {
@@ -304,4 +412,96 @@ export default class SalesReceiptService {
closedAt: moment().toMySqlDateTime(), closedAt: moment().toMySqlDateTime(),
}); });
} }
/**
* Writes the sale invoice income journal entries.
* @param {number} tenantId - Tenant id.
* @param {ISaleInvoice} saleInvoice - Sale invoice id.
*/
public async writesIncomeJournalEntries(
tenantId: number,
saleInvoice: ISaleReceipt & {
entries: IItemEntry & { item: IItem };
},
override: boolean = false
): Promise<void> {
const journal = new JournalPoster(tenantId);
const journalCommands = new JournalCommands(journal);
if (override) {
await journalCommands.revertJournalEntries(saleInvoice.id, 'SaleReceipt');
}
// Records the sale invoice journal entries.
await journalCommands.saleReceiptIncomeEntries(saleInvoice);
await Promise.all([
journal.deleteEntries(),
journal.saveBalance(),
journal.saveEntries(),
]);
}
/**
* Reverting the sale receipt journal entries.
* @param {number} tenantId
* @param {number} saleReceiptId
* @return {Promise<void>}
*/
public async revertReceiptJournalEntries(
tenantId: number,
saleReceiptId: number | number[]
): Promise<void> {
return this.journalService.revertJournalTransactions(
tenantId,
saleReceiptId,
'SaleReceipt'
);
}
/**
* Records the inventory transactions from the given bill input.
* @param {Bill} bill - Bill model object.
* @param {number} billId - Bill id.
* @return {Promise<void>}
*/
public async recordInventoryTransactions(
tenantId: number,
saleReceipt: ISaleReceipt,
override?: boolean
): Promise<void> {
await this.inventoryService.recordInventoryTransactionsFromItemsEntries(
tenantId,
saleReceipt.id,
'SaleReceipt',
saleReceipt.receiptDate,
'OUT',
override,
);
// Triggers `onInventoryTransactionsCreated` event.
this.eventDispatcher.dispatch(
events.saleReceipt.onInventoryTransactionsCreated,
{
tenantId,
saleReceipt,
saleReceiptId: saleReceipt.id,
}
);
}
/**
* Reverts the inventory transactions of the given bill id.
* @param {number} tenantId - Tenant id.
* @param {number} billId - Bill id.
* @return {Promise<void>}
*/
public async revertInventoryTransactions(
tenantId: number,
receiptId: number
) {
return this.inventoryService.deleteInventoryTransactions(
tenantId,
receiptId,
'SaleReceipt'
);
}
} }

View File

@@ -0,0 +1,59 @@
import { Container } from 'typedi';
import { EventSubscriber, On } from 'event-dispatch';
import events from 'subscribers/events';
import TenancyService from 'services/Tenancy/TenancyService';
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
@EventSubscriber()
export default class BillSubscriber {
tenancy: TenancyService;
logger: any;
itemsEntriesService: ItemsEntriesService;
/**
* Constructor method.
*/
constructor() {
this.tenancy = Container.get(TenancyService);
this.logger = Container.get('logger');
this.itemsEntriesService = Container.get(ItemsEntriesService);
}
/**
* Increments the sale invoice items once the invoice created.
*/
@On(events.bill.onCreated)
public async handleDecrementSaleInvoiceItemsQuantity({ tenantId, bill }) {
await this.itemsEntriesService.incrementItemsEntries(
tenantId,
bill.entries
);
}
/**
* Decrements the sale invoice items once the invoice deleted.
*/
@On(events.bill.onDeleted)
public async handleIncrementSaleInvoiceItemsQuantity({ tenantId, oldBill }) {
await this.itemsEntriesService.decrementItemsQuantity(
tenantId,
oldBill.entries
);
}
/**
* Handle increment/decrement the different items quantity once the sale invoice be edited.
*/
@On(events.bill.onEdited)
public async handleChangeSaleInvoiceItemsQuantityOnEdit({
tenantId,
bill,
oldBill,
}) {
await this.itemsEntriesService.changeItemsQuantity(
tenantId,
bill.entries,
oldBill.entries
);
}
}

View File

@@ -0,0 +1,68 @@
import { Container } from 'typedi';
import { EventSubscriber, On } from 'event-dispatch';
import events from 'subscribers/events';
import TenancyService from 'services/Tenancy/TenancyService';
@EventSubscriber()
export default class BillSubscriber {
tenancy: TenancyService;
logger: any;
/**
* Constructor method.
*/
constructor() {
this.tenancy = Container.get(TenancyService);
this.logger = Container.get('logger');
}
/**
* Handles vendor balance increment once bill created.
*/
@On(events.bill.onCreated)
async handleVendorBalanceIncrement({ tenantId, billId, bill }) {
const { vendorRepository } = this.tenancy.repositories(tenantId);
// Increments vendor balance.
this.logger.info('[bill] trying to increment vendor balance.', {
tenantId,
billId,
});
await vendorRepository.changeBalance(bill.vendorId, bill.amount);
}
/**
* Handles vendor balance decrement once bill deleted.
*/
@On(events.bill.onDeleted)
async handleVendorBalanceDecrement({ tenantId, billId, oldBill }) {
const { vendorRepository } = this.tenancy.repositories(tenantId);
// Decrements vendor balance.
this.logger.info('[bill] trying to decrement vendor balance.', {
tenantId,
billId,
});
await vendorRepository.changeBalance(oldBill.vendorId, oldBill.amount * -1);
}
/**
* Handles vendor balance difference change.
*/
@On(events.bill.onEdited)
async handleVendorBalanceDiffChange({ tenantId, billId, oldBill, bill }) {
const { vendorRepository } = this.tenancy.repositories(tenantId);
// Changes the diff vendor balance between old and new amount.
this.logger.info('[bill[ change vendor the different balance.', {
tenantId,
billId,
});
await vendorRepository.changeDiffBalance(
bill.vendorId,
bill.amount,
oldBill.amount,
oldBill.vendorId
);
}
}

View File

@@ -0,0 +1,60 @@
import { Container } from 'typedi';
import { EventSubscriber, On } from 'event-dispatch';
import events from 'subscribers/events';
import TenancyService from 'services/Tenancy/TenancyService';
import BillsService from 'services/Purchases/Bills';
@EventSubscriber()
export default class BillSubscriber {
tenancy: TenancyService;
billsService: BillsService;
logger: any;
/**
* Constructor method.
*/
constructor() {
this.tenancy = Container.get(TenancyService);
this.billsService = Container.get(BillsService);
this.logger = Container.get('logger');
}
/**
* Handles writing the inventory transactions once bill created.
*/
@On(events.bill.onCreated)
async handleWritingInventoryTransactions({ tenantId, bill }) {
this.logger.info('[bill] writing the inventory transactions', { tenantId });
this.billsService.recordInventoryTransactions(
tenantId,
bill
);
}
/**
* Handles the overwriting the inventory transactions once bill edited.
*/
@On(events.bill.onEdited)
async handleOverwritingInventoryTransactions({ tenantId, bill }) {
this.logger.info('[bill] overwriting the inventory transactions.', {
tenantId,
});
this.billsService.recordInventoryTransactions(
tenantId,
bill,
true
);
}
/**
* Handles the reverting the inventory transactions once the bill deleted.
*/
@On(events.bill.onDeleted)
async handleRevertInventoryTransactions({ tenantId, billId }) {
this.logger.info('[bill] reverting the bill inventory transactions', {
tenantId,
billId,
});
this.billsService.revertInventoryTransactions(tenantId, billId);
}
}

View File

@@ -0,0 +1,54 @@
import { Container } from 'typedi';
import { EventSubscriber, On } from 'event-dispatch';
import events from 'subscribers/events';
import TenancyService from 'services/Tenancy/TenancyService';
import BillsService from 'services/Purchases/Bills';
@EventSubscriber()
export default class BillSubscriber {
tenancy: TenancyService;
billsService: BillsService;
logger: any;
/**
* Constructor method.
*/
constructor() {
this.tenancy = Container.get(TenancyService);
this.billsService = Container.get(BillsService);
this.logger = Container.get('logger');
}
/**
* Handles writing journal entries once bill created.
*/
@On(events.bill.onCreated)
async handlerWriteJournalEntriesOnCreate({ tenantId, bill }) {
// Writes the journal entries for the given bill transaction.
this.logger.info('[bill] writing bill journal entries.', { tenantId });
await this.billsService.recordJournalTransactions(tenantId, bill);
}
/**
* Handles the overwriting journal entries once bill edited.
*/
@On(events.bill.onEdited)
async handleOverwriteJournalEntriesOnEdit({ tenantId, bill }) {
// Overwrite the journal entries for the given bill transaction.
this.logger.info('[bill] overwriting bill journal entries.', { tenantId });
await this.billsService.recordJournalTransactions(tenantId, bill, true);
}
/**
* Handles revert journal entries on bill deleted.
*/
@On(events.bill.onDeleted)
async handlerDeleteJournalEntries({ tenantId, billId }) {
// Delete associated bill journal transactions.
this.logger.info('[bill] trying to delete journal entries.', {
tenantId,
billId,
});
await this.billsService.revertJournalEntries(tenantId, billId);
}
}

View File

@@ -0,0 +1,22 @@
import { Container } from 'typedi';
import { EventSubscriber, On } from 'event-dispatch';
import events from 'subscribers/events';
import TenancyService from 'services/Tenancy/TenancyService';
import BillsService from 'services/Purchases/Bills';
@EventSubscriber()
export default class BillSubscriber {
tenancy: TenancyService;
billsService: BillsService;
logger: any;
/**
* Constructor method.
*/
constructor() {
this.tenancy = Container.get(TenancyService);
this.billsService = Container.get(BillsService);
this.logger = Container.get('logger');
}
}

View File

@@ -0,0 +1,82 @@
import { Container } from 'typedi';
import { On, EventSubscriber } from 'event-dispatch';
import events from 'subscribers/events';
import TenancyService from 'services/Tenancy/TenancyService';
@EventSubscriber()
export default class SaleInvoiceSubscriber {
logger: any;
tenancy: TenancyService;
/**
* Constructor method.
*/
constructor() {
this.logger = Container.get('logger');
this.tenancy = Container.get(TenancyService);
}
/**
* Handles customer balance increment once sale invoice created.
*/
@On(events.saleInvoice.onCreated)
public async handleCustomerBalanceIncrement({
tenantId,
saleInvoice,
saleInvoiceId,
}) {
const { customerRepository } = this.tenancy.repositories(tenantId);
this.logger.info('[sale_invoice] trying to increment customer balance.', {
tenantId,
});
await customerRepository.changeBalance(
saleInvoice.customerId,
saleInvoice.balance
);
}
/**
* Handles customer balance diff balnace change once sale invoice edited.
*/
@On(events.saleInvoice.onEdited)
public async onSaleInvoiceEdited({
tenantId,
saleInvoice,
oldSaleInvoice,
saleInvoiceId,
}) {
const { customerRepository } = this.tenancy.repositories(tenantId);
this.logger.info('[sale_invoice] trying to change diff customer balance.', {
tenantId,
});
await customerRepository.changeDiffBalance(
saleInvoice.customerId,
saleInvoice.balance,
oldSaleInvoice.balance,
oldSaleInvoice.customerId
);
}
/**
* Handles customer balance decrement once sale invoice deleted.
*/
@On(events.saleInvoice.onDeleted)
public async handleCustomerBalanceDecrement({
tenantId,
saleInvoiceId,
oldSaleInvoice,
}) {
const { customerRepository } = this.tenancy.repositories(tenantId);
this.logger.info('[sale_invoice] trying to decrement customer balance.', {
tenantId,
});
await customerRepository.changeBalance(
oldSaleInvoice.customerId,
oldSaleInvoice.balance * -1
);
}
}

View File

@@ -0,0 +1,71 @@
import { Container } from 'typedi';
import { On, EventSubscriber } from 'event-dispatch';
import events from 'subscribers/events';
import TenancyService from 'services/Tenancy/TenancyService';
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
@EventSubscriber()
export default class SyncItemsQuantityWithInvoices {
logger: any;
tenancy: TenancyService;
itemsEntriesService: ItemsEntriesService;
/**
* Constructor method.
*/
constructor() {
this.logger = Container.get('logger');
this.tenancy = Container.get(TenancyService);
this.itemsEntriesService = Container.get(ItemsEntriesService);
}
/**
* Increments the sale invoice items once the invoice created.
*/
@On(events.saleInvoice.onCreated)
public async handleDecrementSaleInvoiceItemsQuantity({
tenantId,
saleInvoice,
}) {
await this.itemsEntriesService.decrementItemsQuantity(
tenantId,
saleInvoice.entries
);
}
/**
* Decrements the sale invoice items once the invoice deleted.
*/
@On(events.saleInvoice.onDeleted)
public async handleIncrementSaleInvoiceItemsQuantity({
tenantId,
oldSaleInvoice,
}) {
await this.itemsEntriesService.incrementItemsEntries(
tenantId,
oldSaleInvoice.entries
);
}
/**
* Handle increment/decrement the different items quantity once the sale invoice be edited.
*/
@On(events.saleInvoice.onEdited)
public async handleChangeSaleInvoiceItemsQuantityOnEdit({
tenantId,
saleInvoice,
oldSaleInvoice,
}) {
await this.itemsEntriesService.changeItemsQuantity(
tenantId,
saleInvoice.entries.map((entry) => ({
...entry,
quantity: entry.quantity * -1,
})),
oldSaleInvoice.entries.map((entry) => ({
...entry,
quantity: entry.quantity * -1,
}))
);
}
}

View File

@@ -0,0 +1,72 @@
import { Container } from 'typedi';
import { On, EventSubscriber } from 'event-dispatch';
import events from 'subscribers/events';
import TenancyService from 'services/Tenancy/TenancyService';
import SaleInvoicesService from 'services/Sales/SalesInvoices';
@EventSubscriber()
export default class WriteInventoryTransactions {
logger: any;
tenancy: TenancyService;
saleInvoicesService: SaleInvoicesService;
/**
* Constructor method.
*/
constructor() {
this.logger = Container.get('logger');
this.tenancy = Container.get(TenancyService);
this.saleInvoicesService = Container.get(SaleInvoicesService);
}
/**
* Handles the writing inventory transactions once the invoice created.
*/
@On(events.saleInvoice.onCreated)
public async handleWritingInventoryTransactions({ tenantId, saleInvoice }) {
this.logger.info('[sale_invoice] trying to write inventory transactions.', {
tenantId,
});
await this.saleInvoicesService.recordInventoryTranscactions(
tenantId,
saleInvoice
);
}
/**
* Rewriting the inventory transactions once the sale invoice be edited.
*/
@On(events.saleInvoice.onEdited)
public async handleRewritingInventoryTransactions({ tenantId, saleInvoice }) {
this.logger.info('[sale_invoice] trying to write inventory transactions.', {
tenantId,
});
await this.saleInvoicesService.recordInventoryTranscactions(
tenantId,
saleInvoice,
true
);
}
/**
* Handles deleting the inventory transactions once the invoice deleted.
*/
@On(events.saleInvoice.onDeleted)
public async handleDeletingInventoryTransactions({
tenantId,
saleInvoiceId,
oldSaleInvoice,
}) {
this.logger.info(
'[sale_invoice] trying to revert inventory transactions.',
{
tenantId,
saleInvoiceId,
}
);
await this.saleInvoicesService.revertInventoryTransactions(
tenantId,
saleInvoiceId
);
}
}

View File

@@ -0,0 +1,80 @@
import { Container } from 'typedi';
import { On, EventSubscriber } from 'event-dispatch';
import events from 'subscribers/events';
import TenancyService from 'services/Tenancy/TenancyService';
import SaleInvoicesService from 'services/Sales/SalesInvoices';
@EventSubscriber()
export default class SaleInvoiceSubscriber {
logger: any;
tenancy: TenancyService;
saleInvoicesService: SaleInvoicesService;
/**
* Constructor method.
*/
constructor() {
this.logger = Container.get('logger');
this.tenancy = Container.get(TenancyService);
this.saleInvoicesService = Container.get(SaleInvoicesService);
}
/**
* Records journal entries of the non-inventory invoice.
*/
@On(events.saleInvoice.onCreated)
public async handleWriteJournalEntriesOnInvoiceCreated({
tenantId,
saleInvoiceId,
saleInvoice,
authorizedUser,
}) {
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
const saleInvoiceWithItems = await saleInvoiceRepository.findOneById(
saleInvoiceId,
'entries.item'
);
await this.saleInvoicesService.writesIncomeJournalEntries(
tenantId,
saleInvoiceWithItems
);
}
/**
* Records journal entries of the non-inventory invoice.
*/
@On(events.saleInvoice.onEdited)
public async handleRewriteJournalEntriesOnceInvoiceEdit({
tenantId,
saleInvoiceId,
saleInvoice,
authorizedUser,
}) {
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
const saleInvoiceWithItems = await saleInvoiceRepository.findOneById(
saleInvoiceId,
'entries.item'
);
await this.saleInvoicesService.writesIncomeJournalEntries(
tenantId,
saleInvoiceWithItems,
true
);
}
/**
* Handle reverting journal entries once sale invoice delete.
*/
@On(events.saleInvoice.onDeleted)
public async handleRevertingInvoiceJournalEntriesOnDelete({
tenantId,
saleInvoiceId,
}) {
await this.saleInvoicesService.revertInvoiceJournalEntries(
tenantId,
saleInvoiceId,
);
}
}

View File

@@ -0,0 +1,57 @@
import { Container } from 'typedi';
import { On, EventSubscriber } from 'event-dispatch';
import events from 'subscribers/events';
import TenancyService from 'services/Tenancy/TenancyService';
import SettingsService from 'services/Settings/SettingsService';
import SaleEstimateService from 'services/Sales/SalesEstimate';
@EventSubscriber()
export default class SaleInvoiceSubscriber {
logger: any;
tenancy: TenancyService;
settingsService: SettingsService;
saleEstimatesService: SaleEstimateService;
/**
* Constructor method.
*/
constructor() {
this.logger = Container.get('logger');
this.tenancy = Container.get(TenancyService);
this.settingsService = Container.get(SettingsService);
this.saleEstimatesService = Container.get(SaleEstimateService);
}
/**
* Marks the sale estimate as converted from the sale invoice once created.
*/
@On(events.saleInvoice.onCreated)
public async handleMarkEstimateConvert({
tenantId,
saleInvoice,
saleInvoiceId,
}) {
if (saleInvoice.fromEstimateId) {
this.saleEstimatesService.convertEstimateToInvoice(
tenantId,
saleInvoice.fromEstiamteId,
saleInvoiceId
);
}
}
/**
* Handles sale invoice next number increment once invoice created.
*/
@On(events.saleInvoice.onCreated)
public async handleInvoiceNextNumberIncrement({
tenantId,
saleInvoiceId,
saleInvoice,
}) {
await this.settingsService.incrementNextNumber(tenantId, {
key: 'next_number',
group: 'sales_invoices',
});
}
}

View File

@@ -0,0 +1,67 @@
import { Container } from 'typedi';
import { On, EventSubscriber } from 'event-dispatch';
import events from 'subscribers/events';
import TenancyService from 'services/Tenancy/TenancyService';
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
import SalesInvoicesCost from 'services/Sales/SalesInvoicesCost';
@EventSubscriber()
export default class SaleReceiptSubscriber {
logger: any;
tenancy: TenancyService;
itemsEntriesService: ItemsEntriesService;
constructor() {
this.logger = Container.get('logger');
this.tenancy = Container.get(TenancyService);
this.itemsEntriesService = Container.get(ItemsEntriesService);
}
/**
* Increments the sale receipt items once be created.
*/
@On(events.saleReceipt.onCreated)
public async handleDecremenReceiptItemsQuantity({ tenantId, saleReceipt }) {
await this.itemsEntriesService.decrementItemsQuantity(
tenantId,
saleReceipt.entries
);
}
/**
* Decrements the sale receipt items once be deleted.
*/
@On(events.saleReceipt.onDeleted)
public async handleIncrementReceiptItemsQuantity({
tenantId,
oldSaleReceipt,
}) {
await this.itemsEntriesService.incrementItemsEntries(
tenantId,
oldSaleReceipt.entries
);
}
/**
* Handle increment/decrement the different items quantity once
* the sale receipt be edited.
*/
@On(events.saleReceipt.onEdited)
public async handleChangeSaleInvoiceItemsQuantityOnEdit({
tenantId,
saleReceipt,
oldSaleReceipt,
}) {
await this.itemsEntriesService.changeItemsQuantity(
tenantId,
saleReceipt.entries.map((entry) => ({
...entry,
quantity: entry.quantity * -1,
})),
oldSaleReceipt.entries.map((entry) => ({
...entry,
quantity: entry.quantity * -1,
}))
);
}
}

View File

@@ -0,0 +1,68 @@
import { Container } from 'typedi';
import { On, EventSubscriber } from 'event-dispatch';
import events from 'subscribers/events';
import TenancyService from 'services/Tenancy/TenancyService';
import SalesReceiptService from 'services/Sales/SalesReceipts';
@EventSubscriber()
export default class SaleReceiptSubscriber {
logger: any;
tenancy: TenancyService;
saleReceiptsService: SalesReceiptService;
constructor() {
this.logger = Container.get('logger');
this.tenancy = Container.get(TenancyService);
this.saleReceiptsService = Container.get(SalesReceiptService);
}
/**
* Handles the writing inventory transactions once the receipt created.
*/
@On(events.saleReceipt.onCreated)
public async handleWritingInventoryTransactions({ tenantId, saleReceipt }) {
this.logger.info('[sale_receipt] trying to write inventory transactions.', {
tenantId,
});
await this.saleReceiptsService.recordInventoryTransactions(
tenantId,
saleReceipt
);
}
/**
* Rewriting the inventory transactions once the sale invoice be edited.
*/
@On(events.saleReceipt.onEdited)
public async handleRewritingInventoryTransactions({ tenantId, saleReceipt }) {
this.logger.info('[sale_invoice] trying to write inventory transactions.', {
tenantId,
});
await this.saleReceiptsService.recordInventoryTransactions(
tenantId,
saleReceipt,
true
);
}
/**
* Handles deleting the inventory transactions once the receipt deleted.
*/
@On(events.saleReceipt.onDeleted)
public async handleDeletingInventoryTransactions({
tenantId,
saleReceiptId,
}) {
this.logger.info(
'[sale_invoice] trying to revert inventory transactions.',
{
tenantId,
saleReceiptId,
}
);
await this.saleReceiptsService.revertInventoryTransactions(
tenantId,
saleReceiptId
);
}
}

View File

@@ -0,0 +1,75 @@
import { Container } from 'typedi';
import { On, EventSubscriber } from 'event-dispatch';
import events from 'subscribers/events';
import TenancyService from 'services/Tenancy/TenancyService';
import SalesReceiptService from 'services/Sales/SalesReceipts';
@EventSubscriber()
export default class SaleReceiptSubscriber {
logger: any;
tenancy: TenancyService;
saleReceiptsService: SalesReceiptService;
constructor() {
this.logger = Container.get('logger');
this.tenancy = Container.get(TenancyService);
this.saleReceiptsService = Container.get(SalesReceiptService);
}
/**
* Handles writing sale receipt income journal entries once created.
*/
@On(events.saleReceipt.onCreated)
public async handleWriteReceiptIncomeJournalEntrieOnCreate({
tenantId,
saleReceiptId,
}) {
const { SaleReceipt } = this.tenancy.models(tenantId);
const saleReceiptWithItems = await SaleReceipt.query()
.findById(saleReceiptId)
.withGraphFetched('entries.item');
// Writes the sale receipt income journal entries.
await this.saleReceiptsService.writesIncomeJournalEntries(
tenantId,
saleReceiptWithItems
);
}
/**
* Handles sale receipt revert jouranl entries once be deleted.
*/
@On(events.saleReceipt.onDeleted)
public async handleRevertReceiptJournalEntriesOnDeleted({
tenantId,
saleReceiptId,
}) {
await this.saleReceiptsService.revertReceiptJournalEntries(
tenantId,
saleReceiptId
);
}
/**
* Handles writing sale receipt income journal entries once be edited.
*/
@On(events.saleReceipt.onEdited)
public async handleWriteReceiptIncomeJournalEntrieOnEdited({
tenantId,
saleReceiptId,
}) {
const { SaleReceipt } = this.tenancy.models(tenantId);
const saleReceiptWithItems = await SaleReceipt.query()
.findById(saleReceiptId)
.withGraphFetched('entries.item');
// Writes the sale receipt income journal entries.
await this.saleReceiptsService.writesIncomeJournalEntries(
tenantId,
saleReceiptWithItems,
true
);
}
}

View File

@@ -1,5 +1,5 @@
import { Container } from 'typedi'; import { Container } from 'typedi';
import { On, EventSubscriber } from "event-dispatch"; import { On, EventSubscriber } from 'event-dispatch';
import events from 'subscribers/events'; import events from 'subscribers/events';
import TenancyService from 'services/Tenancy/TenancyService'; import TenancyService from 'services/Tenancy/TenancyService';
import SettingsService from 'services/Settings/SettingsService'; import SettingsService from 'services/Settings/SettingsService';

View File

@@ -1,227 +0,0 @@
import { Container } from 'typedi';
import { EventSubscriber, On } from 'event-dispatch';
import { map, head } from 'lodash';
import events from 'subscribers/events';
import TenancyService from 'services/Tenancy/TenancyService';
import BillsService from 'services/Purchases/Bills';
import JournalPosterService from 'services/Sales/JournalPosterService';
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
@EventSubscriber()
export default class BillSubscriber {
tenancy: TenancyService;
billsService: BillsService;
logger: any;
journalPosterService: JournalPosterService;
itemsEntriesService: ItemsEntriesService;
/**
* Constructor method.
*/
constructor() {
this.tenancy = Container.get(TenancyService);
this.billsService = Container.get(BillsService);
this.logger = Container.get('logger');
this.journalPosterService = Container.get(JournalPosterService);
this.itemsEntriesService = Container.get(ItemsEntriesService);
}
/**
* Handles vendor balance increment once bill created.
*/
@On(events.bill.onCreated)
async handleVendorBalanceIncrement({ tenantId, billId, bill }) {
const { vendorRepository } = this.tenancy.repositories(tenantId);
// Increments vendor balance.
this.logger.info('[bill] trying to increment vendor balance.', {
tenantId,
billId,
});
await vendorRepository.changeBalance(bill.vendorId, bill.amount);
}
/**
* Handles writing journal entries once bill created.
*/
@On(events.bill.onCreated)
async handlerWriteJournalEntriesOnCreate({ tenantId, bill }) {
// Writes the journal entries for the given bill transaction.
this.logger.info('[bill] writing bill journal entries.', { tenantId });
await this.billsService.recordJournalTransactions(tenantId, bill);
}
/**
* Handles the overwriting journal entries once bill edited.
*/
@On(events.bill.onEdited)
async handleOverwriteJournalEntriesOnEdit({ tenantId, bill }) {
// Overwrite the journal entries for the given bill transaction.
this.logger.info('[bill] overwriting bill journal entries.', { tenantId });
await this.billsService.recordJournalTransactions(tenantId, bill, true);
}
/**
* Handles vendor balance decrement once bill deleted.
*/
@On(events.bill.onDeleted)
async handleVendorBalanceDecrement({ tenantId, billId, oldBill }) {
const { vendorRepository } = this.tenancy.repositories(tenantId);
// Decrements vendor balance.
this.logger.info('[bill] trying to decrement vendor balance.', {
tenantId,
billId,
});
await vendorRepository.changeBalance(oldBill.vendorId, oldBill.amount * -1);
}
/**
* Handles revert journal entries on bill deleted.
*/
@On(events.bill.onDeleted)
async handlerDeleteJournalEntries({ tenantId, billId }) {
// Delete associated bill journal transactions.
this.logger.info('[bill] trying to delete journal entries.', {
tenantId,
billId,
});
await this.journalPosterService.revertJournalTransactions(
tenantId,
billId,
'Bill'
);
}
/**
* Handles vendor balance difference change.
*/
@On(events.bill.onEdited)
async handleVendorBalanceDiffChange({ tenantId, billId, oldBill, bill }) {
const { vendorRepository } = this.tenancy.repositories(tenantId);
// Changes the diff vendor balance between old and new amount.
this.logger.info('[bill[ change vendor the different balance.', {
tenantId,
billId,
});
await vendorRepository.changeDiffBalance(
bill.vendorId,
bill.amount,
oldBill.amount,
oldBill.vendorId
);
}
/**
* Handles writing the inventory transactions once bill created.
*/
@On(events.bill.onCreated)
async handleWritingInventoryTransactions({ tenantId, bill }) {
this.logger.info('[bill] writing the inventory transactions', { tenantId });
this.billsService.recordInventoryTransactions(
tenantId,
bill.id,
bill.billDate
);
}
/**
* Handles the overwriting the inventory transactions once bill edited.
*/
@On(events.bill.onEdited)
async handleOverwritingInventoryTransactions({ tenantId, bill }) {
this.logger.info('[bill] overwriting the inventory transactions.', {
tenantId,
});
this.billsService.recordInventoryTransactions(
tenantId,
bill.id,
bill.billDate,
true
);
}
/**
* Handles the reverting the inventory transactions once the bill deleted.
*/
@On(events.bill.onDeleted)
async handleRevertInventoryTransactions({ tenantId, billId }) {
this.logger.info('[bill] reverting the bill inventory transactions', {
tenantId,
billId,
});
this.billsService.revertInventoryTransactions(tenantId, billId);
}
/**
* Schedules items cost compute jobs once the inventory transactions created
* of the bill transaction.
*/
@On(events.bill.onInventoryTransactionsCreated)
public async handleComputeItemsCosts({ tenantId, billId }) {
this.logger.info('[bill] trying to compute the bill items cost.', {
tenantId,
billId,
});
await this.billsService.scheduleComputeCostByBillId(tenantId, billId);
}
/**
* Handles computes items costs once the inventory transactions deleted.
*/
@On(events.bill.onInventoryTransactionsDeleted)
public async handleComputeCostsOnInventoryTransactionsDeleted({
tenantId,
billId,
oldInventoryTransactions,
}) {
const inventoryItemsIds = map(oldInventoryTransactions, 'itemId');
const startingDates = map(oldInventoryTransactions, 'date');
const startingDate = head(startingDates);
await this.billsService.scheduleComputeCostByItemsIds(
tenantId,
inventoryItemsIds,
startingDate
);
}
/**
* Increments the sale invoice items once the invoice created.
*/
@On(events.bill.onCreated)
public async handleDecrementSaleInvoiceItemsQuantity({ tenantId, bill }) {
await this.itemsEntriesService.incrementItemsEntries(
tenantId,
bill.entries
);
}
/**
* Decrements the sale invoice items once the invoice deleted.
*/
@On(events.bill.onDeleted)
public async handleIncrementSaleInvoiceItemsQuantity({ tenantId, oldBill }) {
await this.itemsEntriesService.decrementItemsQuantity(
tenantId,
oldBill.entries
);
}
/**
* Handle increment/decrement the different items quantity once the sale invoice be edited.
*/
@On(events.bill.onEdited)
public async handleChangeSaleInvoiceItemsQuantityOnEdit({
tenantId,
bill,
oldBill,
}) {
await this.itemsEntriesService.changeItemsQuantity(
tenantId,
bill.entries,
oldBill.entries
);
}
}

View File

@@ -83,8 +83,6 @@ export default {
onDelivered: 'onSaleInvoiceDelivered', onDelivered: 'onSaleInvoiceDelivered',
onBulkDelete: 'onSaleInvoiceBulkDeleted', onBulkDelete: 'onSaleInvoiceBulkDeleted',
onPublished: 'onSaleInvoicePublished', onPublished: 'onSaleInvoicePublished',
onInventoryTransactionsCreated: 'onInvoiceInventoryTransactionsCreated',
onInventoryTransactionsDeleted: 'onInvoiceInventoryTransactionsDeleted',
}, },
/** /**
@@ -128,8 +126,6 @@ export default {
onDeleted: 'onBillDeleted', onDeleted: 'onBillDeleted',
onBulkDeleted: 'onBillBulkDeleted', onBulkDeleted: 'onBillBulkDeleted',
onPublished: 'onBillPublished', onPublished: 'onBillPublished',
onInventoryTransactionsCreated: 'onBillInventoryTransactionsCreated',
onInventoryTransactionsDeleted: 'onBillInventoryTransactionsDeleted'
}, },
/** /**
@@ -189,6 +185,9 @@ export default {
* Inventory service. * Inventory service.
*/ */
inventory: { inventory: {
onInventoryTransactionsCreated: 'onInventoryTransactionsCreated',
onInventoryTransactionsDeleted: 'onInventoryTransactionsDeleted',
onComputeItemCostJobScheduled: 'onComputeItemCostJobScheduled', onComputeItemCostJobScheduled: 'onComputeItemCostJobScheduled',
onComputeItemCostJobStarted: 'onComputeItemCostJobStarted', onComputeItemCostJobStarted: 'onComputeItemCostJobStarted',
onComputeItemCostJobCompleted: 'onComputeItemCostJobCompleted' onComputeItemCostJobCompleted: 'onComputeItemCostJobCompleted'

View File

@@ -1,5 +1,6 @@
import { Container } from 'typedi'; import { Container } from 'typedi';
import { EventSubscriber, On } from 'event-dispatch'; import { EventSubscriber, On } from 'event-dispatch';
import { map, head } from 'lodash';
import events from 'subscribers/events'; import events from 'subscribers/events';
import SaleInvoicesCost from 'services/Sales/SalesInvoicesCost'; import SaleInvoicesCost from 'services/Sales/SalesInvoicesCost';
@@ -7,16 +8,20 @@ import SaleInvoicesCost from 'services/Sales/SalesInvoicesCost';
export class InventorySubscriber { export class InventorySubscriber {
depends: number = 0; depends: number = 0;
startingDate: Date; startingDate: Date;
saleInvoicesCost: SaleInvoicesCost;
agenda: any;
constructor() {
this.saleInvoicesCost = Container.get(SaleInvoicesCost);
this.agenda = Container.get('agenda');
}
/** /**
* Handle run writing the journal entries once the compute items jobs completed. * Handle run writing the journal entries once the compute items jobs completed.
*/ */
@On(events.inventory.onComputeItemCostJobCompleted) @On(events.inventory.onComputeItemCostJobCompleted)
async onComputeItemCostJobFinished({ itemId, tenantId, startingDate }) { async onComputeItemCostJobFinished({ itemId, tenantId, startingDate }) {
const saleInvoicesCost = Container.get(SaleInvoicesCost); const dependsComputeJobs = await this.agenda.jobs({
const agenda = Container.get('agenda');
const dependsComputeJobs = await agenda.jobs({
name: 'compute-item-cost', name: 'compute-item-cost',
nextRunAt: { $ne: null }, nextRunAt: { $ne: null },
'data.tenantId': tenantId, 'data.tenantId': tenantId,
@@ -25,10 +30,53 @@ export class InventorySubscriber {
if (dependsComputeJobs.length === 0) { if (dependsComputeJobs.length === 0) {
this.startingDate = null; this.startingDate = null;
await saleInvoicesCost.scheduleWriteJournalEntries( await this.saleInvoicesCost.scheduleWriteJournalEntries(
tenantId, tenantId,
startingDate startingDate
); );
} }
} }
/**
*
*/
@On(events.inventory.onInventoryTransactionsCreated)
async handleScheduleItemsCostOnInventoryTransactionsCreated({
tenantId,
inventoryEntries,
transactionId,
transactionType,
transactionDate,
transactionDirection,
override
}) {
const inventoryItemsIds = map(inventoryEntries, 'itemId');
await this.saleInvoicesCost.scheduleComputeCostByItemsIds(
tenantId,
inventoryItemsIds,
transactionDate,
);
}
/**
* Schedules compute items cost once the inventory transactions deleted.
*/
@On(events.inventory.onInventoryTransactionsDeleted)
async handleScheduleItemsCostOnInventoryTransactionsDeleted({
tenantId,
transactionType,
transactionId,
oldInventoryTransactions
}) {
const inventoryItemsIds = map(oldInventoryTransactions, 'itemId');
const startingDates = map(oldInventoryTransactions, 'date');
const startingDate = head(startingDates);
await this.saleInvoicesCost.scheduleComputeCostByItemsIds(
tenantId,
inventoryItemsIds,
startingDate
);
}
} }

View File

@@ -1,330 +0,0 @@
import { Container } from 'typedi';
import { head, map } from 'lodash';
import { On, EventSubscriber } from 'event-dispatch';
import events from 'subscribers/events';
import TenancyService from 'services/Tenancy/TenancyService';
import SettingsService from 'services/Settings/SettingsService';
import SaleEstimateService from 'services/Sales/SalesEstimate';
import SaleInvoicesService from 'services/Sales/SalesInvoices';
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
import SalesInvoicesCost from 'services/Sales/SalesInvoicesCost';
@EventSubscriber()
export default class SaleInvoiceSubscriber {
logger: any;
tenancy: TenancyService;
settingsService: SettingsService;
saleEstimatesService: SaleEstimateService;
saleInvoicesService: SaleInvoicesService;
itemsEntriesService: ItemsEntriesService;
salesInvoicesCost: SalesInvoicesCost;
constructor() {
this.logger = Container.get('logger');
this.tenancy = Container.get(TenancyService);
this.settingsService = Container.get(SettingsService);
this.saleEstimatesService = Container.get(SaleEstimateService);
this.saleInvoicesService = Container.get(SaleInvoicesService);
this.itemsEntriesService = Container.get(ItemsEntriesService);
this.salesInvoicesCost = Container.get(SalesInvoicesCost);
}
/**
* Handles customer balance increment once sale invoice created.
*/
@On(events.saleInvoice.onCreated)
public async handleCustomerBalanceIncrement({
tenantId,
saleInvoice,
saleInvoiceId,
}) {
const { customerRepository } = this.tenancy.repositories(tenantId);
this.logger.info('[sale_invoice] trying to increment customer balance.', {
tenantId,
});
await customerRepository.changeBalance(
saleInvoice.customerId,
saleInvoice.balance
);
}
/**
* Marks the sale estimate as converted from the sale invoice once created.
*/
@On(events.saleInvoice.onCreated)
public async handleMarkEstimateConvert({
tenantId,
saleInvoice,
saleInvoiceId,
}) {
if (saleInvoice.fromEstimateId) {
this.saleEstimatesService.convertEstimateToInvoice(
tenantId,
saleInvoice.fromEstiamteId,
saleInvoiceId
);
}
}
/**
* Handles sale invoice next number increment once invoice created.
*/
@On(events.saleInvoice.onCreated)
public async handleInvoiceNextNumberIncrement({
tenantId,
saleInvoiceId,
saleInvoice,
}) {
await this.settingsService.incrementNextNumber(tenantId, {
key: 'next_number',
group: 'sales_invoices',
});
}
/**
* Handles the writing inventory transactions once the invoice created.
*/
@On(events.saleInvoice.onCreated)
public async handleWritingInventoryTransactions({ tenantId, saleInvoice }) {
this.logger.info('[sale_invoice] trying to write inventory transactions.', {
tenantId,
});
await this.saleInvoicesService.recordInventoryTranscactions(
tenantId,
saleInvoice.id,
saleInvoice.invoiceDate
);
}
/**
* Handles handle write income journal entries of sale invoice.
*/
@On(events.saleInvoice.onCreated)
public async handleWriteInvoiceIncomeJournalEntries({
tenantId,
saleInvoiceId,
saleInvoice,
authorizedUser,
}) {
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
const saleInvoiceWithItems = await saleInvoiceRepository.findOneById(
saleInvoiceId,
'entries.item'
);
await this.saleInvoicesService.writesIncomeJournalEntries(
tenantId,
saleInvoiceWithItems
);
}
/**
* Increments the sale invoice items once the invoice created.
*/
@On(events.saleInvoice.onCreated)
public async handleDecrementSaleInvoiceItemsQuantity({
tenantId,
saleInvoice,
}) {
await this.itemsEntriesService.decrementItemsQuantity(
tenantId,
saleInvoice.entries
);
}
/**
* Rewriting the inventory transactions once the sale invoice be edited.
*/
@On(events.saleInvoice.onEdited)
public async handleRewritingInventoryTransactions({ tenantId, saleInvoice }) {
this.logger.info('[sale_invoice] trying to write inventory transactions.', {
tenantId,
});
await this.saleInvoicesService.recordInventoryTranscactions(
tenantId,
saleInvoice.id,
saleInvoice.invoiceDate,
true
);
}
/**
* Handles customer balance diff balnace change once sale invoice edited.
*/
@On(events.saleInvoice.onEdited)
public async onSaleInvoiceEdited({
tenantId,
saleInvoice,
oldSaleInvoice,
saleInvoiceId,
}) {
const { customerRepository } = this.tenancy.repositories(tenantId);
this.logger.info('[sale_invoice] trying to change diff customer balance.', {
tenantId,
});
await customerRepository.changeDiffBalance(
saleInvoice.customerId,
saleInvoice.balance,
oldSaleInvoice.balance,
oldSaleInvoice.customerId
);
}
/**
* Records journal entries of the non-inventory invoice.
*/
@On(events.saleInvoice.onEdited)
public async handleRewriteJournalEntriesOnceInvoiceEdit({
tenantId,
saleInvoiceId,
saleInvoice,
authorizedUser,
}) {
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
const saleInvoiceWithItems = await saleInvoiceRepository.findOneById(
saleInvoiceId,
'entries.item'
);
await this.saleInvoicesService.writesIncomeJournalEntries(
tenantId,
saleInvoiceWithItems,
true
);
}
/**
* Handles customer balance decrement once sale invoice deleted.
*/
@On(events.saleInvoice.onDeleted)
public async handleCustomerBalanceDecrement({
tenantId,
saleInvoiceId,
oldSaleInvoice,
}) {
const { customerRepository } = this.tenancy.repositories(tenantId);
this.logger.info('[sale_invoice] trying to decrement customer balance.', {
tenantId,
});
await customerRepository.changeBalance(
oldSaleInvoice.customerId,
oldSaleInvoice.balance * -1
);
}
/**
* Handle reverting journal entries once sale invoice delete.
*/
@On(events.saleInvoice.onDelete)
public async handleRevertingInvoiceJournalEntriesOnDelete({
tenantId,
saleInvoiceId,
}) {
await this.saleInvoicesService.revertInvoiceJournalEntries(
tenantId,
saleInvoiceId,
);
}
/**
* Handles deleting the inventory transactions once the invoice deleted.
*/
@On(events.saleInvoice.onDelete)
public async handleDeletingInventoryTransactions({
tenantId,
saleInvoiceId,
oldSaleInvoice,
}) {
this.logger.info(
'[sale_invoice] trying to revert inventory transactions.',
{
tenantId,
saleInvoiceId,
}
);
await this.saleInvoicesService.revertInventoryTransactions(
tenantId,
saleInvoiceId
);
}
/**
* Schedules compute invoice items cost job.
*/
@On(events.saleInvoice.onInventoryTransactionsCreated)
public async handleComputeCostsOnInventoryTransactionsCreated({
tenantId,
saleInvoiceId,
}) {
this.logger.info(
'[sale_invoice] trying to compute the invoice items cost.',
{
tenantId,
saleInvoiceId,
}
);
await this.salesInvoicesCost.scheduleComputeCostByInvoiceId(
tenantId,
saleInvoiceId
);
}
/**
* Schedules compute items cost once the inventory transactions deleted.
*/
@On(events.saleInvoice.onInventoryTransactionsDeleted)
public async handleComputeCostsOnInventoryTransactionsDeleted({
tenantId,
saleInvoiceId,
oldInventoryTransactions,
}) {
const inventoryItemsIds = map(oldInventoryTransactions, 'itemId');
const startingDates = map(oldInventoryTransactions, 'date');
const startingDate = head(startingDates);
await this.salesInvoicesCost.scheduleComputeCostByItemsIds(
tenantId,
inventoryItemsIds,
startingDate
);
}
/**
* Decrements the sale invoice items once the invoice deleted.
*/
@On(events.saleInvoice.onDeleted)
public async handleIncrementSaleInvoiceItemsQuantity({
tenantId,
oldSaleInvoice,
}) {
await this.itemsEntriesService.incrementItemsEntries(
tenantId,
oldSaleInvoice.entries
);
}
/**
* Handle increment/decrement the different items quantity once the sale invoice be edited.
*/
@On(events.saleInvoice.onEdited)
public async handleChangeSaleInvoiceItemsQuantityOnEdit({
tenantId,
saleInvoice,
oldSaleInvoice,
}) {
await this.itemsEntriesService.changeItemsQuantity(
tenantId,
saleInvoice.entries.map((entry) => ({
...entry,
quantity: entry.quantity * -1,
})),
oldSaleInvoice.entries.map((entry) => ({
...entry,
quantity: entry.quantity * -1,
}))
);
}
}

View File

@@ -174,8 +174,6 @@ const getDefinedOption = (key, group) => {
const isDefinedOptionConfigurable = (key, group) => { const isDefinedOptionConfigurable = (key, group) => {
const definedOption = getDefinedOption(key, group); const definedOption = getDefinedOption(key, group);
console.log(definedOption, 'definedOption');
return definedOption?.config || false; return definedOption?.config || false;
}; };