From 91e8fa52f447ce0acf7c0b41b725dee859a5e399 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Mon, 21 Feb 2022 16:26:57 +0530 Subject: [PATCH 01/10] feat: setup Data Import page --- models/doctype/Item/Item.js | 1 + src/background.js | 28 ++++++ src/components/Button.vue | 8 +- src/dataImport.ts | 9 ++ src/messages.js | 1 + src/pages/DataImport.vue | 165 ++++++++++++++++++++++++++++++++ src/pages/DataImport/index.vue | 163 ------------------------------- src/pages/Settings/Settings.vue | 2 + src/router.js | 6 ++ src/sidebarConfig.js | 4 + 10 files changed, 223 insertions(+), 164 deletions(-) create mode 100644 src/dataImport.ts create mode 100644 src/pages/DataImport.vue delete mode 100644 src/pages/DataImport/index.vue diff --git a/models/doctype/Item/Item.js b/models/doctype/Item/Item.js index 0adb9b2a..aabf92fc 100644 --- a/models/doctype/Item/Item.js +++ b/models/doctype/Item/Item.js @@ -2,6 +2,7 @@ import frappe, { t } from 'frappe'; export default { name: 'Item', + label: t`Item`, doctype: 'DocType', isSingle: 0, regional: 1, diff --git a/src/background.js b/src/background.js index b69e3581..069e27a1 100644 --- a/src/background.js +++ b/src/background.js @@ -255,6 +255,34 @@ ipcMain.handle(IPC_ACTIONS.GET_COA_LIST, async () => { return coas; }); +ipcMain.handle(IPC_ACTIONS.GET_FILE, async (event, options) => { + const response = { + name: '', + filePath: '', + success: false, + data: null, + canceled: false, + }; + const window = event.sender.getOwnerBrowserWindow(); + const { filePaths, canceled } = await dialog.showOpenDialog(window, options); + + response.filePath = filePaths?.[0]; + response.canceled = canceled; + + if (!response.filePath) { + return response; + } + + response.success = true; + if (canceled) { + return response; + } + + response.name = path.basename(response.filePath); + response.data = await fs.readFile(response.filePath); + return response; +}); + /* ------------------------------ * Register autoUpdater events lis * ------------------------------*/ diff --git a/src/components/Button.vue b/src/components/Button.vue index 5a18f317..9baeeb64 100644 --- a/src/components/Button.vue +++ b/src/components/Button.vue @@ -24,11 +24,17 @@ export default { type: Boolean, default: false, }, + padding: { + type: Boolean, + default: true, + }, }, computed: { style() { return { - padding: this.icon ? '6px 12px' : '6px 24px', + ...(this.padding + ? { padding: this.icon ? '6px 12px' : '6px 24px' } + : {}), color: this.type === 'primary' ? '#fff' : '#112B42', 'background-image': this.type === 'primary' diff --git a/src/dataImport.ts b/src/dataImport.ts new file mode 100644 index 00000000..d4393d1a --- /dev/null +++ b/src/dataImport.ts @@ -0,0 +1,9 @@ +export const importable = [ + 'SalesInvoice', + 'PurchaseInvoice', + 'Payment', + 'JournalEntry', + 'Customer', + 'Supplier', + 'Item', +]; diff --git a/src/messages.js b/src/messages.js index fcb599d1..3ec6db53 100644 --- a/src/messages.js +++ b/src/messages.js @@ -26,6 +26,7 @@ export const IPC_ACTIONS = { GET_LANGUAGE_MAP: 'get-language-map', CHECK_FOR_UPDATES: 'check-for-updates', GET_COA_LIST: 'get-coa-list', + GET_FILE: 'get-file', }; // ipcMain.send(...) diff --git a/src/pages/DataImport.vue b/src/pages/DataImport.vue new file mode 100644 index 00000000..00a77122 --- /dev/null +++ b/src/pages/DataImport.vue @@ -0,0 +1,165 @@ + + + + + + {{ t`Data Import` }} + + + + + {{ t`Cancel` }} + + + + + + + + { + importType = v; + } + " + /> + + {{ helperText }} + + + + + + + + {{ secondaryLabel }} + {{ + primaryLabel + }} + + + + + + + diff --git a/src/pages/DataImport/index.vue b/src/pages/DataImport/index.vue deleted file mode 100644 index de9bdc5b..00000000 --- a/src/pages/DataImport/index.vue +++ /dev/null @@ -1,163 +0,0 @@ - - - - {{ t`Data Import` }} - showTable(doctype)" - /> - Upload CSV - Download CSV Template - Submit - - - - - - - diff --git a/src/pages/Settings/Settings.vue b/src/pages/Settings/Settings.vue index fff11dca..6d1c4481 100644 --- a/src/pages/Settings/Settings.vue +++ b/src/pages/Settings/Settings.vue @@ -1,4 +1,6 @@ + + diff --git a/src/router.js b/src/router.js index ace0cd23..906bb636 100644 --- a/src/router.js +++ b/src/router.js @@ -1,6 +1,7 @@ import ChartOfAccounts from '@/pages/ChartOfAccounts'; // standard views import Dashboard from '@/pages/Dashboard/Dashboard'; +import DataImport from '@/pages/DataImport'; // custom views import GetStarted from '@/pages/GetStarted'; import InvoiceForm from '@/pages/InvoiceForm'; @@ -94,6 +95,11 @@ const routes = [ edit: (route) => route.query, }, }, + { + path: '/data_import', + name: 'Data Import', + component: DataImport, + }, { path: '/settings', name: 'Settings', diff --git a/src/sidebarConfig.js b/src/sidebarConfig.js index 79e513f4..cbeb148f 100644 --- a/src/sidebarConfig.js +++ b/src/sidebarConfig.js @@ -121,6 +121,10 @@ const config = { route: '/list/Tax', doctype: 'Tax', }, + { + label: t`Data Import`, + route: '/data_import', + }, { label: t`Settings`, route: '/settings', From 23e504a67d23e0b4abb8071f75be320808f60835 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Mon, 21 Feb 2022 17:57:01 +0530 Subject: [PATCH 02/10] feat: code to generate templates --- src/dataImport.ts | 88 ++++++++++++++++++++++++++++++++++++++++ src/pages/DataImport.vue | 59 ++++++++++++++++++++------- src/types/model.ts | 26 ++++++++++++ 3 files changed, 159 insertions(+), 14 deletions(-) create mode 100644 src/types/model.ts diff --git a/src/dataImport.ts b/src/dataImport.ts index d4393d1a..30defe32 100644 --- a/src/dataImport.ts +++ b/src/dataImport.ts @@ -1,3 +1,6 @@ +import { Field, FieldType } from '@/types/model'; +import frappe from 'frappe'; + export const importable = [ 'SalesInvoice', 'PurchaseInvoice', @@ -7,3 +10,88 @@ export const importable = [ 'Supplier', 'Item', ]; + +interface TemplateField { + label: string; + fieldname: string; + required: boolean; +} + +type LabelFieldMap = { + [key: string]: string; +}; + +export function getTemplateFields(doctype: string): TemplateField[] { + const fields: TemplateField[] = []; + + // @ts-ignore + const primaryFields: Field[] = frappe.models[doctype].fields; + const tableTypes: string[] = []; + + primaryFields.forEach( + ({ label, fieldtype, childtype, fieldname, required }) => { + if (fieldtype === FieldType.Table && childtype) { + tableTypes.push(childtype); + } + + fields.push({ + label, + fieldname, + required: Boolean(required ?? false), + }); + } + ); + + tableTypes.forEach((childtype) => { + // @ts-ignore + const childFields: Field[] = frappe.models[childtype].fields; + childFields.forEach(({ label, fieldtype, fieldname, required }) => { + if (fieldtype === FieldType.Table) { + return; + } + + fields.push({ label, fieldname, required: Boolean(required ?? false) }); + }); + }); + + return fields; +} + +function getLabelFieldMap(templateFields: TemplateField[]): LabelFieldMap { + const map: LabelFieldMap = {}; + + templateFields.reduce((acc, tf) => { + const key = tf.label as string; + acc[key] = tf.fieldname; + return acc; + }, map); + + return map; +} + +function getTemplate(templateFields: TemplateField[]): string { + const labels = templateFields.map(({ label }) => `"${label}"`).join(','); + return [labels, ''].join('\n'); +} + +export class Importer { + doctype: string; + templateFields: TemplateField[]; + _map: LabelFieldMap; + _template: string; + + constructor(doctype: string) { + this.doctype = doctype; + this.templateFields = getTemplateFields(doctype); + this._map = getLabelFieldMap(this.templateFields); + this._template = getTemplate(this.templateFields); + } + + get map() { + return this._map; + } + + get template() { + return this._template; + } +} diff --git a/src/pages/DataImport.vue b/src/pages/DataImport.vue index 00a77122..24aba801 100644 --- a/src/pages/DataImport.vue +++ b/src/pages/DataImport.vue @@ -37,11 +37,7 @@ input-class="bg-gray-100 text-gray-900 text-base" class="w-1/4" :value="importType" - @change=" - (v) => { - importType = v; - } - " + @change="setImportType" /> - {{ secondaryLabel }} - {{ + {{ secondaryLabel }} + {{ primaryLabel }} @@ -68,17 +69,18 @@
+ {{ helperText }} +