diff --git a/erpnext/accounts/doctype/loyalty_point_entry/__init__.py b/erpnext/accounts/doctype/loyalty_point_entry/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.js b/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.js
new file mode 100644
index 00000000000..d7dc7f3f3c7
--- /dev/null
+++ b/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Loyalty Point Entry', {
+ refresh: function(frm) {
+
+ }
+});
diff --git a/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.json b/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.json
new file mode 100644
index 00000000000..be95f009d3a
--- /dev/null
+++ b/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.json
@@ -0,0 +1,419 @@
+{
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "autoname": "LPE.####",
+ "beta": 0,
+ "creation": "2018-01-23 05:40:18.117583",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "loyalty_program",
+ "fieldtype": "Link",
+ "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": "Loyalty Program",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Loyalty Program",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "loyalty_program_tier",
+ "fieldtype": "Data",
+ "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": "Loyalty Program Tier",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "customer",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Customer",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Customer",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "sales_invoice",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Sales Invoice",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Sales Invoice",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "redeem_against",
+ "fieldtype": "Link",
+ "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": "Redeem Against",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Loyalty Point Entry",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "loyalty_points",
+ "fieldtype": "Int",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Loyalty Points",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "purchase_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": "Purchase 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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "expiry_date",
+ "fieldtype": "Date",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Expiry Date",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "posting_date",
+ "fieldtype": "Date",
+ "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": "Posting Date",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "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": "Company",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Company",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 0,
+ "max_attachments": 0,
+ "modified": "2018-03-26 08:52:09.468010",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Loyalty Point Entry",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "amend": 0,
+ "apply_user_permissions": 0,
+ "cancel": 0,
+ "create": 0,
+ "delete": 0,
+ "email": 1,
+ "export": 1,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Auditor",
+ "set_user_permissions": 0,
+ "share": 0,
+ "submit": 0,
+ "write": 0
+ },
+ {
+ "amend": 0,
+ "apply_user_permissions": 0,
+ "cancel": 0,
+ "create": 0,
+ "delete": 0,
+ "email": 1,
+ "export": 1,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts Manager",
+ "set_user_permissions": 0,
+ "share": 0,
+ "submit": 0,
+ "write": 0
+ },
+ {
+ "amend": 0,
+ "apply_user_permissions": 0,
+ "cancel": 0,
+ "create": 0,
+ "delete": 0,
+ "email": 1,
+ "export": 1,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts User",
+ "set_user_permissions": 0,
+ "share": 0,
+ "submit": 0,
+ "write": 0
+ }
+ ],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "customer",
+ "track_changes": 1,
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py b/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py
new file mode 100644
index 00000000000..37fce0b8567
--- /dev/null
+++ b/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+from frappe.utils import today
+
+exclude_from_linked_with = True
+
+class LoyaltyPointEntry(Document):
+ pass
+
+
+def get_loyalty_point_entries(customer, loyalty_program, expiry_date=None, company=None):
+ if not expiry_date:
+ date = today()
+ args_list = [customer, loyalty_program, expiry_date]
+ condition = ''
+ if company:
+ condition = " and company=%s "
+ args_list.append(company)
+ loyalty_point_details = frappe.db.sql('''select name, loyalty_points, expiry_date, loyalty_program_tier
+ from `tabLoyalty Point Entry` where customer=%s and loyalty_program=%s and expiry_date>=%s and loyalty_points>0
+ {condition} order by expiry_date'''.format(condition=condition), tuple(args_list), as_dict=1)
+ return loyalty_point_details
diff --git a/erpnext/accounts/doctype/loyalty_point_entry/test_loyalty_point_entry.js b/erpnext/accounts/doctype/loyalty_point_entry/test_loyalty_point_entry.js
new file mode 100644
index 00000000000..a916b67522c
--- /dev/null
+++ b/erpnext/accounts/doctype/loyalty_point_entry/test_loyalty_point_entry.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Loyalty Point Entry", function (assert) {
+ let done = assert.async();
+
+ // number of asserts
+ assert.expect(1);
+
+ frappe.run_serially([
+ // insert a new Loyalty Point Entry
+ () => frappe.tests.make('Loyalty Point Entry', [
+ // values to be set
+ {key: 'value'}
+ ]),
+ () => {
+ assert.equal(cur_frm.doc.key, 'value');
+ },
+ () => done()
+ ]);
+
+});
diff --git a/erpnext/accounts/doctype/loyalty_point_entry/test_loyalty_point_entry.py b/erpnext/accounts/doctype/loyalty_point_entry/test_loyalty_point_entry.py
new file mode 100644
index 00000000000..b6e2d57b9fb
--- /dev/null
+++ b/erpnext/accounts/doctype/loyalty_point_entry/test_loyalty_point_entry.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+
+class TestLoyaltyPointEntry(unittest.TestCase):
+ pass
diff --git a/erpnext/accounts/doctype/loyalty_point_entry_redemption/__init__.py b/erpnext/accounts/doctype/loyalty_point_entry_redemption/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/erpnext/accounts/doctype/loyalty_point_entry_redemption/loyalty_point_entry_redemption.json b/erpnext/accounts/doctype/loyalty_point_entry_redemption/loyalty_point_entry_redemption.json
new file mode 100644
index 00000000000..8a8dfbb5227
--- /dev/null
+++ b/erpnext/accounts/doctype/loyalty_point_entry_redemption/loyalty_point_entry_redemption.json
@@ -0,0 +1,134 @@
+{
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "beta": 0,
+ "creation": "2018-03-26 01:32:10.108450",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "sales_invoice",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Sales Invoice",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "redemption_date",
+ "fieldtype": "Date",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Redemption Date",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "redeemed_points",
+ "fieldtype": "Int",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Redeemed Points",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 1,
+ "max_attachments": 0,
+ "modified": "2018-03-26 03:12:59.173071",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Loyalty Point Entry Redemption",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1,
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/loyalty_point_entry_redemption/loyalty_point_entry_redemption.py b/erpnext/accounts/doctype/loyalty_point_entry_redemption/loyalty_point_entry_redemption.py
new file mode 100644
index 00000000000..e4382b6c789
--- /dev/null
+++ b/erpnext/accounts/doctype/loyalty_point_entry_redemption/loyalty_point_entry_redemption.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+
+class LoyaltyPointEntryRedemption(Document):
+ pass
diff --git a/erpnext/accounts/doctype/loyalty_program/__init__.py b/erpnext/accounts/doctype/loyalty_program/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/erpnext/accounts/doctype/loyalty_program/loyalty_program.js b/erpnext/accounts/doctype/loyalty_program/loyalty_program.js
new file mode 100644
index 00000000000..524a671801b
--- /dev/null
+++ b/erpnext/accounts/doctype/loyalty_program/loyalty_program.js
@@ -0,0 +1,65 @@
+// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Loyalty Program', {
+ setup: function(frm) {
+ var help_content =
+ `
+
+
+
+ ${__('Notes')}
+
+
+ -
+ ${__("Loyalty Points will be calculated from the spent done (via the Sales Invoice), based on collection factor mentioned.")}
+
+ -
+ ${__("There can be multiple tiered collection factor based on the total spent. But the conversion factor for redemption will always be same for all the tier.")}
+
+ -
+ ${__("In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent")}
+
+ -
+ ${__("If unlimited expiry for the Loyalty Points, keep the Expiry Duration empty or 0.")}
+
+ -
+ ${__("If Auto Opt In is checked, then the customers will be automatically linked with the concerned Loyalty Program (on save)")}
+
+ -
+ ${__("One customer can be part of only single Loyalty Program.")}
+
+
+ |
+
`;
+ set_field_options("loyalty_program_help", help_content);
+ },
+
+ onload: function(frm) {
+ frm.set_query("expense_account", function(doc) {
+ return {
+ filters: {
+ "root_type": "Expense",
+ 'is_group': 0,
+ 'company': doc.company
+ }
+ };
+ });
+
+ frm.set_query("cost_center", function() {
+ return {
+ filters: {
+ company: frm.doc.company
+ }
+ };
+ });
+
+ frm.set_value("company", frappe.defaults.get_user_default("Company"));
+ },
+
+ refresh: function(frm) {
+ if (frm.doc.loyalty_program_type === "Single Tier Program" && frm.doc.collection_rules.length > 1) {
+ frappe.throw(__("Please select the Multiple Tier Program type for more than one collection rules."));
+ }
+ }
+});
diff --git a/erpnext/accounts/doctype/loyalty_program/loyalty_program.json b/erpnext/accounts/doctype/loyalty_program/loyalty_program.json
new file mode 100644
index 00000000000..4536a7a2897
--- /dev/null
+++ b/erpnext/accounts/doctype/loyalty_program/loyalty_program.json
@@ -0,0 +1,687 @@
+{
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "autoname": "field:loyalty_program_name",
+ "beta": 0,
+ "creation": "2018-01-23 06:23:05.731431",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "disabled",
+ "fieldtype": "Check",
+ "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": "Disabled",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "column_break_2",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "auto_opt_in",
+ "fieldtype": "Check",
+ "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": "Auto Opt In (For all customers)",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "section_break_2",
+ "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,
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "loyalty_program_name",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Loyalty Program Name",
+ "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": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "loyalty_program_type",
+ "fieldtype": "Select",
+ "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": "Loyalty Program Type",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Single Tier Program\nMultiple Tier Program",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "column_break_7",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "customer_group",
+ "fieldtype": "Link",
+ "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": "Customer Group",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Customer Group",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "customer_territory",
+ "fieldtype": "Link",
+ "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": "Customer Territory",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Territory",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "rules",
+ "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": "Collection Tier",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "collection_rules",
+ "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": "Collection Rules",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Loyalty Program Collection",
+ "permlevel": 0,
+ "precision": "",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "redemption",
+ "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": "Redemption",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "description": "1 Loyalty Points = How much base currency?",
+ "fieldname": "conversion_factor",
+ "fieldtype": "Float",
+ "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": "Conversion Factor",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "expiry_duration",
+ "fieldtype": "Int",
+ "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": "Expiry Duration (in days)",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "column_break_10",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "expense_account",
+ "fieldtype": "Link",
+ "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": "Expense Account",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Account",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "cost_center",
+ "fieldtype": "Link",
+ "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": "Cost Center",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Cost Center",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "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": "Company",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Company",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "help_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": "Help Section",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "loyalty_program_help",
+ "fieldtype": "HTML",
+ "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": "Loyalty Program Help",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 0,
+ "max_attachments": 0,
+ "modified": "2018-03-21 10:20:25.468206",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Loyalty Program",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "amend": 0,
+ "apply_user_permissions": 0,
+ "cancel": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 0,
+ "write": 1
+ }
+ ],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1,
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/loyalty_program/loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/loyalty_program.py
new file mode 100644
index 00000000000..593e4265317
--- /dev/null
+++ b/erpnext/accounts/doctype/loyalty_program/loyalty_program.py
@@ -0,0 +1,125 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+from frappe import _
+from frappe.model.document import Document
+from frappe.utils import today, flt
+
+class LoyaltyProgram(Document):
+ pass
+
+
+def get_loyalty_details(customer, loyalty_program, expiry_date=None, company=None):
+ if not expiry_date:
+ expiry_date = today()
+ args_list = [customer, loyalty_program, expiry_date]
+ condition = ''
+ if company:
+ condition = " and company=%s "
+ args_list.append(company)
+ loyalty_point_details = frappe.db.sql('''select sum(loyalty_points) as loyalty_points,
+ sum(purchase_amount) as total_spent from `tabLoyalty Point Entry`
+ where customer=%s and loyalty_program=%s and (expiry_date>=%s) {condition}
+ group by customer'''.format(condition=condition), tuple(args_list), as_dict=1)
+ if loyalty_point_details:
+ return loyalty_point_details[0]
+ else:
+ return {"loyalty_points": 0, "total_spent": 0}
+
+@frappe.whitelist()
+def get_loyalty_program_details(customer, loyalty_program=None, expiry_date=None, company=None, silent=False):
+ lp_details = frappe._dict()
+ customer_loyalty_program = frappe.db.get_value("Customer", customer, "loyalty_program")
+
+ if not (customer_loyalty_program or silent):
+ frappe.throw(_("Customer isn't enrolled in any Loyalty Program"))
+ elif silent and not customer_loyalty_program:
+ return frappe._dict({"loyalty_program": None})
+
+ if loyalty_program and loyalty_program != customer_loyalty_program:
+ frappe.throw(_("Customer isn't enrolled in this Loyalty Program"))
+
+ if not loyalty_program:
+ loyalty_program = customer_loyalty_program
+ if not company:
+ company = frappe.db.get_default("company") or frappe.get_all("Company")[0].name
+
+ lp_details.update(get_loyalty_details(customer, loyalty_program, expiry_date, company))
+
+ lp_details.update({"loyalty_program": loyalty_program})
+ loyalty_program = frappe.get_doc("Loyalty Program", lp_details.loyalty_program)
+
+ lp_details.expiry_duration = loyalty_program.expiry_duration
+ lp_details.conversion_factor = loyalty_program.conversion_factor
+ lp_details.expense_account = loyalty_program.expense_account
+ lp_details.cost_center = loyalty_program.cost_center
+ lp_details.company = loyalty_program.company
+
+ tier_spent_level = sorted([d.as_dict() for d in loyalty_program.collection_rules], key=lambda rule:rule.min_spent, reverse=True)
+ for i, d in enumerate(tier_spent_level):
+ if i==0 or lp_details.total_spent < d.min_spent:
+ lp_details.tier_name = d.tier_name
+ lp_details.collection_factor = d.collection_factor
+ else:
+ break
+ return lp_details
+
+@frappe.whitelist()
+def get_redeemption_factor(loyalty_program=None, customer=None):
+ customer_loyalty_program = None
+ if not loyalty_program:
+ customer_loyalty_program = frappe.db.get_value("Customer", customer, "loyalty_program")
+ loyalty_program = customer_loyalty_program
+ if loyalty_program:
+ return frappe.db.get_value("Loyalty Program", loyalty_program, "conversion_factor")
+ else:
+ frappe.throw(_("Customer isn't enrolled in any Loyalty Program"))
+
+
+def validate_loyalty_points(ref_doc, points_to_redeem):
+ loyalty_program = None
+ posting_date = None
+
+ if ref_doc.doctype == "Sales Invoice":
+ posting_date = ref_doc.posting_date
+ else:
+ posting_date = today()
+
+ if hasattr(ref_doc, "loyalty_program") and ref_doc.loyalty_program:
+ loyalty_program = ref_doc.loyalty_program
+ else:
+ loyalty_program = frappe.db.get_value("Customer", ref_doc.customer, ["loyalty_program"])
+
+ if loyalty_program and frappe.db.get_value("Loyalty Program", loyalty_program, ["company"]) !=\
+ ref_doc.company:
+ frappe.throw(_("The Loyalty Program isn't valid for the selected company"))
+
+ if loyalty_program and points_to_redeem:
+ loyalty_program_details = get_loyalty_program_details(ref_doc.customer, loyalty_program,
+ posting_date, ref_doc.company)
+
+ if points_to_redeem > loyalty_program_details.loyalty_points:
+ frappe.throw(_("You don't have enought Loyalty Points to redeem"))
+
+ loyalty_amount = flt(points_to_redeem * loyalty_program_details.conversion_factor)
+
+ if loyalty_amount > ref_doc.grand_total:
+ frappe.throw(_("You can't redeem Loyalty Points having more value than the Grand Total."))
+
+ if not ref_doc.loyalty_amount and ref_doc.loyalty_amount != loyalty_amount:
+ ref_doc.loyalty_amount = loyalty_amount
+
+ if ref_doc.doctype == "Sales Invoice":
+ ref_doc.loyalty_program = loyalty_program
+ if not ref_doc.loyalty_redemption_account:
+ ref_doc.loyalty_redemption_account = loyalty_program_details.expense_account
+
+ if not ref_doc.loyalty_redemption_cost_center:
+ ref_doc.loyalty_redemption_cost_center = loyalty_program_details.cost_center
+
+ elif ref_doc.doctype == "Sales Order":
+ return loyalty_amount
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.js b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.js
new file mode 100644
index 00000000000..9321c14e1f0
--- /dev/null
+++ b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Loyalty Program", function (assert) {
+ let done = assert.async();
+
+ // number of asserts
+ assert.expect(1);
+
+ frappe.run_serially([
+ // insert a new Loyalty Program
+ () => frappe.tests.make('Loyalty Program', [
+ // values to be set
+ {key: 'value'}
+ ]),
+ () => {
+ assert.equal(cur_frm.doc.key, 'value');
+ },
+ () => done()
+ ]);
+
+});
diff --git a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py
new file mode 100644
index 00000000000..a5cf7655999
--- /dev/null
+++ b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+
+class TestLoyaltyProgram(unittest.TestCase):
+ pass
diff --git a/erpnext/accounts/doctype/loyalty_program_collection/__init__.py b/erpnext/accounts/doctype/loyalty_program_collection/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/erpnext/accounts/doctype/loyalty_program_collection/loyalty_program_collection.json b/erpnext/accounts/doctype/loyalty_program_collection/loyalty_program_collection.json
new file mode 100644
index 00000000000..6ea52169b51
--- /dev/null
+++ b/erpnext/accounts/doctype/loyalty_program_collection/loyalty_program_collection.json
@@ -0,0 +1,161 @@
+{
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "beta": 0,
+ "creation": "2018-01-23 06:56:37.163859",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 0,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "tier_name",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Tier Name",
+ "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": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "min_spent",
+ "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": "Minimum Total Spent",
+ "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": "column_break_3",
+ "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,
+ "description": "For how much spent = 1 Loyalty Point",
+ "fieldname": "collection_factor",
+ "fieldtype": "Currency",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Collection Factor (=1 LP)",
+ "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": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 1,
+ "max_attachments": 0,
+ "modified": "2018-01-23 07:19:10.316392",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Loyalty Program Collection",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1,
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/loyalty_program_collection/loyalty_program_collection.py b/erpnext/accounts/doctype/loyalty_program_collection/loyalty_program_collection.py
new file mode 100644
index 00000000000..42cc38cb4c2
--- /dev/null
+++ b/erpnext/accounts/doctype/loyalty_program_collection/loyalty_program_collection.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+
+class LoyaltyProgramCollection(Document):
+ pass
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index fd48faa3a29..a633cc31a61 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -240,14 +240,23 @@ def make_payment_request(**args):
"""Make payment request"""
args = frappe._dict(args)
+
ref_doc = frappe.get_doc(args.dt, args.dn)
grand_total = get_amount(ref_doc, args.dt)
+ if args.loyalty_points and args.dt == "Sales Order":
+ from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_loyalty_points
+ loyalty_amount = validate_loyalty_points(ref_doc, int(args.loyalty_points))
+ frappe.db.set_value("Sales Order", args.dn, "loyalty_points", int(args.loyalty_points), update_modified=False)
+ frappe.db.set_value("Sales Order", args.dn, "loyalty_amount", loyalty_amount, update_modified=False)
+ grand_total = grand_total - loyalty_amount
+
gateway_account = get_gateway_details(args) or frappe._dict()
existing_payment_request = frappe.db.get_value("Payment Request",
{"reference_doctype": args.dt, "reference_name": args.dn, "docstatus": ["!=", 2]})
if existing_payment_request:
+ frappe.db.set_value("Payment Request", existing_payment_request, "grand_total", grand_total, update_modified=False)
pr = frappe.get_doc("Payment Request", existing_payment_request)
else:
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 8f57352e780..f2b9ba295c8 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -17,6 +17,8 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
onload: function() {
var me = this;
this._super();
+ console.log("class erpnext.accounts.SalesInvoiceController, onload this->", this);
+
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
// show debit_to in print format
@@ -36,6 +38,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
},
refresh: function(doc, dt, dn) {
+ console.log("triggered the SalesInvoiceController");
this._super();
if(cur_frm.msgbox && cur_frm.msgbox.$wrapper.is(":visible")) {
// hide new msgbox
@@ -228,7 +231,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
price_list: this.frm.doc.selling_price_list,
}, function() {
me.apply_pricing_rule();
- })
+ });
},
make_inter_company_invoice: function() {
@@ -355,7 +358,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
});
}
}
- else this.frm.trigger("refresh")
+ else this.frm.trigger("refresh");
},
amount: function(){
@@ -364,13 +367,21 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
change_amount: function(){
if(this.frm.doc.paid_amount > this.frm.doc.grand_total){
- this.calculate_write_off_amount()
+ this.calculate_write_off_amount();
}else {
- this.frm.set_value("change_amount", 0.0)
- this.frm.set_value("base_change_amount", 0.0)
+ this.frm.set_value("change_amount", 0.0);
+ this.frm.set_value("base_change_amount", 0.0);
}
this.frm.refresh_fields();
+ },
+
+ loyalty_amount: function(){
+ console.log("triggered the loyalty amount");
+ this.calculate_outstanding_amount();
+ this.frm.refresh_field("outstanding_amount");
+ this.frm.refresh_field("paid_amount");
+ this.frm.refresh_field("base_paid_amount");
}
});
@@ -521,7 +532,12 @@ cur_frm.set_query("asset", "items", function(doc, cdt, cdn) {
});
frappe.ui.form.on('Sales Invoice', {
+ refresh: function(frm) {
+ frm.add_fetch('customer', 'loyalty_program', 'loyalty_program');
+ },
+
setup: function(frm){
+
frm.custom_make_buttons = {
'Delivery Note': 'Delivery',
'Sales Invoice': 'Sales Return',
@@ -612,7 +628,71 @@ frappe.ui.form.on('Sales Invoice', {
refresh_field(['timesheets'])
}
})
+ },
+
+ onload: function(frm) {
+ frm.redemption_conversion_factor = null;
+ },
+
+ redeem_loyalty_points: function(frm) {
+ frm.events.get_loyalty_details(frm);
+ },
+
+ loyalty_points: function(frm) {
+ if (frm.redemption_conversion_factor) {
+ frm.events.set_loyalty_points(frm);
+ } else {
+ frappe.call({
+ method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_redeemption_factor",
+ args: {
+ "loyalty_program": frm.doc.loyalty_program
+ },
+ callback: function(r) {
+ if (r) {
+ frm.redemption_conversion_factor = r.message;
+ frm.events.set_loyalty_points(frm);
+ }
+ }
+ });
+ }
+ },
+
+ get_loyalty_details: function(frm) {
+ if (frm.doc.customer && frm.doc.redeem_loyalty_points) {
+ frappe.call({
+ method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_loyalty_program_details",
+ args: {
+ "customer": frm.doc.customer,
+ "till_date": frm.doc.posting_date,
+ "company": frm.doc.company
+ },
+ callback: function(r) {
+ if (r) {
+ frm.set_value("loyalty_program", r.message.loyalty_program);
+ frm.set_value("loyalty_redemption_account", r.message.expense_account);
+ frm.set_value("loyalty_redemption_cost_center", r.message.cost_center);
+ frm.redemption_conversion_factor = r.message.conversion_factor;
+ // let max_loyalty_points = parseInt((frm.doc.grand_total-frm.doc.total_advance)/r.message.conversion_factor);
+ // let redeemable_points = max_loyalty_points > r.message.loyalty_points ? r.message.loyalty_points : max_loyalty_points;
+ // frm.set_value("loyalty_points", redeemable_points);
+ }
+ }
+ });
+ }
+ },
+
+ set_loyalty_points: function(frm) {
+ if (frm.redemption_conversion_factor) {
+ let loyalty_amount = flt(frm.redemption_conversion_factor*flt(frm.doc.loyalty_points), precision("loyalty_amount"));
+ var remaining_amount = flt(frm.doc.grand_total - frm.doc.total_advance)
+ if (frm.doc.grand_total && (remaining_amount < loyalty_amount)) {
+ let redeemable_amount = parseInt(remaining_amount/frm.redemption_conversion_factor);
+ frappe.throw(__("You can only redeem max {0} points in this order.",[redeemable_amount]));
+ }
+ frm.set_value("loyalty_amount", loyalty_amount);
+ }
}
+
})
frappe.ui.form.on('Sales Invoice Timesheet', {
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index 89bfc8f0d2a..663042fc596 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -381,6 +381,38 @@
"translatable": 0,
"unique": 0
},
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "redeem_loyalty_points",
+ "fieldtype": "Check",
+ "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": "Redeem Loyalty Points",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 1,
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
@@ -2423,6 +2455,234 @@
"translatable": 0,
"unique": 0
},
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "collapsible_depends_on": "",
+ "columns": 0,
+ "depends_on": "redeem_loyalty_points",
+ "fieldname": "loyalty_points_redemption",
+ "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": "Loyalty Points Redemption",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "loyalty_points",
+ "fieldtype": "Int",
+ "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": "Loyalty Points",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "loyalty_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": "Loyalty Amount",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "column_break_77",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "loyalty_program",
+ "fieldtype": "Link",
+ "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": "Loyalty Program",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Loyalty Program",
+ "permlevel": 0,
+ "precision": "",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "loyalty_redemption_account",
+ "fieldtype": "Link",
+ "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": "Redemption Account",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Account",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "loyalty_redemption_cost_center",
+ "fieldtype": "Link",
+ "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": "Redemption Cost Center",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Cost Center",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
@@ -3426,7 +3686,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "depends_on": "is_pos",
+ "depends_on": "eval: doc.is_pos || doc.redeem_loyalty_points",
"fieldname": "paid_amount",
"fieldtype": "Currency",
"hidden": 0,
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 4c617b457af..7f11f3563ea 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe, erpnext
import frappe.defaults
-from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days
+from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr
from frappe import _, msgprint, throw
from erpnext.accounts.party import get_party_account, get_due_date
from erpnext.controllers.stock_controller import update_gl_entries_after
@@ -21,6 +21,8 @@ from erpnext.stock.doctype.batch.batch import set_batch_nos
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, get_delivery_note_serial_no
from erpnext.setup.doctype.company.company import update_company_current_month_sales
from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center
+from erpnext.accounts.doctype.loyalty_program.loyalty_program import \
+ get_loyalty_program_details, get_loyalty_details, validate_loyalty_points
from six import iteritems
@@ -106,6 +108,9 @@ class SalesInvoice(SellingController):
self.set_status()
if self.is_pos and not self.is_return:
self.verify_payment_amount_is_positive()
+ if self.redeem_loyalty_points and self.loyalty_program and self.loyalty_points:
+ validate_loyalty_points(self, self.loyalty_points)
+
def before_save(self):
set_account_for_mode_of_payment(self)
@@ -152,6 +157,15 @@ class SalesInvoice(SellingController):
self.update_project()
update_linked_invoice(self.doctype, self.name, self.inter_company_invoice_reference)
+ # create the loyalty point ledger entry if the customer is enrolled in any loyalty program
+ if not self.is_return and self.loyalty_program:
+ self.make_loyalty_point_entry()
+ elif self.is_return and self.return_against and self.loyalty_program:
+ against_si_doc = frappe.get_doc("Sales Invoice", self.return_against)
+ against_si_doc.delete_loyalty_point_entry()
+ if self.redeem_loyalty_points and self.loyalty_points:
+ self.apply_loyalty_points()
+
def validate_pos_paid_amount(self):
if len(self.payments) == 0 and self.is_pos:
frappe.throw(_("At least one mode of payment is required for POS invoice."))
@@ -191,6 +205,11 @@ class SalesInvoice(SellingController):
if frappe.db.get_single_value('Selling Settings', 'sales_update_frequency') == "Each Transaction":
update_company_current_month_sales(self.company)
self.update_project()
+ if not self.is_return and self.loyalty_program:
+ self.delete_loyalty_point_entry()
+ elif self.is_return and self.return_against and self.loyalty_program:
+ against_si_doc = frappe.get_doc("Sales Invoice", self.return_against)
+ against_si_doc.make_loyalty_point_entry()
unlink_inter_company_invoice(self.doctype, self.name, self.inter_company_invoice_reference)
@@ -607,7 +626,8 @@ class SalesInvoice(SellingController):
from erpnext.accounts.general_ledger import make_gl_entries
# if POS and amount is written off, updating outstanding amt after posting all gl entries
- update_outstanding = "No" if (cint(self.is_pos) or self.write_off_account) else "Yes"
+ update_outstanding = "No" if (cint(self.is_pos) or self.write_off_account or
+ cint(self.redeem_loyalty_points)) else "Yes"
make_gl_entries(gl_entries, cancel=(self.docstatus == 2),
update_outstanding=update_outstanding, merge_entries=False)
@@ -640,6 +660,7 @@ class SalesInvoice(SellingController):
# merge gl entries before adding pos entries
gl_entries = merge_similar_entries(gl_entries)
+ self.make_loyalty_point_redemption_gle(gl_entries)
self.make_pos_gl_entries(gl_entries)
self.make_gle_for_change_amount(gl_entries)
@@ -716,6 +737,29 @@ class SalesInvoice(SellingController):
erpnext.is_perpetual_inventory_enabled(self.company):
gl_entries += super(SalesInvoice, self).get_gl_entries()
+ def make_loyalty_point_redemption_gle(self, gl_entries):
+ if cint(self.redeem_loyalty_points):
+ gl_entries.append(
+ self.get_gl_dict({
+ "account": self.debit_to,
+ "party_type": "Customer",
+ "party": self.customer,
+ "against": "Expense account - " + cstr(self.loyalty_redemption_account) + " for the Loyalty Program",
+ "credit": self.loyalty_amount,
+ "against_voucher": self.return_against if cint(self.is_return) else self.name,
+ "against_voucher_type": self.doctype
+ })
+ )
+ gl_entries.append(
+ self.get_gl_dict({
+ "account": self.loyalty_redemption_account,
+ "cost_center": self.loyalty_redemption_cost_center,
+ "against": self.customer,
+ "debit": self.loyalty_amount,
+ "remark": "Loyalty Points redeemed by the customer"
+ })
+ )
+
def make_pos_gl_entries(self, gl_entries):
if cint(self.is_pos):
for payment_mode in self.payments:
@@ -920,6 +964,71 @@ class SalesInvoice(SellingController):
if entry.amount < 0:
frappe.throw(_("Row #{0} (Payment Table): Amount must be positive").format(entry.idx))
+ # collection of the loyalty points, create the ledger entry for that.
+ def make_loyalty_point_entry(self):
+ loyalty_program_details = get_loyalty_program_details(self.customer, company=self.company)
+ if loyalty_program_details:
+ points_earned = int(self.grand_total/loyalty_program_details.collection_factor)
+ doc = frappe.get_doc({
+ "doctype": "Loyalty Point Entry",
+ "company": self.company,
+ "loyalty_program": loyalty_program_details.loyalty_program,
+ "loyalty_program_tier": loyalty_program_details.tier_name,
+ "customer": self.customer,
+ "sales_invoice": self.name,
+ "loyalty_points": points_earned,
+ "purchase_amount": self.grand_total,
+ "expiry_date": add_days(self.posting_date, loyalty_program_details.expiry_duration),
+ "posting_date": self.posting_date
+ })
+ doc.flags.ignore_permissions = 1
+ doc.save()
+ # frappe.db.set_value("Customer", self.customer, "loyalty_program_tier", loyalty_program_details.tier_name)
+
+ # valdite the redemption and then delete the loyalty points earned on cancel of the invoice
+ def delete_loyalty_point_entry(self):
+ lp_entry = frappe.db.sql("select name from `tabLoyalty Point Entry` where sales_invoice=%s",
+ (self.name), as_dict=1)[0]
+ against_lp_entry = frappe.db.sql('''select name, sales_invoice from `tabLoyalty Point Entry`
+ where redeem_against=%s''', (lp_entry.name), as_dict=1)
+ if against_lp_entry:
+ invoice_list = ", ".join([d.sales_invoice for d in against_lp_entry])
+ frappe.throw(_('''Sales Invoice can't be cancelled since the Loyalty Points earned has been redeemed.
+ First cancel the Sales Invoice No {0}''').format(invoice_list))
+ else:
+ frappe.db.sql('''delete from `tabLoyalty Point Entry` where sales_invoice=%s''', (self.name))
+
+ # redeem the loyalty points.
+ def apply_loyalty_points(self):
+ from erpnext.accounts.doctype.loyalty_point_entry.loyalty_point_entry \
+ import get_loyalty_point_entries
+ loyalty_point_entries = get_loyalty_point_entries(self.customer, self.loyalty_program, self.posting_date, self.company)
+
+ points_to_redeem = self.loyalty_amount
+ for lp_entry in loyalty_point_entries:
+ if lp_entry.loyalty_points > points_to_redeem:
+ redeemed_points = points_to_redeem
+ else:
+ redeemed_points = lp_entry.loyalty_points
+ doc = frappe.get_doc({
+ "doctype": "Loyalty Point Entry",
+ "company": self.company,
+ "loyalty_program": self.loyalty_program,
+ "loyalty_program_tier": lp_entry.loyalty_program_tier,
+ "customer": self.customer,
+ "sales_invoice": self.name,
+ "redeem_against": lp_entry.name,
+ "loyalty_points": -(redeemed_points),
+ "purchase_amount": self.grand_total,
+ "expiry_date": lp_entry.expiry_date,
+ "posting_date": self.posting_date
+ })
+ doc.flags.ignore_permissions = 1
+ doc.save()
+ points_to_redeem -= redeemed_points
+ if points_to_redeem < 1: # since points_to_redeem is integer
+ break
+
def book_income_for_deferred_revenue(self):
# book the income on the last day, but it will be trigger on the 1st of month at 12:00 AM
# start_date: 1st of the last month or the start date
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 6f9db344165..bf1c13954c1 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -462,7 +462,7 @@ class calculate_taxes_and_totals(object):
if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
grand_total = self.doc.rounded_total or self.doc.grand_total
if self.doc.party_account_currency == self.doc.currency:
- total_amount_to_pay = flt(grand_total - self.doc.total_advance
+ total_amount_to_pay = flt(grand_total - self.doc.total_advance
- flt(self.doc.write_off_amount), self.doc.precision("grand_total"))
else:
total_amount_to_pay = flt(flt(grand_total *
@@ -481,11 +481,11 @@ class calculate_taxes_and_totals(object):
paid_amount = self.doc.paid_amount \
if self.doc.party_account_currency == self.doc.currency else self.doc.base_paid_amount
-
self.doc.outstanding_amount = flt(total_amount_to_pay - flt(paid_amount) + flt(change_amount),
self.doc.precision("outstanding_amount"))
def calculate_paid_amount(self):
+
paid_amount = base_paid_amount = 0.0
if self.doc.is_pos:
@@ -497,6 +497,10 @@ class calculate_taxes_and_totals(object):
elif not self.doc.is_return:
self.doc.set('payments', [])
+ if self.doc.redeem_loyalty_points and self.doc.loyalty_amount:
+ base_paid_amount += self.doc.loyalty_amount
+ paid_amount += (self.doc.loyalty_amount / flt(self.doc.conversion_rate))
+
self.doc.paid_amount = flt(paid_amount, self.doc.precision("paid_amount"))
self.doc.base_paid_amount = flt(base_paid_amount, self.doc.precision("base_paid_amount"))
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index c24945ea98b..303d21f4db1 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -568,9 +568,8 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
calculate_outstanding_amount: function(update_paid_amount) {
// NOTE:
- // paid_amount and write_off_amount is only for POS Invoice
+ // paid_amount and write_off_amount is only for POS/Loyalty Point Redemption Invoice
// total_advance is only for non POS Invoice
-
if(this.frm.doc.doctype == "Sales Invoice" && this.frm.doc.is_return){
this.calculate_paid_amount();
}
@@ -602,16 +601,19 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
}
if(this.frm.doc.doctype == "Sales Invoice") {
- this.set_default_payment(total_amount_to_pay, update_paid_amount);
+ let total_amount_for_payment = (this.frm.doc.redeem_loyalty_points && this.frm.doc.loyalty_amount)
+ ? flt(total_amount_to_pay - this.frm.doc.loyalty_amount, precision("base_grand_total"))
+ : total_amount_to_pay;
+ this.set_default_payment(total_amount_for_payment, update_paid_amount);
this.calculate_paid_amount();
}
this.calculate_change_amount();
var paid_amount = (this.frm.doc.party_account_currency == this.frm.doc.currency) ?
this.frm.doc.paid_amount : this.frm.doc.base_paid_amount;
-
this.frm.doc.outstanding_amount = flt(total_amount_to_pay - flt(paid_amount) +
flt(this.frm.doc.change_amount * this.frm.doc.conversion_rate), precision("outstanding_amount"));
+ console.log("set the outstanding amount");
}
},
@@ -644,9 +646,14 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
} else if(!this.frm.doc.is_return){
this.frm.doc.payments = [];
}
+ if (this.frm.doc.redeem_loyalty_points && this.frm.doc.loyalty_amount) {
+ base_paid_amount += this.frm.doc.loyalty_amount;
+ paid_amount += flt(this.frm.doc.loyalty_amount / me.frm.doc.conversion_rate, precision("paid_amount"));
+ }
this.frm.doc.paid_amount = flt(paid_amount, precision("paid_amount"));
this.frm.doc.base_paid_amount = flt(base_paid_amount, precision("base_paid_amount"));
+ console.log("paid amount set as -> ", paid_amount, base_paid_amount);
},
calculate_change_amount: function(){
diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js
index 88e66ca6680..5499ab07919 100644
--- a/erpnext/selling/doctype/customer/customer.js
+++ b/erpnext/selling/doctype/customer/customer.js
@@ -103,6 +103,11 @@ frappe.ui.form.on("Customer", {
// indicator
erpnext.utils.set_party_dashboard_indicators(frm);
+ //
+ if (frm.doc.__onload.dashboard_info.loyalty_point) {
+ frm.dashboard.add_indicator(__('Loyalty Point: {0}', [frm.doc.__onload.dashboard_info.loyalty_point]), 'blue');
+ }
+
} else {
frappe.contacts.clear_address_and_contact(frm);
}
diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json
index 3fdf6cff39e..5215854522e 100644
--- a/erpnext/selling/doctype/customer/customer.json
+++ b/erpnext/selling/doctype/customer/customer.json
@@ -1470,6 +1470,97 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
+ "columns": 0,
+ "fieldname": "column_break_38",
+ "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": "Loyalty Points",
+ "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": "loyalty_program",
+ "fieldtype": "Link",
+ "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": "Loyalty Program",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Loyalty Program",
+ "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": "loyalty_program_tier",
+ "fieldtype": "Read Only",
+ "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": "Loyalty Program Tier",
+ "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": 1,
"collapsible_depends_on": "default_sales_partner",
"columns": 0,
"fieldname": "sales_team_section_break",
@@ -1677,7 +1768,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-06-26 13:12:30.677834",
+ "modified": "2018-06-27 12:12:30.677834",
"modified_by": "Administrator",
"module": "Selling",
"name": "Customer",
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index b5961a9ab8f..1e63e592b11 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -24,6 +24,9 @@ class Customer(TransactionBase):
def load_dashboard_info(self):
info = get_dashboard_info(self.doctype, self.name)
+ loyalty_point_details = self.get_loyalty_points()
+ if loyalty_point_details and loyalty_point_details.get("loyalty_points"):
+ info["loyalty_point"] = loyalty_point_details.loyalty_points
self.set_onload('dashboard_info', info)
def autoname(self):
@@ -33,6 +36,11 @@ class Customer(TransactionBase):
else:
set_name_by_naming_series(self)
+ def get_loyalty_points(self):
+ if self.loyalty_program:
+ from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_details
+ return get_loyalty_details(self.name, self.loyalty_program)
+
def get_customer_name(self):
if frappe.db.get_value("Customer", self.customer_name):
count = frappe.db.sql("""select ifnull(MAX(CAST(SUBSTRING_INDEX(name, ' ', -1) AS UNSIGNED)), 0) from tabCustomer
@@ -51,6 +59,7 @@ class Customer(TransactionBase):
self.flags.old_lead = self.lead_name
validate_party_accounts(self)
self.validate_credit_limit_on_change()
+ self.set_loyalty_program()
self.check_customer_group_change()
def check_customer_group_change(self):
@@ -178,6 +187,17 @@ class Customer(TransactionBase):
if frappe.defaults.get_global_default('cust_master_name') == 'Customer Name':
frappe.db.set(self, "customer_name", newdn)
+ def set_loyalty_program(self):
+ if not self.loyalty_program:
+ loyalty_programs = frappe.get_all("Loyalty Program", fields=["name", "customer_group",
+ "customer_territory"], filters={"auto_opt_in": 1, "disabled": 0})
+ from frappe.desk.treeview import get_children
+ for loyalty_program in loyalty_programs:
+ customer_groups = get_children("Customer Group", loyalty_program.customer_group, )
+ if self.customer_group in customer_groups and\
+ self.territory in get_children("Territory", loyalty_program.customer_territory):
+ self.loyalty_program = loyalty_program.name
+
def get_customer_list(doctype, txt, searchfield, start, page_len, filters=None):
if frappe.db.get_default("cust_master_name") == "Customer Name":
diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json
index c34f26bf7e1..9223adb4702 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.json
+++ b/erpnext/selling/doctype/sales_order/sales_order.json
@@ -1910,6 +1910,99 @@
"translatable": 0,
"unique": 0
},
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "loyalty_points_redemption",
+ "fieldtype": "Section Break",
+ "hidden": 1,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Loyalty Points Redemption",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "loyalty_points",
+ "fieldtype": "Int",
+ "hidden": 1,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Loyalty Points",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "loyalty_amount",
+ "fieldtype": "Currency",
+ "hidden": 1,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Loyalty Amount",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 6fbe587593b..ee6b1c7e2b9 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -558,6 +558,10 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False):
if target.company_address:
target.update(get_fetch_values("Sales Invoice", 'company_address', target.company_address))
+ # set the redeem loyalty points if provided via shopping cart
+ if source.loyalty_points and source.order_type == "Shopping Cart":
+ target.redeem_loyalty_points = 1
+
def update_item(source, target, source_parent):
target.amount = flt(source.amount) - flt(source.billed_amt)
target.base_amount = target.amount * flt(source_parent.conversion_rate)
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.js b/erpnext/selling/page/point_of_sale/point_of_sale.js
index 7893b007d53..294b2bad7c6 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.js
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.js
@@ -95,7 +95,9 @@ erpnext.pos.PointOfSale = class PointOfSale {
frm: this.frm,
wrapper: this.wrapper.find('.cart-container'),
events: {
- on_customer_change: (customer) => this.frm.set_value('customer', customer),
+ on_customer_change: (customer) => {
+ this.frm.set_value('customer', customer);
+ },
on_field_change: (item_code, field, value, batch_no) => {
this.update_item_in_cart(item_code, field, value, batch_no);
},
@@ -119,6 +121,46 @@ erpnext.pos.PointOfSale = class PointOfSale {
},
get_item_details: (item_code) => {
return this.items.get(item_code);
+ },
+ get_loyalty_details: () => {
+ var me = this;
+ if (this.frm.doc.customer) {
+ frappe.call({
+ method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_loyalty_program_details",
+ args: {
+ "customer": me.frm.doc.customer,
+ "till_date": me.frm.doc.posting_date,
+ "company": me.frm.doc.company,
+ "silent": true
+ },
+ callback: function(r) {
+ if (r.message.loyalty_program && r.message.loyalty_points) {
+ me.cart.events.set_loyalty_details(r.message, true);
+ }
+ if (!r.message.loyalty_program) {
+ var loyalty_details = {
+ loyalty_points: 0,
+ loyalty_program: '',
+ expense_account: '',
+ cost_center: ''
+ }
+ me.cart.events.set_loyalty_details(loyalty_details, false);
+ }
+ }
+ });
+ }
+ },
+ set_loyalty_details: (details, view_status) => {
+ if (view_status) {
+ this.cart.available_loyalty_points.$wrapper.removeClass("hide");
+ } else {
+ this.cart.available_loyalty_points.$wrapper.addClass("hide");
+ }
+ this.cart.available_loyalty_points.set_value(details.loyalty_points);
+ this.cart.available_loyalty_points.refresh_input();
+ this.frm.set_value("loyalty_program", details.loyalty_program);
+ this.frm.set_value("loyalty_redemption_account", details.expense_account);
+ this.frm.set_value("loyalty_redemption_cost_center", details.cost_center);
}
}
});
@@ -563,6 +605,7 @@ class POSCart {
make() {
this.make_dom();
this.make_customer_field();
+ this.make_loyalty_points();
this.make_numpad();
}
@@ -598,16 +641,27 @@ class POSCart {
-
`);
+
+
this.$cart_items = this.wrapper.find('.cart-items');
this.$empty_state = this.wrapper.find('.cart-items .empty-state');
this.$taxes_and_totals = this.wrapper.find('.taxes-and-totals');
this.$discount_amount = this.wrapper.find('.discount-amount');
this.$grand_total = this.wrapper.find('.grand-total');
this.$qty_total = this.wrapper.find('.quantity-total');
+ // this.$loyalty_button = this.wrapper.find('.loyalty-button');
+
+ // this.$loyalty_button.on('click', () => {
+ // this.loyalty_button.show();
+ // })
this.toggle_taxes_and_totals(false);
this.$grand_total.on('click', () => {
@@ -765,6 +819,7 @@ class POSCart {
},
onchange: () => {
this.events.on_customer_change(this.customer_field.get_value());
+ this.events.get_loyalty_details();
}
},
parent: this.wrapper.find('.customer-field'),
@@ -774,6 +829,21 @@ class POSCart {
this.customer_field.set_value(this.frm.doc.customer);
}
+
+ make_loyalty_points() {
+ this.available_loyalty_points = frappe.ui.form.make_control({
+ df: {
+ fieldtype: 'Int',
+ label: 'Available Loyalty Points',
+ read_only: 1,
+ fieldname: 'available_loyalty_points'
+ },
+ parent: this.wrapper.find('.loyalty-program-field')
+ });
+ this.available_loyalty_points.set_value(this.frm.doc.loyalty_points);
+ }
+
+
disable_numpad_control() {
let disabled_btns = [];
if(!this.frm.allow_edit_rate) {
@@ -1458,7 +1528,8 @@ class Payment {
this.set_flag();
this.dialog = new frappe.ui.Dialog({
fields: this.get_fields(),
- width: 800
+ width: 800,
+ invoice_frm: this.frm
});
this.set_title();
@@ -1541,6 +1612,44 @@ class Payment {
fieldtype: 'HTML',
fieldname: 'numpad'
},
+ {
+ fieldtype: 'Section Break',
+ depends_on: 'eval: this.invoice_frm.doc.loyalty_program'
+ },
+ {
+ fieldtype: 'Check',
+ label: 'Redeem Loyalty Points',
+ fieldname: 'redeem_loyalty_points',
+ onchange: () => {
+ me.update_cur_frm_value("redeem_loyalty_points", () => {
+ frappe.flags.redeem_loyalty_points = false;
+ me.update_loyalty_points();
+ });
+ }
+ },
+ {
+ fieldtype: 'Column Break',
+ },
+ {
+ fieldtype: 'Int',
+ fieldname: "loyalty_points",
+ label: __("Loyalty Points"),
+ depends_on: "redeem_loyalty_points",
+ onchange: () => {
+ me.update_cur_frm_value("loyalty_points", () => {
+ frappe.flags.loyalty_points = false;
+ me.update_loyalty_points();
+ });
+ }
+ },
+ {
+ fieldtype: 'Currency',
+ label: __("Loyalty Amount"),
+ fieldname: "loyalty_amount",
+ options: me.frm.doc.currency,
+ read_only: 1,
+ depends_on: "redeem_loyalty_points"
+ },
{
fieldtype: 'Section Break',
},
@@ -1603,6 +1712,9 @@ class Payment {
set_flag() {
frappe.flags.write_off_amount = true;
frappe.flags.change_amount = true;
+ frappe.flags.loyalty_points = true;
+ frappe.flags.redeem_loyalty_points = true;
+ frappe.flags.payment_method = true;
}
update_cur_frm_value(fieldname, callback) {
@@ -1619,15 +1731,15 @@ class Payment {
update_payment_value(fieldname, value) {
var me = this;
- $.each(this.frm.doc.payments, function(i, data) {
- if (__(data.mode_of_payment) == __(fieldname)) {
- frappe.model.set_value('Sales Invoice Payment', data.name, 'amount', value)
- .then(() => {
- me.update_change_amount();
- me.update_write_off_amount();
- });
- }
- });
+ $.each(this.frm.doc.payments, function(i, data) {
+ if (__(data.mode_of_payment) == __(fieldname)) {
+ frappe.model.set_value('Sales Invoice Payment', data.name, 'amount', value)
+ .then(() => {
+ me.update_change_amount();
+ me.update_write_off_amount();
+ });
+ }
+ });
}
update_change_amount() {
@@ -1643,4 +1755,22 @@ class Payment {
this.dialog.set_value("paid_amount", this.frm.doc.paid_amount);
this.dialog.set_value("outstanding_amount", this.frm.doc.outstanding_amount);
}
+
+ update_payment_amount() {
+ var me = this;
+ $.each(this.frm.doc.payments, function(i, data) {
+ console.log("setting the ", data.mode_of_payment, " for the value", data.amount);
+ me.dialog.set_value(data.mode_of_payment, data.amount);
+ });
+ }
+
+ update_loyalty_points() {
+ if (this.dialog.get_value("redeem_loyalty_points")) {
+ this.dialog.set_value("loyalty_points", this.frm.doc.loyalty_points);
+ this.dialog.set_value("loyalty_amount", this.frm.doc.loyalty_amount);
+ this.update_payment_amount();
+ this.show_paid_amount();
+ }
+ }
+
}
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 36836954f81..cd02e3003ba 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -570,6 +570,7 @@ class TestDeliveryNote(unittest.TestCase):
self.assertEqual(dn.name, dt.delivery_stops[0].delivery_note)
def create_delivery_note(**args):
+ print (frappe.session.user)
dn = frappe.new_doc("Delivery Note")
args = frappe._dict(args)
dn.posting_date = args.posting_date or nowdate()
diff --git a/erpnext/templates/pages/order.html b/erpnext/templates/pages/order.html
index 3a6d2254fb8..712eefd8cf6 100644
--- a/erpnext/templates/pages/order.html
+++ b/erpnext/templates/pages/order.html
@@ -18,26 +18,26 @@
{% block page_content %}
-
-
- {{ _(doc.indicator_title) or _(doc.status) or _("Submitted") }}
-
+
+
+ {{ _(doc.indicator_title) or _(doc.status) or _("Submitted") }}
+
-
- {{ frappe.utils.formatdate(doc.transaction_date, 'medium') }}
+
+ {{ frappe.utils.formatdate(doc.transaction_date, 'medium') }}
{% if doc.valid_till %}
- {{ _("Valid Till") }}: {{ frappe.utils.formatdate(doc.valid_till, 'medium') }}
+ {{ _("Valid Till") }}: {{ frappe.utils.formatdate(doc.valid_till, 'medium') }}
{% endif %}
-
+
{% if doc.doctype == 'Supplier Quotation' %}
- {{ doc.supplier_name}}
+ {{ doc.supplier_name}}
{% else %}
- {{ doc.customer_name}}
+ {{ doc.customer_name}}
{% endif %}
{% if doc.contact_display %}
@@ -51,64 +51,94 @@
-
-
-
- {% for d in doc.items %}
-
-
- {{ item_name_and_description(d) }}
-
-
- {{ d.qty }}
- {% if d.delivered_qty is defined and d.delivered_qty != None %}
-
{{
- _("Delivered: {0}").format(d.delivered_qty) }}
- {% endif %}
-
-
- {{ d.get_formatted("amount") }}
-
{{
- _("@ {0}").format(d.get_formatted("rate")) }}
-
-
- {% endfor %}
-
+
+
+
+ {% for d in doc.items %}
+
+
+ {{ item_name_and_description(d) }}
+
+
+ {{ d.qty }}
+ {% if d.delivered_qty is defined and d.delivered_qty != None %}
+
{{
+ _("Delivered: {0}").format(d.delivered_qty) }}
+ {% endif %}
+
+
+ {{ d.get_formatted("amount") }}
+
{{
+ _("@ {0}").format(d.get_formatted("rate")) }}
+
+
+ {% endfor %}
+
-
-
-
-
- {% include "erpnext/templates/includes/order/order_taxes.html" %}
-
-
+
+
+
+
+ {% include "erpnext/templates/includes/order/order_taxes.html" %}
+
+
-
-
-
- {% if enabled_checkout %}
- {% if (doc.doctype=="Sales Order" and doc.per_billed <= 0)
- or (doc.doctype=="Sales Invoice" and doc.outstanding_amount > 0) %}
-