mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-22 07:40:32 +00:00
feat: compute items cost of inventory adjustments transcations.
This commit is contained in:
@@ -115,8 +115,8 @@ export default class InventoryAdjustmentsController extends BaseController {
|
|||||||
tenantId,
|
tenantId,
|
||||||
adjustmentId
|
adjustmentId
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
|
id: adjustmentId,
|
||||||
message: 'The inventory adjustment has been deleted successfully.',
|
message: 'The inventory adjustment has been deleted successfully.',
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -25,14 +25,14 @@ export default class ItemsController extends BaseController {
|
|||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/',
|
'/',
|
||||||
[...this.validateItemSchema, ...this.validateNewItemSchema],
|
this.validateItemSchema,
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.newItem.bind(this)),
|
asyncMiddleware(this.newItem.bind(this)),
|
||||||
this.handlerServiceErrors
|
this.handlerServiceErrors
|
||||||
);
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/:id/activate',
|
'/:id/activate',
|
||||||
[...this.validateSpecificItemSchema],
|
this.validateSpecificItemSchema,
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.activateItem.bind(this)),
|
asyncMiddleware(this.activateItem.bind(this)),
|
||||||
this.handlerServiceErrors
|
this.handlerServiceErrors
|
||||||
@@ -83,30 +83,6 @@ export default class ItemsController extends BaseController {
|
|||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* New item validation schema.
|
|
||||||
*/
|
|
||||||
get validateNewItemSchema(): ValidationChain[] {
|
|
||||||
return [
|
|
||||||
check('opening_quantity').default(0).isInt({ min: 0 }).toInt(),
|
|
||||||
check('opening_cost')
|
|
||||||
.if(body('opening_quantity').exists().isInt({ min: 1 }))
|
|
||||||
.exists()
|
|
||||||
.isFloat(),
|
|
||||||
check('opening_cost')
|
|
||||||
.optional({ nullable: true })
|
|
||||||
.isFloat({ min: 0 })
|
|
||||||
.toFloat(),
|
|
||||||
check('opening_date')
|
|
||||||
.if(
|
|
||||||
body('opening_quantity').exists().isFloat({ min: 1 }) ||
|
|
||||||
body('opening_cost').exists().isFloat({ min: 1 })
|
|
||||||
)
|
|
||||||
.exists(),
|
|
||||||
check('opening_date').optional({ nullable: true }).isISO8601().toDate(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate item schema.
|
* Validate item schema.
|
||||||
*/
|
*/
|
||||||
@@ -503,6 +479,11 @@ export default class ItemsController extends BaseController {
|
|||||||
errors: [{ type: 'ITEM_HAS_ASSOCIATED_TRANSACTINS', code: 320 }],
|
errors: [{ type: 'ITEM_HAS_ASSOCIATED_TRANSACTINS', code: 320 }],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (error.errorType === 'ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT') {
|
||||||
|
return res.status(400).send({
|
||||||
|
errors: [{ type: 'ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT', code: 330 }],
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,10 +18,6 @@ exports.up = function (knex) {
|
|||||||
table.text('purchase_description').nullable();
|
table.text('purchase_description').nullable();
|
||||||
table.integer('quantity_on_hand');
|
table.integer('quantity_on_hand');
|
||||||
|
|
||||||
table.integer('opening_quantity');
|
|
||||||
table.decimal('opening_cost', 13, 3);
|
|
||||||
table.date('opening_date');
|
|
||||||
|
|
||||||
table.text('note').nullable();
|
table.text('note').nullable();
|
||||||
table.boolean('active');
|
table.boolean('active');
|
||||||
table.integer('category_id').unsigned().index().references('id').inTable('items_categories');
|
table.integer('category_id').unsigned().index().references('id').inTable('items_categories');
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ exports.up = function(knex) {
|
|||||||
table.integer('quantity').unsigned();
|
table.integer('quantity').unsigned();
|
||||||
table.decimal('rate', 13, 3).unsigned();
|
table.decimal('rate', 13, 3).unsigned();
|
||||||
|
|
||||||
table.string('lot_number').index();
|
table.integer('lot_number').index();
|
||||||
|
table.integer('cost_account_id').unsigned().index().references('id').inTable('accounts');
|
||||||
|
|
||||||
table.string('transaction_type').index();
|
table.string('transaction_type').index();
|
||||||
table.integer('transaction_id').unsigned().index();
|
table.integer('transaction_id').unsigned().index();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
type IAdjustmentTypes = 'increment' | 'decrement' | 'value_adjustment';
|
type IAdjustmentTypes = 'increment' | 'decrement';
|
||||||
|
|
||||||
export interface IQuickInventoryAdjustmentDTO {
|
export interface IQuickInventoryAdjustmentDTO {
|
||||||
date: Date | string;
|
date: Date | string;
|
||||||
@@ -20,6 +20,7 @@ export interface IInventoryAdjustment {
|
|||||||
reason: string;
|
reason: string;
|
||||||
description: string;
|
description: string;
|
||||||
referenceNo: string;
|
referenceNo: string;
|
||||||
|
inventoryDirection?: 'IN' | 'OUT',
|
||||||
entries: IInventoryAdjustmentEntry[];
|
entries: IInventoryAdjustmentEntry[];
|
||||||
userId: number;
|
userId: number;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ export type TInventoryTransactionDirection = 'IN' | 'OUT';
|
|||||||
|
|
||||||
export interface IInventoryTransaction {
|
export interface IInventoryTransaction {
|
||||||
id?: number,
|
id?: number,
|
||||||
date: Date,
|
date: Date|string,
|
||||||
direction: TInventoryTransactionDirection,
|
direction: TInventoryTransactionDirection,
|
||||||
itemId: number,
|
itemId: number,
|
||||||
quantity: number,
|
quantity: number,
|
||||||
rate: number,
|
rate: number,
|
||||||
transactionType: string,
|
transactionType: string,
|
||||||
transactionId: number,
|
transactionId: number,
|
||||||
lotNumber: string,
|
lotNumber: number,
|
||||||
entryId: number,
|
entryId: number,
|
||||||
createdAt?: Date,
|
createdAt?: Date,
|
||||||
updatedAt?: Date,
|
updatedAt?: Date,
|
||||||
@@ -25,7 +25,7 @@ export interface IInventoryLotCost {
|
|||||||
rate: number,
|
rate: number,
|
||||||
remaining: number,
|
remaining: number,
|
||||||
cost: number,
|
cost: number,
|
||||||
lotNumber: string|number,
|
lotNumber: number,
|
||||||
transactionType: string,
|
transactionType: string,
|
||||||
transactionId: number,
|
transactionId: number,
|
||||||
entryId: number
|
entryId: number
|
||||||
|
|||||||
@@ -22,10 +22,6 @@ export interface IItem{
|
|||||||
|
|
||||||
quantityOnHand: number,
|
quantityOnHand: number,
|
||||||
|
|
||||||
openingQuantity: number,
|
|
||||||
openingCost: number,
|
|
||||||
openingDate: Date,
|
|
||||||
|
|
||||||
note: string,
|
note: string,
|
||||||
active: boolean,
|
active: boolean,
|
||||||
|
|
||||||
@@ -58,10 +54,6 @@ export interface IItemDTO {
|
|||||||
|
|
||||||
quantityOnHand: number,
|
quantityOnHand: number,
|
||||||
|
|
||||||
openingQuantity?: number,
|
|
||||||
openingCost?: number,
|
|
||||||
openingDate?: Date,
|
|
||||||
|
|
||||||
note: string,
|
note: string,
|
||||||
active: boolean,
|
active: boolean,
|
||||||
|
|
||||||
|
|||||||
@@ -23,10 +23,12 @@ import 'subscribers/SaleReceipt/SyncItemsQuantity';
|
|||||||
import 'subscribers/SaleReceipt/WriteInventoryTransactions';
|
import 'subscribers/SaleReceipt/WriteInventoryTransactions';
|
||||||
import 'subscribers/SaleReceipt/WriteJournalEntries';
|
import 'subscribers/SaleReceipt/WriteJournalEntries';
|
||||||
|
|
||||||
|
import 'subscribers/Inventory/Inventory';
|
||||||
|
import 'subscribers/Inventory/InventoryAdjustment';
|
||||||
|
|
||||||
import 'subscribers/customers';
|
import 'subscribers/customers';
|
||||||
import 'subscribers/vendors';
|
import 'subscribers/vendors';
|
||||||
import 'subscribers/paymentMades';
|
import 'subscribers/paymentMades';
|
||||||
import 'subscribers/paymentReceives';
|
import 'subscribers/paymentReceives';
|
||||||
import 'subscribers/saleEstimates';
|
import 'subscribers/saleEstimates';
|
||||||
import 'subscribers/inventory';
|
|
||||||
import 'subscribers/items';
|
import 'subscribers/items';
|
||||||
@@ -35,6 +35,8 @@ import ManualJournal from 'models/ManualJournal';
|
|||||||
import ManualJournalEntry from 'models/ManualJournalEntry';
|
import ManualJournalEntry from 'models/ManualJournalEntry';
|
||||||
import Media from 'models/Media';
|
import Media from 'models/Media';
|
||||||
import MediaLink from 'models/MediaLink';
|
import MediaLink from 'models/MediaLink';
|
||||||
|
import InventoryAdjustment from 'models/InventoryAdjustment';
|
||||||
|
import InventoryAdjustmentEntry from 'models/InventoryAdjustmentEntry';
|
||||||
|
|
||||||
export default (knex) => {
|
export default (knex) => {
|
||||||
const models = {
|
const models = {
|
||||||
@@ -73,6 +75,8 @@ export default (knex) => {
|
|||||||
Vendor,
|
Vendor,
|
||||||
Customer,
|
Customer,
|
||||||
Contact,
|
Contact,
|
||||||
|
InventoryAdjustment,
|
||||||
|
InventoryAdjustmentEntry,
|
||||||
};
|
};
|
||||||
return mapValues(models, (model) => model.bindKnex(knex));
|
return mapValues(models, (model) => model.bindKnex(knex));
|
||||||
}
|
}
|
||||||
@@ -17,6 +17,34 @@ export default class AccountTransaction extends TenantModel {
|
|||||||
return ['createdAt'];
|
return ['createdAt'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static get virtualAttributes() {
|
||||||
|
return ['referenceTypeFormatted'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve formatted reference type.
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
get referenceTypeFormatted() {
|
||||||
|
return AccountTransaction.getReferenceTypeFormatted(this.referenceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference type formatted.
|
||||||
|
*/
|
||||||
|
static getReferenceTypeFormatted(referenceType) {
|
||||||
|
const mapped = {
|
||||||
|
'SaleInvoice': 'Sale invoice',
|
||||||
|
'SaleReceipt': 'Sale receipt',
|
||||||
|
'PaymentReceive': 'Payment receive',
|
||||||
|
'BillPayment': 'Payment made',
|
||||||
|
'VendorOpeningBalance': 'Vendor opening balance',
|
||||||
|
'CustomerOpeningBalance': 'Customer opening balance',
|
||||||
|
'InventoryAdjustment': 'Inventory adjustment'
|
||||||
|
};
|
||||||
|
return mapped[referenceType] || '';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model modifiers.
|
* Model modifiers.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -16,6 +16,28 @@ export default class InventoryAdjustment extends TenantModel {
|
|||||||
return ['created_at'];
|
return ['created_at'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Virtual attributes.
|
||||||
|
*/
|
||||||
|
static get virtualAttributes() {
|
||||||
|
return ['inventoryDirection'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve formatted reference type.
|
||||||
|
*/
|
||||||
|
get inventoryDirection() {
|
||||||
|
return InventoryAdjustment.getInventoryDirection(this.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getInventoryDirection(type) {
|
||||||
|
const directions = {
|
||||||
|
'increment': 'IN',
|
||||||
|
'decrement': 'OUT',
|
||||||
|
};
|
||||||
|
return directions[type] || '';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Relationship mapping.
|
* Relationship mapping.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -231,7 +231,6 @@ export default class JournalCommands {
|
|||||||
referenceId: expense.id,
|
referenceId: expense.id,
|
||||||
date: expense.paymentDate,
|
date: expense.paymentDate,
|
||||||
userId,
|
userId,
|
||||||
draft: !expense.publishedAt,
|
|
||||||
};
|
};
|
||||||
const paymentJournalEntry = new JournalEntry({
|
const paymentJournalEntry = new JournalEntry({
|
||||||
credit: expense.totalAmount,
|
credit: expense.totalAmount,
|
||||||
@@ -330,7 +329,6 @@ export default class JournalCommands {
|
|||||||
note: entry.note,
|
note: entry.note,
|
||||||
date: manualJournalObj.date,
|
date: manualJournalObj.date,
|
||||||
userId: manualJournalObj.userId,
|
userId: manualJournalObj.userId,
|
||||||
draft: !manualJournalObj.status,
|
|
||||||
index: entry.index,
|
index: entry.index,
|
||||||
});
|
});
|
||||||
if (entry.debit) {
|
if (entry.debit) {
|
||||||
@@ -354,7 +352,7 @@ export default class JournalCommands {
|
|||||||
inventoryCostLot: IInventoryLotCost & { item: IItem }
|
inventoryCostLot: IInventoryLotCost & { item: IItem }
|
||||||
) {
|
) {
|
||||||
const commonEntry = {
|
const commonEntry = {
|
||||||
referenceType: 'SaleInvoice',
|
referenceType: inventoryCostLot.transactionType,
|
||||||
referenceId: inventoryCostLot.transactionId,
|
referenceId: inventoryCostLot.transactionId,
|
||||||
date: inventoryCostLot.date,
|
date: inventoryCostLot.date,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ export default class AccountsService {
|
|||||||
* @param {number} accountId
|
* @param {number} accountId
|
||||||
* @return {IAccount}
|
* @return {IAccount}
|
||||||
*/
|
*/
|
||||||
private async getAccountOrThrowError(tenantId: number, accountId: number) {
|
public async getAccountOrThrowError(tenantId: number, accountId: number) {
|
||||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||||
|
|
||||||
this.logger.info('[accounts] validating the account existance.', {
|
this.logger.info('[accounts] validating the account existance.', {
|
||||||
|
|||||||
@@ -30,25 +30,34 @@ export default class ExchangeRatesService implements IExchangeRatesService {
|
|||||||
eventDispatcher: EventDispatcherInterface;
|
eventDispatcher: EventDispatcherInterface;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
tenancy: TenancyService;
|
tenancy: TenancyService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new exchange rate.
|
* Creates a new exchange rate.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {IExchangeRateDTO} exchangeRateDTO
|
* @param {IExchangeRateDTO} exchangeRateDTO
|
||||||
* @returns {Promise<IExchangeRate>}
|
* @returns {Promise<IExchangeRate>}
|
||||||
*/
|
*/
|
||||||
public async newExchangeRate(tenantId: number, exchangeRateDTO: IExchangeRateDTO): Promise<IExchangeRate> {
|
public async newExchangeRate(
|
||||||
|
tenantId: number,
|
||||||
|
exchangeRateDTO: IExchangeRateDTO
|
||||||
|
): Promise<IExchangeRate> {
|
||||||
const { ExchangeRate } = this.tenancy.models(tenantId);
|
const { ExchangeRate } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
this.logger.info('[exchange_rates] trying to insert new exchange rate.', { tenantId, exchangeRateDTO });
|
this.logger.info('[exchange_rates] trying to insert new exchange rate.', {
|
||||||
|
tenantId,
|
||||||
|
exchangeRateDTO,
|
||||||
|
});
|
||||||
await this.validateExchangeRatePeriodExistance(tenantId, exchangeRateDTO);
|
await this.validateExchangeRatePeriodExistance(tenantId, exchangeRateDTO);
|
||||||
|
|
||||||
const exchangeRate = await ExchangeRate.query().insertAndFetch({
|
const exchangeRate = await ExchangeRate.query().insertAndFetch({
|
||||||
...exchangeRateDTO,
|
...exchangeRateDTO,
|
||||||
date: moment(exchangeRateDTO.date).format('YYYY-MM-DD'),
|
date: moment(exchangeRateDTO.date).format('YYYY-MM-DD'),
|
||||||
});
|
});
|
||||||
this.logger.info('[exchange_rates] inserted successfully.', { tenantId, exchangeRateDTO });
|
this.logger.info('[exchange_rates] inserted successfully.', {
|
||||||
|
tenantId,
|
||||||
|
exchangeRateDTO,
|
||||||
|
});
|
||||||
return exchangeRate;
|
return exchangeRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,14 +67,28 @@ export default class ExchangeRatesService implements IExchangeRatesService {
|
|||||||
* @param {number} exchangeRateId - Exchange rate id.
|
* @param {number} exchangeRateId - Exchange rate id.
|
||||||
* @param {IExchangeRateEditDTO} editExRateDTO - Edit exchange rate DTO.
|
* @param {IExchangeRateEditDTO} editExRateDTO - Edit exchange rate DTO.
|
||||||
*/
|
*/
|
||||||
public async editExchangeRate(tenantId: number, exchangeRateId: number, editExRateDTO: IExchangeRateEditDTO): Promise<void> {
|
public async editExchangeRate(
|
||||||
|
tenantId: number,
|
||||||
|
exchangeRateId: number,
|
||||||
|
editExRateDTO: IExchangeRateEditDTO
|
||||||
|
): Promise<void> {
|
||||||
const { ExchangeRate } = this.tenancy.models(tenantId);
|
const { ExchangeRate } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
this.logger.info('[exchange_rates] trying to edit exchange rate.', { tenantId, exchangeRateId, editExRateDTO });
|
this.logger.info('[exchange_rates] trying to edit exchange rate.', {
|
||||||
|
tenantId,
|
||||||
|
exchangeRateId,
|
||||||
|
editExRateDTO,
|
||||||
|
});
|
||||||
await this.validateExchangeRateExistance(tenantId, exchangeRateId);
|
await this.validateExchangeRateExistance(tenantId, exchangeRateId);
|
||||||
|
|
||||||
await ExchangeRate.query().where('id', exchangeRateId).update({ ...editExRateDTO });
|
await ExchangeRate.query()
|
||||||
this.logger.info('[exchange_rates] exchange rate edited successfully.', { tenantId, exchangeRateId, editExRateDTO });
|
.where('id', exchangeRateId)
|
||||||
|
.update({ ...editExRateDTO });
|
||||||
|
this.logger.info('[exchange_rates] exchange rate edited successfully.', {
|
||||||
|
tenantId,
|
||||||
|
exchangeRateId,
|
||||||
|
editExRateDTO,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,7 +96,10 @@ export default class ExchangeRatesService implements IExchangeRatesService {
|
|||||||
* @param {number} tenantId - Tenant id.
|
* @param {number} tenantId - Tenant id.
|
||||||
* @param {number} exchangeRateId - Exchange rate id.
|
* @param {number} exchangeRateId - Exchange rate id.
|
||||||
*/
|
*/
|
||||||
public async deleteExchangeRate(tenantId: number, exchangeRateId: number): Promise<void> {
|
public async deleteExchangeRate(
|
||||||
|
tenantId: number,
|
||||||
|
exchangeRateId: number
|
||||||
|
): Promise<void> {
|
||||||
const { ExchangeRate } = this.tenancy.models(tenantId);
|
const { ExchangeRate } = this.tenancy.models(tenantId);
|
||||||
await this.validateExchangeRateExistance(tenantId, exchangeRateId);
|
await this.validateExchangeRateExistance(tenantId, exchangeRateId);
|
||||||
|
|
||||||
@@ -83,29 +109,43 @@ export default class ExchangeRatesService implements IExchangeRatesService {
|
|||||||
/**
|
/**
|
||||||
* Listing exchange rates details.
|
* Listing exchange rates details.
|
||||||
* @param {number} tenantId - Tenant id.
|
* @param {number} tenantId - Tenant id.
|
||||||
* @param {IExchangeRateFilter} exchangeRateFilter - Exchange rates list filter.
|
* @param {IExchangeRateFilter} exchangeRateFilter - Exchange rates list filter.
|
||||||
*/
|
*/
|
||||||
public async listExchangeRates(tenantId: number, exchangeRateFilter: IExchangeRateFilter): Promise<void> {
|
public async listExchangeRates(
|
||||||
|
tenantId: number,
|
||||||
|
exchangeRateFilter: IExchangeRateFilter
|
||||||
|
): Promise<void> {
|
||||||
const { ExchangeRate } = this.tenancy.models(tenantId);
|
const { ExchangeRate } = this.tenancy.models(tenantId);
|
||||||
const exchangeRates = await ExchangeRate.query()
|
const exchangeRates = await ExchangeRate.query().pagination(
|
||||||
.pagination(exchangeRateFilter.page - 1, exchangeRateFilter.pageSize);
|
exchangeRateFilter.page - 1,
|
||||||
|
exchangeRateFilter.pageSize
|
||||||
|
);
|
||||||
|
|
||||||
return exchangeRates;
|
return exchangeRates;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes exchange rates in bulk.
|
* Deletes exchange rates in bulk.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number[]} exchangeRatesIds
|
* @param {number[]} exchangeRatesIds
|
||||||
*/
|
*/
|
||||||
public async deleteBulkExchangeRates(tenantId: number, exchangeRatesIds: number[]): Promise<void> {
|
public async deleteBulkExchangeRates(
|
||||||
|
tenantId: number,
|
||||||
|
exchangeRatesIds: number[]
|
||||||
|
): Promise<void> {
|
||||||
const { ExchangeRate } = this.tenancy.models(tenantId);
|
const { ExchangeRate } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
this.logger.info('[exchange_rates] trying delete in bulk.', { tenantId, exchangeRatesIds });
|
this.logger.info('[exchange_rates] trying delete in bulk.', {
|
||||||
|
tenantId,
|
||||||
|
exchangeRatesIds,
|
||||||
|
});
|
||||||
await this.validateExchangeRatesIdsExistance(tenantId, exchangeRatesIds);
|
await this.validateExchangeRatesIdsExistance(tenantId, exchangeRatesIds);
|
||||||
|
|
||||||
await ExchangeRate.query().whereIn('id', exchangeRatesIds).delete();
|
await ExchangeRate.query().whereIn('id', exchangeRatesIds).delete();
|
||||||
this.logger.info('[exchange_rates] deleted successfully.', { tenantId, exchangeRatesIds });
|
this.logger.info('[exchange_rates] deleted successfully.', {
|
||||||
|
tenantId,
|
||||||
|
exchangeRatesIds,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -114,16 +154,23 @@ export default class ExchangeRatesService implements IExchangeRatesService {
|
|||||||
* @param {IExchangeRateDTO} exchangeRateDTO - Exchange rate DTO.
|
* @param {IExchangeRateDTO} exchangeRateDTO - Exchange rate DTO.
|
||||||
* @return {Promise<void>}
|
* @return {Promise<void>}
|
||||||
*/
|
*/
|
||||||
private async validateExchangeRatePeriodExistance(tenantId: number, exchangeRateDTO: IExchangeRateDTO): Promise<void> {
|
private async validateExchangeRatePeriodExistance(
|
||||||
|
tenantId: number,
|
||||||
|
exchangeRateDTO: IExchangeRateDTO
|
||||||
|
): Promise<void> {
|
||||||
const { ExchangeRate } = this.tenancy.models(tenantId);
|
const { ExchangeRate } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
this.logger.info('[exchange_rates] trying to validate period existance.', { tenantId });
|
this.logger.info('[exchange_rates] trying to validate period existance.', {
|
||||||
|
tenantId,
|
||||||
|
});
|
||||||
const foundExchangeRate = await ExchangeRate.query()
|
const foundExchangeRate = await ExchangeRate.query()
|
||||||
.where('currency_code', exchangeRateDTO.currencyCode)
|
.where('currency_code', exchangeRateDTO.currencyCode)
|
||||||
.where('date', exchangeRateDTO.date);
|
.where('date', exchangeRateDTO.date);
|
||||||
|
|
||||||
if (foundExchangeRate.length > 0) {
|
if (foundExchangeRate.length > 0) {
|
||||||
this.logger.info('[exchange_rates] given exchange rate period exists.', { tenantId });
|
this.logger.info('[exchange_rates] given exchange rate period exists.', {
|
||||||
|
tenantId,
|
||||||
|
});
|
||||||
throw new ServiceError(ERRORS.EXCHANGE_RATE_PERIOD_EXISTS);
|
throw new ServiceError(ERRORS.EXCHANGE_RATE_PERIOD_EXISTS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,14 +181,25 @@ export default class ExchangeRatesService implements IExchangeRatesService {
|
|||||||
* @param {number} exchangeRateId - Exchange rate id.
|
* @param {number} exchangeRateId - Exchange rate id.
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
private async validateExchangeRateExistance(tenantId: number, exchangeRateId: number) {
|
private async validateExchangeRateExistance(
|
||||||
|
tenantId: number,
|
||||||
|
exchangeRateId: number
|
||||||
|
) {
|
||||||
const { ExchangeRate } = this.tenancy.models(tenantId);
|
const { ExchangeRate } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
this.logger.info('[exchange_rates] trying to validate exchange rate id existance.', { tenantId, exchangeRateId });
|
this.logger.info(
|
||||||
const foundExchangeRate = await ExchangeRate.query().findById(exchangeRateId);
|
'[exchange_rates] trying to validate exchange rate id existance.',
|
||||||
|
{ tenantId, exchangeRateId }
|
||||||
|
);
|
||||||
|
const foundExchangeRate = await ExchangeRate.query().findById(
|
||||||
|
exchangeRateId
|
||||||
|
);
|
||||||
|
|
||||||
if (!foundExchangeRate) {
|
if (!foundExchangeRate) {
|
||||||
this.logger.info('[exchange_rates] exchange rate not found.', { tenantId, exchangeRateId });
|
this.logger.info('[exchange_rates] exchange rate not found.', {
|
||||||
|
tenantId,
|
||||||
|
exchangeRateId,
|
||||||
|
});
|
||||||
throw new ServiceError(ERRORS.EXCHANGE_RATE_NOT_FOUND);
|
throw new ServiceError(ERRORS.EXCHANGE_RATE_NOT_FOUND);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,15 +210,26 @@ export default class ExchangeRatesService implements IExchangeRatesService {
|
|||||||
* @param {number[]} exchangeRatesIds - Exchange rates ids.
|
* @param {number[]} exchangeRatesIds - Exchange rates ids.
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
private async validateExchangeRatesIdsExistance(tenantId: number, exchangeRatesIds: number[]): Promise<void> {
|
private async validateExchangeRatesIdsExistance(
|
||||||
|
tenantId: number,
|
||||||
|
exchangeRatesIds: number[]
|
||||||
|
): Promise<void> {
|
||||||
const { ExchangeRate } = this.tenancy.models(tenantId);
|
const { ExchangeRate } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const storedExchangeRates = await ExchangeRate.query().whereIn('id', exchangeRatesIds);
|
const storedExchangeRates = await ExchangeRate.query().whereIn(
|
||||||
const storedExchangeRatesIds = storedExchangeRates.map((category) => category.id);
|
'id',
|
||||||
const notFoundExRates = difference(exchangeRatesIds, storedExchangeRatesIds);
|
exchangeRatesIds
|
||||||
|
);
|
||||||
|
const storedExchangeRatesIds = storedExchangeRates.map(
|
||||||
|
(category) => category.id
|
||||||
|
);
|
||||||
|
const notFoundExRates = difference(
|
||||||
|
exchangeRatesIds,
|
||||||
|
storedExchangeRatesIds
|
||||||
|
);
|
||||||
|
|
||||||
if (notFoundExRates.length > 0) {
|
if (notFoundExRates.length > 0) {
|
||||||
throw new ServiceError(ERRORS.NOT_FOUND_EXCHANGE_RATES);
|
throw new ServiceError(ERRORS.NOT_FOUND_EXCHANGE_RATES);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export default class InventoryService {
|
|||||||
direction: TInventoryTransactionDirection,
|
direction: TInventoryTransactionDirection,
|
||||||
date: Date | string,
|
date: Date | string,
|
||||||
lotNumber: number
|
lotNumber: number
|
||||||
) {
|
): IInventoryTransaction[] {
|
||||||
return itemEntries.map((entry: IItemEntry) => ({
|
return itemEntries.map((entry: IItemEntry) => ({
|
||||||
...pick(entry, ['itemId', 'quantity', 'rate']),
|
...pick(entry, ['itemId', 'quantity', 'rate']),
|
||||||
lotNumber,
|
lotNumber,
|
||||||
@@ -150,19 +150,36 @@ export default class InventoryService {
|
|||||||
*/
|
*/
|
||||||
async recordInventoryTransactions(
|
async recordInventoryTransactions(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
inventoryEntries: IInventoryTransaction[],
|
transactions: IInventoryTransaction[],
|
||||||
deleteOld: boolean
|
override: boolean = false
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
inventoryEntries.forEach(async (entry: IInventoryTransaction) => {
|
const bulkInsertOpers = [];
|
||||||
await this.recordInventoryTransaction(tenantId, entry, deleteOld);
|
|
||||||
|
transactions.forEach((transaction: IInventoryTransaction) => {
|
||||||
|
const oper = this.recordInventoryTransaction(
|
||||||
|
tenantId,
|
||||||
|
transaction,
|
||||||
|
override
|
||||||
|
);
|
||||||
|
bulkInsertOpers.push(oper);
|
||||||
});
|
});
|
||||||
|
const inventoryTransactions = await Promise.all(bulkInsertOpers);
|
||||||
|
|
||||||
|
// Triggers `onInventoryTransactionsCreated` event.
|
||||||
|
this.eventDispatcher.dispatch(
|
||||||
|
events.inventory.onInventoryTransactionsCreated,
|
||||||
|
{
|
||||||
|
tenantId,
|
||||||
|
inventoryTransactions,
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the inventory transactiosn on the storage from the given
|
* Writes the inventory transactiosn on the storage from the given
|
||||||
* inventory transactions entries.
|
* inventory transactions entries.
|
||||||
*
|
*
|
||||||
* @param {number} tenantId -
|
* @param {number} tenantId -
|
||||||
* @param {IInventoryTransaction} inventoryEntry -
|
* @param {IInventoryTransaction} inventoryEntry -
|
||||||
* @param {boolean} deleteOld -
|
* @param {boolean} deleteOld -
|
||||||
*/
|
*/
|
||||||
@@ -203,7 +220,7 @@ export default class InventoryService {
|
|||||||
transactionDirection: TInventoryTransactionDirection,
|
transactionDirection: TInventoryTransactionDirection,
|
||||||
override: boolean = false
|
override: boolean = false
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// Gets the next inventory lot number.
|
// Retrieve the next inventory lot number.
|
||||||
const lotNumber = this.getNextLotNumber(tenantId);
|
const lotNumber = this.getNextLotNumber(tenantId);
|
||||||
|
|
||||||
// Loads the inventory items entries of the given sale invoice.
|
// Loads the inventory items entries of the given sale invoice.
|
||||||
@@ -231,20 +248,6 @@ export default class InventoryService {
|
|||||||
);
|
);
|
||||||
// Increment and save the next lot number settings.
|
// Increment and save the next lot number settings.
|
||||||
await this.incrementNextLotNumber(tenantId);
|
await this.incrementNextLotNumber(tenantId);
|
||||||
|
|
||||||
// Triggers `onInventoryTransactionsCreated` event.
|
|
||||||
this.eventDispatcher.dispatch(
|
|
||||||
events.inventory.onInventoryTransactionsCreated,
|
|
||||||
{
|
|
||||||
tenantId,
|
|
||||||
inventoryEntries,
|
|
||||||
transactionId,
|
|
||||||
transactionType,
|
|
||||||
transactionDate,
|
|
||||||
transactionDirection,
|
|
||||||
override,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -253,7 +256,7 @@ export default class InventoryService {
|
|||||||
* @param {string} transactionType
|
* @param {string} transactionType
|
||||||
* @param {number} transactionId
|
* @param {number} transactionId
|
||||||
* @return {Promise<{
|
* @return {Promise<{
|
||||||
* oldInventoryTransactions: IInventoryTransaction[]
|
* oldInventoryTransactions: IInventoryTransaction[]
|
||||||
* }>}
|
* }>}
|
||||||
*/
|
*/
|
||||||
async deleteInventoryTransactions(
|
async deleteInventoryTransactions(
|
||||||
@@ -261,7 +264,9 @@ export default class InventoryService {
|
|||||||
transactionId: number,
|
transactionId: number,
|
||||||
transactionType: string
|
transactionType: string
|
||||||
): Promise<{ oldInventoryTransactions: IInventoryTransaction[] }> {
|
): Promise<{ oldInventoryTransactions: IInventoryTransaction[] }> {
|
||||||
const { inventoryTransactionRepository } = this.tenancy.repositories(tenantId);
|
const { inventoryTransactionRepository } = this.tenancy.repositories(
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
|
|
||||||
// Retrieve the inventory transactions of the given sale invoice.
|
// Retrieve the inventory transactions of the given sale invoice.
|
||||||
const oldInventoryTransactions = await inventoryTransactionRepository.find({
|
const oldInventoryTransactions = await inventoryTransactionRepository.find({
|
||||||
|
|||||||
@@ -11,11 +11,13 @@ import {
|
|||||||
IPaginationMeta,
|
IPaginationMeta,
|
||||||
IInventoryAdjustmentsFilter,
|
IInventoryAdjustmentsFilter,
|
||||||
ISystemUser,
|
ISystemUser,
|
||||||
|
IInventoryTransaction,
|
||||||
} from 'interfaces';
|
} from 'interfaces';
|
||||||
import events from 'subscribers/events';
|
import events from 'subscribers/events';
|
||||||
import AccountsService from 'services/Accounts/AccountsService';
|
import AccountsService from 'services/Accounts/AccountsService';
|
||||||
import ItemsService from 'services/Items/ItemsService';
|
import ItemsService from 'services/Items/ItemsService';
|
||||||
import HasTenancyService from 'services/Tenancy/TenancyService';
|
import HasTenancyService from 'services/Tenancy/TenancyService';
|
||||||
|
import InventoryService from './Inventory';
|
||||||
|
|
||||||
const ERRORS = {
|
const ERRORS = {
|
||||||
INVENTORY_ADJUSTMENT_NOT_FOUND: 'INVENTORY_ADJUSTMENT_NOT_FOUND',
|
INVENTORY_ADJUSTMENT_NOT_FOUND: 'INVENTORY_ADJUSTMENT_NOT_FOUND',
|
||||||
@@ -39,6 +41,9 @@ export default class InventoryAdjustmentService {
|
|||||||
@EventDispatcher()
|
@EventDispatcher()
|
||||||
eventDispatcher: EventDispatcherInterface;
|
eventDispatcher: EventDispatcherInterface;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
inventoryService: InventoryService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transformes the quick inventory adjustment DTO to model object.
|
* Transformes the quick inventory adjustment DTO to model object.
|
||||||
* @param {IQuickInventoryAdjustmentDTO} adjustmentDTO -
|
* @param {IQuickInventoryAdjustmentDTO} adjustmentDTO -
|
||||||
@@ -55,14 +60,15 @@ export default class InventoryAdjustmentService {
|
|||||||
{
|
{
|
||||||
index: 1,
|
index: 1,
|
||||||
itemId: adjustmentDTO.itemId,
|
itemId: adjustmentDTO.itemId,
|
||||||
...(['increment', 'decrement'].indexOf(adjustmentDTO.type) !== -1
|
|
||||||
? {
|
|
||||||
quantity: adjustmentDTO.quantity,
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
...('increment' === adjustmentDTO.type
|
...('increment' === adjustmentDTO.type
|
||||||
? {
|
? {
|
||||||
cost: adjustmentDTO.cost,
|
quantity: adjustmentDTO.quantity,
|
||||||
|
cost: adjustmentDTO.cost,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
...('decrement' === adjustmentDTO.type
|
||||||
|
? {
|
||||||
|
quantity: adjustmentDTO.quantity,
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
},
|
},
|
||||||
@@ -138,6 +144,7 @@ export default class InventoryAdjustmentService {
|
|||||||
quickAdjustmentDTO,
|
quickAdjustmentDTO,
|
||||||
authorizedUser
|
authorizedUser
|
||||||
);
|
);
|
||||||
|
// Saves the inventory adjustment with assocaited entries to the storage.
|
||||||
const inventoryAdjustment = await InventoryAdjustment.query().upsertGraph({
|
const inventoryAdjustment = await InventoryAdjustment.query().upsertGraph({
|
||||||
...invAdjustmentObject,
|
...invAdjustmentObject,
|
||||||
});
|
});
|
||||||
@@ -146,6 +153,8 @@ export default class InventoryAdjustmentService {
|
|||||||
events.inventoryAdjustment.onQuickCreated,
|
events.inventoryAdjustment.onQuickCreated,
|
||||||
{
|
{
|
||||||
tenantId,
|
tenantId,
|
||||||
|
inventoryAdjustment,
|
||||||
|
inventoryAdjustmentId: inventoryAdjustment.id,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
@@ -192,6 +201,7 @@ export default class InventoryAdjustmentService {
|
|||||||
// Triggers `onInventoryAdjustmentDeleted` event.
|
// Triggers `onInventoryAdjustmentDeleted` event.
|
||||||
await this.eventDispatcher.dispatch(events.inventoryAdjustment.onDeleted, {
|
await this.eventDispatcher.dispatch(events.inventoryAdjustment.onDeleted, {
|
||||||
tenantId,
|
tenantId,
|
||||||
|
inventoryAdjustmentId,
|
||||||
});
|
});
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
'[inventory_adjustment] the adjustment deleted successfully.',
|
'[inventory_adjustment] the adjustment deleted successfully.',
|
||||||
@@ -226,4 +236,61 @@ export default class InventoryAdjustmentService {
|
|||||||
pagination,
|
pagination,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the inventory transactions from the inventory adjustment transaction.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {IInventoryAdjustment} inventoryAdjustment
|
||||||
|
* @return {Promise<void>}
|
||||||
|
*/
|
||||||
|
async writeInventoryTransactions(
|
||||||
|
tenantId: number,
|
||||||
|
inventoryAdjustment: IInventoryAdjustment,
|
||||||
|
override: boolean = false,
|
||||||
|
): Promise<void> {
|
||||||
|
// Gets the next inventory lot number.
|
||||||
|
const lotNumber = this.inventoryService.getNextLotNumber(tenantId);
|
||||||
|
|
||||||
|
const commonTransaction = {
|
||||||
|
direction: inventoryAdjustment.inventoryDirection,
|
||||||
|
date: inventoryAdjustment.date,
|
||||||
|
transactionType: 'InventoryAdjustment',
|
||||||
|
transactionId: inventoryAdjustment.id,
|
||||||
|
};
|
||||||
|
const inventoryTransactions = [];
|
||||||
|
|
||||||
|
inventoryAdjustment.entries.forEach((entry) => {
|
||||||
|
inventoryTransactions.push({
|
||||||
|
...commonTransaction,
|
||||||
|
itemId: entry.itemId,
|
||||||
|
quantity: entry.quantity,
|
||||||
|
rate: entry.cost,
|
||||||
|
lotNumber,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// Saves the given inventory transactions to the storage.
|
||||||
|
this.inventoryService.recordInventoryTransactions(
|
||||||
|
tenantId,
|
||||||
|
inventoryTransactions,
|
||||||
|
override
|
||||||
|
);
|
||||||
|
// Increment and save the next lot number settings.
|
||||||
|
await this.inventoryService.incrementNextLotNumber(tenantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverts the inventory transactions from the inventory adjustment transaction.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} inventoryAdjustmentId
|
||||||
|
*/
|
||||||
|
async revertInventoryTransactions(
|
||||||
|
tenantId: number,
|
||||||
|
inventoryAdjustmentId: number
|
||||||
|
): Promise<{ oldInventoryTransactions: IInventoryTransaction[] }> {
|
||||||
|
return this.inventoryService.deleteInventoryTransactions(
|
||||||
|
tenantId,
|
||||||
|
inventoryAdjustmentId,
|
||||||
|
'InventoryAdjustment'
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
|||||||
import TenancyService from 'services/Tenancy/TenancyService';
|
import TenancyService from 'services/Tenancy/TenancyService';
|
||||||
import { ServiceError } from 'exceptions';
|
import { ServiceError } from 'exceptions';
|
||||||
import InventoryService from 'services/Inventory/Inventory';
|
import InventoryService from 'services/Inventory/Inventory';
|
||||||
|
import InventoryAdjustmentEntry from 'models/InventoryAdjustmentEntry';
|
||||||
|
|
||||||
const ERRORS = {
|
const ERRORS = {
|
||||||
NOT_FOUND: 'NOT_FOUND',
|
NOT_FOUND: 'NOT_FOUND',
|
||||||
@@ -27,6 +28,8 @@ const ERRORS = {
|
|||||||
|
|
||||||
ITEMS_HAVE_ASSOCIATED_TRANSACTIONS: 'ITEMS_HAVE_ASSOCIATED_TRANSACTIONS',
|
ITEMS_HAVE_ASSOCIATED_TRANSACTIONS: 'ITEMS_HAVE_ASSOCIATED_TRANSACTIONS',
|
||||||
ITEM_HAS_ASSOCIATED_TRANSACTINS: 'ITEM_HAS_ASSOCIATED_TRANSACTINS',
|
ITEM_HAS_ASSOCIATED_TRANSACTINS: 'ITEM_HAS_ASSOCIATED_TRANSACTINS',
|
||||||
|
|
||||||
|
ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT: 'ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT',
|
||||||
};
|
};
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
@@ -52,7 +55,7 @@ export default class ItemsService implements IItemsService {
|
|||||||
* @param {number} itemId
|
* @param {number} itemId
|
||||||
* @return {Promise<void>}
|
* @return {Promise<void>}
|
||||||
*/
|
*/
|
||||||
private async getItemOrThrowError(
|
public async getItemOrThrowError(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
itemId: number
|
itemId: number
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
@@ -235,16 +238,10 @@ export default class ItemsService implements IItemsService {
|
|||||||
* @return {IItem}
|
* @return {IItem}
|
||||||
*/
|
*/
|
||||||
private transformNewItemDTOToModel(itemDTO: IItemDTO) {
|
private transformNewItemDTOToModel(itemDTO: IItemDTO) {
|
||||||
const inventoryAttrs = ['openingQuantity', 'openingCost', 'openingDate'];
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...omit(itemDTO, inventoryAttrs),
|
...itemDTO,
|
||||||
...(itemDTO.type === 'inventory' ? pick(itemDTO, inventoryAttrs) : {}),
|
|
||||||
active: defaultTo(itemDTO.active, 1),
|
active: defaultTo(itemDTO.active, 1),
|
||||||
quantityOnHand:
|
quantityOnHand: itemDTO.type === 'inventory' ? 0 : null,
|
||||||
itemDTO.type === 'inventory'
|
|
||||||
? defaultTo(itemDTO.openingQuantity, 0)
|
|
||||||
: null,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,7 +279,7 @@ export default class ItemsService implements IItemsService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
const item = await Item.query().insertAndFetch({
|
const item = await Item.query().insertAndFetch({
|
||||||
...this.transformNewItemDTOToModel(itemDTO)
|
...this.transformNewItemDTOToModel(itemDTO),
|
||||||
});
|
});
|
||||||
this.logger.info('[items] item inserted successfully.', {
|
this.logger.info('[items] item inserted successfully.', {
|
||||||
tenantId,
|
tenantId,
|
||||||
@@ -297,46 +294,6 @@ export default class ItemsService implements IItemsService {
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Records the opening items inventory transaction.
|
|
||||||
* @param {number} tenantId -
|
|
||||||
* @param itemId -
|
|
||||||
* @param openingQuantity -
|
|
||||||
* @param openingCost -
|
|
||||||
* @param openingDate -
|
|
||||||
*/
|
|
||||||
public async recordOpeningItemsInventoryTransaction(
|
|
||||||
tenantId: number,
|
|
||||||
itemId: number,
|
|
||||||
openingQuantity: number,
|
|
||||||
openingCost: number,
|
|
||||||
openingDate: Date
|
|
||||||
): Promise<void> {
|
|
||||||
// Gets the next inventory lot number.
|
|
||||||
const lotNumber = this.inventoryService.getNextLotNumber(tenantId);
|
|
||||||
|
|
||||||
// Records the inventory transaction.
|
|
||||||
const inventoryTransaction = await this.inventoryService.recordInventoryTransaction(
|
|
||||||
tenantId,
|
|
||||||
{
|
|
||||||
date: openingDate,
|
|
||||||
quantity: openingQuantity,
|
|
||||||
rate: openingCost,
|
|
||||||
direction: 'IN',
|
|
||||||
transactionType: 'OpeningItem',
|
|
||||||
itemId,
|
|
||||||
lotNumber,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
// Records the inventory cost lot transaction.
|
|
||||||
await this.inventoryService.recordInventoryCostLotTransaction(tenantId, {
|
|
||||||
...omit(inventoryTransaction, ['updatedAt', 'createdAt']),
|
|
||||||
cost: openingQuantity * openingCost,
|
|
||||||
remaining: 0,
|
|
||||||
});
|
|
||||||
await this.inventoryService.incrementNextLotNumber(tenantId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edits the item metadata.
|
* Edits the item metadata.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -398,16 +355,19 @@ export default class ItemsService implements IItemsService {
|
|||||||
*/
|
*/
|
||||||
public async deleteItem(tenantId: number, itemId: number) {
|
public async deleteItem(tenantId: number, itemId: number) {
|
||||||
const { Item } = this.tenancy.models(tenantId);
|
const { Item } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
this.logger.info('[items] trying to delete item.', { tenantId, itemId });
|
this.logger.info('[items] trying to delete item.', { tenantId, itemId });
|
||||||
|
|
||||||
// Retreive the given item or throw not found service error.
|
// Retreive the given item or throw not found service error.
|
||||||
await this.getItemOrThrowError(tenantId, itemId);
|
await this.getItemOrThrowError(tenantId, itemId);
|
||||||
|
|
||||||
|
// Validate the item has no associated inventory transactions.
|
||||||
|
await this.validateHasNoInventoryAdjustments(tenantId, itemId);
|
||||||
|
|
||||||
// Validate the item has no associated invoices or bills.
|
// Validate the item has no associated invoices or bills.
|
||||||
await this.validateHasNoInvoicesOrBills(tenantId, itemId);
|
await this.validateHasNoInvoicesOrBills(tenantId, itemId);
|
||||||
|
|
||||||
await Item.query().findById(itemId).delete();
|
await Item.query().findById(itemId).delete();
|
||||||
|
|
||||||
this.logger.info('[items] deleted successfully.', { tenantId, itemId });
|
this.logger.info('[items] deleted successfully.', { tenantId, itemId });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,6 +478,9 @@ export default class ItemsService implements IItemsService {
|
|||||||
// Validates the given items exist on the storage.
|
// Validates the given items exist on the storage.
|
||||||
await this.validateItemsIdsExists(tenantId, itemsIds);
|
await this.validateItemsIdsExists(tenantId, itemsIds);
|
||||||
|
|
||||||
|
// Validate the item has no associated inventory transactions.
|
||||||
|
await this.validateHasNoInventoryAdjustments(tenantId, itemsIds);
|
||||||
|
|
||||||
// Validate the items have no associated invoices or bills.
|
// Validate the items have no associated invoices or bills.
|
||||||
await this.validateHasNoInvoicesOrBills(tenantId, itemsIds);
|
await this.validateHasNoInvoicesOrBills(tenantId, itemsIds);
|
||||||
|
|
||||||
@@ -541,7 +504,6 @@ export default class ItemsService implements IItemsService {
|
|||||||
Item,
|
Item,
|
||||||
itemsFilter
|
itemsFilter
|
||||||
);
|
);
|
||||||
|
|
||||||
const { results, pagination } = await Item.query()
|
const { results, pagination } = await Item.query()
|
||||||
.onBuild((builder) => {
|
.onBuild((builder) => {
|
||||||
builder.withGraphFetched('inventoryAccount');
|
builder.withGraphFetched('inventoryAccount');
|
||||||
@@ -584,4 +546,24 @@ export default class ItemsService implements IItemsService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the given item has no associated inventory adjustment transactions.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @param {number} itemId -
|
||||||
|
*/
|
||||||
|
private async validateHasNoInventoryAdjustments(
|
||||||
|
tenantId: number,
|
||||||
|
itemId: number[] | number,
|
||||||
|
): Promise<void> {
|
||||||
|
const { InventoryAdjustmentEntry } = this.tenancy.models(tenantId);
|
||||||
|
const itemsIds = Array.isArray(itemId) ? itemId : [itemId];
|
||||||
|
|
||||||
|
const inventoryAdjEntries = await InventoryAdjustmentEntry.query()
|
||||||
|
.whereIn('item_id', itemsIds);
|
||||||
|
|
||||||
|
if (inventoryAdjEntries.length > 0) {
|
||||||
|
throw new ServiceError(ERRORS.ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { Container, Service, Inject } from 'typedi';
|
import { Container, Service, Inject } from 'typedi';
|
||||||
|
import { chain } from 'lodash';
|
||||||
|
import moment from 'moment';
|
||||||
import JournalPoster from 'services/Accounting/JournalPoster';
|
import JournalPoster from 'services/Accounting/JournalPoster';
|
||||||
import InventoryService from 'services/Inventory/Inventory';
|
import InventoryService from 'services/Inventory/Inventory';
|
||||||
import TenancyService from 'services/Tenancy/TenancyService';
|
import TenancyService from 'services/Tenancy/TenancyService';
|
||||||
import { IInventoryLotCost, IItem } from 'interfaces';
|
import { IInventoryLotCost, IInventoryTransaction, IItem } from 'interfaces';
|
||||||
import JournalCommands from 'services/Accounting/JournalCommands';
|
import JournalCommands from 'services/Accounting/JournalCommands';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
@@ -24,7 +26,7 @@ export default class SaleInvoicesCost {
|
|||||||
tenantId: number,
|
tenantId: number,
|
||||||
inventoryItemsIds: number[],
|
inventoryItemsIds: number[],
|
||||||
startingDate: Date
|
startingDate: Date
|
||||||
) {
|
): Promise<void> {
|
||||||
const asyncOpers: Promise<[]>[] = [];
|
const asyncOpers: Promise<[]>[] = [];
|
||||||
|
|
||||||
inventoryItemsIds.forEach((inventoryItemId: number) => {
|
inventoryItemsIds.forEach((inventoryItemId: number) => {
|
||||||
@@ -35,7 +37,61 @@ export default class SaleInvoicesCost {
|
|||||||
);
|
);
|
||||||
asyncOpers.push(oper);
|
asyncOpers.push(oper);
|
||||||
});
|
});
|
||||||
return Promise.all([...asyncOpers]);
|
await Promise.all([...asyncOpers]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the max dated inventory transactions in the transactions that
|
||||||
|
* have the same item id.
|
||||||
|
* @param {IInventoryTransaction[]} inventoryTransactions
|
||||||
|
* @return {IInventoryTransaction[]}
|
||||||
|
*/
|
||||||
|
getMaxDateInventoryTransactions(
|
||||||
|
inventoryTransactions: IInventoryTransaction[]
|
||||||
|
): IInventoryTransaction[] {
|
||||||
|
return chain(inventoryTransactions)
|
||||||
|
.reduce((acc: any, transaction) => {
|
||||||
|
const compatatorDate = acc[transaction.itemId];
|
||||||
|
|
||||||
|
if (
|
||||||
|
!compatatorDate ||
|
||||||
|
moment(compatatorDate.date).isBefore(transaction.date)
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[transaction.itemId]: {
|
||||||
|
...transaction,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
.values()
|
||||||
|
.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes items costs by the given inventory transaction.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {IInventoryTransaction[]} inventoryTransactions
|
||||||
|
*/
|
||||||
|
async computeItemsCostByInventoryTransactions(
|
||||||
|
tenantId: number,
|
||||||
|
inventoryTransactions: IInventoryTransaction[]
|
||||||
|
) {
|
||||||
|
const asyncOpers: Promise<[]>[] = [];
|
||||||
|
const reducedTransactions = this.getMaxDateInventoryTransactions(
|
||||||
|
inventoryTransactions
|
||||||
|
);
|
||||||
|
reducedTransactions.forEach((transaction) => {
|
||||||
|
const oper: Promise<[]> = this.inventoryService.scheduleComputeItemCost(
|
||||||
|
tenantId,
|
||||||
|
transaction.itemId,
|
||||||
|
transaction.date
|
||||||
|
);
|
||||||
|
asyncOpers.push(oper);
|
||||||
|
});
|
||||||
|
await Promise.all([...asyncOpers]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -90,7 +146,7 @@ export default class SaleInvoicesCost {
|
|||||||
return Promise.all([
|
return Promise.all([
|
||||||
journal.deleteEntries(),
|
journal.deleteEntries(),
|
||||||
journal.saveEntries(),
|
journal.saveEntries(),
|
||||||
journal.saveBalance()
|
journal.saveBalance(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,19 +43,13 @@ export class InventorySubscriber {
|
|||||||
@On(events.inventory.onInventoryTransactionsCreated)
|
@On(events.inventory.onInventoryTransactionsCreated)
|
||||||
async handleScheduleItemsCostOnInventoryTransactionsCreated({
|
async handleScheduleItemsCostOnInventoryTransactionsCreated({
|
||||||
tenantId,
|
tenantId,
|
||||||
inventoryEntries,
|
inventoryTransactions
|
||||||
transactionId,
|
|
||||||
transactionType,
|
|
||||||
transactionDate,
|
|
||||||
transactionDirection,
|
|
||||||
override
|
|
||||||
}) {
|
}) {
|
||||||
const inventoryItemsIds = map(inventoryEntries, 'itemId');
|
const inventoryItemsIds = map(inventoryTransactions, 'itemId');
|
||||||
|
|
||||||
await this.saleInvoicesCost.scheduleComputeCostByItemsIds(
|
await this.saleInvoicesCost.computeItemsCostByInventoryTransactions(
|
||||||
tenantId,
|
tenantId,
|
||||||
inventoryItemsIds,
|
inventoryTransactions
|
||||||
transactionDate,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +63,12 @@ export class InventorySubscriber {
|
|||||||
transactionId,
|
transactionId,
|
||||||
oldInventoryTransactions
|
oldInventoryTransactions
|
||||||
}) {
|
}) {
|
||||||
|
// Ignore compute item cost with theses transaction types.
|
||||||
|
const ignoreWithTransactionTypes = ['OpeningItem'];
|
||||||
|
|
||||||
|
if (ignoreWithTransactionTypes.indexOf(transactionType) !== -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const inventoryItemsIds = map(oldInventoryTransactions, 'itemId');
|
const inventoryItemsIds = map(oldInventoryTransactions, 'itemId');
|
||||||
const startingDates = map(oldInventoryTransactions, 'date');
|
const startingDates = map(oldInventoryTransactions, 'date');
|
||||||
const startingDate = head(startingDates);
|
const startingDate = head(startingDates);
|
||||||
49
server/src/subscribers/Inventory/InventoryAdjustment.ts
Normal file
49
server/src/subscribers/Inventory/InventoryAdjustment.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { Container } from 'typedi';
|
||||||
|
import { On, EventSubscriber } from 'event-dispatch';
|
||||||
|
import events from 'subscribers/events';
|
||||||
|
import TenancyService from 'services/Tenancy/TenancyService';
|
||||||
|
import InventoryAdjustmentService from 'services/Inventory/InventoryAdjustmentService';
|
||||||
|
|
||||||
|
@EventSubscriber()
|
||||||
|
export default class InventoryAdjustmentsSubscriber {
|
||||||
|
logger: any;
|
||||||
|
tenancy: TenancyService;
|
||||||
|
inventoryAdjustment: InventoryAdjustmentService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor method.
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.logger = Container.get('logger');
|
||||||
|
this.tenancy = Container.get(TenancyService);
|
||||||
|
this.inventoryAdjustment = Container.get(InventoryAdjustmentService);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles writing inventory transactions once the quick adjustment created.
|
||||||
|
*/
|
||||||
|
@On(events.inventoryAdjustment.onQuickCreated)
|
||||||
|
async handleWriteInventoryTransactionsOnceQuickCreated({
|
||||||
|
tenantId,
|
||||||
|
inventoryAdjustment,
|
||||||
|
}) {
|
||||||
|
await this.inventoryAdjustment.writeInventoryTransactions(
|
||||||
|
tenantId,
|
||||||
|
inventoryAdjustment
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles reverting invetory transactions once the inventory adjustment deleted.
|
||||||
|
*/
|
||||||
|
@On(events.inventoryAdjustment.onDeleted)
|
||||||
|
async handleRevertInventoryTransactionsOnceDeleted({
|
||||||
|
tenantId,
|
||||||
|
inventoryAdjustmentId
|
||||||
|
}) {
|
||||||
|
await this.inventoryAdjustment.revertInventoryTransactions(
|
||||||
|
tenantId,
|
||||||
|
inventoryAdjustmentId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user