Commit ec166c05 by Thibault Grandjean

Merge branch 'dev_cooperatic' into 1858-readonly-calendar

parents 47bc1fde e2469f81
Pipeline #1672 passed with stage
in 1 minute 33 seconds
...@@ -62,6 +62,7 @@ var html_elts = { ...@@ -62,6 +62,7 @@ var html_elts = {
real_capture : $('#real_capture'), real_capture : $('#real_capture'),
multi_results : $('#multi_results_preview'), multi_results : $('#multi_results_preview'),
cooperative_state : $('#cooperative_state'), cooperative_state : $('#cooperative_state'),
status_explanation: $('#status_explanation'),
next_shifts : $('#next_shifts') next_shifts : $('#next_shifts')
}; };
...@@ -91,8 +92,12 @@ function fill_member_slide(member) { ...@@ -91,8 +92,12 @@ function fill_member_slide(member) {
} }
html_elts.image_medium.html('<img src="'+img_src+'" width="128" />'); html_elts.image_medium.html('<img src="'+img_src+'" width="128" />');
html_elts.cooperative_state.html(member.cooperative_state); html_elts.cooperative_state.html(member.cooperative_state);
if (member.cooperative_state == 'Désinscrit(e)' || member.cooperative_state == 'Rattrapage') coop_info.addClass('b_red'); if (member.cooperative_state == 'Rattrapage') {
else if (member.cooperative_state == 'En alerte' || member.cooperative_state == 'Délai accordé') coop_info.addClass('b_orange'); var explanation = "Tu as dû manquer un service! Pour pouvoir faire tes courses aujourd'hui, tu dois d'abord sélectionner un rattrapage sur ton espace membre."
html_elts.status_explanation.html(explanation)
}
if (member.cooperative_state == 'Désinscrit(e)') coop_info.addClass('b_red');
else if (member.cooperative_state == 'En alerte' || member.cooperative_state == 'Délai accordé' || member.cooperative_state == 'Rattrapage') coop_info.addClass('b_orange');
if (member.shifts.length > 0) { if (member.shifts.length > 0) {
html_elts.next_shifts.append('Prochains services : '); html_elts.next_shifts.append('Prochains services : ');
......
...@@ -96,14 +96,7 @@ function process_form_submission(event) { ...@@ -96,14 +96,7 @@ function process_form_submission(event) {
closeModal(); closeModal();
if (!err) { if (!err) {
var msg = "Vous êtes maintenant enregistré ! "; window.location.href = em_url;
msg += "<a href='" + em_url + "'>Cliquez ici</a> ";
msg += "pour découvrir l'espace membre";
$('p.intro').remove();
vform.remove();
display_msg_box(msg);
} }
} }
......
...@@ -36,7 +36,7 @@ class CagetteMembersSpace(models.Model): ...@@ -36,7 +36,7 @@ class CagetteMembersSpace(models.Model):
try: try:
cond = [ cond = [
['partner_id', '=', partner_id], ['partner_id', '=', partner_id],
['create_date', '>', date_from], ['date_begin', '>', date_from],
['date_begin', '<', today], ['date_begin', '<', today],
['state', '!=', 'draft'], ['state', '!=', 'draft'],
['state', '!=', 'open'], ['state', '!=', 'open'],
...@@ -88,10 +88,10 @@ class CagetteMembersSpace(models.Model): ...@@ -88,10 +88,10 @@ class CagetteMembersSpace(models.Model):
res = res + [] res = res + []
# Add amnesty line # Add amnesty line
is_amnesty = getattr(settings, 'AMNISTIE_DATE', 'false') is_amnesty = getattr(settings, 'AMNISTIE_DATE', False)
company_code = getattr(settings, 'COMPANY_CODE', '') company_code = getattr(settings, 'COMPANY_CODE', '')
if is_amnesty and company_code == "lacagette": if is_amnesty and company_code == "lacagette":
amnesty={} amnesty = {}
amnesty['is_amnesty'] = True amnesty['is_amnesty'] = True
amnesty['create_date'] = is_amnesty amnesty['create_date'] = is_amnesty
amnesty['date_begin'] = is_amnesty amnesty['date_begin'] = is_amnesty
...@@ -107,7 +107,7 @@ class CagetteMembersSpace(models.Model): ...@@ -107,7 +107,7 @@ class CagetteMembersSpace(models.Model):
paginated_res = res[offset:end_index] paginated_res = res[offset:end_index]
except Exception as e: except Exception as e:
print(str(e)) coop_logger.error("get_shifts_history : %s", str(e))
return paginated_res return paginated_res
\ No newline at end of file
...@@ -91,8 +91,10 @@ function add_or_change_shift(new_shift_id) { ...@@ -91,8 +91,10 @@ function add_or_change_shift(new_shift_id) {
selected_shift = null; selected_shift = null;
if (error.status === 400) { if (error.status === 400) {
alert(`Désolé ! Le service que vous souhaitez échanger démarre dans moins de 24h. ` + alert(`Désolé ! Le service que tu souhaites échanger démarre dans moins de 24h. ` +
`Il n'est plus possible de l'échanger.`); `Afin de faciliter la logistique des services, il n'est plus possible de l'échanger. ` +
`Si tu ne peux vraiment pas venir, tu seras noté.e absent.e à ton service. ` +
`Tu devras alors sélectionner un service de rattrapage sur ton espace membre.`);
} else { } else {
alert(`Une erreur est survenue. ` + alert(`Une erreur est survenue. ` +
`Il est néanmoins possible que la requête ait abouti, ` + `Il est néanmoins possible que la requête ait abouti, ` +
...@@ -497,4 +499,4 @@ function init_shifts_exchange() { ...@@ -497,4 +499,4 @@ function init_shifts_exchange() {
vw = window.innerWidth; vw = window.innerWidth;
init_calendar_page(); init_calendar_page();
}); });
} }
\ No newline at end of file
...@@ -16,7 +16,8 @@ const possible_cooperative_state = { ...@@ -16,7 +16,8 @@ const possible_cooperative_state = {
alert: "En alerte", alert: "En alerte",
up_to_date: "À jour", up_to_date: "À jour",
unsubscribed: "Désinscrit.e des créneaux", unsubscribed: "Désinscrit.e des créneaux",
delay: "En délai" delay: "En délai",
gone: "Parti.e"
}; };
/* - Data */ /* - Data */
...@@ -177,7 +178,7 @@ function init_my_info_data() { ...@@ -177,7 +178,7 @@ function init_my_info_data() {
$(".delay_date_stop").text(f_date_delay_stop); $(".delay_date_stop").text(f_date_delay_stop);
$(".delay_date_stop_container").show(); $(".delay_date_stop_container").show();
} else if (partner_data.cooperative_state === 'unsubscribed') { } else if (partner_data.cooperative_state === 'unsubscribed' || partner_data.cooperative_state === 'gone') {
$(".member_shift_name").text('X'); $(".member_shift_name").text('X');
$(".unsuscribed_form_link") $(".unsuscribed_form_link")
......
...@@ -197,6 +197,23 @@ ...@@ -197,6 +197,23 @@
border-radius: 3px; border-radius: 3px;
} }
#coverage_form > div {
display:inline-block;
float:left;
}
#coverage_form .input-wrapper {
margin-right: 3px;
}
#coverage_days_input, #percent_adjust_input {
display: block;
}
#coverage_days_input {
margin-bottom: 3px;
}
/* -- Table */ /* -- Table */
#products_table_filter{ #products_table_filter{
text-align: right !important; text-align: right !important;
...@@ -224,6 +241,10 @@ ...@@ -224,6 +241,10 @@
margin-left: 5px; margin-left: 5px;
} }
.main.fa-info-circle {
color: #0275d8;
cursor: pointer;
}
.custom_cell_content { .custom_cell_content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
...@@ -346,6 +367,15 @@ ...@@ -346,6 +367,15 @@
width: 90%; width: 90%;
} }
/* product actions modal*/
.npa-options {
width: fit-content;
text-align: left;
margin: auto;
}
.npa-options label {
display: block;
}
/* - Orders created screen */ /* - Orders created screen */
.order_created_header { .order_created_header {
...@@ -392,6 +422,8 @@ ...@@ -392,6 +422,8 @@
padding: 15px; padding: 15px;
} }
/* - Miscellaneous */ /* - Miscellaneous */
footer { footer {
......
...@@ -189,7 +189,14 @@ function add_product() { ...@@ -189,7 +189,14 @@ function add_product() {
* Set the computed qty for the first supplier only. * Set the computed qty for the first supplier only.
*/ */
function compute_products_coverage_qties() { function compute_products_coverage_qties() {
const pc_adjust = $('#percent_adjust_input').val();
let coeff = 1;
if (!isNaN(parseFloat(pc_adjust))) {
coeff = (1 + parseFloat(pc_adjust) /100);
}
if (order_doc.coverage_days != null) { if (order_doc.coverage_days != null) {
order_doc.coeff = coeff;
for (const [ for (const [
key, key,
product product
...@@ -202,12 +209,16 @@ function compute_products_coverage_qties() { ...@@ -202,12 +209,16 @@ function compute_products_coverage_qties() {
const incoming_qty = product.incoming_qty; const incoming_qty = product.incoming_qty;
const daily_conso = product.daily_conso; const daily_conso = product.daily_conso;
purchase_qty_for_coverage = order_doc.coverage_days * daily_conso - stock - incoming_qty; purchase_qty_for_coverage = order_doc.coverage_days * daily_conso - stock - incoming_qty;
purchase_qty_for_coverage = (purchase_qty_for_coverage < 0) ? 0 : purchase_qty_for_coverage; purchase_qty_for_coverage = (purchase_qty_for_coverage < 0) ? 0 : purchase_qty_for_coverage;
// Reduce to nb of packages to purchase // Reduce to nb of packages to purchase
purchase_package_qty_for_coverage = purchase_qty_for_coverage / product.suppliersinfo[0].package_qty; purchase_package_qty_for_coverage = purchase_qty_for_coverage / product.suppliersinfo[0].package_qty;
if (order_doc.coeff != 1) {
purchase_package_qty_for_coverage *= order_doc.coeff;
}
// Round up to unit for all products // Round up to unit for all products
purchase_package_qty_for_coverage = Math.ceil(purchase_package_qty_for_coverage); purchase_package_qty_for_coverage = Math.ceil(purchase_package_qty_for_coverage);
...@@ -227,7 +238,7 @@ function check_products_data() { ...@@ -227,7 +238,7 @@ function check_products_data() {
if (suppliers_id.length > 0) { if (suppliers_id.length > 0) {
$.notify( $.notify(
"Vérfication des informations produits...", "Vérification des informations produits...",
{ {
globalPosition:"top left", globalPosition:"top left",
className: "info" className: "info"
...@@ -677,35 +688,32 @@ function _compute_total_values_by_supplier() { ...@@ -677,35 +688,32 @@ function _compute_total_values_by_supplier() {
/* - PRODUCT */ /* - PRODUCT */
/** function save_products_npa_minimal_stock(product, inputs) {
* Update 'purchase_ok' of a product let actions = {npa: [], minimal_stock: 0, id: product.id, name: product.name};
* inputs.each(function (i,e) {
* @param {int} p_id product id const input = $(e)
* @param {Boolean} npa value to set purchase_ok to if (input.attr('type') == 'checkbox') {
*/ if (input.prop('checked') == true) {
function set_product_npa(p_id, npa) { actions.npa.push(input.val())
openModal(); }
} else if (input.attr('name') == "minimal_stock") {
const data = { actions.minimal_stock = input.val()
product_tmpl_id: p_id, }
purchase_ok: !npa });
};
// Fetch supplier products
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: "/products/update_product_purchase_ok", url: "/products/update_npa_and_minimal_stock",
dataType: "json", dataType: "json",
traditional: true, traditional: true,
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
data: JSON.stringify(data), data: JSON.stringify(actions),
success: () => { success: () => {
const index = products.findIndex(p => p.id == p_id); const index = products.findIndex(p => p.id == product.id);
// Give time for modal to fade // Give time for modal to fade
setTimeout(function() { setTimeout(function() {
$(".actions_buttons_area .right_action_buttons").notify( $(".actions_buttons_area .right_action_buttons").notify(
"Produit passé en NPA !", "Actions enregistrées !",
{ {
elementPosition:"bottom right", elementPosition:"bottom right",
className: "success", className: "success",
...@@ -713,19 +721,21 @@ function set_product_npa(p_id, npa) { ...@@ -713,19 +721,21 @@ function set_product_npa(p_id, npa) {
} }
); );
}, 500); }, 500);
products[index].minimal_stock = actions.minimal_stock;
// Remove NPA products if (actions.npa.length > 0) {
products.splice(index, 1); // Remove NPA products
update_main_screen(); products.splice(index, 1);
update_cdb_order(); update_main_screen();
update_cdb_order();
}
closeModal(); closeModal();
}, },
error: function(data) { error: function(data) {
let msg = "erreur serveur lors de la sauvegarde du NPA". let msg = "erreur serveur lors de la sauvegarde".
msg += ` (product_tmpl_id: ${p_id})`; msg += ` (product_tmpl_id: ${product.id})`;
err = {msg: msg, ctx: 'set_product_npa'}; err = {msg: msg, ctx: 'save_products_npa_minimal_stock'};
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') { if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error; err.msg += ' : ' + data.responseJSON.error;
} }
...@@ -736,8 +746,10 @@ function set_product_npa(p_id, npa) { ...@@ -736,8 +746,10 @@ function set_product_npa(p_id, npa) {
update_main_screen(); update_main_screen();
} }
}); });
} }
/* - INVENTORY */ /* - INVENTORY */
/** /**
...@@ -1316,8 +1328,8 @@ function prepare_datatable_columns() { ...@@ -1316,8 +1328,8 @@ function prepare_datatable_columns() {
{ {
data: "id", data: "id",
title: `<div id="table_header_select_all" class="txtcenter"> title: `<div id="table_header_select_all" class="txtcenter">
<span class="select_all_text">Sélectionner</span> <!--<span class="select_all_text">Sélectionner</span>-->
<label for="select_all_products_cb">- Tout</label> <label for="select_all_products_cb">Tout</label>
<input type="checkbox" class="select_product_cb" id="select_all_products_cb" name="select_all_products_cb" value="all"> <input type="checkbox" class="select_product_cb" id="select_all_products_cb" name="select_all_products_cb" value="all">
</div>`, </div>`,
className: "dt-body-center", className: "dt-body-center",
...@@ -1442,12 +1454,11 @@ function prepare_datatable_columns() { ...@@ -1442,12 +1454,11 @@ function prepare_datatable_columns() {
}); });
columns.push({ columns.push({
data: "purchase_ok", title: ``,
title: `NPA`,
className: "dt-body-center", className: "dt-body-center",
orderable: false, orderable: false,
render: function (data) { render: function (data) {
return `<input type="checkbox" class="product_npa_cb" value="purchase_ok" ${data ? '' : 'checked'}>`; return `<button type="button" class="btn--primary product_actions">Actions</button>`;
}, },
width: "4%" width: "4%"
}); });
...@@ -1478,7 +1489,7 @@ function display_products(params) { ...@@ -1478,7 +1489,7 @@ function display_products(params) {
const data = prepare_datatable_data(); const data = prepare_datatable_data();
const columns = prepare_datatable_columns(); const columns = prepare_datatable_columns();
let sort_order_dir = "asc"; let sort_order_dir = "desc";
if (params != undefined && typeof params.sort_order_dir != "undefined") { if (params != undefined && typeof params.sort_order_dir != "undefined") {
sort_order_dir = params.sort_order_dir; sort_order_dir = params.sort_order_dir;
...@@ -1488,10 +1499,11 @@ function display_products(params) { ...@@ -1488,10 +1499,11 @@ function display_products(params) {
columns: columns, columns: columns,
order: [ order: [
[ [
6, // Order by default by first supplier 5, // Order by default by first supplier
sort_order_dir sort_order_dir
] ]
], ],
stateSave: true,
orderClasses: false, orderClasses: false,
aLengthMenu: [ aLengthMenu: [
[ [
...@@ -1601,10 +1613,6 @@ function display_products(params) { ...@@ -1601,10 +1613,6 @@ function display_products(params) {
} }
} }
}) })
.on('change', 'tbody td .product_qty_input', function () {
// Since data change is saved on blur, set focus on change in case of arrows pressed
$(this).focus();
})
.on('keypress', 'tbody td .product_qty_input', function(e) { .on('keypress', 'tbody td .product_qty_input', function(e) {
// Validate on Enter pressed // Validate on Enter pressed
if (e.which == 13) { if (e.which == 13) {
...@@ -1620,7 +1628,7 @@ function display_products(params) { ...@@ -1620,7 +1628,7 @@ function display_products(params) {
next_input.focus(); next_input.focus();
// Scroll to a position where the target input is not hidden by the sticky suppliers container // Scroll to a position where the target input is not hidden by the sticky suppliers container
const suppliers_container_top_offset = const suppliers_container_top_offset =
$("#suppliers_container").offset().top $("#suppliers_container").offset().top
- $(window).scrollTop() - $(window).scrollTop()
+ $("#suppliers_container").outerHeight(); + $("#suppliers_container").outerHeight();
...@@ -1636,7 +1644,38 @@ function display_products(params) { ...@@ -1636,7 +1644,38 @@ function display_products(params) {
// On arrow down pressed, focus previous row input // On arrow down pressed, focus previous row input
$(this).closest("tr").next().find(".product_qty_input").focus(); $(this).closest("tr").next().find(".product_qty_input").focus();
}
} else if (e.which == 13) {
e.preventDefault();
// On enter pressed, focus previous row input
$(this).closest("tr").next().find(".product_qty_input").focus();
}
})
.on('click', 'tbody td .product_actions', function(e){
// Save / unsave selected row
const p_id = products_table.row($(this).closest('tr')).data().id;
const product = products.find(p => p.id == p_id);
let modal_product_actions = $('#templates #modal_product_actions');
modal_product_actions.find(".product_name").text(product.name);
//modal_product_actions.find(".product_npa").text(null ? 'Ne Pas Acheter' : 'Peut Être Acheté');
openModal(
modal_product_actions.html(),
() => {
if (is_time_to('validate_product_actions')) {
save_products_npa_minimal_stock(product, modal.find('input'));
}
},
'Valider',
false
);
modal.find('input[name="minimal_stock"]').val(product.minimal_stock)
}); });
// Associate product to supplier on click on 'X' in the table // Associate product to supplier on click on 'X' in the table
...@@ -1768,34 +1807,7 @@ function display_products(params) { ...@@ -1768,34 +1807,7 @@ function display_products(params) {
} }
}); });
// Set product is NPA (Ne Pas Acheter)
$('#products_table').on('click', 'tbody td .product_npa_cb', function () {
// Save / unsave selected row
const p_id = products_table.row($(this).closest('tr')).data().id;
const npa = this.checked;
const product = products.find(p => p.id == p_id);
let modal_product_npa = $('#templates #modal_product_npa');
modal_product_npa.find(".product_name").text(product.name);
modal_product_npa.find(".product_npa").text(npa ? 'Ne Pas Acheter' : 'Peut Être Acheté');
openModal(
modal_product_npa.html(),
() => {
if (is_time_to('validate_set_product_npa')) {
set_product_npa(p_id, npa);
}
},
'Valider',
false,
true,
() => {
this.checked = !this.checked;
}
);
});
return 0; return 0;
} }
...@@ -1881,6 +1893,10 @@ function update_main_screen(params) { ...@@ -1881,6 +1893,10 @@ function update_main_screen(params) {
$("#coverage_days_input").val(''); $("#coverage_days_input").val('');
} }
if (order_doc.coeff && order_doc.coeff != 1) {
$("#percent_adjust_input").val(-Math.ceil((1 - order_doc.coeff) * 100));
}
if (order_doc.stats_date_period !== undefined && order_doc.stats_date_period !== null) { if (order_doc.stats_date_period !== undefined && order_doc.stats_date_period !== null) {
$("#stats_date_period_select").val(order_doc.stats_date_period); $("#stats_date_period_select").val(order_doc.stats_date_period);
} else { } else {
...@@ -1888,6 +1904,9 @@ function update_main_screen(params) { ...@@ -1888,6 +1904,9 @@ function update_main_screen(params) {
} }
} }
function display_average_consumption_explanation() {
openModal($('#explanations').html())
}
/** /**
* Update DOM display on the order selection screen * Update DOM display on the order selection screen
*/ */
...@@ -2260,6 +2279,8 @@ $(document).ready(function() { ...@@ -2260,6 +2279,8 @@ $(document).ready(function() {
return 0; return 0;
}); });
$(document).on("click",".fa-info-circle", display_average_consumption_explanation)
$.datepicker.regional['fr'] = { $.datepicker.regional['fr'] = {
monthNames: [ monthNames: [
'Janvier', 'Janvier',
......
from outils.common_imports import * from outils.common_imports import *
from outils.for_view_imports import * from outils.for_view_imports import *
from outils.common import OdooAPI
from orders.models import Order, Orders, CagetteSuppliers from orders.models import Order, Orders, CagetteSuppliers
from products.models import CagetteProduct, CagetteProducts from products.models import CagetteProduct, CagetteProducts
...@@ -20,7 +21,9 @@ def helper(request): ...@@ -20,7 +21,9 @@ def helper(request):
'couchdb_server': settings.COUCHDB['url'], 'couchdb_server': settings.COUCHDB['url'],
'db': settings.COUCHDB['dbs']['orders'], 'db': settings.COUCHDB['dbs']['orders'],
'odoo_server': settings.ODOO['url'], 'odoo_server': settings.ODOO['url'],
'metabase_url': getattr(settings, 'ORDERS_HELPER_METABASE_URL', '') 'metabase_url': getattr(settings, 'ORDERS_HELPER_METABASE_URL', ''),
'nb_past_days_to_compute_sales_average': OdooAPI().get_system_param('lacagette_products.nb_past_days_to_compute_sales_average'),
'nb_of_consecutive_non_sale_days_considered_as_break': OdooAPI().get_system_param('lacagette_products.nb_of_consecutive_non_sale_days_considered_as_break')
} }
template = loader.get_template('orders/helper.html') template = loader.get_template('orders/helper.html')
......
...@@ -74,6 +74,18 @@ class OdooAPI: ...@@ -74,6 +74,18 @@ class OdooAPI:
def authenticate(self, login, password): def authenticate(self, login, password):
return self.common.authenticate(self.db, login, password, {}) return self.common.authenticate(self.db, login, password, {})
def get_system_param(self, key):
value = ''
try:
res = self.search_read('ir.config_parameter',
[['key', '=', key]],
['value'])
if res:
value = res[0]['value']
except Exception as e:
coop_logger.error('get_system_param: (%s) %s', key, str(e))
return value
class CouchDB: class CouchDB:
"""Class to handle interactions with CouchDB""" """Class to handle interactions with CouchDB"""
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -7,6 +7,7 @@ import csv ...@@ -7,6 +7,7 @@ import csv
import tempfile import tempfile
import pymysql.cursors import pymysql.cursors
import datetime import datetime
import re
vcats = [] vcats = []
...@@ -252,10 +253,43 @@ class CagetteProduct(models.Model): ...@@ -252,10 +253,43 @@ class CagetteProduct(models.Model):
return res return res
@staticmethod
def update_npa_and_minimal_stock(data):
"""Update NPA (ne pas acheter) and minimal stock data"""
res = {}
try:
api = OdooAPI()
f = {'minimal_stock': data['minimal_stock']}
if 'simple-npa' in data['npa']:
f['purchase_ok'] = 0
if 'npa-in-name' in data['npa']:
# Add [NPA] in product name if needed
f['name'] = data['name'] if ('[NPA]' in data['name']) else data['name'] + " [NPA]"
f['purchase_ok'] = 0
elif '[NPA]' in data['name']:
# Remove [NPA] from name
f['name'] = re.sub(r'( \[NPA\])', '', data['name'])
current_name = data['name'] if ('name' not in f) else f['name']
if 'fds-in-name' in data['npa']:
f['name'] = current_name if '[FDS]' in data['name'] else current_name + " [FDS]"
f['purchase_ok'] = 0
elif '[FDS]' in current_name:
f['name'] = re.sub(r'( \[FDS\])', '', current_name)
if len(data['npa']) == 0:
f['purchase_ok'] = 1
res["update"] = api.update('product.template', data['id'], f)
except Exception as e:
res["error"] = str(e)
coop_logger.error("update_npa_and_minimal_stock : %s %s", str(e), str(data))
return res
class CagetteProducts(models.Model): class CagetteProducts(models.Model):
"""Initially used to make massive barcode update.""" """Initially used to make massive barcode update."""
@staticmethod @staticmethod
def get_simple_list(): def get_simple_list():
res = [] res = []
...@@ -577,7 +611,8 @@ class CagetteProducts(models.Model): ...@@ -577,7 +611,8 @@ class CagetteProducts(models.Model):
"uom_id", "uom_id",
"purchase_ok", "purchase_ok",
"supplier_taxes_id", "supplier_taxes_id",
"product_variant_ids" "product_variant_ids",
"minimal_stock"
] ]
c = [['id', 'in', ptids], ['purchase_ok', '=', True]] c = [['id', 'in', ptids], ['purchase_ok', '=', True]]
products_t = api.search_read('product.template', c, f) products_t = api.search_read('product.template', c, f)
......
...@@ -11,6 +11,7 @@ urlpatterns = [ ...@@ -11,6 +11,7 @@ urlpatterns = [
url(r'^update_product_stock$', views.update_product_stock), url(r'^update_product_stock$', views.update_product_stock),
url(r'^update_product_purchase_ok$', views.update_product_purchase_ok), url(r'^update_product_purchase_ok$', views.update_product_purchase_ok),
url(r'^update_product_internal_ref$', views.update_product_internal_ref), url(r'^update_product_internal_ref$', views.update_product_internal_ref),
url(r'^update_npa_and_minimal_stock$', views.update_npa_and_minimal_stock),
url(r'^labels_appli_csv(\/?[a-z]*)$', views.labels_appli_csv, name='labels_appli_csv'), url(r'^labels_appli_csv(\/?[a-z]*)$', views.labels_appli_csv, name='labels_appli_csv'),
url(r'^label_print/([0-9]+)/?([0-9\.]*)/?([a-z]*)/?([0-9]*)$', views.label_print), url(r'^label_print/([0-9]+)/?([0-9\.]*)/?([a-z]*)/?([0-9]*)$', views.label_print),
url(r'^shelf_labels$', views.shelf_labels), # massive print url(r'^shelf_labels$', views.shelf_labels), # massive print
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
from outils.common_imports import * from outils.common_imports import *
from outils.for_view_imports import * from outils.for_view_imports import *
from members.models import CagetteUser
from products.models import CagetteProduct from products.models import CagetteProduct
from products.models import CagetteProducts from products.models import CagetteProducts
from inventory.models import CagetteInventory from inventory.models import CagetteInventory
...@@ -105,25 +106,51 @@ def update_product_stock(request): ...@@ -105,25 +106,51 @@ def update_product_stock(request):
def update_product_purchase_ok(request): def update_product_purchase_ok(request):
res = {} res = {}
data = json.loads(request.body.decode()) is_connected_user = CagetteUser.are_credentials_ok(request)
if is_connected_user is True:
data = json.loads(request.body.decode())
res = CagetteProduct.update_product_purchase_ok(data["product_tmpl_id"], data["purchase_ok"]) res = CagetteProduct.update_product_purchase_ok(data["product_tmpl_id"], data["purchase_ok"])
if ('error' in res): if ('error' in res):
return JsonResponse(res, status=500) return JsonResponse(res, status=500)
else:
return JsonResponse({"res": res})
else: else:
return JsonResponse({"res": res}) return JsonResponse(res, status=403)
def update_product_internal_ref(request): def update_product_internal_ref(request):
res = {} res = {}
data = json.loads(request.body.decode()) is_connected_user = CagetteUser.are_credentials_ok(request)
if is_connected_user is True:
data = json.loads(request.body.decode())
res = CagetteProduct.update_product_internal_ref(data["product_tmpl_id"], data["default_code"]) res = CagetteProduct.update_product_internal_ref(data["product_tmpl_id"], data["default_code"])
if ('error' in res): if ('error' in res):
return JsonResponse(res, status=500) return JsonResponse(res, status=500)
else:
return JsonResponse({"res": res})
else: else:
return JsonResponse({"res": res}) return JsonResponse(res, status=403)
def update_npa_and_minimal_stock(request):
res = {}
is_connected_user = CagetteUser.are_credentials_ok(request)
if is_connected_user is True:
try:
data = json.loads(request.body.decode())
res = CagetteProduct.update_npa_and_minimal_stock(data)
except Exception as e:
res['error'] = str(e)
coop_logger.error("Update npa and minimal stock : %s", res['error'])
if ('error' in res):
return JsonResponse(res, status=500)
else:
return JsonResponse({"res": res})
else:
return JsonResponse(res, status=403)
def labels_appli_csv(request, params): def labels_appli_csv(request, params):
"""Generate files to put in DAV directory to be retrieved by scales app.""" """Generate files to put in DAV directory to be retrieved by scales app."""
......
...@@ -155,6 +155,8 @@ class CagetteShift(models.Model): ...@@ -155,6 +155,8 @@ class CagetteShift(models.Model):
"origin": 'memberspace', "origin": 'memberspace',
"is_makeup": data['is_makeup'], "is_makeup": data['is_makeup'],
"state": 'open'} "state": 'open'}
if shift_type == "standard":
fieldsDatas['template_created'] = 1 # It's not true but otherwise, presence add 1 standard point, which is not wanted
st_r_id = self.o_api.create('shift.registration', fieldsDatas) st_r_id = self.o_api.create('shift.registration', fieldsDatas)
except Exception as e: except Exception as e:
...@@ -211,7 +213,7 @@ class CagetteShift(models.Model): ...@@ -211,7 +213,7 @@ class CagetteShift(models.Model):
action = 'create' action = 'create'
# Get partner extension ids # Get partner extension ids
cond = [['id','=',data['idPartner']]] cond = [['id', '=', data['idPartner']]]
fields = ['extension_ids'] fields = ['extension_ids']
partner_extensions = self.o_api.search_read('res.partner', cond, fields) partner_extensions = self.o_api.search_read('res.partner', cond, fields)
response = False response = False
......
...@@ -107,6 +107,8 @@ ...@@ -107,6 +107,8 @@
</div> </div>
<div id="cooperative_state"> <div id="cooperative_state">
</div> </div>
<div id="status_explanation" style="font-weight:bold;">
</div>
<div id="next_shifts"> <div id="next_shifts">
</div> </div>
</div> </div>
......
...@@ -107,7 +107,7 @@ ...@@ -107,7 +107,7 @@
<div class="input-container panel"> <div class="input-container panel">
<div class="grp_text"> <div class="grp_text">
<h3><b>Créneau/service c'est quoi la différence?</b></h3> <h3><b>Créneau/service c'est quoi la différence ?</b></h3>
<p> <p>
Un créneau, c'est une plage récurrente, par exemple, tous les jeudi de semaine A de 13h30 à 16h30.<br/> Un créneau, c'est une plage récurrente, par exemple, tous les jeudi de semaine A de 13h30 à 16h30.<br/>
Un service, c'est une plage horaire en particulier, par exemple le jeudi 28 novembre de 13h30 à 16h30. Un service, c'est une plage horaire en particulier, par exemple le jeudi 28 novembre de 13h30 à 16h30.
...@@ -116,7 +116,7 @@ ...@@ -116,7 +116,7 @@
</div> </div>
<div class="grp_text"> <div class="grp_text">
<h3><b>Échanger son service:</b></h3> <h3><b>Échanger son service</b></h3>
<p> <p>
Si tu ne peux pas venir effectuer ton service à la date prévue il te faut l'échanger sur ton espace membre.<br/> Si tu ne peux pas venir effectuer ton service à la date prévue il te faut l'échanger sur ton espace membre.<br/>
<b>ATTENTION:</b> si tu es en binôme c'est via l'espace membre du titulaire du binôme qu'il faut le faire. <b>ATTENTION:</b> si tu es en binôme c'est via l'espace membre du titulaire du binôme qu'il faut le faire.
...@@ -132,6 +132,24 @@ ...@@ -132,6 +132,24 @@
</a> </a>
</div> </div>
</div> </div>
<div class="grp_text">
<h3><b>Oubli de validation de service</b></h3>
<p>
Si tu as effectué ton service mais que tu as oublié de le valider à l'entrée du magasin, il te faut remplir le formulaire suivant en indiquant la date de ton service.
</p>
<div class="faq_link_button_area">
<a
href="javascript:void(0);"
target="_blank"
type="button"
class="btn--primary faq_link_button"
id="request_form_link_btn2"
>
Oubli validation service
</a>
</div>
</div>
<div class="grp_text"> <div class="grp_text">
<h3><b>Demande de congé maladie ou parental</b></h3> <h3><b>Demande de congé maladie ou parental</b></h3>
...@@ -142,9 +160,8 @@ ...@@ -142,9 +160,8 @@
<p></b></p> <p></b></p>
</div> </div>
<div class="grp_text"> <div class="grp_text"><h3><b>Les congés maladie</b></h3>
<b><h3>Les congés maladie:</h3></b> Les congés maladie peuvent être pris en cas d’impossibilité physique de réaliser son service pendant une longue durée qui prend en compte<b> minimum 2 services</b>. Si ton impossibilité est d'un mois il t'est demandé de déplacer ton service.<br />
Les congés maladie peuvent être pris en cas d’impossibilité physique de réaliser son service pendant une longue durée qui prend en compte<b> minimum 2 services</b>. Si ton impossibilité est d'un mois il t'es demandé de déplacer ton service.<br />
En cas de rhume nous encourageons les coops à simplement déplacer leur service depuis l’espace membre. La coop a besoin de la participation de chacun.e !<br /> En cas de rhume nous encourageons les coops à simplement déplacer leur service depuis l’espace membre. La coop a besoin de la participation de chacun.e !<br />
<b>ATTENTION:</b> Compte tenu de la facilité offerte par le statut de binôme, les personnes formant un binôme ne pourront pas<br /> <b>ATTENTION:</b> Compte tenu de la facilité offerte par le statut de binôme, les personnes formant un binôme ne pourront pas<br />
bénéficier de congés maladie. Il sera alors possible de se désolidariser de son binôme en remplissant le formulaire adéquat sur l’espace membre afin d’en bénéficier.<br /> bénéficier de congés maladie. Il sera alors possible de se désolidariser de son binôme en remplissant le formulaire adéquat sur l’espace membre afin d’en bénéficier.<br />
...@@ -398,7 +415,9 @@ ...@@ -398,7 +415,9 @@
Nous avons créé des formulaires spécifiques pour la plupart des problèmes rencontrés par les membres. Changer de créneau, créer un binôme, ajouter un produit à la gamme, partir en vacances... <br /> Nous avons créé des formulaires spécifiques pour la plupart des problèmes rencontrés par les membres. Changer de créneau, créer un binôme, ajouter un produit à la gamme, partir en vacances... <br />
Cela dit, nous en découvrons de nouveaux tous les jours.<br /> Cela dit, nous en découvrons de nouveaux tous les jours.<br />
Si tu n'as pas su quel formulaire remplir, tu es au bon endroit. <br /> Si tu n'as pas su quel formulaire remplir, tu es au bon endroit. <br />
Vas-y dit nous tout !<br /> Vas-y dit nous tout !<br /><br />
Attention: si tu souhaites contacter le BDM pour prévenir que tu seras absent-e à ton service cela ne sert à rien! Il te faut déplacer ton service via ton espace membre. Il n'est cependant pas possible d'échanger un service qui commence dans moins de 24h pour des raisons de logistiques. Si tu ne peux pas venir tu seras donc comptabilisé-e absent-e. Tu basculeras en statut "Rattrapage" et ne pourras plus faire tes courses. Il te faudra sélectionner dans ton espace membre un rattrapage à faire dans les 6 prochains mois pour basculer en statut "Délai" et pouvoir faire de nouveau tes courses.<br />
Merci de ne pas contacter le Bureau des membres pour cela, il te donnera exactement la même réponse.<br />
<div class="faq_link_button_area"> <div class="faq_link_button_area">
<a <a
href="javascript:void(0);" href="javascript:void(0);"
......
<h3>Comment sont calculées les conso. moyennes / jour ?</h3>
<div class="txtleft">
<p>
La fonction qui calcule les consommations moyennes prend en paramètre une date de départ. <br/>
Si elle n'est pas indiquée, la date prise en compte sera "<em>aujourd'hui - nb de jours paramétré dans Odoo</em>".<br/>
Ce nombre de jours paramétrable se trouve en suivant les menus suivants :<br>
Configuration > Technique > Paramètres > Paramètres systèmes. <br/>
La valeur est définie avec la clef "<em>lacagette_products.nb_past_days_to_compute_sales_average</em>".<br/>
Ce paramètre vaut <strong>{{nb_past_days_to_compute_sales_average}}</strong> au moment du chargement de cette page.<br/>
Les ventes du dimanche sont exclues du calcul.<br/>
</p>
<p>
Une requête est faite sur l'ensemble des passages en caisse, de la date de départ à hier, récupérant pour tous les jours de la période (dimanches exclus) les quantités vendues des articles achetés chez le fournisseur.<br/>
Pour chaque article, le nombre de jours considérés pour faire la moyenne est défini commme suit :<br/>
<em>Nb de jours ouvrés de la période - Nb de jours des périodes de rupture</em><br/>
Les périodes de ruptures sont caractérisées par un nombre de jours consécutifs assez important de jours sans vente de l'article.<br/>
Le nombre de jours consécutifs sans vente pour considérer l'article en rupture est actuellement de <strong>{{nb_of_consecutive_non_sale_days_considered_as_break}}</strong><br/>
(c'est le paramètre système avec la clef "<em>lacagette_products.nb_of_consecutive_non_sale_days_considered_as_break</em>").<br/>
</p>
<p>
Pour chaque article, la consommation moyenne par jour est obtenue en divisant la quantité totale vendue sur la période par le nombre de jours significatifs.
</p>
</div>
...@@ -93,8 +93,13 @@ ...@@ -93,8 +93,13 @@
</select> </select>
</form> </form>
<form action="javascript:;" id="coverage_form" class="order_form_item"> <form action="javascript:;" id="coverage_form" class="order_form_item">
<input type="number" name="coverage_days" id="coverage_days_input" placeholder="Nb jours de couverture" min="1"> <div class="input-wrapper">
<button type="submit" class='btn--primary'>Calculer les besoins</button> <input type="number" name="coverage_days" id="coverage_days_input" placeholder="Nb jours de couverture" min="1">
<input type="number" name="percent_adjustement" id="percent_adjust_input" placeholder="ajustement en %">
</div>
<div>
<button type="submit" class='btn--primary'>Calculer les besoins</button> <i class='main fa fa-info-circle fa-lg'></i>
</div>
</form> </form>
</div> </div>
...@@ -270,14 +275,21 @@ ...@@ -270,14 +275,21 @@
<hr/> <hr/>
</div> </div>
<div id="modal_product_npa"> <div id="modal_product_actions">
<h3>Attention !</h3> Actions sur <h3><span class="product_name"></span></h3>
<p> <p>
Vous vous apprêtez à passer le produit <span class="product_name"></span> en <span class="product_npa"></span>.<br/> <h4>NPA</h4>
Dès que vous aurez cliqué sur "Valider", le produit sera retiré du tableau et l'information sera enregistrée dans Odoo. <div class="npa-options">
<label><input type="checkbox" name="npa-actions" value="simple-npa" /> Mettre le produit en NPA </label>
<label><input type="checkbox" name="npa-actions" value="npa-in-name" /> Mettre le produit en NPA et afficher NPA</label>
<label><input type="checkbox" name="npa-actions" value="fds-in-name" /> Mettre le produit en NPA et afficher FDS</label>
</div>
</p> </p>
<p>Êtez-vous sûr ?</p> <p>
<hr/> <h4>Stock minimum</h4>
<input type="number" name="minimal_stock" value="" />
</p>
</div> </div>
<div id="modal_create_order"> <div id="modal_create_order">
...@@ -294,7 +306,9 @@ ...@@ -294,7 +306,9 @@
<br/> <br/>
<hr/> <hr/>
</div> </div>
<div id="explanations">
{% include "orders/explanations.html" %}
</div>
<div id="modal_create_order__supplier_date_planned"> <div id="modal_create_order__supplier_date_planned">
<div class="modal_input_area"> <div class="modal_input_area">
<span class="modal_input_label supplier_name"></span> <span class="modal_input_label supplier_name"></span>
......
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