diff --git a/src/importer.ts b/src/importer.ts index 5cf692ef..fbf67b60 100644 --- a/src/importer.ts +++ b/src/importer.ts @@ -1,6 +1,7 @@ import { Fyo } from 'fyo'; 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 { ValidationError } from 'fyo/utils/errors'; import { @@ -75,6 +76,12 @@ export class Importer { */ 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) { if (!fyo.schemaMap[schemaName]) { throw new ValidationError( @@ -85,6 +92,7 @@ export class Importer { this.hasChildTables = false; this.schemaName = schemaName; this.fyo = fyo; + this.docs = []; this.valueMatrix = []; 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); + + 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 = new Map(); + + /** + * Record key is doc.name, childSchemaName, childDoc.name + */ + const childTableMap: Record< + string, + Record> + > = {}; + + 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); + + 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 { try { const parsed = parseCSV(data); diff --git a/src/pages/ImportWizard.vue b/src/pages/ImportWizard.vue index d57a5b8e..5ace8853 100644 --- a/src/pages/ImportWizard.vue +++ b/src/pages/ImportWizard.vue @@ -223,11 +223,13 @@ >

{{ t`Import Success` }} 🎉

- {{ t`Successfully created the following ${names.length} entries:` }} + {{ + t`Successfully created the following ${succeeded.length} entries:` + }}

0; }, canSelectFile(): boolean { return !this.file; @@ -650,7 +650,8 @@ export default defineComponent({ }, clear(): void { this.file = null; - this.names = []; + this.succeeded = []; + this.failed = []; this.nullOrImporter = null; this.importType = ''; this.complete = false; @@ -671,41 +672,29 @@ export default defineComponent({ await saveData(template, filePath); }, async importData(): Promise { - /* 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( - ', ' - )}`, - }); + this.importer.pushFromValueMatrixToDocs(); + + let doneCount = 0; + for (const doc of this.importer.docs) { + 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) { - 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.isMakingEntries = false; this.complete = true; - */ }, setImportType(importType: string): void { this.clear(); @@ -716,14 +705,9 @@ export default defineComponent({ this.importType = importType; this.nullOrImporter = new Importer(importType, fyo); }, - setLoadingStatus( - isMakingEntries: boolean, - entriesMade: number, - totalEntries: number - ): void { - this.isMakingEntries = isMakingEntries; + setLoadingStatus(entriesMade: number, totalEntries: number): void { this.percentLoading = entriesMade / totalEntries; - this.messageLoading = isMakingEntries + this.messageLoading = this.isMakingEntries ? `${entriesMade} entries made out of ${totalEntries}...` : ''; },