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
97eaabd2
Commit
97eaabd2
authored
Aug 30, 2021
by
Damien Moulard
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'aide_a_la_commande' into dev_cooperatic
parents
f4fd7818
aa99f68f
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
857 additions
and
164 deletions
+857
-164
config_lacagette.py
coops_configurations/config_lacagette.py
+3
-0
models.py
orders/models.py
+6
-1
oders_helper_style.css
orders/static/css/oders_helper_style.css
+109
-21
orders_helper.js
orders/static/js/orders_helper.js
+537
-95
urls.py
orders/urls.py
+1
-0
views.py
orders/views.py
+23
-7
models.py
products/models.py
+75
-15
urls.py
products/urls.py
+1
-0
views.py
products/views.py
+15
-2
reception_index.js
reception/static/js/reception_index.js
+1
-1
stock_movements.js
stock/static/js/stock_movements.js
+16
-10
helper.html
templates/orders/helper.html
+70
-12
No files found.
coops_configurations/config_lacagette.py
View file @
97eaabd2
...
...
@@ -109,3 +109,6 @@ RECEPTION_PB = "Ici, vous pouvez signaler toute anomalie lors d'une réception,
# display or not column "Autres" in reception process
DISPLAY_COL_AUTRES
=
False
# URL to the metabase dashboard for orders helper
ORDERS_HELPER_METABASE_URL
=
"https://metabase.lacagette-coop.fr/dashboard/16"
orders/models.py
View file @
97eaabd2
...
...
@@ -258,6 +258,11 @@ class Order(models.Model):
}
for
line
in
order_lines
:
product_line_name
=
line
[
"name"
]
if
"product_code"
in
line
and
line
[
"product_code"
]
is
not
False
:
product_code
=
str
(
line
[
"product_code"
])
product_line_name
=
"["
+
product_code
+
"] "
+
product_line_name
order_data
[
"order_line"
]
.
append
(
[
0
,
...
...
@@ -267,7 +272,7 @@ class Order(models.Model):
"price_policy"
:
"uom"
,
"indicative_package"
:
True
,
"product_id"
:
line
[
"product_variant_ids"
][
0
],
"name"
:
line
[
"name"
]
,
"name"
:
product_line_name
,
"date_planned"
:
date_planned
,
"account_analytic_id"
:
False
,
"product_qty_package"
:
line
[
"product_qty_package"
],
...
...
orders/static/css/oders_helper_style.css
View file @
97eaabd2
...
...
@@ -19,7 +19,7 @@
flex-direction
:
column
;
justify-content
:
center
;
align-items
:
center
;
padding
:
5px
30px
5
px
30px
;
padding
:
7px
30px
7
px
30px
;
margin
:
0
10px
5px
10px
;
}
...
...
@@ -31,6 +31,26 @@
background-color
:
#a1a2a3
;
}
.link_as_button
:hover
{
text-decoration
:
none
;
color
:
white
;
}
.link_as_button
:active
{
text-decoration
:
none
;
color
:
white
;
}
.link_as_button
:focus
{
text-decoration
:
none
;
color
:
white
;
}
.remove_order_modal_text
{
font-size
:
2rem
;
}
.remove_order_name
{
font-weight
:
bold
;
}
/* - Order selection screen */
#new_order_area
{
margin-bottom
:
40px
;
...
...
@@ -50,6 +70,25 @@
padding-top
:
15px
;
}
.order_pill
{
flex-direction
:
row
;
}
.pill_order_name
{
flex
:
3
0
auto
;
}
.remove_order_icon
{
flex
:
0
1
auto
;
color
:
#912930
;
margin-left
:
5px
;
cursor
:
pointer
;
z-index
:
2
;
transform
:
scale
(
1.2
);
}
.remove_order_icon
:hover
{
color
:
#e62720
;
}
.order_last_update
{
font-weight
:
bold
;
}
...
...
@@ -82,6 +121,40 @@
justify-content
:
flex-start
;
}
.right_action_buttons
{
display
:
flex
;
}
#actions_buttons_wrapper
{
position
:
relative
;
margin-right
:
5px
;
}
#toggle_action_buttons
{
width
:
250px
;
position
:
relative
;
}
.toggle_action_buttons_icon
{
position
:
absolute
;
top
:
50%
;
transform
:
translateY
(
-50%
);
right
:
15px
;
}
#actions_buttons_container
{
position
:
absolute
;
display
:
flex
;
flex-direction
:
column
;
width
:
250px
;
display
:
none
;
}
.action_button
{
width
:
100%
;
min-height
:
45px
;
}
/* -- Order data */
#order_data_container
{
font-size
:
1.8rem
;
...
...
@@ -92,17 +165,27 @@
}
#order_forms_container
{
margin-top
:
3
0px
;
margin-top
:
2
0px
;
display
:
flex
;
flex-wrap
:
wrap
;
justify-content
:
space-evenly
;
}
.order_form_item
{
margin-top
:
10px
;
}
#supplier_input
{
width
:
350px
;
border-radius
:
3px
;
}
#date_planned_input
,
#coverage_days_input
{
#stats_date_period_select
{
margin-left
:
5px
;
min-width
:
200px
;
}
#date_planned_input
,
#coverage_days_input
,
#stats_date_period_select
{
border-radius
:
3px
;
}
...
...
@@ -144,18 +227,27 @@
width
:
100px
;
}
.product_ref_input
{
padding
:
.5rem
.5rem
;
}
.supplier_package_qty
{
font-style
:
italic
;
font-size
:
1.3rem
;
}
.product_not_from_supplier
{
background-color
:
#e
7e9ed
;
background-color
:
#e
8ebf0
;
cursor
:
pointer
;
}
.product_not_from_supplier
:hover
{
background-color
:
#c7cace
;
background-color
:
#d3d7db
;
}
.product_ref_cell
:hover
{
background-color
:
#d3d7db
;
cursor
:
pointer
;
}
.product_name
,
.supplier_name
,
.product_npa
{
...
...
@@ -166,6 +258,10 @@
cursor
:
pointer
;
}
.focused_line
{
background-color
:
#76cf71
!important
;
}
/* -- Footer */
#main_content_footer
{
...
...
@@ -185,19 +281,24 @@
align-items
:
center
;
flex-wrap
:
wrap
;
margin
:
30px
0
20px
0
;
position
:
-webkit-sticky
;
position
:
sticky
;
top
:
20px
;
z-index
:
3
;
}
.supplier_pill
{
background-color
:
#
e7e9ed
c5
;
border
:
1px
solid
black
;
background-color
:
#
a0daff
;
border
:
1px
solid
#6ea8cc
;
}
.pill_supplier_name
{
font-weight
:
bold
;
}
.supplier_
total_value_container
{
.supplier_
data
{
font-size
:
1.5rem
;
display
:
flex
;
}
.remove_supplier_icon
{
...
...
@@ -253,19 +354,6 @@
margin-top
:
10px
;
}
.download_order_file_button
:hover
{
text-decoration
:
none
;
color
:
white
;
}
.download_order_file_button
:active
{
text-decoration
:
none
;
color
:
white
;
}
.download_order_file_button
:focus
{
text-decoration
:
none
;
color
:
white
;
}
#recap_delivery_date
{
font-weight
:
bold
;
}
...
...
orders/static/js/orders_helper.js
View file @
97eaabd2
...
...
@@ -16,6 +16,7 @@ var dbc = null,
order_doc
=
{
_id
:
null
,
coverage_days
:
null
,
stats_date_period
:
''
,
last_update
:
{
timestamp
:
null
,
fingerprint
:
null
...
...
@@ -42,6 +43,7 @@ function reset_data() {
order_doc
=
{
_id
:
null
,
coverage_days
:
null
,
stats_date_period
:
''
,
last_update
:
{
timestamp
:
null
,
fingerprint
:
null
...
...
@@ -81,6 +83,39 @@ function dates_diff(date1, date2) {
return
diff
;
}
/**
* Compute the date from which to calculate stats of sells,
* depending on the selected parameter.
*
* @returns String value of the date, ISO format
*/
function
_compute_stats_date_from
()
{
let
val
=
''
;
if
(
order_doc
.
stats_date_period
!==
''
)
{
let
date
=
new
Date
();
switch
(
order_doc
.
stats_date_period
)
{
case
'1week'
:
date
.
setDate
(
date
.
getDate
()
-
7
);
break
;
case
'2weeks'
:
date
.
setDate
(
date
.
getDate
()
-
14
);
break
;
default
:
break
;
}
let
day
=
(
"0"
+
date
.
getDate
()).
slice
(
-
2
);
let
month
=
(
"0"
+
(
date
.
getMonth
()
+
1
)).
slice
(
-
2
);
let
year
=
date
.
getFullYear
();
val
=
`
${
year
}
-
${
month
}
-
${
day
}
`
;
}
return
val
;
}
/* - PRODUCTS */
/**
...
...
@@ -110,10 +145,15 @@ function add_product() {
return
-
1
;
}
let
data
=
{
pids
:
[
product
.
tpl_id
],
stats_from
:
_compute_stats_date_from
()
};
$
.
ajax
({
type
:
'POST'
,
url
:
'/products/get_product_for_order_helper'
,
data
:
JSON
.
stringify
(
[
product
.
tpl_id
]
),
data
:
JSON
.
stringify
(
data
),
dataType
:
"json"
,
traditional
:
true
,
contentType
:
"application/json; charset=utf-8"
,
...
...
@@ -199,7 +239,8 @@ function check_products_data() {
type
:
'GET'
,
url
:
'/orders/get_supplier_products'
,
data
:
{
sids
:
suppliers_id
sids
:
suppliers_id
,
stats_from
:
_compute_stats_date_from
()
},
dataType
:
"json"
,
traditional
:
true
,
...
...
@@ -208,21 +249,24 @@ function check_products_data() {
for
(
let
product
of
data
.
res
.
products
)
{
const
p_index
=
products
.
findIndex
(
p
=>
p
.
id
==
product
.
id
);
// Override products data with new data (without suppliersinfo so we don't override qty)
const
updated_suppliersinfo
=
product
.
suppliersinfo
;
if
(
p_index
===
-
1
)
{
// Add product if it wasn't fetched before (made available since last access to order)
products
.
push
(
product
);
}
else
{
// Save old product suppliersinfo to keep user qty inputs
const
old_suppliersinfo
=
[...
products
[
p_index
].
suppliersinfo
];
delete
product
.
suppliersinfo
;
products
[
p_index
]
=
{
...
products
[
p_index
],
...
product
}
;
// Update product data
products
[
p_index
]
=
product
;
// Update suppliers info
for
(
let
psi_index
in
products
[
p_index
].
suppliersinfo
)
{
const
updated_psi
=
update
d_suppliersinfo
.
find
(
psi
=>
psi
.
supplier_id
==
products
[
p_index
].
suppliersinfo
[
psi_index
].
supplier_id
);
// Re-set qties
for
(
let
psi_index
in
products
[
p_index
].
suppliersinfo
)
{
const
old_psi
=
ol
d_suppliersinfo
.
find
(
psi
=>
psi
.
supplier_id
==
products
[
p_index
].
suppliersinfo
[
psi_index
].
supplier_id
);
if
(
updated_psi
!==
undefined
)
{
products
[
p_index
].
suppliersinfo
[
psi_index
].
package_qty
=
updated_psi
.
package_
qty
;
products
[
p_index
].
suppliersinfo
[
psi_index
].
price
=
updated_psi
.
price
;
if
(
old_psi
!==
undefined
&&
old_psi
.
qty
!==
undefined
)
{
products
[
p_index
].
suppliersinfo
[
psi_index
].
qty
=
old_psi
.
qty
;
}
}
}
}
...
...
@@ -248,6 +292,73 @@ function check_products_data() {
});
}
/**
* Update the product internal reference ('default_code')
*
* @param {HTMLElement} input_el
* @param {int} p_id
* @param {int} p_index
*/
function
update_product_ref
(
input_el
,
p_id
,
p_index
)
{
const
val
=
$
(
input_el
).
val
();
const
existing_val
=
products
[
p_index
].
default_code
.
replace
(
"[input]"
,
""
);
products
[
p_index
].
default_code
=
val
;
const
row
=
$
(
input_el
).
closest
(
'tr'
);
const
new_row_data
=
prepare_datatable_data
([
p_id
])[
0
];
products_table
.
row
(
row
).
data
(
new_row_data
)
.
draw
();
$
(
'#products_table'
)
.
off
(
'blur'
,
'tbody .product_ref_input'
)
.
off
(
'keypress'
,
'tbody .product_ref_input'
);
// Update in backend if value changed
if
(
existing_val
!==
val
)
{
const
data
=
{
'product_tmpl_id'
:
p_id
,
'default_code'
:
val
};
// Send request to create association
$
.
ajax
({
type
:
"POST"
,
url
:
"/products/update_product_internal_ref"
,
dataType
:
"json"
,
traditional
:
true
,
contentType
:
"application/json; charset=utf-8"
,
data
:
JSON
.
stringify
(
data
),
success
:
()
=>
{
update_cdb_order
();
$
(
".actions_buttons_area .right_action_buttons"
).
notify
(
"Référence sauvegardée !"
,
{
elementPosition
:
"bottom right"
,
className
:
"success"
,
arrowShow
:
false
}
);
},
error
:
function
(
data
)
{
let
msg
=
"erreur serveur lors de la sauvegarde de la référence"
;
msg
+=
` (product_tmpl_id:
${
product
.
id
}
`
;
err
=
{
msg
:
msg
,
ctx
:
'update_product_ref'
};
if
(
typeof
data
.
responseJSON
!=
'undefined'
&&
typeof
data
.
responseJSON
.
error
!=
'undefined'
)
{
err
.
msg
+=
' : '
+
data
.
responseJSON
.
error
;
}
report_JS_error
(
err
,
'orders'
);
alert
(
'Erreur lors de la sauvegarde de la référence dans Odoo. Veuillez recharger la page et ré-essayer plus tard.'
);
}
});
}
}
/* - SUPPLIERS */
...
...
@@ -259,9 +370,9 @@ function check_products_data() {
function
add_supplier
()
{
const
user_input
=
$
(
"#supplier_input"
).
val
();
// Check if user input is a valid supplier
let
supplier
=
suppliers_list
.
find
(
s
=>
s
.
display_name
===
user_input
);
// Check if user input is a valid supplier
if
(
supplier
===
undefined
)
{
alert
(
"Le fournisseur renseigné n'est pas valide.
\
n"
+
"Veuillez sélectionner un fournisseur dans la liste déroulante."
);
...
...
@@ -279,21 +390,22 @@ function add_supplier() {
openModal
();
supplier
.
total_value
=
0
;
selected_suppliers
.
push
(
supplier
);
let
url
=
"/orders/get_supplier_products"
;
url
+=
"?sids="
+
encodeURIComponent
(
supplier
.
id
);
// Fetch supplier products
$
.
ajax
({
type
:
'GET'
,
url
:
url
,
url
:
"/orders/get_supplier_products"
,
data
:
{
sids
:
[
supplier
.
id
],
stats_from
:
_compute_stats_date_from
()
},
dataType
:
"json"
,
traditional
:
true
,
contentType
:
"application/json; charset=utf-8"
,
success
:
function
(
data
)
{
supplier
.
total_value
=
0
;
supplier
.
total_packages
=
0
;
selected_suppliers
.
push
(
supplier
);
save_supplier_products
(
supplier
,
data
.
res
.
products
);
update_main_screen
();
$
(
"#supplier_input"
).
val
(
""
);
...
...
@@ -385,6 +497,7 @@ function save_supplier_product_association(product, supplier, cell) {
product
.
suppliersinfo
.
push
({
supplier_id
:
supplier
.
id
,
package_qty
:
package_qty
,
product_code
:
false
,
price
:
price
});
...
...
@@ -403,8 +516,9 @@ function save_supplier_product_association(product, supplier, cell) {
closeModal
();
},
error
:
function
(
data
)
{
let
msg
=
"erreur serveur lors de la sauvegarde de l'association product/supplier"
.
msg
+=
` (product_tmpl_id:
${
product
.
id
}
; supplier_id:
${
supplier
.
id
}
)`
;
let
msg
=
"erreur serveur lors de la sauvegarde de l'association product/supplier"
;
msg
+=
` (product_tmpl_id:
${
product
.
id
}
; supplier_id:
${
supplier
.
id
}
)`
;
err
=
{
msg
:
msg
,
ctx
:
'save_supplier_product_association'
};
if
(
typeof
data
.
responseJSON
!=
'undefined'
&&
typeof
data
.
responseJSON
.
error
!=
'undefined'
)
{
...
...
@@ -421,6 +535,59 @@ function save_supplier_product_association(product, supplier, cell) {
}
/**
* Send to server the deletion of association product-supplier
*
* @param {object} product
* @param {object} supplier
*/
function
end_supplier_product_association
(
product
,
supplier
)
{
openModal
();
const
data
=
{
product_tmpl_id
:
product
.
id
,
supplier_id
:
supplier
.
id
};
// Send request to create association
$
.
ajax
({
type
:
"POST"
,
url
:
"/orders/end_supplier_product_association"
,
dataType
:
"json"
,
traditional
:
true
,
contentType
:
"application/json; charset=utf-8"
,
data
:
JSON
.
stringify
(
data
),
success
:
()
=>
{
// Remove relation locally
let
p_index
=
products
.
findIndex
(
p
=>
p
.
id
==
product
.
id
);
let
psi_index
=
product
.
suppliersinfo
.
findIndex
(
psi
=>
psi
.
supplier_id
==
supplier
.
id
);
products
[
p_index
].
suppliersinfo
.
splice
(
psi_index
,
1
);
// Update table
display_products
();
update_cdb_order
();
closeModal
();
},
error
:
function
(
data
)
{
let
msg
=
"erreur serveur lors de la suppression de l'association product/supplier"
.
msg
+=
` (product_tmpl_id:
${
product
.
id
}
; supplier_id:
${
supplier
.
id
}
)`
;
err
=
{
msg
:
msg
,
ctx
:
'end_supplier_product_association'
};
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 suppression de l
\'
association. Veuillez ré-essayer plus tard.'
);
}
});
return
0
;
}
/**
* When products are fetched, save them and the relation with the supplier.
* If product already saved, add the supplier to its suppliers list.
* Else, add product with supplier.
...
...
@@ -435,11 +602,13 @@ function save_supplier_products(supplier, new_products) {
if
(
index
===
-
1
)
{
products
.
push
(
np
);
}
else
{
// Prevent adding du
c
plicate supplierinfo
// Prevent adding duplicate supplierinfo
let
index_existing_supplierinfo
=
products
[
index
].
suppliersinfo
.
findIndex
(
psi
=>
psi
.
supplier_id
==
supplier
.
id
);
if
(
index_existing_supplierinfo
===
-
1
)
{
np_supplierinfo
=
np
.
suppliersinfo
[
0
];
// Find the right supplierinfo in new product
let
np_supplierinfo
=
np
.
suppliersinfo
.
find
(
psi
=>
psi
.
supplier_id
==
supplier
.
id
);
products
[
index
].
suppliersinfo
.
push
(
np_supplierinfo
);
}
}
...
...
@@ -484,15 +653,20 @@ function _compute_total_values_by_supplier() {
// Reinit
for
(
let
s
of
selected_suppliers
)
{
s
.
total_value
=
0
;
s
.
total_packages
=
0
;
}
for
(
let
p
of
products
)
{
for
(
let
supinfo
of
p
.
suppliersinfo
)
{
let
supplier_index
=
selected_suppliers
.
findIndex
(
s
=>
s
.
id
==
supinfo
.
supplier_id
);
// Value
let
product_supplier_value
=
(
'qty'
in
supinfo
)
?
supinfo
.
qty
*
supinfo
.
package_qty
*
supinfo
.
price
:
0
;
selected_suppliers
[
supplier_index
].
total_value
+=
product_supplier_value
;
// Packages
selected_suppliers
[
supplier_index
].
total_packages
+=
(
'qty'
in
supinfo
)
?
supinfo
.
qty
:
0
;
}
}
}
...
...
@@ -526,11 +700,12 @@ function set_product_npa(p_id, npa) {
// Give time for modal to fade
setTimeout
(
function
()
{
$
.
notify
(
$
(
".actions_buttons_area .right_action_buttons"
)
.
notify
(
"Produit passé en NPA !"
,
{
globalPosition
:
"top right"
,
className
:
"success"
elementPosition
:
"bottom right"
,
className
:
"success"
,
arrowShow
:
false
}
);
},
500
);
...
...
@@ -596,7 +771,7 @@ function generate_inventory() {
modal_create_inventory
.
html
(),
()
=>
{
if
(
is_time_to
(
'validate_generate_inventory'
))
{
$
(
'#
do_inventory
'
).
empty
()
$
(
'#
toggle_action_buttons .button_content
'
).
empty
()
.
append
(
`<i class="fas fa-spinner fa-spin"></i>`
);
$
.
ajax
({
type
:
"POST"
,
...
...
@@ -610,12 +785,12 @@ function generate_inventory() {
// Give time for modal to fade
setTimeout
(
function
()
{
$
(
'#
do_inventory
'
).
empty
()
.
append
(
`
Faire un inventaire
`
);
$
(
'#
do_inventory
'
).
notify
(
$
(
'#
toggle_action_buttons .button_content
'
).
empty
()
.
append
(
`
Actions
`
);
$
(
'#
toggle_action_buttons
'
).
notify
(
"Inventaire créé !"
,
{
global
Position
:
"bottom center"
,
element
Position
:
"bottom center"
,
className
:
"success"
}
);
...
...
@@ -733,6 +908,8 @@ function create_cdb_order() {
/**
* Update order data of an existing order in couchdb
*
* @returns Promise resolved after update is complete
*/
function
update_cdb_order
()
{
order_doc
.
products
=
products
;
...
...
@@ -761,6 +938,28 @@ function update_cdb_order() {
}
/**
* Delete an order in couchdb.
*
* @returns Promise resolved after delete is complete
*/
function
delete_cdb_order
()
{
order_doc
.
_deleted
=
true
;
return
new
Promise
((
resolve
,
reject
)
=>
{
dbc
.
put
(
order_doc
,
function
callback
(
err
,
result
)
{
if
(
!
err
&&
result
!==
undefined
)
{
resolve
();
}
else
{
alert
(
"Erreur lors de la suppression de la commande... Si l'erreur persiste contactez un administrateur svp."
);
console
.
log
(
err
);
reject
(
new
Error
(
"Error while deleting order"
));
}
});
});
}
/**
* Create the Product Orders in Odoo
*/
function
create_orders
()
{
...
...
@@ -810,6 +1009,7 @@ function create_orders() {
// If a qty is set for a supplier for a product
if
(
'qty'
in
p_supplierinfo
&&
p_supplierinfo
.
qty
!=
0
)
{
const
supplier_id
=
p_supplierinfo
.
supplier_id
;
const
product_code
=
p_supplierinfo
.
product_code
;
orders_data
.
suppliers_data
[
supplier_id
].
lines
.
push
({
'package_qty'
:
p_supplierinfo
.
package_qty
,
...
...
@@ -820,7 +1020,8 @@ function create_orders() {
'product_uom'
:
p
.
uom_id
[
0
],
'price_unit'
:
p_supplierinfo
.
price
,
'supplier_taxes_id'
:
p
.
supplier_taxes_id
,
'product_variant_ids'
:
p
.
product_variant_ids
'product_variant_ids'
:
p
.
product_variant_ids
,
'product_code'
:
product_code
});
}
}
...
...
@@ -866,13 +1067,14 @@ function create_orders() {
get_order_attachments
();
// Clear data
order_doc
.
_deleted
=
true
;
update_cdb_order
().
then
(()
=>
{
update_order_selection_screen
();
delete_cdb_order
().
finally
(()
=>
{
// Continue with workflow anyway
update_order_selection_screen
().
then
(()
=>
{
reset_data
();
switch_screen
(
'orders_created'
);
closeModal
();
});
});
reset_data
();
switch_screen
(
'orders_created'
);
closeModal
();
},
error
:
function
(
data
)
{
let
msg
=
"erreur serveur lors de la création des product orders"
;
...
...
@@ -1128,9 +1330,20 @@ function prepare_datatable_columns() {
{
data
:
"default_code"
,
title
:
"Ref"
,
width
:
"6%"
,
render
:
function
(
data
)
{
return
(
data
===
false
)
?
""
:
data
;
width
:
"8%"
,
render
:
function
(
data
,
type
,
full
)
{
if
(
data
===
false
)
{
return
""
;
}
else
if
(
data
.
includes
(
"[input]"
))
{
let
val
=
data
.
replace
(
"[input]"
,
""
);
return
`<div class="custom_cell_content">
<input type="text" class="product_ref_input" id="
${
full
.
id
}
_ref_input" value="
${
val
}
">
</div>`
;
}
else
{
return
data
;
}
}
},
{
...
...
@@ -1173,7 +1386,7 @@ function prepare_datatable_columns() {
return
`<div id="
${
base_id
}
_cell_content" class="custom_cell_content">X</div>`
;
}
else
{
let
content
=
`<div id="
${
base_id
}
_cell_content" class="custom_cell_content">
<input type="number" class="product_qty_input" id="
${
base_id
}
_qty_input" min="
0
" value=
${
data
}
>`
;
<input type="number" class="product_qty_input" id="
${
base_id
}
_qty_input" min="
-1
" value=
${
data
}
>`
;
if
(
full
.
package_qty
===
'X'
)
{
let
product_data
=
products
.
find
(
p
=>
p
.
id
==
full
.
id
);
...
...
@@ -1254,8 +1467,11 @@ function display_products(params) {
return
-
1
;
}
//
Empty datatable if it already exis
ts
//
If datatable already exists, empty & clear even
ts
if
(
products_table
)
{
$
(
products_table
.
table
().
header
()).
off
();
$
(
'#products_table'
).
off
();
products_table
.
clear
().
destroy
();
$
(
'#products_table'
).
empty
();
}
...
...
@@ -1276,7 +1492,6 @@ function display_products(params) {
sort_order_dir
]
],
stripeClasses
:
[],
// Remove datatable cells coloring
orderClasses
:
false
,
aLengthMenu
:
[
[
...
...
@@ -1298,13 +1513,15 @@ function display_products(params) {
scrollX
:
true
,
language
:
{
url
:
'/static/js/datatables/french.json'
},
createdRow
:
function
(
row
)
{
for
(
const
cell_node
of
row
.
cells
)
{
for
(
var
i
=
0
;
i
<
row
.
cells
.
length
;
i
++
)
{
const
cell_node
=
row
.
cells
[
i
];
const
cell
=
$
(
cell_node
);
if
(
cell
.
hasClass
(
"supplier_input_cell"
))
{
if
(
cell
.
text
()
==
"X"
)
{
cell
.
addClass
(
'product_not_from_supplier'
);
}
if
(
cell
.
hasClass
(
"supplier_input_cell"
)
&&
cell
.
text
()
===
"X"
)
{
cell
.
addClass
(
'product_not_from_supplier'
);
}
else
if
(
i
===
1
)
{
// Column at index 1 is product reference
cell
.
addClass
(
'product_ref_cell'
);
}
}
}
...
...
@@ -1314,35 +1531,86 @@ function display_products(params) {
$
(
'#main_content_footer'
).
show
();
$
(
'#do_inventory'
).
show
();
//
On inputs change
$
(
'#products_table'
).
on
(
'
change
'
,
'tbody td .product_qty_input'
,
function
()
{
let
val
=
(
$
(
this
).
val
()
==
''
)
?
0
:
$
(
this
).
val
(
);
//
Color line on input focus
$
(
'#products_table'
).
on
(
'
focus
'
,
'tbody td .product_qty_input'
,
function
()
{
const
row
=
$
(
this
).
closest
(
'tr'
);
val
=
parseFloat
(
val
);
row
.
addClass
(
'focused_line'
);
});
// Manage data on inputs blur
$
(
'#products_table'
).
on
(
'blur'
,
'tbody td .product_qty_input'
,
function
()
{
// Remove line coloring on input blur
const
row
=
$
(
this
).
closest
(
'tr'
);
// If value is a number
if
(
!
isNaN
(
val
))
{
const
id_split
=
$
(
this
).
attr
(
'id'
)
.
split
(
'_'
);
const
prod_id
=
id_split
[
1
];
const
supplier_id
=
id_split
[
3
];
row
.
removeClass
(
'focused_line'
);
// Save value
save_product_supplier_qty
(
prod_id
,
supplier_id
,
val
);
let
val
=
(
$
(
this
).
val
()
==
''
)
?
0
:
$
(
this
).
val
();
const
id_split
=
$
(
this
).
attr
(
'id'
)
.
split
(
'_'
);
const
prod_id
=
id_split
[
1
];
const
supplier_id
=
id_split
[
3
];
if
(
val
==
-
1
)
{
let
modal_end_supplier_product_association
=
$
(
'#templates #modal_end_supplier_product_association'
);
// Update row
const
product
=
products
.
find
(
p
=>
p
.
id
==
prod_id
);
const
new_row_data
=
prepare_datatable_data
([
product
.
id
])[
0
];
products_table
.
row
(
$
(
this
).
closest
(
'tr'
)).
data
(
new_row_data
)
.
draw
(
);
modal_end_supplier_product_association
.
find
(
".product_name"
).
text
(
product
.
name
);
const
supplier
=
selected_suppliers
.
find
(
s
=>
s
.
id
==
supplier_id
);
update_cdb_order
();
display_total_values
();
modal_end_supplier_product_association
.
find
(
".supplier_name"
).
text
(
supplier
.
display_name
);
openModal
(
modal_end_supplier_product_association
.
html
(),
()
=>
{
if
(
is_time_to
(
'validate_end_supplier_product_association'
))
{
end_supplier_product_association
(
product
,
supplier
);
}
},
'Valider'
,
false
,
true
,
()
=>
{
// Reset value in input on cancel
const
psi
=
product
.
suppliersinfo
.
find
(
psi_item
=>
psi_item
.
supplier_id
==
supplier_id
);
$
(
this
).
val
(
psi
.
qty
);
}
);
}
else
{
$
(
this
).
val
(
''
);
val
=
parseFloat
(
val
);
// If value is a number
if
(
!
isNaN
(
val
))
{
// Save value
save_product_supplier_qty
(
prod_id
,
supplier_id
,
val
);
// Update row
const
product
=
products
.
find
(
p
=>
p
.
id
==
prod_id
);
const
new_row_data
=
prepare_datatable_data
([
product
.
id
])[
0
];
products_table
.
row
(
$
(
this
).
closest
(
'tr'
)).
data
(
new_row_data
)
.
draw
();
update_cdb_order
();
display_total_values
();
}
else
{
$
(
this
).
val
(
''
);
}
}
});
})
.
on
(
'change'
,
'tbody td .product_qty_input'
,
function
()
{
// Since data change is saved on blur, set focus on change in case of arrows pressed
$
(
this
).
focus
();
})
.
on
(
'keypress'
,
'tbody td .product_qty_input'
,
function
(
e
)
{
if
(
e
.
which
==
13
)
{
// Validate on Enter pressed
$
(
this
).
blur
();
}
});
// Associate product to supplier on click on 'X' in the table
$
(
'#products_table'
).
on
(
'click'
,
'tbody .product_not_from_supplier'
,
function
()
{
...
...
@@ -1402,6 +1670,41 @@ function display_products(params) {
new_product_supplier_association
.
package_qty
=
$
(
this
).
val
();
});
});
// Display input on click on product ref cell
$
(
'#products_table'
).
on
(
'click'
,
'tbody .product_ref_cell'
,
function
()
{
if
(
$
(
this
).
find
(
'input'
).
length
===
0
)
{
const
row
=
$
(
this
).
closest
(
'tr'
);
const
p_id
=
products_table
.
row
(
row
).
data
().
id
;
const
p_index
=
products
.
findIndex
(
p
=>
p
.
id
===
p_id
);
const
existing_ref
=
products
[
p_index
].
default_code
===
false
?
''
:
products
[
p_index
].
default_code
;
products
[
p_index
].
default_code
=
"[input]"
+
existing_ref
;
const
new_row_data
=
prepare_datatable_data
([
p_id
])[
0
];
products_table
.
row
(
row
).
data
(
new_row_data
)
.
draw
();
let
ref_input
=
$
(
`#
${
p_id
}
_ref_input`
);
ref_input
.
focus
();
ref_input
.
select
();
$
(
'#products_table'
)
.
on
(
'blur'
,
'tbody .product_ref_input'
,
function
()
{
update_product_ref
(
this
,
p_id
,
p_index
);
})
.
on
(
'keypress'
,
'tbody .product_ref_input'
,
function
(
e
)
{
// Validate on Enter pressed
if
(
e
.
which
==
13
)
{
update_product_ref
(
this
,
p_id
,
p_index
);
}
});
}
});
// Select row(s) on checkbox change
$
(
products_table
.
table
().
header
()).
on
(
'click'
,
'th #select_all_products_cb'
,
function
()
{
if
(
this
.
checked
)
{
...
...
@@ -1474,6 +1777,8 @@ function display_products(params) {
* Unselect all rows from datatable.
*/
function
unselect_all_rows
()
{
$
(
"#select_all_products_cb"
).
prop
(
"checked"
,
false
);
products_table
.
rows
().
every
(
function
()
{
const
node
=
$
(
this
.
node
());
...
...
@@ -1499,6 +1804,9 @@ function display_total_values() {
$
(
`#pill_supplier_
${
supplier
.
id
}
`
).
find
(
'.supplier_total_value'
)
.
text
(
parseFloat
(
supplier
.
total_value
).
toFixed
(
2
));
order_total_value
+=
supplier
.
total_value
;
$
(
`#pill_supplier_
${
supplier
.
id
}
`
).
find
(
'.supplier_total_packages'
)
.
text
(
+
parseFloat
(
supplier
.
total_packages
).
toFixed
(
2
));
}
order_total_value
=
parseFloat
(
order_total_value
).
toFixed
(
2
);
...
...
@@ -1510,8 +1818,11 @@ function display_total_values() {
*/
function
update_main_screen
(
params
)
{
// Remove listener before recreating them
$
(
'#products_table'
).
off
(
'focus'
,
'tbody td .product_qty_input'
);
$
(
'#products_table'
).
off
(
'blur'
,
'tbody td .product_qty_input'
);
$
(
'#products_table'
).
off
(
'change'
,
'tbody td .product_qty_input'
);
$
(
'#products_table'
).
off
(
'click'
,
'tbody .product_not_from_supplier'
);
$
(
'#products_table'
).
off
(
'click'
,
'tbody .product_ref_cell'
);
$
(
'#products_table'
).
off
(
'click'
,
'thead th #select_all_products_cb'
);
$
(
'#products_table'
).
off
(
'click'
,
'tbody td .select_product_cb'
);
$
(
".remove_supplier_icon"
).
off
();
...
...
@@ -1542,41 +1853,91 @@ function update_main_screen(params) {
}
else
{
$
(
"#coverage_days_input"
).
val
(
''
);
}
if
(
order_doc
.
stats_date_period
!==
undefined
&&
order_doc
.
stats_date_period
!==
null
)
{
$
(
"#stats_date_period_select"
).
val
(
order_doc
.
stats_date_period
);
}
else
{
$
(
"#stats_date_period_select"
).
val
(
''
);
}
}
/**
* Update DOM display on the order selection screen
*/
function
update_order_selection_screen
()
{
dbc
.
allDocs
({
include_docs
:
true
}).
then
(
function
(
result
)
{
// Remove listener before recreating them
$
(
".order_pill"
).
off
();
return
new
Promise
((
resolve
)
=>
{
dbc
.
allDocs
({
include_docs
:
true
})
.
then
(
function
(
result
)
{
// Remove listener before recreating them
$
(
".order_pill"
).
off
();
let
existing_orders_container
=
$
(
"#existing_orders"
);
let
existing_orders_container
=
$
(
"#existing_orders"
);
existing_orders_container
.
empty
();
$
(
'#new_order_name'
).
val
(
''
);
existing_orders_container
.
empty
();
$
(
'#new_order_name'
).
val
(
''
);
if
(
result
.
rows
.
length
===
0
)
{
existing_orders_container
.
append
(
`<i>Aucune commande en cours...</i>`
);
}
else
{
for
(
let
row
of
result
.
rows
)
{
let
template
=
$
(
"#templates #order_pill_template"
);
if
(
result
.
rows
.
length
===
0
)
{
existing_orders_container
.
append
(
`<i>Aucune commande en cours...</i>`
);
}
else
{
for
(
let
row
of
result
.
rows
)
{
let
template
=
$
(
"#templates #order_pill_template"
);
template
.
find
(
".pill_order_name"
).
text
(
row
.
id
);
template
.
find
(
".pill_order_name"
).
text
(
row
.
id
);
existing_orders_container
.
append
(
template
.
html
());
}
existing_orders_container
.
append
(
template
.
html
());
}
$
(
".order_pill"
).
on
(
"click"
,
order_pill_on_click
);
}
})
.
catch
(
function
(
err
)
{
alert
(
'Erreur lors de la synchronisation des commandes. Vous pouvez créer une nouvelle commande.'
);
console
.
log
(
err
);
});
$
(
".order_pill"
).
on
(
"click"
,
order_pill_on_click
);
$
(
".remove_order_icon"
).
on
(
"click"
,
function
(
e
)
{
e
.
preventDefault
();
e
.
stopImmediatePropagation
();
order_name_container
=
$
(
this
).
prev
()[
0
];
let
order_id
=
$
(
order_name_container
).
text
();
let
modal_remove_order
=
$
(
'#templates #modal_remove_order'
);
modal_remove_order
.
find
(
".remove_order_name"
).
text
(
order_id
);
openModal
(
modal_remove_order
.
html
(),
()
=>
{
if
(
is_time_to
(
'validate_remove_order'
))
{
dbc
.
get
(
order_id
).
then
((
doc
)
=>
{
order_doc
=
doc
;
delete_cdb_order
().
then
(()
=>
{
update_order_selection_screen
().
then
(()
=>
{
reset_data
();
setTimeout
(
function
()
{
$
.
notify
(
"Commande supprimée !"
,
{
globalPosition
:
"top left"
,
className
:
"success"
}
);
},
500
);
});
})
.
catch
(()
=>
{
console
.
log
(
"error deleting order"
);
});
});
}
},
'Valider'
);
});
}
resolve
();
})
.
catch
(
function
(
err
)
{
alert
(
'Erreur lors de la synchronisation des commandes. Vous pouvez créer une nouvelle commande.'
);
console
.
log
(
err
);
});
});
}
/**
...
...
@@ -1679,6 +2040,10 @@ $(document).ready(function() {
init_pouchdb_sync
();
// Main screen
if
(
metabase_url
!==
''
)
{
$
(
'#access_metabase'
).
show
();
}
$
(
"#coverage_form"
).
on
(
"submit"
,
function
(
e
)
{
e
.
preventDefault
();
if
(
is_time_to
(
'submit_coverage_form'
,
1000
))
{
...
...
@@ -1698,6 +2063,31 @@ $(document).ready(function() {
}
});
$
(
"#toggle_action_buttons"
).
on
(
"click"
,
function
()
{
if
(
$
(
'#actions_buttons_container'
).
is
(
":visible"
))
{
$
(
'#actions_buttons_container'
).
hide
();
$
(
'.toggle_action_buttons_icon'
).
empty
()
.
append
(
'<i class="fas fa-chevron-down"></i>'
);
}
else
{
$
(
'#actions_buttons_container'
).
show
();
$
(
'.toggle_action_buttons_icon'
).
empty
()
.
append
(
'<i class="fas fa-chevron-up"></i>'
);
}
});
// Close dropdown menu on click outside
$
(
document
).
click
(
function
(
event
)
{
let
target
=
$
(
event
.
target
);
if
(
!
target
.
closest
(
'#actions_buttons_wrapper'
).
length
&&
$
(
'#actions_buttons_container'
).
is
(
":visible"
)
)
{
$
(
'#actions_buttons_container'
).
hide
();
$
(
'.toggle_action_buttons_icon'
).
empty
()
.
append
(
'<i class="fas fa-chevron-down"></i>'
);
}
});
$
(
"#supplier_form"
).
on
(
"submit"
,
function
(
e
)
{
e
.
preventDefault
();
...
...
@@ -1713,12 +2103,64 @@ $(document).ready(function() {
}
});
$
(
"#stats_date_period_select"
).
on
(
"change"
,
function
(
e
)
{
e
.
preventDefault
();
if
(
is_time_to
(
'change_stats_date_period'
,
1000
))
{
openModal
();
order_doc
.
stats_date_period
=
$
(
this
).
val
();
check_products_data
()
.
then
(()
=>
{
update_cdb_order
();
update_main_screen
();
closeModal
();
});
}
});
$
(
"#do_inventory"
).
on
(
"click"
,
function
()
{
if
(
is_time_to
(
'generate_inventory'
,
1000
))
{
generate_inventory
();
}
});
$
(
"#delete_order_button"
).
on
(
"click"
,
function
()
{
if
(
is_time_to
(
'press_delete_order_button'
,
1000
))
{
let
modal_remove_order
=
$
(
'#templates #modal_remove_order'
);
modal_remove_order
.
find
(
".remove_order_name"
).
text
(
order_doc
.
_id
);
openModal
(
modal_remove_order
.
html
(),
()
=>
{
if
(
is_time_to
(
'validate_remove_order'
))
{
delete_cdb_order
().
then
(()
=>
{
update_order_selection_screen
().
then
(()
=>
{
reset_data
();
switch_screen
(
'order_selection'
);
setTimeout
(
function
()
{
$
.
notify
(
"Commande supprimée !"
,
{
globalPosition
:
"top left"
,
className
:
"success"
}
);
},
500
);
});
})
.
catch
(()
=>
{
console
.
log
(
"error deleting order"
);
});
}
},
'Valider'
);
}
});
$
(
'#back_to_order_selection_from_main'
).
on
(
'click'
,
function
()
{
if
(
is_time_to
(
'back_to_order_selection_from_main'
,
1000
))
{
back
();
...
...
orders/urls.py
View file @
97eaabd2
...
...
@@ -13,6 +13,7 @@ urlpatterns = [
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
),
url
(
r'^end_supplier_product_association$'
,
views
.
end_supplier_product_association
),
url
(
r'^create_orders$'
,
views
.
create_orders
),
url
(
r'^get_orders_attachment$'
,
views
.
get_orders_attachment
),
]
orders/views.py
View file @
97eaabd2
...
...
@@ -19,7 +19,8 @@ def helper(request):
'title'
:
'Aide à la commande'
,
'couchdb_server'
:
settings
.
COUCHDB
[
'url'
],
'db'
:
settings
.
COUCHDB
[
'dbs'
][
'orders'
],
'odoo_server'
:
settings
.
ODOO
[
'url'
]
'odoo_server'
:
settings
.
ODOO
[
'url'
],
'metabase_url'
:
getattr
(
settings
,
'ORDERS_HELPER_METABASE_URL'
,
''
)
}
template
=
loader
.
get_template
(
'orders/helper.html'
)
...
...
@@ -42,7 +43,8 @@ def get_supplier_products(request):
""" Get supplier products """
suppliers_id
=
request
.
GET
.
getlist
(
'sids'
,
''
)
res
=
CagetteProducts
.
get_products_for_order_helper
(
suppliers_id
)
stats_from
=
request
.
GET
.
get
(
'stats_from'
)
res
=
CagetteProducts
.
get_products_for_order_helper
(
suppliers_id
,
[],
stats_from
)
if
'error'
in
res
:
return
JsonResponse
(
res
,
status
=
500
)
...
...
@@ -52,15 +54,29 @@ def get_supplier_products(request):
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
)
except
Exception
as
e
:
res
[
"error"
]
=
str
(
e
)
data
=
json
.
loads
(
request
.
body
.
decode
())
res
=
CagetteProduct
.
associate_supplier_to_product
(
data
)
if
'error'
in
res
:
return
JsonResponse
(
res
,
status
=
500
)
else
:
return
JsonResponse
({
'res'
:
res
})
return
JsonResponse
({
'res'
:
res
})
def
end_supplier_product_association
(
request
):
""" This product is now unavailable from this supplier """
res
=
{}
data
=
json
.
loads
(
request
.
body
.
decode
())
res
=
CagetteProduct
.
end_supplier_product_association
(
data
)
if
'error'
in
res
:
return
JsonResponse
(
res
,
status
=
500
)
else
:
return
JsonResponse
({
'res'
:
res
})
def
create_orders
(
request
):
""" Create products orders """
res
=
{
"created"
:
[]
}
...
...
products/models.py
View file @
97eaabd2
...
...
@@ -130,6 +130,7 @@ class CagetteProduct(models.Model):
@staticmethod
def
associate_supplier_to_product
(
data
):
api
=
OdooAPI
()
res
=
{}
product_tmpl_id
=
data
[
"product_tmpl_id"
]
partner_id
=
data
[
"supplier_id"
]
...
...
@@ -140,6 +141,8 @@ class CagetteProduct(models.Model):
c
=
[[
'product_tmpl_id'
,
'='
,
product_tmpl_id
]]
res_products
=
api
.
search_read
(
'product.product'
,
c
,
f
)
product
=
res_products
[
0
]
today
=
datetime
.
date
.
today
()
.
strftime
(
"
%
Y-
%
m-
%
d"
)
f
=
{
'product_tmpl_id'
:
product_tmpl_id
,
...
...
@@ -149,9 +152,40 @@ class CagetteProduct(models.Model):
'price'
:
price
,
'base_price'
:
price
,
'package_qty'
:
package_qty
,
'date_start'
:
today
,
'sequence'
:
1000
# lowest priority for the new suppliers
}
res
=
api
.
create
(
'product.supplierinfo'
,
f
)
try
:
res
[
'create'
]
=
api
.
create
(
'product.supplierinfo'
,
f
)
except
Exception
as
e
:
res
[
'error'
]
=
str
(
e
)
return
res
@staticmethod
def
end_supplier_product_association
(
data
):
api
=
OdooAPI
()
res
=
{}
product_tmpl_id
=
data
[
"product_tmpl_id"
]
partner_id
=
data
[
"supplier_id"
]
f
=
[
"id"
]
c
=
[[
'product_tmpl_id'
,
'='
,
product_tmpl_id
],
[
'name'
,
'='
,
partner_id
],
[
'date_end'
,
'='
,
False
]]
res_supplierinfo
=
api
.
search_read
(
'product.supplierinfo'
,
c
,
f
)
psi_id
=
res_supplierinfo
[
0
][
'id'
]
today
=
datetime
.
date
.
today
()
.
strftime
(
"
%
Y-
%
m-
%
d"
)
f
=
{
'date_end'
:
today
}
try
:
res
[
"update"
]
=
api
.
update
(
'product.supplierinfo'
,
psi_id
,
f
)
except
Exception
as
e
:
res
[
'error'
]
=
str
(
e
)
return
res
...
...
@@ -172,6 +206,23 @@ class CagetteProduct(models.Model):
return
res
@staticmethod
def
update_product_internal_ref
(
product_tmpl_id
,
default_code
):
api
=
OdooAPI
()
res
=
{}
f
=
{
'default_code'
:
default_code
}
try
:
res
[
"update"
]
=
api
.
update
(
'product.template'
,
product_tmpl_id
,
f
)
except
Exception
as
e
:
res
[
"error"
]
=
str
(
e
)
print
(
str
(
e
))
return
res
class
CagetteProducts
(
models
.
Model
):
"""Initially used to make massive barcode update."""
...
...
@@ -456,31 +507,32 @@ class CagetteProducts(models.Model):
return
res
@staticmethod
def
get_products_for_order_helper
(
supplier_ids
,
pids
=
[]):
def
get_products_for_order_helper
(
supplier_ids
,
pids
=
[]
,
stats_from
=
None
):
"""
One of the two parameters must be not empty
.
Get products by supplier if one or more supplier_id is set.
If supplier_ids is empty, get products specified in pids. In this case, suppliers info won't be fetched
.
supplier_ids: Get products by supplier if one or more supplier id is set. If set, pids is ignored
.
pids: If set & supplier_ids is None/empty, get products specified in pids. In this case, suppliers info won't be fetched.
stats_from: date from which we should calculate sells stats
.
"""
api
=
OdooAPI
()
res
=
{}
# todo : try with no result
try
:
today
=
datetime
.
date
.
today
()
.
strftime
(
"
%
Y-
%
m-
%
d"
)
if
supplier_ids
is
not
None
and
len
(
supplier_ids
)
>
0
:
# Get products/supplier relation
f
=
[
"product_tmpl_id"
,
'date_start'
,
'date_end'
,
'package_qty'
,
'price'
,
'name'
]
f
=
[
"product_tmpl_id"
,
'date_start'
,
'date_end'
,
'package_qty'
,
'price'
,
'name'
,
'product_code'
]
c
=
[[
'name'
,
'in'
,
[
int
(
x
)
for
x
in
supplier_ids
]]]
psi
=
api
.
search_read
(
'product.supplierinfo'
,
c
,
f
)
# Filter valid data
ptids
=
[]
valid_psi
=
[]
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
)):
and
(
p
[
"date_start"
]
is
False
or
p
[
"date_start"
]
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
)):
valid_psi
.
append
(
p
)
ptids
.
append
(
p
[
"product_tmpl_id"
][
0
])
else
:
ptids
=
[
int
(
x
)
for
x
in
pids
]
...
...
@@ -507,6 +559,10 @@ class CagetteProducts(models.Model):
# 'from': '2019-04-10',
# 'to': '2019-08-10',
}
if
stats_from
is
not
None
and
stats_from
!=
''
:
sales_average_params
[
'from'
]
=
stats_from
sales
=
CagetteProducts
.
get_template_products_sales_average
(
sales_average_params
)
if
'list'
in
sales
and
len
(
sales
[
'list'
])
>
0
:
...
...
@@ -517,12 +573,16 @@ class CagetteProducts(models.Model):
# Add supplier data to product data
for
i
,
fp
in
enumerate
(
filtered_products_t
):
if
supplier_ids
is
not
None
and
len
(
supplier_ids
)
>
0
:
psi_item
=
next
(
item
for
item
in
psi
if
item
[
"product_tmpl_id"
]
is
not
False
and
item
[
"product_tmpl_id"
][
0
]
==
fp
[
"id"
])
filtered_products_t
[
i
][
'suppliersinfo'
]
=
[{
'supplier_id'
:
int
(
psi_item
[
"name"
][
0
]),
'package_qty'
:
psi_item
[
"package_qty"
],
'price'
:
psi_item
[
"price"
]
}]
# Add all the product suppliersinfo (products from multiple suppliers into the suppliers list provided)
filtered_products_t
[
i
][
'suppliersinfo'
]
=
[]
for
psi_item
in
valid_psi
:
if
psi_item
[
"product_tmpl_id"
]
is
not
False
and
psi_item
[
"product_tmpl_id"
][
0
]
==
fp
[
"id"
]:
filtered_products_t
[
i
][
'suppliersinfo'
]
.
append
({
'supplier_id'
:
int
(
psi_item
[
"name"
][
0
]),
'package_qty'
:
psi_item
[
"package_qty"
],
'price'
:
psi_item
[
"price"
],
'product_code'
:
psi_item
[
"product_code"
]
})
for
s
in
sales
:
if
s
[
"id"
]
==
fp
[
"id"
]:
...
...
products/urls.py
View file @
97eaabd2
...
...
@@ -10,6 +10,7 @@ urlpatterns = [
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
),
url
(
r'^update_product_internal_ref$'
,
views
.
update_product_internal_ref
),
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 @
97eaabd2
...
...
@@ -42,8 +42,10 @@ def get_simple_list(request):
def
get_product_for_order_helper
(
request
):
res
=
{}
try
:
pids
=
json
.
loads
(
request
.
body
.
decode
())
res
=
CagetteProducts
.
get_products_for_order_helper
(
None
,
pids
)
data
=
json
.
loads
(
request
.
body
.
decode
())
pids
=
data
[
'pids'
]
stats_from
=
data
[
'stats_from'
]
res
=
CagetteProducts
.
get_products_for_order_helper
(
None
,
pids
,
stats_from
)
except
Exception
as
e
:
coop_logger
.
error
(
"get_product_for_help_order_line :
%
s"
,
str
(
e
))
res
[
'error'
]
=
str
(
e
)
...
...
@@ -112,6 +114,17 @@ def update_product_purchase_ok(request):
else
:
return
JsonResponse
({
"res"
:
res
})
def
update_product_internal_ref
(
request
):
res
=
{}
data
=
json
.
loads
(
request
.
body
.
decode
())
res
=
CagetteProduct
.
update_product_internal_ref
(
data
[
"product_tmpl_id"
],
data
[
"default_code"
])
if
(
'error'
in
res
):
return
JsonResponse
(
res
,
status
=
500
)
else
:
return
JsonResponse
({
"res"
:
res
})
def
labels_appli_csv
(
request
,
params
):
"""Generate files to put in DAV directory to be retrieved by scales app."""
withCandidate
=
False
...
...
reception/static/js/reception_index.js
View file @
97eaabd2
...
...
@@ -366,7 +366,7 @@ function display_orders_table() {
$
(
'#orders'
).
empty
();
}
for
(
let
j
in
orders
)
{
console
.
log
(
orders
[
j
].
id
)
console
.
log
(
orders
[
j
].
id
)
;
}
table_orders
=
$
(
'#orders'
).
DataTable
({
data
:
orders
,
...
...
stock/static/js/stock_movements.js
View file @
97eaabd2
...
...
@@ -166,6 +166,7 @@ function init_datatable() {
let
data
=
row
.
data
();
let
validated_data
=
qty_validation
(
qty
,
data
.
uom
.
id
);
if
(
validated_data
>=
0
)
{
data
.
qty
=
validated_data
;
row
.
remove
().
draw
();
...
...
@@ -423,28 +424,33 @@ var update_existing_product = function(product, added_qty, undo_option = false)
function
qty_validation
(
qty
,
uom_id
)
{
if
(
qty
==
null
||
qty
==
''
)
{
$
.
notify
(
"Il n'y a pas de quantité indiquée, ou ce n'est pas un nombre"
,
{
globalPosition
:
"top right"
,
className
:
"error"
});
globalPosition
:
"top right"
,
className
:
"error"
});
return
-
1
;
}
if
(
uom_id
==
1
)
{
if
(
qty
/
parseInt
(
qty
)
!=
1
&&
qty
!=
0
){
if
(
qty
/
parseInt
(
qty
)
!=
1
&&
qty
!=
0
)
{
$
.
notify
(
"Une quantité avec décimale est indiquée alors que c'est un article à l'unité"
,
{
globalPosition
:
"top right"
,
className
:
"error"
});
return
-
2
;}
globalPosition
:
"top right"
,
className
:
"error"
});
return
-
2
;
}
qty
=
parseInt
(
qty
);
// product by unit
}
else
{
qty
=
parseFloat
(
qty
).
toFixed
(
2
);
}
if
(
isNaN
(
qty
)){
if
(
isNaN
(
qty
))
{
$
.
notify
(
"Une quantité n'est pas un nombre"
,
{
globalPosition
:
"top right"
,
className
:
"error"
});
return
-
3
;}
globalPosition
:
"top right"
,
className
:
"error"
});
return
-
3
;
}
return
qty
;
}
...
...
templates/orders/helper.html
View file @
97eaabd2
...
...
@@ -40,10 +40,29 @@
<button
type=
"button"
class=
"btn--danger"
id=
"back_to_order_selection_from_main"
>
<i
class=
"fas fa-arrow-left"
></i>
Retour
</button>
<div
class=
"rights_buttons"
>
<button
type=
"button"
class=
'btn--primary'
id=
"do_inventory"
style=
"display:none;"
>
Faire un inventaire
</button>
<div
class=
"right_action_buttons"
>
<div
id=
"actions_buttons_wrapper"
>
<button
type=
"button"
class=
'btn--primary'
id=
"toggle_action_buttons"
>
<span
class=
"button_content"
>
Actions
</span>
<span
class=
"toggle_action_buttons_icon"
>
<i
class=
"fas fa-chevron-down"
></i>
</span>
</button>
<div
id=
"actions_buttons_container"
>
<button
type=
"button"
class=
'btn--primary action_button'
id=
"do_inventory"
style=
"display:none;"
>
Faire un inventaire
</button>
<button
type=
"button"
class=
'btn--danger action_button'
id=
"delete_order_button"
>
Supprimer la commande
</button>
</div>
</div>
<a
class=
'btn--warning link_as_button'
id=
"access_metabase"
style=
"display:none;"
href=
"{{metabase_url}}"
target=
"_blank"
>
Stats Métabase
</a>
</div>
</div>
...
...
@@ -57,14 +76,23 @@
</div>
<div
class=
"txtcenter"
id=
"order_forms_container"
>
<form
action=
"javascript:;"
id=
"coverage_form"
>
<input
type=
"number"
name=
"coverage_days"
id=
"coverage_days_input"
placeholder=
"Nb jours de couverture"
min=
"1"
>
<button
type=
"submit"
class=
'btn--primary'
>
Calculer les besoins
</button>
</form>
<form
action=
"javascript:;"
id=
"supplier_form"
>
<form
action=
"javascript:;"
id=
"supplier_form"
class=
"order_form_item"
>
<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>
<form
action=
"javascript:;"
id=
"stats_date_from_form"
class=
"order_form_item"
>
<label
for=
"stats_date_period_select"
>
Période de calcul de la conso moyenne
</label>
<select
name=
"stats_date_period_select"
id=
"stats_date_period_select"
>
<option
value=
""
>
Par défaut
</option>
<option
value=
"1week"
>
1 semaine
</option>
<option
value=
"2weeks"
>
2 semaines
</option>
</select>
</form>
<form
action=
"javascript:;"
id=
"coverage_form"
class=
"order_form_item"
>
<input
type=
"number"
name=
"coverage_days"
id=
"coverage_days_input"
placeholder=
"Nb jours de couverture"
min=
"1"
>
<button
type=
"submit"
class=
'btn--primary'
>
Calculer les besoins
</button>
</form>
</div>
<div
id=
"suppliers_container"
></div>
...
...
@@ -125,8 +153,14 @@
<span
class=
"pill_supplier_name"
></span>
<i
class=
"fas fa-times remove_supplier_icon"
></i>
</div>
<div
class=
"supplier_total_value_container"
>
Total:
<span
class=
"supplier_total_value"
>
0
</span>
€
<div
class=
"supplier_data"
>
<div
class=
"supplier_total_value_container"
>
Total :
<span
class=
"supplier_total_value"
>
0
</span>
€
</div>
|
<div
class=
"supplier_total_packages_container"
>
Nb colis :
<span
class=
"supplier_total_packages"
>
0
</span>
</div>
</div>
</div>
</div>
...
...
@@ -134,6 +168,7 @@
<div
id=
"order_pill_template"
>
<div
class=
"pill order_pill btn btn--primary"
>
<span
class=
"pill_order_name"
></span>
<i
class=
"fas fa-times remove_order_icon"
></i>
</div>
</div>
...
...
@@ -144,7 +179,7 @@
<h4
class=
"new_order_date_planned"
></h4>
<div
class=
'download_order_file'
>
<i
class=
"fas fa-spinner fa-spin download_order_file_loading"
></i>
<a
class=
'btn--success download_order_file_button'
style=
"display:none;"
href=
"#"
>
<a
class=
'btn--success download_order_file_button
link_as_button
'
style=
"display:none;"
href=
"#"
>
Télécharger le fichier de commande
</a>
</div>
...
...
@@ -164,6 +199,15 @@
<p>
Voulez-vous quand même y accéder ?
</p>
<hr/>
</div>
<div
id=
"modal_remove_order"
>
<h3>
Attention !
</h3>
<p
class=
"remove_order_modal_text"
>
Vous vous apprêtez à
<b
style=
"color: #d9534f;"
>
supprimer
</b>
cette commande en cours :
<span
class=
"remove_order_name"
></span>
.
<br/>
</p>
<p>
Êtez-vous sûr ?
</p>
<hr/>
</div>
<div
id=
"modal_remove_supplier"
>
<h3>
Attention !
</h3>
...
...
@@ -201,6 +245,19 @@
<p>
Êtez-vous sûr ?
</p>
<hr/>
</div>
<div
id=
"modal_end_supplier_product_association"
>
<h3>
Attention !
</h3>
<p>
Vous vous apprêtez à rendre le produit
<span
class=
"product_name"
></span>
indisponible chez le fournisseur
<span
class=
"supplier_name"
></span>
.
</p>
<p>
L'association sera supprimé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>
...
...
@@ -252,6 +309,7 @@
var
couchdb_dbname
=
'{{db}}'
;
var
couchdb_server
=
'{{couchdb_server}}'
+
couchdb_dbname
;
var
odoo_server
=
'{{odoo_server}}'
;
var
metabase_url
=
'{{metabase_url}}'
;
</script>
<script
src=
"{% static "
js
/
all_common
.
js
"
%}?
v=
"></script>
<script type="
text
/
javascript
"
src=
"{% static 'js/orders_helper.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