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 Schedulable

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 Schedulable

config

Methods included from Models::AfterCommittable

#after_commit

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#currencyObject (readonly)



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

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

#default_email_senderObject (readonly)



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

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

#nameObject (readonly)



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

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

#numberObject (readonly)



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

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

#short_nameObject (readonly)



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

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:



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

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:



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

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:



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

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)


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

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


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

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


219
220
221
# File 'app/models/company.rb', line 219

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

Instance Method Details

#accounting_business_unitBusinessUnit



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

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

#activity_type_assignment_queuesActiveRecord::Relation<ActivityTypeAssignmentQueue>

Returns:

See Also:



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

has_many :activity_type_assignment_queues, inverse_of: :company

#add_business_days(from, business_days) ⇒ Date

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

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.

Parameters:

  • from (Date, Time)

    starting point

  • business_days (Integer)

    number of working days to advance

Returns:

  • (Date)


203
204
205
206
207
# File 'app/models/company.rb', line 203

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:

See Also:



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

belongs_to :address, optional: true

#administration_business_unitBusinessUnit



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

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



323
324
325
# File 'app/models/company.rb', line 323

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



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

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

#budgetsActiveRecord::Relation<Budget>

Returns:

  • (ActiveRecord::Relation<Budget>)

See Also:



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

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:



264
265
266
267
268
269
270
271
272
273
274
275
# File 'app/models/company.rb', line 264

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:

See Also:



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

has_many :business_units

#canada?Boolean

Returns:

  • (Boolean)


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

def canada?
  id == CAN
end

#catalogsActiveRecord::Relation<Catalog>

Returns:

  • (ActiveRecord::Relation<Catalog>)

See Also:



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

has_many :catalogs, inverse_of: :company

#cc_bank_accountBankAccount



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

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

#company_holidaysActiveRecord::Relation<CompanyHoliday>

Returns:

See Also:



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

has_many :company_holidays, dependent: :destroy

#company_labelString

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

Returns:

  • (String)


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

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

#countryCountry

Returns:

See Also:



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

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)


226
227
228
# File 'app/models/company.rb', line 226

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)


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

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

#customerCustomer

Returns:

See Also:



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

has_one :customer

#default_business_unitBusinessUnit



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

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

#email_templatesActiveRecord::Relation<EmailTemplate>

Returns:

See Also:



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

has_many :email_templates, as: :resource

#holiday_datesArray<Date>

Get all holiday dates for this company

Returns:

  • (Array<Date>)

    List of holiday dates



140
141
142
# File 'app/models/company.rb', line 140

def holiday_dates
  company_holidays.pluck(:holiday_date)
end

#invoicesActiveRecord::Relation<Invoice>

Returns:

  • (ActiveRecord::Relation<Invoice>)

See Also:



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

has_many :invoices

#ledger_company_accountsActiveRecord::Relation<LedgerCompanyAccount>

Returns:

See Also:



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

has_many :ledger_company_accounts

#ledger_transactionsActiveRecord::Relation<LedgerTransaction>

Returns:

See Also:



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

has_many :ledger_transactions

#localeSymbol

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

Returns:

  • (Symbol)


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

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

#netherland?Boolean

Returns:

  • (Boolean)


285
286
287
# File 'app/models/company.rb', line 285

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



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'app/models/company.rb', line 165

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



148
149
150
151
152
153
154
155
156
157
158
# File 'app/models/company.rb', line 148

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:

  • (Boolean)


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

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

#outgoing_paymentsActiveRecord::Relation<OutgoingPayment>

Returns:

See Also:



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

has_many :outgoing_payments

#parent_companyCompany

Returns:

See Also:



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

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

#paypal_bank_accountBankAccount



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

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



290
291
292
293
294
295
296
297
# File 'app/models/company.rb', line 290

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:

  • (ActiveRecord::Relation<Rma>)

See Also:



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

has_many :rmas

#sales_business_unitBusinessUnit



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

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:



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

delegate :schema_dot_org_aggregate_rating, to: :class

#storesActiveRecord::Relation<Store>

Returns:

  • (ActiveRecord::Relation<Store>)

See Also:



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

has_many :stores

#summary_nameString Also known as: to_s

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

Returns:

  • (String)


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

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

#supplierSupplier

Returns:

See Also:



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

has_one :supplier

#tax_ratesActiveRecord::Relation<TaxRate>

Returns:

  • (ActiveRecord::Relation<TaxRate>)

See Also:



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

has_many :tax_rates

#usa?Boolean

Returns:

  • (Boolean)


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

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



129
130
131
132
133
134
135
136
# File 'app/models/company.rb', line 129

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



185
186
187
188
189
# File 'app/models/company.rb', line 185

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



115
116
117
# File 'app/models/company.rb', line 115

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