From 636d206b0e6a49ae16ea5cd0cc7c6f896eb63c9f Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Thu, 18 Dec 2025 13:48:12 +0200 Subject: [PATCH] fix: bugs sprint --- .../modules/Expenses/ExpensesImportable.ts | 4 +- .../Import/ImportFileDataTransformer.ts | 2 +- .../src/modules/Import/ImportableRegistry.ts | 8 +- packages/server/src/modules/Import/_utils.ts | 114 +++++++++--------- .../commands/SaleInvoicesImportable.ts | 4 +- .../Tenancy/TenancyModels/Tenancy.module.ts | 2 +- .../Accounts/AccountsSuggestField.tsx | 1 + .../Expenses/ExpenseForm/ExpenseForm.tsx | 11 +- .../ExpenseForm/ExpenseFormPageProvider.tsx | 11 +- 9 files changed, 86 insertions(+), 71 deletions(-) diff --git a/packages/server/src/modules/Expenses/ExpensesImportable.ts b/packages/server/src/modules/Expenses/ExpensesImportable.ts index 1767cc241..1faf5bceb 100644 --- a/packages/server/src/modules/Expenses/ExpensesImportable.ts +++ b/packages/server/src/modules/Expenses/ExpensesImportable.ts @@ -5,10 +5,10 @@ import { ExpensesSampleData } from './constants'; import { CreateExpense } from './commands/CreateExpense.service'; import { CreateExpenseDto } from './dtos/Expense.dto'; import { ImportableService } from '../Import/decorators/Import.decorator'; -import { ManualJournal } from '../ManualJournals/models/ManualJournal'; +import { Expense } from './models/Expense.model'; @Injectable() -@ImportableService({ name: ManualJournal.name }) +@ImportableService({ name: Expense.name }) export class ExpensesImportable extends Importable { constructor(private readonly createExpenseService: CreateExpense) { super(); diff --git a/packages/server/src/modules/Import/ImportFileDataTransformer.ts b/packages/server/src/modules/Import/ImportFileDataTransformer.ts index 005698ee6..d20df20fa 100644 --- a/packages/server/src/modules/Import/ImportFileDataTransformer.ts +++ b/packages/server/src/modules/Import/ImportFileDataTransformer.ts @@ -18,7 +18,7 @@ import { CurrencyParsingDTOs } from './_constants'; export class ImportFileDataTransformer { constructor( private readonly resource: ResourceService, - ) {} + ) { } /** * Parses the given sheet data before passing to the service layer. diff --git a/packages/server/src/modules/Import/ImportableRegistry.ts b/packages/server/src/modules/Import/ImportableRegistry.ts index 5d4f6563b..c118d6fbd 100644 --- a/packages/server/src/modules/Import/ImportableRegistry.ts +++ b/packages/server/src/modules/Import/ImportableRegistry.ts @@ -6,7 +6,7 @@ import { ContextIdFactory, ModuleRef } from '@nestjs/core'; @Injectable() export class ImportableRegistry { - constructor(private readonly moduleRef: ModuleRef) {} + constructor(private readonly moduleRef: ModuleRef) { } /** * Retrieves the importable service instance of the given resource name. * @param {string} name @@ -15,6 +15,12 @@ export class ImportableRegistry { public async getImportable(name: string) { const _name = this.sanitizeResourceName(name); const importable = getImportableService(_name); + + if (!importable) { + throw new Error( + `No importable service found for resource "${_name}". Make sure the resource has an @ImportableService decorator registered.`, + ); + } const contextId = ContextIdFactory.create(); const importableInstance = await this.moduleRef.resolve(importable, contextId, { diff --git a/packages/server/src/modules/Import/_utils.ts b/packages/server/src/modules/Import/_utils.ts index 588252da0..d30d85ee8 100644 --- a/packages/server/src/modules/Import/_utils.ts +++ b/packages/server/src/modules/Import/_utils.ts @@ -253,28 +253,28 @@ export const getResourceColumns = (resourceColumns: { }) => { const mapColumn = (group: string) => - ([fieldKey, { name, importHint, required, order, ...field }]: [ - string, - IModelMetaField2, - ]) => { - const extra: Record = {}; - const key = fieldKey; + ([fieldKey, { name, importHint, required, order, ...field }]: [ + string, + IModelMetaField2, + ]) => { + const extra: Record = {}; + const key = fieldKey; - if (group) { - extra.group = group; - } - if (field.fieldType === 'collection') { - extra.fields = mapColumns(field.fields, key); - } - return { - key, - name, - required, - hint: importHint, - order, - ...extra, + if (group) { + extra.group = group; + } + if (field.fieldType === 'collection') { + extra.fields = mapColumns(field.fields, key); + } + return { + key, + name, + required, + hint: importHint, + order, + ...extra, + }; }; - }; const sortColumn = (a, b) => a.order && b.order ? a.order - b.order : a.order ? -1 : b.order ? 1 : 0; @@ -287,49 +287,49 @@ export const getResourceColumns = (resourceColumns: { // Prases the given object value based on the field key type. export const valueParser = (fields: ResourceMetaFieldsMap, tenantModels: any, trx?: Knex.Transaction) => - async (value: any, key: string, group = '') => { - let _value = value; + async (value: any, key: string, group = '') => { + let _value = value; - const fieldKey = key.includes('.') ? key.split('.')[0] : key; - const field = group ? fields[group]?.fields[fieldKey] : fields[fieldKey]; + const fieldKey = key.includes('.') ? key.split('.')[0] : key; + const field = group ? fields[group]?.fields[fieldKey] : fields[fieldKey]; - // Parses the boolean value. - if (field.fieldType === 'boolean') { - _value = parseBoolean(value); + // Parses the boolean value. + if (field.fieldType === 'boolean') { + _value = parseBoolean(value); - // Parses the enumeration value. - } else if (field.fieldType === 'enumeration') { - const option = get(field, 'options', []).find( - (option) => option.label?.toLowerCase() === value?.toLowerCase(), - ); - _value = get(option, 'key'); - // Parses the numeric value. - } else if (field.fieldType === 'number') { - _value = multiNumberParse(value); - // Parses the relation value. - } else if (field.fieldType === 'relation') { - const RelationModel = tenantModels[field.relationModel]; + // Parses the enumeration value. + } else if (field.fieldType === 'enumeration') { + const option = get(field, 'options', []).find( + (option) => option.label?.toLowerCase() === value?.toLowerCase(), + ); + _value = get(option, 'key'); + // Parses the numeric value. + } else if (field.fieldType === 'number') { + _value = multiNumberParse(value); + // Parses the relation value. + } else if (field.fieldType === 'relation') { + const RelationModel = tenantModels[field.relationModel]; - if (!RelationModel) { - throw new Error(`The relation model of ${key} field is not exist.`); - } - const relationQuery = RelationModel.query(trx); - const relationKeys = castArray(field?.relationImportMatch); + if (!RelationModel) { + throw new Error(`The relation model of ${key} field is not exist.`); + } + const relationQuery = RelationModel.query(trx); + const relationKeys = castArray(field?.relationImportMatch); - relationQuery.where(function () { - relationKeys.forEach((relationKey: string) => { - this.orWhereRaw('LOWER(??) = LOWER(?)', [relationKey, value]); + relationQuery.where(function () { + relationKeys.forEach((relationKey: string) => { + this.orWhereRaw('LOWER(??) = LOWER(?)', [relationKey, value]); + }); }); - }); - const result = await relationQuery.first(); - _value = get(result, 'id'); - } else if (field.fieldType === 'collection') { - const ObjectFieldKey = key.includes('.') ? key.split('.')[1] : key; - const _valueParser = valueParser(fields, tenantModels); - _value = await _valueParser(value, ObjectFieldKey, fieldKey); - } - return _value; - }; + const result = await relationQuery.first(); + _value = get(result, 'id'); + } else if (field.fieldType === 'collection') { + const ObjectFieldKey = key.includes('.') ? key.split('.')[1] : key; + const _valueParser = valueParser(fields, tenantModels); + _value = await _valueParser(value, ObjectFieldKey, fieldKey); + } + return _value; + }; /** * Parses the field key and detarmines the key path. diff --git a/packages/server/src/modules/SaleInvoices/commands/SaleInvoicesImportable.ts b/packages/server/src/modules/SaleInvoices/commands/SaleInvoicesImportable.ts index af299cea4..f215d2300 100644 --- a/packages/server/src/modules/SaleInvoices/commands/SaleInvoicesImportable.ts +++ b/packages/server/src/modules/SaleInvoices/commands/SaleInvoicesImportable.ts @@ -5,10 +5,10 @@ import { Importable } from '@/modules/Import/Importable'; import { CreateSaleInvoiceDto } from '../dtos/SaleInvoice.dto'; import { SaleInvoicesSampleData } from '../constants'; import { ImportableService } from '@/modules/Import/decorators/Import.decorator'; -import { ManualJournal } from '@/modules/ManualJournals/models/ManualJournal'; +import { SaleInvoice } from '../models/SaleInvoice'; @Injectable() -@ImportableService({ name: ManualJournal.name }) +@ImportableService({ name: SaleInvoice.name }) export class SaleInvoicesImportable extends Importable { constructor(private readonly createInvoiceService: CreateSaleInvoice) { super(); diff --git a/packages/server/src/modules/Tenancy/TenancyModels/Tenancy.module.ts b/packages/server/src/modules/Tenancy/TenancyModels/Tenancy.module.ts index e16537b95..0bc1d639c 100644 --- a/packages/server/src/modules/Tenancy/TenancyModels/Tenancy.module.ts +++ b/packages/server/src/modules/Tenancy/TenancyModels/Tenancy.module.ts @@ -107,4 +107,4 @@ const modelProviders = models.map((model) => RegisterTenancyModel(model)); imports: [...modelProviders], exports: [...modelProviders], }) -export class TenancyModelsModule {} +export class TenancyModelsModule { } diff --git a/packages/webapp/src/components/Accounts/AccountsSuggestField.tsx b/packages/webapp/src/components/Accounts/AccountsSuggestField.tsx index bb7aa7c8a..e701554b6 100644 --- a/packages/webapp/src/components/Accounts/AccountsSuggestField.tsx +++ b/packages/webapp/src/components/Accounts/AccountsSuggestField.tsx @@ -102,6 +102,7 @@ function AccountsSuggestFieldRoot({ return ( { @@ -103,10 +106,10 @@ function ExpenseForm({ }); setSubmitting(false); - if (submitPayload.redirect) { + if (currentSubmitPayload.redirect) { history.push('/expenses'); } - if (submitPayload.resetForm) { + if (currentSubmitPayload.resetForm) { resetForm(); } }; diff --git a/packages/webapp/src/containers/Expenses/ExpenseForm/ExpenseFormPageProvider.tsx b/packages/webapp/src/containers/Expenses/ExpenseForm/ExpenseFormPageProvider.tsx index c6da3817d..ef3abffb9 100644 --- a/packages/webapp/src/containers/Expenses/ExpenseForm/ExpenseFormPageProvider.tsx +++ b/packages/webapp/src/containers/Expenses/ExpenseForm/ExpenseFormPageProvider.tsx @@ -59,8 +59,13 @@ function ExpenseFormPageProvider({ query, expenseId, ...props }) { const { mutateAsync: createExpenseMutate } = useCreateExpense(); const { mutateAsync: editExpenseMutate } = useEditExpense(); - // Submit form payload. - const [submitPayload, setSubmitPayload] = React.useState({}); + // Submit form payload - using ref for synchronous access. + const submitPayloadRef = React.useRef({}); + + // Setter to update the ref. + const setSubmitPayload = React.useCallback((payload) => { + submitPayloadRef.current = payload; + }, []); // Detarmines whether the form in new mode. const isNewMode = !expenseId; @@ -69,7 +74,7 @@ function ExpenseFormPageProvider({ query, expenseId, ...props }) { const provider = { isNewMode, expenseId, - submitPayload, + submitPayloadRef, // Expose ref for synchronous access currencies, customers,