Merge branch 'version-12-hotfix' into version-12

This commit is contained in:
Sahil Khan 2019-08-07 16:21:14 +05:30
commit 9335cdd536
25 changed files with 1405 additions and 2555 deletions

View File

@ -5,7 +5,7 @@ import frappe
from erpnext.hooks import regional_overrides from erpnext.hooks import regional_overrides
from frappe.utils import getdate from frappe.utils import getdate
__version__ = '12.0.4' __version__ = '12.0.5'
def get_default_company(user=None): def get_default_company(user=None):
'''Get default company for user''' '''Get default company for user'''

View File

@ -6,16 +6,10 @@ from frappe import _
def get_data(): def get_data():
return { return {
'fieldname': 'bank', 'fieldname': 'bank',
'non_standard_fieldnames': {
'Paymnet Order': 'company_bank'
},
'transactions': [ 'transactions': [
{ {
'label': _('Bank Deatils'), 'label': _('Bank Deatils'),
'items': ['Bank Account', 'Bank Guarantee'] 'items': ['Bank Account', 'Bank Guarantee']
},
{
'items': ['Payment Order']
} }
] ]
} }

View File

@ -648,13 +648,18 @@ def get_orders_to_be_billed(posting_date, party_type, party,
orders = [] orders = []
if voucher_type: if voucher_type:
ref_field = "base_grand_total" if party_account_currency == company_currency else "grand_total" if party_account_currency == company_currency:
grand_total_field = "base_grand_total"
rounded_total_field = "base_rounded_total"
else:
grand_total_field = "grand_total"
rounded_total_field = "rounded_total"
orders = frappe.db.sql(""" orders = frappe.db.sql("""
select select
name as voucher_no, name as voucher_no,
{ref_field} as invoice_amount, if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) as invoice_amount,
({ref_field} - advance_paid) as outstanding_amount, (if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) - advance_paid) as outstanding_amount,
transaction_date as posting_date transaction_date as posting_date
from from
`tab{voucher_type}` `tab{voucher_type}`
@ -663,13 +668,14 @@ def get_orders_to_be_billed(posting_date, party_type, party,
and docstatus = 1 and docstatus = 1
and company = %s and company = %s
and ifnull(status, "") != "Closed" and ifnull(status, "") != "Closed"
and {ref_field} > advance_paid and if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) > advance_paid
and abs(100 - per_billed) > 0.01 and abs(100 - per_billed) > 0.01
{condition} {condition}
order by order by
transaction_date, name transaction_date, name
""".format(**{ """.format(**{
"ref_field": ref_field, "rounded_total_field": rounded_total_field,
"grand_total_field": grand_total_field,
"voucher_type": voucher_type, "voucher_type": voucher_type,
"party_type": scrub(party_type), "party_type": scrub(party_type),
"condition": condition "condition": condition

View File

@ -382,7 +382,7 @@ def get_qty_amount_data_for_cumulative(pr_doc, doc, items=[]):
`tab{child_doc}`.amount `tab{child_doc}`.amount
FROM `tab{child_doc}`, `tab{parent_doc}` FROM `tab{child_doc}`, `tab{parent_doc}`
WHERE WHERE
`tab{child_doc}`.parent = `tab{parent_doc}`.name and {date_field} `tab{child_doc}`.parent = `tab{parent_doc}`.name and `tab{parent_doc}`.{date_field}
between %s and %s and `tab{parent_doc}`.docstatus = 1 between %s and %s and `tab{parent_doc}`.docstatus = 1
{condition} group by `tab{child_doc}`.name {condition} group by `tab{child_doc}`.name
""".format(parent_doc = doctype, """.format(parent_doc = doctype,

View File

@ -93,6 +93,7 @@ def check_if_in_list(gle, gl_map, dimensions=None):
def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False): def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
if not from_repost: if not from_repost:
validate_account_for_perpetual_inventory(gl_map) validate_account_for_perpetual_inventory(gl_map)
validate_cwip_accounts(gl_map)
round_off_debit_credit(gl_map) round_off_debit_credit(gl_map)
@ -123,6 +124,16 @@ def validate_account_for_perpetual_inventory(gl_map):
frappe.throw(_("Account: {0} can only be updated via Stock Transactions") frappe.throw(_("Account: {0} can only be updated via Stock Transactions")
.format(entry.account), StockAccountInvalidTransaction) .format(entry.account), StockAccountInvalidTransaction)
def validate_cwip_accounts(gl_map):
if not cint(frappe.db.get_value("Asset Settings", None, "disable_cwip_accounting")) \
and gl_map[0].voucher_type == "Journal Entry":
cwip_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount
where account_type = 'Capital Work in Progress' and is_group=0""")]
for entry in gl_map:
if entry.account in cwip_accounts:
frappe.throw(_("Account: <b>{0}</b> is capital Work in progress and can not be updated by Journal Entry").format(entry.account))
def round_off_debit_credit(gl_map): def round_off_debit_credit(gl_map):
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency")) currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency"))

View File

@ -30,7 +30,9 @@ def update_last_purchase_rate(doc, is_submit):
# for it to be considered for latest purchase rate # for it to be considered for latest purchase rate
if flt(d.conversion_factor): if flt(d.conversion_factor):
last_purchase_rate = flt(d.base_rate) / flt(d.conversion_factor) last_purchase_rate = flt(d.base_rate) / flt(d.conversion_factor)
else: # Check if item code is present
# Conversion factor should not be mandatory for non itemized items
elif d.item_code:
frappe.throw(_("UOM Conversion factor is required in row {0}").format(d.idx)) frappe.throw(_("UOM Conversion factor is required in row {0}").format(d.idx))
# update last purchsae rate # update last purchsae rate

View File

@ -94,6 +94,13 @@ def get_data():
"name": "BOM Update Tool", "name": "BOM Update Tool",
"description": _("Replace BOM and update latest price in all BOMs"), "description": _("Replace BOM and update latest price in all BOMs"),
}, },
{
"type": "page",
"label": _("BOM Comparison Tool"),
"name": "bom-comparison-tool",
"description": _("Compare BOMs for changes in Raw Materials and Operations"),
"data_doctype": "BOM"
},
] ]
}, },
{ {

View File

@ -1192,6 +1192,10 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
.format(child_item.idx, child_item.item_code)) .format(child_item.idx, child_item.item_code))
else: else:
child_item.rate = flt(d.get("rate")) child_item.rate = flt(d.get("rate"))
if child_item.price_list_rate:
child_item.discount_percentage = flt((1 - flt(child_item.rate) / flt(child_item.price_list_rate)) * 100.0, \
child_item.precision("discount_percentage"))
child_item.flags.ignore_validate_update_after_submit = True child_item.flags.ignore_validate_update_after_submit = True
if new_child_flag: if new_child_flag:
child_item.idx = len(parent.items) + 1 child_item.idx = len(parent.items) + 1

View File

@ -395,7 +395,9 @@ class BuyingController(StockController):
def set_qty_as_per_stock_uom(self): def set_qty_as_per_stock_uom(self):
for d in self.get("items"): for d in self.get("items"):
if d.meta.get_field("stock_qty"): if d.meta.get_field("stock_qty"):
if not d.conversion_factor: # Check if item code is present
# Conversion factor should not be mandatory for non itemized items
if not d.conversion_factor and d.item_code:
frappe.throw(_("Row {0}: Conversion Factor is mandatory").format(d.idx)) frappe.throw(_("Row {0}: Conversion Factor is mandatory").format(d.idx))
d.stock_qty = flt(d.qty) * flt(d.conversion_factor) d.stock_qty = flt(d.qty) * flt(d.conversion_factor)

View File

@ -82,7 +82,7 @@ class calculate_taxes_and_totals(object):
item.net_rate = item.rate item.net_rate = item.rate
if not item.qty and self.doc.is_return: if not item.qty and self.doc.get("is_return"):
item.amount = flt(-1 * item.rate, item.precision("amount")) item.amount = flt(-1 * item.rate, item.precision("amount"))
else: else:
item.amount = flt(item.rate * item.qty, item.precision("amount")) item.amount = flt(item.rate * item.qty, item.precision("amount"))

View File

@ -130,6 +130,11 @@ def get_customers_suppliers(doctype, user):
suppliers = [] suppliers = []
meta = frappe.get_meta(doctype) meta = frappe.get_meta(doctype)
customer_field_name = get_customer_field_name(doctype)
has_customer_field = meta.has_field(customer_field_name)
has_supplier_field = meta.has_field('supplier')
if has_common(["Supplier", "Customer"], frappe.get_roles(user)): if has_common(["Supplier", "Customer"], frappe.get_roles(user)):
contacts = frappe.db.sql(""" contacts = frappe.db.sql("""
select select
@ -141,27 +146,40 @@ def get_customers_suppliers(doctype, user):
where where
`tabContact`.name=`tabDynamic Link`.parent and `tabContact`.email_id =%s `tabContact`.name=`tabDynamic Link`.parent and `tabContact`.email_id =%s
""", user, as_dict=1) """, user, as_dict=1)
customers = [c.link_name for c in contacts if c.link_doctype == 'Customer'] \ customers = [c.link_name for c in contacts if c.link_doctype == 'Customer']
if meta.get_field("customer") else None suppliers = [c.link_name for c in contacts if c.link_doctype == 'Supplier']
suppliers = [c.link_name for c in contacts if c.link_doctype == 'Supplier'] \
if meta.get_field("supplier") else None
elif frappe.has_permission(doctype, 'read', user=user): elif frappe.has_permission(doctype, 'read', user=user):
customers = [customer.name for customer in frappe.get_list("Customer")] \ customer_list = frappe.get_list("Customer")
if meta.get_field("customer") else None customers = suppliers = [customer.name for customer in customer_list]
suppliers = [supplier.name for supplier in frappe.get_list("Customer")] \
if meta.get_field("supplier") else None
return customers, suppliers return customers if has_customer_field else None, \
suppliers if has_supplier_field else None
def has_website_permission(doc, ptype, user, verbose=False): def has_website_permission(doc, ptype, user, verbose=False):
doctype = doc.doctype doctype = doc.doctype
customers, suppliers = get_customers_suppliers(doctype, user) customers, suppliers = get_customers_suppliers(doctype, user)
if customers: if customers:
return frappe.get_all(doctype, filters=[(doctype, "customer", "in", customers), return frappe.db.exists(doctype, filters=get_customer_filter(doc, customers))
(doctype, "name", "=", doc.name)]) and True or False
elif suppliers: elif suppliers:
fieldname = 'suppliers' if doctype == 'Request for Quotation' else 'supplier' fieldname = 'suppliers' if doctype == 'Request for Quotation' else 'supplier'
return frappe.get_all(doctype, filters=[(doctype, fieldname, "in", suppliers), return frappe.db.exists(doctype, filters={
(doctype, "name", "=", doc.name)]) and True or False 'name': doc.name,
fieldname: ["in", suppliers]
})
else: else:
return False return False
def get_customer_filter(doc, customers):
doctype = doc.doctype
filters = frappe._dict()
filters.name = doc.name
filters[get_customer_field_name(doctype)] = ['in', customers]
if doctype == 'Quotation':
filters.party_type = 'Customer'
return filters
def get_customer_field_name(doctype):
if doctype == 'Quotation':
return 'party_name'
else:
return 'customer'

View File

@ -64,13 +64,20 @@ class EmployeeAdvance(Document):
def update_claimed_amount(self): def update_claimed_amount(self):
claimed_amount = frappe.db.sql(""" claimed_amount = frappe.db.sql("""
select sum(ifnull(allocated_amount, 0)) SELECT sum(ifnull(allocated_amount, 0))
from `tabExpense Claim Advance` FROM `tabExpense Claim Advance` eca, `tabExpense Claim` ec
where employee_advance = %s and docstatus=1 and allocated_amount > 0 WHERE
eca.employee_advance = %s
AND ec.approval_status="Approved"
AND ec.name = eca.parent
AND ec.docstatus=1
AND eca.allocated_amount > 0
""", self.name)[0][0] or 0 """, self.name)[0][0] or 0
if claimed_amount:
frappe.db.set_value("Employee Advance", self.name, "claimed_amount", flt(claimed_amount)) frappe.db.set_value("Employee Advance", self.name, "claimed_amount", flt(claimed_amount))
self.reload()
self.set_status()
frappe.db.set_value("Employee Advance", self.name, "status", self.status)
@frappe.whitelist() @frappe.whitelist()
def get_due_advance_amount(employee, posting_date): def get_due_advance_amount(employee, posting_date):

View File

@ -183,7 +183,7 @@ frappe.ui.form.on("Expense Claim", {
refresh: function(frm) { refresh: function(frm) {
frm.trigger("toggle_fields"); frm.trigger("toggle_fields");
if(frm.doc.docstatus == 1) { if(frm.doc.docstatus === 1 && frm.doc.approval_status !== "Rejected") {
frm.add_custom_button(__('Accounting Ledger'), function() { frm.add_custom_button(__('Accounting Ledger'), function() {
frappe.route_options = { frappe.route_options = {
voucher_no: frm.doc.name, voucher_no: frm.doc.name,
@ -194,7 +194,7 @@ frappe.ui.form.on("Expense Claim", {
}, __("View")); }, __("View"));
} }
if (frm.doc.docstatus===1 if (frm.doc.docstatus===1 && !cint(frm.doc.is_paid) && cint(frm.doc.grand_total) > 0
&& (cint(frm.doc.total_amount_reimbursed) < cint(frm.doc.total_sanctioned_amount)) && (cint(frm.doc.total_amount_reimbursed) < cint(frm.doc.total_sanctioned_amount))
&& frappe.model.can_create("Payment Entry")) { && frappe.model.can_create("Payment Entry")) {
frm.add_custom_button(__('Payment'), frm.add_custom_button(__('Payment'),

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -320,7 +320,8 @@ class ProductionPlan(Document):
'qty': data.get("stock_qty") * item.get("qty"), 'qty': data.get("stock_qty") * item.get("qty"),
'production_plan': self.name, 'production_plan': self.name,
'company': self.company, 'company': self.company,
'fg_warehouse': item.get("fg_warehouse") 'fg_warehouse': item.get("fg_warehouse"),
'update_consumed_material_cost_in_project': 0
}) })
work_order = self.create_work_order(data) work_order = self.create_work_order(data)
@ -430,7 +431,7 @@ def download_raw_materials(production_plan):
continue continue
item_list.append(['', '', '', '', bin_dict.get('warehouse'), item_list.append(['', '', '', '', bin_dict.get('warehouse'),
bin_dict.get('projected_qty'), bin_dict.get('actual_qty')]) bin_dict.get('projected_qty', 0), bin_dict.get('actual_qty', 0)])
build_csv_response(item_list, doc.name) build_csv_response(item_list, doc.name)
@ -507,8 +508,8 @@ def get_material_request_items(row, sales_order,
required_qty = 0 required_qty = 0
if ignore_existing_ordered_qty or bin_dict.get("projected_qty", 0) < 0: if ignore_existing_ordered_qty or bin_dict.get("projected_qty", 0) < 0:
required_qty = total_qty required_qty = total_qty
elif total_qty > bin_dict.get("projected_qty"): elif total_qty > bin_dict.get("projected_qty", 0):
required_qty = total_qty - bin_dict.get("projected_qty") required_qty = total_qty - bin_dict.get("projected_qty", 0)
if required_qty > 0 and required_qty < row['min_order_qty']: if required_qty > 0 and required_qty < row['min_order_qty']:
required_qty = row['min_order_qty'] required_qty = row['min_order_qty']
item_group_defaults = get_item_group_defaults(row.item_code, company) item_group_defaults = get_item_group_defaults(row.item_code, company)

View File

@ -4,6 +4,7 @@
"creation": "2013-01-10 16:34:16", "creation": "2013-01-10 16:34:16",
"doctype": "DocType", "doctype": "DocType",
"document_type": "Setup", "document_type": "Setup",
"engine": "InnoDB",
"field_order": [ "field_order": [
"item", "item",
"naming_series", "naming_series",
@ -12,9 +13,6 @@
"item_name", "item_name",
"image", "image",
"bom_no", "bom_no",
"allow_alternative_item",
"use_multi_level_bom",
"skip_transfer",
"column_break1", "column_break1",
"company", "company",
"qty", "qty",
@ -22,7 +20,13 @@
"produced_qty", "produced_qty",
"sales_order", "sales_order",
"project", "project",
"settings_section",
"allow_alternative_item",
"use_multi_level_bom",
"column_break_18",
"skip_transfer",
"from_wip_warehouse", "from_wip_warehouse",
"update_consumed_material_cost_in_project",
"warehouses", "warehouses",
"wip_warehouse", "wip_warehouse",
"fg_warehouse", "fg_warehouse",
@ -442,13 +446,28 @@
"oldfieldtype": "Data", "oldfieldtype": "Data",
"options": "Work Order", "options": "Work Order",
"read_only": 1 "read_only": 1
},
{
"fieldname": "settings_section",
"fieldtype": "Section Break",
"label": "Settings"
},
{
"fieldname": "column_break_18",
"fieldtype": "Column Break"
},
{
"default": "1",
"fieldname": "update_consumed_material_cost_in_project",
"fieldtype": "Check",
"label": "Update Consumed Material Cost In Project"
} }
], ],
"icon": "fa fa-cogs", "icon": "fa fa-cogs",
"idx": 1, "idx": 1,
"image_field": "image", "image_field": "image",
"is_submittable": 1, "is_submittable": 1,
"modified": "2019-05-27 09:36:16.707719", "modified": "2019-07-31 00:13:38.218277",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Work Order", "name": "Work Order",
@ -477,6 +496,7 @@
"role": "Stock User" "role": "Stock User"
} }
], ],
"sort_field": "modified",
"sort_order": "ASC", "sort_order": "ASC",
"title_field": "production_item", "title_field": "production_item",
"track_changes": 1, "track_changes": 1,

View File

@ -0,0 +1,213 @@
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,, 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
? `<th rowspan="${values_changed.length}">${item_code}</th>`
: '';
return `
<tr>
${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;
let cells = fields
.map(df => `<td>${doc[df.fieldname]}</td>`)
.join('');
return `<tr>${cells}</tr>`;
}).join('');
let header = fields.map(df => `<th>${df.label}</th>`).join('');
return `
<h4 class="margin-top">${$.format(title, [df.label])}</h4>
<table class="table table-bordered">
<tr>${header}</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;
}, {});
}

View File

@ -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"
}

View File

@ -60,7 +60,15 @@ $.extend(erpnext, {
var me = this; var me = this;
$btn.on("click", function() { $btn.on("click", function() {
me.show_serial_batch_selector(grid_row.frm, grid_row.doc); let callback = '';
let on_close = '';
if (grid_row.doc.serial_no) {
grid_row.doc.has_serial_no = true;
}
me.show_serial_batch_selector(grid_row.frm, grid_row.doc,
callback, on_close, true);
}); });
}, },
}); });

View File

@ -255,27 +255,44 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
}); });
return; return;
} else { } else {
var fields = [ const fields = [{
{fieldtype:'Table', fieldname: 'items', label: 'Items',
fieldtype: 'Table',
fieldname: 'items',
description: __('Select BOM and Qty for Production'), description: __('Select BOM and Qty for Production'),
fields: [ fields: [{
{fieldtype:'Read Only', fieldname:'item_code', fieldtype: 'Read Only',
label: __('Item Code'), in_list_view:1}, fieldname: 'item_code',
{fieldtype:'Link', fieldname:'bom', options: 'BOM', reqd: 1, label: __('Item Code'),
label: __('Select BOM'), in_list_view:1, get_query: function(doc) { in_list_view: 1
}, {
fieldtype: 'Link',
fieldname: 'bom',
options: 'BOM',
reqd: 1,
label: __('Select BOM'),
in_list_view: 1,
get_query: function (doc) {
return { filters: { item: doc.item_code } }; return { filters: { item: doc.item_code } };
}}, }
{fieldtype:'Float', fieldname:'pending_qty', reqd: 1, }, {
label: __('Qty'), in_list_view:1}, fieldtype: 'Float',
{fieldtype:'Data', fieldname:'sales_order_item', reqd: 1, fieldname: 'pending_qty',
label: __('Sales Order Item'), hidden:1} reqd: 1,
], label: __('Qty'),
in_list_view: 1
}, {
fieldtype: 'Data',
fieldname: 'sales_order_item',
reqd: 1,
label: __('Sales Order Item'),
hidden: 1
}],
data: r.message, data: r.message,
get_data: function() { get_data: () => {
return r.message return r.message
} }
} }]
]
var d = new frappe.ui.Dialog({ var d = new frappe.ui.Dialog({
title: __('Select Items to Manufacture'), title: __('Select Items to Manufacture'),
fields: fields, fields: fields,

View File

@ -251,13 +251,12 @@ def _get_cart_quotation(party=None):
if quotation: if quotation:
qdoc = frappe.get_doc("Quotation", quotation[0].name) qdoc = frappe.get_doc("Quotation", quotation[0].name)
else: else:
[company, price_list] = frappe.db.get_value("Shopping Cart Settings", None, ["company", "price_list"]) company = frappe.db.get_value("Shopping Cart Settings", None, ["company"])
qdoc = frappe.get_doc({ qdoc = frappe.get_doc({
"doctype": "Quotation", "doctype": "Quotation",
"naming_series": get_shopping_cart_settings().quotation_series or "QTN-CART-", "naming_series": get_shopping_cart_settings().quotation_series or "QTN-CART-",
"quotation_to": party.doctype, "quotation_to": party.doctype,
"company": company, "company": company,
"selling_price_list": price_list,
"order_type": "Shopping Cart", "order_type": "Shopping Cart",
"status": "Draft", "status": "Draft",
"docstatus": 0, "docstatus": 0,

View File

@ -145,6 +145,10 @@ class StockEntry(StockController):
self.precision("transfer_qty", item)) self.precision("transfer_qty", item))
def update_cost_in_project(self): def update_cost_in_project(self):
if (self.work_order and not frappe.db.get_value("Work Order",
self.work_order, "update_consumed_material_cost_in_project")):
return
if self.project: if self.project:
amount = frappe.db.sql(""" select ifnull(sum(sed.amount), 0) amount = frappe.db.sql(""" select ifnull(sum(sed.amount), 0)
from from

3
package-lock.json generated
View File

@ -1,3 +0,0 @@
{
"lockfileVersion": 1
}