Commit 6cc5a2c1 by Yvon

Merge branch 'dev_cooperatic' into supercafoutch-prod-20220811

parents 4af3f5b1 9153baf5
...@@ -85,3 +85,5 @@ ENV/ ...@@ -85,3 +85,5 @@ ENV/
# Rope project settings # Rope project settings
.ropeproject .ropeproject
.idea
...@@ -61,6 +61,21 @@ odoo.define('pos_payment_terminal.pos_payment_terminal', function (require) { ...@@ -61,6 +61,21 @@ odoo.define('pos_payment_terminal.pos_payment_terminal', function (require) {
var order = this.pos.get_order(); var order = this.pos.get_order();
var line = order.selected_paymentline; var line = order.selected_paymentline;
var data = self.get_data_send(order, line, currency_iso); var data = self.get_data_send(order, line, currency_iso);
//Special case of meal voucher payement line :
if(line.is_meal_voucher && line.is_meal_voucher()) { //Make sure function exists (module meal_voucher might not be active)
//TODO : factor (already 3 occurences of this code in pos_meal_voucher)
var total_eligible = order.get_total_meal_voucher_eligible();
var total_received = order.get_total_meal_voucher_received();
var max_amount = this.pos.config.max_meal_voucher_amount;
var current_max = total_eligible;
if (max_amount) {
current_max = Math.min(total_eligible, max_amount);
}
// Check how much is still possible to pay with meal voucher
// The selected line is "by default" set to the rest to pay of the order
const max_curent_amount = current_max-total_received +order.selected_paymentline.get_amount();
data["amount"] = round_pr(Math.max(0,max_curent_amount), this.pos.currency.rounding);
}
if (this.wait_terminal_answer()) { if (this.wait_terminal_answer()) {
screen.$('.delete-button').css('display', 'none'); screen.$('.delete-button').css('display', 'none');
this.message('payment_terminal_transaction_start_with_return', {'payment_info' : JSON.stringify(data)}, { timeout: 240000 }).then(function (answer) { this.message('payment_terminal_transaction_start_with_return', {'payment_info' : JSON.stringify(data)}, { timeout: 240000 }).then(function (answer) {
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from . import product_product, product_template from . import product_product, product_template, product_supplierinfo
# -*- encoding: utf-8 -*-
from openerp import models, fields, api
class ProductSupplierInfo(models.Model):
_inherit = "product.supplierinfo"
@api.model
def default_get(self, fields):
res = super(ProductSupplierInfo, self).default_get(fields)
res['indicative_package'] = True
return res
...@@ -6,16 +6,17 @@ ...@@ -6,16 +6,17 @@
Adaptation pour corriger des fonctions""", Adaptation pour corriger des fonctions""",
'description': """ 'description': """
Recherche par num. de coop\n > Recherche par num. de coop\n
(la recherche inclut le code barre ou non,\n (la recherche inclut le code barre ou non,
le réglage se fait avec la clé lacagette_custom_pos.reduce_pos_members_search)\n le réglage se fait avec la clé lacagette_custom_pos.reduce_pos_members_search)\n
Après une recherche, le champ perd le focus\n Après une recherche, le champ perd le focus\n
Utilisation du pavé numérique du clavier sur le panier\n > Utilisation du pavé numérique du clavier sur le panier\n
Personnalisation de la popup d'erreur "codebarre non reconnu" :\n > Personnalisation de la popup d'erreur "codebarre non reconnu" :\n
- Faire clignoter la popup d'erreur quand le codebarre n'est pas reconnu - Faire clignoter la popup d'erreur quand le codebarre n'est pas reconnu
- Jouer le son d'erreur plusieurs fois - Jouer le son d'erreur plusieurs fois
- [FONCTION DÉSACTIVÉE] Afficher dans la popup le dernier produit scanné\n - [FONCTION DÉSACTIVÉE] Afficher dans la popup le dernier produit scanné\n
Bloquer ou non la fermeture d'une session de caisse s'il reste une commande en cours (configurable pour chaque caisse) > Bloquer ou non la fermeture d'une session de caisse s'il reste une commande en cours (configurable pour chaque caisse)\n
> Afficher une popup pour saisir le poids d'un produit au poids lorsqu'il est sélectionné
""", """,
'author': "fracolo", 'author': "fracolo",
...@@ -25,7 +26,7 @@ ...@@ -25,7 +26,7 @@
# Check https://github.com/odoo/odoo/blob/master/openerp/addons/base/module/module_data.xml # Check https://github.com/odoo/odoo/blob/master/openerp/addons/base/module/module_data.xml
# for the full list # for the full list
'category': 'POS', 'category': 'POS',
'version': '0.0.1', 'version': '0.0.2',
# any module necessary for this one to work correctly # any module necessary for this one to work correctly
'depends': ['base', 'point_of_sale'], 'depends': ['base', 'point_of_sale'],
......
...@@ -40,9 +40,21 @@ odoo.define('lacagette_custom_pos.DB', function(require) { ...@@ -40,9 +40,21 @@ odoo.define('lacagette_custom_pos.DB', function(require) {
} }
}); });
/* Custom behavior on barcode error */ /* Custom behavior on barcode error and return to products*/
var popups = require('point_of_sale.popups'); var popups = require('point_of_sale.popups');
var gui = require('point_of_sale.gui'); var gui = require('point_of_sale.gui');
let payment_this = null;
let empty_payments_lines = function(self) {
// Every payment line is removed (partially taken from odoo/addons/point_of_sale/static/src/js/screens.js)
// payment_this is initialized during screens.PaymentScreenWidget.start()
var lines = payment_this.pos.get_order().get_paymentlines();
for ( var i = 0; i < lines.length; i++ ) {
payment_this.pos.get_order().remove_paymentline(lines[i]);
payment_this.reset_input();
payment_this.render_paymentlines();
}
}
// Custom popup definition // Custom popup definition
var CustomErrorBarcodePopupWidget = popups.extend({ var CustomErrorBarcodePopupWidget = popups.extend({
...@@ -64,6 +76,18 @@ odoo.define('lacagette_custom_pos.DB', function(require) { ...@@ -64,6 +76,18 @@ odoo.define('lacagette_custom_pos.DB', function(require) {
}); });
gui.define_popup({name:'custom-error-barcode', widget: CustomErrorBarcodePopupWidget}); gui.define_popup({name:'custom-error-barcode', widget: CustomErrorBarcodePopupWidget});
var CustomReturnToOrderMessageWidget = popups.extend({
template:'CustomReturnToOrderMessageWidget',
show: function(options) {
var self = this;
this._super(options);
this.$('.return-to-order').off('click').click(function(){
empty_payments_lines()
self.gui.show_screen("products");
});
}
});
gui.define_popup({name:'custom-return-to-order', widget: CustomReturnToOrderMessageWidget});
var screens = require('point_of_sale.screens'); var screens = require('point_of_sale.screens');
...@@ -113,6 +137,22 @@ odoo.define('lacagette_custom_pos.DB', function(require) { ...@@ -113,6 +137,22 @@ odoo.define('lacagette_custom_pos.DB', function(require) {
} }
}) })
/** Init payment screen **/
screens.PaymentScreenWidget.include({
start: function() {
var self = this;
payment_this = self;
$('.payment-screen .top-content .back').off();
$(document).on('click', '.payment-screen .top-content .back', function(event){
if (payment_this.pos.get_order().get_paymentlines().length > 0) {
self.gui.show_popup('custom-return-to-order');
} else {
self.gui.show_screen("products");
}
});
}
})
/* Make search input loose focus after 5s without a key press */ /* Make search input loose focus after 5s without a key press */
screens.ProductCategoriesWidget.include({ screens.ProductCategoriesWidget.include({
// Redefine init function of the widget handling research // Redefine init function of the widget handling research
...@@ -154,9 +194,51 @@ odoo.define('lacagette_custom_pos.DB', function(require) { ...@@ -154,9 +194,51 @@ odoo.define('lacagette_custom_pos.DB', function(require) {
clearTimeout(blur_timeout) clearTimeout(blur_timeout)
blur_timeout = setTimeout(function() { blur_timeout = setTimeout(function() {
$('.searchbox input').blur() $('.searchbox input').blur()
this.lastSubmittedQuery = null;
}, 5000) }, 5000)
} }
}; };
// If a barcode has been search, let's add it to the order
this.perform_search = function(category, query, buy_result){
// redefine odoo/addons/point_of_sale/static/src/js/screens.js to fit need
var products;
if(query){
// let's prepare query string to find out whether it corresponds to a product barcode
let significant_string = query.replace(/[^0-9]/g, '')
if (significant_string.length > 13) {
// The last scanned barcode is the last 13 figures
significant_string = significant_string.substring(significant_string.length - 13)
}
if (significant_string.length >= 8) {
// A minimum 8 characters long figures string has been extracted : let's add corresponding product if possible
const scan_result = this.pos.barcode_reader.barcode_parser.parse_barcode(significant_string)
significant_string = scan_result.base_code
var found_product;
found_product = this.pos.db.get_product_by_barcode(significant_string)
if (found_product) {
this.pos.scan_product(scan_result) // scan_result contain eventually weight quantity or other encodeded data
this.clear_search();
$('.searchbox input').blur();
}
} else {
products = this.pos.db.search_product_in_category(category.id,query);
if(buy_result && products.length === 1){
this.pos.get_order().add_product(products[0]);
this.clear_search();
}else{
this.product_list_widget.set_product_list(products);
}
}
}else{
products = this.pos.db.get_product_by_category(this.category.id);
this.product_list_widget.set_product_list(products);
}
}
} }
}) })
......
...@@ -220,5 +220,134 @@ odoo.define("lacagette_custom_pos.screens", function (require) { ...@@ -220,5 +220,134 @@ odoo.define("lacagette_custom_pos.screens", function (require) {
} }
} }
}, },
barcode_product_action: function(code){
if (this.gui.get_current_screen() === "products") {
this._super(code);
}
},
});
screens.ProductScreenWidget.include({
click_product: function(product) {
if (product.to_weight && this.pos.config.iface_electronic_scale) {
this.gui.show_screen('scale',{product: product});
} else if (product.to_weight) {
// Unselect order line, to disable number key handler (adds to product qty in list)
this.pos.get_order().deselect_orderline();
this.gui.show_popup("textinput", {
'title': _t("Produit au poids"),
'body': _t("Veuillez saisir le poids du produit."),
confirm: function(value) {
let weight = parseFloat(value.replace(',', '.'));
if (!isNaN(weight)) {
this.pos.get_order().add_product(product,{ quantity: weight });
// Select last order line
this.pos.get_order().select_orderline(this.pos.get_order().get_last_orderline());
} else {
this.gui.show_popup("alert", {
'title': _t("Poids non valide"),
'body': _t("Le poids rentré n'est pas un nombre, opération abandonnée."),
cancel: function(value) {
// Reselect last order line
this.pos.get_order().select_orderline(this.pos.get_order().get_last_orderline());
}
});
}
},
cancel: function(vaue) {
// Reselect last order line
this.pos.get_order().select_orderline(this.pos.get_order().get_last_orderline());
},
});
} else {
this.pos.get_order().add_product(product);
}
}
});
screens.PaymentScreenWidget.include({
order_is_valid: function(force_validation) {
var self = this;
var order = this.pos.get_order();
// FIXME: this check is there because the backend is unable to
// process empty orders. This is not the right place to fix it.
if (order.get_orderlines().length === 0) {
this.gui.show_popup('error',{
'title': _t('Empty Order'),
'body': _t('There must be at least one product in your order before it can be validated'),
});
return false;
}
var plines = order.get_paymentlines();
for (var i = 0; i < plines.length; i++) {
if (plines[i].get_type() === 'bank' && plines[i].get_amount() < 0) {
this.gui.show_popup('error',{
'message': _t('Negative Bank Payment'),
'comment': _t('You cannot have a negative amount in a Bank payment. Use a cash payment method to return money to the customer.'),
});
return false;
}
}
if (!order.is_paid() || this.invoicing) {
return false;
}
// The exact amount must be paid if there is no cash payment method defined.
if (Math.abs(order.get_total_with_tax() - order.get_total_paid()) > 0.00001) {
var cash = false;
for (var i = 0; i < this.pos.cashregisters.length; i++) {
cash = cash || (this.pos.cashregisters[i].journal.type === 'cash');
}
if (!cash) {
this.gui.show_popup('error',{
title: _t('Cannot return change without a cash payment method'),
body: _t('There is no cash payment method available in this point of sale to handle the change.\n\n Please pay the exact amount or add a cash payment method in the point of sale configuration'),
});
return false;
}
}
// if the change is too large, it's probably an input error, make the user confirm.
if (!force_validation && order.get_total_with_tax() > 0 && (order.get_total_with_tax() * 1000 < order.get_total_paid())) {
this.gui.show_popup('confirm',{
title: _t('Please Confirm Large Amount'),
body: _t('Are you sure that the customer wants to pay') +
' ' +
this.format_currency(order.get_total_paid()) +
' ' +
_t('for an order of') +
' ' +
this.format_currency(order.get_total_with_tax()) +
' ' +
_t('? Clicking "Confirm" will validate the payment.'),
confirm: function() {
self.validate_order('confirm');
},
});
return false;
}
var totalPaidExceptCash = 0; //block if this amount is more than total with tax
for (i = 0; i < plines.length; i++) {
if (plines[i].get_type() !== 'cash') {
totalPaidExceptCash += plines[i].get_amount();
}
}
if (!force_validation && order.get_total_with_tax() > 0 && ((order.get_total_with_tax() + 0.00001) < totalPaidExceptCash)) {
this.gui.show_popup('error',{
title: _t('Le rendu de monnaie est possible seulement sur les lignes espèces.'),
body: _t('Pour valider le paiement, la somme des montants offerts des lignes (hors espèces) doit donc être inférieure au total dû.'),
});
return false;
}
return true;
},
}); });
}); });
...@@ -25,4 +25,24 @@ ...@@ -25,4 +25,24 @@
</div> </div>
</div> </div>
</div> </div>
<div t-name="CustomReturnToOrderMessageWidget">
<div class="modal-dialog">
<div class="popup popup-return-warning">
<p class="title">
Attention !
</p>
<p class="body">
Les paiements seront réinitialisés si vous retournez au panier.
</p>
<div class="footer">
<div class="button cancel">
Annuler
</div>
<div class="button return-to-order">
Retour au panier
</div>
</div>
</div>
</div>
</div>
</template> </template>
# -*- coding: utf-8 -*-
{
'name': "La Cagette - Exemptions",
'summary': """
Tuning exemptions rules""",
'description': """
Add a config field for the extension shift template id
""",
'author': "Damien Moulard",
'website': "https://lacagette-coop.fr",
#
'category': 'Uncategorized',
'version': '0.0.1',
# any module necessary for this one to work correctly
'depends': ['base'],
# always loaded
'data': [
'data/ir_config_parameter_data.xml'
],
'installable': True,
}
<?xml version="1.0"?>
<odoo noupdate="1">
<record id="committees_shift_id" model="ir.config_parameter">
<field name="key">lacagette_exemptions.exemptions_shift_id</field>
<field name="value">0</field>
</record>
</odoo>
...@@ -23,6 +23,9 @@ class ShiftRegistration(models.Model): ...@@ -23,6 +23,9 @@ class ShiftRegistration(models.Model):
"Set to True when partners are both present. Used to detect if extra_shift_done is to be incremented when recording presences", "Set to True when partners are both present. Used to detect if extra_shift_done is to be incremented when recording presences",
default= False) default= False)
cancellation_description = fields.Char(
"So that the bdm can explain the reason for the registration cancellation and sign"
)
@api.multi @api.multi
def write(self, vals): def write(self, vals):
......
...@@ -13,6 +13,21 @@ class ShiftTemplate(models.Model): ...@@ -13,6 +13,21 @@ class ShiftTemplate(models.Model):
_inherit = 'shift.template' _inherit = 'shift.template'
def _will_member_be_still_exempted_at_date(self, partner, rec_date):
answer = False
if partner.cooperative_state == "exempted":
#_logger.debug("%s is exempted now, but what about at %s", str(partner.id), str(rec_date))
rec_date = datetime.strptime(rec_date, '%Y-%m-%d')
for l in partner.leave_ids:
#_logger.debug("leave start end : %s %s", str(l.start_date), str(l.stop_date))
if (l.start_date is False or rec_date >= datetime.strptime(l.start_date, '%Y-%m-%d') and
l.stop_date is False or rec_date <= datetime.strptime(l.stop_date, '%Y-%m-%d')):
answer = True
#_logger.debug("Partner will be still exempted")
return answer
@api.model @api.model
def ng_create_shifts_from_template(self, instance=None, after=False, before=False): def ng_create_shifts_from_template(self, instance=None, after=False, before=False):
conf = self.env['ir.config_parameter'] conf = self.env['ir.config_parameter']
...@@ -87,6 +102,8 @@ class ShiftTemplate(models.Model): ...@@ -87,6 +102,8 @@ class ShiftTemplate(models.Model):
for attendee in ticket.registration_ids: for attendee in ticket.registration_ids:
state, strl_id = attendee._get_state(rec_date) state, strl_id = attendee._get_state(rec_date)
if state: if state:
# only register if attendee is not exempted when the event will occur
if self._will_member_be_still_exempted_at_date(attendee.partner_id, rec_date) is False:
vals = { vals = {
'partner_id': attendee.partner_id.id, 'partner_id': attendee.partner_id.id,
'user_ids': [(6, 0, template.user_ids.ids)], 'user_ids': [(6, 0, template.user_ids.ids)],
...@@ -115,9 +132,9 @@ class ShiftTemplate(models.Model): ...@@ -115,9 +132,9 @@ class ShiftTemplate(models.Model):
('week_list', '=', today.strftime('%a').upper()[0:2]), ('week_list', '=', today.strftime('%a').upper()[0:2]),
('last_shift_date', '<=', datetime.strftime(futur, "%Y-%m-%d"))] ('last_shift_date', '<=', datetime.strftime(futur, "%Y-%m-%d"))]
templates = self.env['shift.template'].search(filters) templates = self.env['shift.template'].search(filters)
# templates = self.env['shift.template'].search([('id', '=', 60)]) #templates = self.env['shift.template'].search([('id', '=', 123)])
for template in templates: for template in templates:
# _logger.info("Shift template = %s", str(template)) #_logger.debug("Shift template = %s", str(template))
self.ng_create_shifts_from_template( instance=template, self.ng_create_shifts_from_template( instance=template,
before=fields.Datetime.to_string( before=fields.Datetime.to_string(
datetime.today() + timedelta(days=shift_creation_days))) datetime.today() + timedelta(days=shift_creation_days)))
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
Customization of Purchase process""", Customization of Purchase process""",
'description': """ 'description': """
Customization of Purchase process Customization of Purchase process & orders export method
""", """,
'author': "cooperatic", 'author': "cooperatic",
......
...@@ -40,3 +40,27 @@ class PurchaseOrder(models.Model): ...@@ -40,3 +40,27 @@ class PurchaseOrder(models.Model):
export_url += '/export/' + str(self.id) export_url += '/export/' + str(self.id)
requests.get(export_url) requests.get(export_url)
return res return res
@api.multi
def get_received_orders_between_dates(self, date_from, date_to):
res = {}
sql = """
SELECT po.name as id_po, sp.date_done, po.amount_untaxed, po.amount_total, po.state, rp.name as supplier_name
FROM purchase_order as po
LEFT JOIN stock_picking as sp ON po.name=sp.origin
LEFT JOIN res_partner as rp ON po.partner_id=rp.id
WHERE sp.date_done IS NOT NULL
AND sp.date_done >= '{date_from}'
AND sp.date_done <= '{date_to}'
ORDER BY sp.date_done ASC
"""
sql = sql.format(date_from=date_from, date_to=date_to)
try:
self.env.cr.execute(sql)
res["data"] = self.env.cr.dictfetchall()
except Exception as e:
res["error"] = str(e)
return res
...@@ -33,6 +33,7 @@ that allows customer to buy food products in grocery stores or pay in restaurant ...@@ -33,6 +33,7 @@ that allows customer to buy food products in grocery stores or pay in restaurant
Configuration Configuration
============= =============
Products Products
~~~~~~~~ ~~~~~~~~
...@@ -46,19 +47,28 @@ Products ...@@ -46,19 +47,28 @@ Products
.. figure:: https://raw.githubusercontent.com/legalsylvain/pos/12.0-ADD-pos_meal_voucher/pos_meal_voucher/static/description/product_category_form.png .. figure:: https://raw.githubusercontent.com/legalsylvain/pos/12.0-ADD-pos_meal_voucher/pos_meal_voucher/static/description/product_category_form.png
Journals
~~~~~~~~
* configures your Account journals, mentioning the type of Meal Voucher: * configures your Account journals, mentioning the type of Meal Voucher:
- Paper : the journal will be used when scanning meal voucher barcodes - Paper : the journal will be used when scanning meal voucher barcodes
- Dematerialized: the journal will be used for dematerialized meal vouchers - Dematerialized: the journal will be used for dematerialized meal vouchers
- Mixed: Specific configuration if your accountant want to use a single journal for Credit card AND dematerialized meal vouchers. In that case, the button of this journal will be duplicated, and an extra text can be set to display an alternative label. - Mixed: Specific configuration if your accountant want to use a single journal for Credit card AND dematerialized meal vouchers. In that case, the button of this journal will be duplicated, and an extra text can be set to display an alternative label.
.. figure:: https://raw.githubusercontent.com/legalsylvain/pos/12.0-ADD-pos_meal_voucher/pos_meal_voucher/static/description/account_journal_form.png .. figure:: https://raw.githubusercontent.com/legalsylvain/pos/12.0-ADD-pos_meal_voucher/pos_meal_voucher/static/description/account_journal_form.png
Point of sale
~~~~~~~~
* go your point of sale configuration form, and set the maximum amount allowed by ticket. (optional) * go your point of sale configuration form, and set the maximum amount allowed by ticket. (optional)
.. figure:: https://raw.githubusercontent.com/legalsylvain/pos/12.0-ADD-pos_meal_voucher/pos_meal_voucher/static/description/pos_config_form.png .. figure:: https://raw.githubusercontent.com/legalsylvain/pos/12.0-ADD-pos_meal_voucher/pos_meal_voucher/static/description/pos_config_form.png
* configure if you want to allow or forbid to exceed the maximum allowed by ticket and for order (Give change on meal voucher) * configure if you want to display a cuttlery icon before products name in pos
* configure if you want to allow or forbid to exceed the maximum allowed by ticket and by order (Give change on meal voucher)
* configure if you want the cashier to fill in the meal voucher issuer on CB payments
Usage Usage
...@@ -89,8 +99,8 @@ It is a non blocking warning, because we don't want to prevent an order to be do ...@@ -89,8 +99,8 @@ It is a non blocking warning, because we don't want to prevent an order to be do
if products are not correctly set, or if a recent law changed the maximum amount that can if products are not correctly set, or if a recent law changed the maximum amount that can
be used each day. (A recent case occured in France, during the Covid-19 pandemy) be used each day. (A recent case occured in France, during the Covid-19 pandemy)
If you want to make it impossible to finish an order if the amount of meal voucher is too big then uncheck the option "meal_voucher_change_accepted" If you want to make it impossible to finish an order if the amount of meal voucher is too big, then uncheck the option "meal_voucher_change_accepted".
In this case if the cashier tries to validate the order the warning will redirect him to the paiement page. In this case if the cashier tries to validate the order the warning will redirect him to the payment page.
Note Note
~~~~ ~~~~
......
# Copyright (C) 2020 - Today: GRAP (http://www.grap.coop) # Copyright (C) 2020 - Today: GRAP (http://www.grap.coop)
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) # @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
# TODO: Rendre l'affichage MonA paramétrable
{ {
"name": "Point Of Sale - Meal Voucher", "name": "Point Of Sale - Meal Voucher",
"summary": "Handle meal vouchers in Point of Sale" "summary": "Handle meal vouchers in Point of Sale"
...@@ -15,6 +16,7 @@ ...@@ -15,6 +16,7 @@
], ],
"data": [ "data": [
"data/barcode_rule.xml", "data/barcode_rule.xml",
"data/ir_config_parameter_data.xml",
"views/view_account_journal.xml", "views/view_account_journal.xml",
"views/view_pos_config.xml", "views/view_pos_config.xml",
"views/view_product_category.xml", "views/view_product_category.xml",
......
<?xml version="1.0"?>
<odoo noupdate="0">
<record id="meal_voucher_issuers" model="ir.config_parameter">
<field name="key">pos_meal_voucher.meal_voucher_issuers</field>
<field name="value">["Bimpli", "Up", "Sodexo", "Eden Red", "Swile"]</field>
</record>
</odoo>
...@@ -11,3 +11,6 @@ class AccountBankStatementLine(models.Model): ...@@ -11,3 +11,6 @@ class AccountBankStatementLine(models.Model):
statement_note = fields.Char( statement_note = fields.Char(
string="Number ID of the meal voucher") string="Number ID of the meal voucher")
meal_voucher_issuer = fields.Char(
string="Issuing entity of the meal voucher")
...@@ -20,3 +20,7 @@ class PosConfig(models.Model): ...@@ -20,3 +20,7 @@ class PosConfig(models.Model):
meal_voucher_change_accepted = fields.Boolean( meal_voucher_change_accepted = fields.Boolean(
string="Give change on meal voucher", string="Give change on meal voucher",
default=True) default=True)
meal_voucher_ask_for_issuer = fields.Boolean(
string="Ask the cashier for the meal voucher issuer for CB payments",
default=True)
...@@ -15,6 +15,7 @@ class PosOrder(models.Model): ...@@ -15,6 +15,7 @@ class PosOrder(models.Model):
def _payment_fields(self, cr, uid, ui_paymentline, context=None): def _payment_fields(self, cr, uid, ui_paymentline, context=None):
res = super(PosOrder, self)._payment_fields(cr, uid, ui_paymentline) res = super(PosOrder, self)._payment_fields(cr, uid, ui_paymentline)
res["statement_note"] = ui_paymentline.get("statement_note", False) res["statement_note"] = ui_paymentline.get("statement_note", False)
res["meal_voucher_issuer"] = ui_paymentline.get("meal_voucher_issuer", False)
return res return res
def add_payment(self, cr, uid, order_id, data, context=None): def add_payment(self, cr, uid, order_id, data, context=None):
...@@ -72,12 +73,17 @@ class PosOrder(models.Model): ...@@ -72,12 +73,17 @@ class PosOrder(models.Model):
statement_note = '' statement_note = ''
if 'statement_note' in data: if 'statement_note' in data:
statement_note = data['statement_note'] statement_note = data['statement_note']
meal_voucher_issuer = ''
if 'meal_voucher_issuer' in data:
meal_voucher_issuer = data['meal_voucher_issuer']
args.update({ args.update({
'statement_id': statement_id, 'statement_id': statement_id,
'pos_statement_id': order_id, 'pos_statement_id': order_id,
'journal_id': journal_id, 'journal_id': journal_id,
'ref': order.session_id.name, 'ref': order.session_id.name,
'statement_note': statement_note, 'statement_note': statement_note,
'meal_voucher_issuer': meal_voucher_issuer,
}) })
statement_line_obj.create(cr, uid, args, context=context) statement_line_obj.create(cr, uid, args, context=context)
......
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
background: rgba(255,255,255,0.4); background: rgba(255,255,255,0.4);
} }
.payment-screen div.meal-voucher-summary{ .payment-screen div.meal-voucher-summary, .payment-screen div.monA-summary{
border-top: dashed 1px gainsboro; border-top: dashed 1px gainsboro;
} }
...@@ -45,21 +45,24 @@ ...@@ -45,21 +45,24 @@
color: red; color: red;
} }
.payment-screen div.meal-voucher-summary table{ .payment-screen div.meal-voucher-summary table, .payment-screen div.monA-summary table{
width: 100%; width: 100%;
} }
.payment-screen div.meal-voucher-summary tbody{ .payment-screen div.meal-voucher-summary tbody, .payment-screen div.monA-summary tbody{
background: white; background: white;
} }
.payment-screen div.meal-voucher-summary th { .payment-screen div.meal-voucher-summary th , .payment-screen div.monA-summary th{
font-size: 16px; font-size: 16px;
padding: 8px; padding: 8px;
text-align: center; text-align: center;
} }
.payment-screen div.meal-voucher-summary td { .payment-screen div.meal-voucher-summary td, .payment-screen div.monA-summary td{
font-size: 22px; font-size: 22px;
padding: 8px 12px; padding: 8px 12px;
} }
.popup-textinput select {
margin: 10px 16px !important;
}
\ No newline at end of file
...@@ -174,17 +174,20 @@ odoo.define("pos_meal_voucher.models", function (require) { ...@@ -174,17 +174,20 @@ odoo.define("pos_meal_voucher.models", function (require) {
// See odoo/addons/point_of_sale/models/pos_order.py:59 // See odoo/addons/point_of_sale/models/pos_order.py:59
// and then in the name of the statement line. // and then in the name of the statement line.
// See odoo/addons/point_of_sale/models/pos_order.py:950 // See odoo/addons/point_of_sale/models/pos_order.py:950
this.statement_note = ''; this.statement_note = this.statement_note || '';
this.manual_meal_voucher = false; this.meal_voucher_issuer = this.meal_voucher_issuer || '';
this.manual_meal_voucher = this.manual_meal_voucher || false;
}, },
init_from_JSON: function (json) { init_from_JSON: function (json) {
PaymentlineSuper.init_from_JSON.apply(this, arguments); PaymentlineSuper.init_from_JSON.apply(this, arguments);
this.statement_note = json.statement_note; this.statement_note = json.statement_note;
this.meal_voucher_issuer = json.meal_voucher_issuer;
}, },
export_as_JSON: function () { export_as_JSON: function () {
var res = PaymentlineSuper.export_as_JSON.apply(this, arguments); var res = PaymentlineSuper.export_as_JSON.apply(this, arguments);
res.statement_note = this.statement_note; res.statement_note = this.statement_note;
res.meal_voucher_issuer = this.meal_voucher_issuer;
return res; return res;
}, },
...@@ -203,9 +206,54 @@ odoo.define("pos_meal_voucher.models", function (require) { ...@@ -203,9 +206,54 @@ odoo.define("pos_meal_voucher.models", function (require) {
return ( return (
this.cashregister.journal.meal_voucher_type == "paper") ; this.cashregister.journal.meal_voucher_type == "paper") ;
}, },
set_meal_voucher_issuer: function(value){
this.order.assert_editable();
this.meal_voucher_issuer = value;
this.trigger('change',this);
},
}); });
models.Paymentline = Paymentline; models.Paymentline = Paymentline;
var gui = require('point_of_sale.gui');
var PopupWidget = require('point_of_sale.popups');
var SelectOrTextInputPopupWidget = PopupWidget.extend({
template: 'TextInputPopupWidget',
show: function(options){
options = options || {};
this._super(options);
this.renderElement();
// On input change of value, reset select, and vice versa
this.$("input").on("input", (e) => {
if (e.target.value !== '') {
this.$("select").val('').change();
}
})
this.$("select").on("change", (e) => {
if (e.target.value !== '') {
this.$("input").val('');
}
})
},
click_confirm: function(){
var value =
this.$('select').find(":selected").val() ?
this.$('select').find(":selected").val() :
this.$('input,textarea').val();
// At least one field is mandatory
if (value === '') {
return;
}
this.gui.close_popup();
if( this.options.confirm ){
this.options.confirm.call(this,value);
}
},
});
gui.define_popup({name:'select-or-textinput', widget: SelectOrTextInputPopupWidget});
}); });
...@@ -10,6 +10,8 @@ odoo.define("pos_meal_voucher.screens", function (require) { ...@@ -10,6 +10,8 @@ odoo.define("pos_meal_voucher.screens", function (require) {
var formats = require('web.formats'); var formats = require('web.formats');
var _t = core._t; var _t = core._t;
var QWeb = core.qweb; var QWeb = core.qweb;
var Model = require('web.DataModel');
var config_parameter = new Model('ir.config_parameter');
screens.ScreenWidget.include({ screens.ScreenWidget.include({
barcode_meal_voucher_payment_action: function (code) { barcode_meal_voucher_payment_action: function (code) {
...@@ -115,7 +117,6 @@ odoo.define("pos_meal_voucher.screens", function (require) { ...@@ -115,7 +117,6 @@ odoo.define("pos_meal_voucher.screens", function (require) {
screens.PaymentScreenWidget.include({ screens.PaymentScreenWidget.include({
//check if POS if configured to accept payment in meal voucher //check if POS if configured to accept payment in meal voucher
meal_voucher_activated: function() { meal_voucher_activated: function() {
var is_meal_voucher_method = this.pos.cashregisters.map(function(cash_register) { var is_meal_voucher_method = this.pos.cashregisters.map(function(cash_register) {
...@@ -133,8 +134,6 @@ odoo.define("pos_meal_voucher.screens", function (require) { ...@@ -133,8 +134,6 @@ odoo.define("pos_meal_voucher.screens", function (require) {
var self = this; var self = this;
this._super(parent, options); this._super(parent, options);
// This is a keydown handler that prevents backspace from // This is a keydown handler that prevents backspace from
// doing a back navigation. It also makes sure that keys that // doing a back navigation. It also makes sure that keys that
// do not generate a keypress in Chrom{e,ium} (eg. delete, // do not generate a keypress in Chrom{e,ium} (eg. delete,
...@@ -195,6 +194,20 @@ odoo.define("pos_meal_voucher.screens", function (require) { ...@@ -195,6 +194,20 @@ odoo.define("pos_meal_voucher.screens", function (require) {
}; };
this.meal_voucher_issuers = [];
// get meal voucher issuers from config
config_parameter.call('get_param', ['pos_meal_voucher.meal_voucher_issuers'])
.then( function(value){
try {
self.meal_voucher_issuers = JSON.parse(value);
} catch (error) {
self.gui.show_popup("alert", {
'title': _t("Problème de configuration du POS"),
'body': _t("Le paramètre des émetteurs de CB déj est mal formatté dans la configuration d'Odoo. Veuillez demander à un.e salarié.e de régler ce problème (Configuration > Paramètres systèmes > pos_meal_voucher.meal_voucher_issuers). Vous pouvez continuer à utiliser la caisse.")
});
}
});
}, },
payment_input: function(input) { payment_input: function(input) {
...@@ -262,7 +275,6 @@ odoo.define("pos_meal_voucher.screens", function (require) { ...@@ -262,7 +275,6 @@ odoo.define("pos_meal_voucher.screens", function (require) {
this.reset_input(); this.reset_input();
this.render_paymentlines(); this.render_paymentlines();
}, },
render_paymentmethods: function() { render_paymentmethods: function() {
var self = this; var self = this;
var methods = this._super.apply(this, arguments); var methods = this._super.apply(this, arguments);
...@@ -271,6 +283,12 @@ odoo.define("pos_meal_voucher.screens", function (require) { ...@@ -271,6 +283,12 @@ odoo.define("pos_meal_voucher.screens", function (require) {
}); });
return methods; return methods;
}, },
remove_selected_paymentline: function(order) {
this.pos.get_order().remove_paymentline(order.selected_paymentline);
var paymentScreen = this.pos.gui.current_screen;
paymentScreen.reset_input();
paymentScreen.render_paymentlines();
},
click_paymentmethods: function(id) { click_paymentmethods: function(id) {
var self = this; var self = this;
var methods = this._super.apply(this, arguments); var methods = this._super.apply(this, arguments);
...@@ -279,12 +297,28 @@ odoo.define("pos_meal_voucher.screens", function (require) { ...@@ -279,12 +297,28 @@ odoo.define("pos_meal_voucher.screens", function (require) {
var paymentScreen = this.pos.gui.current_screen; var paymentScreen = this.pos.gui.current_screen;
var order = this.pos.get_order(); var order = this.pos.get_order();
if (
order.selected_paymentline.is_meal_voucher()
&& order.get_total_meal_voucher_eligible() == 0
) {
this.gui.show_popup("alert", {
'title': _t("Impossible de payer en titre restaurant"),
'body': _t("Aucun article du panier n'est éligible en titres restaurants (Montant éligible : 0€)."),
cancel: function() {
self.remove_selected_paymentline(order);
}
});
return false;
}
// if user choose card meal voucher
if(order.selected_paymentline.is_meal_voucher() && order.selected_paymentline.is_dematerialized_meal_voucher()){ if(order.selected_paymentline.is_meal_voucher() && order.selected_paymentline.is_dematerialized_meal_voucher()){
// update selected (last) payment line & order with meal voucher data
function update_order_meal_voucher_data(issuer = '') {
var total_eligible = order.get_total_meal_voucher_eligible(); var total_eligible = order.get_total_meal_voucher_eligible();
var total_received = order.get_total_meal_voucher_received(); var total_received = order.get_total_meal_voucher_received();
var max_amount = this.pos.config.max_meal_voucher_amount; var max_amount = self.pos.config.max_meal_voucher_amount;
var current_max = total_eligible; var current_max = total_eligible;
if (max_amount) { if (max_amount) {
current_max = Math.min(total_eligible, max_amount); current_max = Math.min(total_eligible, max_amount);
...@@ -295,11 +329,45 @@ odoo.define("pos_meal_voucher.screens", function (require) { ...@@ -295,11 +329,45 @@ odoo.define("pos_meal_voucher.screens", function (require) {
const max_curent_amount = current_max-total_received +order.selected_paymentline.get_amount(); const max_curent_amount = current_max-total_received +order.selected_paymentline.get_amount();
order.selected_paymentline.set_amount(max_curent_amount); order.selected_paymentline.set_amount(max_curent_amount);
order.selected_paymentline.set_meal_voucher_issuer(issuer);
paymentScreen.order_changes(); paymentScreen.order_changes();
paymentScreen.render_paymentlines(); paymentScreen.render_paymentlines();
paymentScreen.$(".paymentline.selected .edit").text(paymentScreen.format_currency_no_symbol(max_curent_amount)); paymentScreen.$(".paymentline.selected .edit").text(paymentScreen.format_currency_no_symbol(max_curent_amount));
} }
// If required by the config, ask for the meal voucher issuer
if (this.pos.config.meal_voucher_ask_for_issuer) {
let select_data = [{
'val': "",
'text': "- Choississez un émetteur"
}];
for (let issuer of this.meal_voucher_issuers) {
select_data.push({
"val": issuer,
"text": issuer
});
}
this.gui.show_popup("select-or-textinput", {
'title': _t("Émetteur du titre restaurant"),
'body': _t("Si l'émetteur n'est pas dans la liste ci-dessus, veuillez le noter dans le champs ci-dessous : "),
'selectText': _t("Veuillez sélectionner l'émetteur de CB déj dans la liste ci-dessous : "),
'selectData': select_data,
confirm: function(value) {
let issuer = value;
update_order_meal_voucher_data(issuer)
},
cancel: function(value) {
self.remove_selected_paymentline(order);
},
});
} else {
update_order_meal_voucher_data();
}
}
// If user choose paper meal voucher // If user choose paper meal voucher
// Open a popup asking for the number of the meal voucher // Open a popup asking for the number of the meal voucher
if(order.selected_paymentline.is_meal_voucher() && order.selected_paymentline.is_paper_meal_voucher()){ if(order.selected_paymentline.is_meal_voucher() && order.selected_paymentline.is_paper_meal_voucher()){
...@@ -317,22 +385,11 @@ odoo.define("pos_meal_voucher.screens", function (require) { ...@@ -317,22 +385,11 @@ odoo.define("pos_meal_voucher.screens", function (require) {
}, },
cancel: function(vaue) { cancel: function(vaue) {
this.pos.get_order().remove_paymentline(order.selected_paymentline); self.remove_selected_paymentline(order);
var paymentScreen = this.pos.gui.current_screen;
paymentScreen.reset_input();
paymentScreen.render_paymentlines();
}, },
}); });
} }
}, },
render_paymentlines: function() { render_paymentlines: function() {
var self = this; var self = this;
...@@ -344,12 +401,10 @@ odoo.define("pos_meal_voucher.screens", function (require) { ...@@ -344,12 +401,10 @@ odoo.define("pos_meal_voucher.screens", function (require) {
// Update meal voucher summary // Update meal voucher summary
var total_eligible = order.get_total_meal_voucher_eligible(); var total_eligible = order.get_total_meal_voucher_eligible();
this.el.querySelector("#meal-voucher-eligible-amount").textContent = this.format_currency(total_eligible);
var max_amount = this.pos.config.max_meal_voucher_amount; var max_amount = this.pos.config.max_meal_voucher_amount;
if (max_amount !== 0) {
this.el.querySelector("#meal-voucher-max-amount").textContent = this.format_currency(max_amount); this.el.querySelector("#meal-voucher-eligible-amount").textContent = this.format_currency(Math.min(total_eligible,max_amount));
}
var total_received = order.get_total_meal_voucher_received(); var total_received = order.get_total_meal_voucher_received();
this.el.querySelector("#meal-voucher-received-amount").textContent = this.format_currency(total_received); this.el.querySelector("#meal-voucher-received-amount").textContent = this.format_currency(total_received);
...@@ -369,6 +424,9 @@ odoo.define("pos_meal_voucher.screens", function (require) { ...@@ -369,6 +424,9 @@ odoo.define("pos_meal_voucher.screens", function (require) {
this.$(".meal-voucher-summary").addClass("oe_hidden"); this.$(".meal-voucher-summary").addClass("oe_hidden");
} }
// Update MonA summary
this.el.querySelector("#monA-eligible-amount").textContent = this.format_currency(total_eligible)
}, },
order_is_valid: function(force_validation) { order_is_valid: function(force_validation) {
......
...@@ -47,19 +47,27 @@ ...@@ -47,19 +47,27 @@
<i id="meal-voucher-eligible-warning" class="fa fa-warning"/> <i id="meal-voucher-eligible-warning" class="fa fa-warning"/>
</td> </td>
</tr> </tr>
<t t-if="widget.pos.config.max_meal_voucher_amount !== 0">
<tr> <tr>
<td>Max Amount</td> <td>Total Received</td>
<td> <td>
<span id="meal-voucher-max-amount">0.00 €</span> <span id="meal-voucher-received-amount">0.00 €</span>
<i id="meal-voucher-max-warning" class="fa fa-warning"/>
</td> </td>
</tr> </tr>
</t> </tbody>
</table>
</div>
<div class="monA-summary">
<table class="monA-summary">
<thead>
<tr> <tr>
<td>Total Received</td> <th colspan="2">MonA</th>
</tr>
</thead>
<tbody>
<tr>
<td>Total Eligible</td>
<td> <td>
<span id="meal-voucher-received-amount">0.00 €</span> <span id="monA-eligible-amount">0.00 €</span>
</td> </td>
</tr> </tr>
</tbody> </tbody>
...@@ -77,9 +85,28 @@ ...@@ -77,9 +85,28 @@
</td> </td>
</t> </t>
</t> </t>
<t t-jquery="td.col-name" t-operation="append">
<t t-if="line.meal_voucher_issuer !== ''">
<span class="meal-voucher-issuer">
[<t t-esc="line.meal_voucher_issuer"/>]
</span>
</t>
</t>
</t> </t>
<t t-extend="TextInputPopupWidget"> <t t-extend="TextInputPopupWidget">
<t t-jquery="p.title" t-operation="after"> <t t-jquery="p.title" t-operation="after">
<t t-if="widget.options.selectText">
<p class="body"><t t-esc="widget.options.selectText"/></p>
</t>
<t t-if="widget.options.selectData">
<select class="select body">
<t t-as="element" t-foreach="widget.options.selectData">
<option t-att-value="element.val">
<t t-esc="element.text"/>
</option>
</t>
</select>
</t>
<p class="body"><t t-esc=" widget.options.body || '' "/></p> <p class="body"><t t-esc=" widget.options.body || '' "/></p>
</t> </t>
</t> </t>
......
...@@ -15,6 +15,7 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). ...@@ -15,6 +15,7 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
<field name="max_meal_voucher_amount"/> <field name="max_meal_voucher_amount"/>
<field name="meal_voucher_display_product_screen"/> <field name="meal_voucher_display_product_screen"/>
<field name="meal_voucher_change_accepted"/> <field name="meal_voucher_change_accepted"/>
<field name="meal_voucher_ask_for_issuer"/>
</group> </group>
</field> </field>
</field> </field>
......
...@@ -20,7 +20,7 @@ ofxparse==0.14 ...@@ -20,7 +20,7 @@ ofxparse==0.14
passlib==1.6.2 passlib==1.6.2
psutil==2.2.0 psutil==2.2.0
psycogreen==1.0 psycogreen==1.0
psycopg2==2.7.1 psycopg2==2.7.3.2
pyPdf==1.13 pyPdf==1.13
pydot==1.0.2 pydot==1.0.2
pyparsing==2.0.3 pyparsing==2.0.3
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment