Loyalty Program (#12631)

* First Cut for the Loyalty Program

* finished the collection part

* redmeption for the loyalty point

	update the loyalty point entry in the FIFO style
	make the accounting entry knocking the debtors account against the expense account selected in the loyalty program
	update the outstanding balance in the client side

* completed for the desk viewe

* wrap up for the desk and shopping cart

* pos

* fix and test the travis
This commit is contained in:
Manas Solanki 2018-07-06 12:36:57 +05:30 committed by Nabin Hait
parent 5d134e0465
commit da486eeebd
34 changed files with 2706 additions and 91 deletions

View File

@ -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) {
}
});

View File

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

View File

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

View File

@ -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()
]);
});

View File

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

View File

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

View File

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

View File

@ -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 =
`<table class="table table-bordered" style="background-color: #f9f9f9;">
<tr><td>
<h4>
<i class="fa fa-hand-right"></i>
${__('Notes')}
</h4>
<ul>
<li>
${__("Loyalty Points will be calculated from the spent done (via the Sales Invoice), based on collection factor mentioned.")}
</li>
<li>
${__("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.")}
</li>
<li>
${__("In the case of multi-tier program, Customers will be auto assigned to the concerned tier as per their spent")}
</li>
<li>
${__("If unlimited expiry for the Loyalty Points, keep the Expiry Duration empty or 0.")}
</li>
<li>
${__("If Auto Opt In is checked, then the customers will be automatically linked with the concerned Loyalty Program (on save)")}
</li>
<li>
${__("One customer can be part of only single Loyalty Program.")}
</li>
</ul>
</td></tr>
</table>`;
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."));
}
}
});

View File

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

View File

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

View File

@ -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()
]);
});

View File

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

View File

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

View File

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

View File

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

View File

@ -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', {

View File

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

View File

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

View File

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

View File

@ -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(){

View File

@ -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);
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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 {
</div>
</div>
</div>
<div class="number-pad-container">
<div class="row">
<div class="number-pad-container col-sm-6"></div>
<div class="col-sm-6 loyalty-program-section">
<div class="loyalty-program-field"> </div>
</div>
</div>
</div>
`);
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();
}
}
}

View File

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

View File

@ -18,26 +18,26 @@
{% block page_content %}
<div class="row transaction-subheading">
<div class="col-xs-6">
<span class="indicator {{ doc.indicator_color or ("blue" if doc.docstatus==1 else "darkgrey") }}">
{{ _(doc.indicator_title) or _(doc.status) or _("Submitted") }}
</span>
<div class="col-xs-6">
<span class="indicator {{ doc.indicator_color or ("blue" if doc.docstatus==1 else "darkgrey") }}">
{{ _(doc.indicator_title) or _(doc.status) or _("Submitted") }}
</span>
</div>
<div class="col-xs-6 text-muted text-right small">
{{ frappe.utils.formatdate(doc.transaction_date, 'medium') }}
<div class="col-xs-6 text-muted text-right small">
{{ frappe.utils.formatdate(doc.transaction_date, 'medium') }}
{% if doc.valid_till %}
<p>
{{ _("Valid Till") }}: {{ frappe.utils.formatdate(doc.valid_till, 'medium') }}
{{ _("Valid Till") }}: {{ frappe.utils.formatdate(doc.valid_till, 'medium') }}
</p>
{% endif %}
</div>
</div>
</div>
<p class='small' style='padding-top: 15px;'>
{% if doc.doctype == 'Supplier Quotation' %}
<b>{{ doc.supplier_name}}</b>
<b>{{ doc.supplier_name}}</b>
{% else %}
<b>{{ doc.customer_name}}</b>
<b>{{ doc.customer_name}}</b>
{% endif %}
{% if doc.contact_display %}
<br>
@ -51,64 +51,94 @@
<div class="order-container">
<!-- items -->
<div class="order-item-table">
<div class="row order-items order-item-header text-muted">
<div class="col-sm-6 col-xs-6 h6 text-uppercase">
{{ _("Item") }}
</div>
<div class="col-sm-3 col-xs-3 text-right h6 text-uppercase">
{{ _("Quantity") }}
</div>
<div class="col-sm-3 col-xs-3 text-right h6 text-uppercase">
{{ _("Amount") }}
</div>
</div>
{% for d in doc.items %}
<div class="row order-items">
<div class="col-sm-6 col-xs-6">
{{ item_name_and_description(d) }}
</div>
<div class="col-sm-3 col-xs-3 text-right">
{{ d.qty }}
{% if d.delivered_qty is defined and d.delivered_qty != None %}
<p class="text-muted small">{{
_("Delivered: {0}").format(d.delivered_qty) }}</p>
{% endif %}
</div>
<div class="col-sm-3 col-xs-3 text-right">
{{ d.get_formatted("amount") }}
<p class="text-muted small">{{
_("@ {0}").format(d.get_formatted("rate")) }}</p>
</div>
</div>
{% endfor %}
</div>
<!-- items -->
<div class="order-item-table">
<div class="row order-items order-item-header text-muted">
<div class="col-sm-6 col-xs-6 h6 text-uppercase">
{{ _("Item") }}
</div>
<div class="col-sm-3 col-xs-3 text-right h6 text-uppercase">
{{ _("Quantity") }}
</div>
<div class="col-sm-3 col-xs-3 text-right h6 text-uppercase">
{{ _("Amount") }}
</div>
</div>
{% for d in doc.items %}
<div class="row order-items">
<div class="col-sm-6 col-xs-6">
{{ item_name_and_description(d) }}
</div>
<div class="col-sm-3 col-xs-3 text-right">
{{ d.qty }}
{% if d.delivered_qty is defined and d.delivered_qty != None %}
<p class="text-muted small">{{
_("Delivered: {0}").format(d.delivered_qty) }}</p>
{% endif %}
</div>
<div class="col-sm-3 col-xs-3 text-right">
{{ d.get_formatted("amount") }}
<p class="text-muted small">{{
_("@ {0}").format(d.get_formatted("rate")) }}</p>
</div>
</div>
{% endfor %}
</div>
<!-- taxes -->
<div class="order-taxes row">
<div class="col-sm-6"><!-- empty --></div>
<div class="col-sm-6 text-right">
{% include "erpnext/templates/includes/order/order_taxes.html" %}
</div>
</div>
<!-- taxes -->
<div class="order-taxes row">
<div class="col-sm-6"><!-- empty --></div>
<div class="col-sm-6 text-right">
{% include "erpnext/templates/includes/order/order_taxes.html" %}
</div>
</div>
</div>
<div class="cart-taxes row small">
<div class="col-sm-6"><!-- empty --></div>
<div class="col-sm-6">
{% if enabled_checkout %}
{% if (doc.doctype=="Sales Order" and doc.per_billed <= 0)
or (doc.doctype=="Sales Invoice" and doc.outstanding_amount > 0) %}
<div class="page-header-actions-block" data-html-block="header-actions">
<p>
<a href="/api/method/erpnext.accounts.doctype.payment_request.payment_request.make_payment_request?dn={{ doc.name }}&dt={{ doc.doctype }}&submit_doc=1&order_type=Shopping Cart"
class="btn btn-primary btn-sm">{{ _("Pay") }} {{ doc.get_formatted("grand_total") }} </a>
</p>
{% if enabled_checkout and ((doc.doctype=="Sales Order" and doc.per_billed <= 0)
or (doc.doctype=="Sales Invoice" and doc.outstanding_amount > 0)) %}
<div class="panel panel-default">
<div class="panel-heading">
<div class="row">
<div class="form-column col-sm-6 address-title">
<strong>Payment</strong>
</div>
{% endif %}
{% endif %}
</div>
</div>
<div class="panel-collapse">
<div class="panel-body text-muted small">
<div class="row">
<div class="form-column col-sm-6">
{% if available_loyalty_points %}
<div class="form-group">
<div class="h6">Enter Loyalty Points</div>
<div class="control-input-wrapper">
<div class="control-input">
<input class="form-control" type="number" min="0" max="{{ available_loyalty_points }}" id="loyalty-point-to-redeem">
</div>
<p class="help-box small text-muted hidden-xs"> Available Points: {{ available_loyalty_points }} </p>
</div>
</div>
{% endif %}
</div>
<div class="form-column col-sm-6">
<div id="loyalty-points-status" style="text-align: right"></div>
<div class="page-header-actions-block" data-html-block="header-actions">
<p>
<a href="/api/method/erpnext.accounts.doctype.payment_request.payment_request.make_payment_request?dn={{ doc.name }}&dt={{ doc.doctype }}&submit_doc=1&order_type=Shopping Cart"
class="btn btn-primary btn-sm" id="pay-for-order">{{ _("Pay") }} {{ doc.get_formatted("grand_total") }} </a>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
{% endif %}
{% if attachments %}
<div class="order-item-table">
@ -131,7 +161,20 @@
</div>
{% if doc.terms %}
<div class="terms-and-condition text-muted small">
<hr><p>{{ doc.terms }}</p>
<hr><p>{{ doc.terms }}</p>
</div>
{% endif %}
{% endblock %}
{% block script %}
<script> {% include "templates/pages/order.js" %} </script>
<script>
window.doc_info = {
customer: '{{doc.customer}}',
doctype: '{{ doc.doctype }}',
doctype_name: '{{ doc.name }}',
grand_total: '{{ doc.grand_total }}',
currency: '{{ doc.currency }}'
}
</script>
{% endblock %}

View File

@ -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;
}
}
});
}
}
})

View File

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