colppy 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: