account_move.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. # -*- coding: utf-8 -*-
  2. ##############################################################################
  3. #
  4. # OpenERP, Open Source Management Solution
  5. # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
  6. #
  7. # This program is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU Affero General Public License as
  9. # published by the Free Software Foundation, either version 3 of the
  10. # License, or (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU Affero General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU Affero General Public License
  18. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. #
  20. ##############################################################################
  21. import logging
  22. from openerp import models, fields, tools, api, _
  23. from openerp.exceptions import Warning as ValidationError
  24. import json
  25. import base64
  26. import requests
  27. import io
  28. import hashlib
  29. _logger = logging.getLogger(__name__)
  30. class AccountInvoice(models.Model):
  31. _name = 'account.invoice'
  32. _inherit = 'account.invoice'
  33. _description = 'Add extra data to SET in account invoice'
  34. numero_factura = fields.Char(string='Factura Nº', related='number', store = True)
  35. sucursal = fields.Char(string='Establecimiento', compute='_compute_suc_sec', store=True)
  36. sec = fields.Char(string='Punto', compute='_compute_suc_sec', store=True)
  37. descripcion = fields.Text("Descripción")
  38. nro_actual = fields.Char('Nro Actual', compute='_compute_suc_sec', store=True)
  39. talonario_id = fields.Many2one('talonario', 'Talonario') # Remover required temporalmente
  40. timbrado_name = fields.Char(related='talonario_id.name', string='Número de timbrado', readonly=True)
  41. talonario_tipo_documento = fields.Selection(related='talonario_id.tipo_documento', string='Tipo de documento', readonly=True)
  42. fecha_final = fields.Date(related='talonario_id.fecha_final', string='Fecha de expiración de timbrado', readonly=True)
  43. nro_fin = fields.Integer(
  44. string='Nro Final',
  45. related='talonario_id.nro_fin',
  46. store=True,
  47. readonly=True
  48. )
  49. no_mostrar_libro_iva = fields.Boolean(string='No visualizar en el libro IVA')
  50. importacion = fields.Boolean(string='Fact. de importación')
  51. importacion_gasto = fields.Boolean(string='Fact. de gastos de importación')
  52. # envio_lote_id = fields.Many2one('envio.lotes', string='Envio Lote')
  53. # qr_code = fields.Binary('Código QR', compute='_generate_qr_code')
  54. # texto_qr = fields.Char(invisible=True)
  55. dProtAut = fields.Char('Código de operación')
  56. operacion_credito = fields.Selection([('1','Normal'),('2','Contingencia')],'Operación Crédito')
  57. tipo_factura = fields.Selection([('1','1'),('2','2')],'Tipo de Factura')
  58. metodo_pago = fields.Selection([('1','Dinero En Efectivo'),('2','Transferencia'),('3','Cheque'),('4','Tarjeta de crédito'),('5','Tarjeta de débito'),('6','Giro'),('7','Billetera Electronica'),('8','Tarjeta Empresarial'),('9','Vales'),('10','Retención')],'Método de Pago')
  59. # mje_resultado_ids = fields.One2many('mje.resultado', 'invoice_id', string='Resultados de mensajes')
  60. # dEstRes = fields.Char('Estado de respuesta')
  61. @api.depends('number', 'journal_id', 'state')
  62. def _compute_suc_sec(self):
  63. for invoice in self:
  64. if invoice.state == 'cancel' and invoice.type == 'out_invoice':
  65. invoice.sucursal = ''
  66. invoice.sec = ''
  67. invoice.nro_actual = ''
  68. elif invoice.journal_id.id == 2 and invoice.number and invoice.type == 'out_invoice':
  69. parts = invoice.number.split('-')
  70. invoice.sucursal = parts[0] if len(parts) > 0 else ''
  71. invoice.sec = parts[1] if len(parts) > 1 else ''
  72. invoice.nro_actual = parts[2] if len(parts) > 2 else ''
  73. else:
  74. invoice.sucursal = ''
  75. invoice.sec = ''
  76. invoice.nro_actual = ''
  77. @api.constrains('nro_actual', 'nro_fin')
  78. def _check_nro_actual_vs_nro_fin(self):
  79. for invoice in self:
  80. if invoice.talonario_id and invoice.nro_fin and invoice.nro_actual:
  81. try:
  82. nro_actual_int = int(invoice.nro_actual)
  83. except ValueError:
  84. raise ValidationError("El número actual debe ser un número válido.")
  85. if nro_actual_int > invoice.nro_fin:
  86. raise ValidationError(
  87. "El número actual (%s) excede el número final permitido en el talonario (%s)." %
  88. (nro_actual_int, invoice.nro_fin)
  89. )
  90. @api.onchange('talonario_id')
  91. def _onchange_talonario_id(self):
  92. if self.talonario_id:
  93. self.timbrado_name = self.talonario_id.name
  94. self.talonario_tipo_documento = self.talonario_id.tipo_documento
  95. self.fecha_final = self.talonario_id.fecha_final
  96. else:
  97. self.timbrado_name = False
  98. self.talonario_tipo_documento = False
  99. self.fecha_final = False
  100. @api.onchange('type', 'company_id')
  101. def _onchange_type_set_talonario_domain(self):
  102. """
  103. - out_invoice -> talonario.tipo_documento '1' (Factura)
  104. - out_refund -> talonario.tipo_documento '5' (Nota de crédito)
  105. Siempre filtra por active=True y company_id de la factura.
  106. Autoselecciona si existe exactamente uno.
  107. """
  108. domain = []
  109. if self.type == 'out_invoice':
  110. domain = [('tipo_documento', '=', '1'), ('activo', '=', True)]
  111. elif self.type == 'out_refund':
  112. domain = [('tipo_documento', '=', '5'), ('activo', '=', True)]
  113. else:
  114. domain = [('id', '=', False)]
  115. if self.company_id:
  116. domain.append(('company_id', '=', self.company_id.id))
  117. res = {'domain': {'talonario_id': domain}}
  118. # Autoseleccionar si hay exactamente un talonario válido
  119. if not self.talonario_id and domain and domain != [('id', '=', False)]:
  120. talonarios = self.env['talonario'].search(domain, limit=2)
  121. if len(talonarios) == 1:
  122. self.talonario_id = talonarios[0].id
  123. return res
  124. @api.model
  125. def create(self, vals):
  126. """
  127. Si no se pasa talonario_id, se asigna automáticamente:
  128. - out_invoice -> talonario.tipo_documento '1'
  129. - out_refund -> talonario.tipo_documento '5'
  130. Filtrado por active=True y company_id de la factura.
  131. """
  132. if not vals.get('talonario_id'):
  133. inv_type = vals.get('type') or self._context.get('type')
  134. company_id = vals.get('company_id') or self._context.get('company_id')
  135. if inv_type in ('out_invoice', 'out_refund'):
  136. tipo = '1' if inv_type == 'out_invoice' else '5'
  137. domain = [('tipo_documento', '=', tipo), ('activo', '=', True)]
  138. if company_id:
  139. domain.append(('company_id', '=', company_id))
  140. tal = self.env['talonario'].search(domain, limit=1)
  141. if tal:
  142. vals['talonario_id'] = tal.id
  143. return super(AccountInvoice, self).create(vals)
  144. # class AccountInvoice1(models.Model):
  145. # _inherit = 'account.invoice'
  146. #
  147. # def send_invoice_to_sifen(self):
  148. # for record in self:
  149. # url = 'https://sifen.set.gov.py/api'
  150. # headers = {'Content-Type': 'application/json'}
  151. # data = record.prepare_invoice_data()
  152. # response = requests.post(url, json=data, headers=headers)
  153. # if response.status_code == 200:
  154. # self.handle_response(response.json())
  155. # else:
  156. # raise UserError('Error sending invoice to SIFEN: {}'.format(response.content))
  157. #
  158. # def prepare_invoice_data(self):
  159. # self.ensure_one()
  160. # return {
  161. # "tipoDocumento": self.talonario_tipo_documento,
  162. # "establecimiento": self.sucursal,
  163. # "codigoSeguridadAleatorio": self.cdc,
  164. # "punto": self.sec,
  165. # "numero": self.nro_actual,
  166. # "descripcion": self.descripcion,
  167. # "observacion": self.observacion,
  168. # "tipoContribuyente": self.tipo_contribuyente,
  169. # "fecha": self.date_invoice.isoformat(),
  170. # "tipoEmision": self.tipo_emision,
  171. # "tipoTransaccion": self.tipo_contribuyente,
  172. # "tipoImpuesto": self.tipo_impuesto,
  173. # "moneda": self.moneda,
  174. # "condicionAnticipo": self.condicion_anticipo,
  175. # "condicionTipoCambio": self.condicion_tipo_cambio,
  176. # "cambio": self.cambio,
  177. # "cliente": {
  178. # "contribuyente": self.partner_id.situacion,
  179. # "ruc": self.partner_id.ruc,
  180. # "razonSocial": self.partner_id.name,
  181. # "nombreFantasia": self.partner_id.name,
  182. # "tipoOperacion": self.partner_id.tipo_operacion,
  183. # "direccion": self.partner_id.street,
  184. # "numeroCasa": self.partner_id.nro_casa,
  185. # "departamento": self.partner_id.street,
  186. # "departamentoDescripcion": self.partner_id.street,
  187. # "distrito": self.partner_id.city,
  188. # "distritoDescripcion": self.partner_id.street,
  189. # "ciudad": self.partner_id.city,
  190. # "ciudadDescripcion": self.partner_id.street,
  191. # "pais": self.partner_id.country_id.code,
  192. # "paisDescripcion": self.partner_id.country_id.name,
  193. # "tipoContribuyente": self.partner_id.situacion,
  194. # "documentoTipo": self.partner_id.tipo_documento_receptor,
  195. # "documentoNumero": self.partner_id.nro_documento,
  196. # "telefono": self.partner_id.phone,
  197. # "celular": self.partner_id.mobile,
  198. # "email": self.partner_id.email,
  199. # "codigo": self.partner_id.dv
  200. # },
  201. # # Añadir otros campos según sea necesario
  202. # }
  203. #
  204. # def handle_response(self, response):
  205. # if response.get('status') == 'approved':
  206. # self.write({'state': 'open'})
  207. # else:
  208. # raise UserError('Factura no fue aprobado por la SIFEN: {}'.format(response.get('message')))
  209. #
  210. # def _generate_cdc(self):
  211. # data_to_hash = (
  212. # str(self.talonario_tipo_documento) +
  213. # self.sucursal +
  214. # self.cdc +
  215. # self.sec +
  216. # self.nro_actual +
  217. # self.date_invoice.strftime('%Y%m%d%H%M%S') +
  218. # str(self.tipo_transaccion) +
  219. # str(self.amount_total)
  220. # )
  221. # md5_hash = hashlib.md5(data_to_hash.encode('utf-8')).digest()
  222. # cdc_base32 = base64.b32encode(md5_hash).decode('utf-8').rstrip('=')
  223. # self.cdc = cdc_base32
  224. #
  225. # def _generate_qr_code(self):
  226. # import qrcode
  227. # from io import BytesIO
  228. # qr_data = "cdc={}&ruc={}&date={}".format(self.cdc, self.partner_id.ruc, self.date_invoice)
  229. # qr = qrcode.QRCode(
  230. # version=1,
  231. # error_correction=qrcode.constants.ERROR_CORRECT_L,
  232. # box_size=10,
  233. # border=4,
  234. # )
  235. # qr.add_data(qr_data)
  236. # qr.make(fit=True)
  237. # img = qr.make_image(fill='black', back_color='white')
  238. # buffer = BytesIO()
  239. # img.save(buffer, format="PNG")
  240. # self.qr_code = base64.b64encode(buffer.getvalue())