This commit is contained in:
elforjani3
2020-12-19 13:58:50 +02:00
17 changed files with 452 additions and 258 deletions

View File

@@ -197,8 +197,8 @@ export default class BillsController extends BaseController {
* @param {Response} res * @param {Response} res
*/ */
async editBill(req: Request, res: Response, next: NextFunction) { async editBill(req: Request, res: Response, next: NextFunction) {
const { id: billId, user } = req.params; const { id: billId } = req.params;
const { tenantId } = req; const { tenantId, user } = req;
const billDTO: IBillEditDTO = this.matchedBodyData(req); const billDTO: IBillEditDTO = this.matchedBodyData(req);
try { try {

View File

@@ -339,7 +339,18 @@ export default class PaymentReceivesController extends BaseController {
errors: [{ type: 'INVALID_PAYMENT_AMOUNT', code: 1000 }], errors: [{ type: 'INVALID_PAYMENT_AMOUNT', code: 1000 }],
}); });
} }
console.log(error.errorType); if (error.errorType === 'INVOICES_NOT_DELIVERED_YET') {
return res.boom.badRequest(null, {
errors: [{
type: 'INVOICES_NOT_DELIVERED_YET', code: 200,
data: {
not_delivered_invoices_ids: error.payload.notDeliveredInvoices.map(
(invoice) => invoice.id
)
}
}],
});
}
} }
next(error); next(error);
} }

View File

@@ -7,10 +7,10 @@ import SaleInvoiceService from 'services/Sales/SalesInvoices';
import ItemsService from 'services/Items/ItemsService'; import ItemsService from 'services/Items/ItemsService';
import DynamicListingService from 'services/DynamicListing/DynamicListService'; import DynamicListingService from 'services/DynamicListing/DynamicListService';
import { ServiceError } from 'exceptions'; import { ServiceError } from 'exceptions';
import { ISaleInvoiceOTD, ISalesInvoicesFilter } from 'interfaces'; import { ISaleInvoiceDTO, ISalesInvoicesFilter } from 'interfaces';
@Service() @Service()
export default class SaleInvoicesController extends BaseController{ export default class SaleInvoicesController extends BaseController {
@Inject() @Inject()
itemsService: ItemsService; itemsService: ItemsService;
@@ -34,17 +34,15 @@ export default class SaleInvoicesController extends BaseController{
], ],
this.validationResult, this.validationResult,
asyncMiddleware(this.newSaleInvoice.bind(this)), asyncMiddleware(this.newSaleInvoice.bind(this)),
this.handleServiceErrors, this.handleServiceErrors
); );
router.post( router.post(
'/:id/deliver', '/:id/deliver',
[ [...this.specificSaleInvoiceValidation],
...this.specificSaleInvoiceValidation,
],
this.validationResult, this.validationResult,
asyncMiddleware(this.deliverSaleInvoice.bind(this)), asyncMiddleware(this.deliverSaleInvoice.bind(this)),
this.handleServiceErrors, this.handleServiceErrors
) );
router.post( router.post(
'/:id', '/:id',
[ [
@@ -53,29 +51,28 @@ export default class SaleInvoicesController extends BaseController{
], ],
this.validationResult, this.validationResult,
asyncMiddleware(this.editSaleInvoice.bind(this)), asyncMiddleware(this.editSaleInvoice.bind(this)),
this.handleServiceErrors, this.handleServiceErrors
); );
router.delete( router.delete(
'/:id', '/:id',
this.specificSaleInvoiceValidation, this.specificSaleInvoiceValidation,
this.validationResult, this.validationResult,
asyncMiddleware(this.deleteSaleInvoice.bind(this)), asyncMiddleware(this.deleteSaleInvoice.bind(this)),
this.handleServiceErrors, this.handleServiceErrors
); );
router.get( router.get(
'/payable', [ '/payable',
...this.dueSalesInvoicesListValidationSchema, [...this.dueSalesInvoicesListValidationSchema],
],
this.validationResult, this.validationResult,
asyncMiddleware(this.getPayableInvoices.bind(this)), asyncMiddleware(this.getPayableInvoices.bind(this)),
this.handleServiceErrors, this.handleServiceErrors
); );
router.get( router.get(
'/:id', '/:id',
this.specificSaleInvoiceValidation, this.specificSaleInvoiceValidation,
this.validationResult, this.validationResult,
asyncMiddleware(this.getSaleInvoice.bind(this)), asyncMiddleware(this.getSaleInvoice.bind(this)),
this.handleServiceErrors, this.handleServiceErrors
); );
router.get( router.get(
'/', '/',
@@ -83,8 +80,8 @@ export default class SaleInvoicesController extends BaseController{
this.validationResult, this.validationResult,
asyncMiddleware(this.getSalesInvoices.bind(this)), asyncMiddleware(this.getSalesInvoices.bind(this)),
this.handleServiceErrors, this.handleServiceErrors,
this.dynamicListService.handlerErrorsToResponse, this.dynamicListService.handlerErrorsToResponse
) );
return router; return router;
} }
@@ -131,7 +128,7 @@ export default class SaleInvoicesController extends BaseController{
query('column_sort_by').optional(), query('column_sort_by').optional(),
query('sort_order').optional().isIn(['desc', 'asc']), query('sort_order').optional().isIn(['desc', 'asc']),
query('page').optional().isNumeric().toInt(), query('page').optional().isNumeric().toInt(),
query('page_size').optional().isNumeric().toInt(), query('page_size').optional().isNumeric().toInt(),
]; ];
} }
@@ -139,9 +136,7 @@ export default class SaleInvoicesController extends BaseController{
* Due sale invoice list validation schema. * Due sale invoice list validation schema.
*/ */
get dueSalesInvoicesListValidationSchema() { get dueSalesInvoicesListValidationSchema() {
return [ return [query('customer_id').optional().isNumeric().toInt()];
query('customer_id').optional().isNumeric().toInt(),
];
} }
/** /**
@@ -152,19 +147,20 @@ export default class SaleInvoicesController extends BaseController{
*/ */
async newSaleInvoice(req: Request, res: Response, next: NextFunction) { async newSaleInvoice(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req; const { tenantId } = req;
const saleInvoiceOTD: ISaleInvoiceOTD = this.matchedBodyData(req); const saleInvoiceOTD: ISaleInvoiceDTO = this.matchedBodyData(req);
try { try {
// Creates a new sale invoice with associated entries. // Creates a new sale invoice with associated entries.
const storedSaleInvoice = await this.saleInvoiceService.createSaleInvoice( const storedSaleInvoice = await this.saleInvoiceService.createSaleInvoice(
tenantId, saleInvoiceOTD, tenantId,
saleInvoiceOTD
); );
return res.status(200).send({ return res.status(200).send({
id: storedSaleInvoice.id, id: storedSaleInvoice.id,
message: 'The sale invoice has been created successfully.', message: 'The sale invoice has been created successfully.',
}); });
} catch (error) { } catch (error) {
next(error) next(error);
} }
} }
@@ -177,20 +173,24 @@ export default class SaleInvoicesController extends BaseController{
async editSaleInvoice(req: Request, res: Response, next: NextFunction) { async editSaleInvoice(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req; const { tenantId } = req;
const { id: saleInvoiceId } = req.params; const { id: saleInvoiceId } = req.params;
const saleInvoiceOTD: ISaleInvoiceOTD = this.matchedBodyData(req); const saleInvoiceOTD: ISaleInvoiceDTO = this.matchedBodyData(req);
try { try {
// Update the given sale invoice details. // Update the given sale invoice details.
await this.saleInvoiceService.editSaleInvoice(tenantId, saleInvoiceId, saleInvoiceOTD); await this.saleInvoiceService.editSaleInvoice(
tenantId,
saleInvoiceId,
saleInvoiceOTD
);
return res.status(200).send({ return res.status(200).send({
id: saleInvoiceId, id: saleInvoiceId,
message: 'The sale invoice has beeen edited successfully.', message: 'The sale invoice has been edited successfully.',
}); });
} catch (error) { } catch (error) {
next(error); next(error);
} }
} }
/** /**
* Deliver the given sale invoice. * Deliver the given sale invoice.
* @param {Request} req - * @param {Request} req -
@@ -226,7 +226,7 @@ export default class SaleInvoicesController extends BaseController{
try { try {
// Deletes the sale invoice with associated entries and journal transaction. // Deletes the sale invoice with associated entries and journal transaction.
await this.saleInvoiceService.deleteSaleInvoice(tenantId, saleInvoiceId); await this.saleInvoiceService.deleteSaleInvoice(tenantId, saleInvoiceId);
return res.status(200).send({ return res.status(200).send({
id: saleInvoiceId, id: saleInvoiceId,
message: 'The sale invoice has been deleted successfully.', message: 'The sale invoice has been deleted successfully.',
@@ -247,7 +247,8 @@ export default class SaleInvoicesController extends BaseController{
try { try {
const saleInvoice = await this.saleInvoiceService.getSaleInvoice( const saleInvoice = await this.saleInvoiceService.getSaleInvoice(
tenantId, saleInvoiceId, tenantId,
saleInvoiceId
); );
return res.status(200).send({ sale_invoice: saleInvoice }); return res.status(200).send({ sale_invoice: saleInvoice });
} catch (error) { } catch (error) {
@@ -260,7 +261,11 @@ export default class SaleInvoicesController extends BaseController{
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
public async getSalesInvoices(req: Request, res: Response, next: NextFunction) { public async getSalesInvoices(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req; const { tenantId } = req;
const filter: ISalesInvoicesFilter = { const filter: ISalesInvoicesFilter = {
filterRoles: [], filterRoles: [],
@@ -277,9 +282,7 @@ export default class SaleInvoicesController extends BaseController{
salesInvoices, salesInvoices,
filterMeta, filterMeta,
pagination, pagination,
} = await this.saleInvoiceService.salesInvoicesList( } = await this.saleInvoiceService.salesInvoicesList(tenantId, filter);
tenantId, filter,
);
return res.status(200).send({ return res.status(200).send({
sales_invoices: salesInvoices, sales_invoices: salesInvoices,
pagination: this.transfromToResponse(pagination), pagination: this.transfromToResponse(pagination),
@@ -292,17 +295,24 @@ export default class SaleInvoicesController extends BaseController{
/** /**
* Retrieve due sales invoices. * Retrieve due sales invoices.
* @param {Request} req - * @param {Request} req -
* @param {Response} res - * @param {Response} res -
* @param {NextFunction} next - * @param {NextFunction} next -
* @return {Response|void} * @return {Response|void}
*/ */
public async getPayableInvoices(req: Request, res: Response, next: NextFunction) { public async getPayableInvoices(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req; const { tenantId } = req;
const { customerId } = this.matchedQueryData(req); const { customerId } = this.matchedQueryData(req);
try { try {
const salesInvoices = await this.saleInvoiceService.getPayableInvoices(tenantId, customerId); const salesInvoices = await this.saleInvoiceService.getPayableInvoices(
tenantId,
customerId
);
return res.status(200).send({ return res.status(200).send({
sales_invoices: this.transfromToResponse(salesInvoices), sales_invoices: this.transfromToResponse(salesInvoices),
@@ -314,12 +324,17 @@ export default class SaleInvoicesController extends BaseController{
/** /**
* Handles service errors. * Handles service errors.
* @param {Error} error * @param {Error} error
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
* @param {NextFunction} next * @param {NextFunction} next
*/ */
handleServiceErrors(error: Error, req: Request, res: Response, next: NextFunction) { handleServiceErrors(
error: Error,
req: Request,
res: Response,
next: NextFunction
) {
if (error instanceof ServiceError) { if (error instanceof ServiceError) {
if (error.errorType === 'INVOICE_NUMBER_NOT_UNIQUE') { if (error.errorType === 'INVOICE_NUMBER_NOT_UNIQUE') {
return res.boom.badRequest(null, { return res.boom.badRequest(null, {
@@ -328,7 +343,7 @@ export default class SaleInvoicesController extends BaseController{
} }
if (error.errorType === 'SALE_INVOICE_NOT_FOUND') { if (error.errorType === 'SALE_INVOICE_NOT_FOUND') {
return res.status(404).send({ return res.status(404).send({
errors: [{ type: 'SALE.INVOICE.NOT.FOUND', code: 200 }] errors: [{ type: 'SALE.INVOICE.NOT.FOUND', code: 200 }],
}); });
} }
if (error.errorType === 'ENTRIES_ITEMS_IDS_NOT_EXISTS') { if (error.errorType === 'ENTRIES_ITEMS_IDS_NOT_EXISTS') {
@@ -372,6 +387,6 @@ export default class SaleInvoicesController extends BaseController{
}); });
} }
} }
next(error); next(error);
} }
} }

View File

@@ -46,6 +46,7 @@ export interface IBill {
openedAt: Date | string, openedAt: Date | string,
entries: IItemEntry[], entries: IItemEntry[],
userId: number,
}; };
export interface IBillsFilter extends IDynamicListFilterDTO { export interface IBillsFilter extends IDynamicListFilterDTO {

View File

@@ -12,7 +12,7 @@ export interface ISaleInvoice {
deliveredAt: string|Date, deliveredAt: string|Date,
} }
export interface ISaleInvoiceOTD { export interface ISaleInvoiceDTO {
invoiceDate: Date, invoiceDate: Date,
dueDate: Date, dueDate: Date,
referenceNo: string, referenceNo: string,
@@ -24,11 +24,11 @@ export interface ISaleInvoiceOTD {
delivered: boolean, delivered: boolean,
} }
export interface ISaleInvoiceCreateDTO extends ISaleInvoiceOTD { export interface ISaleInvoiceCreateDTO extends ISaleInvoiceDTO {
fromEstiamteId: number, fromEstiamteId: number,
}; };
export interface ISaleInvoiceEditDTO extends ISaleInvoiceOTD { export interface ISaleInvoiceEditDTO extends ISaleInvoiceDTO {
}; };

View File

@@ -5,13 +5,6 @@ import { defaultToTransform } from 'utils';
import { QueryBuilder } from 'knex'; import { QueryBuilder } from 'knex';
export default class SaleInvoice extends TenantModel { export default class SaleInvoice extends TenantModel {
/**
* Virtual attributes.
*/
static get virtualAttributes() {
return ['dueAmount'];
}
/** /**
* Table name * Table name
*/ */
@@ -60,10 +53,10 @@ export default class SaleInvoice extends TenantModel {
/** /**
* Retrieve the invoice due amount. * Retrieve the invoice due amount.
* (Invoice amount - payment amount = Due amount) * Equation (Invoice amount - payment amount = Due amount)
* @return {boolean} * @return {boolean}
*/ */
dueAmount() { get dueAmount() {
return Math.max(this.balance - this.paymentAmount, 0); return Math.max(this.balance - this.paymentAmount, 0);
} }

View File

@@ -11,6 +11,8 @@ interface IJournalTransactionsFilter {
toAmount: number, toAmount: number,
contactsIds?: number[], contactsIds?: number[],
contactType?: string, contactType?: string,
referenceType?: string[],
referenceId?: number[],
}; };
export default class AccountTransactionsRepository extends TenantRepository { export default class AccountTransactionsRepository extends TenantRepository {
@@ -42,6 +44,12 @@ export default class AccountTransactionsRepository extends TenantRepository {
if (filter.contactType) { if (filter.contactType) {
query.where('contact_type', filter.contactType); query.where('contact_type', filter.contactType);
} }
if (filter.referenceType && filter.referenceType.length > 0) {
query.whereIn('reference_type', filter.referenceType);
}
if (filter.referenceId && filter.referenceId.length > 0) {
query.whereIn('reference_id', filter.referenceId);
}
}); });
}); });
} }

View File

@@ -177,7 +177,20 @@ export default class CachableRepository extends EntityRepository{
* *
* @param {string|number[]} values - * @param {string|number[]} values -
*/ */
async deleteWhereIn(values: string | number[]) { async deleteWhereIn(field: string, values: string | number[]) {
const result = await super.deleteWhereIn(field, values);
// Flushes the repository cache after delete operation.
this.flushCache();
return result;
}
/**
*
* @param {string|number[]} values
*/
async deleteWhereIdIn(values: string | number[]) {
const result = await super.deleteWhereIdIn(values); const result = await super.deleteWhereIdIn(values);
// Flushes the repository cache after delete operation. // Flushes the repository cache after delete operation.

View File

@@ -175,7 +175,7 @@ export default class EntityRepository {
} }
/** /**
* * Deletes the given entries in the array on the specific field.
* @param {string} field - * @param {string} field -
* @param {number|string} values - * @param {number|string} values -
*/ */

View File

@@ -1,4 +1,6 @@
import { sumBy, chain } from 'lodash'; import { sumBy, chain } from 'lodash';
import moment from 'moment';
import { IBill } from 'interfaces';
import JournalPoster from "./JournalPoster"; import JournalPoster from "./JournalPoster";
import JournalEntry from "./JournalEntry"; import JournalEntry from "./JournalEntry";
import { AccountTransaction } from 'models'; import { AccountTransaction } from 'models';
@@ -7,6 +9,7 @@ import {
IManualJournal, IManualJournal,
IExpense, IExpense,
IExpenseCategory, IExpenseCategory,
IItem,
} from 'interfaces'; } from 'interfaces';
interface IInventoryCostEntity { interface IInventoryCostEntity {
@@ -57,6 +60,68 @@ export default class JournalCommands{
this.models = this.journal.models; this.models = this.journal.models;
} }
/**
* Records the bill journal entries.
* @param {IBill} bill
* @param {boolean} override - Override the old bill entries.
*/
async bill(bill: IBill, override: boolean = false): Promise<void> {
const { transactionsRepository, accountRepository } = this.repositories;
const { Item, ItemEntry } = this.models;
const entriesItemsIds = bill.entries.map((entry) => entry.itemId);
// Retrieve the bill transaction items.
const storedItems = await Item.query().whereIn('id', entriesItemsIds);
const storedItemsMap = new Map(storedItems.map((item) => [item.id, item]));
const payableAccount = await accountRepository.findOne({ slug: 'accounts-payable' });
const formattedDate = moment(bill.billDate).format('YYYY-MM-DD');
const commonJournalMeta = {
debit: 0,
credit: 0,
referenceId: bill.id,
referenceType: 'Bill',
date: formattedDate,
userId: bill.userId,
};
// Overrides the old bill entries.
if (override) {
const entries = await transactionsRepository.journal({
referenceType: ['Bill'],
referenceId: [bill.id],
});
this.journal.fromTransactions(entries);
this.journal.removeEntries();
}
const payableEntry = new JournalEntry({
...commonJournalMeta,
credit: bill.amount,
account: payableAccount.id,
contactId: bill.vendorId,
contactType: 'Vendor',
index: 1,
});
this.journal.credit(payableEntry);
bill.entries.forEach((entry, index) => {
const item: IItem = storedItemsMap.get(entry.itemId);
const amount = ItemEntry.calcAmount(entry);
const debitEntry = new JournalEntry({
...commonJournalMeta,
debit: amount,
account:
['inventory'].indexOf(item.type) !== -1
? item.inventoryAccountId
: item.costAccountId,
index: index + 2,
});
this.journal.debit(debitEntry);
});
}
/** /**
* Customer opening balance journals. * Customer opening balance journals.
* @param {number} customerId * @param {number} customerId

View File

@@ -21,7 +21,7 @@ export default class JournalPoster implements IJournalPoster {
deletedEntriesIds: number[] = []; deletedEntriesIds: number[] = [];
entries: IJournalEntry[] = []; entries: IJournalEntry[] = [];
balancesChange: IAccountsChange = {}; balancesChange: IAccountsChange = {};
accountsDepGraph: IAccountsChange = {}; accountsDepGraph: IAccountsChange;
accountsBalanceTable: { [key: number]: number; } = {}; accountsBalanceTable: { [key: number]: number; } = {};
@@ -250,12 +250,12 @@ export default class JournalPoster implements IJournalPoster {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
public async saveEntries() { public async saveEntries() {
const { AccountTransaction } = this.models; const { transactionsRepository } = this.repositories;
const saveOperations: Promise<void>[] = []; const saveOperations: Promise<void>[] = [];
this.entries.forEach((entry) => { this.entries.forEach((entry) => {
const oper = AccountTransaction.query() const oper = transactionsRepository
.insert({ .create({
accountId: entry.account, accountId: entry.account,
...omit(entry, ['account']), ...omit(entry, ['account']),
}); });
@@ -309,12 +309,10 @@ export default class JournalPoster implements IJournalPoster {
* @return {Promise<void>} * @return {Promise<void>}
*/ */
public async deleteEntries() { public async deleteEntries() {
const { AccountTransaction } = this.models; const { transactionsRepository } = this.repositories;
if (this.deletedEntriesIds.length > 0) { if (this.deletedEntriesIds.length > 0) {
await AccountTransaction.query() await transactionsRepository.deleteWhereIdIn(this.deletedEntriesIds);
.whereIn('id', this.deletedEntriesIds)
.delete();
} }
} }
@@ -332,7 +330,6 @@ export default class JournalPoster implements IJournalPoster {
}); });
} }
/** /**
* Calculates the entries balance change. * Calculates the entries balance change.
* @public * @public

View File

@@ -27,6 +27,7 @@ import {
import { ServiceError } from 'exceptions'; import { ServiceError } from 'exceptions';
import ItemsService from 'services/Items/ItemsService'; import ItemsService from 'services/Items/ItemsService';
import ItemsEntriesService from 'services/Items/ItemsEntriesService'; import ItemsEntriesService from 'services/Items/ItemsEntriesService';
import JournalCommands from 'services/Accounting/JournalCommands';
const ERRORS = { const ERRORS = {
BILL_NOT_FOUND: 'BILL_NOT_FOUND', BILL_NOT_FOUND: 'BILL_NOT_FOUND',
@@ -36,7 +37,7 @@ const ERRORS = {
BILL_ITEMS_NOT_FOUND: 'BILL_ITEMS_NOT_FOUND', BILL_ITEMS_NOT_FOUND: 'BILL_ITEMS_NOT_FOUND',
BILL_ENTRIES_IDS_NOT_FOUND: 'BILL_ENTRIES_IDS_NOT_FOUND', BILL_ENTRIES_IDS_NOT_FOUND: 'BILL_ENTRIES_IDS_NOT_FOUND',
NOT_PURCHASE_ABLE_ITEMS: 'NOT_PURCHASE_ABLE_ITEMS', NOT_PURCHASE_ABLE_ITEMS: 'NOT_PURCHASE_ABLE_ITEMS',
BILL_ALREADY_OPEN: 'BILL_ALREADY_OPEN' BILL_ALREADY_OPEN: 'BILL_ALREADY_OPEN',
}; };
/** /**
@@ -83,7 +84,10 @@ export default class BillsService extends SalesInvoicesCost {
const foundVendor = await vendorRepository.findOneById(vendorId); const foundVendor = await vendorRepository.findOneById(vendorId);
if (!foundVendor) { if (!foundVendor) {
this.logger.info('[bill] the given vendor not found.', { tenantId, vendorId }); this.logger.info('[bill] the given vendor not found.', {
tenantId,
vendorId,
});
throw new ServiceError(ERRORS.BILL_VENDOR_NOT_FOUND); throw new ServiceError(ERRORS.BILL_VENDOR_NOT_FOUND);
} }
return foundVendor; return foundVendor;
@@ -93,16 +97,21 @@ export default class BillsService extends SalesInvoicesCost {
* Validates the given bill existance. * Validates the given bill existance.
* @async * @async
* @param {number} tenantId - * @param {number} tenantId -
* @param {number} billId - * @param {number} billId -
*/ */
private async getBillOrThrowError(tenantId: number, billId: number) { private async getBillOrThrowError(tenantId: number, billId: number) {
const { Bill } = this.tenancy.models(tenantId); const { Bill } = this.tenancy.models(tenantId);
this.logger.info('[bill] trying to get bill.', { tenantId, billId }); this.logger.info('[bill] trying to get bill.', { tenantId, billId });
const foundBill = await Bill.query().findById(billId).withGraphFetched('entries'); const foundBill = await Bill.query()
.findById(billId)
.withGraphFetched('entries');
if (!foundBill) { if (!foundBill) {
this.logger.info('[bill] the given bill not found.', { tenantId, billId }); this.logger.info('[bill] the given bill not found.', {
tenantId,
billId,
});
throw new ServiceError(ERRORS.BILL_NOT_FOUND); throw new ServiceError(ERRORS.BILL_NOT_FOUND);
} }
return foundBill; return foundBill;
@@ -115,13 +124,19 @@ export default class BillsService extends SalesInvoicesCost {
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {Function} next
*/ */
private async validateBillNumberExists(tenantId: number, billNumber: string, notBillId?: number) { private async validateBillNumberExists(
tenantId: number,
billNumber: string,
notBillId?: number
) {
const { Bill } = this.tenancy.models(tenantId); const { Bill } = this.tenancy.models(tenantId);
const foundBills = await Bill.query().where('bill_number', billNumber).onBuild((builder) => { const foundBills = await Bill.query()
if (notBillId) { .where('bill_number', billNumber)
builder.whereNot('id', notBillId); .onBuild((builder) => {
} if (notBillId) {
}); builder.whereNot('id', notBillId);
}
});
if (foundBills.length > 0) { if (foundBills.length > 0) {
throw new ServiceError(ERRORS.BILL_NUMBER_EXISTS); throw new ServiceError(ERRORS.BILL_NUMBER_EXISTS);
@@ -130,17 +145,17 @@ export default class BillsService extends SalesInvoicesCost {
/** /**
* Converts bill DTO to model. * Converts bill DTO to model.
* @param {number} tenantId * @param {number} tenantId
* @param {IBillDTO} billDTO * @param {IBillDTO} billDTO
* @param {IBill} oldBill * @param {IBill} oldBill
* *
* @returns {IBill} * @returns {IBill}
*/ */
private async billDTOToModel( private async billDTOToModel(
tenantId: number, tenantId: number,
billDTO: IBillDTO | IBillEditDTO, billDTO: IBillDTO | IBillEditDTO,
oldBill?: IBill,
authorizedUser: ISystemUser, authorizedUser: ISystemUser,
oldBill?: IBill
) { ) {
const { ItemEntry } = this.tenancy.models(tenantId); const { ItemEntry } = this.tenancy.models(tenantId);
let invLotNumber = oldBill?.invLotNumber; let invLotNumber = oldBill?.invLotNumber;
@@ -155,21 +170,21 @@ export default class BillsService extends SalesInvoicesCost {
const amount = sumBy(entries, 'amount'); const amount = sumBy(entries, 'amount');
return { return {
...formatDateFields( ...formatDateFields(omit(billDTO, ['open', 'entries']), [
omit(billDTO, ['open']), 'billDate',
['billDate', 'dueDate'] 'dueDate',
), ]),
amount, amount,
invLotNumber, invLotNumber,
entries: entries.map((entry) => ({ entries: entries.map((entry) => ({
reference_type: 'Bill', reference_type: 'Bill',
...omit(entry, ['amount', 'id']), ...omit(entry, ['amount', 'id']),
})), })),
// Avoid rewrite the open date in edit mode when already opened. // Avoid rewrite the open date in edit mode when already opened.
...(billDTO.open && (!oldBill?.openedAt)) && ({ ...(billDTO.open &&
openedAt: moment().toMySqlDateTime(), !oldBill?.openedAt && {
}), openedAt: moment().toMySqlDateTime(),
}),
userId: authorizedUser.id, userId: authorizedUser.id,
}; };
} }
@@ -196,8 +211,16 @@ export default class BillsService extends SalesInvoicesCost {
): Promise<IBill> { ): Promise<IBill> {
const { Bill } = this.tenancy.models(tenantId); const { Bill } = this.tenancy.models(tenantId);
this.logger.info('[bill] trying to create a new bill', { tenantId, billDTO }); this.logger.info('[bill] trying to create a new bill', {
const billObj = await this.billDTOToModel(tenantId, billDTO, null, authorizedUser); tenantId,
billDTO,
});
const billObj = await this.billDTOToModel(
tenantId,
billDTO,
authorizedUser,
null
);
// Retrieve vendor or throw not found service error. // Retrieve vendor or throw not found service error.
await this.getVendorOrThrowError(tenantId, billDTO.vendorId); await this.getVendorOrThrowError(tenantId, billDTO.vendorId);
@@ -207,19 +230,28 @@ export default class BillsService extends SalesInvoicesCost {
await this.validateBillNumberExists(tenantId, billDTO.billNumber); await this.validateBillNumberExists(tenantId, billDTO.billNumber);
} }
// Validate items IDs existance. // Validate items IDs existance.
await this.itemsEntriesService.validateItemsIdsExistance(tenantId, billDTO.entries); await this.itemsEntriesService.validateItemsIdsExistance(
tenantId,
billDTO.entries
);
// Validate non-purchasable items. // Validate non-purchasable items.
await this.itemsEntriesService.validateNonPurchasableEntriesItems(tenantId, billDTO.entries); await this.itemsEntriesService.validateNonPurchasableEntriesItems(
tenantId,
billDTO.entries
);
// Inserts the bill graph object to the storage. // Inserts the bill graph object to the storage.
const bill = await Bill.query().insertGraph({ ...billObj }); const bill = await Bill.query().insertGraph({ ...billObj });
// Triggers `onBillCreated` event. // Triggers `onBillCreated` event.
await this.eventDispatcher.dispatch(events.bill.onCreated, { await this.eventDispatcher.dispatch(events.bill.onCreated, {
tenantId, bill, billId: bill.id, tenantId,
bill,
billId: bill.id,
});
this.logger.info('[bill] bill inserted successfully.', {
tenantId,
billId: bill.id,
}); });
this.logger.info('[bill] bill inserted successfully.', { tenantId, billId: bill.id });
return bill; return bill;
} }
@@ -253,7 +285,12 @@ export default class BillsService extends SalesInvoicesCost {
const oldBill = await this.getBillOrThrowError(tenantId, billId); const oldBill = await this.getBillOrThrowError(tenantId, billId);
// Transforms the bill DTO object to model object. // Transforms the bill DTO object to model object.
const billObj = await this.billDTOToModel(tenantId, billDTO, oldBill, authorizedUser); const billObj = await this.billDTOToModel(
tenantId,
billDTO,
authorizedUser,
oldBill
);
// Retrieve vendor details or throw not found service error. // Retrieve vendor details or throw not found service error.
await this.getVendorOrThrowError(tenantId, billDTO.vendorId); await this.getVendorOrThrowError(tenantId, billDTO.vendorId);
@@ -263,22 +300,39 @@ export default class BillsService extends SalesInvoicesCost {
await this.validateBillNumberExists(tenantId, billDTO.billNumber, billId); await this.validateBillNumberExists(tenantId, billDTO.billNumber, billId);
} }
// Validate the entries ids existance. // Validate the entries ids existance.
await this.itemsEntriesService.validateEntriesIdsExistance(tenantId, billId, 'Bill', billDTO.entries); await this.itemsEntriesService.validateEntriesIdsExistance(
tenantId,
billId,
'Bill',
billDTO.entries
);
// Validate the items ids existance on the storage. // Validate the items ids existance on the storage.
await this.itemsEntriesService.validateItemsIdsExistance(tenantId, billDTO.entries); await this.itemsEntriesService.validateItemsIdsExistance(
tenantId,
billDTO.entries
);
// Accept the purchasable items only. // Accept the purchasable items only.
await this.itemsEntriesService.validateNonPurchasableEntriesItems(tenantId, billDTO.entries); await this.itemsEntriesService.validateNonPurchasableEntriesItems(
tenantId,
billDTO.entries
);
// Update the bill transaction. // Update the bill transaction.
const bill = await Bill.query().upsertGraphAndFetch({ const bill = await Bill.query().upsertGraphAndFetch({
id: billId, id: billId,
...billObj, ...billObj,
}); });
// Triggers event `onBillEdited`. // Triggers event `onBillEdited`.
await this.eventDispatcher.dispatch(events.bill.onEdited, { tenantId, billId, oldBill, bill }); await this.eventDispatcher.dispatch(events.bill.onEdited, {
this.logger.info('[bill] bill upserted successfully.', { tenantId, billId }); tenantId,
billId,
oldBill,
bill,
});
this.logger.info('[bill] bill upserted successfully.', {
tenantId,
billId,
});
return bill; return bill;
} }
@@ -306,13 +360,17 @@ export default class BillsService extends SalesInvoicesCost {
await Promise.all([deleteBillEntriesOper, deleteBillOper]); await Promise.all([deleteBillEntriesOper, deleteBillOper]);
// Triggers `onBillDeleted` event. // Triggers `onBillDeleted` event.
await this.eventDispatcher.dispatch(events.bill.onDeleted, { tenantId, billId, oldBill }); await this.eventDispatcher.dispatch(events.bill.onDeleted, {
tenantId,
billId,
oldBill,
});
} }
/** /**
* Records the inventory transactions from the given bill input. * Records the inventory transactions from the given bill input.
* @param {Bill} bill * @param {Bill} bill
* @param {number} billId * @param {number} billId
*/ */
public recordInventoryTransactions( public recordInventoryTransactions(
tenantId: number, tenantId: number,
@@ -320,19 +378,20 @@ export default class BillsService extends SalesInvoicesCost {
billId: number, billId: number,
override?: boolean override?: boolean
) { ) {
const inventoryTransactions = bill.entries const inventoryTransactions = bill.entries.map((entry) => ({
.map((entry) => ({ ...pick(entry, ['item_id', 'quantity', 'rate']),
...pick(entry, ['item_id', 'quantity', 'rate']), lotNumber: bill.invLotNumber,
lotNumber: bill.invLotNumber, transactionType: 'Bill',
transactionType: 'Bill', transactionId: billId,
transactionId: billId, direction: 'IN',
direction: 'IN', date: bill.bill_date,
date: bill.bill_date, entryId: entry.id,
entryId: entry.id, }));
}));
return this.inventoryService.recordInventoryTransactions( return this.inventoryService.recordInventoryTransactions(
tenantId, inventoryTransactions, override tenantId,
inventoryTransactions,
override
); );
} }
@@ -342,62 +401,16 @@ export default class BillsService extends SalesInvoicesCost {
* @param {IBill} bill * @param {IBill} bill
* @param {Integer} billId * @param {Integer} billId
*/ */
public async recordJournalTransactions(tenantId: number, bill: IBill, billId?: number) { public async recordJournalTransactions(
const { AccountTransaction, Item, ItemEntry } = this.tenancy.models(tenantId); tenantId: number,
const { accountRepository } = this.tenancy.repositories(tenantId); bill: IBill,
override: boolean = false
const entriesItemsIds = bill.entries.map((entry) => entry.itemId); ) {
const formattedDate = moment(bill.billDate).format('YYYY-MM-DD');
const storedItems = await Item.query().whereIn('id', entriesItemsIds);
const storedItemsMap = new Map(storedItems.map((item) => [item.id, item]));
const payableAccount = await accountRepository.find({ slug: 'accounts-payable' });
const journal = new JournalPoster(tenantId); const journal = new JournalPoster(tenantId);
const journalCommands = new JournalCommands(journal);
const commonJournalMeta = { await journalCommands.bill(bill, override);
debit: 0,
credit: 0,
referenceId: bill.id,
referenceType: 'Bill',
date: formattedDate,
userId: bill.userId,
};
if (billId) {
const transactions = await AccountTransaction.query()
.whereIn('reference_type', ['Bill'])
.whereIn('reference_id', [billId])
.withGraphFetched('account.type');
journal.loadEntries(transactions);
journal.removeEntries();
}
const payableEntry = new JournalEntry({
...commonJournalMeta,
credit: bill.amount,
account: payableAccount.id,
contactId: bill.vendorId,
contactType: 'Vendor',
index: 1,
});
journal.credit(payableEntry);
bill.entries.forEach((entry, index) => {
const item: IItem = storedItemsMap.get(entry.itemId);
const amount = ItemEntry.calcAmount(entry);
const debitEntry = new JournalEntry({
...commonJournalMeta,
debit: amount,
account:
['inventory'].indexOf(item.type) !== -1
? item.inventoryAccountId
: item.costAccountId,
index: index + 2,
});
journal.debit(debitEntry);
});
return Promise.all([ return Promise.all([
journal.deleteEntries(), journal.deleteEntries(),
journal.saveEntries(), journal.saveEntries(),
@@ -412,16 +425,29 @@ export default class BillsService extends SalesInvoicesCost {
*/ */
public async getBills( public async getBills(
tenantId: number, tenantId: number,
billsFilter: IBillsFilter, billsFilter: IBillsFilter
): Promise<{ bills: IBill, pagination: IPaginationMeta, filterMeta: IFilterMeta }> { ): Promise<{
bills: IBill;
pagination: IPaginationMeta;
filterMeta: IFilterMeta;
}> {
const { Bill } = this.tenancy.models(tenantId); const { Bill } = this.tenancy.models(tenantId);
const dynamicFilter = await this.dynamicListService.dynamicList(tenantId, Bill, billsFilter); const dynamicFilter = await this.dynamicListService.dynamicList(
tenantId,
Bill,
billsFilter
);
this.logger.info('[bills] trying to get bills data table.', { tenantId, billsFilter }); this.logger.info('[bills] trying to get bills data table.', {
const { results, pagination } = await Bill.query().onBuild((builder) => { tenantId,
builder.withGraphFetched('vendor'); billsFilter,
dynamicFilter.buildQuery()(builder); });
}).pagination(billsFilter.page - 1, billsFilter.pageSize); const { results, pagination } = await Bill.query()
.onBuild((builder) => {
builder.withGraphFetched('vendor');
dynamicFilter.buildQuery()(builder);
})
.pagination(billsFilter.page - 1, billsFilter.pageSize);
return { return {
bills: results, bills: results,
@@ -432,8 +458,8 @@ export default class BillsService extends SalesInvoicesCost {
/** /**
* Retrieve all due bills or for specific given vendor id. * Retrieve all due bills or for specific given vendor id.
* @param {number} tenantId - * @param {number} tenantId -
* @param {number} vendorId - * @param {number} vendorId -
*/ */
public async getDueBills( public async getDueBills(
tenantId: number, tenantId: number,
@@ -460,8 +486,12 @@ export default class BillsService extends SalesInvoicesCost {
public async getBill(tenantId: number, billId: number): Promise<IBill> { public async getBill(tenantId: number, billId: number): Promise<IBill> {
const { Bill } = this.tenancy.models(tenantId); const { Bill } = this.tenancy.models(tenantId);
this.logger.info('[bills] trying to fetch specific bill with metadata.', { tenantId, billId }); this.logger.info('[bills] trying to fetch specific bill with metadata.', {
const bill = await Bill.query().findById(billId) tenantId,
billId,
});
const bill = await Bill.query()
.findById(billId)
.withGraphFetched('vendor') .withGraphFetched('vendor')
.withGraphFetched('entries'); .withGraphFetched('entries');
@@ -486,9 +516,9 @@ export default class BillsService extends SalesInvoicesCost {
.whereIn('id', billItemsIds) .whereIn('id', billItemsIds)
.where('type', 'inventory'); .where('type', 'inventory');
const inventoryItemsIds = inventoryItems.map(i => i.id); const inventoryItemsIds = inventoryItems.map((i) => i.id);
if (inventoryItemsIds.length > 0) { if (inventoryItemsIds.length > 0) {
await this.scheduleComputeItemsCost( await this.scheduleComputeItemsCost(
tenantId, tenantId,
inventoryItemsIds, inventoryItemsIds,
@@ -499,13 +529,10 @@ export default class BillsService extends SalesInvoicesCost {
/** /**
* Mark the bill as open. * Mark the bill as open.
* @param {number} tenantId * @param {number} tenantId
* @param {number} billId * @param {number} billId
*/ */
public async openBill( public async openBill(tenantId: number, billId: number): Promise<void> {
tenantId: number,
billId: number,
): Promise<void> {
const { Bill } = this.tenancy.models(tenantId); const { Bill } = this.tenancy.models(tenantId);
// Retrieve the given bill or throw not found error. // Retrieve the given bill or throw not found error.
@@ -520,4 +547,4 @@ export default class BillsService extends SalesInvoicesCost {
openedAt: moment().toMySqlDateTime(), openedAt: moment().toMySqlDateTime(),
}); });
} }
} }

View File

@@ -35,7 +35,8 @@ const ERRORS = {
DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET_TYPE: 'DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET_TYPE', DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET_TYPE: 'DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET_TYPE',
INVALID_PAYMENT_AMOUNT: 'INVALID_PAYMENT_AMOUNT', INVALID_PAYMENT_AMOUNT: 'INVALID_PAYMENT_AMOUNT',
INVOICES_IDS_NOT_FOUND: 'INVOICES_IDS_NOT_FOUND', INVOICES_IDS_NOT_FOUND: 'INVOICES_IDS_NOT_FOUND',
ENTRIES_IDS_NOT_EXISTS: 'ENTRIES_IDS_NOT_EXISTS' ENTRIES_IDS_NOT_EXISTS: 'ENTRIES_IDS_NOT_EXISTS',
INVOICES_NOT_DELIVERED_YET: 'INVOICES_NOT_DELIVERED_YET'
}; };
/** /**
* Payment receive service. * Payment receive service.
@@ -151,6 +152,13 @@ export default class PaymentReceiveService {
if (notFoundInvoicesIDs.length > 0) { if (notFoundInvoicesIDs.length > 0) {
throw new ServiceError(ERRORS.INVOICES_IDS_NOT_FOUND); throw new ServiceError(ERRORS.INVOICES_IDS_NOT_FOUND);
} }
// Filters the not delivered invoices.
const notDeliveredInvoices = storedInvoices.filter((invoice) => !invoice.isDelivered);
if (notDeliveredInvoices.length > 0) {
throw new ServiceError(ERRORS.INVOICES_NOT_DELIVERED_YET, null, { notDeliveredInvoices });
}
return storedInvoices;
} }
/** /**

View File

@@ -7,7 +7,7 @@ import {
} from 'decorators/eventDispatcher'; } from 'decorators/eventDispatcher';
import { import {
ISaleInvoice, ISaleInvoice,
ISaleInvoiceOTD, ISaleInvoiceDTO,
IItemEntry, IItemEntry,
ISalesInvoicesFilter, ISalesInvoicesFilter,
IPaginationMeta, IPaginationMeta,
@@ -121,7 +121,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
/** /**
* Transform DTO object to model object. * Transform DTO object to model object.
* @param {number} tenantId - Tenant id. * @param {number} tenantId - Tenant id.
* @param {ISaleInvoiceOTD} saleInvoiceDTO - Sale invoice DTO. * @param {ISaleInvoiceDTO} saleInvoiceDTO - Sale invoice DTO.
*/ */
transformDTOToModel( transformDTOToModel(
tenantId: number, tenantId: number,
@@ -134,10 +134,10 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
); );
return { return {
...formatDateFields( ...formatDateFields(omit(saleInvoiceDTO, ['delivered', 'entries']), [
omit(saleInvoiceDTO, ['delivered', 'entries']), 'invoiceDate',
['invoiceDate', 'dueDate'] 'dueDate',
), ]),
// Avoid rewrite the deliver date in edit mode when already published. // Avoid rewrite the deliver date in edit mode when already published.
...(saleInvoiceDTO.delivered && ...(saleInvoiceDTO.delivered &&
!oldSaleInvoice?.deliveredAt && { !oldSaleInvoice?.deliveredAt && {
@@ -156,9 +156,9 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
* Creates a new sale invoices and store it to the storage * Creates a new sale invoices and store it to the storage
* with associated to entries and journal transactions. * with associated to entries and journal transactions.
* @async * @async
* @param {number} tenantId = * @param {number} tenantId - Tenant id.
* @param {ISaleInvoice} saleInvoiceDTO - * @param {ISaleInvoice} saleInvoiceDTO - Sale invoice object DTO.
* @return {ISaleInvoice} * @return {Promise<ISaleInvoice>}
*/ */
public async createSaleInvoice( public async createSaleInvoice(
tenantId: number, tenantId: number,
@@ -200,7 +200,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
const saleInvoice = await saleInvoiceRepository.upsertGraph({ const saleInvoice = await saleInvoiceRepository.upsertGraph({
...saleInvoiceObj, ...saleInvoiceObj,
}); });
// Triggers the event `onSaleInvoiceCreated`.
await this.eventDispatcher.dispatch(events.saleInvoice.onCreated, { await this.eventDispatcher.dispatch(events.saleInvoice.onCreated, {
tenantId, tenantId,
saleInvoice, saleInvoice,
@@ -217,9 +217,10 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
/** /**
* Edit the given sale invoice. * Edit the given sale invoice.
* @async * @async
* @param {number} tenantId - * @param {number} tenantId - Tenant id.
* @param {Number} saleInvoiceId - * @param {Number} saleInvoiceId - Sale invoice id.
* @param {ISaleInvoice} saleInvoice - * @param {ISaleInvoice} saleInvoice - Sale invoice DTO object.
* @return {Promise<ISaleInvoice>}
*/ */
public async editSaleInvoice( public async editSaleInvoice(
tenantId: number, tenantId: number,
@@ -228,9 +229,6 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
): Promise<ISaleInvoice> { ): Promise<ISaleInvoice> {
const { SaleInvoice, ItemEntry } = this.tenancy.models(tenantId); const { SaleInvoice, ItemEntry } = this.tenancy.models(tenantId);
const balance = sumBy(saleInvoiceDTO.entries, (e) =>
ItemEntry.calcAmount(e)
);
const oldSaleInvoice = await this.getInvoiceOrThrowError( const oldSaleInvoice = await this.getInvoiceOrThrowError(
tenantId, tenantId,
saleInvoiceId saleInvoiceId
@@ -242,13 +240,11 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
saleInvoiceDTO, saleInvoiceDTO,
oldSaleInvoice oldSaleInvoice
); );
// Validate customer existance. // Validate customer existance.
await this.customersService.getCustomerByIdOrThrowError( await this.customersService.getCustomerByIdOrThrowError(
tenantId, tenantId,
saleInvoiceDTO.customerId saleInvoiceDTO.customerId
); );
// Validate sale invoice number uniquiness. // Validate sale invoice number uniquiness.
if (saleInvoiceDTO.invoiceNo) { if (saleInvoiceDTO.invoiceNo) {
await this.validateInvoiceNumberUnique( await this.validateInvoiceNumberUnique(
@@ -281,15 +277,9 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
const saleInvoice: ISaleInvoice = await SaleInvoice.query().upsertGraphAndFetch( const saleInvoice: ISaleInvoice = await SaleInvoice.query().upsertGraphAndFetch(
{ {
id: saleInvoiceId, id: saleInvoiceId,
...omit(saleInvoiceObj, ['entries', 'invLotNumber']), ...saleInvoiceObj,
entries: saleInvoiceObj.entries.map((entry) => ({
reference_type: 'SaleInvoice',
...omit(entry, ['amount']),
})),
} }
); );
// Triggers `onSaleInvoiceEdited` event. // Triggers `onSaleInvoiceEdited` event.
await this.eventDispatcher.dispatch(events.saleInvoice.onEdited, { await this.eventDispatcher.dispatch(events.saleInvoice.onEdited, {
saleInvoice, saleInvoice,

View File

@@ -1,10 +1,9 @@
import { Container, Inject, Service } from 'typedi'; import { Container } from 'typedi';
import { EventSubscriber, On } from 'event-dispatch'; import { EventSubscriber, On } from 'event-dispatch';
import events from 'subscribers/events'; import events from 'subscribers/events';
import TenancyService from 'services/Tenancy/TenancyService'; import TenancyService from 'services/Tenancy/TenancyService';
import BillsService from 'services/Purchases/Bills'; import BillsService from 'services/Purchases/Bills';
import JournalPosterService from 'services/Sales/JournalPosterService'; import JournalPosterService from 'services/Sales/JournalPosterService';
import VendorRepository from 'repositories/VendorRepository';
@EventSubscriber() @EventSubscriber()
export default class BillSubscriber { export default class BillSubscriber {
@@ -13,11 +12,13 @@ export default class BillSubscriber {
logger: any; logger: any;
journalPosterService: JournalPosterService; journalPosterService: JournalPosterService;
/**
* Constructor method.
*/
constructor() { constructor() {
this.tenancy = Container.get(TenancyService); this.tenancy = Container.get(TenancyService);
this.billsService = Container.get(BillsService); this.billsService = Container.get(BillsService);
this.logger = Container.get('logger'); this.logger = Container.get('logger');
this.journalPosterService = Container.get(JournalPosterService); this.journalPosterService = Container.get(JournalPosterService);
} }
@@ -29,7 +30,10 @@ export default class BillSubscriber {
const { vendorRepository } = this.tenancy.repositories(tenantId); const { vendorRepository } = this.tenancy.repositories(tenantId);
// Increments vendor balance. // Increments vendor balance.
this.logger.info('[bill] trying to increment vendor balance.', { tenantId, billId }); this.logger.info('[bill] trying to increment vendor balance.', {
tenantId,
billId,
});
await vendorRepository.changeBalance(bill.vendorId, bill.amount); await vendorRepository.changeBalance(bill.vendorId, bill.amount);
} }
@@ -37,11 +41,20 @@ export default class BillSubscriber {
* Handles writing journal entries once bill created. * Handles writing journal entries once bill created.
*/ */
@On(events.bill.onCreated) @On(events.bill.onCreated)
@On(events.bill.onEdited) async handlerWriteJournalEntriesOnCreate({ tenantId, bill }) {
async handlerWriteJournalEntries({ tenantId, billId, bill }) {
// Writes the journal entries for the given bill transaction. // Writes the journal entries for the given bill transaction.
this.logger.info('[bill] writing bill journal entries.', { tenantId }); this.logger.info('[bill] writing bill journal entries.', { tenantId });
await this.billsService.recordJournalTransactions(tenantId, bill, billId); 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);
} }
/** /**
@@ -52,7 +65,10 @@ export default class BillSubscriber {
const { vendorRepository } = this.tenancy.repositories(tenantId); const { vendorRepository } = this.tenancy.repositories(tenantId);
// Decrements vendor balance. // Decrements vendor balance.
this.logger.info('[bill] trying to decrement vendor balance.', { tenantId, billId }); this.logger.info('[bill] trying to decrement vendor balance.', {
tenantId,
billId,
});
await vendorRepository.changeBalance(oldBill.vendorId, oldBill.amount * -1); await vendorRepository.changeBalance(oldBill.vendorId, oldBill.amount * -1);
} }
@@ -62,8 +78,15 @@ export default class BillSubscriber {
@On(events.bill.onDeleted) @On(events.bill.onDeleted)
async handlerDeleteJournalEntries({ tenantId, billId }) { async handlerDeleteJournalEntries({ tenantId, billId }) {
// Delete associated bill journal transactions. // Delete associated bill journal transactions.
this.logger.info('[bill] trying to delete journal entries.', { tenantId, billId }); this.logger.info('[bill] trying to delete journal entries.', {
await this.journalPosterService.revertJournalTransactions(tenantId, billId, 'Bill'); tenantId,
billId,
});
await this.journalPosterService.revertJournalTransactions(
tenantId,
billId,
'Bill'
);
} }
/** /**
@@ -74,12 +97,15 @@ export default class BillSubscriber {
const { vendorRepository } = this.tenancy.repositories(tenantId); const { vendorRepository } = this.tenancy.repositories(tenantId);
// Changes the diff vendor balance between old and new amount. // Changes the diff vendor balance between old and new amount.
this.logger.info('[bill[ change vendor the different balance.', { tenantId, billId }); this.logger.info('[bill[ change vendor the different balance.', {
tenantId,
billId,
});
await vendorRepository.changeDiffBalance( await vendorRepository.changeDiffBalance(
bill.vendorId, bill.vendorId,
bill.amount, bill.amount,
oldBill.amount, oldBill.amount,
oldBill.vendorId, oldBill.vendorId
); );
} }
} }

View File

@@ -1,9 +1,10 @@
import { Container } from 'typedi'; import { Container } from 'typedi';
import { On, EventSubscriber } from "event-dispatch"; import { On, EventSubscriber } from 'event-dispatch';
import events from 'subscribers/events'; import events from 'subscribers/events';
import TenancyService from 'services/Tenancy/TenancyService'; import TenancyService from 'services/Tenancy/TenancyService';
import SettingsService from 'services/Settings/SettingsService'; import SettingsService from 'services/Settings/SettingsService';
import SaleEstimateService from 'services/Sales/SalesEstimate'; import SaleEstimateService from 'services/Sales/SalesEstimate';
@EventSubscriber() @EventSubscriber()
export default class SaleInvoiceSubscriber { export default class SaleInvoiceSubscriber {
logger: any; logger: any;
@@ -22,23 +23,36 @@ export default class SaleInvoiceSubscriber {
* Handles customer balance increment once sale invoice created. * Handles customer balance increment once sale invoice created.
*/ */
@On(events.saleInvoice.onCreated) @On(events.saleInvoice.onCreated)
public async handleCustomerBalanceIncrement({ tenantId, saleInvoice, saleInvoiceId }) { public async handleCustomerBalanceIncrement({
tenantId,
saleInvoice,
saleInvoiceId,
}) {
const { customerRepository } = this.tenancy.repositories(tenantId); const { customerRepository } = this.tenancy.repositories(tenantId);
this.logger.info('[sale_invoice] trying to increment customer balance.', { tenantId }); this.logger.info('[sale_invoice] trying to increment customer balance.', {
await customerRepository.changeBalance(saleInvoice.customerId, saleInvoice.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) @On(events.saleInvoice.onCreated)
public async handleMarkEstimateConvert({ tenantId, saleInvoice, saleInvoiceId }) { public async handleMarkEstimateConvert({
if (saleInvoice.fromEstiamteId) { tenantId,
saleInvoice,
saleInvoiceId,
}) {
if (saleInvoice.fromEstimateId) {
this.saleEstimatesService.convertEstimateToInvoice( this.saleEstimatesService.convertEstimateToInvoice(
tenantId, tenantId,
saleInvoice.fromEstiamteId, saleInvoice.fromEstiamteId,
saleInvoiceId, saleInvoiceId
); );
} }
} }
@@ -47,29 +61,42 @@ export default class SaleInvoiceSubscriber {
* Handles customer balance diff balnace change once sale invoice edited. * Handles customer balance diff balnace change once sale invoice edited.
*/ */
@On(events.saleInvoice.onEdited) @On(events.saleInvoice.onEdited)
public async onSaleInvoiceEdited({ tenantId, saleInvoice, oldSaleInvoice, saleInvoiceId }) { public async onSaleInvoiceEdited({
tenantId,
saleInvoice,
oldSaleInvoice,
saleInvoiceId,
}) {
const { customerRepository } = this.tenancy.repositories(tenantId); const { customerRepository } = this.tenancy.repositories(tenantId);
this.logger.info('[sale_invoice] trying to change diff customer balance.', { tenantId }); this.logger.info('[sale_invoice] trying to change diff customer balance.', {
tenantId,
});
await customerRepository.changeDiffBalance( await customerRepository.changeDiffBalance(
saleInvoice.customerId, saleInvoice.customerId,
saleInvoice.balance, saleInvoice.balance,
oldSaleInvoice.balance, oldSaleInvoice.balance,
oldSaleInvoice.customerId, oldSaleInvoice.customerId
) );
} }
/** /**
* Handles customer balance decrement once sale invoice deleted. * Handles customer balance decrement once sale invoice deleted.
*/ */
@On(events.saleInvoice.onDeleted) @On(events.saleInvoice.onDeleted)
public async handleCustomerBalanceDecrement({ tenantId, saleInvoiceId, oldSaleInvoice }) { public async handleCustomerBalanceDecrement({
tenantId,
saleInvoiceId,
oldSaleInvoice,
}) {
const { customerRepository } = this.tenancy.repositories(tenantId); const { customerRepository } = this.tenancy.repositories(tenantId);
this.logger.info('[sale_invoice] trying to decrement customer balance.', { tenantId }); this.logger.info('[sale_invoice] trying to decrement customer balance.', {
await customerRepository.changeBalance( tenantId,
});
await customerRepository.changeBalance(
oldSaleInvoice.customerId, oldSaleInvoice.customerId,
oldSaleInvoice.balance * -1, oldSaleInvoice.balance * -1
); );
} }
@@ -77,10 +104,14 @@ export default class SaleInvoiceSubscriber {
* Handles sale invoice next number increment once invoice created. * Handles sale invoice next number increment once invoice created.
*/ */
@On(events.saleInvoice.onCreated) @On(events.saleInvoice.onCreated)
public async handleInvoiceNextNumberIncrement({ tenantId, saleInvoiceId, saleInvoice }) { public async handleInvoiceNextNumberIncrement({
tenantId,
saleInvoiceId,
saleInvoice,
}) {
await this.settingsService.incrementNextNumber(tenantId, { await this.settingsService.incrementNextNumber(tenantId, {
key: 'next_number', key: 'next_number',
group: 'sales_invoices', group: 'sales_invoices',
}); });
} }
} }

View File

@@ -18,6 +18,9 @@ export default class VendorsSubscriber {
this.vendorsService = Container.get(VendorsService); this.vendorsService = Container.get(VendorsService);
} }
/**
* Writes the open balance journal entries once the vendor created.
*/
@On(events.vendors.onCreated) @On(events.vendors.onCreated)
async handleWriteOpeningBalanceEntries({ tenantId, vendorId, vendor }) { async handleWriteOpeningBalanceEntries({ tenantId, vendorId, vendor }) {
// Writes the vendor opening balance journal entries. // Writes the vendor opening balance journal entries.
@@ -30,6 +33,9 @@ export default class VendorsSubscriber {
} }
} }
/**
* Revert the opening balance journal entries once the vendor deleted.
*/
@On(events.vendors.onDeleted) @On(events.vendors.onDeleted)
async handleRevertOpeningBalanceEntries({ tenantId, vendorId }) { async handleRevertOpeningBalanceEntries({ tenantId, vendorId }) {
await this.vendorsService.revertOpeningBalanceEntries( await this.vendorsService.revertOpeningBalanceEntries(
@@ -37,6 +43,9 @@ export default class VendorsSubscriber {
); );
} }
/**
* Revert the opening balance journal entries once the vendors deleted in bulk.
*/
@On(events.vendors.onBulkDeleted) @On(events.vendors.onBulkDeleted)
async handleBulkRevertOpeningBalanceEntries({ tenantId, vendorsIds }) { async handleBulkRevertOpeningBalanceEntries({ tenantId, vendorsIds }) {
await this.vendorsService.revertOpeningBalanceEntries( await this.vendorsService.revertOpeningBalanceEntries(