287 lines
14 KiB
Python
287 lines
14 KiB
Python
# -*- 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 = ("<b>Auto validation Failed</b> <br/> <b>Reason:</b> There are not enough stock available for <a href=# data-oe-model=product.product data-oe-id=%d>%s</a> product on <a href=# data-oe-model=stock.location data-oe-id=%d>%s</a> Location") % (product.id,product.name,location.id,location.name)
|
|
picking.message_post(body=message)
|
|
else:
|
|
message = ("<b>Auto validation Failed</b> <br/> <b>Reason:</b> There are no Batches/Serial no's available for <a href=# data-oe-model=product.product data-oe-id=%d>%s</a> 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
|
|
|