# -*- coding: utf-8 -*- from odoo import fields, models, api, _ from odoo.exceptions import UserError, ValidationError import logging _logger = logging.getLogger(__name__) # mapping invoice type to refund type TYPE2REFUND = { 'out_invoice': 'out_refund', # Customer Invoice 'in_invoice': 'in_refund', # Vendor Bill 'out_refund': 'out_invoice', # Customer Refund 'in_refund': 'in_invoice', # Vendor Refund } class AccountInvoice(models.Model): _inherit = 'account.invoice' # # overridden this method to deduct discounted amount from total of invoice @api.one @api.depends('invoice_line_ids.price_subtotal', 'tax_line_ids.amount', 'currency_id', 'company_id', 'date_invoice', 'type', 'discount') def _compute_amount(self): round_curr = self.currency_id.round self.amount_untaxed = sum(line.price_subtotal for line in self.invoice_line_ids) self.amount_tax = sum(round_curr(line.amount) for line in self.tax_line_ids) amount_total = self.amount_untaxed + self.amount_tax - self.discount self.round_off_amount = self.env['rounding.off'].round_off_value_to_nearest(amount_total) self.amount_total = self.amount_untaxed + self.amount_tax - self.discount + self.round_off_amount amount_total_company_signed = self.amount_total amount_untaxed_signed = self.amount_untaxed if self.currency_id and self.company_id and self.currency_id != self.company_id.currency_id: currency_id = self.currency_id.with_context(date=self.date_invoice) amount_total_company_signed = currency_id.compute(self.amount_total, self.company_id.currency_id) amount_untaxed_signed = currency_id.compute(self.amount_untaxed, self.company_id.currency_id) sign = self.type in ['in_refund', 'out_refund'] and -1 or 1 self.amount_total_company_signed = amount_total_company_signed * sign self.amount_total_signed = self.amount_total * sign self.amount_untaxed_signed = amount_untaxed_signed * sign discount_type = fields.Selection([('none', 'No Discount'), ('fixed', 'Fixed'), ('percentage', 'Percentage')], string="Discount Method", default='none') discount = fields.Monetary(string="Discount") discount_percentage = fields.Float(string="Discount Percentage") disc_acc_id = fields.Many2one('account.account', string="Discount Account Head") round_off_amount = fields.Monetary(string="Round Off Amount", compute=_compute_amount) @api.onchange('invoice_line_ids') def onchange_invoice_lines(self): amount_total = self.amount_untaxed + self.amount_tax if self.discount_type == 'fixed': self.discount_percentage = (self.discount / amount_total) * 100 elif self.discount_type == 'percentage': self.discount = amount_total * self.discount_percentage / 100 @api.onchange('discount', 'discount_percentage', 'discount_type') def onchange_discount(self): amount_total = self.amount_untaxed + self.amount_tax if self.discount: self.discount_percentage = (self.discount / amount_total) * 100 if self.discount_percentage: self.discount = amount_total * self.discount_percentage / 100 @api.multi def _find_batch(self, product, qty, location, picking): _logger.info("\n\n***** Product :%s, Quantity :%s Location :%s\n*****",product,qty,location) lot_objs = self.env['stock.production.lot'].search([('product_id','=',product.id),('life_date','>=',str(fields.datetime.now()))]) _logger.info('\n *** Searched Lot Objects:%s \n',lot_objs) if any(lot_objs): #Sort losts based on the expiry date FEFO(First Expiry First Out) lot_objs = list(lot_objs) sorted_lot_list = sorted(lot_objs, key=lambda l: l.life_date) _logger.info('\n *** Sorted based on FEFO :%s \n',sorted_lot_list) done_qty = qty res_lot_ids = [] lot_ids_for_query = tuple([lot.id for lot in sorted_lot_list]) self._cr.execute("SELECT SUM(qty) FROM stock_quant WHERE lot_id IN %s and location_id=%s",(lot_ids_for_query,location.id,)) qry_rslt = self._cr.fetchall() available_qty = qry_rslt[0] and qry_rslt[0][0] or 0 if available_qty >= qty: for lot_obj in sorted_lot_list: quants = lot_obj.quant_ids.filtered(lambda q: q.location_id == location) for quant in quants: if done_qty >= 0: res_lot_ids.append(lot_obj) done_qty = done_qty - quant.qty return res_lot_ids else: message = ("Auto validation Failed
Reason: There are not enough stock available for %s product on %s Location") % (product.id,product.name,location.id,location.name) picking.message_post(body=message) else: message = ("Auto validation Failed
Reason: There are no Batches/Serial no's available for %s product") % (product.id,product.name) picking.message_post(body=message) return False @api.multi def action_move_create(self): #This method is overriden to pass the Discount Journal Entry. """ Creates invoice related analytics and financial move lines """ account_move = self.env['account.move'] for inv in self: if not inv.journal_id.sequence_id: raise UserError(_('Please define sequence on the journal related to this invoice.')) if not inv.invoice_line_ids: raise UserError(_('Please create some invoice lines.')) if inv.move_id: continue ctx = dict(self._context, lang=inv.partner_id.lang) if not inv.date_invoice: inv.with_context(ctx).write({'date_invoice': fields.Date.context_today(self)}) company_currency = inv.company_id.currency_id # create move lines (one per invoice line + eventual taxes and analytic lines) iml = inv.invoice_line_move_line_get() iml += inv.tax_line_move_line_get() diff_currency = inv.currency_id != company_currency # create one move line for the total and possibly adjust the other lines amount total, total_currency, iml = inv.with_context(ctx).compute_invoice_totals(company_currency, iml) name = inv.name or '/' if inv.payment_term_id: totlines = inv.with_context(ctx).payment_term_id.with_context(currency_id=company_currency.id).compute(total, inv.date_invoice)[0] res_amount_currency = total_currency ctx['date'] = inv._get_currency_rate_date() for i, t in enumerate(totlines): if inv.currency_id != company_currency: amount_currency = company_currency.with_context(ctx).compute(t[1], inv.currency_id) else: amount_currency = False # last line: add the diff res_amount_currency -= amount_currency or 0 if i + 1 == len(totlines): amount_currency += res_amount_currency iml.append({ 'type': 'dest', 'name': name, 'price': t[1], 'account_id': inv.account_id.id, 'date_maturity': t[0], 'amount_currency': diff_currency and amount_currency, 'currency_id': diff_currency and inv.currency_id.id, 'invoice_id': inv.id }) else: iml.append({ 'type': 'dest', 'name': name, 'price': total, 'account_id': inv.account_id.id, 'date_maturity': inv.date_due, 'amount_currency': diff_currency and total_currency, 'currency_id': diff_currency and inv.currency_id.id, 'invoice_id': inv.id }) part = self.env['res.partner']._find_accounting_partner(inv.partner_id) line = [(0, 0, self.line_get_convert(l, part.id)) for l in iml] line = inv.group_lines(iml, line) journal = inv.journal_id.with_context(ctx) line = inv.finalize_invoice_move_lines(line) date = inv.date or inv.date_invoice move_vals = { 'ref': inv.reference, 'line_ids': line, 'journal_id': journal.id, 'date': date, 'narration': inv.comment, } ctx['company_id'] = inv.company_id.id ctx['invoice'] = inv ctx_nolang = ctx.copy() ctx_nolang.pop('lang', None) move = account_move.with_context(ctx_nolang).create(move_vals) #=============Customized code starts========= if inv.discount: if inv.type == 'out_refund': move_line = move.line_ids.filtered(lambda l:l.name==inv.name) move_line.credit -= inv.discount move_line_vals = { 'name':'Discount', 'company_id':move.company_id.id, 'account_id':inv.disc_acc_id.id, 'credit':inv.discount, 'date_maturity':date, 'currency_id': diff_currency and inv.currency_id.id, 'invoice_id': inv.id, 'partner_id':move_line.partner_id.id, 'move_id':move.id, } self.env['account.move.line'].create(move_line_vals) else: move_line = move.line_ids.filtered(lambda l:l.name=='/') move_line.debit -= inv.discount move_line_vals = { 'name':'Discount', 'company_id':move.company_id.id, 'account_id':inv.disc_acc_id.id, 'debit':inv.discount, 'date_maturity':date, 'currency_id': diff_currency and inv.currency_id.id, 'invoice_id': inv.id, 'partner_id':move_line.partner_id.id, 'move_id':move.id, } self.env['account.move.line'].create(move_line_vals) #===========Customized code ends============= # Pass invoice in context in method post: used if you want to get the same # account move reference when creating the same invoice after a cancelled one: move.post() # make the invoice point to that move vals = { 'move_id': move.id, 'date': date, 'move_name': move.name, } inv.with_context(ctx).write(vals) return True @api.model def _prepare_refund(self, invoice, date_invoice=None, date=None, description=None, journal_id=None): """ Prepare the dict of values to create the new refund from the invoice. This method may be overridden to implement custom refund generation (making sure to call super() to establish a clean extension chain). :param record invoice: invoice to refund :param string date_invoice: refund creation date from the wizard :param integer date: force date from the wizard :param string description: description of the refund from the wizard :param integer journal_id: account.journal from the wizard :return: dict of value to create() the refund """ values = {} for field in self._get_refund_copy_fields(): if invoice._fields[field].type == 'many2one': values[field] = invoice[field].id else: values[field] = invoice[field] or False values['invoice_line_ids'] = self._refund_cleanup_lines(invoice.invoice_line_ids) tax_lines = invoice.tax_line_ids taxes_to_change = { line.tax_id.id: line.tax_id.refund_account_id.id for line in tax_lines.filtered(lambda l: l.tax_id.refund_account_id != l.tax_id.account_id) } cleaned_tax_lines = self._refund_cleanup_lines(tax_lines) values['tax_line_ids'] = self._refund_tax_lines_account_change(cleaned_tax_lines, taxes_to_change) if journal_id: journal = self.env['account.journal'].browse(journal_id) elif invoice['type'] == 'in_invoice': journal = self.env['account.journal'].search([('type', '=', 'purchase')], limit=1) else: journal = self.env['account.journal'].search([('type', '=', 'sale')], limit=1) values['journal_id'] = journal.id values['type'] = TYPE2REFUND[invoice['type']] values['date_invoice'] = date_invoice or fields.Date.context_today(invoice) values['state'] = 'draft' values['number'] = False values['origin'] = invoice.number values['payment_term_id'] = False values['refund_invoice_id'] = invoice.id #=============Customized code starts========= Added Custom discount fields in refund values['discount_type'] = invoice.discount_type values['discount'] = invoice.discount values['discount_percentage'] = invoice.discount_percentage values['disc_acc_id'] = invoice.disc_acc_id.id #===========Customized code ends============= if date: values['date'] = date if description: values['name'] = description return values