[feature] [refactor] Shipping Rule for Buying + Refactor (#11628)

* Shipping rule for Buying

* [refactor] shipping rule
This commit is contained in:
Rushabh Mehta 2017-11-17 14:31:09 +05:30 committed by GitHub
parent 66e65dc104
commit 30dc9a14c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 13041 additions and 12246 deletions

File diff suppressed because it is too large Load Diff

View File

@ -555,6 +555,40 @@ class TestPurchaseInvoice(unittest.TestCase):
#check outstanding after advance cancellation
self.assertEqual(flt(pi.outstanding_amount), flt(pi.rounded_total + pi.total_advance))
def test_purchase_invoice_with_shipping_rule(self):
from erpnext.accounts.doctype.shipping_rule.test_shipping_rule \
import create_shipping_rule
shipping_rule = create_shipping_rule(shipping_rule_type = "Buying", shipping_rule_name = "Shipping Rule - Purchase Invoice Test")
pi = frappe.copy_doc(test_records[0])
pi.shipping_rule = shipping_rule.name
pi.insert()
shipping_amount = 0.0
for condition in shipping_rule.get("conditions"):
if not condition.to_value or (flt(condition.from_value) <= pi.net_total <= flt(condition.to_value)):
shipping_amount = condition.shipping_amount
shipping_charge = {
"doctype": "Purchase Taxes and Charges",
"category": "Valuation and Total",
"charge_type": "Actual",
"account_head": shipping_rule.account,
"cost_center": shipping_rule.cost_center,
"tax_amount": shipping_amount,
"description": shipping_rule.name,
"add_deduct_tax": "Add"
}
pi.append("taxes", shipping_charge)
pi.save()
self.assertEquals(pi.net_total, 1250)
self.assertEquals(pi.total_taxes_and_charges, 462.3)
self.assertEquals(pi.grand_total, 1712.3)
def unlink_payment_on_cancel_of_invoice(enable=1):
accounts_settings = frappe.get_doc("Accounts Settings")
accounts_settings.unlink_payment_on_cancellation_of_invoice = enable

View File

@ -1371,6 +1371,39 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEquals(expected_values[gle.account][1], gle.debit)
self.assertEquals(expected_values[gle.account][2], gle.credit)
def test_sales_invoice_with_shipping_rule(self):
from erpnext.accounts.doctype.shipping_rule.test_shipping_rule \
import create_shipping_rule
shipping_rule = create_shipping_rule(shipping_rule_type = "Selling", shipping_rule_name = "Shipping Rule - Sales Invoice Test")
si = frappe.copy_doc(test_records[2])
si.shipping_rule = shipping_rule.name
si.insert()
shipping_amount = 0.0
for condition in shipping_rule.get("conditions"):
if not condition.to_value or (flt(condition.from_value) <= si.net_total <= flt(condition.to_value)):
shipping_amount = condition.shipping_amount
shipping_charge = {
"doctype": "Sales Taxes and Charges",
"category": "Valuation and Total",
"charge_type": "Actual",
"account_head": shipping_rule.account,
"cost_center": shipping_rule.cost_center,
"tax_amount": shipping_amount,
"description": shipping_rule.name
}
si.append("taxes", shipping_charge)
si.save()
self.assertEquals(si.net_total, 1250)
self.assertEquals(si.total_taxes_and_charges, 577.05)
self.assertEquals(si.grand_total, 1827.05)
def create_sales_invoice(**args):
si = frappe.new_doc("Sales Invoice")
args = frappe._dict(args)

View File

@ -1,3 +1,15 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
frappe.ui.form.on('Shipping Rule', {
refresh: function(frm) {
frm.trigger('toggle_reqd');
},
calculate_based_on: function(frm) {
frm.trigger('toggle_reqd');
},
toggle_reqd: function(frm) {
frm.toggle_reqd("shipping_amount", frm.doc.calculate_based_on === 'Fixed');
frm.toggle_reqd("conditions", frm.doc.calculate_based_on !== 'Fixed');
}
});

View File

@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "field:label",
@ -12,6 +13,7 @@
"editable_grid": 0,
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -23,7 +25,8 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Shipping Rule Label",
"length": 0,
@ -40,6 +43,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -50,6 +54,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Disabled",
@ -68,134 +73,53 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Net Total",
"fieldname": "calculate_based_on",
"fieldname": "column_break_4",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "shipping_rule_type",
"fieldtype": "Select",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Calculate Based On",
"length": 0,
"no_copy": 0,
"options": "Net Total\nNet Weight",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.disabled",
"fieldname": "rule_conditions_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Shipping Rule Conditions",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "conditions",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Shipping Rule Conditions",
"length": 0,
"no_copy": 0,
"options": "Shipping Rule Condition",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.disabled",
"fieldname": "section_break_6",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Valid for Countries",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "worldwide_shipping",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Worldwide Shipping",
"label": "Shipping Rule Type",
"length": 0,
"no_copy": 0,
"options": "Selling\nBuying",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@ -209,36 +133,7 @@
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.worldwide_shipping",
"fieldname": "countries",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Valid for Countries",
"length": 0,
"no_copy": 0,
"options": "Shipping Rule Country",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -250,8 +145,10 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Accounting",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@ -266,6 +163,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -276,6 +174,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Company",
@ -294,6 +193,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -304,6 +204,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
@ -320,6 +221,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -330,6 +232,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Shipping Account",
@ -348,6 +251,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -358,6 +262,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Cost Center",
@ -374,20 +279,264 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "shipping_amount_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Fixed",
"fieldname": "calculate_based_on",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Calculate Based On",
"length": 0,
"no_copy": 0,
"options": "Fixed\nNet Total",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_8",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.calculate_based_on==='Fixed'",
"fieldname": "shipping_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Shipping Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": "",
"columns": 0,
"depends_on": "eval:doc.calculate_based_on!=='Fixed'",
"fieldname": "rule_conditions_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Shipping Rule Conditions",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "conditions",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Shipping Rule Conditions",
"length": 0,
"no_copy": 0,
"options": "Shipping Rule Condition",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fieldname": "section_break_6",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Restrict to Countries",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fieldname": "countries",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Valid for Countries",
"length": 0,
"no_copy": 0,
"options": "Shipping Rule Country",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-truck",
"idx": 1,
"image_view": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2016-11-07 05:18:06.472734",
"modified": "2017-11-17 13:10:34.302259",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Shipping Rule",
@ -403,7 +552,6 @@
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@ -424,7 +572,6 @@
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@ -445,7 +592,6 @@
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@ -466,7 +612,6 @@
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@ -481,6 +626,8 @@
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_order": "ASC",
"track_changes": 0,
"track_seen": 0
}

View File

@ -15,18 +15,10 @@ class ManyBlankToValuesError(frappe.ValidationError): pass
class ShippingRule(Document):
def validate(self):
self.validate_value("calculate_based_on", "in", ["Net Total", "Net Weight"])
self.conditions = self.get("conditions")
self.validate_from_to_values()
self.sort_shipping_rule_conditions()
self.validate_overlapping_shipping_rule_conditions()
if self.worldwide_shipping:
self.countries = []
elif not len([d.country for d in self.countries if d.country]):
frappe.throw(_("Please specify a country for this Shipping Rule or check Worldwide Shipping"))
def validate_from_to_values(self):
zero_to_values = []
@ -47,6 +39,76 @@ class ShippingRule(Document):
throw(_('There can only be one Shipping Rule Condition with 0 or blank value for "To Value"'),
ManyBlankToValuesError)
def apply(self, doc):
'''Apply shipping rule on given doc. Called from accounts controller'''
shipping_amount = 0.0
by_value = False
self.validate_countries(doc)
if self.calculate_based_on == 'Net Total':
value = doc.base_net_total
by_value = True
elif self.calculate_based_on == 'Fixed':
shipping_amount = self.shipping_amount
# shipping amount by value, apply conditions
if by_value:
shipping_amount = self.get_shipping_amount_from_rules(value)
# convert to order currency
if doc.currency != doc.company_currency:
shipping_amount = flt(shipping_amount / doc.conversion_rate, 2)
self.add_shipping_rule_to_tax_table(doc, shipping_amount)
def get_shipping_amount_from_rules(self, value):
for condition in self.get("conditions"):
if not condition.to_value or (flt(condition.from_value) <= value <= flt(condition.to_value)):
return condition.shipping_amount
return 0.0
def validate_countries(self, doc):
# validate applicable countries
if self.countries:
shipping_country = doc.get_shipping_address().get('country')
if not shipping_country:
frappe.throw(_('Shipping Address does not have country, which is required for this Shipping Rule'))
if shipping_country not in [d.country for d in self.countries]:
frappe.throw(_('Shipping rule not applicable for country {0}'.format(shipping_country)))
def add_shipping_rule_to_tax_table(self, doc, shipping_amount):
shipping_charge = {
"charge_type": "Actual",
"account_head": self.account,
"cost_center": self.cost_center
}
if self.shipping_rule_type == "Selling":
# check if not applied on purchase
if not doc.meta.get_field('taxes').options == 'Sales Taxes and Charges':
frappe.throw(_('Shipping rule only applicable for Selling'))
shipping_charge["doctype"] = "Sales Taxes and Charges"
else:
# check if not applied on sales
if not doc.meta.get_field('taxes').options == 'Purchase Taxes and Charges':
frappe.throw(_('Shipping rule only applicable for Buying'))
shipping_charge["doctype"] = "Purchase Taxes and Charges"
shipping_charge["category"] = "Valuation and Total"
shipping_charge["add_deduct_tax"] = "Add"
existing_shipping_charge = doc.get("taxes", filters=shipping_charge)
if existing_shipping_charge:
# take the last record found
existing_shipping_charge[-1].tax_amount = shipping_amount
else:
shipping_charge["tax_amount"] = shipping_amount
shipping_charge["description"] = self.label
doc.append("taxes", shipping_charge)
def sort_shipping_rule_conditions(self):
"""Sort Shipping Rule Conditions based on increasing From Value"""
self.shipping_rules_conditions = sorted(self.conditions, key=lambda d: flt(d.from_value))

View File

@ -7,6 +7,7 @@
"doctype": "Shipping Rule",
"label": "_Test Shipping Rule",
"name": "_Test Shipping Rule",
"shipping_rule_type": "Selling",
"conditions": [
{
"doctype": "Shipping Rule Condition",
@ -26,9 +27,12 @@
"doctype": "Shipping Rule Condition",
"from_value": 201,
"parentfield": "conditions",
"shipping_amount": 0.0
"shipping_amount": 200.0
}
],
"countries": [
{"country": "India"}
],
"worldwide_shipping": 1
},
{
@ -73,6 +77,7 @@
"doctype": "Shipping Rule",
"label": "_Test Shipping Rule - Rest of the World",
"name": "_Test Shipping Rule - Rest of the World",
"shipping_rule_type": "Buying",
"conditions": [
{
"doctype": "Shipping Rule Condition",
@ -95,6 +100,9 @@
"shipping_amount": 1500.0
}
],
"worldwide_shipping": 1
"worldwide_shipping": 1,
"countries": [
{"country": "Germany"}
]
}
]

View File

@ -7,6 +7,8 @@ QUnit.test("test Shipping Rule", function(assert) {
() => {
return frappe.tests.make("Shipping Rule", [
{label: "Next Day Shipping"},
{shipping_rule_type: "Selling"},
{calculate_based_on: 'Net Total'},
{conditions:[
[
{from_value:1},

View File

@ -36,3 +36,38 @@ class TestShippingRule(unittest.TestCase):
shipping_rule.get("conditions")[1].from_value = range_b[0]
shipping_rule.get("conditions")[1].to_value = range_b[1]
self.assertRaises(OverlappingConditionError, shipping_rule.insert)
def create_shipping_rule(shipping_rule_type, shipping_rule_name):
sr = frappe.new_doc("Shipping Rule")
sr.account = "_Test Account Shipping Charges - _TC"
sr.calculate_based_on = "Net Total"
sr.company = "_Test Company"
sr.cost_center = "_Test Cost Center - _TC"
sr.label = shipping_rule_name
sr.name = shipping_rule_name
sr.shipping_rule_type = shipping_rule_type
sr.append("conditions", {
"doctype": "Shipping Rule Condition",
"from_value": 0,
"parentfield": "conditions",
"shipping_amount": 50.0,
"to_value": 100
})
sr.append("conditions", {
"doctype": "Shipping Rule Condition",
"from_value": 101,
"parentfield": "conditions",
"shipping_amount": 100.0,
"to_value": 200
})
sr.append("conditions", {
"doctype": "Shipping Rule Condition",
"from_value": 201,
"parentfield": "conditions",
"shipping_amount": 200.0,
"to_value": 2000
})
sr.insert(ignore_permissions=True)
sr.submit()
return sr

View File

@ -0,0 +1,37 @@
QUnit.module('Shipping Rule');
QUnit.test("test Shipping Rule", function(assert) {
assert.expect(1);
let done = assert.async();
frappe.run_serially([
() => {
return frappe.tests.make("Shipping Rule", [
{label: "Two Day Shipping"},
{shipping_rule_type: "Buying"},
{fixed_shipping_amount: 0},
{conditions:[
[
{from_value:1},
{to_value:200},
{shipping_amount:100}
],
[
{from_value:201},
{to_value:3000},
{shipping_amount:200}
],
]},
{countries:[
[
{country:'India'}
]
]},
{account:'Accounts Payable - '+frappe.get_abbr(frappe.defaults.get_default("Company"))},
{cost_center:'Main - '+frappe.get_abbr(frappe.defaults.get_default("Company"))}
]);
},
() => {assert.ok(cur_frm.doc.name=='Two Day Shipping');},
() => done()
]);
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,43 @@
QUnit.module('Buying');
QUnit.test("test: purchase order with shipping rule", function(assert) {
assert.expect(3);
let done = assert.async();
frappe.run_serially([
() => {
return frappe.tests.make('Purchase Order', [
{supplier: 'Test Supplier'},
{is_subcontracted: 'No'},
{buying_price_list: 'Test-Buying-USD'},
{currency: 'USD'},
{"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
{items: [
[
{"item_code": 'Test Product 4'},
{"qty": 5},
{"uom": 'Unit'},
{"rate": 500 },
{"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
{"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)},
{"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))}
]
]},
{shipping_rule:'Two Day Shipping'}
]);
},
() => {
// Check grand total
assert.ok(cur_frm.doc.total_taxes_and_charges == 200, "Taxes and charges correct");
assert.ok(cur_frm.doc.grand_total == 2700, "Grand total correct");
},
() => frappe.timeout(0.3),
() => frappe.tests.click_button('Submit'),
() => frappe.tests.click_button('Yes'),
() => frappe.timeout(0.3),
() => done()
]);
});

View File

@ -299,6 +299,27 @@ class AccountsController(TransactionBase):
"allocated_amount": flt(d.amount) if d.against_order else 0
})
def apply_shipping_rule(self):
if self.shipping_rule:
shipping_rule = frappe.get_doc("Shipping Rule", self.shipping_rule)
shipping_rule.apply(self)
self.calculate_taxes_and_totals()
def get_shipping_address(self):
'''Returns Address object from shipping address fields if present'''
# shipping address fields can be `shipping_address_name` or `shipping_address`
# try getting value from both
for fieldname in ('shipping_address_name', 'shipping_address'):
shipping_field = self.meta.get_field(fieldname)
if shipping_field and shipping_field.fieldtype == 'Link':
if self.get(fieldname):
return frappe.get_doc('Address', self.get(fieldname))
return {}
def get_advance_entries(self, include_unallocated=True):
if self.doctype == "Sales Invoice":
party_account = self.debit_to

View File

@ -420,4 +420,5 @@ class BuyingController(StockController):
if d.schedule_date and getdate(d.schedule_date) < getdate(self.transaction_date):
frappe.throw(_("Expected Date cannot be before Transaction Date"))
else:
frappe.throw(_("Please enter Schedule Date"))
frappe.throw(_("Please enter Schedule Date"))

View File

@ -66,38 +66,6 @@ class SellingController(StockController):
self.set_price_list_currency("Selling")
self.set_missing_item_details(for_validate=for_validate)
def apply_shipping_rule(self):
if self.shipping_rule:
shipping_rule = frappe.get_doc("Shipping Rule", self.shipping_rule)
value = self.base_net_total
# TODO
# shipping rule calculation based on item's net weight
shipping_amount = 0.0
for condition in shipping_rule.get("conditions"):
if not condition.to_value or (flt(condition.from_value) <= value <= flt(condition.to_value)):
shipping_amount = condition.shipping_amount
break
shipping_charge = {
"doctype": "Sales Taxes and Charges",
"charge_type": "Actual",
"account_head": shipping_rule.account,
"cost_center": shipping_rule.cost_center
}
existing_shipping_charge = self.get("taxes", filters=shipping_charge)
if existing_shipping_charge:
# take the last record found
existing_shipping_charge[-1].tax_amount = shipping_amount
else:
shipping_charge["tax_amount"] = shipping_amount
shipping_charge["description"] = shipping_rule.label
self.append("taxes", shipping_charge)
self.calculate_taxes_and_totals()
def remove_shipping_charge(self):
if self.shipping_rule:
shipping_rule = frappe.get_doc("Shipping Rule", self.shipping_rule)

View File

@ -233,7 +233,8 @@ class calculate_taxes_and_totals(object):
# if tax/charges is for deduction, multiply by -1
if getattr(tax, "category", None):
tax_amount = 0.0 if (tax.category == "Valuation") else tax_amount
tax_amount *= -1.0 if (tax.add_deduct_tax == "Deduct") else 1.0
if self.doc.doctype in ["Purchase Order", "Purchase Invoice", "Purchase Receipt", "Supplier Quotation"]:
tax_amount *= -1.0 if (tax.add_deduct_tax == "Deduct") else 1.0
return tax_amount
def set_cumulative_total(self, row_idx, tax):

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 214 KiB

View File

@ -1,7 +1,7 @@
# Shipping Rule
Using Shipping Rule you can define the cost for delivering the product to the customer.
You can define different shipping rules for the same item across different territories.
Using Shipping Rule you can define the cost for delivering the product to the customer and also to the supplier.
You can define different shipping rules or a fixed shipping amount for the same item across different territories.
<img class="screenshot" alt="Shipping Rule" src="/docs/assets/img/selling/shipping-rule.png">

View File

@ -464,3 +464,4 @@ erpnext.patches.v9_0.update_employee_loan_details
erpnext.patches.v9_2.delete_healthcare_domain_default_items
erpnext.patches.v9_1.create_issue_opportunity_type
erpnext.patches.v9_2.rename_translated_domains_in_en
erpnext.patches.v9_0.set_shipping_type_for_existing_shipping_rules

View File

@ -0,0 +1,18 @@
# Copyright (c) 2017, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
frappe.reload_doctype("Shipping Rule")
# default "calculate_based_on"
frappe.db.sql('''update `tabShipping Rule`
set calculate_based_on = "Net Weight"
where ifnull(calculate_based_on, '') = '' ''')
# default "shipping_rule_type"
frappe.db.sql('''update `tabShipping Rule`
set shipping_rule_type = "Selling"
where ifnull(shipping_rule_type, '') = '' ''')

View File

@ -18,6 +18,14 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({
this.setup_queries();
this._super();
this.frm.set_query('shipping_rule', function() {
return {
filters: {
"shipping_rule_type": "Buying"
}
};
});
/* eslint-disable */
// no idea where me is coming from
if(this.frm.get_field('shipping_address')) {

View File

@ -36,7 +36,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
cur_frm.cscript.set_gross_profit(item);
cur_frm.cscript.calculate_taxes_and_totals();
})
});
frappe.ui.form.on(this.frm.cscript.tax_table, "rate", function(frm, cdt, cdn) {
cur_frm.cscript.calculate_taxes_and_totals();
@ -98,7 +98,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
var me = this;
if(this.frm.fields_dict["items"].grid.get_field('batch_no')) {
this.frm.set_query("batch_no", "items", function(doc, cdt, cdn) {
return me.set_query_for_batch(doc, cdt, cdn)
return me.set_query_for_batch(doc, cdt, cdn);
});
}
@ -116,7 +116,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
filters: [
['Print Format', 'doc_type', '=', cur_frm.doctype],
]
}
};
});
}
@ -553,6 +553,21 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
this.frm.set_df_property("conversion_rate", "read_only", erpnext.stale_rate_allowed());
},
shipping_rule: function() {
var me = this;
if(this.frm.doc.shipping_rule) {
return this.frm.call({
doc: this.frm.doc,
method: "apply_shipping_rule",
callback: function(r) {
if(!r.exc) {
me.calculate_taxes_and_totals();
}
}
}).fail(() => this.frm.set_value('shipping_rule', ''));
}
},
set_actual_charges_based_on_currency: function() {
var me = this;
$.each(this.frm.doc.taxes || [], function(i, d) {
@ -701,12 +716,12 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
"base_grand_total", "base_rounded_total", "base_in_words", "base_discount_amount",
"base_paid_amount", "base_write_off_amount", "base_operating_cost", "base_raw_material_cost",
"base_total_cost", "base_scrap_material_cost", "base_rounding_adjustment"],
this.frm.doc.currency != company_currency);
this.frm.doc.currency != company_currency);
this.frm.toggle_display(["plc_conversion_rate", "price_list_currency"],
this.frm.doc.price_list_currency != company_currency);
var show =cint(cur_frm.doc.discount_amount) ||
var show = cint(cur_frm.doc.discount_amount) ||
((cur_frm.doc.taxes || []).filter(function(d) {return d.included_in_print_rate===1}).length);
if(frappe.meta.get_docfield(cur_frm.doctype, "net_total"))
@ -816,7 +831,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
if (!r.exc && r.message) {
me._set_values_for_item_list(r.message);
me.calculate_taxes_and_totals();
if(me.frm.doc.apply_discount_on) me.frm.trigger("apply_discount_on")
if(me.frm.doc.apply_discount_on) me.frm.trigger("apply_discount_on");
}
}
});
@ -895,8 +910,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
// if doctype is Quotation Item / Sales Order Iten then add Margin Type and rate in item_list
if (in_list(["Quotation Item", "Sales Order Item", "Delivery Note Item", "Sales Invoice Item"]), d.doctype){
item_list[0]["margin_type"] = d.margin_type
item_list[0]["margin_rate_or_amount"] = d.margin_rate_or_amount
item_list[0]["margin_type"] = d.margin_type;
item_list[0]["margin_rate_or_amount"] = d.margin_rate_or_amount;
}
}
};
@ -1101,12 +1116,12 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
},
get_method_for_payment: function(){
var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry"
var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry";
if(cur_frm.doc.__onload && cur_frm.doc.__onload.make_payment_via_journal_entry){
if(in_list(['Sales Invoice', 'Purchase Invoice'], cur_frm.doc.doctype)){
method = "erpnext.accounts.doctype.journal_entry.journal_entry.get_payment_entry_against_invoice"
method = "erpnext.accounts.doctype.journal_entry.journal_entry.get_payment_entry_against_invoice";
}else {
method= "erpnext.accounts.doctype.journal_entry.journal_entry.get_payment_entry_against_order"
method= "erpnext.accounts.doctype.journal_entry.journal_entry.get_payment_entry_against_order";
}
}
@ -1132,9 +1147,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
'item_code': item.item_code,
'posting_date': me.frm.doc.posting_date || frappe.datetime.nowdate(),
}
if (item.warehouse) filters["warehouse"] = item.warehouse
if (item.warehouse) filters["warehouse"] = item.warehouse;
return {
return s
query : "erpnext.controllers.queries.get_batch_no",
filters: filters
}

View File

@ -17,6 +17,13 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
onload: function() {
this._super();
this.setup_queries();
this.frm.set_query('shipping_rule', function() {
return {
filters: {
"shipping_rule_type": "Selling"
}
};
});
},
setup_queries: function() {
@ -233,21 +240,6 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
});
},
shipping_rule: function() {
var me = this;
if(this.frm.doc.shipping_rule) {
return this.frm.call({
doc: this.frm.doc,
method: "apply_shipping_rule",
callback: function(r) {
if(!r.exc) {
me.calculate_taxes_and_totals();
}
}
})
}
},
batch_no: function(doc, cdt, cdn) {
var me = this;
var item = frappe.get_doc(cdt, cdn);

View File

@ -127,6 +127,7 @@ class TestShoppingCart(unittest.TestCase):
"selling_price_list": "_Test Price List Rest of the World",
"currency": "USD",
"taxes_and_charges" : "_Test Tax 1",
"conversion_rate":1,
"transaction_date" : nowdate(),
"valid_till" : add_months(nowdate(), 1),
"items": [{

File diff suppressed because it is too large Load Diff