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],
);
console.log(isNewMode, 'Val');
useEffect(() => {
!isNewMode
? 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 asyncMiddleware from 'api/middleware/asyncMiddleware';
import BaseController from '../BaseController';
@@ -48,7 +48,7 @@ export default class GeneralLedgerReportController extends BaseController{
* @param {Request} req -
* @param {Response} res -
*/
async generalLedger(req: Request, res: Response) {
async generalLedger(req: Request, res: Response, next: NextFunction) {
const { tenantId, settings } = req;
const filter = this.matchedQueryData(req);
@@ -68,7 +68,7 @@ export default class GeneralLedgerReportController extends BaseController{
query: this.transfromToResponse(query),
});
} catch (error) {
console.log(error);
next(error);
}
}
}

View File

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

View File

@@ -73,7 +73,7 @@ export default class ProfitLossSheetController extends BaseController {
query: this.transfromToResponse(query),
});
} 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.',
});
} catch (error) {
console.log(error);
next(error);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,40 +1,50 @@
import { ISalesInvoicesFilter } from "./SaleInvoice";
import { ISalesInvoicesFilter } from './SaleInvoice';
export interface ISaleReceipt {
id?: number,
customerId: number,
depositAccountId: number,
receiptDate: Date,
sendToEmail: string,
referenceNo: string,
receiptMessage: string,
receiptNumber: string,
statement: string,
closedAt: Date|string,
entries: any[],
};
id?: number;
customerId: number;
depositAccountId: number;
receiptDate: Date;
sendToEmail: string;
referenceNo: string;
receiptMessage: string;
receiptNumber: string;
amount: number;
statement: string;
closedAt: Date | string;
entries: any[];
}
export interface ISalesReceiptsFilter {
};
export interface ISalesReceiptsFilter {}
export interface ISaleReceiptDTO {
customerId: number,
depositAccountId: number,
receiptDate: Date,
sendToEmail: string,
referenceNo: string,
receiptMessage: string,
statement: string,
closed: boolean,
entries: any[],
};
customerId: number;
depositAccountId: number;
receiptDate: Date;
sendToEmail: string;
referenceNo: string;
receiptMessage: string;
statement: string;
closed: boolean;
entries: any[];
}
export interface ISalesReceiptService {
createSaleReceipt(tenantId: number, saleReceiptDTO: ISaleReceiptDTO): Promise<void>;
createSaleReceipt(
tenantId: number,
saleReceiptDTO: ISaleReceiptDTO
): Promise<void>;
editSaleReceipt(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.
* @param {} -
*/
public async handler(job, done: Function): Promise<void> {
const Logger = Container.get('logger');
@@ -51,7 +50,6 @@ export default class ComputeItemCostJob {
/**
* Handle the job started.
* @param {Job} job - .
*/
async onJobStart(job) {
const { startingDate, itemId, tenantId } = job.attrs.data;

View File

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

View File

@@ -3,7 +3,7 @@ import SubscriptionService from 'services/Subscription/Subscription';
export default class MailNotificationTrialEnd {
/**
*
*
* @param {Job} job -
*/
handler(job) {
@@ -12,16 +12,23 @@ export default class MailNotificationTrialEnd {
const subscriptionService = Container.get(SubscriptionService);
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 {
subscriptionService.mailMessages.sendRemainingTrialPeriod(
phoneNumber, remainingDays,
phoneNumber,
remainingDays
);
Logger.info(
`Send mail notification subscription end soon - finished: ${job.attrs.data}`
);
} catch (error) {
Logger.info(
`Send mail notification subscription end soon - failed: ${job.attrs.data}, error: ${e}`
);
Logger.info(`Send mail notification subscription end soon - finished: ${job.attrs.data}`);
} catch(error) {
Logger.info(`Send mail notification subscription end soon - failed: ${job.attrs.data}, error: ${e}`);
done(e);
}
}
}
}

View File

@@ -5,13 +5,28 @@ import 'subscribers/organization';
import 'subscribers/inviteUser';
import 'subscribers/manualJournals';
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/vendors';
import 'subscribers/paymentMades';
import 'subscribers/paymentReceives';
import 'subscribers/saleEstimates';
import 'subscribers/saleReceipts';
import 'subscribers/inventory';
import 'subscribers/items';

View File

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

View File

@@ -1,6 +1,6 @@
import { sumBy, chain } from 'lodash';
import moment, { LongDateFormatKey } from 'moment';
import { IBill, IManualJournalEntry, ISystemUser } from 'interfaces';
import { IBill, IManualJournalEntry, ISaleReceipt, ISystemUser } from 'interfaces';
import JournalPoster from './JournalPoster';
import JournalEntry from './JournalEntry';
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 {
IInventoryLotCost,
IInventoryTransaction,
IItem,
TInventoryTransactionDirection,
IItemEntry,
IItemEntryTransactionType,
} from 'interfaces';
import InventoryAverageCost from 'services/Inventory/InventoryAverageCost';
import InventoryCostLotTracker from 'services/Inventory/InventoryCostLotTracker';
import TenancyService from 'services/Tenancy/TenancyService';
import events from 'subscribers/events';
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
type TCostMethod = 'FIFO' | 'LIFO' | 'AVG';
@@ -26,22 +28,23 @@ export default class InventoryService {
@EventDispatcher()
eventDispatcher: EventDispatcherInterface;
@Inject()
itemsEntriesService: ItemsEntriesService;
/**
* Transforms the items entries to inventory transactions.
*/
transformItemEntriesToInventory(
itemEntries: IItemEntry[],
transactionType: string,
transactionId: number,
direction: 'IN' | 'OUT',
direction: TInventoryTransactionDirection,
date: Date | string,
lotNumber: number
) {
return itemEntries.map((entry: IItemEntry) => ({
...pick(entry, ['itemId', 'quantity', 'rate']),
lotNumber,
transactionType,
transactionId,
transactionType: entry.referenceType,
transactionId: entry.referenceId,
direction,
date,
entryId: entry.id,
@@ -130,7 +133,6 @@ export default class InventoryService {
tenantId,
}
);
// Triggers `onComputeItemCostJobScheduled` event.
await this.eventDispatcher.dispatch(
events.inventory.onComputeItemCostJobScheduled,
@@ -157,10 +159,12 @@ export default class InventoryService {
}
/**
*
* @param {number} tenantId
* @param {IInventoryTransaction} inventoryEntry
* @param {boolean} deleteOld
* Writes the inventory transactiosn on the storage from the given
* inventory transactions entries.
*
* @param {number} tenantId -
* @param {IInventoryTransaction} inventoryEntry -
* @param {boolean} deleteOld -
*/
async recordInventoryTransaction(
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.
* @param {number} tenantId - Tenant id.
* @param {string} transactionType
* @param {number} transactionId
* @return {Promise}
* @return {Promise<{
* oldInventoryTransactions: IInventoryTransaction[]
* }>}
*/
async deleteInventoryTransactions(
tenantId: number,
transactionId: number,
transactionType: string
): Promise<void> {
const { InventoryTransaction } = this.tenancy.models(tenantId);
): Promise<{ oldInventoryTransactions: IInventoryTransaction[] }> {
const { inventoryTransactionRepository } = this.tenancy.repositories(tenantId);
await InventoryTransaction.query()
.where('transaction_type', transactionType)
.where('transaction_id', transactionId)
.delete();
// Retrieve the inventory transactions of the given sale invoice.
const oldInventoryTransactions = await inventoryTransactionRepository.find({
transactionId,
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 { Inject, Service } from 'typedi';
import {
@@ -16,19 +16,17 @@ import { formatDateFields } from 'utils';
import {
IBillDTO,
IBill,
IItem,
ISystemUser,
IBillEditDTO,
IPaginationMeta,
IFilterMeta,
IBillsFilter,
IItemEntry,
IInventoryTransaction,
} from 'interfaces';
import { ServiceError } from 'exceptions';
import ItemsService from 'services/Items/ItemsService';
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
import JournalCommands from 'services/Accounting/JournalCommands';
import JournalPosterService from 'services/Sales/JournalPosterService';
const ERRORS = {
BILL_NOT_FOUND: 'BILL_NOT_FOUND',
@@ -71,6 +69,9 @@ export default class BillsService extends SalesInvoicesCost {
@Inject()
itemsEntriesService: ItemsEntriesService;
@Inject()
journalPosterService: JournalPosterService;
/**
* Validates whether the vendor is exist.
* @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.
* @param {number} tenantId -
@@ -544,36 +445,6 @@ export default class BillsService extends SalesInvoicesCost {
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.
* @param {number} tenantId
@@ -588,10 +459,89 @@ export default class BillsService extends SalesInvoicesCost {
if (oldBill.isOpen) {
throw new ServiceError(ERRORS.BILL_ALREADY_OPEN);
}
// Record the bill opened at on the storage.
await Bill.query().findById(billId).patch({
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 { Service, Inject } from 'typedi';
import { IEstimatesFilter, IFilterMeta, IPaginationMeta, ISaleEstimate, ISaleEstimateDTO } from 'interfaces';
import {
IEstimatesFilter,
IFilterMeta,
IPaginationMeta,
ISaleEstimate,
ISaleEstimateDTO,
} from 'interfaces';
import {
EventDispatcher,
EventDispatcherInterface,
@@ -14,7 +20,6 @@ import { ServiceError } from 'exceptions';
import CustomersService from 'services/Contacts/CustomersService';
import moment from 'moment';
const ERRORS = {
SALE_ESTIMATE_NOT_FOUND: 'SALE_ESTIMATE_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_ALREADY_REJECTED: 'SALE_ESTIMATE_ALREADY_REJECTED',
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.
* @Service
@@ -52,26 +58,32 @@ export default class SaleEstimateService {
/**
* Retrieve sale estimate or throw service error.
* @param {number} tenantId
* @param {number} tenantId
* @return {ISaleEstimate}
*/
async getSaleEstimateOrThrowError(tenantId: number, saleEstimateId: number) {
const { SaleEstimate } = this.tenancy.models(tenantId);
const foundSaleEstimate = await SaleEstimate.query().findById(saleEstimateId);
const foundSaleEstimate = await SaleEstimate.query().findById(
saleEstimateId
);
if (!foundSaleEstimate) {
throw new ServiceError(ERRORS.SALE_ESTIMATE_NOT_FOUND);
}
return foundSaleEstimate;
}
/**
* Validate the estimate number unique on the storage.
* @param {Request} req
* @param {Response} res
* @param {Function} next
* @param {Request} req
* @param {Response} res
* @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 foundSaleEstimate = await SaleEstimate.query()
@@ -88,34 +100,35 @@ export default class SaleEstimateService {
/**
* Transform DTO object ot model object.
* @param {number} tenantId
* @param {ISaleEstimateDTO} saleEstimateDTO
* @param {ISaleEstimate} oldSaleEstimate
* @param {number} tenantId
* @param {ISaleEstimateDTO} saleEstimateDTO
* @param {ISaleEstimate} oldSaleEstimate
* @return {ISaleEstimate}
*/
transformDTOToModel(
tenantId: number,
estimateDTO: ISaleEstimateDTO,
oldSaleEstimate?: ISaleEstimate,
oldSaleEstimate?: ISaleEstimate
): ISaleEstimate {
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 {
amount,
...formatDateFields(
omit(estimateDTO, ['delivered', 'entries']),
['estimateDate', 'expirationDate']
),
entries: estimateDTO.entries.map((entry) => ({
...formatDateFields(omit(estimateDTO, ['delivered', 'entries']), [
'estimateDate',
'expirationDate',
]),
entries: estimateDTO.entries.map((entry) => ({
reference_type: 'SaleEstimate',
...omit(entry, ['total', 'amount', 'id']),
})),
// Avoid rewrite the deliver date in edit mode when already published.
...(estimateDTO.delivered && (!oldSaleEstimate?.deliveredAt)) && ({
deliveredAt: moment().toMySqlDateTime(),
}),
...(estimateDTO.delivered &&
!oldSaleEstimate?.deliveredAt && {
deliveredAt: moment().toMySqlDateTime(),
}),
};
}
@@ -139,23 +152,35 @@ export default class SaleEstimateService {
// Validate estimate number uniquiness on the storage.
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.
await this.customersService.getCustomer(tenantId, estimateDTO.customerId);
// 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.
await this.itemsEntriesService.validateNonSellableEntriesItems(tenantId, estimateDTO.entries);
await this.itemsEntriesService.validateNonSellableEntriesItems(
tenantId,
estimateDTO.entries
);
const saleEstimate = await SaleEstimate.query()
.upsertGraphAndFetch({ ...estimateObj });
const saleEstimate = await SaleEstimate.query().upsertGraphAndFetch({
...estimateObj,
});
this.logger.info('[sale_estimate] insert sale estimated success.');
await this.eventDispatcher.dispatch(events.saleEstimate.onCreated, {
tenantId, saleEstimate, saleEstimateId: saleEstimate.id,
tenantId,
saleEstimate,
saleEstimateId: saleEstimate.id,
});
return saleEstimate;
@@ -175,38 +200,61 @@ export default class SaleEstimateService {
estimateDTO: ISaleEstimateDTO
): Promise<ISaleEstimate> {
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.
const estimateObj = this.transformDTOToModel(tenantId, estimateDTO, oldSaleEstimate);
const estimateObj = this.transformDTOToModel(
tenantId,
estimateDTO,
oldSaleEstimate
);
// Validate estimate number uniquiness on the storage.
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.
await this.customersService.getCustomer(tenantId, estimateDTO.customerId);
// 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.
await this.itemsEntriesService.validateItemsIdsExistance(tenantId, estimateDTO.entries);
await this.itemsEntriesService.validateItemsIdsExistance(
tenantId,
estimateDTO.entries
);
// 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.');
const saleEstimate = await SaleEstimate.query()
.upsertGraphAndFetch({
id: estimateId,
...estimateObj
});
const saleEstimate = await SaleEstimate.query().upsertGraphAndFetch({
id: estimateId,
...estimateObj,
});
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;
}
@@ -218,28 +266,41 @@ export default class SaleEstimateService {
* @param {IEstimate} estimateId
* @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);
// 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.
if (oldSaleEstimate.convertedToInvoiceId) {
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()
.where('reference_id', estimateId)
.where('reference_type', 'SaleEstimate')
.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, {
tenantId, saleEstimateId: estimateId, oldSaleEstimate,
tenantId,
saleEstimateId: estimateId,
oldSaleEstimate,
});
}
@@ -255,7 +316,7 @@ export default class SaleEstimateService {
.findById(estimateId)
.withGraphFetched('entries')
.withGraphFetched('customer');
if (!estimate) {
throw new ServiceError(ERRORS.SALE_ESTIMATE_NOT_FOUND);
}
@@ -270,19 +331,26 @@ export default class SaleEstimateService {
public async estimatesList(
tenantId: number,
estimatesFilter: IEstimatesFilter
): Promise<{ salesEstimates: ISaleEstimate[], pagination: IPaginationMeta, filterMeta: IFilterMeta }> {
): Promise<{
salesEstimates: ISaleEstimate[];
pagination: IPaginationMeta;
filterMeta: IFilterMeta;
}> {
const { SaleEstimate } = this.tenancy.models(tenantId);
const dynamicFilter = await this.dynamicListService.dynamicList(tenantId, SaleEstimate, estimatesFilter);
const { results, pagination } = await SaleEstimate.query().onBuild(builder => {
builder.withGraphFetched('customer');
builder.withGraphFetched('entries');
dynamicFilter.buildQuery()(builder);
}).pagination(
estimatesFilter.page - 1,
estimatesFilter.pageSize,
const dynamicFilter = await this.dynamicListService.dynamicList(
tenantId,
SaleEstimate,
estimatesFilter
);
const { results, pagination } = await SaleEstimate.query()
.onBuild((builder) => {
builder.withGraphFetched('customer');
builder.withGraphFetched('entries');
dynamicFilter.buildQuery()(builder);
})
.pagination(estimatesFilter.page - 1, estimatesFilter.pageSize);
return {
salesEstimates: results,
pagination,
@@ -299,12 +367,15 @@ export default class SaleEstimateService {
async convertEstimateToInvoice(
tenantId: number,
estimateId: number,
invoiceId: number,
invoiceId: number
): Promise<void> {
const { SaleEstimate } = this.tenancy.models(tenantId);
// 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({
convertedToInvoiceId: invoiceId,
@@ -320,16 +391,18 @@ export default class SaleEstimateService {
*/
async unlinkConvertedEstimateFromInvoice(
tenantId: number,
invoiceId: number,
invoiceId: number
): Promise<void> {
const { SaleEstimate } = this.tenancy.models(tenantId);
await SaleEstimate.query().where({
convertedToInvoiceId: invoiceId,
}).patch({
convertedToInvoiceId: null,
convertedToInvoiceAt: null,
});
await SaleEstimate.query()
.where({
convertedToInvoiceId: invoiceId,
})
.patch({
convertedToInvoiceId: null,
convertedToInvoiceAt: null,
});
}
/**
@@ -339,12 +412,15 @@ export default class SaleEstimateService {
*/
public async deliverSaleEstimate(
tenantId: number,
saleEstimateId: number,
saleEstimateId: number
): Promise<void> {
const { SaleEstimate } = this.tenancy.models(tenantId);
// 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.
if (saleEstimate.isDelivered) {
@@ -352,29 +428,32 @@ export default class SaleEstimateService {
}
// Record the delivered at on the storage.
await SaleEstimate.query().where('id', saleEstimateId).patch({
deliveredAt: moment().toMySqlDateTime()
deliveredAt: moment().toMySqlDateTime(),
});
}
/**
* Mark the sale estimate as approved from the customer.
* @param {number} tenantId
* @param {number} saleEstimateId
* @param {number} tenantId
* @param {number} saleEstimateId
*/
public async approveSaleEstimate(
tenantId: number,
saleEstimateId: number,
saleEstimateId: number
): Promise<void> {
const { SaleEstimate } = this.tenancy.models(tenantId);
// 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.
if (!saleEstimate.isDelivered) {
throw new ServiceError(ERRORS.SALE_ESTIMATE_NOT_DELIVERED);
}
// Throws error in case the sale estimate already approved.
// Throws error in case the sale estimate already approved.
if (saleEstimate.isApproved) {
throw new ServiceError(ERRORS.SALE_ESTIMATE_ALREADY_APPROVED);
}
@@ -386,17 +465,20 @@ export default class SaleEstimateService {
/**
* Mark the sale estimate as rejected from the customer.
* @param {number} tenantId
* @param {number} saleEstimateId
* @param {number} tenantId
* @param {number} saleEstimateId
*/
public async rejectSaleEstimate(
tenantId: number,
saleEstimateId: number,
saleEstimateId: number
): Promise<void> {
const { SaleEstimate } = this.tenancy.models(tenantId);
// 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.
if (!saleEstimate.isDelivered) {
@@ -412,4 +494,4 @@ export default class SaleEstimateService {
approvedAt: null,
});
}
}
}

View File

@@ -341,7 +341,7 @@ export default class SaleInvoicesService {
}
// Record the delivered at on the storage.
await saleInvoiceRepository.update(
{ deliveredAt: moment().toMySqlDateTime(), },
{ deliveredAt: moment().toMySqlDateTime() },
{ id: saleInvoiceId }
);
// Triggers `onSaleInvoiceDelivered` event.
@@ -416,48 +416,17 @@ export default class SaleInvoicesService {
*/
public async recordInventoryTranscactions(
tenantId: number,
saleInvoiceId: number,
saleInvoiceDate: Date,
saleInvoice: ISaleInvoice,
override?: boolean
): Promise<void> {
// Gets the next inventory lot number.
const lotNumber = this.inventoryService.getNextLotNumber(tenantId);
// Loads the inventory items entries of the given sale invoice.
const inventoryEntries = await this.itemsEntriesService.getInventoryEntries(
await this.inventoryService.recordInventoryTransactionsFromItemsEntries(
tenantId,
saleInvoice.id,
'SaleInvoice',
saleInvoiceId
);
// 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,
saleInvoice.invoiceDate,
'OUT',
saleInvoiceDate,
lotNumber
);
// Records the inventory transactions of the given sale invoice.
await this.inventoryService.recordInventoryTransactions(
tenantId,
inventoryTranscations,
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([
journal.deleteEntries(),
journal.saveBalance(),
journal.saveEntries()
journal.saveEntries(),
]);
}
@@ -505,25 +474,14 @@ export default class SaleInvoicesService {
tenantId: number,
saleInvoiceId: number
): 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.
await this.inventoryService.deleteInventoryTransactions(
const {
oldInventoryTransactions,
} = await this.inventoryService.deleteInventoryTransactions(
tenantId,
saleInvoiceId,
'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.
* @async
* @param {Number} saleInvoiceId
* @param {Number} saleInvoiceId -
* @param {ISystemUser} authorizedUser -
* @return {Promise<ISaleInvoice>}
*/
public async getSaleInvoice(
tenantId: number,

View File

@@ -1,9 +1,8 @@
import { Container, Service, Inject } from 'typedi';
import { map } from 'lodash';
import JournalPoster from 'services/Accounting/JournalPoster';
import InventoryService from 'services/Inventory/Inventory';
import TenancyService from 'services/Tenancy/TenancyService';
import { ISaleInvoice, IItemEntry, IInventoryLotCost, IItem } from 'interfaces';
import { IInventoryLotCost, IItem } from 'interfaces';
import JournalCommands from 'services/Accounting/JournalCommands';
@Service()
@@ -39,86 +38,6 @@ export default class SaleInvoicesCost {
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.
* @param {Date} startingDate

View File

@@ -6,7 +6,9 @@ import {
EventDispatcherInterface,
} from 'decorators/eventDispatcher';
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 TenancyService from 'services/Tenancy/TenancyService';
import { formatDateFields } from 'utils';
@@ -15,15 +17,16 @@ import DynamicListingService from 'services/DynamicListing/DynamicListService';
import { ServiceError } from 'exceptions';
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
import { ItemEntry } from 'models';
import InventoryService from 'services/Inventory/Inventory';
const ERRORS = {
SALE_RECEIPT_NOT_FOUND: 'SALE_RECEIPT_NOT_FOUND',
DEPOSIT_ACCOUNT_NOT_FOUND: 'DEPOSIT_ACCOUNT_NOT_FOUND',
DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET: 'DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET',
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()
export default class SalesReceiptService {
@Inject()
@@ -38,6 +41,9 @@ export default class SalesReceiptService {
@Inject()
itemsEntriesService: ItemsEntriesService;
@Inject()
inventoryService: InventoryService;
@EventDispatcher()
eventDispatcher: EventDispatcherInterface;
@@ -46,37 +52,56 @@ export default class SalesReceiptService {
/**
* Validate whether sale receipt exists on the storage.
* @param {number} tenantId -
* @param {number} saleReceiptId -
* @param {number} tenantId -
* @param {number} saleReceiptId -
*/
async getSaleReceiptOrThrowError(tenantId: number, saleReceiptId: number) {
const { SaleReceipt } = this.tenancy.models(tenantId);
this.logger.info('[sale_receipt] trying to validate existance.', { tenantId, saleReceiptId });
const foundSaleReceipt = await SaleReceipt.query().findById(saleReceiptId);
this.logger.info('[sale_receipt] trying to validate existance.', {
tenantId,
saleReceiptId,
});
const foundSaleReceipt = await SaleReceipt.query()
.findById(saleReceiptId)
.withGraphFetched('entries');
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);
}
return foundSaleReceipt;
}
/**
* Validate whether sale receipt deposit account exists on the storage.
* @param {number} tenantId -
* @param {number} tenantId -
* @param {number} accountId -
*/
async validateReceiptDepositAccountExistance(tenantId: number, accountId: number) {
const { accountRepository, accountTypeRepository } = this.tenancy.repositories(tenantId);
async validateReceiptDepositAccountExistance(
tenantId: number,
accountId: number
) {
const {
accountRepository,
accountTypeRepository,
} = this.tenancy.repositories(tenantId);
const depositAccount = await accountRepository.findOneById(accountId);
if (!depositAccount) {
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);
}
}
@@ -87,10 +112,17 @@ export default class SalesReceiptService {
* @param {string} receiptNumber -
* @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);
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()
.findOne('receipt_number', receiptNumber)
.onBuild((builder) => {
@@ -98,9 +130,11 @@ export default class SalesReceiptService {
builder.whereNot('id', notReceiptId);
}
});
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);
}
}
@@ -115,18 +149,20 @@ export default class SalesReceiptService {
saleReceiptDTO: ISaleReceiptDTO,
oldSaleReceipt?: ISaleReceipt
): ISaleReceipt {
const amount = sumBy(saleReceiptDTO.entries, e => ItemEntry.calcAmount(e));
const amount = sumBy(saleReceiptDTO.entries, (e) =>
ItemEntry.calcAmount(e)
);
return {
amount,
...formatDateFields(
omit(saleReceiptDTO, ['closed', 'entries']),
['receiptDate']
),
...formatDateFields(omit(saleReceiptDTO, ['closed', 'entries']), [
'receiptDate',
]),
// Avoid rewrite the deliver date in edit mode when already published.
...(saleReceiptDTO.closed && (!oldSaleReceipt?.closedAt)) && ({
closedAt: moment().toMySqlDateTime(),
}),
...(saleReceiptDTO.closed &&
!oldSaleReceipt?.closedAt && {
closedAt: moment().toMySqlDateTime(),
}),
entries: saleReceiptDTO.entries.map((entry) => ({
reference_type: 'SaleReceipt',
...omit(entry, ['id', 'amount']),
@@ -140,31 +176,56 @@ export default class SalesReceiptService {
* @param {ISaleReceipt} saleReceipt
* @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);
// Transform sale receipt DTO to model.
const saleReceiptObj = this.transformObjectDTOToModel(saleReceiptDTO);
// 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.
await this.itemsEntriesService.validateItemsIdsExistance(tenantId, saleReceiptDTO.entries);
await this.itemsEntriesService.validateItemsIdsExistance(
tenantId,
saleReceiptDTO.entries
);
// Validate the sellable items.
await this.itemsEntriesService.validateNonSellableEntriesItems(tenantId, saleReceiptDTO.entries);
await this.itemsEntriesService.validateNonSellableEntriesItems(
tenantId,
saleReceiptDTO.entries
);
// Validate sale receipt number uniuqiness.
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 });
const saleReceipt = await SaleReceipt.query().insertGraphAndFetch({ ...saleReceiptObj });
await this.eventDispatcher.dispatch(events.saleReceipt.onCreated, { tenantId, saleReceipt });
this.logger.info('[sale_receipt] sale receipt inserted successfully.', { tenantId });
this.logger.info('[sale_receipt] trying to insert sale receipt graph.', {
tenantId,
saleReceiptDTO,
});
const saleReceipt = await SaleReceipt.query().upsertGraph({
...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;
}
@@ -175,37 +236,61 @@ export default class SalesReceiptService {
* @param {ISaleReceipt} saleReceipt
* @return {void}
*/
public async editSaleReceipt(tenantId: number, saleReceiptId: number, saleReceiptDTO: any) {
const { SaleReceipt, ItemEntry } = this.tenancy.models(tenantId);
public async editSaleReceipt(
tenantId: number,
saleReceiptId: number,
saleReceiptDTO: any
) {
const { SaleReceipt } = this.tenancy.models(tenantId);
// 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.
const saleReceiptObj = this.transformObjectDTOToModel(saleReceiptDTO, oldSaleReceipt);
const saleReceiptObj = this.transformObjectDTOToModel(
saleReceiptDTO,
oldSaleReceipt
);
// 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.
await this.itemsEntriesService.validateItemsIdsExistance(tenantId, saleReceiptDTO.entries);
await this.itemsEntriesService.validateItemsIdsExistance(
tenantId,
saleReceiptDTO.entries
);
// Validate the sellable items.
await this.itemsEntriesService.validateNonSellableEntriesItems(tenantId, saleReceiptDTO.entries);
await this.itemsEntriesService.validateNonSellableEntriesItems(
tenantId,
saleReceiptDTO.entries
);
// Validate sale receipt number uniuqiness.
if (saleReceiptDTO.receiptNumber) {
await this.validateReceiptNumberUnique(tenantId, saleReceiptDTO.receiptNumber, saleReceiptId);
await this.validateReceiptNumberUnique(
tenantId,
saleReceiptDTO.receiptNumber,
saleReceiptId
);
}
const saleReceipt = await SaleReceipt.query().upsertGraphAndFetch({
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, {
oldSaleReceipt, tenantId, saleReceiptId, saleReceipt,
tenantId,
oldSaleReceipt,
saleReceipt,
saleReceiptId,
});
return saleReceipt;
}
@@ -218,20 +303,31 @@ export default class SalesReceiptService {
public async deleteSaleReceipt(tenantId: number, saleReceiptId: number) {
const { SaleReceipt, ItemEntry } = this.tenancy.models(tenantId);
const oldSaleReceipt = await this.getSaleReceiptOrThrowError(
tenantId,
saleReceiptId
);
await ItemEntry.query()
.where('reference_id', saleReceiptId)
.where('reference_type', 'SaleReceipt')
.delete();
await SaleReceipt.query().where('id', saleReceiptId).delete();
this.logger.info('[sale_receipt] deleted successfully.', { tenantId, saleReceiptId });
await this.eventDispatcher.dispatch(events.saleReceipt.onDeleted);
this.logger.info('[sale_receipt] deleted successfully.', {
tenantId,
saleReceiptId,
});
await this.eventDispatcher.dispatch(events.saleReceipt.onDeleted, {
tenantId,
saleReceiptId,
oldSaleReceipt
});
}
/**
* Retrieve sale receipt with associated entries.
* @param {Integer} saleReceiptId
* @param {Integer} saleReceiptId
* @return {ISaleReceipt}
*/
async getSaleReceipt(tenantId: number, saleReceiptId: number) {
@@ -242,7 +338,7 @@ export default class SalesReceiptService {
.withGraphFetched('entries')
.withGraphFetched('customer')
.withGraphFetched('depositAccount');
if (!saleReceipt) {
throw new ServiceError(ERRORS.SALE_RECEIPT_NOT_FOUND);
}
@@ -251,28 +347,37 @@ export default class SalesReceiptService {
/**
* Retrieve sales receipts paginated and filterable list.
* @param {number} tenantId
* @param {ISaleReceiptFilter} salesReceiptsFilter
* @param {number} tenantId
* @param {ISaleReceiptFilter} salesReceiptsFilter
*/
public async salesReceiptsList(
tenantId: number,
salesReceiptsFilter: ISaleReceiptFilter,
): Promise<{ salesReceipts: ISaleReceipt[], pagination: IPaginationMeta, filterMeta: IFilterMeta }> {
salesReceiptsFilter: ISaleReceiptFilter
): Promise<{
salesReceipts: ISaleReceipt[];
pagination: IPaginationMeta;
filterMeta: IFilterMeta;
}> {
const { SaleReceipt } = this.tenancy.models(tenantId);
const dynamicFilter = await this.dynamicListService.dynamicList(tenantId, SaleReceipt, salesReceiptsFilter);
this.logger.info('[sale_receipt] try to get sales receipts list.', { tenantId });
const { results, pagination } = await SaleReceipt.query().onBuild((builder) => {
builder.withGraphFetched('depositAccount');
builder.withGraphFetched('customer');
builder.withGraphFetched('entries');
dynamicFilter.buildQuery()(builder);
}).pagination(
salesReceiptsFilter.page - 1,
salesReceiptsFilter.pageSize,
const dynamicFilter = await this.dynamicListService.dynamicList(
tenantId,
SaleReceipt,
salesReceiptsFilter
);
this.logger.info('[sale_receipt] try to get sales receipts list.', {
tenantId,
});
const { results, pagination } = await SaleReceipt.query()
.onBuild((builder) => {
builder.withGraphFetched('depositAccount');
builder.withGraphFetched('customer');
builder.withGraphFetched('entries');
dynamicFilter.buildQuery()(builder);
})
.pagination(salesReceiptsFilter.page - 1, salesReceiptsFilter.pageSize);
return {
salesReceipts: results,
pagination,
@@ -282,8 +387,8 @@ export default class SalesReceiptService {
/**
* Mark the given sale receipt as closed.
* @param {number} tenantId
* @param {number} saleReceiptId
* @param {number} tenantId
* @param {number} saleReceiptId
* @return {Promise<void>}
*/
async closeSaleReceipt(
@@ -293,7 +398,10 @@ export default class SalesReceiptService {
const { SaleReceipt } = this.tenancy.models(tenantId);
// 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.
if (oldSaleReceipt.isClosed) {
@@ -304,4 +412,96 @@ export default class SalesReceiptService {
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 { On, EventSubscriber } from "event-dispatch";
import { On, EventSubscriber } from 'event-dispatch';
import events from 'subscribers/events';
import TenancyService from 'services/Tenancy/TenancyService';
import SettingsService from 'services/Settings/SettingsService';
@@ -15,7 +15,7 @@ export default class SaleReceiptSubscriber {
this.tenancy = Container.get(TenancyService);
this.settingsService = Container.get(SettingsService);
}
/**
* Handle sale receipt increment next number once be created.
*/
@@ -26,4 +26,4 @@ export default class SaleReceiptSubscriber {
group: 'sales_receipts',
});
}
}
}

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

View File

@@ -1,5 +1,6 @@
import { Container } from 'typedi';
import { EventSubscriber, On } from 'event-dispatch';
import { map, head } from 'lodash';
import events from 'subscribers/events';
import SaleInvoicesCost from 'services/Sales/SalesInvoicesCost';
@@ -7,16 +8,20 @@ import SaleInvoicesCost from 'services/Sales/SalesInvoicesCost';
export class InventorySubscriber {
depends: number = 0;
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.
*/
@On(events.inventory.onComputeItemCostJobCompleted)
async onComputeItemCostJobFinished({ itemId, tenantId, startingDate }) {
const saleInvoicesCost = Container.get(SaleInvoicesCost);
const agenda = Container.get('agenda');
const dependsComputeJobs = await agenda.jobs({
const dependsComputeJobs = await this.agenda.jobs({
name: 'compute-item-cost',
nextRunAt: { $ne: null },
'data.tenantId': tenantId,
@@ -25,10 +30,53 @@ export class InventorySubscriber {
if (dependsComputeJobs.length === 0) {
this.startingDate = null;
await saleInvoicesCost.scheduleWriteJournalEntries(
await this.saleInvoicesCost.scheduleWriteJournalEntries(
tenantId,
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 definedOption = getDefinedOption(key, group);
console.log(definedOption, 'definedOption');
return definedOption?.config || false;
};