Compare commits

..

4 Commits

Author SHA1 Message Date
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
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
Ahmed Bouhuolia
de3d4698ea fix: localize hardcoded strings in financial reports
- Fix cash_flow_statement.net_cash_investing not being localized
- Add translation keys for Account name, Total, sheet name, From/To dates
- Create contact_summary_balance.json for Customer/Vendor Balance Summary
- Create trial_balance_sheet.json for Trial Balance Sheet columns
- Create inventory_item_details.json for Inventory Item Details
- Create transactions_by_contact.json for Transactions by Contact
- Fix hardcoded strings in TrialBalanceSheetTable column labels
- Fix hardcoded 'Total' in CustomerBalanceSummary and VendorBalanceSummary
- Fix hardcoded column headers in InventoryItemDetailsTable
- Fix hardcoded Opening/Closing balance strings

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 22:07:00 +02:00
20 changed files with 88 additions and 41 deletions

View File

@@ -9,5 +9,10 @@
"net_cash_financing": "Net cash provided by financing activities", "net_cash_financing": "Net cash provided by financing activities",
"cash_beginning_period": "Cash at beginning of period", "cash_beginning_period": "Cash at beginning of period",
"net_cash_increase": "NET CASH INCREASE FOR PERIOD", "net_cash_increase": "NET CASH INCREASE FOR PERIOD",
"cash_end_period": "CASH AT END OF PERIOD" "cash_end_period": "CASH AT END OF PERIOD",
"account_name": "Account name",
"total": "Total",
"sheet_name": "Statement of Cash Flow",
"from_date": "From",
"to_date": "To"
} }

View File

@@ -0,0 +1,5 @@
{
"account_name": "Account name",
"total": "Total",
"percentage_column": "% of Column"
}

View File

@@ -0,0 +1,14 @@
{
"opening_balance": "Opening balance",
"closing_balance": "Closing balance",
"date": "Date",
"transaction_type": "Transaction type",
"transaction_number": "Transaction #",
"quantity": "Quantity",
"rate": "Rate",
"total": "Total",
"value": "Value",
"profit_margin": "Profit Margin",
"running_quantity": "Running quantity",
"running_value": "Running Value"
}

View File

@@ -0,0 +1,4 @@
{
"opening_balance": "Opening balance",
"closing_balance": "Closing balance"
}

View File

@@ -0,0 +1,6 @@
{
"account": "Account",
"debit": "Debit",
"credit": "Credit",
"total": "Total"
}

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

@@ -239,7 +239,7 @@ export class CashFlowTable {
section: ICashFlowStatementSection, section: ICashFlowStatementSection,
): ICashFlowStatementSection => { ): ICashFlowStatementSection => {
const label = section.footerLabel const label = section.footerLabel
? section.footerLabel ? this.i18n.t(section.footerLabel)
: this.i18n.t('financial_sheet.total_row', { : this.i18n.t('financial_sheet.total_row', {
args: { value: section.label }, args: { value: section.label },
}); });
@@ -302,7 +302,7 @@ export class CashFlowTable {
* @returns {ITableColumn} * @returns {ITableColumn}
*/ */
private totalColumns = (): ITableColumn[] => { private totalColumns = (): ITableColumn[] => {
return [{ key: 'total', label: this.i18n.t('Total') }]; return [{ key: 'total', label: this.i18n.t('cash_flow_statement.total') }];
}; };
/** /**
@@ -366,7 +366,7 @@ export class CashFlowTable {
*/ */
public tableColumns = (): ITableColumn[] => { public tableColumns = (): ITableColumn[] => {
return R.compose( return R.compose(
R.concat([{ key: 'name', label: this.i18n.t('Account name') }]), R.concat([{ key: 'name', label: this.i18n.t('cash_flow_statement.account_name') }]),
R.when( R.when(
R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)),
R.concat(this.datePeriodsColumns()), R.concat(this.datePeriodsColumns()),

View File

@@ -1,5 +1,6 @@
import * as moment from 'moment'; import * as moment from 'moment';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { I18nService } from 'nestjs-i18n';
import { FinancialSheetMeta } from '../../common/FinancialSheetMeta'; import { FinancialSheetMeta } from '../../common/FinancialSheetMeta';
import { import {
ICashFlowStatementMeta, ICashFlowStatementMeta,
@@ -8,7 +9,10 @@ import {
@Injectable() @Injectable()
export class CashflowSheetMeta { export class CashflowSheetMeta {
constructor(private readonly financialSheetMeta: FinancialSheetMeta) {} constructor(
private readonly financialSheetMeta: FinancialSheetMeta,
private readonly i18n: I18nService,
) {}
/** /**
* Cashflow sheet meta. * Cashflow sheet meta.
@@ -21,9 +25,11 @@ export class CashflowSheetMeta {
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('YYYY/MM/DD');
const formattedFromDate = moment(query.fromDate).format('YYYY/MM/DD'); const formattedFromDate = moment(query.fromDate).format('YYYY/MM/DD');
const formattedDateRange = `From ${formattedFromDate} | To ${formattedToDate}`; const fromLabel = this.i18n.t('cash_flow_statement.from_date');
const toLabel = this.i18n.t('cash_flow_statement.to_date');
const formattedDateRange = `${fromLabel} ${formattedFromDate} | ${toLabel} ${formattedToDate}`;
const sheetName = 'Statement of Cash Flow'; const sheetName = this.i18n.t('cash_flow_statement.sheet_name');
return { return {
...meta, ...meta,

View File

@@ -91,7 +91,7 @@ export class CustomerBalanceSummaryTable {
*/ */
private getTotalColumnsAccessor = (): IColumnMapperMeta[] => { private getTotalColumnsAccessor = (): IColumnMapperMeta[] => {
const columns = [ const columns = [
{ key: 'name', value: this.i18n.t('Total') }, { key: 'name', value: this.i18n.t('contact_summary_balance.total') },
{ key: 'total', accessor: 'total.formattedAmount' }, { key: 'total', accessor: 'total.formattedAmount' },
]; ];
// @ts-ignore // @ts-ignore

View File

@@ -93,7 +93,7 @@ export class InventoryItemDetailsTable {
): ITableRow => { ): ITableRow => {
const columns: Array<IColumnMapperMeta> = [ const columns: Array<IColumnMapperMeta> = [
{ key: 'date', accessor: 'date.formattedDate' }, { key: 'date', accessor: 'date.formattedDate' },
{ key: 'closing', value: this.i18n.t('Opening balance') }, { key: 'closing', value: this.i18n.t('inventory_item_details.opening_balance') },
{ key: 'empty', value: '' }, { key: 'empty', value: '' },
{ key: 'quantity', accessor: 'quantity.formattedNumber' }, { key: 'quantity', accessor: 'quantity.formattedNumber' },
{ key: 'empty', value: '' }, { key: 'empty', value: '' },
@@ -115,7 +115,7 @@ export class InventoryItemDetailsTable {
): ITableRow => { ): ITableRow => {
const columns: Array<IColumnMapperMeta> = [ const columns: Array<IColumnMapperMeta> = [
{ key: 'date', accessor: 'date.formattedDate' }, { key: 'date', accessor: 'date.formattedDate' },
{ key: 'closing', value: this.i18n.t('Closing balance') }, { key: 'closing', value: this.i18n.t('inventory_item_details.closing_balance') },
{ key: 'empty', value: '' }, { key: 'empty', value: '' },
{ key: 'quantity', accessor: 'quantity.formattedNumber' }, { key: 'quantity', accessor: 'quantity.formattedNumber' },
{ key: 'empty', value: '' }, { key: 'empty', value: '' },
@@ -193,16 +193,16 @@ export class InventoryItemDetailsTable {
*/ */
public tableColumns = (): ITableColumn[] => { public tableColumns = (): ITableColumn[] => {
return [ return [
{ key: 'date', label: this.i18n.t('Date') }, { key: 'date', label: this.i18n.t('inventory_item_details.date') },
{ key: 'transaction_type', label: this.i18n.t('Transaction type') }, { key: 'transaction_type', label: this.i18n.t('inventory_item_details.transaction_type') },
{ key: 'transaction_id', label: this.i18n.t('Transaction #') }, { key: 'transaction_id', label: this.i18n.t('inventory_item_details.transaction_number') },
{ key: 'quantity', label: this.i18n.t('Quantity') }, { key: 'quantity', label: this.i18n.t('inventory_item_details.quantity') },
{ key: 'rate', label: this.i18n.t('Rate') }, { key: 'rate', label: this.i18n.t('inventory_item_details.rate') },
{ key: 'total', label: this.i18n.t('Total') }, { key: 'total', label: this.i18n.t('inventory_item_details.total') },
{ key: 'value', label: this.i18n.t('Value') }, { key: 'value', label: this.i18n.t('inventory_item_details.value') },
{ key: 'profit_margin', label: this.i18n.t('Profit Margin') }, { key: 'profit_margin', label: this.i18n.t('inventory_item_details.profit_margin') },
{ key: 'running_quantity', label: this.i18n.t('Running quantity') }, { key: 'running_quantity', label: this.i18n.t('inventory_item_details.running_quantity') },
{ key: 'running_value', label: this.i18n.t('Running Value') }, { key: 'running_value', label: this.i18n.t('inventory_item_details.running_value') },
]; ];
}; };
} }

View File

@@ -52,7 +52,7 @@ export class TransactionsByContactsTableRows {
const columns = [ const columns = [
{ {
key: 'openingBalanceLabel', key: 'openingBalanceLabel',
value: this.i18n.t('Opening balance') as string, value: this.i18n.t('transactions_by_contact.opening_balance') as string,
}, },
...R.repeat({ key: 'empty', value: '' }, 5), ...R.repeat({ key: 'empty', value: '' }, 5),
{ {
@@ -76,7 +76,7 @@ export class TransactionsByContactsTableRows {
const columns = [ const columns = [
{ {
key: 'closingBalanceLabel', key: 'closingBalanceLabel',
value: this.i18n.t('Closing balance') as string, value: this.i18n.t('transactions_by_contact.closing_balance') as string,
}, },
...R.repeat({ key: 'empty', value: '' }, 5), ...R.repeat({ key: 'empty', value: '' }, 5),
{ {

View File

@@ -141,10 +141,10 @@ export class TrialBalanceSheetTable extends R.compose(
return R.compose( return R.compose(
this.tableColumnsCellIndexing, this.tableColumnsCellIndexing,
R.concat([ R.concat([
{ key: 'account', label: 'Account' }, { key: 'account', label: this.i18n.t('trial_balance_sheet.account') },
{ key: 'debit', label: 'Debit' }, { key: 'debit', label: this.i18n.t('trial_balance_sheet.debit') },
{ key: 'credit', label: 'Credit' }, { key: 'credit', label: this.i18n.t('trial_balance_sheet.credit') },
{ key: 'total', label: 'Total' }, { key: 'total', label: this.i18n.t('trial_balance_sheet.total') },
]), ]),
)([]); )([]);
}; };

View File

@@ -91,7 +91,7 @@ export class VendorBalanceSummaryTable {
*/ */
private getTotalColumnsAccessor = (): IColumnMapperMeta[] => { private getTotalColumnsAccessor = (): IColumnMapperMeta[] => {
const columns = [ const columns = [
{ key: 'name', value: this.i18n.t('Total') }, { key: 'name', value: this.i18n.t('contact_summary_balance.total') },
{ key: 'total', accessor: 'total.formattedAmount' }, { key: 'total', accessor: 'total.formattedAmount' },
]; ];
return R.compose( return R.compose(

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

@@ -8,6 +8,7 @@ import {
import { TenancyContext } from './TenancyContext.service'; import { TenancyContext } from './TenancyContext.service';
import { Reflector } from '@nestjs/core'; import { Reflector } from '@nestjs/core';
import { IS_PUBLIC_ROUTE } from '../Auth/Auth.constants'; import { IS_PUBLIC_ROUTE } from '../Auth/Auth.constants';
import { IS_TENANT_AGNOSTIC } from './TenancyGlobal.guard';
export const IS_IGNORE_TENANT_INITIALIZED = 'IS_IGNORE_TENANT_INITIALIZED'; export const IS_IGNORE_TENANT_INITIALIZED = 'IS_IGNORE_TENANT_INITIALIZED';
export const IgnoreTenantInitializedRoute = () => export const IgnoreTenantInitializedRoute = () =>
@@ -35,8 +36,12 @@ export class EnsureTenantIsInitializedGuard implements CanActivate {
IS_PUBLIC_ROUTE, IS_PUBLIC_ROUTE,
[context.getHandler(), context.getClass()], [context.getHandler(), context.getClass()],
); );
const isTenantAgnostic = this.reflector.getAllAndOverride<boolean>(
IS_TENANT_AGNOSTIC,
[context.getHandler(), context.getClass()],
);
// Skip the guard early if the route marked as public or ignored. // Skip the guard early if the route marked as public or ignored.
if (isPublic || isIgnoreEnsureTenantInitialized) { if (isPublic || isIgnoreEnsureTenantInitialized || isTenantAgnostic) {
return true; return true;
} }
const tenant = await this.tenancyContext.getTenant(); const tenant = await this.tenancyContext.getTenant();

View File

@@ -9,6 +9,7 @@ import {
import { TenancyContext } from './TenancyContext.service'; import { TenancyContext } from './TenancyContext.service';
import { Reflector } from '@nestjs/core'; import { Reflector } from '@nestjs/core';
import { IS_PUBLIC_ROUTE } from '../Auth/Auth.constants'; import { IS_PUBLIC_ROUTE } from '../Auth/Auth.constants';
import { IS_TENANT_AGNOSTIC } from './TenancyGlobal.guard';
export const IS_IGNORE_TENANT_SEEDED = 'IS_IGNORE_TENANT_SEEDED'; export const IS_IGNORE_TENANT_SEEDED = 'IS_IGNORE_TENANT_SEEDED';
export const IgnoreTenantSeededRoute = () => export const IgnoreTenantSeededRoute = () =>
@@ -36,7 +37,12 @@ export class EnsureTenantIsSeededGuard implements CanActivate {
context.getHandler(), context.getHandler(),
context.getClass(), context.getClass(),
]); ]);
if (isPublic || isIgnoreEnsureTenantSeeded) { const isTenantAgnostic = this.reflector.getAllAndOverride<boolean>(
IS_TENANT_AGNOSTIC,
[context.getHandler(), context.getClass()],
);
// Skip the guard early if the route marked as public, tenant agnostic or ignored.
if (isPublic || isIgnoreEnsureTenantSeeded || isTenantAgnostic) {
return true; return true;
} }
const tenant = await this.tenancyContext.getTenant(); const tenant = await this.tenancyContext.getTenant();

View File

@@ -128,7 +128,7 @@ export const useAuthMetadata = (props = {}) => {
* Resend the mail of signup verification. * Resend the mail of signup verification.
*/ */
export const useAuthSignUpVerifyResendMail = (props) => { export const useAuthSignUpVerifyResendMail = (props) => {
const apiRequest = useAuthApiRequest(); const apiRequest = useApiRequest();
return useMutation( return useMutation(
() => apiRequest.post(AuthRoute.SignupVerifyResend), () => apiRequest.post(AuthRoute.SignupVerifyResend),