Commit a267e241 by François C.

Merge branch 'dev_cooperatic' into 'dev_principale'

Intégrations dev Coopératic 2 dernières semaines avril

See merge request cooperatic-foodcoops/third-party!11
parents 47152eb8 4922c3e4
...@@ -46,6 +46,7 @@ def archive_envelop(request): ...@@ -46,6 +46,7 @@ def archive_envelop(request):
"done": False, "done": False,
"error": repr(e) "error": repr(e)
} }
coop_logger.error("Payment error : %s \n %s", str(data), str(e))
if res['done']: if res['done']:
# Immediately save a token than this payment has been saved # Immediately save a token than this payment has been saved
...@@ -59,22 +60,19 @@ def archive_envelop(request): ...@@ -59,22 +60,19 @@ def archive_envelop(request):
try: try:
res['partner_name'] = envelop['envelop_content'][partner_id]['partner_name'] res['partner_name'] = envelop['envelop_content'][partner_id]['partner_name']
res['amount'] = envelop['envelop_content'][partner_id]['amount'] res['amount'] = envelop['envelop_content'][partner_id]['amount']
except: except Exception as e:
res['error'] = "Wrong envelop structure" res['error'] = "Wrong envelop structure"
coop_logger.error("Wrong envelop structure : %s", str(e))
res_payments.append(res) res_payments.append(res)
try: try:
# Log the error # Log the error
lf = open("/tmp/erreurs_django.log", "a")
lf.write(datetime.date.today().strftime("%Y-%m-%d") + " - Erreur lors de l'enregistrement du paiement de " + res['partner_name'] + "(odoo_id:" + partner_id + " )")
lf.write(res['error'] + "\n")
lf.close()
msg = 'Erreur lors de l\'enregistrement du paiement ' + envelop['type'] msg = 'Erreur lors de l\'enregistrement du paiement ' + envelop['type']
msg += ' ' + envelop['envelop_content'][partner_id]['amount'] + ' euros ' msg += ' ' + str(envelop['envelop_content'][partner_id]['amount']) + ' euros '
msg += ' (' + res['error'] + ')' msg += ' (' + res['error'] + ')'
CagetteMember(int(partner_id)).attach_message(msg) CagetteMember(int(partner_id)).attach_message(msg)
except: except Exception as e:
pass coop_logger.error("Cannot attach payment error message to member : %s", str(e))
try: try:
# Delete envelop from couchdb # Delete envelop from couchdb
......
...@@ -93,7 +93,9 @@ class CagetteMember(models.Model): ...@@ -93,7 +93,9 @@ class CagetteMember(models.Model):
fp = request.POST.get('fp') # fingerprint (prevent using stolen cookies) fp = request.POST.get('fp') # fingerprint (prevent using stolen cookies)
if login and password: if login and password:
api = OdooAPI() api = OdooAPI()
cond = [['email', '=', login], ['is_member', '=', True]] cond = [['email', '=', login]]
if getattr(settings, 'ALLOW_NON_MEMBER_TO_CONNECT', False) is False:
cond.append(['is_member', '=', True])
fields = ['name', 'email', 'birthdate', 'create_date', 'cooperative_state'] fields = ['name', 'email', 'birthdate', 'create_date', 'cooperative_state']
res = api.search_read('res.partner', cond, fields) res = api.search_read('res.partner', cond, fields)
if (res and len(res) >= 1): if (res and len(res) >= 1):
...@@ -317,7 +319,7 @@ class CagetteMember(models.Model): ...@@ -317,7 +319,7 @@ class CagetteMember(models.Model):
if 'street2' in post_data: if 'street2' in post_data:
received_data['street2'] = post_data['street2'] received_data['street2'] = post_data['street2']
if 'phone' in post_data: if 'phone' in post_data:
received_data['phone'] = post_data['phone'] received_data['phone'] = format_phone_number(post_data['phone'])
r = c_db.updateDoc(received_data, 'odoo_id') r = c_db.updateDoc(received_data, 'odoo_id')
if r: if r:
if ('odoo_id' in r): if ('odoo_id' in r):
...@@ -394,7 +396,7 @@ class CagetteMember(models.Model): ...@@ -394,7 +396,7 @@ class CagetteMember(models.Model):
'street': post_data['address'], 'street': post_data['address'],
'zip': post_data['zip'], 'zip': post_data['zip'],
'city': post_data['city'], 'city': post_data['city'],
'phone': post_data['mobile'], # Because list view default show Phone and people mainly gives mobile 'phone': format_phone_number(post_data['mobile']), # Because list view default show Phone and people mainly gives mobile
'barcode_rule_id': settings.COOP_BARCODE_RULE_ID 'barcode_rule_id': settings.COOP_BARCODE_RULE_ID
} }
if ('_id' in post_data): if ('_id' in post_data):
...@@ -407,10 +409,10 @@ class CagetteMember(models.Model): ...@@ -407,10 +409,10 @@ class CagetteMember(models.Model):
f['street2'] = post_data['street2'] f['street2'] = post_data['street2']
if ('phone' in post_data) and len(post_data['phone']) > 0: if ('phone' in post_data) and len(post_data['phone']) > 0:
if len(f['phone']) == 0: if len(f['phone']) == 0:
f['phone'] = post_data['phone'] f['phone'] = format_phone_number(post_data['phone'])
else: else:
f['mobile'] = f['phone'] f['mobile'] = f['phone']
f['phone'] = post_data['phone'] f['phone'] = format_phone_number(post_data['phone'])
# Create coop # Create coop
if not ('odoo_id' in post_data): if not ('odoo_id' in post_data):
......
...@@ -48,5 +48,4 @@ urlpatterns = [ ...@@ -48,5 +48,4 @@ urlpatterns = [
url(r'^add_pts_to_everybody/([0-9]+)/([a-zA-Z0-9_ ]+)$', admin.add_pts_to_everybody), url(r'^add_pts_to_everybody/([0-9]+)/([a-zA-Z0-9_ ]+)$', admin.add_pts_to_everybody),
# conso / groupe recherche / socio # conso / groupe recherche / socio
url(r'^panel_get_purchases$', views.panel_get_purchases), url(r'^panel_get_purchases$', views.panel_get_purchases),
] ]
...@@ -102,6 +102,7 @@ def prepa_odoo(request): ...@@ -102,6 +102,7 @@ def prepa_odoo(request):
'office_place_string': settings.OFFICE_NAME, 'office_place_string': settings.OFFICE_NAME,
'max_begin_hour': settings.MAX_BEGIN_HOUR, 'max_begin_hour': settings.MAX_BEGIN_HOUR,
'payment_meanings': settings.SUBSCRIPTION_PAYMENT_MEANINGS, 'payment_meanings': settings.SUBSCRIPTION_PAYMENT_MEANINGS,
'input_phone_pattern': getattr(settings, 'INPUT_PHONE_PATTERN', default_input_phone_pattern),
'input_barcode': getattr(settings, 'SUBSCRIPTION_INPUT_BARCODE', False), 'input_barcode': getattr(settings, 'SUBSCRIPTION_INPUT_BARCODE', False),
'ask_for_sex': getattr(settings, 'SUBSCRIPTION_ASK_FOR_SEX', False), 'ask_for_sex': getattr(settings, 'SUBSCRIPTION_ASK_FOR_SEX', False),
'ask_for_street2': getattr(settings, 'SUBSCRIPTION_ADD_STREET2', False), 'ask_for_street2': getattr(settings, 'SUBSCRIPTION_ADD_STREET2', False),
...@@ -135,6 +136,7 @@ def validation_inscription(request, email): ...@@ -135,6 +136,7 @@ def validation_inscription(request, email):
'office_place_string': settings.OFFICE_NAME, 'office_place_string': settings.OFFICE_NAME,
'max_begin_hour': settings.MAX_BEGIN_HOUR, 'max_begin_hour': settings.MAX_BEGIN_HOUR,
'payment_meanings': settings.SUBSCRIPTION_PAYMENT_MEANINGS, 'payment_meanings': settings.SUBSCRIPTION_PAYMENT_MEANINGS,
'input_phone_pattern': getattr(settings, 'INPUT_PHONE_PATTERN', default_input_phone_pattern),
'ask_for_sex': getattr(settings, 'SUBSCRIPTION_ASK_FOR_SEX', False), 'ask_for_sex': getattr(settings, 'SUBSCRIPTION_ASK_FOR_SEX', False),
'ask_for_street2': getattr(settings, 'SUBSCRIPTION_ADD_STREET2', False), 'ask_for_street2': getattr(settings, 'SUBSCRIPTION_ADD_STREET2', False),
'ask_for_second_phone': getattr(settings, 'SUBSCRIPTION_ADD_SECOND_PHONE', False), 'ask_for_second_phone': getattr(settings, 'SUBSCRIPTION_ADD_SECOND_PHONE', False),
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""commons apps functions .""" """commons apps functions ."""
from django.conf import settings from django.conf import settings
default_input_phone_pattern = "^((\+33(-| )\d{1})|\d{2})(\.| )\d{2}(\.| )\d{2}(\.| )\d{2}(\.| )\d{2}$"
def format_phone_number(phone_string):
"""Format phone number for DB insertion (french format)"""
try:
import re
# keep only figures
figures = re.sub(r'[^0-9]', '', phone_string)
international_prefix = ''
if len(figures) > 10:
international_prefix = figures[:len(figures) -9]
figures = figures[-9:]
# for the moment, international prefix is omitted, since only french format is processed
if len(figures) == 9:
figures = '0' + figures
if len(figures) == 10:
number_pairs = [figures[:2]]
for i in range(1,5):
idx = i*2
number_pairs.append(figures[idx:idx + 2])
phone_pairs_separator = getattr(settings, 'PHONE_PAIRS_SEPARATOR', ' ')
output_phone_number = phone_pairs_separator.join(number_pairs)
else:
output_phone_number = phone_string
except:
output_phone_number = phone_string
return output_phone_number
...@@ -79,3 +79,4 @@ LOGGING = { ...@@ -79,3 +79,4 @@ LOGGING = {
} }
""" """
coop_logger = logging.getLogger("coop.framework") coop_logger = logging.getLogger("coop.framework")
...@@ -106,6 +106,14 @@ ...@@ -106,6 +106,14 @@
Maximum accepted checks numbers Maximum accepted checks numbers
- INPUT_PHONE_PATTERN = "^(0\d{9})$"
Regexp pattern which is used to validate values input in phone fields
Default is "^((\+33(-| )\d{1})|\d{2})(\.| )\d{2}(\.| )\d{2}(\.| )\d{2}(\.| )\d{2}$"
- PHONE_PAIRS_SEPARATOR = "."
Character which by used to separate every 2 phone figures (04.67.23.89.21 for example)
Default is " "
### Scales and labels files generation ### Scales and labels files generation
- DAV_PATH = '/data/dav/cagette' - DAV_PATH = '/data/dav/cagette'
......
...@@ -22,7 +22,7 @@ class CagetteMail: ...@@ -22,7 +22,7 @@ class CagetteMail:
html_message=html_msg) html_message=html_msg)
@staticmethod @staticmethod
def sendCartValidation(email, cart): def sendCartValidation(email, cart, mode="shop"):
"""Used by Shop""" """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
...@@ -43,7 +43,10 @@ class CagetteMail: ...@@ -43,7 +43,10 @@ class CagetteMail:
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, subject_prefix = "Votre commande en ligne à "
if mode == "delivery":
subject_prefix = "Votre demande de livraison à "
send_mail(subject_prefix + settings.COMPANY_NAME,
msg, msg,
settings.DEFAULT_FROM_EMAIL, settings.DEFAULT_FROM_EMAIL,
[email], [email],
......
...@@ -52,6 +52,7 @@ INSTALLED_APPS = ( ...@@ -52,6 +52,7 @@ INSTALLED_APPS = (
'orders', 'orders',
'shop', 'shop',
'shelfs', 'shelfs',
'sales',
# 'tests' # 'tests'
) )
......
...@@ -44,6 +44,7 @@ urlpatterns = [ ...@@ -44,6 +44,7 @@ urlpatterns = [
url(r'^website/', include('website.urls')), url(r'^website/', include('website.urls')),
url(r'^shop/', include('shop.urls')), url(r'^shop/', include('shop.urls')),
url(r'^shelfs/', include('shelfs.urls')), url(r'^shelfs/', include('shelfs.urls')),
url(r'^sales/', include('sales.urls')),
] ]
try: try:
......
...@@ -108,6 +108,16 @@ class CagetteProduct(models.Model): ...@@ -108,6 +108,16 @@ class CagetteProduct(models.Model):
def register_start_supplier_shortage(product_id, partner_id, date_start): def register_start_supplier_shortage(product_id, partner_id, date_start):
"""Start a supplier shortage for a product""" """Start a supplier shortage for a product"""
api = OdooAPI() api = OdooAPI()
c = [['product_id', '=', product_id],
['partner_id', '=', partner_id],
['date_start', '=', date_start]]
existing = api.search_read('product.supplier.shortage', c)
if existing:
res = "already on shortage"
return res
f = { f = {
'product_id' : product_id, 'product_id' : product_id,
'partner_id' : partner_id, 'partner_id' : partner_id,
......
...@@ -32,6 +32,21 @@ IFCBarcodes = { ...@@ -32,6 +32,21 @@ IFCBarcodes = {
display_last_error: function() { display_last_error: function() {
alert(this.errors[this.errors.length - 1]); alert(this.errors[this.errors.length - 1]);
}, },
get_quantity_eq_to_encoded_price: function (value, list_price, currency) {
let qty = 0;
try {
let price = parseFloat(value);
if (currency == 'FF')
price = price / 6.55957;
qty = parseFloat(price / list_price).toFixed(3);
} catch (error) {
console.log(error);
}
return qty;
},
get_corresponding_odoo_product: function(bc) { get_corresponding_odoo_product: function(bc) {
//console.log('To analyze :' + bc) //console.log('To analyze :' + bc)
var index = 0, var index = 0,
...@@ -49,31 +64,45 @@ IFCBarcodes = { ...@@ -49,31 +64,45 @@ IFCBarcodes = {
if (bc.indexOf(significant_prefix) === 0) { if (bc.indexOf(significant_prefix) === 0) {
/* /*
Submitted barcode-code matches a pattern rule
For example, For example,
bc = 0493213018809 bc = 0493213018809
pattern = 0493...{NNDDD} pattern = 0493...{NNDDD}
*/ */
//console.log(pattern)
// console.log(bc)
odoo_bc = ''; odoo_bc = '';
pattern_found = true; pattern_found = true;
pattern_type = this.patterns[index].type; 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
// Read pattern character by character /*
Read pattern character by character
to find out Odoo article barcode
and encoded_value (weight, price, units, if exists)
*/
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])) {
// it's a figure, nothing to do but to add it to string
odoo_bc += pattern[i]; odoo_bc += pattern[i];
} else if (pattern[i].indexOf('.') === 0) { } else if (pattern[i].indexOf('.') === 0) {
/*
it's a substitution character,
so add the submitted barcode figure which is in this position
*/
odoo_bc += bc[i]; odoo_bc += bc[i];
} else if (/[ND]/.exec(pattern[i])) { } else if (/[ND]/.exec(pattern[i])) {
/*
A figure which encoding a value is in this position
(corresponding to a 0 in Odoo article barcode)
*/
odoo_bc += '0'; odoo_bc += '0';
/* let's add a decimal sepator if D is read for the first time */
if (pattern[i] === 'D' && encoded_value.indexOf('.') < 0) if (pattern[i] === 'D' && encoded_value.indexOf('.') < 0)
encoded_value += '.'; encoded_value += '.';
encoded_value += bc[i]; encoded_value += bc[i];
} }
} }
// Add check digit at the end of odoo_bc to find out "normalized" code // Add check digit at the end of odoo_bc to find out "normalized" code
bc = odoo_bc + eanCheckDigit(odoo_bc); bc = odoo_bc + eanCheckDigit(odoo_bc);
} }
...@@ -101,16 +130,35 @@ IFCBarcodes = { ...@@ -101,16 +130,35 @@ IFCBarcodes = {
if (product_data !== null) { if (product_data !== null) {
p_uom = (this.uoms)[product_data[this.keys.uom_id]]; p_uom = (this.uoms)[product_data[this.keys.uom_id]];
let qty = 1;
if (encoded_value.length > 0 && !isNaN(encoded_value)) { if (encoded_value.length > 0 && !isNaN(encoded_value)) {
qty = 0; //if no rule is found it will advise user that there is a problem
/*
Warning :
Tests are dependant on La Cagette / Cooperatic uom system and barcode rules
TODO : Defines them outside of this part of code
*/
if (p_uom == 'Unit(s)' || p_uom == 'unité') { if (p_uom == 'Unit(s)' || p_uom == 'unité') {
encoded_value = parseInt(encoded_value, 10); encoded_value = parseInt(encoded_value, 10);
qty = encoded_value;
} else { } else {
encoded_value = parseFloat(encoded_value); encoded_value = parseFloat(encoded_value);
if (pattern_type == 'weight' || pattern_type == 'FF_price_to_weight' || pattern_type == 'price_to_weight') {
if (pattern_type == 'weight') {
qty = encoded_value;
} else {
let list_price = product_data[this.keys.list_price];
let currency = null;
if (pattern_type == 'FF_price_to_weight') currency = 'FF'
qty = parseFloat(this.get_quantity_eq_to_encoded_price(encoded_value, list_price, currency));
}
}
} }
} }
odoo_product = {barcode: bc, data: product_data, rule: pattern_type, value: encoded_value}; odoo_product = {barcode: bc, data: product_data, rule: pattern_type, value: encoded_value, qty: qty};
} }
return odoo_product; return odoo_product;
......
...@@ -209,7 +209,7 @@ function validatePrices() { ...@@ -209,7 +209,7 @@ function validatePrices() {
traditional: true, traditional: true,
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
data: JSON.stringify(update_data), data: JSON.stringify(update_data),
success: function(data) { success: function() {
localStorage.removeItem("order_" + order["id"]); localStorage.removeItem("order_" + order["id"]);
callback_update = true; callback_update = true;
reload(); reload();
...@@ -238,7 +238,7 @@ function validatePrices() { ...@@ -238,7 +238,7 @@ function validatePrices() {
traditional: true, traditional: true,
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
data: JSON.stringify(updates), data: JSON.stringify(updates),
success: function(data) { success: function() {
callback_report = true; callback_report = true;
reload(); reload();
}, },
...@@ -275,7 +275,7 @@ function group_action() { ...@@ -275,7 +275,7 @@ function group_action() {
traditional: true, traditional: true,
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
data: JSON.stringify(group_ids), data: JSON.stringify(group_ids),
success: function(data) { success: function() {
var min_id = 9999999; var min_id = 9999999;
for (var i = 0; i < selected_data.length; i++) { for (var i = 0; i < selected_data.length; i++) {
...@@ -303,7 +303,7 @@ function group_action() { ...@@ -303,7 +303,7 @@ function group_action() {
localStorage.setItem('grouped_orders', JSON.stringify(grouped_orders)); localStorage.setItem('grouped_orders', JSON.stringify(grouped_orders));
// Go to products page of order with smallest id // Go to products page of order with smallest id
// goto(min_id); goto(min_id);
}, },
error: function(data) { error: function(data) {
if (data != null && data.status == 409) { if (data != null && data.status == 409) {
...@@ -334,8 +334,6 @@ $(document).ready(function() { ...@@ -334,8 +334,6 @@ $(document).ready(function() {
// Set date format for DataTable so date ordering can work // Set date format for DataTable so date ordering can work
$.fn.dataTable.moment('D/M/Y'); $.fn.dataTable.moment('D/M/Y');
var saved_grouped_orders = JSON.parse(localStorage.getItem('grouped_orders'));
table_orders = $('#orders').DataTable({ table_orders = $('#orders').DataTable({
ajax: "get_list_orders", ajax: "get_list_orders",
columns:[ columns:[
...@@ -343,7 +341,7 @@ $(document).ready(function() { ...@@ -343,7 +341,7 @@ $(document).ready(function() {
data:"id", data:"id",
title:"Sélectionner", title:"Sélectionner",
className:"dt-body-center", className:"dt-body-center",
render: function (data, type, full, meta) { render: function (data) {
return '<input type="checkbox" id="select_bc_'+data+'" value="'+data+'">'; return '<input type="checkbox" id="select_bc_'+data+'" value="'+data+'">';
}, },
width: "4%", width: "4%",
...@@ -353,7 +351,7 @@ $(document).ready(function() { ...@@ -353,7 +351,7 @@ $(document).ready(function() {
{ {
data:"partner", data:"partner",
title:"Fournisseur", title:"Fournisseur",
render: function (data, type, full, meta) { render: function (data, type, full) {
// Add tooltip with PO over partner name // Add tooltip with PO over partner name
return '<div class="tooltip">' + data + ' <span class="tooltiptext">' + full.name + '</span> </div>'; return '<div class="tooltip">' + data + ' <span class="tooltiptext">' + full.name + '</span> </div>';
} }
...@@ -361,7 +359,7 @@ $(document).ready(function() { ...@@ -361,7 +359,7 @@ $(document).ready(function() {
{ {
data:"reception_status", data:"reception_status",
className:"dt-body-center", className:"dt-body-center",
render: function (data, type, full, meta) { render: function (data) {
if (data == "qty_valid") { if (data == "qty_valid") {
return "<span class='btn--danger'>Pas de prix sur le bon de livraison</span>"; return "<span class='btn--danger'>Pas de prix sur le bon de livraison</span>";
} else { } else {
...@@ -375,7 +373,7 @@ $(document).ready(function() { ...@@ -375,7 +373,7 @@ $(document).ready(function() {
data:"reception_status", data:"reception_status",
title:"Statut", title:"Statut",
className:"dt-body-center", className:"dt-body-center",
render: function (data, type, full, meta) { render: function (data) {
switch (data) { switch (data) {
case 'qty_valid': case 'qty_valid':
return "<span class='btn--success'>Mettre à jour les prix</span>"; return "<span class='btn--success'>Mettre à jour les prix</span>";
...@@ -397,7 +395,7 @@ $(document).ready(function() { ...@@ -397,7 +395,7 @@ $(document).ready(function() {
], ],
iDisplayLength: 25, iDisplayLength: 25,
language: {url : '/static/js/datatables/french.json'}, language: {url : '/static/js/datatables/french.json'},
initComplete: function(settings, json) { // After data is loaded initComplete: function() { // After data is loaded
clean_local_storage(); clean_local_storage();
create_groups_from_server_data(); create_groups_from_server_data();
extract_grouped_orders(); extract_grouped_orders();
...@@ -460,9 +458,7 @@ $(document).ready(function() { ...@@ -460,9 +458,7 @@ $(document).ready(function() {
selection_type = null; selection_type = null;
document.getElementById("group_action").hidden = true; document.getElementById("group_action").hidden = true;
} }
} } else if (this.cellIndex == 4) { // Click on last cell button -> go to products page
// Click on last cell button -> go to products page
else if (this.cellIndex == 4) {
// Extra security if order with a different status gets lost in here // Extra security if order with a different status gets lost in here
if (row_data.reception_status == "qty_valid" || row_data.reception_status == "False") { if (row_data.reception_status == "qty_valid" || row_data.reception_status == "False") {
// Use local storage to pass order data to next page // Use local storage to pass order data to next page
...@@ -470,9 +466,8 @@ $(document).ready(function() { ...@@ -470,9 +466,8 @@ $(document).ready(function() {
goto(row_data.id); goto(row_data.id);
} }
} } else if (this.cellIndex == 3 && row_data.reception_status == "qty_valid") {
// If 'update prices' step, click on before-last cell -> validate all prices // If 'update prices' step, click on before-last cell -> validate all prices
else if (this.cellIndex == 3 && row_data.reception_status == "qty_valid") {
order = row_data; order = row_data;
openModal($('#modal_no_prices').html(), validatePrices, 'Confirmer', false); openModal($('#modal_no_prices').html(), validatePrices, 'Confirmer', false);
} }
......
...@@ -18,13 +18,13 @@ var orders = {}, ...@@ -18,13 +18,13 @@ var orders = {},
is_group = false, is_group = false,
group_ids = []; group_ids = [];
var reception_status, var reception_status = null,
list_to_process = [], list_to_process = [],
list_processed = [], list_processed = [],
table_to_process, table_to_process = null,
table_processed, table_processed = null,
editing_product = null, // Store the product currently being edited editing_product = null, // Store the product currently being edited
editing_origin, // Keep track of where editing_product comes from editing_origin = null, // Keep track of where editing_product comes from
processed_row_counter = 0, // Order in which products were added in processed list processed_row_counter = 0, // Order in which products were added in processed list
user_comments = "", user_comments = "",
updatedProducts = [], // Keep record of updated products updatedProducts = [], // Keep record of updated products
...@@ -108,6 +108,8 @@ function select_product_from_bc(barcode) { ...@@ -108,6 +108,8 @@ function select_product_from_bc(barcode) {
console.error(err); console.error(err);
report_JS_error(err, 'reception'); report_JS_error(err, 'reception');
} }
return 0;
} }
/* INIT */ /* INIT */
...@@ -231,7 +233,7 @@ function initLists() { ...@@ -231,7 +233,7 @@ function initLists() {
data:"product_id.1", data:"product_id.1",
title:"Produit", title:"Produit",
width: "45%", width: "45%",
render: function (data, type, full, meta) { render: function (data, type, full) {
// Add tooltip with barcode over product name // Add tooltip with barcode over product name
let display_barcode = "Aucun"; let display_barcode = "Aucun";
...@@ -300,7 +302,7 @@ function initLists() { ...@@ -300,7 +302,7 @@ function initLists() {
data:"product_id.1", data:"product_id.1",
title:"Produit", title:"Produit",
width: "55%", width: "55%",
render: function (data, type, full, meta) { render: function (data, type, full) {
// Add tooltip with barcode over product name // Add tooltip with barcode over product name
let display_barcode = "Aucun"; let display_barcode = "Aucun";
...@@ -344,7 +346,7 @@ function initLists() { ...@@ -344,7 +346,7 @@ function initLists() {
title:"Autres", title:"Autres",
className:"dt-body-center", className:"dt-body-center",
orderable: false, orderable: false,
render: function (data, type, full, meta) { render: function (data, type, full) {
let disabled = (full.supplier_shortage) ? "disabled" : ''; let disabled = (full.supplier_shortage) ? "disabled" : '';
return "<select class='select_product_action'>" return "<select class='select_product_action'>"
...@@ -889,7 +891,7 @@ function editProductInfo (productToEdit, value = null) { ...@@ -889,7 +891,7 @@ function editProductInfo (productToEdit, value = null) {
} }
// Validate product edition // Validate product edition
function validateEdition(form) { function validateEdition(form = null) {
if (editing_product != null) { if (editing_product != null) {
if (editProductInfo(editing_product)) { if (editProductInfo(editing_product)) {
clearLineEdition(); clearLineEdition();
...@@ -900,10 +902,12 @@ function validateEdition(form) { ...@@ -900,10 +902,12 @@ function validateEdition(form) {
// Set the quantity to 0 for all the products in to_process // Set the quantity to 0 for all the products in to_process
function setAllQties() { function setAllQties() {
// Iterate over all rows in to_process // Iterate over all rows in to_process
table_to_process.rows().every(function (rowIdx, tableLoop, rowLoop) { table_to_process.rows().every(function () {
var data = this.data(); var data = this.data();
editProductInfo(data, 0); editProductInfo(data, 0);
return true;
}); });
list_to_process = []; list_to_process = [];
table_to_process.rows().remove() table_to_process.rows().remove()
...@@ -1128,7 +1132,9 @@ function send() { ...@@ -1128,7 +1132,9 @@ function send() {
traditional: true, traditional: true,
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
data: JSON.stringify(update_data), data: JSON.stringify(update_data),
success: function(data) { success: function() {
closeModal();
try { try {
// If step 1 (counting), open pop-up with procedure explanation // If step 1 (counting), open pop-up with procedure explanation
if (reception_status == "False") { if (reception_status == "False") {
...@@ -1213,11 +1219,11 @@ function send() { ...@@ -1213,11 +1219,11 @@ function send() {
} }
// Go back to to_process list if modal closed // Go back to to_process list if modal closed
$('#modal_closebtn_top').on('click', function (e) { $('#modal_closebtn_top').on('click', function () {
document.location.href = "/reception"; document.location.href = "/reception";
}); });
$('#modal_closebtn_bottom').on('click', function (e) { $('#modal_closebtn_bottom').on('click', function () {
document.location.href = "/reception"; document.location.href = "/reception";
}); });
...@@ -1303,7 +1309,7 @@ function confirmPricesAllValid() { ...@@ -1303,7 +1309,7 @@ function confirmPricesAllValid() {
function confirm_all_left_is_good() { function confirm_all_left_is_good() {
// all products left are to be considered as well filled // all products left are to be considered as well filled
// Iterate over all rows in to_process // Iterate over all rows in to_process
table_to_process.rows().every(function (rowIdx, tableLoop, rowLoop) { table_to_process.rows().every(function () {
let data = this.data(); let data = this.data();
var value = null; var value = null;
...@@ -1313,6 +1319,8 @@ function confirm_all_left_is_good() { ...@@ -1313,6 +1319,8 @@ function confirm_all_left_is_good() {
value = data.price_unit; value = data.price_unit;
} }
editProductInfo(data, value); editProductInfo(data, value);
return true;
}); });
list_to_process = []; list_to_process = [];
table_to_process.rows().remove() table_to_process.rows().remove()
...@@ -1560,12 +1568,12 @@ $(document).ready(function() { ...@@ -1560,12 +1568,12 @@ $(document).ready(function() {
container_edition.addEventListener('animationend', onAnimationEnd); container_edition.addEventListener('animationend', onAnimationEnd);
container_edition.addEventListener('webkitAnimationEnd', onAnimationEnd); container_edition.addEventListener('webkitAnimationEnd', onAnimationEnd);
function onAnimationEnd(e) { function onAnimationEnd() {
container_edition.classList.remove('blink_me'); container_edition.classList.remove('blink_me');
} }
// Disable mousewheel on an input number field when in focus // Disable mousewheel on an input number field when in focus
$('#edition_input').on('focus', function (e) { $('#edition_input').on('focus', function () {
$(this).on('wheel.disableScroll', function (e) { $(this).on('wheel.disableScroll', function (e) {
e.preventDefault(); e.preventDefault();
/* /*
...@@ -1580,7 +1588,7 @@ $(document).ready(function() { ...@@ -1580,7 +1588,7 @@ $(document).ready(function() {
*/ */
}); });
}) })
.on('blur', function (e) { .on('blur', function () {
$(this).off('wheel.disableScroll'); $(this).off('wheel.disableScroll');
}); });
...@@ -1635,6 +1643,13 @@ $(document).ready(function() { ...@@ -1635,6 +1643,13 @@ $(document).ready(function() {
} }
}); });
$("#edition_input").keypress(function(event) {
// Force validation when enter pressed in edition
if (event.keyCode == 13 || event.which == 13) {
validateEdition();
}
});
// Barcode reader // Barcode reader
$(document).pos(); $(document).pos();
$(document).on('scan.pos.barcode', function(event) { $(document).on('scan.pos.barcode', function(event) {
......
from django.contrib import admin
# Register your models here.
from django.apps import AppConfig
class SalesConfig(AppConfig):
name = 'sales'
from django.db import models
from outils.common_imports import *
from outils.common import OdooAPI
class CagetteSales(models.Model):
"""Class to manage operations on envelops"""
def __init__(self):
"""Init with odoo id."""
self.o_api = OdooAPI()
def get_sales(self, date_from, date_to):
res = []
# Get pos sessions
cond = [['stop_at', '>=', date_from], ['stop_at', '<=', date_to], ['state', '=', "closed"]]
fields = []
sessions = self.o_api.search_read('pos.session', cond, fields)
# Get bank statements of these sessions
statements = []
for s in sessions:
statements = statements + s["statement_ids"]
# Get payment lines
cond = [['statement_id', 'in', statements]]
fields = ["partner_id", "amount", "journal_id", "create_date", "date"]
payments = self.o_api.search_read('account.bank.statement.line', cond, fields, order="create_date ASC")
item = None
try:
for payment in payments:
if item is not None and item["partner_id"][0] == payment["partner_id"][0] and item["date"] == payment["date"]:
res[len(res)-1]["total_amount"] += round(float(payment["amount"]), 2)
res[len(res)-1]["payments"].append({
"amount": round(float(payment["amount"]), 2),
"journal_id": payment["journal_id"]
})
else:
item = {
"partner_id": payment["partner_id"],
"create_date": payment["create_date"],
"date": payment["date"],
"total_amount": round(float(payment["amount"]), 2),
"payments": [
{
"amount": round(float(payment["amount"]), 2),
"journal_id": payment["journal_id"]
}
]
}
res.append(item)
except Exception as e:
pass
return res
.select_sales_dates {
margin-top: 25px;
}
.sales_date {
display: inline-block;
margin-right: 10px;
}
.main {
margin-top: 25px;
}
.table_area {
margin: auto;
width: 90%;
padding: 10px;
}
.select_sales_date_input {
border-radius:5px;
}
\ No newline at end of file
var dateFormat = "yy-mm-dd",
from_datepicker = null,
to_datepicker = null,
orders_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 (getDate(from_datepicker.val()) &&
getDate(to_datepicker.val())) {
$('#dates_selection_button').prop('disabled', false);
} else {
$('#dates_selection_button').prop('disabled', true);
}
}
function display_orders(orders) {
// Empty datatable if already exists
if (orders_table) {
orders_table.destroy();
}
console.log(orders);
orders_table = $('#orders_table').DataTable({
data: orders,
columns:[
{
data:"create_date",
title:"Date",
width: "10%"
},
{
data:"partner_id",
title:"Membre",
width: "50%",
render: function (data) {
return data[1];
}
},
{
data:"total_amount",
title: "Montant du panier",
className:"dt-body-center",
render: function (data) {
return parseFloat(data).toFixed(2) + ' €';
}
},
{
data:"payments",
title:"Paiements",
className:"dt-body-center",
orderable: false,
render: function (data) {
let res = '<ul>';
for (p of data) {
res += `<li>${p.journal_id[1]} : ${p.amount} €</li>`
}
res += "</ul>"
return res;
}
}
],
order: [
[
0,
"asc"
]
],
dom: 'rtip',
iDisplayLength: 25,
language: {url : '/static/js/datatables/french.json'}
});
$('.main').show();
}
function get_sales() {
openModal();
var url = "/sales/get_sales";
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_orders(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_sales_date_input').change(function() {
enable_validation();
});
$( "#sales_form" ).submit(function( event ) {
event.preventDefault();
get_sales();
});
// $('#dates_selection_button').click(function() {
// get_sales();
// });
});
"""."""
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index),
url(r'^get_sales$', views.get_sales),
]
from outils.common_imports import *
from outils.for_view_imports import *
from sales.models import CagetteSales
def index(request):
"""Display sales export screen"""
context = {'title': 'Export de ventes'}
template = loader.get_template('sales/index.html')
# m = CagetteSales()
# sales = m.get_sales()
# print(sales)
return HttpResponse(template.render(context, request))
def get_sales(request):
res = {}
date_from = request.GET.get('from', '')
date_to = request.GET.get('to', '')
m = CagetteSales()
res = m.get_sales(date_from, date_to)
if 'errors' in res and res['errors']:
return JsonResponse(res, status=500)
else:
return JsonResponse({'res': res})
\ No newline at end of file
...@@ -47,7 +47,11 @@ def get_all_children(branch): ...@@ -47,7 +47,11 @@ def get_all_children(branch):
children += get_all_children(c) children += get_all_children(c)
return children return children
def get_all_children_ids(branch):
ids = []
for c in get_all_children(branch):
ids.append(c['id'])
return ids
class CagetteShop(models.Model): class CagetteShop(models.Model):
"""Class to handle cagette Shop.""" """Class to handle cagette Shop."""
...@@ -62,7 +66,7 @@ class CagetteShop(models.Model): ...@@ -62,7 +66,7 @@ class CagetteShop(models.Model):
def filter_products_according_settings(pdts): def filter_products_according_settings(pdts):
res = pdts res = pdts
try: try:
conditions = settings.SHOP_LIMIT_PRODUCTS conditions = getattr(settings, 'SHOP_LIMIT_PRODUCTS', [])
filtered = [] filtered = []
for p in pdts: for p in pdts:
keep_it = True keep_it = True
...@@ -124,13 +128,14 @@ class CagetteShop(models.Model): ...@@ -124,13 +128,14 @@ class CagetteShop(models.Model):
fields = ['parent_id', 'name'] fields = ['parent_id', 'name']
res = api.search_read('product.category', [], fields) res = api.search_read('product.category', [], fields)
tree = build_tree_from_categories(res) tree = build_tree_from_categories(res)
except: except Exception as e:
pass coop_logger.error('get_product_categories : %s', str(e))
return tree return tree
@staticmethod @staticmethod
def get_cat_children_ids(categ_id): def get_cat_children_ids(categ_id):
cat_ids = [categ_id] cat_ids = [categ_id]
tree = CagetteShop.get_product_categories() tree = CagetteShop.get_product_categories()
branch = None branch = None
for cats in tree: for cats in tree:
...@@ -159,15 +164,23 @@ class CagetteShop(models.Model): ...@@ -159,15 +164,23 @@ class CagetteShop(models.Model):
return children return children
@staticmethod @staticmethod
def get_categories_nb_of_products():
"""Needs lacagette_categories Odoo module to be activated"""
res = {}
try:
api = OdooAPI()
res = api.execute('lacagette.categories', 'get_all_with_products_count', {})
except Exception as e:
coop_logger.error('get_categories_nb_of_products %s', str(e))
res['error'] = str(e)
return res
@staticmethod
def get_category_products(categ_id): def get_category_products(categ_id):
res = {} res = {}
try: try:
pdts = [] pdts = []
limit_conditions = [] limit_conditions = getattr(settings, 'SHOP_LIMIT_PRODUCTS', [])
try:
limit_conditions = settings.SHOP_LIMIT_PRODUCTS
except:
pass
api = OdooAPI() api = OdooAPI()
cat_ids = CagetteShop.get_cat_children_ids(categ_id) cat_ids = CagetteShop.get_cat_children_ids(categ_id)
# removed ['qty_available', '>', 0] # removed ['qty_available', '>', 0]
...@@ -183,6 +196,7 @@ class CagetteShop(models.Model): ...@@ -183,6 +196,7 @@ class CagetteShop(models.Model):
res['pdts'] = CagetteShop.filter_products_according_settings(pdts) res['pdts'] = CagetteShop.filter_products_according_settings(pdts)
except Exception as e: except Exception as e:
coop_logger.error('get_category_products %s %s', categ_id, str(e))
res['error'] = str(e) res['error'] = str(e)
return res return res
...@@ -239,7 +253,7 @@ class CagetteShop(models.Model): ...@@ -239,7 +253,7 @@ class CagetteShop(models.Model):
@staticmethod @staticmethod
def registrerCart(cart, partner_id): def registrerCart(cart, partner_id, mode="shop"):
result = {} result = {}
try: try:
cart['submitted_time'] = time.time() cart['submitted_time'] = time.time()
...@@ -260,7 +274,7 @@ class CagetteShop(models.Model): ...@@ -260,7 +274,7 @@ class CagetteShop(models.Model):
if result: if result:
try: try:
from outils.mail import CagetteMail from outils.mail import CagetteMail
CagetteMail.sendCartValidation(partner['email'], cart) CagetteMail.sendCartValidation(partner['email'], cart, mode)
except Exception as e: except Exception as e:
coop_logger.error("Shop, registrerCart : %s, %s", str(e), str(cart)) coop_logger.error("Shop, registrerCart : %s, %s", str(e), str(cart))
except Exception as e: except Exception as e:
......
...@@ -252,7 +252,10 @@ li.tab { border-right: 1px solid white; } ...@@ -252,7 +252,10 @@ li.tab { border-right: 1px solid white; }
#my-orders-sumup .date {text-align: left;padding: 0 4px;} #my-orders-sumup .date {text-align: left;padding: 0 4px;}
#my-orders-sumup tbody tr:hover {background-color: #b3b7c4;} #my-orders-sumup tbody tr:hover {background-color: #b3b7c4;}
td.actions .fa-trash, td.actions .fa-paper-plane, td.date .fa-edit {cursor: pointer;} td.actions .fa-trash,
td.actions .fa-eye,
td.actions .fa-paper-plane,
td.date .fa-edit {cursor: pointer;}
.no-action-available-msg {margin-top: 15px;} .no-action-available-msg {margin-top: 15px;}
#survey_link {text-decoration: none;} #survey_link {text-decoration: none;}
......
...@@ -26,6 +26,7 @@ var main_content = $('#main-content'), ...@@ -26,6 +26,7 @@ var main_content = $('#main-content'),
dragSrcEl = null, dragSrcEl = null,
forbidden_slots = [], forbidden_slots = [],
closing_dates = [], closing_dates = [],
my_sent_orders = [],
right_column = $('#right-column'), right_column = $('#right-column'),
visit_mode = false, visit_mode = false,
timer = null; timer = null;
...@@ -717,7 +718,7 @@ var addProductToCart = function() { ...@@ -717,7 +718,7 @@ var addProductToCart = function() {
var msg = ""; var msg = "";
var too_much = "Vous avez pris plus de produit que le stock indicatif.\nVous n'aurez peut-être pas toute la quantité."; var too_much = "Vous avez pris plus de produit que le stock indicatif.\nVous n'aurez peut-être pas toute la quantité.";
if (parseFloat(qty) > available_qty) { if (parseFloat(qty) > available_qty && stock_warning == true) {
msg = too_much; msg = too_much;
} }
var u = p_div.find('.unit').text() var u = p_div.find('.unit').text()
...@@ -757,7 +758,7 @@ var addProductToCart = function() { ...@@ -757,7 +758,7 @@ var addProductToCart = function() {
} }
} }
if (typeof answer.warning !== "undefined") { if (typeof answer.warning !== "undefined") {
if (answer.warning == "max_qty") if (answer.warning == "max_qty" && stock_warning == true)
msg = too_much; msg = too_much;
} }
}); });
...@@ -1059,11 +1060,28 @@ var loadAllAvailableBoughtProducts = function() { ...@@ -1059,11 +1060,28 @@ var loadAllAvailableBoughtProducts = function() {
} }
}; };
var shouldCategoryBeShown = function (cat_id) {
let answer = true;
if (excluded_cat.indexOf(cat_id) > -1) {
answer = false;
}
if (typeof cat_nb_pdts != "undefined") {
let list = cat_nb_pdts.list;
let cat_ids = Object.keys(list).map(x => parseInt(x,10));
//cat_ids is now an array of category ids which have product
if (cat_ids.indexOf(cat_id) < 0) {
// cat_id is corresponding to a category which have no product
answer = false;
}
}
return answer;
}
var appendChildrenCatToMenu = function (catdiv, children) { var appendChildrenCatToMenu = function (catdiv, 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 (shouldCategoryBeShown(e.id)) {
var li = $('<li>').addClass("nav-item"); var li = $('<li>').addClass("nav-item");
// Remove TVA in cat name // Remove TVA in cat name
...@@ -1092,7 +1110,6 @@ var getCategChildren = function() { ...@@ -1092,7 +1110,6 @@ var getCategChildren = function() {
if (typeof category_elts[cat_id] == "undefined") { if (typeof category_elts[cat_id] == "undefined") {
try { try {
$.ajax({ $.ajax({
//url :'/shop/get_categ_products',
url : '/shop/get_cat_children', url : '/shop/get_cat_children',
data: {id: cat_id}, data: {id: cat_id},
dataType: 'json' dataType: 'json'
...@@ -1236,12 +1253,13 @@ var displaySentOrders = function() { ...@@ -1236,12 +1253,13 @@ var displaySentOrders = function() {
} }
} else if (typeof rData.res.data.orders != "undefined") { } else if (typeof rData.res.data.orders != "undefined") {
if (rData.res.data.orders.length > 0) { if (rData.res.data.orders.length > 0) {
my_sent_orders = rData.res.data.orders;
var eye = '<i class="fas fa-eye fl"></i>'; var eye = '<i class="fas fa-eye fl"></i>';
var delete_icon = '<i class="fas fa-trash fr"></i>'; var delete_icon = '<i class="fas fa-trash fr"></i>';
var edit = '<i class="fas fa-edit"></i>'; var edit = '<i class="fas fa-edit"></i>';
var show_no_action_available_msg = false; var show_no_action_available_msg = false;
$.each(rData.res.data.orders, function(i, o) { $.each(my_sent_orders, function(i, o) {
var bdate_content = "<span>" + o.best_date + "</span>"; var bdate_content = "<span>" + o.best_date + "</span>";
if (o.state == "init" || o.state == "validating") bdate_content += " " + edit; if (o.state == "init" || o.state == "validating") bdate_content += " " + edit;
...@@ -1267,7 +1285,8 @@ var displaySentOrders = function() { ...@@ -1267,7 +1285,8 @@ var displaySentOrders = function() {
.text(o.products.length); .text(o.products.length);
var td4 = $('<td>').addClass('amount') var td4 = $('<td>').addClass('amount')
.text(parseFloat(o.total).toFixed(2)); .text(parseFloat(o.total).toFixed(2));
//var td5 = $('<td>').addClass('actions').html(eye + ' ' + delete_icon)
actions_content = eye + ' ' + actions_content;
var td5 = $('<td>').addClass('actions') var td5 = $('<td>').addClass('actions')
.html(actions_content); .html(actions_content);
...@@ -1374,6 +1393,36 @@ var changeBestDate = function() { ...@@ -1374,6 +1393,36 @@ var changeBestDate = function() {
}; };
var showSentCart = function() {
let clicked = $(this),
clicked_tr = clicked.closest('tr'),
id = clicked_tr.data('id'),
content = $('<div>'),
table = $('<table>');
let header = $('<tr><th>Article</th><th>Qté</th><th>Prix Total (T.T.C)</th></tr>');
let bottom_msg = $('<p>').html("<em>Contenu non modifiable.</em>")
table.append(header);
$.each(my_sent_orders, function(i,e) {
if (e._id == id) {
$.each(e.products, function (j,p) {
let tr = $('<tr>'),
name = $('<td>').text(p.name),
qty = $('<td>').text(p.qty),
total = $('<td>').text(p.total)
tr.append(name);
tr.append(qty);
tr.append(total);
table.append(tr);
})
}
})
content.append(table);
content.append(bottom_msg);
displayMsg(content.html());
}
var destroySentCart = function() { var destroySentCart = function() {
var clicked = $(this); var clicked = $(this);
var clicked_tr = clicked.closest('tr'), var clicked_tr = clicked.closest('tr'),
...@@ -1561,15 +1610,19 @@ valid_cart.click(function() { ...@@ -1561,15 +1610,19 @@ valid_cart.click(function() {
$('#get_my_bought_products').click(loadAllAvailableBoughtProducts); $('#get_my_bought_products').click(loadAllAvailableBoughtProducts);
$(document).on('change', '[name^="bday"]', filterHourOptions);
$(document).on('change', '[name="bhour"]', adaptTimeGivenForValidationMsg); $(document).on('change', '[name="bhour"]', adaptTimeGivenForValidationMsg);
$(document).on('click', '#alim_categ > div, #non_alim_categ > div', getCategChildren); $(document).on('click', '#alim_categ > div, #non_alim_categ > div', getCategChildren);
$(document).on('click', '#alim_categ ul li span, #non_alim_categ ul li span', getCategProducts); $(document).on('click', '#alim_categ ul li span, #non_alim_categ ul li span', getCategProducts);
$(document).on('click', '.product button', addProductToCart); $(document).on('click', '.product button', addProductToCart);
$(document).on('click', '.forbidden-slots .fs-close', closeForbiddenList); $(document).on('click', '.forbidden-slots .fs-close', closeForbiddenList);
$(document).on('click', 'td.date .fa-edit', changeBestDate); $(document).on('click', 'td.date .fa-edit', changeBestDate);
$(document).on('click', 'td.actions .fa-eye', showSentCart);
$(document).on('click', 'td.actions .fa-trash', destroySentCart); $(document).on('click', 'td.actions .fa-trash', destroySentCart);
if (shop_mode == 'shop')
$(document).on('change', '[name^="bday"]', filterHourOptions);
$(document).on( $(document).on(
'click', '.new-order', 'click', '.new-order',
function() { function() {
......
...@@ -8,49 +8,28 @@ from shop.models import CagetteShop ...@@ -8,49 +8,28 @@ from shop.models import CagetteShop
@never_cache @never_cache
def shop_index(request): def shop_index(request):
return index(request, mode='shop') return index(request, mode='shop')
@never_cache @never_cache
def delivery_index(request): def delivery_index(request):
return index(request, mode='delivery') return index(request, mode='delivery')
def index(request, mode="shop"): def _get_index_context(credentials, shop_settings, mode):
template = loader.get_template('shop/index.html')
credentials = CagetteMember.get_credentials(request)
shop_settings = CagetteShop.get_shop_settings()
context = {'title': 'Commande / Réservation', context = {'title': 'Commande / Réservation',
'mode': mode, 'mode': mode,
'COMPANY_NAME': settings.COMPANY_NAME, 'COMPANY_NAME': settings.COMPANY_NAME,
'SHOP_CATEGORIES': settings.SHOP_CATEGORIES, 'header_img': getattr(settings, 'SHOP_HEADER_IMG', '/static/img/header.jpg')
'EXCLUDE_SHOP_CATEGORIES': settings.EXCLUDE_SHOP_CATEGORIES, }
'MIN_DELAY_FOR_SLOT': settings.MIN_DELAY_FOR_SLOT,
'HOURS_FOR_VALIDATION': settings.HOURS_FOR_VALIDATION_SHOP}
if 'capital_message' in shop_settings: if 'capital_message' in shop_settings:
context['capital_message'] = shop_settings['capital_message'] context['capital_message'] = shop_settings['capital_message']
allowed_states = ["up_to_date", "alert", "delay"]
# Uncomment if 'coop_state' in credentials .... etc
# to prevent other states people to use the shop
allowed = True
if ('failure' in credentials): if ('failure' in credentials):
# Visitor has not been identified
template = loader.get_template('website/connect.html')
context['msg'] = '' context['msg'] = ''
if 'msg' in credentials: if 'msg' in credentials:
context['msg'] = credentials['msg'] context['msg'] = credentials['msg']
context['password_placeholder'] = 'Mot de passe' context['password_placeholder'] = 'Mot de passe'
context['password_notice'] = "Par défaut, la date de naissance (jjmmaaaa)" context['password_notice'] = "Par défaut, la date de naissance (jjmmaaaa)"
context['with_shop_header'] = True context['with_shop_header'] = True
try:
context['header_img'] = settings.SHOP_HEADER_IMG
except:
context['header_img'] = '/static/img/header.jpg'
else: else:
if hasattr(settings, 'SHOP_OPENING'):
context['SHOP_OPENING'] = settings.SHOP_OPENING
if hasattr(settings, 'SHOP_SLOT_SIZE'):
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 mode == 'shop' and 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 context['DELIVERY_CAN_BUY'] = False
...@@ -58,22 +37,53 @@ def index(request, mode="shop"): ...@@ -58,22 +37,53 @@ def index(request, mode="shop"):
context['SHOP_CAN_BUY'] = False context['SHOP_CAN_BUY'] = False
context['DELIVERY_CAN_BUY'] = settings.DELIVERY_CAN_BUY context['DELIVERY_CAN_BUY'] = settings.DELIVERY_CAN_BUY
context['SHOP_CATEGORIES'] = getattr(settings, 'SHOP_CATEGORIES', [])
context['EXCLUDE_SHOP_CATEGORIES'] = getattr(settings, 'EXCLUDE_SHOP_CATEGORIES', [])
context['MIN_DELAY_FOR_SLOT'] = getattr(settings, 'MIN_DELAY_FOR_SLOT', 30)
context['HOURS_FOR_VALIDATION'] = getattr(settings, 'HOURS_FOR_VALIDATION_SHOP', 2)
context['SHOP_OPENING'] = getattr(settings, 'SHOP_OPENING', {})
context['SHOP_SLOT_SIZE'] = getattr(settings, 'SHOP_SLOT_SIZE', 15)
context['SHOP_OPENING_START_DATE'] = getattr(settings, 'SHOP_OPENING_START_DATE', None)
context['survey_link'] = getattr(settings, 'SHOP_SURVEY_LINK', '')
context['extra_menus'] = getattr(settings, 'SHOP_EXTRA_MENUS', None)
context['SHOW_SUBSTITUTION_OPTION'] = getattr(settings, 'SHOW_SUBSTITUTION_OPTION', False)
context['CART_VALIDATION_BOTTOM_MSG'] = getattr(settings, 'CART_VALIDATION_BOTTOM_MSG', "")
context['SHOP_BOTTOM_VALIDATION_MSG'] = getattr(settings, 'SHOP_BOTTOM_VALIDATION_MSG',\
"Si vous arrivez avec un retard de plus d'une heure, la commande pourrait ne plus être disponible.")
stock_warning = getattr(settings, 'SHOP_STOCK_WARNING', True)
if stock_warning is True:
context['SHOP_STOCK_WARNING'] = 'true'
else:
context['SHOP_STOCK_WARNING'] = 'false'
return context
def index(request, mode="shop"):
template = loader.get_template('shop/index.html')
credentials = CagetteMember.get_credentials(request)
shop_settings = CagetteShop.get_shop_settings()
allowed_states = ["up_to_date", "alert", "delay"]
# Uncomment if 'coop_state' in credentials .... etc
# to prevent other states people to use the shop
allowed = True
context = _get_index_context(credentials, shop_settings, mode)
if ('failure' in credentials):
# Visitor has not been identified
template = loader.get_template('website/connect.html')
else:
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']
context['promoted_pdts'] = d_p_pdts['promoted'] context['promoted_pdts'] = d_p_pdts['promoted']
context['survey_link'] = '' cat_nb_pdts = CagetteShop.get_categories_nb_of_products()
if 'error' in cat_nb_pdts:
if hasattr(settings, 'SHOP_EXTRA_MENUS'): context['cat_nb_pdts'] = None
context['extra_menus'] = settings.SHOP_EXTRA_MENUS else:
if hasattr(settings, 'SHOP_SURVEY_LINK'): context['cat_nb_pdts'] = cat_nb_pdts
context['survey_link'] = settings.SHOP_SURVEY_LINK
context['SHOW_SUBSTITUTION_OPTION'] = True
if hasattr(settings, 'SHOW_SUBSTITUTION_OPTION'):
if settings.SHOW_SUBSTITUTION_OPTION is False:
del context['SHOW_SUBSTITUTION_OPTION']
if hasattr(settings, 'CART_VALIDATION_BOTTOM_MSG'):
context['CART_VALIDATION_BOTTOM_MSG'] = settings.CART_VALIDATION_BOTTOM_MSG
# if 'coop_state' in credentials and not (credentials['coop_state'] in allowed_states): # if 'coop_state' in credentials and not (credentials['coop_state'] in allowed_states):
# allowed = False # allowed = False
...@@ -119,6 +129,7 @@ def get_categ_products(request): ...@@ -119,6 +129,7 @@ def get_categ_products(request):
result['error'] = 'Authentification non valide' result['error'] = 'Authentification non valide'
return JsonResponse({'res': result}) return JsonResponse({'res': result})
def search_product(request): def search_product(request):
result = {} result = {}
credentials = CagetteMember.get_credentials(request) credentials = CagetteMember.get_credentials(request)
...@@ -177,7 +188,10 @@ def cart(request): ...@@ -177,7 +188,10 @@ def cart(request):
credentials = CagetteMember.get_credentials(request) credentials = CagetteMember.get_credentials(request)
if 'success' in credentials: if 'success' in credentials:
try: try:
result['cart'] = CagetteShop.registrerCart(cart, request.COOKIES['id']) mode = "shop"
if 'type' in cart:
mode = cart['type']
result['cart'] = CagetteShop.registrerCart(cart, request.COOKIES['id'], mode)
except Exception as e: except Exception as e:
result['error'] = str(e) result['error'] = str(e)
else: else:
...@@ -308,4 +322,4 @@ def remove_unused_orders(request): ...@@ -308,4 +322,4 @@ def remove_unused_orders(request):
res['deleted'] = CagetteShop.remove_unused_orders() res['deleted'] = CagetteShop.remove_unused_orders()
except Exception as e: except Exception as e:
res['error'] = str(e) res['error'] = str(e)
return JsonResponse(res) return JsonResponse(res)
\ No newline at end of file
...@@ -256,10 +256,10 @@ function init_confirmation_datatable() { ...@@ -256,10 +256,10 @@ function init_confirmation_datatable() {
}); });
} }
function without_consent_update_product(p) { function without_consent_update_product(p, added_qty) {
let undo_option = true; let undo_option = true;
update_existing_product(p, undo_option); update_existing_product(p, added_qty, undo_option);
} }
function get_stored_product_with_bc(barcode) { function get_stored_product_with_bc(barcode) {
...@@ -307,19 +307,17 @@ function fetch_product_from_bc(barcode) { ...@@ -307,19 +307,17 @@ function fetch_product_from_bc(barcode) {
'uom': barcodes['uoms'][p.data[barcodes['keys']['uom_id']]], 'uom': barcodes['uoms'][p.data[barcodes['keys']['uom_id']]],
'standard_price' : p.data[barcodes['keys']['standard_price']], // cost 'standard_price' : p.data[barcodes['keys']['standard_price']], // cost
'list_price': p.data[barcodes['keys']['list_price']] // public price 'list_price': p.data[barcodes['keys']['list_price']], // public price
'qty': p.qty
}; };
product['uom']['id'] = p.data[barcodes['keys']['uom_id']]; product['uom']['id'] = p.data[barcodes['keys']['uom_id']];
product['value'] = parseFloat(p.value) || 1;
product['rule'] = p.rule; product['rule'] = p.rule;
p_existing = get_stored_product_with_bc(p.barcode); p_existing = get_stored_product_with_bc(p.barcode);
if (p_existing !== null) { if (p_existing !== null) {
product.qty = p_existing.qty; without_consent_update_product(p_existing, product.qty);
without_consent_update_product(product);
return 0; return 0;
} else { } else {
add_product(product); add_product(product);
...@@ -335,16 +333,6 @@ function fetch_product_from_bc(barcode) { ...@@ -335,16 +333,6 @@ function fetch_product_from_bc(barcode) {
var add_product = function(product) { var add_product = function(product) {
try { try {
// Add to list // Add to list
product.qty = 1;
if (typeof product.value == "number" || (product.value.length > 0 && !isNaN(product.value))) {
//encoded value will be translated in quantity
if (product.rule == "FF_price_to_weight") {
product.qty = get_quantity_eq_to_franc_price(product);
} else {
product.qty = parseFloat(product.value);
}
}
products.push(product); products.push(product);
...@@ -385,26 +373,11 @@ var update_in_products = function(product) { ...@@ -385,26 +373,11 @@ var update_in_products = function(product) {
else console.log("Le produit n'a pas pu être trouvé dans la variable products !"); else console.log("Le produit n'a pas pu être trouvé dans la variable products !");
}; };
var get_quantity_eq_to_franc_price = function(product) {
let value = 0;
try {
let price = parseFloat(product.value / 6.55957);
value = parseFloat(price / product.list_price).toFixed(3);
} catch (error) {
console.log(error);
}
return value;
};
/* /*
* Update a line in the table: update quantity * Update a line in the table: update quantity
*/ */
var update_existing_product = function(product, undo_option = false) { var update_existing_product = function(product, added_qty, undo_option = false) {
// By default added qty is 1 unit
let added_qty = 1;
let op = "augmentée"; let op = "augmentée";
let notify_options = { let notify_options = {
...@@ -413,35 +386,8 @@ var update_existing_product = function(product, undo_option = false) { ...@@ -413,35 +386,8 @@ var update_existing_product = function(product, undo_option = false) {
clickToHide: false clickToHide: false
}; };
// type product qty value
if (product.rule == 'weight' || product.rule == 'FF_price_to_weight' && product.value) {
// Quantities are kg or price
product.qty = parseFloat(product.qty) || 0;
if (product.rule == 'weight') {
added_qty = parseFloat(product.value);
} else {
if (product.value < 0) added_qty = parseFloat(product.value); // value is already a qty
else added_qty = parseFloat(get_quantity_eq_to_franc_price(product));
}
} else {
//Quantity is by "defaut" considered as to be in "unit"
product.qty = parseInt(product.qty, 10);
if (product.rule == "" && product.value) {
added_qty = product.value;
}
}
product.qty += added_qty; product.qty += added_qty;
/* Surprisingly, this assignment by addition (0 + value)
always correctly "typing" the value
whereas "product.qty = added_qty"
is sometimes typed as "string" !!
*/
// always set to empty to avoid next operation on same product to be misprocessed
product.value = "";
// Find index of row which match product id in the first column // Find index of row which match product id in the first column
var indexes = products_table.rows().eq(0) var indexes = products_table.rows().eq(0)
...@@ -470,10 +416,7 @@ var update_existing_product = function(product, undo_option = false) { ...@@ -470,10 +416,7 @@ var update_existing_product = function(product, undo_option = false) {
notify_options.autoHide = false; notify_options.autoHide = false;
// notify_options.autoHideDelay = 10000; // notify_options.autoHideDelay = 10000;
notify_options.style = 'cancelable'; notify_options.style = 'cancelable';
// msg = $('<span>').text(msg)
// .attr('data-barcode', product.barcode)
// .attr('data-addedqty', added_qty);
// msg = msg.html()
msg = '<span class="msg" data-barcode="' + product.barcode + '" data-added_qty="' + added_qty + '">' msg = '<span class="msg" data-barcode="' + product.barcode + '" data-added_qty="' + added_qty + '">'
+ "<b>" + product.name + "</b><br/>" + msg + "<b>" + product.name + "</b><br/>" + msg
+ '</span>'; + '</span>';
...@@ -919,8 +862,7 @@ $(document).ready(function() { ...@@ -919,8 +862,7 @@ $(document).ready(function() {
let product = get_stored_product_with_bc(bc); let product = get_stored_product_with_bc(bc);
if (product !== null) { if (product !== null) {
product.value = - added_qty; update_existing_product(product, - added_qty);
update_existing_product(product);
} else { } else {
alert("Le produit n'a pas été retrouvé dans la mémoire de travail."); alert("Le produit n'a pas été retrouvé dans la mémoire de travail.");
} }
......
...@@ -21,9 +21,9 @@ ...@@ -21,9 +21,9 @@
<input name="city" placeholder="Ville" class="b_green" autocomplete="address-level4"/> <input name="city" placeholder="Ville" class="b_green" autocomplete="address-level4"/>
<input name="country" placeholder="Pays" class="b_yellow" autocomplete="address-level4"/> <input name="country" placeholder="Pays" class="b_yellow" autocomplete="address-level4"/>
<span class="phone-wrapper{% if ask_for_second_phone %}-2{% endif %}"> <span class="phone-wrapper{% if ask_for_second_phone %}-2{% endif %}">
<input type="tel" name="mobile" placeholder="Tél. mobile" class="b_green" pattern="^(\+\d{1,3}(-| ))?\d{1,2}(\.| )\d{1,2}(\.| )\d{1,2}(\.| )\d{1,2}(\.| )\d{0,2}?$" autocomplete="address-level4"/> <input type="tel" name="mobile" placeholder="Tél. mobile" class="b_green" pattern="{{input_phone_pattern}}" autocomplete="address-level4"/>
{% if ask_for_second_phone %} {% if ask_for_second_phone %}
<input type="tel" name="phone" placeholder="Tél. fixe" class="b_green" pattern="^(\+\d{1,3}(-| ))?\d{1,2}(\.| )\d{1,2}(\.| )\d{1,2}(\.| )\d{1,2}(\.| )\d{0,2}?$" autocomplete="address-level4"/> <input type="tel" name="phone" placeholder="Tél. fixe" class="b_green" pattern="{{input_phone_pattern}}" autocomplete="address-level4"/>
{% endif %} {% endif %}
</span> </span>
</p> </p>
......
{% extends "base.html" %}
{% load static %}
{% block additionnal_css %}
<link rel="stylesheet" href="{% static 'css/datatables/jquery.dataTables.css' %}">
<link rel="stylesheet" href="{% static 'jquery-ui-1.12.1/jquery-ui.min.css' %}">
<link rel="stylesheet" href="{% static "css/sales.css" %}?v=">
{% endblock %}
{% block additionnal_scripts %}
<script type="text/javascript" src="{% static 'jquery-ui-1.12.1/jquery-ui.min.js' %}?v="></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>
{% endblock %}
{% block content %}
<div class="page_body">
<div id="content_main" class="page_content">
<div class="header txtcenter">
<h1>Extraction de ventes</h1>
</div>
<div class="select_sales_dates txtcenter">
<form id="sales_form" action="javascript:;">
<div class="sales_date">
<label for="from">Entre :</label>
<input type="text" name="from" id="from" class="select_sales_date_input" required>
</div>
<div class="sales_date">
<label for="to">et :</label>
<input type="text" name="to" id="to" class="select_sales_date_input" required>
</div>
<button type="submit" class="btn--primary" id="dates_selection_button" disabled>C'est parti !</button>
</form>
</div>
<div class="main" style="display:none;">
<div class="table_area">
<table id="orders_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>
<script src="{% static "js/sales.js" %}?v="></script>
{% endblock %}
...@@ -391,7 +391,7 @@ ...@@ -391,7 +391,7 @@
<option value="23:00">23:00</option> <option value="23:00">23:00</option>
</select> </select>
{% endif %} {% 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/>
...@@ -405,7 +405,7 @@ ...@@ -405,7 +405,7 @@
{%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/> 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/> 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é). Vous devrez rechoisir un horaire (le panier sera mémorisé).
</strong> </strong>
...@@ -436,7 +436,7 @@ ...@@ -436,7 +436,7 @@
</p> </p>
<p> <p>
{% if mode == 'shop' %} {% 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>{{SHOP_BOTTOM_VALIDATION_MSG}}</i></strong>
{%endif%} {%endif%}
</p> </p>
<p> <p>
...@@ -500,6 +500,10 @@ ...@@ -500,6 +500,10 @@
{%if SHOP_OPENING_START_DATE%} {%if SHOP_OPENING_START_DATE%}
const opening_start_date = new Date('{{SHOP_OPENING_START_DATE}}') const opening_start_date = new Date('{{SHOP_OPENING_START_DATE}}')
{%endif%} {%endif%}
{%if cat_nb_pdts%}
const cat_nb_pdts = {{cat_nb_pdts|safe}}
{%endif%}
const stock_warning = {{SHOP_STOCK_WARNING|safe}}
</script> </script>
<script src="{% static 'js/all_common.js' %}?v="></script> <script src="{% static 'js/all_common.js' %}?v="></script>
<script type="text/javascript" src="{% static 'js/shop.js' %}?v="></script> <script type="text/javascript" src="{% static 'js/shop.js' %}?v="></script>
......
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