This commit is contained in:
elforjani3
2021-01-12 16:14:53 +02:00
17 changed files with 314 additions and 142 deletions

View File

@@ -7,7 +7,7 @@ import BaseController from '../BaseController';
import BalanceSheetStatementService from 'services/FinancialStatements/BalanceSheet/BalanceSheetService'; import BalanceSheetStatementService from 'services/FinancialStatements/BalanceSheet/BalanceSheetService';
@Service() @Service()
export default class BalanceSheetStatementController extends BaseController{ export default class BalanceSheetStatementController extends BaseController {
@Inject() @Inject()
balanceSheetService: BalanceSheetStatementService; balanceSheetService: BalanceSheetStatementService;
@@ -32,25 +32,15 @@ export default class BalanceSheetStatementController extends BaseController{
*/ */
get balanceSheetValidationSchema(): ValidationChain[] { get balanceSheetValidationSchema(): ValidationChain[] {
return [ return [
query('accounting_method') query('accounting_method').optional().isIn(['cash', 'accural']),
.optional()
.isIn(['cash', 'accural']),
query('from_date').optional(), query('from_date').optional(),
query('to_date').optional(), query('to_date').optional(),
query('display_columns_type') query('display_columns_type').optional().isIn(['date_periods', 'total']),
.optional()
.isIn(['date_periods', 'total']),
query('display_columns_by') query('display_columns_by')
.optional({ nullable: true, checkFalsy: true }) .optional({ nullable: true, checkFalsy: true })
.isIn(['year', 'month', 'week', 'day', 'quarter']), .isIn(['year', 'month', 'week', 'day', 'quarter']),
query('number_format.no_cents') query('number_format.no_cents').optional().isBoolean().toBoolean(),
.optional() query('number_format.divide_1000').optional().isBoolean().toBoolean(),
.isBoolean()
.toBoolean(),
query('number_format.divide_1000')
.optional()
.isBoolean()
.toBoolean(),
query('account_ids').isArray().optional(), query('account_ids').isArray().optional(),
query('account_ids.*').isNumeric().toInt(), query('account_ids.*').isNumeric().toInt(),
query('none_zero').optional().isBoolean().toBoolean(), query('none_zero').optional().isBoolean().toBoolean(),
@@ -69,14 +59,20 @@ export default class BalanceSheetStatementController extends BaseController{
...filter, ...filter,
accountsIds: castArray(filter.accountsIds), accountsIds: castArray(filter.accountsIds),
}; };
const organizationName = settings.get({ group: 'organization', key: 'name' }); const organizationName = settings.get({
const baseCurrency = settings.get({ group: 'organization', key: 'base_currency' }); group: 'organization',
key: 'name',
});
const baseCurrency = settings.get({
group: 'organization',
key: 'base_currency',
});
try { try {
const { const {
data, data,
columns, columns,
query query,
} = await this.balanceSheetService.balanceSheet(tenantId, filter); } = await this.balanceSheetService.balanceSheet(tenantId, filter);
return res.status(200).send({ return res.status(200).send({
@@ -85,9 +81,9 @@ export default class BalanceSheetStatementController extends BaseController{
data: this.transfromToResponse(data), data: this.transfromToResponse(data),
columns: this.transfromToResponse(columns), columns: this.transfromToResponse(columns),
query: this.transfromToResponse(query), query: this.transfromToResponse(query),
}) });
} catch (error) { } catch (error) {
next(error); next(error);
} }
} }
}; }

View File

@@ -6,8 +6,7 @@ import { Inject, Service } from 'typedi';
import GeneralLedgerService from 'services/FinancialStatements/GeneralLedger/GeneralLedgerService'; import GeneralLedgerService from 'services/FinancialStatements/GeneralLedger/GeneralLedgerService';
@Service() @Service()
export default class GeneralLedgerReportController extends BaseController{ export default class GeneralLedgerReportController extends BaseController {
@Inject() @Inject()
generalLedgetService: GeneralLedgerService; generalLedgetService: GeneralLedgerService;
@@ -17,7 +16,8 @@ export default class GeneralLedgerReportController extends BaseController{
router() { router() {
const router = Router(); const router = Router();
router.get('/', router.get(
'/',
this.validationSchema, this.validationSchema,
this.validationResult, this.validationResult,
asyncMiddleware(this.generalLedger.bind(this)) asyncMiddleware(this.generalLedger.bind(this))
@@ -35,9 +35,9 @@ export default class GeneralLedgerReportController extends BaseController{
query('basis').optional(), query('basis').optional(),
query('number_format.no_cents').optional().isBoolean().toBoolean(), query('number_format.no_cents').optional().isBoolean().toBoolean(),
query('number_format.divide_1000').optional().isBoolean().toBoolean(), query('number_format.divide_1000').optional().isBoolean().toBoolean(),
query('none_transactions').optional().isBoolean().toBoolean(), query('none_transactions').default(true).isBoolean().toBoolean(),
query('accounts_ids').optional(), query('accounts_ids').optional().isArray({ min: 1 }),
query('accounts_ids.*').isNumeric().toInt(), query('accounts_ids.*').isInt().toInt(),
query('orderBy').optional().isIn(['created_at', 'name', 'code']), query('orderBy').optional().isIn(['created_at', 'name', 'code']),
query('order').optional().isIn(['desc', 'asc']), query('order').optional().isIn(['desc', 'asc']),
]; ];
@@ -52,15 +52,20 @@ export default class GeneralLedgerReportController extends BaseController{
const { tenantId, settings } = req; const { tenantId, settings } = req;
const filter = this.matchedQueryData(req); const filter = this.matchedQueryData(req);
const organizationName = settings.get({ group: 'organization', key: 'name' }); const organizationName = settings.get({
const baseCurrency = settings.get({ group: 'organization', key: 'base_currency' }); group: 'organization',
key: 'name',
});
const baseCurrency = settings.get({
group: 'organization',
key: 'base_currency',
});
try { try {
const { const { data, query } = await this.generalLedgetService.generalLedger(
data, tenantId,
query, filter
} = await this.generalLedgetService.generalLedger(tenantId, filter); );
return res.status(200).send({ return res.status(200).send({
organization_name: organizationName, organization_name: organizationName,
base_currency: baseCurrency, base_currency: baseCurrency,

View File

@@ -16,6 +16,13 @@ export default class InventoryAdjustmentsController extends BaseController {
router() { router() {
const router = 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( router.delete(
'/:id', '/:id',
[param('id').exists().isNumeric().toInt()], [param('id').exists().isNumeric().toInt()],
@@ -62,6 +69,7 @@ export default class InventoryAdjustmentsController extends BaseController {
.exists() .exists()
.isFloat() .isFloat()
.toInt(), .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. * Retrieve the inventory adjustments paginated list.
* @param {Request} req * @param {Request} req

View File

@@ -9,20 +9,20 @@ const balanceSheetStructure: IBalanceSheetStructureSection[] = [
{ {
name: 'Current Asset', name: 'Current Asset',
type: 'accounts_section', type: 'accounts_section',
_accountsTypesRelated: ['current_asset'], accountsTypesRelated: ['current_asset'],
}, },
{ {
name: 'Fixed Asset', name: 'Fixed Asset',
type: 'accounts_section', type: 'accounts_section',
_accountsTypesRelated: ['fixed_asset'], accountsTypesRelated: ['fixed_asset'],
}, },
{ {
name: 'Other Asset', name: 'Other Asset',
type: 'accounts_section', type: 'accounts_section',
_accountsTypesRelated: ['other_asset'], accountsTypesRelated: ['other_asset'],
}, },
], ],
_forceShow: true, alwaysShow: true,
}, },
{ {
name: 'Liabilities and Equity', name: 'Liabilities and Equity',
@@ -37,17 +37,17 @@ const balanceSheetStructure: IBalanceSheetStructureSection[] = [
{ {
name: 'Current Liability', name: 'Current Liability',
type: 'accounts_section', type: 'accounts_section',
_accountsTypesRelated: ['current_liability'], accountsTypesRelated: ['current_liability'],
}, },
{ {
name: 'Long Term Liability', name: 'Long Term Liability',
type: 'accounts_section', type: 'accounts_section',
_accountsTypesRelated: ['long_term_liability'], accountsTypesRelated: ['long_term_liability'],
}, },
{ {
name: 'Other Liability', name: 'Other Liability',
type: 'accounts_section', type: 'accounts_section',
_accountsTypesRelated: ['other_liability'], accountsTypesRelated: ['other_liability'],
}, },
], ],
}, },
@@ -55,10 +55,10 @@ const balanceSheetStructure: IBalanceSheetStructureSection[] = [
name: 'Equity', name: 'Equity',
sectionType: 'equity', sectionType: 'equity',
type: 'accounts_section', type: 'accounts_section',
_accountsTypesRelated: ['equity'], accountsTypesRelated: ['equity'],
}, },
], ],
_forceShow: true, alwaysShow: true,
}, },
]; ];

View File

@@ -9,6 +9,7 @@ exports.up = function(knex) {
table.string('reference_no').index(); table.string('reference_no').index();
table.string('description'); table.string('description');
table.integer('user_id').unsigned(); table.integer('user_id').unsigned();
table.date('published_at');
table.timestamps(); table.timestamps();
}); });
}; };

View File

@@ -37,8 +37,8 @@ export interface IBalanceSheetStructureSection {
sectionType?: string, sectionType?: string,
type: 'section' | 'accounts_section', type: 'section' | 'accounts_section',
children?: IBalanceSheetStructureSection[], children?: IBalanceSheetStructureSection[],
_accountsTypesRelated?: string[], accountsTypesRelated?: string[],
_forceShow?: boolean, alwaysShow?: boolean,
} }
export interface IBalanceSheetAccountTotal { export interface IBalanceSheetAccountTotal {
@@ -69,6 +69,6 @@ export interface IBalanceSheetSection {
total: IBalanceSheetAccountTotal, total: IBalanceSheetAccountTotal,
totalPeriods?: IBalanceSheetAccountTotal[]; totalPeriods?: IBalanceSheetAccountTotal[];
_accountsTypesRelated?: string[], accountsTypesRelated?: string[],
_forceShow?: boolean, _forceShow?: boolean,
} }

View File

@@ -1,9 +1,8 @@
type IAdjustmentTypes = 'increment' | 'decrement'; type IAdjustmentTypes = 'increment' | 'decrement';
export interface IQuickInventoryAdjustmentDTO { export interface IQuickInventoryAdjustmentDTO {
date: Date | string; date: Date | string;
type: IAdjustmentTypes, type: IAdjustmentTypes;
adjustmentAccountId: number; adjustmentAccountId: number;
reason: string; reason: string;
description: string; description: string;
@@ -11,31 +10,33 @@ export interface IQuickInventoryAdjustmentDTO {
itemId: number; itemId: number;
quantity: number; quantity: number;
cost: number; cost: number;
}; publish: boolean;
}
export interface IInventoryAdjustment { export interface IInventoryAdjustment {
id?: number, id?: number;
date: Date | string; date: Date | string;
adjustmentAccountId: number; adjustmentAccountId: number;
reason: string; reason: string;
description: string; description: string;
referenceNo: string; referenceNo: string;
inventoryDirection?: 'IN' | 'OUT', inventoryDirection?: 'IN' | 'OUT';
entries: IInventoryAdjustmentEntry[]; entries: IInventoryAdjustmentEntry[];
userId: number; userId: number;
}; publishedAt?: Date|null;
}
export interface IInventoryAdjustmentEntry { export interface IInventoryAdjustmentEntry {
id?: number, id?: number;
adjustmentId?: number, adjustmentId?: number;
index: number, index: number;
itemId: number; itemId: number;
quantity?: number; quantity?: number;
cost?: number; cost?: number;
value?: number; value?: number;
}; }
export interface IInventoryAdjustmentsFilter{ export interface IInventoryAdjustmentsFilter {
page: number, page: number;
pageSize: number, pageSize: number;
}; }

View File

@@ -20,7 +20,7 @@ export default class InventoryAdjustment extends TenantModel {
* Virtual attributes. * Virtual attributes.
*/ */
static get virtualAttributes() { static get virtualAttributes() {
return ['inventoryDirection']; return ['inventoryDirection', 'isPublished'];
} }
/** /**
@@ -30,6 +30,14 @@ export default class InventoryAdjustment extends TenantModel {
return InventoryAdjustment.getInventoryDirection(this.type); return InventoryAdjustment.getInventoryDirection(this.type);
} }
/**
* Detarmines whether the adjustment is published.
* @return {boolean}
*/
get isPublished() {
return !!this.publishedAt;
}
static getInventoryDirection(type) { static getInventoryDirection(type) {
const directions = { const directions = {
'increment': 'IN', 'increment': 'IN',

View File

@@ -324,6 +324,7 @@ export default class JournalPoster implements IJournalPoster {
transactions.forEach((transaction) => { transactions.forEach((transaction) => {
this.entries.push({ this.entries.push({
...transaction, ...transaction,
referenceTypeFormatted: transaction.referenceTypeFormatted,
account: transaction.accountId, account: transaction.accountId,
accountNormal: get(transaction, 'account.type.normal'), accountNormal: get(transaction, 'account.type.normal'),
}); });
@@ -417,7 +418,7 @@ export default class JournalPoster implements IJournalPoster {
* @param {Number} account - * @param {Number} account -
* @param {Date|String} closingDate - * @param {Date|String} closingDate -
*/ */
getTrialBalance(accountId, closingDate, dateType) { getTrialBalance(accountId, closingDate) {
const momentClosingDate = moment(closingDate); const momentClosingDate = moment(closingDate);
const result = { const result = {
credit: 0, credit: 0,
@@ -426,8 +427,8 @@ export default class JournalPoster implements IJournalPoster {
}; };
this.entries.forEach((entry) => { this.entries.forEach((entry) => {
if ( if (
(!momentClosingDate.isAfter(entry.date, dateType) && (!momentClosingDate.isAfter(entry.date, 'day') &&
!momentClosingDate.isSame(entry.date, dateType)) || !momentClosingDate.isSame(entry.date, 'day')) ||
(entry.account !== accountId && accountId) (entry.account !== accountId && accountId)
) { ) {
return; return;
@@ -478,8 +479,8 @@ export default class JournalPoster implements IJournalPoster {
accountId: number, accountId: number,
contactId: number, contactId: number,
contactType: string, contactType: string,
closingDate: Date|string, closingDate?: Date|string,
openingDate: Date|string, openingDate?: Date|string,
) { ) {
const momentClosingDate = moment(closingDate); const momentClosingDate = moment(closingDate);
const momentOpeningDate = moment(openingDate); const momentOpeningDate = moment(openingDate);

View File

@@ -85,7 +85,7 @@ export default class BalanceSheetStatement extends FinancialSheet {
* @return {IBalanceSheetAccountTotal[]} * @return {IBalanceSheetAccountTotal[]}
*/ */
private getSectionTotalPeriods( private getSectionTotalPeriods(
sections: Array<IBalanceSheetAccount|IBalanceSheetSection> sections: Array<IBalanceSheetAccount | IBalanceSheetSection>
): IBalanceSheetAccountTotal[] { ): IBalanceSheetAccountTotal[] {
return this.dateRangeSet.map((date, index) => { return this.dateRangeSet.map((date, index) => {
const amount = sumBy(sections, `totalPeriods[${index}].amount`); const amount = sumBy(sections, `totalPeriods[${index}].amount`);
@@ -238,12 +238,12 @@ export default class BalanceSheetStatement extends FinancialSheet {
name: structure.name, name: structure.name,
sectionType: structure.sectionType, sectionType: structure.sectionType,
type: structure.type, type: structure.type,
...(structure.type === 'accounts_section') ...(structure.type === 'accounts_section'
? this.structureRelatedAccountsMapper( ? this.structureRelatedAccountsMapper(
structure._accountsTypesRelated, structure.accountsTypesRelated,
accounts accounts
) )
: this.structureSectionMapper(structure, accounts), : this.structureSectionMapper(structure, accounts)),
}; };
return result; return result;
} }
@@ -259,13 +259,30 @@ export default class BalanceSheetStatement extends FinancialSheet {
): IBalanceSheetSection[] { ): IBalanceSheetSection[] {
return ( return (
reportStructure reportStructure
.map((structure: IBalanceSheetStructureSection) => .map((structure: IBalanceSheetStructureSection) => {
this.balanceSheetStructureMapper(structure, balanceSheetAccounts) const sheetSection = this.balanceSheetStructureMapper(
) structure,
// Filter the structure sections that have no children. balanceSheetAccounts
);
return [sheetSection, structure];
})
// Filter the structure sections that have no children and not always show.
.filter( .filter(
(structure: IBalanceSheetSection) => ([sheetSection, structure]: [
structure.children.length > 0 || structure._forceShow 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[]} * @return {IBalanceSheetSection[]}
*/ */
public reportData(): 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( return this.balanceSheetStructureWalker(
BalanceSheetStructure, BalanceSheetStructure,
this.accounts this.accounts

View File

@@ -1,12 +1,13 @@
import { sumBy, chain } from 'lodash'; import { sumBy, chain, omit } from 'lodash';
import { import {
IJournalEntry, IJournalEntry,
IJournalPoster, IJournalPoster,
IJournalReportEntriesGroup, IJournalReportEntriesGroup,
IJournalReportQuery, IJournalReportQuery,
IJournalReport IJournalReport,
} from "interfaces"; } from 'interfaces';
import FinancialSheet from "../FinancialSheet"; import FinancialSheet from '../FinancialSheet';
import { AccountTransaction } from 'models';
export default class JournalSheet extends FinancialSheet { export default class JournalSheet extends FinancialSheet {
tenantId: number; tenantId: number;
@@ -23,7 +24,7 @@ export default class JournalSheet extends FinancialSheet {
tenantId: number, tenantId: number,
query: IJournalReportQuery, query: IJournalReportQuery,
journal: IJournalPoster, journal: IJournalPoster,
baseCurrency: string, baseCurrency: string
) { ) {
super(); super();
@@ -34,22 +35,37 @@ export default class JournalSheet extends FinancialSheet {
this.baseCurrency = baseCurrency; 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. * Mapping journal entries groups.
* @param {IJournalEntry[]} entriesGroup - * @param {IJournalEntry[]} entriesGroup -
* @param {string} key - * @param {string} key -
* @return {IJournalReportEntriesGroup} * @return {IJournalReportEntriesGroup}
*/ */
entriesGroupMapper( entriesGroupsMapper(
entriesGroup: IJournalEntry[], entriesGroup: IJournalEntry[],
key: string, key: string
): IJournalReportEntriesGroup { ): IJournalReportEntriesGroup {
const totalCredit = sumBy(entriesGroup, 'credit'); const totalCredit = sumBy(entriesGroup, 'credit');
const totalDebit = sumBy(entriesGroup, 'debit'); const totalDebit = sumBy(entriesGroup, 'debit');
return { return {
id: key, id: key,
entries: entriesGroup, entries: this.entriesMapper(entriesGroup),
currencyCode: this.baseCurrency, currencyCode: this.baseCurrency,
@@ -69,10 +85,9 @@ export default class JournalSheet extends FinancialSheet {
entriesWalker(entries: IJournalEntry[]): IJournalReportEntriesGroup[] { entriesWalker(entries: IJournalEntry[]): IJournalReportEntriesGroup[] {
return chain(entries) return chain(entries)
.groupBy((entry) => `${entry.referenceId}-${entry.referenceType}`) .groupBy((entry) => `${entry.referenceId}-${entry.referenceType}`)
.map(( .map((entriesGroup: IJournalEntry[], key: string) =>
entriesGroup: IJournalEntry[], this.entriesGroupsMapper(entriesGroup, key)
key: string )
) => this.entriesGroupMapper(entriesGroup, key))
.value(); .value();
} }

View File

@@ -1,4 +1,3 @@
import { import {
ITrialBalanceSheetQuery, ITrialBalanceSheetQuery,
ITrialBalanceAccount, ITrialBalanceAccount,
@@ -8,7 +7,7 @@ import {
import FinancialSheet from '../FinancialSheet'; import FinancialSheet from '../FinancialSheet';
import { flatToNestedArray } from 'utils'; import { flatToNestedArray } from 'utils';
export default class TrialBalanceSheet extends FinancialSheet{ export default class TrialBalanceSheet extends FinancialSheet {
tenantId: number; tenantId: number;
query: ITrialBalanceSheetQuery; query: ITrialBalanceSheetQuery;
accounts: IAccount & { type: IAccountType }[]; accounts: IAccount & { type: IAccountType }[];
@@ -27,7 +26,7 @@ export default class TrialBalanceSheet extends FinancialSheet{
query: ITrialBalanceSheetQuery, query: ITrialBalanceSheetQuery,
accounts: IAccount & { type: IAccountType }[], accounts: IAccount & { type: IAccountType }[],
journalFinancial: any, journalFinancial: any,
baseCurrency: string, baseCurrency: string
) { ) {
super(); super();
@@ -44,11 +43,13 @@ export default class TrialBalanceSheet extends FinancialSheet{
* Account mapper. * 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); const trial = this.journalFinancial.getTrialBalanceWithDepands(account.id);
// Retrieve all entries that associated to the given account. // Retrieve all entries that associated to the given account.
const entries = this.journalFinancial.getAccountEntries(account.id) const entries = this.journalFinancial.getAccountEntries(account.id);
return { return {
id: account.id, id: account.id,
@@ -78,22 +79,28 @@ export default class TrialBalanceSheet extends FinancialSheet{
): ITrialBalanceAccount[] { ): ITrialBalanceAccount[] {
const flattenAccounts = accounts const flattenAccounts = accounts
// Mapping the trial balance accounts sections. // 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 accounts that have no transaction when `noneTransactions` is on.
.filter((trialAccount: ITrialBalanceAccount): boolean => .filter(
!(!trialAccount.hasTransactions && this.query.noneTransactions), (trialAccount: ITrialBalanceAccount): boolean =>
!(!trialAccount.hasTransactions && this.query.noneTransactions)
) )
// Filter accounts that have zero total amount when `noneZero` is on. // Filter accounts that have zero total amount when `noneZero` is on.
.filter( .filter(
(trialAccount: ITrialBalanceAccount): boolean => (trialAccount: ITrialBalanceAccount): boolean =>
!(trialAccount.credit === 0 && trialAccount.debit === 0 && this.query.noneZero) !(
trialAccount.credit === 0 &&
trialAccount.debit === 0 &&
this.query.noneZero
)
); );
return flatToNestedArray( return flatToNestedArray(flattenAccounts, {
flattenAccounts, id: 'id',
{ id: 'id', parentId: 'parentAccountId' }, parentId: 'parentAccountId',
); });
} }
/** /**

View File

@@ -1,13 +1,12 @@
import { Service, Inject } from "typedi"; import { Service, Inject } from 'typedi';
import moment from 'moment'; import moment from 'moment';
import TenancyService from 'services/Tenancy/TenancyService'; import TenancyService from 'services/Tenancy/TenancyService';
import { ITrialBalanceSheetQuery, ITrialBalanceStatement } from 'interfaces'; import { ITrialBalanceSheetQuery, ITrialBalanceStatement } from 'interfaces';
import TrialBalanceSheet from "./TrialBalanceSheet"; import TrialBalanceSheet from './TrialBalanceSheet';
import Journal from 'services/Accounting/JournalPoster'; import Journal from 'services/Accounting/JournalPoster';
@Service() @Service()
export default class TrialBalanceSheetService { export default class TrialBalanceSheetService {
@Inject() @Inject()
tenancy: TenancyService; tenancy: TenancyService;
@@ -43,9 +42,8 @@ export default class TrialBalanceSheetService {
*/ */
public async trialBalanceSheet( public async trialBalanceSheet(
tenantId: number, tenantId: number,
query: ITrialBalanceSheetQuery, query: ITrialBalanceSheetQuery
): Promise<ITrialBalanceStatement> { ): Promise<ITrialBalanceStatement> {
const filter = { const filter = {
...this.defaultQuery, ...this.defaultQuery,
...query, ...query,
@@ -57,10 +55,15 @@ export default class TrialBalanceSheetService {
// Settings tenant service. // Settings tenant service.
const settings = this.tenancy.settings(tenantId); const settings = this.tenancy.settings(tenantId);
const baseCurrency = settings.get({ group: 'organization', key: 'base_currency' }); const baseCurrency = settings.get({
group: 'organization',
this.logger.info('[trial_balance_sheet] trying to calcualte the report.', { tenantId, filter }); key: 'base_currency',
});
this.logger.info('[trial_balance_sheet] trying to calcualte the report.', {
tenantId,
filter,
});
// Retrieve all accounts on the storage. // Retrieve all accounts on the storage.
const accounts = await accountRepository.all('type'); const accounts = await accountRepository.all('type');
const accountsGraph = await accountRepository.getDependencyGraph(); const accountsGraph = await accountRepository.getDependencyGraph();
@@ -72,8 +75,11 @@ export default class TrialBalanceSheetService {
sumationCreditDebit: true, sumationCreditDebit: true,
}); });
// Transform transactions array to journal collection. // Transform transactions array to journal collection.
const transactionsJournal = Journal.fromTransactions(transactions, tenantId, accountsGraph); const transactionsJournal = Journal.fromTransactions(
transactions,
tenantId,
accountsGraph
);
// Trial balance report instance. // Trial balance report instance.
const trialBalanceInstance = new TrialBalanceSheet( const trialBalanceInstance = new TrialBalanceSheet(
tenantId, tenantId,
@@ -88,6 +94,6 @@ export default class TrialBalanceSheetService {
return { return {
data: trialBalanceSheetData, data: trialBalanceSheetData,
query: filter, query: filter,
} };
} }
} }

View File

@@ -1,5 +1,6 @@
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import { omit } from 'lodash'; import { omit } from 'lodash';
import moment from 'moment';
import { import {
EventDispatcher, EventDispatcher,
EventDispatcherInterface, EventDispatcherInterface,
@@ -54,8 +55,13 @@ export default class InventoryAdjustmentService {
authorizedUser: ISystemUser authorizedUser: ISystemUser
): IInventoryAdjustment { ): IInventoryAdjustment {
return { return {
...omit(adjustmentDTO, ['quantity', 'cost', 'itemId']), ...omit(adjustmentDTO, ['quantity', 'cost', 'itemId', 'publish']),
userId: authorizedUser.id, userId: authorizedUser.id,
...(adjustmentDTO.publish
? {
publishedAt: moment().toMySqlDateTime(),
}
: {}),
entries: [ entries: [
{ {
index: 1, index: 1,
@@ -212,6 +218,50 @@ export default class InventoryAdjustmentService {
); );
} }
/**
* Publish the inventory adjustment transaction.
* @param tenantId
* @param inventoryAdjustmentId
*/
async publishInventoryAdjustment(
tenantId: number,
inventoryAdjustmentId: number
): Promise<void> {
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. * Retrieve the inventory adjustments paginated list.
* @param {number} tenantId * @param {number} tenantId
@@ -246,7 +296,7 @@ export default class InventoryAdjustmentService {
async writeInventoryTransactions( async writeInventoryTransactions(
tenantId: number, tenantId: number,
inventoryAdjustment: IInventoryAdjustment, inventoryAdjustment: IInventoryAdjustment,
override: boolean = false, override: boolean = false
): Promise<void> { ): Promise<void> {
// Gets the next inventory lot number. // Gets the next inventory lot number.
const lotNumber = this.inventoryService.getNextLotNumber(tenantId); const lotNumber = this.inventoryService.getNextLotNumber(tenantId);

View File

@@ -27,6 +27,9 @@ export default class InventoryAdjustmentsSubscriber {
tenantId, tenantId,
inventoryAdjustment, inventoryAdjustment,
}) { }) {
// Can't continue if the inventory adjustment is not published.
if (!inventoryAdjustment.isPublished) { return; }
await this.inventoryAdjustment.writeInventoryTransactions( await this.inventoryAdjustment.writeInventoryTransactions(
tenantId, tenantId,
inventoryAdjustment inventoryAdjustment
@@ -39,11 +42,29 @@ export default class InventoryAdjustmentsSubscriber {
@On(events.inventoryAdjustment.onDeleted) @On(events.inventoryAdjustment.onDeleted)
async handleRevertInventoryTransactionsOnceDeleted({ async handleRevertInventoryTransactionsOnceDeleted({
tenantId, tenantId,
inventoryAdjustmentId inventoryAdjustmentId,
oldInventoryTransaction,
}) { }) {
// Can't continue if the inventory adjustment is not published.
if (!oldInventoryTransaction.isPublished) { return; }
await this.inventoryAdjustment.revertInventoryTransactions( await this.inventoryAdjustment.revertInventoryTransactions(
tenantId, tenantId,
inventoryAdjustmentId, inventoryAdjustmentId,
); );
} }
/**
* Handles writing inventory transactions once the quick adjustment created.
*/
@On(events.inventoryAdjustment.onPublished)
async handleWriteInventoryTransactionsOncePublished({
tenantId,
inventoryAdjustment,
}) {
await this.inventoryAdjustment.writeInventoryTransactions(
tenantId,
inventoryAdjustment
)
}
} }

View File

@@ -193,9 +193,13 @@ export default {
onComputeItemCostJobCompleted: 'onComputeItemCostJobCompleted' onComputeItemCostJobCompleted: 'onComputeItemCostJobCompleted'
}, },
/**
* Inventory adjustment service.
*/
inventoryAdjustment: { inventoryAdjustment: {
onCreated: 'onInventoryAdjustmentCreated', onCreated: 'onInventoryAdjustmentCreated',
onQuickCreated: 'onInventoryAdjustmentQuickCreated', onQuickCreated: 'onInventoryAdjustmentQuickCreated',
onDeleted: 'onInventoryAdjustmentDeleted', onDeleted: 'onInventoryAdjustmentDeleted',
onPublished: 'onInventoryAdjustmentPublished',
} }
} }