Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
T
third-party
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
2
Merge Requests
2
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
cooperatic-foodcoops
third-party
Commits
70860307
Commit
70860307
authored
Mar 18, 2025
by
Yvon Kerdoncuff
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
entirely rework inventaire par produits app
parent
abe34fb6
Pipeline
#4183
failed with stage
Changes
6
Pipelines
1
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
49 additions
and
146 deletions
+49
-146
models.py
products/models.py
+4
-5
products.css
products/static/css/products.css
+5
-9
products_index.js
products/static/js/products_index.js
+37
-113
urls.py
products/urls.py
+1
-1
views.py
products/views.py
+2
-13
index.html
templates/products/index.html
+0
-5
No files found.
products/models.py
View file @
70860307
...
...
@@ -397,12 +397,11 @@ class CagetteProducts(models.Model):
return
api
.
search_read
(
'product.product'
,
cond
,
fields
)
@staticmethod
def
get_a
ctive_products_from_name
(
name
):
def
get_a
ll_active_products
(
):
api
=
OdooAPI
()
cond
=
[[
'active'
,
'='
,
True
],
[
'name'
,
'ilike'
,
name
]]
fields
=
[
'id'
,
'uom_id'
,
'name'
,
'qty_available'
,
'barcode'
,
'active'
,
'shelf_id'
,
'product_tmpl_id'
]
return
api
.
search_read
(
'product.product'
,
cond
,
fields
)
cond
=
[[
'active'
,
'='
,
True
]]
fields
=
[
'id'
,
'uom_id'
,
'name'
,
'qty_available'
,
'barcode'
]
return
api
.
search_read
(
'product.product'
,
cond
,
fields
,
limit
=
False
)
@staticmethod
def
get_vrac_products_from_cats
(
cats
,
withCandidate
=
False
,
fields
=
[]):
...
...
products/static/css/products.css
View file @
70860307
.main
,
.barcode_or_name_search_area
{
margin-top
:
20px
;
}
#barcode_or_name_selector
{
border
:
1px
solid
#555
;
border-radius
:
15px
;
}
.stock_edit_input
{
max-width
:
100px
;
}
.dataTables_filter
{
display
:
block
!important
;
/* S'assure qu'elle est bien affichée */
}
\ No newline at end of file
products/static/js/products_index.js
View file @
70860307
/*
* Récupération d'un produit par scan et affichage des informations du produits.
* Les informations actuellements récupérées sont :
* - Numéro de Rayon (not anymore : too slow)
* - Stock théorique
*
* Les informations actuellement modifiables sont :
...
...
@@ -9,29 +8,17 @@
*/
var
products
=
[],
products_table
=
null
,
search_chars
=
[];
search_history
=
[];
products_table
=
null
;
function
reset_focus
()
{
$
(
'#barcode_or_name_selector'
).
val
(
''
);
$
(
'#barcode_or_name_selector'
).
focus
();
}
function
add_product
(
product
)
{
try
{
// Add to list
products
.
push
(
product
);
if
(
products_table
==
null
)
{
function
init
(
products
)
{
// create table, show panel
products_table
=
$
(
'#products_table'
).
DataTable
({
data
:
products
,
columns
:
[
{
data
:
"id"
,
title
:
"id"
,
visible
:
false
},
columns
:
[
{
data
:
"id"
,
title
:
"id"
,
visible
:
false
},
{
data
:
"name"
,
title
:
"Produit"
,
data
:
"name"
,
title
:
"Produit"
,
width
:
"50%"
,
render
:
function
(
data
,
type
,
full
,
meta
)
{
// Add tooltip with barcode over product name
...
...
@@ -47,15 +34,15 @@ function add_product(product) {
}
},
{
data
:
"uom_id.1"
,
data
:
"uom_id.1"
,
title
:
"Unité de vente"
,
className
:
"dt-body-center"
,
className
:
"dt-body-center"
,
orderable
:
false
},
{
data
:
"qty_available"
,
data
:
"qty_available"
,
title
:
"Stock théorique"
,
className
:
"dt-body-center"
,
className
:
"dt-body-center"
,
render
:
function
(
data
,
type
,
full
,
meta
)
{
return
'<input type="number" class="stock_edit_input" value="'
+
data
+
'">'
+
' <button type="button" class="stock_edit_button btn--primary"><i class="fas fa-lg fa-check"></i></button>'
;
...
...
@@ -68,9 +55,11 @@ function add_product(product) {
"asc"
]
],
paging
:
false
,
dom
:
'lrtip'
,
// Remove the search input from that table
language
:
{
url
:
'/static/js/datatables/french.json'
}
pageLength
:
10
,
deferRender
:
true
,
processing
:
true
,
searching
:
true
,
language
:
{
url
:
'/static/js/datatables/french.json'
}
});
// Listener on 'Update product stock' button
...
...
@@ -83,11 +72,11 @@ function add_product(product) {
.
find
(
'input'
)
.
val
();
if
(
row_data
.
uom_id
[
0
]
==
1
)
{
if
(
qty
!=
0
&&
qty
/
parseInt
(
qty
)
!=
1
)
{
if
(
qty
!=
0
&&
qty
/
parseInt
(
qty
)
!=
1
)
{
$
.
notify
(
"Ce produit est vendu à l'unité : la valeur doit être un nombre entier !"
,
{
globalPosition
:
"top left"
,
globalPosition
:
"top left"
,
className
:
"error"
}
);
...
...
@@ -112,40 +101,23 @@ function add_product(product) {
$
.
notify
(
"Valeur inchangée."
,
{
globalPosition
:
"top left"
,
globalPosition
:
"top left"
,
className
:
"info"
}
);
$
(
"#products_table_filter input"
).
focus
()
}
}
else
{
$
.
notify
(
"Valeur invalide."
,
{
globalPosition
:
"top left"
,
globalPosition
:
"top left"
,
className
:
"error"
}
);
$
(
"#products_table_filter input"
).
focus
()
}
});
}
else
{
// Add row to table
var
rowNode
=
products_table
.
row
.
add
(
product
).
draw
(
false
)
.
node
();
let
onAnimationEnd
=
function
()
{
rowNode
.
classList
.
remove
(
'blink_me'
);
};
// Handle blinking effect for newly added row
$
(
rowNode
).
addClass
(
'blink_me'
);
rowNode
.
addEventListener
(
'animationend'
,
onAnimationEnd
);
rowNode
.
addEventListener
(
'webkitAnimationEnd'
,
onAnimationEnd
);
}
}
catch
(
e
)
{
err
=
{
msg
:
e
.
name
+
' : '
+
e
.
message
,
ctx
:
'add_product'
};
console
.
error
(
err
);
report_JS_error
(
err
,
'produits'
);
}
}
function
update_product_stock
(
p_data
)
{
...
...
@@ -162,6 +134,7 @@ function update_product_stock(p_data) {
delete
p_data
.
qty
;
closeModal
();
$
(
"#products_table_filter input"
).
focus
()
$
.
notify
(
"Stock mis à jour !"
,
{
...
...
@@ -169,8 +142,6 @@ function update_product_stock(p_data) {
className
:
"success"
}
);
reset_focus
();
},
error
:
function
(
data
)
{
// server error
...
...
@@ -189,39 +160,24 @@ function update_product_stock(p_data) {
}
// Fetch a product when barcode is read
function
fetch_products_from_bc_or_name
(
input
)
{
if
(
input
==
''
)
{
reset_focus
();
return
0
;
}
if
(
search_history
.
includes
(
input
))
{
$
.
notify
(
"Recherche déjà effectuée !"
,
{
globalPosition
:
"top left"
,
className
:
"info"
}
);
reset_focus
();
return
0
;
}
else
{
search_history
.
push
(
input
)
}
function
fetch_products
()
{
openModal
();
$
.
ajax
({
url
:
"/products/get_
products_data_by_barcode_or_name?input="
+
input
,
url
:
"/products/get_
all_active_products"
,
success
:
function
(
data
)
{
reset_focus
();
for
(
const
product
of
data
.
products
)
{
add_product
(
product
);
init
(
data
.
products
)
closeModal
();
// Attendre que le DOM soit complètement prêt avant de donner le focus
setTimeout
(
function
()
{
// Vérifier si l'input de la recherche est disponible
var
searchInput
=
$
(
"#products_table_filter input"
);
if
(
searchInput
.
length
)
{
searchInput
.
focus
();
// Mettre le focus sur le champ de recherche
}
},
100
);
// Attendre un court instant pour s'assurer que DataTables est complètement initialisé
},
error
:
function
(
data
)
{
closeModal
();
if
(
data
.
status
==
404
)
{
// Product not found (shouldn't rise)
$
.
notify
(
...
...
@@ -234,7 +190,7 @@ function fetch_products_from_bc_or_name(input) {
reset_focus
();
}
else
{
// server error
err
=
{
msg
:
"erreur serveur lors de la récupération du produit"
,
ctx
:
'fetch_products
_from_bc_or_name
'
};
err
=
{
msg
:
"erreur serveur lors de la récupération du produit"
,
ctx
:
'fetch_products'
};
if
(
typeof
data
.
responseJSON
!=
'undefined'
&&
typeof
data
.
responseJSON
.
error
!=
'undefined'
)
{
err
.
msg
+=
' : '
+
data
.
responseJSON
.
error
;
}
...
...
@@ -248,37 +204,5 @@ function fetch_products_from_bc_or_name(input) {
$
(
document
).
ready
(
function
()
{
$
.
ajaxSetup
({
headers
:
{
"X-CSRFToken"
:
getCookie
(
'csrftoken'
)
}
});
reset_focus
();
$
(
'#button_barcode_or_name_selector'
).
on
(
'click'
,
function
()
{
input
=
$
(
'#barcode_or_name_selector'
).
val
();
fetch_products_from_bc_or_name
(
input
);
});
// Interception de la touche "Enter" dans le champ de texte
$
(
'#barcode_or_name_selector'
).
on
(
'keypress'
,
function
(
e
)
{
if
(
e
.
which
===
13
)
{
// Si la touche pressée est "Enter" (key code 13)
e
.
preventDefault
();
// Empêche le comportement par défaut (rechargement ou soumission de formulaire)
input
=
$
(
'#barcode_or_name_selector'
).
val
();
fetch_products_from_bc_or_name
(
input
);
// Appelle la fonction de recherche
}
});
// Barcode reader: listen for 13 digits read in a very short time
$
(
'#search_input'
).
keypress
(
function
(
e
)
{
if
(
e
.
which
>=
48
&&
e
.
which
<=
57
)
{
search_chars
.
push
(
String
.
fromCharCode
(
e
.
which
));
}
if
(
search_chars
.
length
>=
13
)
{
var
barcode
=
search_chars
.
join
(
""
);
if
(
!
isNaN
(
barcode
))
{
search_chars
=
[];
setTimeout
(
function
()
{
fetch_products_from_bc_or_name
(
barcode
);
},
300
);
}
}
});
fetch_products
(
''
);
});
products/urls.py
View file @
70860307
...
...
@@ -7,7 +7,7 @@ urlpatterns = [
url
(
r'^simple_list$'
,
views
.
get_simple_list
),
url
(
r'^get_product_for_order_helper$'
,
views
.
get_product_for_order_helper
),
url
(
r'^get_product_data$'
,
views
.
get_product_data
),
url
(
r'^get_
products_data_by_barcode_or_name$'
,
views
.
get_products_data_by_barcode_or_name
),
url
(
r'^get_
all_active_products$'
,
views
.
get_all_active_products
),
url
(
r'^get_products_stdprices$'
,
views
.
get_products_stdprices
),
url
(
r'^update_product_stock$'
,
views
.
update_product_stock
),
url
(
r'^update_product_purchase_ok$'
,
views
.
update_product_purchase_ok
),
...
...
products/views.py
View file @
70860307
...
...
@@ -55,19 +55,8 @@ def get_product_for_order_helper(request):
else
:
return
JsonResponse
(
res
,
safe
=
False
)
def
get_products_data_by_barcode_or_name
(
request
):
input
=
request
.
GET
[
'input'
]
# check if it is probably a barcode input
if
input
.
isdigit
()
and
len
(
input
)
>
6
:
products
=
CagetteProduct
.
get_product_from_barcode
(
input
)
# or a text input
else
:
products
=
CagetteProducts
.
get_active_products_from_name
(
input
)
#commented because too slow
#for product in products:
# add_shelf_sortorder(product)
def
get_all_active_products
(
request
):
products
=
CagetteProducts
.
get_all_active_products
()
if
not
products
:
return
JsonResponse
({
"product"
:
products
},
status
=
404
)
else
:
...
...
templates/products/index.html
View file @
70860307
...
...
@@ -18,11 +18,6 @@
<h1>
Inventaire par produits
</h1>
</div>
<div
class=
"barcode_or_name_search_area txtcenter"
>
<input
type=
"text"
id=
"barcode_or_name_selector"
name=
"barcode_or_name_selector"
placeholder=
"Nom ou codebarre"
>
<button
type=
"button"
class=
"btn--primary"
id=
"button_barcode_or_name_selector"
name=
"button"
>
Recherche
</button>
</div>
<div
class=
"main"
>
<table
id=
"products_table"
class=
"display"
cellspacing=
"0"
></table>
</div>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment