Class: Entity

Inherits:
Ekylibre::Record::Base show all
Includes:
Attachable, 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?, attr_readonly_with_conditions, #check_if_destroyable?, #check_if_updateable?, columns_definition, complex_scopes, customizable?, #customizable?, #customized?, #editable?, has_picture, #human_attribute_name, human_attribute_name_with_id, nomenclature_reflections, #old_record, refers_to, scope_with_registration, simple_scopes, #updateable?

Instance Attribute Details

#old_passwordObject

Returns the value of attribute old_password


76
77
78
# File 'app/models/entity.rb', line 76

def old_password
  @old_password
end

#password_confirmationObject

Returns the value of attribute password_confirmation


76
77
78
# File 'app/models/entity.rb', line 76

def password_confirmation
  @password_confirmation
end

Class Method Details

.best_clients(limit = -1)) ⇒ Object


439
440
441
# File 'app/models/entity.rb', line 439

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

.export(_options = {}) ⇒ Object

code = “ entity = Entity.build(”cols.collect{|k,v| “:#{v} => item”}.join(', ')+“, :nature => nature, :sale_catalog_id => sale_catalog.id, :language => #selfself.of_companyself.of_company.languageself.of_company.language.inspect, :client => true)n”

code += "    if entity.save\n"
if cols[:address].is_a? Hash
  code += "      address = entity.addresses.build("+cols[:address].collect{|k,v| ":#{v} => item[#{k}]"}.join(', ')+")\n"
  code += "      unless address.save\n"
  code += "        problems[item_index.to_s] ||= []\n"
  code += "        problems[item_index.to_s] += address.errors.full_messages\n"
  code += "      end\n"
end
for k, v in (cols[:special]||{}).select{|k,v| v == :generate_string_custom_field}
  code += "      datum = entity.custom_field_data.build(:custom_field_id => custom_field_#{k}.id, :string_value => item[#{k}])\n"
  code += "      unless datum.save\n"
  code += "        problems[item_index.to_s] ||= []\n"
  code += "        problems[item_index.to_s] += datum.errors.full_messages\n"
  code += "      end\n"
end
for k, v in cols[:custom_field]||{}
  if custom_field = CustomField.find_by_id(k.to_s[2..-1].to_i)
    if custom_field.nature == 'string'
      code += "      datum = entity.custom_field_data.build(:custom_field_id => #{custom_field.id}, :string_value => item[#{k}])\n"
      code += "      unless datum.save\n"
      code += "        problems[item_index.to_s] ||= []\n"
      code += "        problems[item_index.to_s] += datum.errors.full_messages\n"
      code += "      end\n"
      # elsif custom_field.nature == 'choice'
      #   code += "    co = entity.addresses.create("+cols[:address].collect{|k,v| ":#{v} => item[#{k}]"}.join(', ')+")\n" if cols[:address].is_a? Hash
    end
  end
end
code += "    else\n"
code += "      problems[item_index.to_s] ||= []\n"
code += "      problems[item_index.to_s] += entity.errors.full_messages\n"
code += "    end\n"
code += "  end\n"
code += "  raise ActiveRecord::Rollback\n" unless options[:no_simulation]
code += "end\n"
# list = code.split("\n"); list.each_index{|x| puts((x+1).to_s.rjust(4)+": "+list[x])}
eval(code)
return {:errors => problems, :items_count => item_index-1}

end


548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
# File 'app/models/entity.rb', line 548

def self.export(_options = {})
    # entities = Entity.where(options)
    csv_string = Ekylibre::CSV.generate do |csv|
      csv << ['Code', 'Type', 'Catégorie', 'Nom', 'Prénom', 'Dest-Service', 'Bat.-Res.-ZI', 'N° et voie', 'Lieu dit', 'Code Postal', 'Ville', 'Téléphone', 'Mobile', 'Fax', 'Email', 'Site Web', 'Taux de réduction']
      each do |entity|
        address = EntityAddress.find_by(entity_id: entity.id, by_default: true, deleted_at: nil)
        item = []
        item << ["'" + entity.number.to_s, entity.nature.name, entity.sale_catalog.name, entity.name, entity.first_name]
        item << if !address.nil?
                  [address.item_2, address.item_3, address.item_4, address.item_5, address.item_6_code, address.item_6_city, address.phone, address.mobile, address.fax, address.email, address.website]
                else
                  ['', '', '', '', '', '', '', '', '', '', '']
      end
        item << [entity.reduction_percentage.to_s.tr('.', ',')]
        csv << item.flatten
      end
    end
    csv_string
end

.exportable_columnsObject


223
224
225
226
227
# File 'app/models/entity.rb', line 223

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

.importable_columnsObject


443
444
445
446
447
448
449
450
451
452
453
454
455
456
# File 'app/models/entity.rb', line 443

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| [: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| [: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

.new_with_cast(*attributes, &block) ⇒ Object

Auto-cast entity to best matching class with type column


212
213
214
215
216
217
218
219
220
# File 'app/models/entity.rb', line 212

def new_with_cast(*attributes, &block)
  if (h = attributes.first).is_a?(Hash) && !h.nil? &&
     (type = h[:type] || h['type']) && !type.empty? &&
     (klass = type.constantize) != self
    raise "Can not cast #{name} to #{klass.name}" unless klass <= self
    return klass.new(*attributes, &block)
  end
  new_without_cast(*attributes, &block)
end

.of_companyObject

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


231
232
233
234
235
236
237
238
239
240
241
242
# File 'app/models/entity.rb', line 231

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…)


282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# File 'app/models/entity.rb', line 282

def (nature)
  natures = [:client, :supplier]
  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?
    prefix = Nomen::Account[nature.to_s.pluralize].send(Account.accounting_system)
    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


321
322
323
324
325
# File 'app/models/entity.rb', line 321

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


268
269
270
271
272
273
274
275
# File 'app/models/entity.rb', line 268

def balance
  amount = 0.0
  amount += incoming_payments.sum(:amount)
  amount -= sales_invoices.sum(:amount)
  amount -= outgoing_payments.sum(:amount)
  amount += purchase_invoices.sum(:amount)
  amount
end

#default_mail_address_idObject


331
332
333
# File 'app/models/entity.rb', line 331

def default_mail_address_id
  default_mail_address ? default_mail_address.id : nil
end

#default_mail_coordinateObject


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

def default_mail_coordinate
  default_mail_address ? default_mail_address.coordinate : nil
end

#descriptionObject


357
358
359
360
361
362
# File 'app/models/entity.rb', line 357

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

#destroyable?Boolean

Returns:

  • (Boolean)

435
436
437
# File 'app/models/entity.rb', line 435

def destroyable?
  !(of_company? || sales_invoices.any? || participations.any? || sales.any? || parcels.any? || purchases.any?)
end

#has_another_tracking?(serial, product_id) ⇒ Boolean

Returns:

  • (Boolean)

277
278
279
# File 'app/models/entity.rb', line 277

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

#labelObject


250
251
252
# File 'app/models/entity.rb', line 250

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

#last_incoming_paymentObject


263
264
265
# File 'app/models/entity.rb', line 263

def last_incoming_payment
  incoming_payments.last_updateds.first
end

#last_subscription(nature) ⇒ Object


349
350
351
# File 'app/models/entity.rb', line 349

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

335
336
337
338
339
340
# File 'app/models/entity.rb', line 335

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


342
343
344
345
346
347
# File 'app/models/entity.rb', line 342

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(entity, author = nil) ⇒ Object

Merge given entity into record. Alls related records of given entity will point on self.

Raises:

  • (StandardError)

366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
# File 'app/models/entity.rb', line 366

def merge_with(entity, author = nil)
  raise StandardError, 'Company entity is not mergeable' if entity.of_company?
  Ekylibre::Record::Base.transaction do
    # EntityAddress
    threads = EntityAddress.unscoped.where(entity_id: id).uniq.pluck(:thread)
    other_threads = EntityAddress.unscoped.where(entity_id: entity.id).uniq.pluck(:thread)
    other_threads.each do |thread|
      thread.succ! while threads.include?(thread)
      threads << thread
      EntityAddress.unscoped.where(entity_id: entity.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}=#{entity.id} AND #{column.references} IN #{models_group}")
        elsif column.references == base_model # Straight
          connection.execute("UPDATE #{table} SET #{column.name}=#{id} WHERE #{column.name}=#{entity.id}")
        end
      end
    end

    # Update attributes
    [: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}=", entity.send(attr)) if send(attr).blank?
    end
    if entity.picture.file? && !picture.file?
      self.picture = File.open(entity.picture.path(:original))
    end

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

    save!

    # Add observation
    content = "Merged entity (ID=#{entity.id}):\n"
    for attr, value in entity.attributes.sort
      value = entity.send(attr).to_s
      content << "  - #{Entity.human_attribute_name(attr)} : #{value}\n" unless value.blank?
    end
    Entity.custom_fields.each do |custom_field|
      value = entity.custom_fields[custom_field.column_name].to_s
      content << "  - #{custom_field.name} : #{value}\n" unless value.blank?
    end

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

    # Remove doublon
    entity.destroy
  end
end

#othersObject

Returns an entity scope for.all other entities


246
247
248
# File 'app/models/entity.rb', line 246

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

#picture_path(style = :original) ⇒ Object


353
354
355
# File 'app/models/entity.rb', line 353

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

#sirenObject


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

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


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

def siren_number
  siret_number[0..8]
end

#warningObject


316
317
318
319
# File 'app/models/entity.rb', line 316

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