Class: Entity

Inherits:
Ekylibre::Record::Base show all
Includes:
Attachable, Autocastable, Commentable, Customizable, Versionable
Defined in:
app/models/entity.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Customizable

#custom_value, #set_custom_value, #validate_custom_fields

Methods included from Versionable

#add_creation_version, #add_destruction_version, #add_update_version, #last_version, #notably_changed?, #version_object

Methods inherited from Ekylibre::Record::Base

#already_updated?, #check_if_destroyable?, #check_if_updateable?, columns_definition, #customizable?, customizable?, #customized?, #destroyable?, #editable?, has_picture, #human_attribute_name, nomenclature_reflections, #old_record, refers_to, #unsuppress, #updateable?

Methods included from Userstamp::Stampable

included

Methods included from Userstamp::Stamper

included

Instance Attribute Details

#old_passwordObject

Returns the value of attribute old_password


86
87
88
# File 'app/models/entity.rb', line 86

def old_password
  @old_password
end

#password_confirmationObject

Returns the value of attribute password_confirmation


86
87
88
# File 'app/models/entity.rb', line 86

def password_confirmation
  @password_confirmation
end

Class Method Details

.best_clients(limit = -1)) ⇒ Object


503
504
505
# File 'app/models/entity.rb', line 503

def self.best_clients(limit = -1)
  clients.sort_by { |client| -client.sales.count }[0...limit]
end

.exportable_columnsObject


252
253
254
255
256
# File 'app/models/entity.rb', line 252

def exportable_columns
  content_columns.delete_if do |c|
    %i[active lock_version deliveries_conditions].include?(c.name.to_sym)
  end
end

.importable_columnsObject


507
508
509
510
511
512
513
514
515
516
517
518
519
520
# File 'app/models/entity.rb', line 507

def self.importable_columns
  columns = []
  columns << [tc('import.dont_use'), 'special-dont_use']
  columns << [tc('import.generate_string_custom_field'), 'special-generate_string_custom_field']
  # columns << [tc("import.generate_choice_custom_field"), "special-generate_choice_custom_field"]
  cols = Entity.content_columns.delete_if { |c| %i[active full_name lock_version updated_at created_at].include?(c.name.to_sym) || c.type == :boolean }.collect(&:name)
  columns += cols.collect { |c| [Entity.model_name.human + '/' + Entity.human_attribute_name(c), 'entity-' + c] }.sort
  cols = EntityAddress.content_columns.collect(&:name).delete_if { |c| %i[number started_at stopped_at deleted address by_default closed_at lock_version active updated_at created_at].include?(c.to_sym) } + %w[item_6_city item_6_code]
  columns += cols.collect { |c| [EntityAddress.model_name.human + '/' + EntityAddress.human_attribute_name(c), 'address-' + c] }.sort
  columns += %w[name abbreviation].collect { |c| [EntityNature.model_name.human + '/' + EntityNature.human_attribute_name(c), 'entity_nature-' + c] }.sort
  # columns += ["name"].collect{|c| [Catalog.model_name.human+"/"+Catalog.human_attribute_name(c), "product_price_listing-"+c]}.sort
  columns += CustomField.where("nature in ('string')").collect { |c| [CustomField.model_name.human + '/' + c.name, 'custom_field-id' + c.id.to_s] }.sort
  columns
end

.of_companyObject

Returns a default company entity. TODO: Externalizes these informations to prevent export/overwriting errors


260
261
262
263
264
265
266
267
268
269
270
271
# File 'app/models/entity.rb', line 260

def of_company
  company = find_by(of_company: true)
  unless company
    user = User.order(:id).first
    company = Entity.create!(
      nature: :organization,
      last_name: user ? user.last_name : 'COMPANY',
      of_company: true
    )
  end
  company
end

Instance Method Details

#account(nature) ⇒ Object

This method creates automatically an account for the entity for its usage (client, supplier…)


333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
# File 'app/models/entity.rb', line 333

def (nature)
  natures = %i[client supplier employee]
  conversions = { payer: :client, payee: :supplier }
  nature = nature.to_sym
  nature = conversions[nature] || nature
  unless natures.include?(nature)
    raise ArgumentError, "Unknown nature #{nature.inspect} (#{natures.to_sentence} are accepted)"
  end
   = send("#{nature}_account")
  if .nil?
     = nature.to_s.pluralize
     = :staff_due_remunerations if nature == :employee
    prefix = Preference[:"#{nature}_account_radix"]
    if prefix.blank?
      prefix = Nomen::Account.find().send(Account.accounting_system)
    end
    if Preference[:use_entity_codes_for_account_numbers]
      number = prefix.to_s + self.number.to_s
      unless  = Account.find_by(number: number)
         = Account.create(number: number, name: full_name, reconcilable: true)
      end
    else
      suffix = '1'
      suffix = suffix.upper_ascii[0..5].rjust(6, '0')
       = 1
      # x = Time.zone.now
      i = 0
      until .nil?
         = Account.find_by('number LIKE ?', prefix.to_s + suffix.to_s)
        suffix.succ! unless .nil?
        i += 1
      end
       = Account.create(number: prefix.to_s + suffix.to_s, name: full_name, reconcilable: true)
    end
    reload.update_column("#{nature}_account_id", .id)
  end
  
end

#add_event(usage, operator, at = Time.zone.now) ⇒ Object


377
378
379
380
381
# File 'app/models/entity.rb', line 377

def add_event(usage, operator, at = Time.zone.now)
  if operator && item = Nomen::EventNature[usage]
    Event.create!(name: item.human_name, started_at: at, duration: item.default_duration.to_i, participations_attributes: { '0' => { participant_id: id, state: 'informative' }, '1' => { participant_id: operator.id, state: 'accepted' } })
  end
end

#balanceObject


324
325
326
# File 'app/models/entity.rb', line 324

def balance
  economic_situation[:trade_balance]
end

#born_onObject


494
495
496
# File 'app/models/entity.rb', line 494

def born_on
  born_at.to_date
end

#client_accounting_balanceObject


291
292
293
294
# File 'app/models/entity.rb', line 291

def client_accounting_balance
  return 0.0 unless client?
  economic_situation[:client_accounting_balance]
end

#default_mail_address_idObject


387
388
389
# File 'app/models/entity.rb', line 387

def default_mail_address_id
  default_mail_address ? default_mail_address.id : nil
end

#default_mail_coordinateObject


383
384
385
# File 'app/models/entity.rb', line 383

def default_mail_coordinate
  default_mail_address ? default_mail_address.coordinate : nil
end

#entity_payment_mode_nameObject


274
275
276
# File 'app/models/entity.rb', line 274

def entity_payment_mode_name
  supplier_payment_mode&.name
end

#financial_year_with_opened_exchange?Boolean

Returns:

  • (Boolean)

498
499
500
501
# File 'app/models/entity.rb', line 498

def financial_year_with_opened_exchange?
  return false unless persisted?
  financial_years.any?(&:opened_exchange?)
end

#has_another_tracking?(serial, product_id) ⇒ Boolean

Returns:

  • (Boolean)

328
329
330
# File 'app/models/entity.rb', line 328

def has_another_tracking?(serial, product_id)
  trackings.where('serial=? AND product_id!=? ', serial, product_id).count > 0
end

#labelObject


306
307
308
# File 'app/models/entity.rb', line 306

def label
  number.to_s + '. ' + full_name.to_s
end

#last_incoming_paymentObject


319
320
321
# File 'app/models/entity.rb', line 319

def last_incoming_payment
  incoming_payments.last_updateds.first
end

#last_subscription(nature) ⇒ Object


405
406
407
# File 'app/models/entity.rb', line 405

def last_subscription(nature)
  subscriptions.where(nature: nature).order(stopped_on: :desc).first
end

391
392
393
394
395
396
# File 'app/models/entity.rb', line 391

def link_to!(entity, options = {})
  nature = options[:as] || :undefined
  unless direct_links.actives.where(nature: nature.to_s, linked_id: entity.id).any?
    direct_links.create!(nature: nature.to_s, linked_id: entity.id)
  end
end

#maximal_reduction_percentage(computed_at = Time.zone.today) ⇒ Object


398
399
400
401
402
403
# File 'app/models/entity.rb', line 398

def maximal_reduction_percentage(computed_at = Time.zone.today)
  Subscription
    .joins("JOIN #{SubscriptionNature.table_name} AS sn ON (#{Subscription.table_name}.nature_id = sn.id) LEFT JOIN #{EntityLink.table_name} AS el ON (el.nature = sn.entity_link_nature AND #{Subscription.table_name}.subscriber_id IN (entity_id, linked_id))")
    .where("? IN (#{Subscription.table_name}.subscriber_id, entity_id, linked_id) AND ? BETWEEN #{Subscription.table_name}.started_at AND #{Subscription.table_name}.stopped_at AND COALESCE(#{Subscription.table_name}.sale_id, 0) NOT IN (SELECT id FROM #{Sale.table_name} WHERE state='estimate')", id, computed_at)
    .maximum(:reduction_percentage).to_f || 0.0
end

#merge_with(other, options = {}) ⇒ Object

Merge given entity into record. Alls related records of given entity will point on self. Given entity is destroyed at the end, self remains.

Raises:

  • (StandardError)

422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
# File 'app/models/entity.rb', line 422

def merge_with(other, options = {})
  raise StandardError, 'Company entity is not mergeable' if other.of_company?
  author = options[:author]
  Ekylibre::Record::Base.transaction do
    # EntityAddress
    threads = EntityAddress.unscoped.where(entity_id: id).uniq.pluck(:thread).delete_if(&:blank?)
    other_threads = EntityAddress.unscoped.where(entity_id: other.id).uniq.pluck(:thread).delete_if(&:blank?)
    other_threads.each do |thread|
      thread.succ! while threads.include?(thread)
      threads << thread
      EntityAddress.unscoped.where(entity_id: other.id).update_all(thread: thread, by_default: false)
    end

    # Relations with DB approach to prevent missing reflection
    connection = self.class.connection
    base_class = self.class.base_class
    base_model = base_class.name.underscore.to_sym
    models_set = ([base_class] + base_class.descendants)
    models_group = '(' + models_set.map do |model|
      "'#{model.name}'"
    end.join(', ') + ')'
    Ekylibre::Schema.tables.each do |table, columns|
      columns.each do |_name, column|
        next unless column.references
        if column.references.is_a?(String) # Polymorphic
          connection.execute("UPDATE #{table} SET #{column.name}=#{id} WHERE #{column.name}=#{other.id} AND #{column.references} IN #{models_group}")
        elsif column.references == base_model # Straight
          connection.execute("UPDATE #{table} SET #{column.name}=#{id} WHERE #{column.name}=#{other.id}")
        end
      end
    end

    # Update attributes
    %i[currency country last_name first_name activity_code description born_at dead_at deliveries_conditions first_met_at meeting_origin proposer siret_number supplier_account client_account vat_number language authorized_payments_count].each do |attr|
      send("#{attr}=", other.send(attr)) if send(attr).blank?
    end
    if other.picture.file? && !picture.file?
      self.picture = File.open(other.picture.path(:original))
    end

    # Update custom fields
    self.custom_fields ||= {}
    other.custom_fields ||= {}
    Entity.custom_fields.each do |custom_field|
      attr = custom_field.column_name
      if self.custom_fields[attr].blank? && other.custom_fields[attr].present?
        self.custom_fields[attr] = other.custom_fields[attr]
      end
    end

    save!

    # Add summary observation of the merge
    if author
      content = "Merged entity (ID=#{other.id}):\n"
      other.attributes.sort.each do |attr, _value|
        value = other.send(attr).to_s
        content << "  - #{Entity.human_attribute_name(attr)} : #{value}\n" if value.present?
      end
      Entity.custom_fields.each do |custom_field|
        value = other.custom_fields[custom_field.column_name].to_s
        content << "  - #{custom_field.name} : #{value}\n" if value.present?
      end

      observations.create!(content: content, importance: 'normal', author: author)
    end

    # Remove doublon
    other.destroy
  end
end

#name_with_postal_code_and_cityObject


413
414
415
416
417
418
# File 'app/models/entity.rb', line 413

def name_with_postal_code_and_city
  desc = (number.nil? ? '' : number) + '. ' + full_name
  c = default_mail_address
  desc += ' (' + c.mail_line_6.to_s + ')' unless c.nil?
  desc
end

#othersObject

Returns an entity scope for.all other entities


302
303
304
# File 'app/models/entity.rb', line 302

def others
  self.class.where('id != ?', (id || 0))
end

#picture_path(style = :original) ⇒ Object


409
410
411
# File 'app/models/entity.rb', line 409

def picture_path(style = :original)
  picture.path(style)
end

#sirenObject


314
315
316
317
# File 'app/models/entity.rb', line 314

def siren
  ActiveSupport::Deprecation.warn('Entity#siren is deprecated. Please use Entity#siren_number instead. This method will be removed in Ekylibre 3.')
  siren_number
end

#siren_numberObject


310
311
312
# File 'app/models/entity.rb', line 310

def siren_number
  siret_number[0..8] if siret_number
end

#supplier_accounting_balanceObject


296
297
298
299
# File 'app/models/entity.rb', line 296

def supplier_accounting_balance
  return 0.0 unless supplier?
  economic_situation[:supplier_accounting_balance]
end

#toggle!Object

Convert a contact into organization or inverse


279
280
281
282
283
284
285
# File 'app/models/entity.rb', line 279

def toggle!
  if contact? && first_name.present?
    self.last_name = first_name + ' ' + last_name
  end
  self.nature = contact? ? :organization : :contact
  save!
end

#unbalanced?Boolean

Returns:

  • (Boolean)

287
288
289
# File 'app/models/entity.rb', line 287

def unbalanced?
  EconomicSituation.unbalanced.pluck(:id).include? id
end

#warningObject


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

def warning
  count = observations.where(importance: 'important').count
  # count += self.balance<0 ? 1 : 0
end