Compare commits

..

7 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
37 changed files with 169 additions and 31 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

@@ -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

@@ -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

@@ -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

@@ -149,6 +149,11 @@ export class FinancialSheet {
}; };
} }
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

@@ -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

@@ -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

@@ -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'];
} }
/** /**

View File

@@ -16,6 +16,13 @@ export class Role extends TenantBaseModel {
return 'roles'; return 'roles';
} }
/**
* Timestamps columns.
*/
get timestamps() {
return [];
}
/** /**
* Relationship mapping. * Relationship mapping.
*/ */

View File

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

View File

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

View File

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

View File

@@ -10,6 +10,10 @@ export class Setting extends BaseModel {
return 'settings'; return 'settings';
} }
get timestamps() {
return [];
}
/** /**
* Extra metadata query to query with the current authenticate user. * Extra metadata query to query with the current authenticate user.
* @param {Object} query * @param {Object} query

View File

@@ -50,6 +50,13 @@ export class TenantMetadata extends BaseModel {
*/ */
static tableName = 'tenants_metadata'; static tableName = 'tenants_metadata';
/**
* Timestamps columns.
*/
get timestamps() {
return [];
}
/** /**
* Virtual attributes. * Virtual attributes.
*/ */

View File

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

View File

@@ -53,7 +53,7 @@ export class ItemEntry extends BaseModel {
* @returns {string[]} * @returns {string[]}
*/ */
get timestamps() { get timestamps() {
return ['created_at', 'updated_at']; return ['createdAt', 'updatedAt'];
} }
/** /**

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,6 +9,13 @@ export class ItemWarehouseQuantity extends BaseModel{
return 'items_warehouses_quantity'; return 'items_warehouses_quantity';
} }
/**
* Timestamps columns.
*/
get timestamps() {
return [];
}
/** /**
* Relation mappings. * Relation mappings.
*/ */

View File

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

View File

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

View File

@@ -19,6 +19,13 @@ export class WarehouseTransferEntry extends TenantBaseModel {
return 'warehouses_transfers_entries'; return 'warehouses_transfers_entries';
} }
/**
* Timestamps columns.
*/
get timestamps() {
return [];
}
/** /**
* Virtual attributes. * Virtual attributes.
*/ */

View File

@@ -2,7 +2,7 @@
import React from 'react'; import React from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { Intent, Menu, MenuItem, Tag } from '@blueprintjs/core'; import { Intent, Menu, MenuItem, Tag } from '@blueprintjs/core';
import { FormatDateCell, Icon } from '@/components'; import { Icon } from '@/components';
import { safeCallback } from '@/utils'; import { safeCallback } from '@/utils';
import { useAccountTransactionsContext } from './AccountTransactionsProvider'; import { useAccountTransactionsContext } from './AccountTransactionsProvider';
import FinancialLoadingBar from '@/containers/FinancialStatements/FinancialLoadingBar'; import FinancialLoadingBar from '@/containers/FinancialStatements/FinancialLoadingBar';
@@ -75,8 +75,7 @@ export function useAccountTransactionsColumns() {
{ {
id: 'date', id: 'date',
Header: intl.get('date'), Header: intl.get('date'),
accessor: 'date', accessor: 'formatted_date',
Cell: FormatDateCell,
width: 110, width: 110,
className: 'date', className: 'date',
clickable: true, clickable: true,

View File

@@ -82,7 +82,7 @@ function CashflowBankAccount({
const handleEditAccount = () => { const handleEditAccount = () => {
openDialog(DialogsName.AccountForm, { openDialog(DialogsName.AccountForm, {
action: AccountDialogAction.Edit, action: AccountDialogAction.Edit,
id: account.id, accountId: account.id,
}); });
}; };
// Handle money in menu item actions. // Handle money in menu item actions.

View File

@@ -2,7 +2,6 @@
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import React from 'react'; import React from 'react';
import { FormatDateCell } from '@/components';
import { useAccountDrawerTableOptionsContext } from './AccountDrawerTableOptionsProvider'; import { useAccountDrawerTableOptionsContext } from './AccountDrawerTableOptionsProvider';
/** /**
@@ -15,8 +14,7 @@ export const useAccountReadEntriesColumns = () => {
() => [ () => [
{ {
Header: intl.get('transaction_date'), Header: intl.get('transaction_date'),
accessor: 'date', accessor: 'formatted_date',
Cell: FormatDateCell,
width: 110, width: 110,
textOverview: true, textOverview: true,
}, },