Commit 036306df by François C.

Ajouts modules La Cagette

parent f21e727e
# -*- coding: utf-8 -*-
from . import breaking_model
#from . import models
# -*- coding: utf-8 -*-
{
'name': "lacagette_breaking",
'summary': """
Short (1 phrase/line) summary of the module's purpose, used as
subtitle on modules listing or apps.openerp.com""",
'description': """
Following artiles breaking
""",
'author': "Colin Morand ",
'website': "http://www.yourcompany.com",
# 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': 'Uncategorized',
'version': '0.1',
# any module necessary for this one to work correctly
'depends': ['base'],
# always loaded
'data': [
# 'security/ir.model.access.csv',
#'views/views.xml',
#'views/templates.xml',
],
# only loaded in demonstration mode
'demo': [
#'demo/demo.xml',
],
}
# -*- coding: utf-8 -*-
from openerp import models, fields, api
import numpy as np
class stockBreaking(models.Model):
_name ="stock.breaking"
_description = "Table of breaking following artile"
state_breaking = fields.Integer("State of article", index=True)
qty = fields.Float("Qty of move trigger")
product_id = fields.Many2one('product.product', 'Article', index=True)
notifying = fields.Boolean( 'notifying breaking', default = False, requered = True)
@api.model
def get_artile_breaking(self):
sql = """SELECT id, state_breaking, product_id, create_date
FROM stock_breaking
WHERE id IN (SELECT MAX(id) FROM stock_breaking GROUP BY product_id) AND state_breaking = 0"""
self.env.cr.execute(sql)
res = self.env.cr.dictfetchall()
return (res)
@api.model
def get_artile_breaking_period_by_list(self,lProduct_id):
sql ="""SELECT t.product_id, TO_CHAR(t.create_date, 'YYYY-MM-DD"T"HH24:MI') as start, TO_CHAR(coalesce(t.next_d, now()),'YYYY-MM-DD"T"HH24:MI') as end
FROM (SELECT t1.product_id,
t1.create_date,
t1.state_breaking,
lag(t1.create_date) OVER (PARTITION BY product_id ORDER BY t1.create_date DESC) as next_d
FROM stock_breaking as t1
) as t
WHERE t.state_breaking = 0 and product_id in ({}) ORDER BY t.product_id"""
print(sql.format(str(lProduct_id)[1:-1]))
self.env.cr.execute(sql.format(str(lProduct_id)[1:-1]))
res = self.env.cr.dictfetchall()
print (res)
return (res)
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
,,,,,,,
,,,,,,,
access_model_stock_breaking,access_model_stock_breaking,model_stock_breaking,stock.group_stock_user,1,1,1,1
# -*- coding: utf-8 -*-
{
'name': "La Cagette CPO personnalisé",
'summary': """
Add products list to inventory""",
'description': """
Add create list function
Add lacagette_cpo.proxy_url parameter to process file
(ex http://10.246.163.159:34001/inventory/generate_inventory_list)
""",
'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': 'Purchase',
'version': '0.0.1',
# any module necessary for this one to work correctly
'depends': ['base', 'purchase'],
# 'qweb': ['static/src/xml/qweb.xml'],
# always loaded
'data': [
# be careful : called sequently (exterenal ID must be put before)
#'security/ir.model.access.csv',
#'templates.xml',
'static/src/xml/templates.xml',
'data/ir_config_parameter_data.xml'
],
'installable': True,
}
<?xml version="1.0"?>
<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>
</record>
</odoo>
.pos .clientlist-screen .client-picture {width: 192px !important; height: 192px !important;}
.pos .clientlist-screen .client-picture > img {width: 128px !important; height: 128px !important;}
odoo.define('lacagette_cpo.inv', function(require) {
"use strict";
var FormView = require('web.FormView')
var Model = require('web.DataModel');
var order = null
var open_ckeckboxes_button = $('<button>').attr('type','button')
.addClass('add-checkboxes-btn')
.text('Ajout coches inventaire')
var send_checked_button = $('<button>').attr('type','button')
.addClass('send-checked-products')
.text('Envoyer liste pour inventaire')
var config_parameter = new Model('ir.config_parameter');
var last_generation_call = 0
FormView.include({
load_record: function() {
var self = this;
return this._super.apply(this, arguments)
.then(function() {
order = self.get_fields_values()
});
}
});
function generate_checkboxes() {
var table = $('table.oe_list_content')
if (table.find('.products_to_check_col').length == 0) {
//prepend void header column
table.find('.oe_list_header_columns')
.prepend($('<th>')
.addClass('products_to_check_col')
.css({'width':'5px', 'padding':'2px'})
.html('<input type="checkbox" id="check_all_for_inv"/>'))
//adding checkboxes
table.find('tbody tr').each(function(i,e) {
if (typeof $(e).data('id') != "undefined")
$(e).prepend($('<td>').addClass('for_inv_cb')
.html('<input type="checkbox" name="for_inventory" class="cb_item_for_inv"/>'))
})
$('#check_all_for_inv').change(function() {
if ($('#check_all_for_inv:checked').length > 0) {
$('.cb_item_for_inv').prop('checked', true);
} else {
$('.cb_item_for_inv').prop('checked', false);
}
});
}
}
function send_checked_products_for_inventory() {
var d = new Date()
var elapsed_since_last_call = d.getTime() - last_generation_call
if (elapsed_since_last_call > 5000) {
last_generation_call = d.getTime()
var table = $('table.oe_list_content'),
ids = []
table.find('.cb_item_for_inv:checked').each(function(i,e) {
ids.push($(e).closest('tr').data('id'))
})
if (ids.length > 0) {
config_parameter.call('get_param', ['lacagette_cpo.proxy_url'])
.then(function(url){
if (url && url.length > 0) {
//TODO show message
$.ajax({url:url,
method: 'POST',
dataType: "json",
data: {
lines: JSON.stringify(ids),
type: send_checked_button.data('type')
},
})
.done(function(rData){
if (typeof rData.res != "undefined" && typeof rData.res.error === "undefined"){
alert("Fichier pour inventaire généré")
} else {
alert(JSON.stringify(rData))
}
})
}
})
}
}
}
function for_inv_changed(event) {
//event.stopPropagation(); is useless !!
$('.modal-dialog .close').trigger('click')
}
function locationHashChanged() {
if (/purchase.order/.exec(window.location.href)) {
if (order != null && order.id != false && order.state =='draft') {
var sheet = $('.oe_form_sheet')
if (sheet.find('.add-checkboxes-btn').length == 0){
var buttons_wrapper = $('<div>').css('float','right')
if (typeof order.order_line === "undefined") {
send_checked_button.attr('data-type','cpo')
} else {
send_checked_button.attr('data-type','order')
}
buttons_wrapper.append(open_ckeckboxes_button).append(send_checked_button)
sheet.prepend(buttons_wrapper)
//https://stackoverflow.com/questions/30793066/how-to-avoid-memory-leaks-from-jquery
$(document)
.off('.generate_to_inv_checkboxes .send_checked_for_inv .for_inv')
.on('click.generate_to_inv_checkboxes', '.add-checkboxes-btn', generate_checkboxes)
.on('click.send_checked_for_inv', '.send-checked-products', send_checked_products_for_inventory)
.on('change.for_inv', '[name="for_inventory"]', for_inv_changed)
}
}
order = null // absolutely needed !
}
}
window.onhashchange = locationHashChanged; // not already used by any module -> be careful of side-effects if one use it one day !
});
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<template id="assets" name="lacagette_cpo_assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script type="text/javascript" src="/lacagette_cpo/static/src/js/backend.js"></script>
<link rel="stylesheet" href="/lacagette_cpo/static/src/css/lacagette_cpo.css" />
</xpath>
</template>
</data>
</openerp>
# -*- coding: utf-8 -*-
{
'name': "La Cagette POS personnalisé",
'summary': """
Adaptation pour corriger des fonctions""",
'description': """
Recherche par num. de coop
""",
'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': 'POS',
'version': '0.0.1',
# any module necessary for this one to work correctly
'depends': ['base', 'point_of_sale'],
# 'qweb': ['static/src/xml/qweb.xml'],
# always loaded
'data': [
# be careful : called sequently (exterenal ID must be put before)
#'security/ir.model.access.csv',
#'templates.xml',
'static/src/xml/templates.xml',
],
'installable': True,
}
.pos .clientlist-screen .client-picture {width: 192px !important; height: 192px !important;}
.pos .clientlist-screen .client-picture > img {width: 128px !important; height: 128px !important;}
odoo.define('lacagette_custom_pos.DB', function(require) {
"use strict";
var PosDB = require('point_of_sale.DB');
PosDB.include({
_partner_search_string: function(partner){
var str = partner.name;
//Diacritric search
try {
str += '|' + Diacritics.replace(partner.name)
} catch(e) {
console.log(e)
}
if(partner.barcode){
str += '|' + partner.barcode;
}
if(partner.barcode_base){
str += '|' + partner.barcode_base;
}
str = '' + partner.id + ':' + str.replace(':','') + '\n';
return str;
},
});
});
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<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>
</xpath>
<xpath expr="//link[@id='pos-stylesheet']" position="after">
<link rel="stylesheet" href="/lacagette_custom_pos/static/src/css/lacagette_custom_pos.css" />
</xpath>
</template>
</data>
</openerp>
# -*- coding: utf-8 -*-
from . import models
# -*- coding: utf-8 -*-
{
'name': "La Cagette POS data",
'summary': """
Retrieving POS related data - via API or Odoo Menu""",
'description': """
Personnal purchases, pos payments, etc...
""",
'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': 'Uncategorized',
'version': '0.3.2',
# any module necessary for this one to work correctly
'depends': ['base', 'point_of_sale'],
'qweb': ['static/src/xml/qweb.xml'],
# always loaded
'data': [
# be careful : called sequently (exterenal ID must be put before)
#'security/ir.model.access.csv',
#'templates.xml',
'static/src/xml/templates.xml',
'views/view_product_sales.xml',
'views/product_template_view.xml',
],
'installable': True,
}
# -*- coding: utf-8 -*-
from . import pos_member_purchases
from . import pos_payments_export
from . import pos_product_sales
from . import product_template
# -*- coding: utf-8 -*-
from openerp import _, api, models, fields
from . import product_categories
class PosMemberPurchases(models.Model):
_name = "lacagette.pos_member_purchases"
def _retrieve_purchases(self, ids, filters):
res = []
try:
sql = """SELECT
coop.id as coop_id,coop.barcode_base as coop_num,coop.city as coop_city, coop.birthdate as coop_birthdate,
po.date_order, po.week_number, po.week_day,
pol.price_unit as product_price, pol.qty as product_qty, pol.discount as product_discount,
pt.name as product_name, pt.categ_id as product_categ_id,
p.barcode as product_barcode, p.id as product_id
FROM
pos_order as po
JOIN pos_order_line as pol ON (pol.order_id = po.id)
JOIN product_product as p ON (p.id = pol.product_id)
JOIN product_template as pt ON (pt.id = p.product_tmpl_id)
JOIN res_partner as coop ON (coop.id = po.partner_id)
WHERE coop.barcode_base IN %s AND coop.barcode like '0420%%'
"""
sql_var = [tuple(ids)]
if 'month' in filters:
sql += " AND TO_CHAR(po.date_order, 'YYYY-MM') = %s"
sql_var.append(filters['month'])
sql += " ORDER BY coop.id, po.date_order"
self.env.cr.execute(sql , sql_var)
req_res = self.env.cr.dictfetchall()
if len(req_res) > 0:
for p in req_res:
for attr in p.keys():
if p[attr] is None:
p[attr] = ''
res.append(p)
except Exception as e:
res = "Erreur req SL "+ str(e)
return res
def _retrieve_member_purchased_products(self, member_id):
res = []
try:
sql = """SELECT
pol.product_id, SUM(qty) as total
FROM pos_order as po
JOIN pos_order_line as pol ON (pol.order_id = po.id)
WHERE
po.partner_id = """
sql += str(int(member_id))
sql += """GROUP BY pol.product_id
ORDER BY total DESC
"""
self.env.cr.execute(sql)
res = self.env.cr.dictfetchall()
except Exception as e:
res = "Erreur req SL "+ str(e)
return res
@api.model
def get_member_purchases(self, data):
res = {}
try:
num = int(data['partner_coop_num'])
purchases = self._retrieve_purchases([str(num)],{})
res['purchases'] = purchases
res['pcat'] = self.env['lacagette.product_categories'].get_all()
except Exception as e:
res['error'] = str(e)
return res
@api.model
def get_member_available_products_among_bought_pool(self, data):
res = {}
try:
res['pdts'] = self._retrieve_member_purchased_products(data['member_id'])
except Exception as e:
res['error'] = str(e)
return res
@api.model
def get_members_purchases(self, data):
res = {}
try:
nums = []
for n in data['partners_coop_num']:
try:
num = int(n)
nums.append(num)
except:
pass
filters = {}
if 'month' in data:
month = data['month']
y, m = month.split('-')
try:
y = int(y)
m = int(m)
filters['month'] = month
except:
pass
purchases = self._retrieve_purchases(nums,filters)
res['purchases'] = purchases
res['pcat'] = self.env['lacagette.product_categories'].get_all()
except Exception as e:
res['error'] = str(e)
return res
# -*- coding: utf-8 -*-
from openerp import _, api, models, fields
class PosPaymentsExport(models.Model):
_name = "lacagette.pos_payments_export"
def _retrieve_sessions_orders_min_max_dates(self, session_ids):
res = []
try:
sql = """
SELECT
session_id, MIN(date_order) as min_date, MAX(date_order) as max_date
FROM pos_order
WHERE session_id IN %s
GROUP BY session_id
"""
self.env.cr.execute(sql , [tuple(session_ids)])
req_res = self.env.cr.dictfetchall()
if len(req_res) > 0:
for p in req_res:
for attr in p.keys():
if p[attr] is None:
p[attr] = ''
res.append(p)
except Exception as e:
res = "Erreur requete SQL : "+ str(e)
return res
def _retrieve_sessions_payments(self, session_ids):
res = []
try:
sql = """
SELECT
name,
(SELECT name FROM account_journal WHERE id = abs.journal_id) as journal,
total_entry_encoding_sales as total_amount,
pos_session_id
FROM account_bank_statement as abs
WHERE
abs.pos_session_id IN %s
"""
self.env.cr.execute(sql , [tuple(session_ids)])
req_res = self.env.cr.dictfetchall()
if len(req_res) > 0:
for p in req_res:
for attr in p.keys():
if p[attr] is None:
p[attr] = ''
res.append(p)
except Exception as e:
res = "Erreur requete SQL : "+ str(e)
return res
def _retrieve_sessions(self, month):
res = []
try:
sql = """
SELECT
id, name, total_amount, stop_at,
(SELECT name FROM res_partner WHERE id = (select partner_id from res_users where id = pos_session.user_id)) as caisse
FROM pos_session
WHERE
TO_CHAR(stop_at, 'YYYY-MM') = %s
AND state = 'closed'
ORDER BY stop_at ASC
"""
self.env.cr.execute(sql , [month])
req_res = self.env.cr.dictfetchall()
if len(req_res) > 0:
for p in req_res:
for attr in p.keys():
if p[attr] is None:
p[attr] = ''
res.append(p)
except Exception as e:
res = "Erreur requete SQL : "+ str(e)
return res
@api.model
def get_pos_payments(self, data):
res = {}
try:
if 'month' in data:
month = data['month']
y, m = month.split('-')
try:
y = int(y)
m = int(m)
sessions = self._retrieve_sessions(month)
if sessions:
sessions_ids = []
for s in sessions:
sessions_ids. append(s['id'])
orders_dates = self._retrieve_sessions_orders_min_max_dates(sessions_ids)
payments = self._retrieve_sessions_payments(sessions_ids)
sessions_mm_dates = {}
sessions_payments = {}
for row in orders_dates:
sessions_mm_dates[str(row['session_id'])] = {'min': row['min_date'], 'max': row['max_date']}
for row in payments:
key = str(row['pos_session_id'])
if not (key in sessions_payments):
sessions_payments[key] = []
sessions_payments[key].append(row)
for s in sessions:
if str(s['id']) in sessions_mm_dates:
s['mm_dates'] = sessions_mm_dates[str(s['id'])]
else:
s['mm_dates'] = {}
if str(s['id']) in sessions_payments:
s['payments'] = sessions_payments[str(s['id'])]
res['sessions'] = sessions
# res['orders_dates'] = orders_dates
# res['payments'] = payments
except Exception as e:
res['error'] = str(e)
except Exception as e:
res['error'] = str(e)
return res
# -*- coding: utf-8 -*-
from openerp import _, api, models, fields
from . import product_categories
class PosProductSales(models.Model):
_name = "lacagette.pos_product_sales"
def _retrieve_pols(self, month):
res = []
try:
sql = """
SELECT
id, order_id, price_unit, qty, discount, product_id, TO_CHAR(create_date, 'YYYY-MM-DD') as date,
(SELECT partner_id FROM pos_order WHERE id = order_id) as partner_id
FROM pos_order_line
WHERE
TO_CHAR(create_date, 'YYYY-MM') = %s
ORDER BY create_date ASC
"""
self.env.cr.execute(sql , [month])
req_res = self.env.cr.dictfetchall()
if len(req_res) > 0:
for p in req_res:
for attr in p.keys():
if p[attr] is None:
p[attr] = ''
res.append(p)
except Exception as e:
res = "Erreur requete SQL : "+ str(e)
return res
def _get_products(self, pids):
res = {}
try:
sql = """
SELECT
p_id, categ_id, barcode,
CASE WHEN CHAR_LENGTH(french_name) > 1
THEN french_name
ELSE name_template
END AS p_name
FROM
(SELECT
*,
(SELECT value FROM ir_translation WHERE lang='fr_FR' AND res_id =p_t_id AND name='product.template,name') AS french_name
FROM
(SELECT
p.name_template,
pt.id as p_t_id,
p.id as p_id,
p.barcode, pt.categ_id
FROM product_product p
JOIN product_template pt
ON (p.product_tmpl_id = pt.id)
WHERE p.id IN %s
) as products
) as t
"""
self.env.cr.execute(sql , [tuple(pids)])
req_res = self.env.cr.dictfetchall()
if len(req_res) > 0:
for p in req_res:
for attr in p.keys():
if p[attr] is None:
p[attr] = ''
res[str(p['p_id'])] = p
except Exception as e:
res = "Erreur requete SQL : "+ str(e)
return res
def _sump_up_pols(self, pols):
products = {}
full_total = 0
full_discount = 0
order_ids = []
partner_ids = []
for p in pols:
## Chaque ligne de "ticket" est ajoutée à la liste général des produits liés aux transactions
key = str(p['product_id'])
price = p['price_unit']
if not (key in products):
## Pas encore connu dans la "base" locale -> la ligne est initialisée
products[key] = {'total': 0, 'qty': 0, 'details': {}}
if not (p['date'] in products[key]['details']):
# On initialise une liste des transactions de produits pour cette "date"
products[key]['details'][p['date']] = []
qty = p['qty']
discount = p['discount']
total = float(price) * float(qty)
full_total += total
if (int(discount) > 0):
full_discount += int(discount)*total/100
disc_key = 'discount_'+ str(int(discount))
if not (disc_key in products[key]):
products[key][disc_key] = 1
else:
products[key][disc_key] += 1
total = (100 - int(discount)) * total / 100
## le détail de niveau 0 est ajouté
atomic_details = {'qty': qty, 'price': price, 'subtotal': round(total,2), 'discount':discount}
products[key]['details'][p['date']].append(atomic_details)
products[key]['total'] += total
products[key]['qty'] += qty
## Pour le compte de passages en caisse (differents order_id)
if not (p['order_id'] in order_ids):
if total > 0:
order_ids.append(p['order_id'])
if not (p['partner_id']) in partner_ids:
partner_ids.append(p['partner_id'])
for k in products.keys():
products[k]['total'] = round(products[k]['total'], 2)
products[k]['qty'] = round(products[k]['qty'], 2)
details = products[k]['details']
products[k]['details'] = []
for d in sorted(details):
## Important : nous enregistrons les informations d'une ligne de la liste générée
sub_total_val = 0
sub_total_qty = 0
for nz_line in details[d]:
sub_total_val += nz_line['subtotal']
sub_total_qty += nz_line['qty']
products[k]['details'].append({'date': d, 'val': round(sub_total_val,2), 'qty': round(sub_total_qty,2),'transacs': details[d]})
return {'products': products, 'full_total': round(full_total,2), 'full_discount': round(full_discount,2), 'pol_nb': len(pols), 'orders': len(order_ids), 'partners': len(partner_ids)}
@api.model
def get_pos_data(self, data):
res = {}
try:
if 'month' in data:
month = data['month']
y, m = month.split('-')
try:
y = int(y)
m = int(m)
pols = self._retrieve_pols(month)
if pols:
res['sales'] = self._sump_up_pols(pols)
pids = []
for p in pols:
pids.append(p['product_id'])
res['products'] = self._get_products(pids)
res['pcat'] = self.env['lacagette.product_categories'].get_all()
except Exception as e:
res['error'] = str(e)
except Exception as e:
res['error'] = str(e)
return res
@api.model
def get_product_tempate_sales(self, id, params):
res = {'data': []}
try:
id = int(id) # to prevent injection
if params['interval'] == 'week':
where_clause = "product_id = (SELECT id FROM product_product WHERE product_tmpl_id = " + str(id) + ")"
sql = """
SELECT to_char(date_trunc('week', create_date), 'YYYY-IW') as week , sum(qty) as qty
FROM pos_order_line WHERE %s
GROUP BY week ORDER BY week asc
""" % where_clause
self.env.cr.execute(sql)
sales_data = self.env.cr.dictfetchall()
res['data'] = []
sql_dates = """
SELECT to_char(i::date, 'YYYY-IW') AS week
FROM generate_series((SELECT MIN(create_date) FROM pos_order_line WHERE %s),
now() + interval '1 week',
'1 week'::interval) i
""" % (where_clause)
self.env.cr.execute(sql_dates)
weeks = self.env.cr.dictfetchall()
res['labels'] = []
non_empty_weeks = non_empty_weeks_sales = 0
for w in weeks:
res['labels'].append(w['week'])
qty = 0
for s in sales_data:
if s['week'] == w['week']:
qty = s['qty']
non_empty_weeks += 1
non_empty_weeks_sales += qty
res['data'].append(qty)
res['non_empty_weeks'] = non_empty_weeks
res['non_empty_weeks_sales'] = non_empty_weeks_sales
except Exception as e:
res['error'] = str(e)
return res
# -*- coding: utf-8 -*-
from openerp import _, api, models, fields
class ProductCategories(models.Model):
_name = "lacagette.product_categories"
alim_nonalim = (74, 121)
@api.multi
def get_all(self):
res = {}
try:
sql = """
SELECT
id, name
FROM (
WITH RECURSIVE
starting (id, name, parent_id) AS
(
SELECT t.id, t.name, t.parent_id
FROM product_category AS t
WHERE t.id IN %s
),
-- descendants
descendants(id, name, parent_id, ancestor_id) AS
(
SELECT s.id, s.name, s.parent_id, s.id
FROM starting AS s
UNION ALL
SELECT t.id, CONCAT(d.name, '/',t.name) as name, t.parent_id, d.ancestor_id
FROM product_category AS t JOIN descendants AS d ON t.parent_id = d.id
)
TABLE descendants
) as cat
ORDER BY ancestor_id, parent_id, name;
"""
self.env.cr.execute(sql, [self.alim_nonalim])
req_res = self.env.cr.dictfetchall()
if req_res:
for c in req_res:
res[str(c['id'])] = c['name']
except Exception as e:
res['error'] = str(e)
return res
# -*- encoding: utf-8 -*-
##############################################################################
#
# Product - Average Consumption Module for Odoo
# Copyright (C) 2013-Today GRAP (http://www.grap.coop)
# @author Julien WESTE
# @author Sylvain LE GAL (https://twitter.com/legalsylvain)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp import models, fields, api
class ProductTemplate(models.Model):
_inherit = "product.template"
total_sales_qty = fields.Float(string="Total POS", compute='_compute_total_sold_qty')
@api.multi
def _compute_total_sold_qty(self):
for product_t in self:
tsq = -1
try:
sql = """
SELECT sum(qty) as tsq
FROM pos_order_line
WHERE product_id = (SELECT id FROM product_product WHERE product_tmpl_id = %s)
"""
self.env.cr.execute(sql , [product_t.id])
req_res = self.env.cr.dictfetchall()
if len(req_res) > 0:
tsq = req_res[0]['tsq']
except Exception as e:
print(str(e))
product_t.total_sales_qty = tsq
@api.multi
def view_sales_client_action(self):
return {
'res_model': 'lacagette.pos_product_sales',
'type': 'ir.actions.client',
'tag': 'custom_pos_sales',
'target': 'new',
}
openerp.lacagette_pos_data = function (instance) {
var numMedian = function(a) {
a = a.slice(0).sort(function(x, y) {
return x - y;
});
var b = (a.length + 1) / 2;
return (a.length % 2) ? a[b - 1] : (a[b - 1.5] + a[b - 0.5]) / 2;
}
var averageQty = function(a, back_qty) {
//compute sum of the last "back_qty" elements of a
var ac = a,
sum = average = 0,
i = 0
while (i < back_qty && ac.length > 0) {
i += 1
sum += ac.pop()
}
if (i > 0) average = Math.round((sum / i) * 100) / 100
return average
}
// Declare which method has to be called according ir.actions.client
instance.web.client_actions.add('custom_pos_sales', 'instance.lacagette_pos_data.ShowProductSales');
instance.lacagette_pos_data.ShowProductSales = instance.web.Widget.extend({
template: 'product_pos_sale_details',
className: 'oe_pos_data',
context: null,
init: function(parent, action) {
this._super(parent, action);
this.context = action.context
//console.log(action)
},
start: function () {
jQuery('.modal-title').text("Vente POS de l'article")
new instance.web.Model('lacagette.pos_product_sales').call('get_product_tempate_sales', [this.context.active_id, {interval: 'week'}])
.then(function(result){
if (typeof result.error == "undefined"){
var average_qty_per_week = Math.round((result.non_empty_weeks_sales / result.non_empty_weeks) * 100)/100
var weeks_without_sales_pc = Math.round(100 - (result.non_empty_weeks / result.labels.length) * 100)
var non_empty_qties = result.data.filter(function(val) {
return val !== 0;
});
var median_qty_without_empty = numMedian(non_empty_qties)
var average_last_4_non_empty_weeks = averageQty(non_empty_qties, 4)
var comment = "Semaines sans vente : " + weeks_without_sales_pc + "% <br/>"
comment += "Vente hebdomadaire moyenne (hors semaines vides) : "+average_qty_per_week + "<br/>"
comment += "Quantité hebdomadaire médiane (hors semaines vides) : " + median_qty_without_empty + "<br/>"
comment += "Quantité hebdomadaire moyenne (4 dernières semaines non vides) " + average_last_4_non_empty_weeks
jQuery('#pos_data_sales_comments').html(comment)
var config = {
type: 'bar',
data: {labels: result.labels,
datasets: [{
label: 'Quantités vendues',
data: result.data
}]
},
options: {
title: {
display: true,
text: 'Graph. des ventes par semaine'
},
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
},
legend: { display: false }
}
};
myChart = new Chart(document.getElementById("product_sales_chart"), config);
}
})
return this._super()
},
destroy: function(){
return this._super()
}
});
};
This source diff could not be displayed because it is too large. You can view the blob instead.
<?xml version="1.0"?>
<t name="Product POS sales" t-name="lacagette_pos_datata.product_pos_sale_details">
<div>
<a href="https://cooperatic.fr">Cooperatic</a>
</div>
</t>
<?xml version="1.0" encoding="UTF-8"?>
<template>
<div t-name="product_pos_sale_details" style="height: 100%;">
<div id="pos_data_sales_comments"></div>
<canvas id="product_sales_chart"></canvas>
</div>
</template>
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<template id="assets" name="lacagette_pos_sales_assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" href="/lacagette_pos_data/static/src/css/backend.css" />
<script type="text/javascript" src="/lacagette_pos_data/static/src/js/chart.2.9.3.js"></script>
<script type="text/javascript" src="/lacagette_pos_data/static/src/js/backend.js"></script>
</xpath>
</template>
</data>
</openerp>
<?xml version="1.0"?>
<openerp>
<data>
<template id="product_pos_sale_details">
<div>
<a href="https://cooperatic.fr">Cooperatic</a>
</div>
</template>
</data>
</openerp>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_template_history_form" model="ir.ui.view">
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_form_view"/>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button name="view_sales_client_action" type="object"
icon="fa-line-chart" class="oe_stat_button display_sales_stats"
groups="stock.group_stock_manager"
string="">
<field name="total_sales_qty" widget="statinfo"/>
</button>
</div>
</field>
</record>
</odoo>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_product_sales_action" model="ir.actions.act_url">
<field name="name">Products sales</field>
<field name="url">https://lacagette-coop.fr/example</field>
<field name="target">new</field>
</record>
<record id="view_product_sales_client" model="ir.actions.client">
<field name="name">Products sales</field>
<field name="tag">custom_pos_sales</field>
<field name="target">new</field>
</record>
</odoo>
# -*- coding: utf-8 -*-
from . import models
# -*- coding: utf-8 -*-
{
'name': "La Cagette Product Supplier Shortage",
'summary': """
Indicate that a product is out of stock on the supplier level""",
'description': """
Indicate that a product is out of stock on the supplier level
""",
'author': "damien.moulard",
'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': 'Uncategorized',
'version': '0.3.2',
# any module necessary for this one to work correctly
'depends': ['base'],
# always loaded
'data': [
'security/ir.model.access.csv',
],
'installable': True,
}
# -*- coding: utf-8 -*-
from . import product_supplier_shortage
# -*- coding: utf-8 -*-
from openerp import _, api, models, fields
class ProductSupplierShortage(models.Model):
_name = "product.supplier.shortage"
_description = 'A product is out of stock at the supplier'
product_id = fields.Many2one('product.product', string='Product', required=True)
partner_id = fields.Many2one('res.partner', string='Supplier', required=True)
date_start = fields.Date(string='Shortage start date', required=True)
date_end = fields.Date(string='Shortage end date')
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
,,,,,,,
,,,,,,,
access_model_product_supplier_shortage,access_model_product_supplier_shortage,model_product_supplier_shortage,stock.group_stock_manager,1,1,1,1
# -*- coding: utf-8 -*-
from . import models
# -*- coding: utf-8 -*-
{
'name': "La Cagette Products",
'summary': """
Retrieve data such as barcode for all available products (too long request with standard API)""",
'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 products
# -*- coding: utf-8 -*-
from openerp import _, api, models, fields
class LaCagetteProducts(models.Model):
_name = "lacagette.products"
def get_uoms(self):
res = {}
try:
sql = """
SELECT
id, name, uom_type
FROM product_uom
WHERE active = true
"""
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['id'])] = {'name': u['name'], 'type': u['uom_type']}
except Exception as e:
res['error'] = str(e)
return res
def get_bc_with_minimun_data(self):
res = {}
try:
sql = """
SELECT
p.id,
p.barcode,
(CASE WHEN translation.value IS NULL
THEN t.name
ELSE translation.value
END
) as display_name,
t.base_price AS standard_price,
t.list_price,
t.uom_id,
t.sale_ok,
t.purchase_ok,
t.available_in_pos
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
"""
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)
if len(res['list']) > 0:
res['uoms'] = self.get_uoms()
except Exception as e:
res['error'] = str(e)
return res
@api.model
def get_barcodes(self, data):
return self.get_bc_with_minimun_data()
# -*- coding: utf-8 -*-
# Copyright (C) 2020-Today: La Cagette (<http://lacagette-coop.fr/>)
# @author: La Cagette
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import models
\ No newline at end of file
# -*- coding: utf-8 -*-
{
'name': "lacagette_purchase",
'summary': """
Customization of Purchase process""",
'description': """
Customization of Purchase process
""",
'author': "cooperatic",
'website': "https://cooperatic.fr",
'category': 'Uncategorized',
'version': '0.1.1',
# any module necessary for this one to work correctly
'depends': ['base','purchase'],
'data': [
'data/ir_config_parameter_data.xml'
],
}
<?xml version="1.0"?>
<odoo noupdate="1">
<record id="config_django_orders_proxy" model="ir.config_parameter">
<field name="key">cagette_purchase.orders_proxy_url</field>
<field name="value"> </field>
</record>
</odoo>
\ No newline at end of file
# -*- coding: utf-8 -*-
from . import purchase_order
\ No newline at end of file
# -*- coding: utf-8 -*-
from openerp import api, models, fields
class PurchaseOrder(models.Model):
_inherit = 'purchase.order'
@api.multi
def write(self, vals):
res = super(PurchaseOrder, self).write(vals)
if 'state' in vals and vals['state'] == 'purchase':
import requests
conf = self.env['ir.config_parameter']
export_url = str(conf.get_param('cagette_purchase.orders_proxy_url')).strip()
export_url += '/export/' + str(self.id)
requests.get(export_url)
return res
# -*- coding: utf-8 -*-
from . import models
# -*- coding: utf-8 -*-
{
'name': "La Cagette Sales Helper",
'summary': """
Retrieve and display products information to help with future sales - via API or Odoo Menu""",
'description': """
Display on the product page buttons with Total Losses, Autoconsommation, Employees meals...
Odoo system parameters need to be set: losses, autoconso and meals locations id
""",
'author': "damien.moulard",
'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': 'Uncategorized',
'version': '0.3.2',
# any module necessary for this one to work correctly
'depends': ['base', 'point_of_sale'],
'qweb': ['static/src/xml/qweb.xml'],
# always loaded
'data': [
# be careful : called sequently (exterenal ID must be put before)
#'security/ir.model.access.csv',
#'templates.xml',
'static/src/xml/templates.xml',
'views/view_product_data_details.xml',
'views/product_template_view.xml',
'data/ir_config_parameter_data.xml'
],
'installable': True,
}
<?xml version="1.0"?>
<odoo noupdate="0">
<record id="config_losses_location_id" model="ir.config_parameter">
<field name="key">lacagette_sales_helper.losses_location_id</field>
<field name="value">33</field>
</record>
<record id="config_autoconso_location_id" model="ir.config_parameter">
<field name="key">lacagette_sales_helper.autoconso_location_id</field>
<field name="value">27</field>
</record>
<record id="config_meals_location_id" model="ir.config_parameter">
<field name="key">lacagette_sales_helper.meals_location_id</field>
<field name="value">27</field>
</record>
</odoo>
# -*- coding: utf-8 -*-
from . import product_template
from . import sales_helper
# -*- encoding: utf-8 -*-
##############################################################################
#
# Product - Average Consumption Module for Odoo
# Copyright (C) 2013-Today GRAP (http://www.grap.coop)
# @author Julien WESTE
# @author Sylvain LE GAL (https://twitter.com/legalsylvain)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp import models, fields, api
class ProductTemplate(models.Model):
_inherit = "product.template"
total_losses_qty = fields.Float(string="Total Pertes", compute='_compute_total_losses_qty')
total_autoconso_qty = fields.Float(string="Total Autoconsommation", compute='_compute_total_autoconso_qty')
total_meals_qty = fields.Float(string="Total Repas Salariés", compute='_compute_total_meals_qty')
sql_stock_move = """
SELECT sum(product_uom_qty) as qty
FROM stock_move
WHERE product_id = (SELECT id FROM product_product WHERE product_tmpl_id = %s)
AND state = 'done'
AND location_dest_id = %s
"""
@api.multi
def _compute_total_losses_qty(self):
for product_t in self:
qty = -1
try:
conf = self.env['ir.config_parameter']
losses_loc_id = conf.get_param("lacagette_sales_helper.losses_location_id")
self.env.cr.execute(self.sql_stock_move , [product_t.id, losses_loc_id])
req_res = self.env.cr.dictfetchall()
if len(req_res) > 0:
qty = req_res[0]['qty']
except Exception as e:
print(str(e))
product_t.total_losses_qty = qty
@api.multi
def _compute_total_autoconso_qty(self):
for product_t in self:
qty = -1
try:
conf = self.env['ir.config_parameter']
autoconso_loc_id = conf.get_param("lacagette_sales_helper.autoconso_location_id")
self.env.cr.execute(self.sql_stock_move , [product_t.id, autoconso_loc_id])
req_res = self.env.cr.dictfetchall()
if len(req_res) > 0:
qty = req_res[0]['qty']
except Exception as e:
print(str(e))
product_t.total_autoconso_qty = qty
@api.multi
def _compute_total_meals_qty(self):
for product_t in self:
qty = -1
try:
conf = self.env['ir.config_parameter']
meals_loc_id = conf.get_param("lacagette_sales_helper.meals_location_id")
self.env.cr.execute(self.sql_stock_move , [product_t.id, meals_loc_id])
req_res = self.env.cr.dictfetchall()
if len(req_res) > 0:
qty = req_res[0]['qty']
except Exception as e:
print(str(e))
product_t.total_meals_qty = qty
@api.multi
def view_losses_client_action(self):
return {
'res_model': 'lacagette.sales_helper',
'type': 'ir.actions.client',
'tag': 'custom_product_losses',
'target': 'new',
}
@api.multi
def view_autoconso_client_action(self):
return {
'res_model': 'lacagette.sales_helper',
'type': 'ir.actions.client',
'tag': 'custom_product_autoconso',
'target': 'new',
}
@api.multi
def view_meals_client_action(self):
return {
'res_model': 'lacagette.sales_helper',
'type': 'ir.actions.client',
'tag': 'custom_product_meals',
'target': 'new',
}
# -*- coding: utf-8 -*-
from openerp import _, api, models, fields
class SalesHelper(models.Model):
_name = "lacagette.sales_helper"
@api.model
def get_product_template_data(self, id, params):
""""""
res = {'data': []}
try:
id = int(id) # to prevent injection
if params['interval'] == 'week':
conf = self.env['ir.config_parameter']
# Get stock movement location id
if params['type'] == 'losses':
loc_id = conf.get_param("lacagette_sales_helper.losses_location_id")
elif params['type'] == 'autoconso':
loc_id = conf.get_param("lacagette_sales_helper.autoconso_location_id")
elif params['type'] == 'meals':
loc_id = conf.get_param("lacagette_sales_helper.meals_location_id")
where_clause = """
product_id = (SELECT id FROM product_product WHERE product_tmpl_id = """ + str(id) + """)
AND state = 'done'
AND location_dest_id = %s
""" % loc_id
sql = """
SELECT to_char(date_trunc('week', create_date), 'YYYY-IW') as week , sum(product_uom_qty) as qty
FROM stock_move WHERE %s
GROUP BY week ORDER BY week asc
""" % where_clause
self.env.cr.execute(sql)
p_stock_movement_data = self.env.cr.dictfetchall()
sql_dates = """
SELECT to_char(date_trunc('week', i::date), 'YYYY-IW') AS week
FROM generate_series((SELECT MIN(create_date) FROM stock_move WHERE %s),
now() + interval '1 week',
'1 week'::interval) i
""" % (where_clause)
self.env.cr.execute(sql_dates)
weeks = self.env.cr.dictfetchall()
res['data'] = []
res['labels'] = []
nb_non_empty_weeks = non_empty_weeks_qty = 0
for w in weeks:
res['labels'].append(w['week'])
qty = 0
for s in p_stock_movement_data:
if s['week'] == w['week']:
qty = s['qty']
nb_non_empty_weeks += 1
non_empty_weeks_qty += qty
res['data'].append(qty)
res['nb_non_empty_weeks'] = nb_non_empty_weeks
res['non_empty_weeks_qty'] = non_empty_weeks_qty
except Exception as e:
res['error'] = str(e)
return res
openerp.lacagette_sales_helper = function (instance) {
var numMedian = function(a) {
a = a.slice(0).sort(function(x, y) {
return x - y;
});
var b = (a.length + 1) / 2;
return (a.length % 2) ? a[b - 1] : (a[b - 1.5] + a[b - 0.5]) / 2;
}
var averageQty = function(a, back_qty) {
//compute sum of the last "back_qty" elements of a
var ac = a,
sum = average = 0,
i = 0
while (i < back_qty && ac.length > 0) {
i += 1
sum += ac.pop()
}
if (i > 0) average = Math.round((sum / i) * 100) / 100
return average
}
// Declare which method has to be called according ir.actions.client
instance.web.client_actions.add('custom_product_losses', 'instance.lacagette_sales_helper.ShowProductLosses');
instance.web.client_actions.add('custom_product_autoconso', 'instance.lacagette_sales_helper.ShowProductAutoconso');
instance.web.client_actions.add('custom_product_meals', 'instance.lacagette_sales_helper.ShowProductMeals');
instance.lacagette_sales_helper.ShowProductLosses = instance.web.Widget.extend({
template: 'product_losses_details',
className: 'oe_stock_movements_data',
context: null,
init: function(parent, action) {
this._super(parent, action);
this.context = action.context
//console.log(action)
},
start: function () {
jQuery('.modal-title').text("Pertes de l'article")
new instance.web.Model('lacagette.sales_helper')
.call('get_product_template_data', [this.context.active_id, {interval: 'week', type: 'losses'}])
.then(function(result){
if (typeof result.error == "undefined"){
var average_qty_moved_per_week = Math.round((result.non_empty_weeks_qty / result.nb_non_empty_weeks) * 100)/100
var weeks_without_movement = Math.round(100 - (result.nb_non_empty_weeks / result.labels.length) * 100)
var non_empty_qties = result.data.filter(function(val) {
return val !== 0;
});
var median_qty_without_empty = numMedian(non_empty_qties)
var average_last_4_non_empty_weeks = averageQty(non_empty_qties, 4)
var comment = "Semaines sans pertes : " + weeks_without_movement + "% <br/>"
comment += "Perte hebdomadaire moyenne (hors semaines vides) : " + average_qty_moved_per_week + "<br/>"
comment += "Quantité hebdomadaire médiane (hors semaines vides) : " + median_qty_without_empty + "<br/>"
comment += "Quantité hebdomadaire moyenne (4 dernières semaines non vides) " + average_last_4_non_empty_weeks
jQuery('#product_losses_comments').html(comment)
var config = {
type: 'bar',
data: {
labels: result.labels,
datasets: [{
label: 'Quantités en pertes',
data: result.data
}]
},
options: {
title: {
display: true,
text: 'Graph. des pertes par semaine'
},
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
},
legend: { display: false }
}
};
myChart = new Chart(document.getElementById("product_losses_chart"), config);
}
})
return this._super()
},
destroy: function(){
return this._super()
}
});
instance.lacagette_sales_helper.ShowProductAutoconso = instance.web.Widget.extend({
template: 'product_autoconso_details',
className: 'oe_stock_movements_data',
context: null,
init: function(parent, action) {
this._super(parent, action);
this.context = action.context
//console.log(action)
},
start: function () {
jQuery('.modal-title').text("Autoconsommation de l'article")
new instance.web.Model('lacagette.sales_helper')
.call('get_product_template_data', [this.context.active_id, {interval: 'week', type: 'autoconso'}])
.then(function(result){
if (typeof result.error == "undefined"){
var average_qty_moved_per_week = Math.round((result.non_empty_weeks_qty / result.nb_non_empty_weeks) * 100)/100
var non_empty_qties = result.data.filter(function(val) {
return val !== 0;
});
var median_qty_without_empty = numMedian(non_empty_qties)
var average_last_4_non_empty_weeks = averageQty(non_empty_qties, 4)
var comment = "Autoconsommation hebdomadaire moyenne (hors semaines vides) : " + average_qty_moved_per_week + "<br/>"
comment += "Quantité hebdomadaire médiane (hors semaines vides) : " + median_qty_without_empty + "<br/>"
comment += "Quantité hebdomadaire moyenne (4 dernières semaines non vides) " + average_last_4_non_empty_weeks
jQuery('#product_autoconso_comments').html(comment)
var config = {
type: 'bar',
data: {
labels: result.labels,
datasets: [{
label: 'Quantités en autoconso',
data: result.data
}]
},
options: {
title: {
display: true,
text: 'Graph. des autoconso par semaine'
},
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
},
legend: { display: false }
}
};
myChart = new Chart(document.getElementById("product_autoconso_chart"), config);
}
})
return this._super()
},
destroy: function(){
return this._super()
}
});
instance.lacagette_sales_helper.ShowProductMeals = instance.web.Widget.extend({
template: 'product_meals_details',
className: 'oe_stock_movements_data',
context: null,
init: function(parent, action) {
this._super(parent, action);
this.context = action.context
//console.log(action)
},
start: function () {
jQuery('.modal-title').text("Repas salariés de l'article")
new instance.web.Model('lacagette.sales_helper')
.call('get_product_template_data', [this.context.active_id, {interval: 'week', type: 'meals'}])
.then(function(result){
if (typeof result.error == "undefined"){
var average_qty_moved_per_week = Math.round((result.non_empty_weeks_qty / result.nb_non_empty_weeks) * 100)/100
var non_empty_qties = result.data.filter(function(val) {
return val !== 0;
});
var median_qty_without_empty = numMedian(non_empty_qties)
var average_last_4_non_empty_weeks = averageQty(non_empty_qties, 4)
var comment = "Qté en Repas salarié hebdomadaire moyenne (hors semaines vides) : " + average_qty_moved_per_week + "<br/>"
comment += "Quantité hebdomadaire médiane (hors semaines vides) : " + median_qty_without_empty + "<br/>"
comment += "Quantité hebdomadaire moyenne (4 dernières semaines non vides) " + average_last_4_non_empty_weeks
jQuery('#product_meals_comments').html(comment)
var config = {
type: 'bar',
data: {
labels: result.labels,
datasets: [{
label: 'Quantités en repas salarié',
data: result.data
}]
},
options: {
title: {
display: true,
text: 'Graph. des repas salariés par semaine'
},
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
},
legend: { display: false }
}
};
myChart = new Chart(document.getElementById("product_meals_chart"), config);
}
})
return this._super()
},
destroy: function(){
return this._super()
}
});
};
<?xml version="1.0" encoding="UTF-8"?>
<template>
<div t-name="product_losses_details" style="height: 100%;">
<div id="product_losses_comments"></div>
<canvas id="product_losses_chart"></canvas>
</div>
<div t-name="product_autoconso_details" style="height: 100%;">
<div id="product_autoconso_comments"></div>
<canvas id="product_autoconso_chart"></canvas>
</div>
<div t-name="product_meals_details" style="height: 100%;">
<div id="product_meals_comments"></div>
<canvas id="product_meals_chart"></canvas>
</div>
</template>
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<template id="lacagette_sales_helper_assets" name="lacagette_sales_helper_assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" href="/lacagette_sales_helper/static/src/css/backend.css" />
<script type="text/javascript" src="/lacagette_pos_data/static/src/js/chart.2.9.3.js"></script>
<script type="text/javascript" src="/lacagette_sales_helper/static/src/js/backend.js"></script>
</xpath>
</template>
</data>
</openerp>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_template_losses_history_form" model="ir.ui.view">
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_form_view"/>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button name="view_losses_client_action" type="object"
icon="fa-line-chart" class="oe_stat_button display_losses_stats"
groups="stock.group_stock_manager"
string="">
<field name="total_losses_qty" widget="statinfo"/>
</button>
</div>
</field>
</record>
<record id="view_template_autoconso_history_form" model="ir.ui.view">
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_form_view"/>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button name="view_autoconso_client_action" type="object"
icon="fa-line-chart" class="oe_stat_button display_autoconso_stats"
groups="stock.group_stock_manager"
string="">
<field name="total_autoconso_qty" widget="statinfo"/>
</button>
</div>
</field>
</record>
<record id="view_template_meals_history_form" model="ir.ui.view">
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_form_view"/>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button name="view_meals_client_action" type="object"
icon="fa-line-chart" class="oe_stat_button display_meals_stats"
groups="stock.group_stock_manager"
string="">
<field name="total_meals_qty" widget="statinfo"/>
</button>
</div>
</field>
</record>
</odoo>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_product_losses_action" model="ir.actions.act_url">
<field name="name">Products losses</field>
<field name="url">https://lacagette-coop.fr/example</field>
<field name="target">new</field>
</record>
<record id="view_product_losses_client" model="ir.actions.client">
<field name="name">Products losses</field>
<field name="tag">custom_product_losses</field>
<field name="target">new</field>
</record>
<record id="view_product_autoconso_client" model="ir.actions.client">
<field name="name">Products autoconsommation</field>
<field name="tag">custom_product_autoconso</field>
<field name="target">new</field>
</record>
<record id="view_product_meals_client" model="ir.actions.client">
<field name="name">Products meals</field>
<field name="tag">custom_product_meals</field>
<field name="target">new</field>
</record>
</odoo>
# -*- coding: utf-8 -*-
# Copyright (C) 2020-Today: La Cagette (<http://lacagette-coop.fr/>)
# @author: La Cagette
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import models
# -*- coding: utf-8 -*-
{
'name': "lacagette_shelfs",
'summary': """
Implements Shelfs in Odoo DB and API""",
'description': """
API access to manage shelfs
""",
'author': "cooperatic",
'website': "https://cooperatic.fr",
'category': 'Uncategorized',
'version': '9.0.1.2.4',
# any module necessary for this one to work correctly
'depends': ['base','product'],
'data': [
'security/ir.model.access.csv',
]
}
# -*- coding: utf-8 -*-
from . import product_product
from . import product_shelfs
# -*- coding: utf-8 -*-
from openerp import api, models, fields
class ProductProduct(models.Model):
_inherit = 'product.product'
shelf_id = fields.Many2one('product.shelfs',required=False)
last_inv_delta = fields.Float(string="Last Inventory Quantity Delta", digits=(10,2))
last_inv_losses = fields.Float(string="Last Inventory Losses", digits=(10,2))
# -*- coding: utf-8 -*-
from openerp import models, fields, api
class productShelfs(models.Model):
_name ="product.shelfs"
_description = "Table of shelfs"
name = fields.Char(string='Name', required=True)
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")
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))
_sql_constraints = [
('unique_name', 'unique(name)', 'Name must be unique !'),
('unique_order', 'unique(sort_order)', 'Num must be unique !')
]
@api.model
def get(self, params):
res = {}
try:
if 'ids' in params:
pass
else:
sql = """
SELECT
s.*,
(SELECT count(*) FROM product_product WHERE shelf_id = s.id) as p_nb
FROM product_shelfs as s
"""
self.env.cr.execute(sql)
res = self.env.cr.dictfetchall()
except Exception as e:
res['error'] = str(e)
return res
@api.model
def add_products(self, params):
res = {}
try:
if 'pids' in params and 'shelf_id' in params:
sql = """
UPDATE product_product SET shelf_id = %s
WHERE id in %s
"""
self.env.cr.execute(sql, [params['shelf_id'], tuple(params['pids'])])
else:
res['error'] = 'Wrong parameters'
except Exception as e:
res['error'] = str(e)
return res
@api.multi
def unlink(self):
"""
When tryin to unlink
Traceback (most recent call last):
File "/home/lacagette/Odoo/odoo/openerp/http.py", line 115, in dispatch_rpc
result = dispatch(method, params)
File "/home/lacagette/Odoo/odoo/openerp/service/model.py", line 37, in dispatch
res = fn(db, uid, *params)
File "/home/lacagette/Odoo/odoo/openerp/service/model.py", line 173, in execute_kw
return execute(db, uid, obj, method, *args, **kw or {})
File "/home/lacagette/Odoo/odoo/openerp/service/model.py", line 118, in wrapper
return f(dbname, *args, **kwargs)
File "/home/lacagette/Odoo/odoo/openerp/service/model.py", line 181, in execute
res = execute_cr(cr, uid, obj, method, *args, **kw)
File "/home/lacagette/Odoo/odoo/openerp/service/model.py", line 170, in execute_cr
return getattr(object, method)(cr, uid, *args, **kw)
File "/home/lacagette/Odoo/odoo/openerp/api.py", line 250, in wrapper
return old_api(self, *args, **kwargs)
File "/home/lacagette/Odoo/odoo/openerp/api.py", line 381, in old_api
result = method(recs, *args, **kwargs)
File "/home/lacagette/Odoo/lacagette_addons/lacagette_shelfs/models/product_shelfs.py", line 19, in unlink
self.delete_workflow()
File "/home/lacagette/Odoo/odoo/openerp/api.py", line 248, in wrapper
return new_api(self, *args, **kwargs)
File "/home/lacagette/Odoo/odoo/openerp/api.py", line 574, in new_api
result = method(self._model, cr, uid, self.ids, *args, **old_kwargs)
File "/home/lacagette/Odoo/odoo/openerp/models.py", line 3616, in delete_workflow
workflow.trg_delete(uid, self._name, res_id, cr)
File "/home/lacagette/Odoo/odoo/openerp/workflow/__init__.py", line 45, in trg_delete
return WorkflowService.new(cr, uid, res_type, res_id).delete()
File "/home/lacagette/Odoo/odoo/openerp/workflow/service.py", line 19, in new
return cls(Session(cr, uid), Record(model_name, record_id))
File "/home/lacagette/Odoo/odoo/openerp/workflow/helpers.py", line 13, in __init__
assert isinstance(record_id, (int, long))
AssertionError
"""
res = True
try:
sql = """
DELETE FROM product_shelfs
WHERE
id = %s;
UPDATE product_product SET shelf_id = NULL WHERE shelf_id = %s
"""
self.env.cr.execute(sql , [self.id, self.id])
except Exception as e:
res = "Erreur requete SQL : "+ str(e)
return res
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
,,,,,,,
,,,,,,,
access_model_product_shelfs,access_model_product_shelfs,model_product_shelfs,stock.group_stock_manager,1,1,1,1
# -*- coding: utf-8 -*-
from openerp import models, fields, api
class LacagetteToolsModel(models.Model):
_name = "lacagette_tools"
@api.model
def get_stockQuant_last_Sale(self):
"""Recupère la date de la dernière vente de tout les article """
sql = """SELECT t4.value as name, t2.maxDate, t1.id, t.uom_id, t.purchase_ok,
COALESCE((SELECT sum(t3.qty) FROM stock_quant as t3
WHERE location_id = 12 AND t3.product_id =t1.id),0) as stockQt
FROM product_product as t1
left join product_template as t on t1.product_tmpl_id = t.id
right join ir_translation as t4 on t.id = t4.res_id
INNER JOIN
( SELECT MAX(write_date) as maxDate, product_id
FROM stock_quant
WHERE location_id = 9
GROUP BY product_id) as t2
ON t1.id = t2.product_id
WHERE t.active = true and t.sale_ok = true and t4.name='product.template,name'
ORDER BY t2.maxDate;"""
self.env.cr.execute(sql)
res = self.env.cr.dictfetchall()
return (res)
@api.model
def get_list_article_of_fournisseur(self, partner_id):
"""Recupère la liste des article d'un fournisseur (partner_id) avec leur nom et la qantité en stock"""
sql="""SELECT distinct(t1.product_id), t2.name_template,
(SELECT coalesce(sum(stock_quant.qty),0)
FROM
stock_quant
where
stock_quant.location_id =12
AND stock_quant.product_id =t1.product_id) as stock_qty
FROM purchase_order_line as t1 left join product_product as t2 on
t1.product_id =t2.id
WHERE t1.partner_id ={} AND t2.active = true"""
self.env.cr.execute(sql.format(partner_id))
res = self.env.cr.dictfetchall()
return (res)
@api.model
def get_sale_qty_by_article(self, lProduct_id):
"""Recupère le moment de la vent et la quantité pour un article"""
sql="""SELECT product_id, coalesce(sum(qty),0) as sumQty
FROM stock_quant
WHERE location_id = 9 and product_id in ({}) and write_date > CURRENT_TIMESTAMP - INTERVAL '4 WEEK' group by product_id"""
self.env.cr.execute(sql.format(str(lProduct_id)[1:-1]))
res = self.env.cr.dictfetchall()
print (res)
return (res)
@api.model
def get_sale_qty_by_from(self, nbWeek):
"""Recupère le moment de la vent et la quantité pour un article"""
sql="""SELECT t.product_id, coalesce(sum(t.qty),0) as sumQty, coalesce(t3.value, 'Neant') as name
FROM stock_quant as t
left join product_product as t1 on t1.id = t.product_id
left join product_template as t2 on t1.product_tmpl_id = t2.id
right join ir_translation as t3 on t2.id = t3.res_id
WHERE t.location_id = 9 and t.write_date::date >= CURRENT_TIMESTAMP::date - INTERVAL '{} WEEK'
AND t3.name='product.template,name'
GROUP BY t.product_id, t3.value"""
self.env.cr.execute( sql.format(str(nbWeek)))
res = self.env.cr.dictfetchall()
print (res)
return res
@api.model
def get_sale_article_by_date(self, myDate):
"""Recupère les article qui ont été ventu a une date"""
sql="""SELECT distinct(product_id)
FROM stock_quant
WHERE location_id = 9 and write_date::date = '{}'::date group by product_id"""
self.env.cr.execute(sql.format(myDate))
res = self.env.cr.dictfetchall()
#print (res)
return (res)
# -*- coding: utf-8 -*-
#from . import controllers
#from . import models
from . import LacagetteToolsModel
# -*- coding: utf-8 -*-
{
'name': "lacagetteTools",
'summary': """
API methods to query sales/ inventory
""",
'description': """
API methods to query sales/ inventory shortages
""",
'author': "La Cagette",
'website': "http://www.yourcompany.com",
# 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': 'Uncategorized',
'version': '0.1.2',
# any module necessary for this one to work correctly
'depends': ['base'],
# always loaded
'data': [
# 'security/ir.model.access.csv',
#'views/views.xml',
#'views/templates.xml',
],
# only loaded in demonstration mode
'demo': [
# 'demo/demo.xml',
],
}
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