From 70aea9bf2dd0f0dfae8ec1f6bc057bef8515f9b3 Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Fri, 23 Jul 2021 01:32:10 +0200 Subject: [PATCH] WIP: Allocate landed cost. --- .../api/controllers/Purchases/LandedCost.ts | 16 ++-- ...200722173423_create_items_entries_table.js | 24 ++++-- server/src/interfaces/LandedCost.ts | 10 ++- server/src/models/BillLandedCost.js | 11 ++- .../services/Purchases/LandedCost/index.ts | 84 ++++++++++--------- 5 files changed, 91 insertions(+), 54 deletions(-) diff --git a/server/src/api/controllers/Purchases/LandedCost.ts b/server/src/api/controllers/Purchases/LandedCost.ts index da32f2b8b..b1f403c3c 100644 --- a/server/src/api/controllers/Purchases/LandedCost.ts +++ b/server/src/api/controllers/Purchases/LandedCost.ts @@ -5,7 +5,6 @@ import { ServiceError } from 'exceptions'; import AllocateLandedCostService from 'services/Purchases/LandedCost'; import LandedCostListing from 'services/Purchases/LandedCost/LandedCostListing'; import BaseController from '../BaseController'; -import { ResultSetDependencies } from 'mathjs'; @Service() export default class BillAllocateLandedCost extends BaseController { @@ -221,8 +220,8 @@ export default class BillAllocateLandedCost extends BaseController { errors: [ { type: 'BILL_NOT_FOUND', - code: 400, message: 'The give bill id not found.', + code: 100, }, ], }); @@ -232,8 +231,8 @@ export default class BillAllocateLandedCost extends BaseController { errors: [ { type: 'LANDED_COST_TRANSACTION_NOT_FOUND', - code: 200, message: 'The given landed cost transaction id not found.', + code: 200, }, ], }); @@ -243,8 +242,8 @@ export default class BillAllocateLandedCost extends BaseController { errors: [ { type: 'LANDED_COST_ENTRY_NOT_FOUND', - code: 300, message: 'The given landed cost tranasction entry id not found.', + code: 300, }, ], }); @@ -252,7 +251,10 @@ export default class BillAllocateLandedCost extends BaseController { if (error.errorType === 'COST_AMOUNT_BIGGER_THAN_UNALLOCATED_AMOUNT') { return res.status(400).send({ errors: [ - { type: 'COST_AMOUNT_BIGGER_THAN_UNALLOCATED_AMOUNT', code: 300 }, + { + type: 'COST_AMOUNT_BIGGER_THAN_UNALLOCATED_AMOUNT', + code: 400, + }, ], }); } @@ -261,8 +263,8 @@ export default class BillAllocateLandedCost extends BaseController { errors: [ { type: 'LANDED_COST_ITEMS_IDS_NOT_FOUND', - code: 200, message: 'The given entries ids of purchase invoice not found.', + code: 500, }, ], }); @@ -272,8 +274,8 @@ export default class BillAllocateLandedCost extends BaseController { errors: [ { type: 'BILL_LANDED_COST_NOT_FOUND', - code: 200, message: 'The given bill located landed cost not found.', + code: 600, }, ], }); diff --git a/server/src/database/migrations/20200722173423_create_items_entries_table.js b/server/src/database/migrations/20200722173423_create_items_entries_table.js index a4c809cec..616234ac9 100644 --- a/server/src/database/migrations/20200722173423_create_items_entries_table.js +++ b/server/src/database/migrations/20200722173423_create_items_entries_table.js @@ -1,19 +1,31 @@ - -exports.up = function(knex) { +exports.up = function (knex) { return knex.schema.createTable('items_entries', (table) => { table.increments(); table.string('reference_type').index(); table.string('reference_id').index(); table.integer('index').unsigned(); - table.integer('item_id').unsigned().index().references('id').inTable('items'); + table + .integer('item_id') + .unsigned() + .index() + .references('id') + .inTable('items'); table.text('description'); table.integer('discount').unsigned(); table.integer('quantity').unsigned(); table.integer('rate').unsigned(); - table.integer('sell_account_id').unsigned().references('id').inTable('accounts'); - table.integer('cost_account_id').unsigned().references('id').inTable('accounts'); + table + .integer('sell_account_id') + .unsigned() + .references('id') + .inTable('accounts'); + table + .integer('cost_account_id') + .unsigned() + .references('id') + .inTable('accounts'); table.boolean('landed_cost').defaultTo(false); table.decimal('allocated_cost_amount', 13, 3); @@ -21,6 +33,6 @@ exports.up = function(knex) { }); }; -exports.down = function(knex) { +exports.down = function (knex) { return knex.schema.dropTableIfExists('items_entries'); }; diff --git a/server/src/interfaces/LandedCost.ts b/server/src/interfaces/LandedCost.ts index a719b31c2..141e0f721 100644 --- a/server/src/interfaces/LandedCost.ts +++ b/server/src/interfaces/LandedCost.ts @@ -82,4 +82,12 @@ export interface IBillLandedCostTransaction { allocationMethod: string; costAccountId: number, description: string; -}; \ No newline at end of file + + allocatedEntries?: IBillLandedCostTransactionEntry[], +}; + +export interface IBillLandedCostTransactionEntry { + cost: number; + entryId: number; + billLocatedCostId: number, +} \ No newline at end of file diff --git a/server/src/models/BillLandedCost.js b/server/src/models/BillLandedCost.js index c7a0624fc..f517dbbaf 100644 --- a/server/src/models/BillLandedCost.js +++ b/server/src/models/BillLandedCost.js @@ -19,10 +19,19 @@ export default class BillLandedCost extends TenantModel { /** * Relationship mapping. */ - static get relationMappings() { + static get relationMappings() { const BillLandedCostEntry = require('models/BillLandedCostEntry'); + const Bill = require('models/Bill'); return { + bill: { + relation: Model.BelongsToOneRelation, + modelClass: Bill.default, + join: { + from: 'bill_located_costs.billId', + to: 'bills.id', + }, + }, allocateEntries: { relation: Model.HasManyRelation, modelClass: BillLandedCostEntry.default, diff --git a/server/src/services/Purchases/LandedCost/index.ts b/server/src/services/Purchases/LandedCost/index.ts index 604ddc3e9..9b4c1ffb7 100644 --- a/server/src/services/Purchases/LandedCost/index.ts +++ b/server/src/services/Purchases/LandedCost/index.ts @@ -8,11 +8,13 @@ import { IBillLandedCost, ILandedCostItemDTO, ILandedCostDTO, + IBillLandedCostTransaction, + IBillLandedCostTransactionEntry, } from 'interfaces'; import InventoryService from 'services/Inventory/Inventory'; import HasTenancyService from 'services/Tenancy/TenancyService'; import { ERRORS } from './constants'; -import { mergeObjectsBykey } from 'utils'; +import { transformToMap } from 'utils'; import JournalPoster from 'services/Accounting/JournalPoster'; import JournalEntry from 'services/Accounting/JournalEntry'; import TransactionLandedCost from './TransctionLandedCost'; @@ -141,7 +143,10 @@ export default class AllocateLandedCostService { transactionId: number, amount: number ) => { - const Model = this.transactionLandedCost.getModel(tenantId, transactionType); + const Model = this.transactionLandedCost.getModel( + tenantId, + transactionType + ); // Decrement the allocate cost amount of cost transaction. return Model.query() @@ -216,22 +221,6 @@ export default class AllocateLandedCostService { return sumBy(landedCostDTO.items, 'cost'); }; - /** - * Validate allocate cost transaction should not be bill transaction. - * @param {number} purchaseInvoiceId - * @param {string} transactionType - * @param {number} transactionId - */ - private validateAllocateCostNotSameBill = ( - purchaseInvoiceId: number, - transactionType: string, - transactionId: number - ): void => { - if (transactionType === 'Bill' && transactionId === purchaseInvoiceId) { - throw new ServiceError(ERRORS.ALLOCATE_COST_SHOULD_NOT_BE_BILL); - } - }; - /** * Validates the landed cost entry amount. * @param {number} unallocatedCost - @@ -248,6 +237,24 @@ export default class AllocateLandedCostService { } }; + /** + * Merges item entry to bill located landed cost entry. + * @param {IBillLandedCostTransactionEntry[]} locatedEntries - + * @param {IItemEntry[]} billEntries - + * @returns {(IBillLandedCostTransactionEntry & { entry: IItemEntry })[]} + */ + private mergeLocatedWithBillEntries = ( + locatedEntries: IBillLandedCostTransactionEntry[], + billEntries: IItemEntry[] + ): (IBillLandedCostTransactionEntry & { entry: IItemEntry })[] => { + const billEntriesByEntryId = transformToMap(billEntries, 'id'); + + return locatedEntries.map((entry) => ({ + ...entry, + entry: billEntriesByEntryId.get(entry.entryId), + })); + }; + /** * Records inventory transactions. * @param {number} tenantId @@ -255,25 +262,24 @@ export default class AllocateLandedCostService { */ private recordInventoryTransactions = async ( tenantId: number, - allocateEntries, - purchaseInvoice: IBill, - landedCostId: number + billLandedCost: IBillLandedCostTransaction, + bill: IBill ) => { - const costEntries = mergeObjectsBykey( - purchaseInvoice.entries, - allocateEntries.map((e) => ({ ...e, id: e.itemId })), - 'id' + // Retrieve the merged allocated entries with bill entries. + const allocateEntries = this.mergeLocatedWithBillEntries( + billLandedCost.allocateEntries, + bill.entries ); - // Inventory transaction. - const inventoryTransactions = costEntries.map((entry) => ({ - date: purchaseInvoice.billDate, - itemId: entry.itemId, + // Mappes the allocate cost entries to inventory transactions. + const inventoryTransactions = allocateEntries.map((allocateEntry) => ({ + date: bill.billDate, + itemId: allocateEntry.entry.itemId, direction: 'IN', quantity: 0, - rate: entry.cost, + rate: allocateEntry.cost, transactionType: 'LandedCost', - transactionId: landedCostId, - entryId: entry.id, + transactionId: billLandedCost.id, + entryId: allocateEntry.entryId, })); return this.inventoryService.recordInventoryTransactions( @@ -345,12 +351,11 @@ export default class AllocateLandedCostService { purchaseInvoiceId ); // Records the inventory transactions. - // await this.recordInventoryTransactions( - // tenantId, - // allocateCostDTO.items, - // purchaseInvoice, - // landedCostTransaction.id - // ); + await this.recordInventoryTransactions( + tenantId, + billLandedCost, + purchaseInvoice + ); // Increment landed cost amount on transaction and entry. await this.incrementLandedCostAmount( tenantId, @@ -360,7 +365,7 @@ export default class AllocateLandedCostService { amount ); // Write the landed cost journal entries. - // await this.writeJournalEntry(tenantId, purchaseInvoice, billLandedCost); + // await this.writeJournalEntry(tenantId, billLandedCost, purchaseInvoice); return { billLandedCost }; }; @@ -373,6 +378,7 @@ export default class AllocateLandedCostService { */ private writeJournalEntry = async ( tenantId: number, + landedCostEntry: any, purchaseInvoice: IBill, landedCost: IBillLandedCost ) => {