mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 22:30:31 +00:00
WIP: Allocate landed cost.
This commit is contained in:
@@ -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,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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');
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
) => {
|
) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user