Skip to content

Instantly share code, notes, and snippets.

@alcidesrivera
Last active October 31, 2015 04:41
Show Gist options
  • Save alcidesrivera/182b3ec8be78a19f8695 to your computer and use it in GitHub Desktop.
Save alcidesrivera/182b3ec8be78a19f8695 to your computer and use it in GitHub Desktop.
# -*- coding: utf-8 -*-
##############################################################################
#
# Account Module - Ecuador
# Copyright (C) 2014 Cristian Salamea All Rights Reserved
# $Id$
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import time
import logging
from openerp.osv import osv, fields
from tools import config
from tools.translate import _
from tools import ustr
import decimal_precision as dp
import netsvc
class AccountWithdrawing(osv.osv):
def name_get(self, cr, uid, ids, context=None):
if context is None:
context = {}
if not ids:
return []
res = []
reads = self.browse(cr, uid, ids, context=context)
for record in reads:
name = record.name
res.append((record.id, name))
return res
def _amount_total(self, cr, uid, ids, field_name, args, context):
res = {}
retentions = self.browse(cr, uid, ids, context)
for ret in retentions:
total = 0
for tax in ret.tax_ids:
total += tax.amount
res[ret.id] = abs(total)
return res
def _get_period(self, cr, uid, ids, fields, args, context):
res = {}
period_obj = self.pool.get('account.period')
for obj in self.browse(cr, uid, ids, context):
res[obj.id] = period_obj.find(cr, uid, obj.date)[0]
return res
STATES_VALUE = {'draft': [('readonly', False)]}
_name = 'account.retention'
_description = 'Withdrawing Documents'
_order = 'date desc, name desc'
_columns = {
'name': fields.char('Número', size=64, readonly=True,
required=True,
states=STATES_VALUE),
'manual': fields.boolean('Numeración Manual', readonly=True,
states=STATES_VALUE),
'num_document': fields.char('Num. Comprobante', size=50,
readonly=True,
states=STATES_VALUE),
'auth_id': fields.many2one(
'account.authorisation',
'Autorizacion',
readonly=True,
states=STATES_VALUE,
required=True,
domain=[('in_type','=','interno')]
),
'type': fields.selection(
[('in_invoice','Factura'),
('liq_purchase','Liquidacion Compra')],
string='Tipo Comprobante',
readonly=True, states=STATES_VALUE
),
'in_type': fields.selection(
[('ret_in_invoice', u'Retención a Proveedor'),
('ret_out_invoice', u'Retención de Cliente')],
string='Tipo',
states=STATES_VALUE,
readonly=True),
'date': fields.date('Fecha Emision', readonly=True,
states={'draft': [('readonly', False)]}, required=True),
'period_id': fields.many2one(
'account.period',
'Periodo',
required=True
),
'tax_ids': fields.one2many(
'account.invoice.tax',
'retention_id',
'Detalle de Impuestos',
readonly=True,
states=STATES_VALUE
),
'invoice_id': fields.many2one(
'account.invoice',
string='Documento',
required=False,
readonly=True,
states=STATES_VALUE,
domain=[('state','=','open')]
),
'partner_id': fields.related(
'invoice_id',
'partner_id',
type='many2one',
relation='res.partner',
string='Empresa',
readonly=True
),
'move_id': fields.related(
'invoice_id',
'move_id',
type='many2one',
relation='account.move',
string='Asiento Contable',
readonly=True
),
'state': fields.selection(
[('draft','Borrador'),
('early','Anticipado'),
('done','Validado'),
('cancel','Anulado')],
readonly=True,
string='Estado'
),
'amount_total': fields.function(
_amount_total, string='Total',
method=True, store=True,
digits_compute=dp.get_precision('Account')
),
'to_cancel': fields.boolean('Para anulación',readonly=True, states=STATES_VALUE),
'company_id': fields.many2one(
'res.company',
'Company',
required=True,
change_default=True,
readonly=True,
states={'draft':[('readonly',False)]}
),
}
def _get_period(self, cr, uid, context=None):
res = self.pool.get('account.period').find(cr, uid, context=context)
return res and res[0] or False
def _get_type(self, cr, uid, context):
if context.has_key('type') and \
context['type'] in ['in_invoice', 'out_invoice']:
return 'in_invoice'
else:
return 'liq_purchase'
def _get_in_type(self, cr, uid, context):
if context.has_key('type') and \
context['type'] in ['in_invoice', 'liq_purchase']:
return 'ret_in_invoice'
else:
return 'ret_in_invoice'
_defaults = {
'state': 'draft',
'in_type': _get_in_type,
'type': _get_type,
'name': '/',
'manual': True,
'date': time.strftime('%Y-%m-%d'),
'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.invoice', context=c),
'period_id': _get_period
}
_sql_constraints = [('unique_number_name', 'unique(name)', u'El número de retención es único.')]
def unlink(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context):
if obj.state in ['done']:
raise osv.except_osv('Aviso','No se permite borrar retenciones validadas.')
res = super(AccountWithdrawing, self).unlink(cr, uid, ids, context)
return res
def onchange_invoice(self, cr, uid, ids, invoice_id):
res = {'value': {'num_document': ''}}
if not invoice_id:
return res
invoice = self.pool.get('account.invoice').browse(cr, uid, invoice_id)
if not invoice.auth_inv_id:
return res
num_document = invoice.supplier_number
res['value']['num_document'] = num_document
res['value']['type'] = invoice.type
return res
def button_validate(self, cr, uid, ids, context=None):
"""
Botón de validación de Retención que se usa cuando
se creó una retención manual, esta se relacionará
con la factura seleccionada.
"""
invoice_obj = self.pool.get('account.invoice')
if context is None:
context = {}
for ret in self.browse(cr, uid, ids, context):
if ret.manual:
self.action_validate(cr, uid, [ret.id], ret.name)
invoice_obj.write(cr, uid, ret.invoice_id.id, {'retention_id': ret.id})
else:
self.action_validate(cr, uid, [ret.id])
return True
def action_validate(self, cr, uid, ids, number=None):
'''
cr: cursor de la base de datos
uid: ID de usuario
ids: lista ID del objeto instanciado
number: Numero posible para usar en el documento
Metodo que valida el documento, su principal
accion es numerar el documento segun el parametro number
'''
seq_obj = self.pool.get('ir.sequence')
for ret in self.browse(cr, uid, ids):
if ret.to_cancel:
raise osv.except_osv('Alerta', 'El documento fue marcado para anular.')
seq_id = ret.invoice_id.journal_id.auth_ret_id.sequence_id.id
seq = seq_obj.browse(cr, uid, seq_id)
ret_num = number
if number is None:
ret_number = seq_obj.get(cr, uid, seq.code)
else:
padding = seq.padding
ret_number = str(number).zfill(padding)
self._amount_total(cr, uid, [ret.id], [], {}, {})
number = ret.auth_id.serie_entidad + ret.auth_id.serie_emision + ret_number
self.write(cr, uid, ret.id, {'state': 'done', 'name':number})
self.log(cr, uid, ret.id, _("La retención %s fue generada.") % number)
return True
def action_cancel(self, cr, uid, ids, context=None):
'''
cr: cursor de la base de datos
uid: ID de usuario
ids: lista ID del objeto instanciado
Metodo para cambiar de estado a cancelado
el documento
'''
auth_obj = self.pool.get('account.authorisation')
for ret in self.browse(cr, uid, ids):
data = {'state': 'cancel'}
if ret.to_cancel:
if len(ret.name) == 9 and auth_obj.is_valid_number(cr, uid, ret.auth_id.id, int(ret.name)):
number = ret.auth_id.serie_entidad + ret.auth_id.serie_emision + ret.name
data.update({'name': number})
else:
raise osv.except_osv('Error', u'El número no es de 9 dígitos y/o no pertenece a la autorización seleccionada.')
self.write(cr, uid, ret.id, data)
return True
def action_draft(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context):
name = obj.name[6:]
self.write(cr, uid, ids, {'state': 'draft', 'name': name}, context)
return True
def action_early(self, cr, uid, ids, *args):
'''
cr: cursor de la base de datos
uid: ID de usuario
ids: lista ID del objeto instanciado
Metodo para cambiar de estado a cancelado
el documento
'''
self.write(cr, uid, ids, {'state': 'early'})
return True
class AccountInvoiceTax(osv.osv):
_name = 'account.invoice.tax'
_inherit = 'account.invoice.tax'
_columns = {
'fiscal_year' : fields.char('Ejercicio Fiscal', size = 4),
'tax_group' : fields.selection([('vat','IVA Diferente de 0%'),
('vat0','IVA 0%'),
('novat','No objeto de IVA'),
('ret_vat_b', 'Retención de IVA (Bienes)'),
('ret_vat_srv', 'Retención de IVA (Servicios)'),
('ret_ir', 'Ret. Imp. Renta'),
('no_ret_ir', 'No sujetos a Ret. de Imp. Renta'),
('imp_ad', 'Imps. Aduanas'),
('ice', 'ICE'),
('other','Other')], 'Grupo', required=True),
'percent' : fields.char('Porcentaje', size=20),
'num_document': fields.char('Num. Comprobante', size=50),
'retention_id': fields.many2one('account.retention', 'Retención', select=True),
}
def compute(self, cr, uid, invoice_id, context=None):
tax_grouped = {}
tax_obj = self.pool.get('account.tax')
cur_obj = self.pool.get('res.currency')
inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context=context)
cur = inv.currency_id
company_currency = self.pool['res.company'].browse(cr, uid, inv.company_id.id).currency_id.id
for line in inv.invoice_line:
for tax in tax_obj.compute_all(cr, uid, line.invoice_line_tax_id, (line.price_unit* (1-(line.discount or 0.0)/100.0)), line.quantity, line.product_id, inv.partner_id)['taxes']:
val={}
val['tax_group'] = tax['tax_group']
val['percent'] = tax['porcentaje']
val['invoice_id'] = inv.id
val['name'] = tax['name']
val['amount'] = tax['amount']
val['manual'] = False
val['sequence'] = tax['sequence']
val['base'] = cur_obj.round(cr, uid, cur, tax['price_unit'] * line['quantity'])
# Hack to EC
if tax['tax_group'] in ['ret_vat_b', 'ret_vat_srv']:
ret = float(str(tax['porcentaje'])) / 100
bi = tax['price_unit'] * line['quantity']
imp = (abs(tax['amount']) / (ret * bi)) * 100
val['base'] = (tax['price_unit'] * line['quantity']) * imp / 100
else:
val['base'] = tax['price_unit'] * line['quantity']
if inv.type in ('out_invoice','in_invoice'):
val['base_code_id'] = tax['base_code_id']
val['tax_code_id'] = tax['tax_code_id']
val['base_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, val['base'] * tax['base_sign'], context={'date': inv.date_invoice or fields.date.context_today(self, cr, uid, context=context)}, round=False)
val['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, val['amount'] * tax['tax_sign'], context={'date': inv.date_invoice or fields.date.context_today(self, cr, uid, context=context)}, round=False)
val['account_id'] = tax['account_collected_id'] or line.account_id.id
val['account_analytic_id'] = tax['account_analytic_collected_id']
else:
val['base_code_id'] = tax['ref_base_code_id']
val['tax_code_id'] = tax['ref_tax_code_id']
val['base_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, val['base'] * tax['ref_base_sign'], context={'date': inv.date_invoice or fields.date.context_today(self, cr, uid, context=context)}, round=False)
val['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, val['amount'] * tax['ref_tax_sign'], context={'date': inv.date_invoice or fields.date.context_today(self, cr, uid, context=context)}, round=False)
val['account_id'] = tax['account_paid_id'] or line.account_id.id
val['account_analytic_id'] = tax['account_analytic_paid_id']
# If the taxes generate moves on the same financial account as the invoice line
# and no default analytic account is defined at the tax level, propagate the
# analytic account from the invoice line to the tax line. This is necessary
# in situations were (part of) the taxes cannot be reclaimed,
# to ensure the tax move is allocated to the proper analytic account.
if not val.get('account_analytic_id') and line.account_analytic_id and val['account_id'] == line.account_id.id:
val['account_analytic_id'] = line.account_analytic_id.id
key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
if not key in tax_grouped:
tax_grouped[key] = val
else:
tax_grouped[key]['amount'] += val['amount']
tax_grouped[key]['base'] += val['base']
tax_grouped[key]['base_amount'] += val['base_amount']
tax_grouped[key]['tax_amount'] += val['tax_amount']
for t in tax_grouped.values():
t['base'] = cur_obj.round(cr, uid, cur, t['base'])
t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
t['base_amount'] = cur_obj.round(cr, uid, cur, t['base_amount'])
t['tax_amount'] = cur_obj.round(cr, uid, cur, t['tax_amount'])
return tax_grouped
_defaults = {
'fiscal_year': time.strftime('%Y'),
}
class Invoice(osv.osv):
_inherit = 'account.invoice'
__logger = logging.getLogger(_inherit)
def onchange_company_id(self, cr, uid, ids, company_id, part_id, type, invoice_line, currency_id):
#TODO: add the missing context parameter when forward-porting in trunk so we can remove
# this hack!
context = self.pool['res.users'].context_get(cr, uid)
val = {}
dom = {}
obj_journal = self.pool.get('account.journal')
account_obj = self.pool.get('account.account')
inv_line_obj = self.pool.get('account.invoice.line')
if company_id and part_id and type:
acc_id = False
partner_obj = self.pool.get('res.partner').browse(cr,uid,part_id)
if partner_obj.property_account_payable and partner_obj.property_account_receivable:
if partner_obj.property_account_payable.company_id.id != company_id and partner_obj.property_account_receivable.company_id.id != company_id:
property_obj = self.pool.get('ir.property')
rec_pro_id = property_obj.search(cr, uid, [('name','=','property_account_receivable'),('res_id','=','res.partner,'+str(part_id)+''),('company_id','=',company_id)])
pay_pro_id = property_obj.search(cr, uid, [('name','=','property_account_payable'),('res_id','=','res.partner,'+str(part_id)+''),('company_id','=',company_id)])
if not rec_pro_id:
rec_pro_id = property_obj.search(cr, uid, [('name','=','property_account_receivable'),('company_id','=',company_id)])
if not pay_pro_id:
pay_pro_id = property_obj.search(cr, uid, [('name','=','property_account_payable'),('company_id','=',company_id)])
rec_line_data = property_obj.read(cr, uid, rec_pro_id, ['name','value_reference','res_id'])
pay_line_data = property_obj.read(cr, uid, pay_pro_id, ['name','value_reference','res_id'])
rec_res_id = rec_line_data and rec_line_data[0].get('value_reference',False) and int(rec_line_data[0]['value_reference'].split(',')[1]) or False
pay_res_id = pay_line_data and pay_line_data[0].get('value_reference',False) and int(pay_line_data[0]['value_reference'].split(',')[1]) or False
if not rec_res_id and not pay_res_id:
raise osv.except_osv(_('Configuration Error!'),
_('Cannot find a chart of account, you should create one from Settings\Configuration\Accounting menu.'))
if type in ('out_invoice', 'out_refund'):
acc_id = rec_res_id
else:
acc_id = pay_res_id
val= {'account_id': acc_id}
if ids:
if company_id:
inv_obj = self.browse(cr,uid,ids)
for line in inv_obj[0].invoice_line:
if line.account_id:
if line.account_id.company_id.id != company_id:
result_id = account_obj.search(cr, uid, [('name','=',line.account_id.name),('company_id','=',company_id)])
if not result_id:
raise osv.except_osv(_('Configuration Error!'),
_('Cannot find a chart of account, you should create one from Settings\Configuration\Accounting menu.'))
inv_line_obj.write(cr, uid, [line.id], {'account_id': result_id[-1]})
else:
if invoice_line:
for inv_line in invoice_line:
obj_l = account_obj.browse(cr, uid, inv_line[2]['account_id'])
if obj_l.company_id.id != company_id:
raise osv.except_osv(_('Configuration Error!'),
_('Invoice line account\'s company and invoice\'s company does not match.'))
else:
continue
if company_id and type:
journal_mapping = {
'out_invoice': 'sale',
'out_refund': 'sale_refund',
'in_refund': 'purchase_refund',
'in_invoice': 'purchase',
'liq_purchase': 'purchase'
}
journal_type = journal_mapping[type]
journal_ids = obj_journal.search(cr, uid, [('company_id','=',company_id), ('type', '=', journal_type)])
if journal_ids:
val['journal_id'] = journal_ids[0]
ir_values_obj = self.pool.get('ir.values')
res_journal_default = ir_values_obj.get(cr, uid, 'default', 'type=%s' % (type), ['account.invoice'])
for r in res_journal_default:
if r[1] == 'journal_id' and r[2] in journal_ids:
val['journal_id'] = r[2]
if not val.get('journal_id', False):
journal_type_map = dict(obj_journal._columns['type'].selection)
journal_type_label = self.pool['ir.translation']._get_source(cr, uid, None, ('code','selection'),
context.get('lang'),
journal_type_map.get(journal_type))
raise osv.except_osv(_('Configuration Error!'),
_('Cannot find any account journal of %s type for this company.\n\nYou can create one in the menu: \nConfiguration\Journals\Journals.') % ('"%s"' % journal_type_label))
dom = {'journal_id': [('id', 'in', journal_ids)]}
else:
journal_ids = obj_journal.search(cr, uid, [])
return {'value': val, 'domain': dom}
def onchange_sustento(self, cr, uid, ids, sustento_id):
res = {'value': {}}
if not sustento_id:
return res
sustento = self.pool.get('account.ats.sustento').browse(cr, uid, sustento_id)
res['value']['name'] = sustento.type
return res
def print_invoice(self, cr, uid, ids, context=None):
'''
cr: cursor de la base de datos
uid: ID de usuario
ids: lista ID del objeto instanciado
Metodo para imprimir reporte de liquidacion de compra
'''
if not context:
context = {}
invoice = self.browse(cr, uid, ids, context)[0]
datas = {'ids': [invoice.id], 'model': 'account.invoice'}
return {
'type': 'ir.actions.report.xml',
'report_name': 'invoice_report',
'model': 'account.invoice',
'datas': datas,
'nodestroy': True,
}
def print_move(self, cr, uid, ids, context=None):
'''
cr: cursor de la base de datos
uid: ID de usuario
ids: lista ID del objeto instanciado
Metodo para imprimir comprobante contable
'''
if not context:
context = {}
invoice = self.browse(cr, uid, ids, context)[0]
datas = {'ids': [invoice.move_id.id], 'model': 'account.move'}
return {
'type': 'ir.actions.report.xml',
'report_name': 'report_move',
'model': 'account.move',
'datas': datas,
'nodestroy': True,
}
def print_liq_purchase(self, cr, uid, ids, context=None):
'''
cr: cursor de la base de datos
uid: ID de usuario
ids: lista ID del objeto instanciado
Metodo para imprimir reporte de liquidacion de compra
'''
if not context:
context = {}
invoice = self.browse(cr, uid, ids, context)[0]
datas = {'ids': [invoice.id], 'model': 'account.invoice'}
return {
'type': 'ir.actions.report.xml',
'report_name': 'report_liq_purchase',
'model': 'account.invoice',
'datas': datas,
'nodestroy': True,
}
def print_retention(self, cr, uid, ids, context=None):
'''
cr: cursor de la base de datos
uid: ID de usuario
ids: lista ID del objeto instanciado
Metodo para imprimir reporte de retencion
'''
if not context:
context = {}
invoice = self.browse(cr, uid, ids, context)[0]
datas = {'ids' : [invoice.retention_id.id],
'model': 'account.retention'}
if invoice.retention_id:
return {
'type': 'ir.actions.report.xml',
'report_name': 'account.retention',
'model': 'account.retention',
'datas': datas,
'nodestroy': True,
}
else:
raise except_osv('Aviso', 'No tiene retención')
def _amount_all(self, cr, uid, ids, fields, args, context=None):
"""
Compute all total values in invoice object
params:
@cr cursor to DB
@uid user id logged
@ids active object ids
@fields used fields in function, severals if use multi arg
"""
res = {}
cur_obj = self.pool.get('res.currency')
invoices = self.browse(cr, uid, ids, context=context)
for invoice in invoices:
cur = invoice.currency_id
res[invoice.id] = {
'amount_vat': 0.0,
'amount_untaxed': 0.0,
'amount_tax': 0.0,
'amount_tax_retention': 0.0,
'amount_tax_ret_ir': 0.0,
'taxed_ret_ir': 0.0,
'amount_tax_ret_vatb': 0.0,
'amount_tax_ret_vatsrv': 0.00,
'taxed_ret_vatb': 0.0,
'taxed_ret_vatsrv': 0.00,
'amount_vat_cero': 0.0,
'amount_novat': 0.0,
'amount_noret_ir': 0.0,
'amount_total': 0.0,
'amount_pay': 0.0,
'amount_ice': 0.0,
'discount': 0.0
}
#Total General
for line in invoice.invoice_line:
res[invoice.id]['amount_untaxed'] += line.price_subtotal
for line in invoice.tax_line:
if line.tax_group == 'vat':
res[invoice.id]['amount_vat'] += line.base
res[invoice.id]['amount_tax'] += line.amount
elif line.tax_group == 'vat0':
res[invoice.id]['amount_vat_cero'] += line.base
elif line.tax_group == 'novat':
res[invoice.id]['amount_novat'] += line.base
elif line.tax_group == 'no_ret_ir':
res[invoice.id]['amount_noret_ir'] += line.base
elif line.tax_group in ['ret_vat_b', 'ret_vat_srv', 'ret_ir']:
res[invoice.id]['amount_tax_retention'] += line.amount
if line.tax_group == 'ret_vat_b':#in ['ret_vat_b', 'ret_vat_srv']:
res[invoice.id]['amount_tax_ret_vatb'] += line.base
res[invoice.id]['taxed_ret_vatb'] += line.amount
elif line.tax_group == 'ret_vat_srv':
res[invoice.id]['amount_tax_ret_vatsrv'] += line.base
res[invoice.id]['taxed_ret_vatsrv'] += line.amount
elif line.tax_group == 'ret_ir':
res[invoice.id]['amount_tax_ret_ir'] += line.base
res[invoice.id]['taxed_ret_ir'] += line.amount
elif line.tax_group == 'ice':
res[invoice.id]['amount_ice'] += line.amount
res[invoice.id]['discount'] += line.discount_value
# base vat not defined, amount_vat_cero by default
if res[invoice.id]['amount_vat'] == 0 and res[invoice.id]['amount_vat_cero'] == 0:
res[invoice.id]['amount_vat_cero'] = res[invoice.id]['amount_untaxed']
res[invoice.id]['amount_total'] = res[invoice.id]['amount_tax'] + res[invoice.id]['amount_untaxed'] \
+ res[invoice.id]['amount_tax_retention']
res[invoice.id]['amount_pay'] = res[invoice.id]['amount_tax'] + res[invoice.id]['amount_untaxed']
return res
def _get_invoice_line(self, cr, uid, ids, context=None):
result = {}
for line in self.pool.get('account.invoice.line').browse(cr, uid, ids, context=context):
result[line.invoice_id.id] = True
return result.keys()
def _get_invoice_tax(self, cr, uid, ids, context=None):
result = {}
for tax in self.pool.get('account.invoice.tax').browse(cr, uid, ids, context=context):
result[tax.invoice_id.id] = True
return result.keys()
def name_get(self, cr, uid, ids, context=None):
if not ids:
return []
types = {
'out_invoice': _('Invoice'),
'in_invoice': _('Supplier Invoice'),
'out_refund': _('Refund'),
'in_refund': _('Supplier Refund'),
'liq_purchase': _('Liquid. de Compra')
}
return [(r['id'], '%s %s' % (r['number'] or types[r['type']], r['name'] or '')) for r in self.read(cr, uid, ids, ['type', 'number', 'name'], context, load='_classic_write')]
def _check_retention(self, cr, uid, ids, field_name, context, args):
res = {}
for inv in self.browse(cr, uid, ids, context):
res[inv.id] = {
'retention_ir': False,
'retention_vat': False,
'no_retention_ir': False,
}
for tax in inv.tax_line:
if tax.tax_group in ['ret_vat_b', 'ret_vat_srv']:
res[inv.id]['retention_vat'] = True
elif tax.tax_group == 'ret_ir':
res[inv.id]['retention_ir'] = True
elif tax.tax_group == 'no_ret_ir':
res[inv.id]['no_retention_ir'] = True
return res
def _get_supplier_number(self, cr, uid, ids, fields, args, context):
res = {}
for inv in self.browse(cr, uid, ids, context):
number = '/'
if inv.type == 'in_invoice' and inv.auth_inv_id:
n = inv.supplier_invoice_number and inv.supplier_invoice_number.zfill(9) or '*'
number = ''.join([inv.auth_inv_id.serie_entidad,inv.auth_inv_id.serie_emision,n])
res[inv.id] = number
return res
HELP_RET_TEXT = '''Automatico: El sistema identificara los impuestos y creara la retencion automaticamente, \
Manual: El usuario ingresara el numero de retencion \
Agrupar: Podra usar la opcion para agrupar facturas del sistema en una sola retencion.'''
VAR_STORE = {
'account.invoice': (lambda self, cr, uid, ids, c={}: ids, ['invoice_line'], 20),
'account.invoice.tax': (_get_invoice_tax, None, 20),
'account.invoice.line': (_get_invoice_line, ['price_unit','invoice_line_tax_id','quantity','discount','invoice_id'], 20),
}
PRECISION_DP = dp.get_precision('Account')
_columns = {
'supplier_number': fields.function(_get_supplier_number, method=True, type='char', size=32,
string='Factura de Proveedor', store=True),
'amount_ice': fields.function(_amount_all, method=True, digits_compute=PRECISION_DP, string='ICE',
store=VAR_STORE, multi='all'),
'amount_vat': fields.function(_amount_all, method=True,
digits_compute=PRECISION_DP, string='Base 12 %',
store=VAR_STORE,
multi='all'),
'amount_untaxed': fields.function(_amount_all, method=True,
digits_compute=PRECISION_DP, string='Untaxed',
store=VAR_STORE,
multi='all'),
'amount_tax': fields.function(_amount_all, method=True,
digits_compute=PRECISION_DP, string='Tax',
store=VAR_STORE,
multi='all'),
'amount_total': fields.function(_amount_all, method=True,
digits_compute=PRECISION_DP, string='Total a Pagar',
store=VAR_STORE,
multi='all'),
'amount_pay': fields.function(_amount_all, method=True,
digits_compute=PRECISION_DP, string='Total',
store=VAR_STORE,
multi='all'),
'amount_noret_ir': fields.function(_amount_all, method=True,
digits_compute=PRECISION_DP, string='Monto no sujeto a IR',
store=VAR_STORE,
multi='all'),
'amount_tax_retention': fields.function(_amount_all, method=True,
digits_compute=PRECISION_DP, string='Total Retencion',
store=VAR_STORE,
multi='all'),
'amount_tax_ret_ir': fields.function( _amount_all, method=True,
digits_compute=PRECISION_DP, string='Base IR',
store=VAR_STORE,
multi='all'),
'taxed_ret_ir': fields.function( _amount_all, method=True,
digits_compute=PRECISION_DP, string='Impuesto IR',
store=VAR_STORE,
multi='all'),
'amount_tax_ret_vatb' : fields.function( _amount_all,
method=True,
digits_compute=PRECISION_DP,
string='Base Ret. IVA',
store=VAR_STORE,
multi='all'),
'taxed_ret_vatb' : fields.function( _amount_all,
method=True,
digits_compute=PRECISION_DP,
string='Retencion en IVA',
store=VAR_STORE,
multi='all'),
'amount_tax_ret_vatsrv' : fields.function( _amount_all,
method=True,
digits_compute=PRECISION_DP, string='Base Ret. IVA',
store=VAR_STORE,
multi='all'),
'taxed_ret_vatsrv' : fields.function( _amount_all, method=True,
digits_compute=PRECISION_DP,
string='Retencion en IVA',
store=VAR_STORE,
multi='all'),
'amount_vat_cero' : fields.function( _amount_all, method=True,
digits_compute=PRECISION_DP, string='Base IVA 0%',
store=VAR_STORE,
multi='all'),
'amount_novat' : fields.function( _amount_all, method=True,
digits_compute=PRECISION_DP, string='Base No IVA',
store=VAR_STORE,
multi='all'),
'create_retention_type': fields.selection([('normal','Automatico'),
('manual', 'Manual'),
('reserve','Num Reservado'),
('no_retention', 'No Generar')],
string='Numerar Retención',
readonly=True,
help=HELP_RET_TEXT,
states = {'draft': [('readonly', False)]}),
'auth_inv_id' : fields.many2one('account.authorisation', 'Autorización SRI',
help = 'Autorizacion del SRI para documento recibido',
readonly=True,
states={'draft': [('readonly', False)]}),
'retention_id': fields.many2one('account.retention', store=True,
string='Retención de Impuestos',
readonly=True),
'retention_ir': fields.function(_check_retention, store=True,
string="Tiene Retención en IR",
method=True, type='boolean',
multi='ret'),
'retention_vat': fields.function(_check_retention, store=True,
string='Tiene Retencion en IVA',
method=True, type='boolean',
multi='ret'),
'no_retention_ir': fields.function(_check_retention, store=True,
string='No objeto de Retención',
method=True, type='boolean',
multi='ret'),
'type': fields.selection([
('out_invoice','Customer Invoice'),
('in_invoice','Supplier Invoice'),
('out_refund','Customer Refund'),
('in_refund','Supplier Refund'),
('liq_purchase','Liquidacion de Compra')
],'Type', readonly=True, select=True, change_default=True),
'manual_ret_num': fields.integer('Num. Retención', readonly=True,
states = {'draft': [('readonly', False)]}),
'sustento_id': fields.many2one('account.ats.sustento',
'Sustento del Comprobante'),
}
_defaults = {
'create_retention_type': 'manual',
}
def onchange_journal_id(self, cr, uid, ids, journal_id=False, context=None):
"""
Metodo redefinido para cargar la autorizacion de facturas de venta
"""
result = {}
if journal_id:
journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
currency_id = journal.currency and journal.currency.id or journal.company_id.currency_id.id
company_id = journal.company_id.id
if context.get('type') == 'out_invoice' and not journal.auth_id:
return {
'warning': {
'title': 'Error',
'message': u'No se ha configurado una autorización en este diario.'
}
}
result = {'value': {
'currency_id': currency_id,
'company_id': company_id,
'auth_inv_id': journal.auth_id.id
}
}
return result
def _check_invoice_number(self, cr, uid, ids):
"""
Metodo de validacion de numero de factura y numero de
retencion
numero de factura: suppplier_invoice_number
numero de retencion: manual_ret_num
"""
auth_obj = self.pool.get('account.authorisation')
INV_MIN_LIMIT = 9 # CHECK: mover a compañia ?
INV_MAX_LIMIT = 15
LIMITS = [
INV_MIN_LIMIT,
INV_MAX_LIMIT
]
for obj in self.browse(cr, uid, ids):
if obj.state in ['open', 'paid', 'cancel']:
return True
if not len(obj.supplier_invoice_number) in LIMITS:
raise osv.except_osv('Error', u'Son %s dígitos en el núm. de Factura.' % INVOICE_LENGTH_LIMIT)
auth = obj.auth_inv_id
inv_number = obj.supplier_invoice_number
if len(obj.supplier_invoice_number) == INV_MAX_LIMIT:
inv_number = obj.supplier_invoice_number[6:15]
if not auth:
raise osv.except_osv('Error', u'No se ha configurado una autorización de documentos, revisar Partner y Diario Contable.')
if not auth_obj.is_valid_number(cr, uid, auth.id, int(inv_number)):
raise osv.except_osv('Error', u'Número de factura fuera de rango.')
# validacion de numero de retencion para facturas de proveedor
if obj.type == 'in_invoice':
if not obj.journal_id.auth_ret_id:
raise except_osv('Error', u'No ha cofigurado una autorización de retenciones.')
if not auth_obj.is_valid_number(cr, uid, obj.journal_id.auth_ret_id.id, int(obj.manual_ret_num)):
raise osv.except_osv('Error', u'El número de retención no es válido.')
return True
_constraints = [
(_check_invoice_number,
u'Número fuera de rango de autorización activa.', ['Número Factura']),
]
_sql_constraints = [
('unique_inv_supplier', 'unique(supplier_invoice_number,type,partner_id)', u'El número de factura es único.'),
]
def copy_data(self, cr, uid, id, default=None, context=None):
res = super(Invoice, self).copy_data(cr, uid, id, default, context=context)
res.update({'reference': False,
'auth_inv_id': False,
'retention_id': False,
'supplier_invoice_number': False,
'manual_ret_num': False,
'retention_numbers': False})
return res
def onchange_partner_id(self, cr, uid, ids, type, partner_id,\
date_invoice=False, payment_term=False, partner_bank_id=False, company_id=False):
auth_obj = self.pool.get('account.authorisation')
res1 = super(Invoice, self).onchange_partner_id(cr, uid, ids, type,
partner_id, date_invoice,
payment_term, partner_bank_id,
company_id)
if res1['value'].has_key('reference_type'):
res1['value'].pop('reference_type')
res = auth_obj.search(cr, uid, [('partner_id','=',partner_id),('in_type','=','externo')], limit=1)
if res:
res1['value']['auth_inv_id'] = res[0]
return res1
def action_cancel_draft(self, cr, uid, ids, context):
retention_obj = self.pool.get('account.retention')
for inv in self.browse(cr, uid, ids, context):
if inv.retention_id:
retention_obj.unlink(cr, uid, [inv.retention_id.id], context)
super(Invoice, self).action_cancel_draft(cr, uid, ids, context)
return True
def action_retention_create(self, cr, uid, ids, *args):
'''
@cr: DB cursor
@uid: active ID user
@ids: active IDs objects
Este metodo genera el documento de retencion en varios escenarios
considera casos de:
* Generar retencion automaticamente
* Generar retencion de reemplazo
* Cancelar retencion generada
'''
context = args and args[0] or {}
invoices = self.browse(cr, uid, ids)
ret_obj = self.pool.get('account.retention')
invtax_obj = self.pool.get('account.invoice.tax')
ret_cache_obj = self.pool.get('account.retention.cache')
ir_seq_obj = self.pool.get('ir.sequence')
for inv in invoices:
num_ret = False
if inv.create_retention_type == 'no_retention':
continue
if inv.retention_id and not inv.retention_vat and not inv.retention_ir:
num_next = inv.journal_id.auth_ret_id.sequence_id.number_next
seq = inv.journal_id.auth_ret_id.sequence_id
if num_next - 1 == int(inv.retention_id.name):
ir_seq_obj.write(cr, uid, seq.id, {'number_next': num_next-1})
else:
ret_cache_obj.create(cr, uid, {'name': inv.retention_id.name})
if inv.type in ['in_invoice', 'liq_purchase'] and (inv.retention_ir or inv.retention_vat):
if inv.journal_id.auth_ret_id.sequence_id:
ret_data = {'name':'/',
'number': '/',
'invoice_id': inv.id,
'num_document': inv.supplier_number,
'auth_id': inv.journal_id.auth_ret_id.id,
'type': inv.type,
'in_type': 'ret_in_invoice',
'date': inv.date_invoice,
'period_id': inv.period_id.id
}
ret_id = ret_obj.create(cr, uid, ret_data)
for line in inv.tax_line:
if line.tax_group in ['ret_vat_b', 'ret_vat_srv', 'ret_ir']:
num = inv.supplier_number
invtax_obj.write(cr, uid, line.id, {'retention_id': ret_id, 'num_document': num})
if num_ret:
ret_obj.action_validate(cr, uid, [ret_id], num_ret)
elif inv.create_retention_type == 'normal':
ret_obj.action_validate(cr, uid, [ret_id])
elif inv.create_retention_type == 'manual':
if inv.manual_ret_num == 0:
raise osv.except_osv('Error', 'El número de retención es incorrecto.')
ret_obj.action_validate(cr, uid, [ret_id], inv.manual_ret_num)
elif inv.create_retention_type == 'reserve':
if inv.retention_numbers:
ret_num = ret_cache_obj.get_number(cr, uid, inv.retention_numbers)
ret_obj.action_validate(cr, uid, [ret_id], ret_num)
else:
raise osv.except_osv('Error', 'Corrija el método de numeración de la retención')
self.write(cr, uid, [inv.id], {'retention_id': ret_id})
else:
raise osv.except_osv('Error de Configuracion',
'No se ha configurado una secuencia para las retenciones en Compra')
self._log_event(cr, uid, ids)
return True
def recreate_retention(self, cr, uid, ids, context=None):
'''
Metodo que implementa la recreacion de la retención
TODO: recibir el numero de retención del campo manual
'''
if context is None:
context = {}
context.update({'recreate_retention': True})
for inv in self.browse(cr, uid, ids, context):
self.action_retention_cancel(cr, uid, [inv.id], context)
self.action_retention_create(cr, uid, [inv.id], context)
return True
def action_retention_cancel(self, cr, uid, ids, *args):
invoices = self.browse(cr, uid, ids)
ret_obj = self.pool.get('account.retention')
for inv in invoices:
if inv.retention_id:
ret_obj.action_cancel(cr, uid, [inv.retention_id.id])
return True
class AccountInvoiceLine(osv.osv):
_inherit = 'account.invoice.line'
def _amount_line(self, cr, uid, ids, prop, unknow_none, unknow_dict):
res = {}
tax_obj = self.pool.get('account.tax')
cur_obj = self.pool.get('res.currency')
for line in self.browse(cr, uid, ids):
res[line.id] = {'amount_tax': 0, 'price_subtotal': 0}
price = line.price_unit * (1-(line.discount or 0.0)/100.0)
taxes = tax_obj.compute_all(cr, uid, line.invoice_line_tax_id, price, line.quantity, product=line.product_id, partner=line.invoice_id.partner_id)
res[line.id]['discount_value'] = (line.price_unit * line.quantity * line.discount) / 100
res[line.id]['price_subtotal'] = taxes['total']
res[line.id]['amount_tax'] = taxes.get('taxes') and sum([t['amount'] for t in taxes['taxes'] if t['tax_group'] == 'vat']) or 0
if line.invoice_id:
cur = line.invoice_id.currency_id
res[line.id]['discount_value'] = cur_obj.round(cr, uid, cur, res[line.id]['discount_value'])
res[line.id]['price_subtotal'] = cur_obj.round(cr, uid, cur, res[line.id]['price_subtotal'])
res[line.id]['amount_tax'] = cur_obj.round(cr, uid, cur, res[line.id]['amount_tax'])
return res
_columns = {
'discount_value': fields.function(_amount_line, string="Discount Value", type="float", digits_compute=dp.get_precision('Account'), store=True, multi='line'),
'amount_tax': fields.function(_amount_line, string="Tax", type="float", digits_compute=dp.get_precision('Account'), store=True, multi='line'),
'price_subtotal': fields.function(_amount_line, string="Amount", type="float", digits_compute=dp.get_precision('Account'), store=True, multi='line'),
}
def move_line_get(self, cr, uid, invoice_id, context=None):
res = []
tax_obj = self.pool.get('account.tax')
cur_obj = self.pool.get('res.currency')
if context is None:
context = {}
inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context=context)
company_currency = self.pool['res.company'].browse(cr, uid, inv.company_id.id).currency_id.id
for line in inv.invoice_line:
mres = self.move_line_get_item(cr, uid, line, context)
if not mres:
continue
res.append(mres)
tax_code_found= False
for tax in tax_obj.compute_all(cr, uid, line.invoice_line_tax_id,
(line.price_unit * (1.0 - (line['discount'] or 0.0) / 100.0)),
line.quantity, line.product_id,
inv.partner_id)['taxes']:
if inv.type in ('out_invoice', 'in_invoice', 'liq_purchase'):
tax_code_id = tax['base_code_id']
tax_amount = line.price_subtotal * tax['base_sign']
else:
tax_code_id = tax['ref_base_code_id']
tax_amount = line.price_subtotal * tax['ref_base_sign']
if tax_code_found:
if not tax_code_id:
continue
res.append(self.move_line_get_item(cr, uid, line, context))
res[-1]['price'] = 0.0
res[-1]['account_analytic_id'] = False
elif not tax_code_id:
continue
tax_code_found = True
res[-1]['tax_code_id'] = tax_code_id
res[-1]['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, tax_amount, context={'date': inv.date_invoice})
return res
def product_id_change(self, cr, uid, ids, product, uom_id, qty=0, name='', type='out_invoice', partner_id=False, fposition_id=False, price_unit=False, currency_id=False, context=None, company_id=None):
if context is None:
context = {}
company_id = company_id if company_id != None else context.get('company_id',False)
context = dict(context)
context.update({'company_id': company_id, 'force_company': company_id})
if not partner_id:
raise osv.except_osv(_('No Partner Defined!'),_("You must first select a partner!") )
if not product:
if type in ('in_invoice', 'in_refund'):
return {'value': {}, 'domain':{'product_uom':[]}}
else:
return {'value': {'price_unit': 0.0}, 'domain':{'product_uom':[]}}
part = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context)
fpos_obj = self.pool.get('account.fiscal.position')
fpos = fposition_id and fpos_obj.browse(cr, uid, fposition_id, context=context) or False
if part.lang:
context.update({'lang': part.lang})
result = {}
res = self.pool.get('product.product').browse(cr, uid, product, context=context)
if type in ('out_invoice','out_refund'):
a = res.property_account_income.id
if not a:
a = res.categ_id.property_account_income_categ.id
else:
a = res.property_account_expense.id
if not a:
a = res.categ_id.property_account_expense_categ.id
a = fpos_obj.map_account(cr, uid, fpos, a)
if a:
result['account_id'] = a
if type in ('out_invoice', 'out_refund'):
taxes = res.taxes_id and res.taxes_id or (a and self.pool.get('account.account').browse(cr, uid, a, context=context).tax_ids or False)
else:
taxes = res.supplier_taxes_id and res.supplier_taxes_id or (a and self.pool.get('account.account').browse(cr, uid, a, context=context).tax_ids or False)
tax_id = fpos_obj.map_tax(cr, uid, fpos, taxes)
if type in ('in_invoice', 'in_refund'):
result.update( {'price_unit': price_unit or res.standard_price,'invoice_line_tax_id': tax_id} )
else:
result.update({'price_unit': res.list_price, 'invoice_line_tax_id': tax_id})
result['name'] = res.partner_ref
result['uos_id'] = uom_id or res.uom_id.id
if res.description:
result['name'] += '\n'+res.description
domain = {'uos_id':[('category_id','=',res.uom_id.category_id.id)]}
res_final = {'value':result, 'domain':domain}
if not company_id or not currency_id:
return res_final
company = self.pool.get('res.company').browse(cr, uid, company_id, context=context)
currency = self.pool.get('res.currency').browse(cr, uid, currency_id, context=context)
if company.currency_id.id != currency.id:
if type in ('in_invoice', 'in_refund'):
res_final['value']['price_unit'] = res.standard_price
new_price = res_final['value']['price_unit'] * currency.rate
res_final['value']['price_unit'] = new_price
if result['uos_id'] and result['uos_id'] != res.uom_id.id:
selected_uom = self.pool.get('product.uom').browse(cr, uid, result['uos_id'], context=context)
new_price = self.pool.get('product.uom')._compute_price(cr, uid, res.uom_id.id, res_final['value']['price_unit'], result['uos_id'])
res_final['value']['price_unit'] = new_price
return res_final
class AccountInvoiceRefund(osv.TransientModel):
_inherit = 'account.invoice.refund'
def _get_description(self, cr, uid, context=None):
number = '/'
if not context.get('active_id'):
return number
invoice = self.pool.get('account.invoice').browse(cr, uid, context.get('active_id'))
if invoice.type == 'out_invoice':
number = invoice.number
else:
number = invoice.supplier_number
return number
_defaults = {
'description': _get_description,
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment