Browse Source

adding orders and consumables work

funding-tag
Holger Frey 5 years ago
parent
commit
3e0d879c72
  1. 8
      ordr3/repo.py
  2. 4
      ordr3/schemas/orders.py
  3. 9
      ordr3/services.py
  4. 97
      ordr3/static/script.js
  5. 50
      ordr3/static/style.css
  6. 46
      ordr3/templates/orders/add.jinja2
  7. 69
      ordr3/views/orders.py

8
ordr3/repo.py

@ -123,6 +123,14 @@ class SqlAlchemyRepository(AbstractOrderRepository):
.all() .all()
) )
def list_consumables(self, limit_date, statuses):
return (
self.session.query(models.OrderItem)
.filter(models.OrderItem.created_on > limit_date)
.filter(models.OrderItem.status.in_(statuses))
.all()
)
def add_user(self, user): def add_user(self, user):
""" add a user to the database """ """ add a user to the database """
self._add_item_to_db(user) self._add_item_to_db(user)

4
ordr3/schemas/orders.py

@ -213,6 +213,8 @@ class AddOrderSchema(CSRFSchema):
@classmethod @classmethod
def as_form(cls, request, **override): def as_form(cls, request, **override):
""" returns the schema as a form """ """ returns the schema as a form """
vendor_autocorrect_url = override.pop("autocorrect_url")
settings = { settings = {
"buttons": ("Place Order", "Cancel"), "buttons": ("Place Order", "Cancel"),
"css_class": "deform o3-col-form o3-order-form", "css_class": "deform o3-col-form o3-order-form",
@ -221,7 +223,7 @@ class AddOrderSchema(CSRFSchema):
form = super().as_form(request, **settings) form = super().as_form(request, **settings)
# set the url for vendor check # set the url for vendor check
vendor_url = request.resource_url(request.context.__parent__, "vendor") vendor_url = vendor_autocorrect_url
vendor_widget = form["item"]["vendor"].widget vendor_widget = form["item"]["vendor"].widget
vendor_widget.attributes["data-url"] = vendor_url vendor_widget.attributes["data-url"] = vendor_url

9
ordr3/services.py

@ -1,6 +1,6 @@
import uuid import uuid
import hashlib import hashlib
from datetime import datetime from datetime import datetime, timedelta
from collections import namedtuple from collections import namedtuple
from urllib.parse import urlparse from urllib.parse import urlparse
@ -40,11 +40,8 @@ def find_consumables(repo, repeat=3, days=365 * 2):
def _find_consumables(repo, repeat=3, days=365 * 2): def _find_consumables(repo, repeat=3, days=365 * 2):
""" helper function for find_consumables() implementation """ """ helper function for find_consumables() implementation """
now = datetime.now() limit_date = datetime.now() - timedelta(days=days)
by_date = ( relevant = repo.list_consumables(limit_date, CONSUMABLE_STATI)
o for o in repo.list_orders() if (now - o.created_on).days < days
)
relevant = (o for o in by_date if o.status in CONSUMABLE_STATI)
counter = {} counter = {}
for order in relevant: for order in relevant:
item = counter.setdefault( item = counter.setdefault(

97
ordr3/static/script.js

@ -6,6 +6,16 @@ var capitalize = function(some_string) {
} }
}; };
var copy_with_fade = function(event) {
var target = $(event.delegateTarget);
var $temp = $("<input>");
$("body").append($temp);
$temp.val($(target).html()).select();
document.execCommand("copy");
$temp.remove();
$(target).fadeTo(100, 0).delay(100).fadeTo(100, 1);
}
var load_more = function(target) { var load_more = function(target) {
var next = target.attr("data-next") var next = target.attr("data-next")
var href = new URL(window.location); var href = new URL(window.location);
@ -106,13 +116,7 @@ $(function() {
$(".o3-copy").on("click", function(event) { $(".o3-copy").on("click", function(event) {
// copy to clipboard // copy to clipboard
var target = $(event.delegateTarget); copy_with_fade(event);
var $temp = $("<input>");
$("body").append($temp);
$temp.val($(target).html()).select();
document.execCommand("copy");
$temp.remove();
$(target).fadeTo(100, 0).delay(100).fadeTo(100, 1);
}); });
$(".o3-confirmation").on("click", function(event) { $(".o3-confirmation").on("click", function(event) {
@ -218,7 +222,84 @@ $(function() {
}); });
}); });
$(".o3-add-new-order .item-cas_description input").on("keyup", function(event){
var target = $(event.delegateTarget);
var search = target.val().toLowerCase();
var search_length = search.length;
if (search == "") {
$(".o3-consumables table tbody tr").each( function( index, element ){
var row = $(element)
var text_elm = row.find(".o3-consumable-cas .text-truncate");
var pure_text = text_elm.text()
text_elm.html(pure_text);
row.show();
});
} else {
$(".o3-consumables table tbody tr").each( function( index, element ){
var row = $(element)
var cas = row.attr("title").toLowerCase()
var start = cas.indexOf(search);
var display = (start !== -1)
var text_elm = row.find(".o3-consumable-cas .text-truncate");
var pure_text = text_elm.text()
row.toggle(display)
if (display) {
var head = pure_text.substring(0, start);
var center = pure_text.substring(start, start+search_length);
var tails = pure_text.substring(start+search_length, pure_text.length);
var new_html = head + '<span class="font-weight-bolder">' + center + "</span>" + tails;
text_elm.html(new_html);
} else {
text_elm.html(pure_text);
}
});
}
});
$(".o3-consumable-category-filter").on("click", function(event) {
var target = $(event.delegateTarget);
var filter_category = target.attr("data-filter");
if (filter_category == "all") {
$(".o3-consumables table tbody tr").show();
} else {
$(".o3-consumables table tbody tr").each( function( index, element ){
var row = $(element)
var display = row.data("category") == filter_category;
row.toggle(display)
});
}
$(".o3-consumable-category-filter").removeClass("active");
target.addClass("active");
return false;
});
$(".o3-consumables table tbody tr").on("click", function(event) {
var target = $(event.delegateTarget);
var data_cas = target.attr("title");
var data_category = target.attr("data-category");
var data_catalog = target.attr("data-catalog");
var data_vendor = target.attr("data-vendor");
var data_size = target.attr("data-size");
var data_price = target.attr("data-price");
$(".item-cas_description input").val(data_cas);
$(".item-category select").val(data_category);
$(".item-catalog_nr input").val(data_catalog);
$(".item-vendor input").val(data_vendor);
$(".item-package_size input").val(data_size);
$(".item-unit_price .item-amount input").val(data_price);
update_price();
$(".item-cas_description input").focus()
});
var infinite = new Waypoint.Infinite({ var infinite = new Waypoint.Infinite({
element: $('.infinite-container')[0] element: $('.infinite-container')[0],
onAfterPageLoad: function(items) {
items.find(".o3-copy").on("click", function(event) {
copy_with_fade(event);
});
}
}); });
}); });

50
ordr3/static/style.css

@ -194,21 +194,24 @@ td.o3-actions a:hover {
width: 7rem; width: 7rem;
} }
.o3-set-multi-status {
cursor:pointer;
}
a.d-inline-block.text-truncate { a.d-inline-block.text-truncate {
max-width:100%; max-width:100%;
} }
.o3-content .table.o3-log-table th, .o3-content .table.o3-log-table td { .o3-content .table.o3-log-table th, .o3-content .table.o3-log-table td {
padding-left:0px; padding-left:0px;
padding-top:0px; padding-top:0px;
} }
.o3-col-form .form-group.row { .o3-col-form .form-group.row {
padding-right:15px; padding-right:15px;
} }
span.item-amount { span.item-amount {
padding-left:0px; padding-left:0px;
@ -243,5 +246,42 @@ label.o3-form-copy {
label.o3-form-copy:hover { label.o3-form-copy:hover {
text-decoration:underline; text-decoration:underline;
} }
.o3-consumables .nav {
border-bottom:1px solid #ced4da!important;
margin-bottom:1rem;
margin-top:2rem;
color:#6c757d!important;
}
.o3-consumables .nav li {
margin-top:.5rem
}
.o3-consumables .nav a {
}
.o3-consumables .nav a.active {
color:#343a40!important;
text-decoration:underline;
}
.o3-consumables table {
table-layout: fixed;
}
.o3-consumables table th {
border-top:0px;
}
.o3-consumables table tbody tr {
cursor: pointer;
}
.o3-consumables table .o3-consumable-cas {
width:75%;
}
.o3-consumables table .o3-consumable-cas .text-truncate {
display:block;
}

46
ordr3/templates/orders/add.jinja2

@ -0,0 +1,46 @@
{% extends "ordr3:templates/layout_full.jinja2" %}
{% block subtitle %} Place a New Order {% endblock subtitle %}
{% block content %}
<div class="col-5 o3-add-new-order">
<h4 class="mb-2 mb-4 text-truncate">Place a New Order</h4>
{{form.render()|safe}}
</div>
<div class="col-5 o3-consumables">
<h4 class="mb-2 text-muted mb-4 text-truncate">Consumables</h4>
<ul class="nav">
<li class="nav-item">
<a class="nav-link active o3-consumable-category-filter text-secondary" href="#" data-filter="all">all</a>
</li>
{% for category in categories %}
<li class="nav-item">
<a class="nav-link o3-consumable-category-filter text-secondary" href="#" data-filter="{{ category.name }}">{{ category.name|title }}</a>
</li>
{% endfor %}
</ul>
<table class="table table-hover ">
<!--
<thead>
<tr>
<th scope="col">CAS / Description</th>
<th scope="col" class="text-right">Size</th>
</tr>
</thead>
-->
<tbody >
{% for con in consumables %}
<tr title="{{ con.cas_description }}" data-category="{{ con.category.name }}" data-catalog="{{ con.catalog_nr }}" data-vendor="{{ con.vendor }}" data-size="{{ con.package_size }}" data-price="{{ '%.2f'|format(con.unit_price) }}">
<td class="o3-consumable-cas"><span class="text-truncate">{{ con.cas_description }}</span></td>
<td class="text-nowrap text-right">{{ con.package_size }}</td>
{% endfor %}
</tbody>
</table>
</div>
{% endblock content %}

69
ordr3/views/orders.py

@ -303,6 +303,72 @@ def check_vendor_name(context, request):
return result._asdict() return result._asdict()
@view_config(
context="ordr3:resources.OrderList",
name="add",
permission="add",
request_method="GET",
renderer="ordr3:templates/orders/add.jinja2",
)
def new_order(context, request):
autocorrect_url = request.resource_url(context, "vendor")
form = orders.AddOrderSchema.as_form(
request, autocorrect_url=autocorrect_url
)
consumables = services.find_consumables(request.repo)
return {
"form": form,
"categories": models.OrderCategory,
"consumables": consumables,
}
@view_config(
context="ordr3:resources.OrderList",
name="add",
permission="add",
request_method="POST",
renderer="ordr3:templates/orders/add.jinja2",
)
def place_new_order(context, request):
""" process the reorder form """
autocorrect_url = request.resource_url(context, "vendor")
form = orders.AddOrderSchema.as_form(
request, autocorrect_url=autocorrect_url
)
if "Place_Order" not in request.POST:
return HTTPFound(request.resource_url(context.__parent__))
data = request.POST.items()
try:
appstruct = form.validate(data)
except deform.ValidationFailure:
consumables = services.find_consumables(request.repo)
return {
"form": form,
"categories": models.OrderCategory,
"consumables": consumables,
}
# form validation sucessful, change order
order = models.OrderItem(None, None, None, None, None, None, None, None)
update_order_with_form_data(order, appstruct)
services.create_log_entry(order, models.OrderStatus.OPEN, request.user)
request.repo.add_order(order)
request.emit(
events.FlashMessage.info(
f"The order {order.cas_description} has been placed."
)
)
return HTTPFound(request.resource_url(context.__parent__))
@view_config( @view_config(
context="ordr3:resources.Order", context="ordr3:resources.Order",
permission="view", permission="view",
@ -422,8 +488,7 @@ def place_reorder(context, request):
return {"form": form} return {"form": form}
# form validation sucessful, change order # form validation sucessful, change order
default = [None] * 8 order = models.OrderItem(None, None, None, None, None, None, None, None)
order = models.OrderItem(*default)
update_order_with_form_data(order, appstruct) update_order_with_form_data(order, appstruct)
services.create_log_entry(order, models.OrderStatus.OPEN, request.user) services.create_log_entry(order, models.OrderStatus.OPEN, request.user)

Loading…
Cancel
Save