Files
test/stock_barcode/models/stock_picking.py
qihao.gong@jikimo.com 3c89404543 质量模块和库存扫码
2023-07-24 11:42:15 +08:00

337 lines
16 KiB
Python

# -*- coding: utf-8 -*-
from odoo import fields, models, api, _
from odoo.tools import html2plaintext, is_html_empty
from odoo.exceptions import UserError
class StockPicking(models.Model):
_inherit = 'stock.picking'
_barcode_field = 'name'
def action_cancel_from_barcode(self):
self.ensure_one()
view = self.env.ref('stock_barcode.stock_barcode_cancel_operation_view')
return {
'name': _('Cancel this operation ?'),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'stock_barcode.cancel.operation',
'views': [(view.id, 'form')],
'view_id': view.id,
'target': 'new',
'context': dict(self.env.context, default_picking_id=self.id),
}
@api.model
def action_open_new_picking(self):
""" Creates a new picking of the current picking type and open it.
:return: the action used to open the picking, or false
:rtype: dict
"""
context = self.env.context
if context.get('active_model') == 'stock.picking.type':
picking_type = self.env['stock.picking.type'].browse(context.get('active_id'))
if picking_type.exists():
new_picking = self._create_new_picking(picking_type)
return new_picking._get_client_action()['action']
return False
def action_open_picking(self):
""" method to open the form view of the current record
from a button on the kanban view
"""
self.ensure_one()
view_id = self.env.ref('stock.view_picking_form').id
return {
'name': _('Open picking form'),
'res_model': 'stock.picking',
'view_mode': 'form',
'view_id': view_id,
'type': 'ir.actions.act_window',
'res_id': self.id,
}
def action_open_picking_client_action(self):
""" method to open the form view of the current record
from a button on the kanban view
"""
self.ensure_one()
action = self.env["ir.actions.actions"]._for_xml_id("stock_barcode.stock_barcode_picking_client_action")
action = dict(action, target='fullscreen')
action['context'] = {'active_id': self.id}
return action
def action_print_barcode_pdf(self):
return self.action_open_label_type()
def action_print_delivery_slip(self):
return self.env.ref('stock.action_report_delivery').report_action(self)
def action_print_packges(self):
return self.env.ref('stock.action_report_picking_packages').report_action(self)
def _get_stock_barcode_data(self):
# Avoid to get the products full name because code and name are separate in the barcode app.
self = self.with_context(display_default_code=False)
move_lines = self.move_line_ids
lots = move_lines.lot_id
owners = move_lines.owner_id
# Fetch all implied products in `self` and adds last used products to avoid additional rpc.
products = move_lines.product_id
packagings = products.packaging_ids
uoms = products.uom_id | move_lines.product_uom_id
# If UoM setting is active, fetch all UoM's data.
if self.env.user.has_group('uom.group_uom'):
uoms |= self.env['uom.uom'].search([])
# Fetch `stock.quant.package` and `stock.package.type` if group_tracking_lot.
packages = self.env['stock.quant.package']
package_types = self.env['stock.package.type']
if self.env.user.has_group('stock.group_tracking_lot'):
packages |= move_lines.package_id | move_lines.result_package_id
packages |= self.env['stock.quant.package']._get_usable_packages()
package_types = package_types.search([])
# Fetch `stock.location`
source_locations = self.env['stock.location'].search([('id', 'child_of', self.location_id.ids)])
destination_locations = self.env['stock.location'].search([('id', 'child_of', self.location_dest_id.ids)])
locations = move_lines.location_id | move_lines.location_dest_id | source_locations | destination_locations
data = {
"records": {
"stock.picking": self.read(self._get_fields_stock_barcode(), load=False),
"stock.picking.type": self.picking_type_id.read(self.picking_type_id._get_fields_stock_barcode(), load=False),
"stock.move.line": move_lines.read(move_lines._get_fields_stock_barcode(), load=False),
# `self` can be a record set (e.g.: a picking batch), set only the first partner in the context.
"product.product": products.with_context(partner_id=self[:1].partner_id.id).read(products._get_fields_stock_barcode(), load=False),
"product.packaging": packagings.read(packagings._get_fields_stock_barcode(), load=False),
"res.partner": owners.read(owners._get_fields_stock_barcode(), load=False),
"stock.location": locations.read(locations._get_fields_stock_barcode(), load=False),
"stock.package.type": package_types.read(package_types._get_fields_stock_barcode(), False),
"stock.quant.package": packages.read(packages._get_fields_stock_barcode(), load=False),
"stock.lot": lots.read(lots._get_fields_stock_barcode(), load=False),
"uom.uom": uoms.read(uoms._get_fields_stock_barcode(), load=False),
},
"nomenclature_id": [self.env.company.nomenclature_id.id],
"source_location_ids": source_locations.ids,
"destination_locations_ids": destination_locations.ids,
}
# Extracts pickings' note if it's empty HTML.
for picking in data['records']['stock.picking']:
picking['note'] = False if is_html_empty(picking['note']) else html2plaintext(picking['note'])
data['config'] = self.picking_type_id._get_barcode_config()
data['line_view_id'] = self.env.ref('stock_barcode.stock_move_line_product_selector').id
data['form_view_id'] = self.env.ref('stock_barcode.stock_picking_barcode').id
data['package_view_id'] = self.env.ref('stock_barcode.stock_quant_barcode_kanban').id
return data
@api.model
def _create_new_picking(self, picking_type):
""" Create a new picking for the given picking type.
:param picking_type:
:type picking_type: :class:`~odoo.addons.stock.models.stock_picking.PickingType`
:return: a new picking
:rtype: :class:`~odoo.addons.stock.models.stock_picking.Picking`
"""
# Find source and destination Locations
location_dest_id, location_id = picking_type.warehouse_id._get_partner_locations()
if picking_type.default_location_src_id:
location_id = picking_type.default_location_src_id
if picking_type.default_location_dest_id:
location_dest_id = picking_type.default_location_dest_id
# Create and confirm the picking
return self.env['stock.picking'].create({
'user_id': False,
'picking_type_id': picking_type.id,
'location_id': location_id.id,
'location_dest_id': location_dest_id.id,
'immediate_transfer': True,
})
def _get_client_action(self):
self.ensure_one()
action = self.env["ir.actions.actions"]._for_xml_id("stock_barcode.stock_barcode_picking_client_action")
action = dict(action, target='fullscreen')
action['context'] = {'active_id': self.id}
return {'action': action}
def _get_fields_stock_barcode(self):
""" List of fields on the stock.picking object that are needed by the
client action. The purpose of this function is to be overridden in order
to inject new fields to the client action.
"""
return [
'move_line_ids',
'picking_type_id',
'location_id',
'location_dest_id',
'name',
'state',
'picking_type_code',
'company_id',
'immediate_transfer',
'note',
'picking_type_entire_packs',
'use_create_lots',
'use_existing_lots',
'user_id',
]
@api.model
def filter_on_barcode(self, barcode):
""" Searches ready pickings for the scanned product/package.
"""
barcode_type = None
nomenclature = self.env.company.nomenclature_id
if nomenclature.is_gs1_nomenclature:
parsed_results = nomenclature.parse_barcode(barcode)
if parsed_results:
# filter with the last feasible rule
for result in parsed_results[::-1]:
if result['rule'].type in ('product', 'package'):
barcode_type = result['rule'].type
break
active_id = self.env.context.get('active_id')
picking_type = self.env['stock.picking.type'].browse(self.env.context.get('active_id'))
base_domain = [
('picking_type_id', '=', picking_type.id),
('state', 'not in', ['cancel', 'done', 'draft'])
]
picking_nums = 0
additional_context = {'active_id': active_id}
if barcode_type == 'product' or not barcode_type:
product = self.env['product.product'].search_read([('barcode', '=', barcode)], ['id'], limit=1)
if product:
product_id = product[0]['id']
picking_nums = self.search_count(base_domain + [('product_id', '=', product_id)])
additional_context['search_default_product_id'] = product_id
if self.env.user.has_group('stock.group_tracking_lot') and (barcode_type == 'package' or (not barcode_type and not picking_nums)):
package = self.env['stock.quant.package'].search_read([('name', '=', barcode)], ['id'], limit=1)
if package:
package_id = package[0]['id']
pack_domain = ['|', ('move_line_ids.package_id', '=', package_id), ('move_line_ids.result_package_id', '=', package_id)]
picking_nums = self.search_count(base_domain + pack_domain)
additional_context['search_default_move_line_ids'] = barcode
if not picking_nums:
if barcode_type:
return {
'warning': {
'title': _("No %(picking_type)s ready for this %(barcode_type)s", picking_type=picking_type.name, barcode_type=barcode_type),
}
}
return {
'warning': {
'title': _('No product or package found for barcode %s', barcode),
'message': _('Scan a product or a package to filter the transfers.'),
}
}
action = picking_type._get_action('stock_barcode.stock_picking_action_kanban')
action['context'].update(additional_context)
return {'action': action}
class StockPickingType(models.Model):
_inherit = 'stock.picking.type'
barcode_validation_after_dest_location = fields.Boolean("Force a destination on all products")
barcode_validation_all_product_packed = fields.Boolean("Force all products to be packed")
barcode_validation_full = fields.Boolean(
"Allow full picking validation", default=True,
help="Allow to validate a picking even if nothing was scanned yet (and so, do an immediate transfert)")
restrict_scan_product = fields.Boolean(
"Force Product scan?", help="Line's product must be scanned before the line can be edited")
restrict_put_in_pack = fields.Selection(
[
('mandatory', "After each product"),
('optional', "After group of Products"),
('no', "No"),
], "Force put in pack?",
help="Does the picker have to put in a package the scanned products? If yes, at which rate?",
default="optional", required=True)
restrict_scan_tracking_number = fields.Selection(
[
('mandatory', "Mandatory Scan"),
('optional', "Optional Scan"),
], "Force Lot/Serial scan?", default='optional', required=True)
restrict_scan_source_location = fields.Selection(
[
('no', "No Scan"),
('mandatory', "Mandatory Scan"),
], "Force Source Location scan?", default='no', required=True)
restrict_scan_dest_location = fields.Selection(
[
('mandatory', "After each product"),
('optional', "After group of Products"),
('no', "No"),
], "Force Destination Location scan?",
help="Does the picker have to scan the destination? If yes, at which rate?",
default='optional', required=True)
show_barcode_validation = fields.Boolean(
compute='_compute_show_barcode_validation',
help='Technical field used to compute whether the "Final Validation" group should be displayed, solving combined groups/invisible complexity.')
@api.depends('restrict_scan_product', 'restrict_put_in_pack', 'restrict_scan_dest_location')
def _compute_show_barcode_validation(self):
for picking_type in self:
# reflect all fields invisible conditions
hide_full = picking_type.restrict_scan_product
hide_all_product_packed = not self.user_has_groups('stock.group_tracking_lot') or\
picking_type.restrict_put_in_pack != 'optional'
hide_dest_location = not self.user_has_groups('stock.group_stock_multi_locations') or\
(picking_type.code == 'outgoing' or picking_type.restrict_scan_dest_location != 'optional')
# show if not all hidden
picking_type.show_barcode_validation = not (hide_full and hide_all_product_packed and hide_dest_location)
@api.constrains('restrict_scan_source_location', 'restrict_scan_dest_location')
def _check_restrinct_scan_locations(self):
for picking_type in self:
if picking_type.code == 'internal' and\
picking_type.restrict_scan_dest_location == 'optional' and\
picking_type.restrict_scan_source_location == 'mandatory':
raise UserError(_("If the source location must be scanned for each product, the destination location must be either scanned after each line too, either not scanned at all."))
def get_action_picking_tree_ready_kanban(self):
return self._get_action('stock_barcode.stock_picking_action_kanban')
def _get_barcode_config(self):
self.ensure_one()
# Defines if all lines need to be packed to be able to validate a transfer.
locations_enable = self.env.user.has_group('stock.group_stock_multi_locations')
lines_need_to_be_packed = self.env.user.has_group('stock.group_tracking_lot') and (
self.restrict_put_in_pack == 'mandatory' or (
self.restrict_put_in_pack == 'optional'
and self.barcode_validation_all_product_packed
)
)
config = {
# Boolean fields.
'barcode_validation_after_dest_location': self.barcode_validation_after_dest_location,
'barcode_validation_all_product_packed': self.barcode_validation_all_product_packed,
'barcode_validation_full': not self.restrict_scan_product and self.barcode_validation_full, # Forced to be False when scanning a product is mandatory.
'restrict_scan_product': self.restrict_scan_product,
# Selection fields converted into boolean.
'restrict_scan_tracking_number': self.restrict_scan_tracking_number == 'mandatory',
'restrict_scan_source_location': locations_enable and self.restrict_scan_source_location == 'mandatory',
# Selection fields.
'restrict_put_in_pack': self.restrict_put_in_pack,
'restrict_scan_dest_location': self.restrict_scan_dest_location if locations_enable else 'no',
# Additional parameters.
'lines_need_to_be_packed': lines_need_to_be_packed,
}
return config
def _get_fields_stock_barcode(self):
return [
'default_location_dest_id',
'default_location_src_id',
]