质量模块和库存扫码

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,16 @@
# -*- coding: utf-8 -*-
from . import stock_picking
from . import stock_quant
from . import stock_scrap
from . import stock_location
from . import stock_move_line
from . import stock_package_type
from . import stock_lot
from . import stock_quant_package
from . import stock_warehouse
from . import product_product
from . import product_packaging
from . import res_config_settings
from . import res_partner
from . import uom_uom

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models, api
class ProductPackaging(models.Model):
_inherit = 'product.packaging'
_barcode_field = 'barcode'
def _get_stock_barcode_specific_data(self):
products = self.product_id
return {
'product.product': products.read(self.env['product.product']._get_fields_stock_barcode(), load=False),
'uom.uom': products.uom_id.read(self.env['uom.uom']._get_fields_stock_barcode(), load=False)
}
@api.model
def _get_fields_stock_barcode(self):
return ['barcode', 'product_id', 'qty', 'name']

View File

@@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models, api
class Product(models.Model):
_inherit = 'product.product'
_barcode_field = 'barcode'
@api.model
def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
# sudo is added for external users to get the products
args = self.env.company.sudo().nomenclature_id._preprocess_gs1_search_args(args, ['product'])
return super()._search(args, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid)
@api.model
def _get_fields_stock_barcode(self):
return ['barcode', 'default_code', 'categ_id', 'code', 'detailed_type', 'tracking', 'display_name', 'uom_id']
def _get_stock_barcode_specific_data(self):
return {
'uom.uom': self.uom_id.read(self.env['uom.uom']._get_fields_stock_barcode(), load=False)
}
def prefilled_owner_package_stock_barcode(self, lot_id=False, lot_name=False):
quant = self.env['stock.quant'].search_read(
[
lot_id and ('lot_id', '=', lot_id) or lot_name and ('lot_id.name', '=', lot_name),
('location_id.usage', '=', 'internal'),
('product_id', '=', self.id),
],
['package_id', 'owner_id'],
limit=1, load=False
)
if quant:
quant = quant[0]
res = {'quant': quant, 'records': {}}
if quant and quant['package_id']:
res['records']['stock.quant.package'] = self.env['stock.quant.package'].browse(quant['package_id']).read(self.env['stock.quant.package']._get_fields_stock_barcode(), load=False)
if quant and quant['owner_id']:
res['records']['res.partner'] = self.env['res.partner'].browse(quant['owner_id']).read(self.env['res.partner']._get_fields_stock_barcode(), load=False)
return res

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
barcode_nomenclature_id = fields.Many2one('barcode.nomenclature', related='company_id.nomenclature_id', readonly=False)
stock_barcode_demo_active = fields.Boolean("Demo Data Active", compute='_compute_stock_barcode_demo_active')
show_barcode_nomenclature = fields.Boolean(compute='_compute_show_barcode_nomenclature')
@api.depends('company_id')
def _compute_show_barcode_nomenclature(self):
self.show_barcode_nomenclature = self.module_stock_barcode and self.env['barcode.nomenclature'].search_count([]) > 1
@api.depends('company_id')
def _compute_stock_barcode_demo_active(self):
for rec in self:
rec.stock_barcode_demo_active = bool(self.env['ir.module.module'].search([('name', '=', 'stock_barcode'), ('demo', '=', True)]))

View File

@@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models, api
class Partner(models.Model):
_inherit = 'res.partner'
@api.model
def _get_fields_stock_barcode(self):
return ['display_name']

View File

@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models, api
class Location(models.Model):
_inherit = 'stock.location'
_barcode_field = 'barcode'
@api.model
def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
args = self.env.company.nomenclature_id._preprocess_gs1_search_args(args, ['location', 'location_dest'])
return super()._search(args, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid)
@api.model
def _get_fields_stock_barcode(self):
return ['barcode', 'display_name', 'name', 'parent_path', 'usage']

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models, api
class StockLot(models.Model):
_inherit = 'stock.lot'
_barcode_field = 'name'
def _get_stock_barcode_specific_data(self):
products = self.product_id
return {
'product.product': products.read(self.env['product.product']._get_fields_stock_barcode(), load=False),
'uom.uom': products.uom_id.read(self.env['uom.uom']._get_fields_stock_barcode(), load=False)
}
@api.model
def _get_fields_stock_barcode(self):
return ['name', 'ref', 'product_id']

View File

@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models
class StockMoveLine(models.Model):
_inherit = 'stock.move.line'
product_barcode = fields.Char(related='product_id.barcode')
location_processed = fields.Boolean()
dummy_id = fields.Char(compute='_compute_dummy_id', inverse='_inverse_dummy_id')
picking_location_id = fields.Many2one(related='picking_id.location_id')
picking_location_dest_id = fields.Many2one(related='picking_id.location_dest_id')
product_stock_quant_ids = fields.One2many('stock.quant', compute='_compute_product_stock_quant_ids')
product_packaging_id = fields.Many2one(related='move_id.product_packaging_id')
product_packaging_uom_qty = fields.Float('Packaging Quantity', compute='_compute_product_packaging_uom_qty', help="Quantity of the Packaging in the UoM of the Stock Move Line.")
is_completed = fields.Boolean(compute='_compute_is_completed', help="Check if the quantity done matches the demand")
@api.depends('product_id', 'product_id.stock_quant_ids')
def _compute_product_stock_quant_ids(self):
for line in self:
line.product_stock_quant_ids = line.product_id.stock_quant_ids.filtered(lambda q: q.company_id in self.env.companies and q.location_id.usage == 'internal')
def _compute_dummy_id(self):
self.dummy_id = ''
def _compute_product_packaging_uom_qty(self):
for sml in self:
sml.product_packaging_uom_qty = sml.product_packaging_id.product_uom_id._compute_quantity(sml.product_packaging_id.qty, sml.product_uom_id)
@api.depends('qty_done')
def _compute_is_completed(self):
for line in self:
line.is_completed = line.qty_done == line.reserved_uom_qty
def _inverse_dummy_id(self):
pass
def _get_fields_stock_barcode(self):
return [
'product_id',
'location_id',
'location_dest_id',
'qty_done',
'display_name',
'reserved_uom_qty',
'product_uom_id',
'product_barcode',
'owner_id',
'lot_id',
'lot_name',
'package_id',
'result_package_id',
'dummy_id',
'product_packaging_id',
'product_packaging_uom_qty',
]

View File

@@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models
class PackageType(models.Model):
_inherit = 'stock.package.type'
@api.model
def _get_fields_stock_barcode(self):
return ['barcode', 'name']

View File

@@ -0,0 +1,336 @@
# -*- 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',
]

View File

@@ -0,0 +1,136 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
class StockQuant(models.Model):
_inherit = 'stock.quant'
dummy_id = fields.Char(compute='_compute_dummy_id', inverse='_inverse_dummy_id')
def _compute_dummy_id(self):
self.dummy_id = ''
def _inverse_dummy_id(self):
pass
@api.model
def barcode_write(self, vals):
""" Specially made to handle barcode app saving. Avoids overriding write method because pickings in barcode
will also write to quants and handling context in this case is non-trivial. This method is expected to be
called only when no record and vals is a list of lists of the form: [[1, quant_id, {write_values}],
[0, 0, {write_values}], ...]} where [1, quant_id...] updates an existing quant or {[0, 0, ...]}
when creating a new quant."""
Quant = self.env['stock.quant'].with_context(inventory_mode=True)
# TODO batch
for val in vals:
if val[0] in (0, 1) and not val[2].get('lot_id') and val[2].get('lot_name'):
quant_db = val[0] == 1 and Quant.browse(val[1]) or False
val[2]['lot_id'] = self.env['stock.lot'].create({
'name': val[2].pop('lot_name'),
'product_id': val[2].get('product_id', quant_db and quant_db.product_id.id or False),
'company_id': self.env['stock.location'].browse(val[2].get('location_id') or quant_db.location_id.id).company_id.id
}).id
quant_ids = []
for val in vals:
if val[0] == 1:
quant_id = val[1]
Quant.browse(quant_id).write(val[2])
quant_ids.append(quant_id)
elif val[0] == 0:
quant = Quant.create(val[2])
# in case an existing quant is written on instead (happens when scanning a product
# with quants, but not assigned to user or doesn't have an inventory date to normally show up in view)
if val[2].get('dummy_id'):
quant.write({'dummy_id': val[2].get('dummy_id')})
quant.write({'inventory_date': val[2].get('inventory_date')})
user_id = val[2].get('user_id')
# assign a user if one isn't assigned to avoid line disappearing when page left and returned to
if not quant.user_id and user_id:
quant.write({'user_id': user_id})
quant_ids.append(quant.id)
return self.browse(quant_ids)._get_stock_barcode_data()
def action_validate(self):
quants = self.with_context(inventory_mode=True).filtered(lambda q: q.inventory_quantity_set)
quants._compute_inventory_diff_quantity()
res = quants.action_apply_inventory()
if res:
return res
return True
def action_client_action(self):
""" Open the mobile view specialized in handling barcodes on mobile devices.
"""
action = self.env['ir.actions.actions']._for_xml_id('stock_barcode.stock_barcode_inventory_client_action')
return dict(action, target='fullscreen')
def _get_stock_barcode_data(self):
locations = self.env['stock.location']
company_id = self.env.company.id
package_types = self.env['stock.package.type']
if not self: # `self` is an empty recordset when we open the inventory adjustment.
if self.env.user.has_group('stock.group_stock_multi_locations'):
locations = self.env['stock.location'].search([('usage', 'in', ['internal', 'transit']), ('company_id', '=', company_id)], order='id')
else:
locations = self.env['stock.warehouse'].search([('company_id', '=', company_id)], limit=1).lot_stock_id
self = self.env['stock.quant'].search([('user_id', '=?', self.env.user.id), ('location_id', 'in', locations.ids), ('inventory_date', '<=', fields.Date.today())])
if self.env.user.has_group('stock.group_tracking_lot'):
package_types = package_types.search([])
data = self.with_context(display_default_code=False, barcode_view=True).get_stock_barcode_data_records()
if locations:
data["records"]["stock.location"] = locations.read(locations._get_fields_stock_barcode(), load=False)
if package_types:
data["records"]["stock.package.type"] = package_types.read(package_types._get_fields_stock_barcode(), load=False)
data['line_view_id'] = self.env.ref('stock_barcode.stock_quant_barcode').id
return data
def get_stock_barcode_data_records(self):
products = self.product_id
companies = self.company_id or self.env.company
lots = self.lot_id
owners = self.owner_id
packages = self.package_id
uoms = products.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([])
data = {
"records": {
"stock.quant": self.read(self._get_fields_stock_barcode(), load=False),
"product.product": products.read(products._get_fields_stock_barcode(), load=False),
"stock.quant.package": packages.read(packages._get_fields_stock_barcode(), load=False),
"res.company": companies.read(['name']),
"res.partner": owners.read(owners._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],
"user_id": self.env.user.id,
}
return data
def _get_fields_stock_barcode(self):
return [
'product_id',
'location_id',
'inventory_date',
'inventory_quantity',
'inventory_quantity_set',
'quantity',
'product_uom_id',
'lot_id',
'package_id',
'owner_id',
'inventory_diff_quantity',
'dummy_id',
'user_id',
]
def _get_inventory_fields_write(self):
return ['dummy_id'] + super()._get_inventory_fields_write()

View File

@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models, api
class QuantPackage(models.Model):
_inherit = 'stock.quant.package'
_barcode_field = 'name'
@api.model
def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
args = self.env.company.nomenclature_id._preprocess_gs1_search_args(args, ['package'], 'name')
return super()._search(args, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid)
@api.model
def action_create_from_barcode(self, vals_list):
""" Creates a new package then returns its data to be added in the client side cache.
"""
res = self.create(vals_list)
return {
'stock.quant.package': res.read(self._get_fields_stock_barcode(), False)
}
@api.model
def _get_fields_stock_barcode(self):
return ['name', 'location_id', 'package_type_id', 'quant_ids']
@api.model
def _get_usable_packages(self):
usable_packages_domain = [
'|',
('package_use', '=', 'reusable'),
('location_id', '=', False),
]
return self.env['stock.quant.package'].search(usable_packages_domain)

View File

@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class StockScrap(models.Model):
_name = 'stock.scrap'
_inherit = ['stock.scrap', 'barcodes.barcode_events_mixin']
product_barcode = fields.Char(related='product_id.barcode', string='Barcode', readonly=False)
def on_barcode_scanned(self, barcode):
self.ensure_one()
product = self.env['product.product'].search([('barcode', '=', barcode)])
if product and self.product_id == product:
self.scrap_qty += 1
elif product:
self.scrap_qty = 1
self.product_id = product
self.lot_id = False
else:
lot = self.env['stock.lot'].search([('name', '=', barcode)])
if lot and self.lot_id == lot:
self.scrap_qty += 1
elif lot:
self.scrap_qty = 1
self.lot_id = lot.id
self.product_id = lot.product_id

View File

@@ -0,0 +1,24 @@
from odoo import models
class StockWarehouse(models.Model):
_inherit = 'stock.warehouse'
def _get_picking_type_create_values(self, max_sequence):
values = super()._get_picking_type_create_values(max_sequence)
values[0]['pick_type_id']['restrict_scan_source_location'] = 'mandatory'
values[0]['pick_type_id']['restrict_scan_dest_location'] = 'no'
return values
def _get_picking_type_update_values(self):
values = super()._get_picking_type_update_values()
# When multi-steps delivery is enabled, the source scan setting for the pick is equal to the
# delivery type's one, and the scan source for the delivery is disabled (by default).
if values['pick_type_id'].get('active'):
if self.out_type_id.restrict_scan_source_location == 'mandatory' and self.pick_type_id.restrict_scan_dest_location == 'optional':
values['out_type_id']['restrict_scan_source_location'] = 'no'
values['pick_type_id']['restrict_scan_source_location'] = self.out_type_id.restrict_scan_source_location
values['pick_type_id']['restrict_scan_dest_location'] = 'no'
elif not values['pick_type_id'].get('active') and self.pick_type_id.active:
values['out_type_id']['restrict_scan_source_location'] = self.pick_type_id.restrict_scan_source_location
return values

View File

@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
from odoo import models, api
class UoM(models.Model):
_inherit = 'uom.uom'
@api.model
def _get_fields_stock_barcode(self):
return [
'name',
'category_id',
'factor',
'rounding',
]