diff --git a/server/src/api/controllers/Inventory/InventoryAdjustments.ts b/server/src/api/controllers/Inventory/InventoryAdjustments.ts index 223f4e9d2..ff4276e36 100644 --- a/server/src/api/controllers/Inventory/InventoryAdjustments.ts +++ b/server/src/api/controllers/Inventory/InventoryAdjustments.ts @@ -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( diff --git a/server/src/api/controllers/Sales/PaymentReceives.ts b/server/src/api/controllers/Sales/PaymentReceives.ts index 0d8f99973..37c958978 100644 --- a/server/src/api/controllers/Sales/PaymentReceives.ts +++ b/server/src/api/controllers/Sales/PaymentReceives.ts @@ -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 diff --git a/server/src/api/controllers/Sales/SalesInvoices.ts b/server/src/api/controllers/Sales/SalesInvoices.ts index 2e104def2..c2fbc8aa1 100644 --- a/server/src/api/controllers/Sales/SalesInvoices.ts +++ b/server/src/api/controllers/Sales/SalesInvoices.ts @@ -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 }], }); } } diff --git a/server/src/database/migrations/20200104232647_create_accounts_transactions_table.js b/server/src/database/migrations/20200104232647_create_accounts_transactions_table.js index 490786266..6a80545e6 100644 --- a/server/src/database/migrations/20200104232647_create_accounts_transactions_table.js +++ b/server/src/database/migrations/20200104232647_create_accounts_transactions_table.js @@ -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'); }; diff --git a/server/src/database/migrations/20200715194514_create_payment_receives_table.js b/server/src/database/migrations/20200715194514_create_payment_receives_table.js index 292184c3f..c9a73ba13 100644 --- a/server/src/database/migrations/20200715194514_create_payment_receives_table.js +++ b/server/src/database/migrations/20200715194514_create_payment_receives_table.js @@ -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'); }; diff --git a/server/src/database/migrations/20200722164251_create_inventory_transactions_table.js b/server/src/database/migrations/20200722164251_create_inventory_transactions_table.js index 4e7db963a..9871e5d15 100644 --- a/server/src/database/migrations/20200722164251_create_inventory_transactions_table.js +++ b/server/src/database/migrations/20200722164251_create_inventory_transactions_table.js @@ -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) {}; 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 dc36d5b72..b6313eaba 100644 --- a/server/src/database/migrations/20200722173423_create_items_entries_table.js +++ b/server/src/database/migrations/20200722173423_create_items_entries_table.js @@ -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(); }); }; diff --git a/server/src/database/migrations/20200810121807_create_inventory_cost_lot_tracker_table.js b/server/src/database/migrations/20200810121807_create_inventory_cost_lot_tracker_table.js index 102dec214..55bcbcbb1 100644 --- a/server/src/database/migrations/20200810121807_create_inventory_cost_lot_tracker_table.js +++ b/server/src/database/migrations/20200810121807_create_inventory_cost_lot_tracker_table.js @@ -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'); }; diff --git a/server/src/interfaces/Bill.ts b/server/src/interfaces/Bill.ts index c90bd1387..0598dde6a 100644 --- a/server/src/interfaces/Bill.ts +++ b/server/src/interfaces/Bill.ts @@ -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 { diff --git a/server/src/interfaces/BillPayment.ts b/server/src/interfaces/BillPayment.ts index e444a9688..1ac4eda43 100644 --- a/server/src/interfaces/BillPayment.ts +++ b/server/src/interfaces/BillPayment.ts @@ -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 { diff --git a/server/src/interfaces/InventoryAdjustment.ts b/server/src/interfaces/InventoryAdjustment.ts index ee17b73aa..fc043ebeb 100644 --- a/server/src/interfaces/InventoryAdjustment.ts +++ b/server/src/interfaces/InventoryAdjustment.ts @@ -24,6 +24,7 @@ export interface IInventoryAdjustment { entries: IInventoryAdjustmentEntry[]; userId: number; publishedAt?: Date|null; + createdAt?: Date, } export interface IInventoryAdjustmentEntry { diff --git a/server/src/interfaces/InventoryTransaction.ts b/server/src/interfaces/InventoryTransaction.ts index 47b9756e7..7c2a668c7 100644 --- a/server/src/interfaces/InventoryTransaction.ts +++ b/server/src/interfaces/InventoryTransaction.ts @@ -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 { diff --git a/server/src/interfaces/ItemEntry.ts b/server/src/interfaces/ItemEntry.ts index 6883b3471..3e082c716 100644 --- a/server/src/interfaces/ItemEntry.ts +++ b/server/src/interfaces/ItemEntry.ts @@ -14,6 +14,9 @@ export interface IItemEntry { discount: number, quantity: number, rate: number, + + sellAccountId: number, + costAccountId: number, } export interface IItemEntryDTO { diff --git a/server/src/interfaces/ManualJournal.ts b/server/src/interfaces/ManualJournal.ts index ebef7b511..7d47ae28f 100644 --- a/server/src/interfaces/ManualJournal.ts +++ b/server/src/interfaces/ManualJournal.ts @@ -13,6 +13,8 @@ export interface IManualJournal { description: string; userId: number; entries: IManualJournalEntry[]; + createdAt: Date; + updatedAt: Date; } export interface IManualJournalEntry { diff --git a/server/src/interfaces/PaymentReceive.ts b/server/src/interfaces/PaymentReceive.ts index e9e6fdca3..18f1935ee 100644 --- a/server/src/interfaces/PaymentReceive.ts +++ b/server/src/interfaces/PaymentReceive.ts @@ -11,6 +11,8 @@ export interface IPaymentReceive { statement: string; entries: IPaymentReceiveEntry[]; userId: number; + createdAt: Date, + updatedAt: Date, } export interface IPaymentReceiveCreateDTO { customerId: number; diff --git a/server/src/interfaces/SaleInvoice.ts b/server/src/interfaces/SaleInvoice.ts index dcdaf5fca..b2def5c46 100644 --- a/server/src/interfaces/SaleInvoice.ts +++ b/server/src/interfaces/SaleInvoice.ts @@ -16,6 +16,7 @@ export interface ISaleInvoice { entries: IItemEntry[]; deliveredAt: string | Date; userId: number; + createdAt: Date, } export interface ISaleInvoiceDTO { diff --git a/server/src/interfaces/SaleReceipt.ts b/server/src/interfaces/SaleReceipt.ts index fb73c2ed9..4aaa2d0a6 100644 --- a/server/src/interfaces/SaleReceipt.ts +++ b/server/src/interfaces/SaleReceipt.ts @@ -14,6 +14,8 @@ export interface ISaleReceipt { statement: string; closedAt: Date | string; entries: any[]; + createdAt: Date, + updatedAt: Date, } export interface ISalesReceiptsFilter {} diff --git a/server/src/jobs/writeInvoicesJEntries.ts b/server/src/jobs/writeInvoicesJEntries.ts index c0eed27d7..20bc62a4b 100644 --- a/server/src/jobs/writeInvoicesJEntries.ts +++ b/server/src/jobs/writeInvoicesJEntries.ts @@ -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 { const Logger = Container.get('logger'); const { startingDate, tenantId } = job.attrs.data; diff --git a/server/src/models/AccountTransaction.js b/server/src/models/AccountTransaction.js index afb8b26ec..fc886beaf 100644 --- a/server/src/models/AccountTransaction.js +++ b/server/src/models/AccountTransaction.js @@ -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') diff --git a/server/src/models/DateSession.js b/server/src/models/DateSession.js index d4afa2705..14f981ee7 100644 --- a/server/src/models/DateSession.js +++ b/server/src/models/DateSession.js @@ -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'); } }); } diff --git a/server/src/models/InventoryCostLotTracker.js b/server/src/models/InventoryCostLotTracker.js index 83df8f0dd..8cc9dacd1 100644 --- a/server/src/models/InventoryCostLotTracker.js +++ b/server/src/models/InventoryCostLotTracker.js @@ -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', + }, + } }; } } diff --git a/server/src/models/InventoryTransaction.js b/server/src/models/InventoryTransaction.js index 9b17c9d89..391c4dfcf 100644 --- a/server/src/models/InventoryTransaction.js +++ b/server/src/models/InventoryTransaction.js @@ -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', + }, + } }; } } diff --git a/server/src/repositories/AccountTransactionRepository.ts b/server/src/repositories/AccountTransactionRepository.ts index 9daf0fc56..f65ce6843 100644 --- a/server/src/repositories/AccountTransactionRepository.ts +++ b/server/src/repositories/AccountTransactionRepository.ts @@ -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); + } + } }); }); } diff --git a/server/src/services/Accounting/JournalCommands.ts b/server/src/services/Accounting/JournalCommands.ts index 4ae980b2b..a1ce9c0f1 100644 --- a/server/src/services/Accounting/JournalCommands.ts +++ b/server/src/services/Accounting/JournalCommands.ts @@ -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 { 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({ diff --git a/server/src/services/Accounting/JournalPoster.ts b/server/src/services/Accounting/JournalPoster.ts index 2b7652bda..b99d29223 100644 --- a/server/src/services/Accounting/JournalPoster.ts +++ b/server/src/services/Accounting/JournalPoster.ts @@ -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[] = []; + this.logger.info('[journal] trying to insert accounts transactions.'); + this.entries.forEach((entry) => { const oper = transactionsRepository.create({ accountId: entry.account, diff --git a/server/src/services/Inventory/Inventory.ts b/server/src/services/Inventory/Inventory.ts index ef9bda031..d98686548 100644 --- a/server/src/services/Inventory/Inventory.ts +++ b/server/src/services/Inventory/Inventory.ts @@ -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 { - // 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} - */ - 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', }); } } diff --git a/server/src/services/Inventory/InventoryAdjustmentService.ts b/server/src/services/Inventory/InventoryAdjustmentService.ts index b3fd6492f..b953859ec 100644 --- a/server/src/services/Inventory/InventoryAdjustmentService.ts +++ b/server/src/services/Inventory/InventoryAdjustmentService.ts @@ -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 { - // 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); + ) } /** diff --git a/server/src/services/Inventory/InventoryAverageCost.ts b/server/src/services/Inventory/InventoryAverageCost.ts index 9af75983e..7833128a5 100644 --- a/server/src/services/Inventory/InventoryAverageCost.ts +++ b/server/src/services/Inventory/InventoryAverageCost.ts @@ -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) { diff --git a/server/src/services/Inventory/InventoryCostMethod.ts b/server/src/services/Inventory/InventoryCostMethod.ts index 12a3a0ecf..f04a7d54a 100644 --- a/server/src/services/Inventory/InventoryCostMethod.ts +++ b/server/src/services/Inventory/InventoryCostMethod.ts @@ -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 { + public storeInventoryLotsCost( + costLotsTransactions: IInventoryLotCost[] + ): Promise { 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); } -} \ No newline at end of file +} diff --git a/server/src/services/Items/ItemsEntriesService.ts b/server/src/services/Items/ItemsEntriesService.ts index 43565124d..008a22dc5 100644 --- a/server/src/services/Items/ItemsEntriesService.ts +++ b/server/src/services/Items/ItemsEntriesService.ts @@ -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 { + 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 diff --git a/server/src/services/Items/ItemsService.ts b/server/src/services/Items/ItemsService.ts index 057680a67..6673f34d5 100644 --- a/server/src/services/Items/ItemsService.ts +++ b/server/src/services/Items/ItemsService.ts @@ -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 { diff --git a/server/src/services/Purchases/BillPayments/BillPayments.ts b/server/src/services/Purchases/BillPayments/BillPayments.ts index 7e17fb66f..28947c38d 100644 --- a/server/src/services/Purchases/BillPayments/BillPayments.ts +++ b/server/src/services/Purchases/BillPayments/BillPayments.ts @@ -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() diff --git a/server/src/services/Purchases/Bills.ts b/server/src/services/Purchases/Bills.ts index 8b5548d08..b35284e40 100644 --- a/server/src/services/Purchases/Bills.ts +++ b/server/src/services/Purchases/Bills.ts @@ -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 { 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} */ public async recordInventoryTransactions( @@ -507,19 +537,25 @@ export default class BillsService extends SalesInvoicesCost implements IBillsSer bill: IBill, override?: boolean ): Promise { + // 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); diff --git a/server/src/services/Sales/PaymentReceives/PaymentsReceives.ts b/server/src/services/Sales/PaymentReceives/PaymentsReceives.ts index 069d2a1b4..51aa786c0 100644 --- a/server/src/services/Sales/PaymentReceives/PaymentsReceives.ts +++ b/server/src/services/Sales/PaymentReceives/PaymentsReceives.ts @@ -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({ diff --git a/server/src/services/Sales/SalesInvoices.ts b/server/src/services/Sales/SalesInvoices.ts index e791aad74..f3f40e928 100644 --- a/server/src/services/Sales/SalesInvoices.ts +++ b/server/src/services/Sales/SalesInvoices.ts @@ -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 { 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 { + // 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 ); } diff --git a/server/src/services/Sales/SalesInvoicesCost.ts b/server/src/services/Sales/SalesInvoicesCost.ts index 351bfe9ac..b2180d769 100644 --- a/server/src/services/Sales/SalesInvoicesCost.ts +++ b/server/src/services/Sales/SalesInvoicesCost.ts @@ -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(), diff --git a/server/src/subscribers/SaleInvoices/WriteJournalEntries.ts b/server/src/subscribers/SaleInvoices/WriteJournalEntries.ts index 7ec3d2294..5ecea4f91 100644 --- a/server/src/subscribers/SaleInvoices/WriteJournalEntries.ts +++ b/server/src/subscribers/SaleInvoices/WriteJournalEntries.ts @@ -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 ); } diff --git a/server/src/utils/index.ts b/server/src/utils/index.ts index 59eae7d17..82246a0f2 100644 --- a/server/src/utils/index.ts +++ b/server/src/utils/index.ts @@ -313,7 +313,16 @@ export const parseBoolean = (value: any, defaultValue: T): T | boolean => { return booleanValuesRepresentingTrue.indexOf(normalizedValue) !== -1; }; +var increment = (n) => { + return () => { + n += 1; + return n; + }; +}; + + export { + increment, hashPassword, origin, dateRangeCollection,