Merge branch 'develop'

This commit is contained in:
Nabin Hait 2015-10-27 11:29:35 +05:30
commit fa0adafa82
19 changed files with 256 additions and 51 deletions

View File

@ -1,2 +1,2 @@
from __future__ import unicode_literals from __future__ import unicode_literals
__version__ = '6.6.4' __version__ = '6.6.5'

View File

@ -147,6 +147,8 @@ class Account(Document):
self.validate_warehouse(old_warehouse) self.validate_warehouse(old_warehouse)
if self.warehouse: if self.warehouse:
self.validate_warehouse(self.warehouse) self.validate_warehouse(self.warehouse)
elif self.warehouse:
self.warehouse = None
def validate_warehouse(self, warehouse): def validate_warehouse(self, warehouse):
if frappe.db.get_value("Stock Ledger Entry", {"warehouse": warehouse}): if frappe.db.get_value("Stock Ledger Entry", {"warehouse": warehouse}):

View File

@ -498,14 +498,17 @@ def get_default_bank_cash_account(company, voucher_type, mode_of_payment=None):
if voucher_type=="Bank Entry": if voucher_type=="Bank Entry":
account = frappe.db.get_value("Company", company, "default_bank_account") account = frappe.db.get_value("Company", company, "default_bank_account")
if not account: if not account:
account = frappe.db.get_value("Account", {"company": company, "account_type": "Bank", "is_group": 0}) account = frappe.db.get_value("Account",
{"company": company, "account_type": "Bank", "is_group": 0})
elif voucher_type=="Cash Entry": elif voucher_type=="Cash Entry":
account = frappe.db.get_value("Company", company, "default_cash_account") account = frappe.db.get_value("Company", company, "default_cash_account")
if not account: if not account:
account = frappe.db.get_value("Account", {"company": company, "account_type": "Cash", "is_group": 0}) account = frappe.db.get_value("Account",
{"company": company, "account_type": "Cash", "is_group": 0})
if account: if account:
account_details = frappe.db.get_value("Account", account, ["account_currency", "account_type"], as_dict=1) account_details = frappe.db.get_value("Account", account,
["account_currency", "account_type"], as_dict=1)
return { return {
"account": account, "account": account,
"balance": get_balance_on(account), "balance": get_balance_on(account),
@ -814,11 +817,19 @@ def get_account_balance_and_party_type(account, date, company, debit=None, credi
return grid_values return grid_values
@frappe.whitelist() @frappe.whitelist()
def get_exchange_rate(account, account_currency, company, def get_exchange_rate(account, account_currency=None, company=None,
reference_type=None, reference_name=None, debit=None, credit=None, exchange_rate=None): reference_type=None, reference_name=None, debit=None, credit=None, exchange_rate=None):
from erpnext.setup.utils import get_exchange_rate from erpnext.setup.utils import get_exchange_rate
account_details = frappe.db.get_value("Account", account,
["account_type", "root_type", "account_currency", "company"], as_dict=1)
if not company:
company = account_details.company
if not account_currency:
account_currency = account_details.account_currency
company_currency = get_company_currency(company) company_currency = get_company_currency(company)
account_details = frappe.db.get_value("Account", account, ["account_type", "root_type"], as_dict=1)
if account_currency != company_currency: if account_currency != company_currency:
if reference_type in ("Sales Invoice", "Purchase Invoice") and reference_name: if reference_type in ("Sales Invoice", "Purchase Invoice") and reference_name:

View File

@ -160,6 +160,9 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
me.set_dynamic_labels(); me.set_dynamic_labels();
me.calculate_taxes_and_totals(); me.calculate_taxes_and_totals();
if(callback_fn) callback_fn(); if(callback_fn) callback_fn();
frappe.after_ajax(function() {
cur_frm.doc.__missing_values_set = false;
})
} }
} }
}); });

View File

@ -109,7 +109,8 @@ def get_party_details(party, party_type, args=None):
def get_tax_template(posting_date, args): def get_tax_template(posting_date, args):
"""Get matching tax rule""" """Get matching tax rule"""
args = frappe._dict(args) args = frappe._dict(args)
conditions = [] conditions = ["""(from_date is null or from_date = '' or from_date <= '{0}')
and (to_date is null or to_date = '' or to_date >= '{0}')""".format(posting_date)]
for key, value in args.iteritems(): for key, value in args.iteritems():
if key in "use_for_shopping_cart": if key in "use_for_shopping_cart":
@ -117,16 +118,16 @@ def get_tax_template(posting_date, args):
else: else:
conditions.append("ifnull({0}, '') in ('', '{1}')".format(key, frappe.db.escape(cstr(value)))) conditions.append("ifnull({0}, '') in ('', '{1}')".format(key, frappe.db.escape(cstr(value))))
matching = frappe.db.sql("""select * from `tabTax Rule` tax_rule = frappe.db.sql("""select * from `tabTax Rule`
where {0}""".format(" and ".join(conditions)), as_dict = True) where {0}""".format(" and ".join(conditions)), as_dict = True)
if not matching: if not tax_rule:
return None return None
for rule in matching: for rule in tax_rule:
rule.no_of_keys_matched = 0 rule.no_of_keys_matched = 0
for key in args: for key in args:
if rule.get(key): rule.no_of_keys_matched += 1 if rule.get(key): rule.no_of_keys_matched += 1
rule = sorted(matching, lambda b, a: cmp(a.no_of_keys_matched, b.no_of_keys_matched) or cmp(a.priority, b.priority))[0] rule = sorted(tax_rule, lambda b, a: cmp(a.no_of_keys_matched, b.no_of_keys_matched) or cmp(a.priority, b.priority))[0]
return rule.sales_tax_template or rule.purchase_tax_template return rule.sales_tax_template or rule.purchase_tax_template

View File

@ -209,13 +209,12 @@ erpnext.AccountsChart = Class.extend({
{fieldtype:'Check', fieldname:'is_group', label:__('Is Group'), {fieldtype:'Check', fieldname:'is_group', label:__('Is Group'),
description: __('Further accounts can be made under Groups, but entries can be made against non-Groups')}, description: __('Further accounts can be made under Groups, but entries can be made against non-Groups')},
{fieldtype:'Select', fieldname:'account_type', label:__('Account Type'), {fieldtype:'Select', fieldname:'account_type', label:__('Account Type'),
options: ['', 'Bank', 'Cash', 'Warehouse', 'Receivable', 'Payable', options: ['', 'Bank', 'Cash', 'Warehouse', 'Tax', 'Chargeable'].join('\n'),
'Equity', 'Cost of Goods Sold', 'Fixed Asset', 'Expense Account',
'Income Account', 'Tax', 'Chargeable', 'Temporary'].join('\n'),
description: __("Optional. This setting will be used to filter in various transactions.") }, description: __("Optional. This setting will be used to filter in various transactions.") },
{fieldtype:'Float', fieldname:'tax_rate', label:__('Tax Rate')}, {fieldtype:'Float', fieldname:'tax_rate', label:__('Tax Rate')},
{fieldtype:'Link', fieldname:'warehouse', label:__('Warehouse'), options:"Warehouse"}, {fieldtype:'Link', fieldname:'warehouse', label:__('Warehouse'), options:"Warehouse"},
{fieldtype:'Link', fieldname:'account_currency', label:__('Currency'), options:"Currency"} {fieldtype:'Link', fieldname:'account_currency', label:__('Currency'), options:"Currency",
description: __("Optional. Sets company's default currency, if not specified.")}
] ]
}) })

View File

@ -29,7 +29,7 @@ blogs.
""" """
app_icon = "icon-th" app_icon = "icon-th"
app_color = "#e74c3c" app_color = "#e74c3c"
app_version = "6.6.4" app_version = "6.6.5"
github_link = "https://github.com/frappe/erpnext" github_link = "https://github.com/frappe/erpnext"
error_report_email = "support@erpnext.com" error_report_email = "support@erpnext.com"

View File

@ -18,24 +18,25 @@ erpnext.hr.ExpenseClaimController = frappe.ui.form.Controller.extend({
jv.voucher_type = 'Bank Entry'; jv.voucher_type = 'Bank Entry';
jv.company = cur_frm.doc.company; jv.company = cur_frm.doc.company;
jv.remark = 'Payment against Expense Claim: ' + cur_frm.doc.name; jv.remark = 'Payment against Expense Claim: ' + cur_frm.doc.name;
jv.fiscal_year = cur_frm.doc.fiscal_year;
var expense = cur_frm.doc.expenses || []; var expense = cur_frm.doc.expenses || [];
for(var i = 0; i < expense.length; i++){ for(var i = 0; i < expense.length; i++){
var d1 = frappe.model.add_child(jv, 'Journal Entry Account', 'accounts'); var d1 = frappe.model.add_child(jv, 'Journal Entry Account', 'accounts');
d1.debit = expense[i].sanctioned_amount;
d1.account = expense[i].default_account; d1.account = expense[i].default_account;
d1.debit_in_account_currency = expense[i].sanctioned_amount;
d1.reference_type = cur_frm.doc.doctype; d1.reference_type = cur_frm.doc.doctype;
d1.reference_name = cur_frm.doc.name; d1.reference_name = cur_frm.doc.name;
} }
// credit to bank // credit to bank
var d1 = frappe.model.add_child(jv, 'Journal Entry Account', 'accounts'); var d1 = frappe.model.add_child(jv, 'Journal Entry Account', 'accounts');
d1.credit = cur_frm.doc.total_sanctioned_amount; d1.credit_in_account_currency = cur_frm.doc.total_sanctioned_amount;
d1.reference_type = cur_frm.doc.doctype; d1.reference_type = cur_frm.doc.doctype;
d1.reference_name = cur_frm.doc.name; d1.reference_name = cur_frm.doc.name;
if(r.message) { if(r.message) {
d1.account = r.message.account; d1.account = r.message.account;
d1.balance = r.message.balance; d1.balance = r.message.balance;
d1.account_currency = r.message.account_currency;
d1.account_type = r.message.account_type;
} }
loaddoc('Journal Entry', jv.name); loaddoc('Journal Entry', jv.name);

View File

@ -22,11 +22,16 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) {
} }
if (args) { if (args) {
args.posting_date = frm.doc.transaction_date; args.posting_date = frm.doc.posting_date || frm.doc.transaction_date;
} }
} }
if(!args) return; if(!args) return;
if(frappe.meta.get_docfield(frm.doc.doctype, "taxes")) {
if(!erpnext.utils.validate_mandatory(frm, "Posting/Transaction Date",
args.posting_date, args.party_type=="Customer" ? "customer": "supplier")) return;
}
args.currency = frm.doc.currency; args.currency = frm.doc.currency;
args.company = frm.doc.company; args.company = frm.doc.company;
args.doctype = frm.doc.doctype; args.doctype = frm.doc.doctype;
@ -64,6 +69,15 @@ erpnext.utils.get_address_display = function(frm, address_field, display_field)
if(r.message){ if(r.message){
frm.set_value(display_field, r.message) frm.set_value(display_field, r.message)
} }
if(frappe.meta.get_docfield(frm.doc.doctype, "taxes")) {
if(!erpnext.utils.validate_mandatory(frm, "Customer/Supplier",
frm.doc.customer || frm.doc.supplier, address_field)) return;
if(!erpnext.utils.validate_mandatory(frm, "Posting/Transaction Date",
frm.doc.posting_date || frm.doc.transaction_date, address_field)) return;
} else return;
frappe.call({ frappe.call({
method: "erpnext.accounts.party.set_taxes", method: "erpnext.accounts.party.set_taxes",
args: { args: {
@ -99,3 +113,13 @@ erpnext.utils.get_contact_details = function(frm) {
}) })
} }
} }
erpnext.utils.validate_mandatory = function(frm, label, value, trigger_on) {
if(!value) {
frm.doc[trigger_on] = "";
refresh_field(trigger_on);
frappe.msgprint(__("Please enter {0} first", [label]));
return false;
}
return true;
}

View File

@ -22,8 +22,10 @@ def delete_company_transactions(company_name):
for doctype in frappe.db.sql_list("""select parent from for doctype in frappe.db.sql_list("""select parent from
tabDocField where fieldtype='Link' and options='Company'"""): tabDocField where fieldtype='Link' and options='Company'"""):
if doctype not in ("Account", "Cost Center", "Warehouse", "Budget Detail", "Party Account", "Employee"): if doctype not in ("Account", "Cost Center", "Warehouse", "Budget Detail",
delete_for_doctype(doctype, company_name) "Party Account", "Employee", "Sales Taxes and Charges Template",
"Purchase Taxes and Charges Template", "POS Profile"):
delete_for_doctype(doctype, company_name)
# Clear notification counts # Clear notification counts
clear_notifications() clear_notifications()

View File

@ -139,7 +139,7 @@ class EmailDigest(Document):
for i, e in enumerate(events): for i, e in enumerate(events):
e.starts_on_label = format_time(e.starts_on) e.starts_on_label = format_time(e.starts_on)
e.ends_on_label = format_time(e.ends_on) e.ends_on_label = format_time(e.ends_on) if e.ends_on else None
e.date = formatdate(e.starts) e.date = formatdate(e.starts)
e.link = get_url_to_form("Event", e.name) e.link = get_url_to_form("Event", e.name)
@ -346,7 +346,7 @@ class EmailDigest(Document):
self.get_next_sending() self.get_next_sending()
def fmt_money(self, value): def fmt_money(self, value):
return fmt_money(value, currency = self.currency) return fmt_money(abs(value), currency = self.currency)
def send(): def send():
now_date = now_datetime().date() now_date = now_datetime().date()

View File

@ -52,6 +52,8 @@
<span style="{{ label_css }}"> <span style="{{ label_css }}">
{% if e.all_day %} {% if e.all_day %}
{{ _("All Day") }} {{ _("All Day") }}
{% elif (not e.ends_on_label or e.starts_on_label == e.ends_on_label)%}
{{ e.starts_on_label }}
{% else %} {% else %}
{{ e.starts_on_label }} - {{ e.ends_on_label }} {{ e.starts_on_label }} - {{ e.ends_on_label }}
{% endif %} {% endif %}

View File

@ -735,7 +735,7 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "Warehouse", "label": "From Warehouse",
"no_copy": 0, "no_copy": 0,
"oldfieldname": "warehouse", "oldfieldname": "warehouse",
"oldfieldtype": "Link", "oldfieldtype": "Link",
@ -755,13 +755,15 @@
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"depends_on": "",
"description": "",
"fieldname": "target_warehouse", "fieldname": "target_warehouse",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 0, "in_list_view": 0,
"label": "Target Warehouse", "label": "To Warehouse (Optional)",
"no_copy": 0, "no_copy": 0,
"options": "Warehouse", "options": "Warehouse",
"permlevel": 0, "permlevel": 0,
@ -831,7 +833,7 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 0, "in_list_view": 0,
"label": "Available Qty at Warehouse", "label": "Available Qty at From Warehouse",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "actual_qty", "oldfieldname": "actual_qty",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
@ -857,7 +859,7 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 0, "in_list_view": 0,
"label": "Available Batch Qty at Warehouse", "label": "Available Batch Qty at From Warehouse",
"no_copy": 1, "no_copy": 1,
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
@ -1160,7 +1162,7 @@
"is_submittable": 0, "is_submittable": 0,
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"modified": "2015-10-19 03:04:50.887288", "modified": "2015-10-26 02:19:18.053222",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Delivery Note Item", "name": "Delivery Note Item",

View File

@ -5,6 +5,7 @@ from __future__ import unicode_literals
import frappe import frappe
import json import json
import urllib import urllib
import itertools
from frappe import msgprint, _ from frappe import msgprint, _
from frappe.utils import cstr, flt, cint, getdate, now_datetime, formatdate from frappe.utils import cstr, flt, cint, getdate, now_datetime, formatdate
from frappe.website.website_generator import WebsiteGenerator from frappe.website.website_generator import WebsiteGenerator
@ -130,6 +131,8 @@ class Item(WebsiteGenerator):
self.set_attribute_context(context) self.set_attribute_context(context)
self.set_disabled_attributes(context)
context.parents = self.get_parents(context) context.parents = self.get_parents(context)
return context return context
@ -189,15 +192,63 @@ class Item(WebsiteGenerator):
for attr in self.attributes: for attr in self.attributes:
values = context.attribute_values.setdefault(attr.attribute, []) values = context.attribute_values.setdefault(attr.attribute, [])
# get list of values defined (for sequence) if cint(frappe.db.get_value("Item Attribute", attr.attribute, "numeric_values")):
for attr_value in frappe.db.get_all("Item Attribute Value", for val in sorted(attribute_values_available.get(attr.attribute, []), key=flt):
fields=["attribute_value"], filters={"parent": attr.attribute}, order_by="idx asc"): values.append(val)
if attr_value.attribute_value in attribute_values_available.get(attr.attribute, []): else:
values.append(attr_value.attribute_value) # get list of values defined (for sequence)
for attr_value in frappe.db.get_all("Item Attribute Value",
fields=["attribute_value"], filters={"parent": attr.attribute}, order_by="idx asc"):
if attr_value.attribute_value in attribute_values_available.get(attr.attribute, []):
values.append(attr_value.attribute_value)
context.variant_info = json.dumps(context.variants) context.variant_info = json.dumps(context.variants)
def set_disabled_attributes(self, context):
"""Disable selection options of attribute combinations that do not result in a variant"""
if not self.attributes:
return
context.disabled_attributes = {}
attributes = [attr.attribute for attr in self.attributes]
def find_variant(combination):
for variant in context.variants:
if len(variant.attributes) < len(attributes):
continue
if "combination" not in variant:
ref_combination = []
for attr in variant.attributes:
idx = attributes.index(attr.attribute)
ref_combination.insert(idx, attr.attribute_value)
variant["combination"] = ref_combination
if not (set(combination) - set(variant["combination"])):
# check if the combination is a subset of a variant combination
# eg. [Blue, 0.5] is a possible combination if exists [Blue, Large, 0.5]
return True
for i, attr in enumerate(self.attributes):
if i==0:
continue
combination_source = []
# loop through previous attributes
for prev_attr in self.attributes[:i]:
combination_source.append([context.selected_attributes[prev_attr.attribute]])
combination_source.append(context.attribute_values[attr.attribute])
for combination in itertools.product(*combination_source):
if not find_variant(combination):
context.disabled_attributes.setdefault(attr.attribute, []).append(combination[-1])
def check_warehouse_is_set_for_stock_item(self): def check_warehouse_is_set_for_stock_item(self):
if self.is_stock_item==1 and not self.default_warehouse and frappe.get_all("Warehouse"): if self.is_stock_item==1 and not self.default_warehouse and frappe.get_all("Warehouse"):
frappe.msgprint(_("Default Warehouse is mandatory for stock Item."), frappe.msgprint(_("Default Warehouse is mandatory for stock Item."),

View File

@ -155,7 +155,7 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 0, "in_list_view": 0,
"label": "Warehouse", "label": "From Warehouse",
"no_copy": 0, "no_copy": 0,
"oldfieldname": "warehouse", "oldfieldname": "warehouse",
"oldfieldtype": "Link", "oldfieldtype": "Link",
@ -179,7 +179,7 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 0, "in_list_view": 0,
"label": "Target Warehouse", "label": "To Warehouse (Optional)",
"no_copy": 0, "no_copy": 0,
"options": "Warehouse", "options": "Warehouse",
"permlevel": 0, "permlevel": 0,
@ -511,7 +511,7 @@
"is_submittable": 0, "is_submittable": 0,
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"modified": "2015-10-12 07:38:58.896987", "modified": "2015-10-26 02:25:47.718911",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Packed Item", "name": "Packed Item",

View File

@ -4,6 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import flt, today
def execute(filters=None): def execute(filters=None):
filters = frappe._dict(filters or {}) filters = frappe._dict(filters or {})
@ -18,12 +19,19 @@ def get_columns():
_("Shortage Qty") + ":Float:100"] _("Shortage Qty") + ":Float:100"]
def get_data(filters): def get_data(filters):
item_map = {} bin_list = get_bin_list(filters)
item_map = get_item_map(filters.get("item_code"))
warehouse_company = {} warehouse_company = {}
data = [] data = []
for bin in get_bin_list(filters): for bin in bin_list:
item = item_map.setdefault(bin.item_code, frappe.get_doc("Item", bin.item_code)) item = item_map.get(bin.item_code)
if not item:
# likely an item that has reached its end of life
continue
# item = item_map.setdefault(bin.item_code, get_item(bin.item_code))
company = warehouse_company.setdefault(bin.warehouse, frappe.db.get_value("Warehouse", bin.warehouse, "company")) company = warehouse_company.setdefault(bin.warehouse, frappe.db.get_value("Warehouse", bin.warehouse, "company"))
if filters.brand and filters.brand != item.brand: if filters.brand and filters.brand != item.brand:
@ -45,7 +53,7 @@ def get_data(filters):
data.append([item.name, item.item_name, item.description, item.item_group, item.brand, bin.warehouse, data.append([item.name, item.item_name, item.description, item.item_group, item.brand, bin.warehouse,
item.stock_uom, bin.actual_qty, bin.planned_qty, bin.indented_qty, bin.ordered_qty, bin.reserved_qty, item.stock_uom, bin.actual_qty, bin.planned_qty, bin.indented_qty, bin.ordered_qty, bin.reserved_qty,
bin.projected_qty, re_order_level, re_order_qty, re_order_level - bin.projected_qty]) bin.projected_qty, re_order_level, re_order_qty, re_order_level - flt(bin.projected_qty)])
return data return data
@ -61,3 +69,35 @@ def get_bin_list(filters):
filters=bin_filters, order_by="item_code, warehouse") filters=bin_filters, order_by="item_code, warehouse")
return bin_list return bin_list
def get_item_map(item_code):
"""Optimization: get only the item doc and re_order_levels table"""
condition = ""
if item_code:
condition = 'and item_code = "{0}"'.format(frappe.db.escape(item_code))
items = frappe.db.sql("""select * from `tabItem` item
where is_stock_item = 1
{condition}
and (end_of_life > %(today)s or end_of_life is null or end_of_life='0000-00-00')
and exists (select name from `tabBin` bin where bin.item_code=item.name)"""\
.format(condition=condition), {"today": today()}, as_dict=True)
condition = ""
if item_code:
condition = 'where parent="{0}"'.format(frappe.db.escape(item_code))
reorder_levels = frappe._dict()
for ir in frappe.db.sql("""select * from `tabItem Reorder` {condition}""".format(condition=condition), as_dict=1):
if ir.parent not in reorder_levels:
reorder_levels[ir.parent] = []
reorder_levels[ir.parent].append(ir)
item_map = frappe._dict()
for item in items:
item["reorder_levels"] = reorder_levels.get(item.name) or []
item_map[item.name] = item
return item_map

View File

@ -29,7 +29,8 @@
<div class="item-attribute-selectors"> <div class="item-attribute-selectors">
{% if has_variants %} {% if has_variants %}
{% for d in attributes %} {% for d in attributes %}
<div class="item-view-attribute" {% if attribute_values[d.attribute] -%}
<div class="item-view-attribute {% if (attribute_values[d.attribute] | len)==1 -%} hidden {%- endif %}"
style="margin-bottom: 10px;"> style="margin-bottom: 10px;">
<h6 class="text-muted">{{ _(d.attribute) }}</h6> <h6 class="text-muted">{{ _(d.attribute) }}</h6>
<select class="form-control" <select class="form-control"
@ -37,12 +38,17 @@
data-attribute="{{ d.attribute }}"> data-attribute="{{ d.attribute }}">
{% for value in attribute_values[d.attribute] %} {% for value in attribute_values[d.attribute] %}
<option value="{{ value }}" <option value="{{ value }}"
{% if selected_attributes and selected_attributes[d.attribute]==value -%} selected {%- endif %}> {% if selected_attributes and selected_attributes[d.attribute]==value -%}
selected
{%- elif disabled_attributes and value in disabled_attributes.get(d.attribute, []) -%}
disabled
{%- endif %}>
{{ _(value) }} {{ _(value) }}
</option> </option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
{%- endif %}
{% endfor %} {% endfor %}
{% endif %} {% endif %}
</div> </div>

View File

@ -64,8 +64,23 @@ frappe.ready(function() {
}); });
}); });
$("[itemscope] .item-view-attribute select").on("change", function() { $("[itemscope] .item-view-attribute .form-control").on("change", function() {
var item_code = encodeURIComponent(get_item_code()); try {
var item_code = encodeURIComponent(get_item_code());
} catch(e) {
// unable to find variant
// then chose the closest available one
var attribute = $(this).attr("data-attribute");
var attribute_value = $(this).val()
var item_code = update_attribute_selectors(attribute, attribute_value);
if (!item_code) {
msgprint(__("Please select some other value for {0}", [attribute]))
throw e;
}
}
if (window.location.search.indexOf(item_code)!==-1) { if (window.location.search.indexOf(item_code)!==-1) {
return; return;
} }
@ -83,10 +98,8 @@ var toggle_update_cart = function(qty) {
function get_item_code() { function get_item_code() {
if(window.variant_info) { if(window.variant_info) {
attributes = {}; var attributes = get_selected_attributes();
$('[itemscope]').find(".item-view-attribute select").each(function() {
attributes[$(this).attr('data-attribute')] = $(this).val();
});
for(var i in variant_info) { for(var i in variant_info) {
var variant = variant_info[i]; var variant = variant_info[i];
var match = true; var match = true;
@ -106,3 +119,51 @@ function get_item_code() {
return item_code; return item_code;
} }
} }
function update_attribute_selectors(selected_attribute, selected_attribute_value) {
// find the closest match keeping the selected attribute in focus and get the item code
var attributes = get_selected_attributes();
var previous_match_score = 0;
var matched;
for(var i in variant_info) {
var variant = variant_info[i];
var match_score = 0;
var has_selected_attribute = false;
for(var j in variant.attributes) {
if(attributes[variant.attributes[j].attribute]===variant.attributes[j].attribute_value) {
match_score = match_score + 1;
if (variant.attributes[j].attribute==selected_attribute && variant.attributes[j].attribute_value==selected_attribute_value) {
has_selected_attribute = true;
}
}
}
if (has_selected_attribute && (match_score > previous_match_score)) {
previous_match_score = match_score;
matched = variant;
}
}
if (matched) {
for (var j in matched.attributes) {
var attr = matched.attributes[j];
$('[itemscope]')
.find(repl('.item-view-attribute .form-control[data-attribute="%(attribute)s"]', attr))
.val(attr.attribute_value);
}
return matched.name;
}
}
function get_selected_attributes() {
var attributes = {};
$('[itemscope]').find(".item-view-attribute .form-control").each(function() {
attributes[$(this).attr('data-attribute')] = $(this).val();
});
return attributes;
}

View File

@ -1,6 +1,6 @@
from setuptools import setup, find_packages from setuptools import setup, find_packages
version = "6.6.4" version = "6.6.5"
with open("requirements.txt", "r") as f: with open("requirements.txt", "r") as f:
install_requires = f.readlines() install_requires = f.readlines()