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

- -
-
-
- {{ _("Item") }} -
-
- {{ _("Quantity") }} -
-
- {{ _("Amount") }} -
-
- {% 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 %} -
+ +
+
+
+ {{ _("Item") }} +
+
+ {{ _("Quantity") }} +
+
+ {{ _("Amount") }} +
+
+ {% 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) %} -
-

- {{ _("Pay") }} {{ doc.get_formatted("grand_total") }} -

+{% if enabled_checkout and ((doc.doctype=="Sales Order" and doc.per_billed <= 0) + or (doc.doctype=="Sales Invoice" and doc.outstanding_amount > 0)) %} + +
+
+
+
+ Payment
- {% endif %} - {% endif %} +
+
+
+
+
+ {% if available_loyalty_points %} +
+
Enter Loyalty Points
+
+
+ +
+ +
+
+ {% endif %} +
+ + + +
+ +
+
+
+{% endif %} + {% if attachments %}
@@ -131,7 +161,20 @@
{% if doc.terms %}
-

{{ doc.terms }}

+

{{ doc.terms }}

{% endif %} {% endblock %} + +{% block script %} + + +{% endblock %} \ No newline at end of file diff --git a/erpnext/templates/pages/order.js b/erpnext/templates/pages/order.js new file mode 100644 index 00000000000..21c3a144376 --- /dev/null +++ b/erpnext/templates/pages/order.js @@ -0,0 +1,40 @@ +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ready(function(){ + + var loyalty_points_input = document.getElementById("loyalty-point-to-redeem"); + var loyalty_points_status = document.getElementById("loyalty-points-status"); + loyalty_points_input.onblur = apply_loyalty_points; + + function apply_loyalty_points() { + var loyalty_points = parseInt(loyalty_points_input.value); + if (loyalty_points) { + frappe.call({ + method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_redeemption_factor", + args: { + "customer": doc_info.customer + }, + callback: function(r) { + if (r) { + var message = "" + let loyalty_amount = flt(r.message*loyalty_points); + if (doc_info.grand_total && doc_info.grand_total < loyalty_amount) { + let redeemable_amount = parseInt(doc_info.grand_total/r.message); + message = "You can only redeem max " + redeemable_amount + " points in this order."; + frappe.msgprint(__(message)); + } else { + message = loyalty_points + " Loyalty Points of amount "+ loyalty_amount + " is applied." + frappe.msgprint(__(message)); + var remaining_amount = flt(doc_info.grand_total) - flt(loyalty_amount); + var payment_button = document.getElementById("pay-for-order"); + payment_button.innerHTML = __("Pay Remaining"); + payment_button.href = "/api/method/erpnext.accounts.doctype.payment_request.payment_request.make_payment_request?dn="+doc_info.doctype_name+"&dt="+doc_info.doctype+"&loyalty_points="+loyalty_points+"&submit_doc=1&order_type=Shopping Cart"; + } + loyalty_points_status.innerHTML = message; + } + } + }); + } + } +}) \ No newline at end of file diff --git a/erpnext/templates/pages/order.py b/erpnext/templates/pages/order.py index 2874047ee2e..70bd702ba81 100644 --- a/erpnext/templates/pages/order.py +++ b/erpnext/templates/pages/order.py @@ -32,6 +32,13 @@ def get_context(context): if not frappe.has_website_permission(context.doc): frappe.throw(_("Not Permitted"), frappe.PermissionError) + + # check for the loyalty program of the customer + customer_loyalty_program = frappe.db.get_value("Customer", context.doc.customer, "loyalty_program") + if customer_loyalty_program: + from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details + loyalty_program_details = get_loyalty_program_details(context.doc.customer, customer_loyalty_program) + context.available_loyalty_points = int(loyalty_program_details.get("loyalty_points")) def get_attachments(dt, dn): return frappe.get_all("File",