Commit c735219c by François C.

Merge branch 'dev_cooperatic' into 'dev_principale'

Développements Cooperatic (Aide à la commande, mails suite à l'absence d'un service)

See merge request !16
parents ce2669f1 6ad0f139
...@@ -3,7 +3,9 @@ ...@@ -3,7 +3,9 @@
'name': "La Cagette Products", 'name': "La Cagette Products",
'summary': """ 'summary': """
Retrieve data such as barcode for all available products (too long request with standard API)""", Retrieve data such as barcode for all available products (too long request with standard API),
average sales (for products in purchase order for ex.)
""",
'description': """ 'description': """
...@@ -16,12 +18,13 @@ ...@@ -16,12 +18,13 @@
# 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': 'Product', 'category': 'Product',
'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', 'product'], 'depends': ['base', 'product'],
# always loaded # always loaded
'data': [ 'data': [
'data/ir_config_parameter_data.xml'
], ],
'installable': True, 'installable': True,
} }
<?xml version="1.0"?>
<odoo>
<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>
</record>
<record id="nb_of_consecutive_non_sale_days_considered_as_break" model="ir.config_parameter">
<field name="key">lacagette_products.nb_of_consecutive_non_sale_days_considered_as_break</field>
<field name="value">10</field>
</record>
</odoo>
\ No newline at end of file
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from openerp import _, api, models, fields from openerp import _, api, models, fields
import datetime
import numpy
class LaCagetteProducts(models.Model): class LaCagetteProducts(models.Model):
_name = "lacagette.products" _name = "lacagette.products"
...@@ -67,3 +69,184 @@ class LaCagetteProducts(models.Model): ...@@ -67,3 +69,184 @@ class LaCagetteProducts(models.Model):
@api.model @api.model
def get_barcodes(self, data): def get_barcodes(self, data):
return self.get_bc_with_minimun_data() return self.get_bc_with_minimun_data()
@api.model
def get_simple_list(self, data={}):
"""Retrieve products simple list.
@data: list of parameters (only_purchase_ok)
@return: list (with list key or error key)
"""
res = {}
try:
sql = """
SELECT
p.id,
p.product_tmpl_id as tpl_id,
(CASE WHEN translation.value IS NULL
THEN t.name
ELSE translation.value
END
) as display_name
FROM
product_product AS p
JOIN product_template AS t ON (p.product_tmpl_id = t.id)
LEFT JOIN ir_translation AS translation ON (t.id = translation.res_id AND translation.name = 'product.template,name')
WHERE p.id IS NOT NULL AND p.active = true
"""
if 'only_purchase_ok' in data:
sql += " AND purchase_ok = true"
self.env.cr.execute(sql)
req_res = self.env.cr.dictfetchall()
res['list'] = []
for p in req_res:
for attr in p.keys():
if p[attr] is None:
p[attr] = ''
res['list'].append(p)
except Exception as e:
res['error'] = str(e)
return res
def _compte_consecutive_non_sale_days(self, sale_days, days):
"""
@sale_days : list of sale days
@days: list of full period days (except excluded one, such as sundays)
@return : integer days to remove from days number to compute average
"""
conf = self.env['ir.config_parameter']
minimum_significative_consecutive_days =\
int(conf.get_param('lacagette_products.nb_of_consecutive_non_sale_days_considered_as_break'))
consecutive_found = [] # each serie is added to compute total of them
missing_days = []
for d in days:
if not (d in sale_days):
missing_days.append(d)
current_consecutive_number = 1
for i in range(len(missing_days)):
if i > 0:
current_day = datetime.datetime.strptime(missing_days[i], "%Y-%m-%d")
previous_day = datetime.datetime.strptime(missing_days[i-1], "%Y-%m-%d")
if (current_day - previous_day).days == 1:
current_consecutive_number += 1
else:
if current_consecutive_number >= minimum_significative_consecutive_days:
consecutive_found.append(current_consecutive_number)
current_consecutive_number = 1
return int(numpy.sum(consecutive_found))
def _compute_product_template_sales_average(self, ids, days, result):
res = []
products_qtys = {} # used to compute totals and sigma
products_discounts = {} # used to compute totals
products_sums = {} # used to sum daily sales quantity
found_ids = [] # used to set not found product results to 0
products_sale_days = {} # used to compute consecutive non sale days (to fit average)
for p in result:
pid = p['tpl_id']
found_ids.append(pid)
if not (pid in products_sale_days):
products_sale_days[pid] = [p['day']]
else:
products_sale_days[pid].append(p['day'])
if not (pid in products_qtys):
products_qtys[pid] = [p['qtys']]
else:
products_qtys[pid].append(p['qtys'])
if not (pid in products_discounts):
products_discounts[pid] = [p['discounts']]
else:
products_discounts[pid].append(p['discounts'])
if not (pid in products_sums):
products_sums[pid] = {'total_qty': p['qtys'], 'total_discount': p['discounts']}
else:
products_sums[pid]['total_qty'] += p['qtys']
products_sums[pid]['total_discount'] += p['discounts']
for i in ids:
average_qty = average_discount = sigma = 0
vpc = 1 # Void PerCent (percant of non sales days)
if (i in found_ids):
days_nb_to_remove = self._compte_consecutive_non_sale_days(products_sale_days[i], days)
significative_days = len(days) - days_nb_to_remove
if significative_days > 0:
average_qty = round(numpy.sum(products_qtys[i])/significative_days, 2)
average_discount = round(numpy.sum(products_discounts[i])/significative_days, 2)
# to compute sigma, add 0 for non_sales_days
void = []
for j in range(len(days) - len(products_sale_days[i])):
void.append(0)
sigma = round(numpy.std(products_qtys[i] + void), 2)
vpc = round((float(len(days)) - len(products_sale_days[i]))/len(days), 2)
res.append({'id': i, 'average_qty': average_qty, 'average_discount': average_discount, 'sigma': sigma, 'vpc': vpc})
return res
@api.model
def get_template_products_sales_average(self, params={}):
"""Retrieve products sales average.
@params: list of parameters ('ids', 'period', excluded_days)
@return: list (with list key or error key)
"""
conf = self.env['ir.config_parameter']
delta_day = conf.get_param('lacagette_products.nb_past_days_to_compute_sales_average')
res = {}
if 'ids' in params:
ids = list(filter(lambda x: isinstance(x, int), params['ids']))
if len(ids) > 0:
today = datetime.date.today()
excluded_days = [0]
if 'excluded_days' in params:
excluded_days = params['excluded_days']
if 'to' in params:
dto = params['to']
else:
dto = today - datetime.timedelta(1)
dto = dto.strftime("%Y-%m-%d")
if 'from' in params:
dfrom = params['from']
else:
dfrom = today - datetime.timedelta(int(delta_day))
dfrom = dfrom.strftime("%Y-%m-%d")
try:
sql = """
SELECT
TO_CHAR(create_date, 'YYYY-MM-DD') as day,
(SELECT product_tmpl_id FROM product_product WHERE id = pol.product_id) as tpl_id,
SUM(qty) as qtys, SUM(discount) as discounts
FROM pos_order_line pol
WHERE
qty != 0
AND
product_id
IN (
SELECT id FROM product_product
WHERE product_tmpl_id IN %s
)
AND
create_date > %s AND create_date < %s
GROUP BY day, tpl_id
"""
self.env.cr.execute(sql, [tuple(ids), dfrom, dto])
req_res = self.env.cr.dictfetchall()
sql_dates = """
SELECT date_trunc('day', dd):: date as ddate, extract(DOW FROM dd) as dow
FROM generate_series
( '""" + dfrom + """'::timestamp
, '""" + dto + """'::timestamp
, '1 day'::interval) dd
"""
self.env.cr.execute(sql_dates)
days = list(filter(lambda x: not (x['dow'] in excluded_days), self.env.cr.dictfetchall()))
days = map(lambda x: x['ddate'], days)
res['list'] = self._compute_product_template_sales_average(ids, days, req_res)
except Exception as e:
res['error'] = str(e)
else:
res['error'] = "No valid ids found"
else:
res['error'] = "ids list key is missing"
return res
...@@ -2,3 +2,4 @@ ...@@ -2,3 +2,4 @@
from . import models from . import models
...@@ -10,19 +10,21 @@ ...@@ -10,19 +10,21 @@
- Retrieve all shifts, counting for each of them only active registrations - Retrieve all shifts, counting for each of them only active registrations
""", """,
'author': "damien.moulard", 'author': "damien.moulard / fracolo",
'website': "https://lacagette-coop.fr", 'website': "https://lacagette-coop.fr",
# Categories can be used to filter modules in modules listing # 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 # Check https://github.com/odoo/odoo/blob/master/openerp/addons/base/module/module_data.xml
# for the full list # for the full list
'category': 'Uncategorized', 'category': 'Uncategorized',
'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'], 'depends': ['base', 'coop_shift'],
# always loaded # always loaded
'data': [ 'data': [
'data/ir_cron.xml',
'data/email_template_data.xml'
], ],
'installable': True, 'installable': True,
} }
<?xml version="1.0"?>
<odoo>
<record id="missing_shift_email" model="mail.template">
<field name="name">Mail absence créneau</field>
<field name="model_id" ref="lacagette_shifts.model_shift_registration"/>
<field name="email_from">${(object.partner_id.company_id.email or '')|safe}</field>
<field name="email_to" >${object.partner_id.email|safe}</field>
<field name="lang">${object.partner_id.lang}</field>
<field name="reply_to">${object.company_id.email|safe}</field>
<field name="subject">Absence au service du ${format_tz(object.shift_id.date_begin_tz, tz='UTC+2', format='%d %B %Y de %Hh%M')} à ${format_tz(object.shift_id.date_end_tz, tz='UTC+2', format='%Hh%M')}</field>
<field name="body_html"><![CDATA[
Bonjour ${object.name},
<p>
Sauf erreur, tu n'étais pas présent.e à ton service du ${format_tz(object.shift_id.date_begin_tz, tz='UTC+2', format='%d %B %Y de %Hh%M')} à ${format_tz(object.shift_id.date_end_tz, tz='UTC+2', format='%Hh%M')} . Tu dois désormais rattraper le service que tu as manqué avant ton prochain service.
</p>
Nous comptions sur toi ! Dès que tu sais que tu vas être absent.e, pense à déplacer ton service le plus tôt possible et au moins 24h avant la date : connecte-toi à ton espace Membre, sélectionne le service que tu veux échanger et trouve une autre date.
</p>
<p>Rends-toi sur ton <a href="https://lacagette-coop.fr/?CompteurPoints" target="_blank">espace membre</a> et choisis un service de rattrapage sur le calendrier.</p><p>S'il s'agit d'une erreur et que tu étais là, signale-le via ce <a href="https://docs.google.com/forms/d/e/1FAIpQLSeynADUziwov4IXC0THZORJ0cqi3DtBHUbqvHSKinS-db_d8w/viewform" target="_blank">formulaire</a> qui sera traité par le Bureau des membres.
</p>
<p>
S'il s'agit d'une erreur et que tu étais là, signale-le via ce formulaire qui sera traité par le Bureau des membres.
</p>
<p>
Merci et à très vite à ${object.partner_id.company_id.name} !
</p>
<p>
L'équipe de la ${object.partner_id.company_id.name}.
</p>
]]></field>
</record>
</odoo>
<?xml version="1.0"?>
<!--
Copyright (C) 2021 - Today Cooperatic
Author : fracolo
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<odoo>
<record forcecreate="True" id="cron_missing_shift_email" model="ir.cron">
<field name="name">Missing Shift Email</field>
<field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="nextcall" eval="(DateTime.now() + timedelta(days=1)).strftime('%Y-%m-%d 20:00:00')"/>
<field name="doall" eval="False"/>
<field name="model" eval="'shift.registration'"/>
<field name="function" eval="'send_shift_missing_emails'"/>
<field name="args" eval="'()'"/>
<field name="active" eval="False"/>
<field name="priority">2</field>
</record>
</odoo>
\ No newline at end of file
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from . import lacagette_shifts from . import lacagette_shifts
from . import lacagette_shift_registrations
\ No newline at end of file
# -*- coding: utf-8 -*-
from openerp import _, api, models, fields
import datetime
class ShiftRegistration(models.Model):
_inherit = 'shift.registration'
@api.multi
def send_shift_missing_email(self):
mail_template = self.env.ref('lacagette_shifts.missing_shift_email')
if not mail_template:
return False
for reg_target in self:
mail_template.send_mail(reg_target.id)
return True
@api.model
def send_shift_missing_emails(self):
shift_env = self.env['shift.registration']
# search
now = datetime.datetime.now()
yesterday = now - datetime.timedelta(hours=24)
reg_targets = shift_env.search([
('state', 'in', ['excused']),
('date_begin', '>=', yesterday.strftime("%Y-%m-%d %H:%M:%S")),
('date_begin', '<', now.strftime("%Y-%m-%d %H:%M:%S"))
])
reg_targets.send_shift_missing_email()
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