Browse Source

[MIG] pos_loyalty: Migration to 11.0

pull/308/head
Kiril Vangelovski 6 years ago
committed by Sylvain LE GAL
parent
commit
12f5bda1b3
  1. 87
      pos_loyalty/README.rst
  2. 1
      pos_loyalty/__init__.py
  3. 9
      pos_loyalty/__manifest__.py
  4. 8
      pos_loyalty/demo/templates.xml
  5. 2
      pos_loyalty/models/__init__.py
  6. 1
      pos_loyalty/models/loyalty_program.py
  7. 7
      pos_loyalty/models/loyalty_reward.py
  8. 3
      pos_loyalty/models/loyalty_rule.py
  9. 1
      pos_loyalty/models/pos_config.py
  10. 1
      pos_loyalty/models/pos_order.py
  11. 20
      pos_loyalty/models/pos_order_line.py
  12. 1
      pos_loyalty/models/res_partner.py
  13. 4
      pos_loyalty/readme/CONFIGURE.rst
  14. 3
      pos_loyalty/readme/CONTRIBUTORS.rst
  15. 6
      pos_loyalty/readme/DESCRIPTION.rst
  16. 11
      pos_loyalty/readme/USAGE.rst
  17. 449
      pos_loyalty/static/description/index.html
  18. 163
      pos_loyalty/static/src/js/tests.js
  19. 2
      pos_loyalty/tests/__init__.py
  20. 74
      pos_loyalty/tests/test_pos_loyalty.py
  21. 20
      pos_loyalty/views/pos_config_view.xml

87
pos_loyalty/README.rst

@ -1,11 +1,30 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
===============
Loyalty Program
===============
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fpos-lightgray.png?logo=github
:target: https://github.com/OCA/pos/tree/11.0/pos_loyalty
:alt: OCA/pos
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/pos-11-0/pos-11-0-pos_loyalty
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/184/11.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
This module allows you to define a loyalty program in the point of sale,
where the customers earn loyalty points and get rewards.
@ -13,46 +32,74 @@ This module is a forward-port to v10 of the pos_loyalty module from Odoo's
saas-6 branch.
The functionality was moved to the Enterprise edition in later versions.
Usage
=====
**Table of contents**
.. contents::
:local:
Configuration
=============
To use this module, you need to:
* Go to *Point of Sale > Configuration > Loyalty Programs* and define a new loyalty program with specific rules and gifts.
* Go to *Point of Sale > Configuration > Loyalty Programs* and define a new loyalty program with specific rules and rewards.
* Assign the loyalty program to the desired Point of Sale.
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/{repo_id}/{branch}
Usage
=====
The Loyalty Program defines rules for acquiring points and rewards on which they can be spent.
Rules can be defined globally for all products (fields on loyalty.program) and / or rules that are applied only on specific product or PoS category (loyalty.rule records) on a *points per product sold* or *points per currency spent* basis. The specific rules (loyalty.rule) can be defined as cumulative, which means that they will be aggregated with other matching rules (loyalty.rule records and loyalty.program fields). In the case of non-cumulative rules only the points from that one matching rule are used. Additionally, *fixed points per order* can be added which are applied regardless of whether or not cumulative or non-cumulative rules were applied also.
Rewards can be of three types:
* *Gift* - give a single unit of product for free
* *Discount* - give a discount to the whole order. It should be added at the end of the order so that the correct total price is used.
* *Resale* - allow for customer to sell back his earned points. These are calculated by setting the price on the Resale product (*resale_product.list_price* * *customer.loyalty_points*)
All rewards can define how many points they cost (point_cost) and how many are needed so that the customer can become eligable for the reward (minimum_points). for Gift and Discount rewards minimum_points are considered only if they are greater then the point_cost for that reward (minimum_points > point_cost). For Resale products only minimum_points can be used.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues
<https://github.com/OCA/{project_repo}/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smash it by providing detailed and welcomed feedback.
Bugs are tracked on `GitHub Issues <https://github.com/OCA/pos/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
`feedback <https://github.com/OCA/pos/issues/new?body=module:%20pos_loyalty%0Aversion:%2011.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
~~~~~~~
* OpenERP SA
* RGB Consulting SL
* Lambda IS
Contributors
------------
~~~~~~~~~~~~
* RGB Consulting SL (http://www.rgbconsulting.com)
* Forward-port from Odoo SA saas-6 branch
* Kiril Vangelovski <kiril@lambda-is.com>
Maintainer
----------
Maintainers
~~~~~~~~~~~
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
To contribute to this module, please visit https://odoo-community.org.
This module is part of the `OCA/pos <https://github.com/OCA/pos/tree/11.0/pos_loyalty>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

1
pos_loyalty/__init__.py

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import models

9
pos_loyalty/__manifest__.py

@ -1,19 +1,22 @@
# -*- coding: utf-8 -*-
# Copyright 2004-2010 OpenERP SA
# Copyright 2017 RGB Consulting S.L. (https://www.rgbconsulting.com)
# Copyright 2018 Lambda IS DOOEL <https://www.lambda-is.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
'name': 'Loyalty Program',
'version': '10.0.1.0.0',
'version': '11.0.1.0.0',
'category': 'Point of Sale',
'license': 'AGPL-3',
'author': "OpenERP SA, "
"RGB Consulting SL, "
"Lambda IS, "
"Odoo Community Association (OCA)",
'website': "https://odoo-community.org/",
'depends': ['point_of_sale'],
'demo': [
'demo/templates.xml',
],
'data': [
'security/ir.model.access.csv',
'views/templates.xml',

8
pos_loyalty/demo/templates.xml

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="assets_demo" inherit_id="point_of_sale.assets">
<xpath expr="." position="inside">
<script type="text/javascript" src="/pos_loyalty/static/src/js/tests.js"></script>
</xpath>
</template>
</odoo>

2
pos_loyalty/models/__init__.py

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import loyalty_program
@ -6,4 +5,5 @@ from . import loyalty_reward
from . import loyalty_rule
from . import pos_config
from . import pos_order
from . import pos_order_line
from . import res_partner

1
pos_loyalty/models/loyalty_program.py

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright 2004-2010 OpenERP SA
# Copyright 2017 RGB Consulting S.L. (https://www.rgbconsulting.com)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

7
pos_loyalty/models/loyalty_reward.py

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright 2004-2010 OpenERP SA
# Copyright 2017 RGB Consulting S.L. (https://www.rgbconsulting.com)
# Copyright 2018 Lambda IS DOOEL <https://www.lambda-is.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models, api, _
@ -32,13 +32,18 @@ class LoyaltyReward(models.Model):
help='The Loyalty Program this reward'
' belongs to')
gift_product_id = fields.Many2one(comodel_name='product.product',
domain=[('available_in_pos', '=', True)],
string='Gift Product',
help='The product given as a reward')
discount_product_id = fields.Many2one(comodel_name='product.product',
domain=[
('available_in_pos', '=', True)],
string='Discount Product',
help='The product used to apply '
'discounts')
point_product_id = fields.Many2one(comodel_name='product.product',
domain=[
('available_in_pos', '=', True)],
string='Point Product',
help='Product that represents a point '
'that is sold by the customer')

3
pos_loyalty/models/loyalty_rule.py

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright 2004-2010 OpenERP SA
# Copyright 2017 RGB Consulting S.L. (https://www.rgbconsulting.com)
# Copyright 2018 Lambda IS DOOEL <https://www.lambda-is.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
@ -26,6 +26,7 @@ class LoyaltyRule(models.Model):
help='The Loyalty Program this rule '
'belongs to')
product_id = fields.Many2one(comodel_name='product.product',
domain=[('available_in_pos', '=', True)],
string='Target Product',
help='The product affected by this rule')
category_id = fields.Many2one(comodel_name='pos.category',

1
pos_loyalty/models/pos_config.py

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright 2004-2010 OpenERP SA
# Copyright 2017 RGB Consulting S.L. (https://www.rgbconsulting.com)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

1
pos_loyalty/models/pos_order.py

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright 2004-2010 OpenERP SA
# Copyright 2017 RGB Consulting S.L. (https://www.rgbconsulting.com)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

20
pos_loyalty/models/pos_order_line.py

@ -0,0 +1,20 @@
# Copyright 2018 Lambda IS DOOEL <https://www.lambda-is.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, models
class PosOrderLine(models.Model):
_inherit = 'pos.order.line'
@api.model
def _order_line_fields(self, line, session_id=None):
line = super(PosOrderLine, self)._order_line_fields(
line, session_id=session_id)
if line and 'reward_id' in line[2]:
# Delete the key since field doesn't exist
# and raises a warning in the logs.
# TODO: add field and remove this if data will be
# used on server, example in report / widget.
del line[2]['reward_id']
return line

1
pos_loyalty/models/res_partner.py

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright 2004-2010 OpenERP SA
# Copyright 2017 RGB Consulting S.L. (https://www.rgbconsulting.com)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

4
pos_loyalty/readme/CONFIGURE.rst

@ -0,0 +1,4 @@
To use this module, you need to:
* Go to *Point of Sale > Configuration > Loyalty Programs* and define a new loyalty program with specific rules and rewards.
* Assign the loyalty program to the desired Point of Sale.

3
pos_loyalty/readme/CONTRIBUTORS.rst

@ -0,0 +1,3 @@
* RGB Consulting SL (http://www.rgbconsulting.com)
* Forward-port from Odoo SA saas-6 branch
* Kiril Vangelovski <kiril@lambda-is.com>

6
pos_loyalty/readme/DESCRIPTION.rst

@ -0,0 +1,6 @@
This module allows you to define a loyalty program in the point of sale,
where the customers earn loyalty points and get rewards.
This module is a forward-port to v10 of the pos_loyalty module from Odoo's
saas-6 branch.
The functionality was moved to the Enterprise edition in later versions.

11
pos_loyalty/readme/USAGE.rst

@ -0,0 +1,11 @@
The Loyalty Program defines rules for acquiring points and rewards on which they can be spent.
Rules can be defined globally for all products (fields on loyalty.program) and / or rules that are applied only on specific product or PoS category (loyalty.rule records) on a *points per product sold* or *points per currency spent* basis. The specific rules (loyalty.rule) can be defined as cumulative, which means that they will be aggregated with other matching rules (loyalty.rule records and loyalty.program fields). In the case of non-cumulative rules only the points from that one matching rule are used. Additionally, *fixed points per order* can be added which are applied regardless of whether or not cumulative or non-cumulative rules were applied also.
Rewards can be of three types:
* *Gift* - give a single unit of product for free
* *Discount* - give a discount to the whole order. It should be added at the end of the order so that the correct total price is used.
* *Resale* - allow for customer to sell back his earned points. These are calculated by setting the price on the Resale product (*resale_product.list_price* * *customer.loyalty_points*)
All rewards can define how many points they cost (point_cost) and how many are needed so that the customer can become eligable for the reward (minimum_points). for Gift and Discount rewards minimum_points are considered only if they are greater then the point_cost for that reward (minimum_points > point_cost). For Resale products only minimum_points can be used.

449
pos_loyalty/static/description/index.html

@ -0,0 +1,449 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.14: http://docutils.sourceforge.net/" />
<title>Loyalty Program</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="loyalty-program">
<h1 class="title">Loyalty Program</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/pos/tree/11.0/pos_loyalty"><img alt="OCA/pos" src="https://img.shields.io/badge/github-OCA%2Fpos-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/pos-11-0/pos-11-0-pos_loyalty"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/184/11.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p>This module allows you to define a loyalty program in the point of sale,
where the customers earn loyalty points and get rewards.</p>
<p>This module is a forward-port to v10 of the pos_loyalty module from Odoo’s
saas-6 branch.
The functionality was moved to the Enterprise edition in later versions.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#configuration" id="id1">Configuration</a></li>
<li><a class="reference internal" href="#usage" id="id2">Usage</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id3">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id4">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id5">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id6">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id7">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#id1">Configuration</a></h1>
<p>To use this module, you need to:</p>
<ul class="simple">
<li>Go to <em>Point of Sale &gt; Configuration &gt; Loyalty Programs</em> and define a new loyalty program with specific rules and rewards.</li>
<li>Assign the loyalty program to the desired Point of Sale.</li>
</ul>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#id2">Usage</a></h1>
<p>The Loyalty Program defines rules for acquiring points and rewards on which they can be spent.</p>
<p>Rules can be defined globally for all products (fields on loyalty.program) and / or rules that are applied only on specific product or PoS category (loyalty.rule records) on a <em>points per product sold</em> or <em>points per currency spent</em> basis. The specific rules (loyalty.rule) can be defined as cumulative, which means that they will be aggregated with other matching rules (loyalty.rule records and loyalty.program fields). In the case of non-cumulative rules only the points from that one matching rule are used. Additionally, <em>fixed points per order</em> can be added which are applied regardless of whether or not cumulative or non-cumulative rules were applied also.</p>
<p>Rewards can be of three types:</p>
<ul class="simple">
<li><em>Gift</em> - give a single unit of product for free</li>
<li><em>Discount</em> - give a discount to the whole order. It should be added at the end of the order so that the correct total price is used.</li>
<li><em>Resale</em> - allow for customer to sell back his earned points. These are calculated by setting the price on the Resale product (<em>resale_product.list_price</em> * <em>customer.loyalty_points</em>)</li>
</ul>
<p>All rewards can define how many points they cost (point_cost) and how many are needed so that the customer can become eligable for the reward (minimum_points). for Gift and Discount rewards minimum_points are considered only if they are greater then the point_cost for that reward (minimum_points &gt; point_cost). For Resale products only minimum_points can be used.</p>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id3">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/pos/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/pos/issues/new?body=module:%20pos_loyalty%0Aversion:%2011.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#id4">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id5">Authors</a></h2>
<ul class="simple">
<li>OpenERP SA</li>
<li>RGB Consulting SL</li>
<li>Lambda IS</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id6">Contributors</a></h2>
<ul class="simple">
<li>RGB Consulting SL (<a class="reference external" href="http://www.rgbconsulting.com">http://www.rgbconsulting.com</a>)</li>
<li>Forward-port from Odoo SA saas-6 branch</li>
<li>Kiril Vangelovski &lt;<a class="reference external" href="mailto:kiril&#64;lambda-is.com">kiril&#64;lambda-is.com</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id7">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/pos/tree/11.0/pos_loyalty">OCA/pos</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

163
pos_loyalty/static/src/js/tests.js

@ -0,0 +1,163 @@
// Copyright 2004-2018 Odoo SA
// Copyright 2018 Lambda IS DOOEL <https://www.lambda-is.com>
// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
odoo.define('pos_loyalty.tour.test_pos_loyalty', function (require) {
"use strict";
// Some of the steps are taken from the pos_basic_order tour in point_of_sale.
// Added additional ones necessary for testing the rewards.
var Tour = require("web_tour.tour");
function add_customer(customer_name) {
return [{
content: 'open customer screen',
trigger: '.button.set-customer',
}, {
content: 'choose customer ' + customer_name,
trigger: 'table.client-list tbody.client-list-contents tr.client-line td:contains("' + customer_name + '")',
}, {
content: 'select customer ' + customer_name,
trigger: '.button.next:contains("Set Customer")',
}, {
content: 'Check if customer ' + customer_name + ' is added',
trigger: '.button.set-customer:contains("' + customer_name + '")',
run: function () {}, // it's a check
}];
}
function add_reward(reward_name) {
return [{
content: 'open rewards screen',
trigger: '.control-button:contains("Rewards")',
}, {
content: 'choose reward',
trigger: '.selection-item:contains("' + reward_name + '")',
}]
}
function add_product_to_order(product_name) {
return [{
content: 'buy ' + product_name,
trigger: '.product-list .product-name:contains("' + product_name + '")',
}, {
content: 'the ' + product_name + ' have been added to the order',
trigger: '.order .product-name:contains("' + product_name + '")',
run: function () {}, // it's a check
}];
}
function verify_order_product(product_name) {
return [{
content: 'check if ' + product_name + ' is in order',
trigger: '.orderline .product-name:contains("' + product_name + '")',
run: function () {}, // it's a check
}]
}
function generate_keypad_steps(amount_str, keypad_selector) {
var i, steps = [], current_char;
for (i = 0; i < amount_str.length; ++i) {
current_char = amount_str[i];
steps.push({
content: 'press ' + current_char + ' on payment keypad',
trigger: keypad_selector + ' .input-button:contains("' + current_char + '"):visible'
});
}
return steps;
}
function generate_payment_screen_keypad_steps(amount_str) {
return generate_keypad_steps(amount_str, '.payment-numpad');
}
function generate_product_screen_keypad_steps(amount_str) {
return generate_keypad_steps(amount_str, '.numpad');
}
function verify_order_total(total_str) {
return [{
content: 'order total contains ' + total_str,
trigger: '.order .total .value:contains("' + total_str + '")',
run: function () {}, // it's a check
}];
}
function goto_payment_screen_and_select_payment_method() {
return [{
content: "go to payment screen",
trigger: '.button.pay',
}, {
content: "pay with cash",
trigger: '.paymentmethod:contains("Cash")',
}];
}
function finish_order() {
return [{
content: "validate the order",
trigger: '.button.next:visible',
}, {
content: "verify that the order is being sent to the backend",
trigger: ".js_connecting:visible",
run: function () {}, // it's a check
}, {
content: "verify that the order has been succesfully sent to the backend",
trigger: ".js_connected:visible",
run: function () {}, // it's a check
}, {
content: "next order",
trigger: '.button.next:visible',
}];
}
var steps = [{
content: 'waiting for loading to finish',
trigger: '.o_main_content:has(.loader:hidden)',
run: function () {}, // it's a check
}];
steps = steps.concat(add_customer('Agrolait'));
steps = steps.concat(add_product_to_order('Peaches'));
steps = steps.concat(verify_order_total('5.10'));
steps = steps.concat(add_product_to_order('Peaches')); // buy another kg of peaches
steps = steps.concat(verify_order_total('10.20'));
steps = steps.concat(goto_payment_screen_and_select_payment_method());
steps = steps.concat(generate_payment_screen_keypad_steps("12.20"));
steps = steps.concat([{
content: "verify tendered",
trigger: '.col-tendered:contains("12.20")',
run: function () {}, // it's a check
}, {
content: "verify change",
trigger: '.col-change:contains("2.00")',
run: function () {}, // it's a check
}]);
steps = steps.concat(finish_order());
Tour.register('test_pos_loyalty_acquire_points', { test: true, url: '/pos/web' }, steps);
steps = [{
content: 'waiting for loading to finish',
trigger: '.o_main_content:has(.loader:hidden)',
run: function () {}, // it's a check
}];
steps = steps.concat(add_customer('Agrolait'));
steps = steps.concat(add_reward('Free Peaches'));
steps = steps.concat(verify_order_product('Peaches'));
steps = steps.concat(verify_order_total('0.00'));
steps = steps.concat(goto_payment_screen_and_select_payment_method());
steps = steps.concat([{
content: "verify tendered",
trigger: '.col-tendered:contains("0.00")',
run: function () {}, // it's a check
}]);
steps = steps.concat(finish_order());
Tour.register('test_pos_loyalty_spend_points', { test: true, url: '/pos/web' }, steps);
})

2
pos_loyalty/tests/__init__.py

@ -0,0 +1,2 @@
from . import test_pos_loyalty

74
pos_loyalty/tests/test_pos_loyalty.py

@ -0,0 +1,74 @@
# Copyright 2004-2018 Odoo SA
# Copyright 2018 Lambda IS DOOEL <https://www.lambda-is.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.api import Environment
from odoo.tests import HttpCase
class TestPOSLoyalty(HttpCase):
def test_pos_loyalty(self):
cr = self.registry.cursor()
assert cr == self.registry.test_cr
env = Environment(cr, self.uid, {})
main_pos_config = env.ref('point_of_sale.pos_config_main')
target_product = env.ref('point_of_sale.peche')
free_product = env.ref('point_of_sale.Onions')
customer = env.ref('base.res_partner_2')
loyalty_program = env['loyalty.program'].create({
'name': 'foo',
'rule_ids': [(0, 0, {
'name': 'Peaches',
'type': 'product',
'product_id': target_product.id,
'pp_product': 10,
})],
'reward_ids': [(0, 0, {
'name': 'Free Peaches',
'type': 'gift',
'gift_product_id': target_product.id,
'point_cost': 20,
'minimum_points': 20,
}), (0, 0, {
'name': 'Free Onions',
'type': 'gift',
'gift_product_id': free_product.id,
'point_cost': 20,
'minimum_points': 20,
})]
})
main_pos_config.write({'loyalty_id': loyalty_program.id})
main_pos_config.open_session_cb()
# needed because tests are run before the module is marked as
# installed. In js web will only load qweb coming from modules
# that are returned by the backend in module_boot. Without
# this you end up with js, css but no qweb.
env['ir.module.module'].search(
[('name', '=', 'pos_loyalty')], limit=1).state = 'installed'
cr.release()
# Process an order with 2kg of Peaches which should
# add 20 loyalty points
self.phantom_js("/pos/web",
"odoo.__DEBUG__.services['web_tour.tour'].run("
"'test_pos_loyalty_acquire_points')",
"odoo.__DEBUG__.services['web_tour.tour'].tours"
".test_pos_loyalty_acquire_points.ready",
login="admin")
self.assertEqual(customer.loyalty_points, 20)
# Spend 20 loyalty points on "Free Peaches" reward
self.phantom_js("/pos/web",
"odoo.__DEBUG__.services['web_tour.tour'].run("
"'test_pos_loyalty_spend_points')",
"odoo.__DEBUG__.services['web_tour.tour'].tours"
".test_pos_loyalty_spend_points.ready",
login="admin")
customer_points = customer.read(
['loyalty_points'])[0]['loyalty_points']
self.assertEqual(customer_points, 0)

20
pos_loyalty/views/pos_config_view.xml

@ -1,14 +1,22 @@
<?xml version="1.0"?>
<odoo>
<record id="pos_config_form_view" model="ir.ui.view">
<record id="pos_config_view_form" model="ir.ui.view">
<field name="name">pos.config.form</field>
<field name="model">pos.config</field>
<field name="inherit_id" ref="point_of_sale.view_pos_config_form"/>
<field name="inherit_id" ref="point_of_sale.pos_config_view_form"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='receipt']" position="after">
<group string="Loyalty Program" col="4">
<field name="loyalty_id"/>
</group>
<xpath expr="//div[@id='pricelist_setting']" position="after">
<div class="col-xs-12 col-md-6 o_setting_box" title="abc">
<div class="o_setting_right_pane">
<label for="loyalty_id" string="Loyalty Program (OCA)"/>
<div class="text-muted">
Loyalty program that will be available in this PoS
</div>
<div class="content-group mt16">
<field name="loyalty_id"/>
</div>
</div>
</div>
</xpath>
</field>
</record>

Loading…
Cancel
Save