Commit adf52d03 by François C.

Cooperatic internal repository modifications to integrate before this repository to close

parent b4d49b83
"""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):
return res
@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):
res = {}
......@@ -153,7 +161,7 @@ class CagetteInventory(models.Model):
'products': ids
}
# Crate inventory file, name is timestamp of creation
# Create inventory file, name is timestamp of creation
timestamp = int(time.time())
filename = custom_list_file_path + str(timestamp) + '.json'
with open(filename, 'w+') as outfile:
......
......@@ -12,7 +12,7 @@ function init_datatable() {
{
data:"datetime_created",
title:"Liste",
render: function (data, type, full, meta) {
render: function (data) {
return "Liste du " + data;
}
},
......@@ -31,7 +31,7 @@ function init_datatable() {
title:"Inventaire à faire",
className:"dt-body-center",
width: "15%",
render: function (data, type, full, meta) {
render: function (data) {
if (data == '')
return "<button class='btn--primary do_inventory'>Inventaire en rayon</button>";
else
......
......@@ -9,6 +9,7 @@ urlpatterns = [
url(r'^delete_custom_list$', views.delete_custom_list),
url(r'^custom_list_inventory/([0-9]+)$', views.custom_list_inventory),
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'^generate_inventory_list$', views.generate_inventory_list),
url(r'^get_credentials$', views.get_credentials),
......
......@@ -64,6 +64,17 @@ def get_custom_list_data(request):
except Exception as e:
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):
res = {}
data = json.loads(request.body.decode())
......
......@@ -986,8 +986,18 @@ class CagetteServices(models.Model):
'seats_available']
c = [['active', '=', True]]
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*)")
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 = {}
end = time.strptime(l['end_datetime_tz'], "%Y-%m-%d %H:%M:%S")
end_min = str(end.tm_min)
......@@ -995,6 +1005,7 @@ class CagetteServices(models.Model):
end_min = '00'
line['end'] = str(end.tm_hour) + ':' + end_min
line['max'] = l['seats_max']
# line['reserved'] = nb_reserved
line['reserved'] = l['seats_reserved']
line['week'] = l['week_number']
line['id'] = l['id']
......@@ -1214,4 +1225,3 @@ class CagetteUser(models.Model):
pass
return answer
......@@ -212,8 +212,12 @@ function store_new_coop(event) {
msex = $('input[name="sex"]:checked').val();
}
if (payment_meaning.val() == 'ch' && ch_qty.val() <1) {
if (payment_meaning.val() == 'ch') {
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,
......
......@@ -20,7 +20,8 @@ var current_displayed_member = null,
loaded_services = null,
selected_service = null,
last_search_time = null,
rattrapage_ou_volant = null;
rattrapage_ou_volant = null,
timeout_counter = null;
var search_button = $('.btn--primary.search');
var loading2 = $('.loading2');
var search_field = $('input[name="search_string"]');
......@@ -77,9 +78,10 @@ function fill_member_slide(member) {
html_elts.name.html(member.name);
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;
} else {
img_src = "/static/img/pas-de-photo.png";
no_pict_msg.show();
}
html_elts.image_medium.html('<img src="'+img_src+'" width="128" />');
......@@ -157,8 +159,8 @@ function canSearch() {
return answer;
}
function search_member() {
if (canSearch() == true) {
function search_member(force_search = false) {
if (canSearch() || force_search) {
html_elts.member_slide.hide();
search_box_clear_html_elts();
......@@ -588,6 +590,13 @@ function goto_page(jquery_page_selected) {
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);
search_field.keyup(search_input_listing);
......@@ -657,32 +666,42 @@ html_elts.image_medium.on('click', function() {
});
$(document).ready(function() {
var pressed = false;
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) {
if (e.which >= 48 && e.which <= 57) {
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)) {
chars = [];
pressed = false;
search_member();
goto_page(pages.shopping_entry);
search_field.val(barcode);
last_search_time = null;
search_member(true);
}
}
}, 300);
}
pressed = true;
});
init_webcam();
$('#crop_width').change(function() {
Webcam.reset();
......
......@@ -501,6 +501,7 @@ function coop_problem_delete() {
}
function open_coop_form(e) {
try {
if (e) {
var clicked = $(this);
......@@ -522,6 +523,11 @@ function open_coop_form(e) {
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() {
......@@ -537,6 +543,7 @@ function ask_for_problem_deletion() {
openModal(msg, coop_problem_delete, 'Oui');
}
function add_coop_to_box(box, coop) {
try {
var info = coop.firstname + ' ' + coop.lastname;
if (coop.odoo_state && coop.odoo_state == 'done') {
......@@ -558,10 +565,16 @@ function add_coop_to_box(box, coop) {
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');
}
}
//Called after having retrieved all coops
function dispatch_coops_in_boxes() {
try {
$('div.coop').off('click', open_coop_form);
to_fill_box.html('');
with_errors_box.html('');
......@@ -587,6 +600,11 @@ function dispatch_coops_in_boxes() {
$('div.coop').on('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) {
......@@ -609,6 +627,7 @@ function handle_legacy_states(rows) {
// first function called when loading page
function retrieve_all_coops() {
try {
dbc.allDocs({include_docs: true, descending: true}, function(err, resp) {
if (err) {
return console.log(err);
......@@ -652,6 +671,11 @@ function retrieve_all_coops() {
});
dispatch_coops_in_boxes();
});
} catch (err) {
error = {msg: err.name + ' : ' + err.message, ctx: 'retrieve_all_coops'};
console.log(error);
report_JS_error(error, 'prepa-odoo');
}
}
$(document).ready(function() {
......
//used in prepa-odoo and validation_coop
var coop_page = $('#coop_page');
function show_checks_nb() {
......
from django.test import TestCase
# Create your tests here.
......@@ -73,6 +73,7 @@ def inscriptions(request, type=1):
'ask_for_sex': getattr(settings, 'SUBSCRIPTION_ASK_FOR_SEX', False),
'open_on_sunday': getattr(settings, 'OPEN_ON_SUNDAY', False),
'POUCHDB_VERSION': getattr(settings, 'POUCHDB_VERSION', ''),
'max_chq_nb': getattr(settings, 'MAX_CHQ_NB', 12),
'db': settings.COUCHDB['dbs']['member']}
response = HttpResponse(template.render(context, request))
......
......@@ -212,9 +212,10 @@ class Order(models.Model):
def get_custom_barcode_labels_to_print(self):
import re
fixed_prefix = getattr(settings, 'FIXED_BARCODE_PREFIX', '0490')
labels_data = {'total': 0, 'details': []}
lines = self.get_lines()
bc_pattern = re.compile('^0490')
bc_pattern = re.compile('^' + fixed_prefix)
for l in lines:
if ('barcode' in l) and not (bc_pattern.match(str(l['barcode'])) is None):
labels_data['details'].append(l)
......@@ -256,7 +257,8 @@ class Orders(models.Model):
import re
labels_data = {}
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):
if not (bc_pattern.match(str(l['barcode'])) is None):
if not (l['product_tmpl_id'] in labels_data):
......
......@@ -98,11 +98,14 @@
{'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': 'vir', 'title': 'Virement', 'journal_id': VIREMENT_PAYMENT_ID}]
Used to generate payment meanings in subscription form
- MAX_CHQ_NB = 12
Maximum accepted checks numbers
### Scales and labels files generation
- DAV_PATH = '/data/dav/cagette'
......@@ -125,6 +128,10 @@
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_CAN_BUY = False
......
......@@ -5,6 +5,7 @@ class CagetteMail:
@staticmethod
def sendWelcome(email):
"""Used in members/models.py , but mail is now sent by Odoo"""
from django.core.mail import send_mail
import re
from django.utils.html import strip_tags
......@@ -19,15 +20,16 @@ class CagetteMail:
[email],
fail_silently=False,
html_message=html_msg)
@staticmethod
def sendCartValidation(email, cart):
"""Used by Shop"""
from django.core.mail import send_mail
from django.utils.html import strip_tags
from django.template.loader import render_to_string
from datetime import datetime
import pytz
tz = pytz.timezone("Europe/Paris")
d_obj = datetime.fromtimestamp(cart['submitted_time'], tz)
if ('comment' in cart) and len(cart['comment']) == 0:
......@@ -35,18 +37,13 @@ class CagetteMail:
ctx = {'mag': settings.COMPANY_NAME,
'cart': cart,
'order_date': d_obj.strftime('%d/%m/%Y à %Hh%S (UTC)')}
try:
ctx['survey_link'] = settings.SHOP_SURVEY_LINK
except:
pass
mail_template = 'shop/cart_validation_email.html'
try:
mail_template = settings.VALIDATION_ORDER_MAIL_TEMPLATE
except:
pass
ctx['survey_link'] = getattr(settings, 'SHOP_SURVEY_LINK', None)
mail_template = getattr(settings, 'VALIDATION_ORDER_MAIL_TEMPLATE', 'shop/cart_validation_email.html')
html_msg = render_to_string(mail_template, ctx)
msg = strip_tags(html_msg)
send_mail("Votre commande en ligne à " + settings.COMPANY_NAME ,
send_mail("Votre commande en ligne à " + settings.COMPANY_NAME,
msg,
settings.DEFAULT_FROM_EMAIL,
[email],
......
......@@ -14,7 +14,6 @@ https://docs.djangoproject.com/en/1.8/ref/settings/
import os
from .settings_secret import *
from .settings_constants import *
from .texts.cagette import *
from .config import *
from .customized_errors_filter import *
......
......@@ -165,3 +165,26 @@ footer { position: fixed;
}
.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):
return res
@staticmethod
def get_product_info_for_label_from_template_id(template_id):
"""Get product info for label."""
......@@ -54,14 +55,26 @@ class CagetteProduct(models.Model):
fields = ['barcode', 'product_tmpl_id', 'pricetag_rackinfos',
'price_weight_net', 'price_volume', 'list_price',
'weight_net', 'volume', 'to_weight']
fields += getattr(settings, 'SHELF_LABELS_ADD_FIELDS', [])
return api.search_read('product.product', cond, fields)
additionnal_fields = getattr(settings, 'SHELF_LABELS_ADD_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
def generate_label_for_printing(templ_id, directory, price=None, nb=None):
res = {}
try:
p = CagetteProduct.get_product_info_for_label_from_template_id(templ_id)
if (p and p[0]['product_tmpl_id'][0] == int(templ_id)):
product = p[0]
txt = ''
......@@ -91,6 +104,18 @@ class CagetteProduct(models.Model):
coop_logger.error("Generate label : %s %s", templ_id, str(e))
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):
"""Initially used to make massive barcode update."""
......@@ -166,6 +191,8 @@ class CagetteProducts(models.Model):
def get_products_for_label_appli(withCandidate=False):
fields = ['sale_ok', 'uom_id', 'barcode',
'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)
if len(vcats) > 0:
vrac = CagetteProducts.get_vrac_products_from_cats(vcats, withCandidate, fields)
......@@ -182,6 +209,19 @@ class CagetteProducts(models.Model):
fields = ['uom_id', 'display_name','barcode']
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
def get_all_barcodes():
"""Needs lacagette_products Odoo module to be active."""
......@@ -195,6 +235,12 @@ class CagetteProducts(models.Model):
for p in res['list']:
# transcode result to compact format (for bandwith save and browser memory)
# 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']] = [
p['display_name'],
p['sale_ok'],
......@@ -202,6 +248,7 @@ class CagetteProducts(models.Model):
p['available_in_pos'],
p['id'],
p['standard_price'],
p['list_price'],
p['uom_id']]
if 'uoms' in res and 'list' in res['uoms']:
result['uoms'] = res['uoms']['list']
......@@ -264,8 +311,11 @@ class CagetteProducts(models.Model):
@staticmethod
def get_barcode_rules():
c = [['type', 'in', ['FF_price_to_weight', 'price', 'price_to_weight', 'product', 'weight' ]], ['barcode_nomenclature_id','=', 1]]
rules = OdooAPI().search_read('barcode.rule', c, ['pattern'], order="sequence ASC")
result = {'patterns': [], 'aliases': {}}
try:
import re
c = [['type', 'in', ['FF_price_to_weight', 'price', 'price_to_weight', 'product', 'weight', 'alias']], ['barcode_nomenclature_id','=', 1]]
rules = OdooAPI().search_read('barcode.rule', c, ['pattern', 'type', 'alias'], order="sequence ASC")
# As rules are ordered by sequence, let's find where to stop (.* pattern)
stop_idx = len(rules) - 1
i = 0
......@@ -275,7 +325,18 @@ class CagetteProducts(models.Model):
i += 1
if stop_idx > 0:
rules = rules[:stop_idx - 1]
return rules
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
......@@ -283,16 +344,16 @@ class CagetteProducts(models.Model):
import re
from outils.functions import computeEAN13Check
bc_map = {}
rules = CagetteProducts.get_barcode_rules()
rules = rules['patterns']
# now, just keep rules with N in pattern
rules = list(filter(lambda x: '.' in x['pattern'], rules))
rules = list(map(lambda x: x['pattern'], rules))
# now remove {NN...} from pattern
rules = list(map(lambda x: re.sub(r'{.+}', '', x), rules))
# coop_logger.info('rules = %s', rules)
# now compile regex for pattern
regex = []
for r in rules:
......
......@@ -13,11 +13,12 @@ IFCBarcodes = {
closeModal();
if (typeof bc_data.res.error == "undefined") {
this.patterns = bc_data.res.patterns;
this.aliases = bc_data.res.aliases;
this.codes = bc_data.res.list.pdts;
this.uoms = bc_data.res.list.uoms;
this.keys = bc_data.res.keys;
} else {
this.errors.push(bc_data.error);
this.errors.push(bc_data.res.error);
}
} catch (e) {
err = {msg: e.name + ' : ' + e.message, ctx: 'retrieve barcodes'};
......@@ -33,26 +34,33 @@ IFCBarcodes = {
},
get_corresponding_odoo_product: function(bc) {
//console.log('To analyze :' + bc)
var odoo_product = null;
var index = 0,
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
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
if (bc.indexOf(significant_prefix) === 0) {
// console.log(pattern)
/*
For example,
bc = 0493213018809
pattern = 0493...{NNDDD}
*/
//console.log(pattern)
// console.log(bc)
//0493...{NNDDD} (pattern)
//0493213018809 (bc)
odoo_bc = '';
pattern_found = true;
pattern_type = this.patterns[index].type;
pattern = pattern.replace(/[^0-9.ND]/, '');
bc = bc.slice(0, -1); // remove original check figure
odoo_bc = '';
// Read pattern character by character
for (var i = 0; i < pattern.length; i++) {
if (/[0-9]/.exec(pattern[i])) {
......@@ -72,13 +80,39 @@ IFCBarcodes = {
index++;
}
// let's seek "normalized" bc in codes array
// let's seek "normalized" bc in codes array or alias map
for (alias in this.aliases) {
if (bc == alias) {
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) {
odoo_product = {barcode: code, data: this.codes[code], value: encoded_value};
product_data = this.codes[code];
}
}
}
//console.log(odoo_product)
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};
}
return odoo_product;
}
};
......@@ -93,5 +127,6 @@ init_barcodes = async function() {
else
result = ifcb;
// console.log(result.patterns)
return result;
};
from django.test import TestCase
# Create your tests here.
......@@ -83,7 +83,13 @@ def labels_appli_csv(request, params):
try:
if (params == '/wc'):
withCandidate = True
with_pos_categories = getattr(settings, 'EXPORT_POS_CAT_FOR_SCALES', False)
products = CagetteProducts.get_products_for_label_appli(withCandidate)
if with_pos_categories is True:
pos_categories = CagetteProducts.get_pos_categories()
else:
pos_categories = []
rows = []
for p in products:
if (p['sale_ok'] is True):
......@@ -96,28 +102,31 @@ def labels_appli_csv(request, params):
barcode = ''
if not (barcode.isnumeric()):
barcode = ''
rows.append([p['id'], p['display_name'], barcode,
p_row = [p['id'], p['display_name'], barcode,
p['list_price'],
# p['categ_id'][1],
p['categ'],
uom,
# p['image'].replace(img_temp_folder, ""),
p['image'].replace("\n", ""),
# p['available_in_pos'] # ,p['sale_ok']
])
p['image'].replace("\n", "")]
if with_pos_categories is True:
if p['pos_categ_id']:
p_row.append(p['pos_categ_id'][0])
else:
p_row.append('')
rows.append(p_row)
header = ['id', 'nom', 'code-barre', 'prix',
'categorie', 'unite', 'image'
# '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'
file_copies = []
nb = 1
if hasattr(settings, 'FLV_CSV_NB'):
nb = int(settings.FLV_CSV_NB)
nb = int(getattr(settings, 'FLV_CSV_NB', 1))
for i in range(1, nb + 1):
file_copies.append(settings.DAV_PATH + '/flv_' + str(i) + '.csv')
......@@ -133,6 +142,8 @@ def labels_appli_csv(request, params):
file.close()
for c in file_copies:
copyfile(os_file, c)
except Exception as e:
res['error'] = str(e)
return JsonResponse({'res': res})
......@@ -176,14 +187,14 @@ def get_all_barcodes(request):
'available_in_pos': 3,
'id': 4,
'standard_price': 5,
'uom_id': 6
'list_price': 6,
'uom_id': 7
}
rules = CagetteProducts.get_barcode_rules()
res['patterns'] = rules['patterns']
res['aliases'] = rules['aliases']
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:
coop_logger.error("products_barcodes : %s", str(e))
res['error'] = str(e)
......
......@@ -232,3 +232,7 @@ hr {
}
#main_content {width: 100%;}
.select_product_action {
max-width: 15px;
}
......@@ -38,7 +38,6 @@ function group_goto(i) {
* Set local storage for given order
*/
function set_local_storage(order_data) {
console.log(order_data);
if (Modernizr.localstorage) {
var stored_order = JSON.parse(localStorage.getItem('order_' + order_data.id));
......
......@@ -27,7 +27,7 @@ class Shelf(models.Model):
self.o_api = OdooAPI()
def get(self):
res ={}
res = {}
try:
c = [['id', '=', self.id]]
f = []
......@@ -160,6 +160,7 @@ class Shelf(models.Model):
barcodes = list(map(str, barcodes))
# Get bc as should be stored in Odoo
bc_map = CagetteProducts.get_fixed_barcode_correspondance(barcodes)
p_res = self._get_pdts_from_barcodes(list(bc_map.values()))
if (p_res):
found_bc = []
......@@ -200,6 +201,12 @@ class Shelf(models.Model):
filename = tmp_inv_file_prefix + str(self.id) + '.json'
os.remove(filename)
lockfilename = tmp_inv_file_prefix + str(self.id) + '.lock'
try:
os.remove(lockfilename)
except Exception as e:
pass
return True
except Exception as e:
return False
......@@ -220,6 +227,24 @@ class Shelf(models.Model):
first_inventory = json.load(json_file)
except Exception as 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 poducts in saved data
......
......@@ -106,14 +106,18 @@ function handle_blinking_effect(element) {
// When edition event is fired
function edit_event(clicked) {
// Remove from origin table
var row_data = null;
if (editing_origin == 'to_process') {
let row = table_to_process.row(clicked.parents('tr'));
let row_data = row.data();
row_data = row.data();
remove_from_toProcess(row);
} else {
let row = table_processed.row(clicked.parents('tr'));
let row_data = row.data();
row_data = row.data();
remove_from_processed(row);
}
......@@ -440,6 +444,7 @@ function send() {
shelf.user_comments = user_comments;
var url = "../do_" + originView + "_inventory";
var call_begin_at = new Date().getTime();
$.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } });
$.ajax({
......@@ -475,14 +480,60 @@ function send() {
// Clear local storage before leaving
localStorage.removeItem(originView + '_' + shelf.id);
},
error: function(data) { // 500 error has been thrown
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
console.log(data.responseJSON.error);
}
error: function(jqXHR, textStatus) { // 500 error has been thrown or web server sent a timeout
if (jqXHR.status == 504) {
/*
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();
alert("Erreur lors de la sauvegarde des données. " +
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.");
"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();
}
}
});
} else {
......@@ -776,7 +827,6 @@ $(document).ready(function() {
originView = 'custom_list';
parent_location = '/inventory/custom_lists';
}
//console.log(products)
// Get shelf data from local storage
if (Modernizr.localstorage) {
......
......@@ -16,6 +16,7 @@ var main_content = $('#main-content'),
.removeAttr('id'),
active_phase = 'main',
add_pdts_btn_text = 'AJOUTER AU RAYON',
add_to_shelf_product_ids = [],
barcodes = null;
......@@ -339,14 +340,27 @@ var init_and_fill_selfs_list = 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');
clicked.closest('li').remove();
/*
if (clicked.closest('ul').find('li').length == 0)
main_content.find('button.add-products').css('display','none')
*/
$.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;
});
return found;
};
var addProductToList = async function(barcode) {
if (barcodes == null) barcodes = await init_barcodes(); // May appens (after inactivity?)
//Get Odoo corresponding barcode
......@@ -355,17 +369,22 @@ var addProductToList = async function(barcode) {
odoo_product = barcodes.get_corresponding_odoo_product(barcode);
if (is_product_in_shelf_adding_queue_list(odoo_product.data[barcodes.keys.id])) {
console.log("Already added product");
} else {
add_to_shelf_product_ids.push(odoo_product.data[4]);
if (odoo_product === null) {
alert(barcode + ' : Code-barre inconnu');
} else {
var pdt_line = $('<tr>').attr('data-bc', odoo_product.barcode)
var pdt_line = $('<tr>').attr('data-id', odoo_product.data[barcodes.keys.id])
.attr('data-bc', odoo_product.barcode)
.addClass('obc');
$('<td>').text(barcode)
.appendTo(pdt_line);
$('<td>').text(odoo_product.barcode)
.appendTo(pdt_line);
$('<td>').text(odoo_product.data[0])
$('<td>').text(odoo_product.data[barcodes.keys.name])
.appendTo(pdt_line);
$('<td>').html(delete_icon)
.appendTo(pdt_line);
......@@ -373,6 +392,7 @@ var addProductToList = async function(barcode) {
main_content.find('button.add-products').css('display', 'block')
.html(add_pdts_btn_text);
}
}
};
var addProducts = async function() {
......@@ -380,7 +400,7 @@ var addProducts = async function() {
var data = rowGetData(clicked);
if (barcodes == null) barcodes = await init_barcodes();
add_to_shelf_product_ids = [];
adding_pdts_tpl.find('.shelf').text(data.name + ' (num = ' + data.sort_order+')')
.attr('data-shelfid', data.id);
adding_pdts_tpl.find('#added_products tbody').empty();
......@@ -402,6 +422,7 @@ var recordProductsAddedShelf = function() {
to_add.each(function(i, e) {
barcodes.push($(e).data('bc'));
});
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);
post_form(
......@@ -518,7 +539,7 @@ $(document).ready(function() {
$(document).on('click', '.shelfs .fa-edit', open_update_form);
$(document).on('click', '.shelfs .fa-trash', deleteShelf);
$(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', '#main-content button.add-products', recordProductsAddedShelf);
$(document).on('click', 'td.p_nb', showProductsList);
......
......@@ -8,6 +8,7 @@ urlpatterns = [
url(r'^$', views.index),
url(r'^shelf_view/([0-9]+)$', views.shelf_view),
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'^get_shelves_extra_data$', views.get_shelves_extra_data),
url(r'^(?P<shelf_id>\d+)$', views.shelf_data),
......
......@@ -13,7 +13,7 @@ def index(request):
"""Main shelf page"""
shelfs = Shelfs.get_all()
# TODO : Make the distinction beetween active and inactive products
for s in shelfs :
for s in shelfs:
s['shelf_value'] = -1
context = {'title': 'Rayons',
......@@ -107,8 +107,29 @@ def add_product(request, shelf_id):
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):
"""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 = {}
# TODO : manage error strings array instead of one string
try:
......@@ -121,7 +142,8 @@ def do_shelf_inventory(request):
'name': shelf_data['name'] + ' - ' + inventory_date.strftime("%d/%m/%Y"),
'shelf_id': shelf_data['id'],
'user_comments': shelf_data['user_comments'],
'products': shelf_data['list_processed']
'products': shelf_data['list_processed'],
'status': shelf_data['inventory_status']
}
try:
filename = 'data/inventories_backup/'
......@@ -129,20 +151,26 @@ def do_shelf_inventory(request):
filename += "__" + str(shelf_data['id']) + '.json'
with open(filename, 'w') as outfile:
json.dump(shelf_data, outfile)
except:
pass
except Exception as serr:
coop_logger.error("Inventory backup failure : %s", str(serr))
try:
if shelf_data['inventory_status'] == '' :
if shelf_data['inventory_status'] == '':
# First step: save first products count in temp file
res = m.save_tmp_inventory(inventory_data)
else :
else:
inventory_data['date'] = inventory_date
inventory_data['shelf_name'] = shelf_data['name']
inventory_data['shelf_num'] = shelf_data['sort_order']
# Get data from step 1
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
res['inventory'] = CagetteInventory.update_stock_with_shelf_inventory_data(full_inventory_data)
......@@ -173,7 +201,7 @@ def do_shelf_inventory(request):
except Exception as e:
# 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))
except Exception as err_json:
res['error'] = "Unable to parse received JSON"
......
body {margin: 5px;}
#exchange_instructions, .need_make_up {display:none;}
.need_make_up, .highlight {background: #ffff00;}
.need_make_up_area {margin-top: 15px;}
#partnerData {margin:8px 0;}
#shift_list_wrapper {margin:15px 25px; padding: 5px; border-radius: 5px; border: 2px solid #424242; max-width: 530px; width: 350px;}
......
......@@ -239,7 +239,6 @@ function addMakeUpMsg() {
}
});
make_up_nb = parseInt(partner_points) + shifts_before_limit;
// Besoin de choisir un rattrapage : afficher message rattrapage
if (make_up_nb < 0) {
make_up_nb = Math.abs(make_up_nb);
......@@ -284,7 +283,9 @@ function addMakeUpMsg() {
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();
} else {
$('.need_make_up').hide();
......
......@@ -92,6 +92,10 @@ function djLogError(e) {
}
var french_date_and_time = function(dstring) {
if (shop_mode == 'delivery') {
return dstring;
}
var formatted = dstring.trim().replace(/-/g, "/"); // replace for Safari
//expected = YYYY-MM-DD HH:MM[:00]
......@@ -494,7 +498,7 @@ var getStoredOrder = function() {
var stored = null;
try {
stored = JSON.parse(localStorage.getItem('currentOrder'));
stored = JSON.parse(localStorage.getItem(current_order_name));
} catch (e) {
//WARNING : In this case, make sure the user haven't got any order initialized
//TODO : make a request to retrieve it
......@@ -508,7 +512,7 @@ var getStoredOrderForMigration = function() {
var stored = null;
try {
stored = JSON.parse(localStorage.getItem('saved_order'));
stored = JSON.parse(localStorage.getItem(saved_order_name));
} catch (e) {
djLogError({ctx: "get stored for mig", msg:e});
}
......@@ -523,7 +527,7 @@ var storeOrderForMigration = function() {
delete to_save._rev;
delete to_save.timer_end_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;
storeOrder();
} catch (e) {
......@@ -532,7 +536,7 @@ var storeOrderForMigration = function() {
};
var storeOrder = function() {
try {
localStorage.setItem("currentOrder", JSON.stringify(order));
localStorage.setItem(current_order_name, JSON.stringify(order));
} catch (e) {
djLogError({ctx: "store order", msg:e});
}
......@@ -604,6 +608,7 @@ var pass2step2 = function() {
$('h1').show();
var recup = order.best_date || 'non défini';
if (shop_mode == 'shop')
current_order_bdate.html("Récupération : <strong>" + french_date_and_time(recup) + "</strong>");
if (visit_mode == true) {
$('.product .choice').hide();
......@@ -774,6 +779,8 @@ var isChoosenSlotValid = function(slot) {
var answer = true;
var cause = '';
// For delivery, no slot validation: return true
if (shop_mode == 'shop') {
forbidden_slots.forEach(function(e) {
if (slot == e) {
answer = false; cause = 'full';
......@@ -788,6 +795,7 @@ var isChoosenSlotValid = function(slot) {
if (slot_date - min_date < 0) {
answer = false; cause = 'delay';
}
}
return {res: answer, reason: cause};
};
......@@ -912,7 +920,6 @@ var validCart = function() {
post_form(
'/shop/cart', {order: JSON.stringify(order)},
function(err, result) {
//console.log(result)
if (!err) {
try {
if (typeof result.res.cart != "undefined" && result.res.cart != null) {
......@@ -948,7 +955,7 @@ var validCart = function() {
};
var clearCart = function() {
order = {products: [], total: 0.00};
order = { products: [], total: 0.00, type: shop_mode};
storeOrder();
cart.find('.cart-elt').remove();
updateCartTotal(0, 0);
......@@ -1053,14 +1060,22 @@ var loadAllAvailableBoughtProducts = function() {
};
var appendChildrenCatToMenu = function (catdiv, children) {
// console.log(children)
var ul = catdiv.find('ul');
$.each(children, function(i, e) {
if (excluded_cat.indexOf(e.id) < 0) {
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)
.text(e.name);
.text(name);
li.append(span);
ul.append(li);
......@@ -1347,6 +1362,7 @@ var changeBestDate = function() {
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());
if (shop_mode == 'shop')
fillBDayOptions(bday_change_sel);
updateUnavailableSlots(function() {
......@@ -1473,6 +1489,7 @@ var launch_init_form = function() {
make_user_wait('Préparation du formulaire...');
current_action = 'init_form';
updateUnavailableSlots(function() {
if (shop_mode == 'shop')
fillBDayOptions(bday_sel);
openModal($('#cart_creation_form').html(), initCart, 'Commencer la commande');
});
......@@ -1520,7 +1537,9 @@ var justVisit = function() {
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 content4 = $('#tab-content4');
var category_elts = getStoredCatElts() || {};
......
......@@ -354,6 +354,18 @@ function init_and_fill_order_list() {
orders_table.destroy();
var cols = [
{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: 'best_date', title: "Livraison"},
{data: 'total', title: "Montant"}
......
......@@ -5,7 +5,8 @@ from . import views
from . import admin
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_cat_children$', views.get_cat_children),
url(r'^get_categ_products$', views.get_categ_products),
......
......@@ -5,13 +5,20 @@ from outils.for_view_imports import *
from members.models import CagetteMember
from shop.models import CagetteShop
@never_cache
def shop_index(request):
return index(request, mode='shop')
@never_cache
def index(request):
def delivery_index(request):
return index(request, mode='delivery')
def index(request, mode="shop"):
template = loader.get_template('shop/index.html')
credentials = CagetteMember.get_credentials(request)
shop_settings = CagetteShop.get_shop_settings()
context = {'title': 'Commande / Réservation',
'mode': mode,
'COMPANY_NAME': settings.COMPANY_NAME,
'SHOP_CATEGORIES': settings.SHOP_CATEGORIES,
'EXCLUDE_SHOP_CATEGORIES': settings.EXCLUDE_SHOP_CATEGORIES,
......@@ -44,8 +51,12 @@ def index(request):
context['SHOP_SLOT_SIZE'] = settings.SHOP_SLOT_SIZE
if hasattr(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['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()
context['discounted_pdts'] = d_p_pdts['discounted']
......@@ -129,7 +140,8 @@ def cart_init(request):
try:
# first of all, verifiying cart is respecting time slots constraints
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):
result['ts_respect'] = False
result['error'] = 'Forbidden timeslot'
......
......@@ -8,6 +8,31 @@ from datetime import datetime
class CagetteStock(models.Model):
@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):
"""Do a stock movement : """
......@@ -18,29 +43,33 @@ class CagetteStock(models.Model):
# Set stock movement details according to destination
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
destination = settings.LOSSES_LOC_ID
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
destination = settings.MEALS_LOC_ID
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
destination = settings.AUTOCONSO_LOC_ID
else:
errors.append('Type de mouvement incorrect')
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 = {
'company_id': 1,
'name': picking_name,
'picking_type_id' : picking_type, # mouvement type
'location_id': settings.STOCK_LOC_ID, # movement origin
'location_dest_id': destination, # movement dest
"move_lines": [],
"pack_operation_ids": []
'move_lines': [],
'pack_operation_ids': [],
'operator_id': stock_movement_data['operator']['id']
}
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
from . import views
urlpatterns = [
url(r'^movements', views.movements_page),
url(r'^movements$', views.movements_page),
url(r'^do_movement', views.do_movement),
url(r'^movements_view$', views.movements_view),
url(r'^get_movements$', views.get_movements),
### NOT IN USE: ###
......
......@@ -18,6 +18,15 @@ def movements_page(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):
"""Do the stock movement: losses, self conso or stock correction"""
res = {}
......@@ -46,6 +55,20 @@ def do_movement(request):
else:
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 ###
# ??? a voir si on garde les heur d'ouverture de la cagette ici
......
......@@ -47,7 +47,7 @@
</div>
Recherche :
<input type="text" name="search_string" autocomplete="off" />
<button class="btn--primary search">GO!
<button class="btn--primary search">Cherche-moi !
</button>
<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">
......
......@@ -15,7 +15,8 @@
<script type="text/javascript">
var type = {{type}},
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 %}
let open_on_sunday = true
{% endif %}
......
......@@ -10,6 +10,7 @@
<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/dataTables.plugins.js' %}"></script>
<script type="text/javascript" src="{% static 'js/jquery.pos.js' %}"></script>
{% endblock %}
{% block content %}
......@@ -92,7 +93,6 @@
<span id="valid_all"></span>
<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>
</span>
</div>
{% else %}
......@@ -153,6 +153,14 @@
<p>Êtez-vous sûr ?</p>
<hr />
</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">
<h3>Traitement impossible</h3>
<p>
......@@ -179,6 +187,6 @@
var fixed_barcode_prefix = '{{FIXED_BARCODE_PREFIX}}'
</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>
{% endblock %}
......@@ -29,7 +29,8 @@
<p>Ça veut dire que soit :</p>
<ul>
<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>
<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>
......
......@@ -2,21 +2,25 @@ 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. 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>
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>
......@@ -34,7 +38,7 @@ Commande reçue :
{% endfor %}
</table>
<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/>
{%if cart.accept_substitution %}
Vous acceptez les substitutions de produits.
......@@ -46,9 +50,11 @@ 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 %}
<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/>
Merci de votre compréhension
......
......@@ -36,6 +36,10 @@
<li class="nav-item">
<span class="new-order">Nouvelle commande</span>
</li>
{% elif DELIVERY_CAN_BUY != False %}
<li class="nav-item">
<span class="new-order">Nouvelle commande</span>
</li>
{%endif%}
<li class="nav-item">
<span class="my-orders">Commande(s) envoyée(s)</span>
......@@ -318,7 +322,10 @@
<div id="cart_creation_form">
<p>
<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>
......@@ -341,6 +348,50 @@
<option value="17:00">17:00</option>
<option value="17:30">17:30</option>
</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;">
<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/>
......@@ -354,7 +405,9 @@
{%if HOURS_FOR_VALIDATION > 0 %}
<div class="tv-msg" style="display: none;">
<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>
</div>
{%endif%}
......@@ -367,7 +420,7 @@
<div id="cart_validation_form">
<h2>Validation de la commande</h2>
<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>
{%if SHOW_SUBSTITUTION_OPTION %}
<p class="accept_substitution">
......@@ -382,7 +435,9 @@
{%endif%}
</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>
{%endif%}
</p>
<p>
{{CART_VALIDATION_BOTTOM_MSG}}
......@@ -413,6 +468,9 @@
{% if SHOP_CAN_BUY != False %}
<li><button type="button" class="new-order btn--primary">Passer une nouvelle commande</button></li>
{%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="visit btn--primary">Parcourir le "magasin"</button></li>
......@@ -428,6 +486,7 @@
</div>
</div>
<script type="text/javascript">
const shop_mode = '{{ mode|safe }}'
var categories = {{SHOP_CATEGORIES|safe}}
var min_delay = {{MIN_DELAY_FOR_SLOT}}
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