mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 21:30:31 +00:00
fix: import accounts issue
This commit is contained in:
@@ -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),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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}</>;
|
||||
}
|
||||
|
||||
|
||||
@@ -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],
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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[];
|
||||
|
||||
@@ -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`,
|
||||
|
||||
Reference in New Issue
Block a user