mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 13:20:31 +00:00
fix(Journals): sync posting datetime with jorunal entries.
This commit is contained in:
@@ -106,7 +106,6 @@ export default class InventoryAdjustmentsController extends BaseController {
|
||||
) {
|
||||
const { tenantId, user } = req;
|
||||
const quickInventoryAdjustment = this.matchedBodyData(req);
|
||||
console.log(quickInventoryAdjustment);
|
||||
|
||||
try {
|
||||
const inventoryAdjustment = await this.inventoryAdjustmentService.createQuickAdjustment(
|
||||
|
||||
@@ -324,7 +324,7 @@ export default class PaymentReceivesController extends BaseController {
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
*/
|
||||
async getPaymentReceiveEditPage(
|
||||
async getPaymentReceiveEditPage(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
|
||||
@@ -435,9 +435,7 @@ export default class SaleInvoicesController extends BaseController {
|
||||
}
|
||||
if (error.errorType === 'SALE_INVOICE_NO_IS_REQUIRED') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [
|
||||
{ type: 'SALE_INVOICE_NO_IS_REQUIRED', code: 1500 },
|
||||
],
|
||||
errors: [{ type: 'SALE_INVOICE_NO_IS_REQUIRED', code: 1500 }],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,12 @@ exports.up = function(knex) {
|
||||
table.integer('item_id').unsigned().nullable().index();
|
||||
table.string('note');
|
||||
table.integer('user_id').unsigned().index();
|
||||
table.integer('index').unsigned();
|
||||
|
||||
table.integer('index_group').unsigned().index();
|
||||
table.integer('index').unsigned().index();
|
||||
|
||||
table.date('date').index();
|
||||
table.timestamps();
|
||||
table.datetime('created_at').index();
|
||||
}).raw('ALTER TABLE `ACCOUNTS_TRANSACTIONS` AUTO_INCREMENT = 1000');
|
||||
};
|
||||
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
const { knexSnakeCaseMappers } = require("objection");
|
||||
const { knexSnakeCaseMappers } = require('objection');
|
||||
|
||||
exports.up = function(knex) {
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.createTable('payment_receives', (table) => {
|
||||
table.increments();
|
||||
table.integer('customer_id').unsigned().index().references('id').inTable('contacts');
|
||||
table
|
||||
.integer('customer_id')
|
||||
.unsigned()
|
||||
.index()
|
||||
.references('id')
|
||||
.inTable('contacts');
|
||||
table.date('payment_date').index();
|
||||
table.decimal('amount', 13, 3).defaultTo(0);
|
||||
table.string('currency_code', 3);
|
||||
table.string('reference_no').index();
|
||||
table.integer('deposit_account_id').unsigned().references('id').inTable('accounts');
|
||||
table
|
||||
.integer('deposit_account_id')
|
||||
.unsigned()
|
||||
.references('id')
|
||||
.inTable('accounts');
|
||||
table.string('payment_receive_no').nullable();
|
||||
table.text('statement');
|
||||
table.integer('user_id').unsigned().index();
|
||||
@@ -16,6 +25,6 @@ exports.up = function(knex) {
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function(knex) {
|
||||
exports.down = function (knex) {
|
||||
return knex.schema.dropTableIfExists('payment_receives');
|
||||
};
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.createTable('inventory_transactions', table => {
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.createTable('inventory_transactions', (table) => {
|
||||
table.increments('id');
|
||||
table.date('date').index();
|
||||
|
||||
table.string('direction').index();
|
||||
|
||||
table.integer('item_id').unsigned().index().references('id').inTable('items');
|
||||
table
|
||||
.integer('item_id')
|
||||
.unsigned()
|
||||
.index()
|
||||
.references('id')
|
||||
.inTable('items');
|
||||
table.integer('quantity').unsigned();
|
||||
table.decimal('rate', 13, 3).unsigned();
|
||||
|
||||
table.integer('lot_number').index();
|
||||
table.integer('cost_account_id').unsigned().index().references('id').inTable('accounts');
|
||||
|
||||
table.string('transaction_type').index();
|
||||
table.integer('transaction_id').unsigned().index();
|
||||
|
||||
@@ -21,6 +20,4 @@ exports.up = function(knex) {
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function(knex) {
|
||||
|
||||
};
|
||||
exports.down = function (knex) {};
|
||||
|
||||
@@ -11,6 +11,10 @@ exports.up = function(knex) {
|
||||
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.timestamps();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.createTable('inventory_cost_lot_tracker', table => {
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.createTable('inventory_cost_lot_tracker', (table) => {
|
||||
table.increments();
|
||||
table.date('date').index();
|
||||
table.string('direction').index();
|
||||
@@ -10,14 +9,15 @@ exports.up = function(knex) {
|
||||
table.decimal('rate', 13, 3);
|
||||
table.integer('remaining');
|
||||
table.decimal('cost', 13, 3);
|
||||
table.integer('lot_number').index();
|
||||
|
||||
table.string('transaction_type').index();
|
||||
table.integer('transaction_id').unsigned().index();
|
||||
|
||||
table.integer('entry_id').unsigned().index();
|
||||
});
|
||||
table.datetime('created_at').index();
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function(knex) {
|
||||
exports.down = function (knex) {
|
||||
return knex.schema.dropTableIfExists('inventory_cost_lot_tracker');
|
||||
};
|
||||
|
||||
@@ -46,11 +46,13 @@ export interface IBill {
|
||||
dueAmount: number,
|
||||
overdueDays: number,
|
||||
|
||||
invLotNumber: string,
|
||||
openedAt: Date | string,
|
||||
|
||||
entries: IItemEntry[],
|
||||
userId: number,
|
||||
|
||||
createdAt: Date,
|
||||
updateAt: Date,
|
||||
};
|
||||
|
||||
export interface IBillsFilter extends IDynamicListFilterDTO {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { LongDateFormatKey } from "moment";
|
||||
|
||||
|
||||
export interface IBillPaymentEntry {
|
||||
@@ -18,6 +19,8 @@ export interface IBillPayment {
|
||||
userId: number,
|
||||
entries: IBillPaymentEntry[],
|
||||
statement: string,
|
||||
createdAt: Date,
|
||||
updatedAt: Date,
|
||||
}
|
||||
|
||||
export interface IBillPaymentEntryDTO {
|
||||
|
||||
@@ -24,6 +24,7 @@ export interface IInventoryAdjustment {
|
||||
entries: IInventoryAdjustmentEntry[];
|
||||
userId: number;
|
||||
publishedAt?: Date|null;
|
||||
createdAt?: Date,
|
||||
}
|
||||
|
||||
export interface IInventoryAdjustmentEntry {
|
||||
|
||||
@@ -10,7 +10,6 @@ export interface IInventoryTransaction {
|
||||
rate: number,
|
||||
transactionType: string,
|
||||
transactionId: number,
|
||||
lotNumber: number,
|
||||
entryId: number,
|
||||
createdAt?: Date,
|
||||
updatedAt?: Date,
|
||||
@@ -25,10 +24,12 @@ export interface IInventoryLotCost {
|
||||
rate: number,
|
||||
remaining: number,
|
||||
cost: number,
|
||||
lotNumber: number,
|
||||
transactionType: string,
|
||||
transactionId: number,
|
||||
entryId: number
|
||||
costAccountId: number,
|
||||
sellAccountId: number,
|
||||
entryId: number,
|
||||
createdAt: Date,
|
||||
};
|
||||
|
||||
export interface IItemsQuantityChanges {
|
||||
|
||||
@@ -14,6 +14,9 @@ export interface IItemEntry {
|
||||
discount: number,
|
||||
quantity: number,
|
||||
rate: number,
|
||||
|
||||
sellAccountId: number,
|
||||
costAccountId: number,
|
||||
}
|
||||
|
||||
export interface IItemEntryDTO {
|
||||
|
||||
@@ -13,6 +13,8 @@ export interface IManualJournal {
|
||||
description: string;
|
||||
userId: number;
|
||||
entries: IManualJournalEntry[];
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface IManualJournalEntry {
|
||||
|
||||
@@ -11,6 +11,8 @@ export interface IPaymentReceive {
|
||||
statement: string;
|
||||
entries: IPaymentReceiveEntry[];
|
||||
userId: number;
|
||||
createdAt: Date,
|
||||
updatedAt: Date,
|
||||
}
|
||||
export interface IPaymentReceiveCreateDTO {
|
||||
customerId: number;
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface ISaleInvoice {
|
||||
entries: IItemEntry[];
|
||||
deliveredAt: string | Date;
|
||||
userId: number;
|
||||
createdAt: Date,
|
||||
}
|
||||
|
||||
export interface ISaleInvoiceDTO {
|
||||
|
||||
@@ -14,6 +14,8 @@ export interface ISaleReceipt {
|
||||
statement: string;
|
||||
closedAt: Date | string;
|
||||
entries: any[];
|
||||
createdAt: Date,
|
||||
updatedAt: Date,
|
||||
}
|
||||
|
||||
export interface ISalesReceiptsFilter {}
|
||||
|
||||
@@ -21,6 +21,9 @@ export default class WriteInvoicesJournalEntries {
|
||||
agenda.on(`complete:${eventName}`, this.onJobCompleted.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the job execuation.
|
||||
*/
|
||||
public async handler(job, done: Function): Promise<void> {
|
||||
const Logger = Container.get('logger');
|
||||
const { startingDate, tenantId } = job.attrs.data;
|
||||
|
||||
@@ -112,12 +112,10 @@ export default class AccountTransaction extends TenantModel {
|
||||
filterContactIds(query, contactIds) {
|
||||
query.whereIn('contact_id', contactIds);
|
||||
},
|
||||
|
||||
openingBalance(query, fromDate) {
|
||||
query.modify('filterDateRange', null, fromDate)
|
||||
query.modify('sumationCreditDebit')
|
||||
},
|
||||
|
||||
closingBalance(query, toDate) {
|
||||
query.modify('filterDateRange', null, toDate)
|
||||
query.modify('sumationCreditDebit')
|
||||
|
||||
@@ -11,8 +11,10 @@ export default (Model) => {
|
||||
const maybePromise = super.$beforeUpdate(opt, context);
|
||||
|
||||
return Promise.resolve(maybePromise).then(() => {
|
||||
if (this.timestamps[1]) {
|
||||
this[this.timestamps[1]] = moment().format('YYYY/MM/DD HH:mm:ss');
|
||||
const key = this.timestamps[1];
|
||||
|
||||
if (key && !this[key]) {
|
||||
this[key] = moment().format('YYYY/MM/DD HH:mm:ss');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -21,8 +23,10 @@ export default (Model) => {
|
||||
const maybePromise = super.$beforeInsert(context);
|
||||
|
||||
return Promise.resolve(maybePromise).then(() => {
|
||||
if (this.timestamps[0]) {
|
||||
this[this.timestamps[0]] = moment().format('YYYY/MM/DD HH:mm:ss');
|
||||
const key = this.timestamps[0];
|
||||
|
||||
if (key && !this[key]) {
|
||||
this[key] = moment().format('YYYY/MM/DD HH:mm:ss');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export default class InventoryCostLotTracker extends TenantModel {
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
groupedEntriesCost(query) {
|
||||
groupedEntriesCost(query) {
|
||||
query.select(['date', 'item_id', 'transaction_id', 'transaction_type']);
|
||||
query.sum('cost as cost');
|
||||
|
||||
@@ -46,12 +46,13 @@ export default class InventoryCostLotTracker extends TenantModel {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Item = require('models/Item');
|
||||
const SaleInvoice = require('models/SaleInvoice');
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
|
||||
return {
|
||||
item: {
|
||||
@@ -62,6 +63,25 @@ export default class InventoryCostLotTracker extends TenantModel {
|
||||
to: 'items.id',
|
||||
},
|
||||
},
|
||||
invoice: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: SaleInvoice.default,
|
||||
join: {
|
||||
from: 'inventory_cost_lot_tracker.transactionId',
|
||||
to: 'sales_invoices.id',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('transaction_type', 'SaleInvoice');
|
||||
},
|
||||
},
|
||||
itemEntry: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: ItemEntry.default,
|
||||
join: {
|
||||
from: 'inventory_cost_lot_tracker.entryId',
|
||||
to: 'items_entries.id',
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ export default class InventoryTransaction extends TenantModel {
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Item = require('models/Item');
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
|
||||
return {
|
||||
item: {
|
||||
@@ -52,6 +53,14 @@ export default class InventoryTransaction extends TenantModel {
|
||||
to: 'items.id',
|
||||
},
|
||||
},
|
||||
itemEntry: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: ItemEntry.default,
|
||||
join: {
|
||||
from: 'inventory_transactions.entryId',
|
||||
to: 'items_entries.id',
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@ interface IJournalTransactionsFilter {
|
||||
contactType?: string,
|
||||
referenceType?: string[],
|
||||
referenceId?: number[],
|
||||
index: number|number[]
|
||||
index: number|number[],
|
||||
indexGroup: number|number[],
|
||||
};
|
||||
|
||||
export default class AccountTransactionsRepository extends TenantRepository {
|
||||
@@ -58,6 +59,13 @@ export default class AccountTransactionsRepository extends TenantRepository {
|
||||
query.where('index', filter.index);
|
||||
}
|
||||
}
|
||||
if (filter.indexGroup) {
|
||||
if (Array.isArray(filter.indexGroup)) {
|
||||
query.whereIn('index_group', filter.indexGroup);
|
||||
} else {
|
||||
query.where('index_group', filter.indexGroup);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { sumBy, chain } from 'lodash';
|
||||
import moment, { LongDateFormatKey } from 'moment';
|
||||
import { IBill, IManualJournalEntry, ISaleReceipt, ISystemUser } from 'interfaces';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
IBill,
|
||||
IManualJournalEntry,
|
||||
ISaleReceipt,
|
||||
ISystemUser,
|
||||
} from 'interfaces';
|
||||
import JournalPoster from './JournalPoster';
|
||||
import JournalEntry from './JournalEntry';
|
||||
import { AccountTransaction } from 'models';
|
||||
import {
|
||||
IInventoryTransaction,
|
||||
IManualJournal,
|
||||
IExpense,
|
||||
IExpenseCategory,
|
||||
@@ -14,6 +16,7 @@ import {
|
||||
IInventoryLotCost,
|
||||
IItemEntry,
|
||||
} from 'interfaces';
|
||||
import { increment } from 'utils';
|
||||
|
||||
export default class JournalCommands {
|
||||
journal: JournalPoster;
|
||||
@@ -61,6 +64,8 @@ export default class JournalCommands {
|
||||
|
||||
referenceNumber: bill.referenceNo,
|
||||
transactionNumber: bill.billNumber,
|
||||
|
||||
createdAt: bill.createdAt,
|
||||
};
|
||||
// Overrides the old bill entries.
|
||||
if (override) {
|
||||
@@ -90,9 +95,9 @@ export default class JournalCommands {
|
||||
account:
|
||||
['inventory'].indexOf(item.type) !== -1
|
||||
? item.inventoryAccountId
|
||||
: item.costAccountId,
|
||||
: entry.costAccountId,
|
||||
index: index + 2,
|
||||
itemId: entry.itemId
|
||||
itemId: entry.itemId,
|
||||
});
|
||||
this.journal.debit(debitEntry);
|
||||
});
|
||||
@@ -257,7 +262,7 @@ export default class JournalCommands {
|
||||
const transactions = await transactionsRepository.journal({
|
||||
fromDate: startingDate,
|
||||
referenceType: ['SaleInvoice'],
|
||||
index: [3, 4],
|
||||
indexGroup: 20
|
||||
});
|
||||
this.journal.fromTransactions(transactions);
|
||||
this.journal.removeEntries();
|
||||
@@ -265,11 +270,9 @@ export default class JournalCommands {
|
||||
|
||||
/**
|
||||
* Reverts sale invoice the income journal entries.
|
||||
* @param {number} saleInvoiceId
|
||||
* @param {number} saleInvoiceId
|
||||
*/
|
||||
async revertInvoiceIncomeEntries(
|
||||
saleInvoiceId: number,
|
||||
) {
|
||||
async revertInvoiceIncomeEntries(saleInvoiceId: number) {
|
||||
const { transactionsRepository } = this.repositories;
|
||||
|
||||
const transactions = await transactionsRepository.journal({
|
||||
@@ -289,6 +292,7 @@ export default class JournalCommands {
|
||||
const commonEntry = {
|
||||
transaction_number: manualJournalObj.journalNumber,
|
||||
reference_number: manualJournalObj.reference,
|
||||
createdAt: manualJournalObj.createdAt,
|
||||
};
|
||||
manualJournalObj.entries.forEach((entry: IManualJournalEntry) => {
|
||||
const jouranlEntry = new JournalEntry({
|
||||
@@ -317,36 +321,50 @@ export default class JournalCommands {
|
||||
* -------
|
||||
* - Cost of goods sold -> Debit -> YYYY
|
||||
* - Inventory assets -> Credit -> YYYY
|
||||
*
|
||||
* --------
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @param {JournalPoster} journal
|
||||
*/
|
||||
saleInvoiceInventoryCost(
|
||||
inventoryCostLot: IInventoryLotCost & { item: IItem }
|
||||
inventoryCostLots: IInventoryLotCost &
|
||||
{ item: IItem; itemEntry: IItemEntry }[]
|
||||
) {
|
||||
const commonEntry = {
|
||||
referenceType: inventoryCostLot.transactionType,
|
||||
referenceId: inventoryCostLot.transactionId,
|
||||
date: inventoryCostLot.date,
|
||||
};
|
||||
// XXX Debit - Cost account.
|
||||
const costEntry = new JournalEntry({
|
||||
...commonEntry,
|
||||
debit: inventoryCostLot.cost,
|
||||
account: inventoryCostLot.item.costAccountId,
|
||||
index: 3,
|
||||
itemId: inventoryCostLot.itemId
|
||||
});
|
||||
// XXX Credit - Inventory account.
|
||||
const inventoryEntry = new JournalEntry({
|
||||
...commonEntry,
|
||||
credit: inventoryCostLot.cost,
|
||||
account: inventoryCostLot.item.inventoryAccountId,
|
||||
index: 4,
|
||||
itemId: inventoryCostLot.itemId
|
||||
});
|
||||
this.journal.credit(inventoryEntry);
|
||||
this.journal.debit(costEntry);
|
||||
const getIndexIncrement = increment(0);
|
||||
|
||||
inventoryCostLots.forEach(
|
||||
(
|
||||
inventoryCostLot: IInventoryLotCost & {
|
||||
item: IItem;
|
||||
itemEntry: IItemEntry;
|
||||
}
|
||||
) => {
|
||||
const commonEntry = {
|
||||
referenceType: inventoryCostLot.transactionType,
|
||||
referenceId: inventoryCostLot.transactionId,
|
||||
date: inventoryCostLot.date,
|
||||
indexGroup: 20,
|
||||
createdAt: inventoryCostLot.createdAt,
|
||||
};
|
||||
// XXX Debit - Cost account.
|
||||
const costEntry = new JournalEntry({
|
||||
...commonEntry,
|
||||
debit: inventoryCostLot.cost,
|
||||
account: inventoryCostLot.itemEntry.costAccountId,
|
||||
itemId: inventoryCostLot.itemId,
|
||||
index: getIndexIncrement(),
|
||||
});
|
||||
// XXX Credit - Inventory account.
|
||||
const inventoryEntry = new JournalEntry({
|
||||
...commonEntry,
|
||||
credit: inventoryCostLot.cost,
|
||||
account: inventoryCostLot.item.inventoryAccountId,
|
||||
itemId: inventoryCostLot.itemId,
|
||||
index: getIndexIncrement(),
|
||||
});
|
||||
this.journal.credit(inventoryEntry);
|
||||
this.journal.debit(costEntry);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -354,7 +372,7 @@ export default class JournalCommands {
|
||||
* -----
|
||||
* - Receivable accounts -> Debit -> XXXX
|
||||
* - Income -> Credit -> XXXX
|
||||
*
|
||||
*
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @param {number} receivableAccountsId
|
||||
* @param {number} authorizedUserId
|
||||
@@ -373,6 +391,9 @@ export default class JournalCommands {
|
||||
|
||||
transactionNumber: saleInvoice.invoiceNo,
|
||||
referenceNumber: saleInvoice.referenceNo,
|
||||
|
||||
createdAt: saleInvoice.createdAt,
|
||||
indexGroup: 10,
|
||||
};
|
||||
// XXX Debit - Receivable account.
|
||||
const receivableEntry = new JournalEntry({
|
||||
@@ -384,22 +405,20 @@ export default class JournalCommands {
|
||||
});
|
||||
this.journal.debit(receivableEntry);
|
||||
|
||||
saleInvoice.entries.forEach(
|
||||
(entry: IItemEntry & { item: IItem }, index: number) => {
|
||||
const income: number = entry.quantity * entry.rate;
|
||||
saleInvoice.entries.forEach((entry: IItemEntry, index: number) => {
|
||||
const income: number = entry.quantity * entry.rate;
|
||||
|
||||
// XXX Credit - Income account.
|
||||
const incomeEntry = new JournalEntry({
|
||||
...commonEntry,
|
||||
credit: income,
|
||||
account: entry.item.sellAccountId,
|
||||
note: entry.description,
|
||||
index: index + 2,
|
||||
itemId: entry.itemId,
|
||||
});
|
||||
this.journal.credit(incomeEntry);
|
||||
}
|
||||
);
|
||||
// XXX Credit - Income account.
|
||||
const incomeEntry = new JournalEntry({
|
||||
...commonEntry,
|
||||
credit: income,
|
||||
account: entry.sellAccountId,
|
||||
note: entry.description,
|
||||
index: index + 2,
|
||||
itemId: entry.itemId,
|
||||
});
|
||||
this.journal.credit(incomeEntry);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -407,7 +426,7 @@ export default class JournalCommands {
|
||||
* -----
|
||||
* - Deposit account -> Debit -> XXXX
|
||||
* - Income -> Credit -> XXXX
|
||||
*
|
||||
*
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @param {number} receivableAccountsId
|
||||
* @param {number} authorizedUserId
|
||||
@@ -415,7 +434,7 @@ export default class JournalCommands {
|
||||
async saleReceiptIncomeEntries(
|
||||
saleReceipt: ISaleReceipt & {
|
||||
entries: IItemEntry & { item: IItem };
|
||||
},
|
||||
}
|
||||
): Promise<void> {
|
||||
const commonEntry = {
|
||||
referenceType: 'SaleReceipt',
|
||||
@@ -424,6 +443,7 @@ export default class JournalCommands {
|
||||
userId: saleReceipt.userId,
|
||||
transactionNumber: saleReceipt.receiptNumber,
|
||||
referenceNumber: saleReceipt.referenceNo,
|
||||
createdAt: saleReceipt.createdAt,
|
||||
};
|
||||
// XXX Debit - Deposit account.
|
||||
const depositEntry = new JournalEntry({
|
||||
|
||||
@@ -164,10 +164,12 @@ export default class JournalPoster implements IJournalPoster {
|
||||
);
|
||||
|
||||
const balanceEntries = chain(balanceChanges)
|
||||
.map((change) => change.entries.map(entry => ({
|
||||
...entry,
|
||||
contactId: change.contactId
|
||||
})))
|
||||
.map((change) =>
|
||||
change.entries.map((entry) => ({
|
||||
...entry,
|
||||
contactId: change.contactId,
|
||||
}))
|
||||
)
|
||||
.flatten()
|
||||
.value();
|
||||
|
||||
@@ -376,6 +378,8 @@ export default class JournalPoster implements IJournalPoster {
|
||||
const { transactionsRepository } = this.repositories;
|
||||
const saveOperations: Promise<void>[] = [];
|
||||
|
||||
this.logger.info('[journal] trying to insert accounts transactions.');
|
||||
|
||||
this.entries.forEach((entry) => {
|
||||
const oper = transactionsRepository.create({
|
||||
accountId: entry.account,
|
||||
|
||||
@@ -17,7 +17,6 @@ import InventoryCostLotTracker from 'services/Inventory/InventoryCostLotTracker'
|
||||
import TenancyService from 'services/Tenancy/TenancyService';
|
||||
import events from 'subscribers/events';
|
||||
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
|
||||
import SettingsMiddleware from 'api/middleware/SettingsMiddleware';
|
||||
|
||||
type TCostMethod = 'FIFO' | 'LIFO' | 'AVG';
|
||||
|
||||
@@ -35,20 +34,27 @@ export default class InventoryService {
|
||||
/**
|
||||
* Transforms the items entries to inventory transactions.
|
||||
*/
|
||||
transformItemEntriesToInventory(
|
||||
itemEntries: IItemEntry[],
|
||||
direction: TInventoryTransactionDirection,
|
||||
date: Date | string,
|
||||
lotNumber: number
|
||||
): IInventoryTransaction[] {
|
||||
return itemEntries.map((entry: IItemEntry) => ({
|
||||
...pick(entry, ['itemId', 'quantity', 'rate']),
|
||||
lotNumber,
|
||||
transactionType: entry.referenceType,
|
||||
transactionId: entry.referenceId,
|
||||
direction,
|
||||
date,
|
||||
transformItemEntriesToInventory(transaction: {
|
||||
transactionId: number;
|
||||
transactionType: IItemEntryTransactionType;
|
||||
|
||||
date: Date | string;
|
||||
direction: TInventoryTransactionDirection;
|
||||
entries: IItemEntry[];
|
||||
createdAt: Date;
|
||||
}): IInventoryTransaction[] {
|
||||
return transaction.entries.map((entry: IItemEntry) => ({
|
||||
...pick(entry, [
|
||||
'itemId',
|
||||
'quantity',
|
||||
'rate',
|
||||
]),
|
||||
transactionType: transaction.transactionType,
|
||||
transactionId: transaction.transactionId,
|
||||
direction: transaction.direction,
|
||||
date: transaction.date,
|
||||
entryId: entry.id,
|
||||
createdAt: transaction.createdAt,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -200,7 +206,6 @@ export default class InventoryService {
|
||||
}
|
||||
return InventoryTransaction.query().insert({
|
||||
...inventoryEntry,
|
||||
lotNumber: inventoryEntry.lotNumber,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -215,31 +220,24 @@ export default class InventoryService {
|
||||
*/
|
||||
async recordInventoryTransactionsFromItemsEntries(
|
||||
tenantId: number,
|
||||
transactionId: number,
|
||||
transactionType: IItemEntryTransactionType,
|
||||
transactionDate: Date | string,
|
||||
transactionDirection: TInventoryTransactionDirection,
|
||||
transaction: {
|
||||
transactionId: number;
|
||||
transactionType: IItemEntryTransactionType;
|
||||
|
||||
date: Date | string;
|
||||
direction: TInventoryTransactionDirection;
|
||||
entries: IItemEntry[];
|
||||
createdAt: Date | string;
|
||||
},
|
||||
override: boolean = false
|
||||
): Promise<void> {
|
||||
// Retrieve the next inventory lot number.
|
||||
const lotNumber = this.getNextLotNumber(tenantId);
|
||||
|
||||
// Loads the inventory items entries of the given sale invoice.
|
||||
const inventoryEntries = await this.itemsEntriesService.getInventoryEntries(
|
||||
tenantId,
|
||||
transactionType,
|
||||
transactionId
|
||||
);
|
||||
// Can't continue if there is no entries has inventory items in the invoice.
|
||||
if (inventoryEntries.length <= 0) {
|
||||
if (transaction.entries.length <= 0) {
|
||||
return;
|
||||
}
|
||||
// Inventory transactions.
|
||||
const inventoryTranscations = this.transformItemEntriesToInventory(
|
||||
inventoryEntries,
|
||||
transactionDirection,
|
||||
transactionDate,
|
||||
lotNumber
|
||||
transaction
|
||||
);
|
||||
// Records the inventory transactions of the given sale invoice.
|
||||
await this.recordInventoryTransactions(
|
||||
@@ -247,8 +245,6 @@ export default class InventoryService {
|
||||
inventoryTranscations,
|
||||
override
|
||||
);
|
||||
// Increment and save the next lot number settings.
|
||||
await this.incrementNextLotNumber(tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -309,49 +305,10 @@ export default class InventoryService {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the lot number after the increment.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
*/
|
||||
getNextLotNumber(tenantId: number) {
|
||||
const settings = this.tenancy.settings(tenantId);
|
||||
|
||||
const LOT_NUMBER_KEY = 'lot_number_increment';
|
||||
const storedLotNumber = settings.find({ key: LOT_NUMBER_KEY });
|
||||
|
||||
return storedLotNumber && storedLotNumber.value
|
||||
? parseInt(storedLotNumber.value, 10)
|
||||
: 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the next inventory LOT number.
|
||||
* @param {number} tenantId
|
||||
* @return {Promise<number>}
|
||||
*/
|
||||
async incrementNextLotNumber(tenantId: number) {
|
||||
const settings = this.tenancy.settings(tenantId);
|
||||
|
||||
const LOT_NUMBER_KEY = 'lot_number_increment';
|
||||
const storedLotNumber = settings.find({ key: LOT_NUMBER_KEY });
|
||||
|
||||
let lotNumber = 1;
|
||||
|
||||
if (storedLotNumber && storedLotNumber.value) {
|
||||
lotNumber = parseInt(storedLotNumber.value, 10);
|
||||
lotNumber += 1;
|
||||
}
|
||||
settings.set({ key: LOT_NUMBER_KEY }, lotNumber);
|
||||
|
||||
await settings.save();
|
||||
|
||||
return lotNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark item cost computing is running.
|
||||
* @param {number} tenantId -
|
||||
* @param {boolean} isRunning -
|
||||
* @param {number} tenantId -
|
||||
* @param {boolean} isRunning -
|
||||
*/
|
||||
async markItemsCostComputeRunning(
|
||||
tenantId: number,
|
||||
@@ -368,16 +325,16 @@ export default class InventoryService {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} tenantId
|
||||
* @returns
|
||||
*
|
||||
* @param {number} tenantId
|
||||
* @returns
|
||||
*/
|
||||
isItemsCostComputeRunning(tenantId) {
|
||||
const settings = this.tenancy.settings(tenantId);
|
||||
|
||||
return settings.get({
|
||||
key: 'cost_compute_running',
|
||||
group: 'inventory'
|
||||
group: 'inventory',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import ItemsService from 'services/Items/ItemsService';
|
||||
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||
import HasTenancyService from 'services/Tenancy/TenancyService';
|
||||
import InventoryService from './Inventory';
|
||||
import { increment } from 'utils';
|
||||
|
||||
const ERRORS = {
|
||||
INVENTORY_ADJUSTMENT_NOT_FOUND: 'INVENTORY_ADJUSTMENT_NOT_FOUND',
|
||||
@@ -310,24 +311,21 @@ export default class InventoryAdjustmentService {
|
||||
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,
|
||||
createdAt: inventoryAdjustment.createdAt
|
||||
};
|
||||
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.
|
||||
@@ -335,9 +333,7 @@ export default class InventoryAdjustmentService {
|
||||
tenantId,
|
||||
inventoryTransactions,
|
||||
override
|
||||
);
|
||||
// Increment and save the next lot number settings.
|
||||
await this.inventoryService.incrementNextLotNumber(tenantId);
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -50,10 +50,10 @@ export default class InventoryAverageCostMethod
|
||||
.modify('filterDateRange', this.startingDate)
|
||||
.orderBy('date', 'ASC')
|
||||
.orderByRaw("FIELD(direction, 'IN', 'OUT')")
|
||||
.orderBy('lot_number', 'ASC')
|
||||
.orderBy('createdAt', 'ASC')
|
||||
.where('item_id', this.itemId)
|
||||
.withGraphFetched('item');
|
||||
|
||||
|
||||
// Tracking inventroy transactions and retrieve cost transactions based on
|
||||
// average rate cost method.
|
||||
const costTransactions = this.trackingCostTransactions(
|
||||
@@ -164,7 +164,9 @@ export default class InventoryAverageCostMethod
|
||||
'entryId',
|
||||
'transactionId',
|
||||
'transactionType',
|
||||
'lotNumber',
|
||||
'createdAt',
|
||||
'sellAccountId',
|
||||
'costAccountId',
|
||||
]),
|
||||
};
|
||||
switch (invTransaction.direction) {
|
||||
|
||||
@@ -19,10 +19,12 @@ export default class InventoryCostMethod {
|
||||
|
||||
/**
|
||||
* Stores the inventory lots costs transactions in bulk.
|
||||
* @param {IInventoryLotCost[]} costLotsTransactions
|
||||
* @param {IInventoryLotCost[]} costLotsTransactions
|
||||
* @return {Promise[]}
|
||||
*/
|
||||
public storeInventoryLotsCost(costLotsTransactions: IInventoryLotCost[]): Promise<object> {
|
||||
public storeInventoryLotsCost(
|
||||
costLotsTransactions: IInventoryLotCost[]
|
||||
): Promise<object> {
|
||||
const { InventoryCostLotTracker } = this.tenantModels;
|
||||
const opers: any = [];
|
||||
|
||||
@@ -32,15 +34,13 @@ export default class InventoryCostMethod {
|
||||
.where('id', transaction.lotTransId)
|
||||
.decrement('remaining', transaction.decrement);
|
||||
opers.push(decrementOper);
|
||||
|
||||
} else if(!transaction.lotTransId) {
|
||||
const operation = InventoryCostLotTracker.query()
|
||||
.insert({
|
||||
...omit(transaction, ['decrement', 'invTransId', 'lotTransId']),
|
||||
});
|
||||
} else if (!transaction.lotTransId) {
|
||||
const operation = InventoryCostLotTracker.query().insert({
|
||||
...omit(transaction, ['decrement', 'invTransId', 'lotTransId']),
|
||||
});
|
||||
opers.push(operation);
|
||||
}
|
||||
});
|
||||
return Promise.all(opers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,29 @@ export default class ItemsEntriesService {
|
||||
return inventoryItemsEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the given entries to inventory entries.
|
||||
* @param {IItemEntry[]} entries -
|
||||
* @returns {IItemEntry[]}
|
||||
*/
|
||||
public async filterInventoryEntries(
|
||||
tenantId: number,
|
||||
entries: IItemEntry[]
|
||||
): Promise<IItemEntry[]> {
|
||||
const { Item } = this.tenancy.models(tenantId);
|
||||
const entriesItemsIds = entries.map((e) => e.itemId);
|
||||
|
||||
// Retrieve entries inventory items.
|
||||
const inventoryItems = await Item.query()
|
||||
.whereIn('id', entriesItemsIds)
|
||||
.where('type', 'inventory');
|
||||
|
||||
const inventoryEntries = entries.filter((entry) =>
|
||||
inventoryItems.some((item) => item.id === entry.itemId)
|
||||
);
|
||||
return inventoryEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the entries items ids.
|
||||
* @async
|
||||
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
ACCOUNT_TYPE,
|
||||
} from 'data/AccountTypes';
|
||||
import { ERRORS } from './constants';
|
||||
import { AccountTransaction } from 'models';
|
||||
|
||||
@Service()
|
||||
export default class ItemsService implements IItemsService {
|
||||
|
||||
@@ -569,7 +569,12 @@ export default class BillPaymentsService implements IBillPaymentsService {
|
||||
credit: 0,
|
||||
referenceId: billPayment.id,
|
||||
referenceType: 'BillPayment',
|
||||
|
||||
transactionNumber: billPayment.paymentNumber,
|
||||
referenceNumber: billPayment.reference,
|
||||
|
||||
date: formattedDate,
|
||||
createdAt: billPayment.createdAt,
|
||||
};
|
||||
if (override) {
|
||||
const transactions = await AccountTransaction.query()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { omit, sumBy } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import composeAsync from 'async/compose';
|
||||
import {
|
||||
EventDispatcher,
|
||||
EventDispatcherInterface,
|
||||
@@ -21,7 +22,8 @@ import {
|
||||
IPaginationMeta,
|
||||
IFilterMeta,
|
||||
IBillsFilter,
|
||||
IBillsService
|
||||
IBillsService,
|
||||
IItemEntry,
|
||||
} from 'interfaces';
|
||||
import { ServiceError } from 'exceptions';
|
||||
import ItemsService from 'services/Items/ItemsService';
|
||||
@@ -36,7 +38,9 @@ import { ERRORS } from './constants';
|
||||
* @service
|
||||
*/
|
||||
@Service('Bills')
|
||||
export default class BillsService extends SalesInvoicesCost implements IBillsService {
|
||||
export default class BillsService
|
||||
extends SalesInvoicesCost
|
||||
implements IBillsService {
|
||||
@Inject()
|
||||
inventoryService: InventoryService;
|
||||
|
||||
@@ -167,6 +171,29 @@ export default class BillsService extends SalesInvoicesCost implements IBillsSer
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default cost account to the bill entries.
|
||||
*/
|
||||
setBillEntriesDefaultAccounts(tenantId: number) {
|
||||
return async (entries: IItemEntry[]) => {
|
||||
const { Item } = this.tenancy.models(tenantId);
|
||||
|
||||
const entriesItemsIds = entries.map((e) => e.itemId);
|
||||
const items = await Item.query().whereIn('id', entriesItemsIds);
|
||||
|
||||
return entries.map((entry) => {
|
||||
const item = items.find((i) => i.id === entry.itemId);
|
||||
|
||||
return {
|
||||
...entry,
|
||||
...(item.type !== 'inventory' && {
|
||||
costAccountId: entry.costAccountId || item.costAccountId,
|
||||
}),
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts create bill DTO to model.
|
||||
* @param {number} tenantId
|
||||
@@ -182,11 +209,7 @@ export default class BillsService extends SalesInvoicesCost implements IBillsSer
|
||||
) {
|
||||
const { ItemEntry } = this.tenancy.models(tenantId);
|
||||
|
||||
const entries = billDTO.entries.map((entry) => ({
|
||||
...entry,
|
||||
amount: ItemEntry.calcAmount(entry),
|
||||
}));
|
||||
const amount = sumBy(entries, 'amount');
|
||||
const amount = sumBy(billDTO.entries, (e) => ItemEntry.calcAmount(e));
|
||||
|
||||
// Bill number from DTO or from auto-increment.
|
||||
const billNumber = billDTO.billNumber || oldBill?.billNumber;
|
||||
@@ -196,6 +219,15 @@ export default class BillsService extends SalesInvoicesCost implements IBillsSer
|
||||
tenantId,
|
||||
billDTO.vendorId
|
||||
);
|
||||
const initialEntries = billDTO.entries.map((entry) => ({
|
||||
reference_type: 'Bill',
|
||||
...omit(entry, ['amount']),
|
||||
}));
|
||||
const entries = await composeAsync(
|
||||
// Sets the default cost account to the bill entries.
|
||||
this.setBillEntriesDefaultAccounts(tenantId)
|
||||
)(initialEntries);
|
||||
|
||||
return {
|
||||
...formatDateFields(omit(billDTO, ['open', 'entries']), [
|
||||
'billDate',
|
||||
@@ -204,10 +236,7 @@ export default class BillsService extends SalesInvoicesCost implements IBillsSer
|
||||
amount,
|
||||
currencyCode: vendor.currencyCode,
|
||||
billNumber,
|
||||
entries: entries.map((entry) => ({
|
||||
reference_type: 'Bill',
|
||||
...omit(entry, ['amount']),
|
||||
})),
|
||||
entries,
|
||||
// Avoid rewrite the open date in edit mode when already opened.
|
||||
...(billDTO.open &&
|
||||
!oldBill?.openedAt && {
|
||||
@@ -239,15 +268,6 @@ export default class BillsService extends SalesInvoicesCost implements IBillsSer
|
||||
): Promise<IBill> {
|
||||
const { billRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
this.logger.info('[bill] trying to create a new bill', {
|
||||
tenantId,
|
||||
billDTO,
|
||||
});
|
||||
const billObj = await this.billDTOToModel(
|
||||
tenantId,
|
||||
billDTO,
|
||||
authorizedUser
|
||||
);
|
||||
// Retrieve vendor or throw not found service error.
|
||||
await this.getVendorOrThrowError(tenantId, billDTO.vendorId);
|
||||
|
||||
@@ -264,8 +284,18 @@ export default class BillsService extends SalesInvoicesCost implements IBillsSer
|
||||
tenantId,
|
||||
billDTO.entries
|
||||
);
|
||||
this.logger.info('[bill] trying to create a new bill', {
|
||||
tenantId,
|
||||
billDTO,
|
||||
});
|
||||
// Transform the bill DTO to model object.
|
||||
const billObj = await this.billDTOToModel(
|
||||
tenantId,
|
||||
billDTO,
|
||||
authorizedUser
|
||||
);
|
||||
// Inserts the bill graph object to the storage.
|
||||
const bill = await billRepository.upsertGraph({ ...billObj });
|
||||
const bill = await billRepository.upsertGraph(billObj);
|
||||
|
||||
// Triggers `onBillCreated` event.
|
||||
await this.eventDispatcher.dispatch(events.bill.onCreated, {
|
||||
@@ -309,13 +339,6 @@ export default class BillsService extends SalesInvoicesCost implements IBillsSer
|
||||
this.logger.info('[bill] trying to edit bill.', { tenantId, billId });
|
||||
const oldBill = await this.getBillOrThrowError(tenantId, billId);
|
||||
|
||||
// Transforms the bill DTO object to model object.
|
||||
const billObj = await this.billDTOToModel(
|
||||
tenantId,
|
||||
billDTO,
|
||||
authorizedUser,
|
||||
oldBill
|
||||
);
|
||||
// Retrieve vendor details or throw not found service error.
|
||||
await this.getVendorOrThrowError(tenantId, billDTO.vendorId);
|
||||
|
||||
@@ -340,6 +363,13 @@ export default class BillsService extends SalesInvoicesCost implements IBillsSer
|
||||
tenantId,
|
||||
billDTO.entries
|
||||
);
|
||||
// Transforms the bill DTO to model object.
|
||||
const billObj = await this.billDTOToModel(
|
||||
tenantId,
|
||||
billDTO,
|
||||
authorizedUser,
|
||||
oldBill
|
||||
);
|
||||
// Update the bill transaction.
|
||||
const bill = await billRepository.upsertGraph({
|
||||
id: billId,
|
||||
@@ -498,8 +528,8 @@ export default class BillsService extends SalesInvoicesCost implements IBillsSer
|
||||
|
||||
/**
|
||||
* Records the inventory transactions from the given bill input.
|
||||
* @param {Bill} bill - Bill model object.
|
||||
* @param {number} billId - Bill id.
|
||||
* @param {Bill} bill - Bill model object.
|
||||
* @param {number} billId - Bill id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async recordInventoryTransactions(
|
||||
@@ -507,19 +537,25 @@ export default class BillsService extends SalesInvoicesCost implements IBillsSer
|
||||
bill: IBill,
|
||||
override?: boolean
|
||||
): Promise<void> {
|
||||
// Loads the inventory items entries of the given sale invoice.
|
||||
const inventoryEntries = await this.itemsEntriesService.filterInventoryEntries(
|
||||
tenantId,
|
||||
bill.entries
|
||||
);
|
||||
const transaction = {
|
||||
transactionId: bill.id,
|
||||
transactionType: 'Bill',
|
||||
|
||||
date: bill.billDate,
|
||||
direction: 'IN',
|
||||
entries: inventoryEntries,
|
||||
createdAt: bill.createdAt,
|
||||
};
|
||||
await this.inventoryService.recordInventoryTransactionsFromItemsEntries(
|
||||
tenantId,
|
||||
bill.id,
|
||||
'Bill',
|
||||
bill.billDate,
|
||||
'IN',
|
||||
transaction,
|
||||
override
|
||||
);
|
||||
// Triggers `onInventoryTransactionsCreated` event.
|
||||
this.eventDispatcher.dispatch(events.bill.onInventoryTransactionsCreated, {
|
||||
tenantId,
|
||||
bill,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -582,10 +618,7 @@ export default class BillsService extends SalesInvoicesCost implements IBillsSer
|
||||
* @param {number} tenantId
|
||||
* @param {number} vendorId - Vendor id.
|
||||
*/
|
||||
public async validateVendorHasNoBills(
|
||||
tenantId: number,
|
||||
vendorId: number
|
||||
) {
|
||||
public async validateVendorHasNoBills(tenantId: number, vendorId: number) {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
|
||||
const bills = await Bill.query().where('vendor_id', vendorId);
|
||||
|
||||
@@ -678,8 +678,13 @@ export default class PaymentReceiveService implements IPaymentsReceiveService {
|
||||
credit: 0,
|
||||
referenceId: paymentReceive.id,
|
||||
referenceType: 'PaymentReceive',
|
||||
|
||||
transactionNumber: paymentReceive.paymentReceiveNo,
|
||||
referenceNumber: paymentReceive.referenceNo,
|
||||
|
||||
date: paymentReceive.paymentDate,
|
||||
userId: authorizedUserId,
|
||||
createdAt: paymentReceive.createdAt,
|
||||
};
|
||||
if (override) {
|
||||
const transactions = await transactionsRepository.journal({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { omit, sumBy, join } from 'lodash';
|
||||
import { omit, sumBy, join, entries } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import composeAsync from 'async/compose';
|
||||
import {
|
||||
EventDispatcher,
|
||||
EventDispatcherInterface,
|
||||
@@ -15,7 +16,7 @@ import {
|
||||
ISystemUser,
|
||||
IItem,
|
||||
IItemEntry,
|
||||
ISalesInvoicesService
|
||||
ISalesInvoicesService,
|
||||
} from 'interfaces';
|
||||
import JournalPoster from 'services/Accounting/JournalPoster';
|
||||
import JournalCommands from 'services/Accounting/JournalCommands';
|
||||
@@ -181,6 +182,30 @@ export default class SaleInvoicesService implements ISalesInvoicesService {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cost/sell accounts to the invoice entries.
|
||||
*/
|
||||
setInvoiceEntriesDefaultAccounts(tenantId: number) {
|
||||
return async (entries: IItemEntry[]) => {
|
||||
const { Item } = this.tenancy.models(tenantId);
|
||||
|
||||
const entriesItemsIds = entries.map((e) => e.itemId);
|
||||
const items = await Item.query().whereIn('id', entriesItemsIds);
|
||||
|
||||
return entries.map((entry) => {
|
||||
const item = items.find((i) => i.id === entry.itemId);
|
||||
|
||||
return {
|
||||
...entry,
|
||||
sellAccountId: entry.sellAccountId || item.sellAccountId,
|
||||
...(item.type === 'inventory' && {
|
||||
costAccountId: entry.costAccountId || item.costAccountId,
|
||||
}),
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformes the create DTO to invoice object model.
|
||||
* @param {ISaleInvoiceCreateDTO} saleInvoiceDTO - Sale invoice DTO.
|
||||
@@ -212,6 +237,16 @@ export default class SaleInvoicesService implements ISalesInvoicesService {
|
||||
// Validate the invoice is required.
|
||||
this.validateInvoiceNoRequire(invoiceNo);
|
||||
|
||||
const initialEntries = saleInvoiceDTO.entries.map((entry) => ({
|
||||
referenceType: 'SaleInvoice',
|
||||
...entry,
|
||||
}));
|
||||
|
||||
const entries = await composeAsync(
|
||||
// Sets default cost and sell account to invoice items entries.
|
||||
this.setInvoiceEntriesDefaultAccounts(tenantId)
|
||||
)(initialEntries);
|
||||
|
||||
return {
|
||||
...formatDateFields(
|
||||
omit(saleInvoiceDTO, ['delivered', 'entries', 'fromEstimateId']),
|
||||
@@ -227,10 +262,7 @@ export default class SaleInvoicesService implements ISalesInvoicesService {
|
||||
// Avoid override payment amount in edit mode.
|
||||
...(!oldSaleInvoice && { paymentAmount: 0 }),
|
||||
...(invoiceNo ? { invoiceNo } : {}),
|
||||
entries: saleInvoiceDTO.entries.map((entry) => ({
|
||||
referenceType: 'SaleInvoice',
|
||||
...entry,
|
||||
})),
|
||||
entries,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -259,23 +291,11 @@ export default class SaleInvoicesService implements ISalesInvoicesService {
|
||||
): Promise<ISaleInvoice> {
|
||||
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
// Transform DTO object to model object.
|
||||
const saleInvoiceObj = await this.transformDTOToModel(
|
||||
tenantId,
|
||||
saleInvoiceDTO
|
||||
);
|
||||
// Validate customer existance.
|
||||
await this.customersService.getCustomerByIdOrThrowError(
|
||||
tenantId,
|
||||
saleInvoiceDTO.customerId
|
||||
);
|
||||
// Validate sale invoice number uniquiness.
|
||||
if (saleInvoiceObj.invoiceNo) {
|
||||
await this.validateInvoiceNumberUnique(
|
||||
tenantId,
|
||||
saleInvoiceObj.invoiceNo
|
||||
);
|
||||
}
|
||||
// Validate the from estimate id exists on the storage.
|
||||
if (saleInvoiceDTO.fromEstimateId) {
|
||||
const fromEstimate = await this.saleEstimatesService.getSaleEstimateOrThrowError(
|
||||
@@ -295,11 +315,21 @@ export default class SaleInvoicesService implements ISalesInvoicesService {
|
||||
tenantId,
|
||||
saleInvoiceDTO.entries
|
||||
);
|
||||
|
||||
// Transform DTO object to model object.
|
||||
const saleInvoiceObj = await this.transformDTOToModel(
|
||||
tenantId,
|
||||
saleInvoiceDTO
|
||||
);
|
||||
// Validate sale invoice number uniquiness.
|
||||
if (saleInvoiceObj.invoiceNo) {
|
||||
await this.validateInvoiceNumberUnique(
|
||||
tenantId,
|
||||
saleInvoiceObj.invoiceNo
|
||||
);
|
||||
}
|
||||
this.logger.info('[sale_invoice] inserting sale invoice to the storage.');
|
||||
const saleInvoice = await saleInvoiceRepository.upsertGraph({
|
||||
...saleInvoiceObj,
|
||||
});
|
||||
const saleInvoice = await saleInvoiceRepository.upsertGraph(saleInvoiceObj);
|
||||
|
||||
// Triggers the event `onSaleInvoiceCreated`.
|
||||
await this.eventDispatcher.dispatch(events.saleInvoice.onCreated, {
|
||||
tenantId,
|
||||
@@ -337,25 +367,11 @@ export default class SaleInvoicesService implements ISalesInvoicesService {
|
||||
tenantId,
|
||||
saleInvoiceId
|
||||
);
|
||||
// Transform DTO object to model object.
|
||||
const saleInvoiceObj = await this.transformDTOToModel(
|
||||
tenantId,
|
||||
saleInvoiceDTO,
|
||||
oldSaleInvoice
|
||||
);
|
||||
// Validate customer existance.
|
||||
await this.customersService.getCustomerByIdOrThrowError(
|
||||
tenantId,
|
||||
saleInvoiceDTO.customerId
|
||||
);
|
||||
// Validate sale invoice number uniquiness.
|
||||
if (saleInvoiceDTO.invoiceNo) {
|
||||
await this.validateInvoiceNumberUnique(
|
||||
tenantId,
|
||||
saleInvoiceDTO.invoiceNo,
|
||||
saleInvoiceId
|
||||
);
|
||||
}
|
||||
// Validate items ids existance.
|
||||
await this.itemsEntriesService.validateItemsIdsExistance(
|
||||
tenantId,
|
||||
@@ -373,6 +389,20 @@ export default class SaleInvoicesService implements ISalesInvoicesService {
|
||||
'SaleInvoice',
|
||||
saleInvoiceDTO.entries
|
||||
);
|
||||
// Transform DTO object to model object.
|
||||
const saleInvoiceObj = await this.transformDTOToModel(
|
||||
tenantId,
|
||||
saleInvoiceDTO,
|
||||
oldSaleInvoice
|
||||
);
|
||||
// Validate sale invoice number uniquiness.
|
||||
if (saleInvoiceObj.invoiceNo) {
|
||||
await this.validateInvoiceNumberUnique(
|
||||
tenantId,
|
||||
saleInvoiceDTO.invoiceNo,
|
||||
saleInvoiceId
|
||||
);
|
||||
}
|
||||
// Validate the invoice amount is not smaller than the invoice payment amount.
|
||||
this.validateInvoiceAmountBiggerPaymentAmount(
|
||||
saleInvoiceObj.balance,
|
||||
@@ -445,7 +475,8 @@ export default class SaleInvoicesService implements ISalesInvoicesService {
|
||||
const { ItemEntry } = this.tenancy.models(tenantId);
|
||||
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
// Retrieve the given sale invoice with associated entries or throw not found error.
|
||||
// Retrieve the given sale invoice with associated entries
|
||||
// or throw not found error.
|
||||
const oldSaleInvoice = await this.getInvoiceOrThrowError(
|
||||
tenantId,
|
||||
saleInvoiceId
|
||||
@@ -497,12 +528,23 @@ export default class SaleInvoicesService implements ISalesInvoicesService {
|
||||
saleInvoice: ISaleInvoice,
|
||||
override?: boolean
|
||||
): Promise<void> {
|
||||
// Loads the inventory items entries of the given sale invoice.
|
||||
const inventoryEntries = await this.itemsEntriesService.filterInventoryEntries(
|
||||
tenantId,
|
||||
saleInvoice.entries
|
||||
);
|
||||
const transaction = {
|
||||
transactionId: saleInvoice.id,
|
||||
transactionType: 'SaleInvoice',
|
||||
|
||||
date: saleInvoice.invoiceDate,
|
||||
direction: 'OUT',
|
||||
entries: inventoryEntries,
|
||||
createdAt: saleInvoice.created_at,
|
||||
};
|
||||
await this.inventoryService.recordInventoryTransactionsFromItemsEntries(
|
||||
tenantId,
|
||||
saleInvoice.id,
|
||||
'SaleInvoice',
|
||||
saleInvoice.invoiceDate,
|
||||
'OUT',
|
||||
transaction,
|
||||
override
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Container, Service, Inject } from 'typedi';
|
||||
import { chain } from 'lodash';
|
||||
import { chain, groupBy } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import JournalPoster from 'services/Accounting/JournalPoster';
|
||||
import InventoryService from 'services/Inventory/Inventory';
|
||||
@@ -108,6 +108,20 @@ export default class SaleInvoicesCost {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Grpups by transaction type and id the inventory transactions.
|
||||
* @param {IInventoryTransaction} invTransactions
|
||||
* @returns
|
||||
*/
|
||||
inventoryTransactionsGroupByType(
|
||||
invTransactions: { transactionType: string; transactionId: number }[]
|
||||
): { transactionType: string; transactionId: number }[][] {
|
||||
return chain(invTransactions)
|
||||
.groupBy((t) => `${t.transactionType}-${t.transactionId}`)
|
||||
.values()
|
||||
.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes journal entries from sales invoices.
|
||||
* @param {number} tenantId - The tenant id.
|
||||
@@ -124,25 +138,28 @@ export default class SaleInvoicesCost {
|
||||
|
||||
const inventoryCostLotTrans = await InventoryCostLotTracker.query()
|
||||
.where('direction', 'OUT')
|
||||
.modify('groupedEntriesCost')
|
||||
.modify('filterDateRange', startingDate)
|
||||
.orderBy('date', 'ASC')
|
||||
.where('cost', '>', 0)
|
||||
.withGraphFetched('item');
|
||||
.withGraphFetched('item')
|
||||
.withGraphFetched('itemEntry');
|
||||
|
||||
const accountsDepGraph = await accountRepository.getDependencyGraph();
|
||||
|
||||
const journal = new JournalPoster(tenantId, accountsDepGraph);
|
||||
const journalCommands = new JournalCommands(journal);
|
||||
|
||||
// Groups the inventory cost lots transactions.
|
||||
const inventoryTransactions = this.inventoryTransactionsGroupByType(
|
||||
inventoryCostLotTrans
|
||||
);
|
||||
if (override) {
|
||||
await journalCommands.revertInventoryCostJournalEntries(startingDate);
|
||||
}
|
||||
inventoryCostLotTrans.forEach(
|
||||
(inventoryCostLot: IInventoryLotCost & { item: IItem }) => {
|
||||
journalCommands.saleInvoiceInventoryCost(inventoryCostLot);
|
||||
}
|
||||
);
|
||||
inventoryTransactions.forEach((inventoryLots) => {
|
||||
journalCommands.saleInvoiceInventoryCost(inventoryLots);
|
||||
});
|
||||
|
||||
return Promise.all([
|
||||
journal.deleteEntries(),
|
||||
journal.saveEntries(),
|
||||
|
||||
@@ -51,15 +51,9 @@ export default class SaleInvoiceSubscriber {
|
||||
saleInvoice,
|
||||
authorizedUser,
|
||||
}) {
|
||||
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
const saleInvoiceWithItems = await saleInvoiceRepository.findOneById(
|
||||
saleInvoiceId,
|
||||
'entries.item'
|
||||
);
|
||||
await this.saleInvoicesService.writesIncomeJournalEntries(
|
||||
tenantId,
|
||||
saleInvoiceWithItems,
|
||||
saleInvoice,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
@@ -313,7 +313,16 @@ export const parseBoolean = <T>(value: any, defaultValue: T): T | boolean => {
|
||||
return booleanValuesRepresentingTrue.indexOf(normalizedValue) !== -1;
|
||||
};
|
||||
|
||||
var increment = (n) => {
|
||||
return () => {
|
||||
n += 1;
|
||||
return n;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
export {
|
||||
increment,
|
||||
hashPassword,
|
||||
origin,
|
||||
dateRangeCollection,
|
||||
|
||||
Reference in New Issue
Block a user