facturacr 1.0.9 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,25 +1,27 @@
1
1
  module FE
2
2
  class Document
3
- class Exoneration
3
+ class Exoneration < Element
4
4
  include ActiveModel::Validations
5
5
 
6
6
 
7
7
  DOCUMENT_TYPES = {
8
8
  "01" => "Compras Autorizadas",
9
9
  "02" => "Ventas exentas a diplomáticos",
10
- "03" => "Orden de Compra (Instituciones Públicas y otros organismos)",
10
+ "03" => "Autorizado por Ley especial",
11
11
  "04" => "Exenciones Dirección General de Hacienda",
12
- "05" => "Zonas Francas",
12
+ "05" => "Transitorio V",
13
+ "06" => "Transitorio IX",
14
+ "07" => "Transitorio XVII",
13
15
  "99" => "Otros"
14
16
  }.freeze
15
17
  attr_accessor :document_type, :document_number, :institution, :date, :total_tax, :percentage, :net_total
16
18
 
17
19
  validates :document_type, presence: true, inclusion: DOCUMENT_TYPES.keys
18
- validates :document_number, presence: true
19
- validates :institution, presence: true
20
+ validates :document_number, presence: true, length: { maximum: 40 }
21
+ validates :institution, presence: true, length: { maximum: 160 }
20
22
  validates :date, presence: true
21
23
  validates :total_tax,presence: true
22
- validates :percentage, presence: true
24
+
23
25
 
24
26
  def initialize(args={})
25
27
  @document_type = args[:document_type]
@@ -27,11 +29,12 @@ module FE
27
29
  @institution = args[:institution]
28
30
  @date = args[:date]
29
31
  @total_tax = args[:total_tax]
30
- @percentage = ((@total_tax.to_f / args[:net_total].to_f) * 100).to_i if args[:net_total].present?
32
+ @percentage = args[:percentage]
31
33
  end
32
34
 
33
- def build_xml(node)
34
- raise "Invalid Record: #{errors.messages}" unless valid?
35
+ def build_xml(node, document)
36
+ @document = document
37
+ raise FE::Error.new("invalid exoneration",class: self.class, messages: errors.messages) unless valid?
35
38
  node = Nokogiri::XML::Builder.new if node.nil?
36
39
 
37
40
  node.Exoneracion do |xml|
@@ -39,11 +42,12 @@ module FE
39
42
  xml.NumeroDocumento @document_number
40
43
  xml.NombreInstitucion @institution
41
44
  xml.FechaEmision @date.xmlschema
42
- xml.MontoImpuesto @total_tax
43
- xml.PorcentajeCompra @percentage
45
+ xml.PorcentajeExoneracion @percentage
46
+ xml.MontoExoneracion @total_tax
44
47
  end
45
48
  end
46
49
 
50
+
47
51
  end
48
52
  end
49
- end
53
+ end
@@ -20,20 +20,21 @@ module FE
20
20
 
21
21
  @document_type = args[:type]
22
22
  @raw_id_number = args[:number]
23
- @id_number = "%012d" % args[:number]
24
-
23
+ if @raw_id_number
24
+ @id_number = "%012d" % args[:number]
25
+ end
25
26
  end
26
27
 
27
- def build_xml(node)
28
- raise "Invalid Record: #{errors.messages}" unless valid?
28
+ def build_xml(node, document)
29
+ raise FE::Error.new("invalid identification document", class: self.class, messages: errors.messages) unless valid?
29
30
  node = Nokogiri::XML::Builder.new if node.nil?
30
31
  node.Identificacion do |x|
31
32
  x.Tipo document_type
32
33
  x.Numero raw_id_number
33
34
  end
34
35
  end
35
- def to_xml(builder)
36
- build_xml(builder).to_xml
36
+ def to_xml(builder, document)
37
+ build_xml(builder,document).to_xml
37
38
  end
38
39
  end
39
40
 
@@ -1,22 +1,28 @@
1
- require "facturacr/document"
2
1
  require 'active_model'
3
2
  require 'nokogiri'
4
3
 
5
4
  module FE
6
5
  class Document
7
-
8
-
9
- class Issuer
6
+
7
+
8
+ class Issuer < Element
10
9
  include ActiveModel::Validations
11
-
10
+
12
11
  attr_accessor :name, :identification_document, :comercial_name, :location, :phone, :fax, :email
13
-
14
- validates :name, presence: true, length: { maximum: 80 }
12
+
13
+ validates :name, presence: true
15
14
  validates :identification_document, presence: true
16
- validates :comercial_name, length: {maximum: 80}
17
15
  validates :location, presence: true
18
- validates :email, presence: true, format: {with: /\s*\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*\s*/}
16
+ validates :email, presence: true,length: {maximum: 160}, format:{with: /\s*\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*\s*/}
17
+
18
+ validates :name, length: { maximum: 80}, if: ->{ document.version_42? }
19
+ validates :comercial_name, length: { maximum: 80 }, if: ->{ document.version_42? }
20
+
21
+ validates :name, length: { maximum: 100}, if: ->{ document.version_43? }
22
+ validates :comercial_name, length: { maximum: 100 }, if: ->{ document.version_43? }
23
+
19
24
 
25
+
20
26
  def initialize(args={})
21
27
  @name = args[:name]
22
28
  @identification_document = args[:identification_document]
@@ -24,33 +30,29 @@ module FE
24
30
  @location = args[:location]
25
31
  @phone = args[:phone]
26
32
  @fax = args[:fax]
27
- @email = args[:email]
33
+ @email = args[:email]
28
34
  end
29
-
30
- def build_xml(node)
31
- raise "IdentificationDocument is invalid" if @identification_document.nil? || !@identification_document.is_a?(IdentificationDocument)
32
- raise "Location is invalid" if @location.nil? || !@location.is_a?(Location)
33
- raise "Phone is invalid" if !@phone.nil? && !@phone.is_a?(Phone)
34
- raise "Fax is invalid" if !@fax.nil? && !@fax.is_a?(Fax)
35
-
36
- raise "Issuer is invalid: #{errors.messages}" unless valid?
37
-
35
+
36
+ def build_xml(node, document)
37
+ @document = document
38
+ raise FE::Error.new("issuer invalid",class: self.class, messages: errors.messages) unless valid?
39
+
38
40
  node = Nokogiri::XML::Builder.new if node.nil?
39
41
  node.Emisor do |xml|
40
42
  xml.Nombre @name
41
- identification_document.build_xml(xml)
43
+ identification_document.build_xml(xml,document)
42
44
  xml.NombreComercial @comercial_name if @comercial_name
43
- location.build_xml(xml)
44
- phone.build_xml(xml) if phone.present?
45
- fax.build_xml(xml) if fax.present?
45
+ location.build_xml(xml, document)
46
+ phone.build_xml(xml, document) if phone.present?
47
+ fax.build_xml(xml, document) if fax.present?
46
48
  xml.CorreoElectronico @email
47
- end
49
+ end
48
50
  end
49
-
50
- def to_xml(builder)
51
- build_xml(builder).to_xml
51
+
52
+ def to_xml(builder,document)
53
+ build_xml(builder,document).to_xml
52
54
  end
53
55
  end
54
-
56
+
55
57
  end
56
- end
58
+ end
@@ -1,12 +1,12 @@
1
1
  module FE
2
2
  class Document
3
- class Item
3
+ class Item < Element
4
4
  include ActiveModel::Validations
5
5
 
6
- UNITS = %w[Sp m kg s A K mol cd m² m³ m/s m/s² 1/m kg/m³ A/m² A/m mol/m³ cd/m² 1 rad sr Hz N Pa J W C V F Ω S Wb T H °C lm
6
+ UNITS = %w[ Al Alc Cm I Os Spe St Sp m kg s A K mol cd m² m³ m/s m/s² 1/m kg/m³ A/m² A/m mol/m³ cd/m² 1 rad sr Hz N Pa J W C V F Ω S Wb T H °C lm
7
7
  lx Bq Gy Sv kat Pa·s N·m N/m rad/s rad/s² W/m² J/K J/(kg·K) J/kg W/(m·K) J/m³ V/m C/m³ C/m² F/m H/m J/mol J/(mol·K)
8
8
  C/kg Gy/s W/sr W/(m²·sr) kat/m³ min h d º ´ ´´ L t Np B eV u ua Unid Gal g Km ln cm mL mm Oz Otros].freeze
9
-
9
+ SERVICE_UNITS = %w[Al Alc Os Spe Sp St]
10
10
  CODE_TYPES = {
11
11
  '01' => 'Código del producto del vendedor',
12
12
  '02' => 'Código del producto del comprador',
@@ -15,26 +15,35 @@ module FE
15
15
  '99' => 'Otros'
16
16
  }.freeze
17
17
 
18
- attr_accessor :line_number, :code_type, :code, :quantity, :unit, :description, :unit_price, :total,
19
- :discount, :discount_reason, :subtotal, :taxes, :net_total
18
+ attr_accessor :line_number,:tariff_item,:code, :comercial_code_type, :comercial_code, :quantity, :unit, :description, :unit_price, :total,
19
+ :discount, :discount_reason, :subtotal,:taxable_base ,:taxes,:net_tax ,:net_total, :exoneration, :document_type
20
20
 
21
+ validates :document_type, presence: true, inclusion: FE::Document::DOCUMENT_TYPES.keys
21
22
  validates :line_number, presence: true
22
- validates :code_type, inclusion: CODE_TYPES.keys, if: -> { code.present? }
23
+ validates :tariff_item, presence: true, length: {is: 12}, if:->{document_type.eql?(FE::ExportInvoice::DOCUMENT_TYPE) && !SERVICE_UNITS.include?(unit) && document.version_43? }
23
24
  validates :quantity, presence: true, numericality: { greater_than: 0 }
24
25
  validates :unit, presence: true, inclusion: UNITS
25
- validates :description, presence: true, length: { maximum: 160 }
26
+ validates :description, presence: true, length: { maximum: 200 }
26
27
  validates :unit_price, presence: true
27
28
  validates :total, presence: true
28
29
  validates :discount, numericality: { grater_than: 0 }, if: -> { discount.present? }
29
30
  validates :discount_reason, presence: true, if: -> { discount.present? }
30
31
  validates :subtotal, presence: true
32
+ validates :taxable_base, presence: true, if: ->{ taxes.map{ |t| t.code.eql?("07")}.include?(true) && document.version_43? }
33
+ validates :net_tax,presence:true, if: ->{ exoneration.present? }
31
34
  validates :net_total, presence: true
32
- validates :code, presence: true, length: {maximum: 20}
35
+ validates :comercial_code_type, inclusion: CODE_TYPES.keys, if: -> { comercial_code.present? }
36
+ validates :comercial_code, presence: true, length: {maximum: 20}
37
+ validates :code, length: {maximum: 13} #TODO this will be mandatory after 2020-01-01
38
+
39
+ validate :calculations_ok?
33
40
 
34
41
  def initialize(args = {})
42
+ @document_type = args[:document_type]
35
43
  @line_number = args[:line_number]
36
- @code_type = args[:code_type].presence || '01'
37
44
  @code = args[:code]
45
+ @comercial_code_type = args[:comercial_code_type].presence || '01'
46
+ @comercial_code = args[:comercial_code]
38
47
  @quantity = args[:quantity]
39
48
  @unit = args[:unit]
40
49
  @description = args[:description]
@@ -45,17 +54,39 @@ module FE
45
54
  @subtotal = args[:subtotal]
46
55
  @taxes = args[:taxes] || []
47
56
  @net_total = args[:net_total]
57
+ @exoneration = args[:exoneration]
58
+ @net_tax = args[:net_tax]
59
+ @tariff_item = args[:tariff_item]
60
+
61
+
48
62
  end
49
63
 
50
- def build_xml(node)
51
- raise "Item invalid: #{errors.messages}" unless valid?
64
+ def build_xml(node, document)
65
+ @document = document
66
+ @document_type = document.document_type
67
+ raise FE::Error.new("item invalid",class: self.class, messages: errors.messages) unless valid?
68
+
52
69
  node = Nokogiri::XML::Builder.new if node.nil?
53
70
  node.LineaDetalle do |x|
54
71
  x.NumeroLinea @line_number
55
- if @code.present?
72
+
73
+ x.PartidaArancelaria @tariff_item if @tariff_item.present? && document.version_43?
74
+
75
+ if document.version_43?
76
+ x.Codigo @code if @code.present?
77
+ end
78
+
79
+ if @comercial_code.present? && document.version_43?
80
+ x.CodigoComercial do |x2|
81
+ x2.Tipo @comercial_code_type
82
+ x2.Codigo @comercial_code
83
+ end
84
+ end
85
+
86
+ if @comercial_code.present? && document.version_42?
56
87
  x.Codigo do |x2|
57
- x2.Tipo @code_type
58
- x2.Codigo @code
88
+ x2.Tipo @comercial_code_type
89
+ x2.Codigo @comercial_code
59
90
  end
60
91
  end
61
92
  x.Cantidad @quantity
@@ -63,18 +94,38 @@ module FE
63
94
  x.Detalle @description
64
95
  x.PrecioUnitario @unit_price
65
96
  x.MontoTotal @total
66
- x.MontoDescuento @discount if @discount.present?
67
- x.NaturalezaDescuento @discount_reason if @discount_reason.present?
97
+
98
+ if document.version_42?
99
+ x.MontoDescuento @discount if @discount.present?
100
+ x.NaturalezaDescuento @discount_reason if @discount_reason.present?
101
+ end
102
+ if @discount.present? && document.version_43?
103
+ x.Descuento do |x2|
104
+ x2.MontoDescuento @discount
105
+ x2.NaturalezaDescuento @discount_reason
106
+ end
107
+ end
108
+
68
109
  x.SubTotal @subtotal
110
+
111
+ x.BaseImponible @taxable_base if @taxable_base.present? && document.version_43?
69
112
  @taxes.each do |tax|
70
- tax.build_xml(x)
71
- end
72
- if @exoneration.present?
73
- @exoneration.build_xml(x)
113
+ tax.build_xml(x,document)
74
114
  end
115
+
116
+ x.ImpuestoNeto @net_tax if @net_tax.present? && @exoneration.present?
75
117
  x.MontoTotalLinea @net_total
76
118
  end
119
+
120
+ end
121
+
122
+
123
+
124
+ def calculations_ok?
125
+ errors.add :total, :invalid_amount, message: 'invalid amount' if (@total - (@quantity * @unit_price).round(5)).abs > 1
77
126
  end
127
+
128
+
78
129
  end
79
130
  end
80
131
  end
@@ -4,33 +4,33 @@ require 'nokogiri'
4
4
 
5
5
  module FE
6
6
  class Document
7
-
8
-
9
- class Location
7
+
8
+
9
+ class Location < Element
10
10
  include ActiveModel::Validations
11
-
11
+
12
12
  attr_accessor :province, :county,:district,:neighborhood, :others
13
-
13
+
14
14
  validates :province, presence: true, length: { is: 1 }
15
15
  validates :county, presence: true, length: { is: 2 }
16
16
  validates :district, presence: true, length: { is: 2 }
17
17
  validates :neighborhood, length: { is: 2 }, allow_blank: true
18
- validates :others, presence: true, length: { maximum: 160 }
19
-
18
+ validates :others, presence: true, length: { maximum: 250 }
19
+
20
20
  def initialize(args={})
21
-
21
+
22
22
  @province = args[:province]
23
23
  @county = args[:county]
24
24
  @district = args[:district]
25
25
  @neighborhood = args[:neighborhood]
26
26
  @others = args[:others]
27
-
28
-
27
+
28
+
29
29
  end
30
-
31
- def build_xml(node)
32
- raise "Location invalid: #{errors.messages}" unless valid?
33
- node = Nokogiri::XML::Builder.new if node.nil?
30
+
31
+ def build_xml(node, document)
32
+ raise FE::Error.new("location invalid",class: self.class, messages: errors.messages) unless valid?
33
+ node = Nokogiri::XML::Builder.new if node.nil?
34
34
  node.Ubicacion do |x|
35
35
  x.Provincia @province
36
36
  x.Canton @county
@@ -39,11 +39,11 @@ module FE
39
39
  x.OtrasSenas @others
40
40
  end
41
41
  end
42
-
43
- def to_xml(builder)
44
- build_xml(builder).to_xml
42
+
43
+ def to_xml(builder,document)
44
+ build_xml(builder,document).to_xml
45
45
  end
46
-
46
+
47
47
  end
48
48
  end
49
- end
49
+ end
@@ -0,0 +1,56 @@
1
+ module FE
2
+ class Document
3
+ class OtherCharges < Element
4
+
5
+ include ActiveModel::Validations
6
+
7
+ OTHER_DOCUMENT_TYPES = {
8
+
9
+ "01" => "Contribución parafiscal",
10
+ "02" => "Timbre de la Cruz Roja",
11
+ "03" => "Timbre de Benemérito Cuerpo de Bomberos de Costa Rica",
12
+ "04" => "Cobro de un tercero",
13
+ "05" => "Costos de Exportación",
14
+ "06" => "Impuesto de servicio 10%",
15
+ "07" => "Timbre de Colegios Profesionales",
16
+ "99" => "Otros Cargos"
17
+ }.freeze
18
+
19
+ attr_accessor :document_type, :collector_id_number, :collector_name, :detail, :percentage, :total_charge
20
+
21
+ validates :document_type, presence: true, inclusion: OTHER_DOCUMENT_TYPES.keys
22
+ validates :collector_id_number, presence: false, if: ->{ document_type.eql?("04") }
23
+ validates :detail, presence: true
24
+ validates :total_charge, presence: true
25
+ validates :collector_name, presence: false, if: ->{ document_type.eql?("04") }
26
+
27
+ def initialize(args={})
28
+ @document_type = args[:document_type]
29
+ @collector_id_number=args[:collector_id_number]
30
+ @collector_name = args[:collector_name]
31
+ @detail = args[:detail]
32
+ @percentage =args[:percentage]
33
+ @total_charge = args[:total_charge]
34
+ end
35
+
36
+ def build_xml(node, document)
37
+ raise FE::Error.new("other charges invalid",class: self.class, messages: errors.messages) unless valid?
38
+
39
+ node = Nokogiri::XML::Builder.new if node.nil?
40
+
41
+ node.OtrosCargos do |xml|
42
+ xml.TipoDocumento @document_type
43
+ xml.NumeroIdentidadTercero @third_id_number if @third_id_number.present?
44
+ xml.NombreTercero @third_name if @third_name.present?
45
+ xml.Detalle @detail
46
+ xml.Porcentaje @percentage if @percentage.present?
47
+ xml.MontoCargo @total_charge
48
+
49
+ end
50
+
51
+ end
52
+
53
+
54
+ end
55
+ end
56
+ end