Commit adf52d03 by François C.

Cooperatic internal repository modifications to integrate before this repository to close

parent b4d49b83
Pipeline #769 failed with stage
in 11 seconds
"""Company specific data values."""
EMAIL_DOMAIN = 'lacagette-coop.fr'
OPEN_ON_SUNDAY = True
CAP_JOURNAL_ID = 9
CAP_APPELE_NON_VERSE_ACCOUNT_ID = 529
CAP_APPELE_VERSE_ACCOUNT_ID = 8
CAP_INVOICE_LINE_ACCOUNT_ID = 8
FUNDRAISING_CAT_ID = 1
UNITE_UOM_ID = 1
PARTS_A_PRODUCT_ID = 1008
PARTS_A_PRICE_UNIT = 10.0
COOP_BARCODE_RULE_ID = 11
CHECK_PAYMENT_ID = 8
CASH_PAYMENT_ID = 18
STOCK_LOC_ID = 12
CATEG_FRUIT = 151
CATEG_LEGUME = 152
FLV_CSV_NB = 4
COEFF_MAG_ID = 1
LOSSES_LOC_ID = 33
LOSSES_PICKING_TYPE_ID = 10
AUTOCONSO_LOC_ID = 27
AUTOCONSO_PICKING_TYPE_ID = 7
MEALS_LOC_ID = 36
MEALS_PICKING_TYPE_ID = 16
DEFAULT_MAX_TIMESLOT_CARTS = 5
MIN_DELAY_FOR_SLOT = 4
HOURS_FOR_VALIDATION_SHOP = 3
CART_VALIDATION_BOTTOM_MSG = "Pour des raisons d'hygiène les commandes seront préparées dans des sacs en papier kraft qui vous seront facturées, 0,24€ pour les petits et 0,77€ pour les grands. Merci de votre compréhension"
SUBSCRIPTION_PAYMENT_MEANINGS = [{'code': 'cash', 'title': 'Espèces', 'journal_id': CASH_PAYMENT_ID},
{'code': 'ch', 'title': 'Chèque', 'journal_id': CHECK_PAYMENT_ID}]
EM_URL = ''
RECEPTION_MERGE_ORDERS_PSWD = 'jpsrcp'
DAV_PATH = '/data/dav/cagette'
TOOLS_SERVER = 'https://outils.lacagette-coop.fr'
ADMIN_IDS = [13]
BRINKS_MUST_IDENTIFY = False
SHOP_CAN_BUY = False
# SHOP_OPENING = {'mar.': [{'start': '10:30', 'end': '14:00'}, {'start': '15:30', 'end': '20:00'}],
# 'mer.': [{'start': '10:30', 'end': '14:00'}, {'start': '15:30', 'end': '20:00'}],
# 'jeu.': [{'start': '10:30', 'end': '14:00'}, {'start': '15:30', 'end': '20:00'}],
# 'ven.': [{'start': '10:30', 'end': '14:00'}, {'start': '15:30', 'end': '20:00'}],
# 'sam.': [{'start': '10:30', 'end': '14:00'}, {'start': '15:30', 'end': '20:00'}]
# }
SHOP_OPENING = {}
SHOP_OPENING_START_DATE = '2020-06-02'
SHOP_SLOT_SIZE = 30 # minutes
SHOP_CATEGORIES = {'epicerie': {'id': 75, 'label': 'Epicerie'},
'liquide': {'id': 96, 'label': 'Liquides'},
'produits_frais': {'id': 104, 'label': 'Frais'},
'surgeles': {'id': 115, 'label': 'Surgelés'},
'bazar': {'id': 122, 'label': 'Bazar'},
'droguerie': {'id': 127, 'label': 'Droguerie Hygiène'},
'parfumerie': {'id': 133, 'label': 'Parfumerie'}}
SHOP_EXTRA_MENUS = ['shop/planning_livraison_pains.html', 'shop/combien_ca_pese.html']
SHOP_SURVEY_LINK = 'https://docs.google.com/forms/d/e/1FAIpQLSczl0mMRwx3s9LbUSPYwwFTiiRa6agx7YkQM9cL41eiQnXNUw/viewform'
EXCLUDE_SHOP_CATEGORIES = [108]
PROMOTE_SHELFS_IDS = [68]
DISCOUNT_SHELFS_IDS = [74]
FL_SHELFS = [16, 17, 18]
VRAC_SHELFS = [20, 38]
SHIFT_EXCHANGE_DAYS_TO_HIDE = ''
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 !"
# 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>'
"""Company specific data values."""
EMAIL_DOMAIN = 'lesgrainsdesel.fr'
OPEN_ON_SUNDAY = True
MAG_NAME = ''
OFFICE_NAME = ''
COMPANY_NAME = 'Les Grains de Sel'
MAX_BEGIN_HOUR = '19:00'
WELCOME_ENTRANCE_MSG = 'Bienvenue aux Grains de Sel!'
CAP_JOURNAL_ID = 9
CAP_APPELE_NON_VERSE_ACCOUNT_ID = 529
CAP_APPELE_VERSE_ACCOUNT_ID = 8
CAP_INVOICE_LINE_ACCOUNT_ID = 7
FUNDRAISING_CAT_ID = 1
UNITE_UOM_ID = 1
PARTS_A_PRODUCT_ID = 5
PARTS_B_PRODUCT_ID = 6
PARTS_C_PRODUCT_ID = 7
PARTS_PRICE_UNIT = 10.0
PARTS_A_PRICE_UNIT = PARTS_PRICE_UNIT
COOP_BARCODE_RULE_ID = 55
CHECK_PAYMENT_ID = 8
VIREMENT_PAYMENT_ID = 16
CASH_PAYMENT_ID = 18
CB_PAYMENT_ID = 15
PARR_PAYMENT_ID = 29
STOCK_LOC_ID = 12
LOSSES_LOC_ID = 33
LOSSES_PICKING_TYPE_ID = 10
AUTOCONSO_LOC_ID = 27
AUTOCONSO_PICKING_TYPE_ID = 8
MEALS_LOC_ID = 36
MEALS_PICKING_TYPE_ID = 17
CATEG_FRUIT = 189
CATEG_LEGUME = 189
VRAC_CATEGS = [197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207]
EXPORT_POS_CAT_FOR_SCALES = True
SHELF_LABELS_ADD_FIELDS = ['code', 'category_print_id', 'base_price', 'categ_id', 'country_id', 'label_ids', 'uom_id', 'suppliers']
FLV_CSV_NB = 6
COEFF_MAG_ID = 1
RECEPTION_PDT_LABELS_FN = 'print_product_labels()'
RECEPTION_PDT_LABELS_TEXT = 'Cliquez sur ce bouton pour imprimer les étiquettes code-barres à coller sur les produits'
RECEPTION_PDT_LABELS_BTN_TEXT = 'Lancer l\'impression'
RECEPTION_SHELF_LABEL_PRINT = True
FIXED_BARCODE_PREFIX = '0499'
RECEPTION_ADD_ADMIN_MODE = True
RECEPTION_ADD_ALL_LEFT_IS_GOOD = True
SUBSCRIPTION_PAYMENT_MEANINGS = [
{'code': 'cash', 'title': 'Espèces', 'journal_id': CASH_PAYMENT_ID},
{'code': 'ch', 'title': 'Chèque', 'journal_id': CHECK_PAYMENT_ID},
{'code': 'cb', 'title': 'Carte bancaire', 'journal_id': CB_PAYMENT_ID},
{'code': 'vir', 'title': 'Virement', 'journal_id': VIREMENT_PAYMENT_ID},
{'code': 'parr', 'title': 'Parrainage', 'journal_id': PARR_PAYMENT_ID}
]
SUBSCRIPTION_INPUT_BARCODE = True
SUBSCRIPTION_NAME_SEP = ', '
CONCAT_NAME_ORDER = 'LF'
SUBSCRIPTION_ASK_FOR_SEX = True
WITH_WEBSITE_MENU = True
SUBSCRIPTION_ADD_STREET2 = True
SUBSCRIPTION_ADD_SECOND_PHONE = True
FORCE_HYPHEN_IN_SUBSCRIPTION_FIRSTNAME = False
SHIFT_EXCHANGE_DAYS_TO_HIDE = ''
CALENDAR_NO_MORE_LINK = True
SHIFT_INFO = """Aux Grains de Sel, un service est une plage horaire un jour en particulier, par exemple : le mardi 25/09/2018 à 13h15.
<br />A l'inverse, un créneau est une plage horaire régulière, par exemple, tous les mardi de semaine A à 13h15."""
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 contacter le Bureau Des Membres"""
ADMINS = ['francois@cooperatic.fr']
BRINKS_MUST_IDENTIFY = True
ENTRANCE_FTOP_BUTTON_DISPLAY = False
CUSTOM_CSS_FILES = {'all': ['common_lgds.css'],
'members': ['inscription_lgds.css']}
"""Company specific data values."""
"""Odoo coop specific constants ."""
EMAIL_DOMAIN = 'supercafoutch.fr'
CAP_JOURNAL_ID = 9
CAP_APPELE_NON_VERSE_ACCOUNT_ID = 529
CAP_APPELE_VERSE_ACCOUNT_ID = 8
CAP_INVOICE_LINE_ACCOUNT_ID = 7
FUNDRAISING_CAT_ID = 1
UNITE_UOM_ID = 1
PARTS_A_PRODUCT_ID = 5
PARTS_B_PRODUCT_ID = 6
PARTS_C_PRODUCT_ID = 7
PARTS_PRICE_UNIT = 10.0
PARTS_A_PRICE_UNIT = PARTS_PRICE_UNIT
EXPORT_COMPTA_FORMAT = 'Quadratus'
COOP_BARCODE_RULE_ID = 11
CHECK_PAYMENT_ID = 8
VIREMENT_PAYMENT_ID = 16
CASH_PAYMENT_ID = 18
CB_PAYMENT_ID = 15
HELLO_ASSO_PAYMENT_ID = 29
SUMUP_PAYMENT_ID = 30
STOCK_LOC_ID = 12
CATEG_FRUIT = 151
CATEG_LEGUME = 152
VRAC_CATEGS = [166, 167, 174, 179]
FLV_CSV_NB = 2
COEFF_MAG_ID = 1
RECEPTION_PDT_LABELS_FN = 'print_product_labels()'
RECEPTION_PDT_LABELS_TEXT = 'Cliquez sur ce bouton pour imprimer les étiquettes code-barres à coller sur les produits'
RECEPTION_PDT_LABELS_BTN_TEXT = 'Lancer l\'impression'
RECEPTION_SHELF_LABEL_PRINT = False
FIXED_BARCODE_PREFIX = '0491'
SUBSCRIPTION_PAYMENT_MEANINGS = [
{'code': 'cash', 'title': 'Espèces', 'journal_id': CASH_PAYMENT_ID},
{'code': 'ch', 'title': 'Chèque', 'journal_id': CHECK_PAYMENT_ID},
{'code': 'cb', 'title': 'Carte bancaire', 'journal_id': CB_PAYMENT_ID},
{'code': 'vir', 'title': 'Virement', 'journal_id': VIREMENT_PAYMENT_ID},
{'code': 'hel', 'title': 'HelloAsso', 'journal_id': HELLO_ASSO_PAYMENT_ID}
]
SHOP_HEADER_IMG = 'https://supercafoutch.fr/wp-content/uploads/2018/02/SC-logo-4-invert@1x.png'
SHOP_OPENING = {'jeu.': [{'start': '15:45', 'end': '18:15'}, {'start': '18:30', 'end': '21:00'}],
'ven.': [{'start': '15:45', 'end': '18:15'}, {'start': '18:30', 'end': '21:00'}],
'sam.': [{'start': '10:15', 'end': '12:45'}, {'start': '13:00', 'end': '15:30'}]}
SHOP_SLOT_SIZE = 15 # minutes
SHOP_CATEGORIES = {
'epicerie': {'id': 75, 'label': 'Epicerie'},
'liquide': {'id': 96, 'label': 'Liquides'},
'produits_frais': {'id': 104, 'label': 'Frais'},
'surgeles': {'id': 115, 'label': 'Surgelés'},
'bazar': {'id': 122, 'label': 'Bazar'},
'droguerie': {'id': 127, 'label': 'Droguerie Hygiène'},
'parfumerie': {'id': 133, 'label': 'Parfumerie'}
}
DELIVERY_CAN_BUY = True
EXCLUDE_SHOP_CATEGORIES=[]
DEFAULT_MAX_TIMESLOT_CARTS = 1
MIN_DELAY_FOR_SLOT = 0
HOURS_FOR_VALIDATION_SHOP = 2
SHOW_SUBSTITUTION_OPTION = False
CART_VALIDATION_BOTTOM_MSG = ""
SHOP_LIMIT_PRODUCTS = ['relatively_available', 'no_shelf']
VALIDATION_ORDER_MAIL_TEMPLATE = 'shop/supercafoutch_validation_mail.html'
ADMINS = ['francois@cooperatic.fr']
BRINKS_MUST_IDENTIFY = True
PROMOTE_SHELFS_IDS = []
DISCOUNT_SHELFS_IDS = []
FL_SHELFS = []
VRAC_SHELFS = []
...@@ -105,6 +105,14 @@ class CagetteInventory(models.Model): ...@@ -105,6 +105,14 @@ class CagetteInventory(models.Model):
return res return res
@staticmethod @staticmethod
def get_custom_list_inv_status(list_id):
file_data = {}
with open(custom_list_file_path + list_id + '.json') as json_file:
file_data = json.load(json_file)
return file_data['inventory_status']
@staticmethod
def create_custom_inv_file(line_ids, line_type): def create_custom_inv_file(line_ids, line_type):
res = {} res = {}
...@@ -153,7 +161,7 @@ class CagetteInventory(models.Model): ...@@ -153,7 +161,7 @@ class CagetteInventory(models.Model):
'products': ids 'products': ids
} }
# Crate inventory file, name is timestamp of creation # Create inventory file, name is timestamp of creation
timestamp = int(time.time()) timestamp = int(time.time())
filename = custom_list_file_path + str(timestamp) + '.json' filename = custom_list_file_path + str(timestamp) + '.json'
with open(filename, 'w+') as outfile: with open(filename, 'w+') as outfile:
......
...@@ -12,7 +12,7 @@ function init_datatable() { ...@@ -12,7 +12,7 @@ function init_datatable() {
{ {
data:"datetime_created", data:"datetime_created",
title:"Liste", title:"Liste",
render: function (data, type, full, meta) { render: function (data) {
return "Liste du " + data; return "Liste du " + data;
} }
}, },
...@@ -31,7 +31,7 @@ function init_datatable() { ...@@ -31,7 +31,7 @@ function init_datatable() {
title:"Inventaire à faire", title:"Inventaire à faire",
className:"dt-body-center", className:"dt-body-center",
width: "15%", width: "15%",
render: function (data, type, full, meta) { render: function (data) {
if (data == '') if (data == '')
return "<button class='btn--primary do_inventory'>Inventaire en rayon</button>"; return "<button class='btn--primary do_inventory'>Inventaire en rayon</button>";
else else
......
...@@ -9,6 +9,7 @@ urlpatterns = [ ...@@ -9,6 +9,7 @@ urlpatterns = [
url(r'^delete_custom_list$', views.delete_custom_list), url(r'^delete_custom_list$', views.delete_custom_list),
url(r'^custom_list_inventory/([0-9]+)$', views.custom_list_inventory), url(r'^custom_list_inventory/([0-9]+)$', views.custom_list_inventory),
url(r'^get_custom_list_data$', views.get_custom_list_data), url(r'^get_custom_list_data$', views.get_custom_list_data),
url(r'^inventory_process_state/([0-9]+)$', views.inventory_process_state),
url(r'^do_custom_list_inventory$', views.do_custom_list_inventory), url(r'^do_custom_list_inventory$', views.do_custom_list_inventory),
url(r'^generate_inventory_list$', views.generate_inventory_list), url(r'^generate_inventory_list$', views.generate_inventory_list),
url(r'^get_credentials$', views.get_credentials), url(r'^get_credentials$', views.get_credentials),
......
...@@ -64,6 +64,17 @@ def get_custom_list_data(request): ...@@ -64,6 +64,17 @@ def get_custom_list_data(request):
except Exception as e: except Exception as e:
return JsonResponse(id, status=500) return JsonResponse(id, status=500)
def inventory_process_state(request, list_id):
res = {}
try:
res['state'] = CagetteInventory.get_custom_list_inv_status(list_id)
except Exception as e:
# Exception raised: no file found ; inventory is done
res['state'] = 'done'
return JsonResponse({'res': res})
def do_custom_list_inventory(request): def do_custom_list_inventory(request):
res = {} res = {}
data = json.loads(request.body.decode()) data = json.loads(request.body.decode())
...@@ -97,7 +108,7 @@ def do_custom_list_inventory(request): ...@@ -97,7 +108,7 @@ def do_custom_list_inventory(request):
else: else:
return JsonResponse({'res': res}) return JsonResponse({'res': res})
@csrf_exempt @csrf_exempt
def generate_inventory_list(request): def generate_inventory_list(request):
"""Responding to Odoo ajax call (no csrf).""" """Responding to Odoo ajax call (no csrf)."""
res = {} res = {}
......
...@@ -986,8 +986,18 @@ class CagetteServices(models.Model): ...@@ -986,8 +986,18 @@ class CagetteServices(models.Model):
'seats_available'] 'seats_available']
c = [['active', '=', True]] c = [['active', '=', True]]
shift_templates = api.search_read('shift.template', c, f) shift_templates = api.search_read('shift.template', c, f)
# Get count of active registrations for each shift template
# shift_templates_active_count = api.execute('lacagette_shifts', 'get_active_shifts', [])
# With LGDS tests, seats_reserved reflects better what's shown in Odoo ...
title = re.compile(r"^(\w{1})(\w{3})\. - (\d{2}:\d{2}) ?-? ?(\w*)") title = re.compile(r"^(\w{1})(\w{3})\. - (\d{2}:\d{2}) ?-? ?(\w*)")
for l in shift_templates: for l in shift_templates:
# nb_reserved = 0
# for stac in shift_templates_active_count:
# if stac['shift_template_id'] == l['id']:
# nb_reserved = stac['seats_active_registration']
line = {} line = {}
end = time.strptime(l['end_datetime_tz'], "%Y-%m-%d %H:%M:%S") end = time.strptime(l['end_datetime_tz'], "%Y-%m-%d %H:%M:%S")
end_min = str(end.tm_min) end_min = str(end.tm_min)
...@@ -995,6 +1005,7 @@ class CagetteServices(models.Model): ...@@ -995,6 +1005,7 @@ class CagetteServices(models.Model):
end_min = '00' end_min = '00'
line['end'] = str(end.tm_hour) + ':' + end_min line['end'] = str(end.tm_hour) + ':' + end_min
line['max'] = l['seats_max'] line['max'] = l['seats_max']
# line['reserved'] = nb_reserved
line['reserved'] = l['seats_reserved'] line['reserved'] = l['seats_reserved']
line['week'] = l['week_number'] line['week'] = l['week_number']
line['id'] = l['id'] line['id'] = l['id']
...@@ -1214,4 +1225,3 @@ class CagetteUser(models.Model): ...@@ -1214,4 +1225,3 @@ class CagetteUser(models.Model):
pass pass
return answer return answer
...@@ -212,8 +212,12 @@ function store_new_coop(event) { ...@@ -212,8 +212,12 @@ function store_new_coop(event) {
msex = $('input[name="sex"]:checked').val(); msex = $('input[name="sex"]:checked').val();
} }
if (payment_meaning.val() == 'ch' && ch_qty.val() <1) { if (payment_meaning.val() == 'ch') {
errors.push("Le nombre de chèque est obligatoire."); if (ch_qty.val() <1) {
errors.push("Le nombre de chèque est obligatoire.");
} else if (ch_qty.val() > max_chq_nb) {
errors.push("Le nombre de chèque est trop grand.");
}
} }
$.ajax({url : '/members/exists/' + email, $.ajax({url : '/members/exists/' + email,
......
...@@ -20,7 +20,8 @@ var current_displayed_member = null, ...@@ -20,7 +20,8 @@ var current_displayed_member = null,
loaded_services = null, loaded_services = null,
selected_service = null, selected_service = null,
last_search_time = null, last_search_time = null,
rattrapage_ou_volant = null; rattrapage_ou_volant = null,
timeout_counter = null;
var search_button = $('.btn--primary.search'); var search_button = $('.btn--primary.search');
var loading2 = $('.loading2'); var loading2 = $('.loading2');
var search_field = $('input[name="search_string"]'); var search_field = $('input[name="search_string"]');
...@@ -77,9 +78,10 @@ function fill_member_slide(member) { ...@@ -77,9 +78,10 @@ function fill_member_slide(member) {
html_elts.name.html(member.name); html_elts.name.html(member.name);
var img_src = ''; var img_src = '';
if (typeof member.image_medium.length != "undefined") { if (member.image_medium) {
img_src = 'data:image/'+member.image_extension+';base64,'+member.image_medium; img_src = 'data:image/'+member.image_extension+';base64,'+member.image_medium;
} else { } else {
img_src = "/static/img/pas-de-photo.png";
no_pict_msg.show(); no_pict_msg.show();
} }
html_elts.image_medium.html('<img src="'+img_src+'" width="128" />'); html_elts.image_medium.html('<img src="'+img_src+'" width="128" />');
...@@ -157,8 +159,8 @@ function canSearch() { ...@@ -157,8 +159,8 @@ function canSearch() {
return answer; return answer;
} }
function search_member() { function search_member(force_search = false) {
if (canSearch() == true) { if (canSearch() || force_search) {
html_elts.member_slide.hide(); html_elts.member_slide.hide();
search_box_clear_html_elts(); search_box_clear_html_elts();
...@@ -588,6 +590,13 @@ function goto_page(jquery_page_selected) { ...@@ -588,6 +590,13 @@ function goto_page(jquery_page_selected) {
jquery_page_selected.css('display', 'grid'); jquery_page_selected.css('display', 'grid');
} }
function timeout_to_homepage() {
if (timeout_counter) clearTimeout(timeout_counter);
timeout_counter = setTimeout(function() {
goto_page(pages.first_page);
}, 40000);
}
$('button.search').click(search_member); $('button.search').click(search_member);
search_field.keyup(search_input_listing); search_field.keyup(search_input_listing);
...@@ -657,32 +666,42 @@ html_elts.image_medium.on('click', function() { ...@@ -657,32 +666,42 @@ html_elts.image_medium.on('click', function() {
}); });
$(document).ready(function() { $(document).ready(function() {
var pressed = false;
var chars = []; var chars = [];
//barcode-reader
var shopping_entry_btn = $('a[data-next="shopping_entry"]');
shopping_entry_btn.on('click', function() {
// Always focus on search field
search_field.focus();
// Return to homepage after 40 seconds
timeout_to_homepage();
});
// Force barcode-reader to search member
$(window).keypress(function(e) { $(window).keypress(function(e) {
if (e.which >= 48 && e.which <= 57) { if (e.which >= 48 && e.which <= 57) {
chars.push(String.fromCharCode(e.which)); chars.push(String.fromCharCode(e.which));
} }
if (pressed == false) { timeout_to_homepage();
setTimeout(function() {
if (chars.length >= 13) {
var barcode = chars.join("");
if (!isNaN(barcode)) { setTimeout(function() {
chars = []; if (chars.length >= 13) {
pressed = false; var barcode = chars.join("");
search_member();
}
if (!isNaN(barcode)) {
chars = [];
goto_page(pages.shopping_entry);
search_field.val(barcode);
last_search_time = null;
search_member(true);
} }
}
}, 300); }, 300);
}
pressed = true;
}); });
init_webcam(); init_webcam();
$('#crop_width').change(function() { $('#crop_width').change(function() {
Webcam.reset(); Webcam.reset();
......
...@@ -501,27 +501,33 @@ function coop_problem_delete() { ...@@ -501,27 +501,33 @@ function coop_problem_delete() {
} }
function open_coop_form(e) { function open_coop_form(e) {
if (e) { try {
var clicked = $(this); if (e) {
var clicked = $(this);
coop_target = {};
coop_target.id = clicked.data('id');
coop_target.validation_state = clicked.closest('div.main').attr('id');
} else {
return display_current_coop_form();
}
set_current_coop(coop_target, function() { coop_target = {};
if (coop_target.validation_state == 'to_fill') { coop_target.id = clicked.data('id');
display_current_coop_form(); coop_target.validation_state = clicked.closest('div.main').attr('id');
} else if ((coop_target.validation_state == 'waiting_validation_employee' || coop_target.validation_state == 'waiting_validation_member') && coop_is_connected()) {
display_current_coop_form();
} else if (coop_target.validation_state == 'done') {
ask_for_deletion();
} else { } else {
process_signaler_click(); return display_current_coop_form();
} }
});
set_current_coop(coop_target, function() {
if (coop_target.validation_state == 'to_fill') {
display_current_coop_form();
} else if ((coop_target.validation_state == 'waiting_validation_employee' || coop_target.validation_state == 'waiting_validation_member') && coop_is_connected()) {
display_current_coop_form();
} else if (coop_target.validation_state == 'done') {
ask_for_deletion();
} else {
process_signaler_click();
}
});
} catch (err) {
error = {msg: err.name + ' : ' + err.message, ctx: 'open_coop_form'};
console.error(error);
report_JS_error(error, 'prepa-odoo');
}
} }
function ask_for_deletion() { function ask_for_deletion() {
...@@ -537,56 +543,68 @@ function ask_for_problem_deletion() { ...@@ -537,56 +543,68 @@ function ask_for_problem_deletion() {
openModal(msg, coop_problem_delete, 'Oui'); openModal(msg, coop_problem_delete, 'Oui');
} }
function add_coop_to_box(box, coop) { function add_coop_to_box(box, coop) {
var info = coop.firstname + ' ' + coop.lastname; try {
var info = coop.firstname + ' ' + coop.lastname;
if (coop.odoo_state && coop.odoo_state == 'done') { if (coop.odoo_state && coop.odoo_state == 'done') {
info = coop.barcode_base + ' - ' + info; info = coop.barcode_base + ' - ' + info;
} }
info = $('<span/>').text(info); info = $('<span/>').text(info);
var cbox = $('<div/>').addClass('coop') var cbox = $('<div/>').addClass('coop')
.attr('data-id', coop._id) .attr('data-id', coop._id)
.append(info); .append(info);
// Only connected user can do these actions // Only connected user can do these actions
if ([ if ([
"waiting_validation_employee", "waiting_validation_employee",
"waiting_validation_member", "waiting_validation_member",
"done" "done"
].indexOf(coop.validation_state) > -1 ].indexOf(coop.validation_state) > -1
&& !coop_is_connected()) { && !coop_is_connected()) {
cbox.addClass('coop_no_select'); cbox.addClass('coop_no_select');
}
box.append(cbox);
} catch (e) {
err = {msg: e.name + ' : ' + e.message, ctx: 'add_coop_to_box'};
console.error(err);
report_JS_error(err, 'prepa-odoo');
} }
box.append(cbox);
} }
//Called after having retrieved all coops //Called after having retrieved all coops
function dispatch_coops_in_boxes() { function dispatch_coops_in_boxes() {
$('div.coop').off('click', open_coop_form); try {
to_fill_box.html(''); $('div.coop').off('click', open_coop_form);
with_errors_box.html(''); to_fill_box.html('');
waiting_validation_employee_div.find('.elts').html(''); with_errors_box.html('');
waiting_validation_member_div.find('.elts').html(''); waiting_validation_employee_div.find('.elts').html('');
done_div.find('.elts').html(''); waiting_validation_member_div.find('.elts').html('');
done_div.find('.elts').html('');
$.each(coops.to_fill, function(i, e) {
add_coop_to_box(to_fill_box, e); $.each(coops.to_fill, function(i, e) {
}); add_coop_to_box(to_fill_box, e);
$.each(coops.with_errors, function(i, e) { });
add_coop_to_box(with_errors_box, e); $.each(coops.with_errors, function(i, e) {
}); add_coop_to_box(with_errors_box, e);
$.each(coops.waiting_validation_employee, function(i, e) { });
add_coop_to_box(waiting_validation_employee_div.find('.elts'), e); $.each(coops.waiting_validation_employee, function(i, e) {
}); add_coop_to_box(waiting_validation_employee_div.find('.elts'), e);
$.each(coops.waiting_validation_member, function(i, e) { });
add_coop_to_box(waiting_validation_member_div.find('.elts'), e); $.each(coops.waiting_validation_member, function(i, e) {
}); add_coop_to_box(waiting_validation_member_div.find('.elts'), e);
$.each(coops.done, function(i, e) { });
add_coop_to_box(done_div.find('.elts'), e); $.each(coops.done, function(i, e) {
}); add_coop_to_box(done_div.find('.elts'), e);
});
$('div.coop').on('click', open_coop_form); $('div.coop').on('click', open_coop_form);
$('div.coop_no_select').off('click', open_coop_form); $('div.coop_no_select').off('click', open_coop_form);
} catch (e) {
err = {msg: e.name + ' : ' + e.message, ctx: 'dispatch_coops_in_boxes'};
console.error(err);
report_JS_error(err, 'prepa-odoo');
}
} }
function handle_legacy_states(rows) { function handle_legacy_states(rows) {
...@@ -609,49 +627,55 @@ function handle_legacy_states(rows) { ...@@ -609,49 +627,55 @@ function handle_legacy_states(rows) {
// first function called when loading page // first function called when loading page
function retrieve_all_coops() { function retrieve_all_coops() {
dbc.allDocs({include_docs: true, descending: true}, function(err, resp) { try {
if (err) { dbc.allDocs({include_docs: true, descending: true}, function(err, resp) {
return console.log(err); if (err) {
} return console.log(err);
coops = {'to_fill': [], 'with_errors': [], 'waiting_validation_employee':[], 'waiting_validation_member':[], 'done':[]};
$.each(handle_legacy_states(resp.rows), function(i, e) {
if (e.doc.firstname) {
if (e.doc.errors) {
coops.with_errors.push(e.doc);
} else if (e.doc.validation_state == "to_fill") {
coops.to_fill.push(e.doc);
} else if (e.doc.validation_state == "waiting_validation_employee") {
coops.waiting_validation_employee.push(e.doc);
} else if (e.doc.validation_state == "waiting_validation_member") {
coops.waiting_validation_member.push(e.doc);
} else if (e.doc.validation_state == "done") {
coops.done.push(e.doc);
}
if (e.doc.coop_msg) {
coops.with_errors.push(e.doc);
}
} }
coops = {'to_fill': [], 'with_errors': [], 'waiting_validation_employee':[], 'waiting_validation_member':[], 'done':[]};
$.each(handle_legacy_states(resp.rows), function(i, e) {
if (e.doc.firstname) {
if (e.doc.errors) {
coops.with_errors.push(e.doc);
} else if (e.doc.validation_state == "to_fill") {
coops.to_fill.push(e.doc);
} else if (e.doc.validation_state == "waiting_validation_employee") {
coops.waiting_validation_employee.push(e.doc);
} else if (e.doc.validation_state == "waiting_validation_member") {
coops.waiting_validation_member.push(e.doc);
} else if (e.doc.validation_state == "done") {
coops.done.push(e.doc);
}
}); if (e.doc.coop_msg) {
coops.with_errors.push(e.doc);
}
}
coops['to_fill'].sort(function(a, b) { });
return a.barcode_base - b.barcode_base;
}); coops['to_fill'].sort(function(a, b) {
coops['with_errors'].sort(function(a, b) { return a.barcode_base - b.barcode_base;
return a.barcode_base - b.barcode_base; });
coops['with_errors'].sort(function(a, b) {
return a.barcode_base - b.barcode_base;
});
coops['waiting_validation_employee'].sort(function(a, b) {
return a.barcode_base - b.barcode_base;
});
coops['waiting_validation_member'].sort(function(a, b) {
return a.odoo_id - b.odoo_id;
});
coops['done'].sort(function(a, b) {
return b.timestamp - a.timestamp;
});
dispatch_coops_in_boxes();
}); });
coops['waiting_validation_employee'].sort(function(a, b) { } catch (err) {
return a.barcode_base - b.barcode_base; error = {msg: err.name + ' : ' + err.message, ctx: 'retrieve_all_coops'};
}); console.log(error);
coops['waiting_validation_member'].sort(function(a, b) { report_JS_error(error, 'prepa-odoo');
return a.odoo_id - b.odoo_id; }
});
coops['done'].sort(function(a, b) {
return b.timestamp - a.timestamp;
});
dispatch_coops_in_boxes();
});
} }
$(document).ready(function() { $(document).ready(function() {
......
//used in prepa-odoo and validation_coop
var coop_page = $('#coop_page'); var coop_page = $('#coop_page');
function show_checks_nb() { function show_checks_nb() {
......
from django.test import TestCase from django.test import TestCase
# Create your tests here.
...@@ -73,6 +73,7 @@ def inscriptions(request, type=1): ...@@ -73,6 +73,7 @@ def inscriptions(request, type=1):
'ask_for_sex': getattr(settings, 'SUBSCRIPTION_ASK_FOR_SEX', False), 'ask_for_sex': getattr(settings, 'SUBSCRIPTION_ASK_FOR_SEX', False),
'open_on_sunday': getattr(settings, 'OPEN_ON_SUNDAY', False), 'open_on_sunday': getattr(settings, 'OPEN_ON_SUNDAY', False),
'POUCHDB_VERSION': getattr(settings, 'POUCHDB_VERSION', ''), 'POUCHDB_VERSION': getattr(settings, 'POUCHDB_VERSION', ''),
'max_chq_nb': getattr(settings, 'MAX_CHQ_NB', 12),
'db': settings.COUCHDB['dbs']['member']} 'db': settings.COUCHDB['dbs']['member']}
response = HttpResponse(template.render(context, request)) response = HttpResponse(template.render(context, request))
......
...@@ -212,9 +212,10 @@ class Order(models.Model): ...@@ -212,9 +212,10 @@ class Order(models.Model):
def get_custom_barcode_labels_to_print(self): def get_custom_barcode_labels_to_print(self):
import re import re
fixed_prefix = getattr(settings, 'FIXED_BARCODE_PREFIX', '0490')
labels_data = {'total': 0, 'details': []} labels_data = {'total': 0, 'details': []}
lines = self.get_lines() lines = self.get_lines()
bc_pattern = re.compile('^0490') bc_pattern = re.compile('^' + fixed_prefix)
for l in lines: for l in lines:
if ('barcode' in l) and not (bc_pattern.match(str(l['barcode'])) is None): if ('barcode' in l) and not (bc_pattern.match(str(l['barcode'])) is None):
labels_data['details'].append(l) labels_data['details'].append(l)
...@@ -256,7 +257,8 @@ class Orders(models.Model): ...@@ -256,7 +257,8 @@ class Orders(models.Model):
import re import re
labels_data = {} labels_data = {}
try: try:
bc_pattern = re.compile('^0490|0491') fixed_prefix = getattr(settings, 'FIXED_BARCODE_PREFIX', '0490')
bc_pattern = re.compile('^' + fixed_prefix)
for l in Orders.get_lines(oids): for l in Orders.get_lines(oids):
if not (bc_pattern.match(str(l['barcode'])) is None): if not (bc_pattern.match(str(l['barcode'])) is None):
if not (l['product_tmpl_id'] in labels_data): if not (l['product_tmpl_id'] in labels_data):
......
...@@ -98,11 +98,14 @@ ...@@ -98,11 +98,14 @@
{'code': 'cash', 'title': 'Espèces','journal_id': CASH_PAYMENT_ID}, {'code': 'cash', 'title': 'Espèces','journal_id': CASH_PAYMENT_ID},
{'code': 'ch', 'title': 'Chèque', 'journal_id': CHECK_PAYMENT_ID}, {'code': 'ch', 'title': 'Chèque', 'journal_id': CHECK_PAYMENT_ID},
{'code': 'cb', 'title': 'Carte bancaire', 'journal_id': CB_PAYMENT_ID}, {'code': 'cb', 'title': 'Carte bancaire', 'journal_id': CB_PAYMENT_ID},
{'code': 'vir', 'title': 'Virement', 'journal_id': VIREMENT_PAYMENT_ID} {'code': 'vir', 'title': 'Virement', 'journal_id': VIREMENT_PAYMENT_ID}]
]
Used to generate payment meanings in subscription form Used to generate payment meanings in subscription form
- MAX_CHQ_NB = 12
Maximum accepted checks numbers
### Scales and labels files generation ### Scales and labels files generation
- DAV_PATH = '/data/dav/cagette' - DAV_PATH = '/data/dav/cagette'
...@@ -125,6 +128,10 @@ ...@@ -125,6 +128,10 @@
Fields add to generated text file Fields add to generated text file
- EXPORT_POS_CAT_FOR_SCALES = True
Exports POS categories data as a JSON file, and add a POS Category column in CSV file for scale database update
### Shop module ### Shop module
- SHOP_CAN_BUY = False - SHOP_CAN_BUY = False
......
...@@ -5,6 +5,7 @@ class CagetteMail: ...@@ -5,6 +5,7 @@ class CagetteMail:
@staticmethod @staticmethod
def sendWelcome(email): def sendWelcome(email):
"""Used in members/models.py , but mail is now sent by Odoo"""
from django.core.mail import send_mail from django.core.mail import send_mail
import re import re
from django.utils.html import strip_tags from django.utils.html import strip_tags
...@@ -19,15 +20,16 @@ class CagetteMail: ...@@ -19,15 +20,16 @@ class CagetteMail:
[email], [email],
fail_silently=False, fail_silently=False,
html_message=html_msg) html_message=html_msg)
@staticmethod @staticmethod
def sendCartValidation(email, cart): def sendCartValidation(email, cart):
"""Used by Shop"""
from django.core.mail import send_mail from django.core.mail import send_mail
from django.utils.html import strip_tags from django.utils.html import strip_tags
from django.template.loader import render_to_string from django.template.loader import render_to_string
from datetime import datetime from datetime import datetime
import pytz import pytz
tz = pytz.timezone("Europe/Paris") tz = pytz.timezone("Europe/Paris")
d_obj = datetime.fromtimestamp(cart['submitted_time'], tz) d_obj = datetime.fromtimestamp(cart['submitted_time'], tz)
if ('comment' in cart) and len(cart['comment']) == 0: if ('comment' in cart) and len(cart['comment']) == 0:
...@@ -35,18 +37,13 @@ class CagetteMail: ...@@ -35,18 +37,13 @@ class CagetteMail:
ctx = {'mag': settings.COMPANY_NAME, ctx = {'mag': settings.COMPANY_NAME,
'cart': cart, 'cart': cart,
'order_date': d_obj.strftime('%d/%m/%Y à %Hh%S (UTC)')} 'order_date': d_obj.strftime('%d/%m/%Y à %Hh%S (UTC)')}
try: ctx['survey_link'] = getattr(settings, 'SHOP_SURVEY_LINK', None)
ctx['survey_link'] = settings.SHOP_SURVEY_LINK
except: mail_template = getattr(settings, 'VALIDATION_ORDER_MAIL_TEMPLATE', 'shop/cart_validation_email.html')
pass
mail_template = 'shop/cart_validation_email.html'
try:
mail_template = settings.VALIDATION_ORDER_MAIL_TEMPLATE
except:
pass
html_msg = render_to_string(mail_template, ctx) html_msg = render_to_string(mail_template, ctx)
msg = strip_tags(html_msg) msg = strip_tags(html_msg)
send_mail("Votre commande en ligne à " + settings.COMPANY_NAME , send_mail("Votre commande en ligne à " + settings.COMPANY_NAME,
msg, msg,
settings.DEFAULT_FROM_EMAIL, settings.DEFAULT_FROM_EMAIL,
[email], [email],
......
...@@ -14,7 +14,6 @@ https://docs.djangoproject.com/en/1.8/ref/settings/ ...@@ -14,7 +14,6 @@ https://docs.djangoproject.com/en/1.8/ref/settings/
import os import os
from .settings_secret import * from .settings_secret import *
from .settings_constants import *
from .texts.cagette import * from .texts.cagette import *
from .config import * from .config import *
from .customized_errors_filter import * from .customized_errors_filter import *
......
...@@ -165,3 +165,26 @@ footer { position: fixed; ...@@ -165,3 +165,26 @@ footer { position: fixed;
} }
.mac-msg {background: red; color: #fff; padding: 3px;} .mac-msg {background: red; color: #fff; padding: 3px;}
.notifyjs-cancelable-base {
font-weight: bold;
padding: 8px 15px 8px 14px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
background-color: #fcf8e3;
border: 1px solid #fbeed5;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
white-space: nowrap;
padding-left: 25px;
background-repeat: no-repeat;
background-position: 3px 7px;
}
.notifyjs-cancelable-info {
color: #3A87AD;
background-color: #D9EDF7;
border-color: #BCE8F1;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QYFAhkSsdes/QAAA8dJREFUOMvVlGtMW2UYx//POaWHXg6lLaW0ypAtw1UCgbniNOLcVOLmAjHZolOYlxmTGXVZdAnRfXQm+7SoU4mXaOaiZsEpC9FkiQs6Z6bdCnNYruM6KNBw6YWewzl9z+sHImEWv+vz7XmT95f/+3/+7wP814v+efDOV3/SoX3lHAA+6ODeUFfMfjOWMADgdk+eEKz0pF7aQdMAcOKLLjrcVMVX3xdWN29/GhYP7SvnP0cWfS8caSkfHZsPE9Fgnt02JNutQ0QYHB2dDz9/pKX8QjjuO9xUxd/66HdxTeCHZ3rojQObGQBcuNjfplkD3b19Y/6MrimSaKgSMmpGU5WevmE/swa6Oy73tQHA0Rdr2Mmv/6A1n9w9suQ7097Z9lM4FlTgTDrzZTu4StXVfpiI48rVcUDM5cmEksrFnHxfpTtU/3BFQzCQF/2bYVoNbH7zmItbSoMj40JSzmMyX5qDvriA7QdrIIpA+3cdsMpu0nXI8cV0MtKXCPZev+gCEM1S2NHPvWfP/hL+7FSr3+0p5RBEyhEN5JCKYr8XnASMT0xBNyzQGQeI8fjsGD39RMPk7se2bd5ZtTyoFYXftF6y37gx7NeUtJJOTFlAHDZLDuILU3j3+H5oOrD3yWbIztugaAzgnBKJuBLpGfQrS8wO4FZgV+c1IxaLgWVU0tMLEETCos4xMzEIv9cJXQcyagIwigDGwJgOAtHAwAhisQUjy0ORGERiELgG4iakkzo4MYAxcM5hAMi1WWG1yYCJIcMUaBkVRLdGeSU2995TLWzcUAzONJ7J6FBVBYIggMzmFbvdBV44Corg8vjhzC+EJEl8U1kJtgYrhCzgc/vvTwXKSib1paRFVRVORDAJAsw5FuTaJEhWM2SHB3mOAlhkNxwuLzeJsGwqWzf5TFNdKgtY5qHp6ZFf67Y/sAVadCaVY5YACDDb3Oi4NIjLnWMw2QthCBIsVhsUTU9tvXsjeq9+X1d75/KEs4LNOfcdf/+HthMnvwxOD0wmHaXr7ZItn2wuH2SnBzbZAbPJwpPx+VQuzcm7dgRCB57a1uBzUDRL4bfnI0RE0eaXd9W89mpjqHZnUI5Hh2l2dkZZUhOqpi2qSmpOmZ64Tuu9qlz/SEXo6MEHa3wOip46F1n7633eekV8ds8Wxjn37Wl63VVa+ej5oeEZ/82ZBETJjpJ1Rbij2D3Z/1trXUvLsblCK0XfOx0SX2kMsn9dX+d+7Kf6h8o4AIykuffjT8L20LU+w4AZd5VvEPY+XpWqLV327HR7DzXuDnD8r+ovkBehJ8i+y8YAAAAASUVORK5CYII=);
}
.notifyjs-cancelable-base .buttons {width: 190px; margin: 5px auto;}
.notifyjs-cancelable-base button {width: 90px;text-align: center; margin: 3px;}
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -46,6 +46,7 @@ class CagetteProduct(models.Model): ...@@ -46,6 +46,7 @@ class CagetteProduct(models.Model):
return res return res
@staticmethod @staticmethod
def get_product_info_for_label_from_template_id(template_id): def get_product_info_for_label_from_template_id(template_id):
"""Get product info for label.""" """Get product info for label."""
...@@ -54,14 +55,26 @@ class CagetteProduct(models.Model): ...@@ -54,14 +55,26 @@ class CagetteProduct(models.Model):
fields = ['barcode', 'product_tmpl_id', 'pricetag_rackinfos', fields = ['barcode', 'product_tmpl_id', 'pricetag_rackinfos',
'price_weight_net', 'price_volume', 'list_price', 'price_weight_net', 'price_volume', 'list_price',
'weight_net', 'volume', 'to_weight'] 'weight_net', 'volume', 'to_weight']
fields += getattr(settings, 'SHELF_LABELS_ADD_FIELDS', []) additionnal_fields = getattr(settings, 'SHELF_LABELS_ADD_FIELDS', [])
return api.search_read('product.product', cond, fields) fields += additionnal_fields
product_data = api.search_read('product.product', cond, fields)
if product_data and 'suppliers' in additionnal_fields:
cond = [['product_tmpl_id.id', '=', template_id]]
fields = ['name']
suppliers = api.search_read('product.supplierinfo', cond, fields)
if suppliers:
suppliers_name = []
for s in suppliers:
suppliers_name.append(s['name'][1])
product_data[0]['suppliers'] = ', '.join(list(set(suppliers_name)))
return product_data
@staticmethod @staticmethod
def generate_label_for_printing(templ_id, directory, price=None, nb=None): def generate_label_for_printing(templ_id, directory, price=None, nb=None):
res = {} res = {}
try: try:
p = CagetteProduct.get_product_info_for_label_from_template_id(templ_id) p = CagetteProduct.get_product_info_for_label_from_template_id(templ_id)
if (p and p[0]['product_tmpl_id'][0] == int(templ_id)): if (p and p[0]['product_tmpl_id'][0] == int(templ_id)):
product = p[0] product = p[0]
txt = '' txt = ''
...@@ -91,6 +104,18 @@ class CagetteProduct(models.Model): ...@@ -91,6 +104,18 @@ class CagetteProduct(models.Model):
coop_logger.error("Generate label : %s %s", templ_id, str(e)) coop_logger.error("Generate label : %s %s", templ_id, str(e))
return res return res
@staticmethod
def register_start_supplier_shortage(product_id, partner_id, date_start):
"""Start a supplier shortage for a product"""
api = OdooAPI()
f = {
'product_id' : product_id,
'partner_id' : partner_id,
'date_start' : date_start,
}
res = api.create('product.supplier.shortage', f)
return res
class CagetteProducts(models.Model): class CagetteProducts(models.Model):
"""Initially used to make massive barcode update.""" """Initially used to make massive barcode update."""
...@@ -166,6 +191,8 @@ class CagetteProducts(models.Model): ...@@ -166,6 +191,8 @@ class CagetteProducts(models.Model):
def get_products_for_label_appli(withCandidate=False): def get_products_for_label_appli(withCandidate=False):
fields = ['sale_ok', 'uom_id', 'barcode', fields = ['sale_ok', 'uom_id', 'barcode',
'name', 'display_name', 'list_price', 'categ_id', 'image_medium'] 'name', 'display_name', 'list_price', 'categ_id', 'image_medium']
if getattr(settings, 'EXPORT_POS_CAT_FOR_SCALES', False) is True:
fields.append('pos_categ_id')
to_weight = CagetteProducts.get_products_to_weight(withCandidate, fields) to_weight = CagetteProducts.get_products_to_weight(withCandidate, fields)
if len(vcats) > 0: if len(vcats) > 0:
vrac = CagetteProducts.get_vrac_products_from_cats(vcats, withCandidate, fields) vrac = CagetteProducts.get_vrac_products_from_cats(vcats, withCandidate, fields)
...@@ -182,6 +209,19 @@ class CagetteProducts(models.Model): ...@@ -182,6 +209,19 @@ class CagetteProducts(models.Model):
fields = ['uom_id', 'display_name','barcode'] fields = ['uom_id', 'display_name','barcode']
return api.search_read('product.product', cond, fields) return api.search_read('product.product', cond, fields)
@staticmethod
def get_pos_categories():
api = OdooAPI()
fields = ['name', 'parent_id', 'sequence', 'image_small']
try:
res = api.search_read('pos.category', [], fields)
except Exception as e:
coop_logger.error('Getting POS categories : %s', str(e))
res = []
return res
@staticmethod @staticmethod
def get_all_barcodes(): def get_all_barcodes():
"""Needs lacagette_products Odoo module to be active.""" """Needs lacagette_products Odoo module to be active."""
...@@ -195,6 +235,12 @@ class CagetteProducts(models.Model): ...@@ -195,6 +235,12 @@ class CagetteProducts(models.Model):
for p in res['list']: for p in res['list']:
# transcode result to compact format (for bandwith save and browser memory) # transcode result to compact format (for bandwith save and browser memory)
# real size / 4 (for 2750 products) # real size / 4 (for 2750 products)
# following 2 lines is only useful for La Cagette (changing uom_id in Database has cascade effects...)
# TODO : Use mapping list in config.py
if p['uom_id'] == 3:
p['uom_id'] = 21
if p['uom_id'] == 20:
p['uom_id'] = 1
result['pdts'][p['barcode']] = [ result['pdts'][p['barcode']] = [
p['display_name'], p['display_name'],
p['sale_ok'], p['sale_ok'],
...@@ -202,6 +248,7 @@ class CagetteProducts(models.Model): ...@@ -202,6 +248,7 @@ class CagetteProducts(models.Model):
p['available_in_pos'], p['available_in_pos'],
p['id'], p['id'],
p['standard_price'], p['standard_price'],
p['list_price'],
p['uom_id']] p['uom_id']]
if 'uoms' in res and 'list' in res['uoms']: if 'uoms' in res and 'list' in res['uoms']:
result['uoms'] = res['uoms']['list'] result['uoms'] = res['uoms']['list']
...@@ -264,18 +311,32 @@ class CagetteProducts(models.Model): ...@@ -264,18 +311,32 @@ class CagetteProducts(models.Model):
@staticmethod @staticmethod
def get_barcode_rules(): def get_barcode_rules():
c = [['type', 'in', ['FF_price_to_weight', 'price', 'price_to_weight', 'product', 'weight' ]], ['barcode_nomenclature_id','=', 1]] result = {'patterns': [], 'aliases': {}}
rules = OdooAPI().search_read('barcode.rule', c, ['pattern'], order="sequence ASC") try:
# As rules are ordered by sequence, let's find where to stop (.* pattern) import re
stop_idx = len(rules) - 1 c = [['type', 'in', ['FF_price_to_weight', 'price', 'price_to_weight', 'product', 'weight', 'alias']], ['barcode_nomenclature_id','=', 1]]
i = 0 rules = OdooAPI().search_read('barcode.rule', c, ['pattern', 'type', 'alias'], order="sequence ASC")
for r in rules: # As rules are ordered by sequence, let's find where to stop (.* pattern)
if r['pattern'] == ".*": stop_idx = len(rules) - 1
stop_idx = i i = 0
i += 1 for r in rules:
if stop_idx > 0: if r['pattern'] == ".*":
rules = rules[:stop_idx - 1] stop_idx = i
return rules i += 1
if stop_idx > 0:
rules = rules[:stop_idx - 1]
for r in rules:
if r['type'] == 'alias':
alias_bc = re.sub('[^0-9]', '', r['pattern'])
if len(alias_bc) > 0:
result['aliases'][alias_bc] = r['alias']
elif '{' in r['pattern'] or '.' in r['pattern']:
result['patterns'].append(r)
except Exception as e:
result['error'] = str(e)
coop_logger.error("Get Barcode Rules : %s", str(e))
# coop_logger.info("Fin get bc rules : %s", str(result))
return result
@staticmethod @staticmethod
...@@ -283,16 +344,16 @@ class CagetteProducts(models.Model): ...@@ -283,16 +344,16 @@ class CagetteProducts(models.Model):
import re import re
from outils.functions import computeEAN13Check from outils.functions import computeEAN13Check
bc_map = {} bc_map = {}
rules = CagetteProducts.get_barcode_rules() rules = CagetteProducts.get_barcode_rules()
rules = rules['patterns']
# now, just keep rules with N in pattern # now, just keep rules with N in pattern
rules = list(filter(lambda x: '.' in x['pattern'], rules)) rules = list(filter(lambda x: '.' in x['pattern'], rules))
rules = list(map(lambda x: x['pattern'], rules)) rules = list(map(lambda x: x['pattern'], rules))
# now remove {NN...} from pattern # now remove {NN...} from pattern
rules = list(map(lambda x: re.sub(r'{.+}', '', x), rules)) rules = list(map(lambda x: re.sub(r'{.+}', '', x), rules))
# coop_logger.info('rules = %s', rules)
# now compile regex for pattern # now compile regex for pattern
regex = [] regex = []
for r in rules: for r in rules:
......
...@@ -13,11 +13,12 @@ IFCBarcodes = { ...@@ -13,11 +13,12 @@ IFCBarcodes = {
closeModal(); closeModal();
if (typeof bc_data.res.error == "undefined") { if (typeof bc_data.res.error == "undefined") {
this.patterns = bc_data.res.patterns; this.patterns = bc_data.res.patterns;
this.aliases = bc_data.res.aliases;
this.codes = bc_data.res.list.pdts; this.codes = bc_data.res.list.pdts;
this.uoms = bc_data.res.list.uoms; this.uoms = bc_data.res.list.uoms;
this.keys = bc_data.res.keys; this.keys = bc_data.res.keys;
} else { } else {
this.errors.push(bc_data.error); this.errors.push(bc_data.res.error);
} }
} catch (e) { } catch (e) {
err = {msg: e.name + ' : ' + e.message, ctx: 'retrieve barcodes'}; err = {msg: e.name + ' : ' + e.message, ctx: 'retrieve barcodes'};
...@@ -33,26 +34,33 @@ IFCBarcodes = { ...@@ -33,26 +34,33 @@ IFCBarcodes = {
}, },
get_corresponding_odoo_product: function(bc) { get_corresponding_odoo_product: function(bc) {
//console.log('To analyze :' + bc) //console.log('To analyze :' + bc)
var odoo_product = null;
var index = 0, var index = 0,
pattern_found = false, pattern_found = false,
encoded_value = ''; is_alias = false,
encoded_value = '',
pattern_type = '',
odoo_product = null,
product_data = null;
// Let's find out if it matches a pattern // Let's find out if it matches a pattern
while (index < this.patterns.length -1 && pattern_found === false) { while (index < this.patterns.length -1 && pattern_found === false) {
var pattern = this.patterns[index]; var pattern = this.patterns[index].pattern;
var significant_prefix = pattern.replace(/[^0-9]/g, ''); //remove all but figures var significant_prefix = pattern.replace(/[^0-9]/g, ''); //remove all but figures
if (bc.indexOf(significant_prefix) === 0) { if (bc.indexOf(significant_prefix) === 0) {
// console.log(pattern) /*
For example,
bc = 0493213018809
pattern = 0493...{NNDDD}
*/
//console.log(pattern)
// console.log(bc) // console.log(bc)
//0493...{NNDDD} (pattern) odoo_bc = '';
//0493213018809 (bc)
pattern_found = true; pattern_found = true;
pattern_type = this.patterns[index].type;
pattern = pattern.replace(/[^0-9.ND]/, ''); pattern = pattern.replace(/[^0-9.ND]/, '');
bc = bc.slice(0, -1); // remove original check figure bc = bc.slice(0, -1); // remove original check figure
odoo_bc = '';
// Read pattern character by character // Read pattern character by character
for (var i = 0; i < pattern.length; i++) { for (var i = 0; i < pattern.length; i++) {
if (/[0-9]/.exec(pattern[i])) { if (/[0-9]/.exec(pattern[i])) {
...@@ -72,13 +80,39 @@ IFCBarcodes = { ...@@ -72,13 +80,39 @@ IFCBarcodes = {
index++; index++;
} }
// let's seek "normalized" bc in codes array // let's seek "normalized" bc in codes array or alias map
for (code in this.codes) { for (alias in this.aliases) {
if (code == bc) { if (bc == alias) {
odoo_product = {barcode: code, data: this.codes[code], value: encoded_value}; is_alias = true;
for (barcode in this.codes) {
if (barcode == this.aliases[alias]) {
product_data = this.codes[barcode];
}
}
}
}
if (is_alias === false) {
for (code in this.codes) {
if (code == bc) {
product_data = this.codes[code];
}
}
}
if (product_data !== null) {
p_uom = (this.uoms)[product_data[this.keys.uom_id]];
if (encoded_value.length > 0 && !isNaN(encoded_value)) {
if (p_uom == 'Unit(s)' || p_uom == 'unité') {
encoded_value = parseInt(encoded_value, 10);
} else {
encoded_value = parseFloat(encoded_value);
}
} }
odoo_product = {barcode: bc, data: product_data, rule: pattern_type, value: encoded_value};
} }
//console.log(odoo_product)
return odoo_product; return odoo_product;
} }
}; };
...@@ -93,5 +127,6 @@ init_barcodes = async function() { ...@@ -93,5 +127,6 @@ init_barcodes = async function() {
else else
result = ifcb; result = ifcb;
// console.log(result.patterns) // console.log(result.patterns)
return result; return result;
}; };
from django.test import TestCase from django.test import TestCase
# Create your tests here.
...@@ -83,7 +83,13 @@ def labels_appli_csv(request, params): ...@@ -83,7 +83,13 @@ def labels_appli_csv(request, params):
try: try:
if (params == '/wc'): if (params == '/wc'):
withCandidate = True withCandidate = True
with_pos_categories = getattr(settings, 'EXPORT_POS_CAT_FOR_SCALES', False)
products = CagetteProducts.get_products_for_label_appli(withCandidate) products = CagetteProducts.get_products_for_label_appli(withCandidate)
if with_pos_categories is True:
pos_categories = CagetteProducts.get_pos_categories()
else:
pos_categories = []
rows = [] rows = []
for p in products: for p in products:
if (p['sale_ok'] is True): if (p['sale_ok'] is True):
...@@ -96,28 +102,31 @@ def labels_appli_csv(request, params): ...@@ -96,28 +102,31 @@ def labels_appli_csv(request, params):
barcode = '' barcode = ''
if not (barcode.isnumeric()): if not (barcode.isnumeric()):
barcode = '' barcode = ''
p_row = [p['id'], p['display_name'], barcode,
rows.append([p['id'], p['display_name'], barcode, p['list_price'],
p['list_price'], p['categ'],
# p['categ_id'][1], uom,
p['categ'], p['image'].replace("\n", "")]
uom, if with_pos_categories is True:
# p['image'].replace(img_temp_folder, ""), if p['pos_categ_id']:
p['image'].replace("\n", ""), p_row.append(p['pos_categ_id'][0])
# p['available_in_pos'] # ,p['sale_ok'] else:
]) p_row.append('')
rows.append(p_row)
header = ['id', 'nom', 'code-barre', 'prix', header = ['id', 'nom', 'code-barre', 'prix',
'categorie', 'unite', 'image' 'categorie', 'unite', 'image'
# 'en vente', 'sale_ok' # 'en vente', 'sale_ok'
] ]
if with_pos_categories is True and len(pos_categories) > 0:
header.append('id_categorie_pos')
with open(settings.DAV_PATH + '/pos_categories.json', 'w') as outfile:
json.dump(pos_categories, outfile)
os_file = settings.DAV_PATH + '/flv.csv' os_file = settings.DAV_PATH + '/flv.csv'
file_copies = [] file_copies = []
nb = 1 nb = int(getattr(settings, 'FLV_CSV_NB', 1))
if hasattr(settings, 'FLV_CSV_NB'):
nb = int(settings.FLV_CSV_NB)
for i in range(1, nb + 1): for i in range(1, nb + 1):
file_copies.append(settings.DAV_PATH + '/flv_' + str(i) + '.csv') file_copies.append(settings.DAV_PATH + '/flv_' + str(i) + '.csv')
...@@ -133,6 +142,8 @@ def labels_appli_csv(request, params): ...@@ -133,6 +142,8 @@ def labels_appli_csv(request, params):
file.close() file.close()
for c in file_copies: for c in file_copies:
copyfile(os_file, c) copyfile(os_file, c)
except Exception as e: except Exception as e:
res['error'] = str(e) res['error'] = str(e)
return JsonResponse({'res': res}) return JsonResponse({'res': res})
...@@ -176,14 +187,14 @@ def get_all_barcodes(request): ...@@ -176,14 +187,14 @@ def get_all_barcodes(request):
'available_in_pos': 3, 'available_in_pos': 3,
'id': 4, 'id': 4,
'standard_price': 5, 'standard_price': 5,
'uom_id': 6 'list_price': 6,
'uom_id': 7
} }
rules = CagetteProducts.get_barcode_rules() rules = CagetteProducts.get_barcode_rules()
res['patterns'] = rules['patterns']
res['aliases'] = rules['aliases']
res['time'] = int(round(time.time() * 1000)) - start res['time'] = int(round(time.time() * 1000)) - start
res['patterns'] = []
for r in rules:
if '{' in r['pattern'] or '.' in r['pattern']:
res['patterns'].append(r['pattern'])
except Exception as e: except Exception as e:
coop_logger.error("products_barcodes : %s", str(e)) coop_logger.error("products_barcodes : %s", str(e))
res['error'] = str(e) res['error'] = str(e)
......
...@@ -231,4 +231,8 @@ hr { ...@@ -231,4 +231,8 @@ hr {
white-space: pre-wrap; white-space: pre-wrap;
} }
#main_content {width: 100%;} #main_content {width: 100%;}
\ No newline at end of file
.select_product_action {
max-width: 15px;
}
...@@ -38,7 +38,6 @@ function group_goto(i) { ...@@ -38,7 +38,6 @@ function group_goto(i) {
* Set local storage for given order * Set local storage for given order
*/ */
function set_local_storage(order_data) { function set_local_storage(order_data) {
console.log(order_data);
if (Modernizr.localstorage) { if (Modernizr.localstorage) {
var stored_order = JSON.parse(localStorage.getItem('order_' + order_data.id)); var stored_order = JSON.parse(localStorage.getItem('order_' + order_data.id));
......
...@@ -27,7 +27,7 @@ class Shelf(models.Model): ...@@ -27,7 +27,7 @@ class Shelf(models.Model):
self.o_api = OdooAPI() self.o_api = OdooAPI()
def get(self): def get(self):
res ={} res = {}
try: try:
c = [['id', '=', self.id]] c = [['id', '=', self.id]]
f = [] f = []
...@@ -160,6 +160,7 @@ class Shelf(models.Model): ...@@ -160,6 +160,7 @@ class Shelf(models.Model):
barcodes = list(map(str, barcodes)) barcodes = list(map(str, barcodes))
# Get bc as should be stored in Odoo # Get bc as should be stored in Odoo
bc_map = CagetteProducts.get_fixed_barcode_correspondance(barcodes) bc_map = CagetteProducts.get_fixed_barcode_correspondance(barcodes)
p_res = self._get_pdts_from_barcodes(list(bc_map.values())) p_res = self._get_pdts_from_barcodes(list(bc_map.values()))
if (p_res): if (p_res):
found_bc = [] found_bc = []
...@@ -200,6 +201,12 @@ class Shelf(models.Model): ...@@ -200,6 +201,12 @@ class Shelf(models.Model):
filename = tmp_inv_file_prefix + str(self.id) + '.json' filename = tmp_inv_file_prefix + str(self.id) + '.json'
os.remove(filename) os.remove(filename)
lockfilename = tmp_inv_file_prefix + str(self.id) + '.lock'
try:
os.remove(lockfilename)
except Exception as e:
pass
return True return True
except Exception as e: except Exception as e:
return False return False
...@@ -220,6 +227,24 @@ class Shelf(models.Model): ...@@ -220,6 +227,24 @@ class Shelf(models.Model):
first_inventory = json.load(json_file) first_inventory = json.load(json_file)
except Exception as e: except Exception as e:
coop_logger.error("Unable to process first step file : %s", e) coop_logger.error("Unable to process first step file : %s", e)
import errno
raise FileExistsError(errno.ENOENT, os.strerror(errno.ENOENT), filename)
lockfilename = tmp_inv_file_prefix + str(self.id) + '.lock'
# Look for lock file: if exists, first step file is being processed so stop here
try:
with open(lockfilename) as lock_file:
return {'error': 'First step file busy', 'busy': True}
except Exception as e:
pass
# Verification passed, create the lock file to indicate first step file is being processed
try:
with open(lockfilename, 'w') as lock_file:
json.dump({}, lock_file)
except Exception as e:
coop_logger.error("Unable to create lock file : %s", e)
if first_inventory: if first_inventory:
# if poducts in saved data # if poducts in saved data
......
...@@ -106,14 +106,18 @@ function handle_blinking_effect(element) { ...@@ -106,14 +106,18 @@ function handle_blinking_effect(element) {
// When edition event is fired // When edition event is fired
function edit_event(clicked) { function edit_event(clicked) {
// Remove from origin table // Remove from origin table
var row_data = null;
if (editing_origin == 'to_process') { if (editing_origin == 'to_process') {
let row = table_to_process.row(clicked.parents('tr')); let row = table_to_process.row(clicked.parents('tr'));
let row_data = row.data();
row_data = row.data();
remove_from_toProcess(row); remove_from_toProcess(row);
} else { } else {
let row = table_processed.row(clicked.parents('tr')); let row = table_processed.row(clicked.parents('tr'));
let row_data = row.data();
row_data = row.data();
remove_from_processed(row); remove_from_processed(row);
} }
...@@ -430,7 +434,7 @@ function pre_send() { ...@@ -430,7 +434,7 @@ function pre_send() {
// Proceed with inventory: send the request to the server // Proceed with inventory: send the request to the server
function send() { function send() {
if (is_time_to('submit_inv_qties')) { if (is_time_to('submit_inv_qties')) {
// Loading on // Loading on
var wz = $('#main-waiting-zone').clone(); var wz = $('#main-waiting-zone').clone();
wz.find('.msg').text("Patience, cela peut prendre de nombreuses minutes s'il y a une centaine de produits"); wz.find('.msg').text("Patience, cela peut prendre de nombreuses minutes s'il y a une centaine de produits");
...@@ -440,6 +444,7 @@ function send() { ...@@ -440,6 +444,7 @@ function send() {
shelf.user_comments = user_comments; shelf.user_comments = user_comments;
var url = "../do_" + originView + "_inventory"; var url = "../do_" + originView + "_inventory";
var call_begin_at = new Date().getTime();
$.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } }); $.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } });
$.ajax({ $.ajax({
...@@ -475,14 +480,60 @@ function send() { ...@@ -475,14 +480,60 @@ function send() {
// Clear local storage before leaving // Clear local storage before leaving
localStorage.removeItem(originView + '_' + shelf.id); localStorage.removeItem(originView + '_' + shelf.id);
}, },
error: function(data) { // 500 error has been thrown error: function(jqXHR, textStatus) { // 500 error has been thrown or web server sent a timeout
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') { if (jqXHR.status == 504) {
console.log(data.responseJSON.error); /*
django is too long to respond.
Let it the same time laps before asking if the process is well done
*/
var now = new Date().getTime();
setTimeout(
function() {
$.ajax({
type: 'GET',
url: '../inventory_process_state/' + shelf.id,
success: function(rData) {
if ('res' in rData && 'state' in rData.res) {
// Verification for step 2 only ; step 1 is always fast
if (shelf.inventory_status == 'step1_done' && rData.res.state != 'step1_done') {
// shelf inventory has been already done
localStorage.removeItem(originView + '_' + shelf.id);
closeModal();
back();
} else {
console.log('Still in process : need to call recursively to make other calls');
}
} else {
console.log(rData);
}
}
});
}
, now - call_begin_at
);
} else if (jqXHR.status == 500) {
var message = "Erreur lors de la sauvegarde des données. " +
"Pas de panique, les données de l'inventaire n'ont pas été perdues ! " +
"Merci de contacter un salarié et de réessayer plus tard.";
if (typeof jqXHR.responseJSON != 'undefined' && typeof jqXHR.responseJSON.error != 'undefined') {
//console.log(jqXHR.responseJSON.error);
if ('busy' in jqXHR.responseJSON) {
message = "Inventaire en cours de traitement.";
} else if (jqXHR.responseJSON.error == 'FileExistsError') {
//step1 file has been found => previous request succeeded
message = "Les données avaient déjà été transmises....";
// Clear local storage before leaving
localStorage.removeItem(originView + '_' + shelf.id);
}
}
closeModal();
alert(message);
back();
} }
closeModal();
alert("Erreur lors de la sauvegarde des données. " +
"Pas de panique, les données de l'inventaire n'ont pas été perdues ! " +
"Merci de contacter un salarié et de réessayer plus tard.");
} }
}); });
} else { } else {
...@@ -776,7 +827,6 @@ $(document).ready(function() { ...@@ -776,7 +827,6 @@ $(document).ready(function() {
originView = 'custom_list'; originView = 'custom_list';
parent_location = '/inventory/custom_lists'; parent_location = '/inventory/custom_lists';
} }
//console.log(products)
// Get shelf data from local storage // Get shelf data from local storage
if (Modernizr.localstorage) { if (Modernizr.localstorage) {
......
...@@ -16,6 +16,7 @@ var main_content = $('#main-content'), ...@@ -16,6 +16,7 @@ var main_content = $('#main-content'),
.removeAttr('id'), .removeAttr('id'),
active_phase = 'main', active_phase = 'main',
add_pdts_btn_text = 'AJOUTER AU RAYON', add_pdts_btn_text = 'AJOUTER AU RAYON',
add_to_shelf_product_ids = [],
barcodes = null; barcodes = null;
...@@ -339,14 +340,27 @@ var init_and_fill_selfs_list = function() { ...@@ -339,14 +340,27 @@ var init_and_fill_selfs_list = function() {
} }
}; };
var deleteBarcodeFromList = function () { var deleteBarcodeFromList = function () {
var clicked = $(this); let clicked = $(this);
let new_pids_list = [];
let tr_to_remove = clicked.closest('tr');
let pid_to_remove = tr_to_remove.data('id');
$.each(add_to_shelf_product_ids, function(idx, pid) {
if (pid != pid_to_remove) new_pids_list.push(pid);
});
add_to_shelf_product_ids = new_pids_list;
tr_to_remove.remove();
};
var is_product_in_shelf_adding_queue_list = function(testing_pid) {
let found = false;
$.each(add_to_shelf_product_ids, function(idx, pid) {
if (pid == testing_pid) found = true;
});
clicked.closest('li').remove(); return found;
/*
if (clicked.closest('ul').find('li').length == 0)
main_content.find('button.add-products').css('display','none')
*/
}; };
var addProductToList = async function(barcode) { var addProductToList = async function(barcode) {
if (barcodes == null) barcodes = await init_barcodes(); // May appens (after inactivity?) if (barcodes == null) barcodes = await init_barcodes(); // May appens (after inactivity?)
//Get Odoo corresponding barcode //Get Odoo corresponding barcode
...@@ -355,23 +369,29 @@ var addProductToList = async function(barcode) { ...@@ -355,23 +369,29 @@ var addProductToList = async function(barcode) {
odoo_product = barcodes.get_corresponding_odoo_product(barcode); odoo_product = barcodes.get_corresponding_odoo_product(barcode);
if (odoo_product === null) { if (is_product_in_shelf_adding_queue_list(odoo_product.data[barcodes.keys.id])) {
alert(barcode + ' : Code-barre inconnu'); console.log("Already added product");
} else { } else {
var pdt_line = $('<tr>').attr('data-bc', odoo_product.barcode) add_to_shelf_product_ids.push(odoo_product.data[4]);
.addClass('obc'); if (odoo_product === null) {
alert(barcode + ' : Code-barre inconnu');
$('<td>').text(barcode) } else {
.appendTo(pdt_line); var pdt_line = $('<tr>').attr('data-id', odoo_product.data[barcodes.keys.id])
$('<td>').text(odoo_product.barcode) .attr('data-bc', odoo_product.barcode)
.appendTo(pdt_line); .addClass('obc');
$('<td>').text(odoo_product.data[0])
.appendTo(pdt_line); $('<td>').text(barcode)
$('<td>').html(delete_icon) .appendTo(pdt_line);
.appendTo(pdt_line); $('<td>').text(odoo_product.barcode)
adding_pdts_tpl.find('#added_products tbody').append(pdt_line); .appendTo(pdt_line);
main_content.find('button.add-products').css('display', 'block') $('<td>').text(odoo_product.data[barcodes.keys.name])
.html(add_pdts_btn_text); .appendTo(pdt_line);
$('<td>').html(delete_icon)
.appendTo(pdt_line);
adding_pdts_tpl.find('#added_products tbody').append(pdt_line);
main_content.find('button.add-products').css('display', 'block')
.html(add_pdts_btn_text);
}
} }
}; };
...@@ -380,7 +400,7 @@ var addProducts = async function() { ...@@ -380,7 +400,7 @@ var addProducts = async function() {
var data = rowGetData(clicked); var data = rowGetData(clicked);
if (barcodes == null) barcodes = await init_barcodes(); if (barcodes == null) barcodes = await init_barcodes();
add_to_shelf_product_ids = [];
adding_pdts_tpl.find('.shelf').text(data.name + ' (num = ' + data.sort_order+')') adding_pdts_tpl.find('.shelf').text(data.name + ' (num = ' + data.sort_order+')')
.attr('data-shelfid', data.id); .attr('data-shelfid', data.id);
adding_pdts_tpl.find('#added_products tbody').empty(); adding_pdts_tpl.find('#added_products tbody').empty();
...@@ -402,6 +422,7 @@ var recordProductsAddedShelf = function() { ...@@ -402,6 +422,7 @@ var recordProductsAddedShelf = function() {
to_add.each(function(i, e) { to_add.each(function(i, e) {
barcodes.push($(e).data('bc')); barcodes.push($(e).data('bc'));
}); });
if (is_time_to('add_pdts_to_shelf', 5000)) { // prevent double click or browser hic up bug if (is_time_to('add_pdts_to_shelf', 5000)) { // prevent double click or browser hic up bug
main_content.find('button.add-products').html(loading_img); main_content.find('button.add-products').html(loading_img);
post_form( post_form(
...@@ -518,7 +539,7 @@ $(document).ready(function() { ...@@ -518,7 +539,7 @@ $(document).ready(function() {
$(document).on('click', '.shelfs .fa-edit', open_update_form); $(document).on('click', '.shelfs .fa-edit', open_update_form);
$(document).on('click', '.shelfs .fa-trash', deleteShelf); $(document).on('click', '.shelfs .fa-trash', deleteShelf);
$(document).on('click', '.shelfs .fa-download', downloadInventoryReport); $(document).on('click', '.shelfs .fa-download', downloadInventoryReport);
$(document).on('click', '.bc .fa-trash', deleteBarcodeFromList); $(document).on('click', '.obc .fa-trash', deleteBarcodeFromList);
$(document).on('click', 'td.products .fa-plus-circle', addProducts); $(document).on('click', 'td.products .fa-plus-circle', addProducts);
$(document).on('click', '#main-content button.add-products', recordProductsAddedShelf); $(document).on('click', '#main-content button.add-products', recordProductsAddedShelf);
$(document).on('click', 'td.p_nb', showProductsList); $(document).on('click', 'td.p_nb', showProductsList);
......
...@@ -8,6 +8,7 @@ urlpatterns = [ ...@@ -8,6 +8,7 @@ urlpatterns = [
url(r'^$', views.index), url(r'^$', views.index),
url(r'^shelf_view/([0-9]+)$', views.shelf_view), url(r'^shelf_view/([0-9]+)$', views.shelf_view),
url(r'^shelf_inventory/([0-9]+)$', views.shelf_inventory), url(r'^shelf_inventory/([0-9]+)$', views.shelf_inventory),
url(r'^inventory_process_state/([0-9]+)$', views.inventory_process_state),
url(r'^all$', views.all), url(r'^all$', views.all),
url(r'^get_shelves_extra_data$', views.get_shelves_extra_data), url(r'^get_shelves_extra_data$', views.get_shelves_extra_data),
url(r'^(?P<shelf_id>\d+)$', views.shelf_data), url(r'^(?P<shelf_id>\d+)$', views.shelf_data),
......
...@@ -13,7 +13,7 @@ def index(request): ...@@ -13,7 +13,7 @@ def index(request):
"""Main shelf page""" """Main shelf page"""
shelfs = Shelfs.get_all() shelfs = Shelfs.get_all()
# TODO : Make the distinction beetween active and inactive products # TODO : Make the distinction beetween active and inactive products
for s in shelfs : for s in shelfs:
s['shelf_value'] = -1 s['shelf_value'] = -1
context = {'title': 'Rayons', context = {'title': 'Rayons',
...@@ -107,8 +107,29 @@ def add_product(request, shelf_id): ...@@ -107,8 +107,29 @@ def add_product(request, shelf_id):
return JsonResponse({'res': res}) return JsonResponse({'res': res})
def inventory_process_state(request, shelf_id):
res = {}
try:
s = Shelf(shelf_id)
s_data = s.get()
res['state'] = s_data['inventory_status']
except Exception as e:
res['error'] = str(e)
coop_logger.error("Inventory process state : %s", str(e))
if 'error' in res:
return JsonResponse(res, status=500)
else:
return JsonResponse({'res': res})
def do_shelf_inventory(request): def do_shelf_inventory(request):
"""Process shelf inventory""" """Process shelf inventory"""
"""
If many products are implied, the whole process could last many minutes.
During this time, user can submit data again.
This is managed with 'busy' message returned by Shelf.get_full_inventory_data method
Web server can also return a timeout message during this time.
This is managed by sending a query to above "get_process_state" (within browser ajax error capture)
"""
res = {} res = {}
# TODO : manage error strings array instead of one string # TODO : manage error strings array instead of one string
try: try:
...@@ -121,7 +142,8 @@ def do_shelf_inventory(request): ...@@ -121,7 +142,8 @@ def do_shelf_inventory(request):
'name': shelf_data['name'] + ' - ' + inventory_date.strftime("%d/%m/%Y"), 'name': shelf_data['name'] + ' - ' + inventory_date.strftime("%d/%m/%Y"),
'shelf_id': shelf_data['id'], 'shelf_id': shelf_data['id'],
'user_comments': shelf_data['user_comments'], 'user_comments': shelf_data['user_comments'],
'products': shelf_data['list_processed'] 'products': shelf_data['list_processed'],
'status': shelf_data['inventory_status']
} }
try: try:
filename = 'data/inventories_backup/' filename = 'data/inventories_backup/'
...@@ -129,20 +151,26 @@ def do_shelf_inventory(request): ...@@ -129,20 +151,26 @@ def do_shelf_inventory(request):
filename += "__" + str(shelf_data['id']) + '.json' filename += "__" + str(shelf_data['id']) + '.json'
with open(filename, 'w') as outfile: with open(filename, 'w') as outfile:
json.dump(shelf_data, outfile) json.dump(shelf_data, outfile)
except: except Exception as serr:
pass coop_logger.error("Inventory backup failure : %s", str(serr))
try: try:
if shelf_data['inventory_status'] == '' : if shelf_data['inventory_status'] == '':
# First step: save first products count in temp file # First step: save first products count in temp file
res = m.save_tmp_inventory(inventory_data) res = m.save_tmp_inventory(inventory_data)
else : else:
inventory_data['date'] = inventory_date inventory_data['date'] = inventory_date
inventory_data['shelf_name'] = shelf_data['name'] inventory_data['shelf_name'] = shelf_data['name']
inventory_data['shelf_num'] = shelf_data['sort_order'] inventory_data['shelf_num'] = shelf_data['sort_order']
# Get data from step 1 # Get data from step 1
full_inventory_data = m.get_full_inventory_data(inventory_data) full_inventory_data = m.get_full_inventory_data(inventory_data)
if 'error' in full_inventory_data:
res['error'] = full_inventory_data['error']
if 'busy' in full_inventory_data:
res['busy'] = True
return JsonResponse(res, status=500)
# Proceed with inventory # Proceed with inventory
res['inventory'] = CagetteInventory.update_stock_with_shelf_inventory_data(full_inventory_data) res['inventory'] = CagetteInventory.update_stock_with_shelf_inventory_data(full_inventory_data)
...@@ -173,7 +201,7 @@ def do_shelf_inventory(request): ...@@ -173,7 +201,7 @@ def do_shelf_inventory(request):
except Exception as e: except Exception as e:
# Don't validate if error anywhere in inventory process # Don't validate if error anywhere in inventory process
res['error'] = {'inventory' : str(e)} res['error'] = type(e).__name__
coop_logger.error("Shelf inv. : %s", str(e)) coop_logger.error("Shelf inv. : %s", str(e))
except Exception as err_json: except Exception as err_json:
res['error'] = "Unable to parse received JSON" res['error'] = "Unable to parse received JSON"
......
body {margin: 5px;} body {margin: 5px;}
#exchange_instructions, .need_make_up {display:none;} #exchange_instructions, .need_make_up {display:none;}
.need_make_up, .highlight {background: #ffff00;} .need_make_up, .highlight {background: #ffff00;}
.need_make_up_area {margin-top: 15px;}
#partnerData {margin:8px 0;} #partnerData {margin:8px 0;}
#shift_list_wrapper {margin:15px 25px; padding: 5px; border-radius: 5px; border: 2px solid #424242; max-width: 530px; width: 350px;} #shift_list_wrapper {margin:15px 25px; padding: 5px; border-radius: 5px; border: 2px solid #424242; max-width: 530px; width: 350px;}
...@@ -9,10 +10,10 @@ span.shift_booked{ ...@@ -9,10 +10,10 @@ span.shift_booked{
border-radius: 5px; border-radius: 5px;
border: 2px solid #424242; border: 2px solid #424242;
margin : 5px; margin : 5px;
padding:5px; padding:5px;
} }
.shift_list_item {margin-bottom:5px; padding-bottom:5px;} .shift_list_item {margin-bottom:5px; padding-bottom:5px;}
.shift_list_item label { .shift_list_item label {
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;
padding-left: 20px; padding-left: 20px;
...@@ -27,24 +28,24 @@ h3 {margin-top: 10px;} ...@@ -27,24 +28,24 @@ h3 {margin-top: 10px;}
.intro .fa-info-circle {color: #7ecbee;} .intro .fa-info-circle {color: #7ecbee;}
span.step {border-radius: 3px; border: 2px solid #424242; padding: 3px;} span.step {border-radius: 3px; border: 2px solid #424242; padding: 3px;}
/* Pour les couleur des events dans le calendrier ------------------------------------*/ /* Pour les couleur des events dans le calendrier ------------------------------------*/
.shift_booked .shift_booked
{ {
background-color: #585858; background-color: #585858;
color: #FFFFFF !important; color: #FFFFFF !important;
} }
.shift_full .shift_full
{ {
background-color: #F5A9F2; background-color: #F5A9F2;
} }
.shift_empty .shift_empty
{ {
background-color: #60B000; background-color: #60B000;
} }
.shift_less_alf .shift_less_alf
{ {
background-color: #D9EBD2; background-color: #D9EBD2;
} }
.shift_other .shift_other
{ {
background-color: #FA8282; background-color: #FA8282;
} }
......
...@@ -239,7 +239,6 @@ function addMakeUpMsg() { ...@@ -239,7 +239,6 @@ function addMakeUpMsg() {
} }
}); });
make_up_nb = parseInt(partner_points) + shifts_before_limit; make_up_nb = parseInt(partner_points) + shifts_before_limit;
// Besoin de choisir un rattrapage : afficher message rattrapage // Besoin de choisir un rattrapage : afficher message rattrapage
if (make_up_nb < 0) { if (make_up_nb < 0) {
make_up_nb = Math.abs(make_up_nb); make_up_nb = Math.abs(make_up_nb);
...@@ -284,7 +283,9 @@ function addMakeUpMsg() { ...@@ -284,7 +283,9 @@ function addMakeUpMsg() {
msg += " avant le " + limitDate.toLocaleDateString("fr-fr", optDate) + ", faute de quoi je serai suspendu.e à cette date."; msg += " avant le " + limitDate.toLocaleDateString("fr-fr", optDate) + ", faute de quoi je serai suspendu.e à cette date.";
$('#partnerData').append("<span class=\"need_make_up\"><strong>"+ msg + "</strong></span>"); msg += "<br/>Je dois m'inscrire à tous mes services de rattrapage avant de pouvoir échanger mes services.";
$('#partnerData').append("<p class=\"need_make_up_area\"><span class=\"need_make_up\"><strong>"+ msg + "</strong></span></p>");
$('.need_make_up').show(); $('.need_make_up').show();
} else { } else {
$('.need_make_up').hide(); $('.need_make_up').hide();
......
...@@ -92,6 +92,10 @@ function djLogError(e) { ...@@ -92,6 +92,10 @@ function djLogError(e) {
} }
var french_date_and_time = function(dstring) { var french_date_and_time = function(dstring) {
if (shop_mode == 'delivery') {
return dstring;
}
var formatted = dstring.trim().replace(/-/g, "/"); // replace for Safari var formatted = dstring.trim().replace(/-/g, "/"); // replace for Safari
//expected = YYYY-MM-DD HH:MM[:00] //expected = YYYY-MM-DD HH:MM[:00]
...@@ -494,7 +498,7 @@ var getStoredOrder = function() { ...@@ -494,7 +498,7 @@ var getStoredOrder = function() {
var stored = null; var stored = null;
try { try {
stored = JSON.parse(localStorage.getItem('currentOrder')); stored = JSON.parse(localStorage.getItem(current_order_name));
} catch (e) { } catch (e) {
//WARNING : In this case, make sure the user haven't got any order initialized //WARNING : In this case, make sure the user haven't got any order initialized
//TODO : make a request to retrieve it //TODO : make a request to retrieve it
...@@ -508,7 +512,7 @@ var getStoredOrderForMigration = function() { ...@@ -508,7 +512,7 @@ var getStoredOrderForMigration = function() {
var stored = null; var stored = null;
try { try {
stored = JSON.parse(localStorage.getItem('saved_order')); stored = JSON.parse(localStorage.getItem(saved_order_name));
} catch (e) { } catch (e) {
djLogError({ctx: "get stored for mig", msg:e}); djLogError({ctx: "get stored for mig", msg:e});
} }
...@@ -523,7 +527,7 @@ var storeOrderForMigration = function() { ...@@ -523,7 +527,7 @@ var storeOrderForMigration = function() {
delete to_save._rev; delete to_save._rev;
delete to_save.timer_end_date; delete to_save.timer_end_date;
delete to_save.best_date; delete to_save.best_date;
localStorage.setItem("saved_order", JSON.stringify(to_save)); localStorage.setItem(saved_order_name, JSON.stringify(to_save));
order = to_save; order = to_save;
storeOrder(); storeOrder();
} catch (e) { } catch (e) {
...@@ -532,7 +536,7 @@ var storeOrderForMigration = function() { ...@@ -532,7 +536,7 @@ var storeOrderForMigration = function() {
}; };
var storeOrder = function() { var storeOrder = function() {
try { try {
localStorage.setItem("currentOrder", JSON.stringify(order)); localStorage.setItem(current_order_name, JSON.stringify(order));
} catch (e) { } catch (e) {
djLogError({ctx: "store order", msg:e}); djLogError({ctx: "store order", msg:e});
} }
...@@ -604,7 +608,8 @@ var pass2step2 = function() { ...@@ -604,7 +608,8 @@ var pass2step2 = function() {
$('h1').show(); $('h1').show();
var recup = order.best_date || 'non défini'; var recup = order.best_date || 'non défini';
current_order_bdate.html("Récupération : <strong>" + french_date_and_time(recup) + "</strong>"); if (shop_mode == 'shop')
current_order_bdate.html("Récupération : <strong>" + french_date_and_time(recup) + "</strong>");
if (visit_mode == true) { if (visit_mode == true) {
$('.product .choice').hide(); $('.product .choice').hide();
right_column.hide(); right_column.hide();
...@@ -774,19 +779,22 @@ var isChoosenSlotValid = function(slot) { ...@@ -774,19 +779,22 @@ var isChoosenSlotValid = function(slot) {
var answer = true; var answer = true;
var cause = ''; var cause = '';
forbidden_slots.forEach(function(e) { // For delivery, no slot validation: return true
if (slot == e) { if (shop_mode == 'shop') {
answer = false; cause = 'full'; forbidden_slots.forEach(function(e) {
if (slot == e) {
answer = false; cause = 'full';
}
});
//Does it respect min delay ?
var now = new Date();
var min_date = new Date();
var slot_date = new Date(slot.replace(/-/g, "/")); // replace for Safari
min_date = new Date(min_date.setHours(now.getHours() + min_delay));
if (slot_date - min_date < 0) {
answer = false; cause = 'delay';
} }
});
//Does it respect min delay ?
var now = new Date();
var min_date = new Date();
var slot_date = new Date(slot.replace(/-/g, "/")); // replace for Safari
min_date = new Date(min_date.setHours(now.getHours() + min_delay));
if (slot_date - min_date < 0) {
answer = false; cause = 'delay';
} }
return {res: answer, reason: cause}; return {res: answer, reason: cause};
...@@ -912,7 +920,6 @@ var validCart = function() { ...@@ -912,7 +920,6 @@ var validCart = function() {
post_form( post_form(
'/shop/cart', {order: JSON.stringify(order)}, '/shop/cart', {order: JSON.stringify(order)},
function(err, result) { function(err, result) {
//console.log(result)
if (!err) { if (!err) {
try { try {
if (typeof result.res.cart != "undefined" && result.res.cart != null) { if (typeof result.res.cart != "undefined" && result.res.cart != null) {
...@@ -948,7 +955,7 @@ var validCart = function() { ...@@ -948,7 +955,7 @@ var validCart = function() {
}; };
var clearCart = function() { var clearCart = function() {
order = {products: [], total: 0.00}; order = { products: [], total: 0.00, type: shop_mode};
storeOrder(); storeOrder();
cart.find('.cart-elt').remove(); cart.find('.cart-elt').remove();
updateCartTotal(0, 0); updateCartTotal(0, 0);
...@@ -1053,14 +1060,22 @@ var loadAllAvailableBoughtProducts = function() { ...@@ -1053,14 +1060,22 @@ var loadAllAvailableBoughtProducts = function() {
}; };
var appendChildrenCatToMenu = function (catdiv, children) { var appendChildrenCatToMenu = function (catdiv, children) {
// console.log(children)
var ul = catdiv.find('ul'); var ul = catdiv.find('ul');
$.each(children, function(i, e) { $.each(children, function(i, e) {
if (excluded_cat.indexOf(e.id) < 0) { if (excluded_cat.indexOf(e.id) < 0) {
var li = $('<li>').addClass("nav-item"); var li = $('<li>').addClass("nav-item");
// Remove TVA in cat name
let name = e.name;
name = name.replaceAll(' TVA 20%', '');
name = name.replaceAll(' TVA 5,5%', '');
name = name.replaceAll(' 20%', '');
name = name.replaceAll(' 5,5%', '');
var span = $('<span>').attr('data-id', e.id) var span = $('<span>').attr('data-id', e.id)
.text(e.name); .text(name);
li.append(span); li.append(span);
ul.append(li); ul.append(li);
...@@ -1347,7 +1362,8 @@ var changeBestDate = function() { ...@@ -1347,7 +1362,8 @@ var changeBestDate = function() {
msg.find('select[name="bhour-change"]').html(cart_vform.find('select[name="bhour"]').html()); msg.find('select[name="bhour-change"]').html(cart_vform.find('select[name="bhour"]').html());
msg.find('.slots-constraints').html(cart_vform.find('.slots-constraints').html()); msg.find('.slots-constraints').html(cart_vform.find('.slots-constraints').html());
fillBDayOptions(bday_change_sel); if (shop_mode == 'shop')
fillBDayOptions(bday_change_sel);
updateUnavailableSlots(function() { updateUnavailableSlots(function() {
openModal(msg.html(), function() { openModal(msg.html(), function() {
...@@ -1473,7 +1489,8 @@ var launch_init_form = function() { ...@@ -1473,7 +1489,8 @@ var launch_init_form = function() {
make_user_wait('Préparation du formulaire...'); make_user_wait('Préparation du formulaire...');
current_action = 'init_form'; current_action = 'init_form';
updateUnavailableSlots(function() { updateUnavailableSlots(function() {
fillBDayOptions(bday_sel); if (shop_mode == 'shop')
fillBDayOptions(bday_sel);
openModal($('#cart_creation_form').html(), initCart, 'Commencer la commande'); openModal($('#cart_creation_form').html(), initCart, 'Commencer la commande');
}); });
}; };
...@@ -1520,7 +1537,9 @@ var justVisit = function() { ...@@ -1520,7 +1537,9 @@ var justVisit = function() {
pass2step2(); pass2step2();
}; };
var order = getStoredOrder() || {products: [], total: 0.00}; var current_order_name = shop_mode == 'shop' ? 'currentShopOrder' : 'currentDeliveryOrder';
var saved_order_name = shop_mode == 'shop' ? 'saved_shop_order' : 'saved_delivery_order';
var order = getStoredOrder() || { products: [], total: 0.00, type: shop_mode};
var content1 = $('#tab-content1'); var content1 = $('#tab-content1');
var content4 = $('#tab-content4'); var content4 = $('#tab-content4');
var category_elts = getStoredCatElts() || {}; var category_elts = getStoredCatElts() || {};
......
...@@ -354,6 +354,18 @@ function init_and_fill_order_list() { ...@@ -354,6 +354,18 @@ function init_and_fill_order_list() {
orders_table.destroy(); orders_table.destroy();
var cols = [ var cols = [
{data: 'h_submitted_date', title: "Date commande"}, {data: 'h_submitted_date', title: "Date commande"},
{
data: 'type',
title: "Type",
width: "5%",
render: function (data) {
if (data == 'delivery') {
return 'Livraison';
} else {
return 'Commande';
}
}
},
{data: 'partner.display_name', title: "Nom"}, {data: 'partner.display_name', title: "Nom"},
{data: 'best_date', title: "Livraison"}, {data: 'best_date', title: "Livraison"},
{data: 'total', title: "Montant"} {data: 'total', title: "Montant"}
......
...@@ -5,7 +5,8 @@ from . import views ...@@ -5,7 +5,8 @@ from . import views
from . import admin from . import admin
urlpatterns = [ urlpatterns = [
url(r'^$', views.index), url(r'^$', views.shop_index),
url(r'^delivery$', views.delivery_index),
url(r'^get_all_available_bought_products$', views.get_all_available_bought_products), url(r'^get_all_available_bought_products$', views.get_all_available_bought_products),
url(r'^get_cat_children$', views.get_cat_children), url(r'^get_cat_children$', views.get_cat_children),
url(r'^get_categ_products$', views.get_categ_products), url(r'^get_categ_products$', views.get_categ_products),
......
...@@ -5,13 +5,20 @@ from outils.for_view_imports import * ...@@ -5,13 +5,20 @@ from outils.for_view_imports import *
from members.models import CagetteMember from members.models import CagetteMember
from shop.models import CagetteShop from shop.models import CagetteShop
@never_cache @never_cache
def index(request): def shop_index(request):
return index(request, mode='shop')
@never_cache
def delivery_index(request):
return index(request, mode='delivery')
def index(request, mode="shop"):
template = loader.get_template('shop/index.html') template = loader.get_template('shop/index.html')
credentials = CagetteMember.get_credentials(request) credentials = CagetteMember.get_credentials(request)
shop_settings = CagetteShop.get_shop_settings() shop_settings = CagetteShop.get_shop_settings()
context = {'title': 'Commande / Réservation', context = {'title': 'Commande / Réservation',
'mode': mode,
'COMPANY_NAME': settings.COMPANY_NAME, 'COMPANY_NAME': settings.COMPANY_NAME,
'SHOP_CATEGORIES': settings.SHOP_CATEGORIES, 'SHOP_CATEGORIES': settings.SHOP_CATEGORIES,
'EXCLUDE_SHOP_CATEGORIES': settings.EXCLUDE_SHOP_CATEGORIES, 'EXCLUDE_SHOP_CATEGORIES': settings.EXCLUDE_SHOP_CATEGORIES,
...@@ -44,8 +51,12 @@ def index(request): ...@@ -44,8 +51,12 @@ def index(request):
context['SHOP_SLOT_SIZE'] = settings.SHOP_SLOT_SIZE context['SHOP_SLOT_SIZE'] = settings.SHOP_SLOT_SIZE
if hasattr(settings, 'SHOP_OPENING_START_DATE'): if hasattr(settings, 'SHOP_OPENING_START_DATE'):
context['SHOP_OPENING_START_DATE'] = settings.SHOP_OPENING_START_DATE context['SHOP_OPENING_START_DATE'] = settings.SHOP_OPENING_START_DATE
if hasattr(settings, 'SHOP_CAN_BUY'): if mode == 'shop' and hasattr(settings, 'SHOP_CAN_BUY'):
context['SHOP_CAN_BUY'] = settings.SHOP_CAN_BUY context['SHOP_CAN_BUY'] = settings.SHOP_CAN_BUY
context['DELIVERY_CAN_BUY'] = False
if mode == 'delivery' and hasattr(settings, 'DELIVERY_CAN_BUY'):
context['SHOP_CAN_BUY'] = False
context['DELIVERY_CAN_BUY'] = settings.DELIVERY_CAN_BUY
d_p_pdts = CagetteShop.get_promoted_and_discounted_products() d_p_pdts = CagetteShop.get_promoted_and_discounted_products()
context['discounted_pdts'] = d_p_pdts['discounted'] context['discounted_pdts'] = d_p_pdts['discounted']
...@@ -129,7 +140,8 @@ def cart_init(request): ...@@ -129,7 +140,8 @@ def cart_init(request):
try: try:
# first of all, verifiying cart is respecting time slots constraints # first of all, verifiying cart is respecting time slots constraints
cart = json.loads(request.POST.get('order')) cart = json.loads(request.POST.get('order'))
ts_respect = CagetteShop.isCartRespectingTimeSlotContraints(cart) # Verify for shop only, not for delivery
ts_respect = CagetteShop.isCartRespectingTimeSlotContraints(cart) if cart['type'] == 'shop' else True
if (ts_respect is False): if (ts_respect is False):
result['ts_respect'] = False result['ts_respect'] = False
result['error'] = 'Forbidden timeslot' result['error'] = 'Forbidden timeslot'
......
...@@ -8,6 +8,31 @@ from datetime import datetime ...@@ -8,6 +8,31 @@ from datetime import datetime
class CagetteStock(models.Model): class CagetteStock(models.Model):
@staticmethod @staticmethod
def get_movements(movement_type, date_from, date_to):
o_api = OdooAPI()
errors = []
if movement_type == 'losses':
location_dest_id = settings.LOSSES_LOC_ID
elif movement_type == 'meals':
location_dest_id = settings.MEALS_LOC_ID
elif movement_type == 'autoconso':
location_dest_id = settings.AUTOCONSO_LOC_ID
else:
errors.append('Type de mouvement incorrect')
return {'errors': errors}
f = ['name', 'date_done', 'inventory_value']
c = [['location_dest_id', "=", location_dest_id],
['date_done', ">=", date_from],
['date_done', "<=", date_to],
['state', "=", 'done']]
res = o_api.search_read('stock.picking', c, f)
return res
@staticmethod
def do_stock_movement(stock_movement_data): def do_stock_movement(stock_movement_data):
"""Do a stock movement : """ """Do a stock movement : """
...@@ -18,29 +43,33 @@ class CagetteStock(models.Model): ...@@ -18,29 +43,33 @@ class CagetteStock(models.Model):
# Set stock movement details according to destination # Set stock movement details according to destination
if stock_movement_data['movement_type'] == 'losses': if stock_movement_data['movement_type'] == 'losses':
picking_name = 'Pertes - ' + datetime.now().strftime("%d/%m/%Y %H:%M:%S") picking_name = 'Pertes - '
picking_type = settings.LOSSES_PICKING_TYPE_ID picking_type = settings.LOSSES_PICKING_TYPE_ID
destination = settings.LOSSES_LOC_ID destination = settings.LOSSES_LOC_ID
elif stock_movement_data['movement_type'] == 'meals': elif stock_movement_data['movement_type'] == 'meals':
picking_name = 'Repas Salarié - ' + datetime.now().strftime("%d/%m/%Y %H:%M:%S") picking_name = 'Repas Salarié - '
picking_type = settings.MEALS_PICKING_TYPE_ID picking_type = settings.MEALS_PICKING_TYPE_ID
destination = settings.MEALS_LOC_ID destination = settings.MEALS_LOC_ID
elif stock_movement_data['movement_type'] == 'autoconso': elif stock_movement_data['movement_type'] == 'autoconso':
picking_name = 'Autoconsommation - ' + datetime.now().strftime("%d/%m/%Y %H:%M:%S") picking_name = 'Autoconsommation - '
picking_type = settings.AUTOCONSO_PICKING_TYPE_ID picking_type = settings.AUTOCONSO_PICKING_TYPE_ID
destination = settings.AUTOCONSO_LOC_ID destination = settings.AUTOCONSO_LOC_ID
else: else:
errors.append('Type de mouvement incorrect') errors.append('Type de mouvement incorrect')
return {'errors': errors, 'picking_id': picking} return {'errors': errors, 'picking_id': picking}
picking_name += datetime.now().strftime("%d/%m/%Y %H:%M:%S")
picking_name += ' - ' + stock_movement_data['operator']['name'] + ' ('+ str(stock_movement_data['operator']['barcode_base']) + ')'
fields = { fields = {
'company_id': 1, 'company_id': 1,
'name': picking_name, 'name': picking_name,
'picking_type_id' : picking_type, # mouvement type 'picking_type_id' : picking_type, # mouvement type
'location_id': settings.STOCK_LOC_ID, # movement origin 'location_id': settings.STOCK_LOC_ID, # movement origin
'location_dest_id': destination, # movement dest 'location_dest_id': destination, # movement dest
"move_lines": [], 'move_lines': [],
"pack_operation_ids": [] 'pack_operation_ids': [],
'operator_id': stock_movement_data['operator']['id']
} }
for p in stock_movement_data['products']: for p in stock_movement_data['products']:
......
.main, .barcode_search_area {
margin-top: 40px;
}
#sm_barcode_selector {
border: 1px solid #555;
border-radius:10px;
}
.stock_edit_input {
max-width: 60px;
}
#movement_validation_button {
margin-top: 20px;
}
.notifyjs-test1-base {
background: #5CB85C !important;
}
.confirmation_checking_msgs {
padding-bottom: 10px;
}
.remove_row_icon {
font-size: 1.4em;
color: #d9534f;
cursor: pointer;
}
.blink_me {
animation: blinker 1s ease-in-out;
}
@keyframes blinker {
15% { background-color: #218D9B ; } /* F0AD4E*/
}
#back_to_movement_selection {
position:absolute;
left:0;
margin: 5px;
}
.movement_type_buttons {
margin-top: 60px;
}
.movement_type_button {
height: 2.2em;
width: 30%;
border-radius: 3px;
margin: 10px;
font-size: 1.3em;
}
.movement_type_button_icons {
float: right;
margin: 2px;
}
.page_body {
position: relative;
}
.page_content {
position: absolute;
top: 0;
left: 0;
right: 0;
}
.set_member_to_movement {
margin-top: -10px;
}
.search_member_results_area {
margin-top: 10px;
}
.select_movement_area {
margin-top: 25px;
}
.select_movement_element {
display: inline-block;
margin-right: 10px;
}
.main {
margin-top: 25px;
}
.table_area {
margin: auto;
width: 50%;
padding: 10px;
}
.select_movement_input {
border-radius:5px;
}
.btn_export_movements {
margin-top: 10px;
}
/*
* Extract and display a stock movement type between two dates
*
*/
var dateFormat = "yy-mm-dd",
from_datepicker = null,
to_datepicker = null,
movements_table = null;
// Return a date from a string if valid, else return null
function getDate(element) {
var date = null;
try {
date = $.datepicker.parseDate(dateFormat, element);
} catch (error) {
date = null;
}
return date;
}
// Enable validation button if all fields are valid
function enable_validation() {
if ($('#movement_type_selector').val() != '' &&
getDate(from_datepicker.val()) &&
getDate(to_datepicker.val())) {
$('#movement_selection_button').prop('disabled', false);
} else {
$('#movement_selection_button').prop('disabled', true);
}
}
function display_movements(movements) {
total_value = 0;
for (move of movements) {
total_value += move.inventory_value;
}
$('#count_movements').text(movements.length);
$('#total_value_movements').text(parseFloat(total_value).toFixed(2));
// Empty datatable if already exists
if (movements_table) {
movements_table.destroy();
}
movements_table = $('#movements_table').DataTable({
data: movements,
columns:[
{
data:"name",
title:"Mouvement (Type - Date - Fait par)",
width: "70%"
},
{
data:"inventory_value",
title: "Valeur",
className:"dt-body-center",
render: function (data) {
return parseFloat(data).toFixed(2) + ' €';
}
},
{
data:"date_done",
title:"Trier par Date",
className:"dt-body-center",
render: function (data) {
return '<i>' + data + '</i>';
}
}
],
order: [
[
2,
"desc"
]
],
buttons: [
{
extend: 'excelHtml5',
text: 'Export en Excel',
className: 'btn--primary btn_export_movements'
},
{
extend: 'pdfHtml5',
text: 'Export en PDF',
className: 'btn--primary btn_export_movements'
}
],
dom: '<lr<t>ip><"clear"><B>',
iDisplayLength: 10,
language: {url : '/static/js/datatables/french.json'}
});
$('.main').show();
}
function get_movements() {
openModal();
var url = "/stock/get_movements";
url += '?movement_type=' + encodeURIComponent($('#movement_type_selector').val());
url += '&from=' + encodeURIComponent(from_datepicker.val());
url += '&to=' + encodeURIComponent(to_datepicker.val());
$.ajax({
type: 'GET',
url: url,
dataType:"json",
traditional: true,
contentType: "application/json; charset=utf-8",
success: function(data) {
display_movements(data.res);
closeModal();
},
error: function(data) {
err = {msg: "erreur serveur lors de la sélection des mouvements de stock", ctx: 'get_movements'};
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error;
}
report_JS_error(err, 'stock');
closeModal();
alert('Erreur lors de la récupération, réessayez plus tard');
}
});
}
$(document).ready(function() {
$.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } });
// Set datepicker
$.datepicker.regional['fr'] = {
monthNames: [
'Janvier',
'Fevrier',
'Mars',
'Avril',
'Mai',
'Juin',
'Juillet',
'Aout',
'Septembre',
'Octobre',
'Novembre',
'Decembre'
],
dayNamesMin: [
'Di',
'Lu',
'Ma',
'Me',
'Je',
'Ve',
'Sa'
],
dateFormat: dateFormat,
firstDay: 1,
minDate: 1,
maxDate: '+12M +0D'
};
$.datepicker.setDefaults($.datepicker.regional['fr']);
from_datepicker = $("#from")
.datepicker({
defaultDate: "-1d",
changeMonth: true,
changeYear: true,
yearRange: "-03:+00",
minDate: new Date(2007, 1 - 1, 1),
maxDate: new Date()
})
.on("change", function() {
to_datepicker.datepicker("option", "minDate", getDate(this.value));
});
to_datepicker = $("#to")
.datepicker({
defaultDate: "-1d",
changeMonth: true,
changeYear: true,
yearRange: "-03:+00",
minDate: new Date(2007, 1 - 1, 1),
maxDate: new Date()
})
.on("change", function() {
from_datepicker.datepicker("option", "maxDate", getDate(this.value));
});
$('.select_movement_element').change(function() {
enable_validation();
});
$('#movement_selection_button').click(function() {
get_movements();
});
});
...@@ -4,8 +4,10 @@ from django.conf.urls import url ...@@ -4,8 +4,10 @@ from django.conf.urls import url
from . import views from . import views
urlpatterns = [ urlpatterns = [
url(r'^movements', views.movements_page), url(r'^movements$', views.movements_page),
url(r'^do_movement', views.do_movement), url(r'^do_movement', views.do_movement),
url(r'^movements_view$', views.movements_view),
url(r'^get_movements$', views.get_movements),
### NOT IN USE: ### ### NOT IN USE: ###
......
...@@ -18,6 +18,15 @@ def movements_page(request): ...@@ -18,6 +18,15 @@ def movements_page(request):
return HttpResponse(template.render(context, request)) return HttpResponse(template.render(context, request))
def movements_view(request):
"""Page d'extraction des mouvements de stocks"""
context = {
'title': 'Mouvements de stock'
}
template = loader.get_template('stock/stock_movements_view.html')
return HttpResponse(template.render(context, request))
def do_movement(request): def do_movement(request):
"""Do the stock movement: losses, self conso or stock correction""" """Do the stock movement: losses, self conso or stock correction"""
res = {} res = {}
...@@ -46,6 +55,20 @@ def do_movement(request): ...@@ -46,6 +55,20 @@ def do_movement(request):
else: else:
return JsonResponse({'res': res}) return JsonResponse({'res': res})
def get_movements(request):
res = {}
movement_type = request.GET.get('movement_type', '')
date_from = request.GET.get('from', '')
date_to = request.GET.get('to', '')
res = CagetteStock.get_movements(movement_type, date_from, date_to)
if 'errors' in res and res['errors']:
return JsonResponse(res, status=500)
else:
return JsonResponse({'res': res})
### NOT IN USE ### ### NOT IN USE ###
# ??? a voir si on garde les heur d'ouverture de la cagette ici # ??? a voir si on garde les heur d'ouverture de la cagette ici
......
...@@ -47,7 +47,7 @@ ...@@ -47,7 +47,7 @@
</div> </div>
Recherche : Recherche :
<input type="text" name="search_string" autocomplete="off" /> <input type="text" name="search_string" autocomplete="off" />
<button class="btn--primary search">GO! <button class="btn--primary search">Cherche-moi !
</button> </button>
<span class="loading2"><img width="75" src="/static/img/Pedro_luis_romani_ruiz.gif" alt="Chargement en cours...." /></span> <span class="loading2"><img width="75" src="/static/img/Pedro_luis_romani_ruiz.gif" alt="Chargement en cours...." /></span>
<div class="row-1" id="multi_results_preview"> <div class="row-1" id="multi_results_preview">
...@@ -111,13 +111,13 @@ ...@@ -111,13 +111,13 @@
{% endif %} {% endif %}
</div> </div>
</section> </section>
</div> </div>
</div> </div>
</section> </section>
</div> </div>
<section id="photo_advice"> <section id="photo_advice">
<div class="col-6 big"><i class="fas fa-arrow-circle-up fa-3x "></i> <div class="col-6 big"><i class="fas fa-arrow-circle-up fa-3x "></i>
Regardez la caméra, en haut, pendant la pose <i class="fas fa-arrow-circle-up fa-3x"></i> Regardez la caméra, en haut, pendant la pose <i class="fas fa-arrow-circle-up fa-3x"></i>
...@@ -225,4 +225,4 @@ ...@@ -225,4 +225,4 @@
</section> </section>
<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 %}
\ No newline at end of file
...@@ -15,7 +15,8 @@ ...@@ -15,7 +15,8 @@
<script type="text/javascript"> <script type="text/javascript">
var type = {{type}}, var type = {{type}},
context = 'inscription', context = 'inscription',
force_fn_hyphen = {% if force_firstname_hyphen %}true{% else %}false{% endif%}; force_fn_hyphen = {% if force_firstname_hyphen %}true{% else %}false{% endif%},
max_chq_nb = {{max_chq_nb}};
{% if open_on_sunday %} {% if open_on_sunday %}
let open_on_sunday = true let open_on_sunday = true
{% endif %} {% endif %}
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
<script type="text/javascript" src="{% static 'js/download.js' %}"></script> <script type="text/javascript" src="{% static 'js/download.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/jquery.dataTables.min.js' %}"></script> <script type="text/javascript" src="{% static 'js/datatables/jquery.dataTables.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/dataTables.plugins.js' %}"></script> <script type="text/javascript" src="{% static 'js/datatables/dataTables.plugins.js' %}"></script>
<script type="text/javascript" src="{% static 'js/jquery.pos.js' %}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
...@@ -92,7 +93,6 @@ ...@@ -92,7 +93,6 @@
<span id="valid_all"></span> <span id="valid_all"></span>
<span id="all_left_is_good"> <span id="all_left_is_good">
<button class='btn--danger half_width_button' onclick="openModal($('#templates #modal_all_left_is_good').html(), confirm_all_left_is_good, 'Confirmer', false);">Tout le reste est bon</button> <button class='btn--danger half_width_button' onclick="openModal($('#templates #modal_all_left_is_good').html(), confirm_all_left_is_good, 'Confirmer', false);">Tout le reste est bon</button>
</span> </span>
</div> </div>
{% else %} {% else %}
...@@ -153,6 +153,14 @@ ...@@ -153,6 +153,14 @@
<p>Êtez-vous sûr ?</p> <p>Êtez-vous sûr ?</p>
<hr /> <hr />
</div> </div>
<div id="modal_set_supplier_shortage">
<h3>Attention !</h3>
<p>Vous vous apprêtez indiquer que le produit<b><span class="supplier_shortage_product"></span></b>
est en rupture chez le fournisseur<b><span class="supplier_shortage_supplier"></span></b>.</p>
<p>La quantité du produit sera mise à 0 si vous en êtes à l'étape du comptage de produits.</p>
<p>Êtez-vous sûr ?</p>
<hr />
</div>
<div id="modal_unprocessable_porducts"> <div id="modal_unprocessable_porducts">
<h3>Traitement impossible</h3> <h3>Traitement impossible</h3>
<p> <p>
...@@ -179,6 +187,6 @@ ...@@ -179,6 +187,6 @@
var fixed_barcode_prefix = '{{FIXED_BARCODE_PREFIX}}' var fixed_barcode_prefix = '{{FIXED_BARCODE_PREFIX}}'
</script> </script>
<script src="{% static "js/all_common.js" %}?v="></script> <script src="{% static "js/all_common.js" %}?v="></script>
<script src="{% static "js/common.js" %}?v="></script> <script src='{% static "js/barcodes.js" %}?v='></script>
<script type="text/javascript" src="{% static 'js/reception_produits.js' %}?v="></script> <script type="text/javascript" src="{% static 'js/reception_produits.js' %}?v="></script>
{% endblock %} {% endblock %}
...@@ -29,7 +29,8 @@ ...@@ -29,7 +29,8 @@
<p>Ça veut dire que soit :</p> <p>Ça veut dire que soit :</p>
<ul> <ul>
<li>vous avez raté deux services. Lorsque votre compteur passe à -2, vous êtes immédiatement suspendus,</li> <li>vous avez raté deux services. Lorsque votre compteur passe à -2, vous êtes immédiatement suspendus,</li>
<li>vous avez raté un service et vous ne l'avez pas rattrapé dans les 4 semaines qui ont suivis.</li> <li>vous avez raté un service et vous ne l'avez pas rattrapé dans les 4 semaines qui ont suivis,</li>
<li>vous avez été désinscrit.e pour avoir raté 3 services et venez d'être réinscrit.e.</li>
</ul> </ul>
<p>Heureusement, vous pouvez demander une extension !</p> <p>Heureusement, vous pouvez demander une extension !</p>
<p>Une extension vous donne un délais de quatre semaines supplémentaires pour rattraper le service manqué (ou les deux services manqués).Vous pouvez demander jusqu'à 6 extensions d'affilé.</p> <p>Une extension vous donne un délais de quatre semaines supplémentaires pour rattraper le service manqué (ou les deux services manqués).Vous pouvez demander jusqu'à 6 extensions d'affilé.</p>
......
...@@ -2,21 +2,25 @@ Bonjour,<br/> ...@@ -2,21 +2,25 @@ Bonjour,<br/>
<p> <p>
Votre commande du {{order_date}} a bien été prise en compte Votre commande du {{order_date}} a bien été prise en compte
</p> </p>
{% if cart.type == 'shop' %}
<p> <p>
Merci de venir à l’heure que vous avez indiquée. Merci de venir à l’heure que vous avez indiquée.
</p> </p>
<p> <p>
Au-delà d’une heure de retard, votre commande sera annulée. Au-delà d’une heure de retard, votre commande sera annulée.
</p> </p>
{% endif %}
<p> <p>
Nous vous rappelons que les prix et les quantités disponibles sont affichées à titre indicatif. Si vous avez accepté des substitutions de produits, il seront remplacés, en cas de rupture, en essayant de coller au mieux à votre commande. Le prix de votre panier dépendra du prix réel et des quantités réellement disponibles. Nous vous rappelons que les prix et les quantités disponibles sont affichées à titre indicatif. Si vous avez accepté des substitutions de produits, il seront remplacés, en cas de rupture, en essayant de coller au mieux à votre commande. Le prix de votre panier dépendra du prix réel et des quantités réellement disponibles.
</p> </p>
<p> <p>
La seule modification possible sur le panier commandé sera d’enlever un ou plusieurs articles. La seule modification possible sur le panier commandé sera d’enlever un ou plusieurs articles.
</p> </p>
{% if cart.type == 'shop' %}
<p> <p>
Merci de vous munir de votre chéquier et/ou CB. Merci de vous munir de votre chéquier et/ou CB.
</p> </p>
{% endif %}
<p> <p>
NB: en ce qui concerne le frais, nous mettons dans votre panier les dates les plus proches de la péremption. NB: en ce qui concerne le frais, nous mettons dans votre panier les dates les plus proches de la péremption.
</p> </p>
...@@ -34,7 +38,7 @@ Commande reçue : ...@@ -34,7 +38,7 @@ Commande reçue :
{% endfor %} {% endfor %}
</table> </table>
<br/> <br/>
Total TTC : {{cart.total}} € Total TTC : {{cart.total}} € (le prix qui vous sera demandé pourra être légèrement différent en fonction des disponibilités en magasin)
<br/> <br/>
{%if cart.accept_substitution %} {%if cart.accept_substitution %}
Vous acceptez les substitutions de produits. Vous acceptez les substitutions de produits.
...@@ -46,9 +50,11 @@ Vous avez laissé le commentaire suivant: <br/> ...@@ -46,9 +50,11 @@ Vous avez laissé le commentaire suivant: <br/>
{{cart.comment}} {{cart.comment}}
</p> </p>
{% endif %} {% endif %}
{% if cart.type == 'shop' %}
<p> <p>
Vous avez indiqué venir chercher la commande à cette date : "{{cart.best_date}}" Vous avez indiqué venir chercher la commande à cette date : "{{cart.best_date}}"
</p> </p>
{% endif %}
<p> <p>
Pour des raisons d'hygiène les commandes seront préparées dans des sacs en papier kraft qui vous seront facturées, 0,24€ pour les petits et 0,77€ pour les grands. <br/> Pour des raisons d'hygiène les commandes seront préparées dans des sacs en papier kraft qui vous seront facturées, 0,24€ pour les petits et 0,77€ pour les grands. <br/>
Merci de votre compréhension Merci de votre compréhension
......
...@@ -36,6 +36,10 @@ ...@@ -36,6 +36,10 @@
<li class="nav-item"> <li class="nav-item">
<span class="new-order">Nouvelle commande</span> <span class="new-order">Nouvelle commande</span>
</li> </li>
{% elif DELIVERY_CAN_BUY != False %}
<li class="nav-item">
<span class="new-order">Nouvelle commande</span>
</li>
{%endif%} {%endif%}
<li class="nav-item"> <li class="nav-item">
<span class="my-orders">Commande(s) envoyée(s)</span> <span class="my-orders">Commande(s) envoyée(s)</span>
...@@ -318,7 +322,10 @@ ...@@ -318,7 +322,10 @@
<div id="cart_creation_form"> <div id="cart_creation_form">
<p> <p>
<h2>Initialisation de la commande</h2> <h2>Initialisation de la commande</h2>
<span class="ask-day">Merci d'indiquer ci-dessous quand vous viendrez récupérer la commande au magasin</span><br/> {% if mode == 'shop' %}
<span class="ask-day">
Merci d'indiquer ci-dessous quand vous viendrez récupérer la commande au magasin
</span><br/>
<select name="bday"> <select name="bday">
</select> </select>
...@@ -341,6 +348,50 @@ ...@@ -341,6 +348,50 @@
<option value="17:00">17:00</option> <option value="17:00">17:00</option>
<option value="17:30">17:30</option> <option value="17:30">17:30</option>
</select> </select>
{% elif mode == 'delivery' %}
<span class="ask-day">
Merci d'indiquer ci-dessous un jour et une heure de livraison suggérés.<br>
Le jour et l'heure de la livraison seront choisis en direct avec le/la livreur.se en fonction de ses disponibilités.
</span><br/>
<select name="bday">
<option value="">---> Jour</option>
<option value="lundi">lundi</option>
<option value="mardi">mardi</option>
<option value="mercredi">mercredi</option>
<option value="jeudi">jeudi</option>
<option value="vendredi">vendredi</option>
<option value="samedi">samedi</option>
<option value="dimanche">dimanche</option>
</select>
<select name="bhour">
<option value="">---> Heure</option>
<option value="01:00">00:00</option>
<option value="01:00">01:00</option>
<option value="02:00">02:00</option>
<option value="03:00">03:00</option>
<option value="04:00">04:00</option>
<option value="05:00">05:00</option>
<option value="06:00">06:00</option>
<option value="07:00">07:00</option>
<option value="08:00">08:00</option>
<option value="09:00">09:00</option>
<option value="10:00">10:00</option>
<option value="11:00">11:00</option>
<option value="12:00">12:00</option>
<option value="13:00">13:00</option>
<option value="14:00">14:00</option>
<option value="15:00">15:00</option>
<option value="16:00">16:00</option>
<option value="17:00">17:00</option>
<option value="18:00">18:00</option>
<option value="19:00">19:00</option>
<option value="20:00">20:00</option>
<option value="21:00">21:00</option>
<option value="22:00">22:00</option>
<option value="23:00">23:00</option>
</select>
{% endif %}
<div class="slots-constraints" style="display: none;"> <div class="slots-constraints" style="display: none;">
<span class="delay24h"><strong>avec un délai de {{MIN_DELAY_FOR_SLOT}}h minimum</strong></span> <span class="delay24h"><strong>avec un délai de {{MIN_DELAY_FOR_SLOT}}h minimum</strong></span>
<!--<input type="text" placeholder="Exemple: mardi 24 mars à 10h30" name="best_date" style="width: 350px;"/>--><br/> <!--<input type="text" placeholder="Exemple: mardi 24 mars à 10h30" name="best_date" style="width: 350px;"/>--><br/>
...@@ -354,7 +405,9 @@ ...@@ -354,7 +405,9 @@
{%if HOURS_FOR_VALIDATION > 0 %} {%if HOURS_FOR_VALIDATION > 0 %}
<div class="tv-msg" style="display: none;"> <div class="tv-msg" style="display: none;">
<strong> <strong>
Vous aurez <span class="time-given-for-validation">{{HOURS_FOR_VALIDATION}} heure(s)</span> pour valider votre commande.<br/> Passé ce temps, la réservation du créneau horaire sera annulée.<br/>Vous devrez rechoisir un horaire (le panier sera mémorisé). Vous aurez <span class="time-given-for-validation">{{HOURS_FOR_VALIDATION}} heure(s)</span> pour valider votre commande.<br/>
Passé ce temps, la {% if mode == 'shop' %}réservation du créneau horaire{% elif mode == 'delivery' %}commande{% endif %} sera annulée.<br/>
Vous devrez rechoisir un horaire (le panier sera mémorisé).
</strong> </strong>
</div> </div>
{%endif%} {%endif%}
...@@ -367,7 +420,7 @@ ...@@ -367,7 +420,7 @@
<div id="cart_validation_form"> <div id="cart_validation_form">
<h2>Validation de la commande</h2> <h2>Validation de la commande</h2>
<p class="pickup_date"> <p class="pickup_date">
Récupération au magasin : <span></span> {% if mode == 'shop' %}Récupération au magasin{% elif mode == 'delivery' %}Date et heure de livraison{%endif%} : <span></span>
</p> </p>
{%if SHOW_SUBSTITUTION_OPTION %} {%if SHOW_SUBSTITUTION_OPTION %}
<p class="accept_substitution"> <p class="accept_substitution">
...@@ -382,7 +435,9 @@ ...@@ -382,7 +435,9 @@
{%endif%} {%endif%}
</p> </p>
<p> <p>
{% if mode == 'shop' %}
<strong><i>Si vous arrivez avec un retard de plus d'une heure, la commande pourrait ne plus être disponible.</i></strong> <strong><i>Si vous arrivez avec un retard de plus d'une heure, la commande pourrait ne plus être disponible.</i></strong>
{%endif%}
</p> </p>
<p> <p>
{{CART_VALIDATION_BOTTOM_MSG}} {{CART_VALIDATION_BOTTOM_MSG}}
...@@ -413,6 +468,9 @@ ...@@ -413,6 +468,9 @@
{% if SHOP_CAN_BUY != False %} {% if SHOP_CAN_BUY != False %}
<li><button type="button" class="new-order btn--primary">Passer une nouvelle commande</button></li> <li><button type="button" class="new-order btn--primary">Passer une nouvelle commande</button></li>
{%endif%} {%endif%}
{% if DELIVERY_CAN_BUY != False %}
<li><button type="button" class="new-order btn--primary">Passer une nouvelle commande</button></li>
{%endif%}
<li><button type="button" class="my-orders btn--primary">Gérer mes commandes (*)</button></li> <li><button type="button" class="my-orders btn--primary">Gérer mes commandes (*)</button></li>
<li><button type="button" class="visit btn--primary">Parcourir le "magasin"</button></li> <li><button type="button" class="visit btn--primary">Parcourir le "magasin"</button></li>
...@@ -428,6 +486,7 @@ ...@@ -428,6 +486,7 @@
</div> </div>
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
const shop_mode = '{{ mode|safe }}'
var categories = {{SHOP_CATEGORIES|safe}} var categories = {{SHOP_CATEGORIES|safe}}
var min_delay = {{MIN_DELAY_FOR_SLOT}} var min_delay = {{MIN_DELAY_FOR_SLOT}}
var hours_for_validation = {{HOURS_FOR_VALIDATION}} var hours_for_validation = {{HOURS_FOR_VALIDATION}}
......
Bonjour,<br/>
<p>
Votre commande du {{order_date}} a bien été prise en compte
</p>
{% if cart.type == 'shop' %}
<p>
Merci de venir à l’heure que vous avez indiquée.
</p>
<p>
Au-delà d’une heure de retard, votre commande sera annulée.
</p>
{% endif %}
<p>
Nous vous rappelons que les prix et les quantités disponibles sont affichées à titre indicatif.
</p>
<p>
La seule modification possible sur le panier commandé sera d’enlever un ou plusieurs articles.
</p>
{% if cart.type == 'shop' %}
<p>
Merci de vous munir de votre chéquier et/ou CB.
</p>
{% endif %}
<p>
NB: en ce qui concerne le frais, nous mettons dans votre panier les dates les plus proches de la péremption.
</p>
{%if survey_link %}
<p>
Si vous avez des suggestions d'améliorations à faire, vous pouvez compléter <a href="{{survey_link}}">le formulaire de retours d'utilisation de la boutique en ligne</a>.
</p>
{% endif %}
<p>
Commande reçue :
<table border="1">
<tr><th>Articles</th><th>Quantités</th><th>Prix</th><th>Total</th></tr>
{% for p in cart.products %}
<tr><td>{{p.name}}</td><td>{{p.qty}}</td><td>{{p.price}}</td><td>{{p.total}}</td></tr>
{% endfor %}
</table>
<br/>
Total TTC : {{cart.total}} € (le prix qui vous sera demandé pourra être légèrement différent en fonction des disponibilités en magasin)
<br/>
{%if cart.accept_substitution %}
Vous acceptez les substitutions de produits.
{% endif %}
</p>
{%if cart.comment %}
<p>
Vous avez laissé le commentaire suivant: <br/>
{{cart.comment}}
</p>
{% endif %}
{% if cart.type == 'shop' %}
<p>
Vous avez indiqué venir chercher la commande à cette date : "{{cart.best_date}}"
</p>
{% endif %}
Merci de votre compréhension
</p>
L'équipe de {{mag}}
{% extends "base.html" %}
{% load static %}
{% block additionnal_css %}
<link rel="stylesheet" href="{% static 'css/datatables/jquery.dataTables.css' %}">
<link rel="stylesheet" href="{% static 'css/stock_movements.css' %}">
{% endblock %}
{% block additionnal_scripts %}
<script type="text/javascript" src="{% static 'js/datatables/jquery.dataTables.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/dataTables.plugins.js' %}"></script>
<script type="text/javascript" src="{% static 'js/jquery.pos.js' %}"></script>
<script type="text/javascript" src="{% static 'js/stock_movements.js' %}?v="></script>
<script type="text/javascript" src="{% static 'js/notify.min.js' %}?v="></script>
{% endblock %}
{% block content %}
<div class="page_body">
<div id="content_movement_type_selection" class="page_content">
<div class="header txtcenter">
<h1>Saisie de mouvements de stock</h1>
<p><i>Choisissez le type de mouvement de stock que vous voulez enregistrer</i></p>
</div>
<div class="movement_type_buttons txtcenter">
<button type="button" class="btn--primary movement_type_button" id="losses_type_button">
Pertes
<span class="movement_type_button_icons"><i class="fas fa-arrow-right"></i></span>
</button><br>
<button type="button" class="btn--primary movement_type_button" id="autoconso_type_button">
Autoconsommation
<span class="movement_type_button_icons"><i class="fas fa-arrow-right"></i></span>
</button><br>
<button type="button" class="btn--primary movement_type_button" id="meals_type_button">
Repas salariés
<span class="movement_type_button_icons"><i class="fas fa-arrow-right"></i></span>
</button>
</div>
</div>
<div id="content_main" class="page_content" style="display:none;">
<div class="header txtcenter">
<div id="back_to_movement_selection">
<button type="button" class="btn--danger"><i class="fas fa-arrow-left"></i>&nbsp; Retour</button>
</div>
<h1 id="title_movement_type"></h1>
<p style="margin-bottom:0;"><i>Passez un produit sous la douchette ! Si rien ne se passe, cliquez sur le champ ci-dessous puis ré-essayez.</i></p>
<p><i>Vous pouvez également taper le codebarre, puis appuyer sur la touche Entrée.</i></p>
</div>
<div class="barcode_search_area txtcenter">
<input type="text" id="sm_barcode_selector" placeholder="Codebarre">
<div id="icon_product_not_found" class="tooltip" style="display:none;">
<span class="tooltiptext tooltip-lg tt_twolines">Aucun produit trouvé avec ce code-barre.</span>
<span style="color: #d9534f;"> <i class="fas fa-ban"></i>
</div>
</div>
<div class="main">
<table id="products_table" class="display" cellspacing="0" ></table>
</div>
<div class="footer txtcenter" style="display:none">
<h4>Total (H.T.) : <span class="total_value">0</span></h4>
<button type="button" class="btn--primary" id="movement_validation_button" name="button">Validation du total des produits saisis</button>
</div>
<input type="hidden" name="barcode"/>
</div>
<div id="templates" style="display:none;">
<div id="modal_confirm_movement">
<h4 id="confirm_message_movtype"></h4>
<br>
<div class="confirmation_modal_content">
<div class="confirmation_checking_msgs">
<p id="confirmation_checking_price_msg"><i class="fas fa-spinner fa-spin"></i> Vérification des prix...</p>
<p id="confirmation_price_changed" style="display:none;"><i class="fas fa-exclamation-triangle"></i> Des prix ont changé, veuillez recharger la page après cette opération.</p>
</div>
<table id="confirmation_table" class="display" cellspacing="0" width="100%"></table>
</div>
<br>
<h5>Total (H.T.) : <span class="total_value">0</span></h5>
<hr />
<section class="set_member_to_movement">
<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">
<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>
<hr />
</div>
<div id="modal_confirm_delete_line">
<p>Vous vous apprêtez à suprimer une ligne du tableau, <b>êtes-vous sûr ?</b></p>
</div>
</div>
</div>
<script src="{% static "js/all_common.js" %}?v="></script>
<script src='{% static "js/barcodes.js" %}?v='></script>
{% endblock %}
{% extends "base.html" %}
{% load static %}
{% block additionnal_css %}
<link rel="stylesheet" href="{% static 'css/datatables/jquery.dataTables.css' %}">
<link rel="stylesheet" href="{% static 'css/stock_movements_view.css' %}">
<link rel="stylesheet" href="{% static 'jquery-ui-1.12.1/jquery-ui.min.css' %}">
{% endblock %}
{% block additionnal_scripts %}
<script type="text/javascript" src="{% static 'js/datatables/jquery.dataTables.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/dataTables.plugins.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/datatables.buttons.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/buttons.html5.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/jszip.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/pdfmake.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/vfs_fonts.js' %}"></script>
<script type="text/javascript" src="{% static 'js/stock_movements_view.js' %}?v="></script>
<script type="text/javascript" src="{% static 'jquery-ui-1.12.1/jquery-ui.min.js' %}?v="></script>
{% endblock %}
{% block content %}
<div class="page_body">
<div id="content_main" class="page_content">
<div class="header txtcenter">
<h1>Extraction de mouvements de stock</h1>
</div>
<div class="select_movement_area txtcenter">
<select class="select_movement_element select_movement_input" id="movement_type_selector" name="">
<option value="">-- Choisissez un type de mouvement --</option>
<option value="losses">Pertes</option>
<option value="meals">Repas salariés</option>
<option value="autoconso">Autoconsomation</option>
</select>
<p class="select_movement_element">De : <input type="text" id="from" class="select_movement_input"></p>
<p class="select_movement_element">À : <input type="text" id="to" class="select_movement_input"></p>
<button type="button" class="btn--primary" id="movement_selection_button" disabled>C'est parti !</button>
</div>
<div class="main" style="display:none;">
<div class="txtcenter">
<h3>Total de <span id="count_movements"></span> mouvement(s) d'une valeur de <span id="total_value_movements"></span></h3>
</div>
<div class="table_area">
<table id="movements_table" class="display" cellspacing="0" width="100%"></table>
</div>
</div>
</div>
<div id="templates" style="display:none;">
</div>
</div>
<script src="{% static "js/all_common.js" %}?v="></script>
{% endblock %}
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