Commit 04aedf66 by François C.

Merge branch 'dev_cooperatic' into 'dev_principale'

Intégration des dév. Cooperatic pour la Cagette

See merge request !164
parents 99590052 b1c17470
Pipeline #2157 passed with stage
in 1 minute 28 seconds
...@@ -115,10 +115,13 @@ ENTRANCE_EASY_SHIFT_VALIDATE_MSG = """Si vous faites un service dans un comité, ...@@ -115,10 +115,13 @@ ENTRANCE_EASY_SHIFT_VALIDATE_MSG = """Si vous faites un service dans un comité,
valider votre présence en cherchant<br/> valider votre présence en cherchant<br/>
votre nom ou numéro ci-dessous votre nom ou numéro ci-dessous
""" """
PREPA_ODOO_URL = '[...]'
# Members space / shifts # Members space / shifts
UNSUBSCRIBED_FORM_LINK = 'https://docs.google.com/forms/d/e/1FAIpQLScWcpls-ruYIp7HdrjRF1B1TyuzdqhvlUIcUWynbEujfj3dTg/viewform' 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>' 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' CONFIRME_PRESENT_BTN = 'Clique ici pour valider ta présence'
BLOCK_ACTIONS_FOR_ATTACHED_PEOPLE = False
RECEPTION_PB = "Ici, vous pouvez signaler toute anomalie lors d'une réception, les produits non commandés, cassés ou pourris. \ RECEPTION_PB = "Ici, vous pouvez signaler toute anomalie lors d'une réception, les produits non commandés, cassés ou pourris. \
...@@ -136,3 +139,6 @@ ORDERS_HELPER_METABASE_URL = "url_meta_base" ...@@ -136,3 +139,6 @@ ORDERS_HELPER_METABASE_URL = "url_meta_base"
USE_NEW_MEMBERS_SPACE = True USE_NEW_MEMBERS_SPACE = True
START_DATE_FOR_SHIFTS_HISTORY = "2018-01-01" START_DATE_FOR_SHIFTS_HISTORY = "2018-01-01"
AMNISTIE_DATE= "2021-11-24 00:00:00" AMNISTIE_DATE= "2021-11-24 00:00:00"
# BDM Admin
BDM_SHOW_FTOP_BUTTON = True
...@@ -43,13 +43,32 @@ class CagetteEnvelops(models.Model): ...@@ -43,13 +43,32 @@ class CagetteEnvelops(models.Model):
try: try:
# Get invoice # Get invoice
cond = [['partner_id', '=', data['partner_id']]]
# Get specific invoice if id is given
if 'invoice_id' in data:
cond = [['id', '=', data['invoice_id']], ["number", "!=", False]]
else:
cond = [['partner_id', '=', data['partner_id']], ["number", "!=", False]]
fields = ['id', 'name', 'number', 'partner_id', 'residual_signed'] fields = ['id', 'name', 'number', 'partner_id', 'residual_signed']
invoice_res = self.o_api.search_read('account.invoice', cond, fields) invoice_res = self.o_api.search_read('account.invoice', cond, fields)
# Check if invoice exists # Check if invoice exists
if len(invoice_res) > 0: if len(invoice_res) > 0:
invoice = invoice_res[0] invoice = None
# Get first invoice for which amount being paid <= amount left to pay in invoice
for invoice_item in invoice_res:
if int(float(data['amount']) * 100) <= int(float(invoice_item['residual_signed']) * 100):
invoice = invoice_item
if invoice is None:
res['error'] = 'The amount is too high for the invoices found for this partner.'
try:
# Got an error while logging...
coop_logger.error(res['error'] + ' : %s', str(data))
except Exception as e:
print(str(e))
return res
else: else:
res['error'] = 'No invoice found for this partner, can\'t create payment.' res['error'] = 'No invoice found for this partner, can\'t create payment.'
coop_logger.error(res['error'] + ' : %s', str(data)) coop_logger.error(res['error'] + ' : %s', str(data))
...@@ -79,6 +98,7 @@ class CagetteEnvelops(models.Model): ...@@ -79,6 +98,7 @@ class CagetteEnvelops(models.Model):
except Exception as e: except Exception as e:
res['error'] = repr(e) res['error'] = repr(e)
coop_logger.error(res['error'] + ' : %s', str(args)) coop_logger.error(res['error'] + ' : %s', str(args))
# Exception rises when odoo method returns nothing # Exception rises when odoo method returns nothing
marshal_none_error = 'cannot marshal None unless allow_none is enabled' marshal_none_error = 'cannot marshal None unless allow_none is enabled'
try: try:
...@@ -102,7 +122,6 @@ class CagetteEnvelops(models.Model): ...@@ -102,7 +122,6 @@ class CagetteEnvelops(models.Model):
coop_logger.error(res['error'] + ' : %s', str(data)) coop_logger.error(res['error'] + ' : %s', str(data))
if not ('error' in res): if not ('error' in res):
res['done'] = True res['done'] = True
res['payment_id'] = payment_id res['payment_id'] = payment_id
...@@ -111,6 +130,11 @@ class CagetteEnvelops(models.Model): ...@@ -111,6 +130,11 @@ class CagetteEnvelops(models.Model):
def delete_envelop(self, envelop): def delete_envelop(self, envelop):
return self.c_db.delete(envelop) return self.c_db.delete(envelop)
def archive_envelop(self, envelop):
envelop['archive'] = True
envelop['cashing_date'] = datetime.date.today().strftime("%d/%m/%Y")
return self.c_db.dbc.update([envelop])
def generate_envelop_display_id(self): def generate_envelop_display_id(self):
"""Generate a unique incremental id to display""" """Generate a unique incremental id to display"""
c_db = CouchDB(arg_db='envelops') c_db = CouchDB(arg_db='envelops')
...@@ -172,7 +196,7 @@ class CagetteEnvelops(models.Model): ...@@ -172,7 +196,7 @@ class CagetteEnvelops(models.Model):
else: else:
# Get the oldest check envelops, limited by the number of checks # Get the oldest check envelops, limited by the number of checks
docs = [] docs = []
for item in c_db.dbc.view('index/by_type', key='ch', include_docs=True, limit=payment_data['checks_nb']): for item in c_db.dbc.view('index/by_type_not_archive', key='ch', include_docs=True, limit=payment_data['checks_nb']):
docs.append(item.doc) docs.append(item.doc)
# If only 1 check to save # If only 1 check to save
......
#cash, #ch {
margin-top: 15px;
}
#admin_connexion_button {
position: absolute;
top: 5px;
right: 5px;
}
.envelop_section { .envelop_section {
margin-bottom: 10px; margin-bottom: 10px;
} }
...@@ -6,14 +16,132 @@ ...@@ -6,14 +16,132 @@
display: none; display: none;
cursor: pointer; cursor: pointer;
margin-bottom: 15px; margin-bottom: 15px;
width: 25%;
} }
#cash_envelops { #cash_envelops, #ch_envelops, #archive_cash_envelops, #archive_ch_envelops {
margin-top: 30px; margin-top: 30px;
} }
#ch_envelops { .delete_envelop_button, .envelop_comment {
margin-top: 30px; margin: 0 0 15px 15px;
}
.update_envelop_button, .add_to_envelop_button {
margin: 0 0 15px 10px;
}
.envelop_content_list {
margin: 20px 0 15px 0;
}
/* Update envelop modal */
.envelop_lines {
margin: 20px 0;
display: flex;
flex-direction: column;
align-items: center;
}
.update_envelop_line {
margin: 5px 0;
width: 60%;
display: flex;
position: relative;
}
.deleted_line_through {
border-bottom: 1px solid #d9534f;
position: absolute;
content: "";
width: 100%;
height: 50%;
display: none;
}
.update_envelop_line div {
flex: 50% 1 0;
}
.update_envelop_line .line_partner_name_container {
display: flex;
justify-content: flex-start;
align-items: center;
}
.update_envelop_line .line_partner_name {
text-align: left;
padding: 0 5px;
}
.update_envelop_line .line_partner_input_container {
display: flex;
align-items: center;
}
.delete_envelop_line_icon {
margin-left: 10px;
color: #d9534f;
cursor: pointer;
}
.envelop_comments {
width: 60%;
margin-bottom: 20px;
}
/* Add payments to envelop modal */
.search_member_area {
margin: 20px 0;
}
.search_member_results_area {
margin: 20px 0;
}
.add_to_envelop_lines_area {
margin: 20px 0;
}
.add_to_envelop_lines {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin: 10px 0;
}
.add_to_envelop_line {
display: flex;
justify-content: center;
margin: 10px 0;
}
.add_to_envelop_line .partner_name_container {
display: flex;
justify-content: flex-start;
align-items: center;
}
.add_to_envelop_line .partner_input_container {
display: flex;
flex-direction: column;
align-items: flex-start;
margin-left: 10px;
width: 300px;
}
.line_partner_amount_error {
display: none;
color: #d9534f;
font-style: italic;
}
.confirm_add_payment_details {
font-weight: bold;
margin: 0 3px;
} }
/* Accordion style */ /* Accordion style */
...@@ -23,7 +151,6 @@ ...@@ -23,7 +151,6 @@
color: #212529; color: #212529;
cursor: pointer; cursor: pointer;
padding: 18px; padding: 18px;
/* width: 80%; */
text-align: left; text-align: left;
border: none; border: none;
outline: none; outline: none;
...@@ -32,7 +159,6 @@ ...@@ -32,7 +159,6 @@
.archive_button { .archive_button {
padding: 18px; padding: 18px;
/* width: 20%; */
} }
hr { hr {
......
...@@ -21,13 +21,13 @@ def index(request): ...@@ -21,13 +21,13 @@ def index(request):
return HttpResponse(template.render(context, request)) return HttpResponse(template.render(context, request))
def archive_envelop(request): def archive_envelop(request):
"""Save members payment and destroy the envelop""" """Save members payment and archive the envelop"""
m = CagetteEnvelops() m = CagetteEnvelops()
res_payments = [] res_payments = []
res_envelop = "" res_envelop = ""
envelop = json.loads(request.body.decode()) envelop = json.loads(request.body.decode())
# save each partner payment # save each partner payment
for partner_id in envelop['envelop_content']: for partner_id in envelop['envelop_content']:
# If payment_id in payment details: payment already saved. Skip saving. # If payment_id in payment details: payment already saved. Skip saving.
...@@ -40,6 +40,9 @@ def archive_envelop(request): ...@@ -40,6 +40,9 @@ def archive_envelop(request):
'type' : envelop['type'] 'type' : envelop['type']
} }
if 'invoice_id' in envelop['envelop_content'][partner_id]:
data['invoice_id'] = int(envelop['envelop_content'][partner_id]['invoice_id'])
res = m.save_payment(data) res = m.save_payment(data)
except Exception as e: except Exception as e:
res = { res = {
...@@ -52,7 +55,7 @@ def archive_envelop(request): ...@@ -52,7 +55,7 @@ def archive_envelop(request):
# Immediately save a token than this payment has been saved # Immediately save a token than this payment has been saved
# If an error occurs, this payment won't be saved again # If an error occurs, this payment won't be saved again
envelop['envelop_content'][partner_id]['payment_id'] = res['payment_id'] envelop['envelop_content'][partner_id]['payment_id'] = res['payment_id']
updated_envelop = m.c_db.updateDoc(envelop); updated_envelop = m.c_db.updateDoc(envelop)
envelop['_rev'] = updated_envelop['_rev'] envelop['_rev'] = updated_envelop['_rev']
else: else:
# Handling error when saving payment, return data to display error message # Handling error when saving payment, return data to display error message
...@@ -75,8 +78,8 @@ def archive_envelop(request): ...@@ -75,8 +78,8 @@ def archive_envelop(request):
coop_logger.error("Cannot attach payment error message to member : %s", str(e)) coop_logger.error("Cannot attach payment error message to member : %s", str(e))
try: try:
# Delete envelop from couchdb # archive envelop in couchdb
res_envelop = m.delete_envelop(envelop) res_envelop = m.archive_envelop(envelop)
except Exception as e: except Exception as e:
res_envelop = "error" res_envelop = "error"
......
...@@ -368,10 +368,10 @@ class CagetteInventory(models.Model): ...@@ -368,10 +368,10 @@ class CagetteInventory(models.Model):
return {'missed': missed, 'unchanged': unchanged, 'done': done} return {'missed': missed, 'unchanged': unchanged, 'done': done}
@staticmethod @staticmethod
def update_stock_with_shelf_inventory_data(inventory_data): def update_products_stock(inventory_data, precision=2):
"""Updates Odoo stock after a shelf inventory""" """ Updates Odoo stock after a shelf inventory or another action"""
TWOPLACES = Decimal(10) ** -2 TWOPLACES = Decimal(10) ** -precision
api = OdooAPI() api = OdooAPI()
missed = [] missed = []
unchanged = [] unchanged = []
......
...@@ -94,7 +94,7 @@ def do_custom_list_inventory(request): ...@@ -94,7 +94,7 @@ def do_custom_list_inventory(request):
full_inventory_data = CagetteInventory.get_full_inventory_data(inventory_data) full_inventory_data = CagetteInventory.get_full_inventory_data(inventory_data)
# Proceed with inventory # Proceed with inventory
res['inventory'] = CagetteInventory.update_stock_with_shelf_inventory_data(full_inventory_data) res['inventory'] = CagetteInventory.update_products_stock(full_inventory_data)
# remove file # remove file
CagetteInventory.remove_custom_inv_file(inventory_data['id']) CagetteInventory.remove_custom_inv_file(inventory_data['id'])
......
...@@ -53,9 +53,17 @@ class Command(BaseCommand): ...@@ -53,9 +53,17 @@ class Command(BaseCommand):
byTypeMapFunction = '''function(doc) { byTypeMapFunction = '''function(doc) {
emit(doc.type); emit(doc.type);
}''' }'''
byTypeNotArchiveMapFunction = '''function(doc) {
if(doc.archive != true){
emit(doc.type);
}
}'''
views = { views = {
"by_type": { "by_type": {
"map": byTypeMapFunction "map": byTypeMapFunction
},
"by_type_not_archive": {
"map": byTypeNotArchiveMapFunction
} }
} }
self.createView(dbConn, "index", views) self.createView(dbConn, "index", views)
......
.header {
margin-top: 15px;
}
.management_type_buttons {
margin-top: 60px;
}
.management_type_button {
height: 2.2em;
width: 30%;
border-radius: 3px;
margin: 10px;
font-size: 1.3em;
}
.management_type_button_icons {
float: right;
margin: 2px;
}
.login_area {
position: absolute;
top: 5px;
right: 5px;
}
.header {
margin: 1rem 0;
}
.login_area {
position: absolute;
display: block;
top: 5px;
right: 5px;
}
#back_to_admin_index {
position: absolute;
top: 5px;
left: 5px;
}
/* Buttons */
.management_type_buttons {
margin-top: 60px;
display: flex;
justify-content: center;
}
.management_type_button {
height: 2.2em;
width: 30%;
border-radius: 3px;
margin: 10px;
font-size: 1.3em;
}
.create_pair_button {
display: flex;
justify-content: center;
margin: 120px 0 120px 0;
}
@media screen and (max-width: 992px) {
.create_pair_button {
margin: auto;
}
}
#createPair {
border-radius: 30px;
}
/* Search membres area */
.search_member_form_area {
align-items: center;
padding-bottom: 20px;
}
.search_member_form {
margin-left: 10px;
}
/* Member infos */
.tile_icon {
margin-right: 15px;
color: #00a573;
}
.member_info {
font-weight: bold;
}
.member_status_text_container {
margin: 1rem;
}
/* Attached members table */
.table_area {
margin-top: 20px;
}
#table_top_area {
display: flex;
justify-content: space-between;
}
/* -- Tiles */
.tiles_container {
display: flex;
flex-wrap: wrap;
}
@media screen and (max-width: 992px) {
.tiles_container {
flex-direction: column;
}
}
.tile {
flex: 1 0 20%;
display: flex;
flex-direction: column;
text-align: center;
border-radius: 30px;
margin: 1rem 1rem;
padding: 1rem;
box-shadow: 2px 2px 3px rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.1);
}
.tile_content {
height: 100%;
flex-direction: column;
text-align: center;
font-size: 1.6rem;
}
.spinner {
height:40px;
}
/* Modale */
.attached-members .member div {width: 50px; display: inline-block; margin: 1rem 0;}
.attached-members .member div.name {width: 100%; position: relative;}
.attached-members .member div.select_after_unattached_state {
position: absolute;
right:5rem;
top:0;
bottom:0;
margin: 0;
display:flex;
justify-content: center ;
align-items: center;
}
.attached-members .member .after_unattached_state {
cursor: pointer;
}
\ No newline at end of file
.page_body{
position: relative;
}
.header { .header {
margin: 1.5rem 0; margin: 1rem 0;
} }
.login_area { .login_area {
position: absolute; position: absolute;
top: 0; display: block;
left: 0; top: 5px;
right: 0; right: 5px;
} }
.tabs { #back_to_admin_index {
margin-top: 1em; position: absolute;
margin-bottom: 1em; top: 5px;
overflow: hidden; left: 5px;
}
.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 { #table_top_area {
......
.header {
margin: 1rem 0;
}
.login_area {
position: absolute;
display: block;
top: 5px;
right: 5px;
}
#back_to_admin_index {
position: absolute;
top: 5px;
left: 5px;
}
/* Search members area */
#search_member_area {
margin-top: 30px;
display: flex;
flex-direction: column;
align-items: center;
}
#search_member_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_results_text {
min-width: 150px;
}
.search_member_results {
display: flex;
flex-wrap: wrap;
}
.btn_possible_member {
margin: 0.5rem 1rem;
}
/* Member info area */
#partner_data_area {
width: 80%;
display: none;
flex-direction: column;
align-items: center;
border-radius: 30px;
margin: 30px auto;
padding: 15px;
box-shadow: 2px 2px 3px rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.1);
}
.member_name_container {
margin: 1rem 0 2rem 0;
}
.member_name_icon {
color: #00a573;
margin-right: 5px;
}
.member_info {
font-weight: bold;
}
/* Actions */
#remove_shift_template_button,
#change_shift_template_button,
#subscribe_to_shift_template_button {
display: none;
margin: 15px;
}
.checkbox_area {
margin: 10px 0;
}
#permanent_unsuscribe {
margin-right: 5px;
}
.error_modal_title {
color:#d9534f;
font-weight: bold;
}
/* Calendar */
#shifts_calendar_area {
display: none;
margin-top: 15px;
}
.shift[data-place="Cleme"], [data-select="Cleme"] {background: #c8deff;}
#subs_cap {width: 200px;}
.lat_menu button {margin-bottom:5px;}
.oddeven_selector {margin-right:25px;}
.main_content .shift {float:left;}
.main_content .shift.full {display:none;}
.shift {margin:2px; padding:2px; cursor: cell;}
.shift.alert,span.alert {border-bottom: 3px #e52121 solid;}
.highlighted {box-shadow: 10px 10px 5px grey;}
.lat_menu.highlighted {border: 2px #fffc07 dotted;}
#coop_list_view td.coop:hover,
#coop_list_view td.c_shift:hover {background:#fffc07; cursor:pointer;}
.b_red {color:#ffffff;}
.shift[data-type="compact"] {border: 1px solid #000000;border-radius: 5px; padding:5px; cursor: pointer;}
.shift_template, .next_shift {font-weight: bold;}
\ No newline at end of file
.header {
margin: 1rem 0;
}
.login_area {
position: absolute;
display: block;
top: 5px;
right: 5px;
}
#back_to_admin_index {
position: absolute;
top: 5px;
left: 5px;
}
#table_top_area {
display: none;
width: 90%;
margin: 35px auto 0 auto;
text-align: center;
}
.table_area {
margin: 0 auto;
width: 90%;
display: flex;
justify-content: center;
}
.delete_shift_registration {
color: #d9534f;
cursor: pointer;
}
#member_shifts_table_filter {
padding-top: 0.755em;
}
/* Search members area */
.makeup_row {
background-color: #ffc854 !important;
}
#search_member_area {
margin-top: 30px;
display: flex;
flex-direction: column;
align-items: center;
}
#search_member_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_results_text {
min-width: 150px;
}
.search_member_results {
display: flex;
flex-wrap: wrap;
}
.btn_possible_member {
margin: 0.5rem 1rem;
}
\ No newline at end of file
.nav .fr {margin-left:10px;} .nav .fr {margin-left:10px;}
.nav .fl {margin-right:10px;}
.nav #process_state_container{
min-height: 36px;
}
.nav #process_state{
min-height: 36px;
display: flex;
align-items: center;
gap: 3px;
}
.nav #goto_prepa_odoo_button,
.nav #goto_prepa_odoo_button:hover {
text-decoration: none;
}
#shift_choice > div, #new_coop, #coop_list_view,#coop_registration_details {display:none;} #shift_choice > div, #new_coop, #coop_list_view,#coop_registration_details {display:none;}
...@@ -23,6 +38,71 @@ ...@@ -23,6 +38,71 @@
[data-week="4"] {border: 2px #eed000 solid;} [data-week="4"] {border: 2px #eed000 solid;}
#new_coop [name="email"] {width:25em;} #new_coop [name="email"] {width:25em;}
#new_coop{
max-width: 75%;
}
#mail_generation {position:absolute; bottom:30px;} #mail_generation {position:absolute; bottom:30px;}
#sex {padding: 0;} #sex {padding: 0;}
#existing_partner{
padding-right: 15px;
width: 45%;
}
#new_partner{
padding-left: 15px;
width: 45%;
}
#add_binome{
cursor: pointer;
text-decoration: underline;
font-weight: bold;
}
#existing_member_choice, #new_member_choice{
border: 1px solid black;
width: 40%;
cursor: pointer;
text-align: center;
max-width: 400px;
background-color: #e7e9ed;
color: black;
padding: 1rem 1.5rem;
}
#existing_member_choice:hover, #new_member_choice:hover{
background-color: #ccc;
}
.choice_active{
background-color: #0275d8 !important;
color: white !important;
}
#existing_member_choice{
margin-right: 15px;
}
.choice_button_area{
margin-bottom: 10px;
}
#associate_area{
margin-bottom: 15px;
}
.remove_binome_icon {
margin-left: 10px;
color: #e62720;
}
.remove_binome_icon:hover {
cursor: pointer;
}
.chosen_associate_group {
display: flex;
align-items: center;
}
\ No newline at end of file
...@@ -44,8 +44,10 @@ h1 .member_name {font-weight: bold;} ...@@ -44,8 +44,10 @@ h1 .member_name {font-weight: bold;}
.members_list {list-style: none;} .members_list {list-style: none;}
.members_list li {display:block;margin-bottom:5px;} .members_list li {display:block;margin-bottom:5px;}
.members_list li.btn--inverse {background: #449d44; cursor:not-allowed; color: #FFF; } .members_list li.btn--inverse {background: #449d44; color: #FFF; }
.members_list li.btn--inverse.not_connected {cursor:not-allowed}
.members_list li.btn--inverse.late {background-color: #de9b00; color: white} .members_list li.btn--inverse.late {background-color: #de9b00; color: white}
.members_list li.btn--inverse.both {background-color: #0275d8 ; color: white}
#service_entry_success {font-size: x-large;} #service_entry_success {font-size: x-large;}
#service_entry_success .explanations {margin: 25px 0; font-size: 18px;} #service_entry_success .explanations {margin: 25px 0; font-size: 18px;}
...@@ -64,7 +66,7 @@ h1 .member_name {font-weight: bold;} ...@@ -64,7 +66,7 @@ h1 .member_name {font-weight: bold;}
#service_en_cours .info a {line-height: 24px; font-size:14px; margin-top:15px;} #service_en_cours .info a {line-height: 24px; font-size:14px; margin-top:15px;}
.outside_list a {margin-left:15px;} .outside_list a {margin-left:15px;}
#rattrapage_1 .advice {margin-top:15px;} #rattrapage_1 .advice {margin-top:15px;}
.btn.present {background:#50C878;} .btn.present {background:#50C878; color: white !important; font-weight: bold;}
.btn.return {background: #ff3333; color:#FFF !important; margin-top:25px;} .btn.return {background: #ff3333; color:#FFF !important; margin-top:25px;}
.msg-big {font-size: xx-large; background: #fff; padding:25px; text-align: center;} .msg-big {font-size: xx-large; background: #fff; padding:25px; text-align: center;}
...@@ -72,3 +74,6 @@ h1 .member_name {font-weight: bold;} ...@@ -72,3 +74,6 @@ h1 .member_name {font-weight: bold;}
#member_advice {background: #FFF; color: red;} #member_advice {background: #FFF; color: red;}
.easy_shift_validate {text-align: center; margin-top: 3em;} .easy_shift_validate {text-align: center; margin-top: 3em;}
.button_is_member {background-color: #439D44; color: #fff;}
.button_is_associated_people {background: #0275D8; color: #fff;}
$(document).ready(function() {
if (coop_is_connected()) {
$.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } });
$(".page_content").show();
let location = window.location.href.replace(/\/$/, '');
$('.management_type_button').on('click', function() {
if (this.id == 'manage_makeups_button') {
window.location.assign(location + "/manage_makeups");
} else if (this.id == 'manage_shift_registrations_button') {
window.location.assign(location + "/manage_shift_registrations");
} else if (this.id == 'manage_attached_button') {
window.location.assign(location + "/manage_attached");
} else if (this.id == 'manage_attached_delete_pair_button') {
window.location.assign(location + "/delete_pair");
} else if (this.id == 'manage_attached_create_pair_button') {
window.location.assign(location + "/create_pair");
} else if (this.id == 'manage_leaves_button') {
console.log('coming soon...');
} else if (this.id == 'manage_regular_shifts_button') {
window.location.assign(location + "/manage_regular_shifts");
}
});
} else {
$(".page_content").hide();
}
});
\ No newline at end of file
...@@ -3,33 +3,6 @@ var makeups_members_table = null, ...@@ -3,33 +3,6 @@ var makeups_members_table = null,
members_search_results = [], members_search_results = [],
selected_rows = []; // Contain members id selected_rows = []; // Contain members id
function switch_active_tab() {
// Set tabs
$('.tab').removeClass('active');
$(this).addClass('active');
// Tabs content
$('.tab_content').hide();
let tab = $(this).attr('id');
if (tab == 'tab_makeups') {
$('#tab_makeups_content').show();
}
load_tab_data();
}
/**
* Load data for the current tab
*/
function load_tab_data() {
let current_tab = $('.tab .active').attr('id');
if (current_tab === 'tab_makeups' && makeups_members === null) {
load_makeups_members();
}
}
/** /**
* Load partners who have makeups to do * Load partners who have makeups to do
...@@ -96,6 +69,21 @@ function display_makeups_members() { ...@@ -96,6 +69,21 @@ function display_makeups_members() {
title: "Nom" title: "Nom"
}, },
{ {
data: "shift_type",
title: "Nb de points",
className: "dt-body-center",
width: "10%",
render: function (data, type, row) {
if (data == 'ftop') {
return row.display_ftop_points;
} else if (data == 'standard') {
return row.display_std_points;
}
return null;
}
},
{
data: "makeups_to_do", data: "makeups_to_do",
title: "Nb rattrapages", title: "Nb rattrapages",
className: "dt-body-center", className: "dt-body-center",
...@@ -238,16 +226,36 @@ function update_members_makeups(member_ids, action) { ...@@ -238,16 +226,36 @@ function update_members_makeups(member_ids, action) {
data = []; data = [];
for (mid of member_ids) { for (mid of member_ids) {
member_index = makeups_members.findIndex(m => m.id == mid); member_index = makeups_members.findIndex(m => m.id == mid);
/* Becareful : makeups_members will be modified while nobody knows wether ajax process will succeed or not !
TODO : make the changes only when it is sure that odoo records have been changed
*/
if (action === "increment") { if (action === "increment") {
makeups_members[member_index].makeups_to_do += 1; makeups_members[member_index].makeups_to_do += 1;
} else { } else {
makeups_members[member_index].makeups_to_do -= 1; makeups_members[member_index].makeups_to_do -= 1;
} }
if (makeups_members[member_index].shift_type === 'standard') {
if (action === "increment") {
if (makeups_members[member_index].display_std_points >= -1)
makeups_members[member_index].display_std_points -= 1;
} else if (makeups_members[member_index].display_std_points < 0) {
makeups_members[member_index].display_std_points += 1;
}
} else {
if (action === "increment") {
if (makeups_members[member_index].display_ftop_points >= -1)
makeups_members[member_index].display_ftop_points -= 1;
} else {
makeups_members[member_index].display_ftop_points += 1;
}
}
data.push({ data.push({
member_id: mid, member_id: mid,
target_makeups_nb: makeups_members[member_index].makeups_to_do, target_makeups_nb: makeups_members[member_index].makeups_to_do,
member_shift_type: makeups_members[member_index].shift_type member_shift_type: makeups_members[member_index].shift_type,
display_ftop_points: makeups_members[member_index].display_ftop_points,
display_std_points: makeups_members[member_index].display_std_points
}); });
} }
...@@ -282,6 +290,7 @@ function update_members_makeups(member_ids, action) { ...@@ -282,6 +290,7 @@ function update_members_makeups(member_ids, action) {
function display_possible_members() { function display_possible_members() {
$('.search_member_results_area').show(); $('.search_member_results_area').show();
$('.search_member_results').empty(); $('.search_member_results').empty();
$('.btn_possible_member').off();
let no_result = true; let no_result = true;
...@@ -303,39 +312,42 @@ function display_possible_members() { ...@@ -303,39 +312,42 @@ function display_possible_members() {
$('.search_member_results').append(member_button); $('.search_member_results').append(member_button);
// Set action on member button click }
$('.btn_possible_member').on('click', function() {
for (member of members_search_results) { // Set action on member button click
if (member.id == $(this).attr('member_id')) { $('.btn_possible_member').on('click', function() {
if (makeups_members === null) { for (member of members_search_results) {
makeups_members = []; if (member.id == $(this).attr('member_id')) {
} if (makeups_members === null) {
makeups_members = [];
makeups_members.unshift({
id: member.id,
name: member.name,
makeups_to_do: 0,
shift_type: member.shift_type
});
openModal(
`Ajouter un rattrapage à ${member.name} ?`,
() => {
update_members_makeups([member.id], "increment");
members_search_results = [];
$('#search_member_input').val('');
$('.search_member_results_area').hide();
$('.search_member_results').empty();
},
"Confirmer",
false
);
break;
} }
makeups_members.unshift({
id: member.id,
name: member.name,
makeups_to_do: 0,
shift_type: member.shift_type,
display_std_points: member.display_std_points,
display_ftop_points: member.display_ftop_points
});
openModal(
`Ajouter un rattrapage à ${member.name} ?`,
() => {
update_members_makeups([member.id], "increment");
members_search_results = [];
$('#search_member_input').val('');
$('.search_member_results_area').hide();
$('.search_member_results').empty();
},
"Confirmer",
false
);
break;
} }
}); }
} });
} }
if (no_result === true) { if (no_result === true) {
...@@ -352,34 +364,33 @@ $(document).ready(function() { ...@@ -352,34 +364,33 @@ $(document).ready(function() {
$(".page_content").show(); $(".page_content").show();
load_makeups_members(); load_makeups_members();
$(".tabs .tab").on('click', switch_active_tab);
} else { } else {
$(".page_content").hide(); $(".page_content").hide();
} }
$('#back_to_admin_index').on('click', function() {
let base_location = window.location.href.split("manage_makeups")[0].slice(0, -1);
window.location.assign(base_location);
});
// Set action to search for the member // Set action to search for the member
$('#search_member_form').submit(function() { $('#search_member_form').submit(function() {
let search_str = $('#search_member_input').val(); let search_str = $('#search_member_input').val();
$.ajax({ $.ajax({
url: '/members/search/' + search_str, url: '/members/search/' + search_str +'?search_type=makeups_data',
dataType : 'json', dataType : 'json',
success: function(data) { success: function(data) {
members_search_results = []; members_search_results = [];
members_search_results = data.res;
for (member of data.res) {
if (member.is_member || member.is_associated_people) {
members_search_results.push(member);
}
}
display_possible_members(); display_possible_members();
}, },
error: function() { error: function() {
err = { err = {
msg: "erreur serveur lors de la recherche de membres", msg: "erreur serveur lors de la recherche de membres",
ctx: 'confirm_movement.search_members' ctx: 'members.admin.manage_makeups.search_members'
}; };
report_JS_error(err, 'stock'); report_JS_error(err, 'stock');
......
var member_shifts_table = null,
members_search_results = [],
selected_member = null,
incoming_shifts = null;
/**
* Load partners who have makeups to do
*/
function load_member_future_shifts() {
$.ajax({
type: 'GET',
url: "/shifts/get_list_shift_partner/" + selected_member.id,
dataType:"json",
traditional: true,
contentType: "application/json; charset=utf-8",
success: function(data) {
incoming_shifts = data;
display_member_shifts();
},
error: function(data) {
err = {msg: "erreur serveur lors de la récupération des services du membre", ctx: 'load_member_future_shifts'};
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error;
}
report_JS_error(err, 'members.admin');
closeModal();
alert('Erreur lors de la récupération des services du membre.');
}
});
}
/**
* Display table of member future shifts
*/
function display_member_shifts() {
if (member_shifts_table) {
$('#member_shifts_table').off();
member_shifts_table.clear().destroy();
$('#member_shifts_table').empty();
}
$('#table_top_area #member_name').text(selected_member.name);
$('#table_top_area').show();
member_shifts_table = $('#member_shifts_table').DataTable({
data: incoming_shifts,
columns: [
{
data: "date_begin",
title: "",
visible: false
},
{
data: "shift_id",
title: "Service",
orderable: false,
render: function (data) {
return data[1];
}
},
{
data: null,
title: "",
className: "dt-body-center",
orderable: false,
width: "5%",
render: function () {
return `<i class="fa fa-lg fa-times delete_shift_registration"></i>`;
}
}
],
order: [
[
0,
"asc"
]
],
paging: false,
dom: 'tif',
oLanguage: {
"sProcessing": "Traitement en cours...",
"sSearch": "Rechercher dans le tableau",
"sInfo": "Total de _TOTAL_ &eacute;l&eacute;ments",
"sInfoEmpty": "",
"sInfoFiltered": "(filtr&eacute; de _MAX_ &eacute;l&eacute;ments au total)",
"sInfoPostFix": "",
"sLoadingRecords": "Chargement en cours...",
"sZeroRecords": "Aucun &eacute;l&eacute;ment &agrave; afficher",
"sEmptyTable": "Aucun futur service pour ce.tte membre"
},
createdRow: function(row, rdata) {
if (rdata.is_makeup === true) {
$(row).addClass("makeup_row");
$(row).prop('title', 'Ce service est un rattrapage');
}
}
});
$('#member_shifts_table').on('click', 'tbody td .delete_shift_registration', function () {
const row_data = member_shifts_table.row($(this).parents('tr')).data();
const shift_reg_id = row_data.id;
const shift_is_makeup = row_data.is_makeup;
let msg = `<p>Enlever la présence de <b>${member.name}</b> au service du <b>${row_data.shift_id[1]}</b> ?</p>`;
if (shift_is_makeup === true) {
msg += `<p><i class="fas fa-exclamation-triangle"></i> Ce service est un rattrapage. Le supprimer ajoutera un point au compteur de ce.tte membre.</p>`;
}
openModal(
msg,
() => {
delete_shift_registration(shift_reg_id, shift_is_makeup);
},
"Confirmer",
false
);
});
}
/**
* Send request to delete shift registration
* @param {Int} shift_reg_id Id of the shift_registration to delete
* @param {Boolean} shift_is_makeup Is the shift a makeup?
*/
function delete_shift_registration(shift_reg_id, shift_is_makeup) {
openModal();
data = {
member_id: selected_member.id,
member_shift_type: selected_member.shift_type,
shift_registration_id: shift_reg_id,
shift_is_makeup: shift_is_makeup
};
$.ajax({
type: 'POST',
url: "/members/delete_shift_registration",
data: JSON.stringify(data),
dataType:"json",
traditional: true,
contentType: "application/json; charset=utf-8",
success: function() {
closeModal();
alert("La présence a bien été annulée.");
const i = incoming_shifts.findIndex(is => is.id === shift_reg_id);
incoming_shifts.splice(i, 1);
display_member_shifts();
},
error: function(data) {
err = {msg: "erreur serveur pour supprimer la présence au service", ctx: 'delete_shift_registration'};
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error;
}
report_JS_error(err, 'members.admin');
closeModal();
alert('Erreur serveur pour supprimer la présence au service. Ré-essayez plus tard.');
}
});
}
/**
* Display the members from the search result
*/
function display_possible_members() {
$('.search_member_results_area').show();
$('.search_member_results').empty();
$('.btn_possible_member').off();
let no_result = true;
if (members_search_results.length > 0) {
for (member of members_search_results) {
$(".search_results_text").show();
no_result = false;
// Display results (possible members) as buttons
var member_button = '<button class="btn--success btn_possible_member" member_id="'
+ member.id + '">'
+ member.barcode_base + ' - ' + member.name
+ '</button>';
$('.search_member_results').append(member_button);
}
// Set action on member button click
$('.btn_possible_member').on('click', function() {
for (member of members_search_results) {
if (member.id == $(this).attr('member_id')) {
selected_member = member;
load_member_future_shifts();
$('.search_member_results').empty();
$('.search_member_results_area').hide();
$('#search_member_input').val('');
break;
}
}
});
}
if (no_result === true) {
$(".search_results_text").hide();
$('.search_member_results').html(`<p>
<i>Aucun résultat ! Vérifiez votre recherche, ou si le.la membre n'est pas déjà dans le tableau...</i>
</p>`);
}
}
$(document).ready(function() {
if (coop_is_connected()) {
$.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } });
$(".page_content").show();
} else {
$(".page_content").hide();
}
$('#back_to_admin_index').on('click', function() {
let base_location = window.location.href.split("manage_shift_registrations")[0].slice(0, -1);
window.location.assign(base_location);
});
// Set action to search for the member
$('#search_member_form').submit(function() {
let search_str = $('#search_member_input').val();
$.ajax({
url: '/members/search/' + search_str,
dataType : 'json',
success: function(data) {
members_search_results = [];
for (member of data.res) {
if (member.is_member || member.is_associated_people) {
members_search_results.push(member);
}
}
display_possible_members();
},
error: function() {
err = {
msg: "erreur serveur lors de la recherche de membres",
ctx: 'search_member_form.search_members'
};
report_JS_error(err, 'members.admin');
$.notify("Erreur lors de la recherche de membre, il faut ré-essayer plus tard...", {
globalPosition:"top right",
className: "error"
});
}
});
});
});
...@@ -31,12 +31,14 @@ var search_field = $('input[name="search_string"]'); ...@@ -31,12 +31,14 @@ var search_field = $('input[name="search_string"]');
var shift_title = $('#current_shift_title'); var shift_title = $('#current_shift_title');
var shift_members = $('#current_shift_members'); var shift_members = $('#current_shift_members');
var service_validation = $('#service_validation'); var service_validation = $('#service_validation');
var associated_service_validation = $('#associated_service_validation');
var validation_last_call = 0; var validation_last_call = 0;
var rattrapage_wanted = $('[data-next="rattrapage_1"]'); var rattrapage_wanted = $('[data-next="rattrapage_1"]');
var webcam_is_attached = false; var webcam_is_attached = false;
var photo_advice = $('#photo_advice'); var photo_advice = $('#photo_advice');
var photo_studio = $('#photo_studio'); var photo_studio = $('#photo_studio');
var coop_info = $('.coop-info'); var coop_info = $('.coop-info');
var service_data = null;
const missed_begin_msg = $('#missed_begin_msg').html(); const missed_begin_msg = $('#missed_begin_msg').html();
...@@ -93,8 +95,9 @@ function fill_member_slide(member) { ...@@ -93,8 +95,9 @@ function fill_member_slide(member) {
html_elts.image_medium.html('<img src="'+img_src+'" width="128" />'); html_elts.image_medium.html('<img src="'+img_src+'" width="128" />');
html_elts.cooperative_state.html(member.cooperative_state); html_elts.cooperative_state.html(member.cooperative_state);
if (member.cooperative_state == 'Rattrapage') { if (member.cooperative_state == 'Rattrapage') {
var explanation = "Tu as dû manquer un service! Pour pouvoir faire tes courses aujourd'hui, tu dois d'abord sélectionner un rattrapage sur ton espace membre." var explanation = "Tu as dû manquer un service! Pour pouvoir faire tes courses aujourd'hui, tu dois d'abord sélectionner un rattrapage sur ton espace membre.";
html_elts.status_explanation.html(explanation)
html_elts.status_explanation.html(explanation);
} }
if (member.cooperative_state == 'Désinscrit(e)') coop_info.addClass('b_red'); if (member.cooperative_state == 'Désinscrit(e)') coop_info.addClass('b_red');
else if (member.cooperative_state == 'En alerte' || member.cooperative_state == 'Délai accordé' || member.cooperative_state == 'Rattrapage') coop_info.addClass('b_orange'); else if (member.cooperative_state == 'En alerte' || member.cooperative_state == 'Délai accordé' || member.cooperative_state == 'Rattrapage') coop_info.addClass('b_orange');
...@@ -146,9 +149,17 @@ function preview_results() { ...@@ -146,9 +149,17 @@ function preview_results() {
for (i in results) { for (i in results) {
if (results[i].is_member != false || results[i].is_associated_people != false) { if (results[i].is_member != false) {
var m = $('<button>').attr('data-i', i) var m = $('<button class="button_is_member">').attr('data-i', i)
.text(results[i].name); .text(results[i].barcode_base + ' - ' + results[i].name);
html_elts.multi_results.append(m);
}
if (results[i].is_associated_people != false) {
m = $('<button class="button_is_associated_people"></button_is_member>').attr('data-i', i)
.text('B ' + results[i].barcode_base + ' - ' + results[i].name);
html_elts.multi_results.append(m); html_elts.multi_results.append(m);
} }
...@@ -255,7 +266,7 @@ function get_simple_service_name(s) { ...@@ -255,7 +266,7 @@ function get_simple_service_name(s) {
} }
function move_service_validation_to(page) { function move_service_validation_to(page) {
service_validation.find('.btn').data('stid', '0'); service_data.stid=0;
page.find('.validation_wrapper') page.find('.validation_wrapper')
.append(service_validation.detach()); .append(service_validation.detach());
} }
...@@ -275,11 +286,21 @@ function fill_service_entry(s) { ...@@ -275,11 +286,21 @@ function fill_service_entry(s) {
var li_class = "btn"; var li_class = "btn";
var li_data = ""; var li_data = "";
if (e.state == "done") { if (e.state == "done" && coop_is_connected()) {
li_data = ' data-rid="'+e.id+'" data-mid="'+e.partner_id[0]+'"';
li_class += "--inverse"; li_class += "--inverse";
if (e.is_late == true) { if (e.is_late == true) {
li_class += " late"; li_class += " late";
} }
if (e.associate_registered=='both') {
li_class += " both";
}
} else if (e.state == "done" && !coop_is_connected()) {
li_data = ' data-rid="'+e.id+'" data-mid="'+e.partner_id[0]+'"';
li_class += "--inverse not_connected";
if (e.is_late == true) {
li_class += " late";
}
} else { } else {
li_data = ' data-rid="'+e.id+'" data-mid="'+e.partner_id[0]+'"'; li_data = ' data-rid="'+e.id+'" data-mid="'+e.partner_id[0]+'"';
} }
...@@ -312,13 +333,29 @@ function clean_service_entry() { ...@@ -312,13 +333,29 @@ function clean_service_entry() {
function fill_service_validation(rid, coop_num_name, coop_id) { function fill_service_validation(rid, coop_num_name, coop_id) {
var coop_name_elts = coop_num_name.split(' - '); var coop_name_elts = coop_num_name.split(' - ');
for (member of loaded_services[0].members) {
if (member.id ==rid) {
if (member.associate_name) {
pages.service_entry_validation.find('#service_validation').hide();
pages.service_entry_validation.find('#associated_service_validation').show();
pages.service_entry_validation.find('#associated_btn').text(member.associate_name);
pages.service_entry_validation.find('#partner_btn').text(member.partner_name);
} else {
pages.service_entry_validation.find('#associated_service_validation').hide();
pages.service_entry_validation.find('#service_validation').show();
}
}
}
service_data={
rid: rid,
sid: selected_service.id,
mid: coop_id};
pages.service_entry_validation.find('span.member_name').text(coop_name_elts[1]); pages.service_entry_validation.find('span.member_name').text(coop_name_elts[1]);
move_service_validation_to(pages.service_entry_validation); move_service_validation_to(pages.service_entry_validation);
service_validation.find('.btn')
.data('rid', rid)
.data('sid', selected_service.id)
.data('mid', coop_id);
} }
function select_possible_service() { function select_possible_service() {
...@@ -363,7 +400,6 @@ function get_service_entry_data() { ...@@ -363,7 +400,6 @@ function get_service_entry_data() {
dataType : 'json' dataType : 'json'
}) })
.done(function(rData) { .done(function(rData) {
//console.log(rData);
info_place.text(''); info_place.text('');
var page_title = pages.service_entry.find('h1'); var page_title = pages.service_entry.find('h1');
...@@ -388,6 +424,7 @@ function get_service_entry_data() { ...@@ -388,6 +424,7 @@ function get_service_entry_data() {
page_title.text('Quel est ton service ?'); page_title.text('Quel est ton service ?');
} else { } else {
loaded_services = rData.res;
fill_service_entry(rData.res[0]); fill_service_entry(rData.res[0]);
} }
} }
...@@ -434,22 +471,21 @@ function fill_service_entry_sucess(member) { ...@@ -434,22 +471,21 @@ function fill_service_entry_sucess(member) {
} }
function record_service_presence() { function record_service_presence(e) {
var d = new Date(); var d = new Date();
var elapsed_since_last_call = d.getTime() - validation_last_call; var elapsed_since_last_call = d.getTime() - validation_last_call;
if (elapsed_since_last_call > 10000) { if (elapsed_since_last_call > 1000) {
loading2.show(); loading2.show();
validation_last_call = d.getTime(); validation_last_call = d.getTime();
var clicked = service_validation.find('.btn'); var rid = service_data.rid;
var rid = clicked.data('rid'); var mid = service_data.mid;
var mid = clicked.data('mid'); var sid = service_data.sid;
var sid = clicked.data('sid'); var stid = service_data.stid;
var stid = clicked.data('stid');
post_form( post_form(
'/members/service_presence/', '/members/service_presence/',
{'mid': mid, 'rid': rid, 'sid': sid, 'stid' : stid}, {'mid': mid, 'rid': rid, 'sid': sid, 'stid' : stid, 'cancel': false, 'type': e.data.type},
function(err, rData) { function(err, rData) {
if (!err) { if (!err) {
var res = rData.res; var res = rData.res;
...@@ -471,6 +507,28 @@ function record_service_presence() { ...@@ -471,6 +507,28 @@ function record_service_presence() {
} }
} }
function cancel_service_presence(mid, rid) {
var d = new Date();
var elapsed_since_last_call = d.getTime() - validation_last_call;
if (elapsed_since_last_call > 1000) {
loading2.show();
validation_last_call = d.getTime();
var sid = selected_service.id;
post_form(
'/members/service_presence/',
{'mid': mid, 'rid': rid, 'sid': sid, 'stid' : 0, 'cancel': true},
function(err) {
if (!err) {
get_service_entry_data();
}
loading2.hide();
}
);
}
}
function fill_rattrapage_2() { function fill_rattrapage_2() {
pages.rattrapage_2.find('span.member_name').text(current_displayed_member.name); pages.rattrapage_2.find('span.member_name').text(current_displayed_member.name);
var msg = "Bienvenue pour ton rattrapage !"; var msg = "Bienvenue pour ton rattrapage !";
...@@ -485,13 +543,11 @@ function fill_rattrapage_2() { ...@@ -485,13 +543,11 @@ function fill_rattrapage_2() {
msg = "Tu es en désincrit.e ... La situation doit être réglée avez le Bureau des Membres"; msg = "Tu es en désincrit.e ... La situation doit être réglée avez le Bureau des Membres";
} else { } else {
move_service_validation_to(pages.rattrapage_2); move_service_validation_to(pages.rattrapage_2);
service_data = {
rid : 0,
service_validation.find('.btn') sid : selected_service.id,
.data('rid', 0) stid : shift_ticket_id,
.data('sid', selected_service.id) mid : current_displayed_member.id};
.data('stid', shift_ticket_id)
.data('mid', current_displayed_member.id);
} }
pages.rattrapage_2.find('h2').text(msg); pages.rattrapage_2.find('h2').text(msg);
...@@ -632,7 +688,10 @@ $('.btn[data-next]').click(function() { ...@@ -632,7 +688,10 @@ $('.btn[data-next]').click(function() {
}); });
service_validation.on("click", ".btn", record_service_presence); service_validation.on("click", ".btn", {type:'normal'}, record_service_presence);
associated_service_validation.on("click", "#associated_btn", {type:'associate'}, record_service_presence);
associated_service_validation.on("click", "#partner_btn", {type:'partner'}, record_service_presence);
associated_service_validation.on("click", "#both_btn", {type:'both'}, record_service_presence);
shift_members.on("click", '.btn[data-rid]', function() { shift_members.on("click", '.btn[data-rid]', function() {
var clicked = $(this); var clicked = $(this);
...@@ -644,6 +703,16 @@ shift_members.on("click", '.btn[data-rid]', function() { ...@@ -644,6 +703,16 @@ shift_members.on("click", '.btn[data-rid]', function() {
}); });
shift_members.on("click", '.btn--inverse', function() {
if (coop_is_connected()) {
var clicked = $(this);
var rid = clicked.data('rid');
var mid = clicked.data('mid');
cancel_service_presence(mid, rid);
}
});
pages.shopping_entry.on('css', function() { pages.shopping_entry.on('css', function() {
photo_advice.hide(); photo_advice.hide();
photo_studio.hide(); photo_studio.hide();
......
...@@ -32,6 +32,15 @@ function display_current_coop_form() { ...@@ -32,6 +32,15 @@ function display_current_coop_form() {
let street2_input = form.find('[name="street2"]'), let street2_input = form.find('[name="street2"]'),
phone_input = form.find('[name="phone"]'); phone_input = form.find('[name="phone"]');
if (current_coop.parent_name) {
$('#associated_member').show();
if (current_coop.parent_id)
$('#associated_member_name').text(current_coop.parent_name);
else $('#associated_member_name').text(current_coop.parent_name + " ATTENTION à faire manuellement");
} else {
$('#associated_member').hide();
}
chgt_shift_btn.hide(); chgt_shift_btn.hide();
chgt_shift_btn.off('click', open_shift_choice); chgt_shift_btn.off('click', open_shift_choice);
form.find('[name="firstname"]').val(current_coop.firstname); form.find('[name="firstname"]').val(current_coop.firstname);
......
...@@ -31,10 +31,12 @@ urlpatterns = [ ...@@ -31,10 +31,12 @@ urlpatterns = [
url(r'^latest_coop_id/$', views.latest_coop_id), url(r'^latest_coop_id/$', views.latest_coop_id),
url(r'^get/([0-9]+)$', views.get), url(r'^get/([0-9]+)$', views.get),
url(r'^exists/([a-zA-Z0-9_\-\.\+@]+)$', views.exists), url(r'^exists/([a-zA-Z0-9_\-\.\+@]+)$', views.exists),
url(r'^is_associated/([0-9]+)$', views.is_associated),
url(r'^get_couchdb_odoo_markers/(.+)$', views.get_couchdb_odoo_markers), url(r'^get_couchdb_odoo_markers/(.+)$', views.get_couchdb_odoo_markers),
url(r'^menu/$', views.menu), url(r'^menu/$', views.menu),
url(r'^verify_final_state$', views.verify_final_state), url(r'^verify_final_state$', views.verify_final_state),
url(r'^update_couchdb_barcodes$', views.update_couchdb_barcodes), url(r'^update_couchdb_barcodes$', views.update_couchdb_barcodes),
url(r'^add_shares_to_member$', views.add_shares_to_member),
# Borne accueil # Borne accueil
url(r'^search/([^\/.]+)/?([0-9]*)', views.search), url(r'^search/([^\/.]+)/?([0-9]*)', views.search),
url(r'^save_photo/([0-9]+)$', views.save_photo, name='save_photo'), url(r'^save_photo/([0-9]+)$', views.save_photo, name='save_photo'),
...@@ -49,11 +51,24 @@ urlpatterns = [ ...@@ -49,11 +51,24 @@ urlpatterns = [
url(r'^easy_validate_shift_presence$', views.easy_validate_shift_presence), url(r'^easy_validate_shift_presence$', views.easy_validate_shift_presence),
# conso / groupe recherche / socio # conso / groupe recherche / socio
url(r'^panel_get_purchases$', views.panel_get_purchases), url(r'^panel_get_purchases$', views.panel_get_purchases),
# BDM # BDM
url(r'^save_partner_info$', views.save_partner_info), url(r'^save_partner_info$', views.save_partner_info),
# BDM - members admin # BDM - members admin
url(r'^admin$', admin.admin), url(r'^admin/?$', admin.admin),
url(r'^admin/manage_makeups$', admin.manage_makeups),
url(r'^admin/manage_shift_registrations$', admin.manage_shift_registrations),
url(r'^admin/manage_regular_shifts$', admin.manage_regular_shifts),
url(r'^get_makeups_members$', admin.get_makeups_members), url(r'^get_makeups_members$', admin.get_makeups_members),
url(r'^update_members_makeups$', admin.update_members_makeups), url(r'^update_members_makeups$', admin.update_members_makeups),
url(r'^delete_shift_registration$', admin.delete_shift_registration),
url(r'^delete_shift_template_registration$', admin.delete_shift_template_registration),
url(r'^shift_subscription$', admin.shift_subscription),
url(r'^admin/manage_attached$', admin.manage_attached),
url(r'^admin/manage_attached/create_pair$', admin.create_pair),
url(r'^admin/manage_attached/delete_pair$', admin.delete_pair),
url(r'^get_makeups_members$', admin.get_makeups_members),
url(r'^update_members_makeups$', admin.update_members_makeups),
url(r'^get_member_info/(\d+)$', admin.get_member_info),
url(r'^get_attached_members$', admin.get_attached_members),
] ]
...@@ -8,7 +8,7 @@ from members.models import CagetteMembers ...@@ -8,7 +8,7 @@ from members.models import CagetteMembers
from members.models import CagetteServices from members.models import CagetteServices
from outils.forms import GenericExportMonthForm from outils.forms import GenericExportMonthForm
import datetime
default_fields = ['name', default_fields = ['name',
'image_medium'] 'image_medium']
...@@ -63,6 +63,10 @@ def exists(request, mail): ...@@ -63,6 +63,10 @@ def exists(request, mail):
answer = CagetteMember.exists(mail) answer = CagetteMember.exists(mail)
return JsonResponse({'answer': answer}) return JsonResponse({'answer': answer})
def is_associated(request, id_parent):
answer = CagetteMember.is_associated(id_parent)
return JsonResponse({'answer': answer})
def getmemberimage(request, id): def getmemberimage(request, id):
m = CagetteMember(id) m = CagetteMember(id)
call_res = m.get_image() call_res = m.get_image()
...@@ -83,21 +87,27 @@ def inscriptions(request, type=1): ...@@ -83,21 +87,27 @@ def inscriptions(request, type=1):
""" """
template = loader.get_template('members/inscriptions.html') template = loader.get_template('members/inscriptions.html')
context = {'type': type, 'title': 'Inscriptions', committees_shift_id = CagetteServices.get_committees_shift_id()
'couchdb_server': settings.COUCHDB['url'], context = {
'mag_place_string': settings.MAG_NAME, 'type': type, 'title': 'Inscriptions',
'office_place_string': settings.OFFICE_NAME, 'couchdb_server': settings.COUCHDB['url'],
'max_begin_hour': settings.MAX_BEGIN_HOUR, 'mag_place_string': settings.MAG_NAME,
'payment_meanings': settings.SUBSCRIPTION_PAYMENT_MEANINGS, 'office_place_string': settings.OFFICE_NAME,
'force_firstname_hyphen': getattr(settings, 'FORCE_HYPHEN_IN_SUBSCRIPTION_FIRSTNAME', True), 'max_begin_hour': settings.MAX_BEGIN_HOUR,
'input_barcode': getattr(settings, 'SUBSCRIPTION_INPUT_BARCODE', False), 'payment_meanings': settings.SUBSCRIPTION_PAYMENT_MEANINGS,
'email_domain': getattr(settings, 'EMAIL_DOMAIN', 'lacagette-coop.fr'), 'force_firstname_hyphen': getattr(settings, 'FORCE_HYPHEN_IN_SUBSCRIPTION_FIRSTNAME', True),
'ask_for_sex': getattr(settings, 'SUBSCRIPTION_ASK_FOR_SEX', False), 'input_barcode': getattr(settings, 'SUBSCRIPTION_INPUT_BARCODE', False),
'open_on_sunday': getattr(settings, 'OPEN_ON_SUNDAY', False), 'email_domain': getattr(settings, 'EMAIL_DOMAIN', 'lacagette-coop.fr'),
'POUCHDB_VERSION': getattr(settings, 'POUCHDB_VERSION', ''), 'ask_for_sex': getattr(settings, 'SUBSCRIPTION_ASK_FOR_SEX', False),
'max_chq_nb': getattr(settings, 'MAX_CHQ_NB', 12), 'open_on_sunday': getattr(settings, 'OPEN_ON_SUNDAY', False),
'show_ftop_button': getattr(settings, 'SHOW_FTOP_BUTTON', True), 'POUCHDB_VERSION': getattr(settings, 'POUCHDB_VERSION', ''),
'db': settings.COUCHDB['dbs']['member']} 'max_chq_nb': getattr(settings, 'MAX_CHQ_NB', 12),
'show_ftop_button': getattr(settings, 'SHOW_FTOP_BUTTON', True),
'db': settings.COUCHDB['dbs']['member'],
'ASSOCIATE_MEMBER_SHIFT' : getattr(settings, 'ASSOCIATE_MEMBER_SHIFT', ''),
'prepa_odoo_url' : getattr(settings, 'PREPA_ODOO_URL', '/members/prepa-odoo'),
'committees_shift_id': committees_shift_id,
}
response = HttpResponse(template.render(context, request)) response = HttpResponse(template.render(context, request))
return response return response
...@@ -237,6 +247,8 @@ def update_couchdb_barcodes(request): ...@@ -237,6 +247,8 @@ def update_couchdb_barcodes(request):
def search(request, needle, shift_id): def search(request, needle, shift_id):
"""Search member has been requested.""" """Search member has been requested."""
search_type = request.GET.get('search_type', "full")
try: try:
key = int(needle) key = int(needle)
k_type = 'barcode_base' k_type = 'barcode_base'
...@@ -247,7 +259,7 @@ def search(request, needle, shift_id): ...@@ -247,7 +259,7 @@ def search(request, needle, shift_id):
key = needle key = needle
k_type = 'name' k_type = 'name'
res = CagetteMember.search(k_type, key, shift_id) res = CagetteMember.search(k_type, key, shift_id, search_type)
return JsonResponse({'res': res}) return JsonResponse({'res': res})
...@@ -275,6 +287,9 @@ def record_service_presence(request): ...@@ -275,6 +287,9 @@ def record_service_presence(request):
mid = int(request.POST.get("mid", 0)) # member id mid = int(request.POST.get("mid", 0)) # member id
sid = int(request.POST.get("sid", 0)) # shift id sid = int(request.POST.get("sid", 0)) # shift id
stid = int(request.POST.get("stid", 0)) # shift_ticket_id stid = int(request.POST.get("stid", 0)) # shift_ticket_id
cancel = request.POST.get("cancel") == 'true'
typeAction = str(request.POST.get("type"))
app_env = getattr(settings, 'APP_ENV', "prod") app_env = getattr(settings, 'APP_ENV', "prod")
if (rid > -1 and mid > 0): if (rid > -1 and mid > 0):
overrided_date = "" overrided_date = ""
...@@ -284,28 +299,31 @@ def record_service_presence(request): ...@@ -284,28 +299,31 @@ def record_service_presence(request):
if o_date: if o_date:
overrided_date = re.sub(r'(%20)',' ', o_date.group(1)) overrided_date = re.sub(r'(%20)',' ', o_date.group(1))
# rid = 0 => C'est un rattrapage, sur le service if(not cancel):
if sid > 0 and stid > 0: # rid = 0 => C'est un rattrapage, sur le service
# Add member to service and take presence into account if sid > 0 and stid > 0:
res['rattrapage'] = CagetteServices.record_rattrapage(mid, sid, stid) # Add member to service and take presence into account
if res['rattrapage'] is True: res['rattrapage'] = CagetteServices.record_rattrapage(mid, sid, stid, typeAction)
res['update'] = 'ok' if res['rattrapage'] is True:
else: res['update'] = 'ok'
if (CagetteServices.registration_done(rid, overrided_date) is True):
res['update'] = 'ok'
else: else:
res['update'] = 'ko' if (CagetteServices.registration_done(rid, overrided_date, typeAction) is True):
if res['update'] == 'ok': res['update'] = 'ok'
members = CagetteMember.search('id', mid) else:
m = members[0] res['update'] = 'ko'
for k in ['image_medium', 'barcode', 'barcode_base']: if res['update'] == 'ok':
del m[k] members = CagetteMember.search('id', mid)
next_shift = {} m = members[0]
if len(m['shifts']) > 0: for k in ['image_medium', 'barcode', 'barcode_base']:
next_shift = m['shifts'][0] del m[k]
del m['shifts'] next_shift = {}
m['next_shift'] = next_shift if len(m['shifts']) > 0:
res['member'] = m next_shift = m['shifts'][0]
del m['shifts']
m['next_shift'] = next_shift
res['member'] = m
else: CagetteServices.reopen_registration(rid, overrided_date)
except Exception as e: except Exception as e:
res['error'] = str(e) res['error'] = str(e)
return JsonResponse({'res': res}) return JsonResponse({'res': res})
...@@ -391,6 +409,21 @@ def panel_get_purchases(request): ...@@ -391,6 +409,21 @@ def panel_get_purchases(request):
response = HttpResponse(message) response = HttpResponse(message)
return response return response
def add_shares_to_member(request):
res = {}
try:
data = json.loads(request.body.decode())
partner_id = int(data["partner_id"])
amount = int(data["amount"])
except Exception as e:
res['error'] = "Wrong params"
return JsonResponse(res, safe=False, status=400)
m = CagetteMember(partner_id)
today = datetime.date.today().strftime("%Y-%m-%d")
res = m.create_capital_subscription_invoice(amount, today)
return JsonResponse(res, safe=False)
# # # BDM # # # # # # BDM # # #
def save_partner_info(request): def save_partner_info(request):
......
...@@ -44,7 +44,7 @@ class CagetteMembersSpace(models.Model): ...@@ -44,7 +44,7 @@ class CagetteMembersSpace(models.Model):
['state', '!=', 'replaced'], ['state', '!=', 'replaced'],
['state', '!=', 'replacing'], ['state', '!=', 'replacing'],
] ]
f = ['create_date', 'date_begin', 'shift_id', 'name', 'state', 'is_late', 'is_makeup'] f = ['create_date', 'date_begin', 'shift_id', 'name', 'state', 'is_late', 'is_makeup','associate_registered']
marshal_none_error = 'cannot marshal None unless allow_none is enabled' marshal_none_error = 'cannot marshal None unless allow_none is enabled'
try: try:
......
...@@ -54,6 +54,13 @@ ...@@ -54,6 +54,13 @@
margin: 3rem 0; margin: 3rem 0;
} }
#my_info .choose_makeups,
#my_info .unsuscribed_form_link,
#my_info .remove_future_registration {
font-size: 1.8rem;
word-break: normal;
}
#my_info #member_status_action, #my_info #member_status_action,
#my_info .member_shift_name_area, #my_info .member_shift_name_area,
#my_info .member_coop_number_area { #my_info .member_coop_number_area {
......
...@@ -52,7 +52,6 @@ ...@@ -52,7 +52,6 @@
#shifts_list { #shifts_list {
flex-direction: column; flex-direction: column;
display: none; display: none;
width: min-content;
max-width: 100%; max-width: 100%;
white-space: nowrap; white-space: nowrap;
} }
...@@ -65,11 +64,38 @@ ...@@ -65,11 +64,38 @@
} }
} }
.shift_line_container {
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
}
.shift_line_extra_actions {
display: flex;
justify-content: flex-start;
flex-wrap: nowrap;
}
@media screen and (max-width:768px) {
.shift_line_container {
flex-direction: column;
}
.shift_line_extra_actions {
width: 100%;
}
.affect_associate_registered {
margin: 0.5rem 0;
}
}
.selectable_shift_line { .selectable_shift_line {
min-width: 325px;
display: flex; display: flex;
align-items: center; align-items: center;
margin-left: 15px;
margin: 0.75rem 0;
border-radius: 5px; border-radius: 5px;
} }
...@@ -81,6 +107,35 @@ ...@@ -81,6 +107,35 @@
cursor: not-allowed; cursor: not-allowed;
} }
.affect_associate_registered {
display: flex;
align-items: center;
border-radius: 5px;
}
@media screen and (min-width:768px) {
.selectable_shift_line {
margin: 0 15px;
}
.affect_associate_registered {
margin-left: 15px;
}
}
.selectable_shift{
margin: 1rem 0;
}
.delete_registration_button {
justify-content: center;
align-items: center;
margin: 0.75rem 15px;
color: #d9534f;
cursor: pointer;
display: none;
}
/* -- Calendar screen, makeups message */ /* -- Calendar screen, makeups message */
#need_to_select_makeups_message { #need_to_select_makeups_message {
...@@ -103,6 +158,21 @@ ...@@ -103,6 +158,21 @@
} }
} }
/* -- Calendar screen, can delete registrations message */
#can_delete_future_registrations_area {
display: none;
justify-content: center;
align-items: center;
margin: 0 1rem 1rem 1rem;
}
#can_delete_future_registrations_area button {
white-space: normal;
word-break: normal;
margin: 1rem;
}
/* -- Calendar screen, calendar */ /* -- Calendar screen, calendar */
#calendar { #calendar {
...@@ -147,6 +217,19 @@ td{ ...@@ -147,6 +217,19 @@ td{
color: white; color: white;
} }
.fc-event.shift_booked_makeup {
background-color: #f0ad4e;
cursor: auto;
border-color: #f0ad4e;
}
.fc-event.shift_booked_makeup td {
--fc-list-event-hover-bg-color:#f0ad4e;
}
.fc-list-event.shift_booked_makeup {
color: white;
}
#calendar .fc-list-table { #calendar .fc-list-table {
table-layout: auto; table-layout: auto;
} }
...@@ -194,4 +277,14 @@ td{ ...@@ -194,4 +277,14 @@ td{
#calendar_explaination_button { #calendar_explaination_button {
max-width: 60%; max-width: 60%;
margin: 2rem auto 0.5rem auto; margin: 2rem auto 0.5rem auto;
}
/* -- Assign shift modal */
.modal_affect_shift_buttons {
margin: 1rem 0;
}
.assign_shift_button {
margin: 0.25rem;
} }
\ No newline at end of file
...@@ -166,6 +166,11 @@ body { ...@@ -166,6 +166,11 @@ body {
font-size: 1.5rem; font-size: 1.5rem;
} }
.remove_future_registration {
display: none;
white-space: normal;
}
.unsuscribed_form_link { .unsuscribed_form_link {
display: none; display: none;
text-decoration: none; text-decoration: none;
...@@ -335,3 +340,13 @@ body { ...@@ -335,3 +340,13 @@ body {
.no_content_title { .no_content_title {
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
/* - block_actions_for_attached_people is true or false */
.attached-blocked {
display: none;
}
.attached-unblocked {
display: none;
}
function init_faq() { function init_faq() {
$("#unsuscribe_form_link_btn").prop("href", unsuscribe_form_link); $("#unsuscribe_form_link_btn").prop("href", unsuscribe_form_link);
$("#unsuscribe_form_link_btn2").prop("href", unsuscribe_form_link); $("#unsuscribe_form_link_btn2").prop("href", unsuscribe_form_link);
...@@ -18,8 +17,33 @@ function init_faq() { ...@@ -18,8 +17,33 @@ function init_faq() {
$("#helper_unsubscribe_form_link_btn").prop("href", helper_unsubscribe_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_btn2").prop("href", request_form_link);
$("#request_form_link_btn").prop("href", request_form_link); $("#request_form_link_btn").prop("href", request_form_link);
display_messages_for_attached_people();
} }
$(document).on('click', "#shift_exchange_btn", () => { $(document).on('click', "#shift_exchange_btn", () => {
goto('echange-de-services'); goto('echange-de-services');
}); });
\ No newline at end of file
$(document).on('click', '.accordion', function() {
/* Toggle between adding and removing the "active" class,
to highlight the button that controls the panel */
this.classList.toggle("active");
/* Toggle between hiding and showing the active panel */
var panel = this.nextElementSibling;
if (panel.style.display === "block") {
panel.style.display = "none";
} else {
panel.style.display = "block";
}
});
function display_messages_for_attached_people() {
if (block_actions_for_attached_people === "False") {
$(".attached-unblocked").show();
} else {
$(".attached-blocked").show();
}
}
...@@ -55,7 +55,7 @@ $(document).ready(function() { ...@@ -55,7 +55,7 @@ $(document).ready(function() {
toggleHeader(); toggleHeader();
}); });
if (partner_data.is_associated_people === "True") { if (partner_data.is_associated_people === "True" && block_actions_for_attached_people === "True") {
$(".pairs_info").show(); $(".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() { function init_my_shifts_tile() {
if (incoming_shifts.length === 0) { if (incoming_shifts.length === 0) {
$("#home_tile_my_services #home_incoming_services").text("Aucun service à venir..."); $("#home_tile_my_services #home_incoming_services").text("Aucun service à venir...");
......
...@@ -66,6 +66,31 @@ function prepare_server_data(data) { ...@@ -66,6 +66,31 @@ function prepare_server_data(data) {
} }
} }
if (history_item.associate_registered == false || history_item.associate_registered == undefined) {
history_item.associate_registered = "";
} else {
if (partner_data.associated_partner_id != "False") {
if (history_item.associate_registered==="partner") {
history_item.associate_registered = partner_data.name;
} else if (history_item.associate_registered==="associate") {
history_item.associate_registered = partner_data.associated_partner_name;
} else if (history_item.associate_registered==="both") {
history_item.associate_registered = "Les deux";
} else {
history_item.associate_registered = "";
}
} else if (partner_data.parent_id != "False") {
if (history_item.associate_registered==="partner") {
history_item.associate_registered = partner_data.parent_name;
} else if (history_item.associate_registered==="associate") {
history_item.associate_registered = partner_data.name;
} else if (history_item.associate_registered==="both") {
history_item.associate_registered = "Les deux";
} else {
history_item.associate_registered = "";
}
}
}
history_item.details = ''; history_item.details = '';
if (history_item.state === 'excused' || history_item.state === 'absent') { if (history_item.state === 'excused' || history_item.state === 'absent') {
history_item.details = "Absent.e"; history_item.details = "Absent.e";
...@@ -87,7 +112,6 @@ function prepare_server_data(data) { ...@@ -87,7 +112,6 @@ function prepare_server_data(data) {
function init_history() { function init_history() {
$(".loading-history").hide(); $(".loading-history").hide();
$("#history").show(); $("#history").show();
if (partner_history.length === 0) { if (partner_history.length === 0) {
$("#history").empty() $("#history").empty()
.text("Aucun historique... pour l'instant !"); .text("Aucun historique... pour l'instant !");
...@@ -103,7 +127,7 @@ function init_history() { ...@@ -103,7 +127,7 @@ function init_history() {
{ {
data: "shift_name", data: "shift_name",
title: "<spans class='dt-body-center'>Service</span>", title: "<spans class='dt-body-center'>Service</span>",
width: "60%", width: "50%",
orderable: false orderable: false
}, },
{ {
...@@ -111,6 +135,11 @@ function init_history() { ...@@ -111,6 +135,11 @@ function init_history() {
title: "Détails", title: "Détails",
className: "tablet-l desktop", className: "tablet-l desktop",
orderable: false orderable: false
},
{
data: "associate_registered",
title: "",
orderable: false
} }
], ],
iDisplayLength: -1, iDisplayLength: -1,
...@@ -129,7 +158,7 @@ function init_history() { ...@@ -129,7 +158,7 @@ function init_history() {
if (cell.text() === "Présent.e") { if (cell.text() === "Présent.e") {
$(row).addClass('row_partner_ok'); $(row).addClass('row_partner_ok');
} else if (cell.text() === "Retard") { } else if (cell.text().includes("Retard")) {
$(row).addClass('row_partner_late'); $(row).addClass('row_partner_late');
} else if (cell.text() === "Absent.e") { } else if (cell.text() === "Absent.e") {
$(row).addClass('row_partner_absent'); $(row).addClass('row_partner_absent');
...@@ -157,6 +186,28 @@ function init_incoming_shifts() { ...@@ -157,6 +186,28 @@ function init_incoming_shifts() {
for (shift of incoming_shifts) { for (shift of incoming_shifts) {
let shift_line_template = prepare_shift_line_template(shift.date_begin); let shift_line_template = prepare_shift_line_template(shift.date_begin);
if (partner_data.associated_partner_id != "False") {
if (shift.associate_registered==="partner") {
shift_line_template.find(".shift_line_associate").text(' - '+partner_data.name+'');
} else if (shift.associate_registered==="associate") {
shift_line_template.find(".shift_line_associate").text(' - '+partner_data.associated_partner_name+'');
} else if (shift.associate_registered==="both") {
shift_line_template.find(".shift_line_associate").text(' - Les deux');
} else {
shift_line_template.find(".shift_line_associate").text('A définir');
}
} else if (partner_data.parent_id != "False") {
if (shift.associate_registered==="partner") {
shift_line_template.find(".shift_line_associate").text(' - '+partner_data.parent_name+'');
} else if (shift.associate_registered==="associate") {
shift_line_template.find(".shift_line_associate").text(' - '+partner_data.name+'');
} else if (shift.associate_registered==="both") {
shift_line_template.find(".shift_line_associate").text(' - Les deux');
} else {
shift_line_template.find(".shift_line_associate").text('A définir');
}
}
$("#incoming_shifts").append(shift_line_template.html()); $("#incoming_shifts").append(shift_line_template.html());
} }
} }
......
...@@ -131,6 +131,70 @@ function update_content() { ...@@ -131,6 +131,70 @@ function update_content() {
/* - Shifts */ /* - Shifts */
/** /**
* 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.is_associated_people === "True") ? partner_data.parent_verif_token : partner_data.verif_token,
idPartner: partner_data.concerned_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.');
}
}
});
});
}
/**
* Prepare a shift line to insert into the DOM. * Prepare a shift line to insert into the DOM.
* Is used in: Home - My Shifts tile ; My Shifts - Incoming shifts section * Is used in: Home - My Shifts tile ; My Shifts - Incoming shifts section
* *
...@@ -158,7 +222,11 @@ function prepare_shift_line_template(date_begin) { ...@@ -158,7 +222,11 @@ function prepare_shift_line_template(date_begin) {
*/ */
function init_my_info_data() { function init_my_info_data() {
$(".choose_makeups").off(); $(".choose_makeups").off();
$(".choose_makeups").hide();
$(".remove_future_registration").off();
$(".remove_future_registration").hide();
$(".unsuscribed_form_link").off(); $(".unsuscribed_form_link").off();
$(".unsuscribed_form_link").hide();
$(".member_shift_name").text(partner_data.regular_shift_name); $(".member_shift_name").text(partner_data.regular_shift_name);
...@@ -199,7 +267,6 @@ function init_my_info_data() { ...@@ -199,7 +267,6 @@ function init_my_info_data() {
if ( if (
partner_data.makeups_to_do > 0 partner_data.makeups_to_do > 0
&& partner_data.is_associated_people === "False"
&& partner_data.cooperative_state !== 'unsubscribed' && partner_data.cooperative_state !== 'unsubscribed'
) { ) {
$(".choose_makeups").show(); $(".choose_makeups").show();
...@@ -223,12 +290,17 @@ function init_my_info_data() { ...@@ -223,12 +290,17 @@ function init_my_info_data() {
} }
} }
if (partner_data.extra_shift_done > 0) {
$(".remove_future_registration").show();
$(".remove_future_registration").on('click', () => {
goto('echange-de-services');
});
}
$(".member_coop_number").text(partner_data.barcode_base); $(".member_coop_number").text(partner_data.barcode_base);
} }
$(document).ready(function() { $(document).ready(function() {
// TODO essayer de ne charger les js que au besoin
$.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } }); $.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } });
// If partner is associated (attached), display the pair's main partner shift data // If partner is associated (attached), display the pair's main partner shift data
...@@ -242,7 +314,6 @@ $(document).ready(function() { ...@@ -242,7 +314,6 @@ $(document).ready(function() {
// For associated people, their parent name is attached in their display name // For associated people, their parent name is attached in their display name
let partner_name_split = partner_data.name.split(', '); let partner_name_split = partner_data.name.split(', ');
partner_data.name = partner_name_split[partner_name_split.length - 1]; partner_data.name = partner_name_split[partner_name_split.length - 1];
base_location = (app_env === 'dev') ? '/members_space/' : '/'; base_location = (app_env === 'dev') ? '/members_space/' : '/';
...@@ -257,7 +328,7 @@ $(document).ready(function() { ...@@ -257,7 +328,7 @@ $(document).ready(function() {
// debouncing function from John Hann // debouncing function from John Hann
// http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/ // http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/
var debounce = function (func, threshold, execAsap) { var debounce = function (func, threshold, execAsap) {
var timeout; var timeout = null;
return function debounced () { return function debounced () {
var obj = this, args = arguments; var obj = this, args = arguments;
...@@ -282,4 +353,4 @@ $(document).ready(function() { ...@@ -282,4 +353,4 @@ $(document).ready(function() {
return fn ? this.bind('resize', debounce(fn)) : this.trigger(sr); return fn ? this.bind('resize', debounce(fn)) : this.trigger(sr);
}; };
})(jQuery, 'smartresize'); })(jQuery, 'smartresize');
\ No newline at end of file
...@@ -12,5 +12,6 @@ urlpatterns = [ ...@@ -12,5 +12,6 @@ urlpatterns = [
url(r'^faqBDM$', views.faqBDM), url(r'^faqBDM$', views.faqBDM),
url(r'^no_content$', views.no_content), url(r'^no_content$', views.no_content),
url(r'^get_shifts_history$', views.get_shifts_history), url(r'^get_shifts_history$', views.get_shifts_history),
url(r'^offer_extra_shift$', views.offer_extra_shift),
url(r'^.*', views.index) # Urls unknown from the server will redirect to index url(r'^.*', views.index) # Urls unknown from the server will redirect to index
] ]
...@@ -28,6 +28,8 @@ def index(request, exception=None): ...@@ -28,6 +28,8 @@ def index(request, exception=None):
context = { context = {
'title': 'Espace Membre', 'title': 'Espace Membre',
'COMPANY_LOGO': getattr(settings, 'COMPANY_LOGO', None),
'block_actions_for_attached_people' : getattr(settings, 'BLOCK_ACTIONS_FOR_ATTACHED_PEOPLE', True)
} }
template = loader.get_template('members_space/index.html') template = loader.get_template('members_space/index.html')
...@@ -95,8 +97,16 @@ def index(request, exception=None): ...@@ -95,8 +97,16 @@ def index(request, exception=None):
if partnerData["parent_id"] is not False: if partnerData["parent_id"] is not False:
partnerData["parent_name"] = partnerData["parent_id"][1] partnerData["parent_name"] = partnerData["parent_id"][1]
partnerData["parent_id"] = partnerData["parent_id"][0] partnerData["parent_id"] = partnerData["parent_id"][0]
md5_calc = hashlib.md5(partnerData['parent_create_date'].encode('utf-8')).hexdigest()
partnerData['parent_verif_token'] = md5_calc
partnerData['makeups_to_do'] = partnerData['parent_makeups_to_do']
partnerData['date_delay_stop'] = partnerData['parent_date_delay_stop']
partnerData['can_have_delay'] = cs.member_can_have_delay(int(partnerData["parent_id"]))
partnerData['extra_shift_done'] = partnerData["parent_extra_shift_done"]
else: else:
partnerData["parent_name"] = False partnerData["parent_name"] = False
partnerData['can_have_delay'] = cs.member_can_have_delay(int(partner_id))
# look for associated partner for parents # look for associated partner for parents
cm = CagetteMember(partner_id) cm = CagetteMember(partner_id)
...@@ -108,9 +118,8 @@ def index(request, exception=None): ...@@ -108,9 +118,8 @@ def index(request, exception=None):
if (associated_partner is not None and partnerData["associated_partner_name"].find(str(associated_partner["barcode_base"])) == -1): if (associated_partner is not None and partnerData["associated_partner_name"].find(str(associated_partner["barcode_base"])) == -1):
partnerData["associated_partner_name"] = str(associated_partner["barcode_base"]) + ' - ' + partnerData["associated_partner_name"] partnerData["associated_partner_name"] = str(associated_partner["barcode_base"]) + ' - ' + partnerData["associated_partner_name"]
partnerData['can_have_delay'] = cs.member_can_have_delay(int(partner_id))
m = CagetteMembersSpace() m = CagetteMembersSpace()
context['show_faq'] = getattr(settings, 'MEMBERS_SPACE_FAQ_TEMPLATE', 'members_space/faq.html')
partnerData["comite"] = m.is_comite(partner_id) partnerData["comite"] = m.is_comite(partner_id)
context['partnerData'] = partnerData context['partnerData'] = partnerData
...@@ -177,6 +186,7 @@ def my_info(request): ...@@ -177,6 +186,7 @@ def my_info(request):
template = loader.get_template('members_space/my_info.html') template = loader.get_template('members_space/my_info.html')
context = { context = {
'title': 'Mes Infos', 'title': 'Mes Infos',
'understand_my_status': getattr(settings, 'MEMBERS_SPACE_SHOW_UNDERSTAND_MY_STATUS', True)
} }
return HttpResponse(template.render(context, request)) return HttpResponse(template.render(context, request))
...@@ -197,14 +207,16 @@ def shifts_exchange(request): ...@@ -197,14 +207,16 @@ def shifts_exchange(request):
return HttpResponse(template.render(context, request)) return HttpResponse(template.render(context, request))
def faqBDM(request): def faqBDM(request):
template = loader.get_template('members_space/faq.html') template_path = getattr(settings, 'MEMBERS_SPACE_FAQ_TEMPLATE', 'members_space/faq.html')
context = { content = ''
'title': 'foire aux questions', if template_path:
} template = loader.get_template(template_path)
context = {
'title': 'foire aux questions',
}
content = template.render(context, request)
msettings = MConfig.get_settings('members') return HttpResponse(content)
return HttpResponse(template.render(context, request))
def no_content(request): def no_content(request):
""" Endpoint the front-end will call to load the "No content" page. """ """ Endpoint the front-end will call to load the "No content" page. """
...@@ -225,4 +237,13 @@ def get_shifts_history(request): ...@@ -225,4 +237,13 @@ def get_shifts_history(request):
date_from = getattr(settings, 'START_DATE_FOR_SHIFTS_HISTORY', '2018-01-01') date_from = getattr(settings, 'START_DATE_FOR_SHIFTS_HISTORY', '2018-01-01')
res["data"] = m.get_shifts_history(partner_id, limit, offset, date_from) res["data"] = m.get_shifts_history(partner_id, limit, offset, date_from)
return JsonResponse(res) return JsonResponse(res)
\ No newline at end of file
def offer_extra_shift(request):
res = {}
partner_id = int(request.POST['partner_id'])
m = CagetteMember(partner_id)
res = m.update_extra_shift_done(0)
return JsonResponse(res)
/* Comments : */
/* - Screens */
/* -- Sections */
/* - Common */
.page_body{ .page_body{
position: relative; position: relative;
} }
...@@ -9,8 +15,6 @@ ...@@ -9,8 +15,6 @@
right: 0; right: 0;
} }
/* - Common */
.pill { .pill {
border-radius: 30px; border-radius: 30px;
min-width: 200px; min-width: 200px;
...@@ -52,7 +56,8 @@ ...@@ -52,7 +56,8 @@
} }
/* - Order selection screen */ /* - Order selection screen */
#new_order_area { #new_order_area,
#existing_orders_area {
margin-bottom: 40px; margin-bottom: 40px;
} }
...@@ -163,6 +168,11 @@ ...@@ -163,6 +168,11 @@
border-bottom: 1px solid #004aa6; border-bottom: 1px solid #004aa6;
} }
#common_info_editor_container {
width: 50%;
margin: 15px auto;
}
/* -- Order data */ /* -- Order data */
#order_data_container { #order_data_container {
font-size: 1.8rem; font-size: 1.8rem;
...@@ -173,7 +183,7 @@ ...@@ -173,7 +183,7 @@
} }
#order_forms_container { #order_forms_container {
margin-top: 20px; margin: 25px 0;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: space-evenly; justify-content: space-evenly;
...@@ -193,12 +203,12 @@ ...@@ -193,12 +203,12 @@
min-width: 200px; min-width: 200px;
} }
#date_planned_input, #coverage_days_input, #stats_date_period_select { #date_planned_input, #coverage_days_input, #targeted_amount_input, #percent_adjust_input, #stats_date_period_select {
border-radius: 3px; border-radius: 3px;
} }
#coverage_form > div { #coverage_form > div {
display:inline-block; display:block;
float:left; float:left;
} }
...@@ -206,11 +216,11 @@ ...@@ -206,11 +216,11 @@
margin-right: 3px; margin-right: 3px;
} }
#coverage_days_input, #percent_adjust_input { #coverage_days_input, #targeted_amount_input, #percent_adjust_input {
display: block; display: block;
} }
#coverage_days_input { #coverage_days_input, #targeted_amount_input {
margin-bottom: 3px; margin-bottom: 3px;
} }
...@@ -260,7 +270,8 @@ ...@@ -260,7 +270,8 @@
padding: .5rem .5rem; padding: .5rem .5rem;
} }
.supplier_package_qty { .supplier_package_qty,
.supplier_price {
font-style: italic; font-style: italic;
font-size: 1.3rem; font-size: 1.3rem;
} }
...@@ -317,16 +328,18 @@ ...@@ -317,16 +328,18 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
margin: 30px 0 20px 0; margin: 15px 0;
position: -webkit-sticky; position: -webkit-sticky;
position: sticky; position: sticky;
top: 140px; top: 140px;
z-index: 5; z-index: 5;
pointer-events: none;
} }
.supplier_pill { .supplier_pill {
background-color: #a0daff; background-color: #ffebcd;
border: 1px solid #6ea8cc; border: 2px solid black;
pointer-events: auto;
} }
.pill_supplier_name { .pill_supplier_name {
...@@ -367,7 +380,7 @@ ...@@ -367,7 +380,7 @@
width: 90%; width: 90%;
} }
/* product actions modal*/ /* -- Product actions modal*/
.npa-options { .npa-options {
width: fit-content; width: fit-content;
text-align: left; text-align: left;
...@@ -376,6 +389,59 @@ ...@@ -376,6 +389,59 @@
.npa-options label { .npa-options label {
display: block; display: block;
} }
.product_actions_container {
display: flex;
flex-direction: column;
}
.product_actions_section {
width: 100%;
display: flex;
margin: 1em 0;
}
.product_actions_column {
width: 50%;
}
.product_actions_full_column {
width: 100%;
}
.product_actions_column .tooltip {
margin-left: 5px;
}
.product_prices_title {
margin-bottom: 0 !important;
}
.product_prices_area {
margin: 20px 0;
display: flex;
flex-direction: column;
gap: 10px;
}
.product_price_action {
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
}
.modal_product_actions_title {
font-weight: bold;
font-size: 2.2rem;
margin-bottom: 10px;
}
.checkbox_action_disabled {
cursor: not-allowed;
opacity: .5;
}
/* - Orders created screen */ /* - Orders created screen */
.order_created_header { .order_created_header {
......
...@@ -70,6 +70,11 @@ class OdooAPI: ...@@ -70,6 +70,11 @@ class OdooAPI:
return self.models.execute_kw(self.db, self.uid, self.passwd, return self.models.execute_kw(self.db, self.uid, self.passwd,
entity, 'create', [fields]) entity, 'create', [fields])
def delete(self, entity, ids):
"""Destroy entity instance by given ids."""
return self.models.execute_kw(self.db, self.uid, self.passwd,
entity, 'unlink', [ids])
def execute(self, entity, method, ids, params={}): def execute(self, entity, method, ids, params={}):
return self.models.execute_kw(self.db, self.uid, self.passwd, return self.models.execute_kw(self.db, self.uid, self.passwd,
entity, method, [ids], params) entity, method, [ids], params)
......
...@@ -28,6 +28,9 @@ ...@@ -28,6 +28,9 @@
- COMPANY_NAME = 'Les Grains de Sel' - COMPANY_NAME = 'Les Grains de Sel'
- COMPANY_LOGO = 'https://domaine.name/img/logo.png'
- ADMIN_IDS = [13] - ADMIN_IDS = [13]
Used to show hidden things. for example, input barcode in shelf adding product (Odoo user id array) Used to show hidden things. for example, input barcode in shelf adding product (Odoo user id array)
...@@ -66,6 +69,8 @@ ...@@ -66,6 +69,8 @@
- COOP_BARCODE_RULE_ID = 11 - COOP_BARCODE_RULE_ID = 11
- ASSOCIATE_BARCODE_RULE_ID = 12
- FUNDRAISING_CAT_ID = 1 - FUNDRAISING_CAT_ID = 1
- PARTS_PRICE_UNIT = 10.0 - PARTS_PRICE_UNIT = 10.0
...@@ -126,6 +131,13 @@ ...@@ -126,6 +131,13 @@
La Cagette use False to implement custom rules La Cagette use False to implement custom rules
- ASSOCIATE_MEMBER_SHIFT = ''
Id number of the associate shift template
- PREPA_ODOO_URL = ''
URL of the "prepa_odoo" page
### Scales and labels files generation ### Scales and labels files generation
- DAV_PATH = '/data/dav/cagette' - DAV_PATH = '/data/dav/cagette'
...@@ -311,6 +323,16 @@ ...@@ -311,6 +323,16 @@
Message shown to people when they connect to the Member Space Message shown to people when they connect to the Member Space
- MEMBERS_SPACE_FAQ_TEMPLATE = None
If set to None, "FAQ menu" will not be shown. To use a custom content add a template and set it's relative path
- MEMBERS_SPACE_SHOW_UNDERSTAND_MY_STATUS = False
By default, is True. If False, tile showing explanations is not shown
- BLOCK_ACTIONS_FOR_ATTACHED_PEOPLE = False
Attached people can or not change his services
### Reception ### Reception
- RECEPTION_ADD_ADMIN_MODE = True - RECEPTION_ADD_ADMIN_MODE = True
...@@ -367,6 +389,12 @@ ...@@ -367,6 +389,12 @@
In members_space history display a special activity about amnistie In members_space history display a special activity about amnistie
### BDM Admin
- BDM_SHOW_FTOP_BUTTON = True (by default)
If True, in BDM Admin manage shift template, on the calendar when subscribing a partner to a shift, "Volant" button is included
### Miscellious ### Miscellious
- EXPORT_COMPTA_FORMAT = 'Quadratus' - EXPORT_COMPTA_FORMAT = 'Quadratus'
......
...@@ -6,6 +6,7 @@ from django.http import JsonResponse ...@@ -6,6 +6,7 @@ from django.http import JsonResponse
from django.http import HttpResponseNotFound from django.http import HttpResponseNotFound
from django.http import HttpResponseForbidden from django.http import HttpResponseForbidden
from django.http import HttpResponseServerError from django.http import HttpResponseServerError
from django.http import HttpResponseBadRequest
from django.template import loader from django.template import loader
from django.shortcuts import render from django.shortcuts import render
from django.shortcuts import redirect from django.shortcuts import redirect
......
...@@ -9,7 +9,7 @@ class OdooEntityFieldsForm(forms.Form): ...@@ -9,7 +9,7 @@ class OdooEntityFieldsForm(forms.Form):
class ExportComptaForm(forms.Form): class ExportComptaForm(forms.Form):
mois = forms.DateField( mois = forms.DateField(
required=True, required=True,
widget=MonthYearWidget() widget=MonthYearWidget(years=range(datetime.date.today().year-2,datetime.date.today().year+1))
) )
#fichier = forms.FileField() #fichier = forms.FileField()
# CHOICES = [('zip', '1 fichier par journal'),('compact', '1 seul fichier')] # CHOICES = [('zip', '1 fichier par journal'),('compact', '1 seul fichier')]
......
"""
Delete makeups_to_do for up_to_date members.
Run this script from the project root with:
$ python -m outils.scripts.delete_makeups_for_uptodate_members
"""
import os
from pathlib import Path
from importlib import import_module
import logging
logging.basicConfig(
level=logging.DEBUG,
format='[%(asctime)s] %(levelname)s - %(message)s',
datefmt='%H:%M:%S'
)
logger = logging.getLogger(__file__)
project_path = Path(__file__).resolve().parents[2]
def get_api():
if not os.environ.get('DJANGO_SETTINGS_MODULE'):
os.environ['DJANGO_SETTINGS_MODULE'] = "outils.settings"
module = import_module('outils.common')
return module.OdooAPI()
def get_concerned_users(api):
cond = [
['cooperative_state', '=', 'up_to_date'],
['makeups_to_do', '>', 0]
]
fields = ['id']
return api.search_read('res.partner', cond, fields)
def main():
api = get_api()
concerned_users = get_concerned_users(api)
logger.info('Number of concerned members %i', len(concerned_users))
for user in concerned_users:
logger.debug("Member: %s is concerned", user.get('name'))
api.update('res.partner', user.get('id'), {'makeups_to_do': 0})
logger.debug("Member: %s has no more make ups to do!",
user.get('name'))
new_concerned_users = get_concerned_users(api)
logger.info('Now the number of concerned members %i',
len(new_concerned_users))
if __name__ == "__main__":
main()
SECRET_KEY = 'Mettre_plein_de_caracteres_aleatoires_iezezezeezezci' SECRET_KEY = 'Mettre_plein_de_caracteres_aleatoires_iezezezeezezci'
ODOO = { ODOO = {
'url': 'http://127.0.0.1:8069' 'url': 'http://127.0.0.1:8069',
'user': 'api', 'user': 'api',
'passwd': 'xxxxxxxxxxxx', 'passwd': 'xxxxxxxxxxxx',
'db': 'bd_test', 'db': 'bd_test',
......
...@@ -6,8 +6,9 @@ ...@@ -6,8 +6,9 @@
.b_yellow {background: #fcf3cc;} .b_yellow {background: #fcf3cc;}
.red {color:#FF0000;} .red {color:#FF0000;}
.b_red, .b_less_than_25pc {background:#ff3333 !important;} .b_red, .b_less_than_25pc {background:#ff3333 !important;}
.loading {background-image: url("/static/img/ajax-loader.gif"); background-repeat:no-repeat;} .loading {background-image: url("/static/img/ajax-loader.gif"); background-repeat:no-repeat; background-position: center; background-color: #efefef;}
.loading2 {display: none;} .loading2 {display: none; position:absolute; top:-20px;}
.loading2-container {position:relative;}
body {background: #fff; margin:5px;} body {background: #fff; margin:5px;}
a, a:active, a:focus, a, a:active, a:focus,
...@@ -28,6 +29,11 @@ footer { position: fixed; ...@@ -28,6 +29,11 @@ footer { position: fixed;
z-index: 10; z-index: 10;
} }
.warning_instruction {
font-weight: bold;
font-style: italic;
color: blue;
}
#deconnect, #password_change {float:right; margin-left: 5px;} #deconnect, #password_change {float:right; margin-left: 5px;}
/* The Overlay (background) */ /* The Overlay (background) */
...@@ -148,6 +154,10 @@ footer { position: fixed; ...@@ -148,6 +154,10 @@ footer { position: fixed;
width: 230px !important; width: 230px !important;
} }
.tooltip .tooltip-xl {
width: 320px !important;
}
.tooltip .tt_twolines { .tooltip .tt_twolines {
top: -15px !important; top: -15px !important;
} }
......
var actions_last_dates = {}; var actions_last_dates = {};
var show_enqueued_messages = function() {
var stored = null;
try {
stored = JSON.parse(localStorage.getItem('enqueued_messages'));
alert(stored.join("\n"))
localStorage.removeItem('enqueued_messages')
} catch (e) {
//no rescue system for the moment
}
};
var enqueue_message_for_next_loading = function(msg) {
try {
let messages = [],
stored = localStorage.getItem('enqueued_messages');
if (stored) {
messages = JSON.parse(stored);
}
messages.push(msg)
localStorage.setItem('enqueued_messages', JSON.stringify(messages));
} catch (e) {
//no rescue system for the moment
}
}
function get_litteral_shift_template_name(name) { function get_litteral_shift_template_name(name) {
var l_name = ''; var l_name = '';
...@@ -175,8 +202,8 @@ String.prototype.pad = function(String, len) { ...@@ -175,8 +202,8 @@ String.prototype.pad = function(String, len) {
var btns = $('<div/>').addClass('btns'); var btns = $('<div/>').addClass('btns');
var btn_ok = $('<button/>').addClass('btn--success'); var btn_ok = $('<button/>').addClass('btn--success btn-modal-ok');
var btn_nok = $('<button/>').addClass('btn--danger') var btn_nok = $('<button/>').addClass('btn--danger btn-modal-nok')
.attr('id', 'modal_closebtn_bottom') .attr('id', 'modal_closebtn_bottom')
.text('Fermer'); .text('Fermer');
...@@ -201,6 +228,10 @@ function openModal() { ...@@ -201,6 +228,10 @@ function openModal() {
btn_nok.off('click', closeModal); btn_nok.off('click', closeModal);
btn_ok.off('click', closeModal); btn_ok.off('click', closeModal);
if (btn_ok.hasClass('loading')) {
btn_ok.removeClass('loading');
btn_ok.addClass('btn--success');
}
// If more than one argument, add 'save' button // If more than one argument, add 'save' button
if (arguments[1]) { if (arguments[1]) {
btn_ok.on('click', arguments[1]); // Second argument is callback btn_ok.on('click', arguments[1]); // Second argument is callback
...@@ -209,6 +240,14 @@ function openModal() { ...@@ -209,6 +240,14 @@ function openModal() {
// 4th argument: if set and false, validate button doesn't close the modal // 4th argument: if set and false, validate button doesn't close the modal
if (typeof (arguments[3]) == "undefined" || arguments[3] != false) if (typeof (arguments[3]) == "undefined" || arguments[3] != false)
btn_ok.on('click', closeModal); btn_ok.on('click', closeModal);
/*
else {
btn_ok.on('click', function() {
$(this).addClass("loading");
$(this).removeClass("btn--success");
})
}
*/
btns.append(btn_ok); btns.append(btn_ok);
...@@ -460,22 +499,6 @@ for (i = 0; i < acc.length; i++) { ...@@ -460,22 +499,6 @@ for (i = 0; i < acc.length; i++) {
}); });
} }
$(document).on('click', '.accordion', function(){
/* Toggle between adding and removing the "active" class,
to highlight the button that controls the panel */
this.classList.toggle("active");
/* Toggle between hiding and showing the active panel */
var panel = this.nextElementSibling;
if (panel.style.display === "block") {
panel.style.display = "none";
} else {
panel.style.display = "block";
}
console.log(panel)
});
function report_JS_error(e, m) { function report_JS_error(e, m) {
try { try {
$.post('/log_js_error', {module: m, error: JSON.stringify(e)}); $.post('/log_js_error', {module: m, error: JSON.stringify(e)});
...@@ -498,3 +521,5 @@ function isMacUser() { ...@@ -498,3 +521,5 @@ function isMacUser() {
} }
if (isMacUser() && isSafari()) $('.mac-msg').show(); if (isMacUser() && isSafari()) $('.mac-msg').show();
show_enqueued_messages();
\ No newline at end of file
...@@ -35,14 +35,15 @@ function get_shift_name(s_data) { ...@@ -35,14 +35,15 @@ function get_shift_name(s_data) {
if (s_data && s_data.week) { if (s_data && s_data.week) {
shift_name = weeks_name[s_data.week]; shift_name = weeks_name[s_data.week];
if (s_data.type == 2 && typeof manage_ftop != "undefined" && manage_ftop == true) { if (s_data.type == 2 && typeof manage_ftop != "undefined" && manage_ftop == true && s_data.id != ASSOCIATE_MEMBER_SHIFT) {
shift_name = 'Volant'; shift_name = 'Volant';
} else if(s_data.id == ASSOCIATE_MEMBER_SHIFT) {
shift_name = 'Binôme';
} else { } else {
shift_name += s_data.day + ' - ' + s_data.begin; shift_name += s_data.day + ' - ' + s_data.begin;
shift_name += ' - ' + s_data.place; shift_name += ' - ' + s_data.place;
} }
} }
return shift_name; return shift_name;
} }
...@@ -52,8 +53,13 @@ function subscribe_shift(shift_t_id) { ...@@ -52,8 +53,13 @@ function subscribe_shift(shift_t_id) {
var s_data = shift_templates[shift_t_id].data; var s_data = shift_templates[shift_t_id].data;
var shift_name = get_shift_name(s_data); var shift_name = get_shift_name(s_data);
if (committees_shift_id !== undefined && committees_shift_id !== "None" && shift_name === "Volant") {
shift_name = 'des Comités'
}
let msg = 'On inscrit le membre au créneau ' + shift_name
openModal( openModal(
'On inscrit le membre au créneau ' + shift_name, msg,
function() { function() {
closeModal(); closeModal();
current_coop.shift_template = shift_templates[shift_t_id]; current_coop.shift_template = shift_templates[shift_t_id];
...@@ -98,8 +104,8 @@ function single_shift_click() { ...@@ -98,8 +104,8 @@ function single_shift_click() {
} }
} }
function select_shift_among_compact() { function select_shift_among_compact(event, clicked_item = null, subscribe = true) {
var clicked = $(this); var clicked = clicked_item === null ? $(this) : $(clicked_item);
var day = clicked.closest('td').attr('class'); var day = clicked.closest('td').attr('class');
var hour = clicked.closest('tr').data('begin'); var hour = clicked.closest('tr').data('begin');
var selected = null; var selected = null;
...@@ -128,9 +134,11 @@ function select_shift_among_compact() { ...@@ -128,9 +134,11 @@ function select_shift_among_compact() {
} }
} }
}); });
//console.log(worst_score)
if (selected) if (selected && subscribe === true)
subscribe_shift(selected); subscribe_shift(selected);
return selected
} }
...@@ -324,7 +332,14 @@ function retrieve_and_draw_shift_tempates() { ...@@ -324,7 +332,14 @@ function retrieve_and_draw_shift_tempates() {
$.each(shift_templates, function(i, e) { $.each(shift_templates, function(i, e) {
if (e.data.type == 2 && volant == null) { if (e.data.type == 2 && volant == null) {
volant = e.data.id; // has comitee shift
if (committees_shift_id !== undefined && committees_shift_id !== "None") {
if (e.data.id == parseInt(committees_shift_id)) {
volant = e.data.id
}
} else {
volant = e.data.id;
}
} }
}); });
......
...@@ -150,4 +150,17 @@ function get_module_settings() { ...@@ -150,4 +150,17 @@ function get_module_settings() {
} }
get_module_settings(); get_module_settings();
$(document).on('click', '.accordion', function(){
/* Toggle between adding and removing the "active" class,
to highlight the button that controls the panel */
this.classList.toggle("active");
/* Toggle between hiding and showing the active panel */
var panel = this.nextElementSibling;
if (panel.style.display === "block") {
panel.style.display = "none";
} else {
panel.style.display = "block";
}
});
...@@ -189,34 +189,51 @@ class ExportPOS(View): ...@@ -189,34 +189,51 @@ class ExportPOS(View):
kept_sessions_id.append(s['id']) kept_sessions_id.append(s['id'])
key = y + '-' + m + '-' + d key = y + '-' + m + '-' + d
if not (key in totals): if not (key in totals):
totals[key] = {'CB': 0, 'CSH': 0, 'CHQ': 0, 'TOTAL': 0} totals[key] = {'CB': 0,
'CSH': 0,
'CHQ': 0,
'CB_DEJ': 0,
'CHQ_DEJ': 0,
'TOTAL': 0}
sub_total = 0 sub_total = 0
cb = chq = csh = 0 cb = chq = csh = cbd = chqd = 0
for p in s['payments']: for p in s['payments']:
# p['name'] is a sequence generated string
# Test order is important as CHEQDEJ contains CHEQ for ex.
# p['journal'] could be used but easier to change in Odoo interface
sub_amount = round(p['total_amount'], 2)
if 'CSH' in p['name']: if 'CSH' in p['name']:
csh = round(p['total_amount'], 2) csh = sub_amount
elif 'CHEQDEJ' in p['name']:
chqd = sub_amount
elif 'CHEQ' in p['name']: elif 'CHEQ' in p['name']:
chq = round(p['total_amount'], 2) chq = sub_amount
elif 'CBDEJ' in p['name']:
cbd = sub_amount
elif 'CB' in p['name']: elif 'CB' in p['name']:
cb = round(p['total_amount'], 2) cb = sub_amount
sub_total += round(p['total_amount'], 2) sub_total += sub_amount
totals[key]['CB'] += cb totals[key]['CB'] += cb
totals[key]['CSH'] += csh totals[key]['CSH'] += csh
totals[key]['CHQ'] += chq totals[key]['CHQ'] += chq
totals[key]['CB_DEJ'] += cbd
totals[key]['CHQ_DEJ'] += chqd
totals[key]['TOTAL'] += round(sub_total, 2) totals[key]['TOTAL'] += round(sub_total, 2)
details_lines.append([mois, s['mm_dates']['min'], s['mm_dates']['min'], s['caisse'], s['name'], details_lines.append([mois, s['mm_dates']['min'], s['mm_dates']['min'], s['caisse'], s['name'],
cb, csh, chq, sub_total]) cb, csh, chq, cbd, chqd, sub_total])
wb = Workbook() wb = Workbook()
ws1 = wb.create_sheet("Totaux " + mois, 0) ws1 = wb.create_sheet("Totaux " + mois, 0)
ws2 = wb.create_sheet("Détails " + mois, 1) ws2 = wb.create_sheet("Détails " + mois, 1)
ws1.append(['date', 'CB', 'CSH', 'CHQ', 'Total']) ws1.append(['date', 'CB', 'CSH', 'CHQ', 'CB_DEJ', 'CHQ_DEJ', 'Total'])
for day in sorted(totals): for day in sorted(totals):
cb = totals[day]['CB'] cb = totals[day]['CB']
csh = totals[day]['CSH'] csh = totals[day]['CSH']
chq = totals[day]['CHQ'] chq = totals[day]['CHQ']
cbd = totals[day]['CB_DEJ']
chqd = totals[day]['CHQ_DEJ']
total = totals[day]['TOTAL'] total = totals[day]['TOTAL']
ws1.append([day, cb, csh, chq, total]) ws1.append([day, cb, csh, chq, cbd, chqd, total])
ws2.append(['mois', 'min_date', 'max_date', 'Caisse', 'session', 'CB', 'CSH','CHQ', 'total']) ws2.append(['mois', 'min_date', 'max_date', 'Caisse', 'session', 'CB', 'CSH','CHQ', 'CB_DEJ', 'CHQ_DEJ', 'total'])
for row in details_lines: for row in details_lines:
ws2.append(row) ws2.append(row)
wb_name = 'export_sessions__' + mois + '.xlsx' wb_name = 'export_sessions__' + mois + '.xlsx'
......
...@@ -160,6 +160,7 @@ class CagetteProduct(models.Model): ...@@ -160,6 +160,7 @@ class CagetteProduct(models.Model):
try: try:
res["update"] = api.update('product.supplierinfo', psi_id, f) res["update"] = api.update('product.supplierinfo', psi_id, f)
res["psi_id"] = psi_id
except Exception as e: except Exception as e:
res['error'] = str(e) res['error'] = str(e)
else: else:
...@@ -185,6 +186,7 @@ class CagetteProduct(models.Model): ...@@ -185,6 +186,7 @@ class CagetteProduct(models.Model):
try: try:
res['create'] = api.create('product.supplierinfo', f) res['create'] = api.create('product.supplierinfo', f)
res['psi_id'] = res['create'] # consistency between update & create res
except Exception as e: except Exception as e:
res['error'] = str(e) res['error'] = str(e)
...@@ -254,13 +256,24 @@ class CagetteProduct(models.Model): ...@@ -254,13 +256,24 @@ class CagetteProduct(models.Model):
return res return res
@staticmethod @staticmethod
def update_npa_and_minimal_stock(data): def commit_actions_on_product(data):
"""Update NPA (ne pas acheter) and minimal stock data""" """ Update:
- NPA (ne pas acheter)
- Product is active
- Minimal stock
- price /supplier
"""
res = {} res = {}
try: try:
api = OdooAPI() api = OdooAPI()
f = {'minimal_stock': data['minimal_stock']}
# Minimal & Actual stock, Active
f = {
'minimal_stock': float(data['minimal_stock']),
'active': not data['to_archive']
}
# NPA
if 'simple-npa' in data['npa']: if 'simple-npa' in data['npa']:
f['purchase_ok'] = 0 f['purchase_ok'] = 0
if 'npa-in-name' in data['npa']: if 'npa-in-name' in data['npa']:
...@@ -279,10 +292,20 @@ class CagetteProduct(models.Model): ...@@ -279,10 +292,20 @@ class CagetteProduct(models.Model):
f['name'] = re.sub(r'( \[FDS\])', '', current_name) f['name'] = re.sub(r'( \[FDS\])', '', current_name)
if len(data['npa']) == 0: if len(data['npa']) == 0:
f['purchase_ok'] = 1 f['purchase_ok'] = 1
res["update"] = api.update('product.template', data['id'], f)
res["update"] = api.update('product.template', int(data['id']), f)
# Update suppliers info
res["update_supplierinfo"] = []
for supplierinfo in data["suppliersinfo"]:
f= {'price': supplierinfo["price"]}
res_update_si = api.update('product.supplierinfo', int(supplierinfo['supplierinfo_id']), f)
res["update_supplierinfo"].append(res_update_si)
except Exception as e: except Exception as e:
res["error"] = str(e) res["error"] = str(e)
coop_logger.error("update_npa_and_minimal_stock : %s %s", str(e), str(data)) coop_logger.error("commit_actions_on_product : %s %s", str(e), str(data))
return res return res
class CagetteProducts(models.Model): class CagetteProducts(models.Model):
"""Initially used to make massive barcode update.""" """Initially used to make massive barcode update."""
...@@ -582,7 +605,7 @@ class CagetteProducts(models.Model): ...@@ -582,7 +605,7 @@ class CagetteProducts(models.Model):
if supplier_ids is not None and len(supplier_ids) > 0: if supplier_ids is not None and len(supplier_ids) > 0:
# Get products/supplier relation # Get products/supplier relation
f = ["product_tmpl_id", 'date_start', 'date_end', 'package_qty', 'price', 'name', 'product_code'] f = ["id", "product_tmpl_id", 'date_start', 'date_end', 'package_qty', 'price', 'name', 'product_code']
c = [['name', 'in', [ int(x) for x in supplier_ids]]] c = [['name', 'in', [ int(x) for x in supplier_ids]]]
psi = api.search_read('product.supplierinfo', c, f) psi = api.search_read('product.supplierinfo', c, f)
...@@ -612,7 +635,7 @@ class CagetteProducts(models.Model): ...@@ -612,7 +635,7 @@ class CagetteProducts(models.Model):
"product_variant_ids", "product_variant_ids",
"minimal_stock" "minimal_stock"
] ]
c = [['id', 'in', ptids], ['purchase_ok', '=', True]] c = [['id', 'in', ptids], ['purchase_ok', '=', True], ['active', '=', True]]
products_t = api.search_read('product.template', c, f) products_t = api.search_read('product.template', c, f)
filtered_products_t = [p for p in products_t if p["state"] != "end" and p["state"] != "obsolete"] filtered_products_t = [p for p in products_t if p["state"] != "end" and p["state"] != "obsolete"]
...@@ -640,6 +663,7 @@ class CagetteProducts(models.Model): ...@@ -640,6 +663,7 @@ class CagetteProducts(models.Model):
for psi_item in valid_psi: for psi_item in valid_psi:
if psi_item["product_tmpl_id"] is not False and psi_item ["product_tmpl_id"][0] == fp["id"]: if psi_item["product_tmpl_id"] is not False and psi_item ["product_tmpl_id"][0] == fp["id"]:
filtered_products_t[i]['suppliersinfo'].append({ filtered_products_t[i]['suppliersinfo'].append({
'id': int(psi_item["id"]),
'supplier_id': int(psi_item["name"][0]), 'supplier_id': int(psi_item["name"][0]),
'package_qty': psi_item["package_qty"], 'package_qty': psi_item["package_qty"],
'price': psi_item["price"], 'price': psi_item["price"],
......
...@@ -11,7 +11,7 @@ urlpatterns = [ ...@@ -11,7 +11,7 @@ urlpatterns = [
url(r'^update_product_stock$', views.update_product_stock), url(r'^update_product_stock$', views.update_product_stock),
url(r'^update_product_purchase_ok$', views.update_product_purchase_ok), url(r'^update_product_purchase_ok$', views.update_product_purchase_ok),
url(r'^update_product_internal_ref$', views.update_product_internal_ref), url(r'^update_product_internal_ref$', views.update_product_internal_ref),
url(r'^update_npa_and_minimal_stock$', views.update_npa_and_minimal_stock), url(r'^commit_actions_on_product$', views.commit_actions_on_product),
url(r'^labels_appli_csv(\/?[a-z]*)$', views.labels_appli_csv, name='labels_appli_csv'), url(r'^labels_appli_csv(\/?[a-z]*)$', views.labels_appli_csv, name='labels_appli_csv'),
url(r'^label_print/([0-9]+)/?([0-9\.]*)/?([a-z]*)/?([0-9]*)$', views.label_print), url(r'^label_print/([0-9]+)/?([0-9\.]*)/?([a-z]*)/?([0-9]*)$', views.label_print),
url(r'^shelf_labels$', views.shelf_labels), # massive print url(r'^shelf_labels$', views.shelf_labels), # massive print
......
...@@ -100,7 +100,7 @@ def update_product_stock(request): ...@@ -100,7 +100,7 @@ def update_product_stock(request):
'products': [p] 'products': [p]
} }
res['inventory'] = CagetteInventory.update_stock_with_shelf_inventory_data(inventory_data) res['inventory'] = CagetteInventory.update_products_stock(inventory_data)
return JsonResponse({"res": res}) return JsonResponse({"res": res})
...@@ -134,13 +134,63 @@ def update_product_internal_ref(request): ...@@ -134,13 +134,63 @@ def update_product_internal_ref(request):
else: else:
return JsonResponse(res, status=403) return JsonResponse(res, status=403)
def update_npa_and_minimal_stock(request): def commit_actions_on_product(request):
res = {} res = {}
is_connected_user = CagetteUser.are_credentials_ok(request) is_connected_user = CagetteUser.are_credentials_ok(request)
if is_connected_user is True: if is_connected_user is True:
try: try:
data = json.loads(request.body.decode()) data = json.loads(request.body.decode())
res = CagetteProduct.update_npa_and_minimal_stock(data) product_data = CagetteProducts.get_products_for_order_helper(None, [data["id"]])["products"][0]
# Don't allow to archive product if incomin qty > 0
if data["to_archive"] is True and product_data["incoming_qty"] > 0:
res["code"] = "archiving_with_incoming_qty"
return JsonResponse(res, status=500)
res = CagetteProduct.commit_actions_on_product(data)
do_stock_update = False
# If product to archive and stock > 0: do inventory to set stock to 0
if data["to_archive"] is True and product_data["qty_available"] != 0:
p = {
'id': product_data['product_variant_ids'][0], # Need product id
'uom_id': product_data['uom_id'],
'qty': 0
}
inventory_data = {
'name': 'Archivage - ' + product_data['name'],
'products': [p]
}
do_stock_update = True
# Else update actual stock if changed
elif data["qty_available"] != product_data["qty_available"]:
p = {
'id': product_data['product_variant_ids'][0], # Need product id
'uom_id': product_data['uom_id'],
'qty': data["qty_available"]
}
inventory_data = {
'name': 'MAJ stock depuis Aide à la Commande - ' + product_data['name'],
'products': [p]
}
do_stock_update = True
if do_stock_update is True:
try:
res_inventory = CagetteInventory.update_products_stock(inventory_data, 3)
if res_inventory['errors'] or res_inventory['missed']:
res["code"] = "error_stock_update"
res["error"] = res_inventory['errors']
return JsonResponse(res, status=500)
except Exception as e:
res["code"] = "error_stock_update"
return JsonResponse(res, status=500)
except Exception as e: except Exception as e:
res['error'] = str(e) res['error'] = str(e)
coop_logger.error("Update npa and minimal stock : %s", res['error']) coop_logger.error("Update npa and minimal stock : %s", res['error'])
......
...@@ -14,49 +14,38 @@ class CagetteSales(models.Model): ...@@ -14,49 +14,38 @@ class CagetteSales(models.Model):
def get_sales(self, date_from, date_to): def get_sales(self, date_from, date_to):
res = [] res = []
# Get pos sessions # Get pos orders
cond = [['stop_at', '>=', date_from], ['stop_at', '<=', date_to], ['state', '=', "closed"]] cond = [['date_order', '>=', date_from], ['date_order', '<=', date_to]]
fields = [] fields = ['partner_id', 'statement_ids', 'name']
sessions = self.o_api.search_read('pos.session', cond, fields) orders = self.o_api.search_read('pos.order', cond, fields)
# Get bank statements of these sessions # Get bank statements of these sessions
statements = [] statements = []
for s in sessions: statements_partners = {}
statements = statements + s["statement_ids"] statements_orders = {}
for o in orders:
statements = statements + o["statement_ids"]
for s in o["statement_ids"]:
statements_partners[s] = o["partner_id"][1]
statements_orders[s] = o["name"]
# Get payment lines # Get payment lines
cond = [['statement_id', 'in', statements]] cond = [['id', 'in', statements]]
fields = ["partner_id", "amount", "journal_id", "create_date", "date"] fields = ["amount", "journal_id", "create_date"]
payments = self.o_api.search_read('account.bank.statement.line', cond, fields, order="create_date ASC", limit=50000) payments = self.o_api.search_read('account.bank.statement.line', cond, fields, order="create_date ASC", limit=50000)
item = None
try: try:
for payment in payments: for payment in payments:
# POS session can contain payments from another day (closing session on next morning, ...) res.append({
if payment["date"] >= date_from and payment["date"] <= date_to: "partner": statements_partners[payment["id"]],
# If the consecutive payment in the results is from the same partner on the same day, we consider it's the same basket
if item is not None and item["partner_id"][0] == payment["partner_id"][0] and item["date"] == payment["date"]:
res[len(res)-1]["total_amount"] += round(float(payment["amount"]), 2)
res[len(res)-1]["payments"].append({
"amount": round(float(payment["amount"]), 2),
"journal_id": payment["journal_id"]
})
else:
item = {
"partner_id": payment["partner_id"],
"create_date": payment["create_date"], "create_date": payment["create_date"],
"date": payment["date"], "pos_order_name": statements_orders[payment["id"]],
"total_amount": round(float(payment["amount"]), 2), "total_amount": round(float(payment["amount"]), 2),
"payments": [ "payments": [
{ {
"amount": round(float(payment["amount"]), 2), "amount": round(float(payment["amount"]), 2),
"journal_id": payment["journal_id"] "journal_id": payment["journal_id"]
} }
] ]
} })
res.append(item)
except Exception as e: except Exception as e:
pass coop_logger.error("get_sales %s", str(e))
return res return res
...@@ -38,20 +38,23 @@ function display_orders(orders) { ...@@ -38,20 +38,23 @@ function display_orders(orders) {
columns:[ columns:[
{ {
data:"create_date", data:"create_date",
title:"Date de vente", title:"Date enregistrement",
width: "10%" width: "10%"
}, },
{ {
data:"partner_id", data:"pos_order_name",
title:"Ref. Caisse",
width: "10%"
},
{
data:"partner",
title:"Membre", title:"Membre",
width: "50%", width: "40%"
render: function (data) {
return data[1];
}
}, },
{ {
data:"total_amount", data:"total_amount",
title: "Montant du panier", title: "Montant dû",
className:"dt-body-center", className:"dt-body-center",
render: function (data) { render: function (data) {
return parseFloat(data).toFixed(2) + ' €'; return parseFloat(data).toFixed(2) + ' €';
......
...@@ -173,7 +173,7 @@ def do_shelf_inventory(request): ...@@ -173,7 +173,7 @@ def do_shelf_inventory(request):
return JsonResponse(res, status=500) return JsonResponse(res, status=500)
# Proceed with inventory # Proceed with inventory
res['inventory'] = CagetteInventory.update_stock_with_shelf_inventory_data(full_inventory_data) res['inventory'] = CagetteInventory.update_products_stock(full_inventory_data)
full_inventory_data['inventory_id'] = res['inventory']['inv_id'] full_inventory_data['inventory_id'] = res['inventory']['inv_id']
shelf_data['last_inventory_id'] = res['inventory']['inv_id'] shelf_data['last_inventory_id'] = res['inventory']['inv_id']
......
...@@ -38,14 +38,28 @@ class CagetteShift(models.Model): ...@@ -38,14 +38,28 @@ class CagetteShift(models.Model):
'cooperative_state', 'final_standard_point', 'create_date', 'cooperative_state', 'final_standard_point', 'create_date',
'final_ftop_point', 'shift_type', 'leave_ids', 'makeups_to_do', 'barcode_base', 'final_ftop_point', 'shift_type', 'leave_ids', 'makeups_to_do', 'barcode_base',
'street', 'street2', 'zip', 'city', 'mobile', 'phone', 'email', 'street', 'street2', 'zip', 'city', 'mobile', 'phone', 'email',
'is_associated_people', 'parent_id'] 'is_associated_people', 'parent_id', 'extra_shift_done']
partnerData = self.o_api.search_read('res.partner', cond, fields, 1) partnerData = self.o_api.search_read('res.partner', cond, fields, 1)
if partnerData: if partnerData:
partnerData = partnerData[0] partnerData = partnerData[0]
if partnerData['is_associated_people']:
cond = [['id', '=', partnerData['parent_id'][0]]]
fields = ['create_date', 'makeups_to_do', 'date_delay_stop', 'extra_shift_done']
parentData = self.o_api.search_read('res.partner', cond, fields, 1)
if parentData:
partnerData['parent_create_date'] = parentData[0]['create_date']
partnerData['parent_makeups_to_do'] = parentData[0]['makeups_to_do']
partnerData['parent_date_delay_stop'] = parentData[0]['date_delay_stop']
partnerData['parent_extra_shift_done'] = parentData[0]['extra_shift_done']
if partnerData['shift_type'] == 'standard': if partnerData['shift_type'] == 'standard':
partnerData['in_ftop_team'] = False partnerData['in_ftop_team'] = False
# Because 'in_ftop_team' doesn't seem to be reset to False in Odoo # Because 'in_ftop_team' doesn't seem to be reset to False in Odoo
cond = [['partner_id.id', '=', id]] if partnerData['is_associated_people']:
cond = [['partner_id.id', '=', partnerData['parent_id'][0]]]
else:
cond = [['partner_id.id', '=', id]]
fields = ['shift_template_id', 'is_current'] fields = ['shift_template_id', 'is_current']
shiftTemplate = self.o_api.search_read('shift.template.registration', cond, fields) shiftTemplate = self.o_api.search_read('shift.template.registration', cond, fields)
if (shiftTemplate and len(shiftTemplate) > 0): if (shiftTemplate and len(shiftTemplate) > 0):
...@@ -83,7 +97,7 @@ class CagetteShift(models.Model): ...@@ -83,7 +97,7 @@ class CagetteShift(models.Model):
def get_shift_partner(self, id): def get_shift_partner(self, id):
"""Récupère les shift du membre""" """Récupère les shift du membre"""
fields = ['date_begin', 'date_end','final_standard_point', fields = ['date_begin', 'date_end','final_standard_point',
'shift_id', 'shift_type','partner_id', "id"] # res.partner 'shift_id', 'shift_type','partner_id', "id", "associate_registered", "is_makeup"] # res.partner
cond = [['partner_id.id', '=', id],['state', '=', 'open'], cond = [['partner_id.id', '=', id],['state', '=', 'open'],
['date_begin', '>', datetime.datetime.now().isoformat()]] ['date_begin', '>', datetime.datetime.now().isoformat()]]
shiftData = self.o_api.search_read('shift.registration', cond, fields, order ="date_begin ASC") shiftData = self.o_api.search_read('shift.registration', cond, fields, order ="date_begin ASC")
...@@ -166,10 +180,40 @@ class CagetteShift(models.Model): ...@@ -166,10 +180,40 @@ class CagetteShift(models.Model):
if res: if res:
st_r_id = True st_r_id = True
return st_r_id return st_r_id
def affect_shift(self, data):
"""Affect shift to partner, his associate or both"""
response = None
# partner_id can be 'associated_people' one, which is never use as shift partner_id reference
# So, let's first retrieved data about the res.partner involved
cond = [['id', '=', int(data['idPartner'])]]
fields = ['parent_id']
partner = self.o_api.search_read('res.partner', cond, fields, 1)
if partner:
if partner[0]['parent_id']:
partner_id = partner[0]['parent_id'][0]
else:
partner_id = int(data['idPartner'])
cond = [['partner_id', '=', partner_id],
['id', '=', int(data['idShiftRegistration'])]]
fields = ['id']
try:
# make sure there is coherence between shift.registration id and partner_id (to avoid forged request)
shit_to_affect = self.o_api.search_read('shift.registration', cond, fields, 1)
if (len(shit_to_affect) == 1):
shift_res = shit_to_affect[0]
fieldsDatas = { "associate_registered":data['affected_partner']}
response = self.o_api.update('shift.registration', [shift_res['id']], fieldsDatas)
except Exception as e:
coop_logger.error("Model affect shift : %s", str(e))
else:
coop_logger.error("Model affect shift nobody found : %s", str(cond))
return response
def cancel_shift(self, idsRegisteur): def cancel_shift(self, idsRegisteur, origin='memberspace'):
"""Annule un shift""" """Annule un shift"""
fieldsDatas = { "related_shift_state": 'cancel', fieldsDatas = { "related_shift_state": 'cancel',
"origin": origin,
"state": 'cancel'} "state": 'cancel'}
return self.o_api.update('shift.registration', idsRegisteur, fieldsDatas) return self.o_api.update('shift.registration', idsRegisteur, fieldsDatas)
...@@ -329,4 +373,8 @@ class CagetteShift(models.Model): ...@@ -329,4 +373,8 @@ class CagetteShift(models.Model):
def member_can_have_delay(self, partner_id): def member_can_have_delay(self, partner_id):
""" Can a member have a delay? """ """ Can a member have a delay? """
return self.o_api.execute('res.partner', 'can_have_extension', [partner_id]) return self.o_api.execute('res.partner', 'can_have_extension', [partner_id])
\ No newline at end of file
def update_counter_event(self, fields):
""" Add/remove points """
return self.o_api.create('shift.counter.event', fields)
\ No newline at end of file
...@@ -13,7 +13,9 @@ urlpatterns = [ ...@@ -13,7 +13,9 @@ urlpatterns = [
url(r'^get_test', views.get_test), url(r'^get_test', views.get_test),
# url(r'^get_list', views.get_list), # url(r'^get_list', views.get_list),
url(r'^change_shift', views.change_shift), url(r'^change_shift', views.change_shift),
url(r'^affect_shift', views.affect_shift),
url(r'^add_shift', views.add_shift), url(r'^add_shift', views.add_shift),
url(r'^cancel_shift', views.cancel_shift),
url(r'^request_delay', views.request_delay), url(r'^request_delay', views.request_delay),
url(r'^reset_members_positive_points', views.reset_members_positive_points) url(r'^reset_members_positive_points', views.reset_members_positive_points)
] ]
...@@ -101,8 +101,11 @@ def get_list_shift_calendar(request, partner_id): ...@@ -101,8 +101,11 @@ def get_list_shift_calendar(request, partner_id):
use_new_members_space = getattr(settings, 'USE_NEW_MEMBERS_SPACE', False) use_new_members_space = getattr(settings, 'USE_NEW_MEMBERS_SPACE', False)
listRegisterPartner = [] listRegisterPartner = []
listMakeUpShift = []
for v in registerPartner: for v in registerPartner:
listRegisterPartner.append(v['id']) listRegisterPartner.append(v['id'])
if v['is_makeup']:
listMakeUpShift.append(v['id'])
start = request.GET.get('start') start = request.GET.get('start')
end = request.GET.get('end') end = request.GET.get('end')
...@@ -136,7 +139,10 @@ def get_list_shift_calendar(request, partner_id): ...@@ -136,7 +139,10 @@ def get_list_shift_calendar(request, partner_id):
if len(l) > 0: if len(l) > 0:
if use_new_members_space is True: if use_new_members_space is True:
event["classNames"] = ["shift_booked"] if set(value['registration_ids']) & set(listRegisterPartner) & set(listMakeUpShift):
event["classNames"] = ["shift_booked_makeup"]
else :
event["classNames"] = ["shift_booked"]
else: else:
event["className"] = "shift_booked" event["className"] = "shift_booked"
event["changed"] = False event["changed"] = False
...@@ -223,15 +229,48 @@ def change_shift(request): ...@@ -223,15 +229,48 @@ def change_shift(request):
response = {'result': True} response = {'result': True}
else: else:
response = {'result': False} response = {'msg': "Fail to create shift"}
return JsonResponse(response, status=500)
else: else:
response = {'result': False} response = {'msg': "Bad arguments"}
return JsonResponse(response, status=400)
return JsonResponse(response) return JsonResponse(response)
else: else:
return HttpResponseForbidden() return HttpResponseForbidden()
else: else:
return HttpResponseForbidden() return HttpResponseForbidden()
def affect_shift(request):
if 'verif_token' in request.POST:
if Verification.verif_token(request.POST.get('verif_token'), int(request.POST.get('idPartner'))) is True:
cs = CagetteShift()
if 'idShiftRegistration' in request.POST and 'affected_partner' in request.POST:
# if request is made by associated people, idPartner is it's id, not "master" res.partner
# it's will be handled in model's method (affect_shift)
data = {
"idPartner": int(request.POST['idPartner']),
"idShiftRegistration": int(request.POST['idShiftRegistration']),
"affected_partner": request.POST['affected_partner'],
}
st_r_id = None
try:
st_r_id = cs.affect_shift(data)
except Exception as e:
coop_logger.error("affect shift : %s, %s", str(e), str(data))
if st_r_id:
response = {'result': True}
else:
response = {'msg': "Internal Error"}
return JsonResponse(response, status=500)
return(JsonResponse({'result': True}))
else:
response = {'msg': "Bad args"}
return JsonResponse(response, status=400)
else:
return HttpResponseForbidden()
else:
return HttpResponseForbidden()
def add_shift(request): def add_shift(request):
if 'verif_token' in request.POST: if 'verif_token' in request.POST:
if Verification.verif_token(request.POST.get('verif_token'), int(request.POST.get('idPartner'))) is True: if Verification.verif_token(request.POST.get('verif_token'), int(request.POST.get('idPartner'))) is True:
...@@ -277,6 +316,37 @@ def add_shift(request): ...@@ -277,6 +316,37 @@ def add_shift(request):
else: else:
return HttpResponseForbidden() return HttpResponseForbidden()
def cancel_shift(request):
""" Annule une présence à un shift """
if 'verif_token' in request.POST:
partner_id = int(request.POST.get('idPartner'))
if Verification.verif_token(request.POST.get('verif_token'), partner_id) is True:
cs = CagetteShift()
listRegister = [int(request.POST['idRegister'])]
try:
response = cs.cancel_shift(listRegister)
# decrement extra_shift_done if param exists
if 'extra_shift_done' in request.POST:
target = int(request.POST["extra_shift_done"]) - 1
# extra security
if target < 0:
target = 0
cm = CagetteMember(partner_id)
cm.update_extra_shift_done(target)
return JsonResponse({"res" : 'response'})
except Exception as e:
return JsonResponse({"error" : str(e)}, status=500)
else:
return HttpResponseForbidden()
else:
return HttpResponseForbidden()
def request_delay(request): def request_delay(request):
if 'verif_token' in request.POST: if 'verif_token' in request.POST:
if Verification.verif_token(request.POST.get('verif_token'), int(request.POST.get('idPartner'))) is True: if Verification.verif_token(request.POST.get('verif_token'), int(request.POST.get('idPartner'))) is True:
......
...@@ -46,7 +46,7 @@ def do_movement(request): ...@@ -46,7 +46,7 @@ def do_movement(request):
'products': products 'products': products
} }
res = CagetteInventory.update_stock_with_shelf_inventory_data(inventory_data) res = CagetteInventory.update_products_stock(inventory_data)
else: else:
res = CagetteStock.do_stock_movement(data) res = CagetteStock.do_stock_movement(data)
......
...@@ -9,15 +9,17 @@ ...@@ -9,15 +9,17 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
{%if must_identify %} <div id="admin_connexion_button">
{% include "common/conn_admin.html" %} {%if must_identify %}
{%endif%} {% include "common/conn_admin.html" %}
{%endif%}
</div>
<div id="envelop_cashing_error" class="alert--danger clearfix custom_alert" onClick="toggle_error_alert()"> <div id="envelop_cashing_error" class="alert--danger clearfix custom_alert" onClick="toggle_error_alert()">
<div style="width: 90%" class="fl txtleft" id="error_alert_txt"></div> <div style="width: 90%" class="fl txtleft" id="error_alert_txt"></div>
<div style="width: 10%" class="fr txtright"><i class="fas fa-times"></i></div> <div style="width: 10%" class="fr txtright"><i class="fas fa-times"></i></div>
</div> </div>
<div id="envelop_cashing_success" class="alert--success clearfix custom_alert" onClick="toggle_success_alert()"> <div id="envelop_cashing_success" class="alert--success clearfix custom_alert" onClick="toggle_success_alert()">
<div style="width: 90%" class="fl txtleft">Enveloppe encaissée !</div> <div style="width: 90%" class="fl txtleft success_alert_content">Enveloppe encaissée !</div>
<div style="width: 10%" class="fr txtright"><i class="fas fa-times"></i></div> <div style="width: 10%" class="fr txtright"><i class="fas fa-times"></i></div>
</div> </div>
<div id="envelop_deletion_success" class="alert--success clearfix custom_alert" onClick="toggle_deleted_alert()"> <div id="envelop_deletion_success" class="alert--success clearfix custom_alert" onClick="toggle_deleted_alert()">
...@@ -39,13 +41,115 @@ ...@@ -39,13 +41,115 @@
</div> </div>
</section> </section>
<section id="archive_cash">
<hr>
<h2 class="txtcenter">Enveloppes de liquide archivées</h2>
<div id="archive_cash_envelops" class="flex-container flex-column-reverse">
</div>
</section>
<section id="archive_ch">
<hr>
<h2 class="txtcenter">Enveloppes de chèques archivées</h2>
<div id="archive_ch_envelops" class="flex-container flex-column-reverse">
</div>
</section>
</section> </section>
<div id="templates" style="display:none;">
<div id="modal_update_envelop">
<div class="modal_update_envelop_content">
<h3 class="envelop_name"></h3>
<div class="envelop_lines"></div>
<div class="envelop_comments_area">
<p>Commentaires</p>
<textarea class="envelop_comments"></textarea>
</div>
</div>
</div>
<div id="update_envelop_line_template">
<div class="update_envelop_line">
<div class="line_partner_name_container">
<span class="line_number"></span>
<span class="line_partner_name"></span>
</div>
<div class="line_partner_input_container">
<input type="text" class="line_partner_amount" placeholder="Montant">
<i class="fas fa-trash-alt fa-lg delete_envelop_line_icon"></i>
</div>
<div class="deleted_line_through"></div>
</div>
</div>
<div id="modal_add_to_envelop">
<div class="modal_add_to_envelop_content">
<h3>Ajouter un paiement ou des parts sociales</h3>
<h4><i class="envelop_name"></i></h4>
<hr>
<div class="search_member_area">
<h4>Rechercher un membre</h4>
<form class="search_member_form" action="javascript:;" method="post">
<input type="text" class="search_member_input" value="" placeholder="Nom ou numéro du coop..." required>
<button type="submit" class="btn--primary" class="search_member_button">Recherche</button>
</form>
</div>
<div class="search_member_results_area" style="display:none;">
<div class="search_results_text">
<p><i>Choisissez parmi les membres trouvés :</i></p>
</div>
<div class="search_member_results"></div>
</div>
<div class="add_to_envelop_lines_area" style="display:none;">
<div class="add_to_envelop_lines">
</div>
<div class="validation_buttons">
<button class="btn--primary add_payment_button">Ajouter le paiement à l'enveloppe</button>
<button class="btn--primary add_shares_button">Ajouter des parts sociales </button>
</div>
</div>
</div>
</div>
<div id="add_to_envelop_line_template">
<div class="add_to_envelop_line">
<div class="partner_name_container">
<span class="line_partner_name"></span>
</div>
<div class="partner_input_container">
<input type="text" class="line_partner_amount" placeholder="Montant">
<div class="line_partner_amount_error">Le montant doit être un nombre entier.</div>
</div>
</div>
</div>
<div id="modal_confirm_add_payment">
<p>
Vous vous apprêtez à ajouter un paiement de <span class="confirm_add_payment_details amount"></span>
du membre <span class="confirm_add_payment_details member"></span>
à l'enveloppe <span class="confirm_add_payment_details envelop"></span>.
</p>
<p><i>
<i class="fas fa-exclamation-triangle"></i> Avertissement si ce.tte membre a plusieurs factures d'ouvertes.<br/>
Au moment de l'encaissement de l'enveloppe, ce paiement sera lié à la plus vieille facture ouverte de ce membre.
</i></p>
</div>
<div id="modal_confirm_add_shares">
<p>
Vous vous apprêtez à ajouter pour <span class="confirm_add_payment_details amount"></span>€ de parts sociales
au membre <span class="confirm_add_payment_details member"></span>.
</p>
<p>
Le paiement sera ajouté à l'enveloppe <span class="confirm_add_payment_details envelop"></span>.
</p>
</div>
</div>
<script src="{% static "js/pouchdb.min.js" %}"></script> <script src="{% static "js/pouchdb.min.js" %}"></script>
<script type="text/javascript"> <script type="text/javascript">
{%if must_identify %} var must_identify = '{{must_identify}}';
var must_identify = true
{%endif%}
var couchdb_dbname = '{{db}}'; var couchdb_dbname = '{{db}}';
var couchdb_server = '{{couchdb_server}}' + couchdb_dbname; var couchdb_server = '{{couchdb_server}}' + couchdb_dbname;
var dbc = new PouchDB(couchdb_dbname); var dbc = new PouchDB(couchdb_dbname);
......
...@@ -2,66 +2,49 @@ ...@@ -2,66 +2,49 @@
{% load static %} {% load static %}
{% block additionnal_css %} {% block additionnal_css %}
<link rel="stylesheet" href="{% static 'css/datatables/datatables.min.css' %}"> <link rel="stylesheet" href="{% static 'css/admin/bdm_index.css' %}">
<link rel="stylesheet" href="{% static 'css/members_admin.css' %}">
<link rel="stylesheet" href="{% static 'jquery-ui-1.12.1/jquery-ui.min.css' %}">
{% endblock %} {% endblock %}
{% block additionnal_scripts %} {% block additionnal_scripts %}
<script type="text/javascript" src="{% static 'jquery-ui-1.12.1/jquery-ui.min.js' %}"></script> <script type="text/javascript" src="{% static 'js/admin/bdm_index.js' %}?v="></script>
<script type="text/javascript" src="{% static 'js/datatables/datatables.min.js' %}"></script> <script type="text/javascript" src="{% static 'js/notify.min.js' %}?v="></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="page_body"> <div class="page_body">
<div class="login_area"> <div class="login_area">
{% include "common/conn_admin.html" %} {% include "common/conn_admin.html" %}
</div>
<div class="header txtcenter">
<h1>Bureau des membres</h1>
</div> </div>
<div class="page_content"> <div class="page_content">
<section class="tabs autogrid"> <div class="header txtcenter">
<div class="button tab active" id="tab_makeups"><h5>Rattrapages</h5></div> <h1>Bienvenue sur l'interface d'administration BDM</h1>
</section>
<div id="tab_makeups_content" class="tab_content">
<div id="table_top_area">
<h3>Liste des membres devant effectuer un rattrapage</h3>
<div class="table_grouped_action">
<button type="button" class="btn--primary" id="decrement_selected_members_makeups">
-1 rattrapage pour les membres sélectionnés
</button>
</div>
</div>
<div class="table_area">
<table id="makeups_members_table" class="display" cellspacing="0" width="100%"></table>
</div>
<div id="add_members_area">
<div id="add_members_form_area">
<h4>Ou, ajouter un rattrapage à un.e membre</h4>
<form id="search_member_form" action="javascript:;" method="post">
<input type="text" id="search_member_input" value="" placeholder="Nom ou numéro du coop..." required>
<button type="submit" class="btn--primary" id="search_member_button">Recherche</button>
</form>
</div>
<div class="search_member_results_area" style="display:none;">
<div class="search_results_text">
<p><i>Choisissez parmi les membres trouvés :</i></p>
</div>
<div class="search_member_results"></div>
</div>
</div>
</div> </div>
</div>
<div id="templates" style="display:none;"></div> <div class="management_type_buttons txtcenter">
<button type="button" class="btn--primary management_type_button" id="manage_makeups_button">
Gestion des rattrapages
<span class="management_type_button_icons"><i class="fas fa-arrow-right"></i></span>
</button><br>
<button type="button" class="btn--primary management_type_button" id="manage_shift_registrations_button">
Gestion des présences
<span class="management_type_button_icons"><i class="fas fa-arrow-right"></i></span>
</button><br>
<button type="button" class="btn--primary management_type_button" id="manage_attached_button">
Gestion des binômes
<span class="management_type_button_icons"><i class="fas fa-arrow-right"></i></span>
</button><br>
<button type="button" class="btn--primary management_type_button" id="manage_regular_shifts_button">
Gestion des créneaux
<span class="management_type_button_icons"><i class="fas fa-arrow-right"></i></span>
</button><br>
<button type="button" class="btn--primary management_type_button" id="manage_leaves_button" disabled>
Gestion des congés
<span class="management_type_button_icons"><i class="fas fa-wrench"></i></span>
{# <span class="management_type_button_icons"><i class="fas fa-arrow-right"></i></span> #}
</button><br>
</div>
</div>
</div> </div>
<script src='{% static "js/all_common.js" %}?v='></script> <script src="{% static "js/all_common.js" %}?v="></script>
<script src='{% static "js/members_admin.js" %}?v='></script>
{% endblock %} {% endblock %}
{% extends "base.html" %}
{% load static %}
{% block additionnal_css %}
<link rel="stylesheet" href="{% static 'css/datatables/datatables.min.css' %}">
<link rel="stylesheet" href="{% static 'css/admin/manage_attached.css' %}">
<link rel="stylesheet" href="{% static 'jquery-ui-1.12.1/jquery-ui.min.css' %}">
{% endblock %}
{% block additionnal_scripts %}
<script type="text/javascript" src="{% static 'jquery-ui-1.12.1/jquery-ui.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/datatables.min.js' %}"></script>
{% endblock %}
{% block content %}
<div class="page_body">
<div id="back_to_admin_index">
<button type="button" class="btn--danger"><i class="fas fa-arrow-left"></i>&nbsp; Retour</button>
</div>
<div class="login_area">
{% include "common/conn_admin.html" %}
</div>
<div class="header txtcenter">
<h1>Gestion des Binômes</h1>
</div>
<div class="page_content">
<div class="management_type_buttons txtcenter">
<button type="button" class="btn--primary management_type_button" id="manage_attached_create_pair_button">
Créer un binôme
<span class="management_type_button_icons"></span>
</button><br>
<button type="button" class="btn--primary management_type_button" id="manage_attached_delete_pair_button">
Désolidariser un binôme
<span class="management_type_button_icons"></span>
</button><br>
</div>
</div>
</div>
<script src='{% static "js/all_common.js" %}?v='></script>
<script src='{% static "js/admin/manage_attached.js" %}?v='></script>
<script src='{% static "js/admin/bdm_index.js" %}?v='></script>
{% endblock %}
\ No newline at end of file
{% extends "base.html" %}
{% load static %}
{% block additionnal_css %}
<link rel="stylesheet" href="{% static 'css/datatables/datatables.min.css' %}">
<link rel="stylesheet" href="{% static 'css/admin/manage_attached.css' %}">
<link rel="stylesheet" href="{% static 'jquery-ui-1.12.1/jquery-ui.min.css' %}">
{% endblock %}
{% block additionnal_scripts %}
<script type="text/javascript" src="{% static 'jquery-ui-1.12.1/jquery-ui.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/datatables.min.js' %}"></script>
{% endblock %}
{% block content %}
<div id="template" style="display:None;">
<div id="confirmModal">
<h3>Le binôme est sur le point d'être créé</h3>
<br/>
<p>Voulez-vous vraiment créer le binôme avec comme titulaire <b><span id="parentName"></span></b> et comme suppléant <b><span id="childName"></span></b>
<p>Êtes-vous sur de vouloir continuer ?</p>
<hr/>
</div>
</div>
<div class="page_body">
<div id="back_to_admin_index">
<button type="button" class="btn--danger"><i class="fas fa-arrow-left"></i>&nbsp; Retour</button>
</div>
<div class="login_area">
{% include "common/conn_admin.html" %}
</div>
<div class="header txtcenter">
<h1>Gestion des Binômes</h1>
</div>
<div class="page_content">
<div class="tiles_container">
<div class="tile">
<div class="search_member_form_area" id="search_member_form_area">
<h4>Rechercher le.a coopérateur.ice titulaire</h4>
<form autocomplete="off" id="search_member_form" class="search_member_form" action="javascript:;" method="post">
<input name="searchParent" type="text" id="search_member_input" value="" placeholder="Nom ou numéro du coop..." required>
<img id="spinner1" class="spinner" src="{% static 'img/Loading_2.gif' %}" alt="loading" style="display:none;">
</form>
</div>
<div id="parentInfo" style="display:none;">
<div class="tile_title">
<i class="fas fa-user tile_icon"></i>
<span class="member_info member_name"></span>
<br>
<span>Email : </span>
<span class="member_info member_email"></span>
</div>
<div class="tile_content">
<div class="member_status_text_container">
<span>Statut : </span>
<span class="member_info member_status"></span>
<br>
<span>Nombre de rattrapage(s) : </span>
<span class="member_makeups_to_do member_info"></span>
</div>
<div class="member_shift_name_area">
<span>Créneau : </span>
<span class="member_shift_name member_info"></span>
</div>
</div>
</div>
</div>
<div class="create_pair_button">
<button id="createPair" type="button" name="button" disabled>Créer le binôme</button>
</div>
<div class="tile">
<div class="search_member_form_area" id="search_member_form_area">
<h4>Rechercher le.a coopérateur.ice suppléant.e</h4>
<form autocomplete="off" id="search_member_form_child" class="search_member_form" action="javascript:;" method="post">
<input name="searchChild" type="text" id="search_child_input" value="" placeholder="Nom ou numéro du coop..." required>
<img id="spinner2" class="spinner" src="{% static 'img/Loading_2.gif' %}" alt="loading" style="display:none;">
</form>
</div>
<div id="childInfo" style="display:none;">
<div class="tile_title">
<i class="fas fa-user tile_icon"></i>
<span class="member_info member_name"></span>
<br>
<span>Email : </span>
<span class="member_info member_email"></span>
</div>
<div class="tile_content">
<div class="member_status_text_container">
<span>Statut : </span>
<span class="member_info member_status"></span>
<br>
<span>Nombre de rattrapage(s) : </span>
<span class="member_makeups_to_do member_info"></span>
</div>
<div class="member_shift_name_area">
<span>Créneau : </span>
<span class="member_shift_name member_info"></span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src='{% static "js/all_common.js" %}?v='></script>
<script src='{% static "js/admin/manage_attached.js" %}?v='></script>
{% endblock %}
{% extends "base.html" %}
{% load static %}
{% block additionnal_css %}
<link rel="stylesheet" href="{% static 'css/datatables/datatables.min.css' %}">
<link rel="stylesheet" href="{% static 'css/admin/manage_attached.css' %}">
<link rel="stylesheet" href="{% static 'jquery-ui-1.12.1/jquery-ui.min.css' %}">
{% endblock %}
{% block additionnal_scripts %}
<script type="text/javascript" src="{% static 'jquery-ui-1.12.1/jquery-ui.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/datatables.min.js' %}"></script>
{% endblock %}
{% block content %}
<div id="template" style="display:None;">
<div id="confirmModal">
<h3>Le binôme est sur le point d'être désolidarisé</h3>
<br />
Êtes-vous sûr.e de vouloir désolidariser :
<div>
<div class="attached-members">
<div class="member">
<div class="name">
<strong><span id="parentName"></span></strong> (titulaire)<br />
<i><span id="parentEmail"></span></i>
<div class="select_after_unattached_state">
<input type="checkbox" name="parent_gone" class="after_unattached_state" />
</div>
</div>
</div>
et
<div class="member">
<div class="name">
<strong><span id="childName"></span></strong><br />
<i><span id="childEmail"></span></i>
<div class="select_after_unattached_state">
<input type="checkbox" name="child_gone" class="after_unattached_state" />
</div>
</div>
</div>
</div>
</div>
<p class="warning_instruction">
Si vous souhaitez attribuer le statut "Parti·e" à l'un·e des deux membres, <br/>
cochez la case correspondante.<br/>
Attention, ce statut est réservé aux personnes qui ont demandé à être désinscrit·e·s de leur créneau pour une durée longue ou indéfinie tout en restant membre de La Cagette.
</p>
<hr/>
</div>
</div>
<div class="page_body">
<div id="back_to_admin_index">
<button type="button" class="btn--danger"><i class="fas fa-arrow-left"></i>&nbsp; Retour</button>
</div>
<div class="login_area">
{% include "common/conn_admin.html" %}
</div>
<div class="header txtcenter">
<h1>Gestion des Binômes</h1>
</div>
<div class="page_content">
<div id="subheader">
<h4>Liste des membres en binômes</h4>
</div>
<div class="table_area">
<table id="attached_members_table" class="display" cellspacing="0" width="100%"></table>
</div>
</div>
</div>
<script src='{% static "js/all_common.js" %}?v='></script>
<script src='{% static "js/admin/manage_attached.js" %}?v='></script>
{% endblock %}
{% extends "base.html" %}
{% load static %}
{% block additionnal_css %}
<link rel="stylesheet" href="{% static 'css/datatables/datatables.min.css' %}">
<link rel="stylesheet" href="{% static 'css/admin/manage_makeups.css' %}">
<link rel="stylesheet" href="{% static 'jquery-ui-1.12.1/jquery-ui.min.css' %}">
{% endblock %}
{% block additionnal_scripts %}
<script type="text/javascript" src="{% static 'jquery-ui-1.12.1/jquery-ui.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/datatables.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/notify.min.js' %}?v="></script>
{% endblock %}
{% block content %}
<div class="page_body">
<div id="back_to_admin_index">
<button type="button" class="btn--danger"><i class="fas fa-arrow-left"></i>&nbsp; Retour</button>
</div>
<div class="login_area">
{% include "common/conn_admin.html" %}
</div>
<div class="header txtcenter">
<h1>Gestion des Rattrapages</h1>
</div>
<div class="page_content">
<div id="table_top_area">
<h3>Liste des membres devant effectuer un rattrapage</h3>
<div class="table_grouped_action">
<button type="button" class="btn--primary" id="decrement_selected_members_makeups">
-1 rattrapage pour les membres sélectionnés
</button>
</div>
</div>
<div class="table_area">
<table id="makeups_members_table" class="display" cellspacing="0" width="100%"></table>
</div>
<div id="add_members_area">
<div id="add_members_form_area">
<h4>Ou, ajouter un rattrapage à un.e membre</h4>
<form id="search_member_form" action="javascript:;" method="post">
<input type="text" id="search_member_input" value="" placeholder="Nom ou numéro du coop..." required>
<button type="submit" class="btn--primary" id="search_member_button">Recherche</button>
</form>
</div>
<div class="search_member_results_area" style="display:none;">
<div class="search_results_text">
<p><i>Choisissez parmi les membres trouvés :</i></p>
</div>
<div class="search_member_results"></div>
</div>
</div>
</div>
<div id="templates" style="display:none;"></div>
</div>
<script src='{% static "js/all_common.js" %}?v='></script>
<script src='{% static "js/admin/manage_makeups.js" %}?v='></script>
{% endblock %}
{% extends "base.html" %}
{% load static %}
{% block additionnal_css %}
<link rel="stylesheet" href="{% static 'css/datatables/datatables.min.css' %}">
<link rel="stylesheet" href="{% static 'css/admin/manage_regular_shifts.css' %}">
<link rel="stylesheet" href="{% static 'jquery-ui-1.12.1/jquery-ui.min.css' %}">
{% endblock %}
{% block additionnal_scripts %}
<script type="text/javascript" src="{% static 'jquery-ui-1.12.1/jquery-ui.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/datatables.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/notify.min.js' %}?v="></script>
{% endblock %}
{% block content %}
<div class="page_body">
<div id="back_to_admin_index">
<button type="button" class="btn--danger"><i class="fas fa-arrow-left"></i>&nbsp; Retour</button>
</div>
<div class="login_area">
{% include "common/conn_admin.html" %}
</div>
<div class="header txtcenter">
<h1>Gestion des Créneaux</h1>
</div>
<div class="page_content">
<div id="search_member_area">
<div id="search_member_form_area">
<h4>Rechercher un.e membre</h4>
<form id="search_member_form" action="javascript:;" method="post">
<input type="text" id="search_member_input" value="" placeholder="Nom ou numéro du coop..." required>
<button type="submit" class="btn--primary" id="search_member_button">Recherche</button>
</form>
</div>
<div class="search_member_results_area" style="display:none;">
<div class="search_results_text">
<p><i>Choisissez parmi les membres trouvés :</i></p>
</div>
<div class="search_member_results"></div>
</div>
</div>
<div id="partner_data_area">
<h4 class="member_name_container">
<i class="fas fa-user member_name_icon"></i>
<span class="member_info member_name"></span>
</h4>
<p class="shift_name_container">Créneau : <span class="member_info member_shift"></span></p>
<p class="status_container">Statut : <span class="member_info member_status"></span></p>
<p class="makeups_container">Nb rattrapage(s) : <span class="member_info member_makeups"></span></p>
<div id="actions_on_member">
<button class="btn--primary" id="remove_shift_template_button">
Désinscrire du créneau
</button>
<button class="btn--primary" id="change_shift_template_button">
Changer de créneau
</button>
<button class="btn--primary" id="subscribe_to_shift_template_button">
Réinscrire à un créneau
</button>
</div>
</div>
</div>
</div>
<div id="shifts_calendar_area">
{% include "members/shift_template_choice.html" %}
</div>
<div id="templates" style="display:none;">
<div id="modal_remove_shift_template">
<p>Voulez vraiment désinscrire ce membre du créneau <span class="shift_template_name"></span> ?</p>
<div class="checkbox_area">
<input type="checkbox" id="permanent_unsuscribe" name="permanent_unsuscribe">
<label for="permanent_unsuscribe">Désinscription définitive</label>
</div>
</div>
<div id="modal_error_change_shift_template">
<h3 class="error_modal_title""">Action impossible</h3>
<p>
Le ou la membre est inscrit.e à un rattrapage sur le créneau choisi (<span class="shift_template_name"></span>), cela empêche de l'inscrire sur ce créneau.
</p>
<p>Vous pouvez essayer de l'inscrire sur ce créneau une autre semaine.</p>
</div>
</div>
</div>
<script src="{% static "js/pouchdb.min.js" %}"></script>
<script type="text/javascript">
var type = 2;
var has_committe_shift = '{{has_committe_shift}}'
var max_begin_hour = '{{max_begin_hour}}'
var couchdb_dbname = '{{db}}';
var couchdb_server = '{{couchdb_server}}' + couchdb_dbname;
var ASSOCIATE_MEMBER_SHIFT = '{{ASSOCIATE_MEMBER_SHIFT}}';
</script>
<script src='{% static "js/common.js" %}?v='></script>
<script src='{% static "js/all_common.js" %}?v='></script>
<script src='{% static "js/admin/manage_regular_shifts.js" %}?v='></script>
{% endblock %}
{% extends "base.html" %}
{% load static %}
{% block additionnal_css %}
<link rel="stylesheet" href="{% static 'css/datatables/datatables.min.css' %}">
<link rel="stylesheet" href="{% static 'css/admin/manage_shift_registrations.css' %}">
<link rel="stylesheet" href="{% static 'jquery-ui-1.12.1/jquery-ui.min.css' %}">
{% endblock %}
{% block additionnal_scripts %}
<script type="text/javascript" src="{% static 'jquery-ui-1.12.1/jquery-ui.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/datatables.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/notify.min.js' %}?v="></script>
{% endblock %}
{% block content %}
<div class="page_body">
<div id="back_to_admin_index">
<button type="button" class="btn--danger"><i class="fas fa-arrow-left"></i>&nbsp; Retour</button>
</div>
<div class="login_area">
{% include "common/conn_admin.html" %}
</div>
<div class="header txtcenter">
<h1>Gestion des Présences</h1>
</div>
<div class="page_content">
<div id="search_member_area">
<div id="search_member_form_area">
<h4>Rechercher un.e membre</h4>
<form id="search_member_form" action="javascript:;" method="post">
<input type="text" id="search_member_input" value="" placeholder="Nom ou numéro du coop..." required>
<button type="submit" class="btn--primary" id="search_member_button">Recherche</button>
</form>
</div>
<div class="search_member_results_area" style="display:none;">
<div class="search_results_text">
<p><i>Choisissez parmi les membres trouvés :</i></p>
</div>
<div class="search_member_results"></div>
</div>
</div>
<div id="table_top_area">
<h3>Liste des futurs services de <span id="member_name"></span></h3>
</div>
<div class="table_area">
<table id="member_shifts_table" class="display" cellspacing="0" width="100%"></table>
</div>
</div>
<div id="templates" style="display:none;"></div>
</div>
<script src='{% static "js/all_common.js" %}?v='></script>
<script src='{% static "js/admin/manage_shift_registrations.js" %}?v='></script>
{% endblock %}
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
<div class="row-1 grid-2"> <div class="row-1 grid-2">
<div class="col-1"> <div class="col-1">
<div class="label"> <div class="label">
Biper le badge, ou saisissez le n° de coop. ou le nom Biper le badge, ou saisir le n° de coop. ou le nom
</div> </div>
Recherche : Recherche :
<input type="text" name="search_string" autocomplete="off" /> <input type="text" name="search_string" autocomplete="off" />
...@@ -112,7 +112,7 @@ ...@@ -112,7 +112,7 @@
<div id="next_shifts"> <div id="next_shifts">
</div> </div>
</div> </div>
<a class="btn" data-next="first_page">Coopérateur suivant</a> <a class="btn" data-next="first_page">Coopérateur.rice suivant.e</a>
</div> </div>
<div class="col-1"> <div class="col-1">
<section id="member_advice"> <section id="member_advice">
...@@ -138,9 +138,13 @@ ...@@ -138,9 +138,13 @@
</section> </section>
</section> </section>
<section class="grid-6 has-gutter" id="service_entry"> <section class="grid-6 has-gutter" id="service_entry">
<div class="col-6 row-2"> <div class="col-2 row-2">
<a class="btn btn--primary" data-next="first_page" >Retour accueil</a> <a class="btn btn--primary" data-next="first_page" >Retour accueil</a>
</div> </div>
<div class="col-2 row-2"></div>
<div class="col-2 row-2 login_area">
{% include "common/conn_admin.html" %}
</div>
<div class="col-1"></div> <div class="col-1"></div>
<div class="col-4"> <div class="col-4">
<h1 class="col-4 txtcenter">Qui es-tu ?</h1> <h1 class="col-4 txtcenter">Qui es-tu ?</h1>
...@@ -178,9 +182,23 @@ ...@@ -178,9 +182,23 @@
<section id="service_validation" class="col-6 grid-6 has-gutter"> <section id="service_validation" class="col-6 grid-6 has-gutter">
<div class="col-2"></div> <div class="col-2"></div>
<a class="col-2 btn present">{{CONFIRME_PRESENT_BTN|safe}}</a> <a class="col-2 btn present">{{CONFIRME_PRESENT_BTN|safe}}</a>
<span class="loading2"><img width="75" src="/static/img/Pedro_luis_romani_ruiz.gif" alt="Chargement en cours...." /></span> <div class="col-2 loading2-container">
<div class="col-2"></div> <span class="loading2"><img width="75" src="/static/img/Pedro_luis_romani_ruiz.gif" alt="Chargement en cours...." /></span>
</div>
</section> </section>
<div id="associated_service_validation">
<p class="col-6 txtcenter">Qui est présent à ce service ?</p>
<section class="col-6 grid-5 has-gutter">
<div class="col-1"></div>
<a id="associated_btn" class=" btn present">Membre</a>
<a id="partner_btn" class=" btn present">Associé</a>
<a id="both_btn" class=" btn present">Les deux</a>
<div class="col-1 loading2-container">
<span class="loading2"><img width="75" src="/static/img/Pedro_luis_romani_ruiz.gif" alt="Chargement en cours...." /></span>
</div>
</section>
</div>
</div> </div>
<div class="col-2"></div> <div class="col-2"></div>
<a class="btn col-2 return" data-next="service_entry">Retour</a> <a class="btn col-2 return" data-next="service_entry">Retour</a>
...@@ -208,7 +226,7 @@ ...@@ -208,7 +226,7 @@
{% endif %} {% endif %}
</section> </section>
<div class="col-2"></div> <div class="col-2"></div>
<a class="btn col-2" data-next="first_page">Coopérateur suivant</a> <a class="btn col-2" data-next="first_page">Coopérateur.ice suivant.e</a>
<div class="col-2"></div> <div class="col-2"></div>
</section> </section>
<section class="grid-6 has-gutter" id="rattrapage_1"> <section class="grid-6 has-gutter" id="rattrapage_1">
......
...@@ -26,22 +26,29 @@ ...@@ -26,22 +26,29 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<nav class="col-6 clearfix nav"> <nav class="col-6 clearfix nav">
<div id="process_state" class="fl"></div> <div class="left-nav">
{% if prepa_odoo_url != '' %}
<button id="create_new_coop" class="btn--primary fr">Nouvelle inscription</button> <div id="goto_prepa_odoo" class="fl">
<button id="coop_list_btn" class="btn--info fr" style="display:none;">Liste</button> <a class="btn--info" id="goto_prepa_odoo_button" href='{{prepa_odoo_url}}' target='_blank'>Prepa Odoo</a>
<button id="shift_calendar" class="btn--inverse fr">Vue créneaux</button> </div>
{% endif %}
<div id="process_state_container" class="fl">
<div id="process_state"></div>
</div>
</div>
<button id="create_new_coop" class="btn--primary fr">Nouvelle inscription</button>
<button id="coop_list_btn" class="btn--info fr" style="display:none;">Liste</button>
<button id="shift_calendar" class="btn--inverse fr">Vue créneaux</button>
</nav> </nav>
<section class="center" id="new_coop"> <section class="center" id="new_coop">
<div class="grid-1"> <div class="grid-1">
<div class="item-center"> <div class="item-center">
<h2 class="title"> <h2 class="title">
NOUVEAU MEMBRE NOUVEAU MEMBRE
</h2> </h2>
<form id="coop_create" lang="fr"> <form id="coop_create" lang="fr">
{% if ask_for_sex %} {% if ask_for_sex %}
<p> <p>
{% include "members/sex_input.html" %} {% include "members/sex_input.html" %}
...@@ -65,17 +72,61 @@ ...@@ -65,17 +72,61 @@
<input type="number" min="1" placeholder="Nb de chèques" name="ch_qty" id="ch_qty" style="display:none;"/> <input type="number" min="1" placeholder="Nb de chèques" name="ch_qty" id="ch_qty" style="display:none;"/>
</p> </p>
{% if input_barcode %} {% if input_barcode %}
<p> <p>
<input type="text" name="m_barcode" id="m_barcode" maxlength="13" size="13" placeholder="Code barre" autocomplete="off" required/> <input type="text" name="m_barcode" id="m_barcode" maxlength="13" size="13" placeholder="Code barre" autocomplete="off" required/>
</p> </p>
{% endif %} {% endif %}
<button class="btn--primary">Valider</button>
</form> {% if ASSOCIATE_MEMBER_SHIFT %}
</div> <p id="add_binome" >+ Binomes (facultatif)</p>
<div id="mail_generation"> <div id="associate_area" style="display:none;">
(*) L'adresse mail étant obligatoire, si le nouveau membre n'en a pas, veuillez en créer une en cliquant sur le bouton suivant : <a class="btn--info" id="generate_email">+</a> <div class="choice_button_area d-flex" >
</div> <div id="existing_member_choice" class="member_choice">
A mettre en binome avec un.e membre existant.e
</div>
<div id="new_member_choice" class="member_choice">
A mettre en binome avec un.e nouveau membre
</div>
</div>
<div id="existing_member_choice_action" style="display:none;">
<input type="text" id="search_member_input" value="" placeholder="Nom ou numéro du coop..." >
<div class="btn--primary" id="search_member_button">Recherche</div>
<div class="search_member_results_area" style="display:none;">
<div class="search_results_text">
<p><i>Choisissez parmi les membres trouvés :</i></p>
</div>
<div class="search_member_results"></div>
</div>
<div class="chosen_associate_area" style="display:none;">
<div >
<p><i>Binôme choisi : </i></p>
</div>
<div class="chosen_associate_group">
<span class="chosen_associate"></span>
<i class="fas fa-times remove_binome_icon"></i>
</div>
</div>
</div>
<div id="new_member_choice_action" style="display:none;">
<div >
<div>
<input type="text" id="new_member_input" value="" placeholder="Nom du membre" >
</div>
</div>
</div>
</div>
{% endif %}
<div>
<button class="btn--primary">Valider</button>
</div>
</form>
<div id="mail_generation">
(*) L'adresse mail étant obligatoire, si le nouveau membre n'en a pas, veuillez en créer une en cliquant sur le bouton suivant : <a class="btn--info" id="generate_email">+</a>
</div>
</div> </div>
</section> </section>
{% include "members/shift_template_choice.html" %} {% include "members/shift_template_choice.html" %}
...@@ -90,8 +141,9 @@ ...@@ -90,8 +141,9 @@
<div class="numbox badge"></div> <div class="numbox badge"></div>
<p>Pensez à inscrire ce numéro temporaire de coopérateur au crayon de papier sur les deux formulaires papier.</p> <p>Pensez à inscrire ce numéro temporaire de coopérateur au crayon de papier sur les deux formulaires papier.</p>
--> -->
<p id="parent" hidden>En binôme avec : <span id="parentName"></span></p>
<p>Créneau choisi : <span class="shift_template"></span></p> <p>Créneau choisi : <span class="shift_template"></span></p>
<p>Prochain service : <span class="next_shift"></span></p> <p id="next_shift_registration_detail">Prochain service : <span class="next_shift"></span></p>
<button class="btn--primary" id="next_coop">Coopérateur.rice suivant.e !</button> <button class="btn--primary" id="next_coop">Coopérateur.rice suivant.e !</button>
</div> </div>
...@@ -119,15 +171,18 @@ ...@@ -119,15 +171,18 @@
var couchdb_dbname = '{{db}}'; var couchdb_dbname = '{{db}}';
var couchdb_server = '{{couchdb_server}}' + couchdb_dbname; var couchdb_server = '{{couchdb_server}}' + couchdb_dbname;
var dbc = new PouchDB(couchdb_dbname); var dbc = new PouchDB(couchdb_dbname);
var ASSOCIATE_MEMBER_SHIFT = '{{ASSOCIATE_MEMBER_SHIFT}}';
var sync = PouchDB.sync(couchdb_dbname, couchdb_server, { var sync = PouchDB.sync(couchdb_dbname, couchdb_server, {
live: true, live: true,
retry: true, retry: true,
auto_compaction: false auto_compaction: false
}); });
var mag_place_string = '{{mag_place_string}}'; var mag_place_string = '{{mag_place_string}}';
var office_place_string = '{{office_place_string}}' var office_place_string = '{{office_place_string}}';
var max_begin_hour = '{{max_begin_hour}}' var max_begin_hour = '{{max_begin_hour}}';
var email_domain = '{{email_domain}}' var email_domain = '{{email_domain}}';
var committees_shift_id = '{{committees_shift_id}}';
</script> </script>
<script src="{% static "js/all_common.js" %}?v="></script> <script src="{% static "js/all_common.js" %}?v="></script>
<script src="{% static "js/common.js" %}?v="></script> <script src="{% static "js/common.js" %}?v="></script>
......
...@@ -60,6 +60,9 @@ ...@@ -60,6 +60,9 @@
<input type="text" name="m_barcode" id="m_barcode" disabled/> <input type="text" name="m_barcode" id="m_barcode" disabled/>
</p> </p>
{% endif %} {% endif %}
<div id="associated_member">
En binôme avec : <span id ="associated_member_name"></span>
</div>
</div> </div>
<p class="buttons"> <p class="buttons">
<button class="btn--success" name="valider">Tout est bon</button> <button class="btn--success" name="valider">Tout est bon</button>
......
...@@ -10,8 +10,9 @@ ...@@ -10,8 +10,9 @@
<a href="javascript:void(0);" class="nav_item" id="nav_my_info">Mes Infos</a> <a href="javascript:void(0);" class="nav_item" id="nav_my_info">Mes Infos</a>
<a href="javascript:void(0);" class="nav_item" id="nav_my_shifts">Mes Services</a> <a href="javascript:void(0);" class="nav_item" id="nav_my_shifts">Mes Services</a>
<a href="javascript:void(0);" class="nav_item" id="nav_shifts_exchange">Échange de services</a> <a href="javascript:void(0);" class="nav_item" id="nav_shifts_exchange">Échange de services</a>
{% if show_faq %}
<a href="javascript:void(0);" class="nav_item" id="nav_faq">Problèmes et Demandes</a> <a href="javascript:void(0);" class="nav_item" id="nav_faq">Problèmes et Demandes</a>
{% endif %}
<a <a
href="javascript:void(0);" href="javascript:void(0);"
target="_blank" target="_blank"
......
...@@ -21,6 +21,9 @@ ...@@ -21,6 +21,9 @@
<button type="button" class="btn--danger choose_makeups"> <button type="button" class="btn--danger choose_makeups">
Je sélectionne mes rattrapages Je sélectionne mes rattrapages
</button> </button>
<button type="button" class="btn--success remove_future_registration">
J'ai validé un service à deux, je peux supprimer une présence
</button>
</div> </div>
<div class="member_shift_name_area"> <div class="member_shift_name_area">
<span>Mon créneau : </span> <span>Mon créneau : </span>
......
...@@ -31,15 +31,39 @@ ...@@ -31,15 +31,39 @@
<div id="shift_line_template"> <div id="shift_line_template">
<div class="shift_line"> <div class="shift_line">
<i class="fas fa-chevron-right shift_line_chevron"></i> <i class="fas fa-chevron-right shift_line_chevron"></i>
<span class="shift_line_date"></span> - <span class="shift_line_time"></span> <span class="shift_line_date"></span> - <span class="shift_line_time"></span> <span class="shift_line_associate"> </span>
</div> </div>
</div> </div>
<div id="selectable_shift_line_template"> <div id="selectable_shift_line_template">
<div class="selectable_shift_line btn--primary"> <div class="d-flex shift_line_container selectable_shift">
<input type="checkbox" class="checkbox"> <div class="selectable_shift_line btn--primary">
<div class="selectable_shift_line_text"> <input type="checkbox" class="checkbox">
<span class="shift_line_date"></span> - <span class="shift_line_time"></span> <div class="selectable_shift_line_text">
<span class="shift_line_date"></span> - <span class="shift_line_time"></span>
</div>
</div>
<div class="shift_line_extra_actions">
<div class="affect_associate_registered">
</div>
</div>
</div>
</div>
<div id="delete_registration_button_template">
<div class="delete_registration_button"><i class="fas fa-lg fa-trash"></i></div>
</div>
<div id="modal_affect_shift">
<div>Qui sera présent.e ?</div>
<div class="modal_affect_shift_buttons">
<div id="shift_partner" class="btn--primary assign_shift_button">
</div>
<div id="shift_associate" class=" btn--primary assign_shift_button">
</div>
<div id="shift_both" class=" btn--primary assign_shift_button">
Les deux
</div> </div>
</div> </div>
</div> </div>
...@@ -57,10 +81,12 @@ ...@@ -57,10 +81,12 @@
<div id="calendar_explaination_template"> <div id="calendar_explaination_template">
<h4>Légende du calendrier</h4> <h4>Légende du calendrier</h4>
<a class="example-event fc-daygrid-event fc-daygrid-block-event fc-h-event fc-event fc-event-start fc-event-end fc-event-future shift_booked"><div class="fc-event-main"><div class="fc-event-main-frame"><div class="fc-event-time">06:00</div><div class="fc-event-title-container"><div class="fc-event-title fc-sticky">&nbsp;- 9/12</div></div></div></div></a>
<p>Un service colorié en noir : je suis déjà inscrit.e à ce service.</p>
<a class="example-event fc-daygrid-event fc-daygrid-block-event fc-h-event fc-event fc-event-start fc-event-end fc-event-future shift_less_alf"><div class="fc-event-main"><div class="fc-event-main-frame"><div class="fc-event-time">10:45</div><div class="fc-event-title-container"><div class="fc-event-title fc-sticky">&nbsp;- 3/12</div></div></div></div></a> <a class="example-event fc-daygrid-event fc-daygrid-block-event fc-h-event fc-event fc-event-start fc-event-end fc-event-future shift_less_alf"><div class="fc-event-main"><div class="fc-event-main-frame"><div class="fc-event-time">10:45</div><div class="fc-event-title-container"><div class="fc-event-title fc-sticky">&nbsp;- 3/12</div></div></div></div></a>
<p>Un service colorié en bleu : je peux m'inscrire à ce service.</p> <p>Un service colorié en bleu : je peux m'inscrire à ce service.</p>
<a class="example-event fc-daygrid-event fc-daygrid-block-event fc-h-event fc-event fc-event-start fc-event-end fc-event-future shift_booked"><div class="fc-event-main"><div class="fc-event-main-frame"><div class="fc-event-time">06:00</div><div class="fc-event-title-container"><div class="fc-event-title fc-sticky">&nbsp;- 9/12</div></div></div></div></a>
<p>Un service colorié en noir : je suis déjà inscrit.e à ce service.</p>
<a class="example-event fc-daygrid-event fc-daygrid-block-event fc-h-event fc-event fc-event-start fc-event-end fc-event-future shift_booked_makeup"><div class="fc-event-main"><div class="fc-event-main-frame"><div class="fc-event-time">13:30</div><div class="fc-event-title-container"><div class="fc-event-title fc-sticky">&nbsp;- 7/12</div></div></div></div></a>
<p>Un service colorié en orange : je suis inscrit.e à un rattrapage sur ce service.</p>
<p>3/12 <i class="arrow_explanation_numbers fas fa-arrow-right"></i> il y a déjà 3 places réservées à ce service sur 12 disponibles. <p>3/12 <i class="arrow_explanation_numbers fas fa-arrow-right"></i> il y a déjà 3 places réservées à ce service sur 12 disponibles.
<b>Plus le chiffre de gauche est petit, plus on a besoin de coopérateurs.rices à ce service !</b></p> <b>Plus le chiffre de gauche est petit, plus on a besoin de coopérateurs.rices à ce service !</b></p>
</div> </div>
...@@ -118,12 +144,15 @@ ...@@ -118,12 +144,15 @@
"is_associated_people" : "{{partnerData.is_associated_people}}", "is_associated_people" : "{{partnerData.is_associated_people}}",
"parent_id" : "{{partnerData.parent_id}}", "parent_id" : "{{partnerData.parent_id}}",
"parent_name" : "{{partnerData.parent_name}}", "parent_name" : "{{partnerData.parent_name}}",
"parent_verif_token" : "{{partnerData.parent_verif_token}}",
"associated_partner_id" : "{{partnerData.associated_partner_id}}", "associated_partner_id" : "{{partnerData.associated_partner_id}}",
"associated_partner_name" : "{{partnerData.associated_partner_name}}", "associated_partner_name" : "{{partnerData.associated_partner_name}}",
"verif_token" : "{{partnerData.verif_token}}", "verif_token" : "{{partnerData.verif_token}}",
"leave_stop_date": "{{partnerData.leave_stop_date}}", "leave_stop_date": "{{partnerData.leave_stop_date}}",
"comite": "{{partnerData.comite}}" "comite": "{{partnerData.comite}}",
} "extra_shift_done": parseInt("{{partnerData.extra_shift_done}}", 10)
};
var block_actions_for_attached_people = '{{block_actions_for_attached_people}}';
</script> </script>
<script src="{% static "js/all_common.js" %}?v="></script> <script src="{% static "js/all_common.js" %}?v="></script>
<script src="{% static "js/members-space-home.js" %}?v="></script> <script src="{% static "js/members-space-home.js" %}?v="></script>
......
...@@ -25,6 +25,9 @@ ...@@ -25,6 +25,9 @@
<button type="button" class="btn--danger choose_makeups"> <button type="button" class="btn--danger choose_makeups">
Je sélectionne mes rattrapages Je sélectionne mes rattrapages
</button> </button>
<button type="button" class="btn--success remove_future_registration">
J'ai validé un service à deux, je peux supprimer une présence
</button>
</div> </div>
</div> </div>
</div> </div>
...@@ -102,17 +105,8 @@ ...@@ -102,17 +105,8 @@
</div> </div>
</div> </div>
</div> </div>
<div class="tile full_width_tile"> {% if understand_my_status %}
<div class="tile_title"> {% include "members_space/understand_my_status.html" %}
Comprendre mon statut {% endif %}
</div>
<div class="my_info_line_middle">
Il existe différents statuts à La Cagette donnant ou non le droit de faire ses courses. Voici un schéma explicatif expliquant le passage d'un statut à un autre. Pour toute question relative aux statuts, rendez&#x2011;vous dans la rubrique <a href='faq'>Problèmes&nbsp;&&nbsp;Demandes</a>.
</div>
<a href="/static/img/diagramme_etat_statut_cooperateurs.png" target=”_blank”>
<img class="status_info_image" src="/static/img/diagramme_etat_statut_cooperateurs.png" alt="diagramme_etat_statut_cooperateurs"/>
</a>
</div>
</div> </div>
</div> </div>
...@@ -35,6 +35,14 @@ ...@@ -35,6 +35,14 @@
<span class="select_makeups_message_block">Je dois les sélectionner dans le calendrier. </span> <span class="select_makeups_message_block">Je dois les sélectionner dans le calendrier. </span>
<span class="select_makeups_message_block">Je ne peux pas échanger de service tant que je n'ai pas choisi mes rattrapages. </span> <span class="select_makeups_message_block">Je ne peux pas échanger de service tant que je n'ai pas choisi mes rattrapages. </span>
</div> </div>
<div id="can_delete_future_registrations_area">
<button class="btn--success can_delete_future_registrations_button" id="delete_future_registration">
J'ai validé <span class="extra_shift_done"></span> service(s) à deux, je supprime un service futur
</button>
<button class="btn--success can_delete_future_registrations_button" id="offer_extra_shift">
Je souhaite donner <span class="extra_shift_done"></span> service(s) d'avance à la communauté
</button>
</div>
<div id="calendar_top_info"> <div id="calendar_top_info">
<div id="partner_shifts_list"> <div id="partner_shifts_list">
<h4>Liste de mes services :</h4> <h4>Liste de mes services :</h4>
......
<div class="tile full_width_tile">
<div class="tile_title">
Comprendre mon statut
</div>
<div class="my_info_line_middle">
Il existe différents statuts à La Cagette donnant ou non le droit de faire ses courses. Voici un schéma explicatif expliquant le passage d'un statut à un autre. Pour toute question relative aux statuts, rendez&#x2011;vous dans la rubrique <a href='faq'>Problèmes&nbsp;&&nbsp;Demandes</a>.
</div>
<a href="/static/img/diagramme_etat_statut_cooperateurs.png" target=”_blank”>
<img class="status_info_image" src="/static/img/diagramme_etat_statut_cooperateurs.png" alt="diagramme_etat_statut_cooperateurs"/>
</a>
</div>
\ No newline at end of file
...@@ -2,34 +2,62 @@ ...@@ -2,34 +2,62 @@
<button type="button" class="accordion" style="width:100%"><label>Comment sont calculées les conso. moyennes / jour ?</label></button> <button type="button" class="accordion" style="width:100%"><label>Comment sont calculées les conso. moyennes / jour ?</label></button>
<div class="txtleft" style="display: none;"> <div class="txtleft" style="display: none;">
<p> <p>
La fonction qui calcule les consommations moyennes prend en paramètre une date de départ. <br/> La fonction qui calcule les consommations moyennes prend en paramètre soit :
Si elle n'est pas indiquée, la date prise en compte sera "<em>aujourd'hui - nb de jours paramétré dans Odoo</em>".<br/> </p>
<ul>
<li>un nombre de jours de couverture</li>
<li>un montant en €</li>
</ul>
<p>
qu'il sera ensuite possible d'ajuster en pourcentage.<br/>
</p>
<p>
Si rien n’est indiqué, le paramètre de calcul pris en compte sera : "<em>aujourd'hui - nb de jours paramétré dans Odoo</em>".<br/>
Le nombre de jours paramétré est actuellement de <strong>{{nb_past_days_to_compute_sales_average}}</strong> jours.<br/>
</p>
<blockquote>
Ce nombre de jours paramétrable se trouve en suivant les menus suivants :<br> Ce nombre de jours paramétrable se trouve en suivant les menus suivants :<br>
Configuration > Technique > Paramètres > Paramètres systèmes. <br/> Configuration > Technique > Paramètres > Paramètres systèmes. <br/>
La valeur est définie avec la clef "<em>lacagette_products.nb_past_days_to_compute_sales_average</em>".<br/> La valeur est définie avec la clef "<em>lacagette_products.nb_past_days_to_compute_sales_average</em>".<br/>
Ce paramètre vaut <strong>{{nb_past_days_to_compute_sales_average}}</strong> au moment du chargement de cette page.<br/> </blockquote>
Les ventes du dimanche sont exclues du calcul.<br/> <br>
<p>
<strong>Dans le cas d'un nombre de jours de couverture :</strong>
</p> </p>
<p> <p>
Une requête est faite sur l'ensemble des passages en caisse, de la date de départ à hier, récupérant pour tous les jours de la période (dimanches exclus) les quantités vendues des articles achetés chez le fournisseur.<br/> Une requête est faite sur l'ensemble des passages en caisse, du Nb jours de couverture à hier, récupérant pour tous les jours de la période (dimanches exclus) les quantités vendues des articles achetés chez le fournisseur.<br/>
Pour chaque article, le nombre de jours considérés pour faire la moyenne est défini commme suit :<br/> Pour chaque article, le nombre de jours considérés pour faire la moyenne est défini comme suit :<br/>
<em>Nb de jours ouvrés de la période - Nb de jours des périodes de rupture</em><br/> "<em>Nb de jours ouvrés de la période - Nb de jours des périodes de rupture</em>"<br/>
Les périodes de ruptures sont caractérisées par un nombre de jours consécutifs assez important de jours sans vente de l'article.<br/> Le nombre de jours consécutifs sans vente pour considérer l'article en rupture est actuellement de <strong>{{nb_of_consecutive_non_sale_days_considered_as_break}}</strong> jours.<br/>
Le nombre de jours consécutifs sans vente pour considérer l'article en rupture est actuellement de <strong>{{nb_of_consecutive_non_sale_days_considered_as_break}}</strong><br/>
(c'est le paramètre système avec la clef "<em>lacagette_products.nb_of_consecutive_non_sale_days_considered_as_break</em>").<br/>
</p> </p>
<blockquote>
Cette période de rupture est paramétrable en suivant les menus suivants :<br>
Configuration > Technique > Paramètres > Paramètres systèmes. <br/>
La valeur est définie avec la clef "<em>lacagette_products.nb_of_consecutive_non_sale_days_considered_as_break</em>".<br/>
</blockquote>
<p> <p>
Pour chaque article, la consommation moyenne par jour est obtenue en divisant la quantité totale vendue sur la période par le nombre de jours significatifs. Pour chaque article, la consommation moyenne par jour est obtenue en divisant la quantité totale vendue sur la période par le nombre de jours significatifs.
</p> </p>
<br>
<p>
<strong>Dans le cas d'un montant en € :</strong>
</p>
<p>
En entrant un montant, la fonction calculera et remplira les quantités d'articles à commander pour être au plus près de ce montant (en étant supérieur ou égal), tout en tenant compte des consommations moyennes des articles.
</p>
</div> </div>
</div> </div>
<div> <div>
<button type="button" class="accordion" style="width:100%"><label>Comment sont calculés les besoins ?</label></button> <button type="button" class="accordion" style="width:100%"><label>Comment sont calculés les besoins ?</label></button>
<div class="txtleft" style="display: none;"> <div class="txtleft" style="display: none;">
La quantité à commander pour couvrir les besoins (en jours) est le résultat de : La quantité à commander pour couvrir les besoins (en jours ou à partir d'un montant en €) est le résultat de :
<p> <p>
(<em>nb_jours</em> <strong>x</strong> <em>conso_moyenne</em>) <strong>-</strong> <em>stock_existant</em> <strong>-</strong> <em>quantités_entrantes</em> <strong>+</strong> <em>stock_minimum</em> (<em>nb_jours</em> <strong>x</strong> <em>conso_moyenne</em>) <strong>-</strong> <em>stock_existant</em> <strong>-</strong> <em>quantités_entrantes</em> <strong>+</strong> <em>stock_minimum</em>
</p> </p>
<p>
Pour plus de précisions, ce résultat peut-être ensuite ajusté en pourcentage.
</p>
</div> </div>
</div> </div>
...@@ -5,12 +5,14 @@ ...@@ -5,12 +5,14 @@
<link rel="stylesheet" href="{% static 'css/datatables/jquery.dataTables.css' %}"> <link rel="stylesheet" href="{% static 'css/datatables/jquery.dataTables.css' %}">
<link rel="stylesheet" href="{% static 'jquery-ui-1.12.1/jquery-ui.min.css' %}"> <link rel="stylesheet" href="{% static 'jquery-ui-1.12.1/jquery-ui.min.css' %}">
<link rel="stylesheet" href="{% static 'css/oders_helper_style.css' %}"> <link rel="stylesheet" href="{% static 'css/oders_helper_style.css' %}">
<link rel="stylesheet" href="{% static 'quill/quill.snow.css' %}">
{% endblock %} {% endblock %}
{% block additionnal_scripts %} {% block additionnal_scripts %}
<script type="text/javascript" src="{% static 'jquery-ui-1.12.1/jquery-ui.min.js' %}?v="></script> <script type="text/javascript" src="{% static 'jquery-ui-1.12.1/jquery-ui.min.js' %}?v="></script>
<script type="text/javascript" src="{% static 'js/datatables/jquery.dataTables.min.js' %}"></script> <script type="text/javascript" src="{% static 'js/datatables/jquery.dataTables.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/notify.min.js' %}?v="></script> <script type="text/javascript" src="{% static 'js/notify.min.js' %}?v="></script>
<script type="text/javascript" src="{% static 'quill/quill.min.js' %}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
...@@ -33,6 +35,13 @@ ...@@ -33,6 +35,13 @@
<h2>Ou, continuer une commande en cours de création</h2> <h2>Ou, continuer une commande en cours de création</h2>
<div id="existing_orders"></div> <div id="existing_orders"></div>
</div> </div>
<div id="common_info_area" style="display:none;">
<h2>Informations</h2>
<div id="common_info_editor_container">
<div id="common_info_editor"></div>
</div>
<button class="btn--primary" id="save_common_info">Sauvegarder</button>
</div>
</div> </div>
<div id="main_content" class="page_content" style="display:none;"> <div id="main_content" class="page_content" style="display:none;">
...@@ -41,7 +50,6 @@ ...@@ -41,7 +50,6 @@
<i class="fas fa-arrow-left"></i>&nbsp; Retour <i class="fas fa-arrow-left"></i>&nbsp; Retour
</button> </button>
<div class="right_action_buttons"> <div class="right_action_buttons">
<div id="actions_buttons_wrapper"> <div id="actions_buttons_wrapper">
<button type="button" class='btn--primary' id="toggle_action_buttons"> <button type="button" class='btn--primary' id="toggle_action_buttons">
<span class="button_content"> <span class="button_content">
...@@ -95,10 +103,11 @@ ...@@ -95,10 +103,11 @@
<form action="javascript:;" id="coverage_form" class="order_form_item"> <form action="javascript:;" id="coverage_form" class="order_form_item">
<div class="input-wrapper"> <div class="input-wrapper">
<input type="number" name="coverage_days" id="coverage_days_input" placeholder="Nb jours de couverture" min="1"> <input type="number" name="coverage_days" id="coverage_days_input" placeholder="Nb jours de couverture" min="1">
<input type="number" name="targeted_amount" id="targeted_amount_input" placeholder="Montant en €" min="1">
<input type="number" name="percent_adjustement" id="percent_adjust_input" placeholder="ajustement en %"> <input type="number" name="percent_adjustement" id="percent_adjust_input" placeholder="ajustement en %">
</div> </div>
<div> <div>
<button type="submit" class='btn--primary'>Calculer les besoins</button> <i class='main fa fa-info-circle fa-lg'></i> <button type="submit" class='btn--primary'>Calculer les besoins</button> <i class='main fa fa-info-circle fa-lg average_consumption_explanation_icon'></i>
</div> </div>
</form> </form>
</div> </div>
...@@ -213,7 +222,7 @@ ...@@ -213,7 +222,7 @@
<p class="remove_order_modal_text"> <p class="remove_order_modal_text">
Vous vous apprêtez à <b style="color: #d9534f;">supprimer</b> cette commande en cours : <span class="remove_order_name"></span>.<br/> Vous vous apprêtez à <b style="color: #d9534f;">supprimer</b> cette commande en cours : <span class="remove_order_name"></span>.<br/>
</p> </p>
<p>Êtez-vous sûr ?</p> <p>Êtez-vous sûr.e ?</p>
<hr/> <hr/>
</div> </div>
...@@ -224,7 +233,7 @@ ...@@ -224,7 +233,7 @@
Les produits associés uniquement à ce fournisseur seront supprimés du tableau.<br/> Les produits associés uniquement à ce fournisseur seront supprimés du tableau.<br/>
Les données renseignées dans la colonne de ce fournisseur seront perdues. Les données renseignées dans la colonne de ce fournisseur seront perdues.
</p> </p>
<p>Êtez-vous sûr ?</p> <p>Êtez-vous sûr.e ?</p>
<hr/> <hr/>
</div> </div>
...@@ -250,7 +259,7 @@ ...@@ -250,7 +259,7 @@
<p> <p>
L'association sera sauvegardée dès que vous aurez cliqué sur "Valider".<br/> L'association sera sauvegardée dès que vous aurez cliqué sur "Valider".<br/>
</p> </p>
<p>Êtez-vous sûr ?</p> <p>Êtez-vous sûr.e ?</p>
<hr/> <hr/>
</div> </div>
...@@ -263,7 +272,7 @@ ...@@ -263,7 +272,7 @@
<p> <p>
L'association sera supprimée dès que vous aurez cliqué sur "Valider".<br/> L'association sera supprimée dès que vous aurez cliqué sur "Valider".<br/>
</p> </p>
<p>Êtez-vous sûr ?</p> <p>Êtez-vous sûr.e ?</p>
<hr/> <hr/>
</div> </div>
...@@ -271,25 +280,58 @@ ...@@ -271,25 +280,58 @@
<p> <p>
Vous vous apprêtez à créer un inventaire de <span class="inventory_products_count"></span> produits. Vous vous apprêtez à créer un inventaire de <span class="inventory_products_count"></span> produits.
</p> </p>
<p>Êtez-vous sûr ?</p> <p>Êtez-vous sûr.e ?</p>
<hr/> <hr/>
</div> </div>
<div id="product_price_action_template">
<div class="product_price_action">
<span class="supplier_name"></span>
<input type="number" class="product_supplier_price" name="" value="" />
</div>
</div>
<div id="modal_product_actions"> <div id="modal_product_actions">
Actions sur <h3><span class="product_name"></span></h3> Actions sur <h3><span class="product_name"></span></h3>
<p> <div class="product_actions_container">
<h4>NPA</h4> <div class="product_actions_section">
<div class="npa-options"> <div class="product_actions_column">
<label><input type="checkbox" name="npa-actions" value="simple-npa" /> Mettre le produit en NPA </label> <h4 class="modal_product_actions_title">NPA</h4>
<label><input type="checkbox" name="npa-actions" value="npa-in-name" /> Mettre le produit en NPA et afficher NPA</label> <div class="npa-options">
<label><input type="checkbox" name="npa-actions" value="fds-in-name" /> Mettre le produit en NPA et afficher FDS</label> <label><input type="checkbox" name="npa-actions" value="simple-npa" /> Mettre le produit en NPA </label>
<label><input type="checkbox" name="npa-actions" value="npa-in-name" /> Mettre le produit en NPA et afficher NPA</label>
<label><input type="checkbox" name="npa-actions" value="fds-in-name" /> Mettre le produit en NPA et afficher FDS</label>
</div>
</div>
<div class="product_actions_column">
<h4 class="modal_product_actions_title">Archiver le produit</h4>
<label class="checkbox_action_disabled"><input type="checkbox" name="archive-action" value="archive" disabled /> Archiver </label>
<div class="tooltip">
<i class='main fa fa-info-circle'></i>
<span class="tooltiptext tooltip-xl tt_twolines">
Un produit ne peut pas être archivé si une quantité entrante est prévue.
</span>
</div>
</div>
</div> </div>
</p> <div class="product_actions_section">
<p> <div class="product_actions_column">
<h4>Stock minimum</h4> <h4 class="modal_product_actions_title">Stock minimum</h4>
<input type="number" name="minimal_stock" value="" /> <input type="number" name="minimal_stock" value="" />
</p> </div>
<div class="product_actions_column">
<h4 class="modal_product_actions_title">Stock réel</h4>
<input type="number" name="actual_stock" value="" />
</div>
</div>
<div class="product_actions_section">
<div class="product_actions_full_column">
<h4 class="modal_product_actions_title product_prices_title">Prix</h4>
<i class="product_prices_title_label">(par fournisseur dans cette commande)</i>
<div class="product_prices_area"></div>
</div>
</div>
</div>
</div> </div>
<div id="modal_create_order"> <div id="modal_create_order">
......
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