mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 14:20:31 +00:00
fix: bugs sprint
This commit is contained in:
@@ -5,10 +5,10 @@ import { ExpensesSampleData } from './constants';
|
|||||||
import { CreateExpense } from './commands/CreateExpense.service';
|
import { CreateExpense } from './commands/CreateExpense.service';
|
||||||
import { CreateExpenseDto } from './dtos/Expense.dto';
|
import { CreateExpenseDto } from './dtos/Expense.dto';
|
||||||
import { ImportableService } from '../Import/decorators/Import.decorator';
|
import { ImportableService } from '../Import/decorators/Import.decorator';
|
||||||
import { ManualJournal } from '../ManualJournals/models/ManualJournal';
|
import { Expense } from './models/Expense.model';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ImportableService({ name: ManualJournal.name })
|
@ImportableService({ name: Expense.name })
|
||||||
export class ExpensesImportable extends Importable {
|
export class ExpensesImportable extends Importable {
|
||||||
constructor(private readonly createExpenseService: CreateExpense) {
|
constructor(private readonly createExpenseService: CreateExpense) {
|
||||||
super();
|
super();
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { CurrencyParsingDTOs } from './_constants';
|
|||||||
export class ImportFileDataTransformer {
|
export class ImportFileDataTransformer {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly resource: ResourceService,
|
private readonly resource: ResourceService,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the given sheet data before passing to the service layer.
|
* Parses the given sheet data before passing to the service layer.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { ContextIdFactory, ModuleRef } from '@nestjs/core';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ImportableRegistry {
|
export class ImportableRegistry {
|
||||||
constructor(private readonly moduleRef: ModuleRef) {}
|
constructor(private readonly moduleRef: ModuleRef) { }
|
||||||
/**
|
/**
|
||||||
* Retrieves the importable service instance of the given resource name.
|
* Retrieves the importable service instance of the given resource name.
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
@@ -15,6 +15,12 @@ export class ImportableRegistry {
|
|||||||
public async getImportable(name: string) {
|
public async getImportable(name: string) {
|
||||||
const _name = this.sanitizeResourceName(name);
|
const _name = this.sanitizeResourceName(name);
|
||||||
const importable = getImportableService(_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 contextId = ContextIdFactory.create();
|
||||||
|
|
||||||
const importableInstance = await this.moduleRef.resolve(importable, contextId, {
|
const importableInstance = await this.moduleRef.resolve(importable, contextId, {
|
||||||
|
|||||||
@@ -253,28 +253,28 @@ export const getResourceColumns = (resourceColumns: {
|
|||||||
}) => {
|
}) => {
|
||||||
const mapColumn =
|
const mapColumn =
|
||||||
(group: string) =>
|
(group: string) =>
|
||||||
([fieldKey, { name, importHint, required, order, ...field }]: [
|
([fieldKey, { name, importHint, required, order, ...field }]: [
|
||||||
string,
|
string,
|
||||||
IModelMetaField2,
|
IModelMetaField2,
|
||||||
]) => {
|
]) => {
|
||||||
const extra: Record<string, any> = {};
|
const extra: Record<string, any> = {};
|
||||||
const key = fieldKey;
|
const key = fieldKey;
|
||||||
|
|
||||||
if (group) {
|
if (group) {
|
||||||
extra.group = group;
|
extra.group = group;
|
||||||
}
|
}
|
||||||
if (field.fieldType === 'collection') {
|
if (field.fieldType === 'collection') {
|
||||||
extra.fields = mapColumns(field.fields, key);
|
extra.fields = mapColumns(field.fields, key);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
key,
|
key,
|
||||||
name,
|
name,
|
||||||
required,
|
required,
|
||||||
hint: importHint,
|
hint: importHint,
|
||||||
order,
|
order,
|
||||||
...extra,
|
...extra,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
|
||||||
const sortColumn = (a, b) =>
|
const sortColumn = (a, b) =>
|
||||||
a.order && b.order ? a.order - b.order : a.order ? -1 : b.order ? 1 : 0;
|
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.
|
// Prases the given object value based on the field key type.
|
||||||
export const valueParser =
|
export const valueParser =
|
||||||
(fields: ResourceMetaFieldsMap, tenantModels: any, trx?: Knex.Transaction) =>
|
(fields: ResourceMetaFieldsMap, tenantModels: any, trx?: Knex.Transaction) =>
|
||||||
async (value: any, key: string, group = '') => {
|
async (value: any, key: string, group = '') => {
|
||||||
let _value = value;
|
let _value = value;
|
||||||
|
|
||||||
const fieldKey = key.includes('.') ? key.split('.')[0] : key;
|
const fieldKey = key.includes('.') ? key.split('.')[0] : key;
|
||||||
const field = group ? fields[group]?.fields[fieldKey] : fields[fieldKey];
|
const field = group ? fields[group]?.fields[fieldKey] : fields[fieldKey];
|
||||||
|
|
||||||
// Parses the boolean value.
|
// Parses the boolean value.
|
||||||
if (field.fieldType === 'boolean') {
|
if (field.fieldType === 'boolean') {
|
||||||
_value = parseBoolean(value);
|
_value = parseBoolean(value);
|
||||||
|
|
||||||
// Parses the enumeration value.
|
// Parses the enumeration value.
|
||||||
} else if (field.fieldType === 'enumeration') {
|
} else if (field.fieldType === 'enumeration') {
|
||||||
const option = get(field, 'options', []).find(
|
const option = get(field, 'options', []).find(
|
||||||
(option) => option.label?.toLowerCase() === value?.toLowerCase(),
|
(option) => option.label?.toLowerCase() === value?.toLowerCase(),
|
||||||
);
|
);
|
||||||
_value = get(option, 'key');
|
_value = get(option, 'key');
|
||||||
// Parses the numeric value.
|
// Parses the numeric value.
|
||||||
} else if (field.fieldType === 'number') {
|
} else if (field.fieldType === 'number') {
|
||||||
_value = multiNumberParse(value);
|
_value = multiNumberParse(value);
|
||||||
// Parses the relation value.
|
// Parses the relation value.
|
||||||
} else if (field.fieldType === 'relation') {
|
} else if (field.fieldType === 'relation') {
|
||||||
const RelationModel = tenantModels[field.relationModel];
|
const RelationModel = tenantModels[field.relationModel];
|
||||||
|
|
||||||
if (!RelationModel) {
|
if (!RelationModel) {
|
||||||
throw new Error(`The relation model of ${key} field is not exist.`);
|
throw new Error(`The relation model of ${key} field is not exist.`);
|
||||||
}
|
}
|
||||||
const relationQuery = RelationModel.query(trx);
|
const relationQuery = RelationModel.query(trx);
|
||||||
const relationKeys = castArray(field?.relationImportMatch);
|
const relationKeys = castArray(field?.relationImportMatch);
|
||||||
|
|
||||||
relationQuery.where(function () {
|
relationQuery.where(function () {
|
||||||
relationKeys.forEach((relationKey: string) => {
|
relationKeys.forEach((relationKey: string) => {
|
||||||
this.orWhereRaw('LOWER(??) = LOWER(?)', [relationKey, value]);
|
this.orWhereRaw('LOWER(??) = LOWER(?)', [relationKey, value]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
const result = await relationQuery.first();
|
||||||
const result = await relationQuery.first();
|
_value = get(result, 'id');
|
||||||
_value = get(result, 'id');
|
} else if (field.fieldType === 'collection') {
|
||||||
} else if (field.fieldType === 'collection') {
|
const ObjectFieldKey = key.includes('.') ? key.split('.')[1] : key;
|
||||||
const ObjectFieldKey = key.includes('.') ? key.split('.')[1] : key;
|
const _valueParser = valueParser(fields, tenantModels);
|
||||||
const _valueParser = valueParser(fields, tenantModels);
|
_value = await _valueParser(value, ObjectFieldKey, fieldKey);
|
||||||
_value = await _valueParser(value, ObjectFieldKey, fieldKey);
|
}
|
||||||
}
|
return _value;
|
||||||
return _value;
|
};
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the field key and detarmines the key path.
|
* Parses the field key and detarmines the key path.
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import { Importable } from '@/modules/Import/Importable';
|
|||||||
import { CreateSaleInvoiceDto } from '../dtos/SaleInvoice.dto';
|
import { CreateSaleInvoiceDto } from '../dtos/SaleInvoice.dto';
|
||||||
import { SaleInvoicesSampleData } from '../constants';
|
import { SaleInvoicesSampleData } from '../constants';
|
||||||
import { ImportableService } from '@/modules/Import/decorators/Import.decorator';
|
import { ImportableService } from '@/modules/Import/decorators/Import.decorator';
|
||||||
import { ManualJournal } from '@/modules/ManualJournals/models/ManualJournal';
|
import { SaleInvoice } from '../models/SaleInvoice';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ImportableService({ name: ManualJournal.name })
|
@ImportableService({ name: SaleInvoice.name })
|
||||||
export class SaleInvoicesImportable extends Importable {
|
export class SaleInvoicesImportable extends Importable {
|
||||||
constructor(private readonly createInvoiceService: CreateSaleInvoice) {
|
constructor(private readonly createInvoiceService: CreateSaleInvoice) {
|
||||||
super();
|
super();
|
||||||
|
|||||||
@@ -107,4 +107,4 @@ const modelProviders = models.map((model) => RegisterTenancyModel(model));
|
|||||||
imports: [...modelProviders],
|
imports: [...modelProviders],
|
||||||
exports: [...modelProviders],
|
exports: [...modelProviders],
|
||||||
})
|
})
|
||||||
export class TenancyModelsModule {}
|
export class TenancyModelsModule { }
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ function AccountsSuggestFieldRoot({
|
|||||||
return (
|
return (
|
||||||
<FSuggest
|
<FSuggest
|
||||||
items={filteredAccounts}
|
items={filteredAccounts}
|
||||||
|
itemPredicate={filterAccountsPredicater}
|
||||||
onCreateItemSelect={handleCreateItemSelect}
|
onCreateItemSelect={handleCreateItemSelect}
|
||||||
valueAccessor="id"
|
valueAccessor="id"
|
||||||
textAccessor="name"
|
textAccessor="name"
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ function ExpenseForm({
|
|||||||
createExpenseMutate,
|
createExpenseMutate,
|
||||||
expense,
|
expense,
|
||||||
expenseId,
|
expenseId,
|
||||||
submitPayload,
|
submitPayloadRef,
|
||||||
} = useExpenseFormContext();
|
} = useExpenseFormContext();
|
||||||
|
|
||||||
const isNewMode = !expenseId;
|
const isNewMode = !expenseId;
|
||||||
@@ -86,9 +86,12 @@ function ExpenseForm({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get submit payload from ref for synchronous access
|
||||||
|
const currentSubmitPayload = submitPayloadRef?.current || {};
|
||||||
|
|
||||||
const form = {
|
const form = {
|
||||||
...transformFormValuesToRequest(values),
|
...transformFormValuesToRequest(values),
|
||||||
publish: submitPayload.publish,
|
publish: currentSubmitPayload.publish,
|
||||||
};
|
};
|
||||||
// Handle request success.
|
// Handle request success.
|
||||||
const handleSuccess = (response) => {
|
const handleSuccess = (response) => {
|
||||||
@@ -103,10 +106,10 @@ function ExpenseForm({
|
|||||||
});
|
});
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
|
|
||||||
if (submitPayload.redirect) {
|
if (currentSubmitPayload.redirect) {
|
||||||
history.push('/expenses');
|
history.push('/expenses');
|
||||||
}
|
}
|
||||||
if (submitPayload.resetForm) {
|
if (currentSubmitPayload.resetForm) {
|
||||||
resetForm();
|
resetForm();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -59,8 +59,13 @@ function ExpenseFormPageProvider({ query, expenseId, ...props }) {
|
|||||||
const { mutateAsync: createExpenseMutate } = useCreateExpense();
|
const { mutateAsync: createExpenseMutate } = useCreateExpense();
|
||||||
const { mutateAsync: editExpenseMutate } = useEditExpense();
|
const { mutateAsync: editExpenseMutate } = useEditExpense();
|
||||||
|
|
||||||
// Submit form payload.
|
// Submit form payload - using ref for synchronous access.
|
||||||
const [submitPayload, setSubmitPayload] = React.useState({});
|
const submitPayloadRef = React.useRef({});
|
||||||
|
|
||||||
|
// Setter to update the ref.
|
||||||
|
const setSubmitPayload = React.useCallback((payload) => {
|
||||||
|
submitPayloadRef.current = payload;
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Detarmines whether the form in new mode.
|
// Detarmines whether the form in new mode.
|
||||||
const isNewMode = !expenseId;
|
const isNewMode = !expenseId;
|
||||||
@@ -69,7 +74,7 @@ function ExpenseFormPageProvider({ query, expenseId, ...props }) {
|
|||||||
const provider = {
|
const provider = {
|
||||||
isNewMode,
|
isNewMode,
|
||||||
expenseId,
|
expenseId,
|
||||||
submitPayload,
|
submitPayloadRef, // Expose ref for synchronous access
|
||||||
|
|
||||||
currencies,
|
currencies,
|
||||||
customers,
|
customers,
|
||||||
|
|||||||
Reference in New Issue
Block a user