feat: BOM Comparison Tool
This commit is contained in:
parent
de1ecee3b3
commit
1214e2d2a4
@ -9,6 +9,7 @@ from erpnext.setup.utils import get_exchange_rate
|
|||||||
from frappe.website.website_generator import WebsiteGenerator
|
from frappe.website.website_generator import WebsiteGenerator
|
||||||
from erpnext.stock.get_item_details import get_conversion_factor
|
from erpnext.stock.get_item_details import get_conversion_factor
|
||||||
from erpnext.stock.get_item_details import get_price_list_rate
|
from erpnext.stock.get_item_details import get_price_list_rate
|
||||||
|
from frappe.core.doctype.version.version import get_diff
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
@ -763,3 +764,52 @@ def add_additional_cost(stock_entry, work_order):
|
|||||||
'description': name[0],
|
'description': name[0],
|
||||||
'amount': items.get(name[0])
|
'amount': items.get(name[0])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_bom_diff(bom1, bom2):
|
||||||
|
from frappe.model import table_fields
|
||||||
|
|
||||||
|
doc1 = frappe.get_doc('BOM', bom1)
|
||||||
|
doc2 = frappe.get_doc('BOM', bom2)
|
||||||
|
|
||||||
|
out = get_diff(doc1, doc2)
|
||||||
|
out.row_changed = []
|
||||||
|
out.added = []
|
||||||
|
out.removed = []
|
||||||
|
|
||||||
|
meta = doc1.meta
|
||||||
|
|
||||||
|
identifiers = {
|
||||||
|
'operations': 'operation',
|
||||||
|
'items': 'item_code',
|
||||||
|
'scrap_items': 'item_code',
|
||||||
|
'exploded_items': 'item_code'
|
||||||
|
}
|
||||||
|
|
||||||
|
for df in meta.fields:
|
||||||
|
old_value, new_value = doc1.get(df.fieldname), doc2.get(df.fieldname)
|
||||||
|
|
||||||
|
if df.fieldtype in table_fields:
|
||||||
|
identifier = identifiers[df.fieldname]
|
||||||
|
# make maps
|
||||||
|
old_row_by_identifier, new_row_by_identifier = {}, {}
|
||||||
|
for d in old_value:
|
||||||
|
old_row_by_identifier[d.get(identifier)] = d
|
||||||
|
for d in new_value:
|
||||||
|
new_row_by_identifier[d.get(identifier)] = d
|
||||||
|
|
||||||
|
# check rows for additions, changes
|
||||||
|
for i, d in enumerate(new_value):
|
||||||
|
if d.get(identifier) in old_row_by_identifier:
|
||||||
|
diff = get_diff(old_row_by_identifier[d.get(identifier)], d, for_child=True)
|
||||||
|
if diff and diff.changed:
|
||||||
|
out.row_changed.append((df.fieldname, i, d.get(identifier), diff.changed))
|
||||||
|
else:
|
||||||
|
out.added.append([df.fieldname, d.as_dict()])
|
||||||
|
|
||||||
|
# check for deletions
|
||||||
|
for d in old_value:
|
||||||
|
if not d.get(identifier) in new_row_by_identifier:
|
||||||
|
out.removed.append([df.fieldname, d.as_dict()])
|
||||||
|
|
||||||
|
return out
|
||||||
|
|||||||
@ -0,0 +1,218 @@
|
|||||||
|
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()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: 'Column Break'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('BOM 2'),
|
||||||
|
fieldname: 'name2',
|
||||||
|
fieldtype: 'Link',
|
||||||
|
options: 'BOM',
|
||||||
|
change: () => this.fetch_and_render()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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(`
|
||||||
|
<div class="text-muted margin-top">
|
||||||
|
${__("Fetching...")}
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
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 `
|
||||||
|
<tr>
|
||||||
|
<td>${frappe.meta.get_label(doctype, fieldname)}</td>
|
||||||
|
<td>${value1}</td>
|
||||||
|
<td>${value2}</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
})
|
||||||
|
.join('');
|
||||||
|
|
||||||
|
return `
|
||||||
|
<h4 class="margin-top">${title}</h4>
|
||||||
|
<div>
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<tr>
|
||||||
|
<th width="33%">${__('Field')}</th>
|
||||||
|
<th width="33%">${name1}</th>
|
||||||
|
<th width="33%">${name2}</th>
|
||||||
|
</tr>
|
||||||
|
${values_changed}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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, idx, 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;
|
||||||
|
return `
|
||||||
|
<tr>
|
||||||
|
${i === 0
|
||||||
|
? `<th rowspan="${values_changed.length}">${item_code}</th>`
|
||||||
|
: ''}
|
||||||
|
<td>${frappe.meta.get_label(child_doctype, fieldname)}</td>
|
||||||
|
<td>${value1}</td>
|
||||||
|
<td>${value2}</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
return `
|
||||||
|
<h4 class="margin-top">${__('Changes in {0}', [df.label])}</h4>
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<tr>
|
||||||
|
<th width="25%">${__('Item Code')}</th>
|
||||||
|
<th width="25%">${__('Field')}</th>
|
||||||
|
<th width="25%">${name1}</th>
|
||||||
|
<th width="25%">${name2}</th>
|
||||||
|
</tr>
|
||||||
|
${html}
|
||||||
|
</table>
|
||||||
|
`;
|
||||||
|
}).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;
|
||||||
|
return `
|
||||||
|
<tr>
|
||||||
|
<tr>
|
||||||
|
${fields.map(df => {
|
||||||
|
return `<td>${doc[df.fieldname]}</td>`
|
||||||
|
}).join('')}
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
return `
|
||||||
|
<h4 class="margin-top">${$.format(title, [df.label])}</h4>
|
||||||
|
<table class="table table-bordered">
|
||||||
|
${fields.map(df => {
|
||||||
|
return `<th>${df.label}</th>`
|
||||||
|
}).join('')}
|
||||||
|
</tr>
|
||||||
|
${html}
|
||||||
|
</table>
|
||||||
|
`;
|
||||||
|
}).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;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"content": null,
|
||||||
|
"creation": "2019-07-29 13:24:38.201981",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Page",
|
||||||
|
"idx": 0,
|
||||||
|
"modified": "2019-07-29 13:24:38.201981",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Manufacturing",
|
||||||
|
"name": "bom-comparison-tool",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"page_name": "BOM Comparison Tool",
|
||||||
|
"restrict_to_domain": "Manufacturing",
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "System Manager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Manufacturing User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Manufacturing Manager"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"script": null,
|
||||||
|
"standard": "Yes",
|
||||||
|
"style": null,
|
||||||
|
"system_page": 0,
|
||||||
|
"title": "BOM Comparison Tool"
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user