WIP: Allocate landed cost.

This commit is contained in:
a.bouhuolia
2021-07-23 01:32:10 +02:00
parent 76c6cb3699
commit 70aea9bf2d
5 changed files with 91 additions and 54 deletions

View File

@@ -5,7 +5,6 @@ import { ServiceError } from 'exceptions';
import AllocateLandedCostService from 'services/Purchases/LandedCost'; import AllocateLandedCostService from 'services/Purchases/LandedCost';
import LandedCostListing from 'services/Purchases/LandedCost/LandedCostListing'; import LandedCostListing from 'services/Purchases/LandedCost/LandedCostListing';
import BaseController from '../BaseController'; import BaseController from '../BaseController';
import { ResultSetDependencies } from 'mathjs';
@Service() @Service()
export default class BillAllocateLandedCost extends BaseController { export default class BillAllocateLandedCost extends BaseController {
@@ -221,8 +220,8 @@ export default class BillAllocateLandedCost extends BaseController {
errors: [ errors: [
{ {
type: 'BILL_NOT_FOUND', type: 'BILL_NOT_FOUND',
code: 400,
message: 'The give bill id not found.', message: 'The give bill id not found.',
code: 100,
}, },
], ],
}); });
@@ -232,8 +231,8 @@ export default class BillAllocateLandedCost extends BaseController {
errors: [ errors: [
{ {
type: 'LANDED_COST_TRANSACTION_NOT_FOUND', type: 'LANDED_COST_TRANSACTION_NOT_FOUND',
code: 200,
message: 'The given landed cost transaction id not found.', message: 'The given landed cost transaction id not found.',
code: 200,
}, },
], ],
}); });
@@ -243,8 +242,8 @@ export default class BillAllocateLandedCost extends BaseController {
errors: [ errors: [
{ {
type: 'LANDED_COST_ENTRY_NOT_FOUND', type: 'LANDED_COST_ENTRY_NOT_FOUND',
code: 300,
message: 'The given landed cost tranasction entry id not found.', 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') { if (error.errorType === 'COST_AMOUNT_BIGGER_THAN_UNALLOCATED_AMOUNT') {
return res.status(400).send({ return res.status(400).send({
errors: [ 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: [ errors: [
{ {
type: 'LANDED_COST_ITEMS_IDS_NOT_FOUND', type: 'LANDED_COST_ITEMS_IDS_NOT_FOUND',
code: 200,
message: 'The given entries ids of purchase invoice not found.', message: 'The given entries ids of purchase invoice not found.',
code: 500,
}, },
], ],
}); });
@@ -272,8 +274,8 @@ export default class BillAllocateLandedCost extends BaseController {
errors: [ errors: [
{ {
type: 'BILL_LANDED_COST_NOT_FOUND', type: 'BILL_LANDED_COST_NOT_FOUND',
code: 200,
message: 'The given bill located landed cost not found.', message: 'The given bill located landed cost not found.',
code: 600,
}, },
], ],
}); });

View File

@@ -1,19 +1,31 @@
exports.up = function (knex) {
exports.up = function(knex) {
return knex.schema.createTable('items_entries', (table) => { return knex.schema.createTable('items_entries', (table) => {
table.increments(); table.increments();
table.string('reference_type').index(); table.string('reference_type').index();
table.string('reference_id').index(); table.string('reference_id').index();
table.integer('index').unsigned(); 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.text('description');
table.integer('discount').unsigned(); table.integer('discount').unsigned();
table.integer('quantity').unsigned(); table.integer('quantity').unsigned();
table.integer('rate').unsigned(); table.integer('rate').unsigned();
table.integer('sell_account_id').unsigned().references('id').inTable('accounts'); table
table.integer('cost_account_id').unsigned().references('id').inTable('accounts'); .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.boolean('landed_cost').defaultTo(false);
table.decimal('allocated_cost_amount', 13, 3); 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'); return knex.schema.dropTableIfExists('items_entries');
}; };

View File

@@ -82,4 +82,12 @@ export interface IBillLandedCostTransaction {
allocationMethod: string; allocationMethod: string;
costAccountId: number, costAccountId: number,
description: string; description: string;
allocatedEntries?: IBillLandedCostTransactionEntry[],
}; };
export interface IBillLandedCostTransactionEntry {
cost: number;
entryId: number;
billLocatedCostId: number,
}

View File

@@ -19,10 +19,19 @@ export default class BillLandedCost extends TenantModel {
/** /**
* Relationship mapping. * Relationship mapping.
*/ */
static get relationMappings() { static get relationMappings() {
const BillLandedCostEntry = require('models/BillLandedCostEntry'); const BillLandedCostEntry = require('models/BillLandedCostEntry');
const Bill = require('models/Bill');
return { return {
bill: {
relation: Model.BelongsToOneRelation,
modelClass: Bill.default,
join: {
from: 'bill_located_costs.billId',
to: 'bills.id',
},
},
allocateEntries: { allocateEntries: {
relation: Model.HasManyRelation, relation: Model.HasManyRelation,
modelClass: BillLandedCostEntry.default, modelClass: BillLandedCostEntry.default,

View File

@@ -8,11 +8,13 @@ import {
IBillLandedCost, IBillLandedCost,
ILandedCostItemDTO, ILandedCostItemDTO,
ILandedCostDTO, ILandedCostDTO,
IBillLandedCostTransaction,
IBillLandedCostTransactionEntry,
} from 'interfaces'; } from 'interfaces';
import InventoryService from 'services/Inventory/Inventory'; import InventoryService from 'services/Inventory/Inventory';
import HasTenancyService from 'services/Tenancy/TenancyService'; import HasTenancyService from 'services/Tenancy/TenancyService';
import { ERRORS } from './constants'; import { ERRORS } from './constants';
import { mergeObjectsBykey } from 'utils'; import { transformToMap } from 'utils';
import JournalPoster from 'services/Accounting/JournalPoster'; import JournalPoster from 'services/Accounting/JournalPoster';
import JournalEntry from 'services/Accounting/JournalEntry'; import JournalEntry from 'services/Accounting/JournalEntry';
import TransactionLandedCost from './TransctionLandedCost'; import TransactionLandedCost from './TransctionLandedCost';
@@ -141,7 +143,10 @@ export default class AllocateLandedCostService {
transactionId: number, transactionId: number,
amount: 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. // Decrement the allocate cost amount of cost transaction.
return Model.query() return Model.query()
@@ -216,22 +221,6 @@ export default class AllocateLandedCostService {
return sumBy(landedCostDTO.items, 'cost'); 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. * Validates the landed cost entry amount.
* @param {number} unallocatedCost - * @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. * Records inventory transactions.
* @param {number} tenantId * @param {number} tenantId
@@ -255,25 +262,24 @@ export default class AllocateLandedCostService {
*/ */
private recordInventoryTransactions = async ( private recordInventoryTransactions = async (
tenantId: number, tenantId: number,
allocateEntries, billLandedCost: IBillLandedCostTransaction,
purchaseInvoice: IBill, bill: IBill
landedCostId: number
) => { ) => {
const costEntries = mergeObjectsBykey( // Retrieve the merged allocated entries with bill entries.
purchaseInvoice.entries, const allocateEntries = this.mergeLocatedWithBillEntries(
allocateEntries.map((e) => ({ ...e, id: e.itemId })), billLandedCost.allocateEntries,
'id' bill.entries
); );
// Inventory transaction. // Mappes the allocate cost entries to inventory transactions.
const inventoryTransactions = costEntries.map((entry) => ({ const inventoryTransactions = allocateEntries.map((allocateEntry) => ({
date: purchaseInvoice.billDate, date: bill.billDate,
itemId: entry.itemId, itemId: allocateEntry.entry.itemId,
direction: 'IN', direction: 'IN',
quantity: 0, quantity: 0,
rate: entry.cost, rate: allocateEntry.cost,
transactionType: 'LandedCost', transactionType: 'LandedCost',
transactionId: landedCostId, transactionId: billLandedCost.id,
entryId: entry.id, entryId: allocateEntry.entryId,
})); }));
return this.inventoryService.recordInventoryTransactions( return this.inventoryService.recordInventoryTransactions(
@@ -345,12 +351,11 @@ export default class AllocateLandedCostService {
purchaseInvoiceId purchaseInvoiceId
); );
// Records the inventory transactions. // Records the inventory transactions.
// await this.recordInventoryTransactions( await this.recordInventoryTransactions(
// tenantId, tenantId,
// allocateCostDTO.items, billLandedCost,
// purchaseInvoice, purchaseInvoice
// landedCostTransaction.id );
// );
// Increment landed cost amount on transaction and entry. // Increment landed cost amount on transaction and entry.
await this.incrementLandedCostAmount( await this.incrementLandedCostAmount(
tenantId, tenantId,
@@ -360,7 +365,7 @@ export default class AllocateLandedCostService {
amount amount
); );
// Write the landed cost journal entries. // Write the landed cost journal entries.
// await this.writeJournalEntry(tenantId, purchaseInvoice, billLandedCost); // await this.writeJournalEntry(tenantId, billLandedCost, purchaseInvoice);
return { billLandedCost }; return { billLandedCost };
}; };
@@ -373,6 +378,7 @@ export default class AllocateLandedCostService {
*/ */
private writeJournalEntry = async ( private writeJournalEntry = async (
tenantId: number, tenantId: number,
landedCostEntry: any,
purchaseInvoice: IBill, purchaseInvoice: IBill,
landedCost: IBillLandedCost landedCost: IBillLandedCost
) => { ) => {