Commit 07250cbf by Damien Moulard

fix merge conflict with dev

parents 15c034c5 7a0149bc
Pipeline #1283 passed with stage
in 1 minute 39 seconds
## Add odoo user login button
- In the template, include "conn_admin.html" as following :
```
{% block content %}
{% include "common/conn_admin.html" %}
```
- In the JS code, use the following pattern (for example) :
```
$(document).ready(function() {
if (coop_is_connected()) {
show content
}
}
```
...@@ -97,7 +97,8 @@ SHIFT_INFO = """A la cagette, un service est une plage de trois heures un jour ...@@ -97,7 +97,8 @@ SHIFT_INFO = """A la cagette, un service est une plage de trois heures un jour
PB_INSTRUCTIONS = """Si j'ai un problème, que je suis désinscrit, que je veux changer de créneaux ou quoi que ce soit, merci de vous rendre dans la section \"J'ai un problème\" sur le site web de <a href=\"https://lacagette-coop.fr/?MonEspaceMembre\">La Cagette</a>""" PB_INSTRUCTIONS = """Si j'ai un problème, que je suis désinscrit, que je veux changer de créneaux ou quoi que ce soit, merci de vous rendre dans la section \"J'ai un problème\" sur le site web de <a href=\"https://lacagette-coop.fr/?MonEspaceMembre\">La Cagette</a>"""
ENTRANCE_COME_FOR_SHOPING_MSG = "Hey coucou toi ! Cet été nous sommes plus de <strong>1000 acheteur·euses</strong> pour seulement <strong>300 coopérateur·rice·s</strong> en service. <br />Tu fais tes courses à La Cagette cet été ?<br/> Inscris-toi sur ton espace membre !" ENTRANCE_COME_FOR_SHOPING_MSG = "Hey coucou toi ! Cet été nous sommes plus de <strong>1000 acheteur·euses</strong> pour seulement <strong>300 coopérateur·rice·s</strong> en service. <br />Tu fais tes courses à La Cagette cet été ?<br/> Inscris-toi sur ton espace membre !"
ENTRANCE_EXTRA_BUTTONS_DISPLAY = False
ENTRANCE_EASY_SHIFT_VALIDATE = True
# Members space / shifts # Members space / shifts
UNSUBSCRIBED_MSG = 'Vous êtes désincrit·e, merci de remplir <a href="https://docs.google.com/forms/d/e/1FAIpQLSfPiC2PkSem9x_B5M7LKpoFNLDIz0k0V5I2W3Mra9AnqnQunw/viewform">ce formulaire</a> pour vous réinscrire sur un créneau.<br />Vous pouvez également contacter le Bureau des Membres en remplissant <a href="https://docs.google.com/forms/d/e/1FAIpQLSeZP0m5-EXPVJxEKJk6EjwSyZJtnbiGdYDuAeFI3ENsHAOikg/viewform">ce formulaire</a>' UNSUBSCRIBED_MSG = 'Vous êtes désincrit·e, merci de remplir <a href="https://docs.google.com/forms/d/e/1FAIpQLSfPiC2PkSem9x_B5M7LKpoFNLDIz0k0V5I2W3Mra9AnqnQunw/viewform">ce formulaire</a> pour vous réinscrire sur un créneau.<br />Vous pouvez également contacter le Bureau des Membres en remplissant <a href="https://docs.google.com/forms/d/e/1FAIpQLSeZP0m5-EXPVJxEKJk6EjwSyZJtnbiGdYDuAeFI3ENsHAOikg/viewform">ce formulaire</a>'
CONFIRME_PRESENT_BTN = 'Clique ici pour valider ta présence' CONFIRME_PRESENT_BTN = 'Clique ici pour valider ta présence'
...@@ -111,3 +112,5 @@ DISPLAY_COL_AUTRES = False ...@@ -111,3 +112,5 @@ DISPLAY_COL_AUTRES = False
# Should block service exchange if old service is happening in less than 24h # Should block service exchange if old service is happening in less than 24h
BLOCK_SERVICE_EXCHANGE_24H_BEFORE = True BLOCK_SERVICE_EXCHANGE_24H_BEFORE = True
# URL to the metabase dashboard for orders helper
ORDERS_HELPER_METABASE_URL = "https://metabase.lacagette-coop.fr/dashboard/16"
...@@ -22,7 +22,7 @@ class CagetteMember(models.Model): ...@@ -22,7 +22,7 @@ class CagetteMember(models.Model):
"""Class to handle cagette Odoo member.""" """Class to handle cagette Odoo member."""
m_default_fields = ['name', 'sex', 'image_medium', 'active', m_default_fields = ['name', 'sex', 'image_medium', 'active',
'barcode_base', 'barcode', 'in_ftop_team', 'barcode_base', 'barcode', 'in_ftop_team',
'is_associated_people', 'is_member', 'is_associated_people', 'is_member', 'shift_type',
'display_ftop_points', 'display_std_points', 'display_ftop_points', 'display_std_points',
'is_exempted', 'cooperative_state', 'date_alert_stop'] 'is_exempted', 'cooperative_state', 'date_alert_stop']
...@@ -1176,6 +1176,44 @@ class CagetteServices(models.Model): ...@@ -1176,6 +1176,44 @@ class CagetteServices(models.Model):
result['service_found'] = False result['service_found'] = False
return result return result
@staticmethod
def easy_validate_shift_presence(coop_id):
"""Add a presence point if the request is valid."""
res = {}
try:
api = OdooAPI()
# let verify coop_id is corresponding to a ftop subscriber
cond = [['id', '=', coop_id]]
fields = ['shift_type']
coop = api.search_read('res.partner', cond, fields)
if coop:
if coop[0]['shift_type'] == 'ftop':
evt_name = getattr(settings, 'ENTRANCE_ADD_PT_EVENT_NAME', 'Validation service comité')
c = [['partner_id', '=', coop_id], ['name', '=', evt_name]]
f = ['create_date']
last_point_mvts = api.search_read('shift.counter.event', c, f,
order ="create_date DESC", limit=1)
ok_for_adding_pt = False
if len(last_point_mvts):
now = datetime.datetime.now()
past = datetime.datetime. strptime(last_point_mvts[0]['create_date'],
'%Y-%m-%d %H:%M:%S')
if (now - past).total_seconds() >= 3600 * 24:
ok_for_adding_pt = True
else:
ok_for_adding_pt = True
if ok_for_adding_pt is True:
res['evt_id'] = CagetteMember(coop_id).add_pts('ftop', 1, evt_name)
else:
res['error'] = "One point has been added less then 24 hours ago"
else:
res['error'] = "Unallowed coop"
else:
res['error'] = "Invalid coop id"
except Exception as e:
coop_logger.error("easy_validate_shift_presence : %s %s", str(coop_id), str(e))
return res
class CagetteUser(models.Model): class CagetteUser(models.Model):
@staticmethod @staticmethod
......
...@@ -58,7 +58,8 @@ h1 .member_name {font-weight: bold;} ...@@ -58,7 +58,8 @@ h1 .member_name {font-weight: bold;}
#member_slide .btn[data-next] #member_slide .btn[data-next]
{margin-top: 35px; background: #449d44; color:#FFF;} {margin-top: 35px; background: #449d44; color:#FFF;}
[data-next="rattrapage_1"] {float:right;color:#171A17 !important; margin-top: 25px;} [data-next="rattrapage_1"] {float:right;color:#171A17 !important; margin-top: 25px;}
#service_en_cours .info a {line-height: 24px; font-size:18px; margin-top:15px;} #service_en_cours .info {font-size: 26px;}
#service_en_cours .info a {line-height: 24px; font-size:14px; margin-top:15px;}
.outside_list a {margin-left:15px;} .outside_list a {margin-left:15px;}
#rattrapage_1 .advice {margin-top:15px;} #rattrapage_1 .advice {margin-top:15px;}
.btn.present {background:#50C878;} .btn.present {background:#50C878;}
...@@ -67,3 +68,5 @@ h1 .member_name {font-weight: bold;} ...@@ -67,3 +68,5 @@ h1 .member_name {font-weight: bold;}
.msg-big {font-size: xx-large; background: #fff; padding:25px; text-align: center;} .msg-big {font-size: xx-large; background: #fff; padding:25px; text-align: center;}
#member_advice {background: #FFF; color: red;} #member_advice {background: #FFF; color: red;}
.easy_shift_validate {text-align: center; margin-top: 3em;}
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
})(jQuery); })(jQuery);
var current_displayed_member = null, var current_displayed_member = null,
operator = null,
results = null, results = null,
loaded_services = null, loaded_services = null,
selected_service = null, selected_service = null,
...@@ -23,6 +24,8 @@ var current_displayed_member = null, ...@@ -23,6 +24,8 @@ var current_displayed_member = null,
rattrapage_ou_volant = null, rattrapage_ou_volant = null,
timeout_counter = null; timeout_counter = null;
var search_button = $('.btn--primary.search'); var search_button = $('.btn--primary.search');
var sm_search_member_button = $('#sm_search_member_button'),
sm_search_member_input = $('#sm_search_member_input');
var loading2 = $('.loading2'); var loading2 = $('.loading2');
var search_field = $('input[name="search_string"]'); var search_field = $('input[name="search_string"]');
var shift_title = $('#current_shift_title'); var shift_title = $('#current_shift_title');
...@@ -36,6 +39,8 @@ var photo_advice = $('#photo_advice'); ...@@ -36,6 +39,8 @@ var photo_advice = $('#photo_advice');
var photo_studio = $('#photo_studio'); var photo_studio = $('#photo_studio');
var coop_info = $('.coop-info'); var coop_info = $('.coop-info');
const missed_begin_msg = $('#missed_begin_msg').html();
let no_pict_msg = $('#no-picture-msg'); let no_pict_msg = $('#no-picture-msg');
var pages = { var pages = {
...@@ -162,6 +167,7 @@ function canSearch() { ...@@ -162,6 +167,7 @@ function canSearch() {
function search_member(force_search = false) { function search_member(force_search = false) {
chars = []; // to prevent false "as barcode-reader" input chars = []; // to prevent false "as barcode-reader" input
operator = null;
if (canSearch() || force_search) { if (canSearch() || force_search) {
html_elts.member_slide.hide(); html_elts.member_slide.hide();
...@@ -279,7 +285,15 @@ function fill_service_entry(s) { ...@@ -279,7 +285,15 @@ function fill_service_entry(s) {
rattrapage_wanted.show(); rattrapage_wanted.show();
} }
function clean_search_for_easy_validate_zone() {
$('.search_member_results_area').hide();
$('.search_member_results').empty();
sm_search_member_input.val('');
operator = null;
}
function clean_service_entry() { function clean_service_entry() {
clean_search_for_easy_validate_zone();
rattrapage_wanted.hide(); rattrapage_wanted.hide();
shift_title.text(''); shift_title.text('');
shift_members.html(''); shift_members.html('');
...@@ -346,8 +360,8 @@ function get_service_entry_data() { ...@@ -346,8 +360,8 @@ function get_service_entry_data() {
page_title.text('Qui es-tu ?'); page_title.text('Qui es-tu ?');
try { try {
if (rData.res.length == 0) { if (rData.res.length == 0) {
info_place.text('La période pendant laquelle il est possible de s\'enregistrer est close.'); info_place.html(missed_begin_msg);
page_title.text(''); page_title.html('');
} else { } else {
if (rData.res.length > 1) { if (rData.res.length > 1) {
...@@ -667,6 +681,73 @@ html_elts.image_medium.on('click', function() { ...@@ -667,6 +681,73 @@ html_elts.image_medium.on('click', function() {
} }
}); });
function ask_for_easy_shift_validation() {
//alert("operator = " + JSON.stringify(operator))
msg = "<p>Je suis bien " + operator.name + "<br/> et <br/>je valide mon service 'Comité' </p>";
openModal(msg, function() {
try {
post_form(
'/members/easy_validate_shift_presence',
{
coop_id: operator.id
},
function(err, result) {
if (!err) {
alert("1 point volant vient d'être ajouté.");
clean_search_for_easy_validate_zone();
closeModal();
} else {
if (typeof (err.responseJSON) != "undefined"
&& typeof (err.responseJSON.error) != "undefined") {
alert(err.responseJSON.error);
} else {
console.log(err);
}
}
}
);
} catch (e) {
console.log(e);
}
}, 'Confirmer');
}
// Display the members from the search result (copied from stock_movements)
function display_possible_members() {
$('.search_member_results_area').show();
$('.search_member_results').empty();
if (members_search_results.length > 0) {
for (member of members_search_results) {
let btn_classes = "btn";
if (operator != null && operator.id == member.id) {
btn_classes = "btn--success";
}
// Display results (possible members) as buttons
var member_button = '<button class="' + btn_classes + ' btn_member" member_id="'
+ member.id + '">'
+ member.barcode_base + ' - ' + member.name
+ '</button>';
$('.search_member_results').append(member_button);
// Set action on click on a member button
$('.btn_member').on('click', function() {
for (member of members_search_results) {
if (member.id == $(this).attr('member_id')) {
operator = member;
// Enable validation button when operator is selected
ask_for_easy_shift_validation();
break;
}
}
display_possible_members();
});
}
} else {
$('.search_member_results').html('<p><i>Aucun résultat ! Faites-vous partie d\'un comité ? <br/> Si oui, vérifiez la recherche..</i></p>');
}
}
$(document).ready(function() { $(document).ready(function() {
var shopping_entry_btn = $('a[data-next="shopping_entry"]'); var shopping_entry_btn = $('a[data-next="shopping_entry"]');
...@@ -708,6 +789,44 @@ $(document).ready(function() { ...@@ -708,6 +789,44 @@ $(document).ready(function() {
init_webcam(); init_webcam();
}); });
$('#sm_search_member_form').submit(function() {
if (is_time_to('search_member', 1000)) {
sm_search_member_button.empty().append(`<i class="fas fa-spinner fa-spin"></i>`);
let search_str = sm_search_member_input.val();
$.ajax({
url: '/members/search/' + search_str,
dataType : 'json',
success: function(data) {
members_search_results = [];
for (member of data.res) {
if (member.shift_type == 'ftop') {
members_search_results.push(member);
}
}
display_possible_members();
},
error: function(data) {
err = {
msg: "erreur serveur lors de la recherche de membres",
ctx: 'easy_validate.search_members'
};
report_JS_error(err, 'members');
$.notify("Erreur lors de la recherche de membre, il faut ré-essayer plus tard...", {
globalPosition:"top right",
className: "error"
});
},
complete: function() {
sm_search_member_button.empty().append(`Recherche`);
}
});
}
});
}); });
Webcam.on('live', function() { Webcam.on('live', function() {
......
...@@ -46,6 +46,7 @@ urlpatterns = [ ...@@ -46,6 +46,7 @@ urlpatterns = [
url(r'^remove_data_from_couchdb$', views.remove_data_from_CouchDB), url(r'^remove_data_from_couchdb$', views.remove_data_from_CouchDB),
url(r'^image/([0-9]+)', views.getmemberimage), url(r'^image/([0-9]+)', views.getmemberimage),
url(r'^add_pts_to_everybody/([0-9]+)/([a-zA-Z0-9_ ]+)$', admin.add_pts_to_everybody), url(r'^add_pts_to_everybody/([0-9]+)/([a-zA-Z0-9_ ]+)$', admin.add_pts_to_everybody),
url(r'^easy_validate_shift_presence$', views.easy_validate_shift_presence),
# conso / groupe recherche / socio # conso / groupe recherche / socio
url(r'^panel_get_purchases$', views.panel_get_purchases), url(r'^panel_get_purchases$', views.panel_get_purchases),
] ]
...@@ -24,6 +24,10 @@ def index(request): ...@@ -24,6 +24,10 @@ def index(request):
'WELCOME_SUBTITLE_ENTRANCE_MSG': getattr(settings, 'WELCOME_SUBTITLE_ENTRANCE_MSG', ''), 'WELCOME_SUBTITLE_ENTRANCE_MSG': getattr(settings, 'WELCOME_SUBTITLE_ENTRANCE_MSG', ''),
'ENTRANCE_SHOPPING_BTN': getattr(settings, 'ENTRANCE_SHOPPING_BTN', 'Je viens faire mes courses'), 'ENTRANCE_SHOPPING_BTN': getattr(settings, 'ENTRANCE_SHOPPING_BTN', 'Je viens faire mes courses'),
'ENTRANCE_SERVICE_BTN': getattr(settings, 'ENTRANCE_SERVICE_BTN', 'Je viens faire mon service'), 'ENTRANCE_SERVICE_BTN': getattr(settings, 'ENTRANCE_SERVICE_BTN', 'Je viens faire mon service'),
'ENTRANCE_MISSED_SHIFT_BEGIN_MSG': getattr(settings, 'ENTRANCE_MISSED_SHIFT_BEGIN_MSG',
"La période pendant laquelle il est possible de s'enregistrer est close."),
'ENTRANCE_EASY_SHIFT_VALIDATE_MSG': getattr(settings, 'ENTRANCE_EASY_SHIFT_VALIDATE_MSG',
'Je valide mon service "Comité"'),
'CONFIRME_PRESENT_BTN' : getattr(settings, 'CONFIRME_PRESENT_BTN', 'Présent.e') 'CONFIRME_PRESENT_BTN' : getattr(settings, 'CONFIRME_PRESENT_BTN', 'Présent.e')
} }
for_shoping_msg = getattr(settings, 'ENTRANCE_COME_FOR_SHOPING_MSG', '') for_shoping_msg = getattr(settings, 'ENTRANCE_COME_FOR_SHOPING_MSG', '')
...@@ -33,6 +37,8 @@ def index(request): ...@@ -33,6 +37,8 @@ def index(request):
for_shoping_msg = msettings['msg_accueil']['value'] for_shoping_msg = msettings['msg_accueil']['value']
context['ENTRANCE_COME_FOR_SHOPING_MSG'] = for_shoping_msg context['ENTRANCE_COME_FOR_SHOPING_MSG'] = for_shoping_msg
context['ftop_btn_display'] = getattr(settings, 'ENTRANCE_FTOP_BUTTON_DISPLAY', True) context['ftop_btn_display'] = getattr(settings, 'ENTRANCE_FTOP_BUTTON_DISPLAY', True)
context['extra_btns_display'] = getattr(settings, 'ENTRANCE_EXTRA_BUTTONS_DISPLAY', True)
context['easy_shift_validate'] = getattr(settings, 'ENTRANCE_EASY_SHIFT_VALIDATE', False)
if 'no_picture_member_advice' in msettings: if 'no_picture_member_advice' in msettings:
if len(msettings['no_picture_member_advice']['value']) > 0: if len(msettings['no_picture_member_advice']['value']) > 0:
context['no_picture_member_advice'] = msettings['no_picture_member_advice']['value'] context['no_picture_member_advice'] = msettings['no_picture_member_advice']['value']
...@@ -283,6 +289,22 @@ def record_service_presence(request): ...@@ -283,6 +289,22 @@ def record_service_presence(request):
res['error'] = str(e) res['error'] = str(e)
return JsonResponse({'res': res}) return JsonResponse({'res': res})
def easy_validate_shift_presence(request):
"""Add a presence point if the request is valid."""
res = {}
try:
coop_id = int(request.POST.get("coop_id", "nan"))
res = CagetteServices.easy_validate_shift_presence(coop_id)
except Exception as e:
res['error'] = str(e)
if 'error' in res:
if res['error'] == "One point has been added less then 24 hours ago":
# TODO : use translation (all project wide)
res['error'] = "Vous ne pouvez pas valider plus d'un service par 24h"
return JsonResponse(res, status=500)
else:
return JsonResponse(res, safe=False)
def record_absences(request): def record_absences(request):
return JsonResponse({'res': CagetteServices.record_absences()}) return JsonResponse({'res': CagetteServices.record_absences()})
......
...@@ -258,6 +258,11 @@ class Order(models.Model): ...@@ -258,6 +258,11 @@ class Order(models.Model):
} }
for line in order_lines: for line in order_lines:
product_line_name = line["name"]
if "product_code" in line and line["product_code"] is not False:
product_code = str(line["product_code"])
product_line_name = "[" + product_code + "] " + product_line_name
order_data["order_line"].append( order_data["order_line"].append(
[ [
0, 0,
...@@ -267,7 +272,7 @@ class Order(models.Model): ...@@ -267,7 +272,7 @@ class Order(models.Model):
"price_policy": "uom", "price_policy": "uom",
"indicative_package": True, "indicative_package": True,
"product_id": line["product_variant_ids"][0], "product_id": line["product_variant_ids"][0],
"name": line["name"], "name": product_line_name,
"date_planned": date_planned, "date_planned": date_planned,
"account_analytic_id": False, "account_analytic_id": False,
"product_qty_package":line["product_qty_package"], "product_qty_package":line["product_qty_package"],
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
position: relative; position: relative;
} }
.page_content { .page_content, .login_area {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
padding: 5px 30px 5px 30px; padding: 7px 30px 7px 30px;
margin: 0 10px 5px 10px; margin: 0 10px 5px 10px;
} }
...@@ -31,6 +31,26 @@ ...@@ -31,6 +31,26 @@
background-color: #a1a2a3; background-color: #a1a2a3;
} }
.link_as_button:hover {
text-decoration: none;
color: white;
}
.link_as_button:active {
text-decoration: none;
color: white;
}
.link_as_button:focus {
text-decoration: none;
color: white;
}
.remove_order_modal_text {
font-size: 2rem;
}
.remove_order_name {
font-weight: bold;
}
/* - Order selection screen */ /* - Order selection screen */
#new_order_area { #new_order_area {
margin-bottom: 40px; margin-bottom: 40px;
...@@ -50,6 +70,25 @@ ...@@ -50,6 +70,25 @@
padding-top: 15px; padding-top: 15px;
} }
.order_pill {
flex-direction: row;
}
.pill_order_name {
flex: 3 0 auto;
}
.remove_order_icon {
flex: 0 1 auto;
color: #912930;
margin-left: 5px;
cursor: pointer;
z-index: 2;
transform: scale(1.2);
}
.remove_order_icon:hover {
color: #e62720;
}
.order_last_update { .order_last_update {
font-weight: bold; font-weight: bold;
} }
...@@ -82,6 +121,40 @@ ...@@ -82,6 +121,40 @@
justify-content: flex-start; justify-content: flex-start;
} }
.right_action_buttons {
display: flex;
}
#actions_buttons_wrapper {
position: relative;
margin-right: 5px;
}
#toggle_action_buttons {
width: 250px;
position: relative;
}
.toggle_action_buttons_icon {
position: absolute;
top: 50%;
transform: translateY(-50%);
right: 15px;
}
#actions_buttons_container {
position: absolute;
display: flex;
flex-direction: column;
width: 250px;
display: none;
}
.action_button {
width: 100%;
min-height: 45px;
}
/* -- Order data */ /* -- Order data */
#order_data_container { #order_data_container {
font-size: 1.8rem; font-size: 1.8rem;
...@@ -92,17 +165,27 @@ ...@@ -92,17 +165,27 @@
} }
#order_forms_container { #order_forms_container {
margin-top: 30px; margin-top: 20px;
display: flex; display: flex;
flex-wrap: wrap;
justify-content: space-evenly; justify-content: space-evenly;
} }
.order_form_item {
margin-top: 10px;
}
#supplier_input { #supplier_input {
width: 350px; width: 350px;
border-radius: 3px; border-radius: 3px;
} }
#date_planned_input, #coverage_days_input { #stats_date_period_select {
margin-left: 5px;
min-width: 200px;
}
#date_planned_input, #coverage_days_input, #stats_date_period_select {
border-radius: 3px; border-radius: 3px;
} }
...@@ -144,13 +227,26 @@ ...@@ -144,13 +227,26 @@
width: 100px; width: 100px;
} }
.product_ref_input {
padding: .5rem .5rem;
}
.supplier_package_qty { .supplier_package_qty {
font-style: italic; font-style: italic;
font-size: 1.3rem; font-size: 1.3rem;
} }
.product_not_from_supplier { .product_not_from_supplier {
background-color: #e7e9ed; background-color: #e8ebf0;
cursor: pointer;
}
.product_not_from_supplier:hover {
background-color: #d3d7db;
}
.product_ref_cell:hover {
background-color: #d3d7db;
cursor: pointer; cursor: pointer;
} }
...@@ -162,6 +258,18 @@ ...@@ -162,6 +258,18 @@
cursor: pointer; cursor: pointer;
} }
.focused_line {
background-color: #76cf71 !important;
}
.dataTables_scrollHead {
position: sticky !important;
position: -webkit-sticky !important;
top: 0;
z-index: 3;
background-color: white;
}
/* -- Footer */ /* -- Footer */
#main_content_footer { #main_content_footer {
...@@ -181,19 +289,24 @@ ...@@ -181,19 +289,24 @@
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
margin: 30px 0 20px 0; margin: 30px 0 20px 0;
position: -webkit-sticky;
position: sticky;
top: 140px;
z-index: 5;
} }
.supplier_pill { .supplier_pill {
background-color: #e7e9edc5; background-color: #a0daff;
border: 1px solid black; border: 1px solid #6ea8cc;
} }
.pill_supplier_name { .pill_supplier_name {
font-weight: bold; font-weight: bold;
} }
.supplier_total_value_container { .supplier_data {
font-size: 1.5rem; font-size: 1.5rem;
display: flex;
} }
.remove_supplier_icon { .remove_supplier_icon {
...@@ -249,19 +362,6 @@ ...@@ -249,19 +362,6 @@
margin-top: 10px; margin-top: 10px;
} }
.download_order_file_button:hover {
text-decoration: none;
color: white;
}
.download_order_file_button:active {
text-decoration: none;
color: white;
}
.download_order_file_button:focus {
text-decoration: none;
color: white;
}
#recap_delivery_date { #recap_delivery_date {
font-weight: bold; font-weight: bold;
} }
......
...@@ -16,6 +16,7 @@ var dbc = null, ...@@ -16,6 +16,7 @@ var dbc = null,
order_doc = { order_doc = {
_id: null, _id: null,
coverage_days: null, coverage_days: null,
stats_date_period: '',
last_update: { last_update: {
timestamp: null, timestamp: null,
fingerprint: null fingerprint: null
...@@ -42,6 +43,7 @@ function reset_data() { ...@@ -42,6 +43,7 @@ function reset_data() {
order_doc = { order_doc = {
_id: null, _id: null,
coverage_days: null, coverage_days: null,
stats_date_period: '',
last_update : { last_update : {
timestamp: null, timestamp: null,
fingerprint: null fingerprint: null
...@@ -81,6 +83,39 @@ function dates_diff(date1, date2) { ...@@ -81,6 +83,39 @@ function dates_diff(date1, date2) {
return diff; return diff;
} }
/**
* Compute the date from which to calculate stats of sells,
* depending on the selected parameter.
*
* @returns String value of the date, ISO format
*/
function _compute_stats_date_from() {
let val = '';
if (order_doc.stats_date_period !== '') {
let date = new Date();
switch (order_doc.stats_date_period) {
case '1week':
date.setDate(date.getDate() - 7);
break;
case '2weeks':
date.setDate(date.getDate() - 14);
break;
default:
break;
}
let day = ("0" + date.getDate()).slice(-2);
let month = ("0" + (date.getMonth() +1)).slice(-2);
let year = date.getFullYear();
val = `${year}-${month}-${day}`;
}
return val;
}
/* - PRODUCTS */ /* - PRODUCTS */
/** /**
...@@ -110,15 +145,21 @@ function add_product() { ...@@ -110,15 +145,21 @@ function add_product() {
return -1; return -1;
} }
let data = {
pids: [product.tpl_id],
stats_from: _compute_stats_date_from()
};
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: '/products/get_product_for_order_helper', url: '/products/get_product_for_order_helper',
data: JSON.stringify([product.tpl_id]), data: JSON.stringify(data),
dataType:"json", dataType:"json",
traditional: true, traditional: true,
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
success: function(data) { success: function(data) {
let res = data.products[0]; let res = data.products[0];
if (typeof res.id != "undefined") { if (typeof res.id != "undefined") {
res.suppliersinfo = []; res.suppliersinfo = [];
res.default_code = ' '; res.default_code = ' ';
...@@ -148,6 +189,7 @@ function add_product() { ...@@ -148,6 +189,7 @@ 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() {
if (order_doc.coverage_days != null) {
for (const [ for (const [
key, key,
product product
...@@ -173,6 +215,7 @@ function compute_products_coverage_qties() { ...@@ -173,6 +215,7 @@ function compute_products_coverage_qties() {
products[key].suppliersinfo[0].qty = purchase_package_qty_for_coverage; products[key].suppliersinfo[0].qty = purchase_package_qty_for_coverage;
} }
} }
}
} }
/** /**
...@@ -187,17 +230,21 @@ function check_products_data() { ...@@ -187,17 +230,21 @@ function check_products_data() {
"Vérfication des informations produits...", "Vérfication des informations produits...",
{ {
globalPosition:"top left", globalPosition:"top left",
className: "warning" className: "info"
} }
); );
clicked_order_pill.find('.pill_order_name').empty().append(`<i class="fas fa-spinner fa-spin"></i>`); if (clicked_order_pill != null) {
clicked_order_pill.find('.pill_order_name').empty()
.append(`<i class="fas fa-spinner fa-spin"></i>`);
}
$.ajax({ $.ajax({
type: 'GET', type: 'GET',
url: '/orders/get_supplier_products', url: '/orders/get_supplier_products',
data: { data: {
sids: suppliers_id sids: suppliers_id,
stats_from: _compute_stats_date_from()
}, },
dataType:"json", dataType:"json",
traditional: true, traditional: true,
...@@ -206,19 +253,24 @@ function check_products_data() { ...@@ -206,19 +253,24 @@ function check_products_data() {
for (let product of data.res.products) { for (let product of data.res.products) {
const p_index = products.findIndex(p => p.id == product.id); const p_index = products.findIndex(p => p.id == product.id);
// Override products data with new data (without suppliersinfo so we don't override qty) if (p_index === -1) {
const updated_suppliersinfo = product.suppliersinfo; // Add product if it wasn't fetched before (made available since last access to order)
delete product.suppliersinfo; products.push(product);
products[p_index] = { ...products[p_index], ...product }; } else {
// Save old product suppliersinfo to keep user qty inputs
const old_suppliersinfo = [...products[p_index].suppliersinfo];
// Update product data
products[p_index] = product;
// Update suppliers info // Re-set qties
for (let psi_index in products[p_index].suppliersinfo) { for (let psi_index in products[p_index].suppliersinfo) {
const updated_psi = updated_suppliersinfo.find(psi => psi.supplier_id == products[p_index].suppliersinfo[psi_index].supplier_id); const old_psi = old_suppliersinfo.find(psi => psi.supplier_id == products[p_index].suppliersinfo[psi_index].supplier_id);
if (updated_psi !== undefined) {
products[p_index].suppliersinfo[psi_index].package_qty = updated_psi.package_qty;
products[p_index].suppliersinfo[psi_index].price = updated_psi.price;
}
if (old_psi !== undefined && old_psi.qty !== undefined) {
products[p_index].suppliersinfo[psi_index].qty = old_psi.qty;
}
}
} }
} }
...@@ -244,6 +296,73 @@ function check_products_data() { ...@@ -244,6 +296,73 @@ function check_products_data() {
}); });
} }
/**
* Update the product internal reference ('default_code')
*
* @param {HTMLElement} input_el
* @param {int} p_id
* @param {int} p_index
*/
function update_product_ref(input_el, p_id, p_index) {
const val = $(input_el).val();
const existing_val = products[p_index].default_code.replace("[input]", "");
products[p_index].default_code = val;
const row = $(input_el).closest('tr');
const new_row_data = prepare_datatable_data([p_id])[0];
products_table.row(row).data(new_row_data)
.draw();
$('#products_table')
.off('blur', 'tbody .product_ref_input')
.off('keypress', 'tbody .product_ref_input');
// Update in backend if value changed
if (existing_val !== val) {
const data = {
'product_tmpl_id': p_id,
'default_code': val
};
// Send request to create association
$.ajax({
type: "POST",
url: "/products/update_product_internal_ref",
dataType: "json",
traditional: true,
contentType: "application/json; charset=utf-8",
data: JSON.stringify(data),
success: () => {
update_cdb_order();
$(".actions_buttons_area .right_action_buttons").notify(
"Référence sauvegardée !",
{
elementPosition:"bottom right",
className: "success",
arrowShow: false
}
);
},
error: function(data) {
let msg = "erreur serveur lors de la sauvegarde de la référence";
msg += ` (product_tmpl_id: ${product.id}`;
err = {msg: msg, ctx: 'update_product_ref'};
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error;
}
report_JS_error(err, 'orders');
alert('Erreur lors de la sauvegarde de la référence dans Odoo. Veuillez recharger la page et ré-essayer plus tard.');
}
});
}
}
/* - SUPPLIERS */ /* - SUPPLIERS */
...@@ -255,9 +374,9 @@ function check_products_data() { ...@@ -255,9 +374,9 @@ function check_products_data() {
function add_supplier() { function add_supplier() {
const user_input = $("#supplier_input").val(); const user_input = $("#supplier_input").val();
// Check if user input is a valid supplier
let supplier = suppliers_list.find(s => s.display_name === user_input); let supplier = suppliers_list.find(s => s.display_name === user_input);
// Check if user input is a valid supplier
if (supplier === undefined) { if (supplier === undefined) {
alert("Le fournisseur renseigné n'est pas valide.\n" alert("Le fournisseur renseigné n'est pas valide.\n"
+ "Veuillez sélectionner un fournisseur dans la liste déroulante."); + "Veuillez sélectionner un fournisseur dans la liste déroulante.");
...@@ -275,20 +394,22 @@ function add_supplier() { ...@@ -275,20 +394,22 @@ function add_supplier() {
openModal(); openModal();
supplier.total_value = 0;
selected_suppliers.push(supplier);
let url = "/orders/get_supplier_products";
url += "?sids=" + encodeURIComponent(supplier.id);
// Fetch supplier products // Fetch supplier products
$.ajax({ $.ajax({
type: 'GET', type: 'GET',
url: url, url: "/orders/get_supplier_products",
data: {
sids: [supplier.id],
stats_from: _compute_stats_date_from()
},
dataType:"json", dataType:"json",
traditional: true, traditional: true,
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
success: function(data) { success: function(data) {
supplier.total_value = 0;
supplier.total_packages = 0;
selected_suppliers.push(supplier);
save_supplier_products(supplier, data.res.products); save_supplier_products(supplier, data.res.products);
update_main_screen(); update_main_screen();
$("#supplier_input").val(""); $("#supplier_input").val("");
...@@ -380,6 +501,7 @@ function save_supplier_product_association(product, supplier, cell) { ...@@ -380,6 +501,7 @@ function save_supplier_product_association(product, supplier, cell) {
product.suppliersinfo.push({ product.suppliersinfo.push({
supplier_id: supplier.id, supplier_id: supplier.id,
package_qty: package_qty, package_qty: package_qty,
product_code: false,
price: price price: price
}); });
...@@ -398,7 +520,8 @@ function save_supplier_product_association(product, supplier, cell) { ...@@ -398,7 +520,8 @@ function save_supplier_product_association(product, supplier, cell) {
closeModal(); closeModal();
}, },
error: function(data) { error: function(data) {
let msg = "erreur serveur lors de la sauvegarde de l'association product/supplier". let msg = "erreur serveur lors de la sauvegarde de l'association product/supplier";
msg += ` (product_tmpl_id: ${product.id}; supplier_id: ${supplier.id})`; msg += ` (product_tmpl_id: ${product.id}; supplier_id: ${supplier.id})`;
err = {msg: msg, ctx: 'save_supplier_product_association'}; err = {msg: msg, ctx: 'save_supplier_product_association'};
...@@ -416,6 +539,59 @@ function save_supplier_product_association(product, supplier, cell) { ...@@ -416,6 +539,59 @@ function save_supplier_product_association(product, supplier, cell) {
} }
/** /**
* Send to server the deletion of association product-supplier
*
* @param {object} product
* @param {object} supplier
*/
function end_supplier_product_association(product, supplier) {
openModal();
const data = {
product_tmpl_id: product.id,
supplier_id: supplier.id
};
// Send request to create association
$.ajax({
type: "POST",
url: "/orders/end_supplier_product_association",
dataType: "json",
traditional: true,
contentType: "application/json; charset=utf-8",
data: JSON.stringify(data),
success: () => {
// Remove relation locally
let p_index = products.findIndex(p => p.id == product.id);
let psi_index = product.suppliersinfo.findIndex(psi => psi.supplier_id == supplier.id);
products[p_index].suppliersinfo.splice(psi_index, 1);
// Update table
display_products();
update_cdb_order();
closeModal();
},
error: function(data) {
let msg = "erreur serveur lors de la suppression de l'association product/supplier".
msg += ` (product_tmpl_id: ${product.id}; supplier_id: ${supplier.id})`;
err = {msg: msg, ctx: 'end_supplier_product_association'};
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error;
}
report_JS_error(err, 'orders');
closeModal();
alert('Erreur lors de la suppression de l\'association. Veuillez ré-essayer plus tard.');
}
});
return 0;
}
/**
* When products are fetched, save them and the relation with the supplier. * When products are fetched, save them and the relation with the supplier.
* If product already saved, add the supplier to its suppliers list. * If product already saved, add the supplier to its suppliers list.
* Else, add product with supplier. * Else, add product with supplier.
...@@ -430,11 +606,13 @@ function save_supplier_products(supplier, new_products) { ...@@ -430,11 +606,13 @@ function save_supplier_products(supplier, new_products) {
if (index === -1) { if (index === -1) {
products.push(np); products.push(np);
} else { } else {
// Prevent adding ducplicate supplierinfo // Prevent adding duplicate supplierinfo
let index_existing_supplierinfo = products[index].suppliersinfo.findIndex(psi => psi.supplier_id == supplier.id); let index_existing_supplierinfo = products[index].suppliersinfo.findIndex(psi => psi.supplier_id == supplier.id);
if (index_existing_supplierinfo === -1) { if (index_existing_supplierinfo === -1) {
np_supplierinfo = np.suppliersinfo[0]; // Find the right supplierinfo in new product
let np_supplierinfo = np.suppliersinfo.find(psi => psi.supplier_id == supplier.id);
products[index].suppliersinfo.push(np_supplierinfo); products[index].suppliersinfo.push(np_supplierinfo);
} }
} }
...@@ -479,14 +657,20 @@ function _compute_total_values_by_supplier() { ...@@ -479,14 +657,20 @@ function _compute_total_values_by_supplier() {
// Reinit // Reinit
for (let s of selected_suppliers) { for (let s of selected_suppliers) {
s.total_value = 0; s.total_value = 0;
s.total_packages = 0;
} }
for (let p of products) { for (let p of products) {
for (let supinfo of p.suppliersinfo) { for (let supinfo of p.suppliersinfo) {
let supplier_index = selected_suppliers.findIndex(s => s.id == supinfo.supplier_id); let supplier_index = selected_suppliers.findIndex(s => s.id == supinfo.supplier_id);
// Value
let product_supplier_value = ('qty' in supinfo) ? supinfo.qty * supinfo.package_qty * supinfo.price : 0; let product_supplier_value = ('qty' in supinfo) ? supinfo.qty * supinfo.package_qty * supinfo.price : 0;
selected_suppliers[supplier_index].total_value += product_supplier_value; selected_suppliers[supplier_index].total_value += product_supplier_value;
// Packages
selected_suppliers[supplier_index].total_packages += ('qty' in supinfo) ? supinfo.qty : 0;
} }
} }
} }
...@@ -520,11 +704,12 @@ function set_product_npa(p_id, npa) { ...@@ -520,11 +704,12 @@ function set_product_npa(p_id, npa) {
// Give time for modal to fade // Give time for modal to fade
setTimeout(function() { setTimeout(function() {
$.notify( $(".actions_buttons_area .right_action_buttons").notify(
"Produit passé en NPA !", "Produit passé en NPA !",
{ {
globalPosition:"top right", elementPosition:"bottom right",
className: "success" className: "success",
arrowShow: false
} }
); );
}, 500); }, 500);
...@@ -590,7 +775,8 @@ function generate_inventory() { ...@@ -590,7 +775,8 @@ function generate_inventory() {
modal_create_inventory.html(), modal_create_inventory.html(),
() => { () => {
if (is_time_to('validate_generate_inventory')) { if (is_time_to('validate_generate_inventory')) {
$('#do_inventory').empty().append(`<i class="fas fa-spinner fa-spin"></i>`); $('#toggle_action_buttons .button_content').empty()
.append(`<i class="fas fa-spinner fa-spin"></i>`);
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: "/inventory/generate_inventory_list", url: "/inventory/generate_inventory_list",
...@@ -603,18 +789,20 @@ function generate_inventory() { ...@@ -603,18 +789,20 @@ function generate_inventory() {
// Give time for modal to fade // Give time for modal to fade
setTimeout(function() { setTimeout(function() {
$('#do_inventory').empty().append(`Faire un inventaire`); $('#toggle_action_buttons .button_content').empty()
$('#do_inventory').notify( .append(`Actions`);
$('#toggle_action_buttons').notify(
"Inventaire créé !", "Inventaire créé !",
{ {
globalPosition:"bottom center", elementPosition:"bottom center",
className: "success" className: "success"
} }
); );
}, 200); }, 200);
}, },
error: function(data) { error: function(data) {
$('#do_inventory').empty().append(`Faire un inventaire`); $('#do_inventory').empty()
.append(`Faire un inventaire`);
let msg = "erreur serveur lors de la création de l'inventaire". let msg = "erreur serveur lors de la création de l'inventaire".
err = {msg: msg, ctx: 'generate_inventory'}; err = {msg: msg, ctx: 'generate_inventory'};
...@@ -724,6 +912,8 @@ function create_cdb_order() { ...@@ -724,6 +912,8 @@ function create_cdb_order() {
/** /**
* Update order data of an existing order in couchdb * Update order data of an existing order in couchdb
*
* @returns Promise resolved after update is complete
*/ */
function update_cdb_order() { function update_cdb_order() {
order_doc.products = products; order_doc.products = products;
...@@ -752,6 +942,28 @@ function update_cdb_order() { ...@@ -752,6 +942,28 @@ function update_cdb_order() {
} }
/** /**
* Delete an order in couchdb.
*
* @returns Promise resolved after delete is complete
*/
function delete_cdb_order() {
order_doc._deleted = true;
return new Promise((resolve, reject) => {
dbc.put(order_doc, function callback(err, result) {
if (!err && result !== undefined) {
resolve();
} else {
alert("Erreur lors de la suppression de la commande... Si l'erreur persiste contactez un administrateur svp.");
console.log(err);
reject(new Error("Error while deleting order"));
}
});
});
}
/**
* Create the Product Orders in Odoo * Create the Product Orders in Odoo
*/ */
function create_orders() { function create_orders() {
...@@ -778,17 +990,19 @@ function create_orders() { ...@@ -778,17 +990,19 @@ function create_orders() {
} else { } else {
// Default date : tomorrow // Default date : tomorrow
let date_object = new Date(); let date_object = new Date();
date_object.setDate(date_object.getDate() + 1); date_object.setDate(date_object.getDate() + 1);
// Get ISO format bare string // Get ISO format bare string
formatted_date = date_object.toISOString().replace('T', ' ').split('.')[0]; formatted_date = date_object.toISOString().replace('T', ' ')
.split('.')[0];
} }
// Create an entry for this supplier // Create an entry for this supplier
orders_data.suppliers_data[supplier.id] = { orders_data.suppliers_data[supplier.id] = {
date_planned: formatted_date, date_planned: formatted_date,
lines: [] lines: []
} };
} }
openModal(); openModal();
...@@ -799,6 +1013,7 @@ function create_orders() { ...@@ -799,6 +1013,7 @@ function create_orders() {
// If a qty is set for a supplier for a product // If a qty is set for a supplier for a product
if ('qty' in p_supplierinfo && p_supplierinfo.qty != 0) { if ('qty' in p_supplierinfo && p_supplierinfo.qty != 0) {
const supplier_id = p_supplierinfo.supplier_id; const supplier_id = p_supplierinfo.supplier_id;
const product_code = p_supplierinfo.product_code;
orders_data.suppliers_data[supplier_id].lines.push({ orders_data.suppliers_data[supplier_id].lines.push({
'package_qty': p_supplierinfo.package_qty, 'package_qty': p_supplierinfo.package_qty,
...@@ -809,7 +1024,8 @@ function create_orders() { ...@@ -809,7 +1024,8 @@ function create_orders() {
'product_uom': p.uom_id[0], 'product_uom': p.uom_id[0],
'price_unit': p_supplierinfo.price, 'price_unit': p_supplierinfo.price,
'supplier_taxes_id': p.supplier_taxes_id, 'supplier_taxes_id': p.supplier_taxes_id,
'product_variant_ids': p.product_variant_ids 'product_variant_ids': p.product_variant_ids,
'product_code': product_code
}); });
} }
} }
...@@ -823,6 +1039,8 @@ function create_orders() { ...@@ -823,6 +1039,8 @@ function create_orders() {
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
data: JSON.stringify(orders_data), data: JSON.stringify(orders_data),
success: (result) => { success: (result) => {
$('#created_orders_area').empty();
// Display new orders // Display new orders
for (let new_order of result.res.created) { for (let new_order of result.res.created) {
const supplier_name = suppliers_list.find(s => s.id == new_order.supplier_id).display_name; const supplier_name = suppliers_list.find(s => s.id == new_order.supplier_id).display_name;
...@@ -853,13 +1071,14 @@ function create_orders() { ...@@ -853,13 +1071,14 @@ function create_orders() {
get_order_attachments(); get_order_attachments();
// Clear data // Clear data
order_doc._deleted = true; delete_cdb_order().finally(() => {
update_cdb_order().then(() => { // Continue with workflow anyway
update_order_selection_screen(); update_order_selection_screen().then(() => {
})
reset_data(); reset_data();
switch_screen('orders_created'); switch_screen('orders_created');
closeModal(); closeModal();
});
});
}, },
error: function(data) { error: function(data) {
let msg = "erreur serveur lors de la création des product orders"; let msg = "erreur serveur lors de la création des product orders";
...@@ -932,7 +1151,7 @@ function goto_main_screen(doc) { ...@@ -932,7 +1151,7 @@ function goto_main_screen(doc) {
update_cdb_order(); update_cdb_order();
update_main_screen(); update_main_screen();
switch_screen(); switch_screen();
}) });
} }
function back() { function back() {
...@@ -978,6 +1197,7 @@ function display_suppliers() { ...@@ -978,6 +1197,7 @@ function display_suppliers() {
const clicked_supplier = selected_suppliers.find(s => s.id == supplier_id); const clicked_supplier = selected_suppliers.find(s => s.id == supplier_id);
let modal_remove_supplier = $('#templates #modal_remove_supplier'); let modal_remove_supplier = $('#templates #modal_remove_supplier');
modal_remove_supplier.find(".supplier_name").text(clicked_supplier.display_name); modal_remove_supplier.find(".supplier_name").text(clicked_supplier.display_name);
openModal( openModal(
...@@ -1032,25 +1252,21 @@ function _compute_product_data(product) { ...@@ -1032,25 +1252,21 @@ function _compute_product_data(product) {
} }
/* Coverage related data */ /* Coverage related data */
if (order_doc.coverage_days !== null) { const coverage_days = (order_doc.coverage_days !== null) ? order_doc.coverage_days : 0;
let qty_not_covered = 0; let qty_not_covered = 0;
let days_covered = 0; let days_covered = 0;
if (product.daily_conso !== 0) {
qty_not_covered = product.daily_conso * order_doc.coverage_days - product.qty_available - product.incoming_qty - purchase_qty;
days_covered = qty_not_covered / product.daily_conso;
qty_not_covered = -Math.ceil(qty_not_covered); // round up, so if a value is not fully covered display it if (product.daily_conso !== 0) {
qty_not_covered = product.daily_conso * coverage_days - product.qty_available - product.incoming_qty - purchase_qty;
qty_not_covered = -Math.ceil(qty_not_covered); // round up: display values that are not fully covered
qty_not_covered = (qty_not_covered > 0) ? 0 : qty_not_covered; // only display qty not covered (neg value) qty_not_covered = (qty_not_covered > 0) ? 0 : qty_not_covered; // only display qty not covered (neg value)
days_covered = -Math.ceil(days_covered); days_covered = (product.qty_available + product.incoming_qty + purchase_qty) / product.daily_conso;
days_covered = Math.floor(days_covered);
} }
item.qty_not_covered = qty_not_covered; item.qty_not_covered = qty_not_covered;
item.days_covered = days_covered; item.days_covered = days_covered;
} else {
item.qty_not_covered = 'X';
item.days_covered = 'X';
}
return item; return item;
} }
...@@ -1114,9 +1330,20 @@ function prepare_datatable_columns() { ...@@ -1114,9 +1330,20 @@ function prepare_datatable_columns() {
{ {
data: "default_code", data: "default_code",
title: "Ref", title: "Ref",
width: "6%", width: "8%",
render: function (data) { render: function (data, type, full) {
return (data === false) ? "" : data; if (data === false) {
return "";
} else if (data.includes("[input]")) {
let val = data.replace("[input]", "");
return `<div class="custom_cell_content">
<input type="text" class="product_ref_input" id="${full.id}_ref_input" value="${val}">
</div>`;
} else {
return data;
}
} }
}, },
{ {
...@@ -1159,7 +1386,7 @@ function prepare_datatable_columns() { ...@@ -1159,7 +1386,7 @@ function prepare_datatable_columns() {
return `<div id="${base_id}_cell_content" class="custom_cell_content">X</div>`; return `<div id="${base_id}_cell_content" class="custom_cell_content">X</div>`;
} else { } else {
let content = `<div id="${base_id}_cell_content" class="custom_cell_content"> let content = `<div id="${base_id}_cell_content" class="custom_cell_content">
<input type="number" class="product_qty_input" id="${base_id}_qty_input" min="0" value=${data}>`; <input type="number" class="product_qty_input" id="${base_id}_qty_input" min="-1" value=${data}>`;
if (full.package_qty === 'X') { if (full.package_qty === 'X') {
let product_data = products.find(p => p.id == full.id); let product_data = products.find(p => p.id == full.id);
...@@ -1240,8 +1467,11 @@ function display_products(params) { ...@@ -1240,8 +1467,11 @@ function display_products(params) {
return -1; return -1;
} }
// Empty datatable if it already exists // If datatable already exists, empty & clear events
if (products_table) { if (products_table) {
$(products_table.table().header()).off();
$('#products_table').off();
products_table.clear().destroy(); products_table.clear().destroy();
$('#products_table').empty(); $('#products_table').empty();
} }
...@@ -1262,23 +1492,36 @@ function display_products(params) { ...@@ -1262,23 +1492,36 @@ function display_products(params) {
sort_order_dir sort_order_dir
] ]
], ],
stripeClasses: [], // Remove datatable cells coloring
orderClasses: false, orderClasses: false,
aLengthMenu: [ aLengthMenu: [
[25, 50, 100, 200, -1], [
[25, 50, 100, 200, "Tout"] 25,
50,
100,
200,
-1
],
[
25,
50,
100,
200,
"Tout"
]
], ],
iDisplayLength: -1, iDisplayLength: -1,
scrollX: true, scrollX: true,
language: {url : '/static/js/datatables/french.json'}, language: {url : '/static/js/datatables/french.json'},
createdRow: function(row) { createdRow: function(row) {
for (const cell_node of row.cells) { for (var i = 0; i < row.cells.length; i++) {
const cell_node = row.cells[i];
const cell = $(cell_node); const cell = $(cell_node);
if (cell.hasClass("supplier_input_cell")) { if (cell.hasClass("supplier_input_cell") && cell.text() === "X") {
if (cell.text() == "X") {
cell.addClass('product_not_from_supplier'); cell.addClass('product_not_from_supplier');
} } else if (i === 1) {
// Column at index 1 is product reference
cell.addClass('product_ref_cell');
} }
} }
} }
...@@ -1288,19 +1531,59 @@ function display_products(params) { ...@@ -1288,19 +1531,59 @@ function display_products(params) {
$('#main_content_footer').show(); $('#main_content_footer').show();
$('#do_inventory').show(); $('#do_inventory').show();
// On inputs change // Color line on input focus
$('#products_table').on('change', 'tbody td .product_qty_input', function () { $('#products_table').on('focus', 'tbody td .product_qty_input', function () {
let val = ($(this).val() == '') ? 0 : $(this).val(); const row = $(this).closest('tr');
val = parseFloat(val); row.addClass('focused_line');
});
// Manage data on inputs blur
$('#products_table').on('blur', 'tbody td .product_qty_input', function () {
// Remove line coloring on input blur
const row = $(this).closest('tr');
row.removeClass('focused_line');
let val = ($(this).val() == '') ? 0 : $(this).val();
// If value is a number
if (!isNaN(val)) {
const id_split = $(this).attr('id') const id_split = $(this).attr('id')
.split('_'); .split('_');
const prod_id = id_split[1]; const prod_id = id_split[1];
const supplier_id = id_split[3]; const supplier_id = id_split[3];
if (val == -1) {
let modal_end_supplier_product_association = $('#templates #modal_end_supplier_product_association');
const product = products.find(p => p.id == prod_id);
modal_end_supplier_product_association.find(".product_name").text(product.name);
const supplier = selected_suppliers.find(s => s.id == supplier_id);
modal_end_supplier_product_association.find(".supplier_name").text(supplier.display_name);
openModal(
modal_end_supplier_product_association.html(),
() => {
if (is_time_to('validate_end_supplier_product_association')) {
end_supplier_product_association(product, supplier);
}
},
'Valider',
false,
true,
() => {
// Reset value in input on cancel
const psi = product.suppliersinfo.find(psi_item => psi_item.supplier_id == supplier_id);
$(this).val(psi.qty);
}
);
} else {
val = parseFloat(val);
// If value is a number
if (!isNaN(val)) {
// Save value // Save value
save_product_supplier_qty(prod_id, supplier_id, val); save_product_supplier_qty(prod_id, supplier_id, val);
...@@ -1316,6 +1599,17 @@ function display_products(params) { ...@@ -1316,6 +1599,17 @@ function display_products(params) {
} else { } else {
$(this).val(''); $(this).val('');
} }
}
})
.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) {
if (e.which == 13) {
// Validate on Enter pressed
$(this).blur();
}
}); });
// Associate product to supplier on click on 'X' in the table // Associate product to supplier on click on 'X' in the table
...@@ -1376,6 +1670,41 @@ function display_products(params) { ...@@ -1376,6 +1670,41 @@ function display_products(params) {
new_product_supplier_association.package_qty = $(this).val(); new_product_supplier_association.package_qty = $(this).val();
}); });
}); });
// Display input on click on product ref cell
$('#products_table').on('click', 'tbody .product_ref_cell', function () {
if ($(this).find('input').length === 0) {
const row = $(this).closest('tr');
const p_id = products_table.row(row).data().id;
const p_index = products.findIndex(p => p.id === p_id);
const existing_ref = products[p_index].default_code === false ? '' : products[p_index].default_code;
products[p_index].default_code = "[input]" + existing_ref;
const new_row_data = prepare_datatable_data([p_id])[0];
products_table.row(row).data(new_row_data)
.draw();
let ref_input = $(`#${p_id}_ref_input`);
ref_input.focus();
ref_input.select();
$('#products_table')
.on('blur', 'tbody .product_ref_input', function () {
update_product_ref(this, p_id, p_index);
})
.on('keypress', 'tbody .product_ref_input', function(e) {
// Validate on Enter pressed
if (e.which == 13) {
update_product_ref(this, p_id, p_index);
}
});
}
});
// Select row(s) on checkbox change // Select row(s) on checkbox change
$(products_table.table().header()).on('click', 'th #select_all_products_cb', function () { $(products_table.table().header()).on('click', 'th #select_all_products_cb', function () {
if (this.checked) { if (this.checked) {
...@@ -1448,6 +1777,8 @@ function display_products(params) { ...@@ -1448,6 +1777,8 @@ function display_products(params) {
* Unselect all rows from datatable. * Unselect all rows from datatable.
*/ */
function unselect_all_rows() { function unselect_all_rows() {
$("#select_all_products_cb").prop("checked", false);
products_table.rows().every(function() { products_table.rows().every(function() {
const node = $(this.node()); const node = $(this.node());
...@@ -1468,9 +1799,14 @@ function display_total_values() { ...@@ -1468,9 +1799,14 @@ function display_total_values() {
_compute_total_values_by_supplier(); _compute_total_values_by_supplier();
let order_total_value = 0; let order_total_value = 0;
for (let supplier of selected_suppliers) { for (let supplier of selected_suppliers) {
$(`#pill_supplier_${supplier.id}`).find('.supplier_total_value').text(supplier.total_value); $(`#pill_supplier_${supplier.id}`).find('.supplier_total_value')
.text(parseFloat(supplier.total_value).toFixed(2));
order_total_value += supplier.total_value; order_total_value += supplier.total_value;
$(`#pill_supplier_${supplier.id}`).find('.supplier_total_packages')
.text(+parseFloat(supplier.total_packages).toFixed(2));
} }
order_total_value = parseFloat(order_total_value).toFixed(2); order_total_value = parseFloat(order_total_value).toFixed(2);
...@@ -1482,8 +1818,11 @@ function display_total_values() { ...@@ -1482,8 +1818,11 @@ function display_total_values() {
*/ */
function update_main_screen(params) { function update_main_screen(params) {
// Remove listener before recreating them // Remove listener before recreating them
$('#products_table').off('focus', 'tbody td .product_qty_input');
$('#products_table').off('blur', 'tbody td .product_qty_input');
$('#products_table').off('change', 'tbody td .product_qty_input'); $('#products_table').off('change', 'tbody td .product_qty_input');
$('#products_table').off('click', 'tbody .product_not_from_supplier'); $('#products_table').off('click', 'tbody .product_not_from_supplier');
$('#products_table').off('click', 'tbody .product_ref_cell');
$('#products_table').off('click', 'thead th #select_all_products_cb'); $('#products_table').off('click', 'thead th #select_all_products_cb');
$('#products_table').off('click', 'tbody td .select_product_cb'); $('#products_table').off('click', 'tbody td .select_product_cb');
$(".remove_supplier_icon").off(); $(".remove_supplier_icon").off();
...@@ -1514,19 +1853,28 @@ function update_main_screen(params) { ...@@ -1514,19 +1853,28 @@ function update_main_screen(params) {
} else { } else {
$("#coverage_days_input").val(''); $("#coverage_days_input").val('');
} }
if (order_doc.stats_date_period !== undefined && order_doc.stats_date_period !== null) {
$("#stats_date_period_select").val(order_doc.stats_date_period);
} else {
$("#stats_date_period_select").val('');
}
} }
/** /**
* Update DOM display on the order selection screen * Update DOM display on the order selection screen
*/ */
function update_order_selection_screen() { function update_order_selection_screen() {
return new Promise((resolve) => {
dbc.allDocs({ dbc.allDocs({
include_docs: true include_docs: true
}).then(function (result) { })
.then(function (result) {
// Remove listener before recreating them // Remove listener before recreating them
$(".order_pill").off(); $(".order_pill").off();
let existing_orders_container = $("#existing_orders"); let existing_orders_container = $("#existing_orders");
existing_orders_container.empty(); existing_orders_container.empty();
$('#new_order_name').val(''); $('#new_order_name').val('');
...@@ -1535,18 +1883,61 @@ function update_order_selection_screen() { ...@@ -1535,18 +1883,61 @@ function update_order_selection_screen() {
} else { } else {
for (let row of result.rows) { for (let row of result.rows) {
let template = $("#templates #order_pill_template"); let template = $("#templates #order_pill_template");
template.find(".pill_order_name").text(row.id); template.find(".pill_order_name").text(row.id);
existing_orders_container.append(template.html()); existing_orders_container.append(template.html());
} }
$(".order_pill").on("click", order_pill_on_click); $(".order_pill").on("click", order_pill_on_click);
$(".remove_order_icon").on("click", function(e) {
e.preventDefault();
e.stopImmediatePropagation();
order_name_container = $(this).prev()[0];
let order_id = $(order_name_container).text();
let modal_remove_order = $('#templates #modal_remove_order');
modal_remove_order.find(".remove_order_name").text(order_id);
openModal(
modal_remove_order.html(),
() => {
if (is_time_to('validate_remove_order')) {
dbc.get(order_id).then((doc) => {
order_doc = doc;
delete_cdb_order().then(() => {
update_order_selection_screen().then(() => {
reset_data();
setTimeout(function() {
$.notify(
"Commande supprimée !",
{
globalPosition:"top left",
className: "success"
} }
);
}, 500);
});
})
.catch(() => {
console.log("error deleting order");
});
});
}
},
'Valider'
);
});
}
resolve();
}) })
.catch(function (err) { .catch(function (err) {
alert('Erreur lors de la synchronisation des commandes. Vous pouvez créer une nouvelle commande.'); alert('Erreur lors de la synchronisation des commandes. Vous pouvez créer une nouvelle commande.');
console.log(err); console.log(err);
}); });
});
} }
/** /**
...@@ -1637,6 +2028,10 @@ function init_pouchdb_sync() { ...@@ -1637,6 +2028,10 @@ function init_pouchdb_sync() {
$(document).ready(function() { $(document).ready(function() {
if (coop_is_connected()) {
$('#new_order_form').show();
$('#existing_orders_area').show();
fingerprint = new Fingerprint({canvas: true}).get(); fingerprint = new Fingerprint({canvas: true}).get();
$.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } }); $.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } });
...@@ -1645,6 +2040,10 @@ $(document).ready(function() { ...@@ -1645,6 +2040,10 @@ $(document).ready(function() {
init_pouchdb_sync(); init_pouchdb_sync();
// Main screen // Main screen
if (metabase_url !== '') {
$('#access_metabase').show();
}
$("#coverage_form").on("submit", function(e) { $("#coverage_form").on("submit", function(e) {
e.preventDefault(); e.preventDefault();
if (is_time_to('submit_coverage_form', 1000)) { if (is_time_to('submit_coverage_form', 1000)) {
...@@ -1664,6 +2063,31 @@ $(document).ready(function() { ...@@ -1664,6 +2063,31 @@ $(document).ready(function() {
} }
}); });
$("#toggle_action_buttons").on("click", function() {
if ($('#actions_buttons_container').is(":visible")) {
$('#actions_buttons_container').hide();
$('.toggle_action_buttons_icon').empty()
.append('<i class="fas fa-chevron-down"></i>');
} else {
$('#actions_buttons_container').show();
$('.toggle_action_buttons_icon').empty()
.append('<i class="fas fa-chevron-up"></i>');
}
});
// Close dropdown menu on click outside
$(document).click(function(event) {
let target = $(event.target);
if (
!target.closest('#actions_buttons_wrapper').length
&& $('#actions_buttons_container').is(":visible")
) {
$('#actions_buttons_container').hide();
$('.toggle_action_buttons_icon').empty()
.append('<i class="fas fa-chevron-down"></i>');
}
});
$("#supplier_form").on("submit", function(e) { $("#supplier_form").on("submit", function(e) {
e.preventDefault(); e.preventDefault();
...@@ -1679,12 +2103,65 @@ $(document).ready(function() { ...@@ -1679,12 +2103,65 @@ $(document).ready(function() {
} }
}); });
$("#stats_date_period_select").on("change", function(e) {
e.preventDefault();
if (is_time_to('change_stats_date_period', 1000)) {
openModal();
order_doc.stats_date_period = $(this).val();
check_products_data()
.then(() => {
compute_products_coverage_qties();
update_main_screen();
update_cdb_order();
closeModal();
});
}
});
$("#do_inventory").on("click", function() { $("#do_inventory").on("click", function() {
if (is_time_to('generate_inventory', 1000)) { if (is_time_to('generate_inventory', 1000)) {
generate_inventory(); generate_inventory();
} }
}); });
$("#delete_order_button").on("click", function() {
if (is_time_to('press_delete_order_button', 1000)) {
let modal_remove_order = $('#templates #modal_remove_order');
modal_remove_order.find(".remove_order_name").text(order_doc._id);
openModal(
modal_remove_order.html(),
() => {
if (is_time_to('validate_remove_order')) {
delete_cdb_order().then(() => {
update_order_selection_screen().then(() => {
reset_data();
switch_screen('order_selection');
setTimeout(function() {
$.notify(
"Commande supprimée !",
{
globalPosition:"top left",
className: "success"
}
);
}, 500);
});
})
.catch(() => {
console.log("error deleting order");
});
}
},
'Valider'
);
}
});
$('#back_to_order_selection_from_main').on('click', function() { $('#back_to_order_selection_from_main').on('click', function() {
if (is_time_to('back_to_order_selection_from_main', 1000)) { if (is_time_to('back_to_order_selection_from_main', 1000)) {
back(); back();
...@@ -1694,6 +2171,7 @@ $(document).ready(function() { ...@@ -1694,6 +2171,7 @@ $(document).ready(function() {
$('#create_orders').on('click', function() { $('#create_orders').on('click', function() {
if (is_time_to('create_orders', 1000)) { if (is_time_to('create_orders', 1000)) {
let modal_create_order = $('#templates #modal_create_order'); let modal_create_order = $('#templates #modal_create_order');
modal_create_order.find('.suppliers_date_planned_area').empty(); modal_create_order.find('.suppliers_date_planned_area').empty();
for (let supplier of selected_suppliers) { for (let supplier of selected_suppliers) {
...@@ -1719,7 +2197,8 @@ $(document).ready(function() { ...@@ -1719,7 +2197,8 @@ $(document).ready(function() {
// Add id to input once modal is displayed // Add id to input once modal is displayed
for (let supplier of selected_suppliers) { for (let supplier of selected_suppliers) {
$(`#modal #container_date_planned_supplier_${supplier.id}`).find(".supplier_date_planned").attr('id', `date_planned_supplier_${supplier.id}`); $(`#modal #container_date_planned_supplier_${supplier.id}`).find(".supplier_date_planned")
.attr('id', `date_planned_supplier_${supplier.id}`);
} }
$("#modal .supplier_date_planned") $("#modal .supplier_date_planned")
...@@ -1876,4 +2355,7 @@ $(document).ready(function() { ...@@ -1876,4 +2355,7 @@ $(document).ready(function() {
alert('Erreur lors de la récupération des articles, rechargez la page plus tard'); alert('Erreur lors de la récupération des articles, rechargez la page plus tard');
} }
}); });
} else {
$('#not_connected_content').show();
}
}); });
...@@ -13,6 +13,7 @@ urlpatterns = [ ...@@ -13,6 +13,7 @@ urlpatterns = [
url(r'^get_suppliers$', views.get_suppliers), url(r'^get_suppliers$', views.get_suppliers),
url(r'^get_supplier_products$', views.get_supplier_products), url(r'^get_supplier_products$', views.get_supplier_products),
url(r'^associate_supplier_to_product$', views.associate_supplier_to_product), url(r'^associate_supplier_to_product$', views.associate_supplier_to_product),
url(r'^end_supplier_product_association$', views.end_supplier_product_association),
url(r'^create_orders$', views.create_orders), url(r'^create_orders$', views.create_orders),
url(r'^get_orders_attachment$', views.get_orders_attachment), url(r'^get_orders_attachment$', views.get_orders_attachment),
] ]
...@@ -19,7 +19,8 @@ def helper(request): ...@@ -19,7 +19,8 @@ def helper(request):
'title': 'Aide à la commande', 'title': 'Aide à la commande',
'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', '')
} }
template = loader.get_template('orders/helper.html') template = loader.get_template('orders/helper.html')
...@@ -42,7 +43,8 @@ def get_supplier_products(request): ...@@ -42,7 +43,8 @@ def get_supplier_products(request):
""" Get supplier products """ """ Get supplier products """
suppliers_id = request.GET.getlist('sids', '') suppliers_id = request.GET.getlist('sids', '')
res = CagetteProducts.get_products_for_order_helper(suppliers_id) stats_from = request.GET.get('stats_from')
res = CagetteProducts.get_products_for_order_helper(suppliers_id, [], stats_from)
if 'error' in res: if 'error' in res:
return JsonResponse(res, status=500) return JsonResponse(res, status=500)
...@@ -52,15 +54,29 @@ def get_supplier_products(request): ...@@ -52,15 +54,29 @@ def get_supplier_products(request):
def associate_supplier_to_product(request): def associate_supplier_to_product(request):
""" This product is now supplied by this supplier """ """ This product is now supplied by this supplier """
res = {} res = {}
try:
data = json.loads(request.body.decode()) data = json.loads(request.body.decode())
res = CagetteProduct.associate_supplier_to_product(data) res = CagetteProduct.associate_supplier_to_product(data)
except Exception as e:
res["error"] = str(e) if 'error' in res:
return JsonResponse(res, status=500) return JsonResponse(res, status=500)
else:
return JsonResponse({'res': res})
return JsonResponse({'res': res}) return JsonResponse({'res': res})
def end_supplier_product_association(request):
""" This product is now unavailable from this supplier """
res = {}
data = json.loads(request.body.decode())
res = CagetteProduct.end_supplier_product_association(data)
if 'error' in res:
return JsonResponse(res, status=500)
else:
return JsonResponse({'res': res})
def create_orders(request): def create_orders(request):
""" Create products orders """ """ Create products orders """
res = { "created": [] } res = { "created": [] }
......
...@@ -228,6 +228,22 @@ ...@@ -228,6 +228,22 @@
- ENTRANCE_SERVICE_BTN = "…faire <b>mon service 🤝" - ENTRANCE_SERVICE_BTN = "…faire <b>mon service 🤝"
- ENTRANCE_EXTRA_BUTTONS_DISPLAY = False (no button is shown above shift coop. list) (True if not set)
- ENTRANCE_EASY_SHIFT_VALIDATE = False (default value)
When set to True allow coop to identify and have 1 point (only if FTOP)
- ENTRANCE_ADD_PT_EVENT_NAME = 'Add 1 point name throught easy validate' (default : 'Validation service comité'')
- ENTRANCE_MISSED_SHIFT_BEGIN_MSG (default : "")
This message is dispayed when time to register is last
- ENTRANCE_EASY_SHIFT_VALIDATE_MSG (default = 'Je valide mon service "Comité"')
(makes sens if ENTRANCE_EASY_SHIFT_VALIDATE is True)
### Member space ### Member space
- EM_URL = '' - EM_URL = ''
......
...@@ -59,10 +59,9 @@ function post_form(url, data, callback) { ...@@ -59,10 +59,9 @@ function post_form(url, data, callback) {
$.post(ajax_params) $.post(ajax_params)
.done(function(rData) { .done(function(rData) {
callback(null, rData); callback(null, rData);
}) })
.fail(function(err) { .fail(function(err) {
console.log(err); callback(err, {});
}); });
} }
......
...@@ -9,8 +9,8 @@ ...@@ -9,8 +9,8 @@
}, },
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"lint": "eslint . --max-warnings 362 '**/*.js'", "lint": "eslint . --max-warnings 326 '**/*.js'",
"lint-fix": "eslint . --fix --max-warnings 362 '**/*.js'" "lint-fix": "eslint . --fix --max-warnings 326 '**/*.js'"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
......
...@@ -130,17 +130,46 @@ class CagetteProduct(models.Model): ...@@ -130,17 +130,46 @@ class CagetteProduct(models.Model):
@staticmethod @staticmethod
def associate_supplier_to_product(data): def associate_supplier_to_product(data):
api = OdooAPI() api = OdooAPI()
res = {}
product_tmpl_id = data["product_tmpl_id"] product_tmpl_id = data["product_tmpl_id"]
partner_id = data["supplier_id"] partner_id = data["supplier_id"]
package_qty = data["package_qty"] package_qty = data["package_qty"]
price = data["price"] price = data["price"]
# Look for existing association
f = ["id"]
c = [['product_tmpl_id', '=', product_tmpl_id], ['name', '=', partner_id]]
res_existing_supplierinfo = api.search_read('product.supplierinfo', c, f)
if (len(res_existing_supplierinfo) > 0):
# A relation already exists, update it's start & end dates
psi_id = res_existing_supplierinfo[0]['id']
today = datetime.date.today().strftime("%Y-%m-%d")
f = {
'date_start': today,
'date_end': False,
'price': price,
'base_price': price,
'package_qty': package_qty,
'sequence': 1000 # lowest priority for the new suppliers
}
try:
res["update"] = api.update('product.supplierinfo', psi_id, f)
except Exception as e:
res['error'] = str(e)
else:
# Relation doesn't exists, create one
f = ["id", "standard_price", "purchase_ok"] f = ["id", "standard_price", "purchase_ok"]
c = [['product_tmpl_id', '=', product_tmpl_id]] c = [['product_tmpl_id', '=', product_tmpl_id]]
res_products = api.search_read('product.product', c, f) res_products = api.search_read('product.product', c, f)
product = res_products[0] product = res_products[0]
today = datetime.date.today().strftime("%Y-%m-%d")
f = { f = {
'product_tmpl_id' : product_tmpl_id, 'product_tmpl_id' : product_tmpl_id,
'product_id' : product["id"], 'product_id' : product["id"],
...@@ -148,9 +177,44 @@ class CagetteProduct(models.Model): ...@@ -148,9 +177,44 @@ class CagetteProduct(models.Model):
'product_purchase_ok': product["purchase_ok"], 'product_purchase_ok': product["purchase_ok"],
'price': price, 'price': price,
'base_price': price, 'base_price': price,
'package_qty': package_qty 'package_qty': package_qty,
'date_start': today,
'sequence': 1000 # lowest priority for the new suppliers
} }
res = api.create('product.supplierinfo', f)
try:
res['create'] = api.create('product.supplierinfo', f)
except Exception as e:
res['error'] = str(e)
return res
@staticmethod
def end_supplier_product_association(data):
api = OdooAPI()
res = {}
product_tmpl_id = data["product_tmpl_id"]
partner_id = data["supplier_id"]
f = ["id"]
c = [['product_tmpl_id', '=', product_tmpl_id], ['name', '=', partner_id], ['date_end', '=', False]]
res_supplierinfo = api.search_read('product.supplierinfo', c, f)
# End all active associations in case multiple are open (which shouldn't happen)
for psi in res_supplierinfo:
psi_id = psi['id']
today = datetime.date.today().strftime("%Y-%m-%d")
f = {
'date_end': today
}
try:
res["update"] = api.update('product.supplierinfo', psi_id, f)
except Exception as e:
res['error'] = str(e)
return res return res
...@@ -171,6 +235,23 @@ class CagetteProduct(models.Model): ...@@ -171,6 +235,23 @@ class CagetteProduct(models.Model):
return res return res
@staticmethod
def update_product_internal_ref(product_tmpl_id, default_code):
api = OdooAPI()
res = {}
f = {
'default_code': default_code
}
try:
res["update"] = api.update('product.template', product_tmpl_id, f)
except Exception as e:
res["error"] = str(e)
print(str(e))
return res
class CagetteProducts(models.Model): class CagetteProducts(models.Model):
"""Initially used to make massive barcode update.""" """Initially used to make massive barcode update."""
...@@ -455,31 +536,32 @@ class CagetteProducts(models.Model): ...@@ -455,31 +536,32 @@ class CagetteProducts(models.Model):
return res return res
@staticmethod @staticmethod
def get_products_for_order_helper(supplier_ids, pids = []): def get_products_for_order_helper(supplier_ids, pids = [], stats_from = None):
""" """
One of the two parameters must be not empty. supplier_ids: Get products by supplier if one or more supplier id is set. If set, pids is ignored.
Get products by supplier if one or more supplier_id is set. pids: If set & supplier_ids is None/empty, get products specified in pids. In this case, suppliers info won't be fetched.
If supplier_ids is empty, get products specified in pids. In this case, suppliers info won't be fetched. stats_from: date from which we should calculate sells stats.
""" """
api = OdooAPI() api = OdooAPI()
res = {} res = {}
# todo : try with no result
try: try:
today = datetime.date.today().strftime("%Y-%m-%d") today = datetime.date.today().strftime("%Y-%m-%d")
if supplier_ids is not None and len(supplier_ids) > 0: if supplier_ids is not None and len(supplier_ids) > 0:
# Get products/supplier relation # Get products/supplier relation
f = ["product_tmpl_id", 'date_start', 'date_end', 'package_qty', 'price', 'name'] f = ["product_tmpl_id", 'date_start', 'date_end', 'package_qty', 'price', 'name', 'product_code']
c = [['name', 'in', [ int(x) for x in supplier_ids]]] c = [['name', 'in', [ int(x) for x in supplier_ids]]]
psi = api.search_read('product.supplierinfo', c, f) psi = api.search_read('product.supplierinfo', c, f)
# Filter valid data # Filter valid data
ptids = [] ptids = []
valid_psi = []
for p in psi: for p in psi:
if (p["product_tmpl_id"] is not False if (p["product_tmpl_id"] is not False
and (p["date_start"] is False or p["date_end"] is not False and p["date_start"] <= today) and (p["date_start"] is False or p["date_start"] is not False and p["date_start"] <= today)
and (p["date_end"] is False or p["date_end"] is not False and p["date_end"] >= today)): and (p["date_end"] is False or p["date_end"] is not False and p["date_end"] > today)):
valid_psi.append(p)
ptids.append(p["product_tmpl_id"][0]) ptids.append(p["product_tmpl_id"][0])
else: else:
ptids = [ int(x) for x in pids ] ptids = [ int(x) for x in pids ]
...@@ -506,6 +588,10 @@ class CagetteProducts(models.Model): ...@@ -506,6 +588,10 @@ class CagetteProducts(models.Model):
# 'from': '2019-04-10', # 'from': '2019-04-10',
# 'to': '2019-08-10', # 'to': '2019-08-10',
} }
if stats_from is not None and stats_from != '':
sales_average_params['from'] = stats_from
sales = CagetteProducts.get_template_products_sales_average(sales_average_params) sales = CagetteProducts.get_template_products_sales_average(sales_average_params)
if 'list' in sales and len(sales['list']) > 0: if 'list' in sales and len(sales['list']) > 0:
...@@ -516,12 +602,16 @@ class CagetteProducts(models.Model): ...@@ -516,12 +602,16 @@ class CagetteProducts(models.Model):
# Add supplier data to product data # Add supplier data to product data
for i, fp in enumerate(filtered_products_t): for i, fp in enumerate(filtered_products_t):
if supplier_ids is not None and len(supplier_ids) > 0: if supplier_ids is not None and len(supplier_ids) > 0:
psi_item = next(item for item in psi if item["product_tmpl_id"] is not False and item["product_tmpl_id"][0] == fp["id"]) # Add all the product suppliersinfo (products from multiple suppliers into the suppliers list provided)
filtered_products_t[i]['suppliersinfo'] = [{ filtered_products_t[i]['suppliersinfo'] = []
for psi_item in valid_psi:
if psi_item["product_tmpl_id"] is not False and psi_item ["product_tmpl_id"][0] == fp["id"]:
filtered_products_t[i]['suppliersinfo'].append({
'supplier_id': int(psi_item["name"][0]), 'supplier_id': int(psi_item["name"][0]),
'package_qty': psi_item["package_qty"], 'package_qty': psi_item["package_qty"],
'price': psi_item["price"] 'price': psi_item["price"],
}] 'product_code': psi_item["product_code"]
})
for s in sales: for s in sales:
if s["id"] == fp["id"]: if s["id"] == fp["id"]:
......
...@@ -10,6 +10,7 @@ urlpatterns = [ ...@@ -10,6 +10,7 @@ urlpatterns = [
url(r'^get_products_stdprices$', views.get_products_stdprices), url(r'^get_products_stdprices$', views.get_products_stdprices),
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'^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
......
...@@ -42,8 +42,10 @@ def get_simple_list(request): ...@@ -42,8 +42,10 @@ def get_simple_list(request):
def get_product_for_order_helper(request): def get_product_for_order_helper(request):
res = {} res = {}
try: try:
pids = json.loads(request.body.decode()) data = json.loads(request.body.decode())
res = CagetteProducts.get_products_for_order_helper(None, pids) pids = data['pids']
stats_from = data['stats_from']
res = CagetteProducts.get_products_for_order_helper(None, pids, stats_from)
except Exception as e: except Exception as e:
coop_logger.error("get_product_for_help_order_line : %s", str(e)) coop_logger.error("get_product_for_help_order_line : %s", str(e))
res['error'] = str(e) res['error'] = str(e)
...@@ -112,6 +114,17 @@ def update_product_purchase_ok(request): ...@@ -112,6 +114,17 @@ def update_product_purchase_ok(request):
else: else:
return JsonResponse({"res": res}) return JsonResponse({"res": res})
def update_product_internal_ref(request):
res = {}
data = json.loads(request.body.decode())
res = CagetteProduct.update_product_internal_ref(data["product_tmpl_id"], data["default_code"])
if ('error' in res):
return JsonResponse(res, status=500)
else:
return JsonResponse({"res": res})
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."""
withCandidate = False withCandidate = False
......
...@@ -31,7 +31,7 @@ class CagetteReception(models.Model): ...@@ -31,7 +31,7 @@ class CagetteReception(models.Model):
pids.append(int(r['purchase_id'][0])) pids.append(int(r['purchase_id'][0]))
if len(pids): if len(pids):
f=["id","name","date_order", "partner_id", "date_planned", "amount_untaxed", "amount_total", "x_reception_status"] f=["id","name","date_order", "partner_id", "date_planned", "amount_untaxed", "amount_total", "x_reception_status", 'create_uid']
# Only get orders that need to be treated in Reception # Only get orders that need to be treated in Reception
c = [['id', 'in', pids], ["x_reception_status", "in", [False, 'qty_valid', 'valid_pending', 'br_valid']]] c = [['id', 'in', pids], ["x_reception_status", "in", [False, 'qty_valid', 'valid_pending', 'br_valid']]]
...@@ -49,6 +49,25 @@ class CagetteReception(models.Model): ...@@ -49,6 +49,25 @@ class CagetteReception(models.Model):
"""Return all purchases order lines linked with purchase order id in Odoo.""" """Return all purchases order lines linked with purchase order id in Odoo."""
return Order(id_po).get_lines(withNullQty=nullQty) return Order(id_po).get_lines(withNullQty=nullQty)
def get_mail_create_po(id_po):
"""Return name et email from id_po of order"""
try:
api = OdooAPI()
f = ["create_uid"]
c = [['id', '=', id_po]]
res = api.search_read('purchase.order', c, f)
f = ["email", "display_name"]
c = [['user_ids', '=', int(res[0]['create_uid'][0])]]
res = api.search_read('res.partner', c, f)
except Exception as e:
print(str(e))
return res
def implies_scale_file_generation(self): def implies_scale_file_generation(self):
answer = False answer = False
lines = Order(self.id).get_lines() lines = Order(self.id).get_lines()
...@@ -132,6 +151,7 @@ class CagetteReception(models.Model): ...@@ -132,6 +151,7 @@ class CagetteReception(models.Model):
self.o_api.update('stock.move.operation.link', [linked_move_op_id], {'qty': received_products[pack['product_id'][0]]}) self.o_api.update('stock.move.operation.link', [linked_move_op_id], {'qty': received_products[pack['product_id'][0]]})
pfields['product_uom_qty'] = received_qty pfields['product_uom_qty'] = received_qty
self.o_api.update('stock.move', [move_id], pfields) self.o_api.update('stock.move', [move_id], pfields)
del pfields['product_uom_qty'] # field not in stock.pack.operation
pfields['qty_done'] = pfields['product_qty'] = received_qty pfields['qty_done'] = pfields['product_qty'] = received_qty
self.o_api.update('stock.pack.operation', [int(pack['id'])], pfields) self.o_api.update('stock.pack.operation', [int(pack['id'])], pfields)
processed_lines += 1 processed_lines += 1
......
...@@ -103,18 +103,29 @@ function goto(id) { ...@@ -103,18 +103,29 @@ function goto(id) {
* @param {int} group_index index of group in groups array * @param {int} group_index index of group in groups array
*/ */
function group_goto(group_index) { function group_goto(group_index) {
let missing_orders = [];
// Make sure a couchdb document exists for all group's orders // Make sure a couchdb document exists for all group's orders
for (let i in order_groups.groups[group_index]) { for (let i in order_groups.groups[group_index]) {
let order_data = null; let order_data = null;
let order_id = order_groups.groups[group_index][i];
// Find order data // Find order data
for (let order of orders) { for (let order of orders) {
if (order.id == order_groups.groups[group_index][i]) { if (order.id == order_id) {
order_data = order; order_data = order;
} }
} }
if (order_data != null) {
create_order_doc(order_data); create_order_doc(order_data);
} else {
missing_orders.push(order_id);
}
}
if (missing_orders.length > 0) {
// TODO what to do when orders are missing from group?
} }
// go to first order // go to first order
...@@ -212,7 +223,7 @@ function validatePrices() { ...@@ -212,7 +223,7 @@ function validatePrices() {
var updates = { var updates = {
'group_amount_total' : order['amount_total'], 'group_amount_total' : order['amount_total'],
'update_type' : 'br_vaid', 'update_type' : 'br_valid',
'updated_products' : [], 'updated_products' : [],
'user_comments': "", 'user_comments': "",
'orders' : [order] 'orders' : [order]
...@@ -285,7 +296,10 @@ function group_action() { ...@@ -285,7 +296,10 @@ function group_action() {
* Remove the grouped orders from the order table to prevent grouping in multiple groups. * Remove the grouped orders from the order table to prevent grouping in multiple groups.
*/ */
function display_grouped_orders() { function display_grouped_orders() {
if (table_orders !== null) { if (table_orders !== null) {
var display_something = false;
$('#groups_items').empty(); $('#groups_items').empty();
let groups_display_content = "<ul>"; let groups_display_content = "<ul>";
...@@ -308,7 +322,9 @@ function display_grouped_orders() { ...@@ -308,7 +322,9 @@ function display_grouped_orders() {
} }
} }
if (group_orders.length > 0) {
// Display group // Display group
display_something = true;
document.getElementById("container_groups").hidden = false; document.getElementById("container_groups").hidden = false;
let group_row = `<li class="group_line"> Commandes de `; let group_row = `<li class="group_line"> Commandes de `;
...@@ -333,9 +349,12 @@ function display_grouped_orders() { ...@@ -333,9 +349,12 @@ function display_grouped_orders() {
group_row += "</li>"; group_row += "</li>";
groups_display_content += group_row; groups_display_content += group_row;
} }
}
if (display_something === true) {
$('#container_groups').show(); $('#container_groups').show();
$('#groups_items').append(groups_display_content); $('#groups_items').append(groups_display_content);
} }
}
} }
/** /**
...@@ -346,7 +365,9 @@ function display_orders_table() { ...@@ -346,7 +365,9 @@ function display_orders_table() {
table_orders.clear().destroy(); table_orders.clear().destroy();
$('#orders').empty(); $('#orders').empty();
} }
for (let j in orders) {
console.log(orders[j].id);
}
table_orders = $('#orders').DataTable({ table_orders = $('#orders').DataTable({
data: orders, data: orders,
columns:[ columns:[
......
...@@ -84,10 +84,10 @@ function select_product_from_bc(barcode) { ...@@ -84,10 +84,10 @@ function select_product_from_bc(barcode) {
} }
}); });
if (found.data != null) { if (found.data == null) {
$.each(list_processed, function(i, e) { $.each(list_processed, function(i, e) {
if (e.product_id[0] == p.data[barcodes['keys']['id']]) { if (e.product_id[0] == p.data[barcodes['keys']['id']]) {
found.data = e; found.data = JSON.parse(JSON.stringify(e));
found.place = 'processed'; found.place = 'processed';
} }
}); });
...@@ -99,10 +99,6 @@ function select_product_from_bc(barcode) { ...@@ -99,10 +99,6 @@ function select_product_from_bc(barcode) {
let row = table_to_process.row($('#'+found.data.product_id[0])); let row = table_to_process.row($('#'+found.data.product_id[0]));
remove_from_toProcess(row, found.data); remove_from_toProcess(row, found.data);
} else {
let row = table_processed.row($('#'+found.data.product_id[0]));
remove_from_processed(row, found.data);
} }
} }
} }
...@@ -862,12 +858,20 @@ function editProductInfo (productToEdit, value = null, batch = false) { ...@@ -862,12 +858,20 @@ function editProductInfo (productToEdit, value = null, batch = false) {
var index = searchUpdatedProduct(); var index = searchUpdatedProduct();
var firstUpdate = false; var firstUpdate = false;
let newValue = value; let newValue = value;
var addition = false;
// If 'value' parameter not set, get value from edition input // If 'value' parameter not set, get value from edition input
if (value == null) { if (value == null) {
newValue = parseFloat(document.getElementById('edition_input').value.replace(',', '.')); newValue = parseFloat(document.getElementById('edition_input').value.replace(',', '.'));
} }
$.each(list_processed, function(i, e) {
if (e.product_id[0] == productToEdit.product_id[0]) {
addition = true;
productToEdit = e;
newValue = newValue + productToEdit.product_qty;
}
});
// If qty edition & Check if qty changed // If qty edition & Check if qty changed
if (reception_status == "False" && productToEdit.product_qty != newValue) { if (reception_status == "False" && productToEdit.product_qty != newValue) {
if (index == -1) { // First update if (index == -1) { // First update
...@@ -944,6 +948,12 @@ function editProductInfo (productToEdit, value = null, batch = false) { ...@@ -944,6 +948,12 @@ function editProductInfo (productToEdit, value = null, batch = false) {
update_distant_order(productToEdit.id_po); update_distant_order(productToEdit.id_po);
} }
if (addition) {
let row = table_processed.row($('#'+productToEdit.product_id[0]));
remove_from_processed(row, productToEdit);
}
add_to_processed(productToEdit); add_to_processed(productToEdit);
} catch (e) { } catch (e) {
err = {msg: e.name + ' : ' + e.message, ctx: 'edit product info'}; err = {msg: e.name + ' : ' + e.message, ctx: 'edit product info'};
...@@ -1206,6 +1216,39 @@ function send() { ...@@ -1206,6 +1216,39 @@ function send() {
error_report_data.orders.push(orders[i]); error_report_data.orders.push(orders[i]);
} }
//Create list of articl with no barcode
no_barcode_list = [];
for (var i = 0; i < list_processed.length; i++) {
if (list_processed[i].product_qty != 0 && (list_processed[i].barcode == false || list_processed[i].barcode == null || list_processed[i].barcode == "")) {
no_barcode_list.push([
list_processed[i]["product_id"][0],
list_processed[i]["product_id"][1]
]);
}
}
data_send_no_barcode={
"order" : orders[order_data['id_po']],
"no_barcode_list" : no_barcode_list
};
// Send of articl with no barcode to mail send
if (no_barcode_list.length > 0) {
$.ajax({
type: "POST",
url: "../send_mail_no_barcode",
dataType: "json",
traditional: true,
contentType: "application/json; charset=utf-8",
data: JSON.stringify(data_send_no_barcode),
success: function() {},
error: function() {
alert('Erreur dans l\'envoi des produite sont barre code.');
}
});
}
// Send request for error report // Send request for error report
$.ajax({ $.ajax({
type: "POST", type: "POST",
...@@ -1248,7 +1291,7 @@ function send() { ...@@ -1248,7 +1291,7 @@ function send() {
document.getElementById("nothingToDo").hidden = true; document.getElementById("nothingToDo").hidden = true;
barcodes_to_print = true; barcodes_to_print = true;
} else if (list_processed[i].barcode == false || list_processed[i].barcode == null || list_processed[i].barcode == "") { } /* else if (list_processed[i].barcode == false || list_processed[i].barcode == null || list_processed[i].barcode == "") {
// Products with no barcode // Products with no barcode
var node = document.createElement('li'); var node = document.createElement('li');
let textNode = document.createTextNode(list_processed[i]["product_id"][1]); let textNode = document.createTextNode(list_processed[i]["product_id"][1]);
...@@ -1260,7 +1303,20 @@ function send() { ...@@ -1260,7 +1303,20 @@ function send() {
document.getElementById("barcodesEmpty").hidden = false; document.getElementById("barcodesEmpty").hidden = false;
document.getElementById("nothingToDo").hidden = true; document.getElementById("nothingToDo").hidden = true;
} }
}*/
}
} }
for (let i = 0; i < no_barcode_list.length; i++) {
var node = document.createElement('li');
let textNode = document.createTextNode(no_barcode_list[i]);
node.appendChild(textNode);
document.getElementById('barcodesEmpty_list').appendChild(node);
if (document.getElementById("barcodesEmpty").hidden) {
document.getElementById("barcodesEmpty").hidden = false;
document.getElementById("nothingToDo").hidden = true;
} }
} }
......
...@@ -16,5 +16,6 @@ urlpatterns = [ ...@@ -16,5 +16,6 @@ urlpatterns = [
url(r'^reception_qtiesValidated', views.reception_qtiesValidated), url(r'^reception_qtiesValidated', views.reception_qtiesValidated),
url(r'^reception_pricesValidated', views.reception_pricesValidated), url(r'^reception_pricesValidated', views.reception_pricesValidated),
# url(r'^update_order_status/([0-9]+)$', views.tmp_update_order_status), # url(r'^update_order_status/([0-9]+)$', views.tmp_update_order_status),
url(r'^po_process_picking$', views.po_process_picking) url(r'^po_process_picking$', views.po_process_picking),
url(r'^send_mail_no_barcode', views.send_mail_no_barcode)
] ]
...@@ -221,26 +221,6 @@ def update_orders(request): ...@@ -221,26 +221,6 @@ def update_orders(request):
m.update_order_status(order_id, data['update_type']) m.update_order_status(order_id, data['update_type'])
if data['update_type'] == 'br_valid': if data['update_type'] == 'br_valid':
answer_data[order_id]['finalyze_result'] = m.register_purchase_order_to_finalyze() answer_data[order_id]['finalyze_result'] = m.register_purchase_order_to_finalyze()
# Remove order's group
try:
# TODO remove from couchdb orders & group (here?)
if os.path.exists('temp/grouped_order.json'):
with open('temp/grouped_order.json', 'r') as json_file:
saved_groups = json.load(json_file)
for oi in order_ids:
for i, group in enumerate(saved_groups):
if oi in group:
saved_groups.pop(i)
break
with open('temp/grouped_order.json', 'w') as json_file:
json.dump(saved_groups, json_file)
except Exception as e:
# no saved groups
print(str(e))
# TODO else if first step, save first step data (here?)
else: else:
coop_logger.error("update_orders errors : %s", str(errors)) coop_logger.error("update_orders errors : %s", str(errors))
rep = JsonResponse(answer_data, safe=False) rep = JsonResponse(answer_data, safe=False)
...@@ -259,54 +239,61 @@ def save_error_report(request): ...@@ -259,54 +239,61 @@ def save_error_report(request):
save it to an excel file, save it to an excel file,
attach file to order in odoo attach file to order in odoo
""" """
if request.is_ajax():
if request.method == 'POST': if request.method == 'POST':
data = None
try:
myJson = request.body myJson = request.body
data = json.loads(myJson.decode()) data = json.loads(myJson.decode())
except Exception as e1:
coop_logger.error("Save reception report : Unable to load data %s (%s)", str(e1), str(myJson))
temp_files_name = [] if data and ('orders' in data):
orders_name = "" orders_name_elts = []
orders_partner = "" orders_partner = ""
group_ids = [] group_ids = []
try:
for i, order in enumerate(data['orders']) : for i, order in enumerate(data['orders']) :
# list of temp files: 1 report per order & group # list of temp files: 1 report per order & group
data['orders'][i]['temp_file_name'] = "temp/" + order['name'] + "_rapport-reception_temp.xlsx" data['orders'][i]['temp_file_name'] = "temp/" + order['name'] + "_rapport-reception_temp.xlsx"
group_ids.append(order['id']) group_ids.append(order['id'])
# Concatenate orders name orders_name_elts.append(order['name'])
if orders_name != "" :
orders_name = orders_name + '-'
orders_name = orders_name + order['name']
# Concatenate orders partner # Concatenate orders partner
if order['partner'] != orders_partner : if order['partner'] != orders_partner:
if orders_partner != "" : if orders_partner != "":
orders_partner = orders_partner + ', ' orders_partner = orders_partner + ', '
orders_partner = orders_partner + order['partner'] + ' du ' + order['date_order'] orders_partner = orders_partner + order['partner'] + ' du ' + order['date_order']
# If group of orders # If group of orders
if len(data['orders']) > 1 : if len(data['orders']) > 1:
orders_name = '-'.join(orders_name_elts)
temp_group_file_name = "temp/" + orders_name + "_rapport-reception_temp.xlsx" temp_group_file_name = "temp/" + orders_name + "_rapport-reception_temp.xlsx"
# Add group in data['orders'] # Add group in data['orders']
group_order = { group_order = {
'name' : orders_name, 'name': orders_name,
'partner' : orders_partner, 'partner': orders_partner,
'date_order' : data['orders'][0]['date_order'], 'date_order': data['orders'][0]['date_order'],
'amount_total' : data['group_amount_total'], 'amount_total': data['group_amount_total'],
'updated_products' : data['updated_products'], 'updated_products': data['updated_products'],
'temp_file_name' : temp_group_file_name, 'temp_file_name': temp_group_file_name,
'group_ids' : group_ids 'group_ids': group_ids
} }
data['orders'].append(group_order) data['orders'].append(group_order)
else:
coop_logger.info("data['orders'] is a single PO (not inside group)")
except Exception as e2:
coop_logger.error("Save reception report : Error while create group_order %s", str(e2))
# Save qties & comments after step 1 # Save qties & comments after step 1
if data['update_type'] == 'qty_valid': if data['update_type'] == 'qty_valid':
for order in data['orders']: for order in data['orders']:
try:
wb = Workbook() wb = Workbook()
ws = wb.active ws = wb.active
ws.title = "Commande " + order['name'] ws.title = "Commande " + order['name']
...@@ -321,8 +308,8 @@ def save_error_report(request): ...@@ -321,8 +308,8 @@ def save_error_report(request):
'supplier_shortage']) 'supplier_shortage'])
# If in group add group name # If in group add group name
if len(data['orders']) > 1 : if len(data['orders']) > 1:
ws.append( ['group', orders_name] ) ws.append(['group', orders_name])
try: try:
if 'updated_products' in order: if 'updated_products' in order:
for product in order['updated_products']: for product in order['updated_products']:
...@@ -338,17 +325,17 @@ def save_error_report(request): ...@@ -338,17 +325,17 @@ def save_error_report(request):
else: else:
supplier_shortage = '' supplier_shortage = ''
ws.append( ['produit', ws.append(['produit',
product['product_id'][1], product['product_id'][1],
supplier_code, supplier_code,
str(product['barcode']), str(product['barcode']),
product['old_qty'], product['old_qty'],
product['product_qty'], product['product_qty'],
product['price_unit'], product['price_unit'],
supplier_shortage] ) supplier_shortage])
except Exception as exp: except Exception as exp:
print("Error while updating products") coop_logger.error("Error while updating products in report : %s", str(exp))
print(exp)
if ('user_comments' in data) and data['user_comments'] != "": if ('user_comments' in data) and data['user_comments'] != "":
ws.append( ['commentaire', data['user_comments']] ) ws.append( ['commentaire', data['user_comments']] )
else: else:
...@@ -356,7 +343,8 @@ def save_error_report(request): ...@@ -356,7 +343,8 @@ def save_error_report(request):
# Save file # Save file
wb.save(filename=order['temp_file_name']) wb.save(filename=order['temp_file_name'])
except Exception as e3:
coop_logger.error("Save reception report : Error while create order Workbook %s", str(e3))
# Create report with data from steps 1 & 2 # Create report with data from steps 1 & 2
...@@ -385,13 +373,14 @@ def save_error_report(request): ...@@ -385,13 +373,14 @@ def save_error_report(request):
'supplier_shortage' : row[7] 'supplier_shortage' : row[7]
} }
elif row[0] == 'group': elif row[0] == 'group':
group_name = row[1] # group's orders name group_name = row[1] # group's orders name : Unused
elif row[0] == 'commentaire': elif row[0] == 'commentaire':
data_comment_s1 = row[1] # user comments data_comment_s1 = row[1] # user comments
# Clear step 1 temp file # Clear step 1 temp file
os.remove(order['temp_file_name']) os.remove(order['temp_file_name'])
except: except Exception as e4:
coop_logger.error("Save reception report : reloading first step xlsx file %s", str(e4))
data_comment_s1 = "Données de la première étape absentes !" data_comment_s1 = "Données de la première étape absentes !"
# Add data from step 2 # Add data from step 2
...@@ -450,13 +439,15 @@ def save_error_report(request): ...@@ -450,13 +439,15 @@ def save_error_report(request):
error_total_abs += abs(item['error_line']) error_total_abs += abs(item['error_line'])
data_full.append(item) data_full.append(item)
except Exception as exp: else:
coop_logger.info("Save reception error doc : no 'updated_products' in order (%s)", str(order))
except Exception as e5:
coop_logger.error("Save reception report : Error while updating products %s", str(e5))
# no updated products, do nothing # no updated products, do nothing
print("Error while updating products")
print(exp)
# Add remaining products, the ones edited only in step 1 # Add remaining products, the ones edited only in step 1
for product in data_qties.values(): for product in data_qties.values():
try:
item = { item = {
'product_id': product['nom_contenu'], 'product_id': product['nom_contenu'],
'product_supplier_code': product['supplier_code'], 'product_supplier_code': product['supplier_code'],
...@@ -474,7 +465,9 @@ def save_error_report(request): ...@@ -474,7 +465,9 @@ def save_error_report(request):
error_total_abs += abs(item['error_line']) error_total_abs += abs(item['error_line'])
data_full.append(item) data_full.append(item)
except Exception as e6:
coop_logger.error("Save reception report : Error while creating item from product %s (%s)", str(e6), str(e6))
try:
# Sort by error amount # Sort by error amount
def sortByError(e): def sortByError(e):
return abs(e['error_line']) return abs(e['error_line'])
...@@ -567,23 +560,31 @@ def save_error_report(request): ...@@ -567,23 +560,31 @@ def save_error_report(request):
length = 20 length = 20
ws.column_dimensions[column_cells[3].column_letter].width = length ws.column_dimensions[column_cells[3].column_letter].width = length
except Exception as e7:
coop_logger.error("PO save report : error while creating final Workbook %s", str(e7))
# Save file # Save file
fileName = "temp/" + order['name'] + "_rapport-reception.xlsx" fileName = "temp/" + order['name'] + "_rapport-reception.xlsx"
try: try:
wb.save(filename=fileName) wb.save(filename=fileName)
except Exception as exp:
print("Error while saving file %s"%fileName)
print(str(exp))
#Attach file to order #Attach file to order
if 'group_ids' in order : # group report if 'group_ids' in order: # group report
# Attach group report to each order # Attach group report to each order
for group_item_id in order['group_ids'] : for group_item_id in order['group_ids']:
m = CagetteReception(group_item_id) m = CagetteReception(group_item_id)
m.attach_file(fileName, False) m.attach_file(fileName, False)
os.remove(fileName) os.remove(fileName)
else: else:
m = CagetteReception(order['id']) m = CagetteReception(order['id'])
m.attach_file(fileName) m.attach_file(fileName)
coop_logger.info("%s attached to order id %s", fileName, str(order['id']))
except Exception as e8:
coop_logger.error("PO save report Error while saving file %s (%s)", fileName, str(e8))
else:
coop_logger.error("Save reception error report : unknown state %s (%s) ", str(data['update_type']), str(data))
else:
coop_logger.error("Cant find 'orders' in data (%s)", str(data))
else:
coop_logger.info("Was waiting for a POST method (%s)", str(request.method))
return JsonResponse("ok", safe=False) return JsonResponse("ok", safe=False)
def reception_FAQ(request): def reception_FAQ(request):
...@@ -625,3 +626,39 @@ def reception_pricesValidated(request): ...@@ -625,3 +626,39 @@ def reception_pricesValidated(request):
def po_process_picking(request): def po_process_picking(request):
res = CagetteReception.process_enqueued_po_to_finalyze() res = CagetteReception.process_enqueued_po_to_finalyze()
return JsonResponse(res, safe=False) return JsonResponse(res, safe=False)
def send_mail_no_barcode(request):
"""
Receive json data with liste of product with no barcode
Send mail to order maker
"""
from django.core.mail import send_mail
if request.method == 'POST':
data = None
try:
myJson = request.body
data = json.loads(myJson.decode())
data_partner = CagetteReception.get_mail_create_po(int(data['order']['id']))
msg = settings.NO_BARCODE_MAIL_MSG
for barcode in data["no_barcode_list"]:
msg = msg + ' -' + str(barcode[0]) + '---' + str(barcode[1])+ '\n'
send_mail(settings.NO_BARCODE_MAIL_SUBJECT.format(data['order']['name']),
msg.format(data_partner[0]['display_name'], data['order']['name'],data['order']['date_order'], data['order']['partner']),
settings.DEFAULT_FROM_EMAIL,
[data_partner[0]['email']],
fail_silently=False,)
except Exception as e1:
coop_logger.error("Send_mail_no_barcode : Unable to load data %s (%s)", str(e1), str(myJson))
print(str(e1)+'\n'+ str(myJson))
return JsonResponse("ok", safe=False)
...@@ -162,7 +162,6 @@ function init_datatable() { ...@@ -162,7 +162,6 @@ function init_datatable() {
// Listener on inputs // Listener on inputs
$('#products_table tbody').on('change', '.stock_edit_input', function () { $('#products_table tbody').on('change', '.stock_edit_input', function () {
let qty = $(this).val(); let qty = $(this).val();
let row = products_table.row($(this).parents('tr')); let row = products_table.row($(this).parents('tr'));
let data = row.data(); let data = row.data();
...@@ -172,24 +171,7 @@ function init_datatable() { ...@@ -172,24 +171,7 @@ function init_datatable() {
data.qty = validated_data; data.qty = validated_data;
row.remove().draw(); row.remove().draw();
products_table.row.add(data).draw(); products_table.row.add(data).draw();
} else {
data.qty = null;
row.remove().draw();
products_table.row.add(data).draw();
if (validated_data == -2) {
$.notify("Ce produit est vendu à l'unité : la valeur doit être un nombre entier !", {
globalPosition:"top right",
className: "error"
});
} else if (validated_data == -1 || validated_data == -3) {
$.notify("Valeur invalide.", {
globalPosition:"top right",
className: "error"
});
} }
}
update_total_value(); update_total_value();
}); });
...@@ -441,20 +423,34 @@ var update_existing_product = function(product, added_qty, undo_option = false) ...@@ -441,20 +423,34 @@ var update_existing_product = function(product, added_qty, undo_option = false)
*/ */
function qty_validation(qty, uom_id) { function qty_validation(qty, uom_id) {
if (qty == null || qty == '') { if (qty == null || qty == '') {
$.notify("Il n'y a pas de quantité indiquée, ou ce n'est pas un nombre", {
globalPosition:"top right",
className: "error"});
return -1; return -1;
} }
if (uom_id == 1) { if (uom_id == 1) {
if (qty/parseInt(qty) != 1 && qty != 0) if (qty/parseInt(qty) != 1 && qty != 0) {
$.notify("Une quantité avec décimale est indiquée alors que c'est un article à l'unité", {
globalPosition:"top right",
className: "error"});
return -2; return -2;
}
qty = parseInt(qty); // product by unit qty = parseInt(qty); // product by unit
} else { } else {
qty = parseFloat(qty).toFixed(2); qty = parseFloat(qty).toFixed(2);
} }
if (isNaN(qty)) if (isNaN(qty)) {
$.notify("Une quantité n'est pas un nombre", {
globalPosition:"top right",
className: "error"});
return -3; return -3;
}
return qty; return qty;
} }
......
...@@ -137,13 +137,16 @@ ...@@ -137,13 +137,16 @@
<div class="col-1"></div> <div class="col-1"></div>
<div class="col-4"> <div class="col-4">
<h1 class="col-4 txtcenter">Qui es-tu ?</h1> <h1 class="col-4 txtcenter">Qui es-tu ?</h1>
{% if extra_btns_display %}
<section class="outside_list"> <section class="outside_list">
<a class="btn b_orange" data-next="rattrapage_1" data-type="rattrapage">Je viens faire un rattrapage</a> <a class="btn b_orange" data-next="rattrapage_1" data-type="rattrapage">Je viens faire un rattrapage</a>
{% if ftop_btn_display %} {% if ftop_btn_display %}
<a class="btn b_yellow" data-next="rattrapage_1" data-type="volant">Je suis volant</a> <a class="btn b_yellow" data-next="rattrapage_1" data-type="volant">Je suis volant</a>
{% endif %} {% endif %}
</section> </section>
{% endif %}
</div> </div>
<div class="col-1"></div> <div class="col-1"></div>
<div class="col-1"></div> <div class="col-1"></div>
<section id="service_en_cours" class="col-4"> <section id="service_en_cours" class="col-4">
...@@ -153,6 +156,12 @@ ...@@ -153,6 +156,12 @@
<div id="current_shift_members"> <div id="current_shift_members">
</div> </div>
{% if easy_shift_validate %}
<div class="easy_shift_validate">
<p>{{ENTRANCE_EASY_SHIFT_VALIDATE_MSG|safe}}</p>
{% include "members/member_selection.html" %}
</div>
{% endif %}
</section> </section>
</section> </section>
...@@ -227,6 +236,9 @@ ...@@ -227,6 +236,9 @@
<input type="text" placeholder="crop_height" id="img_crop_height" /><br /> <input type="text" placeholder="crop_height" id="img_crop_height" /><br />
<button type="button" id="init_webcam">Cam</button> <button type="button" id="init_webcam">Cam</button>
</section> </section>
<div style="display:none;">
<p id="missed_begin_msg">{{ENTRANCE_MISSED_SHIFT_BEGIN_MSG|safe}}</p>
</div>
<script src="{% static "js/all_common.js" %}?v="></script> <script src="{% static "js/all_common.js" %}?v="></script>
<script src="{% static "js/members.js" %}?v="></script> <script src="{% static "js/members.js" %}?v="></script>
{% endblock %} {% endblock %}
<form id="sm_search_member_form" action="javascript:;" method="post">
<input type="text" id="sm_search_member_input" value="" placeholder="Votre nom ou numéro..." required>
<button type="submit" class="btn--primary" id="sm_search_member_button">Recherche</button>
</form>
<div class="search_member_results_area" style="display:none;">
<div>
<p>Vous êtes (cliquez sur votre nom) :</p>
</div>
<div class="search_member_results"></div>
</div>
\ No newline at end of file
...@@ -16,14 +16,20 @@ ...@@ -16,14 +16,20 @@
{% block content %} {% block content %}
<div class="page_body"> <div class="page_body">
<div id="select_order_content" class="page_content txtcenter"> <div id="select_order_content" class="page_content txtcenter">
<div class="login_area">
{% include "common/conn_admin.html" %}
</div>
<div id="new_order_area"> <div id="new_order_area">
<h2>Créer une nouvelle commande</h2> <h2>Créer une nouvelle commande</h2>
<form id="new_order_form"> <div class="txtcenter" id="not_connected_content" style="display:none;">
<p>Vous devez vous connecter avec un compte Odoo pour accéder au module d'aide à la commande.</p>
</div>
<form id="new_order_form" style="display:none;">
<input type="text" id="new_order_name" placeholder="Nom de la commande..."> <input type="text" id="new_order_name" placeholder="Nom de la commande...">
<button type="submit" class="btn btn--primary">Valider</button> <button type="submit" class="btn btn--primary">C'est parti !</button>
</form> </form>
</div> </div>
<div id="existing_orders_area"> <div id="existing_orders_area" style="display:none;">
<h2>Ou, continuer une commande en cours de création</h2> <h2>Ou, continuer une commande en cours de création</h2>
<div id="existing_orders"></div> <div id="existing_orders"></div>
</div> </div>
...@@ -34,9 +40,30 @@ ...@@ -34,9 +40,30 @@
<button type="button" class="btn--danger" id="back_to_order_selection_from_main"> <button type="button" class="btn--danger" id="back_to_order_selection_from_main">
<i class="fas fa-arrow-left"></i>&nbsp; Retour <i class="fas fa-arrow-left"></i>&nbsp; Retour
</button> </button>
<button type="button" class='btn--primary' id="do_inventory" style="display:none;"> <div class="right_action_buttons">
<div id="actions_buttons_wrapper">
<button type="button" class='btn--primary' id="toggle_action_buttons">
<span class="button_content">
Actions
</span>
<span class="toggle_action_buttons_icon">
<i class="fas fa-chevron-down"></i>
</span>
</button>
<div id="actions_buttons_container">
<button type="button" class='btn--primary action_button' id="do_inventory" style="display:none;">
Faire un inventaire Faire un inventaire
</button> </button>
<button type="button" class='btn--danger action_button' id="delete_order_button">
Supprimer la commande
</button>
</div>
</div>
<a class='btn--warning link_as_button' id="access_metabase" style="display:none;" href="{{metabase_url}}" target="_blank">
Stats Métabase
</a>
</div>
</div> </div>
<div class="header txtcenter"> <div class="header txtcenter">
...@@ -49,14 +76,23 @@ ...@@ -49,14 +76,23 @@
</div> </div>
<div class="txtcenter" id="order_forms_container"> <div class="txtcenter" id="order_forms_container">
<form action="javascript:;" id="coverage_form"> <form action="javascript:;" id="supplier_form" class="order_form_item">
<input type="number" name="coverage_days" id="coverage_days_input" placeholder="Nb jours de couverture" min="1">
<button type="submit" class='btn--primary'>Calculer les besoins</button>
</form>
<form action="javascript:;" id="supplier_form">
<input type="text" name="supplier" id="supplier_input" placeholder="Rechercher un fournisseur par son nom"> <input type="text" name="supplier" id="supplier_input" placeholder="Rechercher un fournisseur par son nom">
<button type="submit" class='btn--primary'>Ajouter le fournisseur</button> <button type="submit" class='btn--primary'>Ajouter le fournisseur</button>
</form> </form>
<form action="javascript:;" id="stats_date_from_form" class="order_form_item">
<label for="stats_date_period_select">Période de calcul de la conso moyenne </label>
<select name="stats_date_period_select" id="stats_date_period_select">
<option value="">Par défaut</option>
<option value="1week">1 semaine</option>
<option value="2weeks">2 semaines</option>
</select>
</form>
<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">
<button type="submit" class='btn--primary'>Calculer les besoins</button>
</form>
</div> </div>
<div id="suppliers_container"></div> <div id="suppliers_container"></div>
...@@ -117,8 +153,14 @@ ...@@ -117,8 +153,14 @@
<span class="pill_supplier_name"></span> <span class="pill_supplier_name"></span>
<i class="fas fa-times remove_supplier_icon"></i> <i class="fas fa-times remove_supplier_icon"></i>
</div> </div>
<div class="supplier_data">
<div class="supplier_total_value_container"> <div class="supplier_total_value_container">
Total: <span class="supplier_total_value">0</span> Total : <span class="supplier_total_value">0</span>
</div>
&nbsp;&nbsp;|&nbsp;&nbsp;
<div class="supplier_total_packages_container">
Nb colis : <span class="supplier_total_packages">0</span>
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -126,6 +168,7 @@ ...@@ -126,6 +168,7 @@
<div id="order_pill_template"> <div id="order_pill_template">
<div class="pill order_pill btn btn--primary"> <div class="pill order_pill btn btn--primary">
<span class="pill_order_name"></span> <span class="pill_order_name"></span>
<i class="fas fa-times remove_order_icon"></i>
</div> </div>
</div> </div>
...@@ -136,7 +179,7 @@ ...@@ -136,7 +179,7 @@
<h4 class="new_order_date_planned"></h4> <h4 class="new_order_date_planned"></h4>
<div class='download_order_file'> <div class='download_order_file'>
<i class="fas fa-spinner fa-spin download_order_file_loading"></i> <i class="fas fa-spinner fa-spin download_order_file_loading"></i>
<a class='btn--success download_order_file_button' style="display:none;" href="#"> <a class='btn--success download_order_file_button link_as_button' style="display:none;" href="#">
Télécharger le fichier de commande Télécharger le fichier de commande
</a> </a>
</div> </div>
...@@ -157,6 +200,15 @@ ...@@ -157,6 +200,15 @@
<hr/> <hr/>
</div> </div>
<div id="modal_remove_order">
<h3>Attention !</h3>
<p class="remove_order_modal_text">
Vous vous apprêtez à <b style="color: #d9534f;">supprimer</b> cette commande en cours : <span class="remove_order_name"></span>.<br/>
</p>
<p>Êtez-vous sûr ?</p>
<hr/>
</div>
<div id="modal_remove_supplier"> <div id="modal_remove_supplier">
<h3>Attention !</h3> <h3>Attention !</h3>
<p> <p>
...@@ -194,6 +246,19 @@ ...@@ -194,6 +246,19 @@
<hr/> <hr/>
</div> </div>
<div id="modal_end_supplier_product_association">
<h3>Attention !</h3>
<p>
Vous vous apprêtez à rendre le produit <span class="product_name"></span>
indisponible chez le fournisseur <span class="supplier_name"></span>.
</p>
<p>
L'association sera supprimée dès que vous aurez cliqué sur "Valider".<br/>
</p>
<p>Êtez-vous sûr ?</p>
<hr/>
</div>
<div id="modal_create_inventory"> <div id="modal_create_inventory">
<p> <p>
Vous vous apprêtez à créer un inventaire de <span class="inventory_products_count"></span> produits. Vous vous apprêtez à créer un inventaire de <span class="inventory_products_count"></span> produits.
...@@ -244,6 +309,7 @@ ...@@ -244,6 +309,7 @@
var couchdb_dbname = '{{db}}'; var couchdb_dbname = '{{db}}';
var couchdb_server = '{{couchdb_server}}' + couchdb_dbname; var couchdb_server = '{{couchdb_server}}' + couchdb_dbname;
var odoo_server = '{{odoo_server}}'; var odoo_server = '{{odoo_server}}';
var metabase_url = '{{metabase_url}}';
</script> </script>
<script src="{% static "js/all_common.js" %}?v="></script> <script src="{% static "js/all_common.js" %}?v="></script>
<script type="text/javascript" src="{% static 'js/orders_helper.js' %}?v="></script> <script type="text/javascript" src="{% static 'js/orders_helper.js' %}?v="></script>
......
...@@ -83,16 +83,7 @@ ...@@ -83,16 +83,7 @@
<hr /> <hr />
<section class="set_member_to_movement"> <section class="set_member_to_movement">
<p>Avant de valider l'opération, merci de nous dire qui vous êtes :</p> <p>Avant de valider l'opération, merci de nous dire qui vous êtes :</p>
<form id="sm_search_member_form" action="javascript:;" method="post"> {% include "members/member_selection.html" %}
<input type="text" id="sm_search_member_input" value="" placeholder="Votre nom ou numéro..." required>
<button type="submit" class="btn--primary" id="sm_search_member_button">Recherche</button>
</form>
<div class="search_member_results_area" style="display:none;">
<div>
<p>Vous êtes (cliquez sur votre nom) :</p>
</div>
<div class="search_member_results"></div>
</div>
</section> </section>
<hr /> <hr />
</div> </div>
......
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