diff --git a/static/images/add.png b/static/images/add.png new file mode 100644 index 0000000..0613def Binary files /dev/null and b/static/images/add.png differ diff --git a/static/images/edit.png b/static/images/edit.png index 49e0b4f..f52c063 100644 Binary files a/static/images/edit.png and b/static/images/edit.png differ diff --git a/static/images/trash.png b/static/images/trash.png index ed387d9..5677370 100644 Binary files a/static/images/trash.png and b/static/images/trash.png differ diff --git a/static/scripts/tetalab.js b/static/scripts/tetalab.js index 2f1a42d..a1855d7 100644 --- a/static/scripts/tetalab.js +++ b/static/scripts/tetalab.js @@ -9,9 +9,7 @@ var base_border = "#555555"; * GLOBAL * **************************************************************************************/ -/////////////////////////////////////////// // Cookies -/////////////////////////////////////////// function setcookie(cname, cvalue, exdays) { var d = new Date(); d.setTime(d.getTime() + (exdays*24*60*60*1000)); @@ -25,10 +23,7 @@ function getcookie(cname) { if (parts.length == 2) return parts.pop().split(";").shift(); } -/////////////////////////////////////////// // Eye candies -/////////////////////////////////////////// - function invalid_input(obj) { obj.style.backgroundColor = light_red; setTimeout( function() { @@ -106,8 +101,7 @@ function update_componants() { xhttp.send(); } -// Result ordering - +// Result ordering and navigation function update_componants_by_reference(order) { setcookie('c_order', order, 30); setcookie('c_sort', 'reference', 30); @@ -148,7 +142,6 @@ function c_next_page(nexthop) { // Search componants - function search_componants_by_reference(obj) { setcookie('c_reference', obj.value, 30); update_componants(); @@ -170,14 +163,12 @@ function search_componants_by_provider(obj) { } // Delete componant - function confirm_componant_delete() { var msg="La suppression est définitive \net n'est pas autorisée si le \ncomposant fait partie d'un Kit.\n\nConfirmer ?"; return confirm(msg); } // New componant - function new_componant() { var err = false; var obj = {}; @@ -231,7 +222,6 @@ function create_componant() { } // Update componant - function update_componant(obj, componant_id, type) { if (type == 'numeric') { if (isNaN(obj.value)) { @@ -292,8 +282,7 @@ function update_providers() { xhttp.send(); } -// Result ordering - +// Result ordering and navigation function update_providers_by_name(order) { setcookie('p_order', order, 30); setcookie('p_sort', 'name', 30); @@ -340,7 +329,6 @@ function p_next_page(nexthop) { } // Search providers - function search_providers_by_name(obj) { setcookie('p_name', obj.value, 30); update_providers(); @@ -367,7 +355,6 @@ function search_providers_by_comment(obj) { } // New provider - function new_provider() { var err = false; var obj = {}; @@ -410,14 +397,12 @@ function create_provider() { } // Delete provider - function confirm_provider_delete() { var msg="La suppression est définitive \net n'est pas autorisée si le \nfournisseur est référencé \npar un composant.\n\nConfirmer ?"; return confirm(msg); } // Update provider - function update_provider(obj, provider_id, type) { var xhttp = new XMLHttpRequest(); xhttp.onerror = function(){ @@ -472,8 +457,7 @@ function update_kits() { xhttp.send(); } -// Result ordering - +// Result ordering and navigation function update_kits_by_name(order) { setcookie('k_order', order, 30); setcookie('k_sort', 'name', 30); @@ -506,7 +490,6 @@ function k_next_kage(nexthop) { } // Search kits - function search_kits_by_name(obj) { setcookie('k_name', obj.value, 30); update_kits(); @@ -518,7 +501,6 @@ function search_kits_by_designation(obj) { } // New kit - function new_kit() { var err = false; var obj = {}; @@ -569,14 +551,12 @@ function create_kit() { } // Delete kit - function confirm_kit_delete() { var msg="La suppression d'un kit est définitive.\n\nConfirmer ?"; return confirm(msg); } // Update kit - function update_kit(obj, kit_id, type) { var xhttp = new XMLHttpRequest(); xhttp.onerror = function(){ @@ -599,3 +579,146 @@ function update_kit(obj, kit_id, type) { xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); xhttp.send('field='+obj.id+'&value='+obj.value); } + +/* ************************************************************************************** + * KIT COMPOSITIONS + * **************************************************************************************/ + +// Update kit composition +function update_kit_composition(kit_id) { + obj = document.getElementById('kit_composition'); + var xhttp = new XMLHttpRequest(); + xhttp.onerror = function(){ + obj.innerHTML = "Erreur lors de la mise à jour de la liste (1)" + }; + + xhttp.onload = function(){ + if (xhttp.status != 200) { + obj.innerHTML = "Erreur lors de la mise à jour de la liste (2)" + } + }; + + xhttp.onreadystatechange = function() { + if (xhttp.readyState == 4 && xhttp.status == 200) { + var response = xhttp.responseText; + obj.innerHTML = response; + return true; + } + }; + + xhttp.open('POST', '/kits/composition/'+kit_id, true); + xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + xhttp.send(); +} + +// Remove componant from kit +function confirm_componant_remove(designation) { + var msg="Supprimer le composant \n'"+designation+"'\ndu kit ?"; + return confirm(msg); +} + +// Add componant to kit +function add_kit_componant(kit_id, componant_id) { + var quantity = prompt("Quantité"); + if (quantity < 1){ + alert('La quantité doit être au moins égale à 1'); + return; + } + setcookie('kc_quantity', quantity, 30); + setcookie('kc_componant_id', componant_id, 30); + var xhttp = new XMLHttpRequest(); + + xhttp.onerror = function(){ + alert("Erreur lors de l'ajout du composant (1)."); + }; + + xhttp.onload = function(){ + if (xhttp.status != 200) { + alert("Erreur lors de l'ajout du composant (2)."); + } + }; + + xhttp.onreadystatechange = function() { + if (xhttp.readyState == 4 && xhttp.status == 200) { + var response = xhttp.responseText; + update_kit_composition(kit_id); + return true; + } + }; + + xhttp.open('POST', '/kits/add/'+kit_id+'/'+componant_id, true); + xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + xhttp.send(); +} + +// Edit componant_composition quantity +function edit_kit_composition(kit_id, componant_id){ + var quantity = prompt('Nouvelle quantité'); + if (quantity < 1){ + alert('La quantité doit être au moins égale à 1.'); + return; + } + + setcookie('kc_quantity', quantity, 30); + setcookie('kc_componant_id', componant_id, 30); + + var xhttp = new XMLHttpRequest(); + + xhttp.onerror = function(){ + alert("Erreur lors de la mise à jour de la liste (1)"); + }; + + xhttp.onload = function(){ + if (xhttp.status != 200) { + alert("Erreur lors de la mise à jour de la liste (2)"); + } + }; + + xhttp.onreadystatechange = function() { + if (xhttp.readyState == 4 && xhttp.status == 200) { + var response = xhttp.responseText; + update_kit_composition(kit_id); + return true; + } + }; + + xhttp.open('POST', '/kits/composition/update/'+kit_id, true); + xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + xhttp.send(); +} +// Update search result +function update_kit_componants(kit_id) { + obj = document.getElementById('result_container'); + var xhttp = new XMLHttpRequest(); + xhttp.onerror = function(){ + obj.innerHTML = "Erreur lors de la mise à jour de la liste (1)" + }; + + xhttp.onload = function(){ + if (xhttp.status != 200) { + obj.innerHTML = "Erreur lors de la mise à jour de la liste (2)" + } + }; + + xhttp.onreadystatechange = function() { + if (xhttp.readyState == 4 && xhttp.status == 200) { + var response = xhttp.responseText; + obj.innerHTML = response; + return true; + } + }; + xhttp.open('POST', '/kits/componants/update/'+kit_id, true); + xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + xhttp.send(); +} + +// Search componants +function search_kit_componants_by_reference(obj, kit_id) { + setcookie('kc_reference', obj.value, 30); + update_kit_componants(kit_id); +} + +function search_kit_componants_by_designation(obj, kit_id) { + setcookie('kc_designation', obj.value, 30); + update_kit_componants(kit_id); +} diff --git a/static/style/style.css b/static/style/style.css index f82c79f..7dd2aa2 100644 --- a/static/style/style.css +++ b/static/style/style.css @@ -332,6 +332,7 @@ span.page_num { } div.row_block { + display: inline-block; margin: 0 0 4px 0; height: 20px; width: 1000px; @@ -344,13 +345,14 @@ div.nav_page_block { } div.action_bar_block { + display: inline; overflow: hidden; float: center; height: 20px; } div.row_block label { - display: block; + display: inline; float: left; text-align: center; font-weight: bold; @@ -369,7 +371,7 @@ div.row_block text.border_left { } div.row_block text { - display: block; + display: inline; float: left; font-weight: normal; text-align: left; @@ -383,13 +385,27 @@ div.row_block text.num { text-align: right; } +div.row_block text.red { + color: #FF0000; + font-weight: bold; + border-color: #555555; +} + div.nav_page_block text { display: block; white-space: nowrap; } div.action_bar_block input { + display: inline; height: 17px; width: 16px; margin-left: 20px; + margin-top: 1px; + background-color: #F5D00; +} + +div.action_bar_block input:hover { + cursor: pointer; + background-color: #FF5D00; } diff --git a/stock.sql b/stock.sql index 133acf0..1cee582 100644 --- a/stock.sql +++ b/stock.sql @@ -128,3 +128,19 @@ insert into stock_componants (reference, designation, last_price, mean_price, qu values ('Res-19', 'Resistance 28KΩ', 13.34, 12.42, 73, 0, 'B 43', 3); insert into stock_componants (reference, designation, last_price, mean_price, quantity, min_quantity, place, provider_id) values ('Res-20', 'Resistance 29KΩ', 13.34, 12.42, 73, 0, 'B 43', 3); + +insert into stock_kits (name, designation) values ('Kit_test', 'Kit de test'); +insert into stock_kits (name, designation) values ('Kit2_test2', 'Kit de test 2'); +insert into stock_kits (name, designation) values ('MF_K', 'Mother fucking kit'); + +insert into stock_kit_compositions (kit_id, componant_id, quantity) values (1, 5, 1); +insert into stock_kit_compositions (kit_id, componant_id, quantity) values (1, 8, 2); +insert into stock_kit_compositions (kit_id, componant_id, quantity) values (1, 10, 2); +insert into stock_kit_compositions (kit_id, componant_id, quantity) values (1, 12, 7); + +insert into stock_kit_compositions (kit_id, componant_id, quantity) values (2, 5, 2); +insert into stock_kit_compositions (kit_id, componant_id, quantity) values (2, 4, 1); +insert into stock_kit_compositions (kit_id, componant_id, quantity) values (2, 9, 3); +insert into stock_kit_compositions (kit_id, componant_id, quantity) values (2, 22, 2); +insert into stock_kit_compositions (kit_id, componant_id, quantity) values (2, 1, 5); +insert into stock_kit_compositions (kit_id, componant_id, quantity) values (2, 14, 8); diff --git a/templates/error.html b/templates/error.html index c980a2b..b35e94c 100644 --- a/templates/error.html +++ b/templates/error.html @@ -1,7 +1,7 @@ - Référentiel Infrastructure Tetalab - {% block title %}How The Fuck{% endblock %} + Tetastock @@ -10,8 +10,8 @@
- +
+ +
+ {% endblock %} diff --git a/templates/kit_composition.html b/templates/kit_composition.html new file mode 100644 index 0000000..0cc1996 --- /dev/null +++ b/templates/kit_composition.html @@ -0,0 +1,67 @@ +
+ Nombre maximum de kits: {{ max_kit }} + Composition: +
+ + + + + +
+ {% set row_class = cycler('odd', 'even') %} + {% for componant in kit_composition %} + {% set qclass='' %} + {% if componant.needed > componant.quantity %} + {% set qclass='red' %} + {% endif %} +
+ {{ componant.reference }} + {{ componant.designation }} + {{ componant.needed }} + {{ componant.quantity }} +
+ + +
+
+ {% endfor %} +
+
+ Ajouter un composant: +
+ + +
+
+ + +
+
+ +
+ +
diff --git a/templates/kits.html b/templates/kits.html index e02145a..c01421b 100644 --- a/templates/kits.html +++ b/templates/kits.html @@ -2,7 +2,7 @@ {% block bodyheader %} {% endblock %} -{% block title %}Liste des fournisseurs{% endblock %} +{% block title %}Liste des Kits{% endblock %} {% block top_menu %} Gérer les kits {% endblock %} diff --git a/templates/result_componants.html b/templates/result_componants.html index 024c014..92a5f90 100644 --- a/templates/result_componants.html +++ b/templates/result_componants.html @@ -1,5 +1,5 @@
- Résultat: + Résultat: {% set ss='' %} {% if row_count > 1 %} {% set ss='s' %} diff --git a/templates/result_kit_componants.html b/templates/result_kit_componants.html new file mode 100644 index 0000000..c22e641 --- /dev/null +++ b/templates/result_kit_componants.html @@ -0,0 +1,26 @@ +
+ Résultat: +
+ + + + + +
+ {% set row_class = cycler('odd', 'even') %} + {% for componant in componants %} +
+ {{ componant.reference }} + {{ componant.designation }} + {{ componant.quantity }} + {{ componant.place }} +
+ +
+
+ {% endfor %} +
diff --git a/templates/result_providers.html b/templates/result_providers.html index 18d9818..2a5f00f 100644 --- a/templates/result_providers.html +++ b/templates/result_providers.html @@ -1,5 +1,5 @@
- Résultat: + Résultat: {% set ss='' %} {% if row_count > 1 %} {% set ss='s' %} diff --git a/tetastock.py b/tetastock.py index aad9731..bbf46f5 100755 --- a/tetastock.py +++ b/tetastock.py @@ -26,12 +26,14 @@ db = SQLAlchemy(app) # Database ######################################################################## class Stock_users(db.Model): + __tablename__ = 'stock_users' id = db.Column(db.Integer, primary_key=True) mail = db.Column(db.Text, nullable=False) password = db.Column(db.Text, nullable=False) name = db.Column(db.Text, nullable=False) class Stock_componants(db.Model): + __tablename__ = 'stock_componants' id = db.Column(db.Integer, primary_key=True) reference = db.Column(db.Text, nullable=False) designation = db.Column(db.Text, nullable=False) @@ -43,6 +45,7 @@ class Stock_componants(db.Model): provider_id = db.Column(db.Integer, nullable=False) class Stock_providers(db.Model): + __tablename__ = 'stock_providers' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.Text, nullable=False) address = db.Column(db.Text) @@ -51,26 +54,32 @@ class Stock_providers(db.Model): comment = db.Column(db.Text) class Stock_kits(db.Model): + __tablename__ = 'stock_kits' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.Text, nullable=False) designation = db.Column(db.Text, nullable=False) class Stock_kit_compositions(db.Model): + __tablename__ = 'stock_kit_compositions' id = db.Column(db.Integer, primary_key=True) - kit_id = db.Column(db.Integer, db.ForeignKey('Stock_kits.id'), nullable=False) - componant_id = db.Column(db.Integer, db.ForeignKey('Stock_componants.id'), nullable=False) + kit_id = db.Column(db.Integer, db.ForeignKey('stock_kits.id'), nullable=False) + componant_id = db.Column(db.Integer, db.ForeignKey('stock_componants.id'), nullable=False) quantity = db.Column(db.Integer, nullable=False) - price = db.Column(db.Integer, nullable=False) class Stock_orders(db.Model): + __tablename__ = 'stock_orders' id = db.Column(db.Integer, primary_key=True) - componant_id = db.Column(db.Integer, db.ForeignKey('Stock_componants.id')) + componant_id = db.Column(db.Integer, db.ForeignKey('stock_componants.id')) componant_quantity = db.Column(db.Integer, nullable=False) date = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow) injected = db.Column(db.Boolean, nullable=False, default=False) +######################################################################## +# Here comes the fun +######################################################################## + def sync_cookies(response, session): - """ Sync cookies from session """ + """ Sync cookies with session """ for key in session: response.set_cookie(key, value=str(session[key])) if key != u'session': @@ -126,9 +135,10 @@ def check_user(request, session): def resume_session(func): + """ Resume pending session """ @wraps(func) def check(*args, **kwargs): - # Motherfuckin' bunch of defaults values + # A motherfuckin' bunch of defaults values empty=u'' limit = 10 offset = 0 @@ -144,6 +154,8 @@ def resume_session(func): p_count = 0 k_sort = u'name' k_count = 0 + kc_quantity = 0 + kc_limit = 3 offset_reset = [u'c_reference', u'c_designation', u'c_place', u'c_provider', u'p_name', u'p_address', u'p_mail', u'p_url', u'k_name', u'k_designation'] @@ -237,7 +249,16 @@ def resume_session(func): session[u'k_designation'] = empty if not u'k_count' in session: session[u'k_count'] = k_count - + if not u'kc_limit' in session: + session[u'kc_limit'] = kc_limit + if not u'kc_componant_id' in session: + session[u'kc_componant_id'] = empty + if not u'kc_quantity' in session: + session[u'kc_quantity'] = kc_quantity + if not u'kc_designation' in session: + session[u'kc_designation'] = empty + if not u'kc_reference' in session: + session[u'kc_reference'] = empty # Cookies/session sync sync_session(request, session, offset_reset) @@ -266,11 +287,13 @@ def resume_session(func): ######################################################################## @app.errorhandler(404) def page_not_found(e): - return render_template('error.html'), 404 + """ 404 not found """ + return render_template('error.html'), 404 @app.route("/", methods=['GET', 'POST']) @resume_session def authenticate(): + """ Friend or foo ? """ response = app.make_response(render_template('index.html')) sync_cookies(response, session) return response @@ -283,7 +306,7 @@ def authenticate(): @app.route('/componants', methods=['GET', 'POST']) @resume_session def componants(): - """ Main page """ + """ Componants list """ providers = Stock_providers.query.order_by(Stock_providers.id).all() return render_template('componants.html', providers=providers, @@ -336,6 +359,7 @@ def delete_componant(componant_id): except ValueError as e: return render_template('error.html'), 404 except Exception as e: + db.session.rollback() print "[+] Error at delete_componant:" print "------------------------------" print "%s" % e.message @@ -358,6 +382,7 @@ def new_componant(): db.session.add(componant) commit = db.session.commit() except Exception as e: + db.session.rollback() print "[+] Error at new_componant:" print "------------------------------" print "%s" % e.message @@ -440,6 +465,7 @@ def update_componants(): @app.route('/providers', methods=['GET', 'POST']) @resume_session def providers(): + """ Providers list """ return render_template('providers.html', name=session[u'p_name'].decode('utf8'), address=session[u'p_address'].decode('utf8'), @@ -491,6 +517,7 @@ def new_provider(): db.session.add(provider) commit = db.session.commit() except Exception as e: + db.session.rollback() print "[+] Error at new_provider:" print "------------------------------" print "%s" % e.message @@ -511,6 +538,7 @@ def delete_provider(provider_id): except ValueError as e: return render_template('error.html'), 404 except Exception as e: + db.session.rollback() print "[+] Error at delete_provider:" print "------------------------------" print "%s" % e.message @@ -627,6 +655,7 @@ def new_kit(): db.session.add(kit) commit = db.session.commit() except Exception as e: + db.session.rollback() print "[+] Error at new_kit:" print "------------------------------" print "%s" % e.message @@ -648,6 +677,7 @@ def delete_kit(kit_id): except ValueError as e: return render_template('error.html'), 404 except Exception as e: + db.session.rollback() print "[+] Error at delete_kit:" print "------------------------------" print "%s" % e.message @@ -705,6 +735,129 @@ def search_kits(): print '[c]', key, session[key] return response +@app.route('/kits/composition/', methods=['POST']) +@resume_session +def get_kit_composition(kit_id): + default_max_kit = 999999999999999 + kit_composition = [] + max_kit = default_max_kit + composition = Stock_kit_compositions.query.filter_by(kit_id=kit_id).with_entities( + Stock_kit_compositions.componant_id, + Stock_kit_compositions.quantity).all() + # FIXME: Use join rather than this ugly work around + # FIXME: Good luck ! + for c_componant in composition: + componant = Stock_componants.query.filter_by(id=c_componant.componant_id).first() + if componant: + c = {u'id': componant.id, + u'reference': componant.reference, + u'designation': componant.designation, + u'quantity': componant.quantity, + u'needed': c_componant.quantity + } + kit_composition.append(c) + try: + mkit = int(componant.quantity) / int(c_componant.quantity) + except ZeroDivisionError as e: + print "[+] Error at get_kit_composition:" + print "------------------------------" + print "%s" % e.message + print "------------------------------" + if mkit < max_kit: + max_kit = mkit + if max_kit == default_max_kit: + max_kit = 0 + if len(kit_composition) > 0: + return render_template('kit_composition.html', kit_composition=kit_composition, max_kit=max_kit, kit_id=kit_id) + return "Aucun composant" + +@app.route('/kits/remove//', methods=['GET']) +@resume_session +def remove_componant_from_kit(kit_id, componant_id): + """ Remove componant from kit """ + try: + kit_id = int(kit_id) + Stock_kit_compositions.query.filter_by(kit_id=kit_id).filter_by(componant_id=componant_id).delete() + db.session.commit() + except ValueError as e: + return render_template('error.html'), 404 + except Exception as e: + db.session.rollback() + print "[+] Error at remove_componant_from_kit:" + print "------------------------------" + print "%s" % e.message + print "------------------------------" + return get_kit(kit_id) + +@app.route('/kits/add//', methods=['POST', 'GET']) +@resume_session +def add_componant_to_kit(kit_id, componant_id): + """ Add componant to kit """ + try: + kit_id = int(kit_id) + count = Stock_kit_compositions.query.filter_by(kit_id=kit_id, componant_id=componant_id).count() + if count > 0: + # Componant already in kit composition + # => Updating with new quantity + return update_kit_composition(kit_id) + + composition = Stock_kit_compositions(kit_id=kit_id, + componant_id=componant_id, + quantity=session[u'kc_quantity']) + db.session.add(composition) + commit = db.session.commit() + except ValueError as e: + return render_template('error.html'), 404 + except Exception as e: + db.session.rollback() + print "[+] Error at add_componant_to_kit:" + print "------------------------------" + print "%s" % e.message + print "------------------------------" + return 'KO' + if commit != None: + return 'KO' + return 'OK' + +@app.route('/kits/composition/update/', methods=['GET', 'POST']) +@resume_session +def update_kit_composition(kit_id): + """ Update componant quantity """ + try: + kit_id = int(kit_id) + composition = Stock_kit_compositions.query.filter_by( + kit_id=kit_id, componant_id=session[u'kc_componant_id']).first() + composition.quantity = session[u'kc_quantity'] + db.session.commit() + except ValueError as e: + return render_template('error.html'), 404 + except Exception as e: + db.session.rollback() + print "[+] Error at update_kit_composition:" + print "------------------------------" + print "%s" % e.message + print "------------------------------" + return get_kit(kit_id) + +## Componants update result set +@app.route('/kits/componants/update/', methods=['POST']) +@resume_session +def update_kit_componants(kit_id): + """ Display componants list """ + # search by reference + like = '%s%s%s' % ('%', str(session[u'kc_reference']), '%') + componants = Stock_componants.query.filter(Stock_componants.reference.like(like)) + # search by designation + like = '%s%s%s' % ('%', str(session[u'kc_designation']), '%') + componants = componants.filter(Stock_componants.designation.like(like)) + # Applying limit + componants = componants.limit(session[u'kc_limit']) + # Get result + componants = componants.all() + response = app.make_response(render_template('result_kit_componants.html', kit_id=kit_id, componants=componants)) + sync_cookies(response, session) + return response + # Main ####################################### if __name__ == '__main__':