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
26bae4b3
Commit
26bae4b3
authored
May 06, 2022
by
Damien Moulard
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'story_2824' into 'dev_cooperatic'
Add change product shelf function See merge request
!175
parents
bc6ab168
e42f5e6f
Pipeline
#2196
failed with stage
in 1 minute 28 seconds
Changes
8
Pipelines
1
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
327 additions
and
11 deletions
+327
-11
views.py
inventory/views.py
+1
-0
config.md
outils/config.md
+6
-0
models.py
shelfs/models.py
+30
-1
shelfs.css
shelfs/static/css/shelfs.css
+12
-0
shelf_inventory.js
shelfs/static/js/shelf_inventory.js
+240
-5
urls.py
shelfs/urls.py
+2
-1
views.py
shelfs/views.py
+18
-3
shelf_inventory.html
templates/shelfs/shelf_inventory.html
+18
-1
No files found.
inventory/views.py
View file @
26bae4b3
...
...
@@ -47,6 +47,7 @@ def custom_list_inventory(request, id):
context
=
{
'title'
:
'Inventaire'
,
'products'
:
json
.
dumps
(
products
[
'data'
]),
'ahead_shelfs_ids'
:
json
.
dumps
(
getattr
(
settings
,
'SHELFS_TO_BE_AHEAD_IN_SELECT_LIST'
,
[]))
}
# Reuse shelf inventory template: same process
...
...
outils/config.md
View file @
26bae4b3
...
...
@@ -386,6 +386,12 @@
-
MEALS_PICKING_TYPE_ID = 10
### Inventory
-
SHELFS_TO_BE_AHEAD_IN_SELECT_LIST =
[
90,74
]
These shelfs (odoo ids) will be shown first in select list
### New members space
-
USE_NEW_MEMBERS_SPACE = True
...
...
shelfs/models.py
View file @
26bae4b3
...
...
@@ -528,10 +528,13 @@ class Shelf(models.Model):
class
Shelfs
(
models
.
Model
):
def
get_all
():
def
get_all
(
precision
=
'full'
):
res
=
[]
try
:
api
=
OdooAPI
()
if
precision
==
'simple'
:
res
=
api
.
search_read
(
'product.shelfs'
,
[],
[
'name'
,
'sort_order'
],
order
=
'sort_order asc'
)
else
:
res
=
api
.
execute
(
'product.shelfs'
,
'get'
,
{})
except
Exception
as
e
:
coop_logger
.
error
(
"Rayons, get_all :
%
s"
,
str
(
e
))
...
...
@@ -552,3 +555,28 @@ class Shelfs(models.Model):
except
Exception
as
e
:
coop_logger
.
error
(
"Rayons, get_shelfs_sortorder :
%
s"
,
str
(
e
))
return
res
@staticmethod
def
make_products_shelf_links
(
data
):
"""Set shelf_id for each product found in data."""
res
=
{}
try
:
api
=
OdooAPI
()
res
[
'done'
]
=
[]
# First of all, group product by shelf_id to save api server calls
products_shelf
=
{}
for
elt
in
data
:
if
elt
[
'shelf_id'
]
not
in
products_shelf
:
products_shelf
[
elt
[
'shelf_id'
]]
=
[]
products_shelf
[
elt
[
'shelf_id'
]]
.
append
(
int
(
elt
[
'product_id'
]))
# iterate on each shelf element to record changes
for
shelf_id
,
product_ids
in
products_shelf
.
items
():
f
=
{
'shelf_id'
:
shelf_id
}
if
api
.
update
(
'product.product'
,
product_ids
,
f
):
res
[
'done'
]
+=
product_ids
except
Exception
as
e
:
res
[
'error'
]
=
str
(
e
)
coop_logger
.
error
(
"Rayons, make_products_shelf_links :
%
s"
,
str
(
e
))
return
res
\ No newline at end of file
shelfs/static/css/shelfs.css
View file @
26bae4b3
...
...
@@ -147,6 +147,13 @@ table.dataTable {
padding
:
5px
;
}
#header_container_left
{
float
:
left
;
}
#change_shelf_btn
{
float
:
right
;
}
div
#container_edition
{
padding
:
8px
;
background-color
:
#e7e9ed
;
...
...
@@ -296,3 +303,8 @@ hr {
opacity
:
1
;
}
/* Change shelf modal */
.shelf_selection
{
max-width
:
240px
;
}
shelfs/static/js/shelf_inventory.js
View file @
26bae4b3
...
...
@@ -12,7 +12,9 @@ var validation_msg = $('#validation_msg'),
process_all_items_msg
=
$
(
'#process_all_items_msg'
),
faq_content
=
$
(
"#FAQ_modal_content"
),
issues_reporting
=
$
(
"#issues_reporting"
),
add_product_form
=
$
(
"#add_product_form"
);
add_product_form
=
$
(
"#add_product_form"
),
change_shelf_form
=
$
(
"#change_shelf_form"
),
change_shelf_btn
=
$
(
'#change_shelf_btn'
);
var
shelf
=
null
,
parent_location
=
'/shelfs'
,
...
...
@@ -28,7 +30,9 @@ var shelf = null,
adding_product
=
false
,
// True if modal to add a product is open.
barcodes
=
null
,
// Barcodes stored locally
// datetime for which shelf's ongoing_inv_start_datetime is considered null
default_inventory_start_datetime
=
"0001-01-01 00:00:00"
;
default_inventory_start_datetime
=
"0001-01-01 00:00:00"
,
selected_products_for_shelf_change
=
[],
all_shelfs
=
null
;
// Use get_all_shelfs to access it's value
/* UTILS */
...
...
@@ -297,6 +301,25 @@ function validateEdition() {
}
}
/**
* Unselect all rows from datatable
* Only for table_to_process
*/
function
unselect_all_rows
()
{
$
(
"#select_all_products_cb"
).
prop
(
"checked"
,
false
);
table_to_process
.
rows
().
every
(
function
()
{
const
node
=
$
(
this
.
node
());
node
.
removeClass
(
'selected'
);
node
.
find
(
".select_product_cb"
).
first
()
.
prop
(
"checked"
,
false
);
return
0
;
});
selected_rows
=
[];
}
/*
* Update a product info and add it to processed items.
* If 'value' is set, use it as new value.
...
...
@@ -323,6 +346,136 @@ function editProductInfo (productToEdit, value = null) {
return
true
;
}
/* Change shelf process */
/*
data should be an array of {product_id: xx, shelf_id: yy}
*/
function
record_products_shelf_on_server
(
data
)
{
return
new
Promise
(
resolve
=>
{
$
.
ajax
({
type
:
"POST"
,
url
:
"/shelfs/change_products_shelfs"
,
dataType
:
"json"
,
data
:
JSON
.
stringify
(
data
),
traditional
:
true
,
contentType
:
"application/json; charset=utf-8"
,
success
:
function
(
data
)
{
if
(
typeof
data
.
res
!==
"undefined"
&&
typeof
data
.
res
.
done
!==
"undefined"
)
resolve
(
data
.
res
.
done
);
else
resolve
(
null
);
},
error
:
function
()
{
alert
(
"Impossible de mettre à jour les données"
);
resolve
(
null
);
}
});
});
}
// call on change_shelf_btn click action
async
function
open_change_shelf_modal
()
{
selected_products_for_shelf_change
=
[];
$
(
'.select_product_cb:checked'
).
each
(
function
(
idx
,
elt
){
const
row
=
$
(
elt
).
closest
(
'tr'
);
selected_products_for_shelf_change
.
push
(
table_to_process
.
row
(
row
).
data
())
});
if
(
selected_products_for_shelf_change
.
length
>
0
)
{
/*
As button is not shown if no product is selected, should be always true
But, with CSS changes, it could happen that length == 0
*/
let
shelfs
=
await
get_all_shelfs
();
if
(
shelfs
!==
null
)
{
let
modal_content
=
$
(
'#templates #change_shelf_form'
).
clone
(),
shelf_selector
=
$
(
'<select>'
).
addClass
(
'shelf_selection'
),
table
=
modal_content
.
find
(
'table tbody'
).
empty
();
/* construct shelfs selector */
// first of all, sort by name
shelfs
.
sort
((
a
,
b
)
=>
(
a
.
name
>
b
.
name
)
?
1
:
((
b
.
name
>
a
.
name
)
?
-
1
:
0
));
// if ahead_shelfs_ids is not empty, put them ahead
if
(
ahead_shelfs_ids
.
length
>
0
)
{
let
to_move
=
{},
idx
=
0
;
// find index of shelfs to move
shelfs
.
forEach
((
shelf
)
=>
{
if
(
ahead_shelfs_ids
.
indexOf
(
shelf
.
id
)
>
-
1
)
{
to_move
[
shelf
.
id
]
=
idx
;
}
idx
+=
1
;
});
// Respecting ahead_shelfs_ids order, move shelf ahead
// splice can not be used, since more than 1 elt could be involved
let
ahead_elts
=
[];
ahead_shelfs_ids
.
forEach
((
shelf_id
)
=>
{
let
shelf
=
shelfs
[
to_move
[
shelf_id
]];
ahead_elts
.
push
(
shelf
);
});
//remove ahead elts
shelfs
=
shelfs
.
filter
((
item
)
=>
{
return
!
ahead_elts
.
includes
(
item
.
id
)});
// put them ahead by concatenation
shelfs
=
ahead_elts
.
concat
(
shelfs
);
}
shelfs
.
forEach
(
(
shelf
)
=>
{
let
option
=
$
(
'<option>'
)
.
val
(
shelf
.
id
)
.
text
(
shelf
.
name
+
' ('
+
shelf
.
sort_order
+
')'
);
shelf_selector
.
append
(
option
);
});
/* add product rows */
selected_products_for_shelf_change
.
forEach
(
(
product
)
=>
{
let
tr
=
$
(
'<tr>'
).
attr
(
'data-id'
,
product
.
id
)
.
append
(
$
(
'<td>'
).
text
(
product
.
name
))
.
append
(
$
(
'<td>'
).
append
(
shelf_selector
.
clone
()));
table
.
append
(
tr
);
});
openModal
(
modal_content
.
html
(),
()
=>
{
if
(
is_time_to
(
'change_product_shelf'
,
500
))
{
make_change
=
async
()
=>
{
// Prepare data to be transmitted to server to be recorded
let
data
=
[];
$
(
'.overlay-content table tbody tr'
).
each
(
function
(
idx
,
e
){
data
.
push
({
product_id
:
$
(
e
).
data
(
'id'
),
shelf_id
:
$
(
e
).
find
(
'select'
).
val
()
});
});
const
update_result
=
await
record_products_shelf_on_server
(
data
);
if
(
update_result
!==
null
)
{
update_result
.
forEach
(
(
product_id
)
=>
{
remove_from_toProcess
(
table_to_process
.
row
(
$
(
'tr#'
+
product_id
)));
});
let
message
=
"L'opération a bien réussi."
;
if
(
update_result
.
length
!==
data
.
length
)
{
message
=
"L'opération a partiellement réussi.
\
n"
;
message
+=
(
data
.
length
-
update_result
.
length
)
+
" produit(s) non déplacé(s)."
;
//TODO: display which products changes were in error
}
alert
(
message
);
}
}
make_change
();
}
},
'Changer les produits de rayons'
);
}
else
{
alert
(
"Les informations des autres rayons n'ont pas pu être récupérées."
)
}
}
}
/* LISTS HANDLING */
...
...
@@ -342,8 +495,24 @@ function initLists() {
}
// Init table for items to process
var
columns_to_process
=
[
{
data
:
"id"
,
title
:
"id"
,
visible
:
false
},
var
columns_to_process
=
[];
if
(
shelf
.
inventory_status
!==
""
)
{
columns_to_process
.
push
({
data
:
"id"
,
title
:
"id"
,
visible
:
false
});
}
else
{
columns_to_process
.
push
({
title
:
`<div id="table_header_select_all" class="txtcenter">
<input type="checkbox" id="select_all_products_cb" name="select_all_products_cb" value="all">
</div>`
,
className
:
"dt-body-center"
,
orderable
:
false
,
render
:
function
(
data
)
{
return
`<input type="checkbox" class="select_product_cb" />`
;
},
width
:
"4%"
});
}
columns_to_process
=
columns_to_process
.
concat
([
{
data
:
"name"
,
title
:
"Produit"
,
width
:
"60%"
},
{
data
:
"uom_id.1"
,
title
:
"Unité de mesure"
,
className
:
"dt-body-center"
},
{
...
...
@@ -351,7 +520,7 @@ function initLists() {
defaultContent
:
"<a class='btn' id='process_item' href='#'><i class='far fa-edit'></i></a>"
,
"className"
:
"dt-body-center"
,
orderable
:
false
}
];
]
)
;
if
(
originView
==
'custom_list'
)
{
columns_to_process
.
splice
(
1
,
0
,
{
data
:
"shelf_sortorder"
,
title
:
"Rayon"
,
className
:
"dt-body-center"
});
...
...
@@ -451,6 +620,41 @@ function initLists() {
clearLineEdition
();
}
});
// Select row(s) on checkbox change (copied from orders_helper.js -only table_to_process changed-)
$
(
table_to_process
.
table
().
header
()).
on
(
'click'
,
'th #select_all_products_cb'
,
function
()
{
if
(
this
.
checked
)
{
selected_rows
=
[];
table_to_process
.
rows
().
every
(
function
()
{
const
node
=
$
(
this
.
node
());
node
.
addClass
(
'selected'
);
node
.
find
(
".select_product_cb"
).
first
()
.
prop
(
"checked"
,
true
);
// Save selected rows in case the table is updated
selected_rows
.
push
(
this
.
data
().
id
);
return
0
;
});
change_shelf_btn
.
show
();
}
else
{
unselect_all_rows
();
change_shelf_btn
.
hide
();
}
});
$
(
table_to_process
.
table
().
body
()).
on
(
'click'
,
'.select_product_cb'
,
function
()
{
if
(
this
.
checked
)
{
change_shelf_btn
.
show
();
}
else
{
// must hide change_shelf_btn only if no other product is selected
if
(
$
(
'.select_product_cb:checked'
).
length
===
0
)
{
change_shelf_btn
.
hide
();
}
}
});
change_shelf_btn
.
click
(
open_change_shelf_modal
);
}
// Add a line to the 'items to process' list
...
...
@@ -766,6 +970,37 @@ function saveIssuesReport() {
/* INIT */
// (for shelf change)
function
get_all_shelfs
()
{
return
new
Promise
(
resolve
=>
{
if
(
all_shelfs
!==
null
)
{
resolve
(
all_shelfs
);
}
else
{
$
.
ajax
({
type
:
'GET'
,
url
:
"/shelfs/all/simple"
,
dataType
:
"json"
,
traditional
:
true
,
contentType
:
"application/json; charset=utf-8"
,
success
:
function
(
data
)
{
shelfs
=
null
;
if
(
typeof
data
.
res
!==
"undefined"
&&
data
.
res
.
length
>
0
)
shelfs
=
data
.
res
;
resolve
(
shelfs
);
},
error
:
function
(
data
)
{
err
=
{
msg
:
"erreur serveur lors de la récupération des rayons"
,
ctx
:
'get_all_shelfs'
};
if
(
typeof
data
.
responseJSON
!=
'undefined'
&&
typeof
data
.
responseJSON
.
error
!=
'undefined'
)
{
err
.
msg
+=
' : '
+
data
.
responseJSON
.
error
;
}
report_JS_error
(
err
,
'shelf_inventory'
);
resolve
(
null
);
}
});
}
});
}
// Get shelf data from server if not in local storage
function
get_shelf_data
()
{
var
url
=
(
originView
==
'shelf'
)
?
'../'
+
shelf
.
id
:
'../get_custom_list_data?id='
+
shelf
.
id
;
...
...
shelfs/urls.py
View file @
26bae4b3
...
...
@@ -9,8 +9,9 @@ urlpatterns = [
url
(
r'^shelf_view/([0-9]+)$'
,
views
.
shelf_view
),
url
(
r'^shelf_inventory/([0-9]+)$'
,
views
.
shelf_inventory
),
url
(
r'^inventory_process_state/([0-9]+)$'
,
views
.
inventory_process_state
),
url
(
r'^all$'
,
views
.
all
),
url
(
r'^all
/?([a-z]*)
$'
,
views
.
all
),
url
(
r'^get_shelves_extra_data$'
,
views
.
get_shelves_extra_data
),
url
(
r'^change_products_shelfs$'
,
views
.
change_products_shelfs
),
url
(
r'^(?P<shelf_id>\d+)$'
,
views
.
shelf_data
),
url
(
r'^(?P<shelf_id>\d+)/products$'
,
views
.
products
),
url
(
r'^(?P<shelf_id>\d+)/add_product$'
,
views
.
add_product
),
...
...
shelfs/views.py
View file @
26bae4b3
...
...
@@ -47,7 +47,9 @@ def shelf_inventory(request, id):
shelf_products
=
Shelf
(
id
)
.
get_products
()
context
=
{
'title'
:
'Inventaire du rayon'
,
'products'
:
json
.
dumps
(
shelf_products
[
'data'
])}
'products'
:
json
.
dumps
(
shelf_products
[
'data'
]),
'ahead_shelfs_ids'
:
json
.
dumps
(
getattr
(
settings
,
'SHELFS_TO_BE_AHEAD_IN_SELECT_LIST'
,
[]))
}
template
=
loader
.
get_template
(
'shelfs/shelf_inventory.html'
)
return
HttpResponse
(
template
.
render
(
context
,
request
))
...
...
@@ -79,9 +81,9 @@ def delete_ongoing_inv_data(request, shelf_id):
else
:
return
JsonResponse
({
'res'
:
res
})
def
all
(
request
):
def
all
(
request
,
precision
):
"""Get all shelves data"""
return
JsonResponse
({
'res'
:
Shelfs
.
get_all
()})
return
JsonResponse
({
'res'
:
Shelfs
.
get_all
(
precision
)})
def
get_shelves_extra_data
(
request
):
"""Get data that need calculation, so long execution time"""
...
...
@@ -138,6 +140,19 @@ def inventory_process_state(request, shelf_id):
else
:
return
JsonResponse
({
'res'
:
res
})
def
change_products_shelfs
(
request
):
res
=
{}
try
:
data
=
json
.
loads
(
request
.
body
.
decode
())
res
=
Shelfs
.
make_products_shelf_links
(
data
)
except
Exception
as
e
:
res
[
'error'
]
=
str
(
e
)
coop_logger
.
error
(
"change_products_shelfs :
%
s"
,
str
(
e
))
if
'error'
in
res
:
return
JsonResponse
(
res
,
status
=
500
)
else
:
return
JsonResponse
({
'res'
:
res
})
def
do_shelf_inventory
(
request
):
"""Process shelf inventory"""
"""
...
...
templates/shelfs/shelf_inventory.html
View file @
26bae4b3
...
...
@@ -91,6 +91,9 @@
<div
class=
"container_products"
id=
"container_left"
>
<h4
id=
"header_container_left"
>
Produits à compter
</h4>
<button
style=
"display:none;"
id=
"change_shelf_btn"
class=
"btn btn--primary"
>
Changer de rayon
</button>
<table
id=
"table_to_process"
class=
"display"
cellspacing=
"0"
></table>
</div>
<div
class=
"container_products"
id=
"container_right"
>
...
...
@@ -167,10 +170,24 @@
<input
autocomplete=
"off"
type=
"text"
placeholder=
"Code barre du produit"
class=
"add_product_input"
>
<hr
/>
</div>
<div
id=
"change_shelf_form"
>
<h3>
Changement de rayons
</h3>
<hr
/>
<table>
<thead>
<tr>
<th>
Produit
</th>
<th>
Rayon
</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
<script
type=
"text/javascript"
>
var
products
=
{{
products
|
safe
}}
const
ahead_shelfs_ids
=
{{
ahead_shelfs_ids
|
safe
}};
var
products
=
{{
products
|
safe
}};
</script>
<script
src=
"{% static "
js
/
all_common
.
js
"
%}?
v=
"></script>
<script src="
{%
static
"
js
/
common
.
js
"
%}?
v=
"></script>
...
...
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