├── README.md
├── content.js
├── examples
└── bulk-update-speed.webm
├── frappe
├── form.js
├── listview.js
└── papaparse.min.js
├── license.txt
├── logo
└── logo.png
├── manifest.json
└── service-worker.js
/README.md:
--------------------------------------------------------------------------------
1 | ## About
2 | A simple Browser Extension that help you work & develop in Frappe/ERPNext Framework
3 |
4 | ## Get Started
5 | Install the extention from the [Chrome Web Store](https://chrome.google.com/webstore/detail/frappe-development-utils/mfpfeokebfgddkaemagjigbjkmohmpab)
6 | or download this repo and load it to your browser in developer mode Then open any doctype and click the options icon near the field label to open the options dialog.
7 |
8 |
9 | 
10 |
11 | ## Features
12 |
13 | - Support Frappe v13, v14
14 | - Show & change hidden fields in any DocType
15 | - Force Save Non changed forms, Submittable forms & Submitted forms (re submit)
16 | - Highlight the hidden and custom fields in any DocType
17 | - Show forms fields & tables fields details
18 | - Fast Access to Customize Form/DocType for field Options of type Link, Table & Table MultiSelect
19 | - Copy/Insert any child table all/specific rows from/to any ERPNext site
20 | - Copy/Insert any Customize Form custom fields from/to any ERPNext site
21 | - Copy/Insert Doctypes data between ERPNext Sites
22 | - Simple CSV Files bulk import (usefull also for triggering python db apis, webhooks, server scripts etc)
23 |
24 |
25 | #### Credits
26 | - [Frappe Framework](https://frappeframework.com/)
27 | - [PapaParse](https://www.papaparse.com/)
28 |
29 |
30 | ## License
31 |
32 | The MIT License (MIT)
--------------------------------------------------------------------------------
/content.js:
--------------------------------------------------------------------------------
1 | function get_fieldname(label, docfields) {
2 | const field = docfields.find((df) => {
3 | if (df.label && df.label === label) {
4 | return df
5 | }
6 | })
7 | return field["fieldname"]
8 | }
9 |
10 | async function csvToolReadFile(doctype, docfields, file) {
11 | Papa.parse(file, {
12 | header: true,
13 | transformHeader: function (header, idx) {
14 | if (header === "ID") {
15 | return "docname"
16 | }
17 | return get_fieldname(header, docfields)
18 | },
19 | complete: function (results) {
20 | console.log(results);
21 | let docs = []
22 |
23 | for (let i = 0; i < results.data.length; i++) {
24 | let doc = results.data[i]
25 | if (!doc.docname) continue
26 | doc["doctype"] = doctype
27 |
28 | docs.push(doc)
29 | }
30 | backgroundMessage('idf_bg_request__csv-tool-bulk_update', docs);
31 | }
32 | });
33 | }
34 |
35 |
36 | // Listen for page scripts
37 | window.addEventListener("message", async (event) => {
38 | if (event.origin === window.origin) {
39 | // console.log("CS: ", evt.data.eventName);
40 | switch (event.data.eventName) {
41 | case "idf_cs_request__form_trigger":
42 | backgroundMessage("idf_bg_request__form_trigger", event.data.payload);
43 | break;
44 | case "idf_cs_request__listview_setup":
45 | backgroundMessage("idf_bg_request__listview_setup", event.data.payload);
46 | break;
47 | case "idf_cs_request__show_options_dialog":
48 | backgroundMessage("idf_bg_request__show_options_dialog", event.data.payload);
49 | break;
50 | case "idf_cs_request__save-data":
51 | backgroundMessage('idf_bg_request__save-data', event.data.payload);
52 | break;
53 | case "idf_cs_request__listview_show-insert-doc-data-dialog":
54 | backgroundMessage('idf_bg_request__listview_show-insert-doc-data-dialog', event.data.payload);
55 | break;
56 | case "idf_cs_request__listview_show-csv-tool-dialog":
57 | backgroundMessage('idf_bg_request__listview_show-csv-tool-dialog', event.data.payload);
58 | break;
59 | case "idf_cs_request__csv-tool-read_file":
60 | csvToolReadFile(event.data.payload.doctype, event.data.payload.docfields, event.data.payload.file)
61 | break;
62 | case "idf_cs_request__childtable_save":
63 | backgroundMessage('idf_bg_request__childtable_save', event.data.payload);
64 | break;
65 | case "idf_cs_request__childtable_insert":
66 | backgroundMessage('idf_bg_request__childtable_insert', event.data.payload);
67 | break;
68 | case "idf_cs_request__customized_fields_save":
69 | backgroundMessage('idf_bg_request__customized_fields_save', event.data.payload);
70 | break;
71 | case "idf_cs_request__customized_fields_insert":
72 | backgroundMessage('idf_bg_request__customized_fields_insert');
73 | break;
74 | }
75 | }
76 | });
77 |
78 | // utils
79 | async function backgroundMessage(eventName, payload) {
80 | chrome.runtime.sendMessage(
81 | {
82 | eventName: eventName,
83 | payload: payload
84 | }
85 | );
86 | }
87 |
--------------------------------------------------------------------------------
/examples/bulk-update-speed.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ascorbic-acid/frappe_chrome_dev_utils/8a9c571d635c32db96ac1c8d34c837e53d9cd3ac/examples/bulk-update-speed.webm
--------------------------------------------------------------------------------
/frappe/form.js:
--------------------------------------------------------------------------------
1 |
2 | function formGridRender(tabId, doctype, docname) {
3 | idfExec(({ doctype, docname }) => {
4 | let frm = cur_frm;
5 |
6 | let parentField = frappe.meta.get_parentfield(frm.doctype, doctype);
7 |
8 | let openFormGrid = frm.fields_dict[parentField].grid.open_grid_row
9 |
10 | for (let k in openFormGrid.fields_dict) {
11 | let field = openFormGrid.fields_dict[k];
12 |
13 | if (field.df.fieldtype === "Column Break" ||
14 | field.df.fieldtype === "Section Break" ||
15 | field.df.fieldtype === "Tab Break"
16 | ) continue;
17 |
18 | let opsDiv = document.createElement("div");
19 | opsDiv.classList.add("idf-child-control")
20 | opsDiv.style.display = "inline-block"
21 | opsDiv.style.cursor = "pointer";
22 | opsDiv.style.marginRight = "3px";
23 | opsDiv.style.marginLeft = "3px";
24 | opsDiv.innerHTML = window.idfState.idfLogoUrl2;
25 |
26 | opsDiv.addEventListener("click", function (event) {
27 | event.stopPropagation();
28 | postMessage({
29 | eventName: "idf_cs_request__show_options_dialog",
30 | payload: {
31 | doctype: doctype,
32 | fieldname: field.df.fieldname
33 | }
34 | });
35 | });
36 | if (field.wrapper.firstElementChild) {
37 | // checkbox fields
38 | if (field.wrapper.firstElementChild.classList.contains("checkbox")) {
39 | const label = field.wrapper.firstElementChild.querySelector("label")
40 | label.appendChild(opsDiv);
41 | // standard fields
42 | } else if (field.wrapper.firstElementChild.classList.contains("form-group")) {
43 | const label = field.wrapper.firstElementChild.querySelector(".form-group > .clearfix")
44 | label.appendChild(opsDiv);
45 | }
46 | }
47 | }
48 |
49 | },
50 | { doctype, docname }, tabId);
51 | }
52 |
53 | function formRefresh(tabId, doctype, docname) {
54 | idfExec(({ doctype, name }) => {
55 | let frm = cur_frm;
56 |
57 | // add force save button
58 | if (!frm.is_dirty() && (frm.doc.docstatus === 0 || frm.doc.docstatus === 1)) {
59 | // temp fix to avoid duplicate buttons
60 | if (document.querySelector(".idf__force-save-btn")) {
61 | document.querySelector(".idf__force-save-btn").remove();
62 | }
63 | frm.page.add_button(frm.doc.docstatus === 0 ? "Force Save" : "Force Submit", function () {
64 | frm.dirty();
65 | frm.save_or_update();
66 | }, { btn_class: "btn-warning idf__force-save-btn" });
67 | }
68 |
69 | if (frm["idf_inited"]) {
70 | return;
71 | } else {
72 | frm["idf_inited"] = true;
73 | }
74 |
75 | // patch fields
76 | for (let i = 0; i < frm.fields.length; i++) {
77 | let field = frm.fields[i];
78 | if (!field.wrapper.querySelector)
79 | continue;
80 |
81 | let opsDiv = document.createElement("div");
82 | opsDiv.style.display = "inline-block"
83 | opsDiv.style.cursor = "pointer";
84 | opsDiv.style.marginRight = "3px";
85 | opsDiv.style.marginLeft = "3px";
86 | opsDiv.innerHTML = window.idfState.idfLogoUrl2;
87 |
88 | opsDiv.addEventListener("click", function (event) {
89 | postMessage({
90 | eventName: "idf_cs_request__show_options_dialog",
91 | payload: {
92 | doctype: frm.doctype,
93 | fieldname: field.df.fieldname
94 | }
95 | });
96 | });
97 |
98 | if (field.wrapper.firstElementChild) {
99 | // checkbox fields
100 | if (field.wrapper.firstElementChild.classList.contains("checkbox")) {
101 | const label = field.wrapper.firstElementChild.querySelector("label")
102 | label.appendChild(opsDiv);
103 | // standard fields
104 | } else if (field.wrapper.firstElementChild.classList.contains("form-group")) {
105 | const label = field.wrapper.firstElementChild.querySelector(".form-group > .clearfix")
106 | label.appendChild(opsDiv);
107 | // table field
108 | } else if (field.wrapper.firstElementChild.classList.contains("control-label")) {
109 | const label = field.wrapper.firstElementChild;
110 | label.appendChild(opsDiv);
111 | // table field v14
112 | } else if (field.wrapper.firstElementChild.classList.contains("grid-field")) {
113 | const label = field.wrapper.firstElementChild;
114 | label.prepend(opsDiv);
115 | }
116 | }
117 |
118 | if (!field.df.is_custom_field)
119 | field.df.is_custom_field = "0";
120 | if (!field.df.hidden)
121 | field.df.hidden = "0";
122 |
123 | if (location.pathname.includes("/app/customize-form"))
124 | field.df.old_hidden = field.df.hidden;
125 |
126 | // show all hidden fields & highlight custom fields
127 | if (field.df.hidden === 1) {
128 | //save old hidden value
129 | field.df.old_hidden = 1;
130 | field.df.hidden = 0;
131 |
132 | let control_label = field.wrapper.querySelector(".control-label");
133 | if (field.df.is_custom_field === 1) {
134 | field.df.label += " (HIDDEN)";
135 | field.wrapper.style.color = "darksalmon";
136 | if (control_label) {
137 | control_label.style.color = "darksalmon";
138 | }
139 | } else {
140 | field.df.label += " (HIDDEN)";
141 | field.wrapper.style.color = "brown";
142 | if (control_label) {
143 | control_label.style.color = "brown";
144 | }
145 | }
146 | }
147 | }
148 | frm.refresh_fields();
149 | },
150 | { doctype, docname }, tabId);
151 | }
152 |
153 | function formEvent(tabId, eventName, doctype, docname) {
154 | if (eventName === "refresh") {
155 | formRefresh(tabId, doctype, docname)
156 | } else if (eventName == "form_render") {
157 | formGridRender(tabId, doctype, docname)
158 | }
159 | }
160 |
161 | function idfShowOptionsDialog(args, tabId) {
162 | idfExec((args) => {
163 | let fieldData = frappe.meta.get_docfield(args.doctype, args.fieldname);
164 |
165 | // prepare field info
166 | if (!fieldData.options) {
167 | fieldData.options = "";
168 | }
169 |
170 | let openDocButtonsHTML = "";
171 |
172 | if (["Link", "Table", "Table MultiSelect"].includes(fieldData.fieldtype)) {
173 | openDocButtonsHTML = `
174 | C
178 | D
182 | `;
183 | }
184 |
185 | var dialog = new frappe.ui.Dialog({
186 | title: `${window.idfState.idfLogoUrl2} Field Details`,
187 | fields: [{
188 | label: `Details:`,
189 | fieldname: "tables_options_section",
190 | fieldtype: "Section Break"
191 | }, {
192 | label: `Details:`,
193 | fieldname: "field_details_html",
194 | fieldtype: "HTML",
195 | options: `
196 |
197 |
Name: ${fieldData.fieldname}
198 |
Field No.: ${fieldData.idx}
199 |
200 |
Type: ${fieldData.fieldtype}
201 |
In ListView: ${fieldData.in_list_view}
202 |
203 |
204 |
207 | Options: ${fieldData.options}
208 | ${openDocButtonsHTML}
209 |
210 |
211 |
Is Custom: ${fieldData.is_custom_field}
212 |
213 |
223 | `
224 | }, {
225 | fieldtype: "Section Break"
226 | }, {
227 | label: "Extra Actions:",
228 | fieldname: "field_options_section",
229 | fieldtype: "Section Break"
230 | },
231 | {
232 | label: 'Copy Table Data',
233 | fieldname: 'copy_table_data',
234 | fieldtype: 'Button',
235 | click: (val) => {
236 | if (fieldData.fieldtype == "Table") {
237 | let table;
238 | if (dialog.get_value("only_selected_rows")) {
239 | table = fieldData.grid.get_selected_children()
240 | } else {
241 | table = cur_frm.doc[fieldData.fieldname];
242 | }
243 | postMessage({
244 | eventName: "idf_cs_request__childtable_save",
245 | payload: table
246 | });
247 |
248 | frappe.show_alert(`${window.idfState.idfLogoUrl2} Date of table: (${fieldData.fieldname}) saved to browser storage`, 8);
249 | cur_dialog.hide();
250 | } else {
251 | frappe.show_alert(`${window.idfState.idfLogoUrl2} Field: (${fieldData.fieldname}) is not a table`, 8);
252 | }
253 | }
254 | },
255 | {
256 | label: 'Only Selected Rows',
257 | fieldname: 'only_selected_rows',
258 | fieldtype: 'Check',
259 | description: 'if not checked copy all rows'
260 | }
261 | , {
262 | fieldtype: "Column Break"
263 | }, {
264 | label: 'Insert Saved Table Data',
265 | fieldname: 'copy_table_data',
266 | fieldtype: 'Button',
267 | click: (val) => {
268 | if (fieldData.fieldtype == "Table") {
269 | postMessage({
270 | eventName: "idf_cs_request__childtable_insert",
271 | payload: fieldData.fieldname
272 | });
273 |
274 | frappe.show_alert(`${window.idfState.idfLogoUrl2} Data of table: (${fieldData.fieldname}) has been inserted`, 8);
275 | cur_dialog.hide();
276 | } else {
277 | frappe.show_alert(`${window.idfState.idfLogoUrl2} Field: (${fieldData.fieldname}) is not a table`, 8);
278 | }
279 | }
280 | }, // Customize Form
281 | {
282 | fieldtype: "Section Break"
283 | }, {
284 | label: 'Copy Customized Fields',
285 | fieldname: 'copy_customized_fields',
286 | fieldtype: 'Button',
287 | click: (val) => {
288 | if (fieldData.parent == "Customize Form" && fieldData.fieldname == "fields") {
289 | let fields = cur_frm.doc.fields;
290 | let customFields = [];
291 |
292 | for (let i = 0; i < fields.length; i++) {
293 | if (fields[i].is_custom_field === 1) {
294 | fields[i].insert_after_fieldname = fields[i === 0 ? 0 : (i - 1)].fieldname;
295 | customFields.push(fields[i]);
296 | }
297 | }
298 | postMessage({
299 | eventName: "idf_cs_request__customized_fields_save",
300 | payload: customFields
301 | });
302 |
303 | frappe.show_alert(`${window.idfState.idfLogoUrl2} Customized Fields saved to browser storage`, 8);
304 | cur_dialog.hide();
305 | } else {
306 | frappe.show_alert(`${window.idfState.idfLogoUrl2} Only works on (fields) table in Customize Form doctype.`, 8);
307 | }
308 | }
309 | }, {
310 | fieldtype: "Column Break"
311 | }, {
312 | label: 'Insert Saved Customized Fields',
313 | fieldname: 'inser_customized_fields',
314 | fieldtype: 'Button',
315 | click: (val) => {
316 | if (fieldData.parent == "Customize Form" && fieldData.fieldname == "fields") {
317 | postMessage({
318 | eventName: "idf_cs_request__customized_fields_insert"
319 | });
320 |
321 | frappe.show_alert(`${window.idfState.idfLogoUrl2} Customized Fields has been inserted`, 8);
322 | cur_dialog.hide();
323 | } else {
324 | frappe.show_alert(`${window.idfState.idfLogoUrl2} Only works on (fields) table in Customize Form doctype.`, 8);
325 | }
326 | }
327 | }, {
328 | fieldtype: "Section Break"
329 | },],
330 | primary_action_label: 'Done',
331 | primary_action(values) {
332 | cur_dialog.hide();
333 | }
334 | });
335 | dialog.show();
336 | },
337 | args, tabId);
338 | }
339 |
--------------------------------------------------------------------------------
/frappe/listview.js:
--------------------------------------------------------------------------------
1 | function ListviewSetup(tabId) {
2 | idfExec((args) => {
3 | if (cur_list) {
4 | // add bulk edit to list views
5 | // if (cur_list.page.clear_custom_actions) {
6 | // cur_list.page.clear_custom_actions();
7 | // }
8 | let idfGroup = cur_list.page.add_custom_button_group("IDF")
9 |
10 | // set group btn icon
11 | idfGroup.parent().find("span.custom-btn-group-label").html(window.idfState.idfLogoUrl1);
12 |
13 | // save docs
14 | cur_list.page.add_custom_menu_item(idfGroup, "Save Docs", async () => {
15 | let selected_docs = cur_list.get_checked_items();
16 |
17 | if (selected_docs.length == 0) {
18 | frappe.show_alert(`${window.idfState.idfLogoUrl1} Please Select at least one row`, 2);
19 | return;
20 | }
21 |
22 | let docs_insert_dialog = new frappe.ui.Dialog({
23 | title: `${window.idfState.idfLogoUrl1} Saving (${selected_docs.length}) Doc Data of (${cur_list.doctype})`,
24 | fields: [
25 | {
26 | label: __("Include Cancelled Docs"),
27 | fieldname: "include_cancelled",
28 | fieldtype: "Check",
29 | description: __("Note: Cancelled Docs will be inserted as Draft"),
30 | },
31 | { fieldtype: "Column Break" },
32 | {
33 | label: __("Storage Bucket"),
34 | fieldname: "storage_bucket",
35 | fieldtype: "Select",
36 | description: __("Select bucket to store to"),
37 | options: ["General", "Current Doc"],
38 | default: "Current Doc"
39 | },
40 | { fieldtype: "Section Break" },
41 | {
42 | label: __("Keep Old"),
43 | fieldname: "keep_old_data",
44 | fieldtype: "Check",
45 | description: __("Append to Old Docs Data"),
46 | },
47 | { fieldtype: "Column Break" },
48 | {
49 | label: __("Add to Storage Top"),
50 | fieldname: "add_data_to_top",
51 | fieldtype: "Check",
52 | description: __("Useful if you want later insert this group of data first before the old one"),
53 | depends_on: "eval:doc.keep_old_data==1",
54 | }
55 | ],
56 | primary_action_label: `${window.idfState.idfLogoUrl1} Save Docs`,
57 | primary_action: async function (values) {
58 | let docs = []
59 |
60 | for (let i = 0; i < selected_docs.length; i++) {
61 | if (!values.include_cancelled && selected_docs[i].docstatus === 2) continue;
62 |
63 | let res = await frappe.call({
64 | method: "frappe.client.get",
65 | args: { doctype: cur_list.doctype, name: selected_docs[i].name },
66 | })
67 | let res_docs = res.message;
68 |
69 | if (res_docs) {
70 | frappe.show_progress(
71 | `${window.idfState.idfLogoUrl1} Fetching DocType Data`,
72 | i + 1,
73 | selected_docs.length,
74 | `Fetching ${selected_docs[i].name}`,
75 | true
76 | )
77 | docs.push(res_docs)
78 | }
79 |
80 | }
81 | postMessage({
82 | eventName: "idf_cs_request__save-data",
83 | payload: {
84 | doctype: cur_list.doctype,
85 | data: docs,
86 | bucket: values.storage_bucket,
87 | keepOld: values.keep_old_data,
88 | addToTop: values.add_data_to_top
89 | }
90 | });
91 | frappe.show_alert(`${window.idfState.idfLogoUrl1} Saved ${docs.length} DocType Data Successfully`)
92 | docs_insert_dialog.hide()
93 | }
94 | })
95 | docs_insert_dialog.show()
96 | });
97 |
98 | // insert docs
99 | cur_list.page.add_custom_menu_item(idfGroup, "Insert Docs", () => {
100 | postMessage({
101 | eventName: "idf_cs_request__listview_show-insert-doc-data-dialog",
102 | payload: {
103 | doctype: cur_list.doctype
104 | }
105 | });
106 | });
107 |
108 | // insert docs
109 | cur_list.page.add_custom_menu_item(idfGroup, "CSV Tool", () => {
110 | postMessage({
111 | eventName: "idf_cs_request__listview_show-csv-tool-dialog",
112 | payload: {
113 | doctype: cur_list.doctype
114 | }
115 | });
116 | });
117 | }
118 | }
119 | , {}, tabId);
120 | }
121 |
--------------------------------------------------------------------------------
/frappe/papaparse.min.js:
--------------------------------------------------------------------------------
1 | /* @license
2 | Papa Parse
3 | v5.0.2
4 | https://github.com/mholt/PapaParse
5 | License: MIT
6 | */
7 | !function(e,t){"function"==typeof define&&define.amd?define([],t):"object"==typeof module&&"undefined"!=typeof exports?module.exports=t():e.Papa=t()}(this,function s(){"use strict";var f="undefined"!=typeof self?self:"undefined"!=typeof window?window:void 0!==f?f:{};var n=!f.document&&!!f.postMessage,o=n&&/blob:/i.test((f.location||{}).protocol),a={},h=0,b={parse:function(e,t){var r=(t=t||{}).dynamicTyping||!1;q(r)&&(t.dynamicTypingFunction=r,r={});if(t.dynamicTyping=r,t.transform=!!q(t.transform)&&t.transform,t.worker&&b.WORKERS_SUPPORTED){var i=function(){if(!b.WORKERS_SUPPORTED)return!1;var e=(r=f.URL||f.webkitURL||null,i=s.toString(),b.BLOB_URL||(b.BLOB_URL=r.createObjectURL(new Blob(["(",i,")();"],{type:"text/javascript"})))),t=new f.Worker(e);var r,i;return t.onmessage=_,t.id=h++,a[t.id]=t}();return i.userStep=t.step,i.userChunk=t.chunk,i.userComplete=t.complete,i.userError=t.error,t.step=q(t.step),t.chunk=q(t.chunk),t.complete=q(t.complete),t.error=q(t.error),delete t.worker,void i.postMessage({input:e,config:t,workerId:i.id})}var n=null;b.NODE_STREAM_INPUT,"string"==typeof e?n=t.download?new l(t):new p(t):!0===e.readable&&q(e.read)&&q(e.on)?n=new m(t):(f.File&&e instanceof File||e instanceof Object)&&(n=new c(t));return n.stream(e)},unparse:function(e,t){var i=!1,_=!0,g=",",v="\r\n",n='"',s=n+n,r=!1,a=null;!function(){if("object"!=typeof t)return;"string"!=typeof t.delimiter||b.BAD_DELIMITERS.filter(function(e){return-1!==t.delimiter.indexOf(e)}).length||(g=t.delimiter);("boolean"==typeof t.quotes||Array.isArray(t.quotes))&&(i=t.quotes);"boolean"!=typeof t.skipEmptyLines&&"string"!=typeof t.skipEmptyLines||(r=t.skipEmptyLines);"string"==typeof t.newline&&(v=t.newline);"string"==typeof t.quoteChar&&(n=t.quoteChar);"boolean"==typeof t.header&&(_=t.header);if(Array.isArray(t.columns)){if(0===t.columns.length)throw new Error("Option columns is empty");a=t.columns}void 0!==t.escapeChar&&(s=t.escapeChar+n)}();var o=new RegExp(U(n),"g");"string"==typeof e&&(e=JSON.parse(e));if(Array.isArray(e)){if(!e.length||Array.isArray(e[0]))return u(null,e,r);if("object"==typeof e[0])return u(a||h(e[0]),e,r)}else if("object"==typeof e)return"string"==typeof e.data&&(e.data=JSON.parse(e.data)),Array.isArray(e.data)&&(e.fields||(e.fields=e.meta&&e.meta.fields),e.fields||(e.fields=Array.isArray(e.data[0])?e.fields:h(e.data[0])),Array.isArray(e.data[0])||"object"==typeof e.data[0]||(e.data=[e.data])),u(e.fields||[],e.data||[],r);throw new Error("Unable to serialize unrecognized input");function h(e){if("object"!=typeof e)return[];var t=[];for(var r in e)t.push(r);return t}function u(e,t,r){var i="";"string"==typeof e&&(e=JSON.parse(e)),"string"==typeof t&&(t=JSON.parse(t));var n=Array.isArray(e)&&0=this._config.preview;if(o)f.postMessage({results:n,workerId:b.WORKER_ID,finished:a});else if(q(this._config.chunk)&&!t){if(this._config.chunk(n,this._handle),this._handle.paused()||this._handle.aborted())return void(this._halted=!0);n=void 0,this._completeResults=void 0}return this._config.step||this._config.chunk||(this._completeResults.data=this._completeResults.data.concat(n.data),this._completeResults.errors=this._completeResults.errors.concat(n.errors),this._completeResults.meta=n.meta),this._completed||!a||!q(this._config.complete)||n&&n.meta.aborted||(this._config.complete(this._completeResults,this._input),this._completed=!0),a||n&&n.meta.paused||this._nextChunk(),n}this._halted=!0},this._sendError=function(e){q(this._config.error)?this._config.error(e):o&&this._config.error&&f.postMessage({workerId:b.WORKER_ID,error:e,finished:!1})}}function l(e){var i;(e=e||{}).chunkSize||(e.chunkSize=b.RemoteChunkSize),u.call(this,e),this._nextChunk=n?function(){this._readChunk(),this._chunkLoaded()}:function(){this._readChunk()},this.stream=function(e){this._input=e,this._nextChunk()},this._readChunk=function(){if(this._finished)this._chunkLoaded();else{if(i=new XMLHttpRequest,this._config.withCredentials&&(i.withCredentials=this._config.withCredentials),n||(i.onload=y(this._chunkLoaded,this),i.onerror=y(this._chunkError,this)),i.open("GET",this._input,!n),this._config.downloadRequestHeaders){var e=this._config.downloadRequestHeaders;for(var t in e)i.setRequestHeader(t,e[t])}if(this._config.chunkSize){var r=this._start+this._config.chunkSize-1;i.setRequestHeader("Range","bytes="+this._start+"-"+r)}try{i.send()}catch(e){this._chunkError(e.message)}n&&0===i.status?this._chunkError():this._start+=this._config.chunkSize}},this._chunkLoaded=function(){4===i.readyState&&(i.status<200||400<=i.status?this._chunkError():(this._finished=!this._config.chunkSize||this._start>function(e){var t=e.getResponseHeader("Content-Range");if(null===t)return-1;return parseInt(t.substr(t.lastIndexOf("/")+1))}(i),this.parseChunk(i.responseText)))},this._chunkError=function(e){var t=i.statusText||e;this._sendError(new Error(t))}}function c(e){var i,n;(e=e||{}).chunkSize||(e.chunkSize=b.LocalChunkSize),u.call(this,e);var s="undefined"!=typeof FileReader;this.stream=function(e){this._input=e,n=e.slice||e.webkitSlice||e.mozSlice,s?((i=new FileReader).onload=y(this._chunkLoaded,this),i.onerror=y(this._chunkError,this)):i=new FileReaderSync,this._nextChunk()},this._nextChunk=function(){this._finished||this._config.preview&&!(this._rowCount=this._input.size,this.parseChunk(e.target.result)},this._chunkError=function(){this._sendError(i.error)}}function p(e){var r;u.call(this,e=e||{}),this.stream=function(e){return r=e,this._nextChunk()},this._nextChunk=function(){if(!this._finished){var e=this._config.chunkSize,t=e?r.substr(0,e):r;return r=e?r.substr(e):"",this._finished=!r,this.parseChunk(t)}}}function m(e){u.call(this,e=e||{});var t=[],r=!0,i=!1;this.pause=function(){u.prototype.pause.apply(this,arguments),this._input.pause()},this.resume=function(){u.prototype.resume.apply(this,arguments),this._input.resume()},this.stream=function(e){this._input=e,this._input.on("data",this._streamData),this._input.on("end",this._streamEnd),this._input.on("error",this._streamError)},this._checkIsFinished=function(){i&&1===t.length&&(this._finished=!0)},this._nextChunk=function(){this._checkIsFinished(),t.length?this.parseChunk(t.shift()):r=!0},this._streamData=y(function(e){try{t.push("string"==typeof e?e:e.toString(this._config.encoding)),r&&(r=!1,this._checkIsFinished(),this.parseChunk(t.shift()))}catch(e){this._streamError(e)}},this),this._streamError=y(function(e){this._streamCleanUp(),this._sendError(e)},this),this._streamEnd=y(function(){this._streamCleanUp(),i=!0,this._streamData("")},this),this._streamCleanUp=y(function(){this._input.removeListener("data",this._streamData),this._input.removeListener("end",this._streamEnd),this._input.removeListener("error",this._streamError)},this)}function r(g){var a,o,h,i=Math.pow(2,53),n=-i,s=/^\s*-?(\d*\.?\d+|\d+\.?\d*)(e[-+]?\d+)?\s*$/i,u=/(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/,t=this,r=0,f=0,d=!1,e=!1,l=[],c={data:[],errors:[],meta:{}};if(q(g.step)){var p=g.step;g.step=function(e){if(c=e,_())m();else{if(m(),0===c.data.length)return;r+=e.data.length,g.preview&&r>g.preview?o.abort():p(c,t)}}}function v(e){return"greedy"===g.skipEmptyLines?""===e.join("").trim():1===e.length&&0===e[0].length}function m(){if(c&&h&&(k("Delimiter","UndetectableDelimiter","Unable to auto-detect delimiting character; defaulted to '"+b.DefaultDelimiter+"'"),h=!1),g.skipEmptyLines)for(var e=0;e=l.length?"__parsed_extra":l[r]),g.transform&&(s=g.transform(s,n)),s=y(n,s),"__parsed_extra"===n?(i[n]=i[n]||[],i[n].push(s)):i[n]=s}return g.header&&(r>l.length?k("FieldMismatch","TooManyFields","Too many fields: expected "+l.length+" fields but parsed "+r,f+t):r=i.length/2?"\r\n":"\r"}(e,i)),h=!1,g.delimiter)q(g.delimiter)&&(g.delimiter=g.delimiter(e),c.meta.delimiter=g.delimiter);else{var n=function(e,t,r,i,n){var s,a,o,h;n=n||[",","\t","|",";",b.RECORD_SEP,b.UNIT_SEP];for(var u=0;u=L)return R(!0)}else for(g=M,M++;;){if(-1===(g=a.indexOf(O,g+1)))return t||u.push({type:"Quotes",code:"MissingQuotes",message:"Quoted field unterminated",row:h.length,index:M}),w();if(g===i-1)return w(a.substring(M,g).replace(_,O));if(O!==z||a[g+1]!==z){if(O===z||0===g||a[g-1]!==z){var y=E(-1===m?p:Math.min(p,m));if(a[g+1+y]===D){f.push(a.substring(M,g).replace(_,O)),a[M=g+1+y+e]!==O&&(g=a.indexOf(O,M)),p=a.indexOf(D,M),m=a.indexOf(I,M);break}var k=E(m);if(a.substr(g+1+k,n)===I){if(f.push(a.substring(M,g).replace(_,O)),C(g+1+k+n),p=a.indexOf(D,M),g=a.indexOf(O,M),o&&(S(),j))return R();if(L&&h.length>=L)return R(!0);break}u.push({type:"Quotes",code:"InvalidQuotes",message:"Trailing quote on quoted field is malformed",row:h.length,index:M}),g++}}else g++}return w();function b(e){h.push(e),d=M}function E(e){var t=0;if(-1!==e){var r=a.substring(g+1,e);r&&""===r.trim()&&(t=r.length)}return t}function w(e){return t||(void 0===e&&(e=a.substr(M)),f.push(e),M=i,b(f),o&&S()),R()}function C(e){M=e,b(f),f=[],m=a.indexOf(I,M)}function R(e,t){return{data:t||!1?h[0]:h,errors:u,meta:{delimiter:D,linebreak:I,aborted:j,truncated:!!e,cursor:d+(r||0)}}}function S(){A(R(void 0,!0)),h=[],u=[]}function x(e,t,r){var i={nextDelim:void 0,quoteSearch:void 0},n=a.indexOf(O,t+1);if(t" ]
25 | }
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/service-worker.js:
--------------------------------------------------------------------------------
1 | importScripts('frappe/form.js');
2 | importScripts('frappe/listview.js')
3 |
4 |
5 | function formTrigger(tabId, eventName, doctype, docname) {
6 | formEvent(tabId, eventName, doctype, docname);
7 | }
8 |
9 | function listviewSetup(tabId) {
10 | ListviewSetup(tabId);
11 | }
12 |
13 | async function saveDocData(doctype, data = [], bucket = "General", keepOld = 0, addToTop = 0) {
14 |
15 | let storageObject = {};
16 | let storageKey = "storage__doc_data-";
17 |
18 | if (bucket === "General") {
19 | storageKey += "General";
20 | } else {
21 | storageKey += doctype;
22 | }
23 |
24 | if (keepOld) {
25 | let oldData = await chrome.storage.local.get(storageKey);
26 | oldData = oldData[storageKey];
27 |
28 | if (addToTop) {
29 | oldData.unshift(...data);
30 | } else {
31 | oldData.push(...data);
32 | }
33 |
34 | oldData = oldData.filter((v, i, a) => a.findIndex(v2 => (v2.name === v.name)) === i);
35 | storageObject[storageKey] = oldData;
36 | await chrome.storage.local.set(storageObject);
37 | } else {
38 | storageObject[storageKey] = data;
39 | await chrome.storage.local.set(storageObject);
40 | }
41 |
42 | let res = await chrome.storage.local.get(storageKey);
43 | }
44 |
45 | async function showInsertDocDataDialog(doctype, bucket = "Current Doc", tabId) {
46 | let storageKey = "storage__doc_data-";
47 |
48 | if (bucket === "General") {
49 | storageKey += "General";
50 | } else {
51 | storageKey += doctype;
52 | }
53 |
54 | var data = await chrome.storage.local.get(storageKey);
55 | var args = { data: data[storageKey], bucket };
56 |
57 | idfExec((args) => {
58 | if (args.data === undefined || (args.data && args.data.length === 0)) {
59 | frappe.show_alert(`${window.idfState.idfLogoUrl1} No Data Saved`, 2);
60 | }
61 | let doc_insert_dialog = new frappe.ui.Dialog({
62 | title: `${window.idfState.idfLogoUrl1} Insert (${args.data.length}) Saved Doc Data of (${cur_list.doctype})`,
63 | fields: [
64 | {
65 | label: __("Dont Submit Records"),
66 | fieldname: "submitted_as_draft",
67 | fieldtype: "Check",
68 | description: __("Insert Submitted Docs as Draft"),
69 | read_only_depends_on: "eval:doc.insert_doc_as_local===1",
70 | default: 0
71 | },
72 | { fieldtype: "Column Break" },
73 | {
74 | label: __("Storage Bucket"),
75 | fieldname: "storage_bucket",
76 | fieldtype: "Select",
77 | description: __("Select bucket to fetch from"),
78 | options: ["General", "Current Doc"],
79 | default: args.bucket,
80 | onchange: function (e) {
81 | if (e && e.type == "change") {
82 | this.layout.hide();
83 | postMessage({
84 | eventName: "idf_cs_request__listview_show-insert-doc-data-dialog",
85 | payload: {
86 | doctype: cur_list.doctype,
87 | bucket: this.value
88 | }
89 | });
90 | }
91 | }
92 | },
93 | { fieldtype: "Section Break" },
94 | {
95 | label: __("Insert One as Local"),
96 | fieldname: "insert_doc_as_local",
97 | fieldtype: "Check",
98 | description: __("Inserting one doc record localy to edit"),
99 | default: 0,
100 | onchange: function (e) {
101 | if (e && e.type == "change") {
102 | if (this.value) {
103 | // this.layout.set_df_property("submitted_as_draft", "read_only", 1);
104 | // this.layout.set_df_property("stored_doc_name", "hidden", 0);
105 | this.layout.set_value("stored_doc_name", args.data[0].name);
106 | } else {
107 | // this.layout.set_df_property("submitted_as_draft", "read_only", 0);
108 | // this.layout.set_df_property("stored_doc_name", "hidden", 1)
109 | this.layout.set_value("stored_doc_name", "");
110 | }
111 | }
112 | }
113 | },
114 | { fieldtype: "Column Break" },
115 | {
116 | label: __("Stored Doc to Insert"),
117 | fieldname: "stored_doc_name",
118 | fieldtype: "Data",
119 | depends_on: "eval:doc.insert_doc_as_local===1",
120 | read_only: 1,
121 | }
122 | ],
123 | primary_action_label: `${window.idfState.idfLogoUrl1} Insert Docs`,
124 | primary_action: async function (values) {
125 | doc_insert_dialog.hide();
126 | let inserted_docs = [];
127 | let remaining_docs = args.data.slice();
128 |
129 | for (let i = 0; i < args.data.length; i++) {
130 |
131 | if (values.insert_doc_as_local) {
132 | let new_doc = frappe.model.copy_doc(args.data[0]);
133 |
134 | frappe.set_route("Form", new_doc.doctype, new_doc.name);
135 | frappe.show_alert(`${window.idfState.idfLogoUrl1} Inserted doc (${args.data[0].name}) As a New Local Doc`);
136 | remaining_docs.splice(0, 1);
137 |
138 | postMessage({
139 | eventName: "idf_cs_request__save-data",
140 | payload: {
141 | doctype: cur_list.doctype,
142 | data: remaining_docs,
143 | bucket: args.bucket
144 | }
145 | });
146 | return;
147 | }
148 |
149 | frappe.show_progress(`${window.idfState.idfLogoUrl1} Inserting Data`, i + 1, args.data.length, `Inserting ${args.data[i].name}`, true);
150 |
151 | let doc_exists = await frappe.db.exists(cur_list.doctype, args.data[i].name);
152 |
153 | if (doc_exists) {
154 | let exist_doc_values = await new Promise((resolve, reject) => {
155 | let exist_doc_dialog = new frappe.ui.Dialog({
156 | title: __(`${window.idfState.idfLogoUrl1} Please Select Action`),
157 | fields: [{
158 | label: __("Info:"),
159 | fieldname: "info_data",
160 | fieldtype: "Data",
161 | read_only: 1
162 | }, {
163 | fieldtype: "Section Break"
164 | }, {
165 | label: __("Skip This Doc"),
166 | fieldname: "skip_doc",
167 | fieldtype: "Check",
168 | default: 1,
169 | onchange: function (e) {
170 | if (e && e.type == "change") {
171 | if (this.value) {
172 | this.layout.set_value("insert_new_name", 0);
173 | this.layout.set_df_property("new_name", "read_only", 1);
174 | } else {
175 | this.layout.set_value("insert_new_name", 1);
176 |
177 | }
178 | }
179 | }
180 | }, {
181 | fieldtype: "Column Break"
182 | }, {
183 | label: __("Insert with New Name"),
184 | fieldname: "insert_new_name",
185 | fieldtype: "Check",
186 | onchange: function (e) {
187 | if (e && e.type == "change") {
188 | if (this.value) {
189 | this.layout.set_value("skip_doc", 0);
190 | this.layout.set_df_property("new_name", "read_only", 0);
191 | } else {
192 | this.layout.set_value("skip_doc", 1);
193 | this.layout.set_df_property("new_name", "read_only", 1);
194 | }
195 | }
196 | }
197 | }, {
198 | fieldtype: "Section Break"
199 | }, {
200 | label: __("New Name"),
201 | fieldname: "new_name",
202 | fieldtype: "Data",
203 | description: __("Note: System may force auto unique naming, this depend on your config"),
204 | read_only: 1
205 | }],
206 | primary_action_label: __("Apply"),
207 | primary_action: function (values) {
208 | if (this.get_value("insert_new_name") && this.get_value("new_name") === args.data[i].name) {
209 | frappe.show_alert(`${window.idfState.idfLogoUrl1} Please select a new name`);
210 | return;
211 | }
212 | resolve(values);
213 | }
214 | });
215 | exist_doc_dialog.show();
216 | exist_doc_dialog.set_value("new_name", args.data[i].name)
217 | exist_doc_dialog.set_value("info_data", `Doc: (${args.data[i].name}) Already Exist`);
218 | })
219 |
220 | cur_dialog.hide();
221 | frappe.hide_progress();
222 |
223 | if (exist_doc_values.skip_doc) {
224 | continue
225 | } else if (exist_doc_values.insert_new_name) {
226 | // args.data[i]["__newname"] = exist_doc_values.new_name;
227 | args.data[i]["name"] = exist_doc_values.new_name;
228 | }
229 | }
230 |
231 | if (values.submitted_as_draft) {
232 | args.data[i]["docstatus"] = 0;
233 | args.data[i]["status"] = null;
234 | }
235 | if (args.data[i]["docstatus"] === 2) {
236 | args.data[i]["docstatus"] = 0
237 | }
238 | let res = null;
239 | try {
240 |
241 | res = await frappe.call({
242 | method: "frappe.client.insert",
243 | args: {
244 | doc: args.data[i]
245 | }
246 | });
247 |
248 | inserted_docs.push(args.data[i]);
249 | remaining_docs.splice(i, i + 1);
250 | } catch (e) {
251 | // handle insertion errors
252 | let insertion_error_dialog_values = await new Promise((resolve, reject) => {
253 | let insertion_error_dialog = new frappe.ui.Dialog({
254 | title: __(`${window.idfState.idfLogoUrl1} Insertion Error`),
255 | fields: [
256 | {
257 | label: __("Sugessted Solutions"),
258 | fieldtype: "Section Break"
259 | }, {
260 | label: __("Skip This Doc"),
261 | fieldname: "ed_skip_doc",
262 | fieldtype: "Check",
263 | default: 1,
264 | change: function () {
265 | if (this.value) {
266 | this.layout.set_value("ed_stop_operation", 0);
267 | this.layout.set_value("ed_retry_current_operation", 0);
268 | this.layout.set_value("ed_insert_as_local_doc", 0);
269 |
270 |
271 | }
272 |
273 | }
274 | },
275 | { fieldtype: "Column Break" },
276 | {
277 | label: __("Stop the Operation"),
278 | fieldname: "ed_stop_operation",
279 | fieldtype: "Check",
280 | default: 0,
281 | change: function () {
282 | if (this.value) {
283 | this.layout.set_value("ed_skip_doc", 0);
284 | this.layout.set_value("ed_retry_current_operation", 0);
285 | this.layout.set_value("ed_insert_as_local_doc", 0);
286 |
287 | }
288 | }
289 | },
290 | { fieldtype: "Section Break" },
291 | {
292 | label: __("Try Inserting as Local Doc"),
293 | fieldname: "ed_insert_as_local_doc",
294 | fieldtype: "Check",
295 | change: function () {
296 | if (this.value) {
297 | this.layout.set_value("ed_stop_operation", 0);
298 | this.layout.set_value("ed_retry_current_operation", 0);
299 | this.layout.set_value("ed_skip_doc", 0);
300 | }
301 | }
302 | },
303 | { fieldtype: "Column Break" },
304 | {
305 | label: __("Retry Current Operation"),
306 | fieldname: "ed_retry_current_operation",
307 | fieldtype: "Check",
308 | change: function () {
309 | if (this.value) {
310 | this.layout.set_value("ed_stop_operation", 0);
311 | this.layout.set_value("ed_insert_as_local_doc", 0);
312 | this.layout.set_value("ed_skip_doc", 0);
313 | }
314 | }
315 | }, {
316 | label: __("Extra Details"),
317 | fieldtype: "Section Break"
318 | },
319 | {
320 | label: __("Error Doc"),
321 | fieldname: "ed_insertion_error_doc_name",
322 | fieldtype: "Data",
323 | read_only: 1
324 | }, {
325 | label: __("Error Reason"),
326 | fieldname: "ed_insertion_error_traceback",
327 | fieldtype: "Small Text",
328 | read_only: 1
329 | }
330 | ],
331 | primary_action_label: __("Apply"),
332 | primary_action: (values) => resolve(values)
333 | });
334 |
335 | insertion_error_dialog.show();
336 | insertion_error_dialog.wrapper.find(".form-section:nth-child(2)").
337 | css("border-top", "0px").css("margin-top", "-20px");
338 |
339 | insertion_error_dialog.add_custom_action("Help", () => {
340 | frappe.msgprint(
341 | __(`
342 | Some DocTypes depend on link fields which may not exist on your destination site,
343 | For example DocTypes amended from others or return Sales Invoices which has return aginst, etc
344 | One Solution to solve this problem is by trying to insert the required docs first which then will solve them problem.
345 |
346 |
347 | To insert the required docs data follow steps:
348 |
349 |
350 | When saving docs select the General Bucket then copy your required docs
351 | and whenever you get insertion error due to required docs select (Stop Operation)
352 | And go to the source site and save the required doc data with the options (Keep Old)
353 | & (Add to Storage Top) both selected then you can go back to the destination site
354 | and try to insert again, this time the required docs will be inserted first.
355 | `),
356 | __(`${window.idfState.idfLogoUrl1} Insertion Error`)
357 | )
358 | });
359 |
360 | setTimeout(() => {
361 | frappe.hide_progress();
362 | frappe.hide_msgprint();
363 | }, 1000);
364 |
365 | if (e.responseJSON._server_messages) {
366 | let errorMessage = JSON.parse(JSON.parse(e.responseJSON._server_messages)[0]).message
367 | insertion_error_dialog.set_value("ed_insertion_error_traceback", errorMessage);
368 | }
369 | insertion_error_dialog.set_value("ed_insertion_error_doc_name", args.data[i].name);
370 |
371 | })
372 |
373 | cur_dialog.hide();
374 |
375 | if (insertion_error_dialog_values.ed_retry_current_operation) {
376 | cur_dialog.hide();
377 | i -= 1; continue;
378 | }
379 | else if (insertion_error_dialog_values.ed_insert_as_local_doc) {
380 | let new_doc = frappe.model.copy_doc(args.data[i]);
381 |
382 | frappe.set_route("Form", new_doc.doctype, new_doc.name);
383 |
384 | frappe.show_alert(`${window.idfState.idfLogoUrl1} Inserted doc (${args.data[i].name}) As a New Local Doc`);
385 | remaining_docs.splice(i, i + 1);
386 |
387 | postMessage({
388 | eventName: "idf_cs_request__save-data",
389 | payload: {
390 | doctype: cur_list.doctype,
391 | data: remaining_docs,
392 | bucket: args.bucket
393 | }
394 | });
395 |
396 | return;
397 | } else if (insertion_error_dialog_values.ed_skip_doc) {
398 | postMessage({
399 | eventName: "idf_cs_request__save-data",
400 | payload: {
401 | doctype: cur_list.doctype,
402 | data: remaining_docs,
403 | bucket: args.bucket
404 | }
405 | });
406 | continue;
407 | } else if (insertion_error_dialog_values.ed_stop_operation) {
408 | cur_dialog.hide();
409 | return;
410 | }
411 | }
412 | }
413 |
414 | postMessage({
415 | eventName: "idf_cs_request__save-data",
416 | payload: {
417 | doctype: cur_list.doctype,
418 | data: remaining_docs,
419 | bucket: args.bucket
420 | }
421 | });
422 | if (inserted_docs.length > 0 && args.bucket == "General") {
423 | let insertion_report = "";
424 |
425 | for (let insert_doc of inserted_docs) {
426 | insertion_report += `(DocType: ${insert_doc.doctype} | DocName: ${insert_doc.name}) `;
427 | }
428 | frappe.msgprint(
429 | insertion_report,
430 | __(`${window.idfState.idfLogoUrl1} General Bucket Insert info`)
431 | )
432 | }
433 | frappe.show_alert(`${window.idfState.idfLogoUrl1} Inserted (${inserted_docs.length}) DocType Data Successfully`);
434 | }
435 | })
436 | doc_insert_dialog.show();
437 | doc_insert_dialog.set_df_property("stored_doc_name", "hidden", 1);
438 | }
439 | , args, tabId);
440 | }
441 |
442 | async function showCSVToolDialog(payload, tabId) {
443 | let args = {}
444 | idfExec((args) => {
445 | let csv_tool_dialog = new frappe.ui.Dialog({
446 | title: `${window.idfState.idfLogoUrl1} CSV Tool`,
447 | fields: [
448 | { label: __("Doctype"), fieldtype: "Data", fieldname: "current_doctype", read_only: 1, default: cur_list.doctype },
449 | { fieldtype: "Column Break" },
450 | {
451 | label: __("Get Import Template"),
452 | fieldtype: "Button",
453 | description: __("Note: Child Table Fields not supported when importing with this tool"),
454 | click: () => {
455 | frappe.new_doc("Data Import", {
456 | reference_doctype: csv_tool_dialog.get_field("current_doctype").value,
457 | import_type: __("Update Existing Records")
458 | }, () => {
459 | setTimeout(() => {
460 | cur_frm.script_manager.trigger("download_template")
461 | }, 2000)
462 | })
463 |
464 |
465 | }
466 | },
467 | { label: __("Data Import"), fieldtype: "Section Break" },
468 | { label: __("Import Type"), fieldtype: "Select", options: ["Create Records", "Update Records"], default: "Update Records", read_only: 1 },
469 | {
470 | fieldtype: "HTML",
471 | fieldname: "file_type",
472 | options: `
473 |
474 | CSV File
475 |
476 |
477 | `
478 | },
479 | ],
480 | primary_action_label: `${window.idfState.idfLogoUrl1} Apply`,
481 | primary_action: async function (values) {
482 | csv_tool_dialog.hide();
483 | let csvFile = document.querySelector("#formFileLg").files[0];
484 | postMessage({
485 | eventName: "idf_cs_request__csv-tool-read_file",
486 | payload: {
487 | doctype: cur_list.doctype,
488 | docfields: frappe.meta.get_docfields(cur_list.doctype).map(df => {
489 | return { label: df.label, fieldname: df.fieldname }
490 | }),
491 | file: csvFile
492 | }
493 | });
494 | frappe.show_alert(`${window.idfState.idfLogoUrl1} in progress, pelase wait...`);
495 | }
496 | })
497 | csv_tool_dialog.show();
498 | }
499 | , args, tabId);
500 | }
501 |
502 | async function handleCSVToolBulkUpdate(docs, tabId) {
503 | const args = { docs: docs }
504 |
505 | idfExec((args) => {
506 | frappe.call({
507 | method: "frappe.client.bulk_update",
508 | args: {
509 | docs: args.docs
510 | },
511 | callback: function (r) {
512 | console.log(r.message);
513 | frappe.show_alert(`${window.idfState.idfLogoUrl1} Imports (${args.docs.length}), Fails(${r.message.failed_docs.length})`);
514 | }
515 | })
516 | }
517 | , args, tabId);
518 |
519 | }
520 |
521 | async function saveChildTableData(payload) {
522 | await chrome.storage.local.set({
523 | "storage__childtable_data": payload
524 | });
525 | }
526 |
527 | async function insertChildtableData(fieldname, tabId) {
528 | var childData = await chrome.storage.local.get('storage__childtable_data');
529 | var args = {
530 | fieldname,
531 | childData: childData.storage__childtable_data
532 | }
533 |
534 | idfExec((args) => {
535 | cur_frm.clear_table(args.fieldname);
536 | for (row of args.childData) {
537 | // skip insertion of fields
538 | delete row["creation"];
539 | delete row["modified"];
540 | delete row["modified_by"];
541 | delete row["name"];
542 | delete row["owner"];
543 | delete row["parent"];
544 | delete row["parentfield"];
545 | delete row["parenttype"];
546 |
547 | cur_frm.add_child(args.fieldname, row);
548 | }
549 | cur_frm.refresh_field(args.fieldname);
550 | }
551 | , args, tabId);
552 | }
553 |
554 | // Customization
555 | async function saveCustomizedFields(payload) {
556 | await chrome.storage.local.set({
557 | "storage__customized_fields_data": payload
558 | });
559 | }
560 |
561 | async function insertCustomizedFields(tabId) {
562 | var savedData = await chrome.storage.local.get('storage__customized_fields_data');
563 | var args = {
564 | savedData: savedData.storage__customized_fields_data
565 | }
566 |
567 | idfExec((args) => {
568 | let rows = args.savedData;
569 |
570 | for (let i = 0; i < rows.length; i++) {
571 | delete rows[i]["name"];
572 |
573 | let nr = cur_frm.add_child("fields", rows[i]);
574 | let new_index = cur_frm.doc.fields.findIndex(o => o.fieldname === rows[i].insert_after_fieldname) + 1
575 | let new_row = cur_frm.doc.fields.splice(cur_frm.doc.fields.length - 1, 1);
576 |
577 | cur_frm.doc.fields.splice(new_index, 0, new_row[0]);
578 | nr.idx = new_index + 1;
579 |
580 | // update custom rows highlights
581 | for (let i = 0; i < cur_frm.doc.fields.length; i++) {
582 | if (cur_frm.doc.fields[i].is_custom_field === 1) {
583 | let grid_rows = cur_frm.grids[0].wrapper.querySelector(".form-grid > .grid-body > .rows");
584 | if (grid_rows.childNodes[i]) {
585 | grid_rows.childNodes[i].firstChild.classList.add("highlight");
586 | }
587 | }
588 | }
589 | }
590 | cur_frm.refresh_fields();
591 | }
592 | , args, tabId);
593 | }
594 |
595 | chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
596 | if (changeInfo.status == 'complete' && tab.active) {
597 | let args = {}
598 | args.idfLogoUrl1 = `
599 | `;
601 | args.idfLogoUrl2 = `
602 | `;
603 |
604 | idfExec((args) => {
605 | // init idf state
606 | if (!window["idfState"]) {
607 | window["idfState"] = {
608 | pageInit: false,
609 | idfLogoUrl1: args.idfLogoUrl1,
610 | idfLogoUrl2: args.idfLogoUrl2
611 | }
612 | }
613 |
614 | // perfect & official way to detect frappe form events and run scripts
615 | // patch script manager to listen for frappe forms onload, refresh etc
616 | if (!frappe.ui.form.ScriptManager) return;
617 |
618 | if (!window.idfState.pageInit) {
619 | window.idfState.pageInit = true;
620 |
621 | // hooking frappe script manager to add forms scripting features
622 | let oriTrigger = frappe.ui.form.ScriptManager.prototype.trigger;
623 | frappe.ui.form.ScriptManager.prototype.trigger = function (...args) {
624 | setTimeout(() => {
625 | postMessage({
626 | eventName: "idf_cs_request__form_trigger",
627 | payload: args
628 | });
629 | }
630 | , 100);
631 | return oriTrigger.call(this, ...args);
632 | }
633 | // hooking frappe setup_view to add listview scripting features
634 | let oriViewSetup = frappe.views.ListView.prototype.setup_view;
635 | frappe.views.ListView.prototype.setup_view = function (...args) {
636 | setTimeout(() => {
637 | postMessage({
638 | eventName: "idf_cs_request__listview_setup",
639 | payload: args
640 | });
641 | }
642 | , 500);
643 | return oriViewSetup.call(this, ...args);
644 | }
645 | }
646 | },
647 | args, tab.id);
648 | }
649 | })
650 |
651 | // listen for content script messages
652 | chrome.runtime.onMessage.addListener(async (event, sender, sendResponse) => {
653 | const tabId = sender.tab.id;
654 | // console.log("BG: ", event.eventName);
655 | switch (event.eventName) {
656 | case "idf_bg_request__form_trigger":
657 | formTrigger(tabId, ...event.payload);
658 | break;
659 | case "idf_bg_request__listview_setup":
660 | listviewSetup(tabId);
661 | break;
662 | case "idf_bg_request__show_options_dialog":
663 | idfShowOptionsDialog(event.payload, tabId);
664 | break;
665 | case "idf_bg_request__save-data":
666 | saveDocData(
667 | event.payload.doctype,
668 | event.payload.data,
669 | event.payload.bucket,
670 | event.payload.keepOld,
671 | event.payload.addToTop
672 | );
673 | break;
674 | case "idf_bg_request__listview_show-insert-doc-data-dialog":
675 | showInsertDocDataDialog(event.payload.doctype, event.payload.bucket, tabId);
676 | break;
677 | case "idf_bg_request__listview_show-csv-tool-dialog":
678 | showCSVToolDialog(event.payload, tabId);
679 | break;
680 | case "idf_bg_request__csv-tool-bulk_update":
681 | handleCSVToolBulkUpdate(event.payload, tabId);
682 | case "idf_bg_request__childtable_save":
683 | saveChildTableData(event.payload);
684 | break;
685 | case "idf_bg_request__childtable_insert":
686 | insertChildtableData(event.payload, tabId);
687 | break;
688 | case "idf_bg_request__customized_fields_save":
689 | saveCustomizedFields(event.payload);
690 | break;
691 | case "idf_bg_request__customized_fields_insert":
692 | insertCustomizedFields(tabId);
693 | break;
694 | }
695 | });
696 |
697 | // execute functions in page
698 | async function idfExec(handler, args, tabId) {
699 | const ret = await chrome.scripting.executeScript({
700 | target: {
701 | tabId: tabId
702 | },
703 | func: handler,
704 | args: [args],
705 | world: "MAIN"
706 | });
707 | return ret;
708 | }
709 |
--------------------------------------------------------------------------------