Class: Company

Inherits:
ApplicationRecord show all
Includes:
Models::Auditable
Defined in:
app/models/company.rb

Overview

== Schema Information

Table name: companies
Database name: primary

id :integer not null, primary key
consolidated_currency :string(3)
country_iso3 :string(255)
currency :string(255)
default_email_sender :string(50)
legal_name :string(255)
market_zone :string
name :string(100)
number :integer
short_name :string(255)
tax_info :string(255)
accounting_business_unit_id :integer
address_id :integer
administration_business_unit_id :integer
default_business_unit_id :integer
ledger_project_id :integer
parent_company_id :integer
sales_business_unit_id :integer

Indexes

companies_address_id_idx (address_id)
companies_parent_company_id_idx (parent_company_id)
companies_sales_business_unit_id_idx (sales_business_unit_id)
idx_company_number (number)
index_companies_on_ledger_project_id (ledger_project_id)
index_companies_on_market_zone (market_zone)

Foreign Keys

companies_address_id_fk (address_id => addresses.id) ON DELETE => nullify
companies_parent_company_id_fk (parent_company_id => companies.id) ON DELETE => nullify
companies_sales_business_unit_id_fk (sales_business_unit_id => business_units.id) ON DELETE => nullify

Constant Summary collapse

USA =

Usa.

1
CAN =

Can.

2
IND =

Ind.

3
NLD =

Nld.

4
WORKING_HOURS_CONFIG =

Working hours configuration for this company
Default is Mon-Fri 8:30am-5:30pm

{
  mon: { '08:30' => '17:30' },
  tue: { '08:30' => '17:30' },
  wed: { '08:30' => '17:30' },
  thu: { '08:30' => '17:30' },
  fri: { '08:30' => '17:30' }
}.freeze
WORKING_HOURS_TIMEZONES =

Timezone for working hours by company
Future: Could be moved to Store model when we have multiple warehouses per country

{
  USA => 'America/Chicago',
  CAN => 'America/Toronto',
  IND => 'Asia/Kolkata',
  NLD => 'Europe/Amsterdam'
}.freeze

Constants included from Models::Auditable

Models::Auditable::ALWAYS_IGNORED

Constants included from Models::Schedulable

Models::Schedulable::SIMPLE_FORM_OPTIONS

Instance Attribute Summary collapse

Belongs to collapse

Methods included from Models::Auditable

#creator, #updater

Has one collapse

Has many collapse

Delegated Instance Attributes collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Models::Auditable

#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Models::Schedulable

config

Methods included from Models::AfterCommittable

#after_commit

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#currencyObject (readonly)

Validates short name, name, number, default email sender, currency.

Validations:



108
# File 'app/models/company.rb', line 108

validates :short_name, :name, :number, :default_email_sender, :currency, presence: true

#default_email_senderObject (readonly)

Validates short name, name, number, default email sender, currency.

Validations:



108
# File 'app/models/company.rb', line 108

validates :short_name, :name, :number, :default_email_sender, :currency, presence: true

#nameObject (readonly)

Validates short name, name, number, default email sender, currency.

Validations:



108
# File 'app/models/company.rb', line 108

validates :short_name, :name, :number, :default_email_sender, :currency, presence: true

#numberObject (readonly)

Validates short name, name, number, default email sender, currency.

Validations:



108
# File 'app/models/company.rb', line 108

validates :short_name, :name, :number, :default_email_sender, :currency, presence: true

#short_nameObject (readonly)

Validates short name, name, number, default email sender, currency.

Validations:



108
# File 'app/models/company.rb', line 108

validates :short_name, :name, :number, :default_email_sender, :currency, presence: true

Class Method Details

.market_zone_euActiveRecord::Relation<Company>

A relation of Companies that are market zone eu. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Company>)

See Also:



113
# File 'app/models/company.rb', line 113

scope :market_zone_eu, -> { where(market_zone: 'EU').order(:id) }

.market_zone_naActiveRecord::Relation<Company>

A relation of Companies that are market zone na. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Company>)

See Also:



112
# File 'app/models/company.rb', line 112

scope :market_zone_na, -> { where(market_zone: 'NA').order(:id) }

.sales_companiesActiveRecord::Relation<Company>

A relation of Companies that are sales companies. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Company>)

See Also:



111
# File 'app/models/company.rb', line 111

scope :sales_companies, -> { where(id: [USA, CAN, NLD]).order(:id) }

.schema_dot_org_aggregate_ratingnil

Deprecated.

reviews moved to Reviews.io; the rating is fetched
client-side via their API. Kept so old templates don't 500.

Returns:

  • (nil)


332
333
334
335
# File 'app/models/company.rb', line 332

def self.schema_dot_org_aggregate_rating
  # Reviews migrated to Reviews.io - aggregate rating now fetched from Reviews.io API
  nil
end

.select_optionsArray<Array(String, Integer)>

[name, id] pairs for every Company (including IND
placeholder), used in admin filter dropdowns.

Returns:

  • (Array<Array(String, Integer)>)


239
240
241
# File 'app/models/company.rb', line 239

def self.select_options
  Company.order(:id).pluck(:name, :id)
end

.select_options_sales_companiesArray<Array(String, Integer)>

[name, id] pairs for the sales-bearing companies only
(USA, CAN, NLD).

Returns:

  • (Array<Array(String, Integer)>)


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

def self.select_options_sales_companies
  Company.sales_companies.pluck(:name, :id)
end

Instance Method Details

#accounting_business_unitBusinessUnit?

Returns the accounting business unit this record belongs to.

Returns:

  • (BusinessUnit, nil)

    the accounting business unit this record belongs to



65
# File 'app/models/company.rb', line 65

belongs_to :accounting_business_unit, class_name: 'BusinessUnit', optional: true

#activity_type_assignment_queuesActiveRecord::Relation<ActivityTypeAssignmentQueue>

Returns the associated activity type assignment queues.

Returns:



93
# File 'app/models/company.rb', line 93

has_many :activity_type_assignment_queues, inverse_of: :company

#add_business_days(from, business_days) ⇒ Date

Note:

this uses our shipping holidays. Carrier-specific holiday
calendars (a carrier may observe days we don't, or vice versa) are a
future enhancement β€” for now the company's standard holiday set is a
good-enough proxy for both.

Advance +business_days+ working days from +from+, skipping weekends and
this company's holidays. Used for carrier transit-time / arrival-date
estimates.

Parameters:

  • from (Date, Time)

    starting point

  • business_days (Integer)

    number of working days to advance

Returns:

  • (Date)


230
231
232
233
234
# File 'app/models/company.rb', line 230

def add_business_days(from, business_days)
  with_working_hours_config do
    WorkingHours.add_days(from.to_date, business_days.to_i).to_date
  end
end

#addressAddress?

Returns the address this record belongs to.

Returns:

  • (Address, nil)

    the address this record belongs to



57
# File 'app/models/company.rb', line 57

belongs_to :address, optional: true

#administration_business_unitBusinessUnit?

Returns the administration business unit this record belongs to.

Returns:

  • (BusinessUnit, nil)

    the administration business unit this record belongs to



61
# File 'app/models/company.rb', line 61

belongs_to :administration_business_unit, class_name: 'BusinessUnit', optional: true

#assign_next_credit_memo_numberString

Same scheme as #assign_next_invoice_number but pulls from
the per-company next_credit_memo_number_<id> sequence.

Returns:

  • (String)

    9-character credit-memo reference number



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

def assign_next_credit_memo_number
  next_year_aware_reference("next_credit_memo_number_#{id}", "credit_memo_number_#{id}")
end

#assign_next_invoice_numberString

Allocate the next invoice number for this company. Format is
<company_id><YY><5-digit serial> zero-padded to 9 chars,
rolled at calendar-year change. Pulls from a per-company
Postgres sequence (next_invoice_number_<id>).

Returns:

  • (String)

    9-character invoice number



345
346
347
# File 'app/models/company.rb', line 345

def assign_next_invoice_number
  next_year_aware_reference("next_invoice_number_#{id}", "invoice_number_#{id}")
end

#budgetsActiveRecord::Relation<Budget>

Returns the associated budgets.

Returns:

  • (ActiveRecord::Relation<Budget>)

    the associated budgets



97
# File 'app/models/company.rb', line 97

has_many :budgets

#business_unit_for(dept) ⇒ BusinessUnit?

Look up one of the four canonical BusinessUnits by department
tag β€” default / sales / administration / accounting.
GL-posting code uses this so per-row business-unit assignment
comes from the Company rather than being hard-coded.

Parameters:

  • dept (String)

Returns:



291
292
293
294
295
296
297
298
299
300
301
302
# File 'app/models/company.rb', line 291

def business_unit_for(dept)
  case dept
  when 'default'
    default_business_unit
  when 'sales'
    sales_business_unit
  when 'administration'
    administration_business_unit
  when 'accounting'
    accounting_business_unit
  end
end

#business_unitsActiveRecord::Relation<BusinessUnit>

Returns the associated business units.

Returns:

  • (ActiveRecord::Relation<BusinessUnit>)

    the associated business units



101
# File 'app/models/company.rb', line 101

has_many :business_units

#canada?Boolean

Returns whether the record canada.

Returns:

  • (Boolean)

    whether the record canada



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

def canada?
  id == CAN
end

#catalogsActiveRecord::Relation<Catalog>

Returns the associated catalogs.

Returns:

  • (ActiveRecord::Relation<Catalog>)

    the associated catalogs



103
# File 'app/models/company.rb', line 103

has_many :catalogs, inverse_of: :company

#cc_bank_accountBankAccount?

Returns the associated cc bank account.

Returns:

  • (BankAccount, nil)

    the associated cc bank account



74
# File 'app/models/company.rb', line 74

has_one :cc_bank_account, class_name: 'BankAccount', foreign_key: 'cc_company_id'

#company_holidaysActiveRecord::Relation<CompanyHoliday>

Returns the associated company holidays.

Returns:

  • (ActiveRecord::Relation<CompanyHoliday>)

    the associated company holidays



99
# File 'app/models/company.rb', line 99

has_many :company_holidays, dependent: :destroy

#company_labelString

"USA πŸ‡ΊπŸ‡Έ"-style label for the company chooser.

Returns:

  • (String)


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

def company_label
  "#{country_iso3} #{country_iso3166.emoji_flag}"
end

#countryCountry?

Returns the country this record belongs to.

Returns:

  • (Country, nil)

    the country this record belongs to



67
# File 'app/models/company.rb', line 67

belongs_to :country, foreign_key: 'country_iso3', primary_key: 'iso3', optional: true

#country_iso3166ISO3166::Country?

Resolve country_iso3 to an ISO3166::Country instance
(used downstream for emoji flag, name, demonym).

Returns:

  • (ISO3166::Country, nil)


253
254
255
# File 'app/models/company.rb', line 253

def country_iso3166
  ISO3166::Country.find_country_by_alpha3(country_iso3)
end

#currency_symbolString

ISO currency symbol for the company's functional currency.

Returns:

  • (String)


280
281
282
# File 'app/models/company.rb', line 280

def currency_symbol
  Money::Currency.new(currency).symbol
end

#customerCustomer?

Returns the associated customer.

Returns:

  • (Customer, nil)

    the associated customer



70
# File 'app/models/company.rb', line 70

has_one :customer

#default_business_unitBusinessUnit?

Returns the default business unit this record belongs to.

Returns:

  • (BusinessUnit, nil)

    the default business unit this record belongs to



59
# File 'app/models/company.rb', line 59

belongs_to :default_business_unit, class_name: 'BusinessUnit', optional: true

#email_templatesActiveRecord::Relation<EmailTemplate>

Returns the associated email templates.

Returns:

  • (ActiveRecord::Relation<EmailTemplate>)

    the associated email templates



95
# File 'app/models/company.rb', line 95

has_many :email_templates, as: :resource, dependent: :destroy

#holiday_datesArray<Date>

Get all holiday dates for this company

Returns:

  • (Array<Date>)

    List of holiday dates



167
168
169
# File 'app/models/company.rb', line 167

def holiday_dates
  company_holidays.pluck(:holiday_date)
end

#invoicesActiveRecord::Relation<Invoice>

Returns the associated invoices.

Returns:

  • (ActiveRecord::Relation<Invoice>)

    the associated invoices



87
# File 'app/models/company.rb', line 87

has_many :invoices

#ledger_company_accountsActiveRecord::Relation<LedgerCompanyAccount>

Returns the associated ledger company accounts.

Returns:



83
# File 'app/models/company.rb', line 83

has_many :ledger_company_accounts

#ledger_transactionsActiveRecord::Relation<LedgerTransaction>

Returns the associated ledger transactions.

Returns:



85
# File 'app/models/company.rb', line 85

has_many :ledger_transactions

#localeSymbol

I18n locale that matches the company entity. Falls back to
en-US for the placeholder IND row.

Returns:

  • (Symbol)


266
267
268
# File 'app/models/company.rb', line 266

def locale
  { USA => :'en-US', CAN => :'en-CA', NLD => :'nl-NL' }[id] || :'en-US'
end

#netherland?Boolean

Returns whether the record netherland.

Returns:

  • (Boolean)

    whether the record netherland



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

def netherland?
  id == NLD
end

#next_business_day(date) ⇒ Date

Find the next business day on or after the given date
Skips weekends and company holidays

Parameters:

  • date (Date)

    Starting date

Returns:

  • (Date)

    The next business day on or after the given date



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'app/models/company.rb', line 192

def next_business_day(date)
  # Build the midnight Time in the warehouse zone, not the system
  # zone. `Date#to_time` uses the process's local TZ, so on a UTC
  # server it shifts the input back a day before
  # `WorkingHours.advance_to_working_time` can act on it.
  time = Time.find_zone!(working_hours_timezone).local(date.year, date.month, date.day)

  with_working_hours_config do
    if WorkingHours.working_day?(date)
      date
    else
      WorkingHours.advance_to_working_time(time).to_date
    end
  end
end

#next_valid_ship_dateDate

Calculate the next valid ship date for this company
Accounts for weekends and company holidays

Returns:

  • (Date)

    The next valid date we can ship



175
176
177
178
179
180
181
182
183
184
185
# File 'app/models/company.rb', line 175

def next_valid_ship_date
  now = Time.current.in_time_zone(working_hours_timezone)

  with_working_hours_config do
    if now.in_working_hours?
      now.to_date
    else
      WorkingHours.advance_to_working_time(now).to_date
    end
  end
end

#open_on_a_day?(date) ⇒ Boolean

Returns whether the record open on a day.

Parameters:

  • date (Date)

    the date

Returns:

  • (Boolean)

    whether the record open on a day



117
118
119
# File 'app/models/company.rb', line 117

def open_on_a_day?(date)
  company_holidays.where(holiday_date: date).exists?
end

#outgoing_paymentsActiveRecord::Relation<OutgoingPayment>

Returns the associated outgoing payments.

Returns:

  • (ActiveRecord::Relation<OutgoingPayment>)

    the associated outgoing payments



91
# File 'app/models/company.rb', line 91

has_many :outgoing_payments

#parent_companyCompany?

Returns the parent company this record belongs to.

Returns:

  • (Company, nil)

    the parent company this record belongs to



55
# File 'app/models/company.rb', line 55

belongs_to :parent_company, class_name: 'Company', optional: true

#paypal_bank_accountBankAccount?

Returns the associated paypal bank account.

Returns:

  • (BankAccount, nil)

    the associated paypal bank account



76
# File 'app/models/company.rb', line 76

has_one :paypal_bank_account, class_name: 'BankAccount', foreign_key: 'paypal_company_id'

#rma_inspect_emailObject

at some point these can be database editable fields



320
321
322
323
324
325
326
327
# File 'app/models/company.rb', line 320

def rma_inspect_email
  case id
  when CAN
    'rma_inspection_can@warmlyyours.com'
  else
    'rma_inspection_usa@warmlyyours.com'
  end
end

#rmasActiveRecord::Relation<Rma>

Returns the associated rmas.

Returns:

  • (ActiveRecord::Relation<Rma>)

    the associated rmas



89
# File 'app/models/company.rb', line 89

has_many :rmas

#sales_business_unitBusinessUnit?

Returns the sales business unit this record belongs to.

Returns:

  • (BusinessUnit, nil)

    the sales business unit this record belongs to



63
# File 'app/models/company.rb', line 63

belongs_to :sales_business_unit, class_name: 'BusinessUnit', optional: true

#schema_dot_org_aggregate_ratingObject

Alias for Class#schema_dot_org_aggregate_rating

Returns:

  • (Object)

    Class#schema_dot_org_aggregate_rating

See Also:



337
# File 'app/models/company.rb', line 337

delegate :schema_dot_org_aggregate_rating, to: :class

#storesActiveRecord::Relation<Store>

Returns the associated stores.

Returns:

  • (ActiveRecord::Relation<Store>)

    the associated stores



79
# File 'app/models/company.rb', line 79

has_many :stores

#summary_nameString Also known as: to_s

"<number> <name>" label used in breadcrumbs and dropdowns.

Returns:

  • (String)


272
273
274
# File 'app/models/company.rb', line 272

def summary_name
  "#{number} #{name}"
end

#supplierSupplier?

Returns the associated supplier.

Returns:

  • (Supplier, nil)

    the associated supplier



72
# File 'app/models/company.rb', line 72

has_one :supplier

#tax_ratesActiveRecord::Relation<TaxRate>

Returns the associated tax rates.

Returns:

  • (ActiveRecord::Relation<TaxRate>)

    the associated tax rates



81
# File 'app/models/company.rb', line 81

has_many :tax_rates

#usa?Boolean

Returns whether the record usa.

Returns:

  • (Boolean)

    whether the record usa



305
306
307
# File 'app/models/company.rb', line 305

def usa?
  id == USA
end

#with_working_hours_config { ... } ⇒ Object

Execute a block with working hours configured for this company's holidays and timezone
This is the preferred way to do business day calculations

Examples:

Company.find(1).with_working_hours_config do
  1.working.day.from_now
end

Yields:

  • Block to execute with configured working hours

Returns:

  • (Object)

    Result of the block



156
157
158
159
160
161
162
163
# File 'app/models/company.rb', line 156

def with_working_hours_config(&)
  WorkingHours::Config.with_config(
    working_hours: WORKING_HOURS_CONFIG,
    holidays: holiday_dates,
    time_zone: working_hours_timezone,
    &
  )
end

#working_day?(date) ⇒ Boolean

Check if a given date is a working day for this company

Parameters:

  • date (Date)

    The date to check

Returns:

  • (Boolean)

    true if the date is a working day



212
213
214
215
216
# File 'app/models/company.rb', line 212

def working_day?(date)
  with_working_hours_config do
    WorkingHours.working_day?(date)
  end
end

#working_hours_timezoneString

Get the timezone for this company's warehouse operations

Returns:

  • (String)

    IANA timezone identifier



142
143
144
# File 'app/models/company.rb', line 142

def working_hours_timezone
  WORKING_HOURS_TIMEZONES[id] || 'America/Chicago'
end