colppy 0.1.0 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 423516e6c9f241cf1d2e7ddaa3dc32bacf8ed356
4
- data.tar.gz: ccb895a2d95fa3f55b3f2a97f7b0c21743d5c182
3
+ metadata.gz: 56924725187f03b328b0305020b7acee209863df
4
+ data.tar.gz: d22d27f9c912065931f93142177e2ce38eb6b3fc
5
5
  SHA512:
6
- metadata.gz: 59f62eb828dd05967ff21e9d567c39cc8830bd2f3820f9bd331b7f0ae27cc5c0a20d4745c92d82d339c434188b08b330a31e84ab60e8d1a741ed9d34acde72c2
7
- data.tar.gz: 07f3ee5c00ac9e8a79630911765013709fe07fffb1095dc99e8587b6fa0d2d07c8330cef152db5aa2963e7fa32c369257baf9e7a180b541710be4fe1c4ffb208
6
+ metadata.gz: 474bfe2dbb9a742e729752bad31a3aa99c240529bb9c86d5a956f3e6159cc21c3a9e7ed1de8f7d7f575e6429fd4928aa6809c76e592844713a754287ee387742
7
+ data.tar.gz: 823a5176e7bc75a6dc83f0d2e9eaff0227e1399f08be15408a40898fa430f1bb3e5ddc1e006fdc48e5e3763125241c4fe5d446b6eea266452e79fad3355f4596
data/README.md CHANGED
@@ -1,5 +1,6 @@
1
- #Colppy
2
- [![Build Status](https://travis-ci.org/goodpeople/colppy.svg?branch=master)](https://travis-ci.org/goodpeople/colppy)
1
+ # Colppy [![Build Status](https://travis-ci.org/goodpeople/colppy.svg?branch=master)](https://travis-ci.org/goodpeople/colppy)
2
+
3
+ > **Still unstable version and prone to change here and there ;)**
3
4
 
4
5
  ## Installation
5
6
 
@@ -4,6 +4,7 @@ require 'multi_json'
4
4
 
5
5
  require "colppy/version"
6
6
  require "colppy/digest"
7
+ require "colppy/utils"
7
8
  # CORE
8
9
  require "colppy/core/services"
9
10
  require "colppy/core/gateway"
@@ -11,6 +12,7 @@ require "colppy/core/gateway"
11
12
  require "colppy/resource"
12
13
  require "colppy/resources/customer"
13
14
  require "colppy/resources/company"
15
+ require "colppy/resources/invoice"
14
16
  require "colppy/resources/product"
15
17
  require "colppy/resources/sell_invoice"
16
18
  require "colppy/resources/user"
@@ -29,6 +29,12 @@ module Colppy
29
29
  operacion: "editar_cliente"
30
30
  }
31
31
  },
32
+ inventory: {
33
+ accounts_list: {
34
+ provision: "Inventario",
35
+ operacion: "listar_cuentasAsentables"
36
+ }
37
+ },
32
38
  product: {
33
39
  list: {
34
40
  provision: "Inventario",
@@ -1,6 +1,7 @@
1
1
  module Colppy
2
2
  class Resource
3
- VALID_INVOICE_TYPES = %w(A B C E Z I M X)
3
+ attr_reader :data
4
+ include Utils
4
5
 
5
6
  class << self
6
7
  protected
@@ -28,6 +29,14 @@ module Colppy
28
29
  end
29
30
  end
30
31
 
32
+ def initialize(params)
33
+ @data = rename_params_hash(
34
+ params,
35
+ self.class::ATTRIBUTES_MAPPER,
36
+ self.class::DATA_KEYS_SETTERS
37
+ )
38
+ end
39
+
31
40
  def inspect
32
41
  formatted_attrs = attr_inspect.map do |attr|
33
42
  "#{attr}: #{send(attr).inspect}"
@@ -35,6 +44,20 @@ module Colppy
35
44
  "#<#{self.class.name} #{formatted_attrs.join(", ")}>"
36
45
  end
37
46
 
47
+ def []=(key, value)
48
+ key_sym = key.to_sym
49
+ if PROTECTED_DATA_KEYS.include?(key_sym)
50
+ raise ResourceError.new("You cannot change any of this values: #{PROTECTED_DATA_KEYS.join(", ")} manually")
51
+ end
52
+ if DATA_KEYS_SETTERS.include?[key]
53
+ @data[key_sym] = value
54
+ elsif new_key = ATTRIBUTES_MAPPER[key_sym]
55
+ @data[new_key] = value if DATA_KEYS_SETTERS.include?(new_key)
56
+ else
57
+ raise DataError.new("There is no attribute named :#{key_sym}, or you cannot change it manually this way. Try initializing another #{self.class.name} with that attribute as the argument")
58
+ end
59
+ end
60
+
38
61
  private
39
62
 
40
63
  def attr_inspect
@@ -78,6 +101,5 @@ module Colppy
78
101
  )
79
102
  end
80
103
  end
81
-
82
104
  end
83
105
  end
@@ -11,6 +11,13 @@ module Colppy
11
11
  class Company < Resource
12
12
  attr_reader :id, :name
13
13
 
14
+ ATTRIBUTES_MAPPER = {
15
+ idPlan: :plan_id,
16
+ activa: :active,
17
+ fechaVencimiento: :expiration_date
18
+ }.freeze
19
+ PROTECTED_DATA_KEYS = ATTRIBUTES_MAPPER.values
20
+
14
21
  class << self
15
22
  def all(client)
16
23
  list(client)
@@ -35,9 +42,10 @@ module Colppy
35
42
 
36
43
  def initialize(client: nil, **params)
37
44
  @client = client if client && client.is_a?(Colppy::Client)
38
- @id = params.delete(:IdEmpresa)
39
- @name = params.delete(:razonSocial)
40
- @extras = params
45
+
46
+ @id = params.delete(:IdEmpresa) || params.delete(:id)
47
+ @name = params.delete(:razonSocial) || params.delete(:name)
48
+ super(params)
41
49
  end
42
50
 
43
51
  def customers(params = {})
@@ -49,12 +57,12 @@ module Colppy
49
57
  Customer.list(@client, self, params)
50
58
  end
51
59
  end
52
-
53
60
  def customer_by_id(id)
54
61
  ensure_client_valid!
55
62
 
56
63
  Customer.get(@client, self, id)
57
64
  end
65
+ alias :customer :customer_by_id
58
66
 
59
67
  def products(params = {})
60
68
  ensure_client_valid!
@@ -65,20 +73,17 @@ module Colppy
65
73
  Product.list(@client, self, params)
66
74
  end
67
75
  end
68
- alias :customer :customer_by_id
69
-
70
- def product_by_sku(sku)
76
+ def product_by_code(code)
71
77
  ensure_client_valid!
72
78
 
73
79
  params = {
74
80
  filter: [
75
- { field: "codigo", op: "=", value: sku }
81
+ { field: "codigo", op: "=", value: code }
76
82
  ]
77
83
  }
78
84
  response = Product.list(@client, self, params)
79
85
  response[:results].last
80
86
  end
81
-
82
87
  def product_by_id(id)
83
88
  ensure_client_valid!
84
89
 
@@ -101,7 +106,6 @@ module Colppy
101
106
  SellInvoice.list(@client, self, params)
102
107
  end
103
108
  end
104
-
105
109
  def sell_invoice_by_id(id)
106
110
  ensure_client_valid!
107
111
 
@@ -109,6 +113,33 @@ module Colppy
109
113
  end
110
114
  alias :sell_invoice :sell_invoice_by_id
111
115
 
116
+ def available_accounts
117
+ return @available_accounts unless @available_accounts.nil? || @available_accounts.empty?
118
+
119
+ response = @client.call(
120
+ :inventory,
121
+ :accounts_list,
122
+ params.merge(@client.session_params)
123
+ )
124
+ if response[:success]
125
+ @available_accounts = response[:cuentas].map do |account|
126
+ {
127
+ id: account[:Id],
128
+ account_id: account[:idPlanCuenta],
129
+ full_name: account[:Descripcion],
130
+ name: account[:Descripcion].split(' - ')[1]
131
+ }
132
+ end
133
+ end
134
+ end
135
+ def account_name(account_id)
136
+ return if available_accounts.nil? || available_accounts.empty?
137
+
138
+ if account = available_accounts.detect{ |a| a[:account_id].to_s == account_id.to_s }
139
+ account[:name]
140
+ end
141
+ end
142
+
112
143
  def params
113
144
  { idEmpresa: @id }
114
145
  end
@@ -1,6 +1,36 @@
1
1
  module Colppy
2
2
  class Customer < Resource
3
- attr_reader :id, :name, :data
3
+ attr_reader :id, :name
4
+
5
+ ATTRIBUTES_MAPPER = {
6
+ RazonSocial: :name,
7
+ NombreFantasia: :fantasy_name,
8
+ DirPostal: :address,
9
+ DirPostalCiudad: :address_city,
10
+ DirPostalCodigoPostal: :address_zipcode,
11
+ DirPostalProvincia: :address_state,
12
+ DirPostalPais: :address_country,
13
+ DirFiscal: :legal_address,
14
+ DirFiscalCiudad: :legal_address_city,
15
+ DirFiscalCodigoPostal: :legal_address_zipcode,
16
+ DirFiscalProvincia: :legal_address_state,
17
+ DirFiscalPais: :legal_address_country,
18
+ Telefono: :phone_number,
19
+ Fax: :fax_number,
20
+ Activo: :active,
21
+ idCondicionPago: :payment_condition_id,
22
+ idCondicionIva: :tax_condition_id,
23
+ CUIT: :cuit,
24
+ idTipoPercepcion: :tax_perception_id,
25
+ NroCuenta: :account_number,
26
+ CBU: :cbu,
27
+ Banco: :bank,
28
+ porcentajeIVA: :tax,
29
+ Email: :email,
30
+ Saldo: :balance
31
+ }.freeze
32
+ PROTECTED_DATA_KEYS = [:id, :company_id, :name].freeze
33
+ DATA_KEYS_SETTERS = (ATTRIBUTES_MAPPER.values - PROTECTED_DATA_KEYS).freeze
4
34
 
5
35
  class << self
6
36
  def all(client, company)
@@ -55,15 +85,20 @@ module Colppy
55
85
  }]
56
86
  }
57
87
  end
58
-
59
88
  end
60
89
 
61
90
  def initialize(client: nil, company: nil, **params)
62
91
  @client = client if client && client.is_a?(Colppy::Client)
63
92
  @company = company if company && company.is_a?(Colppy::Company)
64
- @id = params.delete(:idCliente)
65
- @name = params.delete(:RazonSocial)
66
- @data = params
93
+
94
+ @id = params.delete(:idCliente) || params.delete(:id)
95
+ @name = params.delete(:RazonSocial) || params.delete(:name)
96
+ super(params)
97
+ end
98
+ DATA_KEYS_SETTERS.each do |data_key|
99
+ define_method("#{data_key}=") do |value|
100
+ @data[data_key.to_sym] = value
101
+ end
67
102
  end
68
103
 
69
104
  def new?
@@ -96,24 +131,12 @@ module Colppy
96
131
  @name = new_name
97
132
  end
98
133
 
99
- def []=(key, value)
100
- key_sym = key.to_sym
101
- if protected_data_keys.include?(key_sym)
102
- raise ResourceError.new("You cannot change any of this values: #{protected_data_keys.join(", ")} manually")
103
- end
104
- @data[key_sym] = value
105
- end
106
-
107
134
  private
108
135
 
109
136
  def attr_inspect
110
137
  [:id, :name]
111
138
  end
112
139
 
113
- def protected_data_keys
114
- [:idUsuario, :idCliente, :idEmpresa]
115
- end
116
-
117
140
  def operation
118
141
  new? ? :create : :update
119
142
  end
@@ -0,0 +1,287 @@
1
+ module Colppy
2
+ class Invoice < Resource
3
+ VALID_PAYMENT_CONDITIONS = ["Contado", "a 15 Dias", "a 30 Dias", "a 60 Dias"]
4
+ VALID_STATUS_ID = [ "Borrador", "Aprobada", "Anulada", "Cobrada" ]
5
+ VALID_INVOICE_TYPES = %w(A B C E Z I M X)
6
+
7
+ def add_item(params)
8
+ if item = Item.new(params, @company)
9
+ @items << item
10
+ end
11
+ end
12
+ def remove_item(key, value)
13
+ @items.delete_if do |item|
14
+ item.send(key.to_sym).to_s == value.to_s
15
+ end
16
+ end
17
+
18
+ def add_payment(params)
19
+ if payment = Payment.new(params)
20
+ @payments << payment
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def parse_items(new_items)
27
+ return [] if new_items.nil? || new_items.empty?
28
+
29
+ new_items.map do |item_data|
30
+ Item.new(item_data, @company)
31
+ end
32
+ end
33
+
34
+ def parse_payments(new_payments)
35
+ return [] if new_payments.nil? || new_payments.empty?
36
+
37
+ new_payments.map do |payment_data|
38
+ Payment.new(payment_data)
39
+ end
40
+ end
41
+
42
+ def parse_taxes_totals(new_taxes_totals)
43
+ return [] if new_taxes_totals.nil? || new_taxes_totals.empty?
44
+
45
+ new_taxes_totals.map do |tax_total_data|
46
+ TaxTotal.new(tax_total_data)
47
+ end
48
+ end
49
+
50
+ def invoice_status
51
+ type = @data[:idEstadoFactura]
52
+ if type && VALID_INVOICE_STATUS.include?(type)
53
+ type
54
+ else
55
+ raise DataError.new("The value of idEstadoFactura=#{type} is invalid. The value should be any of this ones: #{VALID_INVOICE_STATUS.join(", ")}")
56
+ end
57
+ end
58
+
59
+ def invoice_type
60
+ type = @data[:idTipoFactura]
61
+ if type && VALID_INVOICE_TYPES.include?(type)
62
+ type
63
+ else
64
+ raise DataError.new("The idTipoFactura=#{type} is invalid. The value should be any of this ones: #{VALID_INVOICE_TYPES.join(", ")}")
65
+ end
66
+ end
67
+ end
68
+
69
+ class Invoice::Item
70
+ attr_reader :id, :data
71
+ include Utils
72
+
73
+ ATTRIBUTES_MAPPER = {
74
+ idItem: :product_id,
75
+ minimo: :minimum_stock,
76
+ tipoItem: :item_type,
77
+ codigo: :codigo,
78
+ Descripcion: :name,
79
+ unidadMedida: :measure_unit,
80
+ Cantidad: :quantity, # required
81
+ ImporteUnitario: :unit_price,
82
+ IVA: :tax,
83
+ subtotal: :subtotal,
84
+ idPlanCuenta: :sales_account_id,
85
+ Comentario: :comment
86
+ }.freeze
87
+ PROTECTED_DATA_KEYS = [:product_id].freeze
88
+ DATA_KEYS_SETTERS = (ATTRIBUTES_MAPPER.values - PROTECTED_DATA_KEYS).freeze
89
+
90
+ def initialize(params, company = nil)
91
+ @company = company if company.is_a?(Colppy::Company)
92
+
93
+ @id = params.delete(:id)
94
+ @product = params.delete(:product)
95
+ @data = rename_params_hash(params, ATTRIBUTES_MAPPER, DATA_KEYS_SETTERS)
96
+
97
+ if @product && @product.is_a?(Colppy::Product)
98
+ @data[:product_id] = @product.id
99
+ else
100
+ @product = nil
101
+ end
102
+ end
103
+
104
+ DATA_KEYS_SETTERS.each do |data_key|
105
+ define_method("#{data_key}") do
106
+ @data[data_key.to_sym]
107
+ end
108
+ define_method("#{data_key}=") do |value|
109
+ @data[data_key.to_sym] = value
110
+ end
111
+ end
112
+
113
+ def unhandle_data
114
+ @data[:unhandle] || {}
115
+ end
116
+
117
+ def product
118
+ @product ||= @company.product(@data[:product_id])
119
+ end
120
+
121
+ def product_id=(value)
122
+ @product = nil
123
+ @data[:product_id] = value
124
+ end
125
+ def product_id
126
+ @data[:product_id]
127
+ end
128
+
129
+ def tax
130
+ (@data[:tax] || product.tax || 0).to_f
131
+ end
132
+ def charged
133
+ (unit_price || product.sell_price || 0).to_f
134
+ end
135
+ def quantity
136
+ @data[:quantity] || 0
137
+ end
138
+ def total_charged
139
+ ( charged * quantity ).round(2)
140
+ end
141
+ def minimum_stock
142
+ (@data[:minimum_stock] || product.minimum_stock || 0)
143
+ end
144
+ def item_type
145
+ (@data[:item_type] || product.item_type || "P")
146
+ end
147
+ def code
148
+ (@data[:code] || product.code || "")
149
+ end
150
+ def name
151
+ (@data[:name] || product.name || "")
152
+ end
153
+ def measure_unit
154
+ (@data[:measure_unit] || product.measure_unit || "u")
155
+ end
156
+ def sales_account_id
157
+ (@data[:sales_account_id] || product.sales_account || "")
158
+ end
159
+ def comment
160
+ (@data[:comment] || "#{product.name}, #{product.detail}" || "")
161
+ end
162
+
163
+ def save_parameters
164
+ {
165
+ idItem: product_id.to_s,
166
+ minimo: minimum_stock.to_s,
167
+ tipoItem: item_type,
168
+ codigo: code,
169
+ Descripcion: name,
170
+ ccosto1: unhandle_data[:ccosto1] || "",
171
+ ccosto2: unhandle_data[:ccosto2] || "",
172
+ almacen: unhandle_data[:almacen] || "",
173
+ unidadMedida: measure_unit,
174
+ Cantidad: quantity,
175
+ ImporteUnitario: charged,
176
+ porcDesc: unhandle_data[:porcDesc] || 0,
177
+ IVA: tax.to_s,
178
+ subtotal: total_charged,
179
+ idPlanCuenta: sales_account_id,
180
+ Comentario: comment
181
+ }
182
+ end
183
+
184
+ def inspect; end
185
+ end
186
+ class Invoice::Payment
187
+ include Utils
188
+
189
+ VALID_PAYMENT_TYPES = [
190
+ "Cheque Recibido Común",
191
+ "Cheque Recibido Diferido",
192
+ "Transferencia",
193
+ "Efectivo",
194
+ "Tarjeta de Credito"
195
+ ]
196
+
197
+ ATTRIBUTES_MAPPER = {
198
+ idMedioCobro: :payment_type_id, # required
199
+ idPlanCuenta: :payment_account_id, # required
200
+ Banco: :bank,
201
+ nroCheque: :check_number,
202
+ fechaValidez: :valid_date,
203
+ importe: :amount, # required
204
+ VAD: :vad,
205
+ Conciliado: :conciliated,
206
+ idTabla: :table_id,
207
+ idElemento: :element_id,
208
+ idItem: :item_id
209
+ }.freeze
210
+ DATA_KEYS_SETTERS = ATTRIBUTES_MAPPER.values
211
+
212
+ def initialize(params)
213
+ @data = rename_params_hash(params, ATTRIBUTES_MAPPER, DATA_KEYS_SETTERS)
214
+ end
215
+ DATA_KEYS_SETTERS.each do |data_key|
216
+ define_method("#{data_key}=") do |value|
217
+ @data[data_key.to_sym] = value
218
+ end
219
+ end
220
+
221
+ def amount
222
+ @data[:amount] || 0.0
223
+ end
224
+
225
+ def save_parameters
226
+ {
227
+ idMedioCobro: @data[:payment_type_id],
228
+ idPlanCuenta: @data[:payment_account_id],
229
+ Banco: @data[:bank] || "",
230
+ nroCheque: @data[:check_number] || "",
231
+ fechaValidez: @data[:valid_date] || "",
232
+ importe: amount,
233
+ VAD: @data[:vad] || "S",
234
+ Conciliado: @data[:conciliated] || "",
235
+ idTabla: @data[:table_id] || 0,
236
+ idElemento: @data[:element_id] || 0,
237
+ idItem: @data[:item_id] || 0
238
+ }
239
+ end
240
+
241
+ def inspect; end
242
+ end
243
+ class Invoice::TaxTotal
244
+ include Utils
245
+
246
+ def self.add_to_tax_breakdown(tax, amount, taxed_amount, breakdown = nil)
247
+ breakdown = breakdown || { tax_factor: tax, tax_amount: 0.0, taxed_amount: 0.0 }
248
+ breakdown[:tax_amount] += amount
249
+ breakdown[:taxed_amount] += taxed_amount
250
+ breakdown
251
+ end
252
+
253
+ ATTRIBUTES_MAPPER = {
254
+ alicuotaIva: :tax_factor,
255
+ importeIva: :tax_amount,
256
+ baseImpIva: :taxed_amount
257
+ }.freeze
258
+ DATA_KEYS_SETTERS = ATTRIBUTES_MAPPER.values
259
+
260
+ def initialize(params)
261
+ @data = rename_params_hash(params, ATTRIBUTES_MAPPER, DATA_KEYS_SETTERS)
262
+ end
263
+ DATA_KEYS_SETTERS.each do |data_key|
264
+ define_method("#{data_key}=") do |value|
265
+ @data[data_key.to_sym] = value
266
+ end
267
+ end
268
+
269
+ def tax_name
270
+ if @data[:tax_factor] % 1 == 0
271
+ @data[:tax_factor].to_i.to_s
272
+ else
273
+ @data[:tax_factor].to_s
274
+ end
275
+ end
276
+
277
+ def save_parameters
278
+ {
279
+ alicuotaIva: tax_name,
280
+ importeIva: @data[:tax_amount],
281
+ baseImpIva: @data[:taxed_amount]
282
+ }
283
+ end
284
+
285
+ def inspect; end
286
+ end
287
+ end
@@ -1,6 +1,26 @@
1
1
  module Colppy
2
2
  class Product < Resource
3
- attr_reader :id, :name, :sku, :data
3
+ attr_reader :id, :name, :code
4
+
5
+ ATTRIBUTES_MAPPER = {
6
+ idItem: :id,
7
+ idEmpresa: :company_id,
8
+ codigo: :code,
9
+ descripcion: :name,
10
+ detalle: :detail,
11
+ precioVenta: :sell_price,
12
+ ultimoPrecioCompra: :last_purchase_price,
13
+ ctaInventario: :inventory_account,
14
+ ctaCostoVentas: :sales_costs_account,
15
+ ctaIngresoVentas: :sales_account,
16
+ iva: :tax,
17
+ tipoItem: :item_type,
18
+ unidadMedida: :measure_unit,
19
+ minimo: :minimum_stock,
20
+ disponibilidad: :availability
21
+ }.freeze
22
+ PROTECTED_DATA_KEYS = [:id, :company_id, :name, :code].freeze
23
+ DATA_KEYS_SETTERS = (ATTRIBUTES_MAPPER.values - PROTECTED_DATA_KEYS).freeze
4
24
 
5
25
  class << self
6
26
  def all(client, company)
@@ -48,14 +68,36 @@ module Colppy
48
68
  def initialize(client: nil, company: nil, **params)
49
69
  @client = client if client && client.is_a?(Colppy::Client)
50
70
  @company = company if company && company.is_a?(Colppy::Company)
51
- @id = params.delete(:idItem)
52
- @name = params.delete(:descripcion)
53
- @sku = params.delete(:codigo)
54
- @data = params
71
+
72
+ @id = params.delete(:idItem) || params.delete(:id)
73
+ @name = params.delete(:descripcion) || params.delete(:name)
74
+ @code = params.delete(:codigo) || params.delete(:code)
75
+
76
+ super(params)
77
+ end
78
+ DATA_KEYS_SETTERS.each do |data_key|
79
+ define_method("#{data_key}") do
80
+ @data[data_key.to_sym]
81
+ end
82
+ define_method("#{data_key}=") do |value|
83
+ @data[data_key.to_sym] = value
84
+ end
55
85
  end
56
86
 
57
87
  def new?
58
- id.nil? || id.empty?
88
+ @id.nil? || @id.empty?
89
+ end
90
+
91
+ def exist?
92
+ !new?
93
+ end
94
+
95
+ def name=(value)
96
+ @name = value
97
+ end
98
+
99
+ def code=(value)
100
+ @code = value
59
101
  end
60
102
 
61
103
  def save
@@ -78,45 +120,14 @@ module Colppy
78
120
  end
79
121
  end
80
122
 
81
- def name=(new_name)
82
- @name = new_name
83
- end
84
-
85
- def sku=(new_sku)
86
- @sku = new_sku
87
- end
88
-
89
- def []=(key, value)
90
- key_sym = key.to_sym
91
- if protected_data_keys.include?(key_sym)
92
- raise ResourceError.new("You cannot change any of this values: #{protected_data_keys.join(", ")} manually")
93
- end
94
- @data[key_sym] = value
95
- end
96
-
97
- def params_for_invoice
98
- {
99
- idItem: id || "",
100
- minimo: @data[:minimo] || "0",
101
- codigo: sku || "",
102
- tipoItem: @data[:tipoItem] || "P",
103
- unidadMedida: @data[:unidadMedida] || "u",
104
- Descripcion: name || "",
105
- ImporteUnitario: @data[:precioVenta],
106
- IVA: @data[:iva] || "21",
107
- Comentario: @data[:detalle] || "",
108
- idPlanCuenta: @data[:ctaIngresoVentas]
109
- }
123
+ def sales_account
124
+ check_mandatory_account!(:sales_account)
110
125
  end
111
126
 
112
127
  private
113
128
 
114
129
  def attr_inspect
115
- [:id, :sku, :name]
116
- end
117
-
118
- def protected_data_keys
119
- [:idItem, :idEmpresa]
130
+ [:id, :code, :name]
120
131
  end
121
132
 
122
133
  def operation
@@ -132,21 +143,34 @@ module Colppy
132
143
 
133
144
  def params
134
145
  {
135
- idItem: id || "",
146
+ idItem: @id || "",
136
147
  idEmpresa: @company.id,
137
- codigo: sku || "",
138
- descripcion: name || "",
139
- detalle: @data[:detalle] || "",
140
- precioVenta: @data[:precioVenta] || "0",
141
- ultimoPrecioCompra: @data[:minimo] || "0",
142
- ctaInventario: @data[:ctaInventario] || "",
143
- ctaCostoVentas: @data[:ctaCostoVentas] || "",
144
- ctaIngresoVentas: @data[:ctaIngresoVentas] || "",
145
- iva: @data[:iva] || "21",
146
- tipoItem: @data[:tipoItem] || "P",
147
- unidadMedida: @data[:unidadMedida] || "u",
148
- minimo: @data[:minimo] || "0"
148
+ codigo: @code || "",
149
+ descripcion: @name || "",
150
+ detalle: @data[:detail] || "",
151
+ precioVenta: @data[:sell_price] || "0",
152
+ ultimoPrecioCompra: @data[:last_purchase_price] || "",
153
+ ctaInventario: check_mandatory_account!(:inventory_account),
154
+ ctaCostoVentas: check_mandatory_account!(:sales_costs_account),
155
+ ctaIngresoVentas: check_mandatory_account!(:sales_account),
156
+ iva: @data[:tax] || "21",
157
+ tipoItem: @data[:item_type] || "P",
158
+ unidadMedida: @data[:measure_unit] || "u",
159
+ minimo: @data[:minimum_stock] || "0"
149
160
  }
150
161
  end
162
+
163
+ def check_mandatory_account!(account)
164
+ if account_name_or_id = @data[account]
165
+ real_account_name = @company.account_name(account_name_or_id)
166
+ if real_account_name
167
+ real_account_name
168
+ else
169
+ account_name_or_id
170
+ end
171
+ else
172
+ raise DataError.new("The :#{account} is required for the product. You should specify one via product.#{account} = '', or product[:#{account}] = '', or when you initialize it. You should check the available and valid accounts in Colppy")
173
+ end
174
+ end
151
175
  end
152
176
  end
@@ -1,9 +1,41 @@
1
1
  module Colppy
2
- class SellInvoice < Resource
3
- attr_reader :id, :number, :cae, :items, :data, :url
4
- attr_accessor :payments
2
+ class SellInvoice < Invoice
3
+ attr_reader :id, :number, :cae, :url, :items
5
4
 
6
5
  VALID_RECEIPT_TYPES = %w(4 6 8 NCV)
6
+ ATTRIBUTES_MAPPER = {
7
+ descripcion: :description,
8
+ fechaFactura: :invoice_date,
9
+ idCondicionPago: :payment_condition_id, # validate
10
+ fechaPago: :payment_date,
11
+ idEmpresa: :company_id,
12
+ idCliente: :customer_id,
13
+ idEstadoAnterior: :previous_status_id, # validate
14
+ idEstadoFactura: :status_id, # validate
15
+ idMoneda: :currency_id,
16
+ idTipoComprobante: :receipt_type_id, # validate
17
+ idTipoFactura: :invoice_type_id, # validate
18
+ idUsuario: :user_id,
19
+ labelfe: :electronic_bill,
20
+ netoGravado: :total_taxed,
21
+ netoNoGravado: :total_nontaxed,
22
+ nroFactura1: :invoice_number1,
23
+ nroFactura2: :invoice_number2,
24
+ percepcionIIBB: :iibb_perception,
25
+ percepcionIVA: :tax_perception,
26
+ tipoFactura: :invoice_type, # validate
27
+ totalFactura: :total,
28
+ totalIVA: :total_taxes,
29
+ totalpagadofactura: :total_payed,
30
+ valorCambio: :exchange_rate,
31
+ nroRepeticion: :repetition_number,
32
+ periodoRep: :repetition_period,
33
+ nroVencimiento: :expiration_number,
34
+ tipoVencimiento: :expiration_type,
35
+ fechaFin: :end_date
36
+ }.freeze
37
+ PROTECTED_DATA_KEYS = [:id, :company_id, :customer_id, :number, :cae].freeze
38
+ DATA_KEYS_SETTERS = (ATTRIBUTES_MAPPER.values - PROTECTED_DATA_KEYS).freeze
7
39
 
8
40
  class << self
9
41
  def all(client, company)
@@ -61,13 +93,12 @@ module Colppy
61
93
 
62
94
  def extended_response(response, client, company)
63
95
  [ response[:infofactura],
64
- items: response[:itemsFactura],
96
+ itemsFactura: response[:itemsFactura],
65
97
  url: response[:UrlFacturaPdf],
66
98
  client: client,
67
99
  company: company
68
100
  ].inject(&:merge)
69
101
  end
70
-
71
102
  end
72
103
 
73
104
  def initialize(client: nil, company: nil, customer: nil, **params)
@@ -75,15 +106,20 @@ module Colppy
75
106
  @company = company if company && company.is_a?(Colppy::Company)
76
107
  @customer = customer if customer && customer.is_a?(Colppy::Customer)
77
108
 
78
- @id = params.delete(:idFactura)
79
- @number = params.delete(:nroFactura)
109
+ @id = params.delete(:idFactura) || params.delete(:id)
110
+ @number = params.delete(:nroFactura) || params.delete(:number)
80
111
  @cae = params.delete(:cae)
81
112
 
82
- @items = parse_items(params.delete(:items))
83
- @payments = params.delete(:ItemsCobro) || []
113
+ @items = parse_items(params.delete(:itemsFactura))
114
+ @payments = parse_payments(params.delete(:ItemsCobro))
115
+ @taxes_totals = parse_taxes_totals(params.delete(:totalesiva))
84
116
 
85
- @data = params
86
- @url = build_pdf_url
117
+ super(params)
118
+ end
119
+ DATA_KEYS_SETTERS.each do |data_key|
120
+ define_method("#{data_key}=") do |value|
121
+ @data[data_key.to_sym] = value
122
+ end
87
123
  end
88
124
 
89
125
  def new?
@@ -94,250 +130,199 @@ module Colppy
94
130
  cae.nil? || cae.empty?
95
131
  end
96
132
 
133
+ def url
134
+ return if @data[:url].nil? || @data[:url].empty?
135
+
136
+ "#{@data[:url]}?usuario=#{@client.username}&claveSesion=#{@client.session_key}&idEmpresa=#{company_id}&idFactura=#{id}&idCliente=#{customer_id}"
137
+ end
138
+
139
+ def total_charged
140
+ @items.map(&:total_charged).inject(0,:+)
141
+ end
142
+
143
+ def []=(key, value)
144
+ ensure_editability!
145
+
146
+ super
147
+ end
148
+
149
+ def customer=(new_customer)
150
+ @customer = new_customer if new_customer.is_a?(Colppy::Customer)
151
+ end
152
+
97
153
  def save
98
- ensure_editability! && ensure_client_valid! && ensure_company_valid! && ensure_customer_valid!
154
+ ensure_editability!
155
+ ensure_client_valid!
156
+ ensure_payment_setup!
99
157
 
100
158
  response = @client.call(
101
159
  :sell_invoice,
102
160
  :create,
103
161
  save_parameters
104
162
  )
105
- binding.pry
106
163
  if response[:success]
107
- @id = response[:data][:idFactura]
164
+ @cae = response[:cae]
165
+ @id = response[:idfactura]
166
+ @number = response[:nroFactura]
167
+ @data[:invoice_date] = response[:fechaFactura]
168
+ @data[:url] = response[:UrlFacturaPdf]
108
169
  self
109
170
  else
110
171
  false
111
172
  end
112
173
  end
113
174
 
114
- def []=(key, value)
115
- ensure_editability!
116
-
117
- key_sym = key.to_sym
118
- if protected_data_keys.include?(key_sym)
119
- raise ResourceError.new("You cannot change any of this values: #{protected_data_keys.join(", ")} manually")
120
- end
121
- @data[key_sym] = value
122
- end
123
-
124
- def items=(new_items)
125
- @charged_details = nil
126
- @items = parse_items(new_items)
127
- end
128
-
129
175
  private
130
176
 
131
177
  def attr_inspect
132
178
  [:id, :number, :cae]
133
179
  end
134
180
 
135
- def protected_data_keys
136
- [:idFactura, :idEmpresa, :nroFactura]
137
- end
181
+ def customer_id
182
+ return @customer.id if @customer
138
183
 
139
- def parse_items(new_items)
140
- return [] if new_items.nil? || new_items.empty?
184
+ @data[:customer_id] || ""
185
+ end
186
+ def company_id
187
+ return @company.id if @company
141
188
 
142
- new_items.map do |item|
143
- item.tap do |hash|
144
- if item[:idItem] && (item[:product].nil? || item[:product].empty?)
145
- item[:product] = @company.product(item[:idItem])
146
- end
147
- end
189
+ @data[:company_id] || ""
190
+ end
191
+ def payment_condition_id
192
+ validate_type!(:payment_condition_id, VALID_PAYMENT_CONDITIONS)
193
+ end
194
+ def previous_status_id
195
+ if status = @data[:previous_status_id]
196
+ validate_type!(status, VALID_STATUS_ID)
197
+ else
198
+ ""
148
199
  end
149
200
  end
150
-
151
- def build_pdf_url
152
- return if data[:url].nil? || data[:url].empty?
153
-
154
- data[:url] = "#{data[:url]}?usuario=#{@client.username}&claveSesion=#{@client.session_key}&idEmpresa=#{@company.id}&idFactura=#{id}&idCliente=#{data[:idCliente]}"
201
+ def status_id
202
+ validate_type!(:status_id, VALID_STATUS_ID)
203
+ end
204
+ def receipt_type_id
205
+ validate_type!(:receipt_type_id, VALID_RECEIPT_TYPES)
206
+ end
207
+ def invoice_type_id
208
+ validate_type!(:invoice_type_id, VALID_INVOICE_TYPES)
209
+ end
210
+ def invoice_type
211
+ if receipt_type_id == "8"
212
+ "Contado"
213
+ end
214
+ end
215
+ def validate_type!(data_key, valid_types)
216
+ type = @data[data_key]
217
+ if type && valid_types.include?(type)
218
+ type
219
+ else
220
+ raise DataError.new("The #{data_key} is invalid. The value should be any of this ones: #{valid_types.join(", ")}")
221
+ ""
222
+ end
155
223
  end
156
224
 
157
225
  def save_parameters
226
+ charged_amounts = calculate_charged_amounts
158
227
  [
159
228
  @client.session_params,
160
- general_params,
161
- invoice_payments_params,
162
- itemsFactura: invoice_items_params,
163
- totalesiva: total_taxes_params
229
+ general_params(charged_amounts),
230
+ itemsFactura: @items.map(&:save_parameters),
231
+ ItemsCobro: @payments.map(&:save_parameters),
232
+ totalesiva: @taxes_totals.map(&:save_parameters)
164
233
  ].inject(&:merge)
165
234
  end
166
235
 
167
- def general_params
236
+ def general_params(charged_amounts)
168
237
  {
169
- idCliente: @customer.id || @data[:idCliente] || "",
170
- idEmpresa: @company.id,
238
+ idCliente: customer_id,
239
+ idEmpresa: company_id,
240
+ idUsuario: @client.username,
171
241
  descripcion: @data[:descripcion] || "",
172
- fechaFactura: valid_date(@data[:fechaFactura]),
173
- idCondicionPago: @data[:idCondicionPago] || "Contado",
174
- fechaPago: valid_date(@data[:fechaPago]),
175
- idEstadoAnterior: @data[:idEstadoAnterior] || "",
176
- idEstadoFactura: @data[:idEstadoFactura] || "Cobrada",
177
- idFactura: id || "",
178
- idMedioCobro: @data[:idMedioCobro] || "Efectivo",
179
- idMoneda: @data[:idMoneda] || "1",
180
- idTipoComprobante: receipt_type,
181
- idTipoFactura: invoice_type,
182
- netoGravado: (charged_details[:total_taxed] || 0.00).to_s,
183
- netoNoGravado: (charged_details[:total_non_taxed] || 0.00).to_s,
184
- nroFactura1: @data[:nroFactura1] || "0001",
185
- nroFactura2: @data[:nroFactura2] || "",
186
- percepcionIVA: (@data[:percepcionIVA] || 0.00).to_s,
187
- percepcionIIBB: (@data[:percepcionIIBB] || 0.00).to_s,
188
- totalFactura: (charged_details[:total] || 0.00).to_s,
189
- totalIVA: (charged_details[:tax_total] || 0.00).to_s,
190
- valorCambio: @data[:valorCambio] || "1",
242
+ fechaFactura: valid_date(@data[:invoice_date]),
243
+ idCondicionPago: payment_condition_id,
244
+ fechaPago: valid_date(@data[:payment_date]),
245
+ idEstadoAnterior: previous_status_id,
246
+ idEstadoFactura: status_id,
247
+ idFactura: @id || "",
248
+ idMoneda: @data[:currency_id] || "1",
249
+ idTipoComprobante: receipt_type_id,
250
+ idTipoFactura: invoice_type_id,
251
+ netoGravado: charged_amounts[:total_taxed] || 0.00,
252
+ netoNoGravado: charged_amounts[:total_non_taxed] || 0.00,
253
+ nroFactura1: @data[:invoice_number1] || "0001",
254
+ nroFactura2: @data[:invoice_number2] || "00000000",
255
+ percepcionIVA: @data[:tax_perception] || 0.00,
256
+ percepcionIIBB: @data[:iibb_perception] || 0.00,
257
+ totalFactura: charged_amounts[:total] || 0.00,
258
+ totalIVA: charged_amounts[:tax_total] || 0.00,
259
+ valorCambio: @data[:exchange_rate] || "1",
260
+ nroRepeticion: @data[:repetition_number] || "1",
261
+ periodoRep: @data[:repetition_period] || "1",
262
+ nroVencimiento: @data[:expiration_number] || "0",
263
+ tipoVencimiento: @data[:expiration_type] || "1",
264
+ fechaFin: @data[:end_date] || ""
191
265
  }.tap do |params|
192
- params[:labelfe] = "Factura Electrónica" if @data[:labelfe]
193
- end
194
- end
195
-
196
- def invoice_items_params
197
- items.map do |item|
198
- if item[:product] && item[:product].is_a?(Colppy::Product)
199
- product = item[:product]
200
- [
201
- product.params_for_invoice,
202
- item_params(item, false)
203
- ].inject(&:merge)
204
- elsif item.is_a?(Hash)
205
- item_params(item)
206
- end
207
- end
208
- end
209
-
210
- def item_params(item, fill_empty = true)
211
- {
212
- Cantidad: item[:Cantidad],
213
- porcDesc: item[:porcDesc] || "0.00"
214
- }.tap do |params|
215
- if value = item[:ImporteUnitario] || fill_empty
216
- params[:ImporteUnitario] = value || ""
217
- end
218
- if value = item[:idPlanCuenta] || fill_empty
219
- params[:idPlanCuenta] = value || ""
220
- end
221
- if value = item[:IVA] || fill_empty
222
- params[:IVA] = value || "21"
223
- end
224
- if value = item[:Comentario] || fill_empty
225
- params[:Comentario] = value || ""
266
+ params[:tipoFactura] = invoice_type if invoice_type
267
+ if status_id == "Cobrada"
268
+ params[:totalpagadofactura] = @payments.map(&:amount).inject(0,:+)
226
269
  end
270
+ params[:labelfe] = "Factura Electrónica" if @data[:electronic_bill]
227
271
  end
228
272
  end
229
273
 
230
- def invoice_payments_params
231
- return {} unless payments
232
- payment_items = payments.map do |payment|
233
- {
234
- idMedioCobro: payment[:idMedioCobro] || "Efectivo",
235
- idPlanCuenta: payment[:idPlanCuenta] || "Caja en pesos",
236
- Banco: payment[:Banco] || "",
237
- nroCheque: payment[:nroCheque] || "",
238
- fechaValidez: payment[:fechaValidez] || "",
239
- importe: payment[:importe] || "0",
240
- VAD: payment[:VAD] || "S"
241
- }
242
- end
243
- { ItemsCobro: payment_items }
244
- end
245
-
246
- def total_taxes_params
247
- charged_details[:tax_details].map do |tax, values|
248
- {
249
- alicuotaIva: tax.to_s,
250
- baseImpIva: values[:total_taxed].to_s,
251
- importeIva: values[:tax_total].to_s
252
- }
253
- end
254
- end
255
-
256
- def charged_details
257
- return @charged_details unless @charged_details.nil? || @charged_details.empty?
258
-
259
- if items.nil? || items.empty?
260
- raise DataError.new("In order to save an SellInvoice you should at least have one item in it")
274
+ def calculate_charged_amounts
275
+ if @items.nil? || @items.empty?
276
+ raise DataError.new("In order to save an SellInvoice you should at least have one item in it. Add one with .add_item()")
261
277
  else
262
- default_object = {
263
- total_taxed: 0.00,
264
- total_nontaxed: 0.00,
265
- total: 0.00,
266
- tax_total: 0.00,
267
- tax_details:{}
268
- }
269
- @charged_details = items.each_with_object(default_object) do |item, result|
270
- product_data = (item[:product] && item[:product].data) || {}
271
- iva = (item[:IVA] || product_data[:iva] || 0).to_f
272
- charged = (item[:ImporteUnitario] || product_data[:precioVenta] || 0).to_f
273
- quantity = (item[:Cantidad] || 0).to_i
274
- total = ( charged * quantity ).round(2)
275
- if iva > 0
276
- tax = (( total * iva ) / 100).round(2)
277
- result[:tax_total] += tax
278
- result[:total_taxed] += total - tax
279
- result[:tax_details][iva] = {
280
- tax_total: result[:tax_total],
281
- total_taxed: result[:total_taxed]
282
- }
278
+ charged_details = @items.each_with_object(base_charged_amounts) do |item, result|
279
+ tax = item.tax
280
+ total = item.total_charged
281
+ if tax > 0
282
+ dividend = tax.to_f
283
+ divisor = (100.0 + dividend).to_f
284
+
285
+ item_tax_amount = (( total * dividend ) / divisor).round(2)
286
+ item_taxed_amount = (total - item_tax_amount).round(2)
287
+ result[:tax_total] += item_tax_amount
288
+ result[:total_taxed] += item_taxed_amount
289
+ tax_breakdown_data = TaxTotal.add_to_tax_breakdown(
290
+ tax,
291
+ item_tax_amount,
292
+ item_taxed_amount,
293
+ result[:tax_breakdown][tax]
294
+ )
295
+ result[:tax_breakdown][tax] = tax_breakdown_data
283
296
  else
284
297
  result[:total_nontaxed] += total
285
298
  end
286
299
  result[:total] += total
287
300
  end
301
+ @taxes_totals = parse_taxes_totals(charged_details[:tax_breakdown].values)
302
+ charged_details
288
303
  end
289
304
  end
290
305
 
306
+ def base_charged_amounts
307
+ {
308
+ total_taxed: 0.00,
309
+ total_nontaxed: 0.00,
310
+ total: 0.00,
311
+ tax_total: 0.00,
312
+ tax_breakdown: {}
313
+ }
314
+ end
315
+
291
316
  def ensure_editability!
292
317
  unless editable?
293
318
  raise ResourceError.new("You cannot change any value of this invoice, because it's already processed by AFIP. You should create a new one instead")
294
319
  end
295
320
  end
296
321
 
297
- def invoice_status
298
- type = @data[:idEstadoFactura]
299
- if type && VALID_INVOICE_STATUS.include?(type)
300
- type
301
- else
302
- raise DataError.new("The value of idEstadoFactura:#{type} is invalid. The value should be any of this ones: #{VALID_INVOICE_STATUS.join(", ")}")
303
- end
304
- end
305
-
306
- def invoice_type
307
- type = @data[:idTipoFactura]
308
- if type && VALID_INVOICE_TYPES.include?(type)
309
- type
310
- else
311
- raise DataError.new("The idTipoFactura:#{type} is invalid. The value should be any of this ones: #{VALID_INVOICE_TYPES.join(", ")}")
312
- end
313
- end
314
-
315
- def receipt_type
316
- type = @data[:idTipoComprobante]
317
- if type && VALID_RECEIPT_TYPES.include?(type)
318
- type
319
- else
320
- raise DataError.new("The idTipoComprobante:#{type} is invalid. The value should be any of this ones: #{VALID_RECEIPT_TYPES.join(", ")}")
321
- ""
322
+ def ensure_payment_setup!
323
+ if status_id == "Cobrada" && (@payments.nil? || @payments.empty?)
324
+ raise ResourceError.new("You cannot save this invoice, because it's doesn't have any payment associated to it and the status is #{@data[:status_id]}. Add one calling .add_payment({})")
322
325
  end
323
326
  end
324
327
  end
325
328
  end
326
-
327
- # "totalesiva":[
328
- # {
329
- # alicuotaIva: "0",
330
- # baseImpIva: "0.00",
331
- # importeIva: "0.00"
332
- # },
333
- # {
334
- # alicuotaIva: "21",
335
- # baseImpIva: "101.65",
336
- # importeIva: "21.35"
337
- # },
338
- # {
339
- # alicuotaIva: "27",
340
- # baseImpIva: "0.00",
341
- # importeIva: "0.00"
342
- # }
343
- # ]
@@ -0,0 +1,21 @@
1
+ module Colppy
2
+ module Utils
3
+ module_function
4
+
5
+ ATTRIBUTES_MAPPER = {}
6
+ DATA_KEYS_SETTERS = []
7
+
8
+ def rename_params_hash(params, mapper, setter)
9
+ params.each_with_object({}) do |(key, value), hash|
10
+ if new_key = mapper[key]
11
+ hash[new_key] = value
12
+ elsif setter.include?(key)
13
+ hash[key] = value
14
+ else
15
+ hash[:unhandle] ||= {}
16
+ hash[:unhandle][key] = value
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,3 +1,3 @@
1
1
  module Colppy
2
- VERSION = "0.1.0".freeze
2
+ VERSION = "0.2.0".freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: colppy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Agustin Cavilliotti
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-09-01 00:00:00.000000000 Z
11
+ date: 2016-09-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -143,9 +143,11 @@ files:
143
143
  - lib/colppy/resource.rb
144
144
  - lib/colppy/resources/company.rb
145
145
  - lib/colppy/resources/customer.rb
146
+ - lib/colppy/resources/invoice.rb
146
147
  - lib/colppy/resources/product.rb
147
148
  - lib/colppy/resources/sell_invoice.rb
148
149
  - lib/colppy/resources/user.rb
150
+ - lib/colppy/utils.rb
149
151
  - lib/colppy/version.rb
150
152
  homepage: https://github.com/goodpeople/colppy
151
153
  licenses: