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. // Filter query.
const filter = { const filter = {
sortOrder: 'desc', sortOrder: 'desc',
columnSortBy: 'created_at', columnSortBy: 'createdAt',
inactiveMode: false, inactiveMode: false,
structure: IAccountsStructureType.Tree, structure: IAccountsStructureType.Tree,
...this.matchedQueryData(req), ...this.matchedQueryData(req),

View File

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

View File

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

View File

@@ -61,20 +61,38 @@ export class ImportFileUploadService {
resource: _resourceName, resource: _resourceName,
columns: coumnsStringified, columns: coumnsStringified,
}); });
const resourceColumns = this.resourceService.getResourceImportableFields( const resourceColumnsMap = this.resourceService.getResourceImportableFields(
tenantId, tenantId,
_resourceName _resourceName
); );
const resourceColumnsTransformeed = Object.entries(resourceColumns).map( const resourceColumns = this.getResourceColumns(resourceColumnsMap);
([key, { name }]: [string, IModelMetaField]) => ({ key, name })
);
return { return {
import: { import: {
importId: importFile.importId, importId: importFile.importId,
resource: importFile.resource, resource: importFile.resource,
}, },
sheetColumns, 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; resource: string;
}; };
sheetColumns: string[]; sheetColumns: string[];
resourceColumns: { key: string; name: string }[]; resourceColumns: {
key: string;
name: string;
required?: boolean;
hint?: string;
}[];
} }
export interface ImportFileMapPOJO { export interface ImportFileMapPOJO {
@@ -45,7 +50,6 @@ export interface ImportFilePreviewPOJO {
unmappedColumnsCount: number; unmappedColumnsCount: number;
} }
export interface ImportOperSuccess { export interface ImportOperSuccess {
data: unknown; data: unknown;
index: number; index: number;

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ import { useImportFileMapping } from '@/hooks/query/import';
import { Form, Formik, FormikHelpers } from 'formik'; import { Form, Formik, FormikHelpers } from 'formik';
import { useImportFileContext } from './ImportFileProvider'; import { useImportFileContext } from './ImportFileProvider';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { isEmpty } from 'lodash'; import { isEmpty, lowerCase } from 'lodash';
import { AppToaster } from '@/components'; import { AppToaster } from '@/components';
interface ImportFileMappingFormProps { interface ImportFileMappingFormProps {
@@ -34,21 +34,18 @@ export function ImportFileMappingForm({
setStep(2); setStep(2);
}) })
.catch(({ response: { data } }) => { .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({ AppToaster.show({
message: 'Selected the same sheet columns to multiple fields.', message: 'Selected the same sheet columns to multiple fields.',
intent: Intent.DANGER intent: Intent.DANGER,
}) });
} }
setSubmitting(false); setSubmitting(false);
}); });
}; };
return ( return (
<Formik <Formik initialValues={initialValues} onSubmit={handleSubmit}>
initialValues={initialValues}
onSubmit={handleSubmit}
>
<Form>{children}</Form> <Form>{children}</Form>
</Formik> </Formik>
); );
@@ -62,14 +59,20 @@ const transformValueToReq = (value: ImportFileMappingFormValues) => {
}; };
const useImportFileMappingInitialValues = () => { const useImportFileMappingInitialValues = () => {
const { entityColumns } = useImportFileContext(); const { entityColumns, sheetColumns } = useImportFileContext();
return useMemo( return useMemo(
() => () =>
entityColumns.reduce((acc, { key, name }) => { 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; return acc;
}, {}), }, {}),
[entityColumns], [entityColumns, sheetColumns],
); );
}; };

View File

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

View File

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

View File

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

View File

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