Compare commits

..

26 Commits

Author SHA1 Message Date
Ahmed Bouhuolia
6991ec7780 Merge pull request #997 from bigcapitalhq/fix/model-timestamps-missing-columns
fix(models): remove timestamps from models where tables lack createdAt/updatedAt columns
2026-02-26 05:59:49 +02:00
Ahmed Bouhuolia
ad252d2e4a fix(models): remove timestamps from models where tables lack createdAt/updatedAt columns
Add withDateSessionMixin for proper timestamp handling and fix models
to return empty timestamps array when database tables don't have
created_at/updated_at columns. This prevents ORM insert/update errors.

Models updated:
- Branch, Role, RolePermission, ViewColumn, ViewRole
- InventoryAdjustment, InventoryAdjustmentEntry, InventoryTransactionMeta
- BillLandedCostEntry, CreditNote, CreditNoteAppliedInvoice, RefundCreditNote
- PaymentReceived, SaleInvoice, SaleReceipt, Item, ItemEntry
- RefundVendorCredit, VendorCreditAppliedBill
- ItemWarehouseQuantity, Warehouse, WarehouseTransfer, WarehouseTransferEntry
- Setting, TenantMetadataModel, TenantUser

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 05:57:55 +02:00
Ahmed Bouhuolia
12eb8c32dc Merge pull request #996 from bigcapitalhq/fix/account-type-not-selected-banking-edit
fix(webapp): account type not pre-selected when editing from banking page
2026-02-25 22:12:38 +02:00
Ahmed Bouhuolia
ca68918caa fix(webapp): account type not pre-selected when editing from banking page
Change 'id' to 'accountId' in CashflowAccountsGrid to match the
AccountDialogProvider expected payload. The dialog provider expects
'accountId' to fetch account details and populate the form.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 22:08:58 +02:00
Ahmed Bouhuolia
fa1acc9773 Merge pull request #995 from bigcapitalhq/fix/date-formats-banking-transactions
fix: use organization date format in banking transactions and financial reports
2026-02-25 20:35:23 +02:00
Ahmed Bouhuolia
558fc29962 fix: use organization date format in banking transactions and reports
- Add OrganizationSettingsModule to BankingTransactionsModule
- Update GetBankAccountTransactions to pass dateFormat from settings
- Add meta support to FinancialSheet base class
- Refactor TransactionsByReference to use IFinancialReportMeta
- Update frontend to use server-provided formatted_date
2026-02-25 20:33:31 +02:00
Ahmed Bouhuolia
8a32e13a79 Merge pull request #994 from bigcapitalhq/feat/account-settings-service
fix(accounts): add account settings service
2026-02-25 19:29:45 +02:00
Ahmed Bouhuolia
d35915b16b feat(accounts): add account settings service
- Add AccountsSettingsService for managing account-related settings
- Update validators, create and edit services to use settings
- Add constants for account configuration
- Update frontend utils and translations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 19:27:53 +02:00
Ahmed Bouhuolia
b5d1a2c9d0 Merge pull request #992 from bigcapitalhq/fix/organization-date-formats-and-address-fields
fix(financial-statements): use stored date format settings in all reports
2026-02-25 07:10:41 +02:00
Ahmed Bouhuolia
f5e74f3e88 fix(inventory): update baseCurrency retrieval in InventoryDetailsService
- Replace tenantMetadata.baseCurrency with meta.baseCurrency in InventoryDetailsService to ensure consistent currency usage across reports.
2026-02-25 07:10:09 +02:00
Ahmed Bouhuolia
c83132b867 fix(financial-statements): use stored date format settings in all reports
- Replace hardcoded date formats ('YYYY/MM/DD') in all Meta classes with meta.dateFormat
- Add IFinancialReportMeta interface with baseCurrency and dateFormat fields
- Add DEFAULT_REPORT_META constant with default date format 'YYYY MMM DD'
- Update all sheet classes to accept IFinancialReportMeta parameter
- Update all services to pass dateFormat from meta to sheet constructors
- Fix customer/vendor transactions date formatting in table rows
- Add TenancyModule to SalesTaxLiabilityModule for dependency injection
- Make dateFormat optional in JournalSheet and GeneralLedger query types

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 07:05:31 +02:00
Ahmed Bouhuolia
88ff5db0f3 Merge pull request #989 from bigcapitalhq/fix/organization-date-formats-and-address-fields
fix(organization): align date formats and fix address field naming
2026-02-24 22:45:10 +02:00
Ahmed Bouhuolia
f35e85c3d2 fix(organization): align date formats and fix address field naming
- Fix date format mismatch between Miscellaneous and Organization constants
- Fix default date format casing ('DD MMM yyyy' -> 'DD MMM YYYY')
- Rename address fields from address_1/address_2 to address1/address2

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 22:42:29 +02:00
Ahmed Bouhuolia
29decf9c5a Merge pull request #987 from bigcapitalhq/feat/contact-address-country-fields
fix: country and address fields of customer and vendor forms
2026-02-24 20:55:23 +02:00
Ahmed Bouhuolia
f149ff43b4 feat(contacts): add country field to customer and vendor address forms
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 20:53:14 +02:00
Ahmed Bouhuolia
fb05af8c00 Merge pull request #979 from yk-a11y/fix/credit-note-apply-invoice-validation
fix: validate credit note per-entry amount against each invoice due amount
2026-02-24 02:55:47 +02:00
Ahmed Bouhuolia
688b1bfb56 fix(server): add invoices Map for validateInvoicesRemainingAmount 2026-02-24 02:52:28 +02:00
Ahmed Bouhuolia
0f8147daff Merge branch 'develop' into fix/credit-note-apply-invoice-validation 2026-02-24 02:42:23 +02:00
Ahmed Bouhuolia
96b24d4fb9 Merge pull request #982 from bigcapitalhq/fix/credit-notes-applied-invoice-delete
fix: Add DELETE endpoint for credit notes applied invoices
2026-02-24 02:17:37 +02:00
Ahmed Bouhuolia
2a87103bc8 fix: Add DELETE endpoint for credit notes applied invoices
- Add missing DELETE /credit-notes/applied-invoices/:id endpoint
- Fix CreditNotesApplyInvoice controller to use correct service methods
- Add missing GetCreditNoteAssociatedInvoicesToApply endpoint
- Add proper DTO for ApplyCreditNoteToInvoices
- Update frontend creditNote hook to use correct API paths

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 02:15:32 +02:00
Ahmed Bouhuolia
238b60144f Merge pull request #981 from bigcapitalhq/fix/register-verify-dark-mode
fix: add dark mode support to email confirmation UI
2026-02-23 01:10:50 +02:00
Ahmed Bouhuolia
fcee85e358 fix: add dark mode support to email confirmation UI
Refactored RegisterVerify component to use xstyled for styling
with proper dark mode color values instead of hardcoded light theme colors.
2026-02-23 00:48:32 +02:00
Ahmed Bouhuolia
64a10053e3 Merge pull request #980 from bigcapitalhq/fix-signup-verification
fix: signup confirmation
2026-02-23 00:39:45 +02:00
Ahmed Bouhuolia
ce9f2a238f fix: signup confirmation 2026-02-23 00:37:56 +02:00
Yong ke Weng
75b98c39d8 fix: validate credit note per-entry amount against each invoice due amount
The `validateInvoicesRemainingAmount` method was incorrectly comparing the
total credit amount (sum of all entries) against each individual invoice's
due amount. This caused valid credit note applications to be rejected when
applying to multiple invoices where the total exceeded any single invoice's
due amount.

Changed the validation to compare each invoice's due amount against only the
specific entry amount being applied to that invoice.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 09:50:39 -05:00
Ahmed Bouhuolia
80e545072d Merge pull request #975 from bigcapitalhq/fix/localize-financial-reports
fix: localize hardcoded strings in financial reports
2026-02-18 22:09:05 +02:00
139 changed files with 732 additions and 288 deletions

View File

@@ -1,5 +1,6 @@
import { QueryBuilder, Model } from 'objection'; import { QueryBuilder, Model, mixin } from 'objection';
import { ModelHasRelationsError } from '@/common/exceptions/ModelHasRelations.exception'; import { ModelHasRelationsError } from '@/common/exceptions/ModelHasRelations.exception';
import { withDateSessionMixin } from './withDateSessionMixin';
interface PaginationResult<M extends Model> { interface PaginationResult<M extends Model> {
results: M[]; results: M[];
@@ -69,6 +70,7 @@ export class PaginationQueryBuilder<
dependentRelationNames.forEach((relationName: string) => { dependentRelationNames.forEach((relationName: string) => {
recordQuery.withGraphFetched(relationName); recordQuery.withGraphFetched(relationName);
}); });
const record = await recordQuery; const record = await recordQuery;
const hasRelations = dependentRelationNames.some((name) => { const hasRelations = dependentRelationNames.some((name) => {
@@ -97,7 +99,7 @@ export class BaseQueryBuilder<
} }
} }
export class BaseModel extends Model { export class BaseModel extends mixin(Model, [withDateSessionMixin]) {
public readonly id: number; public readonly id: number;
public readonly tableName: string; public readonly tableName: string;

View File

@@ -0,0 +1,40 @@
import * as moment from 'moment';
import { Model } from 'objection';
type Constructor<T = {}> = new (...args: any[]) => T;
export const withDateSessionMixin = <T extends Constructor<Model>>(BaseModel: T) => {
return class DateSession extends BaseModel {
constructor(...args: any[]) {
super(...args);
}
get timestamps() {
return [];
}
$beforeUpdate(opt, context) {
const maybePromise = super.$beforeUpdate(opt, context);
return Promise.resolve(maybePromise).then(() => {
const key = this.timestamps[1];
if (key && !this[key]) {
this[key] = moment().format('YYYY/MM/DD HH:mm:ss');
}
});
}
$beforeInsert(context) {
const maybePromise = super.$beforeInsert(context);
return Promise.resolve(maybePromise).then(() => {
const key = this.timestamps[0];
if (key && !this[key]) {
this[key] = moment().format('YYYY/MM/DD HH:mm:ss');
}
});
}
}
}

View File

@@ -21,6 +21,7 @@ import { AccountsExportable } from './AccountsExportable.service';
import { AccountsImportable } from './AccountsImportable.service'; import { AccountsImportable } from './AccountsImportable.service';
import { BulkDeleteAccountsService } from './BulkDeleteAccounts.service'; import { BulkDeleteAccountsService } from './BulkDeleteAccounts.service';
import { ValidateBulkDeleteAccountsService } from './ValidateBulkDeleteAccounts.service'; import { ValidateBulkDeleteAccountsService } from './ValidateBulkDeleteAccounts.service';
import { AccountsSettingsService } from './AccountsSettings.service';
const models = [RegisterTenancyModel(BankAccount)]; const models = [RegisterTenancyModel(BankAccount)];
@@ -29,6 +30,7 @@ const models = [RegisterTenancyModel(BankAccount)];
controllers: [AccountsController], controllers: [AccountsController],
providers: [ providers: [
AccountsApplication, AccountsApplication,
AccountsSettingsService,
CreateAccountService, CreateAccountService,
TenancyContext, TenancyContext,
CommandAccountValidators, CommandAccountValidators,
@@ -49,9 +51,10 @@ const models = [RegisterTenancyModel(BankAccount)];
exports: [ exports: [
AccountRepository, AccountRepository,
CreateAccountService, CreateAccountService,
AccountsSettingsService,
...models, ...models,
AccountsExportable, AccountsExportable,
AccountsImportable AccountsImportable,
], ],
}) })
export class AccountsModule {} export class AccountsModule {}

View File

@@ -0,0 +1,33 @@
import { Inject, Injectable } from '@nestjs/common';
import { SettingsStore } from '../Settings/SettingsStore';
import { SETTINGS_PROVIDER } from '../Settings/Settings.types';
export interface IAccountsSettings {
accountCodeRequired: boolean;
accountCodeUnique: boolean;
}
@Injectable()
export class AccountsSettingsService {
constructor(
@Inject(SETTINGS_PROVIDER)
private readonly settingsStore: () => SettingsStore,
) {}
/**
* Retrieves account settings (account code required, account code unique).
*/
public async getAccountsSettings(): Promise<IAccountsSettings> {
const settingsStore = await this.settingsStore();
return {
accountCodeRequired: settingsStore.get(
{ group: 'accounts', key: 'account_code_required' },
false,
),
accountCodeUnique: settingsStore.get(
{ group: 'accounts', key: 'account_code_unique' },
true,
),
};
}
}

View File

@@ -106,6 +106,20 @@ export class CommandAccountValidators {
} }
} }
/**
* Throws error if account code is missing or blank when required.
* @param {string|undefined} code - Account code.
*/
public validateAccountCodeRequiredOrThrow(code: string | undefined) {
const trimmed = typeof code === 'string' ? code.trim() : '';
if (!trimmed) {
throw new ServiceError(
ERRORS.ACCOUNT_CODE_REQUIRED,
'Account code is required.',
);
}
}
/** /**
* Validates the account name uniquiness. * Validates the account name uniquiness.
* @param {string} accountName - Account name. * @param {string} accountName - Account name.

View File

@@ -15,6 +15,7 @@ import { events } from '@/common/events/events';
import { CreateAccountDTO } from './CreateAccount.dto'; import { CreateAccountDTO } from './CreateAccount.dto';
import { PartialModelObject } from 'objection'; import { PartialModelObject } from 'objection';
import { TenantModelProxy } from '../System/models/TenantBaseModel'; import { TenantModelProxy } from '../System/models/TenantBaseModel';
import { AccountsSettingsService } from './AccountsSettings.service';
@Injectable() @Injectable()
export class CreateAccountService { export class CreateAccountService {
@@ -32,6 +33,7 @@ export class CreateAccountService {
private readonly uow: UnitOfWork, private readonly uow: UnitOfWork,
private readonly validator: CommandAccountValidators, private readonly validator: CommandAccountValidators,
private readonly tenancyContext: TenancyContext, private readonly tenancyContext: TenancyContext,
private readonly accountsSettings: AccountsSettingsService,
) {} ) {}
/** /**
@@ -43,14 +45,21 @@ export class CreateAccountService {
baseCurrency: string, baseCurrency: string,
params?: CreateAccountParams, params?: CreateAccountParams,
) => { ) => {
const { accountCodeRequired, accountCodeUnique } =
await this.accountsSettings.getAccountsSettings();
// Validate account code required when setting is enabled.
if (accountCodeRequired) {
this.validator.validateAccountCodeRequiredOrThrow(accountDTO.code);
}
// Validate the account code uniquiness when setting is enabled.
if (accountCodeUnique && accountDTO.code?.trim()) {
await this.validator.isAccountCodeUniqueOrThrowError(accountDTO.code);
}
// Validate account name uniquiness. // Validate account name uniquiness.
if (!params.ignoreUniqueName) { if (!params.ignoreUniqueName) {
await this.validator.validateAccountNameUniquiness(accountDTO.name); await this.validator.validateAccountNameUniquiness(accountDTO.name);
} }
// Validate the account code uniquiness.
if (accountDTO.code) {
await this.validator.isAccountCodeUniqueOrThrowError(accountDTO.code);
}
// Retrieve the account type meta or throw service error if not found. // Retrieve the account type meta or throw service error if not found.
this.validator.getAccountTypeOrThrowError(accountDTO.accountType); this.validator.getAccountTypeOrThrowError(accountDTO.accountType);

View File

@@ -7,6 +7,7 @@ import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
import { events } from '@/common/events/events'; import { events } from '@/common/events/events';
import { EditAccountDTO } from './EditAccount.dto'; import { EditAccountDTO } from './EditAccount.dto';
import { TenantModelProxy } from '../System/models/TenantBaseModel'; import { TenantModelProxy } from '../System/models/TenantBaseModel';
import { AccountsSettingsService } from './AccountsSettings.service';
@Injectable() @Injectable()
export class EditAccount { export class EditAccount {
@@ -17,7 +18,8 @@ export class EditAccount {
@Inject(Account.name) @Inject(Account.name)
private readonly accountModel: TenantModelProxy<typeof Account>, private readonly accountModel: TenantModelProxy<typeof Account>,
) { } private readonly accountsSettings: AccountsSettingsService,
) {}
/** /**
* Authorize the account editing. * Authorize the account editing.
@@ -30,6 +32,24 @@ export class EditAccount {
accountDTO: EditAccountDTO, accountDTO: EditAccountDTO,
oldAccount: Account, oldAccount: Account,
) => { ) => {
const { accountCodeRequired, accountCodeUnique } =
await this.accountsSettings.getAccountsSettings();
// Validate account code required when setting is enabled.
if (accountCodeRequired) {
this.validator.validateAccountCodeRequiredOrThrow(accountDTO.code);
}
// Validate the account code uniquiness when setting is enabled.
if (
accountCodeUnique &&
accountDTO.code?.trim() &&
accountDTO.code !== oldAccount.code
) {
await this.validator.isAccountCodeUniqueOrThrowError(
accountDTO.code,
oldAccount.id,
);
}
// Validate account name uniquiness. // Validate account name uniquiness.
await this.validator.validateAccountNameUniquiness( await this.validator.validateAccountNameUniquiness(
accountDTO.name, accountDTO.name,
@@ -40,13 +60,6 @@ export class EditAccount {
oldAccount, oldAccount,
accountDTO, accountDTO,
); );
// Validate the account code not exists on the storage.
if (accountDTO.code && accountDTO.code !== oldAccount.code) {
await this.validator.isAccountCodeUniqueOrThrowError(
accountDTO.code,
oldAccount.id,
);
}
// Retrieve the parent account of throw not found service error. // Retrieve the parent account of throw not found service error.
if (accountDTO.parentAccountId) { if (accountDTO.parentAccountId) {
const parentAccount = await this.validator.getParentAccountOrThrowError( const parentAccount = await this.validator.getParentAccountOrThrowError(

View File

@@ -3,6 +3,7 @@ export const ERRORS = {
ACCOUNT_TYPE_NOT_FOUND: 'account_type_not_found', ACCOUNT_TYPE_NOT_FOUND: 'account_type_not_found',
PARENT_ACCOUNT_NOT_FOUND: 'parent_account_not_found', PARENT_ACCOUNT_NOT_FOUND: 'parent_account_not_found',
ACCOUNT_CODE_NOT_UNIQUE: 'account_code_not_unique', ACCOUNT_CODE_NOT_UNIQUE: 'account_code_not_unique',
ACCOUNT_CODE_REQUIRED: 'account_code_required',
ACCOUNT_NAME_NOT_UNIQUE: 'account_name_not_unqiue', ACCOUNT_NAME_NOT_UNIQUE: 'account_name_not_unqiue',
PARENT_ACCOUNT_HAS_DIFFERENT_TYPE: 'parent_has_different_type', PARENT_ACCOUNT_HAS_DIFFERENT_TYPE: 'parent_has_different_type',
ACCOUNT_TYPE_NOT_ALLOWED_TO_CHANGE: 'account_type_not_allowed_to_changed', ACCOUNT_TYPE_NOT_ALLOWED_TO_CHANGE: 'account_type_not_allowed_to_changed',

View File

@@ -1,5 +1,5 @@
import { ModuleRef } from '@nestjs/core'; import { ModuleRef } from '@nestjs/core';
import bluebird from 'bluebird'; import * as bluebird from 'bluebird';
import { Knex } from 'knex'; import { Knex } from 'knex';
import { import {
validateLinkModelEntryExists, validateLinkModelEntryExists,
@@ -53,7 +53,8 @@ export class LinkAttachment {
const foundLinkModel = await LinkModel().query(trx).findById(modelId); const foundLinkModel = await LinkModel().query(trx).findById(modelId);
validateLinkModelEntryExists(foundLinkModel); validateLinkModelEntryExists(foundLinkModel);
const foundLinks = await this.documentLinkModel().query(trx) const foundLinks = await this.documentLinkModel()
.query(trx)
.where('modelRef', modelRef) .where('modelRef', modelRef)
.where('modelId', modelId) .where('modelId', modelId)
.where('documentId', foundFile.id); .where('documentId', foundFile.id);

View File

@@ -65,7 +65,7 @@ export class AuthController {
return this.authApp.signUp(signupDto); return this.authApp.signUp(signupDto);
} }
@Post('/signup/confirm') @Post('/signup/verify')
@ApiOperation({ summary: 'Confirm user signup' }) @ApiOperation({ summary: 'Confirm user signup' })
@ApiBody({ @ApiBody({
schema: { schema: {

View File

@@ -7,17 +7,13 @@ import {
import { GetAuthenticatedAccount } from './queries/GetAuthedAccount.service'; import { GetAuthenticatedAccount } from './queries/GetAuthedAccount.service';
import { Controller, Get, Post } from '@nestjs/common'; import { Controller, Get, Post } from '@nestjs/common';
import { Throttle } from '@nestjs/throttler'; import { Throttle } from '@nestjs/throttler';
import { IgnoreTenantSeededRoute } from '../Tenancy/EnsureTenantIsSeeded.guards'; import { TenantAgnosticRoute } from '../Tenancy/TenancyGlobal.guard';
import { IgnoreTenantInitializedRoute } from '../Tenancy/EnsureTenantIsInitialized.guard';
import { AuthenticationApplication } from './AuthApplication.sevice'; import { AuthenticationApplication } from './AuthApplication.sevice';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
import { IgnoreUserVerifiedRoute } from './guards/EnsureUserVerified.guard'; import { IgnoreUserVerifiedRoute } from './guards/EnsureUserVerified.guard';
@Controller('/auth') @Controller('/auth')
@ApiTags('Auth') @ApiTags('Auth')
@ApiExcludeController() @TenantAgnosticRoute()
@IgnoreTenantSeededRoute()
@IgnoreTenantInitializedRoute()
@IgnoreUserVerifiedRoute() @IgnoreUserVerifiedRoute()
@Throttle({ auth: {} }) @Throttle({ auth: {} })
export class AuthedController { export class AuthedController {

View File

@@ -13,7 +13,6 @@ import {
IAuthSignedUpEventPayload, IAuthSignedUpEventPayload,
IAuthSigningUpEventPayload, IAuthSigningUpEventPayload,
} from '../Auth.interfaces'; } from '../Auth.interfaces';
import { defaultTo } from 'ramda';
import { ERRORS } from '../Auth.constants'; import { ERRORS } from '../Auth.constants';
import { hashPassword } from '../Auth.utils'; import { hashPassword } from '../Auth.utils';
import { ClsService } from 'nestjs-cls'; import { ClsService } from 'nestjs-cls';
@@ -51,7 +50,7 @@ export class AuthSignupService {
const signupConfirmation = this.configService.get('signupConfirmation'); const signupConfirmation = this.configService.get('signupConfirmation');
const verifyTokenCrypto = crypto.randomBytes(64).toString('hex'); const verifyTokenCrypto = crypto.randomBytes(64).toString('hex');
const verifiedEnabed = defaultTo(signupConfirmation.enabled, false); const verifiedEnabed = signupConfirmation.enabled ?? false;
const verifyToken = verifiedEnabed ? verifyTokenCrypto : ''; const verifyToken = verifiedEnabed ? verifyTokenCrypto : '';
const verified = !verifiedEnabed; const verified = !verifiedEnabed;

View File

@@ -4,7 +4,6 @@ import { SystemUser } from '@/modules/System/models/SystemUser';
import { ServiceError } from '@/modules/Items/ServiceError'; import { ServiceError } from '@/modules/Items/ServiceError';
import { ERRORS } from '../Auth.constants'; import { ERRORS } from '../Auth.constants';
import { events } from '@/common/events/events'; import { events } from '@/common/events/events';
import { ModelObject } from 'objection';
import { ISignUpConfigmResendedEventPayload } from '../Auth.interfaces'; import { ISignUpConfigmResendedEventPayload } from '../Auth.interfaces';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';

View File

@@ -24,6 +24,7 @@ import { GetBankAccountsService } from './queries/GetBankAccounts.service';
import { DynamicListModule } from '../DynamicListing/DynamicList.module'; import { DynamicListModule } from '../DynamicListing/DynamicList.module';
import { BankAccount } from './models/BankAccount'; import { BankAccount } from './models/BankAccount';
import { LedgerModule } from '../Ledger/Ledger.module'; import { LedgerModule } from '../Ledger/Ledger.module';
import { TenancyModule } from '../Tenancy/Tenancy.module';
import { GetBankAccountTransactionsService } from './queries/GetBankAccountTransactions/GetBankAccountTransactions.service'; import { GetBankAccountTransactionsService } from './queries/GetBankAccountTransactions/GetBankAccountTransactions.service';
import { GetBankAccountTransactionsRepository } from './queries/GetBankAccountTransactions/GetBankAccountTransactionsRepo.service'; import { GetBankAccountTransactionsRepository } from './queries/GetBankAccountTransactions/GetBankAccountTransactionsRepo.service';
import { GetUncategorizedTransactions } from './queries/GetUncategorizedTransactions'; import { GetUncategorizedTransactions } from './queries/GetUncategorizedTransactions';
@@ -46,6 +47,7 @@ const models = [
LedgerModule, LedgerModule,
BranchesModule, BranchesModule,
DynamicListModule, DynamicListModule,
TenancyModule,
...models, ...models,
], ],
controllers: [ controllers: [

View File

@@ -4,12 +4,14 @@ import { GetBankAccountTransactionsRepository } from './GetBankAccountTransactio
import { GetBankAccountTransactions } from './GetBankAccountTransactions'; import { GetBankAccountTransactions } from './GetBankAccountTransactions';
import { GetBankTransactionsQueryDto } from '../../dtos/GetBankTranasctionsQuery.dto'; import { GetBankTransactionsQueryDto } from '../../dtos/GetBankTranasctionsQuery.dto';
import { I18nService } from 'nestjs-i18n'; import { I18nService } from 'nestjs-i18n';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
@Injectable() @Injectable()
export class GetBankAccountTransactionsService { export class GetBankAccountTransactionsService {
constructor( constructor(
private readonly getBankAccountTransactionsRepository: GetBankAccountTransactionsRepository, private readonly getBankAccountTransactionsRepository: GetBankAccountTransactionsRepository,
private readonly i18nService: I18nService private readonly i18nService: I18nService,
private readonly tenancyContext: TenancyContext,
) {} ) {}
/** /**
@@ -28,11 +30,16 @@ export class GetBankAccountTransactionsService {
await this.getBankAccountTransactionsRepository.asyncInit(); await this.getBankAccountTransactionsRepository.asyncInit();
// Retrieve the tenant metadata to get the date format.
const tenantMetadata = await this.tenancyContext.getTenantMetadata();
const dateFormat = tenantMetadata?.dateFormat;
// Retrieve the computed report. // Retrieve the computed report.
const report = new GetBankAccountTransactions( const report = new GetBankAccountTransactions(
this.getBankAccountTransactionsRepository, this.getBankAccountTransactionsRepository,
parsedQuery, parsedQuery,
this.i18nService this.i18nService,
dateFormat,
); );
const transactions = report.reportData(); const transactions = report.reportData();
const pagination = this.getBankAccountTransactionsRepository.pagination; const pagination = this.getBankAccountTransactionsRepository.pagination;

View File

@@ -24,17 +24,20 @@ export class GetBankAccountTransactions extends FinancialSheet {
* @param {IAccountTransaction[]} transactions - * @param {IAccountTransaction[]} transactions -
* @param {number} openingBalance - * @param {number} openingBalance -
* @param {ICashflowAccountTransactionsQuery} query - * @param {ICashflowAccountTransactionsQuery} query -
* @param {string} dateFormat - The date format from organization settings.
*/ */
constructor( constructor(
repo: GetBankAccountTransactionsRepository, repo: GetBankAccountTransactionsRepository,
query: ICashflowAccountTransactionsQuery, query: ICashflowAccountTransactionsQuery,
i18n: I18nService, i18n: I18nService,
dateFormat?: string,
) { ) {
super(); super();
this.repo = repo; this.repo = repo;
this.query = query; this.query = query;
this.i18n = i18n; this.i18n = i18n;
this.dateFormat = dateFormat || this.dateFormat;
this.runningBalance = runningBalance(this.repo.openingBalance); this.runningBalance = runningBalance(this.repo.openingBalance);
} }
@@ -98,7 +101,7 @@ export class GetBankAccountTransactions extends FinancialSheet {
return { return {
date: transaction.date, date: transaction.date,
formattedDate: moment(transaction.date).format('YYYY-MM-DD'), formattedDate: this.getDateFormatted(transaction.date),
withdrawal: transaction.credit, withdrawal: transaction.credit,
deposit: transaction.debit, deposit: transaction.debit,

View File

@@ -13,6 +13,13 @@ export class BillLandedCostEntry extends BaseModel {
return 'bill_located_cost_entries'; return 'bill_located_cost_entries';
} }
/**
* Timestamps columns.
*/
get timestamps() {
return [];
}
/** /**
* Relationship mapping. * Relationship mapping.
*/ */

View File

@@ -29,7 +29,7 @@ export class Branch extends BaseModel{
* Timestamps columns. * Timestamps columns.
*/ */
get timestamps() { get timestamps() {
return ['created_at', 'updated_at']; return ['createdAt', 'updatedAt'];
} }
/** /**

View File

@@ -10,7 +10,7 @@ export interface IContactAddress {
billingAddressCity: string; billingAddressCity: string;
billingAddressCountry: string; billingAddressCountry: string;
billingAddressEmail: string; billingAddressEmail: string;
billingAddressZipcode: string; billingAddressPostcode: string;
billingAddressPhone: string; billingAddressPhone: string;
billingAddressState: string; billingAddressState: string;
@@ -19,7 +19,7 @@ export interface IContactAddress {
shippingAddressCity: string; shippingAddressCity: string;
shippingAddressCountry: string; shippingAddressCountry: string;
shippingAddressEmail: string; shippingAddressEmail: string;
shippingAddressZipcode: string; shippingAddressPostcode: string;
shippingAddressPhone: string; shippingAddressPhone: string;
shippingAddressState: string; shippingAddressState: string;
} }
@@ -29,7 +29,7 @@ export interface IContactAddressDTO {
billingAddressCity?: string; billingAddressCity?: string;
billingAddressCountry?: string; billingAddressCountry?: string;
billingAddressEmail?: string; billingAddressEmail?: string;
billingAddressZipcode?: string; billingAddressPostcode?: string;
billingAddressPhone?: string; billingAddressPhone?: string;
billingAddressState?: string; billingAddressState?: string;
@@ -38,7 +38,7 @@ export interface IContactAddressDTO {
shippingAddressCity?: string; shippingAddressCity?: string;
shippingAddressCountry?: string; shippingAddressCountry?: string;
shippingAddressEmail?: string; shippingAddressEmail?: string;
shippingAddressZipcode?: string; shippingAddressPostcode?: string;
shippingAddressPhone?: string; shippingAddressPhone?: string;
shippingAddressState?: string; shippingAddressState?: string;
} }

View File

@@ -34,7 +34,7 @@ export class RefundCreditNote extends BaseModel {
* Timestamps columns. * Timestamps columns.
*/ */
get timestamps() { get timestamps() {
return ['created_at', 'updated_at']; return ['createdAt', 'updatedAt'];
} }
/** /**

View File

@@ -56,7 +56,7 @@ export class CreditNote extends TenantBaseModel {
* Timestamps columns. * Timestamps columns.
*/ */
get timestamps() { get timestamps() {
return ['created_at', 'updated_at']; return ['createdAt', 'updatedAt'];
} }
/** /**

View File

@@ -1,6 +1,7 @@
import { import {
Body, Body,
Controller, Controller,
Delete,
Get, Get,
Param, Param,
Post, Post,
@@ -14,6 +15,10 @@ import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard'; import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types'; import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { CreditNoteAction } from '../CreditNotes/types/CreditNotes.types'; import { CreditNoteAction } from '../CreditNotes/types/CreditNotes.types';
import { GetCreditNoteAssociatedInvoicesToApply } from './queries/GetCreditNoteAssociatedInvoicesToApply.service';
import { CreditNoteApplyToInvoices } from './commands/CreditNoteApplyToInvoices.service';
import { DeleteCreditNoteApplyToInvoices } from './commands/DeleteCreditNoteApplyToInvoices.service';
import { ApplyCreditNoteToInvoicesDto } from './dtos/ApplyCreditNoteToInvoices.dto';
@Controller('credit-notes') @Controller('credit-notes')
@ApiTags('Credit Notes Apply Invoice') @ApiTags('Credit Notes Apply Invoice')
@@ -22,6 +27,9 @@ import { CreditNoteAction } from '../CreditNotes/types/CreditNotes.types';
export class CreditNotesApplyInvoiceController { export class CreditNotesApplyInvoiceController {
constructor( constructor(
private readonly getCreditNoteAssociatedAppliedInvoicesService: GetCreditNoteAssociatedAppliedInvoices, private readonly getCreditNoteAssociatedAppliedInvoicesService: GetCreditNoteAssociatedAppliedInvoices,
private readonly getCreditNoteAssociatedInvoicesToApplyService: GetCreditNoteAssociatedInvoicesToApply,
private readonly creditNoteApplyToInvoicesService: CreditNoteApplyToInvoices,
private readonly deleteCreditNoteApplyToInvoicesService: DeleteCreditNoteApplyToInvoices,
) {} ) {}
@Get(':creditNoteId/applied-invoices') @Get(':creditNoteId/applied-invoices')
@@ -39,6 +47,23 @@ export class CreditNotesApplyInvoiceController {
); );
} }
@Get(':creditNoteId/apply-invoices')
@RequirePermission(CreditNoteAction.View, AbilitySubject.CreditNote)
@ApiOperation({ summary: 'Get credit note associated invoices to apply' })
@ApiResponse({
status: 200,
description: 'Credit note associated invoices to apply',
})
@ApiResponse({ status: 404, description: 'Credit note not found' })
@ApiResponse({ status: 400, description: 'Invalid input data' })
getCreditNoteAssociatedInvoicesToApply(
@Param('creditNoteId') creditNoteId: number,
) {
return this.getCreditNoteAssociatedInvoicesToApplyService.getCreditAssociatedInvoicesToApply(
creditNoteId,
);
}
@Post(':creditNoteId/apply-invoices') @Post(':creditNoteId/apply-invoices')
@RequirePermission(CreditNoteAction.Edit, AbilitySubject.CreditNote) @RequirePermission(CreditNoteAction.Edit, AbilitySubject.CreditNote)
@ApiOperation({ summary: 'Apply credit note to invoices' }) @ApiOperation({ summary: 'Apply credit note to invoices' })
@@ -48,9 +73,32 @@ export class CreditNotesApplyInvoiceController {
}) })
@ApiResponse({ status: 404, description: 'Credit note not found' }) @ApiResponse({ status: 404, description: 'Credit note not found' })
@ApiResponse({ status: 400, description: 'Invalid input data' }) @ApiResponse({ status: 400, description: 'Invalid input data' })
applyCreditNoteToInvoices(@Param('creditNoteId') creditNoteId: number) { applyCreditNoteToInvoices(
return this.getCreditNoteAssociatedAppliedInvoicesService.getCreditAssociatedAppliedInvoices( @Param('creditNoteId') creditNoteId: number,
@Body() applyDto: ApplyCreditNoteToInvoicesDto,
) {
return this.creditNoteApplyToInvoicesService.applyCreditNoteToInvoices(
creditNoteId, creditNoteId,
applyDto,
);
}
@Delete('applied-invoices/:applyCreditToInvoicesId')
@RequirePermission(CreditNoteAction.Edit, AbilitySubject.CreditNote)
@ApiOperation({ summary: 'Delete applied credit note to invoice' })
@ApiResponse({
status: 200,
description: 'Credit note application successfully deleted',
})
@ApiResponse({
status: 404,
description: 'Credit note application not found',
})
deleteApplyCreditNoteToInvoices(
@Param('applyCreditToInvoicesId') applyCreditToInvoicesId: number,
) {
return this.deleteCreditNoteApplyToInvoicesService.deleteApplyCreditNoteToInvoices(
applyCreditToInvoicesId,
); );
} }
} }

View File

@@ -9,6 +9,8 @@ import { CreditNotesModule } from '../CreditNotes/CreditNotes.module';
import { GetCreditNoteAssociatedAppliedInvoices } from './queries/GetCreditNoteAssociatedAppliedInvoices.service'; import { GetCreditNoteAssociatedAppliedInvoices } from './queries/GetCreditNoteAssociatedAppliedInvoices.service';
import { GetCreditNoteAssociatedInvoicesToApply } from './queries/GetCreditNoteAssociatedInvoicesToApply.service'; import { GetCreditNoteAssociatedInvoicesToApply } from './queries/GetCreditNoteAssociatedInvoicesToApply.service';
import { CreditNotesApplyInvoiceController } from './CreditNotesApplyInvoice.controller'; import { CreditNotesApplyInvoiceController } from './CreditNotesApplyInvoice.controller';
import { CreditNoteApplySyncCreditSubscriber } from './subscribers/CreditNoteApplySyncCreditSubscriber';
import { CreditNoteApplySyncInvoicesCreditedAmountSubscriber } from './subscribers/CreditNoteApplySyncInvoicesSubscriber';
@Module({ @Module({
providers: [ providers: [
@@ -19,6 +21,8 @@ import { CreditNotesApplyInvoiceController } from './CreditNotesApplyInvoice.con
CreditNoteApplySyncCredit, CreditNoteApplySyncCredit,
GetCreditNoteAssociatedAppliedInvoices, GetCreditNoteAssociatedAppliedInvoices,
GetCreditNoteAssociatedInvoicesToApply, GetCreditNoteAssociatedInvoicesToApply,
CreditNoteApplySyncCreditSubscriber,
CreditNoteApplySyncInvoicesCreditedAmountSubscriber,
], ],
exports: [DeleteCustomerLinkedCreditNoteService], exports: [DeleteCustomerLinkedCreditNoteService],
imports: [PaymentsReceivedModule, forwardRef(() => CreditNotesModule)], imports: [PaymentsReceivedModule, forwardRef(() => CreditNotesModule)],

View File

@@ -1,6 +1,6 @@
import { Knex } from 'knex'; import { Knex } from 'knex';
import { Injectable, Inject } from '@nestjs/common'; import { Injectable, Inject } from '@nestjs/common';
import Bluebird from 'bluebird'; import * as Bluebird from 'bluebird';
import { ICreditNoteAppliedToInvoice } from '../types/CreditNoteApplyInvoice.types'; import { ICreditNoteAppliedToInvoice } from '../types/CreditNoteApplyInvoice.types';
import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice'; import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice';
import { CreditNoteAppliedInvoice } from '../models/CreditNoteAppliedInvoice'; import { CreditNoteAppliedInvoice } from '../models/CreditNoteAppliedInvoice';

View File

@@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex'; import { Knex } from 'knex';
import { sumBy } from 'lodash'; import { sumBy } from 'lodash';
import { import {
ICreditNoteAppliedToInvoice,
ICreditNoteAppliedToInvoiceModel, ICreditNoteAppliedToInvoiceModel,
IApplyCreditToInvoicesDTO, IApplyCreditToInvoicesDTO,
IApplyCreditToInvoicesCreatedPayload, IApplyCreditToInvoicesCreatedPayload,
@@ -17,6 +18,7 @@ import { CreditNote } from '@/modules/CreditNotes/models/CreditNote';
import { CreditNoteAppliedInvoice } from '../models/CreditNoteAppliedInvoice'; import { CreditNoteAppliedInvoice } from '../models/CreditNoteAppliedInvoice';
import { CommandCreditNoteDTOTransform } from '@/modules/CreditNotes/commands/CommandCreditNoteDTOTransform.service'; import { CommandCreditNoteDTOTransform } from '@/modules/CreditNotes/commands/CommandCreditNoteDTOTransform.service';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { ApplyCreditNoteToInvoicesDto } from '../dtos/ApplyCreditNoteToInvoices.dto';
@Injectable() @Injectable()
export class CreditNoteApplyToInvoices { export class CreditNoteApplyToInvoices {
@@ -48,7 +50,7 @@ export class CreditNoteApplyToInvoices {
*/ */
public async applyCreditNoteToInvoices( public async applyCreditNoteToInvoices(
creditNoteId: number, creditNoteId: number,
applyCreditToInvoicesDTO: IApplyCreditToInvoicesDTO, applyCreditToInvoicesDTO: ApplyCreditNoteToInvoicesDto,
): Promise<CreditNoteAppliedInvoice[]> { ): Promise<CreditNoteAppliedInvoice[]> {
// Saves the credit note or throw not found service error. // Saves the credit note or throw not found service error.
const creditNote = await this.creditNoteModel() const creditNote = await this.creditNoteModel()
@@ -71,7 +73,7 @@ export class CreditNoteApplyToInvoices {
// Validate invoices has remaining amount to apply. // Validate invoices has remaining amount to apply.
this.validateInvoicesRemainingAmount( this.validateInvoicesRemainingAmount(
appliedInvoicesEntries, appliedInvoicesEntries,
creditNoteAppliedModel.amount, creditNoteAppliedModel.entries,
); );
// Validate the credit note remaining amount. // Validate the credit note remaining amount.
this.creditNoteDTOTransform.validateCreditRemainingAmount( this.creditNoteDTOTransform.validateCreditRemainingAmount(
@@ -122,18 +124,20 @@ export class CreditNoteApplyToInvoices {
}; };
/** /**
* Validate the invoice remaining amount. * Validate each invoice has sufficient remaining amount for the applied credit.
* @param {ISaleInvoice[]} invoices * @param {ISaleInvoice[]} invoices
* @param {number} amount * @param {ICreditNoteAppliedToInvoice[]} entries
*/ */
private validateInvoicesRemainingAmount = ( private validateInvoicesRemainingAmount = (
invoices: SaleInvoice[], invoices: SaleInvoice[],
amount: number, entries: ICreditNoteAppliedToInvoice[],
) => { ) => {
const invalidInvoices = invoices.filter( const invoiceMap = new Map(invoices.map((inv) => [inv.id, inv]));
(invoice) => invoice.dueAmount < amount, const invalidEntries = entries.filter((entry) => {
); const invoice = invoiceMap.get(entry.invoiceId);
if (invalidInvoices.length > 0) { return invoice != null && invoice.dueAmount < entry.amount;
});
if (invalidEntries.length > 0) {
throw new ServiceError(ERRORS.INVOICES_HAS_NO_REMAINING_AMOUNT); throw new ServiceError(ERRORS.INVOICES_HAS_NO_REMAINING_AMOUNT);
} }
}; };

View File

@@ -0,0 +1,38 @@
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import {
ArrayMinSize,
IsArray,
IsInt,
IsNotEmpty,
IsNumber,
ValidateNested,
} from 'class-validator';
export class ApplyCreditNoteInvoiceEntryDto {
@IsNotEmpty()
@IsInt()
@ApiProperty({ description: 'Invoice ID to apply credit to', example: 1 })
invoiceId: number;
@IsNotEmpty()
@IsNumber()
@ApiProperty({ description: 'Amount to apply', example: 100.5 })
amount: number;
}
export class ApplyCreditNoteToInvoicesDto {
@IsArray()
@ArrayMinSize(1)
@ValidateNested({ each: true })
@Type(() => ApplyCreditNoteInvoiceEntryDto)
@ApiProperty({
description: 'Entries of invoice ID and amount to apply',
type: [ApplyCreditNoteInvoiceEntryDto],
example: [
{ invoice_id: 1, amount: 100.5 },
{ invoice_id: 2, amount: 50 },
],
})
entries: ApplyCreditNoteInvoiceEntryDto[];
}

View File

@@ -26,7 +26,7 @@ export class CreditNoteAppliedInvoice extends BaseModel {
* Timestamps columns. * Timestamps columns.
*/ */
get timestamps() { get timestamps() {
return ['created_at', 'updated_at']; return ['createdAt', 'updatedAt'];
} }
/** /**

View File

@@ -8,7 +8,7 @@ import { CreditNoteApplySyncInvoicesCreditedAmount } from '../commands/CreditNot
import { events } from '@/common/events/events'; import { events } from '@/common/events/events';
@Injectable() @Injectable()
export default class CreditNoteApplySyncInvoicesCreditedAmountSubscriber { export class CreditNoteApplySyncInvoicesCreditedAmountSubscriber {
constructor( constructor(
private readonly syncInvoicesWithCreditNote: CreditNoteApplySyncInvoicesCreditedAmount, private readonly syncInvoicesWithCreditNote: CreditNoteApplySyncInvoicesCreditedAmount,
) {} ) {}

View File

@@ -29,6 +29,7 @@ export interface IApplyCreditToInvoicesDeletedPayload {
export interface ICreditNoteAppliedToInvoice { export interface ICreditNoteAppliedToInvoice {
amount: number; amount: number;
creditNoteId: number; creditNoteId: number;
invoiceId: number;
} }
export interface ICreditNoteAppliedToInvoiceModel { export interface ICreditNoteAppliedToInvoiceModel {
amount: number; amount: number;

View File

@@ -27,10 +27,10 @@ export class ContactAddressDto {
@IsEmail() @IsEmail()
billingAddressEmail?: string; billingAddressEmail?: string;
@ApiProperty({ required: false, description: 'Billing address zipcode' }) @ApiProperty({ required: false, description: 'Billing address postcode' })
@IsOptional() @IsOptional()
@IsString() @IsString()
billingAddressZipcode?: string; billingAddressPostcode?: string;
@ApiProperty({ required: false, description: 'Billing address phone' }) @ApiProperty({ required: false, description: 'Billing address phone' })
@IsOptional() @IsOptional()
@@ -67,10 +67,10 @@ export class ContactAddressDto {
@IsEmail() @IsEmail()
shippingAddressEmail?: string; shippingAddressEmail?: string;
@ApiProperty({ required: false, description: 'Shipping address zipcode' }) @ApiProperty({ required: false, description: 'Shipping address postcode' })
@IsOptional() @IsOptional()
@IsString() @IsString()
shippingAddressZipcode?: string; shippingAddressPostcode?: string;
@ApiProperty({ required: false, description: 'Shipping address phone' }) @ApiProperty({ required: false, description: 'Shipping address phone' })
@IsOptional() @IsOptional()

View File

@@ -15,6 +15,7 @@ export class FinancialSheet {
negativeFormat: 'mines', negativeFormat: 'mines',
}; };
public baseCurrency: string; public baseCurrency: string;
public dateFormat: string = 'YYYY MMM DD';
/** /**
* Transformes the number format query to settings * Transformes the number format query to settings
@@ -140,13 +141,19 @@ export class FinancialSheet {
* @param {string} format * @param {string} format
* @returns * @returns
*/ */
protected getDateMeta(date: moment.MomentInput, format = 'YYYY-MM-DD') { protected getDateMeta(date: moment.MomentInput, format?: string) {
const dateFormat = format || this.dateFormat || 'YYYY MMM DD';
return { return {
formattedDate: moment(date).format(format), formattedDate: moment(date).format(dateFormat),
date: moment(date).toDate(), date: moment(date).toDate(),
}; };
} }
protected getDateFormatted(date: moment.MomentInput, format?: string) {
const dateFormat = format || this.dateFormat || 'YYYY MMM DD';
return moment(date).format(dateFormat);
}
getPercentageBasis = (base, amount) => { getPercentageBasis = (base, amount) => {
return base ? amount / base : 0; return base ? amount / base : 0;
}; };

View File

@@ -32,18 +32,19 @@ export class APAgingSummaryService {
this.APAgingSummaryRepository.setFilter(filter); this.APAgingSummaryRepository.setFilter(filter);
await this.APAgingSummaryRepository.load(); await this.APAgingSummaryRepository.load();
// Retrieve the aging summary report meta first to get date format.
const meta = await this.APAgingSummaryMeta.meta(filter);
// A/P aging summary report instance. // A/P aging summary report instance.
const APAgingSummaryReport = new APAgingSummarySheet( const APAgingSummaryReport = new APAgingSummarySheet(
filter, filter,
this.APAgingSummaryRepository, this.APAgingSummaryRepository,
{ baseCurrency: meta.baseCurrency, dateFormat: meta.dateFormat },
); );
// A/P aging summary report data and columns. // A/P aging summary report data and columns.
const data = APAgingSummaryReport.reportData(); const data = APAgingSummaryReport.reportData();
const columns = APAgingSummaryReport.reportColumns(); const columns = APAgingSummaryReport.reportColumns();
// Retrieve the aging summary report meta.
const meta = await this.APAgingSummaryMeta.meta(filter);
// Triggers `onPayableAgingViewed` event. // Triggers `onPayableAgingViewed` event.
await this.eventPublisher.emitAsync(events.reports.onPayableAgingViewed, { await this.eventPublisher.emitAsync(events.reports.onPayableAgingViewed, {
query, query,

View File

@@ -14,6 +14,7 @@ import { allPassedConditionsPass } from '@/utils/all-conditions-passed';
import { APAgingSummaryRepository } from './APAgingSummaryRepository'; import { APAgingSummaryRepository } from './APAgingSummaryRepository';
import { Bill } from '@/modules/Bills/models/Bill'; import { Bill } from '@/modules/Bills/models/Bill';
import { APAgingSummaryQueryDto } from './APAgingSummaryQuery.dto'; import { APAgingSummaryQueryDto } from './APAgingSummaryQuery.dto';
import { IFinancialReportMeta, DEFAULT_REPORT_META } from '../../types/Report.types';
export class APAgingSummarySheet extends AgingSummaryReport { export class APAgingSummarySheet extends AgingSummaryReport {
readonly repository: APAgingSummaryRepository; readonly repository: APAgingSummaryRepository;
@@ -31,12 +32,14 @@ export class APAgingSummarySheet extends AgingSummaryReport {
constructor( constructor(
query: APAgingSummaryQueryDto, query: APAgingSummaryQueryDto,
repository: APAgingSummaryRepository, repository: APAgingSummaryRepository,
meta: IFinancialReportMeta,
) { ) {
super(); super();
this.query = query; this.query = query;
this.repository = repository; this.repository = repository;
this.numberFormat = this.query.numberFormat; this.numberFormat = this.query.numberFormat;
this.dateFormat = meta.dateFormat || DEFAULT_REPORT_META.dateFormat;
this.overdueInvoicesByContactId = this.repository.overdueBillsByVendorId; this.overdueInvoicesByContactId = this.repository.overdueBillsByVendorId;
this.currentInvoicesByContactId = this.repository.dueBillsByVendorId; this.currentInvoicesByContactId = this.repository.dueBillsByVendorId;

View File

@@ -28,18 +28,19 @@ export class ARAgingSummaryService {
this.ARAgingSummaryRepository.setFilter(filter); this.ARAgingSummaryRepository.setFilter(filter);
await this.ARAgingSummaryRepository.load(); await this.ARAgingSummaryRepository.load();
// Retrieve the aging summary report meta first to get date format.
const meta = await this.ARAgingSummaryMeta.meta(filter);
// A/R aging summary report instance. // A/R aging summary report instance.
const ARAgingSummaryReport = new ARAgingSummarySheet( const ARAgingSummaryReport = new ARAgingSummarySheet(
filter, filter,
this.ARAgingSummaryRepository, this.ARAgingSummaryRepository,
{ baseCurrency: meta.baseCurrency, dateFormat: meta.dateFormat },
); );
// A/R aging summary report data and columns. // A/R aging summary report data and columns.
const data = ARAgingSummaryReport.reportData(); const data = ARAgingSummaryReport.reportData();
const columns = ARAgingSummaryReport.reportColumns(); const columns = ARAgingSummaryReport.reportColumns();
// Retrieve the aging summary report meta.
const meta = await this.ARAgingSummaryMeta.meta(filter);
// Triggers `onReceivableAgingViewed` event. // Triggers `onReceivableAgingViewed` event.
await this.eventPublisher.emitAsync( await this.eventPublisher.emitAsync(
events.reports.onReceivableAgingViewed, events.reports.onReceivableAgingViewed,

View File

@@ -14,6 +14,7 @@ import { ARAgingSummaryRepository } from './ARAgingSummaryRepository';
import { Customer } from '@/modules/Customers/models/Customer'; import { Customer } from '@/modules/Customers/models/Customer';
import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice'; import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice';
import { ARAgingSummaryQueryDto } from './ARAgingSummaryQuery.dto'; import { ARAgingSummaryQueryDto } from './ARAgingSummaryQuery.dto';
import { IFinancialReportMeta, DEFAULT_REPORT_META } from '../../types/Report.types';
export class ARAgingSummarySheet extends AgingSummaryReport { export class ARAgingSummarySheet extends AgingSummaryReport {
readonly query: ARAgingSummaryQueryDto; readonly query: ARAgingSummaryQueryDto;
@@ -32,16 +33,19 @@ export class ARAgingSummarySheet extends AgingSummaryReport {
* Constructor method. * Constructor method.
* @param {ARAgingSummaryQueryDto} query - Query * @param {ARAgingSummaryQueryDto} query - Query
* @param {ARAgingSummaryRepository} repository - Repository. * @param {ARAgingSummaryRepository} repository - Repository.
* @param {IFinancialReportMeta} meta - Report meta.
*/ */
constructor( constructor(
query: ARAgingSummaryQueryDto, query: ARAgingSummaryQueryDto,
repository: ARAgingSummaryRepository, repository: ARAgingSummaryRepository,
meta: IFinancialReportMeta,
) { ) {
super(); super();
this.query = query; this.query = query;
this.repository = repository; this.repository = repository;
this.numberFormat = this.query.numberFormat; this.numberFormat = this.query.numberFormat;
this.dateFormat = meta.dateFormat || DEFAULT_REPORT_META.dateFormat;
this.overdueInvoicesByContactId = this.overdueInvoicesByContactId =
this.repository.overdueInvoicesByContactId; this.repository.overdueInvoicesByContactId;

View File

@@ -13,7 +13,7 @@ export class AgingSummaryMeta {
*/ */
public async meta(query: IAgingSummaryQuery): Promise<IAgingSummaryMeta> { public async meta(query: IAgingSummaryQuery): Promise<IAgingSummaryMeta> {
const commonMeta = await this.financialSheetMeta.meta(); const commonMeta = await this.financialSheetMeta.meta();
const formattedAsDate = moment(query.asDate).format('YYYY/MM/DD'); const formattedAsDate = moment(query.asDate).format(commonMeta.dateFormat);
const formattedDateRange = `As ${formattedAsDate}`; const formattedDateRange = `As ${formattedAsDate}`;
return { return {

View File

@@ -19,7 +19,7 @@ import { BalanceSheetFiltering } from './BalanceSheetFiltering';
import { BalanceSheetNetIncome } from './BalanceSheetNetIncome'; import { BalanceSheetNetIncome } from './BalanceSheetNetIncome';
import { BalanceSheetAggregators } from './BalanceSheetAggregators'; import { BalanceSheetAggregators } from './BalanceSheetAggregators';
import { BalanceSheetAccounts } from './BalanceSheetAccounts'; import { BalanceSheetAccounts } from './BalanceSheetAccounts';
import { INumberFormatQuery } from '../../types/Report.types'; import { INumberFormatQuery, IFinancialReportMeta, DEFAULT_REPORT_META } from '../../types/Report.types';
import { FinancialSheet } from '../../common/FinancialSheet'; import { FinancialSheet } from '../../common/FinancialSheet';
export class BalanceSheet extends R.pipe( export class BalanceSheet extends R.pipe(
@@ -66,21 +66,23 @@ export class BalanceSheet extends R.pipe(
/** /**
* Constructor method. * Constructor method.
* @param {IBalanceSheetQuery} query - * @param {IBalanceSheetQuery} query -
* @param {IAccount[]} accounts - * @param {BalanceSheetRepository} repository -
* @param {string} baseCurrency - * @param {I18nService} i18n -
* @param {IFinancialReportMeta} meta -
*/ */
constructor( constructor(
query: IBalanceSheetQuery, query: IBalanceSheetQuery,
repository: BalanceSheetRepository, repository: BalanceSheetRepository,
baseCurrency: string,
i18n: I18nService, i18n: I18nService,
meta: IFinancialReportMeta,
) { ) {
super(); super();
this.query = new BalanceSheetQuery(query); this.query = new BalanceSheetQuery(query);
this.repository = repository; this.repository = repository;
this.baseCurrency = baseCurrency; this.baseCurrency = meta.baseCurrency;
this.numberFormat = this.query.query.numberFormat; this.numberFormat = this.query.query.numberFormat;
this.dateFormat = meta.dateFormat || DEFAULT_REPORT_META.dateFormat;
this.i18n = i18n; this.i18n = i18n;
} }

View File

@@ -40,19 +40,19 @@ export class BalanceSheetInjectable {
// Loads all resources. // Loads all resources.
await this.balanceSheetRepository.asyncInitialize(filter); await this.balanceSheetRepository.asyncInitialize(filter);
// Balance sheet meta first to get date format.
const meta = await this.balanceSheetMeta.meta(filter);
// Balance sheet report instance. // Balance sheet report instance.
const balanceSheetInstanace = new BalanceSheet( const balanceSheetInstanace = new BalanceSheet(
filter, filter,
this.balanceSheetRepository, this.balanceSheetRepository,
tenantMetadata.baseCurrency,
this.i18n, this.i18n,
{ baseCurrency: tenantMetadata.baseCurrency, dateFormat: meta.dateFormat },
); );
// Balance sheet data. // Balance sheet data.
const data = balanceSheetInstanace.reportData(); const data = balanceSheetInstanace.reportData();
// Balance sheet meta.
const meta = await this.balanceSheetMeta.meta(filter);
// Triggers `onBalanceSheetViewed` event. // Triggers `onBalanceSheetViewed` event.
await this.eventPublisher.emitAsync(events.reports.onBalanceSheetViewed, { await this.eventPublisher.emitAsync(events.reports.onBalanceSheetViewed, {
query, query,

View File

@@ -13,7 +13,7 @@ export class BalanceSheetMetaInjectable {
*/ */
public async meta(query: IBalanceSheetQuery): Promise<IBalanceSheetMeta> { public async meta(query: IBalanceSheetQuery): Promise<IBalanceSheetMeta> {
const commonMeta = await this.financialSheetMeta.meta(); const commonMeta = await this.financialSheetMeta.meta();
const formattedAsDate = moment(query.toDate).format('YYYY/MM/DD'); const formattedAsDate = moment(query.toDate).format(commonMeta.dateFormat);
const formattedDateRange = `As ${formattedAsDate}`; const formattedDateRange = `As ${formattedAsDate}`;
const sheetName = 'Balance Sheet Statement'; const sheetName = 'Balance Sheet Statement';

View File

@@ -27,7 +27,7 @@ import { DISPLAY_COLUMNS_BY } from './constants';
import { FinancialSheetStructure } from '../../common/FinancialSheetStructure'; import { FinancialSheetStructure } from '../../common/FinancialSheetStructure';
import { Account } from '@/modules/Accounts/models/Account.model'; import { Account } from '@/modules/Accounts/models/Account.model';
import { ILedger } from '@/modules/Ledger/types/Ledger.types'; import { ILedger } from '@/modules/Ledger/types/Ledger.types';
import { INumberFormatQuery } from '../../types/Report.types'; import { INumberFormatQuery, IFinancialReportMeta, DEFAULT_REPORT_META } from '../../types/Report.types';
import { transformToMapBy } from '@/utils/transform-to-map-by'; import { transformToMapBy } from '@/utils/transform-to-map-by';
import { accumSum } from '@/utils/accum-sum'; import { accumSum } from '@/utils/accum-sum';
import { ModelObject } from 'objection'; import { ModelObject } from 'objection';
@@ -62,12 +62,12 @@ export class CashFlowStatement extends R.pipe(
cashLedger: ILedger, cashLedger: ILedger,
netIncomeLedger: ILedger, netIncomeLedger: ILedger,
query: ICashFlowStatementQuery, query: ICashFlowStatementQuery,
baseCurrency: string,
i18n: I18nService, i18n: I18nService,
meta: IFinancialReportMeta,
) { ) {
super(); super();
this.baseCurrency = baseCurrency; this.baseCurrency = meta.baseCurrency;
this.i18n = i18n; this.i18n = i18n;
this.ledger = ledger; this.ledger = ledger;
this.cashLedger = cashLedger; this.cashLedger = cashLedger;
@@ -76,6 +76,7 @@ export class CashFlowStatement extends R.pipe(
this.accountsByRootType = transformToMapBy(accounts, 'accountRootType'); this.accountsByRootType = transformToMapBy(accounts, 'accountRootType');
this.query = query; this.query = query;
this.numberFormat = this.query.numberFormat; this.numberFormat = this.query.numberFormat;
this.dateFormat = meta.dateFormat || DEFAULT_REPORT_META.dateFormat;
this.dateRangeSet = []; this.dateRangeSet = [];
this.comparatorDateType = this.comparatorDateType =
query.displayColumnsType === 'total' ? 'day' : query.displayColumnsBy; query.displayColumnsType === 'total' ? 'day' : query.displayColumnsBy;

View File

@@ -85,6 +85,9 @@ export class CashFlowStatementService {
const cashLedger = Ledger.fromTransactions(cashAtBeginningTransactions); const cashLedger = Ledger.fromTransactions(cashAtBeginningTransactions);
const netIncomeLedger = Ledger.fromTransactions(netIncome); const netIncomeLedger = Ledger.fromTransactions(netIncome);
// Retrieve the cashflow sheet meta first to get date format.
const meta = await this.cashflowSheetMeta.meta(filter);
// Cash flow statement. // Cash flow statement.
const cashFlowInstance = new CashFlowStatement( const cashFlowInstance = new CashFlowStatement(
accounts, accounts,
@@ -92,11 +95,9 @@ export class CashFlowStatementService {
cashLedger, cashLedger,
netIncomeLedger, netIncomeLedger,
filter, filter,
tenant.metadata.baseCurrency,
this.i18n, this.i18n,
{ baseCurrency: tenant.metadata.baseCurrency, dateFormat: meta.dateFormat },
); );
// Retrieve the cashflow sheet meta.
const meta = await this.cashflowSheetMeta.meta(filter);
return { return {
data: cashFlowInstance.reportData(), data: cashFlowInstance.reportData(),

View File

@@ -23,8 +23,8 @@ export class CashflowSheetMeta {
query: ICashFlowStatementQuery, query: ICashFlowStatementQuery,
): Promise<ICashFlowStatementMeta> { ): Promise<ICashFlowStatementMeta> {
const meta = await this.financialSheetMeta.meta(); const meta = await this.financialSheetMeta.meta();
const formattedToDate = moment(query.toDate).format('YYYY/MM/DD'); const formattedToDate = moment(query.toDate).format(meta.dateFormat);
const formattedFromDate = moment(query.fromDate).format('YYYY/MM/DD'); const formattedFromDate = moment(query.fromDate).format(meta.dateFormat);
const fromLabel = this.i18n.t('cash_flow_statement.from_date'); const fromLabel = this.i18n.t('cash_flow_statement.from_date');
const toLabel = this.i18n.t('cash_flow_statement.to_date'); const toLabel = this.i18n.t('cash_flow_statement.to_date');
const formattedDateRange = `${fromLabel} ${formattedFromDate} | ${toLabel} ${formattedToDate}`; const formattedDateRange = `${fromLabel} ${formattedFromDate} | ${toLabel} ${formattedToDate}`;

View File

@@ -8,7 +8,7 @@ import {
import { ContactBalanceSummaryReport } from '../ContactBalanceSummary/ContactBalanceSummary'; import { ContactBalanceSummaryReport } from '../ContactBalanceSummary/ContactBalanceSummary';
import { ILedger } from '@/modules/Ledger/types/Ledger.types'; import { ILedger } from '@/modules/Ledger/types/Ledger.types';
import { ModelObject } from 'objection'; import { ModelObject } from 'objection';
import { INumberFormatQuery } from '../../types/Report.types'; import { INumberFormatQuery, IFinancialReportMeta, DEFAULT_REPORT_META } from '../../types/Report.types';
import { Customer } from '@/modules/Customers/models/Customer'; import { Customer } from '@/modules/Customers/models/Customer';
export class CustomerBalanceSummaryReport extends ContactBalanceSummaryReport { export class CustomerBalanceSummaryReport extends ContactBalanceSummaryReport {
@@ -23,21 +23,22 @@ export class CustomerBalanceSummaryReport extends ContactBalanceSummaryReport {
* @param {IJournalPoster} receivableLedger * @param {IJournalPoster} receivableLedger
* @param {ICustomer[]} customers * @param {ICustomer[]} customers
* @param {ICustomerBalanceSummaryQuery} filter * @param {ICustomerBalanceSummaryQuery} filter
* @param {string} baseCurrency * @param {IFinancialReportMeta} meta
*/ */
constructor( constructor(
ledger: ILedger, ledger: ILedger,
customers: ModelObject<Customer>[], customers: ModelObject<Customer>[],
filter: ICustomerBalanceSummaryQuery, filter: ICustomerBalanceSummaryQuery,
baseCurrency: string meta: IFinancialReportMeta,
) { ) {
super(); super();
this.ledger = ledger; this.ledger = ledger;
this.baseCurrency = baseCurrency; this.baseCurrency = meta.baseCurrency;
this.customers = customers; this.customers = customers;
this.filter = filter; this.filter = filter;
this.numberFormat = this.filter.numberFormat; this.numberFormat = this.filter.numberFormat;
this.dateFormat = meta.dateFormat || DEFAULT_REPORT_META.dateFormat;
} }
/** /**

View File

@@ -19,7 +19,7 @@ export class CustomerBalanceSummaryMeta {
query: ICustomerBalanceSummaryQuery, query: ICustomerBalanceSummaryQuery,
): Promise<ICustomerBalanceSummaryMeta> { ): Promise<ICustomerBalanceSummaryMeta> {
const commonMeta = await this.financialSheetMeta.meta(); const commonMeta = await this.financialSheetMeta.meta();
const formattedAsDate = moment(query.asDate).format('YYYY/MM/DD'); const formattedAsDate = moment(query.asDate).format(commonMeta.dateFormat);
const formattedDateRange = `As ${formattedAsDate}`; const formattedDateRange = `As ${formattedAsDate}`;
return { return {

View File

@@ -63,15 +63,16 @@ export class CustomerBalanceSummaryService {
// Ledger query. // Ledger query.
const ledger = new Ledger(customersEntries); const ledger = new Ledger(customersEntries);
// Retrieve the customer balance summary meta first to get date format.
const meta = await this.customerBalanceSummaryMeta.meta(filter);
// Report instance. // Report instance.
const report = new CustomerBalanceSummaryReport( const report = new CustomerBalanceSummaryReport(
ledger, ledger,
customers, customers,
filter, filter,
tenantMetadata.baseCurrency, { baseCurrency: tenantMetadata.baseCurrency, dateFormat: meta.dateFormat },
); );
// Retrieve the customer balance summary meta.
const meta = await this.customerBalanceSummaryMeta.meta(filter);
// Triggers `onCustomerBalanceSummaryViewed` event. // Triggers `onCustomerBalanceSummaryViewed` event.
await this.eventPublisher.emitAsync( await this.eventPublisher.emitAsync(

View File

@@ -19,6 +19,7 @@ import { Account } from '@/modules/Accounts/models/Account.model';
import { ModelObject } from 'objection'; import { ModelObject } from 'objection';
import { flatToNestedArray } from '@/utils/flat-to-nested-array'; import { flatToNestedArray } from '@/utils/flat-to-nested-array';
import { getTransactionTypeLabel } from '@/modules/BankingTransactions/utils'; import { getTransactionTypeLabel } from '@/modules/BankingTransactions/utils';
import { IFinancialReportMeta, DEFAULT_REPORT_META } from '../../types/Report.types';
export class GeneralLedgerSheet extends R.compose(FinancialSheetStructure)( export class GeneralLedgerSheet extends R.compose(FinancialSheetStructure)(
FinancialSheet, FinancialSheet,
@@ -33,18 +34,21 @@ export class GeneralLedgerSheet extends R.compose(FinancialSheetStructure)(
* @param {IGeneralLedgerSheetQuery} query - * @param {IGeneralLedgerSheetQuery} query -
* @param {GeneralLedgerRepository} repository - * @param {GeneralLedgerRepository} repository -
* @param {I18nService} i18n - * @param {I18nService} i18n -
* @param {IFinancialReportMeta} meta -
*/ */
constructor( constructor(
query: IGeneralLedgerSheetQuery, query: IGeneralLedgerSheetQuery,
repository: GeneralLedgerRepository, repository: GeneralLedgerRepository,
i18n: I18nService, i18n: I18nService,
meta: IFinancialReportMeta,
) { ) {
super(); super();
this.query = query; this.query = query;
this.numberFormat = this.query.numberFormat; this.numberFormat = this.query.numberFormat;
this.dateFormat = meta.dateFormat || DEFAULT_REPORT_META.dateFormat;
this.repository = repository; this.repository = repository;
this.baseCurrency = this.repository.tenant.metadata.baseCurrency; this.baseCurrency = meta.baseCurrency;
this.i18n = i18n; this.i18n = i18n;
} }
@@ -87,7 +91,7 @@ export class GeneralLedgerSheet extends R.compose(FinancialSheetStructure)(
return { return {
id: entry.id, id: entry.id,
date: entry.date, date: entry.date,
dateFormatted: moment(entry.date).format('YYYY MMM DD'), dateFormatted: moment(entry.date).format(this.dateFormat),
referenceType: entry.transactionType, referenceType: entry.transactionType,
referenceId: entry.transactionId, referenceId: entry.transactionId,

View File

@@ -6,6 +6,7 @@ export interface IGeneralLedgerSheetQuery {
toDate: Date | string; toDate: Date | string;
basis: string; basis: string;
numberFormat: IGeneralLedgerNumberFormat; numberFormat: IGeneralLedgerNumberFormat;
dateFormat?: string;
noneTransactions: boolean; noneTransactions: boolean;
accountsIds: number[]; accountsIds: number[];
branchesIds?: number[]; branchesIds?: number[];

View File

@@ -19,8 +19,8 @@ export class GeneralLedgerMeta {
): Promise<IGeneralLedgerMeta> { ): Promise<IGeneralLedgerMeta> {
const commonMeta = await this.financialSheetMeta.meta(); const commonMeta = await this.financialSheetMeta.meta();
const formattedToDate = moment(query.toDate).format('YYYY/MM/DD'); const formattedToDate = moment(query.toDate).format(commonMeta.dateFormat);
const formattedFromDate = moment(query.fromDate).format('YYYY/MM/DD'); const formattedFromDate = moment(query.fromDate).format(commonMeta.dateFormat);
const formattedDateRange = `From ${formattedFromDate} | To ${formattedToDate}`; const formattedDateRange = `From ${formattedFromDate} | To ${formattedToDate}`;
return { return {

View File

@@ -37,18 +37,19 @@ export class GeneralLedgerService {
this.generalLedgerRepository.setFilter(filter); this.generalLedgerRepository.setFilter(filter);
await this.generalLedgerRepository.asyncInitialize(); await this.generalLedgerRepository.asyncInitialize();
// Retrieve general ledger report metadata first to get the date format.
const meta = await this.generalLedgerMeta.meta(filter);
// General ledger report instance. // General ledger report instance.
const generalLedgerInstance = new GeneralLedgerSheet( const generalLedgerInstance = new GeneralLedgerSheet(
filter, filter,
this.generalLedgerRepository, this.generalLedgerRepository,
this.i18n, this.i18n,
{ baseCurrency: meta.baseCurrency, dateFormat: meta.dateFormat },
); );
// Retrieve general ledger report data. // Retrieve general ledger report data.
const reportData = generalLedgerInstance.reportData(); const reportData = generalLedgerInstance.reportData();
// Retrieve general ledger report metadata.
const meta = await this.generalLedgerMeta.meta(filter);
// Triggers `onGeneralLedgerViewed` event. // Triggers `onGeneralLedgerViewed` event.
await this.eventEmitter.emitAsync(events.reports.onGeneralLedgerViewed, {}); await this.eventEmitter.emitAsync(events.reports.onGeneralLedgerViewed, {});

View File

@@ -8,6 +8,7 @@ import { InventoryDetails } from './InventoryItemDetails';
import { InventoryItemDetailsRepository } from './InventoryItemDetailsRepository'; import { InventoryItemDetailsRepository } from './InventoryItemDetailsRepository';
import { InventoryDetailsMetaInjectable } from './InventoryItemDetailsMeta'; import { InventoryDetailsMetaInjectable } from './InventoryItemDetailsMeta';
import { getInventoryItemDetailsDefaultQuery } from './constant'; import { getInventoryItemDetailsDefaultQuery } from './constant';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
@Injectable() @Injectable()
export class InventoryDetailsService { export class InventoryDetailsService {
@@ -15,6 +16,7 @@ export class InventoryDetailsService {
private readonly inventoryItemDetailsRepository: InventoryItemDetailsRepository, private readonly inventoryItemDetailsRepository: InventoryItemDetailsRepository,
private readonly inventoryDetailsMeta: InventoryDetailsMetaInjectable, private readonly inventoryDetailsMeta: InventoryDetailsMetaInjectable,
private readonly i18n: I18nService, private readonly i18n: I18nService,
private readonly tenancyContext: TenancyContext,
) {} ) {}
/** /**
@@ -34,13 +36,16 @@ export class InventoryDetailsService {
this.inventoryItemDetailsRepository.setFilter(filter); this.inventoryItemDetailsRepository.setFilter(filter);
await this.inventoryItemDetailsRepository.asyncInit(); await this.inventoryItemDetailsRepository.asyncInit();
// Retrieve the meta first to get date format.
const meta = await this.inventoryDetailsMeta.meta(query);
// Inventory details report mapper. // Inventory details report mapper.
const inventoryDetailsInstance = new InventoryDetails( const inventoryDetailsInstance = new InventoryDetails(
filter, filter,
this.inventoryItemDetailsRepository, this.inventoryItemDetailsRepository,
this.i18n, this.i18n,
{ baseCurrency: meta.baseCurrency, dateFormat: meta.dateFormat },
); );
const meta = await this.inventoryDetailsMeta.meta(query);
return { return {
data: inventoryDetailsInstance.reportData(), data: inventoryDetailsInstance.reportData(),

View File

@@ -17,6 +17,8 @@ import { Item } from '@/modules/Items/models/Item';
import { import {
IFormatNumberSettings, IFormatNumberSettings,
INumberFormatQuery, INumberFormatQuery,
IFinancialReportMeta,
DEFAULT_REPORT_META,
} from '../../types/Report.types'; } from '../../types/Report.types';
import { InventoryTransaction } from '@/modules/InventoryCost/models/InventoryTransaction'; import { InventoryTransaction } from '@/modules/InventoryCost/models/InventoryTransaction';
import { InventoryItemDetailsRepository } from './InventoryItemDetailsRepository'; import { InventoryItemDetailsRepository } from './InventoryItemDetailsRepository';
@@ -35,11 +37,13 @@ export class InventoryDetails extends FinancialSheet {
* Constructor method. * Constructor method.
* @param {InventoryItemDetailsRepository} repository - The repository. * @param {InventoryItemDetailsRepository} repository - The repository.
* @param {I18nService} i18n - The i18n service. * @param {I18nService} i18n - The i18n service.
* @param {IFinancialReportMeta} meta - Report meta.
*/ */
constructor( constructor(
filter: IInventoryDetailsQuery, filter: IInventoryDetailsQuery,
repository: InventoryItemDetailsRepository, repository: InventoryItemDetailsRepository,
i18n: I18nService, i18n: I18nService,
meta: IFinancialReportMeta,
) { ) {
super(); super();
@@ -48,6 +52,7 @@ export class InventoryDetails extends FinancialSheet {
this.query = filter; this.query = filter;
this.numberFormat = this.query.numberFormat; this.numberFormat = this.query.numberFormat;
this.i18n = i18n; this.i18n = i18n;
this.dateFormat = meta.dateFormat || DEFAULT_REPORT_META.dateFormat;
} }
/** /**
@@ -89,7 +94,7 @@ export class InventoryDetails extends FinancialSheet {
*/ */
public getDateMeta(date: Date | string): IInventoryDetailsDate { public getDateMeta(date: Date | string): IInventoryDetailsDate {
return { return {
formattedDate: moment(date).format('YYYY-MM-DD'), formattedDate: moment(date).format(this.dateFormat),
date: moment(date).toDate(), date: moment(date).toDate(),
}; };
} }

View File

@@ -19,8 +19,8 @@ export class InventoryDetailsMetaInjectable {
): Promise<IInventoryItemDetailMeta> { ): Promise<IInventoryItemDetailMeta> {
const commonMeta = await this.financialSheetMeta.meta(); const commonMeta = await this.financialSheetMeta.meta();
const formattedFromDate = moment(query.fromDate).format('YYYY/MM/DD'); const formattedFromDate = moment(query.fromDate).format(commonMeta.dateFormat);
const formattedToDay = moment(query.toDate).format('YYYY/MM/DD'); const formattedToDay = moment(query.toDate).format(commonMeta.dateFormat);
const formattedDateRange = `From ${formattedFromDate} | To ${formattedToDay}`; const formattedDateRange = `From ${formattedFromDate} | To ${formattedToDay}`;
const sheetName = 'Inventory Item Details'; const sheetName = 'Inventory Item Details';

View File

@@ -12,6 +12,7 @@ import { InventoryCostLotTracker } from '@/modules/InventoryCost/models/Inventor
import { FinancialSheet } from '../../common/FinancialSheet'; import { FinancialSheet } from '../../common/FinancialSheet';
import { InventoryValuationSheetRepository } from './InventoryValuationSheetRepository'; import { InventoryValuationSheetRepository } from './InventoryValuationSheetRepository';
import { allPassedConditionsPass } from '@/utils/all-conditions-passed'; import { allPassedConditionsPass } from '@/utils/all-conditions-passed';
import { IFinancialReportMeta, DEFAULT_REPORT_META } from '../../types/Report.types';
export class InventoryValuationSheet extends FinancialSheet { export class InventoryValuationSheet extends FinancialSheet {
readonly query: IInventoryValuationReportQuery; readonly query: IInventoryValuationReportQuery;
@@ -21,16 +22,19 @@ export class InventoryValuationSheet extends FinancialSheet {
* Constructor method. * Constructor method.
* @param {IInventoryValuationReportQuery} query - Inventory valuation query. * @param {IInventoryValuationReportQuery} query - Inventory valuation query.
* @param {InventoryValuationSheetRepository} repository - Inventory valuation sheet repository. * @param {InventoryValuationSheetRepository} repository - Inventory valuation sheet repository.
* @param {IFinancialReportMeta} meta - Report meta.
*/ */
constructor( constructor(
query: IInventoryValuationReportQuery, query: IInventoryValuationReportQuery,
repository: InventoryValuationSheetRepository, repository: InventoryValuationSheetRepository,
meta: IFinancialReportMeta,
) { ) {
super(); super();
this.query = query; this.query = query;
this.repository = repository; this.repository = repository;
this.numberFormat = this.query.numberFormat; this.numberFormat = this.query.numberFormat;
this.dateFormat = meta.dateFormat || DEFAULT_REPORT_META.dateFormat;
} }
/** /**

View File

@@ -18,7 +18,7 @@ export class InventoryValuationMetaInjectable {
query: IInventoryValuationReportQuery, query: IInventoryValuationReportQuery,
): Promise<IInventoryValuationSheetMeta> { ): Promise<IInventoryValuationSheetMeta> {
const commonMeta = await this.financialSheetMeta.meta(); const commonMeta = await this.financialSheetMeta.meta();
const formattedAsDate = moment(query.asDate).format('YYYY/MM/DD'); const formattedAsDate = moment(query.asDate).format(commonMeta.dateFormat);
const formattedDateRange = `As ${formattedAsDate}`; const formattedDateRange = `As ${formattedAsDate}`;
return { return {

View File

@@ -34,16 +34,17 @@ export class InventoryValuationSheetService {
this.inventoryValuationSheetRepository.setFilter(filter); this.inventoryValuationSheetRepository.setFilter(filter);
await this.inventoryValuationSheetRepository.asyncInit(); await this.inventoryValuationSheetRepository.asyncInit();
// Retrieves the inventorty valuation meta first to get date format.
const meta = await this.inventoryValuationMeta.meta(filter);
const inventoryValuationInstance = new InventoryValuationSheet( const inventoryValuationInstance = new InventoryValuationSheet(
filter, filter,
this.inventoryValuationSheetRepository, this.inventoryValuationSheetRepository,
{ baseCurrency: meta.baseCurrency, dateFormat: meta.dateFormat },
); );
// Retrieve the inventory valuation report data. // Retrieve the inventory valuation report data.
const inventoryValuationData = inventoryValuationInstance.reportData(); const inventoryValuationData = inventoryValuationInstance.reportData();
// Retrieves the inventorty valuation meta.
const meta = await this.inventoryValuationMeta.meta(filter);
// Triggers `onInventoryValuationViewed` event. // Triggers `onInventoryValuationViewed` event.
await this.eventPublisher.emitAsync( await this.eventPublisher.emitAsync(
events.reports.onInventoryValuationViewed, events.reports.onInventoryValuationViewed,

View File

@@ -11,6 +11,7 @@ import { FinancialSheet } from '../../common/FinancialSheet';
import { JournalSheetRepository } from './JournalSheetRepository'; import { JournalSheetRepository } from './JournalSheetRepository';
import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types'; import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types';
import { getTransactionTypeLabel } from '@/modules/BankingTransactions/utils'; import { getTransactionTypeLabel } from '@/modules/BankingTransactions/utils';
import { IFinancialReportMeta, DEFAULT_REPORT_META } from '../../types/Report.types';
export class JournalSheet extends FinancialSheet { export class JournalSheet extends FinancialSheet {
readonly query: IJournalReportQuery; readonly query: IJournalReportQuery;
@@ -22,20 +23,24 @@ export class JournalSheet extends FinancialSheet {
* @param {IJournalReportQuery} query - * @param {IJournalReportQuery} query -
* @param {JournalSheetRepository} repository - * @param {JournalSheetRepository} repository -
* @param {I18nService} i18n - * @param {I18nService} i18n -
* @param {IFinancialReportMeta} meta -
*/ */
constructor( constructor(
query: IJournalReportQuery, query: IJournalReportQuery,
repository: JournalSheetRepository, repository: JournalSheetRepository,
i18n: I18nService, i18n: I18nService,
meta: IFinancialReportMeta,
) { ) {
super(); super();
this.query = query; this.query = query;
this.repository = repository; this.repository = repository;
this.baseCurrency = meta.baseCurrency;
this.numberFormat = { this.numberFormat = {
...this.numberFormat, ...this.numberFormat,
...this.query.numberFormat, ...this.query.numberFormat,
}; };
this.dateFormat = meta.dateFormat || DEFAULT_REPORT_META.dateFormat;
this.i18n = i18n; this.i18n = i18n;
} }
@@ -94,7 +99,7 @@ export class JournalSheet extends FinancialSheet {
return { return {
date: moment(groupEntry.date).toDate(), date: moment(groupEntry.date).toDate(),
dateFormatted: moment(groupEntry.date).format('YYYY MMM DD'), dateFormatted: moment(groupEntry.date).format(this.dateFormat),
transactionType: groupEntry.transactionType, transactionType: groupEntry.transactionType,
referenceId: groupEntry.transactionId, referenceId: groupEntry.transactionId,

View File

@@ -8,6 +8,7 @@ export interface IJournalReportQuery {
noCents: boolean; noCents: boolean;
divideOn1000: boolean; divideOn1000: boolean;
}; };
dateFormat?: string;
transactionType: string; transactionType: string;
transactionId: string; transactionId: string;

View File

@@ -17,8 +17,8 @@ export class JournalSheetMeta {
): Promise<IJournalSheetMeta> { ): Promise<IJournalSheetMeta> {
const common = await this.financialSheetMeta.meta(); const common = await this.financialSheetMeta.meta();
const formattedToDate = moment(query.toDate).format('YYYY/MM/DD'); const formattedToDate = moment(query.toDate).format(common.dateFormat);
const formattedFromDate = moment(query.fromDate).format('YYYY/MM/DD'); const formattedFromDate = moment(query.fromDate).format(common.dateFormat);
const formattedDateRange = `From ${formattedFromDate} | To ${formattedToDate}`; const formattedDateRange = `From ${formattedFromDate} | To ${formattedToDate}`;
return { return {

View File

@@ -30,18 +30,19 @@ export class JournalSheetService {
this.journalRepository.setFilter(query); this.journalRepository.setFilter(query);
await this.journalRepository.load(); await this.journalRepository.load();
// Retrieve the journal sheet meta first to get the date format.
const meta = await this.journalSheetMeta.meta(filter);
// Journal report instance. // Journal report instance.
const journalSheetInstance = new JournalSheet( const journalSheetInstance = new JournalSheet(
filter, filter,
this.journalRepository, this.journalRepository,
this.i18n, this.i18n,
{ baseCurrency: meta.baseCurrency, dateFormat: meta.dateFormat },
); );
// Retrieve journal report columns. // Retrieve journal report columns.
const journalSheetData = journalSheetInstance.reportData(); const journalSheetData = journalSheetInstance.reportData();
// Retrieve the journal sheet meta.
const meta = await this.journalSheetMeta.meta(filter);
// Triggers `onJournalViewed` event. // Triggers `onJournalViewed` event.
await this.eventPublisher.emitAsync(events.reports.onJournalViewed, { await this.eventPublisher.emitAsync(events.reports.onJournalViewed, {
query, query,

View File

@@ -28,6 +28,7 @@ import { FinancialSheetStructure } from '../../common/FinancialSheetStructure';
import { FinancialSheet } from '../../common/FinancialSheet'; import { FinancialSheet } from '../../common/FinancialSheet';
import { Account } from '@/modules/Accounts/models/Account.model'; import { Account } from '@/modules/Accounts/models/Account.model';
import { flatToNestedArray } from '@/utils/flat-to-nested-array'; import { flatToNestedArray } from '@/utils/flat-to-nested-array';
import { IFinancialReportMeta, DEFAULT_REPORT_META } from '../../types/Report.types';
export default class ProfitLossSheet extends R.pipe( export default class ProfitLossSheet extends R.pipe(
ProfitLossSheetPreviousYear, ProfitLossSheetPreviousYear,
@@ -71,20 +72,24 @@ export default class ProfitLossSheet extends R.pipe(
/** /**
* Constructor method. * Constructor method.
* @param {ProfitLossSheetRepository} repository -
* @param {IProfitLossSheetQuery} query - * @param {IProfitLossSheetQuery} query -
* @param {IAccount[]} accounts - * @param {I18nService} i18n -
* @param {IJournalPoster} transactionsJournal - * @param {IFinancialReportMeta} meta -
*/ */
constructor( constructor(
repository: ProfitLossSheetRepository, repository: ProfitLossSheetRepository,
query: IProfitLossSheetQuery, query: IProfitLossSheetQuery,
i18n: I18nService, i18n: I18nService,
meta: IFinancialReportMeta,
) { ) {
super(); super();
this.query = new ProfitLossSheetQuery(query); this.query = new ProfitLossSheetQuery(query);
this.repository = repository; this.repository = repository;
this.baseCurrency = meta.baseCurrency;
this.numberFormat = this.query.query.numberFormat; this.numberFormat = this.query.query.numberFormat;
this.dateFormat = meta.dateFormat || DEFAULT_REPORT_META.dateFormat;
this.i18n = i18n; this.i18n = i18n;
} }

View File

@@ -19,8 +19,8 @@ export class ProfitLossSheetMeta {
query: IProfitLossSheetQuery, query: IProfitLossSheetQuery,
): Promise<IProfitLossSheetMeta> { ): Promise<IProfitLossSheetMeta> {
const commonMeta = await this.financialSheetMeta.meta(); const commonMeta = await this.financialSheetMeta.meta();
const formattedToDate = moment(query.toDate).format('YYYY/MM/DD'); const formattedToDate = moment(query.toDate).format(commonMeta.dateFormat);
const formattedFromDate = moment(query.fromDate).format('YYYY/MM/DD'); const formattedFromDate = moment(query.fromDate).format(commonMeta.dateFormat);
const formattedDateRange = `From ${formattedFromDate} | To ${formattedToDate}`; const formattedDateRange = `From ${formattedFromDate} | To ${formattedToDate}`;
const sheetName = 'Cashflow Statement'; const sheetName = 'Cashflow Statement';

View File

@@ -40,18 +40,19 @@ export class ProfitLossSheetService {
this.profitLossRepository.setFilter(filter); this.profitLossRepository.setFilter(filter);
await this.profitLossRepository.asyncInitialize(); await this.profitLossRepository.asyncInitialize();
// Retrieve the profit/loss sheet meta first to get date format.
const meta = await this.profitLossSheetMeta.meta(filter);
// Profit/Loss report instance. // Profit/Loss report instance.
const profitLossInstance = new ProfitLossSheet( const profitLossInstance = new ProfitLossSheet(
this.profitLossRepository, this.profitLossRepository,
filter, filter,
this.i18nService, this.i18nService,
{ baseCurrency: meta.baseCurrency, dateFormat: meta.dateFormat },
); );
// Profit/loss report data and columns. // Profit/loss report data and columns.
const data = profitLossInstance.reportData(); const data = profitLossInstance.reportData();
// Retrieve the profit/loss sheet meta.
const meta = await this.profitLossSheetMeta.meta(filter);
// Triggers `onProfitLossSheetViewed` event. // Triggers `onProfitLossSheetViewed` event.
await this.eventPublisher.emitAsync( await this.eventPublisher.emitAsync(
events.reports.onProfitLossSheetViewed, events.reports.onProfitLossSheetViewed,

View File

@@ -73,17 +73,17 @@ export class PurchasesByItemsService {
// Filter the date range of the sheet. // Filter the date range of the sheet.
builder.modify('filterDateRange', filter.fromDate, filter.toDate); builder.modify('filterDateRange', filter.fromDate, filter.toDate);
}); });
// Retrieve the purchases by items meta first to get date format.
const meta = await this.purchasesByItemsMeta.meta(query);
const purchasesByItemsInstance = new PurchasesByItems( const purchasesByItemsInstance = new PurchasesByItems(
filter, filter,
inventoryItems, inventoryItems,
inventoryTransactions, inventoryTransactions,
tenantMetadata.baseCurrency, { baseCurrency: tenantMetadata.baseCurrency, dateFormat: meta.dateFormat },
); );
const purchasesByItemsData = purchasesByItemsInstance.reportData(); const purchasesByItemsData = purchasesByItemsInstance.reportData();
// Retrieve the purchases by items meta.
const meta = await this.purchasesByItemsMeta.meta(query);
// Triggers `onPurchasesByItemViewed` event. // Triggers `onPurchasesByItemViewed` event.
await this.eventPublisher.emitAsync( await this.eventPublisher.emitAsync(
events.reports.onPurchasesByItemViewed, events.reports.onPurchasesByItemViewed,

View File

@@ -11,6 +11,7 @@ import { FinancialSheet } from '../../common/FinancialSheet';
import { transformToMapBy } from '@/utils/transform-to-map-by'; import { transformToMapBy } from '@/utils/transform-to-map-by';
import { Item } from '@/modules/Items/models/Item'; import { Item } from '@/modules/Items/models/Item';
import { InventoryTransaction } from '@/modules/InventoryCost/models/InventoryTransaction'; import { InventoryTransaction } from '@/modules/InventoryCost/models/InventoryTransaction';
import { IFinancialReportMeta, DEFAULT_REPORT_META } from '../../types/Report.types';
export class PurchasesByItems extends FinancialSheet{ export class PurchasesByItems extends FinancialSheet{
readonly baseCurrency: string; readonly baseCurrency: string;
@@ -29,14 +30,15 @@ export class PurchasesByItems extends FinancialSheet{
query: IPurchasesByItemsReportQuery, query: IPurchasesByItemsReportQuery,
items: Item[], items: Item[],
itemsTransactions: InventoryTransaction[], itemsTransactions: InventoryTransaction[],
baseCurrency: string meta: IFinancialReportMeta,
) { ) {
super(); super();
this.baseCurrency = baseCurrency; this.baseCurrency = meta.baseCurrency;
this.items = items; this.items = items;
this.itemsTransactions = transformToMapBy(itemsTransactions, 'itemId'); this.itemsTransactions = transformToMapBy(itemsTransactions, 'itemId');
this.query = query; this.query = query;
this.numberFormat = this.query.numberFormat; this.numberFormat = this.query.numberFormat;
this.dateFormat = meta.dateFormat || DEFAULT_REPORT_META.dateFormat;
} }
/** /**

View File

@@ -21,8 +21,8 @@ export class PurchasesByItemsMeta {
query: IPurchasesByItemsReportQuery query: IPurchasesByItemsReportQuery
): Promise<IPurchasesByItemsSheetMeta> { ): Promise<IPurchasesByItemsSheetMeta> {
const commonMeta = await this.financialSheetMetaModel.meta(); const commonMeta = await this.financialSheetMetaModel.meta();
const formattedToDate = moment(query.toDate).format('YYYY/MM/DD'); const formattedToDate = moment(query.toDate).format(commonMeta.dateFormat);
const formattedFromDate = moment(query.fromDate).format('YYYY/MM/DD'); const formattedFromDate = moment(query.fromDate).format(commonMeta.dateFormat);
const formattedDateRange = `From ${formattedFromDate} | To ${formattedToDate}`; const formattedDateRange = `From ${formattedFromDate} | To ${formattedToDate}`;
return { return {

View File

@@ -12,6 +12,7 @@ import { Item } from '@/modules/Items/models/Item';
import { transformToMap } from '@/utils/transform-to-key'; import { transformToMap } from '@/utils/transform-to-key';
import { allPassedConditionsPass } from '@/utils/all-conditions-passed'; import { allPassedConditionsPass } from '@/utils/all-conditions-passed';
import { InventoryTransaction } from '@/modules/InventoryCost/models/InventoryTransaction'; import { InventoryTransaction } from '@/modules/InventoryCost/models/InventoryTransaction';
import { IFinancialReportMeta, DEFAULT_REPORT_META } from '../../types/Report.types';
export class SalesByItemsReport extends FinancialSheet { export class SalesByItemsReport extends FinancialSheet {
readonly baseCurrency: string; readonly baseCurrency: string;
@@ -24,21 +25,22 @@ export class SalesByItemsReport extends FinancialSheet {
* @param {ISalesByItemsReportQuery} query * @param {ISalesByItemsReportQuery} query
* @param {IItem[]} items * @param {IItem[]} items
* @param {IAccountTransaction[]} itemsTransactions * @param {IAccountTransaction[]} itemsTransactions
* @param {string} baseCurrency * @param {IFinancialReportMeta} meta
*/ */
constructor( constructor(
query: ISalesByItemsReportQuery, query: ISalesByItemsReportQuery,
items: Item[], items: Item[],
itemsTransactions: ModelObject<InventoryTransaction>[], itemsTransactions: ModelObject<InventoryTransaction>[],
baseCurrency: string, meta: IFinancialReportMeta,
) { ) {
super(); super();
this.baseCurrency = baseCurrency; this.baseCurrency = meta.baseCurrency;
this.items = items; this.items = items;
this.itemsTransactions = transformToMap(itemsTransactions, 'itemId'); this.itemsTransactions = transformToMap(itemsTransactions, 'itemId');
this.query = query; this.query = query;
this.numberFormat = this.query.numberFormat; this.numberFormat = this.query.numberFormat;
this.dateFormat = meta.dateFormat || DEFAULT_REPORT_META.dateFormat;
} }
/** /**

View File

@@ -18,8 +18,8 @@ export class SalesByItemsMeta {
query: ISalesByItemsReportQuery, query: ISalesByItemsReportQuery,
): Promise<ISalesByItemsSheetMeta> { ): Promise<ISalesByItemsSheetMeta> {
const commonMeta = await this.financialSheetMeta.meta(); const commonMeta = await this.financialSheetMeta.meta();
const formattedToDate = moment(query.toDate).format('YYYY/MM/DD'); const formattedToDate = moment(query.toDate).format(commonMeta.dateFormat);
const formattedFromDate = moment(query.fromDate).format('YYYY/MM/DD'); const formattedFromDate = moment(query.fromDate).format(commonMeta.dateFormat);
const formattedDateRange = `From ${formattedFromDate} | To ${formattedToDate}`; const formattedDateRange = `From ${formattedFromDate} | To ${formattedToDate}`;
const sheetName = 'Sales By Items'; const sheetName = 'Sales By Items';

View File

@@ -68,17 +68,17 @@ export class SalesByItemsReportService {
// Filter the date range of the sheet. // Filter the date range of the sheet.
builder.modify('filterDateRange', filter.fromDate, filter.toDate); builder.modify('filterDateRange', filter.fromDate, filter.toDate);
}); });
// Retrieve the sales by items meta first to get date format.
const meta = await this.salesByItemsMeta.meta(query);
const sheet = new SalesByItemsReport( const sheet = new SalesByItemsReport(
filter, filter,
inventoryItems, inventoryItems,
inventoryTransactions, inventoryTransactions,
tenantMetadata.baseCurrency, { baseCurrency: tenantMetadata.baseCurrency, dateFormat: meta.dateFormat },
); );
const salesByItemsData = sheet.reportData(); const salesByItemsData = sheet.reportData();
// Retrieve the sales by items meta.
const meta = await this.salesByItemsMeta.meta(query);
// Triggers `onSalesByItemViewed` event. // Triggers `onSalesByItemViewed` event.
await this.eventPublisher.emitAsync(events.reports.onSalesByItemViewed, { await this.eventPublisher.emitAsync(events.reports.onSalesByItemViewed, {
query, query,

View File

@@ -8,9 +8,10 @@ import { SalesTaxLiabilitySummaryController } from './SalesTaxLiabilitySummary.c
import { FinancialSheetCommonModule } from '../../common/FinancialSheetCommon.module'; import { FinancialSheetCommonModule } from '../../common/FinancialSheetCommon.module';
import { SalesTaxLiabilitySummaryRepository } from './SalesTaxLiabilitySummaryRepository'; import { SalesTaxLiabilitySummaryRepository } from './SalesTaxLiabilitySummaryRepository';
import { SalesTaxLiabilitySummaryMeta } from './SalesTaxLiabilitySummaryMeta'; import { SalesTaxLiabilitySummaryMeta } from './SalesTaxLiabilitySummaryMeta';
import { TenancyModule } from '@/modules/Tenancy/Tenancy.module';
@Module({ @Module({
imports: [FinancialSheetCommonModule], imports: [FinancialSheetCommonModule, TenancyModule],
providers: [ providers: [
SalesTaxLiabiltiySummaryPdf, SalesTaxLiabiltiySummaryPdf,
SalesTaxLiabilitySummaryTableInjectable, SalesTaxLiabilitySummaryTableInjectable,

View File

@@ -10,6 +10,7 @@ import { FinancialSheet } from '../../common/FinancialSheet';
import { ModelObject } from 'objection'; import { ModelObject } from 'objection';
import { TaxRateModel } from '@/modules/TaxRates/models/TaxRate.model'; import { TaxRateModel } from '@/modules/TaxRates/models/TaxRate.model';
import { SalesTaxLiabilitySummaryRepository } from './SalesTaxLiabilitySummaryRepository'; import { SalesTaxLiabilitySummaryRepository } from './SalesTaxLiabilitySummaryRepository';
import { IFinancialReportMeta, DEFAULT_REPORT_META } from '../../types/Report.types';
export class SalesTaxLiabilitySummary extends FinancialSheet { export class SalesTaxLiabilitySummary extends FinancialSheet {
private query: SalesTaxLiabilitySummaryQuery; private query: SalesTaxLiabilitySummaryQuery;
@@ -18,18 +19,19 @@ export class SalesTaxLiabilitySummary extends FinancialSheet {
/** /**
* Sales tax liability summary constructor. * Sales tax liability summary constructor.
* @param {SalesTaxLiabilitySummaryQuery} query * @param {SalesTaxLiabilitySummaryQuery} query
* @param {ITaxRate[]} taxRates * @param {SalesTaxLiabilitySummaryRepository} repository
* @param {SalesTaxLiabilitySummaryPayableById} payableTaxesById * @param {IFinancialReportMeta} meta
* @param {SalesTaxLiabilitySummarySalesById} salesTaxesById
*/ */
constructor( constructor(
query: SalesTaxLiabilitySummaryQuery, query: SalesTaxLiabilitySummaryQuery,
repository: SalesTaxLiabilitySummaryRepository, repository: SalesTaxLiabilitySummaryRepository,
meta: IFinancialReportMeta,
) { ) {
super(); super();
this.query = query; this.query = query;
this.repository = repository; this.repository = repository;
this.dateFormat = meta.dateFormat || DEFAULT_REPORT_META.dateFormat;
} }
/** /**

View File

@@ -14,8 +14,8 @@ export class SalesTaxLiabilitySummaryMeta {
*/ */
public async meta(query: SalesTaxLiabilitySummaryQuery) { public async meta(query: SalesTaxLiabilitySummaryQuery) {
const commonMeta = await this.financialSheetMeta.meta(); const commonMeta = await this.financialSheetMeta.meta();
const formattedToDate = moment(query.toDate).format('YYYY/MM/DD'); const formattedToDate = moment(query.toDate).format(commonMeta.dateFormat);
const formattedFromDate = moment(query.fromDate).format('YYYY/MM/DD'); const formattedFromDate = moment(query.fromDate).format(commonMeta.dateFormat);
const formattedDateRange = `From ${formattedFromDate} | To ${formattedToDate}`; const formattedDateRange = `From ${formattedFromDate} | To ${formattedToDate}`;
const sheetName = 'Sales Tax Liability Summary'; const sheetName = 'Sales Tax Liability Summary';

View File

@@ -3,12 +3,14 @@ import { SalesTaxLiabilitySummary } from './SalesTaxLiabilitySummary';
import { SalesTaxLiabilitySummaryMeta } from './SalesTaxLiabilitySummaryMeta'; import { SalesTaxLiabilitySummaryMeta } from './SalesTaxLiabilitySummaryMeta';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { SalesTaxLiabilitySummaryQuery } from './SalesTaxLiability.types'; import { SalesTaxLiabilitySummaryQuery } from './SalesTaxLiability.types';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
@Injectable() @Injectable()
export class SalesTaxLiabilitySummaryService { export class SalesTaxLiabilitySummaryService {
constructor( constructor(
private readonly repository: SalesTaxLiabilitySummaryRepository, private readonly repository: SalesTaxLiabilitySummaryRepository,
private readonly salesTaxLiabilityMeta: SalesTaxLiabilitySummaryMeta, private readonly salesTaxLiabilityMeta: SalesTaxLiabilitySummaryMeta,
private readonly tenancyContext: TenancyContext,
) {} ) {}
/** /**
@@ -19,11 +21,17 @@ export class SalesTaxLiabilitySummaryService {
public async salesTaxLiability(query: SalesTaxLiabilitySummaryQuery) { public async salesTaxLiability(query: SalesTaxLiabilitySummaryQuery) {
await this.repository.load(); await this.repository.load();
// Retrieve the meta first to get date format.
const meta = await this.salesTaxLiabilityMeta.meta(query);
// Get tenant metadata for baseCurrency
const tenantMetadata = await this.tenancyContext.getTenantMetadata();
const taxLiabilitySummary = new SalesTaxLiabilitySummary( const taxLiabilitySummary = new SalesTaxLiabilitySummary(
query, query,
this.repository, this.repository,
{ baseCurrency: tenantMetadata.baseCurrency, dateFormat: meta.dateFormat },
); );
const meta = await this.salesTaxLiabilityMeta.meta(query);
return { return {
data: taxLiabilitySummary.reportData(), data: taxLiabilitySummary.reportData(),

View File

@@ -14,9 +14,10 @@ enum ROW_TYPE {
export class TransactionsByContactsTableRows { export class TransactionsByContactsTableRows {
public i18n: I18nService; public i18n: I18nService;
public dateFormat: string;
public dateAccessor = (value): string => { public dateAccessor = (value): string => {
return moment(value.date).format('YYYY MMM DD'); return moment(value.date).format(this.dateFormat);
}; };
/** /**

View File

@@ -12,6 +12,7 @@ import { TransactionsByContact } from '../TransactionsByContact/TransactionsByCo
import { Customer } from '@/modules/Customers/models/Customer'; import { Customer } from '@/modules/Customers/models/Customer';
import { INumberFormatQuery } from '../../types/Report.types'; import { INumberFormatQuery } from '../../types/Report.types';
import { TransactionsByCustomersRepository } from './TransactionsByCustomersRepository'; import { TransactionsByCustomersRepository } from './TransactionsByCustomersRepository';
import { IFinancialReportMeta, DEFAULT_REPORT_META } from '../../types/Report.types';
const CUSTOMER_NORMAL = 'debit'; const CUSTOMER_NORMAL = 'debit';
@@ -25,12 +26,14 @@ export class TransactionsByCustomers extends TransactionsByContact {
* Constructor method. * Constructor method.
* @param {ICustomer} customers * @param {ICustomer} customers
* @param {Map<number, IAccountTransaction[]>} transactionsLedger * @param {Map<number, IAccountTransaction[]>} transactionsLedger
* @param {string} baseCurrency * @param {I18nService} i18n
* @param {IFinancialReportMeta} meta
*/ */
constructor( constructor(
filter: ITransactionsByCustomersFilter, filter: ITransactionsByCustomersFilter,
transactionsByCustomersRepository: TransactionsByCustomersRepository, transactionsByCustomersRepository: TransactionsByCustomersRepository,
i18n: I18nService i18n: I18nService,
meta: IFinancialReportMeta,
) { ) {
super(); super();
@@ -38,6 +41,8 @@ export class TransactionsByCustomers extends TransactionsByContact {
this.repository = transactionsByCustomersRepository; this.repository = transactionsByCustomersRepository;
this.numberFormat = this.filter.numberFormat; this.numberFormat = this.filter.numberFormat;
this.i18n = i18n; this.i18n = i18n;
this.baseCurrency = meta.baseCurrency;
this.dateFormat = meta.dateFormat || DEFAULT_REPORT_META.dateFormat;
} }
/** /**

View File

@@ -20,8 +20,8 @@ export class TransactionsByCustomersMeta {
): Promise<ITransactionsByCustomersMeta> { ): Promise<ITransactionsByCustomersMeta> {
const commonMeta = await this.financialSheetMeta.meta(); const commonMeta = await this.financialSheetMeta.meta();
const formattedToDate = moment(query.toDate).format('YYYY/MM/DD'); const formattedToDate = moment(query.toDate).format(commonMeta.dateFormat);
const formattedFromDate = moment(query.fromDate).format('YYYY/MM/DD'); const formattedFromDate = moment(query.fromDate).format(commonMeta.dateFormat);
const formattedDateRange = `From ${formattedFromDate} | To ${formattedToDate}`; const formattedDateRange = `From ${formattedFromDate} | To ${formattedToDate}`;
return { return {

View File

@@ -36,13 +36,16 @@ export class TransactionsByCustomersSheet {
this.transactionsByCustomersRepository.setFilter(filter); this.transactionsByCustomersRepository.setFilter(filter);
await this.transactionsByCustomersRepository.asyncInit(); await this.transactionsByCustomersRepository.asyncInit();
// Retrieve the meta first to get date format.
const meta = await this.transactionsByCustomersMeta.meta(filter);
// Transactions by customers data mapper. // Transactions by customers data mapper.
const reportInstance = new TransactionsByCustomers( const reportInstance = new TransactionsByCustomers(
filter, filter,
this.transactionsByCustomersRepository, this.transactionsByCustomersRepository,
this.i18n, this.i18n,
{ baseCurrency: meta.baseCurrency, dateFormat: meta.dateFormat },
); );
const meta = await this.transactionsByCustomersMeta.meta(filter);
// Triggers `onCustomerTransactionsViewed` event. // Triggers `onCustomerTransactionsViewed` event.
await this.eventPublisher.emitAsync( await this.eventPublisher.emitAsync(

View File

@@ -22,10 +22,12 @@ export class TransactionsByCustomersTable extends TransactionsByContactsTableRow
constructor( constructor(
customersTransactions: ITransactionsByCustomersCustomer[], customersTransactions: ITransactionsByCustomersCustomer[],
i18n: I18nService, i18n: I18nService,
dateFormat: string,
) { ) {
super(); super();
this.customersTransactions = customersTransactions; this.customersTransactions = customersTransactions;
this.i18n = i18n; this.i18n = i18n;
this.dateFormat = dateFormat;
} }
/** /**

View File

@@ -28,6 +28,7 @@ export class TransactionsByCustomersTableInjectable {
const table = new TransactionsByCustomersTable( const table = new TransactionsByCustomersTable(
customersTransactions.data, customersTransactions.data,
this.i18n, this.i18n,
customersTransactions.meta.dateFormat,
); );
return { return {
table: { table: {

View File

@@ -38,7 +38,7 @@ export class TransactionsByReferenceService {
const report = new TransactionsByReference( const report = new TransactionsByReference(
transactions, transactions,
filter, filter,
tenantMetadata.baseCurrency { baseCurrency: tenantMetadata.baseCurrency, dateFormat: tenantMetadata.dateFormat }
); );
return { return {

View File

@@ -4,7 +4,7 @@ import {
import { FinancialSheet } from '../../common/FinancialSheet'; import { FinancialSheet } from '../../common/FinancialSheet';
import { ModelObject } from 'objection'; import { ModelObject } from 'objection';
import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model'; import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model';
import { INumberFormatQuery } from '../../types/Report.types'; import { INumberFormatQuery, IFinancialReportMeta, DEFAULT_REPORT_META } from '../../types/Report.types';
import { TransactionsByReferenceQueryDto } from './TransactionsByReferenceQuery.dto'; import { TransactionsByReferenceQueryDto } from './TransactionsByReferenceQuery.dto';
export class TransactionsByReference extends FinancialSheet { export class TransactionsByReference extends FinancialSheet {
@@ -17,18 +17,19 @@ export class TransactionsByReference extends FinancialSheet {
* Constructor method. * Constructor method.
* @param {ModelObject<AccountTransaction>[]} transactions * @param {ModelObject<AccountTransaction>[]} transactions
* @param {TransactionsByVendorQueryDto} query * @param {TransactionsByVendorQueryDto} query
* @param {string} baseCurrency * @param {IFinancialReportMeta} meta
*/ */
constructor( constructor(
transactions: ModelObject<AccountTransaction>[], transactions: ModelObject<AccountTransaction>[],
query: TransactionsByReferenceQueryDto, query: TransactionsByReferenceQueryDto,
baseCurrency: string meta: IFinancialReportMeta,
) { ) {
super(); super();
this.transactions = transactions; this.transactions = transactions;
this.query = query; this.query = query;
this.baseCurrency = baseCurrency; this.baseCurrency = meta.baseCurrency;
this.dateFormat = meta.dateFormat || DEFAULT_REPORT_META.dateFormat;
// this.numberFormat = this.query.numberFormat; // this.numberFormat = this.query.numberFormat;
} }

View File

@@ -12,6 +12,7 @@ import { TransactionsByContact } from '../TransactionsByContact/TransactionsByCo
import { Vendor } from '@/modules/Vendors/models/Vendor'; import { Vendor } from '@/modules/Vendors/models/Vendor';
import { INumberFormatQuery } from '../../types/Report.types'; import { INumberFormatQuery } from '../../types/Report.types';
import { TransactionsByVendorRepository } from './TransactionsByVendorRepository'; import { TransactionsByVendorRepository } from './TransactionsByVendorRepository';
import { IFinancialReportMeta, DEFAULT_REPORT_META } from '../../types/Report.types';
const VENDOR_NORMAL = 'credit'; const VENDOR_NORMAL = 'credit';
@@ -26,11 +27,13 @@ export class TransactionsByVendor extends TransactionsByContact {
* @param {TransactionsByVendorRepository} transactionsByVendorRepository - Transactions by vendor repository. * @param {TransactionsByVendorRepository} transactionsByVendorRepository - Transactions by vendor repository.
* @param {ITransactionsByVendorsFilter} filter - Transactions by vendors filter. * @param {ITransactionsByVendorsFilter} filter - Transactions by vendors filter.
* @param {I18nService} i18n - Internationalization service. * @param {I18nService} i18n - Internationalization service.
* @param {IFinancialReportMeta} meta - Report meta.
*/ */
constructor( constructor(
transactionsByVendorRepository: TransactionsByVendorRepository, transactionsByVendorRepository: TransactionsByVendorRepository,
filter: ITransactionsByVendorsFilter, filter: ITransactionsByVendorsFilter,
i18n: I18nService, i18n: I18nService,
meta: IFinancialReportMeta,
) { ) {
super(); super();
@@ -38,6 +41,8 @@ export class TransactionsByVendor extends TransactionsByContact {
this.filter = filter; this.filter = filter;
this.numberFormat = this.filter.numberFormat; this.numberFormat = this.filter.numberFormat;
this.i18n = i18n; this.i18n = i18n;
this.baseCurrency = meta.baseCurrency;
this.dateFormat = meta.dateFormat || DEFAULT_REPORT_META.dateFormat;
} }
/** /**

View File

@@ -36,13 +36,16 @@ export class TransactionsByVendorsInjectable {
// Initialize the repository. // Initialize the repository.
await this.transactionsByVendorRepository.asyncInit(); await this.transactionsByVendorRepository.asyncInit();
// Transactions by customers data mapper. // Retrieve the meta first to get date format.
const meta = await this.transactionsByVendorMeta.meta(filter);
// Transactions by vendors data mapper.
const reportInstance = new TransactionsByVendor( const reportInstance = new TransactionsByVendor(
this.transactionsByVendorRepository, this.transactionsByVendorRepository,
filter, filter,
this.i18n, this.i18n,
{ baseCurrency: meta.baseCurrency, dateFormat: meta.dateFormat },
); );
const meta = await this.transactionsByVendorMeta.meta(filter);
// Triggers `onVendorTransactionsViewed` event. // Triggers `onVendorTransactionsViewed` event.
await this.eventPublisher.emitAsync( await this.eventPublisher.emitAsync(

View File

@@ -21,8 +21,8 @@ export class TransactionsByVendorMeta {
): Promise<ITransactionsByVendorMeta> { ): Promise<ITransactionsByVendorMeta> {
const commonMeta = await this.financialSheetMeta.meta(); const commonMeta = await this.financialSheetMeta.meta();
const formattedToDate = moment(query.toDate).format('YYYY/MM/DD'); const formattedToDate = moment(query.toDate).format(commonMeta.dateFormat);
const formattedFromDate = moment(query.fromDate).format('YYYY/MM/DD'); const formattedFromDate = moment(query.fromDate).format(commonMeta.dateFormat);
const formattedDateRange = `From ${formattedFromDate} | To ${formattedToDate}`; const formattedDateRange = `From ${formattedFromDate} | To ${formattedToDate}`;
const sheetName = 'Transactions By Vendor'; const sheetName = 'Transactions By Vendor';

View File

@@ -1,4 +1,5 @@
import * as R from 'ramda'; import * as R from 'ramda';
import { I18nService } from 'nestjs-i18n';
import { ITransactionsByVendorsVendor } from './TransactionsByVendor.types'; import { ITransactionsByVendorsVendor } from './TransactionsByVendor.types';
import { TransactionsByContactsTableRows } from '../TransactionsByContact/TransactionsByContactTableRows'; import { TransactionsByContactsTableRows } from '../TransactionsByContact/TransactionsByContactTableRows';
import { tableRowMapper } from '../../utils/Table.utils'; import { tableRowMapper } from '../../utils/Table.utils';
@@ -19,11 +20,16 @@ export class TransactionsByVendorsTable extends TransactionsByContactsTableRows
* @param {ITransactionsByVendorsVendor[]} vendorsTransactions - * @param {ITransactionsByVendorsVendor[]} vendorsTransactions -
* @param {any} i18n * @param {any} i18n
*/ */
constructor(vendorsTransactions: ITransactionsByVendorsVendor[], i18n) { constructor(
vendorsTransactions: ITransactionsByVendorsVendor[],
i18n: I18nService,
dateFormat: string,
) {
super(); super();
this.vendorsTransactions = vendorsTransactions; this.vendorsTransactions = vendorsTransactions;
this.i18n = i18n; this.i18n = i18n;
this.dateFormat = dateFormat;
} }
/** /**

View File

@@ -25,7 +25,7 @@ export class TransactionsByVendorTableInjectable {
const sheet = await this.transactionsByVendor.transactionsByVendors( const sheet = await this.transactionsByVendor.transactionsByVendors(
query query
); );
const table = new TransactionsByVendorsTable(sheet.data, this.i18n); const table = new TransactionsByVendorsTable(sheet.data, this.i18n, sheet.meta.dateFormat);
return { return {
table: { table: {

View File

@@ -12,6 +12,7 @@ import { Account } from '@/modules/Accounts/models/Account.model';
import { allPassedConditionsPass } from '@/utils/all-conditions-passed'; import { allPassedConditionsPass } from '@/utils/all-conditions-passed';
import { ModelObject } from 'objection'; import { ModelObject } from 'objection';
import { flatToNestedArray } from '@/utils/flat-to-nested-array'; import { flatToNestedArray } from '@/utils/flat-to-nested-array';
import { IFinancialReportMeta, DEFAULT_REPORT_META } from '../../types/Report.types';
export class TrialBalanceSheet extends FinancialSheet { export class TrialBalanceSheet extends FinancialSheet {
/** /**
@@ -42,14 +43,15 @@ export class TrialBalanceSheet extends FinancialSheet {
constructor( constructor(
query: ITrialBalanceSheetQuery, query: ITrialBalanceSheetQuery,
repository: TrialBalanceSheetRepository, repository: TrialBalanceSheetRepository,
baseCurrency: string meta: IFinancialReportMeta,
) { ) {
super(); super();
this.query = query; this.query = query;
this.repository = repository; this.repository = repository;
this.numberFormat = this.query.numberFormat; this.numberFormat = this.query.numberFormat;
this.baseCurrency = baseCurrency; this.baseCurrency = meta.baseCurrency;
this.dateFormat = meta.dateFormat || DEFAULT_REPORT_META.dateFormat;
} }
/** /**

View File

@@ -39,18 +39,18 @@ export class TrialBalanceSheetService {
// Loads the resources. // Loads the resources.
await this.trialBalanceSheetRepository.asyncInitialize(); await this.trialBalanceSheetRepository.asyncInitialize();
// Trial balance sheet meta first to get date format.
const meta = await this.trialBalanceSheetMetaService.meta(filter);
// Trial balance report instance. // Trial balance report instance.
const trialBalanceInstance = new TrialBalanceSheet( const trialBalanceInstance = new TrialBalanceSheet(
filter, filter,
this.trialBalanceSheetRepository, this.trialBalanceSheetRepository,
tenantMetadata.baseCurrency, { baseCurrency: tenantMetadata.baseCurrency, dateFormat: meta.dateFormat },
); );
// Trial balance sheet data. // Trial balance sheet data.
const trialBalanceSheetData = trialBalanceInstance.reportData(); const trialBalanceSheetData = trialBalanceInstance.reportData();
// Trial balance sheet meta.
const meta = await this.trialBalanceSheetMetaService.meta(filter);
// Triggers `onTrialBalanceSheetViewed` event. // Triggers `onTrialBalanceSheetViewed` event.
await this.eventPublisher.emitAsync( await this.eventPublisher.emitAsync(
events.reports.onTrialBalanceSheetView, events.reports.onTrialBalanceSheetView,

View File

@@ -16,8 +16,8 @@ export class TrialBalanceSheetMeta {
): Promise<ITrialBalanceSheetMeta> { ): Promise<ITrialBalanceSheetMeta> {
const commonMeta = await this.financialSheetMeta.meta(); const commonMeta = await this.financialSheetMeta.meta();
const formattedFromDate = moment(query.fromDate).format('YYYY/MM/DD'); const formattedFromDate = moment(query.fromDate).format(commonMeta.dateFormat);
const formattedToDate = moment(query.toDate).format('YYYY/MM/DD'); const formattedToDate = moment(query.toDate).format(commonMeta.dateFormat);
const formattedDateRange = `From ${formattedFromDate} to ${formattedToDate}`; const formattedDateRange = `From ${formattedFromDate} to ${formattedToDate}`;
const sheetName = 'Trial Balance Sheet'; const sheetName = 'Trial Balance Sheet';

View File

@@ -8,7 +8,7 @@ import {
} from './VendorBalanceSummary.types'; } from './VendorBalanceSummary.types';
import { ContactBalanceSummaryReport } from '../ContactBalanceSummary/ContactBalanceSummary'; import { ContactBalanceSummaryReport } from '../ContactBalanceSummary/ContactBalanceSummary';
import { Vendor } from '@/modules/Vendors/models/Vendor'; import { Vendor } from '@/modules/Vendors/models/Vendor';
import { INumberFormatQuery } from '../../types/Report.types'; import { INumberFormatQuery, IFinancialReportMeta, DEFAULT_REPORT_META } from '../../types/Report.types';
import { VendorBalanceSummaryRepository } from './VendorBalanceSummaryRepository'; import { VendorBalanceSummaryRepository } from './VendorBalanceSummaryRepository';
import { Ledger } from '@/modules/Ledger/Ledger'; import { Ledger } from '@/modules/Ledger/Ledger';
@@ -27,15 +27,17 @@ export class VendorBalanceSummaryReport extends ContactBalanceSummaryReport {
constructor( constructor(
repository: VendorBalanceSummaryRepository, repository: VendorBalanceSummaryRepository,
filter: IVendorBalanceSummaryQuery, filter: IVendorBalanceSummaryQuery,
meta: IFinancialReportMeta,
) { ) {
super(); super();
this.repository = repository; this.repository = repository;
this.ledger = this.repository.ledger; this.ledger = this.repository.ledger;
this.baseCurrency = this.repository.baseCurrency; this.baseCurrency = meta.baseCurrency;
this.filter = filter; this.filter = filter;
this.numberFormat = this.filter.numberFormat; this.numberFormat = this.filter.numberFormat;
this.dateFormat = meta.dateFormat || DEFAULT_REPORT_META.dateFormat;
} }

View File

@@ -19,7 +19,7 @@ export class VendorBalanceSummaryMeta {
query: IVendorBalanceSummaryQuery, query: IVendorBalanceSummaryQuery,
): Promise<IVendorBalanceSummaryMeta> { ): Promise<IVendorBalanceSummaryMeta> {
const commonMeta = await this.financialSheetMeta.meta(); const commonMeta = await this.financialSheetMeta.meta();
const formattedAsDate = moment(query.asDate).format('YYYY/MM/DD'); const formattedAsDate = moment(query.asDate).format(commonMeta.dateFormat);
return { return {
...commonMeta, ...commonMeta,

View File

@@ -31,13 +31,15 @@ export class VendorBalanceSummaryService {
this.vendorBalanceSummaryRepository.setFilter(filter); this.vendorBalanceSummaryRepository.setFilter(filter);
await this.vendorBalanceSummaryRepository.asyncInit(); await this.vendorBalanceSummaryRepository.asyncInit();
// Retrieve the vendor balance summary meta first to get date format.
const meta = await this.vendorBalanceSummaryMeta.meta(filter);
// Report instance. // Report instance.
const reportInstance = new VendorBalanceSummaryReport( const reportInstance = new VendorBalanceSummaryReport(
this.vendorBalanceSummaryRepository, this.vendorBalanceSummaryRepository,
filter, filter,
{ baseCurrency: this.vendorBalanceSummaryRepository.baseCurrency, dateFormat: meta.dateFormat },
); );
// Retrieve the vendor balance summary meta.
const meta = await this.vendorBalanceSummaryMeta.meta(filter);
// Triggers `onVendorBalanceSummaryViewed` event. // Triggers `onVendorBalanceSummaryViewed` event.
await this.eventEmitter.emitAsync( await this.eventEmitter.emitAsync(

View File

@@ -52,6 +52,22 @@ export interface IFinancialSheetCommonMeta {
sheetName: string; sheetName: string;
} }
/**
* Report meta interface for sheet constructors.
* Combines baseCurrency and dateFormat for a cleaner API.
*/
export interface IFinancialReportMeta {
baseCurrency: string;
dateFormat: string;
}
/**
* Default report meta values.
*/
export const DEFAULT_REPORT_META: Omit<IFinancialReportMeta, 'baseCurrency'> = {
dateFormat: 'YYYY MMM DD',
};
export enum IFinancialDatePeriodsUnit { export enum IFinancialDatePeriodsUnit {
Day = 'day', Day = 'day',
Month = 'month', Month = 'month',

View File

@@ -32,7 +32,7 @@ export class InventoryAdjustment extends TenantBaseModel {
* Timestamps columns. * Timestamps columns.
*/ */
get timestamps(): Array<string> { get timestamps(): Array<string> {
return ['created_at']; return ['createdAt'];
} }
/** /**

View File

@@ -20,6 +20,13 @@ export class InventoryAdjustmentEntry extends BaseModel {
return 'inventory_adjustments_entries'; return 'inventory_adjustments_entries';
} }
/**
* Timestamps columns.
*/
get timestamps() {
return [];
}
/** /**
* Relationship mapping. * Relationship mapping.
*/ */

View File

@@ -13,6 +13,13 @@ export class InventoryTransactionMeta extends BaseModel {
return 'inventory_transaction_meta'; return 'inventory_transaction_meta';
} }
/**
* Timestamps columns.
*/
get timestamps() {
return [];
}
/** /**
* Relationship mapping. * Relationship mapping.
*/ */

View File

@@ -44,6 +44,13 @@ export class Item extends TenantBaseModel {
return 'items'; return 'items';
} }
/**
* Timestamps columns.
*/
get timestamps() {
return ['createdAt', 'updatedAt'];
}
/** /**
* Model modifiers. * Model modifiers.
*/ */

View File

@@ -1,18 +1,15 @@
import currencies from 'js-money/lib/currency'; import currencies from 'js-money/lib/currency';
export const DATE_FORMATS = [ export const DATE_FORMATS = [
'MM.dd.yy', 'MM/DD/YY',
'dd.MM.yy', 'DD/MM/YY',
'yy.MM.dd', 'YY/MM/DD',
'MM.dd.yyyy', 'MM/DD/yyyy',
'dd.MM.yyyy', 'DD/MM/yyyy',
'yyyy.MM.dd', 'yyyy/MM/DD',
'MM/DD/YYYY', 'DD MMM YYYY',
'M/D/YYYY', 'DD MMMM YYYY',
'dd MMM YYYY', 'MMMM DD, YYYY',
'dd MMMM YYYY',
'MMMM dd, YYYY',
'EEE, MMMM dd, YYYY',
]; ];
export const MONTHS = [ export const MONTHS = [
'january', 'january',

View File

@@ -28,6 +28,7 @@ import { UpdateOrganizationService } from './commands/UpdateOrganization.service
import { IgnoreTenantInitializedRoute } from '../Tenancy/EnsureTenantIsInitialized.guard'; import { IgnoreTenantInitializedRoute } from '../Tenancy/EnsureTenantIsInitialized.guard';
import { IgnoreTenantSeededRoute } from '../Tenancy/EnsureTenantIsSeeded.guards'; import { IgnoreTenantSeededRoute } from '../Tenancy/EnsureTenantIsSeeded.guards';
import { IgnoreTenantModelsInitialize } from '../Tenancy/TenancyInitializeModels.guard'; import { IgnoreTenantModelsInitialize } from '../Tenancy/TenancyInitializeModels.guard';
import { IgnoreUserVerifiedRoute } from '../Auth/guards/EnsureUserVerified.guard';
import { GetBuildOrganizationBuildJob } from './commands/GetBuildOrganizationJob.service'; import { GetBuildOrganizationBuildJob } from './commands/GetBuildOrganizationJob.service';
import { OrganizationBaseCurrencyLocking } from './Organization/OrganizationBaseCurrencyLocking.service'; import { OrganizationBaseCurrencyLocking } from './Organization/OrganizationBaseCurrencyLocking.service';
import { import {
@@ -93,6 +94,7 @@ export class OrganizationController {
@Get('current') @Get('current')
@HttpCode(200) @HttpCode(200)
@IgnoreUserVerifiedRoute()
@ApiOperation({ summary: 'Get current organization' }) @ApiOperation({ summary: 'Get current organization' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,

View File

@@ -12,6 +12,6 @@ export const transformBuildDto = (
): BuildOrganizationDto => { ): BuildOrganizationDto => {
return { return {
...buildDTO, ...buildDTO,
dateFormat: defaultTo(buildDTO.dateFormat, 'DD MMM yyyy'), dateFormat: defaultTo(buildDTO.dateFormat, 'DD MMM YYYY'),
}; };
}; };

View File

@@ -43,7 +43,7 @@ export class PaymentReceived extends TenantBaseModel {
* Timestamps columns. * Timestamps columns.
*/ */
get timestamps() { get timestamps() {
return ['created_at', 'updated_at']; return ['createdAt', 'updatedAt'];
} }
/** /**

Some files were not shown because too many files have changed in this diff Show More