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
9bc038d0
Commit
9bc038d0
authored
Feb 02, 2022
by
François C.
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch '1990-archive-product-from-alc' into 'dev_cooperatic'
1990 archive product from alc See merge request
!112
parents
6ca90bec
a3b1e80f
Pipeline
#1747
passed with stage
in 1 minute 35 seconds
Changes
11
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
188 additions
and
44 deletions
+188
-44
models.py
inventory/models.py
+2
-2
views.py
inventory/views.py
+1
-1
oders_helper_style.css
orders/static/css/oders_helper_style.css
+29
-4
orders_helper.js
orders/static/js/orders_helper.js
+84
-20
common.css
outils/static/css/common.css
+4
-0
models.py
products/models.py
+14
-3
urls.py
products/urls.py
+1
-1
views.py
products/views.py
+34
-3
views.py
shelfs/views.py
+1
-1
views.py
stock/views.py
+1
-1
helper.html
templates/orders/helper.html
+17
-8
No files found.
inventory/models.py
View file @
9bc038d0
...
...
@@ -368,8 +368,8 @@ class CagetteInventory(models.Model):
return
{
'missed'
:
missed
,
'unchanged'
:
unchanged
,
'done'
:
done
}
@staticmethod
def
update_
stock_with_shelf_inventory_data
(
inventory_data
):
"""
Updates Odoo stock after a shelf inventory
"""
def
update_
products_stock
(
inventory_data
):
"""
Updates Odoo stock after a shelf inventory or another action
"""
TWOPLACES
=
Decimal
(
10
)
**
-
2
api
=
OdooAPI
()
...
...
inventory/views.py
View file @
9bc038d0
...
...
@@ -94,7 +94,7 @@ def do_custom_list_inventory(request):
full_inventory_data
=
CagetteInventory
.
get_full_inventory_data
(
inventory_data
)
# Proceed with inventory
res
[
'inventory'
]
=
CagetteInventory
.
update_
stock_with_shelf_inventory_data
(
full_inventory_data
)
res
[
'inventory'
]
=
CagetteInventory
.
update_
products_stock
(
full_inventory_data
)
# remove file
CagetteInventory
.
remove_custom_inv_file
(
inventory_data
[
'id'
])
...
...
orders/static/css/oders_helper_style.css
View file @
9bc038d0
/* Comments : */
/* - Screens */
/* -- Sections */
/* - Common */
.page_body
{
position
:
relative
;
}
...
...
@@ -9,8 +15,6 @@
right
:
0
;
}
/* - Common */
.pill
{
border-radius
:
30px
;
min-width
:
200px
;
...
...
@@ -198,7 +202,7 @@
}
#coverage_form
>
div
{
display
:
inline-
block
;
display
:
block
;
float
:
left
;
}
...
...
@@ -367,7 +371,7 @@
width
:
90%
;
}
/*
p
roduct actions modal*/
/*
-- P
roduct actions modal*/
.npa-options
{
width
:
fit-content
;
text-align
:
left
;
...
...
@@ -376,6 +380,27 @@
.npa-options
label
{
display
:
block
;
}
.modal_product_actions_section
{
margin
:
1em
0
;
}
.modal_product_actions_section
.tooltip
{
margin-left
:
5px
;
}
.modal_product_actions_title
{
font-weight
:
bold
;
font-size
:
2.2rem
;
margin-bottom
:
10px
;
}
.checkbox_action_disabled
{
cursor
:
not-allowed
;
opacity
:
.5
;
}
/* - Orders created screen */
.order_created_header
{
...
...
orders/static/js/orders_helper.js
View file @
9bc038d0
...
...
@@ -266,8 +266,12 @@ function check_products_data() {
traditional
:
true
,
contentType
:
"application/json; charset=utf-8"
,
success
:
function
(
data
)
{
let
loaded_products_ids
=
products
.
map
(
p
=>
p
.
id
);
// Going through products fetched from server
for
(
let
product
of
data
.
res
.
products
)
{
const
p_index
=
products
.
findIndex
(
p
=>
p
.
id
==
product
.
id
);
const
p_id
=
product
.
id
;
const
p_index
=
products
.
findIndex
(
p
=>
p
.
id
==
p_id
);
if
(
p_index
===
-
1
)
{
// Add product if it wasn't fetched before (made available since last access to order)
...
...
@@ -288,9 +292,39 @@ function check_products_data() {
}
}
}
}
// Remove fetched product id from loaded products list
const
loaded_p_index
=
loaded_products_ids
.
indexOf
(
p_id
);
if
(
loaded_p_index
>
-
1
)
{
loaded_products_ids
.
splice
(
loaded_p_index
,
1
);
}
}
$
(
'.notifyjs-wrapper'
).
trigger
(
'notify-hide'
);
/**
* If loaded p_ids are remaining:
* these products were loaded but don't match the conditions to be fetched anymore.
* Remove them.
*/
if
(
loaded_products_ids
.
length
>
0
)
{
for
(
pid
of
loaded_products_ids
)
{
const
p_index
=
products
.
findIndex
(
p
=>
p
.
id
==
pid
);
const
p_name
=
products
[
p_index
].
name
;
products
.
splice
(
p_index
,
1
);
$
.
notify
(
`Produit "
${
p_name
}
" retiré de la commande.\nIl a probablement été passé en archivé ou en NPA sur un autre poste.`
,
{
globalPosition
:
"top left"
,
className
:
"info"
,
autoHideDelay
:
12000
,
clickToHide
:
false
}
);
}
}
resolve
();
},
error
:
function
(
data
)
{
...
...
@@ -693,23 +727,33 @@ function _compute_total_values_by_supplier() {
/* - PRODUCT */
function
save_products_npa_minimal_stock
(
product
,
inputs
)
{
let
actions
=
{
npa
:
[],
minimal_stock
:
0
,
id
:
product
.
id
,
name
:
product
.
name
};
function
commit_actions_on_product
(
product
,
inputs
)
{
let
actions
=
{
npa
:
[],
to_archive
:
false
,
minimal_stock
:
0
,
id
:
product
.
id
,
name
:
product
.
name
};
inputs
.
each
(
function
(
i
,
e
)
{
const
input
=
$
(
e
)
if
(
input
.
attr
(
'
type'
)
==
'checkbox
'
)
{
if
(
input
.
attr
(
'
name'
)
==
'npa-actions
'
)
{
if
(
input
.
prop
(
'checked'
)
==
true
)
{
actions
.
npa
.
push
(
input
.
val
())
actions
.
npa
.
push
(
input
.
val
())
;
}
}
else
if
(
input
.
attr
(
'name'
)
==
"minimal_stock"
)
{
actions
.
minimal_stock
=
input
.
val
()
actions
.
minimal_stock
=
input
.
val
();
}
else
if
(
input
.
attr
(
'name'
)
==
"archive-action"
)
{
if
(
input
.
prop
(
'checked'
)
==
true
&&
product
.
incoming_qty
===
0
)
{
actions
.
to_archive
=
true
;
}
}
});
openModal
();
$
.
ajax
({
type
:
"POST"
,
url
:
"/products/
update_npa_and_minimal_stock
"
,
url
:
"/products/
commit_actions_on_product
"
,
dataType
:
"json"
,
traditional
:
true
,
contentType
:
"application/json; charset=utf-8"
,
...
...
@@ -718,8 +762,9 @@ function save_products_npa_minimal_stock(product, inputs) {
const
index
=
products
.
findIndex
(
p
=>
p
.
id
==
product
.
id
);
products
[
index
].
minimal_stock
=
actions
.
minimal_stock
;
if
(
actions
.
npa
.
length
>
0
)
{
// Remove NPA products
if
(
actions
.
npa
.
length
>
0
||
actions
.
to_archive
===
true
)
{
// Remove NPA & archived products
products
.
splice
(
index
,
1
);
update_main_screen
();
debounceFunction
(
update_cdb_order
);
...
...
@@ -748,18 +793,32 @@ function save_products_npa_minimal_stock(product, inputs) {
let
msg
=
"erreur serveur lors de la sauvegarde"
.
msg
+=
` (product_tmpl_id:
${
product
.
id
}
)`
;
err
=
{
msg
:
msg
,
ctx
:
'
save_products_npa_minimal_stock
'
};
err
=
{
msg
:
msg
,
ctx
:
'
commit_actions_on_product
'
};
if
(
typeof
data
.
responseJSON
!=
'undefined'
&&
typeof
data
.
responseJSON
.
error
!=
'undefined'
)
{
err
.
msg
+=
' : '
+
data
.
responseJSON
.
error
;
}
report_JS_error
(
err
,
'orders'
);
closeModal
();
alert
(
'Erreur lors de la sauvegarde de la donnée. Veuillez ré-essayer plus tard.'
);
update_main_screen
();
try
{
if
(
data
.
responseJSON
.
code
===
"archiving_with_incoming_qty"
)
{
alert
(
"Ce produit a des quantités entrantes, vous ne pouvez pas l'archiver."
)
}
else
if
(
data
.
responseJSON
.
code
===
"error_stock_update"
)
{
alert
(
'Erreur lors de la mise à zéro du stock du produit archivé. Les actions ont bien été réalisées.'
);
}
else
{
alert
(
'Erreur lors de la sauvegarde des données. Veuillez ré-essayer plus tard.'
);
}
}
catch
(
error
)
{
alert
(
'Erreur lors de la sauvegarde des données. Veuillez ré-essayer plus tard.'
);
}
check_products_data
()
.
then
(()
=>
{
update_cdb_order
();
update_main_screen
();
closeModal
();
});
}
});
}
...
...
@@ -1668,20 +1727,25 @@ function display_products(params) {
.
on
(
'click'
,
'tbody td .product_actions'
,
function
(
e
){
// Save / unsave selected row
const
p_id
=
products_table
.
row
(
$
(
this
).
closest
(
'tr'
)).
data
().
id
;
const
product
=
products
.
find
(
p
=>
p
.
id
==
p_id
);
let
modal_product_actions
=
$
(
'#templates #modal_product_actions'
);
modal_product_actions
.
find
(
".product_name"
).
text
(
product
.
name
);
//modal_product_actions.find(".product_npa").text(null ? 'Ne Pas Acheter' : 'Peut Être Acheté');
const
product_can_be_archived
=
product
.
incoming_qty
===
0
;
if
(
product_can_be_archived
==
true
)
{
modal_product_actions
.
find
(
'input[name="archive-action"]'
).
prop
(
"disabled"
,
false
);
modal_product_actions
.
find
(
'input[name="archive-action"]'
).
closest
(
"label"
).
removeClass
(
"checkbox_action_disabled"
);
}
else
{
modal_product_actions
.
find
(
'input[name="archive-action"]'
).
prop
(
"disabled"
,
true
);
modal_product_actions
.
find
(
'input[name="archive-action"]'
).
closest
(
"label"
).
addClass
(
"checkbox_action_disabled"
);
}
openModal
(
modal_product_actions
.
html
(),
()
=>
{
if
(
is_time_to
(
'validate_product_actions'
))
{
save_products_npa_minimal_stock
(
product
,
modal
.
find
(
'input'
));
commit_actions_on_product
(
product
,
modal
.
find
(
'input'
));
}
},
'Valider'
,
...
...
@@ -2292,7 +2356,7 @@ $(document).ready(function() {
return
0
;
});
$
(
document
).
on
(
"click"
,
".
fa-info-circle
"
,
display_average_consumption_explanation
)
$
(
document
).
on
(
"click"
,
".
average_consumption_explanation_icon
"
,
display_average_consumption_explanation
)
$
.
datepicker
.
regional
[
'fr'
]
=
{
monthNames
:
[
...
...
outils/static/css/common.css
View file @
9bc038d0
...
...
@@ -148,6 +148,10 @@ footer { position: fixed;
width
:
230px
!important
;
}
.tooltip
.tooltip-xl
{
width
:
320px
!important
;
}
.tooltip
.tt_twolines
{
top
:
-15px
!important
;
}
...
...
products/models.py
View file @
9bc038d0
...
...
@@ -254,13 +254,20 @@ class CagetteProduct(models.Model):
return
res
@staticmethod
def
update_npa_and_minimal_stock
(
data
):
"""Update NPA (ne pas acheter) and minimal stock data"""
def
commit_actions_on_product
(
data
):
""" Update:
- NPA (ne pas acheter)
- Product is active
- Minimal stock
"""
res
=
{}
try
:
api
=
OdooAPI
()
# Minimal stock
f
=
{
'minimal_stock'
:
data
[
'minimal_stock'
]}
# NPA
if
'simple-npa'
in
data
[
'npa'
]:
f
[
'purchase_ok'
]
=
0
if
'npa-in-name'
in
data
[
'npa'
]:
...
...
@@ -279,6 +286,10 @@ class CagetteProduct(models.Model):
f
[
'name'
]
=
re
.
sub
(
r'( \[FDS\])'
,
''
,
current_name
)
if
len
(
data
[
'npa'
])
==
0
:
f
[
'purchase_ok'
]
=
1
# Active
f
[
"active"
]
=
not
data
[
'to_archive'
]
res
[
"update"
]
=
api
.
update
(
'product.template'
,
data
[
'id'
],
f
)
except
Exception
as
e
:
res
[
"error"
]
=
str
(
e
)
...
...
@@ -612,7 +623,7 @@ class CagetteProducts(models.Model):
"product_variant_ids"
,
"minimal_stock"
]
c
=
[[
'id'
,
'in'
,
ptids
],
[
'purchase_ok'
,
'='
,
True
]]
c
=
[[
'id'
,
'in'
,
ptids
],
[
'purchase_ok'
,
'='
,
True
]
,
[
'active'
,
'='
,
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"
]
...
...
products/urls.py
View file @
9bc038d0
...
...
@@ -11,7 +11,7 @@ urlpatterns = [
url
(
r'^update_product_stock$'
,
views
.
update_product_stock
),
url
(
r'^update_product_purchase_ok$'
,
views
.
update_product_purchase_ok
),
url
(
r'^update_product_internal_ref$'
,
views
.
update_product_internal_ref
),
url
(
r'^
update_npa_and_minimal_stock$'
,
views
.
update_npa_and_minimal_stock
),
url
(
r'^
commit_actions_on_product$'
,
views
.
commit_actions_on_product
),
url
(
r'^labels_appli_csv(\/?[a-z]*)$'
,
views
.
labels_appli_csv
,
name
=
'labels_appli_csv'
),
url
(
r'^label_print/([0-9]+)/?([0-9\.]*)/?([a-z]*)/?([0-9]*)$'
,
views
.
label_print
),
url
(
r'^shelf_labels$'
,
views
.
shelf_labels
),
# massive print
...
...
products/views.py
View file @
9bc038d0
...
...
@@ -100,7 +100,7 @@ def update_product_stock(request):
'products'
:
[
p
]
}
res
[
'inventory'
]
=
CagetteInventory
.
update_
stock_with_shelf_inventory_data
(
inventory_data
)
res
[
'inventory'
]
=
CagetteInventory
.
update_
products_stock
(
inventory_data
)
return
JsonResponse
({
"res"
:
res
})
...
...
@@ -134,13 +134,44 @@ def update_product_internal_ref(request):
else
:
return
JsonResponse
(
res
,
status
=
403
)
def
update_npa_and_minimal_stock
(
request
):
def
commit_actions_on_product
(
request
):
res
=
{}
is_connected_user
=
CagetteUser
.
are_credentials_ok
(
request
)
if
is_connected_user
is
True
:
try
:
data
=
json
.
loads
(
request
.
body
.
decode
())
res
=
CagetteProduct
.
update_npa_and_minimal_stock
(
data
)
product_data
=
CagetteProducts
.
get_products_for_order_helper
(
None
,
[
data
[
"id"
]])[
"products"
][
0
]
# Don't allow to archive product if incomin qty > 0
if
data
[
"to_archive"
]
is
True
and
product_data
[
"incoming_qty"
]
>
0
:
res
[
"code"
]
=
"archiving_with_incoming_qty"
return
JsonResponse
(
res
,
status
=
500
)
res
=
CagetteProduct
.
commit_actions_on_product
(
data
)
# If stock > 0: do inventory to set stock to 0
if
data
[
"to_archive"
]
is
True
and
product_data
[
"qty_available"
]
!=
0
:
try
:
p
=
{
'id'
:
product_data
[
'product_variant_ids'
][
0
],
# Need product id
'uom_id'
:
product_data
[
'uom_id'
],
'qty'
:
-
product_data
[
"qty_available"
]
}
inventory_data
=
{
'name'
:
'Archivage - '
+
product_data
[
'name'
],
'products'
:
[
p
]
}
res_inventory
=
CagetteInventory
.
update_products_stock
(
inventory_data
)
if
res_inventory
[
'errors'
]
or
res_inventory
[
'missed'
]:
res
[
"code"
]
=
"error_stock_update"
res
[
"error"
]
=
res_inventory
[
'errors'
]
return
JsonResponse
(
res
,
status
=
500
)
except
Exception
as
e
:
res
[
"code"
]
=
"error_stock_update"
return
JsonResponse
(
res
,
status
=
500
)
except
Exception
as
e
:
res
[
'error'
]
=
str
(
e
)
coop_logger
.
error
(
"Update npa and minimal stock :
%
s"
,
res
[
'error'
])
...
...
shelfs/views.py
View file @
9bc038d0
...
...
@@ -173,7 +173,7 @@ def do_shelf_inventory(request):
return
JsonResponse
(
res
,
status
=
500
)
# Proceed with inventory
res
[
'inventory'
]
=
CagetteInventory
.
update_
stock_with_shelf_inventory_data
(
full_inventory_data
)
res
[
'inventory'
]
=
CagetteInventory
.
update_
products_stock
(
full_inventory_data
)
full_inventory_data
[
'inventory_id'
]
=
res
[
'inventory'
][
'inv_id'
]
shelf_data
[
'last_inventory_id'
]
=
res
[
'inventory'
][
'inv_id'
]
...
...
stock/views.py
View file @
9bc038d0
...
...
@@ -46,7 +46,7 @@ def do_movement(request):
'products'
:
products
}
res
=
CagetteInventory
.
update_
stock_with_shelf_inventory_data
(
inventory_data
)
res
=
CagetteInventory
.
update_
products_stock
(
inventory_data
)
else
:
res
=
CagetteStock
.
do_stock_movement
(
data
)
...
...
templates/orders/helper.html
View file @
9bc038d0
...
...
@@ -98,7 +98,7 @@
<input
type=
"number"
name=
"percent_adjustement"
id=
"percent_adjust_input"
placeholder=
"ajustement en %"
>
</div>
<div>
<button
type=
"submit"
class=
'btn--primary'
>
Calculer les besoins
</button>
<i
class=
'main fa fa-info-circle fa-lg'
></i>
<button
type=
"submit"
class=
'btn--primary'
>
Calculer les besoins
</button>
<i
class=
'main fa fa-info-circle fa-lg
average_consumption_explanation_icon
'
></i>
</div>
</form>
</div>
...
...
@@ -277,19 +277,28 @@
<div
id=
"modal_product_actions"
>
Actions sur
<h3><span
class=
"product_name"
></span></h3>
<
p
>
<h4>
NPA
</h4>
<
div
class=
"modal_product_actions_section"
>
<h4
class=
"modal_product_actions_title"
>
NPA
</h4>
<div
class=
"npa-options"
>
<label><input
type=
"checkbox"
name=
"npa-actions"
value=
"simple-npa"
/>
Mettre le produit en NPA
</label>
<label><input
type=
"checkbox"
name=
"npa-actions"
value=
"npa-in-name"
/>
Mettre le produit en NPA et afficher NPA
</label>
<label><input
type=
"checkbox"
name=
"npa-actions"
value=
"fds-in-name"
/>
Mettre le produit en NPA et afficher FDS
</label>
</div>
</p>
<p>
<h4>
Stock minimum
</h4>
</div>
<div
class=
"modal_product_actions_section"
>
<h4
class=
"modal_product_actions_title"
>
Archiver le produit
</h4>
<label
class=
"checkbox_action_disabled"
><input
type=
"checkbox"
name=
"archive-action"
value=
"archive"
disabled
/>
Archiver
</label>
<div
class=
"tooltip"
>
<i
class=
'main fa fa-info-circle'
></i>
<span
class=
"tooltiptext tooltip-xl tt_twolines"
>
Un produit ne peut pas être archivé si une quantité entrante est prévue.
</span>
</div>
</div>
<div
class=
"modal_product_actions_section"
>
<h4
class=
"modal_product_actions_title"
>
Stock minimum
</h4>
<input
type=
"number"
name=
"minimal_stock"
value=
""
/>
</p>
</div>
</div>
<div
id=
"modal_create_order"
>
...
...
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