mirror of
https://github.com/frappe/books.git
synced 2024-11-14 01:14:03 +00:00
chore: remove data import
This commit is contained in:
parent
3e5f0b6f83
commit
2b8bc8c9a4
@ -1,412 +0,0 @@
|
|||||||
import { Fyo, t } from 'fyo';
|
|
||||||
import { Converter } from 'fyo/core/converter';
|
|
||||||
import { DocValueMap } from 'fyo/core/types';
|
|
||||||
import { Doc } from 'fyo/model/doc';
|
|
||||||
import { isNameAutoSet } from 'fyo/model/naming';
|
|
||||||
import { Noun, Verb } from 'fyo/telemetry/types';
|
|
||||||
import { ModelNameEnum } from 'models/types';
|
|
||||||
import {
|
|
||||||
Field,
|
|
||||||
FieldType,
|
|
||||||
FieldTypeEnum,
|
|
||||||
OptionField,
|
|
||||||
SelectOption,
|
|
||||||
TargetField,
|
|
||||||
} from 'schemas/types';
|
|
||||||
import {
|
|
||||||
getDefaultMapFromList,
|
|
||||||
getMapFromList,
|
|
||||||
getValueMapFromList,
|
|
||||||
} from 'utils';
|
|
||||||
import { generateCSV, parseCSV } from '../utils/csvParser';
|
|
||||||
|
|
||||||
type Status = {
|
|
||||||
success: boolean;
|
|
||||||
message: string;
|
|
||||||
names: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
type Exclusion = Record<string, string[]>;
|
|
||||||
|
|
||||||
type LoadingStatusCallback = (
|
|
||||||
isMakingEntries: boolean,
|
|
||||||
entriesMade: number,
|
|
||||||
totalEntries: number
|
|
||||||
) => void;
|
|
||||||
|
|
||||||
interface TemplateField {
|
|
||||||
label: string;
|
|
||||||
fieldname: string;
|
|
||||||
required: boolean;
|
|
||||||
schemaName: string;
|
|
||||||
options?: SelectOption[];
|
|
||||||
fieldtype: FieldType;
|
|
||||||
parentField: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const exclusion: Exclusion = {
|
|
||||||
Item: ['image'],
|
|
||||||
Party: ['address', 'outstandingAmount', 'image'],
|
|
||||||
};
|
|
||||||
|
|
||||||
function getFilteredDocFields(
|
|
||||||
df: string | string[],
|
|
||||||
fyo: Fyo
|
|
||||||
): [TemplateField[], string[][]] {
|
|
||||||
let schemaName = df[0];
|
|
||||||
let parentField = df[1] ?? '';
|
|
||||||
|
|
||||||
if (typeof df === 'string') {
|
|
||||||
schemaName = df;
|
|
||||||
parentField = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const primaryFields: Field[] = fyo.schemaMap[schemaName]!.fields;
|
|
||||||
const fields: TemplateField[] = [];
|
|
||||||
const tableTypes: string[][] = [];
|
|
||||||
const exclusionFields: string[] = exclusion[schemaName] ?? [];
|
|
||||||
|
|
||||||
for (const field of primaryFields) {
|
|
||||||
const { label, fieldtype, fieldname, required } = field;
|
|
||||||
|
|
||||||
if (shouldSkip(field, exclusionFields, parentField)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fieldtype === FieldTypeEnum.Table) {
|
|
||||||
const { target } = field as TargetField;
|
|
||||||
tableTypes.push([target, fieldname]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: SelectOption[] = (field as OptionField).options ?? [];
|
|
||||||
|
|
||||||
fields.push({
|
|
||||||
label,
|
|
||||||
fieldname,
|
|
||||||
schemaName,
|
|
||||||
options,
|
|
||||||
fieldtype,
|
|
||||||
parentField,
|
|
||||||
required: required ?? false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return [fields, tableTypes];
|
|
||||||
}
|
|
||||||
|
|
||||||
function shouldSkip(
|
|
||||||
field: Field,
|
|
||||||
exclusionFields: string[],
|
|
||||||
parentField: string
|
|
||||||
): boolean {
|
|
||||||
if (field.meta) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (field.fieldname === 'name' && parentField) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (field.required) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exclusionFields.includes(field.fieldname)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (field.hidden || field.readOnly) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTemplateFields(schemaName: string, fyo: Fyo): TemplateField[] {
|
|
||||||
const fields: TemplateField[] = [];
|
|
||||||
if (!schemaName) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const schemaNames: string[][] = [[schemaName]];
|
|
||||||
while (schemaNames.length > 0) {
|
|
||||||
const sn = schemaNames.pop();
|
|
||||||
if (!sn) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [templateFields, tableTypes] = getFilteredDocFields(sn, fyo);
|
|
||||||
fields.push(...templateFields);
|
|
||||||
schemaNames.push(...tableTypes);
|
|
||||||
}
|
|
||||||
return fields;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Importer {
|
|
||||||
schemaName: string;
|
|
||||||
templateFields: TemplateField[];
|
|
||||||
labelTemplateFieldMap: Record<string, TemplateField> = {};
|
|
||||||
template: string;
|
|
||||||
indices: number[] = [];
|
|
||||||
parsedLabels: string[] = [];
|
|
||||||
parsedValues: string[][] = [];
|
|
||||||
assignedMap: Record<string, string> = {}; // target: import
|
|
||||||
requiredMap: Record<string, boolean> = {};
|
|
||||||
shouldSubmit: boolean = false;
|
|
||||||
labelIndex: number = -1;
|
|
||||||
csv: string[][] = [];
|
|
||||||
fyo: Fyo;
|
|
||||||
|
|
||||||
constructor(schemaName: string, fyo: Fyo) {
|
|
||||||
this.schemaName = schemaName;
|
|
||||||
this.fyo = fyo;
|
|
||||||
this.templateFields = getTemplateFields(schemaName, this.fyo);
|
|
||||||
this.template = generateCSV([this.templateFields.map((t) => t.label)]);
|
|
||||||
this.labelTemplateFieldMap = getMapFromList(this.templateFields, 'label');
|
|
||||||
this.assignedMap = getDefaultMapFromList(this.templateFields, '', 'label');
|
|
||||||
this.requiredMap = getValueMapFromList(
|
|
||||||
this.templateFields,
|
|
||||||
'label',
|
|
||||||
'required'
|
|
||||||
) as Record<string, boolean>;
|
|
||||||
}
|
|
||||||
|
|
||||||
get assignableLabels() {
|
|
||||||
const req: string[] = [];
|
|
||||||
const nreq: string[] = [];
|
|
||||||
|
|
||||||
for (const label in this.labelTemplateFieldMap) {
|
|
||||||
if (this.requiredMap[label]) {
|
|
||||||
req.push(label);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
nreq.push(label);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [req, nreq].flat();
|
|
||||||
}
|
|
||||||
|
|
||||||
get unassignedLabels() {
|
|
||||||
const assigned = Object.keys(this.assignedMap).map(
|
|
||||||
(k) => this.assignedMap[k]
|
|
||||||
);
|
|
||||||
return this.parsedLabels.filter((l) => !assigned.includes(l));
|
|
||||||
}
|
|
||||||
|
|
||||||
get columnLabels() {
|
|
||||||
const req: string[] = [];
|
|
||||||
const nreq: string[] = [];
|
|
||||||
|
|
||||||
this.assignableLabels.forEach((k) => {
|
|
||||||
if (!this.assignedMap[k]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.requiredMap[k]) {
|
|
||||||
req.push(k);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
nreq.push(k);
|
|
||||||
});
|
|
||||||
|
|
||||||
return [...req, ...nreq];
|
|
||||||
}
|
|
||||||
|
|
||||||
get assignedMatrix() {
|
|
||||||
this.indices = this.columnLabels
|
|
||||||
.map((k) => this.assignedMap[k])
|
|
||||||
.filter(Boolean)
|
|
||||||
.map((k) => this.parsedLabels.indexOf(k as string));
|
|
||||||
|
|
||||||
const rows = this.parsedValues.length;
|
|
||||||
const cols = this.columnLabels.length;
|
|
||||||
|
|
||||||
const matrix = [];
|
|
||||||
for (let i = 0; i < rows; i++) {
|
|
||||||
const row = [];
|
|
||||||
for (let j = 0; j < cols; j++) {
|
|
||||||
const ix = this.indices[j];
|
|
||||||
const value = this.parsedValues[i][ix] ?? '';
|
|
||||||
row.push(value);
|
|
||||||
}
|
|
||||||
matrix.push(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
return matrix;
|
|
||||||
}
|
|
||||||
|
|
||||||
dropRow(i: number) {
|
|
||||||
this.parsedValues = this.parsedValues.filter((_, ix) => i !== ix);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateValue(value: string, i: number, j: number) {
|
|
||||||
this.parsedValues[i][this.indices[j]] = value ?? '';
|
|
||||||
}
|
|
||||||
|
|
||||||
selectFile(text: string): boolean {
|
|
||||||
this.csv = parseCSV(text);
|
|
||||||
try {
|
|
||||||
this.initialize(0, true);
|
|
||||||
} catch (err) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize(labelIndex: number, force: boolean = false) {
|
|
||||||
if (
|
|
||||||
(typeof labelIndex !== 'number' && !labelIndex) ||
|
|
||||||
(labelIndex === this.labelIndex && !force)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const source = this.csv.map((row) => [...row]);
|
|
||||||
this.labelIndex = labelIndex;
|
|
||||||
this.parsedLabels = source[labelIndex];
|
|
||||||
this.parsedValues = source.slice(labelIndex + 1);
|
|
||||||
this.setAssigned();
|
|
||||||
}
|
|
||||||
|
|
||||||
setAssigned() {
|
|
||||||
const labels = [...this.parsedLabels];
|
|
||||||
|
|
||||||
for (const k of Object.keys(this.assignedMap)) {
|
|
||||||
const l = this.assignedMap[k] as string;
|
|
||||||
if (!labels.includes(l)) {
|
|
||||||
this.assignedMap[k] = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
labels.forEach((l) => {
|
|
||||||
if (this.assignedMap[l] !== '') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.assignedMap[l] = l;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getDocs(): DocValueMap[] {
|
|
||||||
const fields = this.columnLabels.map((k) => this.labelTemplateFieldMap[k]);
|
|
||||||
const nameIndex = fields.findIndex(({ fieldname }) => fieldname === 'name');
|
|
||||||
|
|
||||||
const docMap: Record<string, DocValueMap> = {};
|
|
||||||
|
|
||||||
const assignedMatrix = this.assignedMatrix;
|
|
||||||
for (let r = 0; r < assignedMatrix.length; r++) {
|
|
||||||
const row = assignedMatrix[r];
|
|
||||||
const cts: Record<string, DocValueMap> = {};
|
|
||||||
const name = row[nameIndex];
|
|
||||||
|
|
||||||
docMap[name] ??= {};
|
|
||||||
|
|
||||||
for (let f = 0; f < fields.length; f++) {
|
|
||||||
const field = fields[f];
|
|
||||||
const value = Converter.toDocValue(row[f], field as Field, this.fyo);
|
|
||||||
|
|
||||||
if (field.parentField) {
|
|
||||||
cts[field.parentField] ??= {};
|
|
||||||
cts[field.parentField][field.fieldname] = value;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
docMap[name][field.fieldname] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const k of Object.keys(cts)) {
|
|
||||||
docMap[name][k] ??= [];
|
|
||||||
(docMap[name][k] as DocValueMap[]).push(cts[k]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.keys(docMap).map((k) => docMap[k]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async importData(setLoadingStatus: LoadingStatusCallback): Promise<Status> {
|
|
||||||
const status: Status = { success: false, names: [], message: '' };
|
|
||||||
const shouldDeleteName = isNameAutoSet(this.schemaName, this.fyo);
|
|
||||||
const docObjs = this.getDocs();
|
|
||||||
|
|
||||||
let entriesMade = 0;
|
|
||||||
setLoadingStatus(true, 0, docObjs.length);
|
|
||||||
|
|
||||||
for (const docObj of docObjs) {
|
|
||||||
if (shouldDeleteName) {
|
|
||||||
delete docObj.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const key in docObj) {
|
|
||||||
if (docObj[key] !== '') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
delete docObj[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
const doc: Doc = this.fyo.doc.getNewDoc(this.schemaName, {}, false);
|
|
||||||
try {
|
|
||||||
await this.makeEntry(doc, docObj);
|
|
||||||
entriesMade += 1;
|
|
||||||
setLoadingStatus(true, entriesMade, docObjs.length);
|
|
||||||
} catch (err) {
|
|
||||||
setLoadingStatus(false, entriesMade, docObjs.length);
|
|
||||||
|
|
||||||
this.fyo.telemetry.log(Verb.Imported, this.schemaName as Noun, {
|
|
||||||
success: false,
|
|
||||||
count: entriesMade,
|
|
||||||
});
|
|
||||||
|
|
||||||
return this.handleError(doc, err as Error, status);
|
|
||||||
}
|
|
||||||
|
|
||||||
status.names.push(doc.name!);
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoadingStatus(false, entriesMade, docObjs.length);
|
|
||||||
status.success = true;
|
|
||||||
|
|
||||||
this.fyo.telemetry.log(Verb.Imported, this.schemaName as Noun, {
|
|
||||||
success: true,
|
|
||||||
count: entriesMade,
|
|
||||||
});
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
addRow() {
|
|
||||||
const emptyRow = Array(this.columnLabels.length).fill('');
|
|
||||||
this.parsedValues.push(emptyRow);
|
|
||||||
}
|
|
||||||
|
|
||||||
async makeEntry(doc: Doc, docObj: DocValueMap) {
|
|
||||||
await doc.setAndSync(docObj);
|
|
||||||
if (this.shouldSubmit) {
|
|
||||||
await doc.submit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleError(doc: Doc, err: Error, status: Status): Status {
|
|
||||||
const messages = [t`Could not import ${this.schemaName} ${doc.name!}.`];
|
|
||||||
|
|
||||||
const message = err.message;
|
|
||||||
if (message?.includes('UNIQUE constraint failed')) {
|
|
||||||
messages.push(t`${doc.name!} already exists.`);
|
|
||||||
} else if (message) {
|
|
||||||
messages.push(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status.names.length) {
|
|
||||||
messages.push(
|
|
||||||
t`The following ${
|
|
||||||
status.names.length
|
|
||||||
} entries were created: ${status.names.join(', ')}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
status.message = messages.join(' ');
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,705 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex flex-col overflow-hidden w-full">
|
|
||||||
<!-- Header -->
|
|
||||||
<PageHeader :title="t`Data Import`">
|
|
||||||
<DropdownWithActions
|
|
||||||
:actions="actions"
|
|
||||||
v-if="(canCancel || importType) && !complete"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
v-if="(canCancel || importType) && !complete && hasImporter"
|
|
||||||
class="text-sm"
|
|
||||||
@click="saveTemplate"
|
|
||||||
>{{ t`Save Template` }}</Button
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
v-if="importType && !complete"
|
|
||||||
type="primary"
|
|
||||||
class="text-sm"
|
|
||||||
@click="handlePrimaryClick"
|
|
||||||
>{{ primaryLabel }}</Button
|
|
||||||
>
|
|
||||||
</PageHeader>
|
|
||||||
|
|
||||||
<div class="flex text-base w-full flex-col" v-if="!complete">
|
|
||||||
<!-- Type selector -->
|
|
||||||
<div
|
|
||||||
class="
|
|
||||||
flex flex-row
|
|
||||||
justify-start
|
|
||||||
items-center
|
|
||||||
w-full
|
|
||||||
gap-2
|
|
||||||
border-b
|
|
||||||
p-4
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<FormControl
|
|
||||||
:df="importableDf"
|
|
||||||
input-class="bg-transparent text-gray-900 text-base"
|
|
||||||
class="w-40 bg-gray-100 rounded"
|
|
||||||
:value="importType"
|
|
||||||
size="small"
|
|
||||||
@change="setImportType"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<p
|
|
||||||
class="text-base ms-2"
|
|
||||||
:class="fileName ? 'text-gray-900 font-semibold' : 'text-gray-700'"
|
|
||||||
>
|
|
||||||
<span v-if="fileName" class="font-normal"
|
|
||||||
>{{ t`Selected file` }}
|
|
||||||
</span>
|
|
||||||
{{ helperText }}{{ fileName ? ',' : '' }}
|
|
||||||
<span v-if="fileName" class="font-normal">
|
|
||||||
{{ t`verify the imported data and click on` }} </span
|
|
||||||
>{{ ' ' }}<span v-if="fileName">{{ t`Import Data` }}</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Settings -->
|
|
||||||
<div v-if="fileName && hasImporter" class="border-b p-4">
|
|
||||||
<h2 class="text-lg font-semibold">{{ t`Importer Settings` }}</h2>
|
|
||||||
<div class="mt-2 flex gap-2">
|
|
||||||
<div
|
|
||||||
v-if="file && isSubmittable"
|
|
||||||
class="
|
|
||||||
gap-2
|
|
||||||
flex
|
|
||||||
justify-between
|
|
||||||
items-center
|
|
||||||
bg-gray-100
|
|
||||||
px-2
|
|
||||||
rounded
|
|
||||||
text-gray-900
|
|
||||||
w-40
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<p>{{ t`Submit on Import` }}</p>
|
|
||||||
<FormControl
|
|
||||||
size="small"
|
|
||||||
input-class="bg-gray-100"
|
|
||||||
:df="{
|
|
||||||
fieldname: 'shouldSubmit',
|
|
||||||
fieldtype: 'Check',
|
|
||||||
}"
|
|
||||||
:value="Number(importer.shouldSubmit)"
|
|
||||||
@change="(value) => (importer.shouldSubmit = !!value)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="
|
|
||||||
flex flex-row
|
|
||||||
justify-center
|
|
||||||
items-center
|
|
||||||
gap-2
|
|
||||||
bg-gray-100
|
|
||||||
ps-2
|
|
||||||
rounded
|
|
||||||
text-gray-900
|
|
||||||
w-40
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<p class="text-gray-900">{{ t`Label Index` }}</p>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
class="
|
|
||||||
bg-gray-100
|
|
||||||
outline-none
|
|
||||||
focus:bg-gray-200
|
|
||||||
px-2
|
|
||||||
py-1
|
|
||||||
rounded-md
|
|
||||||
w-10
|
|
||||||
text-end
|
|
||||||
"
|
|
||||||
min="1"
|
|
||||||
:max="importer.csv.length - 1"
|
|
||||||
:value="labelIndex + 1"
|
|
||||||
@change="setLabelIndex"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
class="w-28 bg-gray-100 focus:bg-gray-200 rounded-md"
|
|
||||||
v-if="canReset"
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
importer.initialize(0, true);
|
|
||||||
canReset = false;
|
|
||||||
}
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<span class="text-gray-900">
|
|
||||||
{{ t`Reset` }}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Label Assigner -->
|
|
||||||
<div v-if="fileName && hasImporter" class="p-4 border-b">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<h2 class="text-lg font-semibold">{{ t`Assign Imported Labels` }}</h2>
|
|
||||||
<p class="text-red-400 text-sm" v-if="isRequiredUnassigned">
|
|
||||||
{{ t`* required fields` }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="gap-2 mt-4 grid grid-flow-col overflow-x-auto no-scrollbar">
|
|
||||||
<div
|
|
||||||
v-for="(f, k) in importer.assignableLabels"
|
|
||||||
:key="'assigner-' + f + '-' + k"
|
|
||||||
>
|
|
||||||
<p class="text-gray-600 text-sm mb-1">
|
|
||||||
{{ f }}
|
|
||||||
<span
|
|
||||||
v-if="importer.requiredMap[f] && !importer.assignedMap[f]"
|
|
||||||
class="text-red-400"
|
|
||||||
>*</span
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
<FormControl
|
|
||||||
size="small"
|
|
||||||
class="w-28"
|
|
||||||
input-class="bg-gray-100"
|
|
||||||
:df="getAssignerField(f)"
|
|
||||||
:value="importer.assignedMap[f] ?? ''"
|
|
||||||
@change="(v) => onAssignedChange(f, v)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Data Verifier -->
|
|
||||||
<div v-if="fileName && hasImporter">
|
|
||||||
<div class="overflow-auto border-b">
|
|
||||||
<!-- Column Name Rows -->
|
|
||||||
<div
|
|
||||||
class="
|
|
||||||
grid grid-flow-col
|
|
||||||
border-b
|
|
||||||
gap-2
|
|
||||||
sticky
|
|
||||||
top-0
|
|
||||||
bg-white
|
|
||||||
px-4
|
|
||||||
h-row-mid
|
|
||||||
items-center
|
|
||||||
"
|
|
||||||
style="width: fit-content"
|
|
||||||
v-if="importer.columnLabels.length > 0"
|
|
||||||
>
|
|
||||||
<div class="w-4 h-4" />
|
|
||||||
<p
|
|
||||||
v-for="(c, i) in importer.columnLabels"
|
|
||||||
class="px-2 w-28 font-semibold text-gray-600"
|
|
||||||
:key="'column-' + i"
|
|
||||||
>
|
|
||||||
{{ c }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<p class="text-gray-600">
|
|
||||||
{{ t`No labels have been assigned.` }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Data Rows -->
|
|
||||||
<div
|
|
||||||
v-if="importer.columnLabels.length > 0"
|
|
||||||
style="max-height: 500px"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="
|
|
||||||
grid grid-flow-col
|
|
||||||
border-b
|
|
||||||
gap-2
|
|
||||||
items-center
|
|
||||||
px-4
|
|
||||||
h-row-mid
|
|
||||||
"
|
|
||||||
style="width: fit-content"
|
|
||||||
v-for="(r, i) in assignedMatrix"
|
|
||||||
:key="'matrix-row-' + i"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="
|
|
||||||
w-4
|
|
||||||
h-4
|
|
||||||
text-gray-600
|
|
||||||
hover:text-gray-900
|
|
||||||
cursor-pointer
|
|
||||||
outline-none
|
|
||||||
"
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
importer.dropRow(i);
|
|
||||||
canReset = true;
|
|
||||||
}
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<FeatherIcon name="x" />
|
|
||||||
</button>
|
|
||||||
<input
|
|
||||||
v-for="(c, j) in r"
|
|
||||||
type="text"
|
|
||||||
class="
|
|
||||||
w-28
|
|
||||||
text-gray-900
|
|
||||||
px-2
|
|
||||||
py-1
|
|
||||||
outline-none
|
|
||||||
rounded
|
|
||||||
focus:bg-gray-200
|
|
||||||
"
|
|
||||||
@change="
|
|
||||||
(e) => {
|
|
||||||
onValueChange(e, i, j);
|
|
||||||
canReset = true;
|
|
||||||
}
|
|
||||||
"
|
|
||||||
:key="'matrix-cell-' + i + '-' + j"
|
|
||||||
:value="c"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Add Row button -->
|
|
||||||
<button
|
|
||||||
class="
|
|
||||||
text-gray-600
|
|
||||||
hover:bg-gray-50
|
|
||||||
flex flex-row
|
|
||||||
w-full
|
|
||||||
px-4
|
|
||||||
h-row-mid
|
|
||||||
border-b
|
|
||||||
items-center
|
|
||||||
outline-none
|
|
||||||
"
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
importer.addRow();
|
|
||||||
canReset = true;
|
|
||||||
}
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<FeatherIcon name="plus" class="w-4 h-4" />
|
|
||||||
<p class="ps-4">
|
|
||||||
{{ t`Add Row` }}
|
|
||||||
</p>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Post Complete Success -->
|
|
||||||
<div v-if="complete" class="flex justify-center h-full items-center">
|
|
||||||
<div
|
|
||||||
class="
|
|
||||||
flex flex-col
|
|
||||||
justify-center
|
|
||||||
items-center
|
|
||||||
gap-8
|
|
||||||
rounded-lg
|
|
||||||
shadow-md
|
|
||||||
p-6
|
|
||||||
"
|
|
||||||
style="width: 450px"
|
|
||||||
>
|
|
||||||
<h2 class="text-xl font-semibold mt-4">{{ t`Import Success` }} 🎉</h2>
|
|
||||||
<p class="text-lg text-center">
|
|
||||||
{{ t`Successfully created the following ${names.length} entries:` }}
|
|
||||||
</p>
|
|
||||||
<div class="max-h-96 overflow-y-auto">
|
|
||||||
<div
|
|
||||||
v-for="(n, i) in names"
|
|
||||||
:key="'name-' + i"
|
|
||||||
class="grid grid-cols-2 gap-2 border-b pb-2 mb-2 pe-4 text-lg w-60"
|
|
||||||
style="grid-template-columns: 2rem auto"
|
|
||||||
>
|
|
||||||
<p class="text-end">{{ i + 1 }}.</p>
|
|
||||||
<p>
|
|
||||||
{{ n }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex w-full justify-between">
|
|
||||||
<Button type="secondary" class="text-sm w-32" @click="clear">{{
|
|
||||||
t`Import More`
|
|
||||||
}}</Button>
|
|
||||||
<Button type="primary" class="text-sm w-32" @click="showMe">{{
|
|
||||||
t`Show Me`
|
|
||||||
}}</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="!importType"
|
|
||||||
class="flex justify-center h-full w-full items-center mb-16"
|
|
||||||
>
|
|
||||||
<HowTo
|
|
||||||
link="https://youtu.be/ukHAgcnVxTQ"
|
|
||||||
class="text-gray-900 rounded-lg text-base border px-3 py-2"
|
|
||||||
>
|
|
||||||
{{ t`How to Use Data Import` }}
|
|
||||||
</HowTo>
|
|
||||||
</div>
|
|
||||||
<Loading
|
|
||||||
v-if="isMakingEntries"
|
|
||||||
:open="isMakingEntries"
|
|
||||||
:percent="percentLoading"
|
|
||||||
:message="messageLoading"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script lang="ts">
|
|
||||||
import { Action as BaseAction } from 'fyo/model/types';
|
|
||||||
import { ValidationError } from 'fyo/utils/errors';
|
|
||||||
import { ModelNameEnum } from 'models/types';
|
|
||||||
import { FieldTypeEnum, OptionField } from 'schemas/types';
|
|
||||||
import Button from 'src/components/Button.vue';
|
|
||||||
import FormControl from 'src/components/Controls/FormControl.vue';
|
|
||||||
import DropdownWithActions from 'src/components/DropdownWithActions.vue';
|
|
||||||
import FeatherIcon from 'src/components/FeatherIcon.vue';
|
|
||||||
import HowTo from 'src/components/HowTo.vue';
|
|
||||||
import PageHeader from 'src/components/PageHeader.vue';
|
|
||||||
import { Importer } from 'src/dataImport';
|
|
||||||
import { fyo } from 'src/initFyo';
|
|
||||||
import { getSavePath, saveData, selectFile } from 'src/utils/ipcCalls';
|
|
||||||
import { docsPathMap } from 'src/utils/misc';
|
|
||||||
import { docsPathRef } from 'src/utils/refs';
|
|
||||||
import { showMessageDialog } from 'src/utils/ui';
|
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
import Loading from '../components/Loading.vue';
|
|
||||||
|
|
||||||
type Action = Pick<BaseAction, 'condition' | 'component'> & {
|
|
||||||
action: Function;
|
|
||||||
};
|
|
||||||
|
|
||||||
type DataImportData = {
|
|
||||||
canReset: boolean;
|
|
||||||
complete: boolean;
|
|
||||||
names: string[];
|
|
||||||
file: null | { name: string; filePath: string; text: string };
|
|
||||||
nullOrImporter: null | Importer;
|
|
||||||
importType: string;
|
|
||||||
isMakingEntries: boolean;
|
|
||||||
percentLoading: number;
|
|
||||||
messageLoading: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: {
|
|
||||||
PageHeader,
|
|
||||||
FormControl,
|
|
||||||
Button,
|
|
||||||
DropdownWithActions,
|
|
||||||
FeatherIcon,
|
|
||||||
HowTo,
|
|
||||||
Loading,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
canReset: false,
|
|
||||||
complete: false,
|
|
||||||
names: ['Bat', 'Baseball', 'Other Shit'],
|
|
||||||
file: null,
|
|
||||||
nullOrImporter: null,
|
|
||||||
importType: '',
|
|
||||||
isMakingEntries: false,
|
|
||||||
percentLoading: 0,
|
|
||||||
messageLoading: '',
|
|
||||||
} as DataImportData;
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
if (fyo.store.isDevelopment) {
|
|
||||||
// @ts-ignore
|
|
||||||
window.di = this;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
hasImporter(): boolean {
|
|
||||||
return !!this.nullOrImporter;
|
|
||||||
},
|
|
||||||
importer(): Importer {
|
|
||||||
if (!this.nullOrImporter) {
|
|
||||||
throw new ValidationError(this.t`Importer not set, reload tool`, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.nullOrImporter as Importer;
|
|
||||||
},
|
|
||||||
importables(): ModelNameEnum[] {
|
|
||||||
const importables = [
|
|
||||||
ModelNameEnum.SalesInvoice,
|
|
||||||
ModelNameEnum.PurchaseInvoice,
|
|
||||||
ModelNameEnum.Payment,
|
|
||||||
ModelNameEnum.Party,
|
|
||||||
ModelNameEnum.Item,
|
|
||||||
ModelNameEnum.JournalEntry,
|
|
||||||
];
|
|
||||||
|
|
||||||
const hasInventory = fyo.doc.singles.AccountingSettings?.enableInventory;
|
|
||||||
if (hasInventory) {
|
|
||||||
importables.push(
|
|
||||||
ModelNameEnum.StockMovement,
|
|
||||||
ModelNameEnum.Shipment,
|
|
||||||
ModelNameEnum.PurchaseReceipt
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return importables;
|
|
||||||
},
|
|
||||||
labelIndex(): number {
|
|
||||||
return this.importer.labelIndex ?? 0;
|
|
||||||
},
|
|
||||||
requiredUnassigned(): string[] {
|
|
||||||
return this.importer.assignableLabels.filter(
|
|
||||||
(k) => this.importer.requiredMap[k] && !this.importer.assignedMap[k]
|
|
||||||
);
|
|
||||||
},
|
|
||||||
isRequiredUnassigned(): boolean {
|
|
||||||
return this.requiredUnassigned.length > 0;
|
|
||||||
},
|
|
||||||
assignedMatrix(): string[][] {
|
|
||||||
return this.nullOrImporter?.assignedMatrix ?? [];
|
|
||||||
},
|
|
||||||
actions(): Action[] {
|
|
||||||
const actions: Action[] = [];
|
|
||||||
|
|
||||||
if (this.file) {
|
|
||||||
actions.push({
|
|
||||||
component: {
|
|
||||||
template: '<span>{{ t`Change File` }}</span>',
|
|
||||||
},
|
|
||||||
condition: () => true,
|
|
||||||
action: this.selectFile,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const cancelAction = {
|
|
||||||
component: {
|
|
||||||
template: '<span class="text-red-700" >{{ t`Cancel` }}</span>',
|
|
||||||
},
|
|
||||||
condition: () => true,
|
|
||||||
action: this.clear,
|
|
||||||
};
|
|
||||||
actions.push(cancelAction);
|
|
||||||
|
|
||||||
return actions;
|
|
||||||
},
|
|
||||||
|
|
||||||
fileName(): string {
|
|
||||||
if (!this.file) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return this.file.name;
|
|
||||||
},
|
|
||||||
helperText(): string {
|
|
||||||
if (!this.importType) {
|
|
||||||
return this.t`Set an Import Type`;
|
|
||||||
} else if (!this.fileName) {
|
|
||||||
return this.t`Select a file for import`;
|
|
||||||
}
|
|
||||||
return this.fileName;
|
|
||||||
},
|
|
||||||
primaryLabel(): string {
|
|
||||||
return this.file ? this.t`Import Data` : this.t`Select File`;
|
|
||||||
},
|
|
||||||
isSubmittable(): boolean {
|
|
||||||
const schemaName = this.importer.schemaName;
|
|
||||||
return fyo.schemaMap[schemaName]?.isSubmittable ?? false;
|
|
||||||
},
|
|
||||||
importableDf(): OptionField {
|
|
||||||
return {
|
|
||||||
fieldname: 'importType',
|
|
||||||
label: this.t`Import Type`,
|
|
||||||
fieldtype: FieldTypeEnum.AutoComplete,
|
|
||||||
placeholder: this.t`Import Type`,
|
|
||||||
options: Object.keys(this.labelSchemaNameMap).map((k) => ({
|
|
||||||
value: k,
|
|
||||||
label: k,
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
labelSchemaNameMap(): Record<string, string> {
|
|
||||||
return this.importables
|
|
||||||
.map((i) => ({
|
|
||||||
name: i,
|
|
||||||
label: fyo.schemaMap[i]?.label ?? i,
|
|
||||||
}))
|
|
||||||
.reduce((acc, { name, label }) => {
|
|
||||||
acc[label] = name;
|
|
||||||
return acc;
|
|
||||||
}, {} as Record<string, string>);
|
|
||||||
},
|
|
||||||
canCancel(): boolean {
|
|
||||||
return !!(this.file || this.importType);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
activated(): void {
|
|
||||||
docsPathRef.value = docsPathMap.DataImport ?? '';
|
|
||||||
},
|
|
||||||
deactivated(): void {
|
|
||||||
docsPathRef.value = '';
|
|
||||||
if (!this.complete) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.clear();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
showMe(): void {
|
|
||||||
const schemaName = this.importer.schemaName;
|
|
||||||
this.clear();
|
|
||||||
this.$router.push(`/list/${schemaName}`);
|
|
||||||
},
|
|
||||||
clear(): void {
|
|
||||||
this.file = null;
|
|
||||||
this.names = [];
|
|
||||||
this.nullOrImporter = null;
|
|
||||||
this.importType = '';
|
|
||||||
this.complete = false;
|
|
||||||
this.canReset = false;
|
|
||||||
this.isMakingEntries = false;
|
|
||||||
this.percentLoading = 0;
|
|
||||||
this.messageLoading = '';
|
|
||||||
},
|
|
||||||
async handlePrimaryClick(): Promise<void> {
|
|
||||||
if (!this.file) {
|
|
||||||
await this.selectFile();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.importData();
|
|
||||||
},
|
|
||||||
setLabelIndex(e: Event): void {
|
|
||||||
const target = e.target as HTMLInputElement;
|
|
||||||
const labelIndex = Number(target?.value ?? '1') - 1;
|
|
||||||
this.nullOrImporter?.initialize(labelIndex);
|
|
||||||
},
|
|
||||||
async saveTemplate(): Promise<void> {
|
|
||||||
const template = this.importer.template;
|
|
||||||
const templateName = this.importType + ' ' + this.t`Template`;
|
|
||||||
const { canceled, filePath } = await getSavePath(templateName, 'csv');
|
|
||||||
|
|
||||||
if (canceled || !filePath) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await saveData(template, filePath);
|
|
||||||
},
|
|
||||||
getAssignerField(targetLabel: string): OptionField {
|
|
||||||
const assigned = this.importer.assignedMap[targetLabel];
|
|
||||||
return {
|
|
||||||
fieldname: 'assignerField',
|
|
||||||
label: targetLabel,
|
|
||||||
placeholder: `Select Label`,
|
|
||||||
fieldtype: FieldTypeEnum.Select,
|
|
||||||
options: [
|
|
||||||
'',
|
|
||||||
...(assigned ? [assigned] : []),
|
|
||||||
...this.importer.unassignedLabels,
|
|
||||||
].map((i) => ({ value: i, label: i })),
|
|
||||||
default: assigned ?? '',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
onAssignedChange(target: string, value: string): void {
|
|
||||||
this.importer.assignedMap[target] = value;
|
|
||||||
},
|
|
||||||
onValueChange(event: Event, i: number, j: number): void {
|
|
||||||
this.importer.updateValue((event.target as HTMLInputElement).value, i, j);
|
|
||||||
},
|
|
||||||
async importData(): Promise<unknown> {
|
|
||||||
if (this.isMakingEntries || this.complete) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isRequiredUnassigned) {
|
|
||||||
return await showMessageDialog({
|
|
||||||
message: this.t`Required Fields not Assigned`,
|
|
||||||
detail: this
|
|
||||||
.t`Please assign the following fields ${this.requiredUnassigned.join(
|
|
||||||
', '
|
|
||||||
)}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.importer.assignedMatrix.length === 0) {
|
|
||||||
return await showMessageDialog({
|
|
||||||
message: this.t`No Data to Import`,
|
|
||||||
detail: this.t`Please select a file with data to import.`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const { success, names, message } = await this.importer.importData(
|
|
||||||
this.setLoadingStatus
|
|
||||||
);
|
|
||||||
if (!success) {
|
|
||||||
return await showMessageDialog({
|
|
||||||
message: this.t`Import Failed`,
|
|
||||||
detail: message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.names = names;
|
|
||||||
this.complete = true;
|
|
||||||
},
|
|
||||||
setImportType(importType: string): void {
|
|
||||||
if (this.importType) {
|
|
||||||
this.clear();
|
|
||||||
}
|
|
||||||
this.importType = importType;
|
|
||||||
this.nullOrImporter = new Importer(
|
|
||||||
this.labelSchemaNameMap[this.importType],
|
|
||||||
fyo
|
|
||||||
);
|
|
||||||
},
|
|
||||||
setLoadingStatus(
|
|
||||||
isMakingEntries: boolean,
|
|
||||||
entriesMade: number,
|
|
||||||
totalEntries: number
|
|
||||||
): void {
|
|
||||||
this.isMakingEntries = isMakingEntries;
|
|
||||||
this.percentLoading = entriesMade / totalEntries;
|
|
||||||
this.messageLoading = isMakingEntries
|
|
||||||
? `${entriesMade} entries made out of ${totalEntries}...`
|
|
||||||
: '';
|
|
||||||
},
|
|
||||||
async selectFile(): Promise<unknown> {
|
|
||||||
const options = {
|
|
||||||
title: this.t`Select File`,
|
|
||||||
filters: [{ name: 'CSV', extensions: ['csv'] }],
|
|
||||||
};
|
|
||||||
|
|
||||||
const { success, canceled, filePath, data, name } = await selectFile(
|
|
||||||
options
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!success && !canceled) {
|
|
||||||
return await showMessageDialog({
|
|
||||||
message: this.t`File selection failed.`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!success || canceled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const text = new TextDecoder().decode(data);
|
|
||||||
const isValid = this.importer.selectFile(text);
|
|
||||||
if (!isValid) {
|
|
||||||
return await showMessageDialog({
|
|
||||||
message: this.t`Bad import data.`,
|
|
||||||
detail: this.t`Could not select file.`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.file = {
|
|
||||||
name,
|
|
||||||
filePath,
|
|
||||||
text,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
@ -362,7 +362,7 @@ type Action = Pick<BaseAction, 'condition' | 'component'> & {
|
|||||||
action: Function;
|
action: Function;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DataImportData = {
|
type ImportWizardData = {
|
||||||
showColumnPicker: boolean;
|
showColumnPicker: boolean;
|
||||||
canReset: boolean;
|
canReset: boolean;
|
||||||
complete: boolean;
|
complete: boolean;
|
||||||
@ -403,7 +403,7 @@ export default defineComponent({
|
|||||||
isMakingEntries: false,
|
isMakingEntries: false,
|
||||||
percentLoading: 0,
|
percentLoading: 0,
|
||||||
messageLoading: '',
|
messageLoading: '',
|
||||||
} as DataImportData;
|
} as ImportWizardData;
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (fyo.store.isDevelopment) {
|
if (fyo.store.isDevelopment) {
|
||||||
@ -621,7 +621,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
activated(): void {
|
activated(): void {
|
||||||
docsPathRef.value = docsPathMap.DataImport ?? '';
|
docsPathRef.value = docsPathMap.ImportWizard ?? '';
|
||||||
},
|
},
|
||||||
deactivated(): void {
|
deactivated(): void {
|
||||||
docsPathRef.value = '';
|
docsPathRef.value = '';
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { ModelNameEnum } from 'models/types';
|
import { ModelNameEnum } from 'models/types';
|
||||||
import ChartOfAccounts from 'src/pages/ChartOfAccounts.vue';
|
import ChartOfAccounts from 'src/pages/ChartOfAccounts.vue';
|
||||||
import Dashboard from 'src/pages/Dashboard/Dashboard.vue';
|
import Dashboard from 'src/pages/Dashboard/Dashboard.vue';
|
||||||
import DataImport from 'src/pages/DataImport.vue';
|
|
||||||
import ImportWizard from 'src/pages/ImportWizard.vue';
|
import ImportWizard from 'src/pages/ImportWizard.vue';
|
||||||
import GeneralForm from 'src/pages/GeneralForm.vue';
|
import GeneralForm from 'src/pages/GeneralForm.vue';
|
||||||
import GetStarted from 'src/pages/GetStarted.vue';
|
import GetStarted from 'src/pages/GetStarted.vue';
|
||||||
@ -138,11 +137,6 @@ const routes: RouteRecordRaw[] = [
|
|||||||
edit: (route) => route.query,
|
edit: (route) => route.query,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/data-import',
|
|
||||||
name: 'Data Import',
|
|
||||||
component: DataImport,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/import-wizard',
|
path: '/import-wizard',
|
||||||
name: 'Import Wizard',
|
name: 'Import Wizard',
|
||||||
|
@ -118,7 +118,7 @@ export const docsPathMap: Record<string, string | undefined> = {
|
|||||||
// Miscellaneous
|
// Miscellaneous
|
||||||
Search: 'miscellaneous/search',
|
Search: 'miscellaneous/search',
|
||||||
NumberSeries: 'miscellaneous/number-series',
|
NumberSeries: 'miscellaneous/number-series',
|
||||||
DataImport: 'miscellaneous/data-import',
|
ImportWizard: 'miscellaneous/import-wizard',
|
||||||
Settings: 'miscellaneous/settings',
|
Settings: 'miscellaneous/settings',
|
||||||
ChartOfAccounts: 'miscellaneous/chart-of-accounts',
|
ChartOfAccounts: 'miscellaneous/chart-of-accounts',
|
||||||
};
|
};
|
||||||
|
@ -323,8 +323,8 @@ function getSetupList(): SearchItem[] {
|
|||||||
group: 'Page',
|
group: 'Page',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t`Data Import`,
|
label: t`Import Wizard`,
|
||||||
route: '/data-import',
|
route: '/import-wizard',
|
||||||
group: 'Page',
|
group: 'Page',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -267,11 +267,6 @@ async function getCompleteSidebar(): Promise<SidebarConfig> {
|
|||||||
route: '/list/Tax',
|
route: '/list/Tax',
|
||||||
schemaName: 'Tax',
|
schemaName: 'Tax',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: t`Data Import`,
|
|
||||||
name: 'data-import',
|
|
||||||
route: '/data-import',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: t`Import Wizard`,
|
label: t`Import Wizard`,
|
||||||
name: 'import-wizard',
|
name: 'import-wizard',
|
||||||
|
Loading…
Reference in New Issue
Block a user