Files
enterprise-addons/helpdesk_stock/models/helpdesk_ticket.py
T

192 lines
8.8 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import itertools
from odoo import api, fields, models, _
from odoo.tools import groupby
class HelpdeskTicket(models.Model):
_inherit = 'helpdesk.ticket'
product_id = fields.Many2one('product.product', string='Product', tracking=True,
check_company=True,
groups="stock.group_stock_user",
domain="[('sale_ok', '=', True), ('id', 'in', suitable_product_ids)]",
help="Product this ticket is about. If selected, only the sales orders, deliveries and invoices including this product will be visible.")
suitable_product_ids = fields.Many2many(
'product.product',
compute='_compute_suitable_product_ids',
export_string_translation=False,
groups="stock.group_stock_user",
)
has_partner_picking = fields.Boolean(compute='_compute_suitable_product_ids', groups="stock.group_stock_user")
tracking = fields.Selection(related='product_id.tracking')
lot_id = fields.Many2one('stock.lot', string='Lot/Serial Number', domain="[('product_id', '=', product_id)]", tracking=True)
pickings_count = fields.Integer('Return Orders Count', compute="_compute_pickings_count")
picking_ids = fields.Many2many('stock.picking', string="Return Orders", copy=False)
replacement_count = fields.Integer(compute='_compute_replacement_count', export_string_translation=False)
@api.depends('partner_id')
def _compute_suitable_product_ids(self):
"""
Computes the suitable products for the given tickets based on the associated
partner's sales orders and outgoing deliveries.
"""
suitable_partners_ids_by_commercial_partner_id = {
commercial_partner_id.id: ids
for commercial_partner_id, ids in self.env['res.partner']._read_group(
domain=[('commercial_partner_id', 'in', self.commercial_partner_id.ids)],
groupby=['commercial_partner_id'],
aggregates=['id:array_agg'],
)
}
for commercial_partner, tickets_list in groupby(self, lambda t: t.commercial_partner_id):
tickets = self.env['helpdesk.ticket'].browse(ticket.id for ticket in tickets_list)
suitable_partner_ids = suitable_partners_ids_by_commercial_partner_id.get(commercial_partner.id)
if not suitable_partner_ids:
tickets.suitable_product_ids = False
tickets.has_partner_picking = False
continue
sale_data = self.env['sale.order.line']._read_group([
('product_id', '!=', False),
('state', '=', 'sale'),
('order_partner_id', 'in', suitable_partner_ids),
], ['order_partner_id'], ['product_id:array_agg'])
order_data = {order_partner.id: product_ids for order_partner, product_ids in sale_data}
picking_data = self.env['stock.picking']._read_group([
('state', '=', 'done'),
('partner_id', 'in', suitable_partner_ids),
('picking_type_code', '=', 'outgoing'),
('is_replacement', '=', False), # exclude replacement pickings
], ['partner_id'], ['id:array_agg'])
# it was not correct, it took only products of stock_move_line from the first partner_id of self
picking_ids = [id_ for __, ids in picking_data for id_ in ids]
outgoing_product = {}
if picking_ids:
move_line_data = self.env['stock.move.line']._read_group([
('state', '=', 'done'),
('picking_id', 'in', picking_ids),
('picking_code', '=', 'outgoing'),
], ['picking_id'], ['product_id:array_agg'])
move_lines = {picking.id: product_ids for picking, product_ids in move_line_data}
if move_lines:
for partner, picking_ids in picking_data:
product_lists = [move_lines[pick] for pick in picking_ids if pick in move_lines]
outgoing_product[partner.id] = list(itertools.chain(*product_lists))
product_ids = {item for partner_id in suitable_partner_ids for item in order_data.get(partner_id, []) + outgoing_product.get(partner_id, [])}
tickets.suitable_product_ids = [fields.Command.set(product_ids)]
tickets.has_partner_picking = any((partner_id in outgoing_product) or (partner_id in order_data) for partner_id in suitable_partner_ids)
@api.onchange('suitable_product_ids')
def onchange_product_id(self):
if self.product_id not in self.suitable_product_ids:
self.product_id = False
@api.depends('picking_ids')
def _compute_replacement_count(self):
replacements_count_per_ticket = dict(
self.env['stock.picking']._read_group(
domain=[('ticket_id', 'in', self.ids), ('is_replacement', '=', True)],
groupby=['ticket_id'],
aggregates=['__count'],
)
)
for ticket in self:
ticket.replacement_count = replacements_count_per_ticket.get(ticket, 0)
@api.depends('picking_ids', 'replacement_count')
def _compute_pickings_count(self):
for ticket in self:
ticket.pickings_count = len(ticket.picking_ids) - ticket.replacement_count
@api.onchange('partner_id', 'team_id')
def _compute_display_extra_info(self):
show_product_id_records = self.filtered(lambda ticket:
(ticket.partner_id or ticket.use_product_repairs) and\
(ticket.use_credit_notes or ticket.use_product_returns or ticket.use_product_repairs)
)
show_product_id_records.display_extra_info = True
super(HelpdeskTicket, self - show_product_id_records)._compute_display_extra_info()
def write(self, vals):
res = super().write(vals)
if 'suitable_product_ids' in vals:
self.filtered(lambda t: t.product_id not in t.suitable_product_ids).product_id = False
return res
def copy_data(self, default=None):
has_stock_access = self.env.user.has_group('stock.group_stock_user')
if not has_stock_access:
if default is None:
default = {}
default['product_id'] = False
vals_list = super().copy_data(default=default)
if not has_stock_access:
for vals in vals_list:
del vals['product_id']
return vals_list
def action_view_pickings(self):
self.ensure_one()
picking_ids = self.picking_ids.filtered(lambda p: not p.is_replacement).ids
action = {
'type': 'ir.actions.act_window',
'name': _('Return Orders'),
'res_model': 'stock.picking',
'view_mode': 'list,form',
'domain': [('id', 'in', picking_ids)],
'context': dict(self.env.context, create=False, default_company_id=self.company_id.id)
}
if len(picking_ids) == 1:
action.update({
'view_mode': 'form',
'res_id': picking_ids[0],
})
return action
def _get_action_replacements_context(self):
return dict(
default_company_id=self.company_id.id,
default_ticket_id=self.id,
default_partner_id=self.partner_id.address_get(['delivery'])['delivery'],
default_origin=self.env._('Ticket: %(ticket_name)s', ticket_name=self.name),
restricted_picking_type_code='outgoing',
default_sale_id=self.sale_order_id.id,
default_is_replacement=True,
replacement_create_trigger=True,
)
def action_view_replacements(self):
self.ensure_one()
replacement_ids = self.picking_ids.filtered(lambda p: p.is_replacement).ids
action = {
'type': 'ir.actions.act_window',
'res_model': 'stock.picking',
'name': self.env._('Delivery Orders'),
'view_mode': 'list,form,kanban,calendar,activity',
'domain': [('id', 'in', replacement_ids)],
'context': self._get_action_replacements_context(),
'help': self.env._("""
<p class="o_view_nocontent_smiling_face o_view_nocontent_stock">No delivery orders yet. Let's create one!</p>
<p>Send customers a replacement for a lost, damaged, or returned item</p>
"""),
}
if len(replacement_ids) == 1:
action.update({
'view_mode': 'form',
'res_id': replacement_ids[0],
})
return action
def action_create_replacement(self):
self.ensure_one()
return {
**self.env['ir.actions.actions']._for_xml_id('stock.action_picking_form'),
'name': self.env._('Create Replacement Order'),
'context': self._get_action_replacements_context(),
}