质量模块和库存扫码

This commit is contained in:
qihao.gong@jikimo.com
2023-07-24 11:42:15 +08:00
parent 8d024ad625
commit 3c89404543
228 changed files with 142596 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import stock_barcode

View File

@@ -0,0 +1,309 @@
# -*- 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": <data used by the stock barcode> {'records' : {'model': [{<record>}, ... ]}, 'other_infos':...}, _get_barcode_data_prefetch
"groups": <security group>, 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')}