feat: validate the payment not delivered on make payment receive.

This commit is contained in:
a.bouhuolia
2020-12-19 13:44:02 +02:00
parent 920875d7d9
commit cc47314a62
8 changed files with 286 additions and 163 deletions

View File

@@ -339,7 +339,18 @@ export default class PaymentReceivesController extends BaseController {
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);
}

View File

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

View File

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

View File

@@ -37,7 +37,7 @@ const ERRORS = {
BILL_ITEMS_NOT_FOUND: 'BILL_ITEMS_NOT_FOUND',
BILL_ENTRIES_IDS_NOT_FOUND: 'BILL_ENTRIES_IDS_NOT_FOUND',
NOT_PURCHASE_ABLE_ITEMS: 'NOT_PURCHASE_ABLE_ITEMS',
BILL_ALREADY_OPEN: 'BILL_ALREADY_OPEN'
BILL_ALREADY_OPEN: 'BILL_ALREADY_OPEN',
};
/**
@@ -84,7 +84,10 @@ export default class BillsService extends SalesInvoicesCost {
const foundVendor = await vendorRepository.findOneById(vendorId);
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);
}
return foundVendor;
@@ -94,16 +97,21 @@ export default class BillsService extends SalesInvoicesCost {
* Validates the given bill existance.
* @async
* @param {number} tenantId -
* @param {number} billId -
* @param {number} billId -
*/
private async getBillOrThrowError(tenantId: number, billId: number) {
const { Bill } = this.tenancy.models(tenantId);
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) {
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);
}
return foundBill;
@@ -116,13 +124,19 @@ export default class BillsService extends SalesInvoicesCost {
* @param {Response} res
* @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 foundBills = await Bill.query().where('bill_number', billNumber).onBuild((builder) => {
if (notBillId) {
builder.whereNot('id', notBillId);
}
});
const foundBills = await Bill.query()
.where('bill_number', billNumber)
.onBuild((builder) => {
if (notBillId) {
builder.whereNot('id', notBillId);
}
});
if (foundBills.length > 0) {
throw new ServiceError(ERRORS.BILL_NUMBER_EXISTS);
@@ -131,17 +145,17 @@ export default class BillsService extends SalesInvoicesCost {
/**
* Converts bill DTO to model.
* @param {number} tenantId
* @param {IBillDTO} billDTO
* @param {IBill} oldBill
*
* @param {number} tenantId
* @param {IBillDTO} billDTO
* @param {IBill} oldBill
*
* @returns {IBill}
*/
private async billDTOToModel(
tenantId: number,
billDTO: IBillDTO | IBillEditDTO,
authorizedUser: ISystemUser,
oldBill?: IBill,
oldBill?: IBill
) {
const { ItemEntry } = this.tenancy.models(tenantId);
let invLotNumber = oldBill?.invLotNumber;
@@ -156,10 +170,10 @@ export default class BillsService extends SalesInvoicesCost {
const amount = sumBy(entries, 'amount');
return {
...formatDateFields(
omit(billDTO, ['open', 'entries']),
['billDate', 'dueDate']
),
...formatDateFields(omit(billDTO, ['open', 'entries']), [
'billDate',
'dueDate',
]),
amount,
invLotNumber,
entries: entries.map((entry) => ({
@@ -167,9 +181,10 @@ export default class BillsService extends SalesInvoicesCost {
...omit(entry, ['amount', 'id']),
})),
// Avoid rewrite the open date in edit mode when already opened.
...(billDTO.open && (!oldBill?.openedAt)) && ({
openedAt: moment().toMySqlDateTime(),
}),
...(billDTO.open &&
!oldBill?.openedAt && {
openedAt: moment().toMySqlDateTime(),
}),
userId: authorizedUser.id,
};
}
@@ -196,8 +211,16 @@ export default class BillsService extends SalesInvoicesCost {
): Promise<IBill> {
const { Bill } = this.tenancy.models(tenantId);
this.logger.info('[bill] trying to create a new bill', { tenantId, billDTO });
const billObj = await this.billDTOToModel(tenantId, billDTO, authorizedUser, null);
this.logger.info('[bill] trying to create a new bill', {
tenantId,
billDTO,
});
const billObj = await this.billDTOToModel(
tenantId,
billDTO,
authorizedUser,
null
);
// Retrieve vendor or throw not found service error.
await this.getVendorOrThrowError(tenantId, billDTO.vendorId);
@@ -207,19 +230,28 @@ export default class BillsService extends SalesInvoicesCost {
await this.validateBillNumberExists(tenantId, billDTO.billNumber);
}
// Validate items IDs existance.
await this.itemsEntriesService.validateItemsIdsExistance(tenantId, billDTO.entries);
await this.itemsEntriesService.validateItemsIdsExistance(
tenantId,
billDTO.entries
);
// 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.
const bill = await Bill.query().insertGraph({ ...billObj });
// Triggers `onBillCreated` event.
// Triggers `onBillCreated` event.
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;
}
@@ -253,7 +285,12 @@ export default class BillsService extends SalesInvoicesCost {
const oldBill = await this.getBillOrThrowError(tenantId, billId);
// Transforms the bill DTO object to model object.
const billObj = await this.billDTOToModel(tenantId, billDTO, authorizedUser, oldBill);
const billObj = await this.billDTOToModel(
tenantId,
billDTO,
authorizedUser,
oldBill
);
// Retrieve vendor details or throw not found service error.
await this.getVendorOrThrowError(tenantId, billDTO.vendorId);
@@ -263,22 +300,39 @@ export default class BillsService extends SalesInvoicesCost {
await this.validateBillNumberExists(tenantId, billDTO.billNumber, billId);
}
// 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.
await this.itemsEntriesService.validateItemsIdsExistance(tenantId, billDTO.entries);
await this.itemsEntriesService.validateItemsIdsExistance(
tenantId,
billDTO.entries
);
// Accept the purchasable items only.
await this.itemsEntriesService.validateNonPurchasableEntriesItems(tenantId, billDTO.entries);
await this.itemsEntriesService.validateNonPurchasableEntriesItems(
tenantId,
billDTO.entries
);
// Update the bill transaction.
const bill = await Bill.query().upsertGraphAndFetch({
id: billId,
...billObj,
});
// Triggers event `onBillEdited`.
await this.eventDispatcher.dispatch(events.bill.onEdited, { tenantId, billId, oldBill, bill });
this.logger.info('[bill] bill upserted successfully.', { tenantId, billId });
await this.eventDispatcher.dispatch(events.bill.onEdited, {
tenantId,
billId,
oldBill,
bill,
});
this.logger.info('[bill] bill upserted successfully.', {
tenantId,
billId,
});
return bill;
}
@@ -306,13 +360,17 @@ export default class BillsService extends SalesInvoicesCost {
await Promise.all([deleteBillEntriesOper, deleteBillOper]);
// 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.
* @param {Bill} bill
* @param {number} billId
* @param {Bill} bill
* @param {number} billId
*/
public recordInventoryTransactions(
tenantId: number,
@@ -320,19 +378,20 @@ export default class BillsService extends SalesInvoicesCost {
billId: number,
override?: boolean
) {
const inventoryTransactions = bill.entries
.map((entry) => ({
...pick(entry, ['item_id', 'quantity', 'rate']),
lotNumber: bill.invLotNumber,
transactionType: 'Bill',
transactionId: billId,
direction: 'IN',
date: bill.bill_date,
entryId: entry.id,
}));
const inventoryTransactions = bill.entries.map((entry) => ({
...pick(entry, ['item_id', 'quantity', 'rate']),
lotNumber: bill.invLotNumber,
transactionType: 'Bill',
transactionId: billId,
direction: 'IN',
date: bill.bill_date,
entryId: entry.id,
}));
return this.inventoryService.recordInventoryTransactions(
tenantId, inventoryTransactions, override
tenantId,
inventoryTransactions,
override
);
}
@@ -345,12 +404,12 @@ export default class BillsService extends SalesInvoicesCost {
public async recordJournalTransactions(
tenantId: number,
bill: IBill,
override: boolean = false,
override: boolean = false
) {
const journal = new JournalPoster(tenantId);
const journalCommands = new JournalCommands(journal);
await journalCommands.bill(bill, override)
await journalCommands.bill(bill, override);
return Promise.all([
journal.deleteEntries(),
@@ -366,16 +425,29 @@ export default class BillsService extends SalesInvoicesCost {
*/
public async getBills(
tenantId: number,
billsFilter: IBillsFilter,
): Promise<{ bills: IBill, pagination: IPaginationMeta, filterMeta: IFilterMeta }> {
billsFilter: IBillsFilter
): Promise<{
bills: IBill;
pagination: IPaginationMeta;
filterMeta: IFilterMeta;
}> {
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 });
const { results, pagination } = await Bill.query().onBuild((builder) => {
builder.withGraphFetched('vendor');
dynamicFilter.buildQuery()(builder);
}).pagination(billsFilter.page - 1, billsFilter.pageSize);
this.logger.info('[bills] trying to get bills data table.', {
tenantId,
billsFilter,
});
const { results, pagination } = await Bill.query()
.onBuild((builder) => {
builder.withGraphFetched('vendor');
dynamicFilter.buildQuery()(builder);
})
.pagination(billsFilter.page - 1, billsFilter.pageSize);
return {
bills: results,
@@ -386,8 +458,8 @@ export default class BillsService extends SalesInvoicesCost {
/**
* Retrieve all due bills or for specific given vendor id.
* @param {number} tenantId -
* @param {number} vendorId -
* @param {number} tenantId -
* @param {number} vendorId -
*/
public async getDueBills(
tenantId: number,
@@ -414,8 +486,12 @@ export default class BillsService extends SalesInvoicesCost {
public async getBill(tenantId: number, billId: number): Promise<IBill> {
const { Bill } = this.tenancy.models(tenantId);
this.logger.info('[bills] trying to fetch specific bill with metadata.', { tenantId, billId });
const bill = await Bill.query().findById(billId)
this.logger.info('[bills] trying to fetch specific bill with metadata.', {
tenantId,
billId,
});
const bill = await Bill.query()
.findById(billId)
.withGraphFetched('vendor')
.withGraphFetched('entries');
@@ -440,9 +516,9 @@ export default class BillsService extends SalesInvoicesCost {
.whereIn('id', billItemsIds)
.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(
tenantId,
inventoryItemsIds,
@@ -453,13 +529,10 @@ export default class BillsService extends SalesInvoicesCost {
/**
* Mark the bill as open.
* @param {number} tenantId
* @param {number} billId
* @param {number} tenantId
* @param {number} billId
*/
public async openBill(
tenantId: number,
billId: number,
): Promise<void> {
public async openBill(tenantId: number, billId: number): Promise<void> {
const { Bill } = this.tenancy.models(tenantId);
// Retrieve the given bill or throw not found error.
@@ -474,4 +547,4 @@ export default class BillsService extends SalesInvoicesCost {
openedAt: moment().toMySqlDateTime(),
});
}
}
}

View File

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

View File

@@ -1,10 +1,9 @@
import { Container, Inject, Service } from 'typedi';
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';
import JournalPosterService from 'services/Sales/JournalPosterService';
import VendorRepository from 'repositories/VendorRepository';
@EventSubscriber()
export default class BillSubscriber {
@@ -13,11 +12,13 @@ export default class BillSubscriber {
logger: any;
journalPosterService: JournalPosterService;
/**
* Constructor method.
*/
constructor() {
this.tenancy = Container.get(TenancyService);
this.billsService = Container.get(BillsService);
this.logger = Container.get('logger');
this.journalPosterService = Container.get(JournalPosterService);
}
@@ -29,7 +30,10 @@ export default class BillSubscriber {
const { vendorRepository } = this.tenancy.repositories(tenantId);
// 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);
}
@@ -61,7 +65,10 @@ export default class BillSubscriber {
const { vendorRepository } = this.tenancy.repositories(tenantId);
// 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);
}
@@ -71,8 +78,15 @@ export default class BillSubscriber {
@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');
this.logger.info('[bill] trying to delete journal entries.', {
tenantId,
billId,
});
await this.journalPosterService.revertJournalTransactions(
tenantId,
billId,
'Bill'
);
}
/**
@@ -83,12 +97,15 @@ export default class BillSubscriber {
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 });
this.logger.info('[bill[ change vendor the different balance.', {
tenantId,
billId,
});
await vendorRepository.changeDiffBalance(
bill.vendorId,
bill.amount,
oldBill.amount,
oldBill.vendorId,
oldBill.vendorId
);
}
}
}

View File

@@ -18,6 +18,9 @@ export default class VendorsSubscriber {
this.vendorsService = Container.get(VendorsService);
}
/**
* Writes the open balance journal entries once the vendor created.
*/
@On(events.vendors.onCreated)
async handleWriteOpeningBalanceEntries({ tenantId, vendorId, vendor }) {
// 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)
async handleRevertOpeningBalanceEntries({ tenantId, vendorId }) {
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)
async handleBulkRevertOpeningBalanceEntries({ tenantId, vendorsIds }) {
await this.vendorsService.revertOpeningBalanceEntries(