fix: import accounts issue

This commit is contained in:
Ahmed Bouhuolia
2024-03-22 20:45:05 +02:00
parent 858e3541cb
commit 973d1832bd
14 changed files with 125 additions and 48 deletions

View File

@@ -349,7 +349,7 @@ export default class AccountsController extends BaseController {
// Filter query.
const filter = {
sortOrder: 'desc',
columnSortBy: 'created_at',
columnSortBy: 'createdAt',
inactiveMode: false,
structure: IAccountsStructureType.Tree,
...this.matchedQueryData(req),

View File

@@ -1,3 +1,4 @@
export interface IModel {
name: string;
tableName: string;
@@ -35,6 +36,8 @@ export interface IModelMetaFieldCommon {
fieldType: IModelColumnType;
customQuery?: Function;
required?: boolean;
importHint?: string;
order?: number;
}
export interface IModelMetaFieldNumber {

View File

@@ -15,6 +15,7 @@ export default {
unique: true,
required: true,
importable: true,
order: 1,
},
description: {
name: 'account.field.description',
@@ -37,6 +38,7 @@ export default {
importable: true,
minLength: 3,
maxLength: 6,
importHint: 'Unique number to identify the account.',
},
rootType: {
name: 'account.field.root_type',
@@ -73,6 +75,7 @@ export default {
})),
required: true,
importable: true,
order: 2,
},
active: {
name: 'account.field.active',
@@ -81,11 +84,11 @@ export default {
filterable: false,
importable: true,
},
openingBalance: {
balance: {
name: 'account.field.balance',
column: 'amount',
fieldType: 'number',
importable: true,
importable: false,
},
currencyCode: {
name: 'account.field.currency',
@@ -99,6 +102,7 @@ export default {
column: 'parent_account_id',
fieldType: 'relation',
to: { model: 'Account', to: 'id' },
importable: false,
},
createdAt: {
name: 'account.field.created_at',

View File

@@ -61,20 +61,38 @@ export class ImportFileUploadService {
resource: _resourceName,
columns: coumnsStringified,
});
const resourceColumns = this.resourceService.getResourceImportableFields(
const resourceColumnsMap = this.resourceService.getResourceImportableFields(
tenantId,
_resourceName
);
const resourceColumnsTransformeed = Object.entries(resourceColumns).map(
([key, { name }]: [string, IModelMetaField]) => ({ key, name })
);
const resourceColumns = this.getResourceColumns(resourceColumnsMap);
return {
import: {
importId: importFile.importId,
resource: importFile.resource,
},
sheetColumns,
resourceColumns: resourceColumnsTransformeed,
resourceColumns,
};
}
getResourceColumns(resourceColumns: { [key: string]: IModelMetaField }) {
return Object.entries(resourceColumns)
.map(
([key, { name, importHint, required, order }]: [
string,
IModelMetaField
]) => ({
key,
name,
required,
hint: importHint,
order,
})
)
.sort((a, b) =>
a.order && b.order ? a.order - b.order : a.order ? -1 : b.order ? 1 : 0
);
}
}

View File

@@ -25,7 +25,12 @@ export interface ImportFileUploadPOJO {
resource: string;
};
sheetColumns: string[];
resourceColumns: { key: string; name: string }[];
resourceColumns: {
key: string;
name: string;
required?: boolean;
hint?: string;
}[];
}
export interface ImportFileMapPOJO {
@@ -45,7 +50,6 @@ export interface ImportFilePreviewPOJO {
unmappedColumnsCount: number;
}
export interface ImportOperSuccess {
data: unknown;
index: number;
@@ -54,4 +58,4 @@ export interface ImportOperSuccess {
export interface ImportOperError {
error: ImportInsertError;
index: number;
}
}

View File

@@ -1,14 +1,27 @@
// @ts-nocheck
import React from 'react';
import { Tooltip } from '@blueprintjs/core';
import { Position, Tooltip } from '@blueprintjs/core';
import { Icon } from '../Icon';
import '@/style/components/Hint.scss';
import { Tooltip2Props } from '@blueprintjs/popover2';
interface HintProps {
content: string;
position?: Position;
iconSize?: number;
tooltipProps?: Partial<Tooltip2Props>;
}
/**
* Field hint.
*/
export function FieldHint({ content, position, iconSize = 12, tooltipProps }) {
export function FieldHint({
content,
position,
iconSize = 12,
tooltipProps,
}: HintProps) {
return (
<span class="hint">
<Tooltip content={content} position={position} {...tooltipProps}>

View File

@@ -1,6 +1,7 @@
import React from 'react';
import { HTMLDivProps, Props } from '@blueprintjs/core';
export interface BoxProps {
export interface BoxProps extends Props, HTMLDivProps {
className?: string;
}

View File

@@ -23,9 +23,14 @@
}
tr td{
:global(.bp4-popover-target .bp4-button){
:global(.bp4-popover-target .bp4-button),
:global(.bp4-popover-wrapper){
max-width: 250px;
}
}
}
}
.requiredSign{
color: rgb(250, 82, 82);
}

View File

@@ -1,10 +1,10 @@
import { useMemo } from 'react';
import clsx from 'classnames';
import { Button, Intent } from '@blueprintjs/core';
import { Button, Intent, Position } from '@blueprintjs/core';
import { useFormikContext } from 'formik';
import { FSelect, Group } from '@/components';
import { FSelect, Group, Hint } from '@/components';
import { ImportFileMappingForm } from './ImportFileMappingForm';
import { useImportFileContext } from './ImportFileProvider';
import { EntityColumn, useImportFileContext } from './ImportFileProvider';
import { CLASSES } from '@/constants';
import { ImportFileContainer } from './ImportFileContainer';
import styles from './ImportFileMapping.module.scss';
@@ -44,21 +44,29 @@ function ImportFileMappingFields() {
() => sheetColumns.map((column) => ({ value: column, text: column })),
[sheetColumns],
);
const columns = entityColumns.map((column, index) => (
const columnMapper = (column: EntityColumn, index: number) => (
<tr key={index}>
<td className={styles.label}>{column.name}</td>
<td className={styles.label}>
{column.name}{' '}
{column.required && <span className={styles.requiredSign}>*</span>}
</td>
<td className={styles.field}>
<FSelect
name={column.key}
items={items}
popoverProps={{ minimal: true }}
minimal={true}
fill={true}
/>
<Group spacing={4}>
<FSelect
name={column.key}
items={items}
popoverProps={{ minimal: true }}
minimal={true}
fill={true}
/>
{column.hint && (
<Hint content={column.hint} position={Position.BOTTOM} />
)}
</Group>
</td>
</tr>
));
);
const columns = entityColumns.map(columnMapper);
return <>{columns}</>;
}

View File

@@ -4,7 +4,7 @@ import { useImportFileMapping } from '@/hooks/query/import';
import { Form, Formik, FormikHelpers } from 'formik';
import { useImportFileContext } from './ImportFileProvider';
import { useMemo } from 'react';
import { isEmpty } from 'lodash';
import { isEmpty, lowerCase } from 'lodash';
import { AppToaster } from '@/components';
interface ImportFileMappingFormProps {
@@ -34,21 +34,18 @@ export function ImportFileMappingForm({
setStep(2);
})
.catch(({ response: { data } }) => {
if (data.errors.find(e => e.type === "DUPLICATED_FROM_MAP_ATTR")) {
if (data.errors.find((e) => e.type === 'DUPLICATED_FROM_MAP_ATTR')) {
AppToaster.show({
message: 'Selected the same sheet columns to multiple fields.',
intent: Intent.DANGER
})
intent: Intent.DANGER,
});
}
setSubmitting(false);
});
};
return (
<Formik
initialValues={initialValues}
onSubmit={handleSubmit}
>
<Formik initialValues={initialValues} onSubmit={handleSubmit}>
<Form>{children}</Form>
</Formik>
);
@@ -62,14 +59,20 @@ const transformValueToReq = (value: ImportFileMappingFormValues) => {
};
const useImportFileMappingInitialValues = () => {
const { entityColumns } = useImportFileContext();
const { entityColumns, sheetColumns } = useImportFileContext();
return useMemo(
() =>
entityColumns.reduce((acc, { key, name }) => {
acc[key] = '';
const _name = lowerCase(name);
const _matched = sheetColumns.find(
(column) => lowerCase(column) === _name,
);
// Match the default column name the same field name
// if matched one of sheet columns has the same field name.
acc[key] = _matched ? _matched : '';
return acc;
}, {}),
[entityColumns],
[entityColumns, sheetColumns],
);
};

View File

@@ -83,6 +83,9 @@ function ImportFilePreviewImported() {
function ImportFilePreviewSkipped() {
const { importPreview } = useImportFilePreviewBootContext();
// Can't continue if there's no skipped items.
if (importPreview.skippedCount <= 0) return null;
return (
<Section
collapseProps={{ defaultIsOpen: false }}
@@ -120,6 +123,9 @@ function ImportFilePreviewSkipped() {
function ImportFilePreviewUnmapped() {
const { importPreview } = useImportFilePreviewBootContext();
// Can't continue if there's no unmapped columns.
if (importPreview?.unmappedColumnsCount <= 0) return null;
return (
<Section
collapseProps={{ defaultIsOpen: false }}

View File

@@ -1,6 +1,7 @@
import { useImportFilePreview } from '@/hooks/query/import';
import { transformToCamelCase } from '@/utils';
import { Spinner } from '@blueprintjs/core';
import React, { createContext, useContext } from 'react';
import { Box } from '@/components';
import { useImportFilePreview } from '@/hooks/query/import';
interface ImportFilePreviewBootContextValue {}
@@ -46,7 +47,13 @@ export const ImportFilePreviewBootProvider = ({
};
return (
<ImportFilePreviewBootContext.Provider value={value}>
{isImportPreviewLoading ? 'loading' : <>{children}</>}
{isImportPreviewLoading ? (
<Box style={{ padding: '2rem', textAlign: 'center' }}>
<Spinner size={26} />
</Box>
) : (
<>{children}</>
)}
</ImportFilePreviewBootContext.Provider>
);
};

View File

@@ -7,9 +7,14 @@ import React, {
useState,
} from 'react';
type EntityColumn = { key: string; name: string };
type SheetColumn = string;
type SheetMap = { from: string; to: string };
export type EntityColumn = {
key: string;
name: string;
required?: boolean;
hint?: string;
};
export type SheetColumn = string;
export type SheetMap = { from: string; to: string };
interface ImportFileContextValue {
sheetColumns: SheetColumn[];

View File

@@ -13,8 +13,8 @@ export const getDashboardRoutes = () => [
{
path: '/accounts/import',
component: lazy(() => import('@/containers/Import/ImportPage')),
breadcrumb: 'Import Accounts',
pageTitle: 'Import Accounts',
breadcrumb: 'Accounts Import',
pageTitle: 'Accounts Import',
},
{
path: `/accounts`,