mirror of
https://github.com/frappe/books.git
synced 2024-12-23 11:29:03 +00:00
incr: convertion of valueMatrix to docs
This commit is contained in:
parent
0194b6c26d
commit
b08c128c45
128
src/importer.ts
128
src/importer.ts
@ -1,6 +1,7 @@
|
|||||||
import { Fyo } from 'fyo';
|
import { Fyo } from 'fyo';
|
||||||
import { Converter } from 'fyo/core/converter';
|
import { Converter } from 'fyo/core/converter';
|
||||||
import { DocValue } from 'fyo/core/types';
|
import { DocValue, DocValueMap } from 'fyo/core/types';
|
||||||
|
import { Doc } from 'fyo/model/doc';
|
||||||
import { getEmptyValuesByFieldTypes } from 'fyo/utils';
|
import { getEmptyValuesByFieldTypes } from 'fyo/utils';
|
||||||
import { ValidationError } from 'fyo/utils/errors';
|
import { ValidationError } from 'fyo/utils/errors';
|
||||||
import {
|
import {
|
||||||
@ -75,6 +76,12 @@ export class Importer {
|
|||||||
*/
|
*/
|
||||||
valueMatrix: ValueMatrix;
|
valueMatrix: ValueMatrix;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data from the valueMatrix rows will be converted into Docs
|
||||||
|
* which will be stored in this array.
|
||||||
|
*/
|
||||||
|
docs: Doc[];
|
||||||
|
|
||||||
constructor(schemaName: string, fyo: Fyo) {
|
constructor(schemaName: string, fyo: Fyo) {
|
||||||
if (!fyo.schemaMap[schemaName]) {
|
if (!fyo.schemaMap[schemaName]) {
|
||||||
throw new ValidationError(
|
throw new ValidationError(
|
||||||
@ -85,6 +92,7 @@ export class Importer {
|
|||||||
this.hasChildTables = false;
|
this.hasChildTables = false;
|
||||||
this.schemaName = schemaName;
|
this.schemaName = schemaName;
|
||||||
this.fyo = fyo;
|
this.fyo = fyo;
|
||||||
|
this.docs = [];
|
||||||
this.valueMatrix = [];
|
this.valueMatrix = [];
|
||||||
|
|
||||||
const templateFields = getTemplateFields(schemaName, fyo, this);
|
const templateFields = getTemplateFields(schemaName, fyo, this);
|
||||||
@ -98,6 +106,124 @@ export class Importer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pushFromValueMatrixToDocs() {
|
||||||
|
const { dataMap, childTableMap } =
|
||||||
|
this.getDataAndChildTableMapFromValueMatrix();
|
||||||
|
|
||||||
|
const schema = this.fyo.db.schemaMap[this.schemaName];
|
||||||
|
const targetFieldnameMap = schema?.fields
|
||||||
|
.filter((f) => f.fieldtype === FieldTypeEnum.Table)
|
||||||
|
.reduce((acc, f) => {
|
||||||
|
const { target, fieldname } = f as TargetField;
|
||||||
|
acc[target] = fieldname;
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, string>);
|
||||||
|
|
||||||
|
for (const [name, data] of dataMap.entries()) {
|
||||||
|
const doc = this.fyo.doc.getNewDoc(this.schemaName, data, false);
|
||||||
|
for (const schemaName in targetFieldnameMap) {
|
||||||
|
const fieldname = targetFieldnameMap[schemaName];
|
||||||
|
const childTable = childTableMap[name][schemaName];
|
||||||
|
if (!childTable) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const childData of childTable.values()) {
|
||||||
|
doc.push(fieldname, childData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.docs.push(doc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getDataAndChildTableMapFromValueMatrix() {
|
||||||
|
/**
|
||||||
|
* Record key is the doc.name value
|
||||||
|
*/
|
||||||
|
const dataMap: Map<string, DocValueMap> = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Record key is doc.name, childSchemaName, childDoc.name
|
||||||
|
*/
|
||||||
|
const childTableMap: Record<
|
||||||
|
string,
|
||||||
|
Record<string, Map<string, DocValueMap>>
|
||||||
|
> = {};
|
||||||
|
|
||||||
|
const nameIndices = this.assignedTemplateFields
|
||||||
|
.map((key, index) => ({ key, index }))
|
||||||
|
.filter((f) => f.key?.endsWith('.name'))
|
||||||
|
.reduce((acc, f) => {
|
||||||
|
if (f.key == null) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
const schemaName = f.key.split('.')[0];
|
||||||
|
acc[schemaName] = f.index;
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, number>);
|
||||||
|
|
||||||
|
const nameIndex = nameIndices?.[this.schemaName];
|
||||||
|
if (nameIndex < 0) {
|
||||||
|
return { dataMap, childTableMap };
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const i in this.valueMatrix) {
|
||||||
|
const row = this.valueMatrix[i];
|
||||||
|
const name = row[nameIndex].value;
|
||||||
|
if (typeof name !== 'string') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const j in row) {
|
||||||
|
const key = this.assignedTemplateFields[j];
|
||||||
|
const tf = this.templateFieldsMap.get(key ?? '');
|
||||||
|
if (!tf || !key) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isChild = this.fyo.schemaMap[tf.schemaName]?.isChild;
|
||||||
|
const vmi = row[j];
|
||||||
|
if (vmi.value == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isChild && !dataMap.has(name)) {
|
||||||
|
dataMap.set(name, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isChild) {
|
||||||
|
dataMap.get(name)![tf.fieldname] = vmi.value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const childNameIndex = nameIndices[tf.schemaName];
|
||||||
|
let childName = row[childNameIndex].value;
|
||||||
|
if (typeof childName !== 'string') {
|
||||||
|
childName = `${tf.schemaName}-${i}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
childTableMap[name] ??= {};
|
||||||
|
childTableMap[name][tf.schemaName] ??= new Map();
|
||||||
|
|
||||||
|
const childMap = childTableMap[name][tf.schemaName];
|
||||||
|
if (!childMap.has(childName)) {
|
||||||
|
childMap.set(childName, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
const childDocValueMap = childMap.get(childName);
|
||||||
|
if (!childDocValueMap) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
childDocValueMap[tf.fieldname] = vmi.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { dataMap, childTableMap };
|
||||||
|
}
|
||||||
|
|
||||||
selectFile(data: string): boolean {
|
selectFile(data: string): boolean {
|
||||||
try {
|
try {
|
||||||
const parsed = parseCSV(data);
|
const parsed = parseCSV(data);
|
||||||
|
@ -223,11 +223,13 @@
|
|||||||
>
|
>
|
||||||
<h2 class="text-xl font-semibold mt-4">{{ t`Import Success` }} 🎉</h2>
|
<h2 class="text-xl font-semibold mt-4">{{ t`Import Success` }} 🎉</h2>
|
||||||
<p class="text-lg text-center">
|
<p class="text-lg text-center">
|
||||||
{{ t`Successfully created the following ${names.length} entries:` }}
|
{{
|
||||||
|
t`Successfully created the following ${succeeded.length} entries:`
|
||||||
|
}}
|
||||||
</p>
|
</p>
|
||||||
<div class="max-h-96 overflow-y-auto">
|
<div class="max-h-96 overflow-y-auto">
|
||||||
<div
|
<div
|
||||||
v-for="(n, i) in names"
|
v-for="(n, i) in succeeded"
|
||||||
:key="'name-' + i"
|
:key="'name-' + i"
|
||||||
class="grid grid-cols-2 gap-2 border-b pb-2 mb-2 pe-4 text-lg w-60"
|
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"
|
style="grid-template-columns: 2rem auto"
|
||||||
@ -358,7 +360,8 @@ type DataImportData = {
|
|||||||
showColumnPicker: boolean;
|
showColumnPicker: boolean;
|
||||||
canReset: boolean;
|
canReset: boolean;
|
||||||
complete: boolean;
|
complete: boolean;
|
||||||
names: string[];
|
succeeded: string[];
|
||||||
|
failed: { name: string; error: Error }[];
|
||||||
file: null | { name: string; filePath: string; text: string };
|
file: null | { name: string; filePath: string; text: string };
|
||||||
nullOrImporter: null | Importer;
|
nullOrImporter: null | Importer;
|
||||||
importType: string;
|
importType: string;
|
||||||
@ -386,7 +389,8 @@ export default defineComponent({
|
|||||||
showColumnPicker: false,
|
showColumnPicker: false,
|
||||||
canReset: false,
|
canReset: false,
|
||||||
complete: false,
|
complete: false,
|
||||||
names: ['Bat', 'Baseball', 'Other Shit'],
|
succeeded: [],
|
||||||
|
failed: [],
|
||||||
file: null,
|
file: null,
|
||||||
nullOrImporter: null,
|
nullOrImporter: null,
|
||||||
importType: '',
|
importType: '',
|
||||||
@ -404,15 +408,11 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
canImportData(): boolean {
|
canImportData(): boolean {
|
||||||
if (this.file) {
|
if (!this.hasImporter) {
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.hasImporter && this.importer.valueMatrix.length) {
|
return this.importer.valueMatrix.length > 0;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
},
|
||||||
canSelectFile(): boolean {
|
canSelectFile(): boolean {
|
||||||
return !this.file;
|
return !this.file;
|
||||||
@ -650,7 +650,8 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
clear(): void {
|
clear(): void {
|
||||||
this.file = null;
|
this.file = null;
|
||||||
this.names = [];
|
this.succeeded = [];
|
||||||
|
this.failed = [];
|
||||||
this.nullOrImporter = null;
|
this.nullOrImporter = null;
|
||||||
this.importType = '';
|
this.importType = '';
|
||||||
this.complete = false;
|
this.complete = false;
|
||||||
@ -671,41 +672,29 @@ export default defineComponent({
|
|||||||
await saveData(template, filePath);
|
await saveData(template, filePath);
|
||||||
},
|
},
|
||||||
async importData(): Promise<void> {
|
async importData(): Promise<void> {
|
||||||
/*
|
|
||||||
if (this.isMakingEntries || this.complete) {
|
if (this.isMakingEntries || this.complete) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isRequiredUnassigned) {
|
this.importer.pushFromValueMatrixToDocs();
|
||||||
return await showMessageDialog({
|
|
||||||
message: this.t`Required Fields not Assigned`,
|
let doneCount = 0;
|
||||||
detail: this
|
for (const doc of this.importer.docs) {
|
||||||
.t`Please assign the following fields ${this.requiredUnassigned.join(
|
this.setLoadingStatus(doneCount, this.importer.docs.length);
|
||||||
', '
|
try {
|
||||||
)}`,
|
await doc.sync();
|
||||||
});
|
doneCount += 1;
|
||||||
|
|
||||||
|
this.succeeded.push(doc.name!);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
this.failed.push({ name: doc.name!, error });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.importer.assignedMatrix.length === 0) {
|
this.isMakingEntries = false;
|
||||||
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;
|
this.complete = true;
|
||||||
*/
|
|
||||||
},
|
},
|
||||||
setImportType(importType: string): void {
|
setImportType(importType: string): void {
|
||||||
this.clear();
|
this.clear();
|
||||||
@ -716,14 +705,9 @@ export default defineComponent({
|
|||||||
this.importType = importType;
|
this.importType = importType;
|
||||||
this.nullOrImporter = new Importer(importType, fyo);
|
this.nullOrImporter = new Importer(importType, fyo);
|
||||||
},
|
},
|
||||||
setLoadingStatus(
|
setLoadingStatus(entriesMade: number, totalEntries: number): void {
|
||||||
isMakingEntries: boolean,
|
|
||||||
entriesMade: number,
|
|
||||||
totalEntries: number
|
|
||||||
): void {
|
|
||||||
this.isMakingEntries = isMakingEntries;
|
|
||||||
this.percentLoading = entriesMade / totalEntries;
|
this.percentLoading = entriesMade / totalEntries;
|
||||||
this.messageLoading = isMakingEntries
|
this.messageLoading = this.isMakingEntries
|
||||||
? `${entriesMade} entries made out of ${totalEntries}...`
|
? `${entriesMade} entries made out of ${totalEntries}...`
|
||||||
: '';
|
: '';
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user