diff --git a/server/src/api/controllers/FinancialStatements/BalanceSheet.ts b/server/src/api/controllers/FinancialStatements/BalanceSheet.ts index d8878ab4a..fe1562e27 100644 --- a/server/src/api/controllers/FinancialStatements/BalanceSheet.ts +++ b/server/src/api/controllers/FinancialStatements/BalanceSheet.ts @@ -1,5 +1,5 @@ import { Inject, Service } from 'typedi'; -import { Router, Request, Response, NextFunction } from 'express'; +import { Router, Request, Response, NextFunction } from 'express'; import { query, ValidationChain } from 'express-validator'; import { castArray } from 'lodash'; import asyncMiddleware from 'api/middleware/asyncMiddleware'; @@ -7,7 +7,7 @@ import BaseController from '../BaseController'; import BalanceSheetStatementService from 'services/FinancialStatements/BalanceSheet/BalanceSheetService'; @Service() -export default class BalanceSheetStatementController extends BaseController{ +export default class BalanceSheetStatementController extends BaseController { @Inject() balanceSheetService: BalanceSheetStatementService; @@ -32,25 +32,15 @@ export default class BalanceSheetStatementController extends BaseController{ */ get balanceSheetValidationSchema(): ValidationChain[] { return [ - query('accounting_method') - .optional() - .isIn(['cash', 'accural']), + query('accounting_method').optional().isIn(['cash', 'accural']), query('from_date').optional(), query('to_date').optional(), - query('display_columns_type') - .optional() - .isIn(['date_periods', 'total']), + query('display_columns_type').optional().isIn(['date_periods', 'total']), query('display_columns_by') .optional({ nullable: true, checkFalsy: true }) .isIn(['year', 'month', 'week', 'day', 'quarter']), - query('number_format.no_cents') - .optional() - .isBoolean() - .toBoolean(), - query('number_format.divide_1000') - .optional() - .isBoolean() - .toBoolean(), + query('number_format.no_cents').optional().isBoolean().toBoolean(), + query('number_format.divide_1000').optional().isBoolean().toBoolean(), query('account_ids').isArray().optional(), query('account_ids.*').isNumeric().toInt(), query('none_zero').optional().isBoolean().toBoolean(), @@ -69,14 +59,20 @@ export default class BalanceSheetStatementController extends BaseController{ ...filter, accountsIds: castArray(filter.accountsIds), }; - const organizationName = settings.get({ group: 'organization', key: 'name' }); - const baseCurrency = settings.get({ group: 'organization', key: 'base_currency' }); + const organizationName = settings.get({ + group: 'organization', + key: 'name', + }); + const baseCurrency = settings.get({ + group: 'organization', + key: 'base_currency', + }); try { const { data, columns, - query + query, } = await this.balanceSheetService.balanceSheet(tenantId, filter); return res.status(200).send({ @@ -85,9 +81,9 @@ export default class BalanceSheetStatementController extends BaseController{ data: this.transfromToResponse(data), columns: this.transfromToResponse(columns), query: this.transfromToResponse(query), - }) + }); } catch (error) { next(error); } } -}; +} diff --git a/server/src/api/controllers/FinancialStatements/GeneralLedger.ts b/server/src/api/controllers/FinancialStatements/GeneralLedger.ts index aed3e2da0..b0357373d 100644 --- a/server/src/api/controllers/FinancialStatements/GeneralLedger.ts +++ b/server/src/api/controllers/FinancialStatements/GeneralLedger.ts @@ -6,8 +6,7 @@ import { Inject, Service } from 'typedi'; import GeneralLedgerService from 'services/FinancialStatements/GeneralLedger/GeneralLedgerService'; @Service() -export default class GeneralLedgerReportController extends BaseController{ - +export default class GeneralLedgerReportController extends BaseController { @Inject() generalLedgetService: GeneralLedgerService; @@ -17,7 +16,8 @@ export default class GeneralLedgerReportController extends BaseController{ router() { const router = Router(); - router.get('/', + router.get( + '/', this.validationSchema, this.validationResult, asyncMiddleware(this.generalLedger.bind(this)) @@ -35,9 +35,9 @@ export default class GeneralLedgerReportController extends BaseController{ query('basis').optional(), query('number_format.no_cents').optional().isBoolean().toBoolean(), query('number_format.divide_1000').optional().isBoolean().toBoolean(), - query('none_transactions').optional().isBoolean().toBoolean(), - query('accounts_ids').optional(), - query('accounts_ids.*').isNumeric().toInt(), + query('none_transactions').default(true).isBoolean().toBoolean(), + query('accounts_ids').optional().isArray({ min: 1 }), + query('accounts_ids.*').isInt().toInt(), query('orderBy').optional().isIn(['created_at', 'name', 'code']), query('order').optional().isIn(['desc', 'asc']), ]; @@ -45,22 +45,27 @@ export default class GeneralLedgerReportController extends BaseController{ /** * Retrieve the general ledger financial statement. - * @param {Request} req - - * @param {Response} res - + * @param {Request} req - + * @param {Response} res - */ async generalLedger(req: Request, res: Response, next: NextFunction) { const { tenantId, settings } = req; const filter = this.matchedQueryData(req); - const organizationName = settings.get({ group: 'organization', key: 'name' }); - const baseCurrency = settings.get({ group: 'organization', key: 'base_currency' }); + const organizationName = settings.get({ + group: 'organization', + key: 'name', + }); + const baseCurrency = settings.get({ + group: 'organization', + key: 'base_currency', + }); try { - const { - data, - query, - } = await this.generalLedgetService.generalLedger(tenantId, filter); - + const { data, query } = await this.generalLedgetService.generalLedger( + tenantId, + filter + ); return res.status(200).send({ organization_name: organizationName, base_currency: baseCurrency, @@ -71,4 +76,4 @@ export default class GeneralLedgerReportController extends BaseController{ next(error); } } -} \ No newline at end of file +} diff --git a/server/src/api/controllers/Inventory/InventoryAdjustments.ts b/server/src/api/controllers/Inventory/InventoryAdjustments.ts index e08f721f3..8889a556e 100644 --- a/server/src/api/controllers/Inventory/InventoryAdjustments.ts +++ b/server/src/api/controllers/Inventory/InventoryAdjustments.ts @@ -16,6 +16,13 @@ export default class InventoryAdjustmentsController extends BaseController { router() { const router = Router(); + router.post( + '/:id/publish', + [param('id').exists().isNumeric().toInt()], + this.validationResult, + this.asyncMiddleware(this.publishInventoryAdjustment.bind(this)), + this.handleServiceErrors + ); router.delete( '/:id', [param('id').exists().isNumeric().toInt()], @@ -62,6 +69,7 @@ export default class InventoryAdjustmentsController extends BaseController { .exists() .isFloat() .toInt(), + check('publish').default(false).isBoolean().toBoolean(), ]; } @@ -124,6 +132,34 @@ export default class InventoryAdjustmentsController extends BaseController { } } + /** + * Publish the given inventory adjustment transaction. + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + */ + async publishInventoryAdjustment( + req: Request, + res: Response, + next: NextFunction + ) { + const { tenantId } = req; + const { id: adjustmentId } = req.params; + + try { + await this.inventoryAdjustmentService.publishInventoryAdjustment( + tenantId, + adjustmentId + ); + return res.status(200).send({ + id: adjustmentId, + message: 'The inventory adjustment has been published successfully.', + }); + } catch (error) { + next(error); + } + } + /** * Retrieve the inventory adjustments paginated list. * @param {Request} req diff --git a/server/src/data/BalanceSheetStructure.ts b/server/src/data/BalanceSheetStructure.ts index 688e02596..d57a58d9c 100644 --- a/server/src/data/BalanceSheetStructure.ts +++ b/server/src/data/BalanceSheetStructure.ts @@ -9,20 +9,20 @@ const balanceSheetStructure: IBalanceSheetStructureSection[] = [ { name: 'Current Asset', type: 'accounts_section', - _accountsTypesRelated: ['current_asset'], + accountsTypesRelated: ['current_asset'], }, { name: 'Fixed Asset', type: 'accounts_section', - _accountsTypesRelated: ['fixed_asset'], + accountsTypesRelated: ['fixed_asset'], }, { name: 'Other Asset', type: 'accounts_section', - _accountsTypesRelated: ['other_asset'], + accountsTypesRelated: ['other_asset'], }, ], - _forceShow: true, + alwaysShow: true, }, { name: 'Liabilities and Equity', @@ -37,17 +37,17 @@ const balanceSheetStructure: IBalanceSheetStructureSection[] = [ { name: 'Current Liability', type: 'accounts_section', - _accountsTypesRelated: ['current_liability'], + accountsTypesRelated: ['current_liability'], }, { name: 'Long Term Liability', type: 'accounts_section', - _accountsTypesRelated: ['long_term_liability'], + accountsTypesRelated: ['long_term_liability'], }, { name: 'Other Liability', type: 'accounts_section', - _accountsTypesRelated: ['other_liability'], + accountsTypesRelated: ['other_liability'], }, ], }, @@ -55,10 +55,10 @@ const balanceSheetStructure: IBalanceSheetStructureSection[] = [ name: 'Equity', sectionType: 'equity', type: 'accounts_section', - _accountsTypesRelated: ['equity'], + accountsTypesRelated: ['equity'], }, ], - _forceShow: true, + alwaysShow: true, }, ]; diff --git a/server/src/database/migrations/20200810121809_create_inventory_adjustments_table.js b/server/src/database/migrations/20200810121809_create_inventory_adjustments_table.js index 76e23f391..4774fcd23 100644 --- a/server/src/database/migrations/20200810121809_create_inventory_adjustments_table.js +++ b/server/src/database/migrations/20200810121809_create_inventory_adjustments_table.js @@ -9,6 +9,7 @@ exports.up = function(knex) { table.string('reference_no').index(); table.string('description'); table.integer('user_id').unsigned(); + table.date('published_at'); table.timestamps(); }); }; diff --git a/server/src/interfaces/BalanceSheet.ts b/server/src/interfaces/BalanceSheet.ts index 9e52c1131..a680fe13c 100644 --- a/server/src/interfaces/BalanceSheet.ts +++ b/server/src/interfaces/BalanceSheet.ts @@ -37,8 +37,8 @@ export interface IBalanceSheetStructureSection { sectionType?: string, type: 'section' | 'accounts_section', children?: IBalanceSheetStructureSection[], - _accountsTypesRelated?: string[], - _forceShow?: boolean, + accountsTypesRelated?: string[], + alwaysShow?: boolean, } export interface IBalanceSheetAccountTotal { @@ -69,6 +69,6 @@ export interface IBalanceSheetSection { total: IBalanceSheetAccountTotal, totalPeriods?: IBalanceSheetAccountTotal[]; - _accountsTypesRelated?: string[], + accountsTypesRelated?: string[], _forceShow?: boolean, } \ No newline at end of file diff --git a/server/src/interfaces/InventoryAdjustment.ts b/server/src/interfaces/InventoryAdjustment.ts index e9a4402ad..ee17b73aa 100644 --- a/server/src/interfaces/InventoryAdjustment.ts +++ b/server/src/interfaces/InventoryAdjustment.ts @@ -1,9 +1,8 @@ - type IAdjustmentTypes = 'increment' | 'decrement'; export interface IQuickInventoryAdjustmentDTO { date: Date | string; - type: IAdjustmentTypes, + type: IAdjustmentTypes; adjustmentAccountId: number; reason: string; description: string; @@ -11,31 +10,33 @@ export interface IQuickInventoryAdjustmentDTO { itemId: number; quantity: number; cost: number; -}; + publish: boolean; +} export interface IInventoryAdjustment { - id?: number, + id?: number; date: Date | string; adjustmentAccountId: number; reason: string; description: string; referenceNo: string; - inventoryDirection?: 'IN' | 'OUT', + inventoryDirection?: 'IN' | 'OUT'; entries: IInventoryAdjustmentEntry[]; userId: number; -}; + publishedAt?: Date|null; +} export interface IInventoryAdjustmentEntry { - id?: number, - adjustmentId?: number, - index: number, + id?: number; + adjustmentId?: number; + index: number; itemId: number; quantity?: number; cost?: number; value?: number; -}; +} -export interface IInventoryAdjustmentsFilter{ - page: number, - pageSize: number, -}; \ No newline at end of file +export interface IInventoryAdjustmentsFilter { + page: number; + pageSize: number; +} diff --git a/server/src/models/InventoryAdjustment.js b/server/src/models/InventoryAdjustment.js index df16a1a5e..9f2a7d779 100644 --- a/server/src/models/InventoryAdjustment.js +++ b/server/src/models/InventoryAdjustment.js @@ -20,7 +20,7 @@ export default class InventoryAdjustment extends TenantModel { * Virtual attributes. */ static get virtualAttributes() { - return ['inventoryDirection']; + return ['inventoryDirection', 'isPublished']; } /** @@ -30,6 +30,14 @@ export default class InventoryAdjustment extends TenantModel { return InventoryAdjustment.getInventoryDirection(this.type); } + /** + * Detarmines whether the adjustment is published. + * @return {boolean} + */ + get isPublished() { + return !!this.publishedAt; + } + static getInventoryDirection(type) { const directions = { 'increment': 'IN', diff --git a/server/src/services/Accounting/JournalPoster.ts b/server/src/services/Accounting/JournalPoster.ts index f7569607b..9d33e8461 100644 --- a/server/src/services/Accounting/JournalPoster.ts +++ b/server/src/services/Accounting/JournalPoster.ts @@ -324,6 +324,7 @@ export default class JournalPoster implements IJournalPoster { transactions.forEach((transaction) => { this.entries.push({ ...transaction, + referenceTypeFormatted: transaction.referenceTypeFormatted, account: transaction.accountId, accountNormal: get(transaction, 'account.type.normal'), }); @@ -417,7 +418,7 @@ export default class JournalPoster implements IJournalPoster { * @param {Number} account - * @param {Date|String} closingDate - */ - getTrialBalance(accountId, closingDate, dateType) { + getTrialBalance(accountId, closingDate) { const momentClosingDate = moment(closingDate); const result = { credit: 0, @@ -426,8 +427,8 @@ export default class JournalPoster implements IJournalPoster { }; this.entries.forEach((entry) => { if ( - (!momentClosingDate.isAfter(entry.date, dateType) && - !momentClosingDate.isSame(entry.date, dateType)) || + (!momentClosingDate.isAfter(entry.date, 'day') && + !momentClosingDate.isSame(entry.date, 'day')) || (entry.account !== accountId && accountId) ) { return; @@ -478,8 +479,8 @@ export default class JournalPoster implements IJournalPoster { accountId: number, contactId: number, contactType: string, - closingDate: Date|string, - openingDate: Date|string, + closingDate?: Date|string, + openingDate?: Date|string, ) { const momentClosingDate = moment(closingDate); const momentOpeningDate = moment(openingDate); diff --git a/server/src/services/FinancialStatements/BalanceSheet/BalanceSheet.ts b/server/src/services/FinancialStatements/BalanceSheet/BalanceSheet.ts index c1ebcc21f..87dcbdaf5 100644 --- a/server/src/services/FinancialStatements/BalanceSheet/BalanceSheet.ts +++ b/server/src/services/FinancialStatements/BalanceSheet/BalanceSheet.ts @@ -85,7 +85,7 @@ export default class BalanceSheetStatement extends FinancialSheet { * @return {IBalanceSheetAccountTotal[]} */ private getSectionTotalPeriods( - sections: Array + sections: Array ): IBalanceSheetAccountTotal[] { return this.dateRangeSet.map((date, index) => { const amount = sumBy(sections, `totalPeriods[${index}].amount`); @@ -203,8 +203,8 @@ export default class BalanceSheetStatement extends FinancialSheet { /** * Mappes the structure sections. - * @param {IBalanceSheetStructureSection} structure - * @param {IAccount} accounts + * @param {IBalanceSheetStructureSection} structure + * @param {IAccount} accounts */ private structureSectionMapper( structure: IBalanceSheetStructureSection, @@ -238,12 +238,12 @@ export default class BalanceSheetStatement extends FinancialSheet { name: structure.name, sectionType: structure.sectionType, type: structure.type, - ...(structure.type === 'accounts_section') + ...(structure.type === 'accounts_section' ? this.structureRelatedAccountsMapper( - structure._accountsTypesRelated, + structure.accountsTypesRelated, accounts ) - : this.structureSectionMapper(structure, accounts), + : this.structureSectionMapper(structure, accounts)), }; return result; } @@ -259,13 +259,30 @@ export default class BalanceSheetStatement extends FinancialSheet { ): IBalanceSheetSection[] { return ( reportStructure - .map((structure: IBalanceSheetStructureSection) => - this.balanceSheetStructureMapper(structure, balanceSheetAccounts) - ) - // Filter the structure sections that have no children. + .map((structure: IBalanceSheetStructureSection) => { + const sheetSection = this.balanceSheetStructureMapper( + structure, + balanceSheetAccounts + ); + return [sheetSection, structure]; + }) + // Filter the structure sections that have no children and not always show. .filter( - (structure: IBalanceSheetSection) => - structure.children.length > 0 || structure._forceShow + ([sheetSection, structure]: [ + IBalanceSheetSection, + IBalanceSheetStructureSection + ]) => { + return sheetSection.children.length > 0 || structure.alwaysShow; + } + ) + // Mappes the balance sheet scetions only + .map( + ([sheetSection, structure]: [ + IBalanceSheetSection, + IBalanceSheetStructureSection + ]) => { + return sheetSection; + } ) ); } @@ -295,6 +312,10 @@ export default class BalanceSheetStatement extends FinancialSheet { * @return {IBalanceSheetSection[]} */ public reportData(): IBalanceSheetSection[] { + // Returns nothing if there is no entries in the journal between the given period. + if (this.journalFinancial.entries.length === 0) { + return []; + } return this.balanceSheetStructureWalker( BalanceSheetStructure, this.accounts diff --git a/server/src/services/FinancialStatements/JournalSheet/JournalSheet.ts b/server/src/services/FinancialStatements/JournalSheet/JournalSheet.ts index ce49f5483..718b75076 100644 --- a/server/src/services/FinancialStatements/JournalSheet/JournalSheet.ts +++ b/server/src/services/FinancialStatements/JournalSheet/JournalSheet.ts @@ -1,12 +1,13 @@ -import { sumBy, chain } from 'lodash'; +import { sumBy, chain, omit } from 'lodash'; import { IJournalEntry, IJournalPoster, IJournalReportEntriesGroup, IJournalReportQuery, - IJournalReport -} from "interfaces"; -import FinancialSheet from "../FinancialSheet"; + IJournalReport, +} from 'interfaces'; +import FinancialSheet from '../FinancialSheet'; +import { AccountTransaction } from 'models'; export default class JournalSheet extends FinancialSheet { tenantId: number; @@ -16,14 +17,14 @@ export default class JournalSheet extends FinancialSheet { /** * Constructor method. - * @param {number} tenantId - * @param {IJournalPoster} journal + * @param {number} tenantId + * @param {IJournalPoster} journal */ constructor( tenantId: number, query: IJournalReportQuery, journal: IJournalPoster, - baseCurrency: string, + baseCurrency: string ) { super(); @@ -34,23 +35,38 @@ export default class JournalSheet extends FinancialSheet { this.baseCurrency = baseCurrency; } + /** + * Mappes the journal entries. + * @param {IJournalEntry[]} entries - + */ + entriesMapper( + entries: IJournalEntry[], + ) { + return entries.map((entry: IJournalEntry) => { + return { + ...omit(entry, 'account'), + currencyCode: this.baseCurrency, + }; + }) + } + /** * Mapping journal entries groups. * @param {IJournalEntry[]} entriesGroup - * @param {string} key - * @return {IJournalReportEntriesGroup} */ - entriesGroupMapper( + entriesGroupsMapper( entriesGroup: IJournalEntry[], - key: string, + key: string ): IJournalReportEntriesGroup { const totalCredit = sumBy(entriesGroup, 'credit'); const totalDebit = sumBy(entriesGroup, 'debit'); return { id: key, - entries: entriesGroup, - + entries: this.entriesMapper(entriesGroup), + currencyCode: this.baseCurrency, credit: totalCredit, @@ -63,16 +79,15 @@ export default class JournalSheet extends FinancialSheet { /** * Mapping the journal entries to entries groups. - * @param {IJournalEntry[]} entries + * @param {IJournalEntry[]} entries * @return {IJournalReportEntriesGroup[]} */ entriesWalker(entries: IJournalEntry[]): IJournalReportEntriesGroup[] { return chain(entries) .groupBy((entry) => `${entry.referenceId}-${entry.referenceType}`) - .map(( - entriesGroup: IJournalEntry[], - key: string - ) => this.entriesGroupMapper(entriesGroup, key)) + .map((entriesGroup: IJournalEntry[], key: string) => + this.entriesGroupsMapper(entriesGroup, key) + ) .value(); } @@ -83,4 +98,4 @@ export default class JournalSheet extends FinancialSheet { reportData(): IJournalReport { return this.entriesWalker(this.journal.entries); } -} \ No newline at end of file +} diff --git a/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts b/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts index 17841111b..0fe38c8a1 100644 --- a/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts +++ b/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts @@ -1,4 +1,3 @@ - import { ITrialBalanceSheetQuery, ITrialBalanceAccount, @@ -8,7 +7,7 @@ import { import FinancialSheet from '../FinancialSheet'; import { flatToNestedArray } from 'utils'; -export default class TrialBalanceSheet extends FinancialSheet{ +export default class TrialBalanceSheet extends FinancialSheet { tenantId: number; query: ITrialBalanceSheetQuery; accounts: IAccount & { type: IAccountType }[]; @@ -17,17 +16,17 @@ export default class TrialBalanceSheet extends FinancialSheet{ /** * Constructor method. - * @param {number} tenantId - * @param {ITrialBalanceSheetQuery} query - * @param {IAccount[]} accounts - * @param journalFinancial + * @param {number} tenantId + * @param {ITrialBalanceSheetQuery} query + * @param {IAccount[]} accounts + * @param journalFinancial */ constructor( tenantId: number, query: ITrialBalanceSheetQuery, accounts: IAccount & { type: IAccountType }[], journalFinancial: any, - baseCurrency: string, + baseCurrency: string ) { super(); @@ -42,13 +41,15 @@ export default class TrialBalanceSheet extends FinancialSheet{ /** * Account mapper. - * @param {IAccount} account + * @param {IAccount} account */ - private accountMapper(account: IAccount & { type: IAccountType }): ITrialBalanceAccount { + private accountMapper( + account: IAccount & { type: IAccountType } + ): ITrialBalanceAccount { const trial = this.journalFinancial.getTrialBalanceWithDepands(account.id); // Retrieve all entries that associated to the given account. - const entries = this.journalFinancial.getAccountEntries(account.id) + const entries = this.journalFinancial.getAccountEntries(account.id); return { id: account.id, @@ -71,29 +72,35 @@ export default class TrialBalanceSheet extends FinancialSheet{ /** * Accounts walker. - * @param {IAccount[]} accounts + * @param {IAccount[]} accounts */ private accountsWalker( accounts: IAccount & { type: IAccountType }[] ): ITrialBalanceAccount[] { const flattenAccounts = accounts // Mapping the trial balance accounts sections. - .map((account: IAccount & { type: IAccountType }) => this.accountMapper(account)) - + .map((account: IAccount & { type: IAccountType }) => + this.accountMapper(account) + ) // Filter accounts that have no transaction when `noneTransactions` is on. - .filter((trialAccount: ITrialBalanceAccount): boolean => - !(!trialAccount.hasTransactions && this.query.noneTransactions), + .filter( + (trialAccount: ITrialBalanceAccount): boolean => + !(!trialAccount.hasTransactions && this.query.noneTransactions) ) // Filter accounts that have zero total amount when `noneZero` is on. .filter( (trialAccount: ITrialBalanceAccount): boolean => - !(trialAccount.credit === 0 && trialAccount.debit === 0 && this.query.noneZero) - ); - - return flatToNestedArray( - flattenAccounts, - { id: 'id', parentId: 'parentAccountId' }, - ); + !( + trialAccount.credit === 0 && + trialAccount.debit === 0 && + this.query.noneZero + ) + ); + + return flatToNestedArray(flattenAccounts, { + id: 'id', + parentId: 'parentAccountId', + }); } /** @@ -102,4 +109,4 @@ export default class TrialBalanceSheet extends FinancialSheet{ public reportData() { return this.accountsWalker(this.accounts); } -} \ No newline at end of file +} diff --git a/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService.ts b/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService.ts index b2c25937e..d82b848f1 100644 --- a/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService.ts +++ b/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetService.ts @@ -1,13 +1,12 @@ -import { Service, Inject } from "typedi"; +import { Service, Inject } from 'typedi'; import moment from 'moment'; import TenancyService from 'services/Tenancy/TenancyService'; import { ITrialBalanceSheetQuery, ITrialBalanceStatement } from 'interfaces'; -import TrialBalanceSheet from "./TrialBalanceSheet"; +import TrialBalanceSheet from './TrialBalanceSheet'; import Journal from 'services/Accounting/JournalPoster'; @Service() export default class TrialBalanceSheetService { - @Inject() tenancy: TenancyService; @@ -36,16 +35,15 @@ export default class TrialBalanceSheetService { /** * Retrieve trial balance sheet statement. * ------------- - * @param {number} tenantId - * @param {IBalanceSheetQuery} query - * + * @param {number} tenantId + * @param {IBalanceSheetQuery} query + * * @return {IBalanceSheetStatement} */ public async trialBalanceSheet( tenantId: number, - query: ITrialBalanceSheetQuery, + query: ITrialBalanceSheetQuery ): Promise { - const filter = { ...this.defaultQuery, ...query, @@ -57,10 +55,15 @@ export default class TrialBalanceSheetService { // Settings tenant service. const settings = this.tenancy.settings(tenantId); - const baseCurrency = settings.get({ group: 'organization', key: 'base_currency' }); - - this.logger.info('[trial_balance_sheet] trying to calcualte the report.', { tenantId, filter }); + const baseCurrency = settings.get({ + group: 'organization', + key: 'base_currency', + }); + this.logger.info('[trial_balance_sheet] trying to calcualte the report.', { + tenantId, + filter, + }); // Retrieve all accounts on the storage. const accounts = await accountRepository.all('type'); const accountsGraph = await accountRepository.getDependencyGraph(); @@ -72,8 +75,11 @@ export default class TrialBalanceSheetService { sumationCreditDebit: true, }); // Transform transactions array to journal collection. - const transactionsJournal = Journal.fromTransactions(transactions, tenantId, accountsGraph); - + const transactionsJournal = Journal.fromTransactions( + transactions, + tenantId, + accountsGraph + ); // Trial balance report instance. const trialBalanceInstance = new TrialBalanceSheet( tenantId, @@ -88,6 +94,6 @@ export default class TrialBalanceSheetService { return { data: trialBalanceSheetData, query: filter, - } + }; } -} \ No newline at end of file +} diff --git a/server/src/services/Inventory/InventoryAdjustmentService.ts b/server/src/services/Inventory/InventoryAdjustmentService.ts index b448514bd..3fed1f326 100644 --- a/server/src/services/Inventory/InventoryAdjustmentService.ts +++ b/server/src/services/Inventory/InventoryAdjustmentService.ts @@ -1,5 +1,6 @@ import { Inject, Service } from 'typedi'; import { omit } from 'lodash'; +import moment from 'moment'; import { EventDispatcher, EventDispatcherInterface, @@ -54,16 +55,21 @@ export default class InventoryAdjustmentService { authorizedUser: ISystemUser ): IInventoryAdjustment { return { - ...omit(adjustmentDTO, ['quantity', 'cost', 'itemId']), + ...omit(adjustmentDTO, ['quantity', 'cost', 'itemId', 'publish']), userId: authorizedUser.id, + ...(adjustmentDTO.publish + ? { + publishedAt: moment().toMySqlDateTime(), + } + : {}), entries: [ { index: 1, itemId: adjustmentDTO.itemId, ...('increment' === adjustmentDTO.type ? { - quantity: adjustmentDTO.quantity, - cost: adjustmentDTO.cost, + quantity: adjustmentDTO.quantity, + cost: adjustmentDTO.cost, } : {}), ...('decrement' === adjustmentDTO.type @@ -212,6 +218,50 @@ export default class InventoryAdjustmentService { ); } + /** + * Publish the inventory adjustment transaction. + * @param tenantId + * @param inventoryAdjustmentId + */ + async publishInventoryAdjustment( + tenantId: number, + inventoryAdjustmentId: number + ): Promise { + const { InventoryAdjustment } = this.tenancy.models(tenantId); + + // Retrieve the inventory adjustment or throw not found service error. + const oldInventoryAdjustment = await this.getInventoryAdjustmentOrThrowError( + tenantId, + inventoryAdjustmentId + ); + this.logger.info('[inventory_adjustment] trying to publish adjustment.', { + tenantId, + inventoryAdjustmentId, + }); + // Publish the inventory adjustment transaction. + await InventoryAdjustment.query() + .findById(inventoryAdjustmentId) + .patch({ + publishedAt: moment().toMySqlDateTime(), + }); + + // Retrieve the inventory adjustment after the modification. + const inventoryAdjustment = await InventoryAdjustment.query() + .findById(inventoryAdjustmentId) + .withGraphFetched('entries'); + + // Triggers `onInventoryAdjustmentDeleted` event. + await this.eventDispatcher.dispatch( + events.inventoryAdjustment.onPublished, + { + tenantId, + inventoryAdjustmentId, + inventoryAdjustment, + oldInventoryAdjustment, + } + ); + } + /** * Retrieve the inventory adjustments paginated list. * @param {number} tenantId @@ -246,7 +296,7 @@ export default class InventoryAdjustmentService { async writeInventoryTransactions( tenantId: number, inventoryAdjustment: IInventoryAdjustment, - override: boolean = false, + override: boolean = false ): Promise { // Gets the next inventory lot number. const lotNumber = this.inventoryService.getNextLotNumber(tenantId); diff --git a/server/src/subscribers/Inventory/Inventory.ts b/server/src/subscribers/Inventory/Inventory.ts index 848519c97..d346aef43 100644 --- a/server/src/subscribers/Inventory/Inventory.ts +++ b/server/src/subscribers/Inventory/Inventory.ts @@ -30,7 +30,7 @@ export class InventorySubscriber { if (dependsComputeJobs.length === 0) { this.startingDate = null; - await this.saleInvoicesCost.scheduleWriteJournalEntries( + await this.saleInvoicesCost.scheduleWriteJournalEntries( tenantId, startingDate ); diff --git a/server/src/subscribers/Inventory/InventoryAdjustment.ts b/server/src/subscribers/Inventory/InventoryAdjustment.ts index d454f8226..cd39d39a7 100644 --- a/server/src/subscribers/Inventory/InventoryAdjustment.ts +++ b/server/src/subscribers/Inventory/InventoryAdjustment.ts @@ -27,6 +27,9 @@ export default class InventoryAdjustmentsSubscriber { tenantId, inventoryAdjustment, }) { + // Can't continue if the inventory adjustment is not published. + if (!inventoryAdjustment.isPublished) { return; } + await this.inventoryAdjustment.writeInventoryTransactions( tenantId, inventoryAdjustment @@ -39,11 +42,29 @@ export default class InventoryAdjustmentsSubscriber { @On(events.inventoryAdjustment.onDeleted) async handleRevertInventoryTransactionsOnceDeleted({ tenantId, - inventoryAdjustmentId + inventoryAdjustmentId, + oldInventoryTransaction, }) { + // Can't continue if the inventory adjustment is not published. + if (!oldInventoryTransaction.isPublished) { return; } + await this.inventoryAdjustment.revertInventoryTransactions( tenantId, inventoryAdjustmentId, ); } + + /** + * Handles writing inventory transactions once the quick adjustment created. + */ + @On(events.inventoryAdjustment.onPublished) + async handleWriteInventoryTransactionsOncePublished({ + tenantId, + inventoryAdjustment, + }) { + await this.inventoryAdjustment.writeInventoryTransactions( + tenantId, + inventoryAdjustment + ) + } } \ No newline at end of file diff --git a/server/src/subscribers/events.ts b/server/src/subscribers/events.ts index 0e55b0406..62c41d7c2 100644 --- a/server/src/subscribers/events.ts +++ b/server/src/subscribers/events.ts @@ -193,9 +193,13 @@ export default { onComputeItemCostJobCompleted: 'onComputeItemCostJobCompleted' }, + /** + * Inventory adjustment service. + */ inventoryAdjustment: { onCreated: 'onInventoryAdjustmentCreated', onQuickCreated: 'onInventoryAdjustmentQuickCreated', onDeleted: 'onInventoryAdjustmentDeleted', + onPublished: 'onInventoryAdjustmentPublished', } }