Commit 39a5c17f by François C.

Merge branch 'dev_principale' into 'master'

Cooperatic and LGDS improvements

See merge request !24
parents a868d541 28dd803c
Pipeline #1027 passed with stage
in 21 seconds
COUCHDB_USER=admin
COUCHDB_PASSWORD=123abc
POSTGRES_USER=foodcoops
POSTGRES_PASSWORD=foodcoops
POSTGRES_DB=foodcoops
# Scripts
*.sh text eol=lf
\ No newline at end of file
...@@ -10,8 +10,10 @@ outils/settings_secret.py ...@@ -10,8 +10,10 @@ outils/settings_secret.py
outils/config.py outils/config.py
outils/texts/* outils/texts/*
outils/js_errors.log outils/js_errors.log
outils/scripts/scripts_settings.py
db.sqlite3 db.sqlite3
*/max_timeslot_carts.txt */max_timeslot_carts.txt
.gitlab-ci.yml .gitlab-ci.yml
shop/shop_admin_settings.json shop/shop_admin_settings.json
shop/errors.log shop/errors.log
.idea
This diff is collapsed. Click to expand it.
"""Company specific data values.""" """Company specific data values."""
MAG_NAME = 'Cleme'
OFFICE_NAME = ''
MAX_BEGIN_HOUR = '19:00'
COMPANY_NAME = 'La Cagette'
WELCOME_ENTRANCE_MSG = 'Bienvenue à La Cagette !'
WELCOME_MAIL_SUBJECT = 'Dernière étape de votre inscription à la Cagette.'
WELCOME_MAIL_TEMPLATE = 'members/bienvenue.html'
EMAIL_DOMAIN = 'lacagette-coop.fr'
OPEN_ON_SUNDAY = True OPEN_ON_SUNDAY = True
FLV_CSV_NB = 4
CAP_JOURNAL_ID = 9 CAP_JOURNAL_ID = 9
CAP_APPELE_NON_VERSE_ACCOUNT_ID = 529 CAP_APPELE_NON_VERSE_ACCOUNT_ID = 529
CAP_APPELE_VERSE_ACCOUNT_ID = 8 CAP_APPELE_VERSE_ACCOUNT_ID = 8
CAP_INVOICE_LINE_ACCOUNT_ID = 8 CAP_INVOICE_LINE_ACCOUNT_ID = 8
FUNDRAISING_CAT_ID = 1 FUNDRAISING_CAT_ID = 1
UNITE_UOM_ID = 1 UNITE_UOM_ID = 1
PARTS_A_PRODUCT_ID = 1008 PARTS_A_PRODUCT_ID = 1008
PARTS_A_PRICE_UNIT = 10.0 PARTS_A_PRICE_UNIT = 10.0
COOP_BARCODE_RULE_ID = 11 COOP_BARCODE_RULE_ID = 11
CHECK_PAYMENT_ID = 8 CHECK_PAYMENT_ID = 8
CASH_PAYMENT_ID = 18 CASH_PAYMENT_ID = 18
STOCK_LOC_ID = 12
STOCK_LOC_ID = 12
CATEG_FRUIT = 151 CATEG_FRUIT = 151
CATEG_LEGUME = 152 CATEG_LEGUME = 152
FLV_CSV_NB = 4
COEFF_MAG_ID = 1 COEFF_MAG_ID = 1
...@@ -50,7 +55,7 @@ EM_URL = '' ...@@ -50,7 +55,7 @@ EM_URL = ''
RECEPTION_MERGE_ORDERS_PSWD = 'jpsrcp' RECEPTION_MERGE_ORDERS_PSWD = 'jpsrcp'
DAV_PATH = '/data/dav/cagette' DAV_PATH = '/shared_dir/dav/'
TOOLS_SERVER = 'https://outils.lacagette-coop.fr' TOOLS_SERVER = 'https://outils.lacagette-coop.fr'
...@@ -78,6 +83,8 @@ SHOP_CATEGORIES = {'epicerie': {'id': 75, 'label': 'Epicerie'}, ...@@ -78,6 +83,8 @@ SHOP_CATEGORIES = {'epicerie': {'id': 75, 'label': 'Epicerie'},
'parfumerie': {'id': 133, 'label': 'Parfumerie'}} 'parfumerie': {'id': 133, 'label': 'Parfumerie'}}
SHOP_EXTRA_MENUS = ['shop/planning_livraison_pains.html', 'shop/combien_ca_pese.html'] 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' SHOP_SURVEY_LINK = 'https://docs.google.com/forms/d/e/1FAIpQLSczl0mMRwx3s9LbUSPYwwFTiiRa6agx7YkQM9cL41eiQnXNUw/viewform'
SHOP_LIMIT_PRODUCTS = ['relatively_available', 'no_shelf']
EXCLUDE_SHOP_CATEGORIES = [108] EXCLUDE_SHOP_CATEGORIES = [108]
PROMOTE_SHELFS_IDS = [68] PROMOTE_SHELFS_IDS = [68]
DISCOUNT_SHELFS_IDS = [74] DISCOUNT_SHELFS_IDS = [74]
...@@ -85,11 +92,18 @@ FL_SHELFS = [16, 17, 18] ...@@ -85,11 +92,18 @@ FL_SHELFS = [16, 17, 18]
VRAC_SHELFS = [20, 38] VRAC_SHELFS = [20, 38]
SHIFT_EXCHANGE_DAYS_TO_HIDE = '' SHIFT_EXCHANGE_DAYS_TO_HIDE = ''
SHIFT_INFO = """A la cagette, un service est une plage de trois heures un jour en particulier, par exemple : le mardi 25/09/2018 à 13h15.
<br />A l'inverse, un créneau est une plage de trois heures 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 vous rendre dans la section \"J'ai un problème\" sur le site web de <a href=\"https://lacagette-coop.fr/?MonEspaceMembre\">La Cagette</a>"""
ENTRANCE_COME_FOR_SHOPING_MSG = "Hey coucou toi ! Cet été nous sommes plus de <strong>1000 acheteur·euses</strong> pour seulement <strong>300 coopérateur·rice·s</strong> en service. <br />Tu fais tes courses à La Cagette cet été ?<br/> Inscris-toi sur ton espace membre !" ENTRANCE_COME_FOR_SHOPING_MSG = "Hey coucou toi ! Cet été nous sommes plus de <strong>1000 acheteur·euses</strong> pour seulement <strong>300 coopérateur·rice·s</strong> en service. <br />Tu fais tes courses à La Cagette cet été ?<br/> Inscris-toi sur ton espace membre !"
# Members space / shifts # Members space / shifts
UNSUBSCRIBED_MSG = 'Vous êtes désincrit·e, merci de remplir <a href="https://docs.google.com/forms/d/e/1FAIpQLSfPiC2PkSem9x_B5M7LKpoFNLDIz0k0V5I2W3Mra9AnqnQunw/viewform">ce formulaire</a> pour vous réinscrire sur un créneau.<br />Vous pouvez également contacter le Bureau des Membres en remplissant <a href="https://docs.google.com/forms/d/e/1FAIpQLSeZP0m5-EXPVJxEKJk6EjwSyZJtnbiGdYDuAeFI3ENsHAOikg/viewform">ce formulaire</a>' UNSUBSCRIBED_MSG = 'Vous êtes désincrit·e, merci de remplir <a href="https://docs.google.com/forms/d/e/1FAIpQLSfPiC2PkSem9x_B5M7LKpoFNLDIz0k0V5I2W3Mra9AnqnQunw/viewform">ce formulaire</a> pour vous réinscrire sur un créneau.<br />Vous pouvez également contacter le Bureau des Membres en remplissant <a href="https://docs.google.com/forms/d/e/1FAIpQLSeZP0m5-EXPVJxEKJk6EjwSyZJtnbiGdYDuAeFI3ENsHAOikg/viewform">ce formulaire</a>'
CONFIRME_PRESENT_BTN = 'Clique ici pour valider ta présence'
RECEPTION_PB = "Ici, vous pouvez signaler toute anomalie lors d'une réception, les produits non commandés, cassés ou pourris. \
Merci d'indiquer un maximum d'informations, le nom du produit et son code barre. \
Dans le cas de produits déteriorés, merci d'envoyer une photo avec votre téléphone à [Adresse_email]"
...@@ -91,4 +91,4 @@ BRINKS_MUST_IDENTIFY = True ...@@ -91,4 +91,4 @@ BRINKS_MUST_IDENTIFY = True
ENTRANCE_FTOP_BUTTON_DISPLAY = False ENTRANCE_FTOP_BUTTON_DISPLAY = False
CUSTOM_CSS_FILES = {'all': ['common_lgds.css'], CUSTOM_CSS_FILES = {'all': ['common_lgds.css'],
'members': ['inscription_lgds.css']} 'members': ['inscription_lgds.css','member_lgds.css']}
version: '3'
services:
app:
build:
context: .
dockerfile: dockerfiles/Dockerfile
env_file: .env
restart: always
ports:
- "8080:8080"
links:
- "couchdb:couchdb"
- "odoo:odoo"
volumes:
- "./:/home/app/"
couchdb:
build:
context: .
dockerfile: dockerfiles/Dockerfile.couchdb
env_file: .env
restart: always
ports:
- "5984:5984" # Expose port because it's used by the frontend
volumes:
- "couchdb-data:/opt/couchdb/data"
database:
image: "postgres:10"
env_file: .env
restart: always
volumes:
- "odoo-pg-data:/var/lib/postgresql/data"
odoo:
image: "registry.gitlab.com/lgds/foodcoops:9.0-cooperatic-2021-04-02"
env_file: .env
restart: always
ports:
- "8069:8069"
links:
- "database:database"
volumes:
- "odoo-shared-data:/home/app/.local/share/Odoo"
volumes:
odoo-shared-data:
odoo-pg-data:
couchdb-data:
FROM python:3
ENV PYTHONUNBUFFERED=1
# Add french locale
RUN apt update && \
apt install -y --no-install-recommends locales && \
rm -rf /var/lib/apt/lists/* && \
sed -i '/^#.* fr_FR.UTF-8 /s/^#//' /etc/locale.gen && \
locale-gen
WORKDIR /home/app
RUN pip install --upgrade pip
COPY requirements.txt /home/app/
RUN pip install -r requirements.txt
# Setup volume to be able to dev application locally
VOLUME /home/app
COPY . /home/app
# Run the application:
EXPOSE 8080
CMD ["./launch.sh", "0.0.0.0", "8080"]
FROM couchdb:3
COPY dockerfiles/local.ini /opt/couchdb/etc/
[couchdb]
single_node=true
users_db_security_editable = true
[httpd]
enable_cors = true
[cors]
credentials = true
origins = *
[replicator]
db = _replicator
[admins]
;admin = 123abc
admin = -pbkdf2-c5101d6618dfa702b4fb72b1ea0f335e1d1d84f9,506213b6a001da088296f40327cc2e86,10
...@@ -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
......
#! /bin/sh #!/usr/bin/env bash
port=34001 port=34001
ip=127.0.0.1 ip=127.0.0.1
if [ ! -z "$1" ]
then
ip=$1
fi
if [ ! -z "$2" ] if [ -n "$1" ]
then then
port=$2 ip=$1
fi
if [ -n "$2" ]
then
port=$2
fi fi
current_path=$(pwd) current_path=$(pwd)
export PYTHONPATH="$current_path:$current_path/lib:$PYTHONPATH" export PYTHONPATH="$current_path:$current_path/lib:$PYTHONPATH"
echo yes | django-admin collectstatic --settings=outils.settings export DJANGO_SETTINGS_MODULE=outils.settings
django-admin runserver $ip:$port --settings=outils.settings # Collect static files
echo yes | django-admin collectstatic
# Make sure couchdb databases exist
python manage.py couchdb
# Run server
django-admin runserver "$ip:$port"
from django.core.management.base import BaseCommand, CommandError
from django.conf import settings
import couchdb
class Command(BaseCommand):
help = 'Initialize needed couchDB databases'
def handle(self, *args, **options):
if 'admin' not in settings.COUCHDB:
raise CommandError('''
Veuillez définir les accès d'admin à CouchDB dans votre fichier settings_secret.py
Vérifiez le fichier settings_secret_example.py pour un exemple.''')
url = settings.COUCHDB['admin']['url'] % (settings.COUCHDB['admin']['user'], settings.COUCHDB['admin']['password'])
dbnames = settings.COUCHDB['dbs']
couchserver = couchdb.Server(url)
for dbname in dbnames.values():
if dbname not in couchserver:
self.stdout.write(self.style.WARNING("Missing database %s" % dbname))
db = couchserver.create(dbname)
self.stdout.write(self.style.SUCCESS("✓ created db"))
if dbname == 'coops':
self.createCoopsViews(db)
elif dbname == 'envelop':
self.createEnvelopViews(db)
# db.security
self.createPublicAccess(db)
def createCoopsViews(self, dbConn):
byFpMapFunction = '''function(doc) {
emit(doc.fingerprint);
}'''
byCompletedMapFunction = '''function(doc) {
emit(doc.completed);
}'''
byOdooMapFunction = '''function(doc) {
emit(doc.odoo_id);
}'''
views = {
"by_fp": {
"map": byFpMapFunction
},
"by_completed": {
"map": byCompletedMapFunction
},
"by_odoo_id": {
"map": byOdooMapFunction
},
}
self.createView(dbConn, "index", views)
def createEnvelopViews(self, dbConn):
byTypeMapFunction = '''function(doc) {
emit(doc.type);
}'''
views = {
"by_type": {
"map": byTypeMapFunction
}
}
self.createView(dbConn, "index", views)
def createView(self, dbConn, designDoc, views):
self.stdout.write(self.style.SUCCESS("✓ created view %s" % designDoc))
data = {
"_id": "_design/%s" % designDoc,
"views": views,
"language": "javascript",
"options": {"partitioned": False }
}
dbConn.save(data)
def createPublicAccess(self, dbConn):
self.stdout.write(self.style.SUCCESS("✓ created security rule"))
security_doc = dbConn.resource.get_json("_security")[2]
dbConn.resource.put("_security", {})
...@@ -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):
......
...@@ -21,6 +21,16 @@ var to_fill_box = $('#to_fill'), ...@@ -21,6 +21,16 @@ var to_fill_box = $('#to_fill'),
problem_delete = $('#problem_delete'), problem_delete = $('#problem_delete'),
vform = $('#coop_validation_form'); vform = $('#coop_validation_form');
// date validation
Date.prototype.isValid = function () {
// If the date object is invalid it
// will return 'NaN' on getTime()
// and NaN is never equal to itself.
return this.getTime() === this.getTime();
};
sync.on('change', function (info) { sync.on('change', function (info) {
// handle change // handle change
if (info.direction == 'pull') { if (info.direction == 'pull') {
...@@ -248,17 +258,30 @@ function save_current_coop(callback) { ...@@ -248,17 +258,30 @@ function save_current_coop(callback) {
sex_error = false; sex_error = false;
if (/([0-9]{2})\/([0-9]{2})\/([0-9]{4})/.exec(birthdate)) { if (/([0-9]{2})\/([0-9]{2})\/([0-9]{4})/.exec(birthdate)) {
var jj = RegExp.$1, try{
var jj = RegExp.$1,
mm = RegExp.$2, mm = RegExp.$2,
aaaa = RegExp.$3; aaaa = RegExp.$3;
if (jj > 31 || mm > 12 || aaaa < 1900 || aaaa > 2018) { let tmp_date = aaaa + "-" + mm + "-" + jj;
// try to create a date object
date_test = new Date(tmp_date);
// if date is invalid a correction is apply in date object. Check it
// january start at 0, so we add + 1 for the month
if ((date_test.getDate() !== parseInt(jj)) || ((date_test.getMonth()+1) !== parseInt(mm)) || (date_test.getFullYear() !== parseInt(aaaa)) || !date_test.isValid())
{
birthdate_error = true;
}
}catch(Exception){
birthdate_error = true; birthdate_error = true;
} }
} else { } else {
birthdate_error = true; birthdate_error = true;
} }
let street2_input = form.find('[name="street2"]'), let street2_input = form.find('[name="street2"]'),
phone_input = form.find('[name="phone"]'); phone_input = form.find('[name="phone"]');
......
...@@ -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),
] ]
...@@ -23,7 +23,8 @@ def index(request): ...@@ -23,7 +23,8 @@ def index(request):
'WELCOME_ENTRANCE_MSG': getattr(settings, 'WELCOME_ENTRANCE_MSG', 'Bienvenue !'), 'WELCOME_ENTRANCE_MSG': getattr(settings, 'WELCOME_ENTRANCE_MSG', 'Bienvenue !'),
'WELCOME_SUBTITLE_ENTRANCE_MSG': getattr(settings, 'WELCOME_SUBTITLE_ENTRANCE_MSG', ''), 'WELCOME_SUBTITLE_ENTRANCE_MSG': getattr(settings, 'WELCOME_SUBTITLE_ENTRANCE_MSG', ''),
'ENTRANCE_SHOPPING_BTN': getattr(settings, 'ENTRANCE_SHOPPING_BTN', 'Je viens faire mes courses'), 'ENTRANCE_SHOPPING_BTN': getattr(settings, 'ENTRANCE_SHOPPING_BTN', 'Je viens faire mes courses'),
'ENTRANCE_SERVICE_BTN': getattr(settings, 'ENTRANCE_SERVICE_BTN', 'Je viens faire mon service') 'ENTRANCE_SERVICE_BTN': getattr(settings, 'ENTRANCE_SERVICE_BTN', 'Je viens faire mon service'),
'CONFIRME_PRESENT_BTN' : getattr(settings, 'CONFIRME_PRESENT_BTN', 'Présent.e')
} }
for_shoping_msg = getattr(settings, 'ENTRANCE_COME_FOR_SHOPING_MSG', '') for_shoping_msg = getattr(settings, 'ENTRANCE_COME_FOR_SHOPING_MSG', '')
...@@ -102,6 +103,7 @@ def prepa_odoo(request): ...@@ -102,6 +103,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 +137,7 @@ def validation_inscription(request, email): ...@@ -135,6 +137,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),
......
...@@ -77,7 +77,10 @@ class OdooAPI: ...@@ -77,7 +77,10 @@ class OdooAPI:
class CouchDB: class CouchDB:
"""Class to handle interactions with CouchDB""" """Class to handle interactions with CouchDB"""
url = settings.COUCHDB['url'] if 'private_url' in settings.COUCHDB:
url = settings.COUCHDB['private_url']
else:
url = settings.COUCHDB['url']
dbs = settings.COUCHDB['dbs'] dbs = settings.COUCHDB['dbs']
db = None db = None
dbc = None dbc = None
......
# -*- 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],
......
# coding: utf-8
import sys, getopt, os
sys.path.append(os.path.abspath('../..'))
from outils.common import OdooAPI
from outils.config import COOP_BARCODE_RULE_ID
from openpyxl import Workbook
from openpyxl import load_workbook
from openpyxl.styles import Alignment
import datetime
# from django.conf import settings
from openpyxl.utils.exceptions import InvalidFileException
def main():
""" Pass file as script arg """
if len(sys.argv) < 2:
print("Il faut renseigner le chemin du fichier contenant les données des rattachés (format Excel).")
exit(2)
api = OdooAPI()
data_file = sys.argv[1]
try:
wb = load_workbook(data_file)
except FileNotFoundError:
print("Fichier introuvable.")
exit(2)
except InvalidFileException:
print("Le fichier fourni est invalide, il doit être au format Excel (.xlsx,.xlsm,.xltx,.xltm)")
exit(2)
while True:
try:
worksheet_number = input(f'Numéro de la feuille contenant les données (entre 0 et {len(wb.worksheets)-1}) : ')
worksheet_number = int(worksheet_number)
ws = wb.worksheets[worksheet_number]
except ValueError:
print("Veuillez rentrer un numéro entier.")
continue
except IndexError:
print("Cette feuille n'existe pas.")
continue
else:
break
columns_valid = input("""
Les colonnes doivent être les suivantes :
A: active*
B: barode_base*
C: name*
D: (ignorée)
E: date_inclusion
F: email
G: birthdate
H: mobile
I: phone
J: street
K: street2
L: zip
M: city
N: sex
O: (ignorée)
P: parent_member_id*
(* ces champs doivent être renseignés)
Vous confirmez ? (O/n) """)
while True:
if (columns_valid == 'n' or columns_valid == 'N'):
print("Veuillez formatter correctement le fichier avant de continuer !")
exit()
elif (columns_valid == 'o' or columns_valid == 'O' or columns_valid== ''):
break
else:
columns_valid = input("Vous confirmez ? (O/n) ")
continue
users = []
has_error = False
for row in ws.iter_rows(min_row=2, values_only=True):
# active
if row[0] is None or row[0] is False or row[0] == "=FALSE()" or row[0] == "=false":
active = False
else:
active = True
# If line is not empty (mandatory field check)
if row[2] is not None:
user = {
"is_member": False,
"is_associated_people": True,
"active": active,
"barcode_rule_id": COOP_BARCODE_RULE_ID,
"name": row[2],
"parent_id": int(row[15]), # for development, override with local existing member id
}
if row[1] is not None and row[1] != "NON":
try:
user["barcode_base"] = int(row[1])
except Exception:
print(f"[Mauvais format du champ 'barcode_base' pour l'utilisateur '{row[2]}' (Attendu : nombre entier)")
has_error = True
if row[4] is not None:
user["comment"] = f"Date d'inclusion : {row[4].date().strftime('%d/%m/%Y')}"
if row[5] is not None:
user["email"] = row[5]
if row[6] is not None:
user["birthdate"] = str(row[6].date())
if row[7] is not None:
try:
mobile = str(row[7]).replace(" ", "")
if mobile[0] != '+' and mobile[0] != '0':
mobile = '0' + mobile
user["mobile"] = mobile
except Exception:
print(f"[Mauvais format du champ 'mobile' pour l'utilisateur '{row[2]}'")
has_error = True
if row[8] is not None:
try:
phone = str(row[8]).replace(" ", "")
if phone[0] != '+' and phone[0] != '0':
phone = '0' + phone
user["phone"] = phone
except Exception:
print(f"[Mauvais format du champ 'phone' pour l'utilisateur '{row[2]}'")
has_error = True
if row[9] is not None:
user["street"] = row[9]
if row[10] is not None:
user["street2"] = row[10]
if row[11] is not None:
try:
zipcode = str(int(row[11]))
if len(zipcode) == 4:
zipcode = '0' + zipcode
user["zip"] = zipcode
except Exception:
print(f"[Mauvais format du champ 'zipcode' pour l'utilisateur '{row[2]}'")
has_error = True
if row[12] is not None:
user["city"] = row[12]
if row[13] == "Femme":
user["sex"] = "f"
elif row[13] == "Homme":
user["sex"] = "m"
users.append(user)
if has_error:
print("L'import a été interrompu, veuillez régler les erreurs.")
exit(2)
res = None
marshal_none_error = 'cannot marshal None unless allow_none is enabled'
for user in users:
try:
res = api.create('res.partner', user)
if res:
print(f"Rattaché.e importé.e avec succès : {user['name']} (id : {res})")
except Exception as e:
if not (marshal_none_error in str(e)):
print(f"Erreur lors de l'insertion de {user['name']}, vérifiez ses données dans le tableau ({str(e)})")
else:
pass
if __name__ == "__main__":
main()
\ No newline at end of file
SECRET_KEY = 'Mettre_plein_de_caracteres_aleatoires_iezezezeezezci'
ODOO = {
'url': 'http://127.0.0.1:8069'
'user': 'api',
'passwd': 'xxxxxxxxxxxx',
'db': 'bd_test',
}
COUCHDB = {
'url': 'http://127.0.0.1:5984',
'dbs': {
'member': 'coops',
'inventory': 'inventory',
'envelops': 'envelop',
'shop': 'shopping_carts'
}
}
...@@ -14,7 +14,6 @@ https://docs.djangoproject.com/en/1.8/ref/settings/ ...@@ -14,7 +14,6 @@ https://docs.djangoproject.com/en/1.8/ref/settings/
import os import os
from .settings_secret import * from .settings_secret import *
from .texts.cagette import *
from .config import * from .config import *
from .customized_errors_filter import * from .customized_errors_filter import *
...@@ -53,6 +52,7 @@ INSTALLED_APPS = ( ...@@ -53,6 +52,7 @@ INSTALLED_APPS = (
'orders', 'orders',
'shop', 'shop',
'shelfs', 'shelfs',
'sales',
# 'tests' # 'tests'
) )
...@@ -221,4 +221,6 @@ LOGGING = { ...@@ -221,4 +221,6 @@ LOGGING = {
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
CORS_ORIGIN_ALLOW_ALL = True # Needed to make dev test with different IP and ports CORS_ORIGIN_ALLOW_ALL = True # Needed to make dev test with different IP and ports
\ No newline at end of file
ADMIN_IDS = [1]
"""Secret data for DB connexion .""" """Secret data for DB connexion ."""
ODOO = { ODOO = {
'url': 'http://127.0.0.1:8069' 'url': 'http://odoo:8069',
'user': 'api', 'user': 'api',
'passwd': 'xxxxxxxxxxxx', 'passwd': 'foodcoops',
'db': 'bd_test', 'db': 'foodcoops',
} }
COUCHDB = { COUCHDB = {
'private_url': 'http://couchdb:5984',
'url': 'http://127.0.0.1:5984', 'url': 'http://127.0.0.1:5984',
'admin': {
'url': 'http://%s:%s@couchdb:5984',
'user': 'admin',
'password': '123abc',
},
'dbs': { 'dbs': {
'member': 'coops', 'member': 'coops',
'inventory': 'inventory', 'inventory': 'inventory',
...@@ -17,7 +23,7 @@ COUCHDB = { ...@@ -17,7 +23,7 @@ COUCHDB = {
} }
} }
""" To ignore """
SQL_OFF = { SQL_OFF = {
'db': 'open_food_facts', 'db': 'open_food_facts',
'user': 'off_user', 'user': 'off_user',
......
...@@ -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:
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
"main": "index.js", "main": "index.js",
"dependencies": {}, "dependencies": {},
"devDependencies": { "devDependencies": {
"eslint": "^7.16.0" "eslint": "^7.24.0"
}, },
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
......
...@@ -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;
......
...@@ -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()
...@@ -1328,9 +1336,18 @@ function openFAQ() { ...@@ -1328,9 +1336,18 @@ function openFAQ() {
function openErrorReport() { function openErrorReport() {
openModal($('#templates #modal_error_report').html(), saveErrorReport, 'Confirmer'); openModal($('#templates #modal_error_report').html(), saveErrorReport, 'Confirmer');
// listener for error report textarea
// this is necessary because default behavior is overwritten by the listener defined in jquery.pos.js;
$("#error_report").keypress(function(e) {
var key = e.keyCode;
if (key === 13) {
this.value += "\n";
}
});
var textarea = document.getElementById("error_report"); var textarea = document.getElementById("error_report");
textarea.value = user_comments; textarea.value = (user_comments != undefined) ? user_comments : "";
textarea.focus(); textarea.focus();
textarea.setSelectionRange(textarea.value.length, textarea.value.length); textarea.setSelectionRange(textarea.value.length, textarea.value.length);
} }
...@@ -1473,7 +1490,7 @@ $(document).ready(function() { ...@@ -1473,7 +1490,7 @@ $(document).ready(function() {
reception_status = orders[Object.keys(orders)[0]].reception_status; reception_status = orders[Object.keys(orders)[0]].reception_status;
// Load user comments from local storage, get it from first order // Load user comments from local storage, get it from first order
user_comments = orders[Object.keys(orders)[0]].user_comments; user_comments = orders[Object.keys(orders)[0]].user_comments || "";
} }
// Fetch orders data // Fetch orders data
...@@ -1560,12 +1577,12 @@ $(document).ready(function() { ...@@ -1560,12 +1577,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 +1597,7 @@ $(document).ready(function() { ...@@ -1580,7 +1597,7 @@ $(document).ready(function() {
*/ */
}); });
}) })
.on('blur', function (e) { .on('blur', function () {
$(this).off('wheel.disableScroll'); $(this).off('wheel.disableScroll');
}); });
...@@ -1635,6 +1652,13 @@ $(document).ready(function() { ...@@ -1635,6 +1652,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) {
......
...@@ -16,5 +16,6 @@ urlpatterns = [ ...@@ -16,5 +16,6 @@ urlpatterns = [
url(r'^reception_qtiesValidated', views.reception_qtiesValidated), url(r'^reception_qtiesValidated', views.reception_qtiesValidated),
url(r'^reception_pricesValidated', views.reception_pricesValidated), url(r'^reception_pricesValidated', views.reception_pricesValidated),
# url(r'^update_order_status/([0-9]+)$', views.tmp_update_order_status), # url(r'^update_order_status/([0-9]+)$', views.tmp_update_order_status),
url(r'^po_process_picking$', views.po_process_picking) url(r'^po_process_picking$', views.po_process_picking),
url(r'^save_order_group$', views.save_order_group)
] ]
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", limit=50000)
item = None
try:
for payment in payments:
# POS session can contain payments from another day (closing session on next morning, ...)
if payment["date"] >= date_from and payment["date"] <= date_to:
# If the consecutive payment in the results is from the same partner on the same day, we consider it's the same basket
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;
}
.btn_export_movements {
margin-top: 10px;
}
\ 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();
}
orders_table = $('#orders_table').DataTable({
data: orders,
columns:[
{
data:"create_date",
title:"Date de vente",
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",
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"
]
],
buttons: [
{
extend: 'excelHtml5',
text: 'Export en Excel',
className: 'btn--primary btn_export_movements'
}
],
dom: '<lr<t>ip><"clear"><B>',
iDisplayLength: 100,
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();
});
});
"""."""
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.");
} }
......
...@@ -4,9 +4,9 @@ ...@@ -4,9 +4,9 @@
* Ecran de rechreche d'un article sur le nom et sur le code barre */ * Ecran de rechreche d'un article sur le nom et sur le code barre */
var table_article; var table_article = null;
var dataSet =[]; var dataSet =[];
var csrftoken; var csrftoken = '';
// lance la recherche sur le nom des l'article // lance la recherche sur le nom des l'article
function search_table_article() { function search_table_article() {
...@@ -30,7 +30,7 @@ $(document).ready(function() { ...@@ -30,7 +30,7 @@ $(document).ready(function() {
{ {
data:"image_small", data:"image_small",
"title":"Photo", "title":"Photo",
"render": function (data, type, full, meta) { "render": function (data, type, full) {
debut = '<button id="page1" type="button" data-toggle="modal" data-target=".modal" data-remote=' + full.id + ' class="btn btn-primary">'; debut = '<button id="page1" type="button" data-toggle="modal" data-target=".modal" data-remote=' + full.id + ' class="btn btn-primary">';
fin = "</button>"; fin = "</button>";
...@@ -41,14 +41,14 @@ $(document).ready(function() { ...@@ -41,14 +41,14 @@ $(document).ready(function() {
{data:"name", "title":"Article", "width": "50%"}, {data:"name", "title":"Article", "width": "50%"},
{data:"qty_available", "title":"En Stock", "width": "10%"}, {data:"qty_available", "title":"En Stock", "width": "10%"},
{data:"uom_id", {data:"uom_id",
"render":function (data, type, row) { "render":function (data) {
return data[1]; return data[1];
}, },
"title":"Unité", "width":"5%"}, "title":"Unité", "width":"5%"},
{data:"reception_status", {data:"reception_status",
"title":"Rupture", "className":"dt-body-center", "title":"Rupture", "className":"dt-body-center",
"render": function (data, type, full, meta) { "render": function (data, type, full) {
if (full.qty_available > 0) { if (full.qty_available > 0) {
return "<div><button id='bt_change' href='#'>Rupture</button></div>"; return "<div><button id='bt_change' href='#'>Rupture</button></div>";
} else { } else {
...@@ -135,7 +135,7 @@ $(document).ready(function() { ...@@ -135,7 +135,7 @@ $(document).ready(function() {
}); });
// Lancement de la rupture sur l'article choisie // Lancement de la rupture sur l'article choisie
function ruptureArticle(test) { function ruptureArticle() {
var jIdArcticle = { 'idArticle': selArctileData.id, 'uom_id' : selArctileData.uom_id[0] }; var jIdArcticle = { 'idArticle': selArctileData.id, 'uom_id' : selArctileData.uom_id[0] };
...@@ -148,7 +148,7 @@ function ruptureArticle(test) { ...@@ -148,7 +148,7 @@ function ruptureArticle(test) {
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
data: JSON.stringify(jIdArcticle), data: JSON.stringify(jIdArcticle),
success: function(data) { success: function() {
document.location.href = "/stock/listArticleBreaking"; document.location.href = "/stock/listArticleBreaking";
}, },
...@@ -159,7 +159,7 @@ function ruptureArticle(test) { ...@@ -159,7 +159,7 @@ function ruptureArticle(test) {
}); });
} }
var selArctileData; var selArctileData = null;
// Fenetre de validation sur l'article // Fenetre de validation sur l'article
......
...@@ -64,7 +64,7 @@ $(document).on('click', '#dp_Search', function() { ...@@ -64,7 +64,7 @@ $(document).on('click', '#dp_Search', function() {
search_table_article(); search_table_article();
}); });
var csrftoken; var csrftoken = '';
$(document).ready(function() { $(document).ready(function() {
csrftoken = getCookie('csrftoken'); csrftoken = getCookie('csrftoken');
...@@ -98,7 +98,7 @@ function actionButton (vUrl, jIdArcticle, followPage) { ...@@ -98,7 +98,7 @@ function actionButton (vUrl, jIdArcticle, followPage) {
traditional: true, traditional: true,
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
data: JSON.stringify(jIdArcticle), data: JSON.stringify(jIdArcticle),
success: function(data) { success: function() {
document.location.href = followPage; document.location.href = followPage;
}, },
error: function(resultat, statut, erreur) { error: function(resultat, statut, erreur) {
...@@ -108,7 +108,7 @@ function actionButton (vUrl, jIdArcticle, followPage) { ...@@ -108,7 +108,7 @@ function actionButton (vUrl, jIdArcticle, followPage) {
}); });
} }
var selArctileData; var selArctileData = null;
// Fenetre de validation sur l'article // Fenetre de validation sur l'article
......
...@@ -15,7 +15,7 @@ $(document).ready(function() { ...@@ -15,7 +15,7 @@ $(document).ready(function() {
{data:"name", "title":"Article", "width": "50%"}, {data:"name", "title":"Article", "width": "50%"},
{data:"maxdate", {data:"maxdate",
"render":function (data, type, row) { "render":function (data) {
my = new Date(data); my = new Date(data);
return my.toLocaleDateString() +" " + my.toLocaleTimeString(); return my.toLocaleDateString() +" " + my.toLocaleTimeString();
...@@ -23,7 +23,7 @@ $(document).ready(function() { ...@@ -23,7 +23,7 @@ $(document).ready(function() {
"title":"Date", "width":"15%"}, "title":"Date", "width":"15%"},
{data:"maxdate", {data:"maxdate",
"render":function (data, type, row) { "render":function (data) {
my = new Date(data); my = new Date(data);
var today = new Date(); var today = new Date();
...@@ -37,7 +37,7 @@ $(document).ready(function() { ...@@ -37,7 +37,7 @@ $(document).ready(function() {
{data:"purchase_ok", "width":"5%", {data:"purchase_ok", "width":"5%",
"title":"Achetable", "className":"dt-body-center", "title":"Achetable", "className":"dt-body-center",
"render": function (data, type, full, meta) { "render": function (data) {
if (data == true) { if (data == true) {
return '<div><input type="checkbox" id="bt_dontPurchase" checked><div>'; return '<div><input type="checkbox" id="bt_dontPurchase" checked><div>';
...@@ -48,14 +48,14 @@ $(document).ready(function() { ...@@ -48,14 +48,14 @@ $(document).ready(function() {
}, },
{data:"reception_status", "width":"5%", {data:"reception_status", "width":"5%",
"title":"Rupture", "className":"dt-body-center", "title":"Rupture", "className":"dt-body-center",
"render": function (data, type, full, meta) { "render": function () {
return "<div><button id='bt_change' href='#'>Stock à 0</button></div>"; return "<div><button id='bt_change' href='#'>Stock à 0</button></div>";
} }
}, },
{data:"reception_status", "width":"5%", {data:"reception_status", "width":"5%",
"title":"Archive", "className":"dt-body-center", "title":"Archive", "className":"dt-body-center",
"render": function (data, type, full, meta) { "render": function () {
return "<div><button id='bt_archive' href='#'>Archive</button></div>"; return "<div><button id='bt_archive' href='#'>Archive</button></div>";
} }
} }
...@@ -103,7 +103,7 @@ $(document).on('click', '#dp_Search', function() { ...@@ -103,7 +103,7 @@ $(document).on('click', '#dp_Search', function() {
search_table_article(); search_table_article();
}); });
var csrftoken; var csrftoken ='';
$(document).ready(function() { $(document).ready(function() {
csrftoken = getCookie('csrftoken'); csrftoken = getCookie('csrftoken');
...@@ -137,7 +137,7 @@ function actionButton (vUrl, jIdArcticle, followPage) { ...@@ -137,7 +137,7 @@ function actionButton (vUrl, jIdArcticle, followPage) {
traditional: true, traditional: true,
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
data: JSON.stringify(jIdArcticle), data: JSON.stringify(jIdArcticle),
success: function(data) { success: function() {
document.location.href = followPage; document.location.href = followPage;
}, },
error: function(resultat, statut, erreur) { error: function(resultat, statut, erreur) {
...@@ -147,7 +147,7 @@ function actionButton (vUrl, jIdArcticle, followPage) { ...@@ -147,7 +147,7 @@ function actionButton (vUrl, jIdArcticle, followPage) {
}); });
} }
var selArctileData; var selArctileData = null;
// Fenetre de validation sur l'article // Fenetre de validation sur l'article
......
...@@ -161,7 +161,7 @@ ...@@ -161,7 +161,7 @@
<div class="validation_wrapper col-6"> <div class="validation_wrapper col-6">
<section id="service_validation" class="col-6 grid-6 has-gutter"> <section id="service_validation" class="col-6 grid-6 has-gutter">
<div class="col-2"></div> <div class="col-2"></div>
<a class="col-2 btn present">Présent.e</a> <a class="col-2 btn present">{{CONFIRME_PRESENT_BTN|safe}}</a>
<span class="loading2"><img width="75" src="/static/img/Pedro_luis_romani_ruiz.gif" alt="Chargement en cours...." /></span> <span class="loading2"><img width="75" src="/static/img/Pedro_luis_romani_ruiz.gif" alt="Chargement en cours...." /></span>
<div class="col-2"></div> <div class="col-2"></div>
</section> </section>
......
...@@ -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>
......
...@@ -54,6 +54,7 @@ ...@@ -54,6 +54,7 @@
<script src="{% static "js/all_common.js" %}?v="></script> <script src="{% static "js/all_common.js" %}?v="></script>
<script type="text/javascript"> <script type="text/javascript">
var merge_orders_pswd = '{{merge_orders_pswd}}'; var merge_orders_pswd = '{{merge_orders_pswd}}';
var server_stored_groups = {{server_stored_groups}};
</script> </script>
<script src="{% static "js/common.js" %}?v="></script> <script src="{% static "js/common.js" %}?v="></script>
{% endblock %} {% endblock %}
...@@ -120,9 +120,7 @@ ...@@ -120,9 +120,7 @@
<hr /> <hr />
<p class="txtleft"> <p class="txtleft">
Ici, vous pouvez signaler toute anomalie lors d'une réception, les produits non commandés, cassés ou pourris. {{RECEPTION_PB}}
Merci d'indiquer un maximum d'informations, le nom du produit et son code barre.
Dans le cas de produits déteriorés, merci d'envoyer une photo avec votre téléphone à <strong>{{RECEPTION_PB_EMAIL}}</strong>
</p> </p>
<br> <br>
<textarea id="error_report"></textarea> <textarea id="error_report"></textarea>
......
{% 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>
<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>
{% 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