forked from OCA/stock-logistics-warehouse
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathproduct.py
232 lines (212 loc) · 9.97 KB
/
product.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# -*- coding: utf-8 -*-
##############################################################################
#
# This module is copyright (C) 2014 Numérigraphe SARL. All Rights Reserved.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp import SUPERUSER_ID
from openerp.osv import orm, fields
import openerp.addons.decimal_precision as dp
# Function which uses the pool to call the method from the other modules too.
from openerp.addons.stock_available import _product_available_fnct
class ProductProduct(orm.Model):
"""Add the computation for the stock available to promise"""
_inherit = 'product.product'
def _product_available(self, cr, uid, ids, field_names=None, arg=False,
context=None):
"""Compute the quantities in Quotations."""
# Compute the core quantities
res = super(ProductProduct, self)._product_available(
cr, uid, ids, field_names=field_names, arg=arg, context=context)
# If we didn't get a field_names list, there's nothing to do
if field_names is None:
return res
if context is None:
context = {}
# Prepare an alternative context without 'uom', to avoid cross-category
# conversions when reading the available stock of components
if 'uom' in context:
context_wo_uom = context.copy()
del context_wo_uom['uom']
else:
context_wo_uom = context
# Compute the quantities quoted/available to promise
if any([f in field_names
for f in ['quoted_qty', 'immediately_usable_qty']]):
date_str, date_args = self._get_dates(cr, uid, ids,
context=context)
# Limit the search to some shops according to the context
shop_str, shop_args = self._get_shops(cr, uid, ids,
context=context)
# Query the total by Product and UoM
cr.execute(
"""
SELECT sum(product_uom_qty), product_id, product_uom
FROM sale_order_line
INNER JOIN sale_order
ON (sale_order_line.order_id = sale_order.id)
WHERE product_id in %s
AND sale_order_line.state = 'draft' """ +
date_str + shop_str +
"GROUP BY sale_order_line.product_id, product_uom",
(tuple(ids),) + date_args + shop_args)
results = cr.fetchall()
# Get the UoM resources we'll need for conversion
# UoMs from the products
uoms_o = {}
product2uom = {}
for product in self.browse(cr, uid, ids, context=context):
product2uom[product.id] = product.uom_id
uoms_o[product.uom_id.id] = product.uom_id
# UoM from the results and the context
uom_obj = self.pool['product.uom']
uoms = map(lambda stock_product_uom_qty: stock_product_uom_qty[2],
results)
if context.get('uom', False):
uoms.append(context['uom'])
uoms = filter(lambda stock_product_uom_qty:
stock_product_uom_qty not in uoms_o.keys(), uoms)
if uoms:
uoms = uom_obj.browse(cr, SUPERUSER_ID, list(set(uoms)),
context=context)
for o in uoms:
uoms_o[o.id] = o
# Compute the quoted quantity
for (amount, prod_id, prod_uom) in results:
# Convert the amount to the product's UoM without rounding
amount = amount / uoms_o[prod_uom].factor
if 'quoted_qty' in field_names:
res[prod_id]['quoted_qty'] -= amount
if 'immediately_usable_qty' in field_names:
res[prod_id]['immediately_usable_qty'] -= amount
# Round and optionally convert the results to the requested UoM
for prod_id, stock_qty in res.iteritems():
if context.get('uom', False):
# Convert to the requested UoM
res_uom = uoms_o[context['uom']]
else:
# The conversion is unneeded but we do need the rounding
res_uom = product2uom[prod_id]
if 'quoted_qty' in field_names:
stock_qty['quoted_qty'] = uom_obj._compute_qty_obj(
cr, SUPERUSER_ID, product2uom[prod_id],
stock_qty['quoted_qty'],
res_uom)
if 'immediately_usable_qty' in field_names:
stock_qty['immediately_usable_qty'] = \
uom_obj._compute_qty_obj(
cr, SUPERUSER_ID, product2uom[prod_id],
stock_qty['immediately_usable_qty'],
res_uom)
return res
def _get_shops(self, cr, uid, ids, context=None):
"""Find the shops matching the current context
See the helptext for the field quoted_qty for details"""
shop_ids = []
# Account for one or several locations in the context
# Take any shop using any warehouse that has these locations as stock
# location
if context.get('location', False):
# Either a single or multiple locations can be in the context
if isinstance(context['location'], (int, long)):
location_ids = [context['location']]
else:
location_ids = context['location']
# Add the children locations
if context.get('compute_child', True):
child_location_ids = self.pool['stock.location'].search(
cr, SUPERUSER_ID,
[('location_id', 'child_of', location_ids)])
location_ids = child_location_ids or location_ids
# Get the corresponding Shops
cr.execute(
"""
SELECT id FROM sale_shop
WHERE warehouse_id IN (
SELECT id
FROM stock_warehouse
WHERE lot_stock_id IN %s)""",
(tuple(location_ids),))
res_location = cr.fetchone()
if res_location:
shop_ids.append(res_location)
# Account for a warehouse in the context
# Take any draft order in any shop using this warehouse
if context.get('warehouse', False):
cr.execute("SELECT id "
"FROM sale_shop "
"WHERE warehouse_id = %s",
(int(context['warehouse']),))
res_wh = cr.fetchone()
if res_wh:
shop_ids.append(res_wh)
# If we are in a single Shop context, only count the quotations from
# this shop
if context.get('shop', False):
shop_ids.append(context['shop'])
# Build the SQL to restrict to the selected shops
shop_str = ''
if shop_ids:
shop_str = 'AND sale_order.shop_id IN %s'
if shop_ids:
shop_ids = (tuple(shop_ids),)
else:
shop_ids = ()
return shop_str, shop_ids
def _get_dates(self, cr, uid, ids, context=None):
"""Build SQL criteria to match the context's from/to dates"""
# If we are in a context with dates, only consider the quotations to be
# delivered at these dates.
# If no delivery date was entered, use the order date instead
if not context:
return '', ()
from_date = context.get('from_date', False)
to_date = context.get('to_date', False)
date_str = ''
date_args = []
if from_date:
date_str = """AND COALESCE(
sale_order.requested_date,
sale_order.date_order) >= %s """
date_args.append(from_date)
if to_date:
date_str += """AND COALESCE(
sale_order.requested_date,
sale_order.date_order) <= %s """
date_args.append(to_date)
if date_args:
date_args = (tuple(date_args),)
else:
date_args = ()
return date_str, date_args
_columns = {
'quoted_qty': fields.function(
_product_available_fnct, method=True, multi='qty_available',
type='float',
digits_compute=dp.get_precision('Product Unit of Measure'),
string='Quoted',
help="Total quantity of this Product that have been included in "
"Quotations (Draft Sale Orders).\n"
"In a context with a single Shop, this includes the "
"Quotation processed at this Shop.\n"
"In a context with a single Warehouse, this includes "
"Quotation processed in any Shop using this Warehouse.\n"
"In a context with a single Stock Location, this includes "
"Quotation processed at any shop using any Warehouse using "
"this Location, or any of its children, as it's Stock "
"Location.\n"
"Otherwise, this includes every Quotation."),
}