# -*- coding: utf-8 -*- # © 2017 Techspawn Solutions # © 2015 Vauxoo # © 2013 Camptocamp # © 2009-2013 Akretion, # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import calendar import math from datetime import datetime from dateutil.relativedelta import relativedelta from odoo import _, api, exceptions, fields, models from odoo.tools import (DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT) from .invoice_no_date import InvoiceNoDate from .product_no_supplier import ProductNoSupplier class ClaimLine(models.Model): _name = "claim.line" _inherit = 'mail.thread' _description = "List of product to return" # _rec_name = "display_name" SUBJECT_LIST = [('none', 'Not specified'), ('legal', 'Legal retractation'), ('cancellation', 'Order cancellation'), ('damaged', 'Damaged delivered product'), ('error', 'Shipping error'), ('exchange', 'Exchange request'), ('lost', 'Lost during transport'), ('perfect_conditions', 'Perfect Conditions'), ('imperfection', 'Imperfection'), ('physical_damage_client', 'Physical Damage by Client'), ('physical_damage_company', 'Physical Damage by Company'), ('other', 'Other')] WARRANT_COMMENT = [ ('valid', _("Valid")), ('expired', _("Expired")), ('not_define', _("Not Defined"))] @api.model def get_warranty_return_partner(self): return self.env['product.supplierinfo'].fields_get( 'warranty_return_partner')['warranty_return_partner']['selection'] number = fields.Char( readonly=True, default='/', help='Claim Line Identification Number') company_id = fields.Many2one( 'res.company', string='Company', readonly=False, change_default=True, default=lambda self: self.env['res.company']._company_default_get( 'claim.line')) date = fields.Date('Claim Line Date', index=True, default=fields.date.today()) name = fields.Char('Description', default='none', required=False, help="More precise description of the problem") priority = fields.Selection([('0_not_define', 'Not Define'), ('1_normal', 'Normal'), ('2_high', 'High'), ('3_very_high', 'Very High')], 'Priority', default='0_not_define', # compute='_compute_priority', store=True, readonly=False, help="Priority attention of claim line") claim_diagnosis = fields.\ Selection([('damaged', 'Product Damaged'), ('repaired', 'Product Repaired'), ('good', 'Product in good condition'), ('hidden', 'Product with hidden physical damage'), ], help="To describe the line product diagnosis") claim_origin = fields.Selection(SUBJECT_LIST, 'Claim Subject', required=False, help="To describe the " "line product problem") product_id = fields.Many2one('product.product', string='Product', help="Returned product") product_returned_quantity = \ fields.Float('Quantity', digits=(12, 2), help="Quantity of product returned") unit_sale_price = fields.Float(digits=(12, 2), help="Unit sale price of the product. " "Auto filled if retrun done " "by invoice selection. Be careful " "and check the automatic " "value as don't take into account " "previous refunds, invoice " "discount, can be for 0 if product " "for free,...") return_value = fields.Float(compute='_compute_line_total_amount', string='Total return', help="Quantity returned * Unit sold price",) prodlot_id = fields.Many2one('stock.production.lot', string='Serial/Lot number', help="The serial/lot of " "the returned product") applicable_guarantee = fields.Selection([('us', 'Company'), ('supplier', 'Supplier'), ('brand', 'Brand manufacturer')], 'Warranty type') guarantee_limit = fields.Date('Warranty limit', readonly=True, help="The warranty limit is " "computed as: invoice date + warranty " "defined on selected product.") warning = fields.Selection(WARRANT_COMMENT, 'Warranty', readonly=True, help="If warranty has expired") display_name = fields.Char('Name', compute='_compute_display_name') warranty_type = fields.Selection( get_warranty_return_partner, help="Who is in charge of the warranty return treatment towards " "the end customer. Company will use the current company " "delivery or default address and so on for supplier and brand " "manufacturer. Does not necessarily mean that the warranty " "to be applied is the one of the return partner (ie: can be " "returned to the company and be under the brand warranty") warranty_return_partner = fields.Many2one('res.partner', string='Warranty Address', help="Where the customer has to " "send back the product(s)") claim_id = fields.Many2one('crm.claim', string='Related claim', ondelete='cascade', help="To link to the case.claim object") state = fields.Selection([('draft', 'Draft'), ('refused', 'Refused'), ('confirmed', 'Confirmed, waiting for product'), ('in_to_control', 'Received, to control'), ('in_to_treate', 'Controlled, to treate'), ('treated', 'Treated')], string='State', default='draft') substate_id = fields.Many2one('substate.substate', string='Sub state', help="Select a sub state to precise the " "standard state. Example 1: " "state = refused; substate could " "be warranty over, not in " "warranty, no problem,... . " "Example 2: state = to treate; " "substate could be to refund, to " "exchange, to repair,...") last_state_change = fields.Date(string='Last change', help="To set the" "last state / substate change") invoice_line_id = fields.Many2one('account.invoice.line', string='Invoice Line', help='The invoice line related' ' to the returned product') refund_line_id = fields.Many2one('account.invoice.line', string='Refund Line', help='The refund line related' ' to the returned product') move_in_id = fields.Many2one('stock.move', string='Move Line from picking in', help='The move line related' ' to the returned product') move_out_id = fields.Many2one('stock.move', string='Move Line from picking out', help='The move line related' ' to the returned product') location_dest_id = fields.Many2one('stock.location', string='Return Stock Location', help='The return stock location' ' of the returned product') claim_type = fields.Many2one(related='claim_id.claim_type', string="Claim Line Type", store=True, help="Claim classification") invoice_date = fields.Datetime(related='invoice_line_id.invoice_id.' 'create_date', help="Date of Claim Invoice") # Method to calculate total amount of the line : qty*UP @api.multi def _compute_line_total_amount(self): for line in self: line.return_value = (line.unit_sale_price * line.product_returned_quantity) @api.multi def copy(self, default=None): self.ensure_one() default = default or {} std_default = { 'move_in_id': False, 'move_out_id': False, 'refund_line_id': False, } std_default.update(default) return super(ClaimLine, self).copy(default=std_default) @api.depends('invoice_date', 'date') def _compute_priority(self): """ To determine the priority of claim line """ for line_id in self: if line_id.invoice_date: days = fields.datetime.strptime(line_id.date, '%Y-%m-%d') - \ fields.datetime.strptime(line_id.invoice_date, DEFAULT_SERVER_DATETIME_FORMAT) if days.days <= 1: line_id.priority = '3_very_high' elif days.days <= 7: line_id.priority = '2_high' else: line_id.priority = '1_normal' def _get_subject(self, num): if num > 0 and num <= len(self.SUBJECT_LIST): return self.SUBJECT_LIST[num - 1][0] else: return self.SUBJECT_LIST[0][0] @staticmethod def warranty_limit(start, warranty_duration): """ Take a duration in float, return the duration in relativedelta ``relative_delta(months=...)`` only accepts integers. We have to extract the decimal part, and then, extend the delta with days. """ decimal_part, months = math.modf(warranty_duration) months = int(months) # If we have a decimal part, we add the number them as days to # the limit. We need to get the month to know the number of # days. delta = relativedelta(months=months) monthday = start + delta __, days_month = calendar.monthrange(monthday.year, monthday.month) # ignore the rest of the days (hours) since we expect a date days = int(days_month * decimal_part) return start + relativedelta(months=months, days=days) def _warranty_limit_values(self, invoice, claim_type, product, claim_date): if not (invoice and claim_type and product and claim_date): return {'guarantee_limit': False, 'warning': False} invoice_date = invoice.create_date if not invoice_date: raise InvoiceNoDate warning = 'not_define' invoice_date = datetime.strptime(invoice_date, DEFAULT_SERVER_DATETIME_FORMAT) if isinstance(claim_type, self.env['crm.claim.type'].__class__): claim_type = claim_type.id if claim_type == self.env.ref('crm_claim_type.' 'crm_claim_type_supplier').id: try: warranty_duration = product.seller_ids[0].warranty_duration except IndexError: raise ProductNoSupplier else: warranty_duration = product.warranty limit = self.warranty_limit(invoice_date, warranty_duration) if warranty_duration > 0: claim_date = datetime.strptime(claim_date, DEFAULT_SERVER_DATETIME_FORMAT) if limit < claim_date: warning = 'expired' else: warning = 'valid' return {'guarantee_limit': limit.strftime(DEFAULT_SERVER_DATE_FORMAT), 'warning': warning} def set_warranty_limit(self): self.ensure_one() claim = self.claim_id invoice_id = self.invoice_line_id and self.invoice_line_id.invoice_id \ or claim.invoice_id try: values = self._warranty_limit_values( invoice_id, claim.claim_type, self.product_id, claim.date) except InvoiceNoDate: raise exceptions.UserError( _('Cannot find any date for invoice. ' 'Must be a validated invoice.') ) except ProductNoSupplier: raise exceptions.UserError( _('The product has no supplier configured.') ) self.write(values) return True @api.model def auto_set_warranty(self): """ Set warranty automatically if the user has not himself pressed on 'Calculate warranty state' button, it sets warranty for him""" for line in self: if not line.warning: line.set_warranty() return True @api.returns('stock.location') def get_destination_location(self, product_id, warehouse_id): """ Compute and return the destination location to take for a return. Always take 'Supplier' one when return type different from company. """ location_dest_id = warehouse_id.lot_stock_id if product_id.seller_ids: seller = product_id.seller_ids[0] if seller.warranty_return_partner != 'company' \ and seller.name and \ seller.name.property_stock_supplier: location_dest_id = seller.name.property_stock_supplier return location_dest_id def _warranty_return_address_values(self, product, company, warehouse): """ Return the partner to be used as return destination and the destination stock location of the line in case of return. We can have various cases here: - company or other: return to company partner or crm_return_address_id if specified - supplier: return to the supplier address """ if not (product and company and warehouse): return { 'warranty_return_partner': False, 'warranty_type': False, 'location_dest_id': False } sellers = product.seller_ids if sellers: seller = sellers[0] return_address_id = seller.warranty_return_address.id return_type = seller.warranty_return_partner else: # when no supplier is configured, returns to the company return_address = (company.crm_return_address_id or company.partner_id) return_address_id = return_address.id return_type = 'company' location_dest = self.get_destination_location(product, warehouse) return { 'warranty_return_partner': return_address_id, 'warranty_type': return_type, 'location_dest_id': location_dest.id } def set_warranty_return_address(self): self.ensure_one() claim = self.claim_id values = self._warranty_return_address_values( self.product_id, claim.company_id, claim.warehouse_id) self.write(values) return True @api.multi def set_warranty(self): """ Calculate warranty limit and address """ for line_id in self: if not line_id.product_id: raise exceptions.UserError(_('Please set product first')) if not line_id.invoice_line_id: raise exceptions.UserError(_('Please set invoice first')) line_id.set_warranty_limit() line_id.set_warranty_return_address() @api.model def _get_sequence_number(self): """ Return the value of the sequence for the number field in the claim.line model. """ return self.env['ir.sequence'].next_by_code('claim.line') @api.model def create(self, vals): """Return write the identify number once the claim line is create. """ vals = vals or {} if ('number' not in vals) or (vals.get('number', False) == '/'): vals['number'] = self._get_sequence_number() res = super(ClaimLine, self).create(vals) return res @api.multi @api.depends('claim_id.code', 'name') def _compute_display_name(self): for line_id in self: line_id.display_name = "%s - %s" % ( line_id.claim_id.code, line_id.name)