Compare commits

...

11 Commits

Author SHA1 Message Date
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
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
32 changed files with 236 additions and 104 deletions

View File

@@ -9,5 +9,10 @@
"net_cash_financing": "Net cash provided by financing activities",
"cash_beginning_period": "Cash at beginning of 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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
import {
Body,
Controller,
Delete,
Get,
Param,
Post,
@@ -14,6 +15,10 @@ import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.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')
@ApiTags('Credit Notes Apply Invoice')
@@ -22,6 +27,9 @@ import { CreditNoteAction } from '../CreditNotes/types/CreditNotes.types';
export class CreditNotesApplyInvoiceController {
constructor(
private readonly getCreditNoteAssociatedAppliedInvoicesService: GetCreditNoteAssociatedAppliedInvoices,
private readonly getCreditNoteAssociatedInvoicesToApplyService: GetCreditNoteAssociatedInvoicesToApply,
private readonly creditNoteApplyToInvoicesService: CreditNoteApplyToInvoices,
private readonly deleteCreditNoteApplyToInvoicesService: DeleteCreditNoteApplyToInvoices,
) {}
@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')
@RequirePermission(CreditNoteAction.Edit, AbilitySubject.CreditNote)
@ApiOperation({ summary: 'Apply credit note to invoices' })
@@ -48,9 +73,32 @@ export class CreditNotesApplyInvoiceController {
})
@ApiResponse({ status: 404, description: 'Credit note not found' })
@ApiResponse({ status: 400, description: 'Invalid input data' })
applyCreditNoteToInvoices(@Param('creditNoteId') creditNoteId: number) {
return this.getCreditNoteAssociatedAppliedInvoicesService.getCreditAssociatedAppliedInvoices(
applyCreditNoteToInvoices(
@Param('creditNoteId') creditNoteId: number,
@Body() applyDto: ApplyCreditNoteToInvoicesDto,
) {
return this.creditNoteApplyToInvoicesService.applyCreditNoteToInvoices(
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 { GetCreditNoteAssociatedInvoicesToApply } from './queries/GetCreditNoteAssociatedInvoicesToApply.service';
import { CreditNotesApplyInvoiceController } from './CreditNotesApplyInvoice.controller';
import { CreditNoteApplySyncCreditSubscriber } from './subscribers/CreditNoteApplySyncCreditSubscriber';
import { CreditNoteApplySyncInvoicesCreditedAmountSubscriber } from './subscribers/CreditNoteApplySyncInvoicesSubscriber';
@Module({
providers: [
@@ -19,6 +21,8 @@ import { CreditNotesApplyInvoiceController } from './CreditNotesApplyInvoice.con
CreditNoteApplySyncCredit,
GetCreditNoteAssociatedAppliedInvoices,
GetCreditNoteAssociatedInvoicesToApply,
CreditNoteApplySyncCreditSubscriber,
CreditNoteApplySyncInvoicesCreditedAmountSubscriber,
],
exports: [DeleteCustomerLinkedCreditNoteService],
imports: [PaymentsReceivedModule, forwardRef(() => CreditNotesModule)],

View File

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

View File

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

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

View File

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

View File

@@ -239,7 +239,7 @@ export class CashFlowTable {
section: ICashFlowStatementSection,
): ICashFlowStatementSection => {
const label = section.footerLabel
? section.footerLabel
? this.i18n.t(section.footerLabel)
: this.i18n.t('financial_sheet.total_row', {
args: { value: section.label },
});
@@ -302,7 +302,7 @@ export class CashFlowTable {
* @returns {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[] => {
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.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)),
R.concat(this.datePeriodsColumns()),

View File

@@ -1,5 +1,6 @@
import * as moment from 'moment';
import { Injectable } from '@nestjs/common';
import { I18nService } from 'nestjs-i18n';
import { FinancialSheetMeta } from '../../common/FinancialSheetMeta';
import {
ICashFlowStatementMeta,
@@ -8,7 +9,10 @@ import {
@Injectable()
export class CashflowSheetMeta {
constructor(private readonly financialSheetMeta: FinancialSheetMeta) {}
constructor(
private readonly financialSheetMeta: FinancialSheetMeta,
private readonly i18n: I18nService,
) {}
/**
* Cashflow sheet meta.
@@ -21,9 +25,11 @@ export class CashflowSheetMeta {
const meta = await this.financialSheetMeta.meta();
const formattedToDate = moment(query.toDate).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 {
...meta,

View File

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

View File

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

View File

@@ -52,7 +52,7 @@ export class TransactionsByContactsTableRows {
const columns = [
{
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),
{
@@ -76,7 +76,7 @@ export class TransactionsByContactsTableRows {
const columns = [
{
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),
{

View File

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

View File

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

View File

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

View File

@@ -8,6 +8,7 @@ import {
import { TenancyContext } from './TenancyContext.service';
import { Reflector } from '@nestjs/core';
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 IgnoreTenantInitializedRoute = () =>
@@ -35,8 +36,12 @@ export class EnsureTenantIsInitializedGuard implements CanActivate {
IS_PUBLIC_ROUTE,
[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.
if (isPublic || isIgnoreEnsureTenantInitialized) {
if (isPublic || isIgnoreEnsureTenantInitialized || isTenantAgnostic) {
return true;
}
const tenant = await this.tenancyContext.getTenant();

View File

@@ -9,6 +9,7 @@ import {
import { TenancyContext } from './TenancyContext.service';
import { Reflector } from '@nestjs/core';
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 IgnoreTenantSeededRoute = () =>
@@ -36,7 +37,12 @@ export class EnsureTenantIsSeededGuard implements CanActivate {
context.getHandler(),
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;
}
const tenant = await this.tenancyContext.getTenant();

View File

@@ -1,4 +1,4 @@
import Bluebird from 'bluebird';
import * as Bluebird from 'bluebird';
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import { IVendorCreditAppliedBill } from '../types/VendorCreditApplyBills.types';

View File

@@ -1,18 +0,0 @@
.root {
text-align: center;
}
.title{
font-size: 18px;
font-weight: 600;
margin-bottom: 0.5rem;
color: #252A31;
}
.description{
margin-bottom: 1rem;
font-size: 15px;
line-height: 1.45;
color: #404854;
}

View File

@@ -1,12 +1,13 @@
// @ts-nocheck
import { Button, Intent } from '@blueprintjs/core';
import { x } from '@xstyled/emotion';
import AuthInsider from './AuthInsider';
import { AuthInsiderCard } from './_components';
import styles from './RegisterVerify.module.scss';
import { AppToaster, Stack } from '@/components';
import { useAuthActions, useAuthUserVerifyEmail } from '@/hooks/state';
import { useAuthSignUpVerifyResendMail } from '@/hooks/query';
import { AuthContainer } from './AuthContainer';
import { useIsDarkMode } from '@/hooks/useDarkMode';
export default function RegisterVerify() {
const { setLogout } = useAuthActions();
@@ -14,6 +15,7 @@ export default function RegisterVerify() {
useAuthSignUpVerifyResendMail();
const emailAddress = useAuthUserVerifyEmail();
const isDarkMode = useIsDarkMode();
const handleResendMailBtnClick = () => {
resendSignUpVerifyMail()
@@ -37,12 +39,24 @@ export default function RegisterVerify() {
return (
<AuthContainer>
<AuthInsider>
<AuthInsiderCard className={styles.root}>
<h2 className={styles.title}>Please verify your email</h2>
<p className={styles.description}>
<AuthInsiderCard textAlign="center">
<x.h2
fontSize="18px"
fontWeight={600}
mb="0.5rem"
color={isDarkMode ? 'rgba(255, 255, 255, 0.85)' : '#252A31'}
>
Please verify your email
</x.h2>
<x.p
mb="1rem"
fontSize="15px"
lineHeight="1.45"
color={isDarkMode ? 'rgba(255, 255, 255, 0.7)' : '#404854'}
>
We sent an email to <strong>{emailAddress}</strong> Click the link
inside to get started.
</p>
</x.p>
<Stack spacing={4}>
<Button

View File

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

View File

@@ -58,16 +58,13 @@ export function useCreateCreditNote(props) {
const queryClient = useQueryClient();
const apiRequest = useApiRequest();
return useMutation(
(values) => apiRequest.post('credit-notes', values),
{
return useMutation((values) => apiRequest.post('credit-notes', values), {
onSuccess: (res, values) => {
// Common invalidate queries.
commonInvalidateQueries(queryClient);
},
...props,
},
);
});
}
/**
@@ -218,8 +215,7 @@ export function useCreateRefundCreditNote(props) {
const apiRequest = useApiRequest();
return useMutation(
([id, values]) =>
apiRequest.post(`credit-notes/${id}/refunds`, values),
([id, values]) => apiRequest.post(`credit-notes/${id}/refunds`, values),
{
onSuccess: (res, [id, values]) => {
// Common invalidate queries.
@@ -240,9 +236,7 @@ export function useDeleteRefundCreditNote(props) {
const queryClient = useQueryClient();
const apiRequest = useApiRequest();
return useMutation(
(id) => apiRequest.delete(`credit-notes/refunds/${id}`),
{
return useMutation((id) => apiRequest.delete(`credit-notes/refunds/${id}`), {
onSuccess: (res, id) => {
// Common invalidate queries.
commonInvalidateQueries(queryClient);
@@ -251,8 +245,7 @@ export function useDeleteRefundCreditNote(props) {
queryClient.invalidateQueries([t.CREDIT_NOTE, id]);
},
...props,
},
);
});
}
/**
@@ -301,7 +294,7 @@ export function useReconcileCreditNote(id, props, requestProps) {
[t.RECONCILE_CREDIT_NOTE, id],
{
method: 'get',
url: `credit-notes/${id}/applied-invoices`,
url: `credit-notes/${id}/apply-invoices`,
...requestProps,
},
{