frappe.pages['bom-comparison-tool'].on_page_load = function(wrapper) { var page = frappe.ui.make_app_page({ parent: wrapper, title: __('BOM Comparison Tool'), single_column: true }); new erpnext.BOMComparisonTool(page); } erpnext.BOMComparisonTool = class BOMComparisonTool { constructor(page) { this.page = page; this.make_form(); } make_form() { this.form = new frappe.ui.FieldGroup({ fields: [ { label: __('BOM 1'), fieldname: 'name1', fieldtype: 'Link', options: 'BOM', change: () => this.fetch_and_render(), get_query: () => { return { filters: { "name": ["not in", [this.form.get_value("name2") || ""]] } } } }, { fieldtype: 'Column Break' }, { label: __('BOM 2'), fieldname: 'name2', fieldtype: 'Link', options: 'BOM', change: () => this.fetch_and_render(), get_query: () => { return { filters: { "name": ["not in", [this.form.get_value("name1") || ""]] } } } }, { fieldtype: 'Section Break' }, { fieldtype: 'HTML', fieldname: 'preview' } ], body: this.page.body }); this.form.make(); } fetch_and_render() { let { name1, name2 } = this.form.get_values(); if (!(name1 && name2)) { this.form.get_field('preview').html(''); return; } // set working state this.form.get_field('preview').html(`
${__("Fetching...")}
`); frappe.call('erpnext.manufacturing.doctype.bom.bom.get_bom_diff', { bom1: name1, bom2: name2 }).then(r => { let diff = r.message; frappe.model.with_doctype('BOM', () => { this.render('BOM', name1, name2, diff); }); }); } render(doctype, name1, name2, diff) { let change_html = (title, doctype, changed) => { let values_changed = this.get_changed_values(doctype, changed) .map(change => { let [fieldname, value1, value2] = change; return ` ${frappe.meta.get_label(doctype, fieldname)} ${value1} ${value2} `; }) .join(''); return `

${title}

${values_changed}
${__('Field')} ${name1} ${name2}
`; } let value_changes = change_html(__('Values Changed'), doctype, diff.changed); let row_changes_by_fieldname = group_items(diff.row_changed, change => change[0]); let table_changes = Object.keys(row_changes_by_fieldname).map(fieldname => { let changes = row_changes_by_fieldname[fieldname]; let df = frappe.meta.get_docfield(doctype, fieldname); let html = changes.map(change => { let [fieldname,, item_code, changes] = change; let df = frappe.meta.get_docfield(doctype, fieldname); let child_doctype = df.options; let values_changed = this.get_changed_values(child_doctype, changes); return values_changed.map((change, i) => { let [fieldname, value1, value2] = change; let th = i === 0 ? `${item_code}` : ''; return ` ${th} ${frappe.meta.get_label(child_doctype, fieldname)} ${value1} ${value2} `; }).join(''); }).join(''); return `

${__('Changes in {0}', [df.label])}

${html}
${__('Item Code')} ${__('Field')} ${name1} ${name2}
`; }).join(''); let get_added_removed_html = (title, grouped_items) => { return Object.keys(grouped_items).map(fieldname => { let rows = grouped_items[fieldname]; let df = frappe.meta.get_docfield(doctype, fieldname); let fields = frappe.meta.get_docfields(df.options) .filter(df => df.in_list_view); let html = rows.map(row => { let [, doc] = row; let cells = fields .map(df => `${doc[df.fieldname]}`) .join(''); return `${cells}`; }).join(''); let header = fields.map(df => `${df.label}`).join(''); return `

${$.format(title, [df.label])}

${header} ${html}
`; }).join(''); }; let added_by_fieldname = group_items(diff.added, change => change[0]); let removed_by_fieldname = group_items(diff.removed, change => change[0]); let added_html = get_added_removed_html(__('Rows Added in {0}'), added_by_fieldname); let removed_html = get_added_removed_html(__('Rows Removed in {0}'), removed_by_fieldname); let html = ` ${value_changes} ${table_changes} ${added_html} ${removed_html} `; this.form.get_field('preview').html(html); } get_changed_values(doctype, changed) { return changed.filter(change => { let [fieldname, value1, value2] = change; if (!value1) value1 = ''; if (!value2) value2 = ''; if (value1 === value2) return false; let df = frappe.meta.get_docfield(doctype, fieldname); if (!df) return false; if (df.hidden) return false; return true; }); } }; function group_items(array, fn) { return array.reduce((acc, item) => { let key = fn(item); acc[key] = acc[key] || []; acc[key].push(item); return acc; }, {}); }