feat: ability to publish and draft inventory adjustment transactions.

This commit is contained in:
a.bouhuolia
2021-01-11 21:05:23 +02:00
parent bbd4ee5962
commit 463c748717
8 changed files with 142 additions and 21 deletions

View File

@@ -16,6 +16,13 @@ export default class InventoryAdjustmentsController extends BaseController {
router() { router() {
const router = Router(); const router = Router();
router.post(
'/:id/publish',
[param('id').exists().isNumeric().toInt()],
this.validationResult,
this.asyncMiddleware(this.publishInventoryAdjustment.bind(this)),
this.handleServiceErrors
);
router.delete( router.delete(
'/:id', '/:id',
[param('id').exists().isNumeric().toInt()], [param('id').exists().isNumeric().toInt()],
@@ -62,6 +69,7 @@ export default class InventoryAdjustmentsController extends BaseController {
.exists() .exists()
.isFloat() .isFloat()
.toInt(), .toInt(),
check('publish').default(false).isBoolean().toBoolean(),
]; ];
} }
@@ -124,6 +132,34 @@ export default class InventoryAdjustmentsController extends BaseController {
} }
} }
/**
* Publish the given inventory adjustment transaction.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
async publishInventoryAdjustment(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const { id: adjustmentId } = req.params;
try {
await this.inventoryAdjustmentService.publishInventoryAdjustment(
tenantId,
adjustmentId
);
return res.status(200).send({
id: adjustmentId,
message: 'The inventory adjustment has been published successfully.',
});
} catch (error) {
next(error);
}
}
/** /**
* Retrieve the inventory adjustments paginated list. * Retrieve the inventory adjustments paginated list.
* @param {Request} req * @param {Request} req

View File

@@ -9,6 +9,7 @@ exports.up = function(knex) {
table.string('reference_no').index(); table.string('reference_no').index();
table.string('description'); table.string('description');
table.integer('user_id').unsigned(); table.integer('user_id').unsigned();
table.date('published_at');
table.timestamps(); table.timestamps();
}); });
}; };

View File

@@ -1,9 +1,8 @@
type IAdjustmentTypes = 'increment' | 'decrement'; type IAdjustmentTypes = 'increment' | 'decrement';
export interface IQuickInventoryAdjustmentDTO { export interface IQuickInventoryAdjustmentDTO {
date: Date | string; date: Date | string;
type: IAdjustmentTypes, type: IAdjustmentTypes;
adjustmentAccountId: number; adjustmentAccountId: number;
reason: string; reason: string;
description: string; description: string;
@@ -11,31 +10,33 @@ export interface IQuickInventoryAdjustmentDTO {
itemId: number; itemId: number;
quantity: number; quantity: number;
cost: number; cost: number;
}; publish: boolean;
}
export interface IInventoryAdjustment { export interface IInventoryAdjustment {
id?: number, id?: number;
date: Date | string; date: Date | string;
adjustmentAccountId: number; adjustmentAccountId: number;
reason: string; reason: string;
description: string; description: string;
referenceNo: string; referenceNo: string;
inventoryDirection?: 'IN' | 'OUT', inventoryDirection?: 'IN' | 'OUT';
entries: IInventoryAdjustmentEntry[]; entries: IInventoryAdjustmentEntry[];
userId: number; userId: number;
}; publishedAt?: Date|null;
}
export interface IInventoryAdjustmentEntry { export interface IInventoryAdjustmentEntry {
id?: number, id?: number;
adjustmentId?: number, adjustmentId?: number;
index: number, index: number;
itemId: number; itemId: number;
quantity?: number; quantity?: number;
cost?: number; cost?: number;
value?: number; value?: number;
}; }
export interface IInventoryAdjustmentsFilter{ export interface IInventoryAdjustmentsFilter {
page: number, page: number;
pageSize: number, pageSize: number;
}; }

View File

@@ -20,7 +20,7 @@ export default class InventoryAdjustment extends TenantModel {
* Virtual attributes. * Virtual attributes.
*/ */
static get virtualAttributes() { static get virtualAttributes() {
return ['inventoryDirection']; return ['inventoryDirection', 'isPublished'];
} }
/** /**
@@ -30,6 +30,14 @@ export default class InventoryAdjustment extends TenantModel {
return InventoryAdjustment.getInventoryDirection(this.type); return InventoryAdjustment.getInventoryDirection(this.type);
} }
/**
* Detarmines whether the adjustment is published.
* @return {boolean}
*/
get isPublished() {
return !!this.publishedAt;
}
static getInventoryDirection(type) { static getInventoryDirection(type) {
const directions = { const directions = {
'increment': 'IN', 'increment': 'IN',

View File

@@ -1,5 +1,6 @@
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import { omit } from 'lodash'; import { omit } from 'lodash';
import moment from 'moment';
import { import {
EventDispatcher, EventDispatcher,
EventDispatcherInterface, EventDispatcherInterface,
@@ -54,16 +55,21 @@ export default class InventoryAdjustmentService {
authorizedUser: ISystemUser authorizedUser: ISystemUser
): IInventoryAdjustment { ): IInventoryAdjustment {
return { return {
...omit(adjustmentDTO, ['quantity', 'cost', 'itemId']), ...omit(adjustmentDTO, ['quantity', 'cost', 'itemId', 'publish']),
userId: authorizedUser.id, userId: authorizedUser.id,
...(adjustmentDTO.publish
? {
publishedAt: moment().toMySqlDateTime(),
}
: {}),
entries: [ entries: [
{ {
index: 1, index: 1,
itemId: adjustmentDTO.itemId, itemId: adjustmentDTO.itemId,
...('increment' === adjustmentDTO.type ...('increment' === adjustmentDTO.type
? { ? {
quantity: adjustmentDTO.quantity, quantity: adjustmentDTO.quantity,
cost: adjustmentDTO.cost, cost: adjustmentDTO.cost,
} }
: {}), : {}),
...('decrement' === adjustmentDTO.type ...('decrement' === adjustmentDTO.type
@@ -212,6 +218,50 @@ export default class InventoryAdjustmentService {
); );
} }
/**
* Publish the inventory adjustment transaction.
* @param tenantId
* @param inventoryAdjustmentId
*/
async publishInventoryAdjustment(
tenantId: number,
inventoryAdjustmentId: number
): Promise<void> {
const { InventoryAdjustment } = this.tenancy.models(tenantId);
// Retrieve the inventory adjustment or throw not found service error.
const oldInventoryAdjustment = await this.getInventoryAdjustmentOrThrowError(
tenantId,
inventoryAdjustmentId
);
this.logger.info('[inventory_adjustment] trying to publish adjustment.', {
tenantId,
inventoryAdjustmentId,
});
// Publish the inventory adjustment transaction.
await InventoryAdjustment.query()
.findById(inventoryAdjustmentId)
.patch({
publishedAt: moment().toMySqlDateTime(),
});
// Retrieve the inventory adjustment after the modification.
const inventoryAdjustment = await InventoryAdjustment.query()
.findById(inventoryAdjustmentId)
.withGraphFetched('entries');
// Triggers `onInventoryAdjustmentDeleted` event.
await this.eventDispatcher.dispatch(
events.inventoryAdjustment.onPublished,
{
tenantId,
inventoryAdjustmentId,
inventoryAdjustment,
oldInventoryAdjustment,
}
);
}
/** /**
* Retrieve the inventory adjustments paginated list. * Retrieve the inventory adjustments paginated list.
* @param {number} tenantId * @param {number} tenantId
@@ -246,7 +296,7 @@ export default class InventoryAdjustmentService {
async writeInventoryTransactions( async writeInventoryTransactions(
tenantId: number, tenantId: number,
inventoryAdjustment: IInventoryAdjustment, inventoryAdjustment: IInventoryAdjustment,
override: boolean = false, override: boolean = false
): Promise<void> { ): Promise<void> {
// Gets the next inventory lot number. // Gets the next inventory lot number.
const lotNumber = this.inventoryService.getNextLotNumber(tenantId); const lotNumber = this.inventoryService.getNextLotNumber(tenantId);

View File

@@ -30,7 +30,7 @@ export class InventorySubscriber {
if (dependsComputeJobs.length === 0) { if (dependsComputeJobs.length === 0) {
this.startingDate = null; this.startingDate = null;
await this.saleInvoicesCost.scheduleWriteJournalEntries( await this.saleInvoicesCost.scheduleWriteJournalEntries(
tenantId, tenantId,
startingDate startingDate
); );

View File

@@ -27,6 +27,9 @@ export default class InventoryAdjustmentsSubscriber {
tenantId, tenantId,
inventoryAdjustment, inventoryAdjustment,
}) { }) {
// Can't continue if the inventory adjustment is not published.
if (!inventoryAdjustment.isPublished) { return; }
await this.inventoryAdjustment.writeInventoryTransactions( await this.inventoryAdjustment.writeInventoryTransactions(
tenantId, tenantId,
inventoryAdjustment inventoryAdjustment
@@ -39,11 +42,29 @@ export default class InventoryAdjustmentsSubscriber {
@On(events.inventoryAdjustment.onDeleted) @On(events.inventoryAdjustment.onDeleted)
async handleRevertInventoryTransactionsOnceDeleted({ async handleRevertInventoryTransactionsOnceDeleted({
tenantId, tenantId,
inventoryAdjustmentId inventoryAdjustmentId,
oldInventoryTransaction,
}) { }) {
// Can't continue if the inventory adjustment is not published.
if (!oldInventoryTransaction.isPublished) { return; }
await this.inventoryAdjustment.revertInventoryTransactions( await this.inventoryAdjustment.revertInventoryTransactions(
tenantId, tenantId,
inventoryAdjustmentId, inventoryAdjustmentId,
); );
} }
/**
* Handles writing inventory transactions once the quick adjustment created.
*/
@On(events.inventoryAdjustment.onPublished)
async handleWriteInventoryTransactionsOncePublished({
tenantId,
inventoryAdjustment,
}) {
await this.inventoryAdjustment.writeInventoryTransactions(
tenantId,
inventoryAdjustment
)
}
} }

View File

@@ -193,9 +193,13 @@ export default {
onComputeItemCostJobCompleted: 'onComputeItemCostJobCompleted' onComputeItemCostJobCompleted: 'onComputeItemCostJobCompleted'
}, },
/**
* Inventory adjustment service.
*/
inventoryAdjustment: { inventoryAdjustment: {
onCreated: 'onInventoryAdjustmentCreated', onCreated: 'onInventoryAdjustmentCreated',
onQuickCreated: 'onInventoryAdjustmentQuickCreated', onQuickCreated: 'onInventoryAdjustmentQuickCreated',
onDeleted: 'onInventoryAdjustmentDeleted', onDeleted: 'onInventoryAdjustmentDeleted',
onPublished: 'onInventoryAdjustmentPublished',
} }
} }