mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 21:00:31 +00:00
Merge branch 'master' of https://github.com/abouolia/Bigcapital
This commit is contained in:
@@ -197,8 +197,8 @@ export default class BillsController extends BaseController {
|
||||
* @param {Response} res
|
||||
*/
|
||||
async editBill(req: Request, res: Response, next: NextFunction) {
|
||||
const { id: billId, user } = req.params;
|
||||
const { tenantId } = req;
|
||||
const { id: billId } = req.params;
|
||||
const { tenantId, user } = req;
|
||||
const billDTO: IBillEditDTO = this.matchedBodyData(req);
|
||||
|
||||
try {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ export interface IBill {
|
||||
openedAt: Date | string,
|
||||
|
||||
entries: IItemEntry[],
|
||||
userId: number,
|
||||
};
|
||||
|
||||
export interface IBillsFilter extends IDynamicListFilterDTO {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -5,13 +5,6 @@ import { defaultToTransform } from 'utils';
|
||||
import { QueryBuilder } from 'knex';
|
||||
|
||||
export default class SaleInvoice extends TenantModel {
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return ['dueAmount'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
@@ -60,10 +53,10 @@ export default class SaleInvoice extends TenantModel {
|
||||
|
||||
/**
|
||||
* Retrieve the invoice due amount.
|
||||
* (Invoice amount - payment amount = Due amount)
|
||||
* Equation (Invoice amount - payment amount = Due amount)
|
||||
* @return {boolean}
|
||||
*/
|
||||
dueAmount() {
|
||||
get dueAmount() {
|
||||
return Math.max(this.balance - this.paymentAmount, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ interface IJournalTransactionsFilter {
|
||||
toAmount: number,
|
||||
contactsIds?: number[],
|
||||
contactType?: string,
|
||||
referenceType?: string[],
|
||||
referenceId?: number[],
|
||||
};
|
||||
|
||||
export default class AccountTransactionsRepository extends TenantRepository {
|
||||
@@ -42,6 +44,12 @@ export default class AccountTransactionsRepository extends TenantRepository {
|
||||
if (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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -177,7 +177,20 @@ export default class CachableRepository extends EntityRepository{
|
||||
*
|
||||
* @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);
|
||||
|
||||
// Flushes the repository cache after delete operation.
|
||||
|
||||
@@ -175,7 +175,7 @@ export default class EntityRepository {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Deletes the given entries in the array on the specific field.
|
||||
* @param {string} field -
|
||||
* @param {number|string} values -
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { sumBy, chain } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { IBill } from 'interfaces';
|
||||
import JournalPoster from "./JournalPoster";
|
||||
import JournalEntry from "./JournalEntry";
|
||||
import { AccountTransaction } from 'models';
|
||||
@@ -7,6 +9,7 @@ import {
|
||||
IManualJournal,
|
||||
IExpense,
|
||||
IExpenseCategory,
|
||||
IItem,
|
||||
} from 'interfaces';
|
||||
|
||||
interface IInventoryCostEntity {
|
||||
@@ -57,6 +60,68 @@ export default class JournalCommands{
|
||||
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.
|
||||
* @param {number} customerId
|
||||
|
||||
@@ -21,7 +21,7 @@ export default class JournalPoster implements IJournalPoster {
|
||||
deletedEntriesIds: number[] = [];
|
||||
entries: IJournalEntry[] = [];
|
||||
balancesChange: IAccountsChange = {};
|
||||
accountsDepGraph: IAccountsChange = {};
|
||||
accountsDepGraph: IAccountsChange;
|
||||
|
||||
accountsBalanceTable: { [key: number]: number; } = {};
|
||||
|
||||
@@ -250,12 +250,12 @@ export default class JournalPoster implements IJournalPoster {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async saveEntries() {
|
||||
const { AccountTransaction } = this.models;
|
||||
const { transactionsRepository } = this.repositories;
|
||||
const saveOperations: Promise<void>[] = [];
|
||||
|
||||
this.entries.forEach((entry) => {
|
||||
const oper = AccountTransaction.query()
|
||||
.insert({
|
||||
const oper = transactionsRepository
|
||||
.create({
|
||||
accountId: entry.account,
|
||||
...omit(entry, ['account']),
|
||||
});
|
||||
@@ -309,12 +309,10 @@ export default class JournalPoster implements IJournalPoster {
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async deleteEntries() {
|
||||
const { AccountTransaction } = this.models;
|
||||
const { transactionsRepository } = this.repositories;
|
||||
|
||||
if (this.deletedEntriesIds.length > 0) {
|
||||
await AccountTransaction.query()
|
||||
.whereIn('id', this.deletedEntriesIds)
|
||||
.delete();
|
||||
await transactionsRepository.deleteWhereIdIn(this.deletedEntriesIds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,7 +330,6 @@ export default class JournalPoster implements IJournalPoster {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the entries balance change.
|
||||
* @public
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
import { ServiceError } from 'exceptions';
|
||||
import ItemsService from 'services/Items/ItemsService';
|
||||
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
|
||||
import JournalCommands from 'services/Accounting/JournalCommands';
|
||||
|
||||
const ERRORS = {
|
||||
BILL_NOT_FOUND: 'BILL_NOT_FOUND',
|
||||
@@ -36,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',
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -83,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;
|
||||
@@ -93,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;
|
||||
@@ -115,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);
|
||||
@@ -130,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,
|
||||
oldBill?: IBill,
|
||||
authorizedUser: ISystemUser,
|
||||
oldBill?: IBill
|
||||
) {
|
||||
const { ItemEntry } = this.tenancy.models(tenantId);
|
||||
let invLotNumber = oldBill?.invLotNumber;
|
||||
@@ -155,21 +170,21 @@ export default class BillsService extends SalesInvoicesCost {
|
||||
const amount = sumBy(entries, 'amount');
|
||||
|
||||
return {
|
||||
...formatDateFields(
|
||||
omit(billDTO, ['open']),
|
||||
['billDate', 'dueDate']
|
||||
),
|
||||
...formatDateFields(omit(billDTO, ['open', 'entries']), [
|
||||
'billDate',
|
||||
'dueDate',
|
||||
]),
|
||||
amount,
|
||||
invLotNumber,
|
||||
entries: entries.map((entry) => ({
|
||||
reference_type: 'Bill',
|
||||
...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, null, authorizedUser);
|
||||
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, oldBill, authorizedUser);
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
@@ -342,62 +401,16 @@ export default class BillsService extends SalesInvoicesCost {
|
||||
* @param {IBill} bill
|
||||
* @param {Integer} billId
|
||||
*/
|
||||
public async recordJournalTransactions(tenantId: number, bill: IBill, billId?: number) {
|
||||
const { AccountTransaction, Item, ItemEntry } = this.tenancy.models(tenantId);
|
||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
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' });
|
||||
|
||||
public async recordJournalTransactions(
|
||||
tenantId: number,
|
||||
bill: IBill,
|
||||
override: boolean = false
|
||||
) {
|
||||
const journal = new JournalPoster(tenantId);
|
||||
const journalCommands = new JournalCommands(journal);
|
||||
|
||||
const commonJournalMeta = {
|
||||
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');
|
||||
await journalCommands.bill(bill, override);
|
||||
|
||||
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([
|
||||
journal.deleteEntries(),
|
||||
journal.saveEntries(),
|
||||
@@ -412,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,
|
||||
@@ -432,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,
|
||||
@@ -460,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');
|
||||
|
||||
@@ -486,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,
|
||||
@@ -499,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.
|
||||
@@ -520,4 +547,4 @@ export default class BillsService extends SalesInvoicesCost {
|
||||
openedAt: moment().toMySqlDateTime(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -37,11 +41,20 @@ export default class BillSubscriber {
|
||||
* Handles writing journal entries once bill created.
|
||||
*/
|
||||
@On(events.bill.onCreated)
|
||||
@On(events.bill.onEdited)
|
||||
async handlerWriteJournalEntries({ tenantId, billId, bill }) {
|
||||
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, 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);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -62,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'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
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';
|
||||
import SaleEstimateService from 'services/Sales/SalesEstimate';
|
||||
|
||||
@EventSubscriber()
|
||||
export default class SaleInvoiceSubscriber {
|
||||
logger: any;
|
||||
@@ -22,23 +23,36 @@ export default class SaleInvoiceSubscriber {
|
||||
* Handles customer balance increment once sale invoice created.
|
||||
*/
|
||||
@On(events.saleInvoice.onCreated)
|
||||
public async handleCustomerBalanceIncrement({ tenantId, saleInvoice, saleInvoiceId }) {
|
||||
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);
|
||||
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.fromEstiamteId) {
|
||||
public async handleMarkEstimateConvert({
|
||||
tenantId,
|
||||
saleInvoice,
|
||||
saleInvoiceId,
|
||||
}) {
|
||||
if (saleInvoice.fromEstimateId) {
|
||||
this.saleEstimatesService.convertEstimateToInvoice(
|
||||
tenantId,
|
||||
saleInvoice.fromEstiamteId,
|
||||
saleInvoiceId,
|
||||
saleInvoiceId
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -47,29 +61,42 @@ export default class SaleInvoiceSubscriber {
|
||||
* Handles customer balance diff balnace change once sale invoice edited.
|
||||
*/
|
||||
@On(events.saleInvoice.onEdited)
|
||||
public async onSaleInvoiceEdited({ tenantId, saleInvoice, oldSaleInvoice, saleInvoiceId }) {
|
||||
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 });
|
||||
this.logger.info('[sale_invoice] trying to change diff customer balance.', {
|
||||
tenantId,
|
||||
});
|
||||
await customerRepository.changeDiffBalance(
|
||||
saleInvoice.customerId,
|
||||
saleInvoice.balance,
|
||||
oldSaleInvoice.balance,
|
||||
oldSaleInvoice.customerId,
|
||||
)
|
||||
oldSaleInvoice.customerId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles customer balance decrement once sale invoice deleted.
|
||||
*/
|
||||
@On(events.saleInvoice.onDeleted)
|
||||
public async handleCustomerBalanceDecrement({ tenantId, saleInvoiceId, oldSaleInvoice }) {
|
||||
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(
|
||||
this.logger.info('[sale_invoice] trying to decrement customer balance.', {
|
||||
tenantId,
|
||||
});
|
||||
await customerRepository.changeBalance(
|
||||
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.
|
||||
*/
|
||||
@On(events.saleInvoice.onCreated)
|
||||
public async handleInvoiceNextNumberIncrement({ tenantId, saleInvoiceId, saleInvoice }) {
|
||||
public async handleInvoiceNextNumberIncrement({
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
saleInvoice,
|
||||
}) {
|
||||
await this.settingsService.incrementNextNumber(tenantId, {
|
||||
key: 'next_number',
|
||||
group: 'sales_invoices',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user