# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from collections import defaultdict from odoo import http, _ from odoo.http import request from odoo.modules.module import get_resource_path from odoo.osv import expression from odoo.tools import pdf, split_every from odoo.tools.misc import file_open class StockBarcodeController(http.Controller): @http.route('/stock_barcode/scan_from_main_menu', type='json', auth='user') def main_menu(self, barcode, **kw): """ Receive a barcode scanned from the main menu and return the appropriate action (open an existing / new picking) or warning. """ barcode_type = None nomenclature = request.env.company.nomenclature_id if nomenclature.is_gs1_nomenclature: parsed_results = nomenclature.parse_barcode(barcode) if parsed_results: # search with the last feasible rule for result in parsed_results[::-1]: if result['rule'].type in ['product', 'package', 'location', 'dest_location']: barcode_type = result['rule'].type break if not barcode_type: ret_open_picking = self._try_open_picking(barcode) if ret_open_picking: return ret_open_picking ret_open_picking_type = self._try_open_picking_type(barcode) if ret_open_picking_type: return ret_open_picking_type if request.env.user.has_group('stock.group_stock_multi_locations') and \ (not barcode_type or barcode_type in ['location', 'dest_location']): ret_new_internal_picking = self._try_new_internal_picking(barcode) if ret_new_internal_picking: return ret_new_internal_picking if not barcode_type or barcode_type == 'product': ret_open_product_location = self._try_open_product_location(barcode) if ret_open_product_location: return ret_open_product_location if request.env.user.has_group('stock.group_tracking_lot') and \ (not barcode_type or barcode_type == 'package'): ret_open_package = self._try_open_package(barcode) if ret_open_package: return ret_open_package if request.env.user.has_group('stock.group_stock_multi_locations'): return {'warning': _('No picking or location or product corresponding to barcode %(barcode)s') % {'barcode': barcode}} else: return {'warning': _('No picking or product corresponding to barcode %(barcode)s') % {'barcode': barcode}} @http.route('/stock_barcode/save_barcode_data', type='json', auth='user') def save_barcode_data(self, model, res_id, write_field, write_vals): if not res_id: return request.env[model].barcode_write(write_vals) target_record = request.env[model].browse(res_id) target_record.write({write_field: write_vals}) return target_record._get_stock_barcode_data() @http.route('/stock_barcode/get_barcode_data', type='json', auth='user') def get_barcode_data(self, model, res_id): """ Returns a dict with values used by the barcode client: { "data": {'records' : {'model': [{}, ... ]}, 'other_infos':...}, _get_barcode_data_prefetch "groups": , self._get_groups_data } """ if not res_id: target_record = request.env[model].with_context(allowed_company_ids=self._get_allowed_company_ids()) else: target_record = request.env[model].browse(res_id).with_context(allowed_company_ids=self._get_allowed_company_ids()) data = target_record._get_stock_barcode_data() data['records'].update(self._get_barcode_nomenclature()) return { 'data': data, 'groups': self._get_groups_data(), } @http.route('/stock_barcode/get_specific_barcode_data', type='json', auth='user') def get_specific_barcode_data(self, barcode, model_name, domains_by_model=False): nomenclature = request.env.company.nomenclature_id # Adapts the search parameters for GS1 specifications. operator = '=' limit = None if nomenclature.is_gs1_nomenclature else 1 if nomenclature.is_gs1_nomenclature: try: # If barcode is digits only, cut off the padding to keep the original barcode only. barcode = str(int(barcode)) operator = 'ilike' except ValueError: pass # Barcode isn't digits only. domains_by_model = domains_by_model or {} barcode_field_by_model = self._get_barcode_field_by_model() result = defaultdict(list) model_names = model_name and [model_name] or list(barcode_field_by_model.keys()) for model in model_names: domain = [(barcode_field_by_model[model], operator, barcode)] domain_for_this_model = domains_by_model.get(model) if domain_for_this_model: domain = expression.AND([domain, domain_for_this_model]) record = request.env[model].with_context(display_default_code=False).search(domain, limit=limit) if record: result[model] += record.read(request.env[model]._get_fields_stock_barcode(), load=False) if hasattr(record, '_get_stock_barcode_specific_data'): additional_result = record._get_stock_barcode_specific_data() for key in additional_result: result[key] += additional_result[key] return result @http.route('/stock_barcode/rid_of_message_demo_barcodes', type='json', auth='user') def rid_of_message_demo_barcodes(self, **kw): """ Edit the main_menu client action so that it doesn't display the 'print demo barcodes sheet' message """ if not request.env.user.has_group('stock.group_stock_user'): return request.not_found() action = request.env.ref('stock_barcode.stock_barcode_action_main_menu') action and action.sudo().write({'params': {'message_demo_barcodes': False}}) @http.route('/stock_barcode/print_inventory_commands', type='http', auth='user') def print_inventory_commands(self): if not request.env.user.has_group('stock.group_stock_user'): return request.not_found() barcode_pdfs = [] # get fixed command barcodes file_path = get_resource_path('stock_barcode', 'static/img', 'barcodes_actions.pdf') with file_open(file_path, "rb") as commands_file: barcode_pdfs.append(commands_file.read()) # make sure we use the selected company if possible allowed_company_ids = self._get_allowed_company_ids() # same domain conditions for picking types and locations domain = [('active', '=', 'True'), ('barcode', '!=', ''), ('company_id', 'in', allowed_company_ids)] # get picking types barcodes picking_type_ids = request.env['stock.picking.type'].search(domain) Report = request.env['ir.actions.report'] for picking_type_batch in split_every(100, picking_type_ids.ids): picking_types_pdf, _ = Report._render_qweb_pdf('stock.action_report_picking_type_label', picking_type_batch) if picking_types_pdf: barcode_pdfs.append(picking_types_pdf) # get locations barcodes if request.env.user.has_group('stock.group_stock_multi_locations'): locations_ids = request.env['stock.location'].search(domain) for location_ids_batch in split_every(100, locations_ids.ids): locations_pdf, _ = Report._render_qweb_pdf('stock.action_report_location_barcode', location_ids_batch) if locations_pdf: barcode_pdfs.append(locations_pdf) merged_pdf = pdf.merge_pdf(barcode_pdfs) pdfhttpheaders = [ ('Content-Type', 'application/pdf'), ('Content-Length', len(merged_pdf)) ] return request.make_response(merged_pdf, headers=pdfhttpheaders) def _try_open_product_location(self, barcode): """ If barcode represent a product, open a list/kanban view to show all the locations of this product. """ result = request.env['product.product'].search_read([ ('barcode', '=', barcode), ], ['id', 'display_name'], limit=1) if result: tree_view_id = request.env.ref('stock.view_stock_quant_tree').id kanban_view_id = request.env.ref('stock_barcode.stock_quant_barcode_kanban_2').id return { 'action': { 'name': result[0]['display_name'], 'res_model': 'stock.quant', 'views': [(tree_view_id, 'list'), (kanban_view_id, 'kanban')], 'type': 'ir.actions.act_window', 'domain': [('product_id', '=', result[0]['id'])], 'context': { 'search_default_internal_loc': True, }, } } def _try_open_picking_type(self, barcode): """ If barcode represent a picking type, open a new picking with this type """ picking_type = request.env['stock.picking.type'].search([ ('barcode', '=', barcode), ], limit=1) if picking_type: picking = request.env['stock.picking']._create_new_picking(picking_type) return picking._get_client_action() return False def _try_open_picking(self, barcode): """ If barcode represents a picking, open it """ corresponding_picking = request.env['stock.picking'].search([ ('name', '=', barcode), ], limit=1) if corresponding_picking: action = corresponding_picking.action_open_picking_client_action() return {'action': action} return False def _try_open_package(self, barcode): """ If barcode represents a package, open it. """ package = request.env['stock.quant.package'].search([('name', '=', barcode)], limit=1) if package: view_id = request.env.ref('stock.view_quant_package_form').id return { 'action': { 'name': 'Open package', 'res_model': 'stock.quant.package', 'views': [(view_id, 'form')], 'type': 'ir.actions.act_window', 'res_id': package.id, 'context': {'active_id': package.id} } } return False def _try_new_internal_picking(self, barcode): """ If barcode represents a location, open a new picking from this location """ corresponding_location = request.env['stock.location'].search([ ('barcode', '=', barcode), ('usage', '=', 'internal') ], limit=1) if corresponding_location: internal_picking_type = request.env['stock.picking.type'].search([('code', '=', 'internal')]) warehouse = corresponding_location.warehouse_id if warehouse: internal_picking_type = internal_picking_type.filtered(lambda r: r.warehouse_id == warehouse) dest_loc = corresponding_location while dest_loc.location_id and dest_loc.location_id.usage == 'internal': dest_loc = dest_loc.location_id if internal_picking_type: # Create and confirm an internal picking picking = request.env['stock.picking'].create({ 'picking_type_id': internal_picking_type[0].id, 'user_id': False, 'location_id': corresponding_location.id, 'location_dest_id': dest_loc.id, 'immediate_transfer': True, }) picking.action_confirm() return picking._get_client_action() else: return {'warning': _('No internal operation type. Please configure one in warehouse settings.')} return False def _get_allowed_company_ids(self): """ Return the allowed_company_ids based on cookies. Currently request.env.company returns the current user's company when called within a controller rather than the selected company in the company switcher and request.env.companies lists the current user's allowed companies rather than the selected companies. :returns: List of active companies. The first company id in the returned list is the selected company. """ cids = request.httprequest.cookies.get('cids', str(request.env.user.company_id.id)) return [int(cid) for cid in cids.split(',')] def _get_groups_data(self): return { 'group_stock_multi_locations': request.env.user.has_group('stock.group_stock_multi_locations'), 'group_tracking_owner': request.env.user.has_group('stock.group_tracking_owner'), 'group_tracking_lot': request.env.user.has_group('stock.group_tracking_lot'), 'group_production_lot': request.env.user.has_group('stock.group_production_lot'), 'group_uom': request.env.user.has_group('uom.group_uom'), 'group_stock_packaging': request.env.user.has_group('product.group_stock_packaging'), } def _get_barcode_nomenclature(self): company = request.env['res.company'].browse(self._get_allowed_company_ids()[0]) nomenclature = company.nomenclature_id return { "barcode.nomenclature": nomenclature.read(load=False), "barcode.rule": nomenclature.rule_ids.read(load=False) } def _get_barcode_field_by_model(self): list_model = [ 'stock.location', 'product.product', 'product.packaging', 'stock.picking', 'stock.lot', 'stock.quant.package', ] return {model: request.env[model]._barcode_field for model in list_model if hasattr(request.env[model], '_barcode_field')}