/
account_journal.py
1404 lines (1160 loc) · 59 KB
/
account_journal.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
from odoo.tools.float_utils import float_round
# from odoo.tools.misc import formatLang
# from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
import re
#########
# helpers
#########
def format_amount(amount, padding=15, decimals=2, sep=""):
if amount < 0:
template = "-{:0>%dd}" % (padding - 1 - len(sep))
else:
template = "{:0>%dd}" % (padding - len(sep))
res = template.format(
int(round(abs(amount) * 10**decimals, decimals)))
if sep:
res = "{0}{1}{2}".format(res[:-decimals], sep, res[-decimals:])
return res
def get_line_tax_base(move_line):
return sum(move_line.move_id.line_ids.filtered(
lambda x: move_line.tax_line_id in x.tax_ids).mapped(
'balance'))
def get_pos_and_number(full_number):
"""
Para un numero nos fijamos si hay '-', si hay:
* mas de 1, entonces devolvemos error
* 1, entonces devolvemos las partes (solo parte númerica)
* 0, entonces devolvemos '0' y parte númerica del número que se pasó
"""
args = full_number.split('-')
if len(args) == 1:
# si no hay '-' tomamos punto de venta 0
return ('0', re.sub('[^0-9]', '', args[0]))
else:
return re.sub('[^0-9]', '', args[0]), re.sub('[^0-9]', '', ''.join(args[1:]))
class AccountJournal(models.Model):
_inherit = 'account.journal'
# TODO ver como queremos separar la de santa fe, arba y demás. usamos uno
# solo y luego logica adentro? un diario para cada una?
settlement_tax = fields.Selection(selection_add=[
# ('vat', 'VAT'),
# ('profits', 'Profits'),
('misiones', 'TXT IIBB aplicado DGR Misiones'),
('drei_aplicado', 'TXT DREI Aplicado'),
('sicore_aplicado', 'TXT SICORE Aplicado'),
('iibb_sufrido', 'TXT IIBB p/ SIFERE'),
('iibb_aplicado', 'TXT Perc/Ret IIBB aplicadas ARBA: Percepciones ( excepto actividad 29, 7 quincenal, 7 y 17 de Bancos)'),
('iibb_aplicado_act_7', 'TXT Perc/Ret IIBB aplicadas ARBA: Percepciones Act. 7 método Percibido (quincenal)'),
('iibb_aplicado_agip', 'TXT Perc/Ret IIBB aplicadas AGIP'),
('iibb_aplicado_api', 'TXT Perc/Ret IIBB aplicadas API'),
('iibb_aplicado_sircar', 'TXT Perc/Ret IIBB aplicadas SIRCAR'),
('iibb_aplicado_dgr_mendoza', 'TXT Perc/Ret IIBB aplicado DGR Mendonza'),
('retenciones_iva', 'TXT Retenciones/Percepciones Sufridas IVA'),
# ('other', 'Other')
])
# def action_create_tax_settlement_entry(self):
# if self.settlement_tax == 'profits':
# self = self.with_context(quincenal=True)
# return super(
# AccountJournal, self).action_create_tax_settlement_entry()
@api.constrains('settlement_tax')
def check_withholding_autmatic_installed(self):
account_withholding_automatic = self.env['ir.module.module'].search([
('name', '=', 'account_withholding_automatic'),
('state', '=', 'installed'),
])
if not account_withholding_automatic and any(self.filtered(
lambda x: x.settlement_tax in ['iibb_aplicado_api',
'sicore_aplicado'])):
raise ValidationError(_(
'No puede utilizar exportación a "SICORE Aplicado"'
' o "Perc/Ret IIBB aplicadas API"'
' si no tiene instalado el módulo de retenciones'
' automáticas (account_withholding_automatic)'))
def iibb_aplicado_dgr_mendoza_files_values(self, move_lines):
self.ensure_one()
ret = ''
for line in move_lines:
# Agente de Retención del Impuesto sobre los Ingresos Brutos
partner = line.partner_id
payment = line.payment_id
move = line.move_id
tax = line.tax_line_id
alicuot_line = tax.get_partner_alicuot(partner, line.date)
if not alicuot_line:
raise ValidationError(_('No hay alicuota configurada en el partner "%s" (id: %s)') % (
partner.name, partner.id))
if not payment:
continue
# Campo 1: CUIT char(13). CUIT del Sujeto retenido o percibido. Ejemplo: 20-10111222-3
# Example "30-58710878-6"
partner.ensure_vat()
content = partner.l10n_ar_formatted_vat
# Campo 2: Denominación char(80). Apellido y Nombre o Razón Social. Formato: 80 posiciones, se completa con
# blancos a la derecha.
# Example "ELECTRICIDAD MAZA SRL "
content += '{:80.80}'.format(partner.name)
# Campo 3: Fecha Comprobante char(8). Fecha del Comprobante de Retención/Percepción según Res.40/2012 (ddmmaaaa)
# Example s"16052020"
content += fields.Date.from_string(move.date).strftime('%d%m%Y')
# Campo 4: Comprobante char(12)- Número de Comprobante de Retención/Percepción según Res.40/2012.
# Formato: 999999999999 (rellenar con ceros (0) a la izquierda) Ejemplo: 000000001521
# Example "000000027860"
content += (payment.withholding_number or '').rjust(12, '0')[:12] # we are forcing 12 first numbers always.
# Campo 5: Fecha Ret./Perc. char(8)- Fecha de efectuada la retención / percepción (ddmmaaaa)
# Example "16052020"
content += fields.Date.from_string(payment.payment_date).strftime('%d%m%Y')
# Campo 6. Base Imponible char(15). Formato: 999999999999.99 (doce enteros, punto decimal y dos decimales,
# dejando espacios en blanco a izquierda para completar las 15 posiciones). Ejemplo: " 345.21"
# Example "000000027229.33"
content += '%15.2f' % payment.withholdable_base_amount
# Campo 7: Alícuota char(5). Alícuota para la retención y/o percepción. Formato: 99.99 (dos enteros,
# punto decimal y dos decimales. Ejemplo: " 3.00"
# Example "03.00"
content += '%5.2f' % alicuot_line.alicuota_retencion
# Campo 8: Importe Ret./Perc. char(15). Importe retenido y/o percibido. Formato: 999999999999.99 (doce enteros,
# punto decimal y dos decimales, dejando espacios en blanco a izquierda para completar las 15 posiciones).
# Ejemplo: " 34.50" "000000000816.88"
content += '%15.2f' % -line.balance
content += '\r\n'
ret += content
# File name
move_line = move_lines and move_lines[0] or self.env['account.move.line']
tipo_agente = 'rr' # This value is fixed just because we are doing the retention txt, when adding the
# perception we need to change it
cuit = move_line.company_id.vat
periodo = fields.Date.from_string(move_line.date).strftime('%Y') or "" # 'pppp' AÑO '2020'
cuota = fields.Date.from_string(move_line.date).strftime('%m') or "" # 'cc'
return [{
'txt_filename': '%s%s%s%s.txt' % (tipo_agente, cuit, periodo, cuota),
'txt_content': ret,
}]
def _get_perception_original_invoice_number(self, line):
self.ensure_one()
res = ''
related_invoice = line.move_id._found_related_invoice() or line.move_id
letter = related_invoice.l10n_latam_document_type_id.l10n_ar_letter
internal_type = related_invoice.l10n_latam_document_type_id.internal_type
# 2 Tipo de comprobante
if internal_type == 'invoice':
document_type = letter == 'E' and 5 or 1
elif internal_type == 'credit_note':
document_type = letter == 'E' and 106 or 102
elif internal_type == 'debit_note':
document_type = letter == 'E' and 6 or 2
elif related_invoice.type == 'out_invoice':
document_type = 20
elif related_invoice.type == 'out_refund':
document_type = 120
else:
raise ValidationError(_('Tipo de comprobante no reconocido'))
res += str(document_type)[:1]
# 3 Letra del comprobante
res += letter
# 4 Número del comprobante
res += '%012d' % int(re.sub('[^0-9]', '', related_invoice.l10n_latam_document_number or ''))
return res
def iibb_aplicado_api_files_values(self, move_lines):
""" Implementado segun especificación en carpeta doc de este repo
"""
def format_amount(amount, integers, decimals=2):
# overwrite default format_amount
template = "%0" + "%ss" % (integers + decimals + 1)
# TODO se podria mejorar haciendo algo asi pero hace falta
# hacer parametro el 16
# "{0:>16.2f}".format(12.1)
return template % "{0:.2f}".format(
round(amount, decimals)).replace('.', ',')
self.ensure_one()
ret = ''
perc = ''
for line in move_lines:
partner = line.partner_id
tax = line.tax_line_id
alicuot_line = tax.get_partner_alicuot(partner, line.date)
if not alicuot_line:
raise ValidationError(_(
'No hay alicuota configurada en el partner '
'"%s" (id: %s)') % (partner.name, partner.id))
# 1 - tipo de operacion
if tax.type_tax_use in ['sale', 'purchase'] and \
tax.amount_type == 'partner_tax':
content = '2'
alicuot = alicuot_line.alicuota_percepcion
# para percepciones ho es obligatorio
articulo_inciso_calculo = \
alicuot_line.api_articulo_inciso_calculo_percepcion \
or '000'
articulo_inciso_retiene = \
alicuot_line.api_codigo_articulo_percepcion
elif tax.type_tax_use in ['customer', 'supplier'] and \
tax.withholding_type == 'partner_tax':
content = '1'
alicuot = alicuot_line.alicuota_retencion
articulo_inciso_calculo = \
alicuot_line.api_articulo_inciso_calculo_retencion
articulo_inciso_retiene = \
alicuot_line.api_codigo_articulo_retencion
else:
raise ValidationError(_(
'Tipo de impuesto %s equivocado. Se aceptan solo '
'percepciones o retenciones con "Cálculo de impuestos" '
'igual a "Alícuota en el Partner". Id de impuestos '
'"%s"') % (tax.tax_group_id.name, tax.id))
if not articulo_inciso_calculo or not articulo_inciso_retiene:
raise ValidationError(_(
'Debe setear la información de "artículo/inciso" en las'
' alícutoas de contacto %s') % partner.name)
# 2 - fecha
content += fields.Date.from_string(line.date).strftime('%d/%m/%Y')
# 3 - Código de artículo Inciso por el que retiene
content += articulo_inciso_retiene
# 4 - tipo de comprobante y
# 5 - letra de comprobante
internal_type = line.l10n_latam_document_type_id.internal_type
move = line.move_id
if internal_type in ('invoice', 'credit_note'):
# factura
content += '01' + line.l10n_latam_document_type_id.l10n_ar_letter
elif internal_type == 'debit_note':
# ND
content += '02' + line.l10n_latam_document_type_id.l10n_ar_letter
else:
# orden de pago (sin letra)
# 09 sería otro comprobante y 10 reinitegro de perc/ret
content += '03 '
# 6 - numero comprobante Texto(16)
if internal_type in ('invoice', 'credit_note', 'debit_note'):
# TODO el aplicativo deberia empezar a aceptar 5 digitos
pos, number = get_pos_and_number(move.l10n_latam_document_number)
content += '{:>04s}'.format(pos)
content += '{:>08s}'.format(number)
content += ' '
else:
content += '%016s' % (move.l10n_latam_document_number or '')
# 7 - fecha comprobante
content += fields.Date.from_string(move.date).strftime('%d/%m/%Y')
# 8 - monto comprobante
content += format_amount(-line.balance, 11, 2)
# 9 - tipo de documento
# nosotros solo permitimos CUIT por ahora
content += '3'
# 10 - numero de documento
content += partner.ensure_vat()
# 11 - Condición frente a Ingresos Brutos
# 1 es inscripto, 2 no inscripto con oblig. a insc y 3 no insc sin
# oblig a insc. TODO implementar 2
gross_income_type = partner.l10n_ar_gross_income_type
if not gross_income_type:
raise ValidationError(_(
'Debe setear el tipo de inscripción de IIBB del partner '
'"%s" (id: %s)') % (
partner.name, partner.id))
if gross_income_type in ['multilateral', 'local']:
content += '1'
else:
content += '3'
# 12 - Número de Inscripción en Ingresos Brutos
content += (re.sub(
'[^0-9]', '',
partner.l10n_ar_gross_income_number or '')).rjust(10, '0')
# 13 - Situación frente a IVA donde:
# ri (1), rni (2), exento (3), monotr (4)
res_iva = partner.l10n_ar_afip_responsibility_type_id
if res_iva.code in ['1', '1FM']:
# RI
content += '1'
elif res_iva.code == '2':
# RNI
content += '2'
elif res_iva.code == '4':
# EXENTO
content += '3'
elif res_iva.code == '6':
# MONOT
content += '4'
else:
raise ValidationError(_(
'La responsabilidad frente a IVA "%s" no está soportada '
'para ret/perc Santa Fe') % res_iva.name)
# 14 - Marca inscripción Otros Gravámenes
# TODO implementar (requiere nuevo campo en odoo?)
content += '0'
# 15 - Marca Inscripción DREI
# TODO revisar si implementamos o no, aparentemente este campo
# activo en drei no se usa o no es lo que esperamos, por ahora
# no lo hacemos requerido para no andar molestando al dope
# if not partner.drei:
# raise ValidationError(_(
# 'Debe seleccionar situación DREI para partner '
# '"%s" (id: %s)') % (
# partner.name, partner.id))
content += partner.drei == 'activo' and '1' or '0'
# 16 - Importe Otros Gravámenes
# TODO implementar
content += format_amount(0.0, 9, 2)
# 17 - Importe IVA (solo si factura)
if line.move_id.is_invoice():
amounts = line.move_id._l10n_ar_get_amounts(company_currency=True)
vat_amount = amounts['vat_amount']
base_amount = amounts['vat_untaxed_base_amount']
else:
vat_amount = 0.0
base_amount = line.payment_id and line.payment_id.withholdable_base_amount or 0.0
content += format_amount(vat_amount, 9, 2)
# 18 - Base Imponible para el cálculo
# tal vez la base deberiamos calcularlo asi, en pagos no porque
# los asientos estan separados
# content += format_amount(-get_line_tax_base(line), 12, 2, ',')
content += format_amount(base_amount, 11, 2)
# 19 - Alícuota / alicuota
content += format_amount(alicuot, 2, 2)
# 20 - Impuesto Determinado
content += format_amount(-line.balance, 11, 2)
# 21 - Derecho Registro e Inspección
# TODO implementar
# es un importe seguramente importe retenido de drei
content += format_amount(0.0, 9, 2)
# 22 - Monto Retenido
# TODO por ahora es igual a impuesto determinado pero, podria ser
# distinto en algún caso?
content += format_amount(-line.balance, 11, 2)
# 23 - Artículo/Inciso para el cálculo
content += articulo_inciso_calculo
# 24 - Tipo de Exención
# TODO implementar. Por ahora no implementamos excenciones ya que
# a priori no las informan
content += '0'
# 25 - Año de Exención
# TODO implementar
content += '0000'
# 26 - Número de Certificado de Exención
# TODO implementar
content += ' '
# 27 - Número de Certificado Propio
# TODO implementar
content += ' '
# new line
content += '\r\n'
if tax.type_tax_use in ['sale', 'purchase']:
perc += content
elif tax.type_tax_use in ['customer', 'supplier']:
ret += content
# return [
# {
# 'txt_filename': 'Perc IIBB API Aplicadas.txt',
# 'txt_content': perc,
# },
# {
# 'txt_filename': 'Ret IIBB API Aplicadas.txt',
# 'txt_content': ret,
# }
return [
{
'txt_filename': 'Perc/Ret IIBB API Aplicadas.txt',
'txt_content': perc + ret,
}]
def iibb_aplicado_agip_files_values(self, move_lines):
""" Ver readme del modulo para descripcion del formato. Tambien
archivos de ejemplo en /doc
"""
self.ensure_one()
if self.company_id.agip_padron_type != 'regimenes_generales':
raise ValidationError(_(
'Por ahora solo esta implementado el padrón de Regímenes '
'Generales, revise la configuración en "Contabilidad / "'
'Configuración / Ajustes"'))
ret_perc = ''
credito = ''
company_currency = self.company_id.currency_id
for line in move_lines.sorted('date'):
# pay_group = payment.payment_group_id
move = line.move_id
payment = line.payment_id
tax = line.tax_line_id
partner = line.partner_id
internal_type = line.l10n_latam_document_type_id.internal_type
if not partner.vat:
raise ValidationError(_(
'El partner "%s" (id %s) no tiene número de identificación '
'seteada') % (partner.name, partner.id))
alicuot_line = tax.get_partner_alicuot(partner, line.date)
if not alicuot_line:
raise ValidationError(_(
'No hay alicuota configurada en el partner '
'"%s" (id: %s)') % (partner.name, partner.id))
# 1 - Tipo de Operación
if tax.type_tax_use in ['sale', 'purchase']:
# tax.amount_type == 'partner_tax':
content = '2'
alicuot = alicuot_line.alicuota_percepcion
elif tax.type_tax_use in ['customer', 'supplier']:
# tax.withholding_type == 'partner_tax':
content = '1'
alicuot = alicuot_line.alicuota_retencion
# notas de credito
if internal_type == 'credit_note':
# 2 - Nro. Nota de crédito
content += '%012d' % int(
re.sub('[^0-9]', '', move.l10n_latam_document_number or ''))
# 3 - Fecha Nota de crédito
content += fields.Date.from_string(
line.date).strftime('%d/%m/%Y')
# 4 - Monto nota de crédito
# TODO implementar devoluciones de pagos
# content += format_amount(
# line.move_id.cc_amount_total, 16, 2, ',')
# la especificacion no lo dice claro pero un errror al importar
# si, lo que se espera es el importe base, ya que dice que
# este, multiplicado por la alícuota, debe ser igual al importe
# a retener/percibir
taxable_amount = line.tax_base_amount
content += format_amount(taxable_amount, 16, 2, ',')
# 5 - Nro. certificado propio
# opcional y el que nos pasaron no tenia
content += ' '
# segun interpretamos de los daots que nos pasaron 6, 7, 8 y 11
# son del comprobante original
or_inv = line.move_id._found_related_invoice()
if not or_inv:
raise ValidationError(_(
'No pudimos encontrar el comprobante original para %s '
'(id %s). Verifique que en la nota de crédito "%s", el'
' campo origen es el número de la factura original'
) % (
line.move_id.display_name,
line.move_id.id,
line.move_id.display_name))
# 6 - Tipo de comprobante origen de la retención
# por ahora solo tenemos facturas implementadas
content += '01'
# 7 - Letra del Comprobante
if payment:
content += ' '
else:
content += or_inv.l10n_latam_document_type_id.l10n_ar_letter
# 8 - Nro de comprobante (original)
content += '%016d' % int(
re.sub('[^0-9]', '', or_inv.l10n_latam_document_number or ''))
# 9 - Nro de documento del Retenido
content += str(partner._get_id_number_sanitize())
# 10 - Código de norma
# por ahora solo padron regimenes generales
content += '029'
# 11 - Fecha de retención/percepción
content += fields.Date.from_string(
or_inv.invoice_date).strftime('%d/%m/%Y')
# 12 - Ret/percep a deducir
content += format_amount(line.balance, 16, 2, ',')
# 13 - Alícuota
content += format_amount(alicuot, 5, 2, ',')
content += '\r\n'
credito += content
continue
# 2 - Código de Norma
# por ahora solo padron regimenes generales
content += '029'
# 3 - Fecha de retención/percepción
content += fields.Date.from_string(line.date).strftime('%d/%m/%Y')
# 4 - Tipo de comprobante origen de la retención
if internal_type == 'invoice':
content += '01'
elif internal_type == 'debit_note':
content += '02'
else:
# orden de pago
content += '03'
# 5 - Letra del Comprobante
# segun vemos en los archivos de ejemplo solo en percepciones
if payment:
content += ' '
else:
content += line.l10n_latam_document_type_id.l10n_ar_letter
# 6 - Nro de comprobante
content += '%016d' % int(
re.sub('[^0-9]', '', move.l10n_latam_document_number or ''))
# 7 - Fecha del comprobante
content += fields.Date.from_string(move.date).strftime('%d/%m/%Y')
# obtenemos montos de los comprobantes
payment_group = line.payment_id.payment_group_id
if payment_group:
# solo en comprobantes A, M segun especificacion
vat_amount = 0.0
total_amount = float_round(payment_group.payments_amount, precision_digits=2)
# es lo mismo que payment_group.matched_amount_untaxed
taxable_amount = float_round(payment.withholdable_base_amount, precision_digits=2)
# lo sacamos por diferencia
other_taxes_amount = company_currency.round(
total_amount - taxable_amount - vat_amount)
elif line.move_id.is_invoice():
amounts = line.move_id._l10n_ar_get_amounts(company_currency=True)
# segun especificacion el iva solo se reporta para estos
if line.l10n_latam_document_type_id.l10n_ar_letter in ['A', 'M']:
vat_amount = amounts['vat_amount']
else:
vat_amount = 0.0
total_amount = (1 if line.move_id.is_inbound() else -1) * line.move_id.amount_total_signed
# por si se olvidaron de poner agip en una linea de factura
# la base la sacamos desde las lineas de impuesto
# taxable_amount = line.move_id.cc_amount_untaxed
taxable_amount = line.tax_base_amount
# tambien lo sacamos por diferencia para no tener error (por el
# calculo trucado de taxable_amount por ejemplo) y
# ademas porque el iva solo se reporta si es factura A, M
other_taxes_amount = company_currency.round(
total_amount - taxable_amount - vat_amount)
# other_taxes_amount = line.move_id.cc_other_taxes_amount
else:
raise ValidationError(_('El impuesto no está asociado'))
# 8 - Monto del comprobante
content += format_amount(total_amount, 16, 2, ',')
# 9 - Nro de certificado propio
content += (payment.withholding_number or '').rjust(16, ' ')
# 10 - Tipo de documento del Retenido
# vat
if partner.l10n_latam_identification_type_id.name not in ['CUIT', 'CUIL', 'CDI']:
raise ValidationError(_(
'EL el partner "%s" (id %s), el tipo de identificación '
'debe ser una de siguientes: CUIT, CUIL, CDI.' % (partner.id, partner.name)))
doc_type_mapping = {'CUIT': '3', 'CUIL': '2', 'CDI': '1'}
content += doc_type_mapping[partner.l10n_latam_identification_type_id.name]
# 11 - Nro de documento del Retenido
content += str(partner._get_id_number_sanitize())
# 12 - Situación IB del Retenido
# 1: Local 2: Convenio Multilateral
# 4: No inscripto 5: Reg.Simplificado
if not partner.l10n_ar_gross_income_type:
raise ValidationError(_(
'Debe setear el tipo de inscripción de IIBB del partner '
'"%s" (id: %s)') % (partner.name, partner.id))
# ahora se reportaria para cualquier inscripto el numero de cuit
gross_income_mapping = {
'local': '5', 'multilateral': '2', 'exempt': '4'}
content += gross_income_mapping[partner.l10n_ar_gross_income_type]
# 13 - Nro Inscripción IB del Retenido
if partner.l10n_ar_gross_income_type == 'exempt':
content += '00000000000'
else:
content += partner.ensure_vat()
# 14 - Situación frente al IVA del Retenido
# 1 - Responsable Inscripto
# 3 - Exento
# 4 - Monotributo
res_iva = partner.l10n_ar_afip_responsibility_type_id
if res_iva.code in ['1', '1FM']:
# RI
content += '1'
elif res_iva.code == '4':
# EXENTO
content += '3'
elif res_iva.code == '6':
# MONOT
content += '4'
else:
raise ValidationError(_(
'La responsabilidad frente a IVA "%s" no está soportada '
'para ret/perc AGIP') % res_iva.name)
# 15 - Razón Social del Retenido
content += '{:30.30}'.format(partner.name)
# 16 - Importe otros conceptos
content += format_amount(other_taxes_amount, 16, 2, ',')
# 17 - Importe IVA
content += format_amount(vat_amount, 16, 2, ',')
# 18 - Monto Sujeto a Retención/ Percepción
content += format_amount(taxable_amount, 16, 2, ',')
# 19 - Alícuota
content += format_amount(alicuot, 5, 2, ',')
# 20 - Retención/Percepción Practicada
content += format_amount(-line.balance, 16, 2, ',')
# 21 - Monto Total Retenido/Percibido
content += format_amount(-line.balance, 16, 2, ',')
content += '\r\n'
ret_perc += content
return [{
'txt_filename': 'Perc/Ret IIBB AGIP Aplicadas.txt',
'txt_content': ret_perc,
}, {
'txt_filename': 'NC Perc/Ret IIBB AGIP Aplicadas.txt',
'txt_content': credito,
}]
def iibb_aplicado_act_7_files_values(self, move_lines):
return self.iibb_aplicado_files_values(move_lines, act_7=True)
def iibb_aplicado_files_values(self, move_lines, act_7=None):
"""
Por ahora es el de arba, renombrar o generalizar para otros
Implementado segun esta especificacion
https://drive.google.com/file/d/0B3trzV0e2WzveHhBTk9xWEl6RjA/view
Implementados:
- 1.2 Percepciones Act. 7 método Percibido (quincenal)
- 1.7 Retenciones ( excepto actividad 26, 6 de Bancos y 17 de
Bancos y No Bancos)
"""
self.ensure_one()
ret = ''
perc = ''
for line in move_lines:
# pay_group = payment.payment_group_id
move = line.move_id
payment = line.payment_id
internal_type = line.l10n_latam_document_type_id.internal_type
document_code = line.l10n_latam_document_type_id.code
line.partner_id.ensure_vat()
content = line.partner_id.l10n_ar_formatted_vat
content += fields.Date.from_string(
line.date).strftime('%d/%m/%Y')
# solo para percepciones
if not payment:
content += (
document_code in ['201', '206', '211'] and 'E' or
document_code in ['203', '208', '213'] and 'H' or
document_code in ['202', '207', '212'] and 'I' or
internal_type == 'invoice' and 'F' or
internal_type == 'credit_note' and 'C' or
internal_type == 'debit_note' and 'D' or 'R')
content += line.l10n_latam_document_type_id.l10n_ar_letter
document_parts = move._l10n_ar_get_document_number_parts(
move.l10n_latam_document_number, move.l10n_latam_document_type_id.code)
# si el punto de venta es de 5 digitos no encontramos doc
# que diga como proceder, tomamos los ultimos 4 digitos
pto_venta = "{:0>4d}".format(document_parts['point_of_sale'])[-4:]
nro_documento = "{:0>8d}".format(document_parts['invoice_number'])[-8:]
content += str(pto_venta)
content += str(nro_documento)
# solo para percepciones
if not payment:
content += format_amount(-get_line_tax_base(line), 12, 2, ',')
# este es para el primer tipo de la especificación
content += format_amount(-line.balance, 11, 2, ',')
# solo para percepciones
# según especificación se requiere fecha nuevamente
# por ahora lo sacamos ya que en ticket 16448 nos mandaron ej.
# donde no se incluía, en realidad tal vez depende de la actividad
# ya que en la primer tabla del pdf la agrega y en la segunda no
if act_7 and not payment:
content += fields.Date.from_string(
line.date).strftime('%d/%m/%Y')
content += 'A'
content += '\r\n'
if payment:
ret += content
else:
perc += content
# para la fecha de la presentación tomamos la fecha de un apunte a liquidar
# el valor de la quincena puede ser 0, 1, 2. deberiamos ver si podemos
# completarlo de alguna manera
period = move_lines and \
fields.Date.from_string(move_lines[0].date).strftime('%Y%mX') or ""
# AR-CUIT-PERIODO-ACTIVIDAD-LOTE_MD5
perc_txt_filename = "AR-%s-%s-%s-LOTEX.txt" % (
self.company_id.vat,
period,
"7", # 7 serian las percepciones
)
# AR-vat-PERIODO-ACTIVIDAD-LOTE_MD5
ret_txt_filename = "AR-%s-%s-%s-LOTEX.txt" % (
self.company_id.vat,
period,
"6", # 6 serian las retenciones
)
return [
{
'txt_filename': perc_txt_filename,
'txt_content': perc,
},
{
'txt_filename': ret_txt_filename,
'txt_content': ret,
}]
def iibb_aplicado_sircar_files_values(self, move_lines):
""" Especificacion en /doc/sircar
"""
self.ensure_one()
ret = ''
perc = ''
for line in move_lines.filtered(
lambda x: not x.payment_id and not x.move_id):
raise ValidationError(_(
'Hay lineas a liquidar que no estan enlazadas a pagos ni '
'facturas lo cual es requerido para generar el TXT'))
line_nbr = 1
for line in move_lines.filtered('payment_id'):
alicuot_line = line.tax_line_id.get_partner_alicuot(
line.partner_id, line.date)
if not alicuot_line:
raise ValidationError(_(
'No hay alicuota configurada en el partner '
'"%s" (id: %s)') % (
line.partner_id.name, line.partner_id.id))
payment = line.payment_id
internal_type = line.l10n_latam_document_type_id.internal_type
# 1 Número de Renglón (único por archivo)
content = []
content.append('%05d' % line_nbr)
# 2 Origen del Comprobante
content.append('1')
# 3 Tipo del Comprobante
if payment.payment_type == 'outbound':
content.append('1')
else:
content.append('2')
# 4 Número del comprobante
content.append('%012d' % int(
re.sub('[^0-9]', '', line.payment_id.withholding_number or '')))
# 5 Cuit del contribuyene
content.append(line.partner_id.ensure_vat())
# 6 Fecha de la percepción
content.append(
fields.Date.from_string(line.date).strftime('%d/%m/%Y'))
# 7 Monto sujeto a percepción
content.append(format_amount(
payment.withholdable_base_amount, 12, 2, '.'))
# 8 alicuota de la retencion
content.append(format_amount(
alicuot_line.alicuota_retencion, 6, 2, '.'))
# 9 Monto retenido
content.append(format_amount(-line.balance, 12, 2, '.'))
# 10 Tipo de Régimen de Percepción
# (código correspondiente según tabla definida por la jurisdicción)
if not alicuot_line.regimen_retencion:
raise ValidationError(_(
'No hay regimen de retencion configurado para la alícuota'
' del partner %s') % line.partner_id.name)
content.append(alicuot_line.regimen_retencion)
# 11 Jurisdicción: código en Convenio Multilateral de la
# jurisdicción a la cual está presentando la DDJJ
if not line.tax_line_id.jurisdiction_code:
raise ValidationError(_(
'No hay etiqueta de jurisdicción configurada!'))
content.append(line.tax_line_id.jurisdiction_code)
# Tipo registro 2. Provincia Cordoba
if line.tax_line_id.jurisdiction_code == '904':
# 12 Tipo de Operación (1-Efectuada, 2-Anulada, 3-Omitida)
content.append('2' if internal_type == 'credit_note' else '1')
# 13 Fecha de Emisión de Constancia (en formato dd/mm/aaaa)
content.append(fields.Date.from_string(line.date).strftime('%d/%m/%Y'))
# 14 Número de Constancia - Numeric(14)
content.append('%014s' % int(re.sub('[^0-9]', '', payment.withholding_number or '0')[:14]))
# 15 Número de Constancia original (sólo para las Anulaciones –ver códigos por jur-) - Numeric(14)
original_invoice = line.move_id._found_related_invoice() or line.move_id
content.append('%014d' % int(re.sub('[^0-9]', '', original_invoice.document_number or ''))
if internal_type == 'credit_note' else '%014d' % 0)
ret += ','.join(content) + '\r\n'
line_nbr += 1
line_nbr = 1
for line in move_lines.filtered(lambda x: x.move_id.is_invoice()):
alicuot_line = line.tax_line_id.get_partner_alicuot(
line.partner_id, line.date)
if not alicuot_line:
raise ValidationError(_(
'No hay alicuota configurada en el partner '
'"%s" (id: %s)') % (
line.partner_id.name, line.partner_id.id))
# 1 Número de Renglón (único por archivo)
content = []
content.append('%05d' % line_nbr)
letter = line.l10n_latam_document_type_id.l10n_ar_letter
# 2 Tipo de comprobante
internal_type = line.l10n_latam_document_type_id.internal_type
if internal_type == 'invoice':
tipo_comprobante = letter == 'E' and 5 or 1
elif internal_type == 'credit_note':
tipo_comprobante = letter == 'E' and 106 or 102
elif internal_type == 'debit_note':
tipo_comprobante = letter == 'E' and 6 or 2
elif line.move_id.type == 'out_invoice':
tipo_comprobante = 20
elif line.move_id.type == 'out_refund':
tipo_comprobante = 120
else:
raise ValidationError(_('Tipo de comprobante no reconocido'))
content.append('%03d' % tipo_comprobante)
# 3 Letra del comprobante
content.append(line.l10n_latam_document_type_id.l10n_ar_letter)
# 4 Número del comprobante
content.append('%012d' % int(
re.sub('[^0-9]', '', line.move_id.l10n_latam_document_number or '')))
# 5 Cuit del contribuyene
content.append(line.partner_id.ensure_vat())
# 6 Fecha de la percepción
content.append(
fields.Date.from_string(line.date).strftime('%d/%m/%Y'))
# 7 Monto sujeto a percepción
content.append(format_amount(-get_line_tax_base(line), 12, 2, '.'))
# 8 alicuota de la percepcion
content.append(format_amount(
alicuot_line.alicuota_percepcion, 6, 2, '.'))
# 9 Monto percibido
content.append(format_amount(-line.balance, 12, 2, '.'))
# 10 Tipo de Régimen de Percepción
# (código correspondiente según tabla definida por la jurisdicción)
if not alicuot_line.regimen_percepcion:
raise ValidationError(_(
'No hay regimen de percepcion configurado para la alícuota'
' del partner %s') % line.partner_id.name)
content.append(alicuot_line.regimen_percepcion)
# 11 Jurisdicción: código en Convenio Multilateral de la
# jurisdicción a la cual está presentando la DDJJ
if not line.tax_line_id.jurisdiction_code:
raise ValidationError(_(
'No hay etiqueta de jurisdicción configurada!'))
content.append(line.tax_line_id.jurisdiction_code)
# Tipo registro 2. Provincia Cordoba
if line.tax_line_id.jurisdiction_code == '904':
# 12 Tipo de Operación (1-Efectuada, 2-Anulada, 3-Omitida, 4-Informativa)
content.append('2' if internal_type == 'credit_note' else '1')
# 13 Número de Constancia original (sólo para 2-Anulaciones) Alfanumérico (14) - ejemplo 1A002311312221
content.append(self._get_perception_original_invoice_number(line)
if internal_type == 'credit_note' else '%014d' % 0)
perc += ','.join(content) + '\r\n'
line_nbr += 1
return [
{
'txt_filename': 'Perc IIBB Aplicadas para SIRCAR.txt',
'txt_content': perc,
},
{
'txt_filename': 'Ret IIBB Aplicadas para SIRCAR.txt',
'txt_content': ret,
}]