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

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