Commit 4af3f5b1 by François C.

Merge branch 'dev_cooperatic' of gl.cooperatic.fr:cooperatic-foodcoops/odoo into dev_cooperatic

parents 67cfd532 fd231727
......@@ -107,7 +107,7 @@ odoo.define('pos_payment_terminal.pos_payment_terminal', function (require) {
this._super.apply(this, arguments);
var line = this.pos.get_order().selected_paymentline;
var auto = line.get_automatic_payment_terminal();
$('.back').hide();
//$('.back').hide();
if (auto) {
this.pos.proxy.payment_terminal_transaction_start(self, self.pos.currency.name);
}
......
......@@ -2,10 +2,10 @@
<odoo noupdate="0">
<record id="config_django_print_proxy" model="ir.config_parameter">
<field name="key">cagette_product.print_proxy_url</field>
<field name="value">https://tools.lacagette-coop.fr/products/label_print</field>
<field name="value">https://django/products/label_print</field>
</record>
<record id="config_django_scale_proxy" model="ir.config_parameter">
<field name="key">cagette_product.scale_proxy_url</field>
<field name="value">https://tools.lacagette-coop.fr/products/labels_appli_csv</field>
<field name="value">https://django/products/labels_appli_csv</field>
</record>
</odoo>
\ No newline at end of file
# -*- coding: utf-8 -*-
from . import models
\ No newline at end of file
# -*- coding: utf-8 -*-
{
'name': "La Cagette Categories",
'summary': """
Retrieve data from internal categories""",
'description': """
""",
'author': "fracolo",
'website': "https://lacagette-coop.fr",
# Categories can be used to filter modules in modules listing
# Check https://github.com/odoo/odoo/blob/master/openerp/addons/base/module/module_data.xml
# for the full list
'category': 'Product',
'version': '0.0.1',
# any module necessary for this one to work correctly
'depends': ['base', 'product'],
# always loaded
'data': [
],
'installable': True,
}
# -*- coding: utf-8 -*-
from . import categories
# -*- coding: utf-8 -*-
from openerp import _, api, models, fields
class LaCagetteCategories(models.Model):
_name = "lacagette.categories"
@api.model
def get_all_with_products_count(self):
res = {}
try:
sql = """
SELECT
categ_id, count(*) as nb
FROM product_template
WHERE
active=true AND sale_ok=true
GROUP BY categ_id;
"""
self.env.cr.execute(sql)
res['list'] = {}
req_res = self.env.cr.dictfetchall()
for u in req_res:
for attr in u.keys():
if u[attr] is None:
u[attr] = ''
res['list'][str(u['categ_id'])] = u['nb']
except Exception as e:
res['error'] = str(e)
return res
......@@ -2,6 +2,6 @@
<odoo noupdate="0">
<record id="config_django_for_inv_proxy" model="ir.config_parameter">
<field name="key">lacagette_cpo.proxy_url</field>
<field name="value">https://tools.lacagette-coop.fr/inventory/inventory/generate_inventory_list</field>
<field name="value">https://django/inventory/inventory/generate_inventory_list</field>
</record>
</odoo>
......@@ -14,7 +14,8 @@
Personnalisation de la popup d'erreur "codebarre non reconnu" :\n
- Faire clignoter la popup d'erreur quand le codebarre n'est pas reconnu
- Jouer le son d'erreur plusieurs fois
- [FONCTION COMMENTEE] Afficher dans la popup le dernier produit scanné
- [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)
""",
'author': "fracolo",
......@@ -35,6 +36,7 @@
# be careful : called sequently (exterenal ID must be put before)
#'security/ir.model.access.csv',
#'templates.xml',
"views/view_pos_config.xml",
'static/src/xml/templates.xml',
],
'installable': True,
......
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * lacagette_custom_pos
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 9.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-04-15 08:00+0000\n"
"PO-Revision-Date: 2022-04-15 08:00+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: lacagette_custom_pos
#: model:ir.model.fields,field_description:lacagette_custom_pos.field_pos_config__reload_on_prices_change
msgid "Reload on prices change"
msgstr "Rechargement si des prix changent"
#. module: lacagette_custom_pos
#: model:ir.model.fields,field_description:lacagette_custom_pos.field_pos_config__can_close_pos_with_ongoing_order
msgid "Allow closing a pos session in case of ongoing order"
msgstr "Autoriser la fermeture d'une session de caisse s'il reste une commande en cours"
#. module: lacagette_custom_pos
#. openerp-web
#: code:lacagette_addons/lacagette_custom_pos/static/src/js/screens.js:73
#, python-format
msgid "POS data must been refreshed"
msgstr "Des données doivent être rafraichies."
#. module: lacagette_custom_pos
#. openerp-web
#: code:lacagette_addons/lacagette_custom_pos/static/src/js/screens.js:73
#, python-format
msgid "Some product prices have been changed, page will be refreshed"
msgstr "Des prix d'articles ont changé, la page va être rechargée."
from . import pos_config
\ No newline at end of file
from openerp import fields, models
class PosConfig(models.Model):
_inherit = 'pos.config'
reload_on_prices_change = fields.Boolean(
string="Reload on prices change",
default=False)
can_close_pos_with_ongoing_order = fields.Boolean(
string="Allow closing a pos session in case of ongoing order",
default=True)
\ No newline at end of file
odoo.define("lacagette_custom_pos.screens", function (require) {
"use strict";
var screens = require("point_of_sale.screens");
var models = require('point_of_sale.models');
var core = require('web.core');
var dataModel = require('web.DataModel');
var lacagette_products = new dataModel('lacagette.products');
var _t = core._t;
const interval = 60 * 60 * 1000; // used for last_price_change call
var reload_on_prices_change = false;
models.load_fields("pos.config", ['reload_on_prices_change', 'can_cclose_pos_with_ongoing_order']);
var close_button_action_setup = false;
const update_last_price_change_data = async function () {
/*
Ask odoo server data about last prices change
According to response, pos_session variables will be set
in order to make decision about reloading or not pos data
*/
lacagette_products.call('get_last_price_change')
.then(
function(received) {
if (typeof received.data !== "undefined" && received.data.length > 0) {
if (typeof posmodel.pos_session.last_price_update != "undefined") {
posmodel.pos_session.previous_price_update = posmodel.pos_session.last_price_update
}
posmodel.pos_session.last_price_update = received.data[0].write_date
posmodel.pos_session.last_price_update_load = Date.now();
if (posmodel.pos_session.last_price_update != posmodel.pos_session.previous_price_update)
posmodel.pos_session.needs_reload = true;
}
}
)
.fail(function(result, ev){
// Store event date. Used to make the "reloading" decision
posmodel.pos_session.last_network_failure = Date.now();
ev.preventDefault();
})
}
screens.ClientListScreenWidget.include({
init: function(parent, options) {
this._super(parent, options);
reload_on_prices_change = this.pos.config.reload_on_prices_change;
if (reload_on_prices_change) {
posmodel.pos_session.load_at = Date.now();
posmodel.pos_session.needs_reload = false;
update_last_price_change_data(); // don't wait for timer for first time
const confirmRefresh = event => {
// Called when unload is unsafe
event.preventDefault();
const message = _t("Are you sure you want to refresh the page?")
(event || window.event).returnValue = confirmationMessage; //Gecko + IE
return message; //Gecko + Webkit, Safari, Chrome etc.
};
const checkUnload = event => {
try {
if (window.navigator.onLine == true) {
return undefined;
} else {
return confirmRefresh(event);
}
} catch(err) {
return confirmRefresh(event);
}
}
window.addEventListener("beforeunload", checkUnload, { capture: true });
// Let's init a perpetual regular call to prices update data
setInterval(() => {
// catch all the errors.
update_last_price_change_data().catch(console.log);
}, interval);
}
},
show: function(){
if (typeof posmodel.pos_session.previous_price_update != "undefined"
&& posmodel.pos_session.needs_reload == true) {
/*
At least one product price has been changed since pos session has been started
We can't reload page without having a look to network state
If page reload is called while network is down, the browser will show a blank page
and there will be no way to show POS screens again.
A smarter way to refresh products data would be to process products data retrieve,
in ajax mode, without having to reload whole page code.
It could be the next improve step for this module.
*/
const now = Date.now();
let can_reload_page = false;
if (typeof posmodel.pos_session.last_network_failure == "undefined") {
/*
no network failure while retreiving price change data.
It doesn't mean that network is now available,
since it could be shutdown after last call
Let reload available
only if wifi icon is green is better
but not safe at all !
beforeunload event capture will ask user for confirmation if network has gone
*/
if ($('.js_connected.oe_icon.oe_green').length > 0)
can_reload_page = true;
} else {
// one network failure occured
if (posmodel.pos_session.last_price_update_load > posmodel.pos_session.last_network_failure) {
can_reload_page = true;
}
}
if (can_reload_page === true) {
this.gui.show_popup("alert", {
'title': _t("POS data must been refreshed"),
'body': _t("Some product prices have been changed, page will be refreshed")
});
setTimeout(function(){
window.location.reload();
}, 5000);
} else {
this._super();
}
} else {
this._super();
}
}
});
screens.ActionpadWidget.include({
renderElement: function() {
this._super();
this.$('.pay').off('click'); // remove base event to redirect to payment screen
this.$('.pay').click(() => {
// Get order lines
let orderlines = this.pos.get_order().orderlines.models
// Prevent payment if the order is empty
if (orderlines.length == 0) {
this.gui.show_popup("alert", {
'title': _t("Paiement non autorisé"),
'body': _t("Le panier doit contenir au moins 1 article.")
});
return;
}
// Prevent payment if any line in order has a 0 qty
let products_empty_qty = []
for (let orderline of orderlines) {
if (orderline.quantity === 0) {
products_empty_qty.push(orderline);
}
}
if (products_empty_qty.length > 0) {
let msg = products_empty_qty.length > 1 ? "Les articles " : "L'article ";
products_empty_qty.forEach((el) => {
msg += el.product.display_name + ", ";
});
msg = msg.substring(0, msg.length - 2); // Remove last ", "
msg += products_empty_qty.length > 1 ? " ont " : " a ";
msg += "une quantité de zéro";
this.gui.show_popup("alert", {
'title': _t("Paiement non autorisé"),
'body': msg
});
return;
}
// Redirect if all checks passed
this.gui.show_screen('payment');
});
}
});
screens.ScreenWidget.include({
init: function(parent,options){
this._super(parent,options);
/*
* If activated in this pos session, prevent closing the session if there is an ongoing order.
* This is a hacky DOM manipulation: the user can technically close the session if he closes the popup and press 'Close' again in less than 2s.
* But this is very unlikely.
*
* ScreenWidget is a base widget extended by many others, we do this here to be sure this code is called...
*/
if (close_button_action_setup === false) {
// ... but we need to make sure it is called only once
close_button_action_setup = true;
if (this.pos.config.can_close_pos_with_ongoing_order === false) {
let header_nodes = document.querySelectorAll(".pos-topheader .pos-rightheader div");
let close_button_div = header_nodes[header_nodes.length- 1];
let self = this;
close_button_div.addEventListener("click", function(e) {
let order = self.pos.get_order();
if (!order.is_empty()) {
if (close_button_div.classList.contains("confirm")) {
close_button_div.classList.remove("confirm");
close_button_div.textContent = _t('Close');
}
self.gui.show_popup("alert", {
'title': _t("Impossible de fermer la caisse"),
'body': _t("Des commandes sont encore en cours.")
});
}
});
}
}
},
});
});
......@@ -5,6 +5,7 @@
<template id="assets" name="lacagette_pos_sales_assets" inherit_id="point_of_sale.assets">
<xpath expr="." position="inside">
<script type="text/javascript" src="/lacagette_custom_pos/static/src/js/backend.js"></script>
<script type="text/javascript" src="/lacagette_custom_pos/static/src/js/screens.js"></script>
</xpath>
<xpath expr="//link[@id='pos-stylesheet']" position="after">
<link rel="stylesheet" href="/lacagette_custom_pos/static/src/css/lacagette_custom_pos.css" />
......
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_custom_pos_config_form" model="ir.ui.view">
<field name="name">lacagette.custom.pos.config.form</field>
<field name="model">pos.config</field>
<field name="inherit_id" ref="point_of_sale.view_pos_config_form"/>
<field name="arch" type="xml">
<field name="cash_control" position="after">
<field name="reload_on_prices_change"/>
<field name="can_close_pos_with_ongoing_order"/>
</field>
</field>
</record>
</data>
</openerp>
\ No newline at end of file
......@@ -19,6 +19,9 @@ class ShiftRegistration(models.Model):
"Who will do the shift, partner, associate or both",
default= False)
should_increment_extra_shift_done = fields.Boolean(
"Set to True when partners are both present. Used to detect if extra_shift_done is to be incremented when recording presences",
default= False)
@api.multi
......
......@@ -8,6 +8,25 @@ _logger = logging.getLogger(__name__)
class ShiftShift(models.Model):
_inherit = 'shift.shift'
@api.multi
def button_makeupok(self):
"""
@Function trigger to change the state from Confirm to Entry
"""
for shift in self:
shift.state = 'entry'
# Automatically mark attendance as "Attended" for
# makeup (ABCD Member)
for reg in shift.registration_ids:
if not reg.partner_id.in_ftop_team and \
not reg.tmpl_reg_line_id and \
reg.origin != 'memberspace' and \
reg.origin != 'bdm' and \
reg.state != 'replacing':
reg.button_reg_close()
@api.model
def run_close_today_ftop_shifts(self):
"""Method called by cron task"""
......
<?xml version="1.0"?>
<odoo>
<odoo noupdate="1">
<record id="nb_past_days_to_compute_sales_average" model="ir.config_parameter">
<field name="key">lacagette_products.nb_past_days_to_compute_sales_average</field>
<field name="value">100</field>
......
......@@ -7,6 +7,22 @@ import numpy
class LaCagetteProducts(models.Model):
_name = "lacagette.products"
@api.model
def get_last_price_change(self):
res = {}
try:
sql = """
SELECT
write_date, product_id
FROM product_price_history
ORDER BY write_date DESC LIMIT 1
"""
self.env.cr.execute(sql)
res['data'] = self.env.cr.dictfetchall()
except Exception as e:
res['error'] = str(e)
return res
def get_uoms(self):
res = {}
try:
......
......@@ -10,10 +10,12 @@ class productShelfs(models.Model):
sort_order = fields.Float(string="Order number", digits=(10,2), required=True)
description = fields.Char(string='Description')
date_last_inventory = fields.Date(string="Last inventory date", default="0001-01-01")
ongoing_inv_start_datetime = fields.Datetime(string="Onging inventory beginning datetime", default="0001-01-01 00:00:00")
inventory_status = fields.Char(string='Inventory status', default="")
last_inventory_id = fields.Integer(string="Last inventory ID", default=0)
last_inv_delta_percentage = fields.Float(string="Last Inventory Quantity Delta Percentage", digits=(10,2))
last_inv_losses_percentage = fields.Float(string="Last Inventory Losses Percentage", digits=(10,2))
date_last_product_added = fields.Date(string="Date of last product added to shelf", default="0001-01-01")
_sql_constraints = [
('unique_name', 'unique(name)', 'Name must be unique !'),
('unique_order', 'unique(sort_order)', 'Num must be unique !')
......@@ -29,7 +31,7 @@ class productShelfs(models.Model):
sql = """
SELECT
s.*,
(SELECT count(*) FROM product_product WHERE shelf_id = s.id) as p_nb
(SELECT count(*) FROM product_product WHERE shelf_id = s.id AND active = true) as p_nb
FROM product_shelfs as s
"""
self.env.cr.execute(sql)
......
......@@ -351,7 +351,7 @@ msgstr "Chèque restaurant"
#: code:addons/pos_meal_voucher/static/src/js/screens.js:197
#, python-format
msgid "To add a meal voucher ticket close this window and scan the ticket. If the ticket can't be read please enter the code"
msgstr "Pour ajouter un chèque restaurant merci de fermer cette fenêtre et scanner le chèque. Si le chèque est illisible veuillez rentrer le code à la main."
msgstr "Pour ajouter un chèque restaurant, merci de scanner le code barre du chèque. Si le chèque est illisible, veuillez rentrer le code à la main."
#. module: pos_meal_voucher
#. openerp-web
......
......@@ -13,7 +13,6 @@ odoo.define("pos_meal_voucher.screens", function (require) {
screens.ScreenWidget.include({
barcode_meal_voucher_payment_action: function (code) {
var order = this.pos.get_order();
if(!order.paper_meal_vouche_number_already_used(code.code)){
// Display the payment screen, if it is not the current one.
......@@ -21,8 +20,29 @@ odoo.define("pos_meal_voucher.screens", function (require) {
this.gui.show_screen("payment");
}
var paymentScreen = this.pos.gui.current_screen;
var order = this.pos.get_order();
var amount = code.value;
// Check if the total amount is OK regarding to max amounts
var total_eligible = order.get_total_meal_voucher_eligible();
var max_amount = this.pos.config.max_meal_voucher_amount;
var total_received = order.get_total_meal_voucher_received() + amount;
// Display info popup if amount is too high
if (total_received > total_eligible) {
this.gui.show_popup("alert", {
'title': _t("Impossible d'utiliser ce Titre Restaurant"),
'body': _t("Vous ne pouvez pas ajouter ce ticket restaurant car le montant total est supérieur au montant éligible en titres restaurant")
});
return;
}
if (total_received > max_amount && max_amount > 0) {
this.gui.show_popup("alert", {
'title': _t("Impossible d'utiliser ce Titre Restaurant"),
'body': _t("Vous ne pouvez pas ajouter ce ticket restaurant car le montant total est supérieur au montant maximum autorisé")
});
return;
}
var cashregister = null;
// find a meal voucher cash register, if exist
for ( var i = 0; i < this.pos.cashregisters.length; i++ ) {
......@@ -46,14 +66,11 @@ odoo.define("pos_meal_voucher.screens", function (require) {
}else{
this.gui.show_popup("alert", {
'title': _t("Meal Voucher Amount already used"),
'body': _t("The paper meal voucher ") +
code.code + _t(" was already used"),
'title': _t("Titre restaurant déjà scanné"),
'body': _t("Le ticket restaurant ") +
code.code + _t(" a déjà été scanné")
});
}
},
// Setup the callback action for the "meal_voucher_payment" barcodes.
......@@ -287,8 +304,8 @@ odoo.define("pos_meal_voucher.screens", function (require) {
// 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()){
this.gui.show_popup("textinput", {
'title': _t("Meal Voucher ticket"),
'body': _t("To add a meal voucher ticket close this window and scan the ticket. If the ticket can't be read please enter the code"),
'title': _t("Chèque Restaurant"),
'body': _t("Pour ajouter un chèque restaurant, merci de scanner le code barre du chèque. Si le chèque est illisible, veuillez rentrer le code à la main."),
confirm: function(value) {
this.pos.get_order().remove_paymentline(order.selected_paymentline);
var paymentScreen = this.pos.gui.current_screen;
......
......@@ -111,13 +111,13 @@
<t t-extend="XmlReceipt"> <!-- Ticket sent to the printer -->
<t t-jquery="div.orderlines line:not([indent]) left" t-operation="prepend"> <!-- hack to get product name line (lines with qty have 'indent' attr) -->
<t t-if='receipt.meal_voucher_used === true and line.product_meal_voucher_ok === true'>
<img t-att-src="receipt.curtlery_img_src" t-att-alt="#"/>
<span>#</span>
</t>
</t>
<t t-jquery="div.orderlines" t-operation="after">
<t t-if="receipt.meal_voucher_used === true">
<line><right>--------</right></line>
<line><left><img t-att-src="receipt.curtlery_img_src" t-att-alt="#" /> Eligible subtotal for</left></line>
<line><left><span>#</span> Eligible subtotal for</left></line>
<line>
<left>meal vouchers:</left>
<right><value t-att-value-decimals='pos.currency.decimals'><t t-esc='receipt.total_meal_voucher_eligible' /></value></right>
......
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