Commit af4b178b by François C.

Ajout module Saisie Mouvement de Stock (repas salariés, pertes, autoconso)

parent b36613dc
...@@ -165,3 +165,26 @@ footer { position: fixed; ...@@ -165,3 +165,26 @@ footer { position: fixed;
} }
.mac-msg {background: red; color: #fff; padding: 3px;} .mac-msg {background: red; color: #fff; padding: 3px;}
.notifyjs-cancelable-base {
font-weight: bold;
padding: 8px 15px 8px 14px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
background-color: #fcf8e3;
border: 1px solid #fbeed5;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
white-space: nowrap;
padding-left: 25px;
background-repeat: no-repeat;
background-position: 3px 7px;
}
.notifyjs-cancelable-info {
color: #3A87AD;
background-color: #D9EDF7;
border-color: #BCE8F1;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QYFAhkSsdes/QAAA8dJREFUOMvVlGtMW2UYx//POaWHXg6lLaW0ypAtw1UCgbniNOLcVOLmAjHZolOYlxmTGXVZdAnRfXQm+7SoU4mXaOaiZsEpC9FkiQs6Z6bdCnNYruM6KNBw6YWewzl9z+sHImEWv+vz7XmT95f/+3/+7wP814v+efDOV3/SoX3lHAA+6ODeUFfMfjOWMADgdk+eEKz0pF7aQdMAcOKLLjrcVMVX3xdWN29/GhYP7SvnP0cWfS8caSkfHZsPE9Fgnt02JNutQ0QYHB2dDz9/pKX8QjjuO9xUxd/66HdxTeCHZ3rojQObGQBcuNjfplkD3b19Y/6MrimSaKgSMmpGU5WevmE/swa6Oy73tQHA0Rdr2Mmv/6A1n9w9suQ7097Z9lM4FlTgTDrzZTu4StXVfpiI48rVcUDM5cmEksrFnHxfpTtU/3BFQzCQF/2bYVoNbH7zmItbSoMj40JSzmMyX5qDvriA7QdrIIpA+3cdsMpu0nXI8cV0MtKXCPZev+gCEM1S2NHPvWfP/hL+7FSr3+0p5RBEyhEN5JCKYr8XnASMT0xBNyzQGQeI8fjsGD39RMPk7se2bd5ZtTyoFYXftF6y37gx7NeUtJJOTFlAHDZLDuILU3j3+H5oOrD3yWbIztugaAzgnBKJuBLpGfQrS8wO4FZgV+c1IxaLgWVU0tMLEETCos4xMzEIv9cJXQcyagIwigDGwJgOAtHAwAhisQUjy0ORGERiELgG4iakkzo4MYAxcM5hAMi1WWG1yYCJIcMUaBkVRLdGeSU2995TLWzcUAzONJ7J6FBVBYIggMzmFbvdBV44Corg8vjhzC+EJEl8U1kJtgYrhCzgc/vvTwXKSib1paRFVRVORDAJAsw5FuTaJEhWM2SHB3mOAlhkNxwuLzeJsGwqWzf5TFNdKgtY5qHp6ZFf67Y/sAVadCaVY5YACDDb3Oi4NIjLnWMw2QthCBIsVhsUTU9tvXsjeq9+X1d75/KEs4LNOfcdf/+HthMnvwxOD0wmHaXr7ZItn2wuH2SnBzbZAbPJwpPx+VQuzcm7dgRCB57a1uBzUDRL4bfnI0RE0eaXd9W89mpjqHZnUI5Hh2l2dkZZUhOqpi2qSmpOmZ64Tuu9qlz/SEXo6MEHa3wOip46F1n7633eekV8ds8Wxjn37Wl63VVa+ej5oeEZ/82ZBETJjpJ1Rbij2D3Z/1trXUvLsblCK0XfOx0SX2kMsn9dX+d+7Kf6h8o4AIykuffjT8L20LU+w4AZd5VvEPY+XpWqLV327HR7DzXuDnD8r+ovkBehJ8i+y8YAAAAASUVORK5CYII=);
}
.notifyjs-cancelable-base .buttons {width: 190px; margin: 5px auto;}
.notifyjs-cancelable-base button {width: 90px;text-align: center; margin: 3px;}
\ No newline at end of file
...@@ -231,4 +231,8 @@ hr { ...@@ -231,4 +231,8 @@ hr {
white-space: pre-wrap; white-space: pre-wrap;
} }
#main_content {width: 100%;} #main_content {width: 100%;}
\ No newline at end of file
.select_product_action {
max-width: 20px;
}
...@@ -220,7 +220,7 @@ function initLists() { ...@@ -220,7 +220,7 @@ function initLists() {
{ {
data:"product_id.1", data:"product_id.1",
title:"Produit", title:"Produit",
width: "50%", width: "40%",
render: function (data, type, full, meta) { render: function (data, type, full, meta) {
// Add tooltip with barcode over product name // Add tooltip with barcode over product name
let display_barcode = "Aucun"; let display_barcode = "Aucun";
...@@ -258,6 +258,12 @@ function initLists() { ...@@ -258,6 +258,12 @@ function initLists() {
defaultContent: "<a class='btn' id='toProcess_line_valid' href='#'><i class='far fa-check-square'></i></a>", defaultContent: "<a class='btn' id='toProcess_line_valid' href='#'><i class='far fa-check-square'></i></a>",
className:"dt-body-center", className:"dt-body-center",
orderable: false orderable: false
},
{
title:"Autres",
defaultContent: "<select class='select_product_action'><option value=''></option><option value='supplier_shortage'>Rupture fournisseur</option></select>",
className:"dt-body-center",
orderable: false
} }
], ],
rowId : "product_id.0", rowId : "product_id.0",
...@@ -292,9 +298,17 @@ function initLists() { ...@@ -292,9 +298,17 @@ function initLists() {
display_barcode = full.barcode; display_barcode = full.barcode;
} }
return '<div class="tooltip">' + data let display = '<div class="tooltip">' + data
+ ' <span class="tooltiptext tt_twolines">Code barre : ' + ' <span class="tooltiptext tt_twolines">Code barre : '
+ display_barcode + '</span> </div>'; + display_barcode + '</span> </div>';
if (full.supplier_shortage) {
display += ' <div class="tooltip"><i class="fas fa-info-circle"></i>'
+ ' <span class="tooltiptext tt_twolines">Rupture fournisseur'
+ '</span> </div>';
}
return display;
} }
}, },
{data:"product_uom.1", title: "Unité vente", className:"dt-body-center", orderable: false}, {data:"product_uom.1", title: "Unité vente", className:"dt-body-center", orderable: false},
...@@ -395,6 +409,38 @@ function initLists() { ...@@ -395,6 +409,38 @@ function initLists() {
} }
}); });
$('#table_to_process tbody').on('change', '.select_product_action', function () {
try {
if ($(this).val() == 'supplier_shortage') {
var row = table_to_process.row($(this).parents('tr'));
var data = row.data();
var modal_shortage = $('#modal_set_supplier_shortage');
modal_shortage.find(".supplier_shortage_product").text(' ' + data.product_id[1]);
modal_shortage.find(".supplier_shortage_supplier").text(' ' + data.partner_id[1]);
openModal(
modal_shortage.html(),
function() {
set_supplier_shortage(row, data);
},
'Valider',
true,
true,
function() {
$(".select_product_action").val('');
}
);
}
} catch (e) {
err = {msg: e.name + ' : ' + e.message, ctx: 'initLists : listener set supplier shortage'};
console.error(err);
report_JS_error(err, 'reception');
}
});
// Edit processed line // Edit processed line
$('#table_processed tbody').on('click', 'a#processed_line_edit', function () { $('#table_processed tbody').on('click', 'a#processed_line_edit', function () {
try { try {
...@@ -550,6 +596,49 @@ function remove_from_processed(row, product) { ...@@ -550,6 +596,49 @@ function remove_from_processed(row, product) {
} }
} }
// Indicate the product is on supplier shortage.
// Direct validation from to_process & set qty to 0
function set_supplier_shortage(row, product) {
try {
product.supplier_shortage = true;
// Step 1: set qty to 0
if (reception_status == 'False') {
product.old_qty = product.product_qty;
product.product_qty = 0;
} else {
// Step 2: for consistency purposes, updated products need these fields to be set
product.old_price_unit = product.price_unit;
product.new_shelf_price = null;
}
updatedProducts.push(product);
add_to_processed(product);
remove_from_toProcess(row, product);
// Update local storage of product's order
if (!orders[product.id_po]['updated_products'])
orders[product.id_po]['updated_products'] = [];
orders[product.id_po]['updated_products'].push(product);
localStorage.setItem("order_" + product.id_po, JSON.stringify(orders[product.id_po]));
// Reset search
document.getElementById('search_input').value = '';
$('table.dataTable').DataTable()
.search('')
.draw();
search_chars = [];
document.getElementById('search_input').focus();
} catch (e) {
err = {msg: e.name + ' : ' + e.message, ctx: 'set_supplier_shortage'};
console.error(err);
report_JS_error(err, 'reception');
}
}
/* EDITION */ /* EDITION */
...@@ -635,9 +724,9 @@ function editProductInfo (productToEdit, value = null) { ...@@ -635,9 +724,9 @@ function editProductInfo (productToEdit, value = null) {
// Edit product info // Edit product info
productToEdit.product_qty = newValue; productToEdit.product_qty = newValue;
/* /*
If qty has changed, we choose to set detailed values as follow: If qty has changed, we choose to set detailed values as follow:
1 package (product_qty_package) of X products (package_qty) 1 package (product_qty_package) of X products (package_qty)
*/ */
productToEdit.product_qty_package = 1; productToEdit.product_qty_package = 1;
productToEdit.package_qty = productToEdit.product_qty; productToEdit.package_qty = productToEdit.product_qty;
} }
...@@ -862,7 +951,7 @@ function data_validation() { ...@@ -862,7 +951,7 @@ function data_validation() {
// Send the request to the server // Send the request to the server
function send() { function send() {
try { try {
// Loading on // Loading on
openModal(); openModal();
// Only send to server the updated lines // Only send to server the updated lines
...@@ -940,8 +1029,6 @@ function send() { ...@@ -940,8 +1029,6 @@ function send() {
prod_order_id = updatedProducts[i].id_po; prod_order_id = updatedProducts[i].id_po;
update_data.orders[prod_order_id]['po'].push(updatedProducts[i]); update_data.orders[prod_order_id]['po'].push(updatedProducts[i]);
} }
// console.log(update_data)
// return -1
$.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } }); $.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } });
$.ajax({ $.ajax({
......
...@@ -4,6 +4,7 @@ from outils.for_view_imports import * ...@@ -4,6 +4,7 @@ from outils.for_view_imports import *
from django.views.generic import View from django.views.generic import View
import os import os
from datetime import date
from openpyxl import Workbook from openpyxl import Workbook
from openpyxl import load_workbook from openpyxl import load_workbook
from openpyxl.styles import Alignment from openpyxl.styles import Alignment
...@@ -138,16 +139,35 @@ def update_orders(request): ...@@ -138,16 +139,35 @@ def update_orders(request):
for order_line in order['po']: for order_line in order['po']:
if order_line['indicative_package'] is False: if order_line['indicative_package'] is False:
m.remove_package_restriction(order_line) m.remove_package_restriction(order_line)
update = m.update_line(int(order_line['id']), data['update_type'], float(order_line['package_qty']), float(order_line['product_qty_package']), float(order_line['price_unit']))
update = m.update_line(int(order_line['id']),
data['update_type'],
float(order_line['package_qty']),
float(order_line['product_qty_package']),
float(order_line['price_unit']))
if not (update is True): if not (update is True):
# indicative_package may have been changed since data have been loaded in browser # indicative_package may have been changed since data have been loaded in browser, retry
m.remove_package_restriction(order_line) m.remove_package_restriction(order_line)
update = m.update_line(int(order_line['id']), data['update_type'], float(order_line['package_qty']), float(order_line['product_qty_package']), float(order_line['price_unit'])) update = m.update_line(int(order_line['id']),
data['update_type'],
float(order_line['package_qty']),
float(order_line['product_qty_package']),
float(order_line['price_unit']))
if not (update is True): if not (update is True):
errors.append(order_line['id']) errors.append(order_line['id'])
# If update succeded, and supplier shortage set, try to register the supplier shortage
if update is True and 'supplier_shortage' in order_line:
try:
answer_data['res_shortage'] = CagetteProduct.register_start_supplier_shortage(
order_line['product_id'][0],
order_line['partner_id'][0],
date.today().strftime("%Y-%m-%d"))
except Exception as e:
errors.append('error registering shortage on p'+order_line['id']+':'+str(e))
# Print etiquette with new price if update if successful # Print etiquette with new price if update if successful
if (print_labels is True) and (update is True) and (data['update_type'] == 'br_valid'): if (print_labels is True) and (update is True) and (data['update_type'] == 'br_valid') and order_line['new_shelf_price']:
try: try:
tools_url = settings.TOOLS_SERVER + '/products/label_print/' tools_url = settings.TOOLS_SERVER + '/products/label_print/'
tools_url += str(order_line['product_tmpl_id']) + '/' tools_url += str(order_line['product_tmpl_id']) + '/'
...@@ -232,7 +252,14 @@ def save_error_report(request): ...@@ -232,7 +252,14 @@ def save_error_report(request):
ws = wb.active ws = wb.active
ws.title = "Commande " + order['name'] ws.title = "Commande " + order['name']
ws.append(['type', 'nom_contenu', 'supplier_code', 'barcode', 'old_qty', 'product_qty', 'price_unit']) ws.append(['type',
'nom_contenu',
'supplier_code',
'barcode',
'old_qty',
'product_qty',
'price_unit',
'supplier_shortage'])
# If in group add group name # If in group add group name
if len(data['orders']) > 1 : if len(data['orders']) > 1 :
...@@ -247,7 +274,19 @@ def save_error_report(request): ...@@ -247,7 +274,19 @@ def save_error_report(request):
else: else:
supplier_code = 'X' supplier_code = 'X'
ws.append( ['produit', product['product_id'][1], supplier_code, str(product['barcode']), product['old_qty'], product['product_qty'], product['price_unit']] ) if 'supplier_shortage' in product:
supplier_shortage = '/!\ Rupture fournisseur'
else:
supplier_shortage = ''
ws.append( ['produit',
product['product_id'][1],
supplier_code,
str(product['barcode']),
product['old_qty'],
product['product_qty'],
product['price_unit'],
supplier_shortage] )
except: except:
# no updated products, do nothing # no updated products, do nothing
pass pass
...@@ -282,7 +321,8 @@ def save_error_report(request): ...@@ -282,7 +321,8 @@ def save_error_report(request):
'barcode' : row[3], 'barcode' : row[3],
'old_qty' : row[4], 'old_qty' : row[4],
'product_qty' : row[5], 'product_qty' : row[5],
'price_unit' : row[6] 'price_unit' : row[6],
'supplier_shortage' : row[7]
} }
elif row[0] == 'group': elif row[0] == 'group':
group_name = row[1] # group's orders name group_name = row[1] # group's orders name
...@@ -311,12 +351,18 @@ def save_error_report(request): ...@@ -311,12 +351,18 @@ def save_error_report(request):
else: else:
supplier_code = 'X' supplier_code = 'X'
if 'supplier_shortage' in product:
supplier_shortage = '/!\ Rupture fournisseur'
else:
supplier_shortage = ''
item = { item = {
'product_id': product['product_id'][1], 'product_id': product['product_id'][1],
'product_supplier_code': supplier_code, 'product_supplier_code': supplier_code,
'product_barcode': product['barcode'], 'product_barcode': product['barcode'],
'old_price_unit': float(product['old_price_unit']), 'old_price_unit': float(product['old_price_unit']),
'price_unit': float(product['price_unit']) 'price_unit': float(product['price_unit']),
'supplier_shortage': supplier_shortage
} }
# If the product was also modified in step 1 # If the product was also modified in step 1
...@@ -326,6 +372,10 @@ def save_error_report(request): ...@@ -326,6 +372,10 @@ def save_error_report(request):
item['expected_amount'] = item['old_qty']*item['old_price_unit'] item['expected_amount'] = item['old_qty']*item['old_price_unit']
item['error_line'] = (item['old_qty'] - item['product_qty'])*item['price_unit'] item['error_line'] = (item['old_qty'] - item['product_qty'])*item['price_unit']
# If product was set on supplier shortage in step 1 and not in step 2
if item['supplier_shortage'] == '' and data_qties[item['product_id']]['supplier_shortage'] != '':
item['supplier_shortage'] = data_qties[item['product_id']]['supplier_shortage']
data_qties.pop(item['product_id']) data_qties.pop(item['product_id'])
else: else:
item['old_qty'] = float(product['product_qty']) item['old_qty'] = float(product['product_qty'])
...@@ -354,7 +404,8 @@ def save_error_report(request): ...@@ -354,7 +404,8 @@ def save_error_report(request):
'old_price_unit': float(product['price_unit']), 'old_price_unit': float(product['price_unit']),
'price_unit': '', 'price_unit': '',
'expected_amount':float(product['old_qty'])*float(product['price_unit']), 'expected_amount':float(product['old_qty'])*float(product['price_unit']),
'error_line': (float(product['old_qty'])-float(product['product_qty']))*float(product['price_unit']) 'error_line': (float(product['old_qty'])-float(product['product_qty']))*float(product['price_unit']),
'supplier_shortage': product['supplier_shortage']
} }
error_total += item['error_line'] error_total += item['error_line']
...@@ -389,13 +440,30 @@ def save_error_report(request): ...@@ -389,13 +440,30 @@ def save_error_report(request):
ws.append( ['Montant total attendu (TTC) : ', str(round(order['amount_total'],2)) + ' €'] ) ws.append( ['Montant total attendu (TTC) : ', str(round(order['amount_total'],2)) + ' €'] )
ws.append( [] ) ws.append( [] )
ws.append( ['Nom produit', 'Code Four.', 'Numéro de Code Barre', 'Qté commande', 'Qté réception', 'Prix unit. initial', 'Prix unit. MAJ', 'Prix total attendu', "Montant erreur livraison (basé sur les différences de prix: (nouveau_prix-ancien_prix)*nouvelle_qté)"] ) ws.append( ['Nom produit',
'Code Four.',
'Numéro de Code Barre',
'Qté commande',
'Qté réception',
'Prix unit. initial',
'Prix unit. MAJ',
'Prix total attendu',
"Montant erreur livraison (basé sur les différences de prix: (nouveau_prix-ancien_prix)*nouvelle_qté)"] )
if len(data_full) == 0: if len(data_full) == 0:
ws.append( ['- Aucune modification -'] ) ws.append( ['- Aucune modification -'] )
else: else:
for product in data_full: for product in data_full:
ws.append( [product['product_id'], product['product_supplier_code'], str(product['product_barcode']), product['old_qty'], product['product_qty'], product['old_price_unit'], product['price_unit'], round(product['expected_amount'], 2), round(product['error_line'], 2)] ) ws.append( [product['product_id'],
product['product_supplier_code'],
str(product['product_barcode']),
product['old_qty'],
product['product_qty'],
product['old_price_unit'],
product['price_unit'],
round(product['expected_amount'], 2),
round(product['error_line'], 2),
product['supplier_shortage']] )
ws.append( [] ) ws.append( [] )
ws.append( ['Montant total de l\'erreur :', '', '', '', '', '', '', '', round(error_total, 2)] ) ws.append( ['Montant total de l\'erreur :', '', '', '', '', '', '', '', round(error_total, 2)] )
......
...@@ -8,6 +8,31 @@ from datetime import datetime ...@@ -8,6 +8,31 @@ from datetime import datetime
class CagetteStock(models.Model): class CagetteStock(models.Model):
@staticmethod @staticmethod
def get_movements(movement_type, date_from, date_to):
o_api = OdooAPI()
errors = []
if movement_type == 'losses':
location_dest_id = settings.LOSSES_LOC_ID
elif movement_type == 'meals':
location_dest_id = settings.MEALS_LOC_ID
elif movement_type == 'autoconso':
location_dest_id = settings.AUTOCONSO_LOC_ID
else:
errors.append('Type de mouvement incorrect')
return {'errors': errors}
f = ['name', 'date_done', 'inventory_value']
c = [['location_dest_id', "=", location_dest_id],
['date_done', ">=", date_from],
['date_done', "<=", date_to],
['state', "=", 'done']]
res = o_api.search_read('stock.picking', c, f)
return res
@staticmethod
def do_stock_movement(stock_movement_data): def do_stock_movement(stock_movement_data):
"""Do a stock movement : """ """Do a stock movement : """
......
.main, .barcode_search_area {
margin-top: 40px;
}
#sm_barcode_selector {
border: 1px solid #555;
border-radius:10px;
}
.stock_edit_input {
max-width: 60px;
}
#movement_validation_button {
margin-top: 20px;
}
.notifyjs-test1-base {
background: #5CB85C !important;
}
.confirmation_checking_msgs {
padding-bottom: 10px;
}
.remove_row_icon {
font-size: 1.4em;
color: #d9534f;
cursor: pointer;
}
.blink_me {
animation: blinker 1s ease-in-out;
}
@keyframes blinker {
15% { background-color: #218D9B ; } /* F0AD4E*/
}
#back_to_movement_selection {
position:absolute;
left:0;
margin: 5px;
}
.movement_type_buttons {
margin-top: 60px;
}
.movement_type_button {
height: 2.2em;
width: 30%;
border-radius: 3px;
margin: 10px;
font-size: 1.3em;
}
.movement_type_button_icons {
float: right;
margin: 2px;
}
.page_body {
position: relative;
}
.page_content {
position: absolute;
top: 0;
left: 0;
right: 0;
}
.select_movement_area {
margin-top: 25px;
}
.select_movement_element {
display: inline-block;
margin-right: 10px;
}
.main {
margin-top: 25px;
}
.table_area {
margin: auto;
width: 50%;
padding: 10px;
}
.select_movement_input {
border-radius:5px;
}
.btn_export_movements {
margin-top: 10px;
}
/* eslint consistent-this: ["warn", "that"] */
/*
* Récupération de protuits par scan et saisie de quantités pour créer des mouvements de stock.
*
* Mouvements disponibles :
* - Pertes
* - Repas salariés
* - Autoconsommation
* - Corrections de stocks
*/
var products = [],
products_table = null,
confirmation_table = null,
barcodes = null,
movement_type = null;
function reset_focus() {
// Wait a little bit to reset, or ignored after paste
setTimeout(function() {
$('#sm_barcode_selector').val('');
$('#icon_product_not_found').hide();
$('#sm_barcode_selector').focus();
}, 100);
}
/*
* Remove a row from datatable
*/
function remove_row(row) {
var data = products_table.row($(row).parents('tr')).data();
for (i in products) {
if (products[i].id == data.id) {
products.splice(i, 1);
break;
}
}
products_table
.row($(row).parents('tr'))
.remove()
.draw();
update_total_value();
}
/*
* Make a row blink
*/
function blink_row(rowNode) {
$(rowNode).addClass('blink_me');
rowNode.addEventListener('animationend', onAnimationEnd);
rowNode.addEventListener('webkitAnimationEnd', onAnimationEnd);
function onAnimationEnd() {
rowNode.classList.remove('blink_me');
}
}
/*
* Init or reinit the products datatable with a new set of products
*/
function init_datatable() {
// Empty datatable if already exists
if (products_table) {
products_table.destroy();
}
// Only init datatable if array of products not empty
if (products.length > 0) {
products_table = $('#products_table').DataTable({
data: products,
columns:[
{data:"id", title: "id", visible: false},
{
data:"name",
title:"Produit",
width: "50%",
render: function (data, type, full) {
// Add tooltip with barcode over product name
let display_barcode = "Aucun";
if ('barcode' in full) {
display_barcode = full.barcode;
}
return '<div class="tooltip">' + data
+ ' <span class="tooltiptext tt_twolines">Code barre : '
+ display_barcode + '</span> </div>';
}
},
{
data:"uom.name",
title: "Unité de vente",
// className:"dt-body-center",
orderable: false
},
{
title: "Quantité",
// className:"dt-body-center",
orderable: false,
render: function (data, type, full) {
var value = ('qty' in full) ? full.qty : '';
return '<input type="number" class="stock_edit_input" value="' + value + '">';
// To force decimal to be . , add lang="en-150" to input tag
// + ' <button type="button" class="stock_edit_button btn--primary"><i class="fas fa-lg fa-check"></i></button>'
}
},
{
data:"standard_price",
title: "Valeur HT",
// className:"dt-body-center",
render: function (data, type, full) {
if ('qty' in full && full.qty != null) {
var value = parseFloat(full.qty*data).toFixed(2);
return value + '€';
} else
return '';
}
},
{
data:"list_price",
title: "Prix Vente TTC",
// className:"dt-body-center",
render: function (data, type, full) {
if ('qty' in full && full.qty != null) {
var value = parseFloat(full.qty*data).toFixed(2);
return value + '€';
} else
return '';
}
},
{
title: "",
orderable: false,
className:"dt-body-center",
width: "1%",
render: function () {
return '<span class="remove_row_icon">' +
'<i class="fas fa-times"></i>' +
'</span>';
}
}
],
order: [
[
0,
"asc"
]
],
paging: false,
dom: 'lrtip', // Remove the search input from that table
language: {url : '/static/js/datatables/french.json'}
});
// Listener on inputs
$('#products_table tbody').on('change', '.stock_edit_input', function () {
let qty = $(this).val();
let row = products_table.row($(this).parents('tr'));
let data = row.data();
let validated_data = qty_validation(qty, data.uom.id);
if (validated_data >= 0) {
data.qty = validated_data;
row.remove().draw();
products_table.row.add(data).draw();
} else {
data.qty = null;
row.remove().draw();
products_table.row.add(data).draw();
if (validated_data == -2) {
$.notify("Ce produit est vendu à l'unité : la valeur doit être un nombre entier !", {
globalPosition:"top right",
className: "error"
});
} else if (validated_data == -1 || validated_data == -3) {
$.notify("Valeur invalide.", {
globalPosition:"top right",
className: "error"
});
}
}
update_total_value();
});
// Remove row
products_table.on('click', '.remove_row_icon', function () {
var that = this;
openModal($('#modal_confirm_delete_line').html(), function() {
remove_row(that);
}, 'Confirmer');
});
// Show validation button
$('.footer').show();
}
}
function init_confirmation_datatable() {
if (confirmation_table) {
confirmation_table.destroy();
}
confirmation_table = $('#confirmation_table').DataTable({
data: products,
autoWidth: false,
columns:[
{data:"id", title: "id", visible: false},
{
data:"name",
title:"Produit",
width: "50%"
},
{
data:"qty",
title: "Quantité",
className:"dt-body-center"
},
{
data:"uom.name",
title: "Unité de vente",
className:"dt-body-center"
},
{
data:"standard_price",
title: "Valeur",
className:"dt-body-center",
render: function (data, type, full) {
var value = parseFloat(full.qty*data).toFixed(2);
return value + '€';
}
}
],
order: [
[
0,
"asc"
]
],
paging: false,
dom: 'lrtp', // Remove the search input from that table
language: {url : '/static/js/datatables/french.json'}
});
}
function without_consent_update_product(p) {
let undo_option = true;
update_existing_product(p, undo_option);
}
function get_stored_product_with_bc(barcode) {
/* return product in products variable which have the same barcode */
let product = null;
for (let p of products) {
if (p.barcode == barcode) {
product = p;
}
}
return product;
}
/*
* Fetch a product when barcode is read
* On dynamic_search mode, we try to read barcode as it is written,
* so the process is slightly different
*/
function fetch_product_from_bc(barcode, dynamic_search = false) {
//console.log(barcode)
if (barcode == '') {
reset_focus();
return -1;
}
let p = barcodes.get_corresponding_odoo_product(barcode);
if (p == null) {
$('#icon_product_not_found').show();
alert("Le code-barre " + barcode + " ne correspond à aucun article connu.");
// if (!dynamic_search) {
// reset_focus();
// }
return -1;
}
/*
p.data[barcodes['keys']['uom_id']] is the Odoo uom database id
barcodes['uoms'] is an associative array of uoms (with id as key)
*/
let product = {
'id': p.data[barcodes['keys']['id']],
'barcode': p.barcode,
'name': p.data[barcodes['keys']['name']],
'uom': barcodes['uoms'][p.data[barcodes['keys']['uom_id']]],
'standard_price' : p.data[barcodes['keys']['standard_price']], // cost
'list_price': p.data[barcodes['keys']['list_price']] // public price
};
product['uom']['id'] = p.data[barcodes['keys']['uom_id']];
product['value'] = parseFloat(p.value) || 1;
product['rule'] = p.rule;
p_existing = get_stored_product_with_bc(p.barcode);
if (p_existing !== null) {
product.qty = p_existing.qty;
without_consent_update_product(product);
return 0;
} else {
add_product(product);
reset_focus();
return 0;
}
}
/*
* Add a product to the datatable
*/
var add_product = function(product) {
try {
// Add to list
product.qty = 1;
if (typeof product.value == "number" || (product.value.length > 0 && !isNaN(product.value))) {
//encoded value will be translated in quantity
if (product.rule == "FF_price_to_weight") {
product.qty = get_quantity_eq_to_franc_price(product);
} else {
product.qty = parseFloat(product.value);
}
}
products.push(product);
if (products_table == null) {
// create table, show panel
init_datatable();
} else {
// Add row to table
var rowNode = products_table.row.add(product).draw(false)
.node();
// Handle blinking effect for new row
blink_row(rowNode);
}
update_total_value();
$('.footer').show(); // if is a second or more access, footer is hidden (init_datatable is not fired)
} catch (e) {
err = {msg: e.name + ' : ' + e.message, ctx: 'add_product'};
console.error(err);
report_JS_error(err, 'stock_movement');
}
};
var update_in_products = function(product) {
//update product in products , after qty has been changed
let i = 0,
p_index = null;
for (let p of products) {
if (p.barcode == product.barcode) {
p_index = i;
}
i++;
}
if (p_index !== null) products[p_index] = product;
else console.log("Le produit n'a pas pu être trouvé dans la variable products !");
};
var get_quantity_eq_to_franc_price = function(product) {
let value = 0;
try {
let price = parseFloat(product.value / 6.55957);
value = parseFloat(price / product.list_price).toFixed(3);
} catch (error) {
console.log(error);
}
return value;
};
/*
* Update a line in the table: update quantity
*/
var update_existing_product = function(product, undo_option = false) {
// By default added qty is 1 unit
let added_qty = 1;
let op = "augmentée";
let notify_options = {
globalPosition:"top right",
className: "info",
clickToHide: false
};
// type product qty value
if (product.rule == 'weight' || product.rule == 'FF_price_to_weight' && product.value) {
// Quantities are kg or price
product.qty = parseFloat(product.qty) || 0;
if (product.rule == 'weight') {
added_qty = parseFloat(product.value);
} else {
if (product.value < 0) added_qty = parseFloat(product.value); // value is already a qty
else added_qty = parseFloat(get_quantity_eq_to_franc_price(product));
}
} else {
//Quantity is by "defaut" considered as to be in "unit"
product.qty = parseInt(product.qty, 10);
if (product.rule == "" && product.value) {
added_qty = product.value;
}
}
product.qty += added_qty;
/* Surprisingly, this assignment by addition (0 + value)
always correctly "typing" the value
whereas "product.qty = added_qty"
is sometimes typed as "string" !!
*/
// always set to empty to avoid next operation on same product to be misprocessed
product.value = "";
// Find index of row which match product id in the first column
var indexes = products_table.rows().eq(0)
.filter(function (rowIdx) {
return products_table.cell(rowIdx, 0).data() === product.id ? true : false;
});
// Loop through them (only one match)
products_table.rows(indexes).every(function () {
this.remove().draw();
var rowNode = products_table.row.add(product).draw()
.node();
// Blink updated row
blink_row(rowNode);
return 0;
});
if (added_qty < 0) op = "diminuée";
let msg = "La quantité a été " + op + " de " + Math.abs(added_qty) + ".";
if (undo_option == true) {
notify_options.clickToHide = true;
notify_options.autoHide = false;
// notify_options.autoHideDelay = 10000;
notify_options.style = 'cancelable';
// msg = $('<span>').text(msg)
// .attr('data-barcode', product.barcode)
// .attr('data-addedqty', added_qty);
// msg = msg.html()
msg = '<span class="msg" data-barcode="' + product.barcode + '" data-added_qty="' + added_qty + '">'
+ "<b>" + product.name + "</b><br/>" + msg
+ '</span>';
}
$.notify(msg, notify_options);
update_in_products(product);
update_total_value();
reset_focus();
};
/*
* Validate qty
* Returns qty or error code
* Error codes :
* >= 0 = ok
* -1 = empty
* -2 = invalid integer
* -3 = invalid value
*
*/
function qty_validation(qty, uom_id) {
if (qty == null || qty == '') {
return -1;
}
if (uom_id == 1) {
if (qty/parseInt(qty) != 1 && qty != 0)
return -2;
qty = parseInt(qty); // product by unit
} else {
qty = parseFloat(qty).toFixed(2);
}
if (isNaN(qty))
return -3;
return qty;
}
// Update the total value from values in datatable
function update_total_value() {
var total = 0;
if (products_table) {
products_table.rows().every(function () {
var data = this.data();
if ('qty' in data) {
total += data.qty*data.standard_price;
}
return 0;
});
}
total = parseFloat(total).toFixed(2);
$('.total_value').text(total);
}
// Validation & open confirmation modal
function confirm_movement() {
// Create a temp variable to get all data from table
let tmp_products_data = [];
// Check again for errors
var error_in_table = false;
products_table.rows().every(function () {
let data = this.data();
let qty_value = $(this.node()).find('input')
.val();
qty_value = qty_validation(qty_value, data.uom.id);
if (qty_value < 0) {
error_in_table = true;
}
tmp_products_data.push(data);
return 0;
});
if (error_in_table) {
$.notify("Il y a une erreur dans le tableau ! (Tous les champs sont obligatoires)", {
globalPosition:"top right",
className: "error"
});
return -1;
}
// If no error, assign global 'products' variable with table data
products = tmp_products_data;
var modal_confirm_movement = $('#modal_confirm_movement');
// Set confirmation message
if (movement_type == 'losses') {
msg = 'Êtes-vous sûr de vouloir passer ces produits en <b>PERTES</b> ?';
} else if (movement_type == 'meals') {
msg = 'Êtes-vous sûr de vouloir passer ces produits en <b>Repas salarié</b> ?';
} else if (movement_type == 'autoconso') {
msg = 'Êtes-vous sûr de vouloir passer ces produits en <b>Autoconsommation</b> ?';
} else if (movement_type == 'stock_correction') {
msg = 'Êtes-vous sûr de vouloir corriger les stocks de ces produits ?';
}
modal_confirm_movement.find('#confirm_message_movtype').html(msg);
openModal(modal_confirm_movement.html(), function() {
do_stock_movement();
}, 'Confirmer', false);
init_confirmation_datatable();
// Check if prices displayed are correct
var ids = [];
for (p of products) {
ids.push(p.id);
}
$.ajax({
type: "POST",
url: "/products/get_products_stdprices",
dataType: "json",
traditional: true,
contentType: "application/json; charset=utf-8",
data: JSON.stringify(ids),
success: function(data) {
var price_changed = false;
for (p of products) {
for (verif_p of data.res) {
if (p.id == verif_p.id && p.standard_price != verif_p.standard_price) {
p.standard_price = verif_p.standard_price;
price_changed = true;
}
}
}
$('#confirmation_checking_price_msg').hide();
if (price_changed) {
$('#confirmation_price_changed').show();
init_confirmation_datatable();
update_total_value();
init_datatable();
}
},
error: function(data) {
// server error
err = {msg: "erreur serveur lors de la récupération des prix", ctx: 'check_prices'};
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error;
}
report_JS_error(err, 'stock');
console.error(err);
$.notify("Erreur lors de la récupération des prix des produits", {
globalPosition:"top right",
className: "error"
});
}
});
return 0;
}
// Proceed with stock movement, according to movement type selected
function do_stock_movement() {
if (is_time_to('do_stock_movement', 500)) {
openModal();
let movement_data = {
'movement_type': movement_type,
'products': products.map(obj => { // transmit only uom id (not all staff)
obj.uom_id = obj.uom.id;
delete obj.uom;
return obj;
})
};
openModal();
$.ajax({
type: "POST",
url: "/stock/do_movement",
dataType: "json",
traditional: true,
contentType: "application/json; charset=utf-8",
data: JSON.stringify(movement_data),
success: function() {
closeModal();
$.notify("Opération réussie !", {
globalPosition:"top right",
className: "success"
});
$('#back_to_movement_selection').trigger('click');
// Reset datatable, other values and focus
products = [];
products_table.clear().draw();
$('.footer').hide();
},
error: function(data) {
// server error
err = {msg: "erreur serveur lors de l'enregistrement du mouvement de stock", ctx: 'do_stock_movement'};
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error;
}
report_JS_error(err, 'stock');
console.error(err);
closeModal();
reset_focus();
$.notify("Erreur lors de l'opération.", {
globalPosition:"top right",
className: "error"
});
}
});
}
}
// Load barcodes at page loading, then barcodes are stored locally
var get_barcodes = async function() {
if (barcodes == null) barcodes = await init_barcodes();
};
function init_notify_cancelable_style() {
try {
$.notify.addStyle('cancelable', {
html:
"<div class='info'>" +
"<div class='clearfix'>" +
"<div data-notify-html/>" +
"<div class='buttons'>" +
"<button class='no btn--danger'>Annuler</button>" +
"<button class='yes btn--info'>Fermer</button>" +
"</div>" +
"</div>" +
"</div>"
});
} catch (e) {
console.log(e);
}
}
$(document).ready(function() {
var barcode_input = $('#sm_barcode_selector');
$.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } });
// Load barcodes
get_barcodes();
// Set focus on input for barcode reader
reset_focus();
$('#movement_validation_button').on('click', function () {
confirm_movement();
});
$(document).pos();
$(document).on('scan.pos.barcode', function(event) {
// Search for barcode only if movement type is selected
if (movement_type != null) {
//access `event.code` - barcode data
var barcode = event.code;
if (barcode.length >=13) {
barcode = barcode.substring(barcode.length-13);
} else if (barcode.length == 12 && barcode.indexOf('0') !== 0) {
// User may use a scanner which remove leading 0
barcode = '0' + barcode;
} else {
//manually submitted after correction
barcode = barcode_input.val();
}
fetch_product_from_bc(barcode, true);
}
});
barcode_input.on('paste', function(e) {
if (movement_type != null) {
let barcode_candidate = e.originalEvent.clipboardData.getData('text')
.replace(/[^0-9]/g, '');
fetch_product_from_bc(barcode_candidate);
}
});
// Change screen with animation when a movement type is selected
$('.movement_type_button').on('click', function() {
// Specific behavior for each movement type selection
if (this.id == 'losses_type_button') {
$('#title_movement_type').text('Saisie de Pertes');
movement_type = 'losses';
} else if (this.id == 'autoconso_type_button') {
$('#title_movement_type').text('Saisie d\'Autoconsommation');
movement_type = 'autoconso';
} else if (this.id == 'meals_type_button') {
$('#title_movement_type').text('Saisie de Repas salariés');
movement_type = 'meals';
}
var oldBox = $("#content_movement_type_selection");
var newBox = $("#content_main");
// Get full width
var outerWidth = oldBox.outerWidth(true);
// Display the new box and place it on the right of the screen
newBox.css({ "left": outerWidth + "px", "right": -outerWidth + "px", "display": "" });
// Make the old content slide to the left
oldBox.animate({ "left": -outerWidth + "px", "right": outerWidth + "px" }, 1000, function() {
// Hide old content after animation
oldBox.css({ "left": "", "right": "", "display": "none" });
});
// Slide new box to regular place
newBox.animate({ "left": "", "right": "" }, 1000);
});
// Back to movement type selection
$('#back_to_movement_selection').on('click', function() {
movement_type = null;
var oldBox = $("#content_main");
var newBox = $("#content_movement_type_selection");
// Get full width
var outerWidth = oldBox.outerWidth(true);
// Display the new box and place it on the right of the screen
newBox.css({ "left": -outerWidth + "px", "right": outerWidth + "px", "display": "" });
// Make the old content slide to the left
oldBox.animate({ "left": outerWidth + "px", "right": -outerWidth + "px" }, 1000, function() {
// Hide old content after animation
oldBox.css({ "left": "", "right": "", "display": "none" });
});
// Slide new box to regular place
newBox.animate({ "left": "", "right": "" }, 1000);
});
init_notify_cancelable_style();
$(document).on('click', '.notifyjs-cancelable-base .no', function() {
//programmatically trigger propogating hide event
let msg = $(this).closest('.notifyjs-cancelable-base')
.find('span.msg');
if (msg.length > 0) {
let bc = msg.data('barcode');
let added_qty = msg.data('added_qty') || 1;
let product = get_stored_product_with_bc(bc);
if (product !== null) {
product.value = - added_qty;
update_existing_product(product);
} else {
alert("Le produit n'a pas été retrouvé dans la mémoire de travail.");
}
} else {
alert("Les informations n'ont pas pu être récupérées.");
}
//$(this).trigger('notify-hide');
});
});
/*
* Extract and display a stock movement type between two dates
*
*/
// var dateFormat = "mm/dd/yy",
var dateFormat = "yy-mm-dd",
from_datepicker = null,
to_datepicker = null,
movements_table = null;
// Return a date from a string if valid, else return null
function getDate(element) {
var date;
try {
date = $.datepicker.parseDate(dateFormat, element);
} catch (error) {
date = null;
}
return date;
}
// Enable validation button if all fields are valid
function enable_validation() {
if ($('#movement_type_selector').val() != '' &&
getDate(from_datepicker.val()) &&
getDate(to_datepicker.val())) {
$('#movement_selection_button').prop('disabled', false);
} else {
$('#movement_selection_button').prop('disabled', true);
}
}
function display_movements(movements) {
total_value = 0;
for (move of movements) {
total_value += move.inventory_value;
}
$('#count_movements').text(movements.length);
$('#total_value_movements').text(parseFloat(total_value).toFixed(2));
// Empty datatable if already exists
if (movements_table) {
movements_table.destroy();
}
movements_table = $('#movements_table').DataTable({
data: movements,
columns:[
{
data:"name",
title:"Mouvement",
width: "70%"
},
{
data:"inventory_value",
title: "Valeur",
className:"dt-body-center",
render: function (data, type, full) {
return parseFloat(data).toFixed(2) + ' €';
}
},
{
data:"date_done",
title:"Trier par Date",
className:"dt-body-center",
render: function (data, type, full) {
return '<i>' + data + '</i>';
}
}
],
order: [
[
2,
"desc"
]
],
buttons: [{ extend: 'csvHtml5', text: 'Export en CSV', className: 'btn--primary btn_export_movements' }],
dom: '<lr<t>ip><"clear"><B>',
iDisplayLength: 10,
language: {url : '/static/js/datatables/french.json'}
});
$('.main').show();
}
function get_movements() {
openModal();
var url = "/stock/get_movements";
url += '?movement_type=' + encodeURIComponent($('#movement_type_selector').val());
url += '&from=' + encodeURIComponent(from_datepicker.val());
url += '&to=' + encodeURIComponent(to_datepicker.val());
$.ajax({
type: 'GET',
url: url,
dataType:"json",
traditional: true,
contentType: "application/json; charset=utf-8",
success: function(data) {
display_movements(data.res);
closeModal();
},
error: function(data) {
err = {msg: "erreur serveur lors de la sélection des mouvements de stock", ctx: 'get_movements'};
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error;
}
report_JS_error(err, 'stock');
closeModal();
alert('Erreur lors de la récupération, réessayez plus tard');
}
});
}
$(document).ready(function() {
$.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } });
// Set datepicker
$.datepicker.regional['fr'] = {
monthNames: [
'Janvier',
'Fevrier',
'Mars',
'Avril',
'Mai',
'Juin',
'Juillet',
'Aout',
'Septembre',
'Octobre',
'Novembre',
'Decembre'
],
dayNamesMin: [
'Di',
'Lu',
'Ma',
'Me',
'Je',
'Ve',
'Sa'
],
dateFormat: dateFormat,
firstDay: 1,
minDate: 1,
maxDate: '+12M +0D'
};
$.datepicker.setDefaults($.datepicker.regional['fr']);
from_datepicker = $("#from")
.datepicker({
defaultDate: "-1d",
changeMonth: true,
changeYear: true,
yearRange: "-03:+00",
minDate: new Date(2007, 1 - 1, 1),
maxDate: new Date()
})
.on("change", function() {
to_datepicker.datepicker("option", "minDate", getDate(this.value));
});
to_datepicker = $("#to")
.datepicker({
defaultDate: "-1d",
changeMonth: true,
changeYear: true,
yearRange: "-03:+00",
minDate: new Date(2007, 1 - 1, 1),
maxDate: new Date()
})
.on("change", function() {
from_datepicker.datepicker("option", "maxDate", getDate(this.value));
});
$('.select_movement_element').change(function() {
enable_validation();
});
$('#movement_selection_button').click(function() {
get_movements();
});
});
...@@ -4,8 +4,10 @@ from django.conf.urls import url ...@@ -4,8 +4,10 @@ from django.conf.urls import url
from . import views from . import views
urlpatterns = [ urlpatterns = [
url(r'^movements', views.movements_page), url(r'^movements$', views.movements_page),
url(r'^do_movement', views.do_movement), url(r'^do_movement', views.do_movement),
url(r'^movements_view$', views.movements_view),
url(r'^get_movements$', views.get_movements),
### NOT IN USE: ### ### NOT IN USE: ###
......
...@@ -18,6 +18,15 @@ def movements_page(request): ...@@ -18,6 +18,15 @@ def movements_page(request):
return HttpResponse(template.render(context, request)) return HttpResponse(template.render(context, request))
def movements_view(request):
"""Page d'extraction des mouvements de stocks"""
context = {
'title': 'Mouvements de stock'
}
template = loader.get_template('stock/stock_movements_view.html')
return HttpResponse(template.render(context, request))
def do_movement(request): def do_movement(request):
"""Do the stock movement: losses, self conso or stock correction""" """Do the stock movement: losses, self conso or stock correction"""
res = {} res = {}
...@@ -46,6 +55,20 @@ def do_movement(request): ...@@ -46,6 +55,20 @@ def do_movement(request):
else: else:
return JsonResponse({'res': res}) return JsonResponse({'res': res})
def get_movements(request):
res = {}
movement_type = request.GET.get('movement_type', '')
date_from = request.GET.get('from', '')
date_to = request.GET.get('to', '')
res = CagetteStock.get_movements(movement_type, date_from, date_to)
if 'errors' in res and res['errors']:
return JsonResponse(res, status=500)
else:
return JsonResponse({'res': res})
### NOT IN USE ### ### NOT IN USE ###
# ??? a voir si on garde les heur d'ouverture de la cagette ici # ??? a voir si on garde les heur d'ouverture de la cagette ici
......
...@@ -92,7 +92,6 @@ ...@@ -92,7 +92,6 @@
<span id="valid_all"></span> <span id="valid_all"></span>
<span id="all_left_is_good"> <span id="all_left_is_good">
<button class='btn--danger half_width_button' onclick="openModal($('#templates #modal_all_left_is_good').html(), confirm_all_left_is_good, 'Confirmer', false);">Tout le reste est bon</button> <button class='btn--danger half_width_button' onclick="openModal($('#templates #modal_all_left_is_good').html(), confirm_all_left_is_good, 'Confirmer', false);">Tout le reste est bon</button>
</span> </span>
</div> </div>
{% else %} {% else %}
...@@ -153,6 +152,14 @@ ...@@ -153,6 +152,14 @@
<p>Êtez-vous sûr ?</p> <p>Êtez-vous sûr ?</p>
<hr /> <hr />
</div> </div>
<div id="modal_set_supplier_shortage">
<h3>Attention !</h3>
<p>Vous vous apprêtez indiquer que le produit<b><span class="supplier_shortage_product"></span></b>
est en rupture chez le fournisseur<b><span class="supplier_shortage_supplier"></span></b>.</p>
<p>La quantité du produit sera mise à 0 si vous en êtes à l'étape du comptage de produits.</p>
<p>Êtez-vous sûr ?</p>
<hr />
</div>
<div id="modal_unprocessable_porducts"> <div id="modal_unprocessable_porducts">
<h3>Traitement impossible</h3> <h3>Traitement impossible</h3>
<p> <p>
......
{% extends "base.html" %}
{% load static %}
{% block additionnal_css %}
<link rel="stylesheet" href="{% static 'css/datatables/jquery.dataTables.css' %}">
<link rel="stylesheet" href="{% static 'css/stock_movements.css' %}">
{% endblock %}
{% block additionnal_scripts %}
<script type="text/javascript" src="{% static 'js/datatables/jquery.dataTables.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/dataTables.plugins.js' %}"></script>
<script type="text/javascript" src="{% static 'js/jquery.pos.js' %}"></script>
<script type="text/javascript" src="{% static 'js/stock_movements.js' %}?v="></script>
<script type="text/javascript" src="{% static 'js/notify.min.js' %}?v="></script>
{% endblock %}
{% block content %}
<div class="page_body">
<div id="content_movement_type_selection" class="page_content">
<div class="header txtcenter">
<h1>Saisie de mouvements de stock</h1>
<p><i>Choisissez le type de mouvement de stock que vous voulez enregistrer</i></p>
</div>
<div class="movement_type_buttons txtcenter">
<button type="button" class="btn--primary movement_type_button" id="losses_type_button">
Pertes
<span class="movement_type_button_icons"><i class="fas fa-arrow-right"></i></span>
</button><br>
<button type="button" class="btn--primary movement_type_button" id="autoconso_type_button">
Autoconsommation
<span class="movement_type_button_icons"><i class="fas fa-arrow-right"></i></span>
</button><br>
<button type="button" class="btn--primary movement_type_button" id="meals_type_button">
Repas salariés
<span class="movement_type_button_icons"><i class="fas fa-arrow-right"></i></span>
</button>
</div>
</div>
<div id="content_main" class="page_content" style="display:none;">
<div class="header txtcenter">
<div id="back_to_movement_selection">
<button type="button" class="btn--danger"><i class="fas fa-arrow-left"></i>&nbsp; Retour</button>
</div>
<h1 id="title_movement_type"></h1>
<p style="margin-bottom:0;"><i>Passez un produit sous la douchette ! Si rien ne se passe, cliquez sur le champ ci-dessous puis ré-essayez.</i></p>
<p><i>Vous pouvez également taper le codebarre, puis appuyer sur la touche Entrée.</i></p>
</div>
<div class="barcode_search_area txtcenter">
<input type="text" id="sm_barcode_selector" placeholder="Codebarre">
<div id="icon_product_not_found" class="tooltip" style="display:none;">
<span class="tooltiptext tooltip-lg tt_twolines">Aucun produit trouvé avec ce code-barre.</span>
<span style="color: #d9534f;"> <i class="fas fa-ban"></i>
</div>
</div>
<div class="main">
<table id="products_table" class="display" cellspacing="0" ></table>
</div>
<div class="footer txtcenter" style="display:none">
<h4>Total (H.T.) : <span class="total_value">0</span></h4>
<button type="button" class="btn--primary" id="movement_validation_button" name="button">Validation du total des produits saisis</button>
</div>
<input type="hidden" name="barcode"/>
</div>
<div id="templates" style="display:none;">
<div id="modal_confirm_movement">
<h4 id="confirm_message_movtype"></h4>
<br>
<div class="confirmation_modal_content">
<div class="confirmation_checking_msgs">
<p id="confirmation_checking_price_msg"><i class="fas fa-spinner fa-spin"></i> Vérification des prix...</p>
<p id="confirmation_price_changed" style="display:none;"><i class="fas fa-exclamation-triangle"></i> Des prix ont changé, veuillez recharger la page après cette opération.</p>
</div>
<table id="confirmation_table" class="display" cellspacing="0" width="100%"></table>
</div>
<br>
<h5>Total (H.T.) : <span class="total_value">0</span></h5>
<hr />
</div>
<div id="modal_confirm_delete_line">
<p>Vous vous apprêtez à suprimer une ligne du tableau, <b>êtes-vous sûr ?</b></p>
</div>
</div>
</div>
<script src="{% static "js/all_common.js" %}?v="></script>
<script src='{% static "js/barcodes.js" %}?v='></script>
{% endblock %}
{% extends "base.html" %}
{% load static %}
{% block additionnal_css %}
<link rel="stylesheet" href="{% static 'css/datatables/jquery.dataTables.css' %}">
<link rel="stylesheet" href="{% static 'css/stock_movements_view.css' %}">
<link rel="stylesheet" href="{% static 'jquery-ui-1.12.1/jquery-ui.min.css' %}">
{% endblock %}
{% block additionnal_scripts %}
<script type="text/javascript" src="{% static 'js/datatables/jquery.dataTables.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/dataTables.plugins.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/datatables.buttons.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables/buttons.html5.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/stock_movements_view.js' %}?v="></script>
<script type="text/javascript" src="{% static 'jquery-ui-1.12.1/jquery-ui.min.js' %}?v="></script>
{% endblock %}
{% block content %}
<div class="page_body">
<div id="content_main" class="page_content">
<div class="header txtcenter">
<h1>Extraction de mouvements de stock</h1>
</div>
<div class="select_movement_area txtcenter">
<select class="select_movement_element select_movement_input" id="movement_type_selector" name="">
<option value="">-- Choisissez un type de mouvement --</option>
<option value="losses">Pertes</option>
<option value="meals">Repas salariés</option>
<option value="autoconso">Autoconsomation</option>
</select>
<p class="select_movement_element">De : <input type="text" id="from" class="select_movement_input"></p>
<p class="select_movement_element">À : <input type="text" id="to" class="select_movement_input"></p>
<button type="button" class="btn--primary" id="movement_selection_button" disabled>C'est parti !</button>
</div>
<div class="main" style="display:none;">
<div class="txtcenter">
<h3>Total de <span id="count_movements"></span> mouvements d'une valeur de <span id="total_value_movements"></span></h3>
</div>
<div class="table_area">
<table id="movements_table" class="display" cellspacing="0" width="100%"></table>
</div>
</div>
</div>
<div id="templates" style="display:none;">
</div>
</div>
<script src="{% static "js/all_common.js" %}?v="></script>
{% endblock %}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment