mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-21 15:20:34 +00:00
fix: import accounts issue
This commit is contained in:
@@ -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),
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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}>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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,11 +44,14 @@ 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}>
|
||||||
|
<Group spacing={4}>
|
||||||
<FSelect
|
<FSelect
|
||||||
name={column.key}
|
name={column.key}
|
||||||
items={items}
|
items={items}
|
||||||
@@ -56,9 +59,14 @@ function ImportFileMappingFields() {
|
|||||||
minimal={true}
|
minimal={true}
|
||||||
fill={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}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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],
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 }}
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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[];
|
||||||
|
|||||||
@@ -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`,
|
||||||
|
|||||||
Reference in New Issue
Block a user