Compare commits

...

2 Commits

Author SHA1 Message Date
Ahmed Bouhuolia
079491823d feat: add hints to import fields 2024-04-09 22:00:04 +02:00
Ahmed Bouhuolia
f7a87a6e9c Merge pull request #400 from bigcapitalhq/clean-up-templ-import-files
feat: clean up the imported temp files
2024-04-09 00:16:36 +02:00
17 changed files with 261 additions and 103 deletions

View File

@@ -132,6 +132,7 @@ export default {
relationModel: 'Item', relationModel: 'Item',
relationImportMatch: ['name', 'code'], relationImportMatch: ['name', 'code'],
required: true, required: true,
importHint: "Matches the item name or code."
}, },
rate: { rate: {
name: 'Rate', name: 'Rate',

View File

@@ -84,6 +84,7 @@ export default {
name: 'bill_payment.field.payment_number', name: 'bill_payment.field.payment_number',
fieldType: 'text', fieldType: 'text',
unique: true, unique: true,
importHint: "The payment number should be unique."
}, },
paymentAccountId: { paymentAccountId: {
name: 'bill_payment.field.payment_account', name: 'bill_payment.field.payment_account',
@@ -91,6 +92,7 @@ export default {
relationModel: 'Account', relationModel: 'Account',
relationImportMatch: ['name', 'code'], relationImportMatch: ['name', 'code'],
required: true, required: true,
importHint: "Matches the account name or code."
}, },
exchangeRate: { exchangeRate: {
name: 'bill_payment.field.exchange_rate', name: 'bill_payment.field.exchange_rate',
@@ -118,6 +120,7 @@ export default {
relationModel: 'Bill', relationModel: 'Bill',
relationImportMatch: 'billNumber', relationImportMatch: 'billNumber',
required: true, required: true,
importHint: "Matches the bill number."
}, },
paymentAmount: { paymentAmount: {
name: 'bill_payment.field.entries.payment_amount', name: 'bill_payment.field.entries.payment_amount',

View File

@@ -130,6 +130,7 @@ export default {
relationModel: 'Item', relationModel: 'Item',
relationImportMatch: ['name', 'code'], relationImportMatch: ['name', 'code'],
required: true, required: true,
importHint: 'Matches the item name or code.',
}, },
rate: { rate: {
name: 'Rate', name: 'Rate',

View File

@@ -68,6 +68,7 @@ export default {
relationModel: 'Account', relationModel: 'Account',
relationImportMatch: ['name', 'code'], relationImportMatch: ['name', 'code'],
required: true, required: true,
importHint: "Matches the account name or code."
}, },
referenceNo: { referenceNo: {
name: 'expense.field.reference_no', name: 'expense.field.reference_no',
@@ -101,6 +102,7 @@ export default {
relationModel: 'Account', relationModel: 'Account',
relationImportMatch: ['name', 'code'], relationImportMatch: ['name', 'code'],
required: true, required: true,
importHint: "Matches the account name or code."
}, },
amount: { amount: {
name: 'expense.field.amount', name: 'expense.field.amount',

View File

@@ -124,117 +124,82 @@ export default {
fields2: { fields2: {
type: { type: {
name: 'item.field.type', name: 'item.field.type',
column: 'type',
fieldType: 'enumeration', fieldType: 'enumeration',
options: [ options: [
{ key: 'inventory', label: 'item.field.type.inventory' }, { key: 'inventory', label: 'item.field.type.inventory' },
{ key: 'service', label: 'item.field.type.service' }, { key: 'service', label: 'item.field.type.service' },
{ key: 'non-inventory', label: 'item.field.type.non-inventory' }, { key: 'non-inventory', label: 'item.field.type.non-inventory' },
], ],
required: true,
}, },
name: { name: {
name: 'item.field.name', name: 'item.field.name',
column: 'name',
fieldType: 'text', fieldType: 'text',
required: true,
}, },
code: { code: {
name: 'item.field.code', name: 'item.field.code',
column: 'code',
fieldType: 'text', fieldType: 'text',
}, },
sellable: { sellable: {
name: 'item.field.sellable', name: 'item.field.sellable',
column: 'sellable',
fieldType: 'boolean', fieldType: 'boolean',
}, },
purchasable: { purchasable: {
name: 'item.field.purchasable', name: 'item.field.purchasable',
column: 'purchasable',
fieldType: 'boolean', fieldType: 'boolean',
}, },
sell_price: { sellPrice: {
name: 'item.field.cost_price', name: 'item.field.sell_price',
column: 'sell_price',
fieldType: 'number', fieldType: 'number',
}, },
cost_price: { cost_price: {
name: 'item.field.cost_price',
fieldType: 'number',
},
costAccount: {
name: 'item.field.cost_account', name: 'item.field.cost_account',
column: 'cost_price', fieldType: 'relation',
fieldType: 'number', relationModel: 'Account',
relationImportMatch: ['name', 'code'],
importHint: 'Matches the account name or code.',
}, },
cost_account: { sellAccount: {
name: 'item.field.sell_account', name: 'item.field.sell_account',
column: 'cost_account_id',
fieldType: 'relation', fieldType: 'relation',
relationModel: 'Account',
relationType: 'enumeration', relationImportMatch: ['name', 'code'],
relationKey: 'costAccount', importHint: 'Matches the account name or code.',
relationEntityLabel: 'name',
relationEntityKey: 'slug',
}, },
sell_account: { inventoryAccount: {
name: 'item.field.sell_description',
column: 'sell_account_id',
fieldType: 'relation',
relationType: 'enumeration',
relationKey: 'sellAccount',
relationEntityLabel: 'name',
relationEntityKey: 'slug',
},
inventory_account: {
name: 'item.field.inventory_account', name: 'item.field.inventory_account',
column: 'inventory_account_id', fieldType: 'relation',
relationModel: 'Account',
relationType: 'enumeration', relationImportMatch: ['name', 'code'],
relationKey: 'inventoryAccount', importHint: 'Matches the account name or code.',
relationEntityLabel: 'name',
relationEntityKey: 'slug',
}, },
sell_description: { sellDescription: {
name: 'Sell description', name: 'Sell Description',
column: 'sell_description',
fieldType: 'text', fieldType: 'text',
}, },
purchase_description: { purchaseDescription: {
name: 'Purchase description', name: 'Purchase Description',
column: 'purchase_description',
fieldType: 'text', fieldType: 'text',
}, },
quantity_on_hand: {
name: 'item.field.quantity_on_hand',
column: 'quantity_on_hand',
fieldType: 'number',
},
note: { note: {
name: 'item.field.note', name: 'item.field.note',
column: 'note',
fieldType: 'text', fieldType: 'text',
}, },
category: { category: {
name: 'item.field.category', name: 'item.field.category',
column: 'category_id', fieldType: 'relation',
relationModel: 'ItemCategory',
relationType: 'enumeration', relationImportMatch: ['name'],
relationKey: 'category', importHint: "Matches the category name."
relationEntityLabel: 'name',
relationEntityKey: 'id',
}, },
active: { active: {
name: 'item.field.active', name: 'item.field.active',
column: 'active',
fieldType: 'boolean', fieldType: 'boolean',
filterable: false,
},
created_at: {
name: 'item.field.created_at',
column: 'created_at',
columnType: 'date',
fieldType: 'date',
}, },
}, },
}; };

View File

@@ -84,10 +84,12 @@ export default {
relationModel: 'Account', relationModel: 'Account',
relationImportMatch: ['name', 'code'], relationImportMatch: ['name', 'code'],
required: true, required: true,
importHint: "Matches the account name or code."
}, },
paymentReceiveNo: { paymentReceiveNo: {
name: 'payment_receive.field.payment_receive_no', name: 'payment_receive.field.payment_receive_no',
fieldType: 'text', fieldType: 'text',
importHint: "The payment number should be unique."
}, },
statement: { statement: {
name: 'payment_receive.field.statement', name: 'payment_receive.field.statement',
@@ -106,6 +108,7 @@ export default {
relationModel: 'SaleInvoice', relationModel: 'SaleInvoice',
relationImportMatch: 'invoiceNo', relationImportMatch: 'invoiceNo',
required: true, required: true,
importHint: "Matches the invoice number."
}, },
paymentAmount: { paymentAmount: {
name: 'payment_receive.field.entries.payment_amount', name: 'payment_receive.field.entries.payment_amount',

View File

@@ -132,6 +132,7 @@ export default {
relationModel: 'Item', relationModel: 'Item',
relationImportMatch: ['name', 'code'], relationImportMatch: ['name', 'code'],
required: true, required: true,
importHint: "Matches the item name or code."
}, },
rate: { rate: {
name: 'invoice.field.rate', name: 'invoice.field.rate',

View File

@@ -142,6 +142,7 @@ export default {
relationModel: 'Item', relationModel: 'Item',
relationImportMatch: ['name', 'code'], relationImportMatch: ['name', 'code'],
required: true, required: true,
importHint: "Matches the item name or code."
}, },
rate: { rate: {
name: 'invoice.field.rate', name: 'invoice.field.rate',

View File

@@ -126,6 +126,7 @@ export default {
relationModel: 'Item', relationModel: 'Item',
relationImportMatch: ['name', 'code'], relationImportMatch: ['name', 'code'],
required: true, required: true,
importHint: "Matches the item name or code."
}, },
rate: { rate: {
name: 'invoice.field.rate', name: 'invoice.field.rate',

View File

@@ -53,23 +53,19 @@ export default {
}, },
payee: { payee: {
name: 'Payee', name: 'Payee',
column: 'payee',
fieldType: 'text', fieldType: 'text',
}, },
description: { description: {
name: 'Description', name: 'Description',
column: 'description',
fieldType: 'text', fieldType: 'text',
}, },
referenceNo: { referenceNo: {
name: 'Reference No.', name: 'Reference No.',
column: 'reference_no',
fieldType: 'text', fieldType: 'text',
}, },
amount: { amount: {
name: 'Amount', name: 'Amount',
column: 'Amount', fieldType: 'number',
fieldType: 'numeric',
required: true, required: true,
}, },
}, },

View File

@@ -122,6 +122,7 @@ export default {
relationModel: 'Item', relationModel: 'Item',
relationImportMatch: ['name', 'code'], relationImportMatch: ['name', 'code'],
required: true, required: true,
importHint: "Matches the item name or code."
}, },
rate: { rate: {
name: 'Rate', name: 'Rate',

View File

@@ -79,27 +79,25 @@ export interface ICashflowTransactionTypeMeta {
} }
export const BankTransactionsSampleData = [ export const BankTransactionsSampleData = [
[ {
{ Amount: '6,410.19',
Amount: '6,410.19', Date: '2024-03-26',
Date: '2024-03-26', Payee: 'MacGyver and Sons',
Payee: 'MacGyver and Sons', 'Reference No.': 'REF-1',
'Reference No.': 'REF-1', Description: 'Commodi quo labore.',
Description: 'Commodi quo labore.', },
}, {
{ Amount: '8,914.17',
Amount: '8,914.17', Date: '2024-01-05',
Date: '2024-01-05', Payee: 'Eichmann - Bergnaum',
Payee: 'Eichmann - Bergnaum', 'Reference No.': 'REF-1',
'Reference No.': 'REF-1', Description: 'Quia enim et.',
Description: 'Quia enim et.', },
}, {
{ Amount: '6,200.88',
Amount: '6,200.88', Date: '2024-02-17',
Date: '2024-02-17', Payee: 'Luettgen, Mraz and Legros',
Payee: 'Luettgen, Mraz and Legros', 'Reference No.': 'REF-1',
'Reference No.': 'REF-1', Description: 'Occaecati consequuntur cum impedit illo.',
Description: 'Occaecati consequuntur cum impedit illo.', },
},
],
]; ];

View File

@@ -2,7 +2,12 @@ import { Inject, Service } from 'typedi';
import { chain } from 'lodash'; import { chain } from 'lodash';
import { Knex } from 'knex'; import { Knex } from 'knex';
import { ServiceError } from '@/exceptions'; import { ServiceError } from '@/exceptions';
import { ERRORS, getSheetColumns, getUnmappedSheetColumns, readImportFile } from './_utils'; import {
ERRORS,
getSheetColumns,
getUnmappedSheetColumns,
readImportFile,
} from './_utils';
import { ImportFileCommon } from './ImportFileCommon'; import { ImportFileCommon } from './ImportFileCommon';
import { ImportFileDataTransformer } from './ImportFileDataTransformer'; import { ImportFileDataTransformer } from './ImportFileDataTransformer';
import ResourceService from '../Resource/ResourceService'; import ResourceService from '../Resource/ResourceService';
@@ -49,10 +54,9 @@ export class ImportFileProcess {
const sheetData = this.importCommon.parseXlsxSheet(buffer); const sheetData = this.importCommon.parseXlsxSheet(buffer);
const header = getSheetColumns(sheetData); const header = getSheetColumns(sheetData);
const resourceFields = this.resource.getResourceFields2( const resource = importFile.resource;
tenantId, const resourceFields = this.resource.getResourceFields2(tenantId, resource);
importFile.resource
);
// Runs the importing operation with ability to return errors that will happen. // Runs the importing operation with ability to return errors that will happen.
const [successedImport, failedImport, allData] = const [successedImport, failedImport, allData] =
await this.uow.withTransaction( await this.uow.withTransaction(
@@ -91,6 +95,7 @@ export class ImportFileProcess {
const skippedCount = errorsCount; const skippedCount = errorsCount;
return { return {
resource,
createdCount, createdCount,
skippedCount, skippedCount,
totalCount, totalCount,

View File

@@ -44,6 +44,7 @@ export interface ImportFileMapPOJO {
} }
export interface ImportFilePreviewPOJO { export interface ImportFilePreviewPOJO {
resource: string;
createdCount: number; createdCount: number;
skippedCount: number; skippedCount: number;
totalCount: number; totalCount: number;

View File

@@ -3,6 +3,7 @@ import { Knex } from 'knex';
import { Importable } from '@/services/Import/Importable'; import { Importable } from '@/services/Import/Importable';
import { IItemCreateDTO } from '@/interfaces'; import { IItemCreateDTO } from '@/interfaces';
import { CreateItem } from './CreateItem'; import { CreateItem } from './CreateItem';
import { ItemsSampleData } from './constants';
@Service() @Service()
export class ItemsImportable extends Importable { export class ItemsImportable extends Importable {
@@ -28,6 +29,6 @@ export class ItemsImportable extends Importable {
* Retrieves the sample data of customers used to download sample sheet. * Retrieves the sample data of customers used to download sample sheet.
*/ */
public sampleData(): any[] { public sampleData(): any[] {
return []; return ItemsSampleData;
} }
} }

View File

@@ -1,4 +1,3 @@
export const ERRORS = { export const ERRORS = {
NOT_FOUND: 'NOT_FOUND', NOT_FOUND: 'NOT_FOUND',
ITEMS_NOT_FOUND: 'ITEMS_NOT_FOUND', ITEMS_NOT_FOUND: 'ITEMS_NOT_FOUND',
@@ -19,7 +18,8 @@ export const ERRORS = {
ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT: ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT:
'ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT', 'ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT',
ITEM_CANNOT_CHANGE_INVENTORY_TYPE: 'ITEM_CANNOT_CHANGE_INVENTORY_TYPE', ITEM_CANNOT_CHANGE_INVENTORY_TYPE: 'ITEM_CANNOT_CHANGE_INVENTORY_TYPE',
TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS: 'TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS', TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS:
'TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS',
INVENTORY_ACCOUNT_CANNOT_MODIFIED: 'INVENTORY_ACCOUNT_CANNOT_MODIFIED', INVENTORY_ACCOUNT_CANNOT_MODIFIED: 'INVENTORY_ACCOUNT_CANNOT_MODIFIED',
ITEM_HAS_ASSOCIATED_TRANSACTIONS: 'ITEM_HAS_ASSOCIATED_TRANSACTIONS', ITEM_HAS_ASSOCIATED_TRANSACTIONS: 'ITEM_HAS_ASSOCIATED_TRANSACTIONS',
@@ -53,8 +53,84 @@ export const DEFAULT_VIEWS = [
slug: 'non-inventory', slug: 'non-inventory',
rolesLogicExpression: '1', rolesLogicExpression: '1',
roles: [ roles: [
{ index: 1, fieldKey: 'type', comparator: 'equals', value: 'non-inventory' }, {
index: 1,
fieldKey: 'type',
comparator: 'equals',
value: 'non-inventory',
},
], ],
columns: DEFAULT_VIEW_COLUMNS, columns: DEFAULT_VIEW_COLUMNS,
}, },
] ];
export const ItemsSampleData = [
{
'Item Type': 'Inventory',
'Item Name': 'Hettinger, Schumm and Bartoletti',
'Item Code': '1000',
Sellable: 'T',
Purchasable: 'T',
'Cost Price': '10000',
'Sell Price': '1000',
'Cost Account': 'Cost of Goods Sold',
'Sell Account': 'Other Income',
'Inventory Account': 'Inventory Asset',
'Sell Description': 'Description ....',
'Purchase Description': 'Description ....',
Category: 'sdafasdfsadf',
Note: 'At dolor est non tempore et quisquam.',
Active: 'TRUE',
},
{
'Item Type': 'Inventory',
'Item Name': 'Schmitt Group',
'Item Code': '1001',
Sellable: 'T',
Purchasable: 'T',
'Cost Price': '10000',
'Sell Price': '1000',
'Cost Account': 'Cost of Goods Sold',
'Sell Account': 'Other Income',
'Inventory Account': 'Inventory Asset',
'Sell Description': 'Description ....',
'Purchase Description': 'Description ....',
Category: 'sdafasdfsadf',
Note: 'Id perspiciatis at adipisci minus accusamus dolor iure dolore.',
Active: 'TRUE',
},
{
'Item Type': 'Inventory',
'Item Name': 'Marks - Carroll',
'Item Code': '1002',
Sellable: 'T',
Purchasable: 'T',
'Cost Price': '10000',
'Sell Price': '1000',
'Cost Account': 'Cost of Goods Sold',
'Sell Account': 'Other Income',
'Inventory Account': 'Inventory Asset',
'Sell Description': 'Description ....',
'Purchase Description': 'Description ....',
Category: 'sdafasdfsadf',
Note: 'Odio odio minus similique.',
Active: 'TRUE',
},
{
'Item Type': 'Inventory',
'Item Name': 'VonRueden, Ruecker and Hettinger',
'Item Code': '1003',
Sellable: 'T',
Purchasable: 'T',
'Cost Price': '10000',
'Sell Price': '1000',
'Cost Account': 'Cost of Goods Sold',
'Sell Account': 'Other Income',
'Inventory Account': 'Inventory Asset',
'Sell Description': 'Description ....',
'Purchase Description': 'Description ....',
Category: 'sdafasdfsadf',
Note: 'Quibusdam dolores illo.',
Active: 'TRUE',
},
];

View File

@@ -1,8 +1,14 @@
// @ts-nocheck // @ts-nocheck
import { useMutation, useQuery, useQueryClient } from 'react-query'; import {
QueryClient,
useMutation,
useQuery,
useQueryClient,
} from 'react-query';
import useApiRequest from '../useRequest'; import useApiRequest from '../useRequest';
import { transformToCamelCase } from '@/utils'; import { transformToCamelCase } from '@/utils';
import { downloadFile, useDownloadFile } from '../useDownloadFile'; import { downloadFile, useDownloadFile } from '../useDownloadFile';
import T from './types';
const QueryKeys = { const QueryKeys = {
ImportPreview: 'ImportPreview', ImportPreview: 'ImportPreview',
@@ -75,6 +81,7 @@ export function useImportFileProcess(props = {}) {
{ {
onSuccess: (res, id) => { onSuccess: (res, id) => {
// Invalidate queries. // Invalidate queries.
invalidateResourcesOnImport(queryClient, res.data.resource);
}, },
...props, ...props,
}, },
@@ -117,3 +124,98 @@ export const useSampleSheetImport = () => {
}, },
); );
}; };
/**
* Invalidates resources cached queries based on the given resource name,
* @param queryClient
* @param resource
*/
const invalidateResourcesOnImport = (
queryClient: QueryClient,
resource: string,
) => {
debugger;
switch (resource) {
case 'Item':
queryClient.invalidateQueries(T.ITEMS);
queryClient.invalidateQueries(T.ITEM);
break;
case 'ItemCategory':
queryClient.invalidateQueries(T.ITEMS_CATEGORIES);
break;
case 'Bill':
queryClient.invalidateQueries(T.BILLS);
queryClient.invalidateQueries(T.BILL);
queryClient.invalidateQueries(T.ITEMS_ASSOCIATED_WITH_BILLS);
break;
case 'SaleInvoice':
queryClient.invalidateQueries(T.SALE_INVOICE);
queryClient.invalidateQueries(T.SALE_INVOICES);
queryClient.invalidateQueries(T.ITEM_ASSOCIATED_WITH_INVOICES);
break;
case 'SaleEstimate':
queryClient.invalidateQueries(T.SALE_ESTIMATE);
queryClient.invalidateQueries(T.SALE_ESTIMATES);
queryClient.invalidateQueries(T.ITEM_ASSOCIATED_WITH_ESTIMATES);
break;
case 'SaleReceipt':
queryClient.invalidateQueries(T.SALE_RECEIPT);
queryClient.invalidateQueries(T.SALE_RECEIPTS);
queryClient.invalidateQueries(T.ITEM_ASSOCIATED_WITH_RECEIPTS);
break;
case 'CreditNote':
queryClient.invalidateQueries(T.CREDIT_NOTE);
queryClient.invalidateQueries(T.CREDIT_NOTES);
break;
case 'VendorCredit':
queryClient.invalidateQueries(T.VENDOR_CREDIT);
queryClient.invalidateQueries(T.VENDOR_CREDITS);
break;
case 'PaymentReceive':
queryClient.invalidateQueries(T.PAYMENT_RECEIVE);
queryClient.invalidateQueries(T.PAYMENT_RECEIVES);
break;
case 'BillPayment':
queryClient.invalidateQueries(T.BILLS_PAYMENT_TRANSACTIONS);
break;
case 'Customer':
queryClient.invalidateQueries(T.CUSTOMERS);
queryClient.invalidateQueries(T.CUSTOMER);
break;
case 'Vendor':
queryClient.invalidateQueries(T.VENDOR);
queryClient.invalidateQueries(T.VENDORS);
break;
case 'Expense':
queryClient.invalidateQueries(T.EXPENSE);
queryClient.invalidateQueries(T.EXPENSES);
break;
case 'ManualJournal':
queryClient.invalidateQueries(T.MANUAL_JOURNAL);
queryClient.invalidateQueries(T.MANUAL_JOURNALS);
break;
case 'UncategorizedCashflowTransaction':
queryClient.invalidateQueries(T.CASH_FLOW_TRANSACTIONS);
queryClient.invalidateQueries(T.CASH_FLOW_TRANSACTIONS);
queryClient.invalidateQueries(T.CASHFLOW_ACCOUNT_TRANSACTIONS_INFINITY);
queryClient.invalidateQueries(
T.CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY,
);
queryClient.invalidateQueries(T.CASHFLOW_UNCAATEGORIZED_TRANSACTION);
break;
}
};