Skip to content

Commit f404c28

Browse files
Add files via upload
1 parent 050cc22 commit f404c28

23 files changed

+13386
-4
lines changed

__init__.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
11
# -*- coding: utf-8 -*-
2-
# flake8: noqa
3-
from . import base_import_module
4-
from . import ir_module
5-
from . import ir_ui_view
2+
# Part of Odoo. See LICENSE file for full copyright and licensing details.
3+
4+
from . import barcode
5+
from . import ir_actions_report
6+
from . import product_strategy
7+
from . import res_company
8+
from . import res_partner
9+
from . import res_users
10+
from . import res_config_settings
11+
from . import stock_location
12+
from . import stock_move
13+
from . import stock_move_line
14+
from . import stock_orderpoint
15+
from . import stock_lot
16+
from . import stock_picking
17+
from . import stock_quant
18+
from . import stock_replenish_mixin
19+
from . import stock_rule
20+
from . import stock_warehouse
21+
from . import stock_scrap
22+
from . import product
23+
from . import stock_package_level
24+
from . import stock_package_type
25+
from . import stock_storage_category

barcode.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# -*- coding: utf-8 -*-
2+
# Part of Odoo. See LICENSE file for full copyright and licensing details.
3+
4+
from odoo import fields, models
5+
6+
7+
class BarcodeRule(models.Model):
8+
_inherit = 'barcode.rule'
9+
10+
type = fields.Selection(selection_add=[
11+
('weight', 'Weighted Product'),
12+
('location', 'Location'),
13+
('lot', 'Lot'),
14+
('package', 'Package')
15+
], ondelete={
16+
'weight': 'set default',
17+
'location': 'set default',
18+
'lot': 'set default',
19+
'package': 'set default',
20+
})

ir_actions_report.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from odoo import models
2+
3+
4+
class IrActionsReport(models.Model):
5+
_inherit = 'ir.actions.report'
6+
7+
def _get_rendering_context(self, report, docids, data):
8+
data = super()._get_rendering_context(report, docids, data)
9+
if report.report_name == 'stock.report_reception_report_label' and not docids:
10+
docids = data['docids']
11+
docs = self.env[report.model].browse(docids)
12+
data.update({
13+
'doc_ids': docids,
14+
'docs': docs,
15+
})
16+
return data

product.py

Lines changed: 1155 additions & 0 deletions
Large diffs are not rendered by default.

product_strategy.py

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
# -*- coding: utf-8 -*-
2+
# Part of Odoo. See LICENSE file for full copyright and licensing details.
3+
4+
from odoo import _, api, fields, models
5+
from odoo.osv import expression
6+
from odoo.exceptions import UserError
7+
from odoo.tools.float_utils import float_compare
8+
9+
10+
class RemovalStrategy(models.Model):
11+
_name = 'product.removal'
12+
_description = 'Removal Strategy'
13+
14+
name = fields.Char('Name', required=True, translate=True)
15+
method = fields.Char("Method", required=True, translate=True, help="FIFO, LIFO...")
16+
17+
18+
class StockPutawayRule(models.Model):
19+
_name = 'stock.putaway.rule'
20+
_order = 'sequence,product_id'
21+
_description = 'Putaway Rule'
22+
_check_company_auto = True
23+
24+
def _default_category_id(self):
25+
if self.env.context.get('active_model') == 'product.category':
26+
return self.env.context.get('active_id')
27+
28+
def _default_location_id(self):
29+
if self.env.context.get('active_model') == 'stock.location':
30+
return self.env.context.get('active_id')
31+
if not self.env.user.has_group('stock.group_stock_multi_warehouses'):
32+
wh = self.env['stock.warehouse'].search(self.env['stock.warehouse']._check_company_domain(self.env.company), limit=1)
33+
input_loc, _ = wh._get_input_output_locations(wh.reception_steps, wh.delivery_steps)
34+
return input_loc
35+
36+
def _default_product_id(self):
37+
if self.env.context.get('active_model') == 'product.template' and self.env.context.get('active_id'):
38+
product_template = self.env['product.template'].browse(self.env.context.get('active_id'))
39+
product_template = product_template.exists()
40+
if product_template.product_variant_count == 1:
41+
return product_template.product_variant_id
42+
elif self.env.context.get('active_model') == 'product.product':
43+
return self.env.context.get('active_id')
44+
45+
product_id = fields.Many2one(
46+
'product.product', 'Product', check_company=True,
47+
default=_default_product_id,
48+
domain="[('product_tmpl_id', '=', context.get('active_id', False))] if context.get('active_model') == 'product.template' else [('type', '!=', 'service')]",
49+
ondelete='cascade')
50+
category_id = fields.Many2one('product.category', 'Product Category',
51+
default=_default_category_id, domain=[('filter_for_stock_putaway_rule', '=', True)], ondelete='cascade')
52+
location_in_id = fields.Many2one(
53+
'stock.location', 'When product arrives in', check_company=True,
54+
domain="[('child_ids', '!=', False)]",
55+
default=_default_location_id, required=True, ondelete='cascade', index=True)
56+
location_out_id = fields.Many2one(
57+
'stock.location', 'Store to sublocation', check_company=True,
58+
domain="[('id', 'child_of', location_in_id)]",
59+
required=True, ondelete='cascade')
60+
sequence = fields.Integer('Priority', help="Give to the more specialized category, a higher priority to have them in top of the list.")
61+
company_id = fields.Many2one(
62+
'res.company', 'Company', required=True,
63+
default=lambda s: s.env.company.id, index=True)
64+
package_type_ids = fields.Many2many('stock.package.type', string='Package Type', check_company=True)
65+
storage_category_id = fields.Many2one(
66+
'stock.storage.category', 'Storage Category',
67+
compute='_compute_storage_category', store=True, readonly=False,
68+
ondelete='cascade', check_company=True)
69+
active = fields.Boolean('Active', default=True)
70+
sublocation = fields.Selection([
71+
('no', 'No'),
72+
('last_used', 'Last Used'),
73+
('closest_location', 'Closest Location')
74+
], default='no')
75+
76+
@api.depends('sublocation')
77+
def _compute_storage_category(self):
78+
for rule in self:
79+
if rule.sublocation != 'closest_location':
80+
rule.storage_category_id = False
81+
82+
@api.onchange('sublocation', 'location_out_id', 'storage_category_id')
83+
def _onchange_sublocation(self):
84+
if self.sublocation == 'closest_location':
85+
child_location_ids = self.env['stock.location'].search([
86+
('id', 'child_of', self.location_out_id.id),
87+
('storage_category_id', '=', self.storage_category_id.id)
88+
])
89+
if not child_location_ids:
90+
return {
91+
'warning': {
92+
'title': _("Warning"),
93+
'message': _("Selected storage category does not exist in the 'store to' location or any of its sublocations"),
94+
},
95+
}
96+
97+
@api.onchange('location_in_id')
98+
def _onchange_location_in(self):
99+
loc_in, loc_out = self.location_in_id, self.location_out_id
100+
if not loc_out or (loc_in and not loc_out._child_of(loc_in)):
101+
self.location_out_id = self.location_in_id
102+
103+
@api.model_create_multi
104+
def create(self, vals_list):
105+
rules = super().create(vals_list)
106+
return rules
107+
108+
def write(self, vals):
109+
if 'company_id' in vals:
110+
for rule in self:
111+
if rule.company_id.id != vals['company_id']:
112+
raise UserError(_("Changing the company of this record is forbidden at this point, you should rather archive it and create a new one."))
113+
return super(StockPutawayRule, self).write(vals)
114+
115+
def _get_last_used_search_domain(self, product):
116+
self.ensure_one()
117+
domain = expression.AND([
118+
[('state', '=', 'done')],
119+
[('location_dest_id', 'child_of', self.location_out_id.id)],
120+
[('product_id', '=', product.id)],
121+
])
122+
if self.package_type_ids:
123+
domain = expression.AND([
124+
domain,
125+
[('result_package_id.package_type_id', 'in', self.package_type_ids.ids)],
126+
])
127+
return domain
128+
129+
def _get_last_used_location(self, product):
130+
self.ensure_one()
131+
return self.env['stock.move.line'].search(
132+
domain=self._get_last_used_search_domain(product),
133+
limit=1,
134+
order='date desc'
135+
).location_dest_id
136+
137+
def _get_putaway_location(self, product, quantity=0, package=None, packaging=None, qty_by_location=None):
138+
# find package type on package or packaging
139+
package_type = self.env['stock.package.type']
140+
if package:
141+
package_type = package.package_type_id
142+
elif packaging:
143+
package_type = packaging.package_type_id
144+
145+
checked_locations = set()
146+
for putaway_rule in self:
147+
location_out = putaway_rule.location_out_id
148+
if putaway_rule.sublocation == 'last_used':
149+
location_dest_id = putaway_rule._get_last_used_location(product)
150+
location_out = location_dest_id or location_out
151+
152+
child_locations = location_out.child_internal_location_ids
153+
154+
if not putaway_rule.storage_category_id:
155+
if location_out in checked_locations:
156+
continue
157+
if location_out._check_can_be_used(product, quantity, package, qty_by_location[location_out.id]):
158+
return location_out
159+
continue
160+
else:
161+
child_locations = child_locations.filtered(lambda loc: loc.storage_category_id == putaway_rule.storage_category_id)
162+
163+
# check if already have the product/package type stored
164+
for location in child_locations:
165+
if location in checked_locations:
166+
continue
167+
if package_type:
168+
if location.quant_ids.filtered(lambda q: q.package_id and q.package_id.package_type_id == package_type):
169+
if location._check_can_be_used(product, quantity, package=package, location_qty=qty_by_location[location.id]):
170+
return location
171+
else:
172+
checked_locations.add(location)
173+
elif float_compare(qty_by_location[location.id], 0, precision_rounding=product.uom_id.rounding) > 0:
174+
if location._check_can_be_used(product, quantity, location_qty=qty_by_location[location.id]):
175+
return location
176+
else:
177+
checked_locations.add(location)
178+
179+
# check locations with matched storage category
180+
for location in child_locations.filtered(lambda l: l.storage_category_id == putaway_rule.storage_category_id):
181+
if location in checked_locations:
182+
continue
183+
if location._check_can_be_used(product, quantity, package, qty_by_location[location.id]):
184+
return location
185+
checked_locations.add(location)
186+
187+
return None

0 commit comments

Comments
 (0)