Class: Sale

Inherits:
Ekylibre::Record::Base show all
Includes:
Attachable, Customizable
Defined in:
app/models/sale.rb

Overview

Informations

License

Ekylibre - Simple agricultural ERP Copyright (C) 2008-2009 Brice Texier, Thibaud Merigon Copyright (C) 2010-2012 Brice Texier Copyright (C) 2012-2016 Brice Texier, David Joulin

This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License along with this program. If not, see www.gnu.org/licenses.

Table: sales

accounted_at        :datetime
address_id          :integer
affair_id           :integer
amount              :decimal(19, 4)   default(0.0), not null
annotation          :text
client_id           :integer          not null
conclusion          :text
confirmed_at        :datetime
created_at          :datetime         not null
creator_id          :integer
credit              :boolean          default(FALSE), not null
credited_sale_id    :integer
currency            :string           not null
custom_fields       :jsonb
delivery_address_id :integer
description         :text
downpayment_amount  :decimal(19, 4)   default(0.0), not null
expiration_delay    :string
expired_at          :datetime
function_title      :string
has_downpayment     :boolean          default(FALSE), not null
id                  :integer          not null, primary key
initial_number      :string
introduction        :text
invoice_address_id  :integer
invoiced_at         :datetime
journal_entry_id    :integer
letter_format       :boolean          default(TRUE), not null
lock_version        :integer          default(0), not null
nature_id           :integer
number              :string           not null
payment_at          :datetime
payment_delay       :string           not null
pretax_amount       :decimal(19, 4)   default(0.0), not null
reference_number    :string
responsible_id      :integer
state               :string           not null
subject             :string
transporter_id      :integer
updated_at          :datetime         not null
updater_id          :integer

Instance Method Summary collapse

Methods included from Customizable

#custom_value, #set_custom_value, #validate_custom_fields

Methods inherited from Ekylibre::Record::Base

#already_updated?, attr_readonly_with_conditions, #check_if_destroyable?, #check_if_updateable?, columns_definition, complex_scopes, customizable?, #customizable?, #customized?, #destroyable?, #editable?, has_picture, #human_attribute_name, human_attribute_name_with_id, nomenclature_reflections, #old_record, #others, refers_to, scope_with_registration, simple_scopes, #updateable?

Instance Method Details

#build_creditObject

Build a new sale with new items ready for correction and save


422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
# File 'app/models/sale.rb', line 422

def build_credit
  attrs = [:affair, :client, :address, :responsible, :nature, :currency, :invoice_address, :transporter].inject({}) do |hash, attribute|
    hash[attribute] = send(attribute) unless send(attribute).nil?
    hash
  end
  attrs[:invoiced_at] = Time.zone.now
  attrs[:credit] = true
  attrs[:credited_sale] = self
  sale_credit = Sale.new(attrs)
  items.each do |item|
    attrs = [:account, :currency, :variant, :unit_pretax_amount, :unit_amount, :reduction_percentage, :tax].inject({}) do |hash, attribute|
      hash[attribute] = item.send(attribute) unless item.send(attribute).nil?
      hash
    end
    attrs[:credited_quantity] = item.creditable_quantity
    attrs[:credited_item] = item
    if attrs[:credited_quantity] > 0
      sale_credit_item = sale_credit.items.build(attrs)
      sale_credit_item.valid?
    end
  end
  # sale_credit.valid?
  sale_credit
end

#can_generate_parcel?Boolean

Check if sale can generate parcel from all the items of the sale

Returns:

  • (Boolean)

270
271
272
# File 'app/models/sale.rb', line 270

def can_generate_parcel?
  items.any? && delivery_address && (order? || invoice?)
end

#cancellable?Boolean

Returns true if sale is cancellable as an invoice

Returns:

  • (Boolean)

417
418
419
# File 'app/models/sale.rb', line 417

def cancellable?
  !credit? && invoice? && amount + credits.sum(:amount) > 0
end

#confirm(confirmed_at = Time.zone.now) ⇒ Object

Confirm the sale order. This permits to define parcels and assert validity of sale


282
283
284
285
286
# File 'app/models/sale.rb', line 282

def confirm(confirmed_at = Time.zone.now)
  return false unless can_confirm?
  update_column(:confirmed_at, confirmed_at || Time.zone.now)
  super
end

#correctObject

Remove all bad dependencies and return at draft state with no parcels


275
276
277
278
279
# File 'app/models/sale.rb', line 275

def correct
  return false unless can_correct?
  parcels.clear
  super
end

#deal_amountObject

Gives the amount to use for affair bookkeeping


221
222
223
# File 'app/models/sale.rb', line 221

def deal_amount
  ((aborted? || refused?) ? 0 : credit? ? -amount : amount)
end

#deal_taxes(mode = :debit) ⇒ Object

Globalizes taxes into an array of hash


226
227
228
229
230
231
232
233
234
235
236
# File 'app/models/sale.rb', line 226

def deal_taxes(mode = :debit)
  return [] if deal_mode_amount(mode).zero?
  taxes = {}
  coeff = (credit? ? -1 : 1).to_d
  # coeff *= (self.send("deal_#{mode}?") ? 1 : -1)
  for item in items
    taxes[item.tax_id] ||= { amount: 0.0.to_d, tax: item.tax }
    taxes[item.tax_id][:amount] += coeff * item.amount
  end
  taxes.values
end

#dealt_atObject

Gives the date to use for affair bookkeeping


216
217
218
# File 'app/models/sale.rb', line 216

def dealt_at
  (invoice? ? invoiced_at : self.created_at)
end

#deliverable?Boolean

Returns true if there is some products to deliver

Returns:

  • (Boolean)

351
352
353
354
355
# File 'app/models/sale.rb', line 351

def deliverable?
  # not self.undelivered(:quantity).zero? and (self.invoice? or self.order?)
  # !self.undelivered_items.count.zero? and (self.invoice? or self.order?)
  true
end

#duplicatable?Boolean

Returns:

  • (Boolean)

311
312
313
# File 'app/models/sale.rb', line 311

def duplicatable?
  !credit
end

#duplicate(attributes = {}) ⇒ Object

Duplicates a sale in estimate state with its items and its active subscriptions

Raises:

  • (StandardError)

317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# File 'app/models/sale.rb', line 317

def duplicate(attributes = {})
  raise StandardError, 'Uncancelable sale' unless duplicatable?
  hash = [
    :client_id, :nature_id, :letter_format, :annotation, :subject,
    :function_title, :introduction, :conclusion, :description
  ].each_with_object({}) do |field, h|
    h[field] = send(field)
  end
  # Items
  items_attributes = {}
  items.order(:position).each_with_index do |item, index|
    attrs = [
      :variant_id, :quantity, :amount, :label, :pretax_amount, :annotation,
      :reduction_percentage, :tax_id, :unit_amount, :unit_pretax_amount
    ].each_with_object({}) do |field, h|
      h[field] = item.send(field)
    end
    # Subscription
    subscription = item.subscription
    if subscription
      attrs[:subscription_attributes] = subscription.following_attributes
    end
    items_attributes[index.to_s] = attrs
  end
  hash[:items_attributes] = items_attributes
  self.class.create!(hash.with_indifferent_access.deep_merge(attributes))
end

#has_content?Boolean

Test if there is some items in the sale.

Returns:

  • (Boolean)

259
260
261
# File 'app/models/sale.rb', line 259

def has_content?
  items.any?
end

#invoice(invoiced_at = Time.zone.now) ⇒ Object

Invoices all the products creating the delivery if necessary. Changes number with an invoice number saving exiting number in initial_number.


290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'app/models/sale.rb', line 290

def invoice(invoiced_at = Time.zone.now)
  return false unless can_invoice?
  ActiveRecord::Base.transaction do
    # Set values for invoice
    self.invoiced_at ||= invoiced_at
    self.confirmed_at ||= self.invoiced_at
    self.payment_at ||= Delay.new(self.payment_delay).compute(self.invoiced_at)
    self.initial_number = number
    if sequence = Sequence.of(:sales_invoices)
      loop do
        self.number = sequence.next_value
        break unless self.class.find_by(number: number, state: 'invoice')
      end
    end
    save!
    client.add_event(:sales_invoice_creation, updater.person) if updater
    return super
  end
  false
end

#invoiced_onObject


211
212
213
# File 'app/models/sale.rb', line 211

def invoiced_on
  dealt_at.to_date
end

#letter?Boolean

Alias for letter_format? method

Returns:

  • (Boolean)

364
365
366
# File 'app/models/sale.rb', line 364

def letter?
  letter_format?
end

#mail_addressObject


368
369
370
# File 'app/models/sale.rb', line 368

def mail_address
  (address || client.default_mail_address).mail_coordinate
end

#nameObject Also known as: label

Label of the sales order depending on the state and the number


358
359
360
# File 'app/models/sale.rb', line 358

def name
  tc("label.#{(credit? && invoice?) ? :credit : self.state}", number: number)
end

#nature=(value) ⇒ Object


248
249
250
251
# File 'app/models/sale.rb', line 248

def nature=(value)
  super(value)
  self.currency = self.nature.currency if self.nature
end

#number_labelObject


372
373
374
# File 'app/models/sale.rb', line 372

def number_label
  tc('number_label.' + (estimate? ? 'proposal' : 'command'), number: number)
end

#partially_closed?Boolean

Returns:

  • (Boolean)

238
239
240
# File 'app/models/sale.rb', line 238

def partially_closed?
  !affair.debit.zero? && !affair.credit.zero?
end

#productsObject


408
409
410
411
412
413
414
# File 'app/models/sale.rb', line 408

def products
  p = []
  for item in items
    p << item.product.name
  end
  ps = p.join(', ')
end

#refreshObject

Save a new time


254
255
256
# File 'app/models/sale.rb', line 254

def refresh
  save
end

#sales_conditionsObject

Build general sales condition for the sale order


395
396
397
398
399
400
401
402
# File 'app/models/sale.rb', line 395

def sales_conditions
  c = []
  c << tc('sales_conditions.downpayment', percentage: self.nature.downpayment_percentage, amount: self.downpayment_amount.l(currency: self.currency)) if amount > self.nature.downpayment_minimum && has_downpayment
  c << tc('sales_conditions.validity', expiration: self.expired_at.l)
  c += self.nature.sales_conditions.to_s.split(/\s*\n\s*/) if self.nature.sales_conditions
  # c += self.responsible.team.sales_conditions.to_s.split(/\s*\n\s*/) if self.responsible and self.responsible.team
  c
end

#sales_mentionsObject


384
385
386
387
388
389
390
391
392
# File 'app/models/sale.rb', line 384

def sales_mentions
  # get preference for sales conditions
  preference_sales_conditions = Preference.global.find_by(name: :sales_conditions)
  if preference_sales_conditions
    return preference_sales_conditions.value
  else
    return nil
  end
end

#sold?Boolean

Returns if the sale has been validated and so if it can be considered as sold.

Returns:

  • (Boolean)

265
266
267
# File 'app/models/sale.rb', line 265

def sold?
  (order? || invoice?)
end

#state_labelObject

Prints human name of current state


346
347
348
# File 'app/models/sale.rb', line 346

def state_label
  self.class.state_machine.state(self.state.to_sym).human_name
end

#statusObject

Returns status of affair if invoiced else “stop”


448
449
450
451
# File 'app/models/sale.rb', line 448

def status
  return affair.status if invoice? && affair
  :stop
end

#supplierObject


242
243
244
# File 'app/models/sale.rb', line 242

def supplier
  Entity.of_company
end

#taxes_amountObject


376
377
378
# File 'app/models/sale.rb', line 376

def taxes_amount
  amount - pretax_amount
end

#unpaid_daysObject


404
405
406
# File 'app/models/sale.rb', line 404

def unpaid_days
  (Time.zone.now - self.invoiced_at) if invoice?
end

#usable_paymentsObject


380
381
382
# File 'app/models/sale.rb', line 380

def usable_payments
  client.incoming_payments.where('COALESCE(used_amount, 0)<COALESCE(amount, 0)').joins(mode: :cash).where(currency: self.currency).order('to_bank_at')
end