Commit da6ff995 by François C.

Merge branch 'dev_principale' into 'master'

Dev principale

See merge request !106
parents 39a5c17f 56d1bbd3
Pipeline #1716 passed with stage
in 1 minute 33 seconds
/static/
/outils/static/
\ No newline at end of file
......@@ -41,7 +41,7 @@ module.exports = {
"block-scoped-var": "off",
"block-spacing": "warn",
"brace-style": "warn",
"callback-return": "warn",
"callback-return": "off",
"camelcase": "off",
"capitalized-comments": "off",
"class-methods-use-this": "error",
......
......@@ -17,3 +17,5 @@ db.sqlite3
shop/shop_admin_settings.json
shop/errors.log
.idea
members/settings.json
.DS_Store
stages:
- test
variables:
COUCHDB_USER: "ci-only"
COUCHDB_PASSWORD: "ci-only"
DJANGO_SETTINGS_MODULE: "outils.settings"
lint:js:
stage: test
image: node:14
cache:
key: node-modules
......@@ -6,3 +15,19 @@ lint:js:
- node_modules/
script:
- make lint-js
unit:django:
stage: test
image: python:3
services:
- couchdb:3
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/pip-cache"
cache:
paths:
- "$CI_PROJECT_DIR/pip-cache"
before_script:
- ./scripts/ci/unit_setup.sh
script:
- python manage.py test
## Add odoo user login button
- In the template, include "conn_admin.html" as following :
```
{% block content %}
{% include "common/conn_admin.html" %}
```
- In the JS code, use the following pattern (for example) :
```
$(document).ready(function() {
if (coop_is_connected()) {
show content
}
}
```
......@@ -2,6 +2,7 @@
MAG_NAME = 'Cleme'
OFFICE_NAME = ''
MAX_BEGIN_HOUR = '19:00'
COMPANY_CODE = 'lacagette'
COMPANY_NAME = 'La Cagette'
WELCOME_ENTRANCE_MSG = 'Bienvenue à La Cagette !'
WELCOME_MAIL_SUBJECT = 'Dernière étape de votre inscription à la Cagette.'
......@@ -54,6 +55,10 @@ EM_URL = ''
RECEPTION_MERGE_ORDERS_PSWD = 'jpsrcp'
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."
DISPLAY_COL_AUTRES = False
DAV_PATH = '/shared_dir/dav/'
TOOLS_SERVER = 'https://outils.lacagette-coop.fr'
......@@ -91,19 +96,43 @@ DISCOUNT_SHELFS_IDS = [74]
FL_SHELFS = [16, 17, 18]
VRAC_SHELFS = [20, 38]
SHIFT_EXCHANGE_DAYS_TO_HIDE = ''
SHIFT_EXCHANGE_DAYS_TO_HIDE = '0'
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_EXTRA_BUTTONS_DISPLAY = False
ENTRANCE_EASY_SHIFT_VALIDATE = True
ENTRANCE_MISSED_SHIFT_BEGIN_MSG = """La période pendant laquelle il est possible de s'enregistrer est close.<br />
Merci de remplir le formulaire <em>"arrivé·e en retard"</em> <br/>
que vous trouverez <em>sur le site internet de La Cagette</em>
dans la rubrique<br />
"Espace Membre" > "J\'ai un problème ou une demande".<br/>
Le bureau des membres traitera votre demande !'
"""
ENTRANCE_EASY_SHIFT_VALIDATE_MSG = """Si vous faites un service dans un comité, merci de <br/>
valider votre présence en cherchant<br/>
votre nom ou numéro ci-dessous
"""
# Members space / shifts
UNSUBSCRIBED_FORM_LINK = 'https://docs.google.com/forms/d/e/1FAIpQLScWcpls-ruYIp7HdrjRF1B1TyuzdqhvlUIcUWynbEujfj3dTg/viewform'
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]"
# display or not column "Autres" in reception process
DISPLAY_COL_AUTRES = False
# Should block service exchange if old service is happening in less than 24h
BLOCK_SERVICE_EXCHANGE_24H_BEFORE = True
# URL to the metabase dashboard for orders helper
ORDERS_HELPER_METABASE_URL = "url_meta_base"
# New members space
USE_NEW_MEMBERS_SPACE = True
START_DATE_FOR_SHIFTS_HISTORY = "2018-01-01"
AMNISTIE_DATE= "2021-11-24 00:00:00"
......@@ -6,6 +6,7 @@ OPEN_ON_SUNDAY = True
MAG_NAME = ''
OFFICE_NAME = ''
COMPANY_CODE = 'lgds'
COMPANY_NAME = 'Les Grains de Sel'
MAX_BEGIN_HOUR = '19:00'
WELCOME_ENTRANCE_MSG = 'Bienvenue aux Grains de Sel!'
......@@ -92,3 +93,6 @@ ENTRANCE_FTOP_BUTTON_DISPLAY = False
CUSTOM_CSS_FILES = {'all': ['common_lgds.css'],
'members': ['inscription_lgds.css','member_lgds.css']}
# Should block service exchange if old service is happening in less than 24h
BLOCK_SERVICE_EXCHANGE_24H_BEFORE = False
\ No newline at end of file
"""Company specific data values."""
COMPANY_CODE = 'supercafoutch'
"""Odoo coop specific constants ."""
EMAIL_DOMAIN = 'supercafoutch.fr'
......@@ -88,3 +89,6 @@ PROMOTE_SHELFS_IDS = []
DISCOUNT_SHELFS_IDS = []
FL_SHELFS = []
VRAC_SHELFS = []
# Should block service exchange if old service is happening in less than 24h
BLOCK_SERVICE_EXCHANGE_24H_BEFORE = False
\ No newline at end of file
Received reception data are stored in this directory (could be desactivated if RECEPTION_DATA_BACKUP is set to False in config.py)
\ No newline at end of file
......@@ -91,7 +91,7 @@ class CagetteEnvelops(models.Model):
if not ('error' in res):
try:
if int(float(data['amount']) * 100) == int(float(invoice['residual_signed']) * 100):
# This payment is what it was left to pay, so change invoice state
# This payment is what was left to pay, so change invoice state
self.o_api.update('account.invoice', [invoice['id']], {'state': 'paid'})
except Exception as e:
res['error'] = repr(e)
......
......@@ -16,6 +16,10 @@ function toggle_success_alert() {
$('#envelop_cashing_success').toggle(250);
}
function toggle_deleted_alert() {
$('#envelop_deletion_success').toggle(250);
}
// Set an envelop content on the document
function set_envelop_dom(envelop, envelop_name, envelop_content_id, envelop_index) {
var envelops_section = $('#' + envelop.type + '_envelops');
......@@ -39,7 +43,7 @@ function set_envelop_dom(envelop, envelop_name, envelop_content_id, envelop_inde
}
new_html += '</div>'
+ '<div class="panel"><ol id="' + envelop_content_id + '"></ol></div>'
+ '<div class="panel panel_' + envelop_content_id + '"><ol id="' + envelop_content_id + '"></ol></div>'
+ '</div>';
$(new_html).appendTo(envelops_section);
......@@ -58,6 +62,9 @@ function set_envelop_dom(envelop, envelop_name, envelop_content_id, envelop_inde
li_node.appendChild(textnode); // Append the text to <li>
document.getElementById(envelop_content_id).appendChild(li_node);
}
let envelop_panel = $(`.panel_${envelop_content_id}`);
envelop_panel.append('<button class="btn--danger delete_envelop_button item-fluid" onClick="openModal(\'<h3>Supprimer enveloppe ?</h3>\', function() {delete_envelop(\'' + envelop.type + '\', ' + envelop_index + ');}, \'Supprimer\')">Supprimer l\'enveloppe</button>');
}
// Set the envelops data according to their type
......@@ -118,67 +125,94 @@ function set_envelops(envelops) {
}
}
function archive_envelop(type, index) {
$('#envelop_cashing_error').hide();
$('#envelop_cashing_success').hide();
// Loading on
openModal();
function delete_envelop(type, index) {
if (is_time_to('delete_envelop', 1000)) {
openModal();
var envelop = null;
if (type == "cash") {
envelop = cash_envelops[index];
} else {
envelop = ch_envelops[index];
}
envelop._deleted = true;
dbc.put(envelop, function callback(err, result) {
if (!err && result !== undefined) {
toggle_deleted_alert();
get_envelops();
} else {
alert("Erreur lors de la suppression de l'enveloppe... Essaye de recharger la page et réessaye.");
console.log(err);
}
});
if (type == "cash") {
envelop = cash_envelops[index];
} else {
envelop = ch_envelops[index];
}
}
// Proceed to envelop cashing
$.ajax({
type: "POST",
url: "/envelops/archive_envelop",
headers: { "X-CSRFToken": getCookie("csrftoken") },
dataType: "json",
traditional: true,
contentType: "application/json; charset=utf-8",
data: JSON.stringify(envelop),
success: function(response) {
closeModal();
var display_success_alert = true;
// Handle errors when saving payments
var error_payments = response.error_payments;
var error_message = "";
for (var i = 0; i < error_payments.length; i++) {
if (error_payments[i].done == false) {
error_message += "<p>Erreur lors de l'enregistrement du paiement de <b>" + error_payments[i]['partner_name']
+ "</b> (id odoo : " + error_payments[i]['partner_id'] + ", valeur à encaisser : " + error_payments[i]['amount'] + "€).";
error_message += "<br/><b>L'opération est à reprendre manuellement dans Odoo pour ce paiement.</b></p>";
function archive_envelop(type, index) {
if (is_time_to('archive_envelop', 1000)) {
$('#envelop_cashing_error').hide();
$('#envelop_cashing_success').hide();
// Loading on
openModal();
if (type == "cash") {
envelop = cash_envelops[index];
} else {
envelop = ch_envelops[index];
}
// Proceed to envelop cashing
$.ajax({
type: "POST",
url: "/envelops/archive_envelop",
headers: { "X-CSRFToken": getCookie("csrftoken") },
dataType: "json",
traditional: true,
contentType: "application/json; charset=utf-8",
data: JSON.stringify(envelop),
success: function(response) {
closeModal();
var display_success_alert = true;
// Handle errors when saving payments
var error_payments = response.error_payments;
var error_message = "";
for (var i = 0; i < error_payments.length; i++) {
if (error_payments[i].done == false) {
error_message += "<p>Erreur lors de l'enregistrement du paiement de <b>" + error_payments[i]['partner_name']
+ "</b> (id odoo : " + error_payments[i]['partner_id'] + ", valeur à encaisser : " + error_payments[i]['amount'] + "€).";
error_message += "<br/><b>L'opération est à reprendre manuellement dans Odoo pour ce paiement.</b></p>";
}
}
}
// If error during envelop deletion
var response_envelop = response.envelop;
// If error during envelop deletion
var response_envelop = response.envelop;
if (response_envelop == "error") {
error_message += "<p>Erreur lors de la suppression de l'enveloppe.<br/>";
error_message += "<b>Sauf contre-indication explicite, les paiements ont bien été enregistrés.</b><br/>";
error_message += "Les paiements déjà comptabilisés ne le seront pas à nouveau, vous pouvez ré-essayer. Si l'erreur persiste, l'enveloppe devra être supprimée manuellement.</p>";
display_success_alert = false;
}
if (response_envelop == "error") {
error_message += "<p>Erreur lors de la suppression de l'enveloppe.<br/>";
error_message += "<b>Sauf contre-indication explicite, les paiements ont bien été enregistrés.</b><br/>";
error_message += "Les paiements déjà comptabilisés ne le seront pas à nouveau, vous pouvez ré-essayer. Si l'erreur persiste, l'enveloppe devra être supprimée manuellement.</p>";
display_success_alert = false;
}
if (error_message !== "") {
$('#error_alert_txt').html(error_message);
toggle_error_alert();
}
if (error_message !== "") {
$('#error_alert_txt').html(error_message);
toggle_error_alert();
}
if (display_success_alert) {
toggle_success_alert();
if (display_success_alert) {
toggle_success_alert();
}
},
error: function() {
closeModal();
alert('Erreur serveur. Merci de ne pas ré-encaisser l\'enveloppe qui a causé l\'erreur.');
}
},
error: function() {
closeModal();
alert('Erreur serveur. Merci de ne pas ré-encaisser l\'enveloppe qui a causé l\'erreur.');
}
});
});
}
}
// Get all the envelops from couch db
......
from django.test import SimpleTestCase
\ No newline at end of file
from django.test import SimpleTestCase
\ No newline at end of file
from django.test import SimpleTestCase
\ No newline at end of file
......@@ -52,7 +52,7 @@ class CagetteInventory(models.Model):
file_data = json.load(json_file)
date_time = datetime.fromtimestamp(int(filename))
d = date_time.strftime("%m/%d/%Y, %H:%M")
d = date_time.strftime("%d/%m/%Y, %H:%M")
file_data['id'] = int(filename)
file_data['datetime_created'] = d
......@@ -113,7 +113,7 @@ class CagetteInventory(models.Model):
return file_data['inventory_status']
@staticmethod
def create_custom_inv_file(line_ids, line_type):
def create_custom_inv_file(line_ids, line_type, default_partners_id=[]):
res = {}
try:
......@@ -127,36 +127,54 @@ class CagetteInventory(models.Model):
api = OdooAPI()
ids = []
order = ['', '']
user = partner = ''
fields = ['create_uid', 'product_id', 'partner_id']
cond = [['id', 'in', line_ids]]
if (line_type == 'cpo'):
model = 'computed.purchase.order.line'
fields += ['computed_purchase_order_id']
user = ''
partners = []
if len(default_partners_id) > 0:
f = ['name']
c = [['id', 'in', default_partners_id]]
partners_name = api.search_read('res.partner', c, f)
for p in partners_name:
partners.append(p['name'])
if line_type == 'product_templates':
fields = ['id']
cond = [['product_tmpl_id', 'in', line_ids]]
model = 'product.product'
user="api"
else:
model = 'purchase.order.line'
fields += ['order_id']
fields = ['create_uid', 'product_id', 'partner_id']
cond = [['id', 'in', line_ids]]
if (line_type == 'cpo'):
model = 'computed.purchase.order.line'
fields += ['computed_purchase_order_id']
else:
model = 'purchase.order.line'
fields += ['order_id']
lines = api.search_read(model, cond, fields)
if len(lines) == len(line_ids):
for l in lines:
ids.append(l['product_id'][0])
user = l['create_uid'][1]
if (line_type == 'cpo'):
order = l['computed_purchase_order_id']
if line_type == 'product_templates':
ids.append(l['id'])
else:
order = l['order_id']
partner = l['partner_id'][1]
ids.append(l['product_id'][0])
user = l['create_uid'][1]
if (line_type == 'cpo'):
order = l['computed_purchase_order_id']
else:
order = l['order_id']
partners.append(l['partner_id'][1])
if (line_type == 'cpo'):
# partner_id isn't defined
f = ['partner_id']
c = [['id', '=', int(order[0])]]
cpo = api.search_read('computed.purchase.order', c, f)
if len(cpo) > 0:
partner = cpo[0]['partner_id'][1]
partners.append(cpo[0]['partner_id'][1])
file_data = {
'order': order[1],
'user': user,
'partner': partner,
'partners': partners,
'inventory_status': '',
'products': ids
}
......
......@@ -4,6 +4,13 @@ var shelfs_table = null,
function init_datatable() {
// For a smooth migration...
for (const i in lists) {
if (('partners' in lists[i]) === false) {
lists[i]['partners'] = [lists[i]['partner']];
}
}
return $('#lists').DataTable({
data: lists, // data passed at page loading
rowId: 'id',
......@@ -17,8 +24,16 @@ function init_datatable() {
}
},
{
data:"partner",
title:"Fournisseur"
data:"partners",
title:"Fournisseur(s)",
render: function (data) {
res = "";
for (const i in data) {
res += `${data[i]}<br/>`;
}
return res;
}
},
{
data:"order",
......
from django.test import TestCase
# Create your tests here.
from django.test import SimpleTestCase
\ No newline at end of file
from django.test import SimpleTestCase
\ No newline at end of file
from django.test import SimpleTestCase
from django.test import Client
class TestUrls(SimpleTestCase):
def testInventoryUrlIsResolved(self):
c = Client()
response = c.get('/reception/')
assert response.status_code == 200, "Inventory url is not resolved"
from django.test import SimpleTestCase
\ No newline at end of file
......@@ -43,7 +43,6 @@ def custom_list_inventory(request, id):
products = CagetteInventory.get_custom_list_products(id)
if 'error' in products:
print(products)
products['data'] = []
context = {'title': 'Inventaire',
......@@ -112,10 +111,25 @@ def do_custom_list_inventory(request):
def generate_inventory_list(request):
"""Responding to Odoo ajax call (no csrf)."""
res = {}
default_partners_id = []
try:
lines = json.loads(request.POST.get('lines'))
ltype = request.POST.get('type')
res = CagetteInventory.create_custom_inv_file(lines, ltype)
except Exception as e:
try:
# POST.get() returns None when request from django
data = json.loads(request.body.decode())
lines = data["lines"]
ltype = data["type"]
if "partners_id" in data:
default_partners_id = data["partners_id"]
except Exception as ee:
res['error'] = str(ee)
coop_looger.error("generate_inventory_list : %s", str(e))
return JsonResponse(res, status=500)
try:
res = CagetteInventory.create_custom_inv_file(lines, ltype, default_partners_id)
except Exception as e:
res['error'] = str(e)
coop_looger.error("generate_inventory_list : %s", str(e))
......
......@@ -36,14 +36,16 @@ video {max-width:none;}
#barcode_base {width:50px;float:left;}
.coop-info {min-width: 300px;padding:5px;}
#lat_menu button {margin-bottom:5px;}
.col-6.big {font-size:200%; border: 2px solid red; padding:10px; text-align:center; background: #ffffff;}
.col-6.big {font-size:200%; border: 2px solid red; padding:10px; text-align:center; background: #FFF;}
#cooperative_state {font-size:150%; font-weight:bold;}
h1 .member_name {font-weight: bold;}
#current_shift_title, .members_list
{border:1px solid #000; border-radius: 5px; padding:5px; margin-bottom:15px;background:#FFF;}
.members_list {list-style: none;}
.members_list li {display:block;margin-bottom:5px;}
.members_list li.btn--inverse {background: #449d44 !important; cursor:not-allowed;}
.members_list li.btn--inverse {background: #449d44; cursor:not-allowed; color: #FFF; }
.members_list li.btn--inverse.late {background-color: #de9b00; color: white}
#service_entry_success {font-size: x-large;}
#service_entry_success .explanations {margin: 25px 0; font-size: 18px;}
......@@ -58,7 +60,8 @@ h1 .member_name {font-weight: bold;}
#member_slide .btn[data-next]
{margin-top: 35px; background: #449d44; color:#FFF;}
[data-next="rattrapage_1"] {float:right;color:#171A17 !important; margin-top: 25px;}
#service_en_cours .info a {line-height: 24px; font-size:18px; margin-top:15px;}
#service_en_cours .info {font-size: 26px;}
#service_en_cours .info a {line-height: 24px; font-size:14px; margin-top:15px;}
.outside_list a {margin-left:15px;}
#rattrapage_1 .advice {margin-top:15px;}
.btn.present {background:#50C878;}
......@@ -67,3 +70,5 @@ h1 .member_name {font-weight: bold;}
.msg-big {font-size: xx-large; background: #fff; padding:25px; text-align: center;}
#member_advice {background: #FFF; color: red;}
.easy_shift_validate {text-align: center; margin-top: 3em;}
.page_body{
position: relative;
}
.header {
margin: 1.5rem 0;
}
.login_area {
position: absolute;
top: 0;
left: 0;
right: 0;
}
.tabs {
margin-top: 1em;
margin-bottom: 1em;
overflow: hidden;
}
.tabs .tab {
background-color: #f1f1f1;
border: 1px solid #ccc;
outline: none;
cursor: pointer;
padding: 14px 16px;
transition: 0.3s;
}
.tabs .tab:hover {
background-color: #ccc;
}
.tabs .active {
background-color: transparent;
border: 1px solid #ccc;
border-width: 1px 0 0 0;
}
.tabs .active:hover {
background-color: white;
}
.tab_content {
animation: fadeEffect 1s; /* Fading effect takes 1 second */
}
/* Go from zero to full opacity */
@keyframes fadeEffect {
from {opacity: 0;}
to {opacity: 1;}
}
#tab_makeups_content {
padding: 2rem 0;
}
#table_top_area {
display: flex;
justify-content: space-between;
}
#decrement_selected_members_makeups {
display: none;
}
.table_area {
margin-top: 20px;
}
.decrement_makeup {
margin-left: 10px;
}
.decrement_makeup, .increment_makeup {
padding: 0.4rem 1.25rem;
}
.select_member_cb {
cursor: pointer;
}
/* Search membres area */
#add_members_area {
margin-top: 30px;
}
#add_members_form_area {
display:flex;
align-items: center;
}
#search_member_form {
margin-left: 10px;
}
.search_member_results_area {
margin-top: 15px;
display: flex;
align-items: center;
}
.search_member_results {
display: flex;
flex-wrap: wrap;
}
.btn_possible_member {
margin: 0 1rem;
}
\ No newline at end of file
......@@ -55,10 +55,10 @@ sync.on('change', function (info) {
update_completed_count();
online = true;
})
.on('denied', function (err) {
.on('denied', function () {
// a document failed to replicate (e.g. due to permissions)
})
.on('complete', function (info) {
.on('complete', function () {
// handle complete
})
.on('error', function (err) {
......@@ -87,9 +87,7 @@ function new_coop_validation() {
coop_list_view.hide();
schoice_view.hide();
ncoop_view.hide();
var barcode_base = current_coop.barcode_base;
var st = get_shift_name(current_coop.shift_template.data);
//coop_registration_details.find('.numbox').text('N° '+ barcode_base);
coop_registration_details.find('.shift_template').text(st);
process_state.html(current_coop.firstname + ' ' +current_coop.lastname);
......@@ -148,12 +146,13 @@ function _really_save_new_coop(email, fname, lname, cap, pm, cn, bc, msex) {
if (email != current_email) {
//delete current_coop after copying revelant data
dbc.remove(current_email, coop._rev, function(err, response) {
dbc.remove(current_email, coop._rev, function(err) {
if (err) {
return console.log(err);
}
//console.log(response);
return null;
});
delete coop._rev;
......@@ -414,6 +413,8 @@ function get_latest_odoo_coop_bb() {
return latest_odoo_coop_bb;
}
return null;
}
function generate_email() {
......@@ -461,7 +462,7 @@ function setLocalInProcess(lip) {
localStorage.setItem("in_process", JSON.stringify(lip));
}
function keep_in_process_work(event) {
function keep_in_process_work() {
//If data registration is in process, save it in localStorage
if (current_coop != null && typeof (current_coop.shift_template) == "undefined") {
local_in_process = getLocalInProcess();
......
......@@ -9,7 +9,7 @@ if (coop_is_connected()) {
env_template = $('#templates [data-type="envelops"]');
// PouchDB sync actions listeners
sync.on('change', function (info) {
sync.on('change', function () {
// handle change
......@@ -27,10 +27,10 @@ if (coop_is_connected()) {
//update_completed_count();
online = true;
})
.on('denied', function (err) {
.on('denied', function () {
// a document failed to replicate (e.g. due to permissions)
})
.on('complete', function (info) {
.on('complete', function () {
// handle complete
})
.on('error', function (err) {
......@@ -333,7 +333,7 @@ if (coop_is_connected()) {
dsha1 = sha1(jss),
member = {_id: dsha1, data: data, hash: dsha1 };
dbc.put(member, function callback(err, result) {
dbc.put(member, function callback(err) {
if (!err) {
closeModal();
page_cleaning();
......@@ -409,6 +409,7 @@ if (coop_is_connected()) {
$('.item').click(display_possible_actions);
// dispatch_coops_in_boxes();
return null;
});
console.log(problematic_members);
......
......@@ -46,10 +46,10 @@ sync.on('change', function (info) {
// replicate resumed (e.g. new changes replicating, user went back online)
online = true;
})
.on('denied', function (err) {
.on('denied', function () {
// a document failed to replicate (e.g. due to permissions)
})
.on('complete', function (info) {
.on('complete', function () {
// handle complete
})
.on('error', function (err) {
......@@ -113,7 +113,7 @@ function put_current_coop_in_buffer_db(callback) {
var can_continue = true;
if (typeof current_coop._old_id != "undefined") {
dbc.remove(current_coop._old_id, current_coop._rev, function(err, response) {
dbc.remove(current_coop._old_id, current_coop._rev, function(err) {
if (err) {
console.log(err); can_continue = false;
}
......@@ -131,7 +131,6 @@ function put_current_coop_in_buffer_db(callback) {
function process_new_warning(event) {
event.preventDefault();
var msg = warning_msg.val();
var btn = $(event.target).find('button');
openModal();
if (msg.length > 0) {
......@@ -208,7 +207,7 @@ function submit_full_coop_form() {
}
post_form(
'/members/coop_validated_data', form_data,
function(err, result) {
function(err) {
if (!err) {
setTimeout(after_save, 1500);
} else {
......@@ -258,21 +257,21 @@ function save_current_coop(callback) {
sex_error = false;
if (/([0-9]{2})\/([0-9]{2})\/([0-9]{4})/.exec(birthdate)) {
try{
try {
var jj = RegExp.$1,
mm = RegExp.$2,
aaaa = RegExp.$3;
mm = RegExp.$2,
aaaa = RegExp.$3;
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())
{
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){
} catch (Exception) {
birthdate_error = true;
}
......@@ -551,6 +550,8 @@ function open_coop_form(e) {
console.error(error);
report_JS_error(error, 'prepa-odoo');
}
return null;
}
function ask_for_deletion() {
......@@ -693,6 +694,8 @@ function retrieve_all_coops() {
return b.timestamp - a.timestamp;
});
dispatch_coops_in_boxes();
return null;
});
} catch (err) {
error = {msg: err.name + ' : ' + err.message, ctx: 'retrieve_all_coops'};
......
......@@ -46,7 +46,8 @@ function display_current_coop_form() {
// form.find('[name="barcode_base"]').val(current_coop.barcode_base);
form.find('[name="email"]').val(current_coop._id);
if (current_coop.shift_template &&
current_coop.shift_template.data.type == 2) {
current_coop.shift_template.data.type == 2 &&
typeof manage_ftop != "undefined" && manage_ftop == true) {
$('#choosen_shift input').hide();
ftop_shift.val('Volant');
ftop_shift.show();
......
......@@ -92,18 +92,11 @@ function process_form_submission(event) {
openModal();
post_form(
'/members/coop_validated_data', form_data,
function(err, result) {
function(err) {
closeModal();
if (!err) {
var msg = "Vous êtes maintenant enregistré ! ";
msg += "<a href='" + em_url + "'>Cliquez ici</a> ";
msg += "pour découvrir l'espace membre";
$('p.intro').remove();
vform.remove();
display_msg_box(msg);
window.location.href = em_url;
}
}
......@@ -119,7 +112,7 @@ function process_form_submission(event) {
openModal();
post_form(
'/members/coop_warning_msg', data,
function(err, result) {
function(err) {
closeModal();
if (!err) {
$('#main_content').remove();
......
from django.test import TestCase
from django.test import SimpleTestCase
\ No newline at end of file
from django.test import SimpleTestCase
\ No newline at end of file
from django.test import SimpleTestCase
from django.test import Client
class TestUrls(SimpleTestCase):
def testMembersUrlIsResolved(self):
c = Client()
response = c.get('/members/')
assert response.status_code == 200, "Members url is not resolved"
from django.test import SimpleTestCase
\ No newline at end of file
......@@ -36,16 +36,24 @@ urlpatterns = [
url(r'^verify_final_state$', views.verify_final_state),
url(r'^update_couchdb_barcodes$', views.update_couchdb_barcodes),
# Borne accueil
url(r'^search/(.+)', views.search),
url(r'^search/([^\/.]+)/?([0-9]*)', views.search),
url(r'^save_photo/([0-9]+)$', views.save_photo, name='save_photo'),
url(r'^services_at_time/([0-9TZ\-\: \.]+)/([0-9\-]+)$', views.services_at_time),
url(r'^service_presence/$', views.record_service_presence),
url(r'^record_absences$', views.record_absences),
url(r'^record_absences/?([0-9\-\ \:]*)$', views.record_absences),
url(r'^close_ftop_service$', views.close_ftop_service),
url(r'^get_credentials$', views.get_credentials),
url(r'^remove_data_from_couchdb$', views.remove_data_from_CouchDB),
url(r'^image/([0-9]+)', views.getmemberimage),
url(r'^add_pts_to_everybody/([0-9]+)/([a-zA-Z0-9_ ]+)$', admin.add_pts_to_everybody),
url(r'^easy_validate_shift_presence$', views.easy_validate_shift_presence),
# conso / groupe recherche / socio
url(r'^panel_get_purchases$', views.panel_get_purchases),
# BDM
url(r'^save_partner_info$', views.save_partner_info),
# BDM - members admin
url(r'^admin$', admin.admin),
url(r'^get_makeups_members$', admin.get_makeups_members),
url(r'^update_members_makeups$', admin.update_members_makeups),
]
......@@ -24,8 +24,15 @@ def index(request):
'WELCOME_SUBTITLE_ENTRANCE_MSG': getattr(settings, 'WELCOME_SUBTITLE_ENTRANCE_MSG', ''),
'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'),
'CONFIRME_PRESENT_BTN' : getattr(settings, 'CONFIRME_PRESENT_BTN', 'Présent.e')
'ENTRANCE_MISSED_SHIFT_BEGIN_MSG': getattr(settings, 'ENTRANCE_MISSED_SHIFT_BEGIN_MSG',
"La période pendant laquelle il est possible de s'enregistrer est close."),
'ENTRANCE_EASY_SHIFT_VALIDATE_MSG': getattr(settings, 'ENTRANCE_EASY_SHIFT_VALIDATE_MSG',
'Je valide mon service "Comité"'),
'CONFIRME_PRESENT_BTN' : getattr(settings, 'CONFIRME_PRESENT_BTN', 'Présent.e'),
'LATE_MODE': getattr(settings, 'ENTRANCE_WITH_LATE_MODE', False),
'ENTRANCE_VALIDATE_PRESENCE_MESSAGE' : getattr(settings, 'ENTRANCE_VALIDATE_PRESENCE_MESSAGE', '')
}
for_shoping_msg = getattr(settings, 'ENTRANCE_COME_FOR_SHOPING_MSG', '')
msettings = MConfig.get_settings('members')
......@@ -33,6 +40,15 @@ def index(request):
for_shoping_msg = msettings['msg_accueil']['value']
context['ENTRANCE_COME_FOR_SHOPING_MSG'] = for_shoping_msg
context['ftop_btn_display'] = getattr(settings, 'ENTRANCE_FTOP_BUTTON_DISPLAY', True)
context['extra_btns_display'] = getattr(settings, 'ENTRANCE_EXTRA_BUTTONS_DISPLAY', True)
context['easy_shift_validate'] = getattr(settings, 'ENTRANCE_EASY_SHIFT_VALIDATE', False)
if context['easy_shift_validate'] is True:
committees_shift_id = CagetteServices.get_committees_shift_id()
if committees_shift_id is None:
return HttpResponse("Le créneau des comités n'est pas configuré dans Odoo !")
else:
context['committees_shift_id'] = committees_shift_id
if 'no_picture_member_advice' in msettings:
if len(msettings['no_picture_member_advice']['value']) > 0:
context['no_picture_member_advice'] = msettings['no_picture_member_advice']['value']
......@@ -80,6 +96,7 @@ def inscriptions(request, type=1):
'open_on_sunday': getattr(settings, 'OPEN_ON_SUNDAY', False),
'POUCHDB_VERSION': getattr(settings, 'POUCHDB_VERSION', ''),
'max_chq_nb': getattr(settings, 'MAX_CHQ_NB', 12),
'show_ftop_button': getattr(settings, 'SHOW_FTOP_BUTTON', True),
'db': settings.COUCHDB['dbs']['member']}
response = HttpResponse(template.render(context, request))
......@@ -108,6 +125,7 @@ def prepa_odoo(request):
'ask_for_sex': getattr(settings, 'SUBSCRIPTION_ASK_FOR_SEX', False),
'ask_for_street2': getattr(settings, 'SUBSCRIPTION_ADD_STREET2', False),
'ask_for_second_phone': getattr(settings, 'SUBSCRIPTION_ADD_SECOND_PHONE', False),
'show_ftop_button': getattr(settings, 'SHOW_FTOP_BUTTON', True),
'db': settings.COUCHDB['dbs']['member']}
# with_addr_complement
......@@ -141,6 +159,7 @@ def validation_inscription(request, email):
'ask_for_sex': getattr(settings, 'SUBSCRIPTION_ASK_FOR_SEX', False),
'ask_for_street2': getattr(settings, 'SUBSCRIPTION_ADD_STREET2', False),
'ask_for_second_phone': getattr(settings, 'SUBSCRIPTION_ADD_SECOND_PHONE', False),
'show_ftop_button': getattr(settings, 'SHOW_FTOP_BUTTON', True),
'em_url': settings.EM_URL,
'WELCOME_ENTRANCE_MSG': settings.WELCOME_ENTRANCE_MSG,
'WELCOME_SUBTITLE_ENTRANCE_MSG': getattr(settings, 'WELCOME_SUBTITLE_ENTRANCE_MSG', '')}
......@@ -216,7 +235,7 @@ def update_couchdb_barcodes(request):
# Borne accueil
def search(request, needle):
def search(request, needle, shift_id):
"""Search member has been requested."""
try:
key = int(needle)
......@@ -228,7 +247,7 @@ def search(request, needle):
key = needle
k_type = 'name'
res = CagetteMember.search(k_type, key)
res = CagetteMember.search(k_type, key, shift_id)
return JsonResponse({'res': res})
......@@ -256,7 +275,15 @@ def record_service_presence(request):
mid = int(request.POST.get("mid", 0)) # member id
sid = int(request.POST.get("sid", 0)) # shift id
stid = int(request.POST.get("stid", 0)) # shift_ticket_id
app_env = getattr(settings, 'APP_ENV', "prod")
if (rid > -1 and mid > 0):
overrided_date = ""
if app_env != "prod":
import re
o_date = re.search(r'/([^\/]+?)$', request.META.get('HTTP_REFERER'))
if o_date:
overrided_date = re.sub(r'(%20)',' ', o_date.group(1))
# rid = 0 => C'est un rattrapage, sur le service
if sid > 0 and stid > 0:
# Add member to service and take presence into account
......@@ -264,7 +291,7 @@ def record_service_presence(request):
if res['rattrapage'] is True:
res['update'] = 'ok'
else:
if (CagetteServices.registration_done(rid) is True):
if (CagetteServices.registration_done(rid, overrided_date) is True):
res['update'] = 'ok'
else:
res['update'] = 'ko'
......@@ -283,8 +310,24 @@ def record_service_presence(request):
res['error'] = str(e)
return JsonResponse({'res': res})
def record_absences(request):
return JsonResponse({'res': CagetteServices.record_absences()})
def easy_validate_shift_presence(request):
"""Add a presence point if the request is valid."""
res = {}
try:
coop_id = int(request.POST.get("coop_id", "nan"))
res = CagetteServices.easy_validate_shift_presence(coop_id)
except Exception as e:
res['error'] = str(e)
if 'error' in res:
if res['error'] == "One point has been added less then 24 hours ago":
# TODO : use translation (all project wide)
res['error'] = "Vous ne pouvez pas valider plus d'un service par 24h"
return JsonResponse(res, status=500)
else:
return JsonResponse(res, safe=False)
def record_absences(request, date):
return JsonResponse({'res': CagetteServices.record_absences(date)})
def close_ftop_service(request):
"""Close the closest past FTOP service"""
......@@ -347,3 +390,23 @@ def panel_get_purchases(request):
message += ' ' + str(res['params'])
response = HttpResponse(message)
return response
# # # BDM # # #
def save_partner_info(request):
""" Endpoint the front-end will call for saving partner information """
res = {}
credentials = CagetteMember.get_credentials(request)
if ('success' in credentials):
data = {}
for post in request.POST:
if post != "idPartner" and data != "verif_token" :
data[post]= request.POST[post]
cm = CagetteMember(int(request.POST['idPartner']))
result = cm.save_partner_info(int(request.POST['idPartner']),data)
res['success']= result
return JsonResponse(res)
else:
res['error'] = "Forbidden"
return JsonResponse(res, safe=False)
from django.contrib import admin
# Register your models here.
from django.apps import AppConfig
class MembersSpaceConfig(AppConfig):
name = 'members_space'
from django.db import models
from outils.common_imports import *
from members.models import CagetteServices
from outils.common import OdooAPI
class CagetteMembersSpace(models.Model):
"""Class to manage othe members space"""
def __init__(self):
"""Init with odoo id."""
self.o_api = OdooAPI()
def is_comite(self, partner_id):
"""Check if partner is from comite."""
cond = [['partner_id.id', '=', partner_id]]
fields = ['shift_template_id', 'is_current']
shiftTemplate = self.o_api.search_read('shift.template.registration', cond, fields)
answer = False
if (shiftTemplate and len(shiftTemplate) > 0):
s_t_id = None
for s_t in shiftTemplate:
if s_t['is_current'] is True:
s_t_id = s_t['shift_template_id'][0]
if s_t_id == CagetteServices.get_committees_shift_id():
answer = True
return answer
def get_shifts_history(self, partner_id, limit, offset, date_from):
""" Get partner shifts history """
res = []
paginated_res = []
today = str(datetime.date.today())
try:
cond = [
['partner_id', '=', partner_id],
['date_begin', '>', date_from],
['date_begin', '<', today],
['state', '!=', 'draft'],
['state', '!=', 'open'],
['state', '!=', 'waiting'],
['state', '!=', 'replaced'],
['state', '!=', 'replacing'],
]
f = ['create_date', 'date_begin', 'shift_id', 'name', 'state', 'is_late', 'is_makeup']
marshal_none_error = 'cannot marshal None unless allow_none is enabled'
try:
res = self.o_api.search_read('shift.registration', cond, f, order='date_begin DESC')
except Exception as e:
if not (marshal_none_error in str(e)):
print(str(e))
coop_logger.error(repr(e) + ' : %s', str(partner_id))
else:
res = []
# Get committees shifts
committees_shifts_name = getattr(settings, 'ENTRANCE_ADD_PT_EVENT_NAME', 'Validation service comité')
cond = [
['partner_id', '=', partner_id],
['name', '=', committees_shifts_name]
]
f = ['create_date']
try:
res_committees_shifts = self.o_api.search_read('shift.counter.event', cond, f, order='create_date DESC')
for committee_shift in res_committees_shifts:
item = {
"create_date": committee_shift["create_date"],
"date_begin": committee_shift["create_date"],
"shift_id": False,
"name": "Services des comités",
"state": "done",
"is_late": False,
"is_makeup": False,
}
res.append(item)
except Exception as e:
if not (marshal_none_error in str(e)):
print(str(e))
coop_logger.error(repr(e) + ' : %s', str(partner_id))
else:
res = res + []
# Add amnesty line
is_amnesty = getattr(settings, 'AMNISTIE_DATE', False)
company_code = getattr(settings, 'COMPANY_CODE', '')
if is_amnesty and company_code == "lacagette":
amnesty = {}
amnesty['is_amnesty'] = True
amnesty['create_date'] = is_amnesty
amnesty['date_begin'] = is_amnesty
amnesty['shift_name'] = 'Amnistie'
amnesty['state'] = ''
res.append(amnesty)
# Ordering here is necessary for pagination
res.sort(key = lambda x: datetime.datetime.strptime(x['date_begin'], '%Y-%m-%d %H:%M:%S'), reverse=True)
# Paginate
end_index = offset + limit
paginated_res = res[offset:end_index]
except Exception as e:
coop_logger.error("get_shifts_history : %s", str(e))
return paginated_res
\ No newline at end of file
#faqBDM {
font-size: 1.8rem;
}
#faqBDM .block {
width: 100%;
}
.info_slots_shifts {
margin: 2rem 0;
}
.grp_text{
margin-top: 25px;
}
.param {margin-bottom: 15px;}
.param label {font-weight: bold;}
input.link {min-width: 50em;}
.submit_button {margin-bottom: 10px;}
/* Style the buttons that are used to open and close the accordion panel */
.accordion {
background-color: #eee;
color: #444;
cursor: pointer;
padding: 18px;
width: 100%;
text-align: left;
border: none;
outline: none;
transition: 0.4s;
}
/* Add a background color to the button if it is clicked on (add the .active class with JS), and when you move the mouse over it (hover) */
.active, .accordion:hover {
background-color: #ccc;
}
/* Style the accordion panel. Note: hidden by default */
.panel {
padding: 0 18px;
padding-bottom: 10px;
background-color: white;
display: none;
overflow: hidden;
border-left: 1px solid #E5E5E5;
border-right: 1px solid #E5E5E5;
border-bottom: 1px solid #E5E5E5;
border-bottom-left-radius: 15px;
border-bottom-right-radius: 15px;
}
button.accordion::after {
content: '\002B';
color: #777;
font-weight: bold;
float: right;
margin-left: 5px;
}
button.accordion.active::after {
content: "\2212";
}
.btn_faq{
white-space: normal;
border-radius: 5px;
margin-top: 3px;
padding-right: 25px;
display: flex;
align-items: flex-end;
}
.full_width{
width: 100%;
}
.faq_link_button_area {
margin-top: 10px;
height: 100%;
}
.faq_link_button {
white-space: normal;
border-radius: 5px;
}
.faq_link_button:hover {
color: #fff;
text-decoration: none;
}
.faq_intro_texts {
margin-bottom: 30px;
}
\ No newline at end of file
/* Add a black background color to the top navigation */
.topnav {
background-color: white;
overflow: hidden;
border-bottom: 1px solid #e7e9ed;
}
/* Style the links inside the navigation bar */
.topnav a {
float: left;
display: block;
color: #333;
text-align: center;
padding: 2rem 1.5rem;
text-decoration: none;
font-size: 17px;
font-weight: bold;
}
/* Change the color of links on hover */
.topnav a:hover {
background-color: #e7e9ed;
color: #333;
}
/* Add an active class to highlight the current page */
.topnav a.active {
background-color: #00a573;
color: white;
}
/* Hide the link that should open and close the topnav on small screens */
.topnav .icon {
display: none;
}
.pairs_info {
background-color: #00a573;
color: white;
padding: 1.5rem 1.2rem;
display: none;
}
@media screen and (max-width: 1146px) {
/* When the screen is less than 1146px pixels wide, hide all links, except for the first one ("Home"). Show the link that contains should open and close the topnav (.icon) */
.topnav a:not(:first-child) {display: none;}
.topnav a.icon {
float: right;
display: block;
}
/* The "responsive" class is added to the topnav with JavaScript when the user clicks on the icon. This class makes the topnav look good on small screens (display the links vertically instead of horizontally) */
.topnav.responsive {position: relative;}
.topnav.responsive a.icon {
position: absolute;
right: 0;
top: 0;
}
.topnav.responsive a {
float: none;
display: block;
text-align: left;
}
#deconnect {
float: none;
}
}
/* We override some styles defined on the home page for the specific needs of the My Info page */
#my_info {
font-size: 2rem;
}
#my_info_content {
display: flex;
flex-direction: column;
margin: 2rem 0;
}
.my_info_line {
display: flex;
flex-wrap: wrap;
width: 100%;
padding: 1.5rem 0;
}
@media screen and (min-width: 351px) and (max-width: 435px) {
.my_info_line {
font-size: 90%;
padding: 2vw 0;
}
}
@media screen and (max-width: 350px) {
.my_info_line {
font-size: 3.5vw;
padding: 2vw 0;
}
}
.my_info_line_left {
width: 50%;
text-align: right;
padding-right: 2rem;
font-weight: bold;
}
.my_info_line_right {
width: 50%;
padding-left: 2rem;
max-width: 100%;
word-break: break-all;
}
.my_info_line_middle {
width: 70%;
max-width: 100%;
word-break: normal;
text-align: center;
margin: 3rem 0;
}
#my_info #member_status_action,
#my_info .member_shift_name_area,
#my_info .member_coop_number_area {
margin-bottom: 0;
}
#my_info .member_shift_name_area {
margin-top: 0;
}
#attached_info {
display: flex;
flex-direction: column;
}
.member_phone_area {
display: flex;
flex-direction: column;
gap: 10px;
}
.edit-btn{
cursor: pointer;
}
#edit_address_form {
display: none ;
}
#edit_address_form #zip_form {
margin: 5px 0;
}
#edit_phone_form {
display: none ;
}
#edit_phone_form #mobile_form {
margin: 5px 0;
}
@media screen and (max-width: 992px) {
#my_info {
font-size: 1.7rem;
}
.my_info_line_left {
width: 30%;
padding-right: 1rem;
}
.my_info_line_right {
width: 70%;
padding-left: 1rem;
}
#attached_info .my_info_line_left {
width: 100%;
text-align: left;
padding: 0;
}
#attached_info .my_info_line_right {
width: 100%;
padding: 0;
}
#my_info .choose_makeups,
#my_info .unsuscribed_form_link {
white-space: normal;
}
#my_info .delay_date_stop_container {
white-space: nowrap;
}
#my_info .member_coop_number_area,
#my_info .member_shift_name_area {
align-items: flex-start;
}
}
.status_info_image {
display: block;
margin: 5rem auto 5rem auto;
width: 50%;
}
#my_shifts {
font-size: 1.8rem;
}
#incoming_shifts {
height: 100%;
flex-direction: column;
display: none;
}
.loading-history, .loading-incoming-shifts {
margin: 2rem 0;
}
#history {
display: flex;
flex-direction: column;
display: none;
}
table.dataTable tbody td {
text-overflow: ellipsis;
}
table.dataTable thead .sorting,
table.dataTable thead .sorting_asc,
table.dataTable thead .sorting_desc {
background : none;
}
table.dataTable.dtr-inline.collapsed>tbody>tr>td:first-child:before,
table.dataTable.dtr-inline.collapsed>tbody>tr>th:first-child:before {
color: #0275d8;
background-color: white;
font-weight: bold;
/* border: none;
font-size: 1.6rem;
height: 16px;
width: 16px;
border-radius: 2em; */
}
@media screen {
}
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td:first-child:before,
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th:first-child:before {
color: #d8534f;
background-color: white;
font-weight: bold;
/* border: none;
font-size: 1.6rem;
height: 16px;
width: 16px;
border-radius: 2em; */
}
.loading-more-history {
display: none;
}
.more_history {
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
font-size: 2rem;
}
.more_history_button {
height: 50px;
width: 50px;
border-radius: 50%;
}
table.dataTable.display tbody tr.row_partner_ok {
background-color: #8feb8b;
}
table.dataTable.display tbody tr.row_partner_late {
background-color: #ffdf7d;
}
table.dataTable.display tbody tr.row_partner_absent {
background-color: #ff847b;
}
table.dataTable.display tbody tr.row_partner_amnistie {
background-color: rgb(78, 78, 78);
color: white;
}
table.dataTable.display tbody tr td {
border-top: 1px solid rgb(119, 119, 119);
}
\ No newline at end of file
.shifts_exchange_page_content {
width: 95%;
margin: 3rem auto;
display: flex;
flex-direction: column;
position: relative;
}
/* -- Suspended screen */
#suspended_content, #unsuscribed_content {
align-items: center;
text-align: center;
}
#shifts_exchange .select_makeups, #shifts_exchange .unsuscribed_form_link, .cant_have_delay_form_link {
margin: 1.5rem 0;
}
/* -- Suspended can't have delay screen */
#suspended_cant_have_delay_content {
align-items: center;
text-align: center;
width: 50%;
}
@media screen and (max-width:992px) {
#suspended_cant_have_delay_content {
align-items: center;
text-align: center;
width: 90%;
}
}
/* -- Calendar screen, area on top of the calendar */
#calendar_top_info {
display: flex;
justify-content: space-between;
}
@media screen and (max-width:992px) {
#calendar_top_info {
display: flex;
flex-direction: column;
justify-content: space-between;
}
}
/* -- Calendar screen, shifts list */
#shifts_list {
flex-direction: column;
display: none;
width: min-content;
max-width: 100%;
white-space: nowrap;
}
@media screen and (max-width:992px) {
#partner_shifts_list {
display: flex;
flex-direction: column;
align-items: center;
}
}
.selectable_shift_line {
display: flex;
align-items: center;
margin-left: 15px;
margin: 0.75rem 0;
border-radius: 5px;
}
.selectable_shift_line .checkbox {
margin-right: 10px;
}
.selectable_shift_line.btn {
cursor: not-allowed;
}
/* -- Calendar screen, makeups message */
#need_to_select_makeups_message {
display: none;
align-self: center;
background-color: #d9534f;
color: white;
margin: 0 1rem 2rem 1rem;
padding: 1rem 1.25rem;
text-align: center;
}
.makeups_warning {
margin-right: 3px;
}
@media screen and (max-width:992px) {
.select_makeups_message_block {
display: block;
}
}
/* -- Calendar screen, calendar */
#calendar {
margin: 2rem 1rem;
}
.loading-calendar {
margin: 3rem auto;
display: none;
}
@media screen and (max-width:992px) {
#calendar {
display: none;
}
}
.fc .fc-event {
cursor: pointer;
margin: 1px 10px !important;
}
.fc-event {
background-color: #008AD9;
border-color: #008AD9;
color: white;
}
td{
--fc-list-event-hover-bg-color:#005BA6;
}
.fc-event.shift_booked {
background-color: #585858;
cursor: auto;
border-color: #585858;
}
.fc-event.shift_booked td {
--fc-list-event-hover-bg-color:#585858;
}
.fc-list-event.shift_booked {
color: white;
}
#calendar .fc-list-table {
table-layout: auto;
}
.resp-header-toolbar {
display: flex;
flex-direction: column;
}
.resp-header-toolbar .fc-toolbar-chunk {
text-align: center;
margin: 0.25rem;
}
.date_old_shift, .time_old_shift, .date_new_shift, .time_new_shift {
font-weight: bold;
}
/* -- Explainations */
#calendar_explaination_area {
max-width: 33%;
border: 2px solid #585858;
border-radius: 15px;
padding: 1rem;
}
.example-event {
max-width: 200px;
margin: 2rem 0 0.5rem 0;
font-size: 1.4rem !important;
padding: 0 !important;
}
@media screen and (max-width:992px) {
.example-event {
margin: 2rem auto 0.5rem auto;
}
}
.arrow_explanation_numbers {
margin: 0 3px;
}
#calendar_explaination_button {
max-width: 60%;
margin: 2rem auto 0.5rem auto;
}
\ No newline at end of file
body {
margin: 0;
}
.page_title {
margin: 35px 0 30px 0;
}
@media screen and (max-width: 435px) {
.page_title {
margin: 4vw 0 3vw 0;
}
}
/* -- Tiles */
.tiles_container {
display: flex;
flex-wrap: wrap;
}
@media screen and (max-width: 992px) {
.tiles_container {
flex-direction: column;
}
}
.tile {
flex: 1 0 45%;
display: flex;
flex-direction: column;
align-items: center;
border-radius: 30px;
margin: 1rem 1rem;
box-shadow: 2px 2px 3px rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.1);
}
.high_tile {
min-height: 350px;
}
.small_tile {
min-height: 250px;
}
.full_width_tile {
flex: 1 0 90%;
min-height: 100px;
}
.tile_title {
display: flex;
justify-content: center;
align-items: center;
border-bottom: 1px solid #e7e9ed;
font-size: 2.4rem;
padding: 2rem 0;
width: 80%;
}
.tile_content {
position: relative;
margin: 3rem 0;
width: 80%;
display: flex;
height: 100%;
}
#home_tile_services_exchange .tile_content {
height: 100%;
flex-direction: column;
align-items: center;
text-align: center;
}
/* -- My Shifts tile */
#home_tile_my_services .tile_content {
height: 100%;
flex-direction: column;
margin: auto;
padding: 2rem 0;
}
@media screen and (min-width: 769px) {
#home_tile_my_services .tile_content {
width: 50%;
}
}
#home_incoming_services {
min-height: 80px;
display: flex;
flex-direction: column;
}
.shift_line {
margin-left: 15px;
line-height: 2;
}
.shift_line_chevron {
color: #D9534F;
margin-right: 5px;
}
#go_to_shift_history_area {
width: 100%;
display: flex;
justify-content: center;
}
#home_go_to_shift_history {
width: 100%;
margin-top: 30px;
}
/* -- My Info tile */
#home_tile_my_info {
position: relative;
}
#home_tile_my_info .tile_content {
margin: 2rem 0;
}
.tile_icon {
margin-right: 15px;
color: #00a573;
}
#home_tile_my_info .tile_content {
height: 100%;
flex-direction: column;
align-items: center;
font-size: 1.6rem;
}
@media screen and (max-width: 576px) {
#home_tile_my_info .tile_content {
font-size: 1.4rem !important;
}
}
#home .member_info {
font-weight: bold;
}
.member_status_text_container {
margin-bottom: 5px;
}
#member_status_action {
display: flex;
margin-bottom: 20px;
}
@media screen and (max-width: 992px) {
#member_status_action {
margin-top: 5px;
margin-bottom: 10px;
}
}
.choose_makeups {
display: none;
font-size: 1.5rem;
}
.unsuscribed_form_link {
display: none;
text-decoration: none;
font-size: 1.7rem;
word-break: normal;
}
.unsuscribed_form_link:hover {
text-decoration: none;
}
@media (max-width: 435px) {
.unsuscribed_form_link {
font-size: 90%;
line-height: 7vw;
}
}
.member_status_up_to_date,
.member_status_exempted {
color: #5cb85c;
}
.member_status_alert,
.member_status_delay {
color: #f0ad4e;
}
.member_status_suspended,
.member_status_unsubscribed {
color: #d9534f;
}
.member_shift_name_area,
.member_coop_number_area {
margin-bottom: 10px;
}
.member_associated_partner_area {
line-height: 1.3;
}
@media screen and (max-width: 992px) {
.member_associated_partner_area {
display: flex;
flex-direction: column;
align-items: center;
}
}
.delay_date_stop_container {
color: #f0ad4e;
margin-top: -1rem;
margin-bottom: 1rem;
display: none;
}
#see_more_info {
white-space: normal;
}
#see_more_info_link {
width: 100%;
}
/* --Shifts exchange tile tile */
.home_link_button_area {
width: 100%;
display: flex;
justify-content: center;
height: 100%;
}
.home_link_button {
width: 80%;
margin: 30px auto auto auto;
white-space: normal;
}
/* -- I have a question tile */
#go_to_forms {
text-decoration: none;
}
#go_to_forms:hover {
color: white;
}
#go_to_forms.active {
color: white !important;
}
/* -- Shop info tile */
#shop_info_content {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
width: 80%;
margin: auto;
}
.shop_info_item {
width: 50%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-size: 2rem;
flex: 1 0 50%;
}
.shop_info_item h1,h2,h3,h4,h5,h6 {
font-size: 2rem;
}
.opening_hours_title {
margin-bottom: 10px;
font-size: 2.3rem;
font-weight: bold;
}
.shop_message_content {
text-align: center;
}
@media screen and (min-width: 769px) {
.shop_info_item {
padding: 0 4rem;
}
.shop_message {
border-left: 1px solid #e7e9ed;
margin: 3rem 0;
}
}
@media screen and (max-width: 992px) {
#shop_info_content {
flex-direction: column;
}
.shop_info_item {
flex: 1 0 50%;
width: 100%;
font-size: 1.6rem;
padding: 1.5rem 0;
}
.shop_info_item h1,h2,h3,h4,h5,h6 {
font-size: 1.6rem;
}
.opening_hours_title {
font-size: 1.9rem;
}
.shop_message {
border-top: 1px solid #e7e9ed;
}
.shop_message_content {
width: 90%;
}
}
/* - No content page */
.message_error {
padding: 7vw;
font-size: 1.8rem;
}
.no_content_title {
margin-bottom: 1.5rem;
}
function init_faq() {
$("#unsuscribe_form_link_btn").prop("href", unsuscribe_form_link);
$("#unsuscribe_form_link_btn2").prop("href", unsuscribe_form_link);
$("#change_template_form_link_btn").prop("href", change_template_form_link);
$("#template_unsubscribe_form_link_btn").prop("href", template_unsubscribe_form_link);
$("#late_service_form_link_btn").prop("href", late_service_form_link);
$("#sick_leave_form_link_btn").prop("href", sick_leave_form_link);
$("#associated_subscribe_form_link_btn").prop("href", associated_subscribe_form_link);
$("#associated_unsubscribe_form_link_btn").prop("href", associated_unsubscribe_form_link);
$("#covid_form_link_btn").prop("href", covid_form_link);
$("#covid_end_form_link_btn").prop("href", covid_end_form_link);
$("#underage_subscribe_form_link_btn").prop("href", underage_subscribe_form_link);
$("#change_email_form_link_btn").prop("href", change_email_form_link);
$("#coop_unsubscribe_form_link_btn").prop("href", coop_unsubscribe_form_link);
$("#helper_subscribe_form_link_btn").prop("href", helper_subscribe_form_link);
$("#helper_unsubscribe_form_link_btn").prop("href", helper_unsubscribe_form_link);
$("#request_form_link_btn2").prop("href", request_form_link);
$("#request_form_link_btn").prop("href", request_form_link);
}
$(document).on('click', "#shift_exchange_btn", () => {
goto('echange-de-services');
});
\ No newline at end of file
/**
* Toggle the navbar on mobile screens
*/
function toggleHeader() {
var x = document.getElementById("topnav");
if (x.className === "topnav") {
x.className += " responsive";
} else {
x.className = "topnav";
}
}
$(document).ready(function() {
// Navbar redirections
$('#nav_home').on('click', (e) => {
e.preventDefault();
if (current_location !== "home") {
goto('home');
}
if (document.getElementById("topnav").className !== "topnav") {
toggleHeader();
}
});
$('#nav_my_info').on('click', (e) => {
e.preventDefault();
if (current_location !== "my_info") {
goto('mes-infos');
}
toggleHeader();
});
$('#nav_my_shifts').on('click', (e) => {
e.preventDefault();
if (current_location !== "my_shifts") {
goto('mes-services');
}
toggleHeader();
});
$('#nav_faq').on('click', (e) => {
e.preventDefault();
if (current_location !== "faq") {
goto('faq');
}
toggleHeader();
});
$('#nav_shifts_exchange').on('click', (e) => {
e.preventDefault();
if (current_location !== "shifts_exchange") {
goto('echange-de-services');
}
toggleHeader();
});
$('#nav_calendar').prop("href", abcd_calendar_link);
$('#nav_calendar').on('click', () => {
toggleHeader();
});
if (partner_data.is_associated_people === "True") {
$(".pairs_info").show();
}
});
/**
* Request a 6 month delay
*/
function request_delay() {
return new Promise((resolve) => {
let today = new Date();
const delay_start = today.getFullYear()+'-'+(today.getMonth()+1)+'-'+today.getDate();
let today_plus_six_month = new Date();
today_plus_six_month.setMonth(today_plus_six_month.getMonth()+6);
const diff_time = Math.abs(today_plus_six_month - today);
const diff_days = Math.ceil(diff_time / (1000 * 60 * 60 * 24));
$.ajax({
type: 'POST',
url: "/shifts/request_delay",
dataType:"json",
data: {
verif_token: partner_data.verif_token,
idPartner: partner_data.partner_id,
start_date: delay_start,
duration: diff_days
},
success: function() {
partner_data.cooperative_state = 'delay';
partner_data.date_delay_stop = today_plus_six_month.getFullYear()+'-'+(today_plus_six_month.getMonth()+1)+'-'+today_plus_six_month.getDate();
resolve();
},
error: function(data) {
if (data.status == 403
&& typeof data.responseJSON != 'undefined'
&& data.responseJSON.message === "delays limit reached") {
closeModal();
let msg_template = $("#cant_have_delay_msg_template");
openModal(
msg_template.html(),
() => {
window.location =member_cant_have_delay_form_link;
},
"J'accède au formulaire",
true,
false
);
} else {
err = {msg: "erreur serveur lors de la création du délai", ctx: 'request_delay'};
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error;
}
report_JS_error(err, 'members_space.home');
closeModal();
alert('Erreur lors de la création du délai.');
}
}
});
});
}
function init_my_shifts_tile() {
if (incoming_shifts.length === 0) {
$("#home_tile_my_services #home_incoming_services").text("Aucun service à venir...");
} else {
$("#home_tile_my_services #home_incoming_services").empty();
let cpt = 0;
for (shift of incoming_shifts) {
if (cpt === 3) {
break;
} else {
let shift_line_template = prepare_shift_line_template(shift.date_begin);
$("#home_tile_my_services #home_incoming_services").append(shift_line_template.html());
cpt++;
}
}
}
}
function init_home() {
$("#go_to_shifts_calendar").on("click", () => {
goto('echange-de-services');
});
$("#home_go_to_shift_history").on("click", () => {
goto('mes-services');
});
$("#see_more_info_link").on('click', (e) => {
e.preventDefault();
goto('mes-infos');
});
// $("#go_to_forms").prop("href", "forms_link");
$("#go_to_forms").on('click', (e) => {
e.preventDefault();
goto('faq');
});
if (partner_data.is_in_association === false) {
$("#home .member_associated_partner_area").hide();
} else {
if (partner_data.is_associated_people === "True") {
$(".member_associated_partner").text(partner_data.parent_name);
} else if (partner_data.associated_partner_id !== "False") {
$(".member_associated_partner").text(partner_data.associated_partner_name);
}
}
// TODO vérif tile my info avec données binomes + rattrapage et délai
// Init my info tile
init_my_info_data();
if (incoming_shifts !== null) {
init_my_shifts_tile();
} else {
load_partner_shifts(partner_data.concerned_partner_id)
.then(init_my_shifts_tile);
}
}
\ No newline at end of file
function init_my_info() {
init_my_info_data();
$(".member_email").text(partner_data.email);
if (partner_data.is_in_association === false) {
$("#attached_info_area").hide();
}
if (partner_data.is_associated_people === "True") {
$(".attached_partner_name").text(partner_data.parent_name);
} else if (partner_data.associated_partner_id !== "False") {
$(".attached_partner_name").text(partner_data.associated_partner_name);
}
$(".member_address").empty();
if (partner_data.street !== "" && partner_data.street !== "False") {
$(".member_address")
.append(partner_data.street + "<br/>");
if (partner_data.street2 !== "" && partner_data.street2 !== "False") {
$(".member_address")
.append(partner_data.street2 + "<br/>");
}
$(".member_address")
.append(partner_data.zip + " " + partner_data.city);
} else {
$(".member_address_line").hide();
}
$(".member_mobile").empty();
if (partner_data.mobile !== "" && partner_data.mobile !== "False" && partner_data.mobile !== false && partner_data.mobile !== null) {
$(".member_mobile")
.append(partner_data.mobile)
.show();
} else {
$(".member_mobile").hide();
}
$(".member_phone").empty();
if (partner_data.phone !== "" && partner_data.phone !== "False" && partner_data.phone !== false && partner_data.phone !== null) {
$(".member_phone")
.append(partner_data.phone)
.show();
} else {
$(".member_phone").hide();
}
if ($(".member_mobile").text() === "" && $(".member_phone").text() === "") {
$(".member_phone_line").hide();
}
$('#edit_address').off('click')
.on('click', () => {
$("#street_form").val(partner_data.street.replace(/&#39;/g, "'"));
// $("#street2_form").val(partner_data.street2);
$("#zip_form").val(partner_data.zip.replace(/&#39;/g, "'"));
$("#city_form").val(partner_data.city.replace(/&#39;/g, "'"));
$('#edit_address_value').hide();
$('#edit_address_form').show();
});
$('#cancel_edit_address').
on('click', () => {
$('#edit_address_form').hide();
$('#edit_address_value').show();
});
$('#save_edit_address').off('click')
.on('click', () => {
data= [];
data['street']= $("#street_form").val();
// data['street2']= $("#street2_form").val();
data['zip']= $("#zip_form").val();
data['city']= $("#city_form").val();
saveInfo(data, 'address');
});
$('#edit_phone').off('click')
.on('click', () => {
if (partner_data.phone === "False") partner_data.phone = "";
if (partner_data.mobile === "False") partner_data.mobile = "";
$("#phone_form").val(partner_data.phone);
$("#mobile_form").val(partner_data.mobile);
$('#edit_phone_value').hide();
$('#edit_phone_form').show();
});
$('#cancel_edit_phone').off('click')
.on('click', () => {
$('#edit_phone_form').hide();
$('#edit_phone_value').show();
});
$('#save_edit_phone').off('click')
.on('click', () => {
console.log('ici');
data =[];
data['phone']= $("#phone_form").val();
data['mobile']= $("#mobile_form").val();
saveInfo(data, 'phone');
});
}
function saveInfo(data, field) {
tData = '&idPartner=' + partner_data.partner_id
+ '&shift_type=' + partner_data.shift_type
+ '&verif_token=' + partner_data.verif_token;
for (d in data) {
tData+="&"+d+"="+data[d];
}
tUrl = '/members/save_partner_info';
$.ajax({
type: 'POST',
url: tUrl,
dataType:"json",
data: tData,
timeout: 3000,
success: function() {
for (d in data) {
partner_data[d]=data[d];
}
init_my_info();
if (field == 'address') {
$('#edit_address_form').hide();
$('#edit_address_value').show();
}
if (field == 'phone') {
$('#edit_phone_form').hide();
$('#edit_phone_value').show();
}
},
error: function(error) {
console.log(error);
}
});
}
var history_table = null;
const history_items_limit = 10;
/**
* Load the partner points history
*/
function load_partner_history(offset = 0) {
return new Promise((resolve) => {
$.ajax({
type: 'GET',
url: "/members_space/get_shifts_history",
data: {
partner_id: partner_data.concerned_partner_id,
verif_token: partner_data.verif_token,
limit: history_items_limit,
offset: offset
},
dataType:"json",
traditional: true,
contentType: "application/json; charset=utf-8",
success: function(data) {
formatted_data = prepare_server_data(data.data);
resolve(formatted_data);
},
error: function(data) {
err = {msg: "erreur serveur lors de la récupération de l'historique", ctx: 'load_partner_history'};
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error;
}
report_JS_error(err, 'members_space.my_shifts');
closeModal();
// TODO Notify
alert('Erreur lors de la récupération de votre historique.');
}
});
});
}
/**
* Format history data to insert in the table
*
* @param {Array} data
* @returns formated data array
*/
function prepare_server_data(data) {
res = [];
for (history_item of data) {
if (history_item.is_amnesty !== undefined) {
let shift_datetime = new Date(history_item.date_begin);
let str_shift_datetime = `${("0" + shift_datetime.getDate()).slice(-2)}/${("0" + (shift_datetime.getMonth() + 1)).slice(-2)}/${shift_datetime.getFullYear()}`;
history_item.shift_name = `${history_item.shift_name} du ${str_shift_datetime}`;
} else {
history_item.shift_name = (history_item.shift_id === false) ? '' : history_item.shift_id[1];
if (history_item.name === "Services des comités") {
let shift_datetime = new Date(history_item.date_begin);
let str_shift_datetime = `${("0" + shift_datetime.getDate()).slice(-2)}/${("0" + (shift_datetime.getMonth() + 1)).slice(-2)}/${shift_datetime.getFullYear()}`;
str_shift_datetime = str_shift_datetime + " " + shift_datetime.toLocaleTimeString("fr-fr", time_options);
history_item.shift_name = `Services des comités ${str_shift_datetime}`;
}
}
history_item.details = '';
if (history_item.state === 'excused' || history_item.state === 'absent') {
history_item.details = "Absent.e";
} else if (history_item.state === 'done' && history_item.is_late != false) {
history_item.details = "Présent.e (En Retard)";
} else if (history_item.state === 'done') {
history_item.details = "Présent.e";
} else if (history_item.state === 'cancel') {
history_item.details = "Annulé";
}
}
return data;
}
/**
* Init the History section: display the history table
*/
function init_history() {
$(".loading-history").hide();
$("#history").show();
if (partner_history.length === 0) {
$("#history").empty()
.text("Aucun historique... pour l'instant !");
} else {
history_table = $('#history_table').DataTable({
data: partner_history,
columns: [
{
data: "date_begin",
title: "",
visible: false
},
{
data: "shift_name",
title: "<spans class='dt-body-center'>Service</span>",
width: "60%",
orderable: false
},
{
data: "details",
title: "Détails",
className: "tablet-l desktop",
orderable: false
}
],
iDisplayLength: -1,
order: [
[
0,
"desc"
]
],
language: {url : '/static/js/datatables/french.json'},
dom: "t",
responsive: true,
createdRow: function(row) {
for (var i = 0; i < row.cells.length; i++) {
const cell = $(row.cells[i]);
if (cell.text() === "Présent.e") {
$(row).addClass('row_partner_ok');
} else if (cell.text() === "Retard") {
$(row).addClass('row_partner_late');
} else if (cell.text() === "Absent.e") {
$(row).addClass('row_partner_absent');
} else if (cell.text().includes("Amnistie")) {
$(row).addClass('row_partner_amnistie');
}
}
}
});
}
}
/**
* Init the Incoming shifts section: display them
*/
function init_incoming_shifts() {
$(".loading-incoming-shifts").hide();
$("#incoming_shifts").show();
if (incoming_shifts.length === 0) {
$("#incoming_shifts").text("Aucun service à venir...");
} else {
$("#incoming_shifts").empty();
for (shift of incoming_shifts) {
let shift_line_template = prepare_shift_line_template(shift.date_begin);
$("#incoming_shifts").append(shift_line_template.html());
}
}
}
function init_my_shifts() {
if (incoming_shifts !== null) {
init_incoming_shifts();
} else {
load_partner_shifts(partner_data.concerned_partner_id)
.then(init_incoming_shifts);
}
if (partner_history !== null) {
init_history();
} else {
load_partner_history()
.then((data) => {
partner_history = data;
for (d of data) {
d.create_date = Date.parse(d.create_date);
}
// Sort by date desc
partner_history.sort((a, b) => b.create_date - a.create_date);
if (partner_history.length>0 && partner_history[partner_history.length-1].is_amnesty != undefined) {
partner_history.pop();
}
init_history();
});
}
$(".more_history_button").on("click", function() {
// Hide button & display loading
$('.more_history_button').hide();
$('.loading-more-history').show();
load_partner_history(partner_history.length)
.then((data) => {
partner_history = partner_history.concat(data);
if (history_table) {
history_table.rows.add(data).draw(false);
}
$('.loading-more-history').hide();
// Show "load more" if there is more to load
if (data.length === history_items_limit) {
$('.more_history_button').show();
}
});
});
}
/**
* Common logic between pages
*/
var base_location = null,
current_location = null,
incoming_shifts = null,
partner_history = null;
var date_options = {weekday: "long", year: "numeric", month: "long", day: "numeric"};
var time_options = {hour: '2-digit', minute:'2-digit'};
const possible_cooperative_state = {
suspended: "Rattrapage",
exempted: "Exempté.e",
alert: "En alerte",
up_to_date: "À jour",
unsubscribed: "Désinscrit.e des créneaux",
delay: "En délai",
gone: "Parti.e"
};
/* - Data */
/**
* Load the shifts the member is registered to
* @param {int} partner_id either the members id, or its parent's if s.he's attached
*/
function load_partner_shifts(partner_id) {
return new Promise((resolve) => {
$.ajax({
type: 'GET',
url: "/shifts/get_list_shift_partner/" + partner_id,
dataType:"json",
traditional: true,
contentType: "application/json; charset=utf-8",
success: function(data) {
incoming_shifts = data;
resolve();
},
error: function(data) {
err = {msg: "erreur serveur lors de la récupération des services", ctx: 'load_partner_shifts'};
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error;
}
report_JS_error(err, 'members_space.index');
closeModal();
// TODO Notify
alert('Erreur lors de la récupération de vos services.');
}
});
});
}
/* - Navigation */
/**
* @param {String} page home | mes-infos | mes-services | echange-de-services | faq
*/
function goto(page) {
if (window.location.pathname === base_location) {
history.pushState({}, '', page);
} else {
history.replaceState({}, '', page);
}
update_dom();
}
/**
* Define which html content to load from server depending on the window location
*
* WARNING: For the routing system to work,
* public urls (those the users will see & navigate to) must be different than the server urls used to fetch resources
* (ex: public url: /members_space/mes-info ; server url: /members_space/my_info)
*/
function update_dom() {
$(".nav_item").removeClass('active');
if (window.location.pathname === base_location || window.location.pathname === base_location + "home") {
current_location = "home";
$("#main_content").load("/members_space/homepage", update_content);
$("#nav_home").addClass("active");
} else if (window.location.pathname === base_location + "mes-infos") {
current_location = "my_info";
$("#main_content").load("/members_space/my_info", update_content);
$("#nav_my_info").addClass("active");
} else if (window.location.pathname === base_location + "mes-services") {
current_location = "my_shifts";
$("#main_content").load("/members_space/my_shifts", update_content);
$("#nav_my_shifts").addClass("active");
} else if (window.location.pathname === base_location + "faq") {
current_location = "faq";
$("#main_content").load("/members_space/faqBDM", update_content);
$("#nav_faq").addClass("active");
} else if (window.location.pathname === base_location + "echange-de-services") {
current_location = "shifts_exchange";
$("#main_content").load("/members_space/shifts_exchange", update_content);
$("#nav_shifts_exchange").addClass("active");
} else {
$("#main_content").load("/members_space/no_content");
}
}
/**
* Update the data displayed depending on the current location
* (ex: insert personal data in the DOM when on the 'My Info' page)
*/
function update_content() {
switch (current_location) {
case 'home':
init_home();
break;
case 'my_info':
init_my_info();
break;
case 'my_shifts':
init_my_shifts();
break;
case 'faq':
init_faq();
break;
case 'shifts_exchange':
init_shifts_exchange();
break;
default:
console.log(`Bad input`);
}
}
/* - Shifts */
/**
* Prepare a shift line to insert into the DOM.
* Is used in: Home - My Shifts tile ; My Shifts - Incoming shifts section
*
* @param {String} date_begin beginning datetime of the shift
* @returns JQuery node object of the formatted template
*/
function prepare_shift_line_template(date_begin) {
let shift_line_template = $("#shift_line_template");
let datetime_shift_start = new Date(date_begin.replace(/\s/, 'T'));
let f_date_shift_start = datetime_shift_start.toLocaleDateString("fr-fr", date_options);
f_date_shift_start = f_date_shift_start.charAt(0).toUpperCase() + f_date_shift_start.slice(1);
shift_line_template.find(".shift_line_date").text(f_date_shift_start);
shift_line_template.find(".shift_line_time").text(datetime_shift_start.toLocaleTimeString("fr-fr", time_options));
return shift_line_template;
}
/* - Member info */
/**
* Init common personal data between screens
*/
function init_my_info_data() {
$(".choose_makeups").off();
$(".unsuscribed_form_link").off();
$(".member_shift_name").text(partner_data.regular_shift_name);
let pns = partner_data.name.split(" - ");
let name = pns.length > 1 ? pns[1] : pns[0];
$(".member_name").text(name);
// Status related
$(".member_status")
.text(possible_cooperative_state[partner_data.cooperative_state])
.addClass("member_status_" + partner_data.cooperative_state);
if (partner_data.cooperative_state === 'delay' && partner_data.date_delay_stop !== 'False') {
const d = new Date(Date.parse(partner_data.date_delay_stop));
const f_date_delay_stop = d.getDate()+'/'+("0" + (d.getMonth() + 1)).slice(-2)+'/'+d.getFullYear();
$(".delay_date_stop").text(f_date_delay_stop);
$(".delay_date_stop_container").show();
} else if (partner_data.cooperative_state === 'unsubscribed' || partner_data.cooperative_state === 'gone') {
$(".member_shift_name").text('X');
$(".unsuscribed_form_link")
.show()
.attr('href', unsuscribe_form_link)
.on('click', function() {
setTimeout(500, () => {
$(this).removeClass('active');
});
});
} else if (partner_data.cooperative_state === 'exempted') {
const d = new Date(Date.parse(partner_data.leave_stop_date));
const f_date_delay_stop = d.getDate()+'/'+("0" + (d.getMonth() + 1)).slice(-2)+'/'+d.getFullYear();
$(".delay_date_stop").text(f_date_delay_stop);
$(".delay_date_stop_container").show();
}
if (
partner_data.makeups_to_do > 0
&& partner_data.is_associated_people === "False"
&& partner_data.cooperative_state !== 'unsubscribed'
) {
$(".choose_makeups").show();
if (
partner_data.cooperative_state === 'suspended'
&& partner_data.date_delay_stop === 'False') {
// If the member is suspended & doesn't have a delay
$(".choose_makeups").on('click', () => {
// Create 6 month delay
request_delay()
.then(() => {
// Then redirect to calendar
goto('echange-de-services');
});
});
} else {
$(".choose_makeups").on('click', () => {
goto('echange-de-services');
});
}
}
$(".member_coop_number").text(partner_data.barcode_base);
}
$(document).ready(function() {
// TODO essayer de ne charger les js que au besoin
$.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } });
// If partner is associated (attached), display the pair's main partner shift data
partner_data.concerned_partner_id =
(partner_data.is_associated_people === "True")
? partner_data.parent_id
: partner_data.partner_id;
partner_data.is_in_association =
partner_data.is_associated_people === "True" || partner_data.associated_partner_id !== "False";
// For associated people, their parent name is attached in their display name
let partner_name_split = partner_data.name.split(', ');
partner_data.name = partner_name_split[partner_name_split.length - 1];
base_location = (app_env === 'dev') ? '/members_space/' : '/';
update_dom();
window.onpopstate = function() {
update_dom();
};
});
(function($, sr) {
// debouncing function from John Hann
// http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/
var debounce = function (func, threshold, execAsap) {
var timeout;
return function debounced () {
var obj = this, args = arguments;
function delayed () {
if (!execAsap)
func.apply(obj, args);
timeout = null;
}
if (timeout)
clearTimeout(timeout);
else if (execAsap)
func.apply(obj, args);
timeout = setTimeout(delayed, threshold || 100);
};
};
// smartresize
jQuery.fn[sr] = function(fn) {
return fn ? this.bind('resize', debounce(fn)) : this.trigger(sr);
};
})(jQuery, 'smartresize');
\ No newline at end of file
from django.test import SimpleTestCase
\ No newline at end of file
from django.test import SimpleTestCase
\ No newline at end of file
from django.test import SimpleTestCase
class TestUrls(SimpleTestCase):
def test_list_url_is_resolved(self):
assert 1==1
\ No newline at end of file
from django.test import SimpleTestCase
\ No newline at end of file
"""."""
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index),
url(r'^homepage$', views.home), # These endpoints must be different than in-app url
url(r'^my_info$', views.my_info),
url(r'^my_shifts$', views.my_shifts),
url(r'^shifts_exchange$', views.shifts_exchange),
url(r'^faqBDM$', views.faqBDM),
url(r'^no_content$', views.no_content),
url(r'^get_shifts_history$', views.get_shifts_history),
url(r'^.*', views.index) # Urls unknown from the server will redirect to index
]
......@@ -222,6 +222,88 @@ class Order(models.Model):
labels_data['total'] += l['product_qty']
return labels_data
def get_order_attachment_id(self):
res = {}
f = ["id"]
c = [['res_model', '=', 'purchase.order'], ['res_id', '=', self.id], ['type', 'in', ['binary', 'url']]]
try:
attachment = self.o_api.search_read('ir.attachment', c, f)
res = attachment[0]
except Exception as e:
res["id_po"] = self.id
res["error"] = str(e)
return res
@staticmethod
def create(supplier_id, date_planned, order_lines):
order_data = {
"partner_id": int(supplier_id),
"partner_ref": False,
"currency_id": 1,
"date_order": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"origin": "Aide à la commande",
"company_id": 1,
"order_line": [],
"notes": False,
"date_planned": date_planned,
"picking_type_id": 1,
"dest_address_id": False,
"incoterm_id": False,
"payment_term_id": False,
"fiscal_position_id": False,
"message_follower_ids": False,
"message_ids": False
}
for line in order_lines:
product_line_name = line["name"]
if "product_code" in line and line["product_code"] is not False:
product_code = str(line["product_code"])
product_line_name = "[" + product_code + "] " + product_line_name
order_data["order_line"].append(
[
0,
False,
{
"package_qty": line["package_qty"],
"price_policy": "uom",
"indicative_package": True,
"product_id": line["product_variant_ids"][0],
"name": product_line_name,
"date_planned": date_planned,
"account_analytic_id": False,
"product_qty_package":line["product_qty_package"],
"product_qty": line["product_qty"],
"product_uom": line["product_uom"],
"price_unit": line["price_unit"],
"discount": 0,
"taxes_id": [
[
6,
False,
line["supplier_taxes_id"]
]
]
}
]
)
api = OdooAPI()
id_po = api.create('purchase.order', order_data)
res_confirm = api.execute('purchase.order', 'button_confirm', [id_po])
res = {
'id_po': id_po,
'confirm_po': True,
'supplier_id': supplier_id,
'date_planned': date_planned
}
return res
class Orders(models.Model):
@staticmethod
......@@ -268,3 +350,15 @@ class Orders(models.Model):
coop_logger.error('Orders get_custom_barcode_labels_to_print(oids) : %s', str(e))
return labels_data
class CagetteSuppliers(models.Model):
@staticmethod
def get_suppliers():
api = OdooAPI()
f = ['id', 'name', 'display_name']
c = [['supplier', '=', 1], ['parent_id', '=', False]]
res = api.search_read('res.partner', c, f)
return res
.page_body{
position: relative;
}
.page_content, .login_area {
position: absolute;
top: 0;
left: 0;
right: 0;
}
/* - Common */
.pill {
border-radius: 30px;
min-width: 200px;
min-height: 40px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 7px 30px 7px 30px;
margin: 0 10px 5px 10px;
}
.disabled {
background-color: #c9cbce;
}
.disabled:hover {
background-color: #a1a2a3;
}
.link_as_button:hover {
text-decoration: none;
color: white;
}
.link_as_button:active {
text-decoration: none;
color: white;
}
.link_as_button:focus {
text-decoration: none;
color: white;
}
.remove_order_modal_text {
font-size: 2rem;
}
.remove_order_name {
font-weight: bold;
}
/* - Order selection screen */
#new_order_area {
margin-bottom: 40px;
}
#new_order_form {
margin-top: 20px;
}
#existing_orders {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
width: 80%;
margin: 0 auto;
padding-top: 15px;
}
.order_pill {
flex-direction: row;
}
.pill_order_name {
flex: 3 0 auto;
}
.remove_order_icon {
flex: 0 1 auto;
color: #912930;
margin-left: 5px;
cursor: pointer;
z-index: 2;
transform: scale(1.2);
}
.remove_order_icon:hover {
color: #e62720;
}
.order_last_update {
font-weight: bold;
}
.order_modified_msg {
font-size: 2rem;
color: #e62720;
}
/* - Main screen */
/* -- Top action button(s) */
#back_to_order_selection {
position: absolute;
}
#main_content .actions_buttons_area {
position: absolute;
width: 100%;
top: 0;
display: flex;
justify-content: space-between;
}
#orders_created .actions_buttons_area {
position: absolute;
width: 100%;
top: 0;
display: flex;
justify-content: flex-start;
}
.right_action_buttons {
display: flex;
}
#actions_buttons_wrapper {
position: relative;
margin-right: 5px;
}
#toggle_action_buttons {
width: 250px;
position: relative;
}
.toggle_action_buttons_icon {
position: absolute;
top: 50%;
transform: translateY(-50%);
right: 15px;
}
#actions_buttons_container {
position: absolute;
display: flex;
flex-direction: column;
width: 250px;
display: none;
}
.action_button {
width: 100%;
min-height: 45px;
}
#do_inventory {
border-top: 1px solid #004aa6;
}
#refresh_order {
border-top: 1px solid #004aa6;
border-bottom: 1px solid #004aa6;
}
/* -- Order data */
#order_data_container {
font-size: 1.8rem;
}
#order_data_separator {
margin: 0 10px 0 10px;
}
#order_forms_container {
margin-top: 20px;
display: flex;
flex-wrap: wrap;
justify-content: space-evenly;
}
.order_form_item {
margin-top: 10px;
}
#supplier_input {
width: 350px;
border-radius: 3px;
}
#stats_date_period_select {
margin-left: 5px;
min-width: 200px;
}
#date_planned_input, #coverage_days_input, #stats_date_period_select {
border-radius: 3px;
}
#coverage_form > div {
display:inline-block;
float:left;
}
#coverage_form .input-wrapper {
margin-right: 3px;
}
#coverage_days_input, #percent_adjust_input {
display: block;
}
#coverage_days_input {
margin-bottom: 3px;
}
/* -- Table */
#products_table_filter{
text-align: right !important;
}
#products_table_filter input{
height: 35px;
width: 300px;
border-radius: 3px;
}
#products_table .help {cursor: help;}
#table_header_select_all{
display: flex;
align-content: center;
justify-content: center;
flex-wrap: wrap;
}
.select_all_text {
margin-top: 5px;
margin-bottom: 5px;
}
#table_header_select_all input{
margin-left: 5px;
}
.main.fa-info-circle {
color: #0275d8;
cursor: pointer;
}
.custom_cell_content {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.product_qty_input {
width: 100px;
}
.product_ref_input {
padding: .5rem .5rem;
}
.supplier_package_qty {
font-style: italic;
font-size: 1.3rem;
}
.product_not_from_supplier {
background-color: #e8ebf0;
cursor: pointer;
}
.product_not_from_supplier:hover {
background-color: #d3d7db;
}
.product_ref_cell:hover {
background-color: #d3d7db;
cursor: pointer;
}
.product_name, .supplier_name, .product_npa {
font-weight: bold;
}
.select_product_cb, .product_npa_cb {
cursor: pointer;
}
.focused_line {
background-color: #76cf71 !important;
}
.dataTables_scrollHead {
position: sticky !important;
position: -webkit-sticky !important;
top: 0;
z-index: 3;
background-color: white;
}
/* -- Footer */
#main_content_footer {
margin: 20px 0 40px 0;
}
#footer_actions {
width: 100%;
display: flex;
justify-content: space-between;
}
/* -- Suppliers list */
#suppliers_container {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
margin: 30px 0 20px 0;
position: -webkit-sticky;
position: sticky;
top: 140px;
z-index: 5;
}
.supplier_pill {
background-color: #a0daff;
border: 1px solid #6ea8cc;
}
.pill_supplier_name {
font-weight: bold;
}
.supplier_data {
font-size: 1.5rem;
display: flex;
}
.remove_supplier_icon {
color: red;
margin-left: 5px;
cursor: pointer;
}
/* -- Attach product to supplier modal */
.modal_input_area {
margin-bottom: 15px;
width: 100%;
display: flex;
justify-content: center;
}
.modal_input_container, .modal_input_label {
width:50%;
margin: 0 15px 0 15px;
}
.modal_input_label {
display: flex;
justify-content: flex-end;
align-items: center;
}
.modal_input {
width: 90%;
}
/* product actions modal*/
.npa-options {
width: fit-content;
text-align: left;
margin: auto;
}
.npa-options label {
display: block;
}
/* - Orders created screen */
.order_created_header {
margin-top: 5px;
margin-bottom: 40px;
}
#created_orders_area {
display: flex;
flex-wrap: wrap;
justify-content: space-evenly;
}
.new_order_item {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 20px;
}
.download_order_file {
margin-top: 10px;
}
#recap_delivery_date {
font-weight: bold;
}
.mail_example_container {
display: flex;
flex-direction: column;
align-items: center;
width: 30%;
margin: 0 auto;
}
.mail_type_text {
width: 100%;
}
.mail_example {
background-color: #e7e9ed;
width: 100%;
padding: 15px;
}
/* - Miscellaneous */
footer {
display: none;
}
\ No newline at end of file
from django.test import TestCase
# Create your tests here.
from django.test import SimpleTestCase
\ No newline at end of file
from django.test import SimpleTestCase
\ No newline at end of file
from django.test import SimpleTestCase
from django.test import Client
class TestUrls(SimpleTestCase):
def testOrdersUrlIsResolved(self):
c = Client()
response = c.get('/orders/')
assert response.status_code == 200, "Orders url is not resolved"
from django.test import SimpleTestCase
\ No newline at end of file
......@@ -8,5 +8,12 @@ urlpatterns = [
url(r'^export/([0-9]+)', views.export_one),
url(r'^export/([a-z]+)', views.export_regex),
url(r'^get_pdf_labels$', views.get_pdf_labels),
url(r'^print_product_labels$', views.print_product_labels)
url(r'^print_product_labels$', views.print_product_labels),
url(r'^helper$', views.helper),
url(r'^get_suppliers$', views.get_suppliers),
url(r'^get_supplier_products$', views.get_supplier_products),
url(r'^associate_supplier_to_product$', views.associate_supplier_to_product),
url(r'^end_supplier_product_association$', views.end_supplier_product_association),
url(r'^create_orders$', views.create_orders),
url(r'^get_orders_attachment$', views.get_orders_attachment),
]
from outils.common_imports import *
from outils.for_view_imports import *
from outils.common import OdooAPI
from orders.models import Order, Orders
from products.models import CagetteProduct
from orders.models import Order, Orders, CagetteSuppliers
from products.models import CagetteProduct, CagetteProducts
from openpyxl import Workbook
from openpyxl.writer.excel import save_virtual_workbook
import datetime
def as_text(value): return str(value) if value is not None else ""
def index(request):
return HttpResponse('Orders')
def helper(request):
context = {
'title': 'Aide à la commande',
'couchdb_server': settings.COUCHDB['url'],
'db': settings.COUCHDB['dbs']['orders'],
'odoo_server': settings.ODOO['url'],
'metabase_url': getattr(settings, 'ORDERS_HELPER_METABASE_URL', ''),
'nb_past_days_to_compute_sales_average': OdooAPI().get_system_param('lacagette_products.nb_past_days_to_compute_sales_average'),
'nb_of_consecutive_non_sale_days_considered_as_break': OdooAPI().get_system_param('lacagette_products.nb_of_consecutive_non_sale_days_considered_as_break')
}
template = loader.get_template('orders/helper.html')
return HttpResponse(template.render(context, request))
def get_suppliers(request):
""" Get suppliers list """
res = {}
try:
res = CagetteSuppliers.get_suppliers()
except Exception as e:
res["error"] = str(e)
return JsonResponse(res, status=500)
return JsonResponse({'res': res})
def get_supplier_products(request):
""" Get supplier products """
suppliers_id = request.GET.getlist('sids', '')
stats_from = request.GET.get('stats_from')
res = CagetteProducts.get_products_for_order_helper(suppliers_id, [], stats_from)
if 'error' in res:
return JsonResponse(res, status=500)
else:
return JsonResponse({'res': res})
def associate_supplier_to_product(request):
""" This product is now supplied by this supplier """
res = {}
data = json.loads(request.body.decode())
res = CagetteProduct.associate_supplier_to_product(data)
if 'error' in res:
return JsonResponse(res, status=500)
else:
return JsonResponse({'res': res})
return JsonResponse({'res': res})
def end_supplier_product_association(request):
""" This product is now unavailable from this supplier """
res = {}
data = json.loads(request.body.decode())
res = CagetteProduct.end_supplier_product_association(data)
if 'error' in res:
return JsonResponse(res, status=500)
else:
return JsonResponse({'res': res})
def create_orders(request):
""" Create products orders """
res = { "created": [] }
try:
data = json.loads(request.body.decode())
# suppliers id are keys in request data
for supplier_id in data["suppliers_data"].keys():
supplier_data = data["suppliers_data"][supplier_id]
res_created = Order.create(
supplier_id,
supplier_data["date_planned"],
supplier_data["lines"]
)
res["created"].append(res_created)
except Exception as e:
res["error"] = str(e)
return JsonResponse(res, status=500)
return JsonResponse({'res': res})
def get_orders_attachment(request):
""" Get order attachment: order file created after PO is finalized """
res = []
po_ids = request.GET.getlist('po_ids')
for id_po in po_ids:
m = Order(int(id_po))
attachment = m.get_order_attachment_id()
if 'error' in attachment:
res.append(attachment)
else:
res.append({
'id_po': id_po,
'id_attachment': attachment["id"]
})
for item in res:
if 'error' in item:
return JsonResponse(res, status=500)
return JsonResponse({'res': res})
def export_one(request, oid):
msg = ''
try:
......@@ -20,7 +134,6 @@ def export_one(request, oid):
order = Order(oid)
order_data = order.export()
if ('success' in order_data) and (order_data['success'] is True):
import datetime
now = datetime.datetime.now()
taxes = 0
company_name = ''
......
......@@ -47,7 +47,7 @@ class OdooAPI:
order='id ASC'):
"""Main api request, retrieving data according search conditions."""
fields_and_context = {'fields': fields,
'context': {'lang': 'fr_FR','tz':'Europe/Paris'},
'context': {'lang': 'fr_FR', 'tz': 'Europe/Paris'},
'limit': limit,
'offset': offset,
'order': order
......@@ -59,8 +59,11 @@ class OdooAPI:
def update(self, entity, ids, fields):
"""Update entities which have ids, with new fields values."""
context = {
'context': {'lang': 'fr_FR', 'tz': 'Europe/Paris'}
}
return self.models.execute_kw(self.db, self.uid, self.passwd,
entity, 'write', [ids, fields])
entity, 'write', [ids, fields], context)
def create(self, entity, fields):
"""Create entity instance with given fields values."""
......@@ -74,6 +77,18 @@ class OdooAPI:
def authenticate(self, login, password):
return self.common.authenticate(self.db, login, password, {})
def get_system_param(self, key):
value = ''
try:
res = self.search_read('ir.config_parameter',
[['key', '=', key]],
['value'])
if res:
value = res[0]['value']
except Exception as e:
coop_logger.error('get_system_param: (%s) %s', key, str(e))
return value
class CouchDB:
"""Class to handle interactions with CouchDB"""
......
......@@ -22,6 +22,10 @@
Used to draw weeks planning
- COMPANY_NAME = 'lgds'
Used for company spesific code
- COMPANY_NAME = 'Les Grains de Sel'
- ADMIN_IDS = [13]
......@@ -114,6 +118,14 @@
Character which by used to separate every 2 phone figures (04.67.23.89.21 for example)
Default is " "
- SHOW_FTOP_BUTTON = True (by default)
If True, in shift_template calendar choice view, "Volant" button is included
- USE_STANDARD_SHIFT = True (by default)
La Cagette use False to implement custom rules
### Scales and labels files generation
- DAV_PATH = '/data/dav/cagette'
......@@ -228,6 +240,39 @@
- ENTRANCE_SERVICE_BTN = "…faire <b>mon service 🤝"
- ENTRANCE_EXTRA_BUTTONS_DISPLAY = False (no button is shown above shift coop. list) (True if not set)
- ENTRANCE_EASY_SHIFT_VALIDATE = False (default value)
When set to True allow coop to identify and have 1 point (only if FTOP)
- ENTRANCE_ADD_PT_EVENT_NAME = 'Add 1 point name throught easy validate' (default : 'Validation service comité'')
- ENTRANCE_MISSED_SHIFT_BEGIN_MSG (default : "")
This message is dispayed when time to register is last
- ENTRANCE_EASY_SHIFT_VALIDATE_MSG (default = 'Je valide mon service "Comité"')
(makes sens if ENTRANCE_EASY_SHIFT_VALIDATE is True)
- ENTRANCE_WITH_LATE_MODE = True
(If member is coming within the grace delay)
- ENTRANCE_VALIDATION_GRACE_DELAY = 60
(if not set, 60 minutes is the default)
- ENTRANCE_VALIDATE_PRESENCE_MESSAGE = """
<div class="explanations">
Ta présence a bien été validée ! Merci de te diriger au fond du magasin pour le lancement du créneau !
</div>
Ton prochain service <span class="service_verb">est prévu</span> le <span class="next_shift"></span>
"""
(La Cagette message, where no point data is displayed)
### Member space
- EM_URL = ''
......@@ -247,7 +292,7 @@
If not set, default view is 'dayGridMonth'
- SHIFT_EXCHANGE_DAYS_TO_HIDE = ''
- SHIFT_EXCHANGE_DAYS_TO_HIDE = '0'
By default, if this variable is not set, sunday is hidden
To hide Sunday and Monday, set this to "0,1"
......@@ -261,6 +306,7 @@
- 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>"""
- UNSUBSCRIBED_FORM_LINK = 'https://docs.google.com/forms/d/e/1FAIpQLScWcpls-ruYIp7HdrjRF1B1TyuzdqhvlUIcUWynbEujfj3dTg/viewform'
- 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>'
Message shown to people when they connect to the Member Space
......@@ -309,7 +355,16 @@
- MEALS_PICKING_TYPE_ID = 10
### New members space
- USE_NEW_MEMBERS_SPACE = True
Should be set to False by default if parameter not set
- START_DATE_FOR_SHIFTS_HISTORY = "2018-01-01"
- AMNISTIE_DATE = "2021-11-24 00:00:00"
In members_space history display a special activity about amnistie
### Miscellious
......
......@@ -21,5 +21,7 @@ def custom_css(request):
def context_setting(request):
"""adding settings variable to context (can be overloaded in views)."""
context = {'odoo': settings.ODOO['url']}
context = {'odoo': settings.ODOO['url'],
'app_env': getattr(settings, 'APP_ENV', "prod"),
'company_code': getattr(settings, 'COMPANY_CODE', '')}
return context
\ No newline at end of file
......@@ -53,6 +53,7 @@ INSTALLED_APPS = (
'shop',
'shelfs',
'sales',
'members_space',
# 'tests'
)
......@@ -99,6 +100,8 @@ STATICFILES_DIRS = (
"website/static",
"shop/static",
"shelfs/static",
"orders/static",
"members_space/static",
# "tests/static"
)
......@@ -224,3 +227,5 @@ DEBUG = True
CORS_ORIGIN_ALLOW_ALL = True # Needed to make dev test with different IP and ports
ADMIN_IDS = [1]
APP_ENV = 'dev' # Default is prod
\ No newline at end of file
......@@ -19,7 +19,9 @@ COUCHDB = {
'member': 'coops',
'inventory': 'inventory',
'envelops': 'envelop',
'shop': 'shopping_carts'
'shop': 'shopping_carts',
'orders': 'orders_test',
'reception': 'reception_test',
}
}
......
......@@ -25,6 +25,7 @@ footer { position: fixed;
color: white;
width:100%;
text-align: center;
z-index: 10;
}
#deconnect, #password_change {float:right; margin-left: 5px;}
......@@ -59,6 +60,11 @@ footer { position: fixed;
margin: auto;
padding:15px;
}
@media screen and (max-width:768px) {
.overlay-content .mconfirm {
width: 100%;
}
}
.overlay-content .mconfirm button {margin:5px;}
.overlay-content > em {
color: #fff;
......@@ -187,4 +193,17 @@ footer { position: fixed;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QYFAhkSsdes/QAAA8dJREFUOMvVlGtMW2UYx//POaWHXg6lLaW0ypAtw1UCgbniNOLcVOLmAjHZolOYlxmTGXVZdAnRfXQm+7SoU4mXaOaiZsEpC9FkiQs6Z6bdCnNYruM6KNBw6YWewzl9z+sHImEWv+vz7XmT95f/+3/+7wP814v+efDOV3/SoX3lHAA+6ODeUFfMfjOWMADgdk+eEKz0pF7aQdMAcOKLLjrcVMVX3xdWN29/GhYP7SvnP0cWfS8caSkfHZsPE9Fgnt02JNutQ0QYHB2dDz9/pKX8QjjuO9xUxd/66HdxTeCHZ3rojQObGQBcuNjfplkD3b19Y/6MrimSaKgSMmpGU5WevmE/swa6Oy73tQHA0Rdr2Mmv/6A1n9w9suQ7097Z9lM4FlTgTDrzZTu4StXVfpiI48rVcUDM5cmEksrFnHxfpTtU/3BFQzCQF/2bYVoNbH7zmItbSoMj40JSzmMyX5qDvriA7QdrIIpA+3cdsMpu0nXI8cV0MtKXCPZev+gCEM1S2NHPvWfP/hL+7FSr3+0p5RBEyhEN5JCKYr8XnASMT0xBNyzQGQeI8fjsGD39RMPk7se2bd5ZtTyoFYXftF6y37gx7NeUtJJOTFlAHDZLDuILU3j3+H5oOrD3yWbIztugaAzgnBKJuBLpGfQrS8wO4FZgV+c1IxaLgWVU0tMLEETCos4xMzEIv9cJXQcyagIwigDGwJgOAtHAwAhisQUjy0ORGERiELgG4iakkzo4MYAxcM5hAMi1WWG1yYCJIcMUaBkVRLdGeSU2995TLWzcUAzONJ7J6FBVBYIggMzmFbvdBV44Corg8vjhzC+EJEl8U1kJtgYrhCzgc/vvTwXKSib1paRFVRVORDAJAsw5FuTaJEhWM2SHB3mOAlhkNxwuLzeJsGwqWzf5TFNdKgtY5qHp6ZFf67Y/sAVadCaVY5YACDDb3Oi4NIjLnWMw2QthCBIsVhsUTU9tvXsjeq9+X1d75/KEs4LNOfcdf/+HthMnvwxOD0wmHaXr7ZItn2wuH2SnBzbZAbPJwpPx+VQuzcm7dgRCB57a1uBzUDRL4bfnI0RE0eaXd9W89mpjqHZnUI5Hh2l2dkZZUhOqpi2qSmpOmZ64Tuu9qlz/SEXo6MEHa3wOip46F1n7633eekV8ds8Wxjn37Wl63VVa+ej5oeEZ/82ZBETJjpJ1Rbij2D3Z/1trXUvLsblCK0XfOx0SX2kMsn9dX+d+7Kf6h8o4AIykuffjT8L20LU+w4AZd5VvEPY+XpWqLV327HR7DzXuDnD8r+ovkBehJ8i+y8YAAAAASUVORK5CYII=);
}
.notifyjs-cancelable-base .buttons {width: 190px; margin: 5px auto;}
.notifyjs-cancelable-base button {width: 90px;text-align: center; margin: 3px;}
\ No newline at end of file
.notifyjs-cancelable-base button {width: 90px;text-align: center; margin: 3px;}
button.accordion::after {
content: '\002B';
color: #777;
font-weight: bold;
float: right;
margin-left: 5px;
}
button.accordion.active::after {
content: "\2212";
}
table.dataTable.dtr-inline.collapsed>tbody>tr>td.child,table.dataTable.dtr-inline.collapsed>tbody>tr>th.child,table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty{cursor:default !important}table.dataTable.dtr-inline.collapsed>tbody>tr>td.child:before,table.dataTable.dtr-inline.collapsed>tbody>tr>th.child:before,table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty:before{display:none !important}table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control,table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control{position:relative;padding-left:30px;cursor:pointer}table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before,table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before{top:50%;left:5px;height:1em;width:1em;margin-top:-9px;display:block;position:absolute;color:white;border:.15em solid white;border-radius:1em;box-shadow:0 0 .2em #444;box-sizing:content-box;text-align:center;text-indent:0 !important;font-family:"Courier New",Courier,monospace;line-height:1em;content:"+";background-color:#31b131}table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td.dtr-control:before,table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th.dtr-control:before{content:"-";background-color:#d33333}table.dataTable.dtr-inline.collapsed.compact>tbody>tr>td.dtr-control,table.dataTable.dtr-inline.collapsed.compact>tbody>tr>th.dtr-control{padding-left:27px}table.dataTable.dtr-inline.collapsed.compact>tbody>tr>td.dtr-control:before,table.dataTable.dtr-inline.collapsed.compact>tbody>tr>th.dtr-control:before{left:4px;height:14px;width:14px;border-radius:14px;line-height:14px;text-indent:3px}table.dataTable.dtr-column>tbody>tr>td.dtr-control,table.dataTable.dtr-column>tbody>tr>th.dtr-control,table.dataTable.dtr-column>tbody>tr>td.control,table.dataTable.dtr-column>tbody>tr>th.control{position:relative;cursor:pointer}table.dataTable.dtr-column>tbody>tr>td.dtr-control:before,table.dataTable.dtr-column>tbody>tr>th.dtr-control:before,table.dataTable.dtr-column>tbody>tr>td.control:before,table.dataTable.dtr-column>tbody>tr>th.control:before{top:50%;left:50%;height:.8em;width:.8em;margin-top:-0.5em;margin-left:-0.5em;display:block;position:absolute;color:white;border:.15em solid white;border-radius:1em;box-shadow:0 0 .2em #444;box-sizing:content-box;text-align:center;text-indent:0 !important;font-family:"Courier New",Courier,monospace;line-height:1em;content:"+";background-color:#31b131}table.dataTable.dtr-column>tbody>tr.parent td.dtr-control:before,table.dataTable.dtr-column>tbody>tr.parent th.dtr-control:before,table.dataTable.dtr-column>tbody>tr.parent td.control:before,table.dataTable.dtr-column>tbody>tr.parent th.control:before{content:"-";background-color:#d33333}table.dataTable>tbody>tr.child{padding:.5em 1em}table.dataTable>tbody>tr.child:hover{background:transparent !important}table.dataTable>tbody>tr.child ul.dtr-details{display:inline-block;list-style-type:none;margin:0;padding:0}table.dataTable>tbody>tr.child ul.dtr-details>li{border-bottom:1px solid #efefef;padding:.5em 0}table.dataTable>tbody>tr.child ul.dtr-details>li:first-child{padding-top:0}table.dataTable>tbody>tr.child ul.dtr-details>li:last-child{border-bottom:none}table.dataTable>tbody>tr.child span.dtr-title{display:inline-block;min-width:75px;font-weight:bold}div.dtr-modal{position:fixed;box-sizing:border-box;top:0;left:0;height:100%;width:100%;z-index:100;padding:10em 1em}div.dtr-modal div.dtr-modal-display{position:absolute;top:0;left:0;bottom:0;right:0;width:50%;height:50%;overflow:auto;margin:auto;z-index:102;overflow:auto;background-color:#f5f5f7;border:1px solid black;border-radius:.5em;box-shadow:0 12px 30px rgba(0, 0, 0, 0.6)}div.dtr-modal div.dtr-modal-content{position:relative;padding:1em}div.dtr-modal div.dtr-modal-close{position:absolute;top:6px;right:6px;width:22px;height:22px;border:1px solid #eaeaea;background-color:#f9f9f9;text-align:center;border-radius:3px;cursor:pointer;z-index:12}div.dtr-modal div.dtr-modal-close:hover{background-color:#eaeaea}div.dtr-modal div.dtr-modal-background{position:fixed;top:0;left:0;right:0;bottom:0;z-index:101;background:rgba(0, 0, 0, 0.6)}@media screen and (max-width: 767px){div.dtr-modal div.dtr-modal-display{width:95%}}
#main_content {text-align: center;}
.param {margin-bottom: 15px;}
.param label {font-weight: bold;}
\ No newline at end of file
.param label {font-weight: bold;}
input.link {min-width: 50em;}
.submit_button {margin-bottom: 10px;}
/* Style the buttons that are used to open and close the accordion panel */
.accordion {
background-color: #eee;
color: #444;
cursor: pointer;
padding: 18px;
width: 100%;
text-align: left;
border: none;
outline: none;
transition: 0.4s;
}
/* Add a background color to the button if it is clicked on (add the .active class with JS), and when you move the mouse over it (hover) */
.active, .accordion:hover {
background-color: #ccc;
}
/* Style the accordion panel. Note: hidden by default */
.panel {
padding: 0 18px;
background-color: white;
display: none;
overflow: hidden;
}
MIT License
Copyright (c) 2021 Adam Shaw
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# FullCalendar
A full-sized drag & drop JavaScript event calendar
- [Project website and demos](http://fullcalendar.io/)
- [Documentation](http://fullcalendar.io/docs)
- [Support](http://fullcalendar.io/support)
- [Contributing](CONTRIBUTING.md)
- [Changelog](CHANGELOG.md)
- [License](LICENSE.txt)
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../lib/main.css' rel='stylesheet' />
<script src='../lib/main.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay,listMonth'
},
initialDate: '2020-09-12',
navLinks: true, // can click day/week names to navigate views
businessHours: true, // display business hours
editable: true,
selectable: true,
events: [
{
title: 'Business Lunch',
start: '2020-09-03T13:00:00',
constraint: 'businessHours'
},
{
title: 'Meeting',
start: '2020-09-13T11:00:00',
constraint: 'availableForMeeting', // defined below
color: '#257e4a'
},
{
title: 'Conference',
start: '2020-09-18',
end: '2020-09-20'
},
{
title: 'Party',
start: '2020-09-29T20:00:00'
},
// areas where "Meeting" must be dropped
{
groupId: 'availableForMeeting',
start: '2020-09-11T10:00:00',
end: '2020-09-11T16:00:00',
display: 'background'
},
{
groupId: 'availableForMeeting',
start: '2020-09-13T10:00:00',
end: '2020-09-13T16:00:00',
display: 'background'
},
// red areas where no events can be dropped
{
start: '2020-09-24',
end: '2020-09-28',
overlap: false,
display: 'background',
color: '#ff9f89'
},
{
start: '2020-09-06',
end: '2020-09-08',
overlap: false,
display: 'background',
color: '#ff9f89'
}
]
});
calendar.render();
});
</script>
<style>
body {
margin: 40px 10px;
padding: 0;
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
font-size: 14px;
}
#calendar {
max-width: 1100px;
margin: 0 auto;
}
</style>
</head>
<body>
<div id='calendar'></div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../lib/main.css' rel='stylesheet' />
<script src='../lib/main.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
headerToolbar: {
left: 'prevYear,prev,next,nextYear today',
center: 'title',
right: 'dayGridMonth,dayGridWeek,dayGridDay'
},
initialDate: '2020-09-12',
navLinks: true, // can click day/week names to navigate views
editable: true,
dayMaxEvents: true, // allow "more" link when too many events
events: [
{
title: 'All Day Event',
start: '2020-09-01'
},
{
title: 'Long Event',
start: '2020-09-07',
end: '2020-09-10'
},
{
groupId: 999,
title: 'Repeating Event',
start: '2020-09-09T16:00:00'
},
{
groupId: 999,
title: 'Repeating Event',
start: '2020-09-16T16:00:00'
},
{
title: 'Conference',
start: '2020-09-11',
end: '2020-09-13'
},
{
title: 'Meeting',
start: '2020-09-12T10:30:00',
end: '2020-09-12T12:30:00'
},
{
title: 'Lunch',
start: '2020-09-12T12:00:00'
},
{
title: 'Meeting',
start: '2020-09-12T14:30:00'
},
{
title: 'Happy Hour',
start: '2020-09-12T17:30:00'
},
{
title: 'Dinner',
start: '2020-09-12T20:00:00'
},
{
title: 'Birthday Party',
start: '2020-09-13T07:00:00'
},
{
title: 'Click for Google',
url: 'http://google.com/',
start: '2020-09-28'
}
]
});
calendar.render();
});
</script>
<style>
body {
margin: 40px 10px;
padding: 0;
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
font-size: 14px;
}
#calendar {
max-width: 1100px;
margin: 0 auto;
}
</style>
</head>
<body>
<div id='calendar'></div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../lib/main.css' rel='stylesheet' />
<script src='../lib/main.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var srcCalendarEl = document.getElementById('source-calendar');
var destCalendarEl = document.getElementById('destination-calendar');
var srcCalendar = new FullCalendar.Calendar(srcCalendarEl, {
editable: true,
initialDate: '2020-09-12',
events: [
{
title: 'event1',
start: '2020-09-11T10:00:00',
end: '2020-09-11T16:00:00'
},
{
title: 'event2',
start: '2020-09-13T10:00:00',
end: '2020-09-13T16:00:00'
}
],
eventLeave: function(info) {
console.log('event left!', info.event);
}
});
var destCalendar = new FullCalendar.Calendar(destCalendarEl, {
initialDate: '2020-09-12',
editable: true,
droppable: true, // will let it receive events!
eventReceive: function(info) {
console.log('event received!', info.event);
}
});
srcCalendar.render();
destCalendar.render();
});
</script>
<style>
body {
margin: 20px 0 0 20px;
font-size: 14px;
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
}
#source-calendar,
#destination-calendar {
float: left;
width: 600px;
margin: 0 20px 20px 0;
}
</style>
</head>
<body>
<div id='source-calendar'></div>
<div id='destination-calendar'></div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../lib/main.css' rel='stylesheet' />
<script src='../lib/main.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
/* initialize the external events
-----------------------------------------------------------------*/
var containerEl = document.getElementById('external-events-list');
new FullCalendar.Draggable(containerEl, {
itemSelector: '.fc-event',
eventData: function(eventEl) {
return {
title: eventEl.innerText.trim()
}
}
});
//// the individual way to do it
// var containerEl = document.getElementById('external-events-list');
// var eventEls = Array.prototype.slice.call(
// containerEl.querySelectorAll('.fc-event')
// );
// eventEls.forEach(function(eventEl) {
// new FullCalendar.Draggable(eventEl, {
// eventData: {
// title: eventEl.innerText.trim(),
// }
// });
// });
/* initialize the calendar
-----------------------------------------------------------------*/
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek'
},
editable: true,
droppable: true, // this allows things to be dropped onto the calendar
drop: function(arg) {
// is the "remove after drop" checkbox checked?
if (document.getElementById('drop-remove').checked) {
// if so, remove the element from the "Draggable Events" list
arg.draggedEl.parentNode.removeChild(arg.draggedEl);
}
}
});
calendar.render();
});
</script>
<style>
body {
margin-top: 40px;
font-size: 14px;
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
}
#external-events {
position: fixed;
left: 20px;
top: 20px;
width: 150px;
padding: 0 10px;
border: 1px solid #ccc;
background: #eee;
text-align: left;
}
#external-events h4 {
font-size: 16px;
margin-top: 0;
padding-top: 1em;
}
#external-events .fc-event {
margin: 3px 0;
cursor: move;
}
#external-events p {
margin: 1.5em 0;
font-size: 11px;
color: #666;
}
#external-events p input {
margin: 0;
vertical-align: middle;
}
#calendar-wrap {
margin-left: 200px;
}
#calendar {
max-width: 1100px;
margin: 0 auto;
}
</style>
</head>
<body>
<div id='wrap'>
<div id='external-events'>
<h4>Draggable Events</h4>
<div id='external-events-list'>
<div class='fc-event fc-h-event fc-daygrid-event fc-daygrid-block-event'>
<div class='fc-event-main'>My Event 1</div>
</div>
<div class='fc-event fc-h-event fc-daygrid-event fc-daygrid-block-event'>
<div class='fc-event-main'>My Event 2</div>
</div>
<div class='fc-event fc-h-event fc-daygrid-event fc-daygrid-block-event'>
<div class='fc-event-main'>My Event 3</div>
</div>
<div class='fc-event fc-h-event fc-daygrid-event fc-daygrid-block-event'>
<div class='fc-event-main'>My Event 4</div>
</div>
<div class='fc-event fc-h-event fc-daygrid-event fc-daygrid-block-event'>
<div class='fc-event-main'>My Event 5</div>
</div>
</div>
<p>
<input type='checkbox' id='drop-remove' />
<label for='drop-remove'>remove after drop</label>
</p>
</div>
<div id='calendar-wrap'>
<div id='calendar'></div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../lib/main.css' rel='stylesheet' />
<script src='../lib/main.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
height: '100%',
expandRows: true,
slotMinTime: '08:00',
slotMaxTime: '20:00',
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek'
},
initialView: 'dayGridMonth',
initialDate: '2020-09-12',
navLinks: true, // can click day/week names to navigate views
editable: true,
selectable: true,
nowIndicator: true,
dayMaxEvents: true, // allow "more" link when too many events
events: [
{
title: 'All Day Event',
start: '2020-09-01',
},
{
title: 'Long Event',
start: '2020-09-07',
end: '2020-09-10'
},
{
groupId: 999,
title: 'Repeating Event',
start: '2020-09-09T16:00:00'
},
{
groupId: 999,
title: 'Repeating Event',
start: '2020-09-16T16:00:00'
},
{
title: 'Conference',
start: '2020-09-11',
end: '2020-09-13'
},
{
title: 'Meeting',
start: '2020-09-12T10:30:00',
end: '2020-09-12T12:30:00'
},
{
title: 'Lunch',
start: '2020-09-12T12:00:00'
},
{
title: 'Meeting',
start: '2020-09-12T14:30:00'
},
{
title: 'Happy Hour',
start: '2020-09-12T17:30:00'
},
{
title: 'Dinner',
start: '2020-09-12T20:00:00'
},
{
title: 'Birthday Party',
start: '2020-09-13T07:00:00'
},
{
title: 'Click for Google',
url: 'http://google.com/',
start: '2020-09-28'
}
]
});
calendar.render();
});
</script>
<style>
html, body {
overflow: hidden; /* don't do scrollbars */
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
font-size: 14px;
}
#calendar-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.fc-header-toolbar {
/*
the calendar will be butting up against the edges,
but let's scoot in the header's buttons
*/
padding-top: 1em;
padding-left: 1em;
padding-right: 1em;
}
</style>
</head>
<body>
<div id='calendar-container'>
<div id='calendar'></div>
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../lib/main.css' rel='stylesheet' />
<script src='../lib/main.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,listYear'
},
displayEventTime: false, // don't show the time column in list view
// THIS KEY WON'T WORK IN PRODUCTION!!!
// To make your own Google API key, follow the directions here:
// http://fullcalendar.io/docs/google_calendar/
googleCalendarApiKey: 'AIzaSyDcnW6WejpTOCffshGDDb4neIrXVUA1EAE',
// US Holidays
events: 'en.usa#holiday@group.v.calendar.google.com',
eventClick: function(arg) {
// opens events in a popup window
window.open(arg.event.url, 'google-calendar-event', 'width=700,height=600');
arg.jsEvent.preventDefault() // don't navigate in main tab
},
loading: function(bool) {
document.getElementById('loading').style.display =
bool ? 'block' : 'none';
}
});
calendar.render();
});
</script>
<style>
body {
margin: 40px 10px;
padding: 0;
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
font-size: 14px;
}
#loading {
display: none;
position: absolute;
top: 10px;
right: 10px;
}
#calendar {
max-width: 1100px;
margin: 0 auto;
}
</style>
</head>
<body>
<div id='loading'>loading...</div>
<div id='calendar'></div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../lib/main.css' rel='stylesheet' />
<script src='https://github.com/mozilla-comm/ical.js/releases/download/v1.4.0/ical.js'></script>
<script src='../lib/main.js'></script>
<script src='../packages/icalendar/main.global.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
displayEventTime: false,
initialDate: '2019-04-01',
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,listYear'
},
events: {
url: 'ics/feed.ics',
format: 'ics',
failure: function() {
document.getElementById('script-warning').style.display = 'block';
}
},
loading: function(bool) {
document.getElementById('loading').style.display =
bool ? 'block' : 'none';
}
});
calendar.render();
});
</script>
<style>
body {
margin: 0;
padding: 0;
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
font-size: 14px;
}
#script-warning {
display: none;
background: #eee;
border-bottom: 1px solid #ddd;
padding: 0 10px;
line-height: 40px;
text-align: center;
font-weight: bold;
font-size: 12px;
color: red;
}
#loading {
display: none;
position: absolute;
top: 10px;
right: 10px;
}
#calendar {
max-width: 1100px;
margin: 40px auto;
padding: 0 10px;
}
</style>
</head>
<body>
<div id='script-warning'>
<code>ics/feed.ics</code> must be servable
</div>
<div id='loading'>loading...</div>
<div id='calendar'></div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../lib/main.css' rel='stylesheet' />
<script src='../lib/main.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek'
},
initialDate: '2020-09-12',
editable: true,
navLinks: true, // can click day/week names to navigate views
dayMaxEvents: true, // allow "more" link when too many events
events: {
url: 'php/get-events.php',
failure: function() {
document.getElementById('script-warning').style.display = 'block'
}
},
loading: function(bool) {
document.getElementById('loading').style.display =
bool ? 'block' : 'none';
}
});
calendar.render();
});
</script>
<style>
body {
margin: 0;
padding: 0;
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
font-size: 14px;
}
#script-warning {
display: none;
background: #eee;
border-bottom: 1px solid #ddd;
padding: 0 10px;
line-height: 40px;
text-align: center;
font-weight: bold;
font-size: 12px;
color: red;
}
#loading {
display: none;
position: absolute;
top: 10px;
right: 10px;
}
#calendar {
max-width: 1100px;
margin: 40px auto;
padding: 0 10px;
}
</style>
</head>
<body>
<div id='script-warning'>
<code>php/get-events.php</code> must be running.
</div>
<div id='loading'>loading...</div>
<div id='calendar'></div>
</body>
</html>
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
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