[IMP] Gift Card: Allow usage of gift card in POS
Split initial module gift card into 2 modules:
- gift card
- sales gift card
This change is made to isolate the sale order from the gift card module.
In order to create the gift card in the point of sale, we added 3 options
in the pos config.
Create and set: generate and set the amount of a new gift card.
Use and set: We use an already existing card and set the amount of this one.
Use: We use a gift card that already has a certain amount.
When you pay with a gift cards, reports are generated. Those reports
give you the code and the barcode of the gift cards.
You also get information such as expiration date and the amount.
closes odoo/odoo#75518
Task-id: 1737
Related: odoo/upgrade#2799
Signed-off-by: pimodoo <pimodoo@users.noreply.github.com>
This commit is contained in:
committed by
Pierre Masereel
parent
ff4d25a427
commit
809f0bbe1b
@@ -3,16 +3,14 @@
|
||||
|
||||
{
|
||||
'name': "Gift Card",
|
||||
'summary': "Use gift card in your sales orders",
|
||||
'description': """Integrate gift card mechanism in sales orders.""",
|
||||
'summary': "Use gift card",
|
||||
'description': """Integrate gift card mechanism""",
|
||||
'category': 'Sales/Sales',
|
||||
'version': '1.0',
|
||||
'depends': ['sale'],
|
||||
'depends': ['product'],
|
||||
'data': [
|
||||
'data/mail_template_data.xml',
|
||||
'data/gift_card_data.xml',
|
||||
'views/views.xml',
|
||||
'views/templates.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'license': 'LGPL-3',
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
<field name="type">service</field>
|
||||
<field name="purchase_ok" eval="False"/>
|
||||
<field name="sale_ok" eval="False"/>
|
||||
<field name="taxes_id" eval="False"/>
|
||||
<field name="supplier_taxes_id" eval="False"/>
|
||||
<field name="image_1920" type="base64" file="gift_card/static/img/gift_card.png"/>
|
||||
</record>
|
||||
|
||||
@@ -20,7 +18,6 @@
|
||||
<field name="type">service</field>
|
||||
<field name="purchase_ok" eval="False"/>
|
||||
<field name="is_gift_card" eval="True"/>
|
||||
<field name="taxes_id" eval="False"/>
|
||||
<field name="image_1920" type="base64" file="gift_card/static/img/gift_card.png"/>
|
||||
</record>
|
||||
</data>
|
||||
|
||||
@@ -3,4 +3,3 @@
|
||||
|
||||
from . import gift_card
|
||||
from . import product
|
||||
from . import sale_order
|
||||
|
||||
@@ -13,10 +13,10 @@ class GiftCard(models.Model):
|
||||
|
||||
@api.model
|
||||
def _generate_code(self):
|
||||
return str(uuid4())[4:-8]
|
||||
return '044' + str(uuid4())[4:-8][3:]
|
||||
|
||||
name = fields.Char(compute='_compute_name')
|
||||
code = fields.Char(default=_generate_code, required=True, readonly=True, copy=False)
|
||||
code = fields.Char(default=lambda x: x._generate_code(), required=True, readonly=True, copy=False)
|
||||
partner_id = fields.Many2one('res.partner', help="If empty, all users can use it")
|
||||
company_id = fields.Many2one('res.company', default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one('res.currency', readonly=True, related='company_id.currency_id')
|
||||
@@ -29,10 +29,6 @@ class GiftCard(models.Model):
|
||||
copy=False
|
||||
)
|
||||
|
||||
buy_line_id = fields.Many2one("sale.order.line", copy=False, readonly=True,
|
||||
help="Sale Order line where this gift card has been bought.")
|
||||
redeem_line_ids = fields.One2many('sale.order.line', 'gift_card_id', string="Redeems")
|
||||
|
||||
_sql_constraints = [
|
||||
('unique_gift_card_code', 'UNIQUE(code)', 'The gift card code must be unique.'),
|
||||
('check_amount', 'CHECK(initial_amount >= 0)', 'The initial amount must be positive.')
|
||||
@@ -42,17 +38,6 @@ class GiftCard(models.Model):
|
||||
for record in self:
|
||||
record.name = _("Gift #%s", record.id)
|
||||
|
||||
@api.depends("initial_amount", "redeem_line_ids")
|
||||
def _compute_balance(self):
|
||||
for record in self:
|
||||
balance = record.initial_amount
|
||||
confirmed_line = record.redeem_line_ids.filtered(lambda l: l.state == 'sale')
|
||||
if confirmed_line:
|
||||
balance -= sum(confirmed_line.mapped(
|
||||
lambda line: line.currency_id._convert(line.price_unit, record.currency_id, record.env.company, line.create_date) * -1
|
||||
))
|
||||
record.balance = balance
|
||||
|
||||
@api.autovacuum
|
||||
def _gc_mark_expired_gift_card(self):
|
||||
self.env['gift.card'].search([
|
||||
@@ -66,3 +51,8 @@ class GiftCard(models.Model):
|
||||
def can_be_used(self):
|
||||
# expired state are computed once a day, so can be not synchro
|
||||
return self.state == 'valid' and self.balance > 0 and self.expired_date >= fields.Date.today()
|
||||
|
||||
@api.depends("initial_amount")
|
||||
def _compute_balance(self):
|
||||
for record in self:
|
||||
record.balance = record.initial_amount
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_gift_card_all,Gift Card Program All,model_gift_card,,0,0,0,0
|
||||
access_gift_card_sales,Gift Card Program Salesman,model_gift_card,sales_team.group_sale_salesman,1,0,0,0
|
||||
access_gift_card_manager,Gift Card Program Sale Manager,model_gift_card,sales_team.group_sale_manager,1,1,1,1
|
||||
|
||||
|
@@ -1,13 +1,6 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
<!-- Actions -->
|
||||
<record id="gift_card_sale_order_action" model="ir.actions.act_window">
|
||||
<field name="name">Gift Cards</field>
|
||||
<field name="res_model">gift.card</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="domain">[('buy_line_id.order_id', '=', active_id)]</field>
|
||||
</record>
|
||||
|
||||
<record id="gift_card_action" model="ir.actions.act_window">
|
||||
<field name="name">Gift Cards</field>
|
||||
<field name="res_model">gift.card</field>
|
||||
@@ -39,25 +32,17 @@
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="buy_line_id" string="Sale Order"/>
|
||||
<field name="create_date"/>
|
||||
<field name="expired_date"/>
|
||||
<field name="code"/>
|
||||
</group>
|
||||
<group>
|
||||
<group name="gift_card">
|
||||
<field name="currency_id" attrs="{'invisible': True}"/>
|
||||
<field name="initial_amount"/>
|
||||
<field name="balance"/>
|
||||
<field name="partner_id"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<field name="redeem_line_ids" options="{'no_create': True}" readonly="1">
|
||||
<tree>
|
||||
<field name="order_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
@@ -74,21 +59,4 @@
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="sale_order_view_extend_gift_card_form" model="ir.ui.view">
|
||||
<field name="name">sale.order.view.form.inherit.gift.card</field>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="inherit_id" ref="sale.view_order_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//button[@name='preview_sale_order']" position="before">
|
||||
<button class="oe_stat_button"
|
||||
name="%(gift_card_sale_order_action)d"
|
||||
attrs="{'invisible': [('gift_card_count', '=', 0)]}"
|
||||
icon="fa-gift"
|
||||
type="action">
|
||||
<field name="gift_card_count" widget="statinfo" string="Gift Cards"/>
|
||||
</button>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
@@ -741,13 +741,7 @@ class PosOrder(models.Model):
|
||||
'target': 'current',
|
||||
}
|
||||
|
||||
def action_receipt_to_customer(self, name, client, ticket):
|
||||
if not self:
|
||||
return False
|
||||
if not client.get('email'):
|
||||
return False
|
||||
|
||||
message = _("<p>Dear %s,<br/>Here is your electronic ticket for the %s. </p>") % (client['name'], name)
|
||||
def _add_mail_attachment(self, name, ticket):
|
||||
filename = 'Receipt-' + name + '.jpg'
|
||||
receipt = self.env['ir.attachment'].create({
|
||||
'name': filename,
|
||||
@@ -758,19 +752,12 @@ class PosOrder(models.Model):
|
||||
'store_fname': filename,
|
||||
'mimetype': 'image/jpeg',
|
||||
})
|
||||
mail_values = {
|
||||
'subject': _('Receipt %s', name),
|
||||
'body_html': message,
|
||||
'author_id': self.env.user.partner_id.id,
|
||||
'email_from': self.env.company.email or self.env.user.email_formatted,
|
||||
'email_to': client['email'],
|
||||
'attachment_ids': [(4, receipt.id)],
|
||||
}
|
||||
attachment = [(4, receipt.id)]
|
||||
|
||||
if self.mapped('account_move'):
|
||||
report = self.env.ref('account.account_invoices')._render_qweb_pdf(self.account_move.ids[0])
|
||||
filename = name + '.pdf'
|
||||
attachment = self.env['ir.attachment'].create({
|
||||
invoice = self.env['ir.attachment'].create({
|
||||
'name': filename,
|
||||
'type': 'binary',
|
||||
'datas': base64.b64encode(report[0]),
|
||||
@@ -779,7 +766,26 @@ class PosOrder(models.Model):
|
||||
'res_id': self.ids[0],
|
||||
'mimetype': 'application/x-pdf'
|
||||
})
|
||||
mail_values['attachment_ids'] += [(4, attachment.id)]
|
||||
attachment += [(4, invoice.id)]
|
||||
|
||||
return attachment
|
||||
|
||||
def action_receipt_to_customer(self, name, client, ticket):
|
||||
if not self:
|
||||
return False
|
||||
if not client.get('email'):
|
||||
return False
|
||||
|
||||
message = _("<p>Dear %s,<br/>Here is your electronic ticket for the %s. </p>") % (client['name'], name)
|
||||
|
||||
mail_values = {
|
||||
'subject': _('Receipt %s', name),
|
||||
'body_html': message,
|
||||
'author_id': self.env.user.partner_id.id,
|
||||
'email_from': self.env.company.email or self.env.user.email_formatted,
|
||||
'email_to': client['email'],
|
||||
'attachment_ids': self._add_mail_attachment(name, ticket),
|
||||
}
|
||||
|
||||
mail = self.env['mail.mail'].sudo().create(mail_values)
|
||||
mail.send()
|
||||
|
||||
@@ -13,6 +13,7 @@ class ResConfigSettings(models.TransientModel):
|
||||
update_stock_quantities = fields.Selection(related="company_id.point_of_sale_update_stock_quantities", readonly=False)
|
||||
module_pos_coupon = fields.Boolean("Coupon and Promotion Programs", help="Allow the use of coupon and promotion programs in PoS.")
|
||||
account_default_pos_receivable_account_id = fields.Many2one(string='Default Account Receivable (PoS)', related='company_id.account_default_pos_receivable_account_id', readonly=False)
|
||||
module_pos_gift_card = fields.Boolean("Gift Cards", help="Allow the use of gift card")
|
||||
|
||||
def set_values(self):
|
||||
super(ResConfigSettings, self).set_values()
|
||||
|
||||
@@ -77,6 +77,17 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 o_setting_box" title="Manage gift card.">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="module_pos_gift_card"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane" id="pos-gift-card">
|
||||
<label for="module_pos_gift_card" string="Gift card"/>
|
||||
<div class="text-muted">
|
||||
Manage gift card
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2>Payments</h2>
|
||||
<div class="row mt16 o_settings_container">
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
@@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': "Gift Card for point of sales module",
|
||||
'summary': "Use gift card in your sales orders",
|
||||
'description': """Integrate gift card mechanism in sales orders.""",
|
||||
'category': 'Sales/Sales',
|
||||
'version': '1.0',
|
||||
'depends': ['gift_card', 'point_of_sale'],
|
||||
'auto_install': True,
|
||||
'data': [
|
||||
'data/gift_card_data.xml',
|
||||
'views/gift_card_views.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
'views/pos_config_views.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'assets': {
|
||||
'point_of_sale.assets': [
|
||||
'pos_gift_card/static/src/css/giftCard.css',
|
||||
'pos_gift_card/static/src/js/models.js',
|
||||
'pos_gift_card/static/src/js/GiftCardButton.js',
|
||||
'pos_gift_card/static/src/js/GiftCardPopup.js',
|
||||
'pos_gift_card/static/src/js/PaymentScreen.js',
|
||||
],
|
||||
'web.assets_qweb': [
|
||||
'pos_gift_card/static/src/xml/**/*',
|
||||
],
|
||||
},
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<!-- the product used to deduce on sale order -->
|
||||
<record id="gift_card.pay_with_gift_card_product" model="product.product">
|
||||
<field name="available_in_pos">True</field>
|
||||
</record>
|
||||
|
||||
<record id="gift_card_report_pdf" model="ir.actions.report">
|
||||
<field name="name">Gift Card</field>
|
||||
<field name="model">gift.card</field>
|
||||
<field name="report_type">qweb-pdf</field>
|
||||
<field name="report_name">pos_gift_card.gift_card_template</field>
|
||||
<field name="binding_type">report</field>
|
||||
<field name="binding_model_id" ref="model_gift_card"/>
|
||||
</record>
|
||||
|
||||
<template id="gift_card_template">
|
||||
<t t-call="web.html_container">
|
||||
<t t-foreach="docs" t-as="o">
|
||||
<t t-call="web.external_layout">
|
||||
<div style="margin:0px; font-size:24px; font-family:arial, 'helvetica neue', helvetica, sans-serif; line-height:36px; color:#333333; text-align: center">
|
||||
Here is your gift card!
|
||||
</div>
|
||||
<div style="padding-top:20px; padding-bottom:20px">
|
||||
<img src="/gift_card/static/img/gift_card.png" style="display:block; border:0; outline:none; text-decoration:none; margin:auto;" width="300"/>
|
||||
</div>
|
||||
<div style="padding:0; margin:0px; padding-top:35px; padding-bottom:35px; text-align:center;">
|
||||
<h3 style="margin:0px; line-height:48px; font-family:arial, 'helvetica neue', helvetica, sans-serif; font-size:40px; font-style:normal; font-weight:normal; color:#333333; text-align:center">
|
||||
<strong><span t-field="o.initial_amount"/></strong>
|
||||
</h3>
|
||||
</div>
|
||||
<div style="padding:0; margin:0px; padding-top:35px; padding-bottom:35px; background-color:#efefef; text-align:center;">
|
||||
<p style="margin:0px; font-size:14px;font-family:arial, 'helvetica neue', helvetica, sans-serif; line-height:21px; color:#333333">
|
||||
<strong>Gift Card Code</strong>
|
||||
</p>
|
||||
<p style="margin:0px; font-size:25px;font-family:arial, 'helvetica neue', helvetica, sans-serif; line-height:38px; color:#A9A9A9">
|
||||
<span t-field="o.code"/>
|
||||
</p>
|
||||
</div>
|
||||
<div style="padding:0; margin:0px; padding-top:10px; padding-bottom:10px; text-align:center;">
|
||||
<h3 style="margin:0px; line-height:17px; font-family:arial, 'helvetica neue', helvetica, sans-serif; font-size:14px; font-style:normal; font-weight:normal; color:#A9A9A9; text-align:center">
|
||||
Card expires <span t-field="o.expired_date"/>
|
||||
</h3>
|
||||
</div>
|
||||
<div style="padding:0; margin:0px; padding-top:10px; padding-bottom:10px; text-align:center;">
|
||||
<img t-att-src="'/report/barcode/Code128/'+o.code" style="width:200px;height:50px" alt="Barcode"/>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import barcode_rule
|
||||
from . import gift_card
|
||||
from . import pos_config
|
||||
from . import pos_order
|
||||
from . import pos_order_line
|
||||
@@ -0,0 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class BarcodeRule(models.Model):
|
||||
_inherit = 'barcode.rule'
|
||||
|
||||
type = fields.Selection(selection_add=[
|
||||
('gift_card', 'Gift Card'),
|
||||
], ondelete={
|
||||
'gift_card': 'set default',
|
||||
})
|
||||
@@ -0,0 +1,47 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class GiftCard(models.Model):
|
||||
_inherit = "gift.card"
|
||||
|
||||
buy_pos_order_line_id = fields.Many2one(
|
||||
"pos.order.line",
|
||||
copy=False,
|
||||
readonly=True,
|
||||
help="Pos Order line where this gift card has been bought.",
|
||||
)
|
||||
redeem_pos_order_line_ids = fields.One2many(
|
||||
"pos.order.line", "gift_card_id", string="Pos Redeems"
|
||||
)
|
||||
|
||||
@api.depends("redeem_pos_order_line_ids")
|
||||
def _compute_balance(self):
|
||||
super()._compute_balance()
|
||||
for record in self:
|
||||
confirmed_line = record.redeem_pos_order_line_ids.filtered(
|
||||
lambda l: l.order_id.state == "paid"
|
||||
)
|
||||
balance = record.balance
|
||||
if confirmed_line:
|
||||
balance -= sum(
|
||||
confirmed_line.mapped(
|
||||
lambda line: line.currency_id._convert(
|
||||
line.price_unit,
|
||||
record.currency_id,
|
||||
record.env.company,
|
||||
line.create_date,
|
||||
)
|
||||
* -1
|
||||
)
|
||||
)
|
||||
record.balance = balance
|
||||
|
||||
def can_be_used_in_pos(self):
|
||||
# expired state are computed once a day, so can be not synchro
|
||||
return self.state == 'valid' and self.balance > 0 and self.expired_date >= fields.Date.today() and self.buy_pos_order_line_id
|
||||
|
||||
def can_be_used(self):
|
||||
return super(GiftCard, self).can_be_used() and not self.buy_pos_order_line_id
|
||||
@@ -0,0 +1,36 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class PosConfig(models.Model):
|
||||
_inherit = "pos.config"
|
||||
|
||||
use_gift_card = fields.Boolean(string="Gift Card")
|
||||
|
||||
gift_card_product_id = fields.Many2one(
|
||||
"product.product",
|
||||
string="Gift Card Product",
|
||||
help="This product is used as reference on customer receipts.",
|
||||
)
|
||||
|
||||
gift_card_settings = fields.Selection(
|
||||
[
|
||||
("create_set", "Generate a new barcode and set a price"),
|
||||
("scan_set", "Scan an existing barcode and set a price"),
|
||||
("scan_use", "Scan an existing barcode with an existing price"),
|
||||
],
|
||||
string="Gift Cards settings",
|
||||
default="create_set",
|
||||
help="Defines the way you want to set your gift cards.",
|
||||
)
|
||||
|
||||
@api.onchange("use_gift_card")
|
||||
def _onchange_giftproduct(self):
|
||||
if self.use_gift_card:
|
||||
self.gift_card_product_id = self.env.ref(
|
||||
"gift_card.pay_with_gift_card_product", False
|
||||
)
|
||||
else:
|
||||
self.gift_card_product_id = False
|
||||
@@ -0,0 +1,64 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
import base64
|
||||
|
||||
|
||||
class PosOrder(models.Model):
|
||||
_inherit = "pos.order"
|
||||
|
||||
gift_card_count = fields.Integer(compute="_compute_gift_card_count")
|
||||
|
||||
@api.depends("lines.generated_gift_card_ids")
|
||||
def _compute_gift_card_count(self):
|
||||
for record in self:
|
||||
record.gift_card_count = len(record.lines.mapped("generated_gift_card_ids"))
|
||||
|
||||
@api.model
|
||||
def create_from_ui(self, orders, draft=False):
|
||||
order_ids = super(PosOrder, self).create_from_ui(orders, draft)
|
||||
for order in self.sudo().browse([o["id"] for o in order_ids]):
|
||||
gift_card_config = order.config_id.gift_card_settings
|
||||
for line in order.lines:
|
||||
if line.product_id.id == order.config_id.gift_card_product_id.id:
|
||||
if not line.gift_card_id:
|
||||
if gift_card_config == "create_set":
|
||||
new_card = line._create_gift_cards()
|
||||
new_card.partner_id = order.partner_id or False
|
||||
line.generated_gift_card_ids = new_card
|
||||
else:
|
||||
gift_card = self.env["gift.card"].search(
|
||||
[("id", "=", line.generated_gift_card_ids.id)]
|
||||
)
|
||||
gift_card.buy_pos_order_line_id = line.id
|
||||
gift_card.expired_date = fields.Date.add(
|
||||
fields.Date.today(), years=1
|
||||
)
|
||||
gift_card.partner_id = order.partner_id or False
|
||||
|
||||
if gift_card_config == "scan_set":
|
||||
gift_card.initial_amount = line.price_unit
|
||||
|
||||
return order_ids
|
||||
|
||||
def get_new_card_ids(self):
|
||||
return self.lines.mapped("generated_gift_card_ids").ids
|
||||
|
||||
def _add_mail_attachment(self, name, ticket):
|
||||
attachment = super()._add_mail_attachment(name, ticket)
|
||||
if self.config_id.use_gift_card and len(self.get_new_card_ids()) > 0:
|
||||
report = self.env.ref('pos_gift_card.gift_card_report_pdf')._render_qweb_pdf(self.get_new_card_ids())
|
||||
filename = name + '.pdf'
|
||||
gift_card = self.env['ir.attachment'].create({
|
||||
'name': filename,
|
||||
'type': 'binary',
|
||||
'datas': base64.b64encode(report[0]),
|
||||
'store_fname': filename,
|
||||
'res_model': 'pos.order',
|
||||
'res_id': self.ids[0],
|
||||
'mimetype': 'application/x-pdf'
|
||||
})
|
||||
attachment += [(4, gift_card.id)]
|
||||
|
||||
return attachment
|
||||
@@ -0,0 +1,34 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class PosOrderLine(models.Model):
|
||||
_inherit = "pos.order.line"
|
||||
|
||||
generated_gift_card_ids = fields.One2many(
|
||||
"gift.card", "buy_pos_order_line_id", string="Bought Gift Card"
|
||||
)
|
||||
gift_card_id = fields.Many2one(
|
||||
"gift.card", help="Deducted from this Gift Card", copy=False
|
||||
)
|
||||
|
||||
def _is_not_sellable_line(self):
|
||||
return self.gift_card_id or super()._is_not_sellable_line()
|
||||
|
||||
def _create_gift_cards(self):
|
||||
return self.env["gift.card"].create(
|
||||
[self._build_gift_card() for _ in range(int(self.qty))]
|
||||
)
|
||||
|
||||
def _build_gift_card(self):
|
||||
return {
|
||||
"initial_amount": self.order_id.currency_id._convert(
|
||||
self.price_unit,
|
||||
self.company_id.currency_id,
|
||||
self.company_id,
|
||||
fields.Date.today(),
|
||||
),
|
||||
"buy_pos_order_line_id": self.id,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_gift_card_sales,Gift Card Program Salesman,model_gift_card,point_of_sale.group_pos_manager,1,0,0,0
|
||||
access_pos_gift_card_manager,Gift Card Program Sale Manager,model_gift_card,point_of_sale.group_pos_manager,1,1,1,1
|
||||
|
@@ -0,0 +1,45 @@
|
||||
.giftCardPopupMain {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
justify-content: space-around;
|
||||
|
||||
text-align: center;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.giftCardPopupContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
padding: 5px;
|
||||
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.giftCardPopupFooter {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.giftCardPopupInput {
|
||||
border-bottom: 1px solid !important;
|
||||
background-color: #F0EEEE !important;
|
||||
box-shadow: none !important;
|
||||
text-align: right !important;
|
||||
}
|
||||
|
||||
.giftCardPopupButton {
|
||||
width: 50% !important;
|
||||
margin: 6px;
|
||||
}
|
||||
|
||||
.giftCardPopupConfirmButton {
|
||||
width: 40% !important;
|
||||
}
|
||||
|
||||
.giftCardPopupIn
|
||||
@@ -0,0 +1,31 @@
|
||||
odoo.define("pos_gift_card.GiftCardButton", function (require) {
|
||||
"use strict";
|
||||
|
||||
const PosComponent = require("point_of_sale.PosComponent");
|
||||
const ProductScreen = require("point_of_sale.ProductScreen");
|
||||
const { useListener } = require("web.custom_hooks");
|
||||
const Registries = require("point_of_sale.Registries");
|
||||
const { Gui } = require("point_of_sale.Gui");
|
||||
|
||||
class GiftCardButton extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
useListener("click", this.onClick);
|
||||
}
|
||||
async onClick() {
|
||||
this.showPopup("GiftCardPopup", {});
|
||||
}
|
||||
}
|
||||
GiftCardButton.template = "GiftCardButton";
|
||||
|
||||
ProductScreen.addControlButton({
|
||||
component: GiftCardButton,
|
||||
condition: function () {
|
||||
return this.env.pos.config.use_gift_card;
|
||||
},
|
||||
});
|
||||
|
||||
Registries.Component.add(GiftCardButton);
|
||||
|
||||
return GiftCardButton;
|
||||
});
|
||||
@@ -0,0 +1,151 @@
|
||||
odoo.define("pos_gift_card.GiftCardPopup", function (require) {
|
||||
"use strict";
|
||||
|
||||
const { useState, useRef } = owl.hooks;
|
||||
const AbstractAwaitablePopup = require("point_of_sale.AbstractAwaitablePopup");
|
||||
const Registries = require("point_of_sale.Registries");
|
||||
|
||||
class GiftCardPopup extends AbstractAwaitablePopup {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.state = useState({
|
||||
giftCardConfig: this.env.pos.config.gift_card_settings,
|
||||
showBarcodeGeneration: false,
|
||||
showNewGiftCardMenu: false,
|
||||
showUseGiftCardMenu: false,
|
||||
showGiftCardDetails: false,
|
||||
amountToSet: 0,
|
||||
giftCardBarcode: "",
|
||||
});
|
||||
}
|
||||
|
||||
switchBarcodeView() {
|
||||
this.state.showBarcodeGeneration = !this.state.showBarcodeGeneration;
|
||||
if (this.state.showUseGiftCardMenu)
|
||||
this.state.showUseGiftCardMenu = false;
|
||||
if (this.state.showGiftCardDetails)
|
||||
this.state.showGiftCardDetails = false;
|
||||
}
|
||||
|
||||
switchToUseGiftCardMenu() {
|
||||
this.switchBarcodeView();
|
||||
this.state.showUseGiftCardMenu = true;
|
||||
}
|
||||
|
||||
switchToShowGiftCardDetails() {
|
||||
this.switchBarcodeView();
|
||||
this.state.showGiftCardDetails = true;
|
||||
}
|
||||
|
||||
addGiftCardProduct(giftCard) {
|
||||
let gift =
|
||||
this.env.pos.db.product_by_id[
|
||||
this.env.pos.config.gift_card_product_id[0]
|
||||
];
|
||||
this.env.pos.get_order().add_product(gift, {
|
||||
price: this.state.amountToSet,
|
||||
quantity: 1,
|
||||
merge: false,
|
||||
generated_gift_card_ids: giftCard ? giftCard.id : false,
|
||||
});
|
||||
}
|
||||
|
||||
async getGiftCard() {
|
||||
if (this.state.giftCardBarcode == "") return;
|
||||
|
||||
let giftCard = this.env.pos.giftCard.find(
|
||||
(gift) => gift.code === this.state.giftCardBarcode
|
||||
);
|
||||
|
||||
if (!giftCard) {
|
||||
giftCard = await this.rpc({
|
||||
model: "gift.card",
|
||||
method: "search_read",
|
||||
args: [[["code", "=", this.state.giftCardBarcode]]],
|
||||
fields: ["code", "initial_amount", "balance"],
|
||||
});
|
||||
if (giftCard.length) {
|
||||
this.env.pos.giftCard.push(giftCard[0])
|
||||
giftCard = giftCard[0];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return giftCard;
|
||||
}
|
||||
|
||||
async scanAndUseGiftCard() {
|
||||
let giftCard = await this.getGiftCard();
|
||||
if (!giftCard) return;
|
||||
|
||||
if (this.state.giftCardConfig === "scan_use")
|
||||
this.state.amountToSet = giftCard.initial_amount;
|
||||
|
||||
this.addGiftCardProduct(giftCard);
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
async generateBarcode() {
|
||||
this.addGiftCardProduct(false);
|
||||
this.confirm();
|
||||
}
|
||||
|
||||
async isGiftCardAlreadyUsed() {
|
||||
let order = this.env.pos.get_order();
|
||||
let giftProduct =
|
||||
this.env.pos.db.product_by_id[
|
||||
this.env.pos.config.gift_card_product_id[0]
|
||||
];
|
||||
|
||||
for (let line of order.orderlines.models) {
|
||||
if (line.product.id === giftProduct.id && line.price < 0) {
|
||||
if (line.gift_card_id === await this.getGiftCard().id) return line;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getPriceToRemove(giftCard) {
|
||||
let currentOrder = this.env.pos.get_order();
|
||||
return currentOrder.get_total_with_tax() > giftCard.balance
|
||||
? -giftCard.balance
|
||||
: -currentOrder.get_total_with_tax();
|
||||
}
|
||||
|
||||
async payWithGiftCard() {
|
||||
let giftCard = await this.getGiftCard();
|
||||
if (!giftCard) return;
|
||||
|
||||
let gift =
|
||||
this.env.pos.db.product_by_id[
|
||||
this.env.pos.config.gift_card_product_id[0]
|
||||
];
|
||||
|
||||
let currentOrder = this.env.pos.get_order();
|
||||
let lineUsed = this.isGiftCardAlreadyUsed()
|
||||
if (lineUsed) currentOrder.remove_orderline(lineUsed);
|
||||
|
||||
currentOrder.add_product(gift, {
|
||||
price: this.getPriceToRemove(giftCard),
|
||||
quantity: 1,
|
||||
merge: false,
|
||||
gift_card_id: giftCard.id,
|
||||
});
|
||||
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
async ShowRemainingAmount() {
|
||||
let giftCard = await this.getGiftCard();
|
||||
if (!giftCard) return;
|
||||
|
||||
this.state.amountToSet = giftCard.balance;
|
||||
}
|
||||
}
|
||||
GiftCardPopup.template = "GiftCardPopup";
|
||||
|
||||
Registries.Component.add(GiftCardPopup);
|
||||
|
||||
return GiftCardPopup;
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
odoo.define('pos_gift_card.PaymentScreen', function(require) {
|
||||
"use strict";
|
||||
|
||||
const PaymentScreen = require('point_of_sale.PaymentScreen');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
var core = require('web.core');
|
||||
var _t = core._t;
|
||||
|
||||
|
||||
const PosGiftCardPaymentScreen = PaymentScreen => class extends PaymentScreen {
|
||||
//@Override
|
||||
async validateOrder(isForceValidate) {
|
||||
if(this.env.pos.config.use_gift_card) {
|
||||
if (await this._isOrderValid(isForceValidate)) {
|
||||
try {
|
||||
let giftProduct = this.env.pos.db.product_by_id[this.env.pos.config.gift_card_product_id[0]];
|
||||
|
||||
for (let line of this.currentOrder.orderlines.models) {
|
||||
if(line.product.id === giftProduct.id && line.price <= 0) {
|
||||
let is_valid = await this.rpc({
|
||||
model: "gift.card",
|
||||
method: 'can_be_used_in_pos',
|
||||
args: [line.gift_card_id],
|
||||
});
|
||||
|
||||
if(!is_valid) {
|
||||
await this.showPopup('ErrorPopup', {
|
||||
'title': _t("Gift Card Error"),
|
||||
'body': _t("Gift card is not valid."),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let gift_card = await this.rpc({
|
||||
model: "gift.card",
|
||||
method: 'search_read',
|
||||
domain: [['id', '=', line.gift_card_id]],
|
||||
fields: ['balance'],
|
||||
});
|
||||
|
||||
if(Math.abs(line.price) > gift_card[0].balance) {
|
||||
await this.showPopup('ErrorPopup', {
|
||||
'title': _t("Gift Card Error"),
|
||||
'body': _t("Gift card balance is too low."),
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.env.pos.giftCard.find(gift => gift.id === gift_card[0].id).balance += line.price;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// do nothing with the error
|
||||
}
|
||||
}
|
||||
}
|
||||
await super.validateOrder(...arguments);
|
||||
}
|
||||
|
||||
async _postPushOrderResolve(order, server_ids) {
|
||||
if(this.env.pos.config.use_gift_card) {
|
||||
let ids = await this.rpc({
|
||||
model: 'pos.order',
|
||||
method: 'get_new_card_ids',
|
||||
args: [server_ids]
|
||||
});
|
||||
if(ids.length > 0)
|
||||
this.env.pos.print_gift_pdf(ids);
|
||||
}
|
||||
return super._postPushOrderResolve(order, server_ids);
|
||||
}
|
||||
};
|
||||
|
||||
Registries.Component.extend(PaymentScreen, PosGiftCardPaymentScreen);
|
||||
|
||||
return PosGiftCardPaymentScreen;
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
odoo.define("pos_gift_card.gift_card", function (require) {
|
||||
"use strict";
|
||||
|
||||
const models = require("point_of_sale.models");
|
||||
|
||||
models.load_fields("pos.order.line", "generated_gift_card_ids");
|
||||
models.load_fields("pos.order.line", "redeem_pos_order_line_ids");
|
||||
|
||||
// Load the products used for creating program reward lines.
|
||||
var existing_models = models.PosModel.prototype.models;
|
||||
var product_index = _.findIndex(existing_models, function (model) {
|
||||
return model.model === 'product.product';
|
||||
});
|
||||
var product_model = existing_models[product_index];
|
||||
|
||||
models.load_models([
|
||||
{
|
||||
model: "gift.card",
|
||||
fields: ["code", "initial_amount", "balance"],
|
||||
loaded: function (self, giftCard) {
|
||||
self.giftCard = giftCard;
|
||||
},
|
||||
}, {
|
||||
model: product_model.model,
|
||||
fields: product_model.fields,
|
||||
order: product_model.order,
|
||||
domain: function (self) {
|
||||
return [['id', '=', self.config.gift_card_product_id[0]]];
|
||||
},
|
||||
context: product_model.context,
|
||||
loaded: product_model.loaded,
|
||||
},
|
||||
]);
|
||||
|
||||
var _order_super = models.Order.prototype;
|
||||
models.Order = models.Order.extend({
|
||||
set_orderline_options: function (orderline, options) {
|
||||
_order_super.set_orderline_options.apply(this, [orderline, options]);
|
||||
if (options && options.generated_gift_card_ids) {
|
||||
orderline.generated_gift_card_ids = [options.generated_gift_card_ids];
|
||||
}
|
||||
if (options && options.gift_card_id) {
|
||||
orderline.gift_card_id = options.gift_card_id;
|
||||
}
|
||||
},
|
||||
wait_for_push_order: function () {
|
||||
if(this.pos.config.use_gift_card) {
|
||||
let giftProduct = this.pos.db.product_by_id[this.pos.config.gift_card_product_id[0]];
|
||||
for (let line of this.orderlines.models) {
|
||||
if(line.product.id === giftProduct.id)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return _order_super.wait_for_push_order.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
var _super_orderline = models.Orderline;
|
||||
models.Orderline = models.Orderline.extend({
|
||||
export_as_JSON: function () {
|
||||
var json = _super_orderline.prototype.export_as_JSON.apply(
|
||||
this,
|
||||
arguments
|
||||
);
|
||||
json.generated_gift_card_ids = this.generated_gift_card_ids;
|
||||
json.gift_card_id = this.gift_card_id;
|
||||
return json;
|
||||
},
|
||||
init_from_JSON: function (json) {
|
||||
_super_orderline.prototype.init_from_JSON.apply(this, arguments);
|
||||
this.generated_gift_card_ids = json.generated_gift_card_ids;
|
||||
this.gift_card_id = json.gift_card_id;
|
||||
},
|
||||
});
|
||||
|
||||
var _posmodel_super = models.PosModel.prototype;
|
||||
models.PosModel = models.PosModel.extend({
|
||||
print_gift_pdf: function (giftCardIds) {
|
||||
this.do_action('pos_gift_card.gift_card_report_pdf', {
|
||||
additional_context: {
|
||||
active_ids: [giftCardIds],
|
||||
},
|
||||
})
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="GiftCardButton" owl="1">
|
||||
<span class="control-button">
|
||||
<i class="fa fa-gift"></i>
|
||||
<span> </span>
|
||||
<span>Gift Card</span>
|
||||
</span>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -0,0 +1,140 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="GiftCardPopup" owl="1">
|
||||
<div role="dialog" class="modal-dialog">
|
||||
<Draggable>
|
||||
<div class="popup popup-textarea">
|
||||
<header class="title drag-handle">
|
||||
Gift Card
|
||||
</header>
|
||||
|
||||
<main class="giftCardPopupMain">
|
||||
<div class="giftCardPopupContainer" t-if="!state.showBarcodeGeneration">
|
||||
<span t-if="state.giftCardConfig == 'create_set'"
|
||||
class="giftCardPopupButton button"
|
||||
t-on-click="switchBarcodeView">Generate barcode</span>
|
||||
<span t-if="state.giftCardConfig == 'scan_set'"
|
||||
class="giftCardPopupButton button"
|
||||
t-on-click="switchBarcodeView">Scan and set price on gift card</span>
|
||||
<span t-if="state.giftCardConfig == 'scan_use'"
|
||||
class="giftCardPopupButton button"
|
||||
t-on-click="switchBarcodeView">Scan gift card</span>
|
||||
</div>
|
||||
|
||||
<div t-if="!state.showUseGiftCardMenu && !state.showGiftCardDetails">
|
||||
<div class="giftCardPopupContainer"
|
||||
t-if="state.showBarcodeGeneration && state.giftCardConfig == 'create_set'">
|
||||
<div>
|
||||
<span>Amount of the gift card:</span><br/>
|
||||
<div>
|
||||
<input t-model.number="state.amountToSet" class="giftCardPopupInput" type="text"/>
|
||||
<span class="currency">
|
||||
<t t-esc="env.pos.getCurrencySymbol()" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="button confirm" t-on-click="generateBarcode">
|
||||
Confirm
|
||||
</div>
|
||||
<div class="button confirm" t-on-click="switchBarcodeView">
|
||||
Cancel
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="giftCardPopupContainer"
|
||||
t-if="state.showBarcodeGeneration && state.giftCardConfig == 'scan_set'">
|
||||
<div>
|
||||
Gift Card Barcode: <br/>
|
||||
<input t-model="state.giftCardBarcode" class="giftCardPopupInput" type="text"/>
|
||||
</div>
|
||||
<div>
|
||||
Amount of the gift card: <br/>
|
||||
<input t-model.number="state.amountToSet" class="giftCardPopupInput" type="text"/>
|
||||
</div>
|
||||
<div>
|
||||
<span class="button confirm" t-on-click="scanAndUseGiftCard">
|
||||
Confirm
|
||||
</span>
|
||||
<span class="button cancel" t-on-click="switchBarcodeView">
|
||||
Discard
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="giftCardPopupContainer"
|
||||
t-if="state.showBarcodeGeneration && state.giftCardConfig == 'scan_use'">
|
||||
<div>
|
||||
Gift Card Barcode:
|
||||
<input t-model="state.giftCardBarcode" class="giftCardPopupInput" type="text"/>
|
||||
</div>
|
||||
<div>
|
||||
<span class="button confirm" t-on-click="scanAndUseGiftCard">
|
||||
Confirm
|
||||
</span>
|
||||
<span class="button cancel" t-on-click="switchBarcodeView">
|
||||
Discard
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="giftCardPopupContainer"
|
||||
t-if="state.showBarcodeGeneration && state.showUseGiftCardMenu">
|
||||
<div>
|
||||
Gift Card Barcode:
|
||||
<input t-model="state.giftCardBarcode" class="giftCardPopupInput" type="text"/>
|
||||
</div>
|
||||
<div>
|
||||
<span class="button confirm" t-on-click="payWithGiftCard">
|
||||
Confirm
|
||||
</span>
|
||||
<span class="button cancel" t-on-click="switchBarcodeView">
|
||||
Discard
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="giftCardPopupContainer"
|
||||
t-if="state.showBarcodeGeneration && state.showGiftCardDetails">
|
||||
<div>
|
||||
Gift Card Barcode:
|
||||
<input t-model="state.giftCardBarcode" class="giftCardPopupInput" type="text"/>
|
||||
</div>
|
||||
<div>
|
||||
Remaining amount of the gift card:
|
||||
<t t-esc="state.amountToSet"/>
|
||||
</div>
|
||||
<div>
|
||||
<span class="button confirm" t-on-click="ShowRemainingAmount">
|
||||
Confirm
|
||||
</span>
|
||||
<span class="button cancel" t-on-click="switchBarcodeView">
|
||||
Discard
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="giftCardPopupContainer" t-if="!state.showBarcodeGeneration">
|
||||
<div class="button giftCardPopupConfirmButton" t-on-click="switchToUseGiftCardMenu">
|
||||
Use a gift card
|
||||
</div>
|
||||
<div class="button giftCardPopupConfirmButton" t-on-click="switchToShowGiftCardDetails">
|
||||
Check a gift card
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="footer giftCardPopupFooter">
|
||||
<div class="button cancel" t-on-click="cancel">
|
||||
Cancel
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</Draggable>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<menuitem
|
||||
id="pos_gift_card_menu"
|
||||
action="gift_card.gift_card_action"
|
||||
parent="point_of_sale.pos_config_menu_catalog"
|
||||
name="Gift Cards"
|
||||
groups="point_of_sale.group_pos_manager"
|
||||
sequence="100"
|
||||
/>
|
||||
|
||||
<record id="pos_gift_card_view_form" model="ir.ui.view">
|
||||
<field name="name">gift.card.form Website</field>
|
||||
<field name="model">gift.card</field>
|
||||
<field name="inherit_id" ref="gift_card.gift_card_view_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='code']" position="after">
|
||||
<field name="buy_pos_order_line_id" options="{'no_create': True}"/>
|
||||
</xpath>
|
||||
<xpath expr="//group[@name='gift_card']" position="after">
|
||||
<group>
|
||||
<field name="redeem_pos_order_line_ids" options="{'no_create': True}" readonly="1">
|
||||
<tree>
|
||||
<field name="id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="pos_gift_card_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.pos_config_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@id='pos-loyalty']" position="after">
|
||||
<div class="col-12 col-lg-6 o_setting_box" id="pos-coupon">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="use_gift_card"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane" title="Gift Card">
|
||||
<label for="use_gift_card"/>
|
||||
<div class="content-group" attrs="{'invisible': [('use_gift_card', '=', False)]}">
|
||||
<div class="mt16" id="gift_card_product">
|
||||
<label string="Gift Card Product" for="gift_card_product_id" class="o_light_label"/>
|
||||
<field name="gift_card_product_id"/><br/>
|
||||
<button name="%(gift_card.gift_card_action)d" icon="fa-arrow-right" type="action" string="Gift Card" class="btn-link"/><br/>
|
||||
<label for="gift_card_settings" string="Gift card settings"/>
|
||||
<field name="gift_card_settings" widget="radio"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="res_config_view_form_inherit_pos_coupon" model="ir.ui.view">
|
||||
<field name="name">res.config.form.inherit.pos.coupon</field>
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="inherit_id" ref="point_of_sale.res_config_settings_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@id='pos-gift-card']/div[last()]" position="after">
|
||||
<div class="mt8" attrs="{'invisible': [('module_pos_gift_card', '=', False)]}">
|
||||
<button name="%(gift_card.gift_card_action)d" icon="fa-arrow-right" type="action" string="Gift Card" class="btn-link"/><br/>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': "Gift Card for sales module",
|
||||
'summary': "Use gift card in your sales orders",
|
||||
'description': """Integrate gift card mechanism in sales orders.""",
|
||||
'category': 'Sales/Sales',
|
||||
'version': '1.0',
|
||||
'depends': ['gift_card', 'sale'],
|
||||
'auto_install': True,
|
||||
'data': [
|
||||
'data/gift_card_data.xml',
|
||||
'data/mail_template_data.xml',
|
||||
'views/sale_order_view.xml',
|
||||
'views/templates.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<!-- the product used to deduce on sale order -->
|
||||
<record id="gift_card.pay_with_gift_card_product" model="product.product">
|
||||
<field name="taxes_id" eval="False"/>
|
||||
<field name="supplier_taxes_id" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record id="gift_card.gift_card_product_50" model="product.product">
|
||||
<field name="taxes_id" eval="False"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import gift_card
|
||||
from . import sale_order
|
||||
@@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class GiftCard(models.Model):
|
||||
_inherit = "gift.card"
|
||||
|
||||
buy_line_id = fields.Many2one("sale.order.line", copy=False, readonly=True,
|
||||
help="Sale Order line where this gift card has been bought.")
|
||||
redeem_line_ids = fields.One2many('sale.order.line', 'gift_card_id', string="Redeems")
|
||||
|
||||
@api.depends("redeem_line_ids")
|
||||
def _compute_balance(self):
|
||||
super()._compute_balance()
|
||||
for record in self:
|
||||
confirmed_line = record.redeem_line_ids.filtered(lambda l: l.state == 'sale')
|
||||
balance = record.balance
|
||||
if confirmed_line:
|
||||
balance -= sum(confirmed_line.mapped(
|
||||
lambda line: line.currency_id._convert(line.price_unit, record.currency_id, record.env.company, line.create_date) * -1
|
||||
))
|
||||
record.balance = balance
|
||||
@@ -51,7 +51,7 @@ class SaleOrder(models.Model):
|
||||
return error
|
||||
|
||||
def _send_gift_card_mail(self):
|
||||
template = self.env.ref('gift_card.mail_template_gift_card', raise_if_not_found=False)
|
||||
template = self.env.ref('sale_gift_card.mail_template_gift_card', raise_if_not_found=False)
|
||||
if template and self.gift_card_count:
|
||||
for gift in self.order_line.mapped("generated_gift_card_ids"):
|
||||
template.send_mail(gift.id, force_send=True, notif_layout='mail.mail_notification_light')
|
||||
@@ -0,0 +1,3 @@
|
||||
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_gift_card_sales,Gift Card Program Salesman,model_gift_card,sales_team.group_sale_salesman,1,0,0,0
|
||||
access_gift_card_manager,Gift Card Program Sale Manager,model_gift_card,sales_team.group_sale_manager,1,1,1,1
|
||||
|
+1
-1
@@ -2,7 +2,7 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import Command
|
||||
from odoo.addons.gift_card.tests.common import TestSaleGiftCardCommon
|
||||
from odoo.addons.sale_gift_card.tests.common import TestSaleGiftCardCommon
|
||||
|
||||
class TestBuyGiftCard(TestSaleGiftCardCommon):
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import Command
|
||||
from odoo.addons.gift_card.tests.common import TestSaleGiftCardCommon
|
||||
from odoo.addons.sale_gift_card.tests.common import TestSaleGiftCardCommon
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
class TestPayWithGiftCard(TestSaleGiftCardCommon):
|
||||
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
<!-- Actions -->
|
||||
<record id="gift_card_sale_order_action" model="ir.actions.act_window">
|
||||
<field name="name">Gift Cards</field>
|
||||
<field name="res_model">gift.card</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="domain">[('buy_line_id.order_id', '=', active_id)]</field>
|
||||
</record>
|
||||
|
||||
<record id="sale_order_view_extend_gift_card_form" model="ir.ui.view">
|
||||
<field name="name">sale.order.view.form.inherit.gift.card</field>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="inherit_id" ref="sale.view_order_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//button[@name='preview_sale_order']" position="before">
|
||||
<button class="oe_stat_button"
|
||||
name="%(gift_card_sale_order_action)d"
|
||||
attrs="{'invisible': [('gift_card_count', '=', 0)]}"
|
||||
icon="fa-gift"
|
||||
type="action">
|
||||
<field name="gift_card_count" widget="statinfo" string="Gift Cards"/>
|
||||
</button>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="sale_gift_card_view_form" model="ir.ui.view">
|
||||
<field name="name">gift.card.form Website</field>
|
||||
<field name="model">gift.card</field>
|
||||
<field name="inherit_id" ref="gift_card.gift_card_view_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='code']" position="after">
|
||||
<field name="buy_line_id" options="{'no_create': True}"/>
|
||||
</xpath>
|
||||
<xpath expr="//group[@name='gift_card']" position="after">
|
||||
<group>
|
||||
<field name="redeem_line_ids" options="{'no_create': True}" readonly="1">
|
||||
<tree>
|
||||
<field name="order_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" ?>
|
||||
<odoo>
|
||||
|
||||
<template id="gift_card.used_gift_card">
|
||||
<template id="sale_gift_card.used_gift_card">
|
||||
<div class="text-muted d-none d-md-block small" t-if="line.gift_card_id">
|
||||
<span>
|
||||
Code:
|
||||
@@ -17,8 +17,8 @@
|
||||
|
||||
<template id="sale_order_portal_content_inherit" name="Gift Card Products Portal" inherit_id="sale.sale_order_portal_content">
|
||||
<xpath expr="//section[@id='details']//td[@id='product_name']/*[last()]" position="after">
|
||||
<t t-if="line.gift_card_id" t-call="gift_card.used_gift_card"/>
|
||||
<t t-if="line.generated_gift_card_ids" t-call="gift_card.sale_purchased_gift_card">
|
||||
<t t-if="line.gift_card_id" t-call="sale_gift_card.used_gift_card"/>
|
||||
<t t-if="line.generated_gift_card_ids" t-call="sale_gift_card.sale_purchased_gift_card">
|
||||
<t t-set="order" t-value="sale_order"/>
|
||||
<t t-set="hide_intro" t-value="1"/>
|
||||
</t>
|
||||
@@ -7,7 +7,7 @@
|
||||
'description': """Integrate gift card mechanism in your ecommerce.""",
|
||||
'category': 'Website/Website',
|
||||
'version': '1.0',
|
||||
'depends': ['website_sale', 'gift_card'],
|
||||
'depends': ['website_sale', 'sale_gift_card'],
|
||||
'data': [
|
||||
'views/template.xml',
|
||||
'views/gift_card_views.xml',
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</t>
|
||||
<t t-else="">
|
||||
<strong t-field="line.name_short"/>
|
||||
<t t-call="gift_card.used_gift_card"/>
|
||||
<t t-call="sale_gift_card.used_gift_card"/>
|
||||
</t>
|
||||
</div>
|
||||
</xpath>
|
||||
@@ -51,13 +51,13 @@
|
||||
|
||||
<template id="cart_summary_inherit_website_gift_card_sale" inherit_id="website_sale.cart_summary">
|
||||
<xpath expr="//td[hasclass('td-product_name')]/div/strong" position="after">
|
||||
<t t-if="line.gift_card_id" t-call="gift_card.used_gift_card"/>
|
||||
<t t-if="line.gift_card_id" t-call="sale_gift_card.used_gift_card"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="website_sale_purchased_gift_card" inherit_id="website_sale.confirmation" >
|
||||
<xpath expr="//div[@id='oe_structure_website_sale_confirmation_2']" position="after">
|
||||
<t t-call="gift_card.sale_purchased_gift_card"/>
|
||||
<t t-call="sale_gift_card.sale_purchased_gift_card"/>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
|
||||
Reference in New Issue
Block a user