Commit 857ba3b1 by Administrator

Merge branch 'aide_a_la_commande' into 'dev_cooperatic'

Aide a la commande

See merge request cooperatic-foodcoops/third-party!35
parents ae3dbb0f e7f80fe7
...@@ -52,7 +52,7 @@ class CagetteInventory(models.Model): ...@@ -52,7 +52,7 @@ class CagetteInventory(models.Model):
file_data = json.load(json_file) file_data = json.load(json_file)
date_time = datetime.fromtimestamp(int(filename)) date_time = datetime.fromtimestamp(int(filename))
d = date_time.strftime("%m/%d/%Y, %H:%M") d = date_time.strftime("%d/%m/%Y, %H:%M")
file_data['id'] = int(filename) file_data['id'] = int(filename)
file_data['datetime_created'] = d file_data['datetime_created'] = d
...@@ -113,7 +113,7 @@ class CagetteInventory(models.Model): ...@@ -113,7 +113,7 @@ class CagetteInventory(models.Model):
return file_data['inventory_status'] return file_data['inventory_status']
@staticmethod @staticmethod
def create_custom_inv_file(line_ids, line_type): def create_custom_inv_file(line_ids, line_type, default_partners_id=[]):
res = {} res = {}
try: try:
...@@ -127,36 +127,54 @@ class CagetteInventory(models.Model): ...@@ -127,36 +127,54 @@ class CagetteInventory(models.Model):
api = OdooAPI() api = OdooAPI()
ids = [] ids = []
order = ['', ''] order = ['', '']
user = partner = '' user = ''
fields = ['create_uid', 'product_id', 'partner_id'] partners = []
cond = [['id', 'in', line_ids]] if len(default_partners_id) > 0:
if (line_type == 'cpo'): f = ['name']
model = 'computed.purchase.order.line' c = [['id', 'in', default_partners_id]]
fields += ['computed_purchase_order_id'] partners_name = api.search_read('res.partner', c, f)
for p in partners_name:
partners.append(p['name'])
if line_type == 'product_templates':
fields = ['id']
cond = [['product_tmpl_id', 'in', line_ids]]
model = 'product.product'
user="api"
else: else:
model = 'purchase.order.line' fields = ['create_uid', 'product_id', 'partner_id']
fields += ['order_id'] cond = [['id', 'in', line_ids]]
if (line_type == 'cpo'):
model = 'computed.purchase.order.line'
fields += ['computed_purchase_order_id']
else:
model = 'purchase.order.line'
fields += ['order_id']
lines = api.search_read(model, cond, fields) lines = api.search_read(model, cond, fields)
if len(lines) == len(line_ids): if len(lines) == len(line_ids):
for l in lines: for l in lines:
ids.append(l['product_id'][0]) if line_type == 'product_templates':
user = l['create_uid'][1] ids.append(l['id'])
if (line_type == 'cpo'):
order = l['computed_purchase_order_id']
else: else:
order = l['order_id'] ids.append(l['product_id'][0])
partner = l['partner_id'][1] user = l['create_uid'][1]
if (line_type == 'cpo'):
order = l['computed_purchase_order_id']
else:
order = l['order_id']
partners.append(l['partner_id'][1])
if (line_type == 'cpo'): if (line_type == 'cpo'):
# partner_id isn't defined # partner_id isn't defined
f = ['partner_id'] f = ['partner_id']
c = [['id', '=', int(order[0])]] c = [['id', '=', int(order[0])]]
cpo = api.search_read('computed.purchase.order', c, f) cpo = api.search_read('computed.purchase.order', c, f)
if len(cpo) > 0: if len(cpo) > 0:
partner = cpo[0]['partner_id'][1] partners.append(cpo[0]['partner_id'][1])
file_data = { file_data = {
'order': order[1], 'order': order[1],
'user': user, 'user': user,
'partner': partner, 'partners': partners,
'inventory_status': '', 'inventory_status': '',
'products': ids 'products': ids
} }
......
...@@ -4,6 +4,13 @@ var shelfs_table = null, ...@@ -4,6 +4,13 @@ var shelfs_table = null,
function init_datatable() { function init_datatable() {
// For a smooth migration...
for (const i in lists) {
if (('partners' in lists[i]) === false) {
lists[i]['partners'] = [lists[i]['partner']]
}
}
return $('#lists').DataTable({ return $('#lists').DataTable({
data: lists, // data passed at page loading data: lists, // data passed at page loading
rowId: 'id', rowId: 'id',
...@@ -17,8 +24,15 @@ function init_datatable() { ...@@ -17,8 +24,15 @@ function init_datatable() {
} }
}, },
{ {
data:"partner", data:"partners",
title:"Fournisseur" title:"Fournisseur(s)",
render: function (data) {
res = "";
for (const i in data) {
res += `${data[i]}<br/>`;
}
return res;
}
}, },
{ {
data:"order", data:"order",
......
...@@ -43,7 +43,6 @@ def custom_list_inventory(request, id): ...@@ -43,7 +43,6 @@ def custom_list_inventory(request, id):
products = CagetteInventory.get_custom_list_products(id) products = CagetteInventory.get_custom_list_products(id)
if 'error' in products: if 'error' in products:
print(products)
products['data'] = [] products['data'] = []
context = {'title': 'Inventaire', context = {'title': 'Inventaire',
...@@ -112,10 +111,25 @@ def do_custom_list_inventory(request): ...@@ -112,10 +111,25 @@ def do_custom_list_inventory(request):
def generate_inventory_list(request): def generate_inventory_list(request):
"""Responding to Odoo ajax call (no csrf).""" """Responding to Odoo ajax call (no csrf)."""
res = {} res = {}
default_partners_id = []
try: try:
lines = json.loads(request.POST.get('lines')) lines = json.loads(request.POST.get('lines'))
ltype = request.POST.get('type') ltype = request.POST.get('type')
res = CagetteInventory.create_custom_inv_file(lines, ltype) except Exception as e:
try:
# POST.get() returns None when request from django
data = json.loads(request.body.decode())
lines = data["lines"]
ltype = data["type"]
if "partners_id" in data:
default_partners_id = data["partners_id"]
except Exception as ee:
res['error'] = str(ee)
coop_looger.error("generate_inventory_list : %s", str(e))
return JsonResponse(res, status=500)
try:
res = CagetteInventory.create_custom_inv_file(lines, ltype, default_partners_id)
except Exception as e: except Exception as e:
res['error'] = str(e) res['error'] = str(e)
coop_looger.error("generate_inventory_list : %s", str(e)) coop_looger.error("generate_inventory_list : %s", str(e))
......
...@@ -268,3 +268,15 @@ class Orders(models.Model): ...@@ -268,3 +268,15 @@ class Orders(models.Model):
coop_logger.error('Orders get_custom_barcode_labels_to_print(oids) : %s', str(e)) coop_logger.error('Orders get_custom_barcode_labels_to_print(oids) : %s', str(e))
return labels_data return labels_data
class CagetteSuppliers(models.Model):
@staticmethod
def get_suppliers():
api = OdooAPI()
f = ['id', 'name', 'display_name']
c = [['supplier', '=', 1], ['parent_id', '=', False]]
res = api.search_read('res.partner', c, f)
return res
.page_body{
position: relative;
}
.page_content {
position: absolute;
top: 0;
left: 0;
right: 0;
}
/* - Common */
.pill {
border-radius: 30px;
min-width: 200px;
min-height: 35px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 8px 15px 8px 15px;
margin: 0 10px 5px 0;
}
.disabled {
background-color: #c9cbce;
}
.disabled:hover {
background-color: #a1a2a3;
}
/* - Order selection screen */
#new_order_area {
margin-bottom: 40px;
}
#new_order_form {
margin-top: 20px;
}
#existing_orders {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
width: 80%;
margin: 0 auto;
padding-top: 15px;
}
.order_last_update {
font-weight: bold;
}
.order_modified_msg {
font-size: 2rem;
color: #e62720;
}
/* - Main screen */
/* -- Top action button(s) */
#back_to_order_selection {
position: absolute;
}
#actions_buttons_area {
position: absolute;
top: 0;
right: 0;
}
/* -- Supplier form */
#supplier_form_container {
margin-top: 30px;
}
#supplier_input {
width: 500px;
margin-right: 10px;
border-radius: 5px;
}
/* -- Table */
#products_table_filter{
text-align: right !important;
}
#products_table_filter input{
height: 40px;
}
#table_header_select_all{
display: flex;
align-content: center;
justify-content: center;
flex-wrap: wrap;
}
#table_header_select_all input{
margin-left: 5px;
}
.product_qty_input {
width: 100px;
}
.product_not_from_supplier {
background-color: #e7e9ed;
cursor: pointer;
}
.product_name, .supplier_name {
font-weight: bold;
}
.select_product_cb {
cursor: pointer;
}
/* -- Suppliers list */
#suppliers_container {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
margin: 20px 0 20px 0;
}
.supplier_pill {
background-color: #e7e9ed;
}
.remove_supplier_icon {
color: red;
margin-left: 5px;
cursor: pointer;
}
\ No newline at end of file
...@@ -8,5 +8,9 @@ urlpatterns = [ ...@@ -8,5 +8,9 @@ urlpatterns = [
url(r'^export/([0-9]+)', views.export_one), url(r'^export/([0-9]+)', views.export_one),
url(r'^export/([a-z]+)', views.export_regex), url(r'^export/([a-z]+)', views.export_regex),
url(r'^get_pdf_labels$', views.get_pdf_labels), url(r'^get_pdf_labels$', views.get_pdf_labels),
url(r'^print_product_labels$', views.print_product_labels) url(r'^print_product_labels$', views.print_product_labels),
url(r'^helper$', views.helper),
url(r'^get_suppliers$', views.get_suppliers),
url(r'^get_supplier_products$', views.get_supplier_products),
url(r'^associate_supplier_to_product$', views.associate_supplier_to_product),
] ]
from outils.common_imports import * from outils.common_imports import *
from outils.for_view_imports import * from outils.for_view_imports import *
from orders.models import Order, Orders from orders.models import Order, Orders, CagetteSuppliers
from products.models import CagetteProduct from products.models import CagetteProduct, CagetteProducts
from openpyxl import Workbook from openpyxl import Workbook
from openpyxl.writer.excel import save_virtual_workbook from openpyxl.writer.excel import save_virtual_workbook
...@@ -13,6 +13,52 @@ def as_text(value): return str(value) if value is not None else "" ...@@ -13,6 +13,52 @@ def as_text(value): return str(value) if value is not None else ""
def index(request): def index(request):
return HttpResponse('Orders') return HttpResponse('Orders')
def helper(request):
context = {
'title': 'Aide à la commande',
'couchdb_server': settings.COUCHDB['url'],
'db': settings.COUCHDB['dbs']['orders']
}
template = loader.get_template('orders/helper.html')
return HttpResponse(template.render(context, request))
def get_suppliers(request):
""" Get suppliers list """
res = {}
try:
res = CagetteSuppliers.get_suppliers()
except Exception as e:
res["error"] = str(e)
return JsonResponse(res, status=500)
return JsonResponse({'res': res})
def get_supplier_products(request):
""" Get supplier products """
sid = request.GET.get('sid', '')
res = CagetteProducts.get_products_by_supplier(sid)
if 'error' in res:
return JsonResponse(res, status=500)
else:
return JsonResponse({'res': res})
def associate_supplier_to_product(request):
""" This product is now supplied by this supplier """
res = {}
try:
data = json.loads(request.body.decode())
res = CagetteProduct.associate_supplier_to_product(data["product_tmpl_id"], data["supplier_id"])
except Exception as e:
res["error"] = str(e)
return JsonResponse(res, status=500)
return JsonResponse({'res': res})
def export_one(request, oid): def export_one(request, oid):
msg = '' msg = ''
try: try:
......
...@@ -99,6 +99,7 @@ STATICFILES_DIRS = ( ...@@ -99,6 +99,7 @@ STATICFILES_DIRS = (
"website/static", "website/static",
"shop/static", "shop/static",
"shelfs/static", "shelfs/static",
"orders/static",
# "tests/static" # "tests/static"
) )
......
...@@ -19,7 +19,8 @@ COUCHDB = { ...@@ -19,7 +19,8 @@ COUCHDB = {
'member': 'coops', 'member': 'coops',
'inventory': 'inventory', 'inventory': 'inventory',
'envelops': 'envelop', 'envelops': 'envelop',
'shop': 'shopping_carts' 'shop': 'shopping_carts',
'orders': 'orders_test'
} }
} }
......
...@@ -6,6 +6,7 @@ from outils.common import OdooAPI ...@@ -6,6 +6,7 @@ from outils.common import OdooAPI
import csv import csv
import tempfile import tempfile
import pymysql.cursors import pymysql.cursors
import datetime
vcats = [] vcats = []
...@@ -126,6 +127,27 @@ class CagetteProduct(models.Model): ...@@ -126,6 +127,27 @@ class CagetteProduct(models.Model):
res = api.create('product.supplier.shortage', f) res = api.create('product.supplier.shortage', f)
return res return res
@staticmethod
def associate_supplier_to_product(product_tmpl_id, partner_id):
api = OdooAPI()
f = ["id", "standard_price", "purchase_ok"]
c = [['product_tmpl_id', '=', product_tmpl_id]]
res_products = api.search_read('product.product', c, f)
product = res_products[0]
f = {
'product_tmpl_id' : product_tmpl_id,
'product_id' : product["id"],
'name' : partner_id,
'product_purchase_ok': product["purchase_ok"],
'price': product["standard_price"], # By default, use product price
'base_price': product["standard_price"],
}
res = api.create('product.supplierinfo', f)
return res
class CagetteProducts(models.Model): class CagetteProducts(models.Model):
"""Initially used to make massive barcode update.""" """Initially used to make massive barcode update."""
...@@ -387,6 +409,43 @@ class CagetteProducts(models.Model): ...@@ -387,6 +409,43 @@ class CagetteProducts(models.Model):
bc_map[bc] = bc bc_map[bc] = bc
return bc_map return bc_map
@staticmethod
def get_products_by_supplier(supplier_id):
api = OdooAPI()
res = {}
# todo : try with no result
try:
today = datetime.date.today().strftime("%Y-%m-%d")
# Get products/supplier relation
f = ["product_tmpl_id", 'date_start', 'date_end']
c = [['name', '=', int(supplier_id)]]
psi = api.search_read('product.supplierinfo', c, f)
# Filter valid data
ptids = []
for p in psi:
if (p["product_tmpl_id"] is not False
and (p["date_start"] is False or p["date_end"] is not False and p["date_start"] <= today)
and (p["date_end"] is False or p["date_end"] is not False and p["date_end"] >= today)):
ptids.append(p["product_tmpl_id"][0])
# Get products templates
f = ["id", "state", "name", "default_code", "qty_available", "incoming_qty", "uom_id"]
c = [['id', 'in', ptids], ['purchase_ok', '=', True]]
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"]
# Note: if product.product is needed, get "product_variant_ids" from product template
res["products"] = filtered_products_t
except Exception as e:
print(str(e))
res["error"] = str(e)
return res
class OFF(models.Model): class OFF(models.Model):
"""OpenFoodFact restricted DB queries.""" """OpenFoodFact restricted DB queries."""
......
...@@ -10,10 +10,6 @@ def index(request): ...@@ -10,10 +10,6 @@ def index(request):
context = {'title': 'Export de ventes'} context = {'title': 'Export de ventes'}
template = loader.get_template('sales/index.html') template = loader.get_template('sales/index.html')
# m = CagetteSales()
# sales = m.get_sales()
# print(sales)
return HttpResponse(template.render(context, request)) return HttpResponse(template.render(context, request))
def get_sales(request): def get_sales(request):
......
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
lists = {{lists|safe}} var lists = {{lists|safe}}
</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>
......
{% extends "base.html" %}
{% load static %}
{% block additionnal_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 'css/oders_helper_style.css' %}">
{% endblock %}
{% 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 'js/datatables/jquery.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="select_order_content" class="page_content txtcenter">
<div id="new_order_area">
<h2>Créer une nouvelle commande</h2>
<form id="new_order_form">
<input type="text" id="new_order_name" placeholder="Nom de la commande...">
<button type="submit" class="btn btn--primary">Valider</button>
</form>
</div>
<div id="existing_orders_area">
<h2>Ou, continuer une commande existante</h2>
<div id="existing_orders"></div>
</div>
</div>
<div id="main_content" class="page_content" style="display:none;">
<div id="back_to_order_selection">
<button type="button" class="btn--danger"><i class="fas fa-arrow-left"></i>&nbsp; Retour</button>
</div>
<div id="actions_buttons_area">
<button type="button" class='btn--primary' id="do_inventory">Faire un inventaire</button>
</div>
<div class="header txtcenter">
<h1>Aide à la commande</h1>
<i>Commande : <span class="order_name_container"></span></i>
</div>
<div class="txtcenter" id="supplier_form_container">
<form action="javascript:;" id="supplier_form">
<input type="text" name="supplier" id="supplier_input" placeholder="Rechercher un fournisseur par son nom">
<button type="submit" class='btn--primary'>Ajouter le fournisseur</button>
</form>
</div>
<div class="txtcenter" id="suppliers_container"></div>
<div class="main" style="display:none;">
<div class="table_area">
<table id="products_table" class="display" cellspacing="0" width="100%"></table>
</div>
</div>
</div>
<div id="templates" style="display:none;">
<div id="supplier_pill_template">
<div class="pill supplier_pill">
<div class="supplier_name_container">
<span class="pill_supplier_name"></span>
<i class="fas fa-times remove_supplier_icon"></i>
</div>
</div>
</div>
<div id="order_pill_template">
<div class="pill order_pill btn btn--primary">
<span class="pill_order_name"></span>
</div>
</div>
<div id="modal_order_access">
<h3>Attention !</h3>
<br/>
<p class="order_modified_msg">
Un autre navigateur a modifié cette commande il y a <span class="order_last_update"></span>.
</p><br/>
<p>
Si quelqu'un d'autre que vous est à l'origine de la modification et que celle-ci est récente,
nous conseillons fortement de ne pas accéder à la commande afin d'éviter les conflits.
</p><br/>
<p>Voulez-vous quand même y accéder ?</p>
<hr/>
</div>
<div id="modal_remove_supplier">
<h3>Attention !</h3>
<p>
Vous vous apprêtez à supprimer le fournisseur <span class="supplier_name"></span> de la sélection.<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.
</p>
<p>Êtez-vous sûr ?</p>
<hr/>
</div>
<div id="modal_attach_product_to_supplier">
<h3>Attention !</h3>
<p>
Vous vous apprêtez à associer le produit <span class="product_name"></span> au fournisseur <span class="supplier_name"></span>.<br/>
L'association sera sauvegardée dès que vous aurez cliqué sur "Valider".<br/>
</p>
<p>Êtez-vous sûr ?</p>
<hr/>
</div>
<div id="modal_create_inventory">
<p>
Vous vous apprêtez à créer un inventaire de <span class="inventory_products_count"></span> produits.
</p>
<p>Êtez-vous sûr ?</p>
<hr/>
</div>
</div>
</div>
<script src="{% static "js/pouchdb.min.js" %}"></script>
<script type="text/javascript">
var couchdb_dbname = '{{db}}';
var couchdb_server = '{{couchdb_server}}' + couchdb_dbname;
</script>
<script src="{% static "js/all_common.js" %}?v="></script>
<script type="text/javascript" src="{% static 'js/orders_helper.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