company 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +37 -1
- data/README.md +114 -6
- data/lib/company/account.rb +22 -0
- data/lib/company/collection.rb +27 -0
- data/lib/company/collections/leads.rb +18 -0
- data/lib/company/error.rb +5 -0
- data/lib/company/errors/throttled.rb +6 -0
- data/lib/company/phone.rb +14 -0
- data/lib/company/resource.rb +42 -0
- data/lib/company/resources/business.rb +24 -0
- data/lib/company/resources/customer.rb +19 -0
- data/lib/company/resources/job.rb +34 -0
- data/lib/company/resources/lead.rb +10 -0
- data/lib/company/resources/line.rb +25 -0
- data/lib/company/resources/location.rb +25 -0
- data/lib/company/resources/quote.rb +10 -0
- data/lib/company/resources/visit.rb +22 -0
- data/lib/company/version.rb +2 -1
- data/lib/company.rb +27 -3
- metadata +34 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ae0aefa370989a019e2e09cc7b18c0eab239914cdc106a4fbea78dddb9941baf
|
|
4
|
+
data.tar.gz: d43aae90b600142a52a959f1e718ea22426d61efd72c5cbc859bd7a03cc7edae
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 014dca594934dcf80080b7ca2ebccc6bcb4cfcfe7fe35cda73368dad8cca899f990f4dd77d00e0dfae67f3d5d8d23bee12416c0391d35ea0e9e7ebf975875589
|
|
7
|
+
data.tar.gz: 1e6d83ff533ee4ac149fb29bed71fe64be5464b383a0d33fa2e7cd8b5a1ac6a17956fab91074c97f5f18bf6474d4406b265488077a622e9f1feb37282cd911bc
|
data/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,43 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
For more information about changelogs, check [Keep a Changelog](http://keepachangelog.com) and
|
|
6
6
|
[Vandamme](http://tech-angels.github.io/vandamme).
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## 1.0.0 - 2026-09-09
|
|
9
|
+
|
|
10
|
+
* [Feature] `Company::Account`, the gateway a set of credentials opens: a gem subclasses it
|
|
11
|
+
and answers `business`, `jobs`, `visits` and `leads` as its platform
|
|
12
|
+
offers them; a reader left out raises `NotImplementedError` naming the gem and the reader
|
|
13
|
+
* [Feature] `Company::Collection`, what a list of an account's records is: `Enumerable`, and
|
|
14
|
+
narrowed to a window measured from now by `upcoming(within)` and `past(within)`, both built
|
|
15
|
+
on the `between(from, to)` a gem answers; `ids` walks it for the IDs unless a gem knows a
|
|
16
|
+
cheaper way
|
|
17
|
+
* [Feature] `Company::Leads`, what files a lead: `create` takes the same words on every
|
|
18
|
+
platform -- name, surname, phone, email, address, description, notes, source -- and a gem
|
|
19
|
+
drops what its platform has no field for
|
|
20
|
+
* [Feature] `Company::Business` -- id, name, phone, subsidiaries -- who the credentials belong to.
|
|
21
|
+
The subsidiaries are the business itself and then every business under it at any depth,
|
|
22
|
+
flat, so the list is never empty; a gem whose platform nests them answers the ones directly
|
|
23
|
+
under it from the private `below`
|
|
24
|
+
* [Feature] `Company::Lead` -- id, customer -- and `Company::Quote` -- id, amount
|
|
25
|
+
* [Feature] `Company::Job` -- id, quote, description, notes, created_at, scheduled_at,
|
|
26
|
+
completed_at, amount, lines, location. The moments answer as Times however the platform
|
|
27
|
+
wrote them and the amounts as dollars in a BigDecimal
|
|
28
|
+
* [Feature] `Company::Line` -- id, name, description, quantity, amount -- a whole quantity read
|
|
29
|
+
whole
|
|
30
|
+
* [Feature] `Company::Visit` -- id, description, starts_at, ends_at, anytime?, job
|
|
31
|
+
* [Feature] `Company::Location` -- id, street, city, zip, latitude, longitude, customer -- and
|
|
32
|
+
`Company::Customer` -- id, name, surname, email, phone
|
|
33
|
+
* [Feature] A record reads the node its platform answered under either kind of key; a kind
|
|
34
|
+
names its attributes, a subclass names under `.keys` the node keys its platform spells
|
|
35
|
+
otherwise -- `{ phone: :phone_number }` -- and `node_keys` answers them through the map, so
|
|
36
|
+
a gem builds its query from what the vocabulary reads
|
|
37
|
+
* [Feature] A phone answers as the ten digits a North American number is, however the platform
|
|
38
|
+
wrote it, and as nil where none is held or none can be dialed
|
|
39
|
+
* [Feature] `Company::Error`, what every error a platform gem raises descends from, and
|
|
40
|
+
`Company::Throttled`, what a platform raises where it holds a request to a rate and answers
|
|
41
|
+
the same question a little later
|
|
42
|
+
|
|
43
|
+
The draft that had `Company` included the way `Enumerable` is, answering `read`, with an
|
|
44
|
+
`Account` record and a `Company::Mock`, never shipped.
|
|
9
45
|
|
|
10
46
|
## 0.1.0 - 2026-08-28
|
|
11
47
|
|
data/README.md
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# Company
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
One vocabulary for the records a field-service business keeps, whichever platform keeps them.
|
|
4
|
+
An account opens the business behind a set of credentials and the jobs, visits and leads it
|
|
5
|
+
holds; a gem that speaks to one platform subclasses `Company::Account` and the kinds it answers
|
|
6
|
+
for, and every reader is named here, once.
|
|
4
7
|
|
|
5
8
|
## How to install
|
|
6
9
|
|
|
@@ -10,16 +13,121 @@ To install on your system, run
|
|
|
10
13
|
|
|
11
14
|
To use inside a bundled Ruby project, add this line to the `Gemfile`:
|
|
12
15
|
|
|
13
|
-
gem 'company', '~>
|
|
16
|
+
gem 'company', '~> 1.0'
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
Semantic Versioning promises that `~> major.minor` never crosses a breaking change, so the pin
|
|
19
|
+
takes every 1.x release and stops short of 2.0.
|
|
20
|
+
|
|
21
|
+
## How it reads
|
|
22
|
+
|
|
23
|
+
Given a gem that answers the vocabulary -- call it Acme -- a caller never learns how Acme's
|
|
24
|
+
platform spells a field, pages a list or counts its money:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
account = Acme::Account.new credentials # => #<Acme::Account>
|
|
28
|
+
|
|
29
|
+
business = account.business # => #<Acme::Business>
|
|
30
|
+
business.id # => '42'
|
|
31
|
+
business.name # => 'Acme, Inc.'
|
|
32
|
+
business.phone # => '5555555555'
|
|
33
|
+
business.subsidiaries # => [#<Acme::Business>, ...]
|
|
34
|
+
|
|
35
|
+
jobs = account.jobs.past(3.months) # => #<Acme::Jobs>
|
|
36
|
+
jobs.ids # => ['j1', 'j2', ...]
|
|
37
|
+
|
|
38
|
+
job = jobs.first
|
|
39
|
+
job.id # => 'j1'
|
|
40
|
+
job.description # => 'Repair'
|
|
41
|
+
job.notes # => 'Ring twice'
|
|
42
|
+
job.created_at # => 2026-09-03 06:00:00 UTC
|
|
43
|
+
job.scheduled_at # => 2026-09-10 01:00:00 UTC
|
|
44
|
+
job.completed_at # => nil
|
|
45
|
+
job.amount # => 19.99
|
|
46
|
+
|
|
47
|
+
quote = job.quote # => #<Acme::Quote>, or nil where the job was won without one
|
|
48
|
+
quote.id # => 'q1'
|
|
49
|
+
quote.amount # => 24.99
|
|
50
|
+
|
|
51
|
+
line = job.lines.first # => #<Acme::Line>
|
|
52
|
+
line.id # => 'l1'
|
|
53
|
+
line.name # => 'Labor'
|
|
54
|
+
line.description # => 'Hours to fix the issue'
|
|
55
|
+
line.quantity # => 1.5
|
|
56
|
+
line.amount # => 24.99
|
|
57
|
+
|
|
58
|
+
location = job.location # => #<Acme::Location>
|
|
59
|
+
location.id # => 'a1'
|
|
60
|
+
location.street # => '100 Acme Circle'
|
|
61
|
+
location.city # => 'Springfield'
|
|
62
|
+
location.zip # => '98920'
|
|
63
|
+
location.latitude # => 45.2335
|
|
64
|
+
location.longitude # => -9.1234
|
|
65
|
+
|
|
66
|
+
customer = location.customer # => #<Acme::Customer>
|
|
67
|
+
customer.id # => 'c1'
|
|
68
|
+
customer.name # => 'Jane'
|
|
69
|
+
customer.surname # => 'Qi'
|
|
70
|
+
customer.email # => 'jane@example.com'
|
|
71
|
+
customer.phone # => '5555555666'
|
|
72
|
+
|
|
73
|
+
visits = account.visits.upcoming(1.day) # => #<Acme::Visits>
|
|
74
|
+
visits.ids # => ['v1', 'v2', ...]
|
|
75
|
+
|
|
76
|
+
visit = visits.first # => #<Acme::Visit>
|
|
77
|
+
visit.id # => 'v1'
|
|
78
|
+
visit.description # => 'Service appointment'
|
|
79
|
+
visit.starts_at # => 2026-09-10 01:00:00 UTC
|
|
80
|
+
visit.ends_at # => 2026-09-10 02:00:00 UTC
|
|
81
|
+
visit.anytime? # => false
|
|
82
|
+
visit.job # => job
|
|
83
|
+
|
|
84
|
+
lead = account.leads.create name: 'Jane', surname: 'Qi', phone: '5555555666',
|
|
85
|
+
email: 'jane@example.com', address: { street: '100 Acme Circle', zip: '98920' },
|
|
86
|
+
description: 'Repair', notes: 'Estimate $20–$30', source: 'Website'
|
|
87
|
+
lead.id # => 'd1'
|
|
88
|
+
lead.customer # => #<Acme::Customer>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
A moment reads as a `Time`, an amount as dollars in a `BigDecimal`, a phone as the ten digits to
|
|
92
|
+
dial, and a field the platform holds nothing for as nil. A list is walked a page at a time, as
|
|
93
|
+
far as it goes or narrowed to a window measured from now.
|
|
94
|
+
|
|
95
|
+
## Answering as a gem
|
|
96
|
+
|
|
97
|
+
A gem subclasses `Company::Account` and answers the readers its platform offers, and subclasses
|
|
98
|
+
a kind wherever its platform spells a key otherwise than the vocabulary:
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
class Acme::Account < Company::Account
|
|
102
|
+
def business = Business.new node: read('company')
|
|
103
|
+
def jobs = Jobs.new account: self
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
class Acme::Business < Company::Business
|
|
107
|
+
def self.keys = { phone: :phone_number }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
class Acme::Jobs < Company::Collection
|
|
111
|
+
def each = ... # walk the platform's pages, yielding a Job each
|
|
112
|
+
def between(from, to) = ... # the same list, narrowed to what starts between the two
|
|
113
|
+
end
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`Company::Business.node_keys` then answers `[:id, :name, :phone_number]`: exactly what to ask
|
|
117
|
+
the platform for. A reader the gem leaves out raises `NotImplementedError` naming the gem; leads
|
|
118
|
+
it leaves out refuse to file one. The least a gem writes is under `test/acme`, and the test that
|
|
119
|
+
runs every reader through it, `test/company/acme_test.rb`, reads as a tutorial.
|
|
120
|
+
|
|
121
|
+
## Errors
|
|
122
|
+
|
|
123
|
+
Every error a gem raises descends from `Company::Error`, so one rescue catches the lot, and one
|
|
124
|
+
for a request held to a rate from `Company::Throttled`, so one retry covers every platform.
|
|
18
125
|
|
|
19
126
|
## Development
|
|
20
127
|
|
|
21
128
|
`bin/setup` gets a clone working, `bin/console` opens a prompt with the library loaded, and
|
|
22
|
-
`bundle exec rake` runs the suite and the
|
|
129
|
+
`bundle exec rake` runs the suite, the linter and the two size limits -- which is what CI runs
|
|
130
|
+
too.
|
|
23
131
|
|
|
24
132
|
## Reference
|
|
25
133
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Company
|
|
2
|
+
# The gateway a set of credentials opens on a platform: the business they belong to and the
|
|
3
|
+
# records it holds. A gem subclasses it and answers what its platform offers; a reader it
|
|
4
|
+
# leaves out raises NotImplementedError, and the leads it leaves out refuse to file one.
|
|
5
|
+
class Account
|
|
6
|
+
# @return [Business] business the credentials belong to.
|
|
7
|
+
def business = unanswered :business
|
|
8
|
+
|
|
9
|
+
# @return [Collection] jobs of the business, each a {Job}; a platform may also `find` one.
|
|
10
|
+
def jobs = unanswered :jobs
|
|
11
|
+
|
|
12
|
+
# @return [Collection] visits of the business, each a {Visit}; a platform may also `find` one.
|
|
13
|
+
def visits = unanswered :visits
|
|
14
|
+
|
|
15
|
+
# @return [Leads] leads of the business: `create` files one, where the platform takes leads.
|
|
16
|
+
def leads = Leads.new
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def unanswered(name) = raise NotImplementedError, "#{self.class} does not answer #{name}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Company
|
|
2
|
+
# A list of records an account holds, walked however the platform pages it and narrowed to a
|
|
3
|
+
# window measured from now. A gem answers `each`, and `between` with the same list narrowed
|
|
4
|
+
# to what starts between two moments, either one open.
|
|
5
|
+
class Collection
|
|
6
|
+
include Enumerable
|
|
7
|
+
|
|
8
|
+
# Both ends are measured from one moment, so nothing slides between them.
|
|
9
|
+
# @param within [ActiveSupport::Duration, nil] how far ahead to look, or as far as there is.
|
|
10
|
+
# @return [Collection] the same list, narrowed to what starts from now on.
|
|
11
|
+
def upcoming(within = nil)
|
|
12
|
+
now = Time.now
|
|
13
|
+
between now, within && now + within
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @param within [ActiveSupport::Duration, nil] how far back to look, or as far as there is.
|
|
17
|
+
# @return [Collection] the same list, narrowed to what started before now.
|
|
18
|
+
def past(within = nil)
|
|
19
|
+
now = Time.now
|
|
20
|
+
between within && now - within, now
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @return [Array<String>] ID of every record, the list walked; a platform with a cheaper
|
|
24
|
+
# way to ask answers it that way instead.
|
|
25
|
+
def ids = map(&:id)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Company
|
|
2
|
+
# The leads of a business: who asked it for work, before there is a job. A gem answers
|
|
3
|
+
# `create` with the lead its platform filed, dropping what the platform has no field for.
|
|
4
|
+
class Leads
|
|
5
|
+
# @param name [String] given name of who asked, or the business's where a person has none.
|
|
6
|
+
# @param surname [String, nil] their surname.
|
|
7
|
+
# @param phone [String, nil] number they are reached on.
|
|
8
|
+
# @param email [String, nil] address they are written to.
|
|
9
|
+
# @param address [Hash, nil] where the work would happen: :street, :city, :state and :zip.
|
|
10
|
+
# @param description [String] what the work is called.
|
|
11
|
+
# @param notes [String, nil] what else was said about it.
|
|
12
|
+
# @param source [String, nil] where the lead came from, as the business names its sources.
|
|
13
|
+
# @return [Lead] the lead as the platform filed it.
|
|
14
|
+
def create(name:, surname:, phone:, email:, address:, description:, notes:, source:)
|
|
15
|
+
raise NotImplementedError, "#{self.class} does not file a lead"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Company
|
|
2
|
+
# The ten digits a North American number is, whatever punctuation it arrived wearing.
|
|
3
|
+
class Phone
|
|
4
|
+
# Ten digits whose area code and exchange open with 2 through 9.
|
|
5
|
+
NANP = /\A[2-9]\d{2}[2-9]\d{6}\z/
|
|
6
|
+
|
|
7
|
+
# @param number [String, nil] number as the platform holds it.
|
|
8
|
+
# @return [String, nil] ten digits to dial, or nil where none is held or none can be dialed.
|
|
9
|
+
def self.from(number)
|
|
10
|
+
digits = number.to_s.delete('^0-9').delete_prefix '1'
|
|
11
|
+
digits if NANP.match? digits
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Company
|
|
2
|
+
# What every record shares: the node the platform answered it as. Built by the gem that read
|
|
3
|
+
# it, never by a caller.
|
|
4
|
+
class Resource
|
|
5
|
+
# The node keys the platform spells otherwise than the vocabulary, by the reader they
|
|
6
|
+
# answer: `{ phone: :phone_number }` reads {Business#phone} off `phone_number`. A gem
|
|
7
|
+
# declares its own; a reader left out reads the key of its own name.
|
|
8
|
+
# @return [Hash] reader-to-key exceptions, empty where every name is its key.
|
|
9
|
+
def self.keys = {}
|
|
10
|
+
|
|
11
|
+
# The keys a platform is expected to answer a node with: each attribute the kind reads,
|
|
12
|
+
# through {.keys}, so a gem asks its platform for exactly these and writes none by hand.
|
|
13
|
+
# @return [Array<Symbol>] node keys, in the order the attributes are read.
|
|
14
|
+
def self.node_keys = attributes.map { |name| keys.fetch name, name }
|
|
15
|
+
|
|
16
|
+
# @param node [Hash] record as the platform answered it, under either kind of key.
|
|
17
|
+
def initialize(node: {})
|
|
18
|
+
@node = node.with_indifferent_access
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @return [String] ID the platform files the record under.
|
|
22
|
+
def id = attribute :id
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def attribute(name) = @node[self.class.keys.fetch(name, name)]
|
|
27
|
+
|
|
28
|
+
def time(name)
|
|
29
|
+
value = attribute name
|
|
30
|
+
value.is_a?(String) ? (Time.iso8601 value if value.present?) : value
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def decimal(name)
|
|
34
|
+
value = attribute name
|
|
35
|
+
BigDecimal value.to_s if value.present?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def record(type, key) = (type.new node: @node[key] if @node[key])
|
|
39
|
+
|
|
40
|
+
def records(type, key) = Array(@node[key]).map { |each| type.new node: each }
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Company
|
|
2
|
+
# Who the account belongs to: the one business behind a set of credentials.
|
|
3
|
+
class Business < Resource
|
|
4
|
+
# What every business reads, by the vocabulary's names.
|
|
5
|
+
def self.attributes = %i[id name phone]
|
|
6
|
+
|
|
7
|
+
# @return [String] business name.
|
|
8
|
+
def name = attribute :name
|
|
9
|
+
|
|
10
|
+
# @return [String, nil] ten digits the business is reached on, nil where none can be dialed.
|
|
11
|
+
def phone = Phone.from attribute(:phone)
|
|
12
|
+
|
|
13
|
+
# Itself first, then every business under it at any depth, in the order the platform lists
|
|
14
|
+
# them: a business on its own is its own one subsidiary, and a franchise reads flat. A gem
|
|
15
|
+
# whose platform nests businesses answers the ones directly under this one from the private
|
|
16
|
+
# `below`; the rest inherit none.
|
|
17
|
+
# @return [Array<Business>] businesses an account may act as, this one included.
|
|
18
|
+
def subsidiaries = [ self, *below.flat_map(&:subsidiaries) ]
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def below = []
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Company
|
|
2
|
+
# A person the business works for.
|
|
3
|
+
class Customer < Resource
|
|
4
|
+
# What every customer reads, by the vocabulary's names.
|
|
5
|
+
def self.attributes = %i[id name surname email phone]
|
|
6
|
+
|
|
7
|
+
# @return [String, nil] what they go by: a given name, or a business's where a person has none.
|
|
8
|
+
def name = attribute :name
|
|
9
|
+
|
|
10
|
+
# @return [String, nil] their surname.
|
|
11
|
+
def surname = attribute :surname
|
|
12
|
+
|
|
13
|
+
# @return [String, nil] address they are written to.
|
|
14
|
+
def email = attribute :email
|
|
15
|
+
|
|
16
|
+
# @return [String, nil] ten digits they are reached on, nil where none can be dialed.
|
|
17
|
+
def phone = Phone.from attribute(:phone)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Company
|
|
2
|
+
# Work the business accepted and scheduled.
|
|
3
|
+
class Job < Resource
|
|
4
|
+
# What every job reads, by the vocabulary's names.
|
|
5
|
+
def self.attributes = %i[id description notes created_at scheduled_at completed_at amount]
|
|
6
|
+
|
|
7
|
+
# @return [String, nil] what the work is called, in the words of whoever opened the job.
|
|
8
|
+
def description = attribute :description
|
|
9
|
+
|
|
10
|
+
# @return [String, nil] what was written on the job for the crew to mind.
|
|
11
|
+
def notes = attribute :notes
|
|
12
|
+
|
|
13
|
+
# @return [Time] moment the job was opened.
|
|
14
|
+
def created_at = time :created_at
|
|
15
|
+
|
|
16
|
+
# @return [Time, nil] moment the work is booked for.
|
|
17
|
+
def scheduled_at = time :scheduled_at
|
|
18
|
+
|
|
19
|
+
# @return [Time, nil] moment the work was finished.
|
|
20
|
+
def completed_at = time :completed_at
|
|
21
|
+
|
|
22
|
+
# @return [BigDecimal, nil] what the job comes to, in dollars.
|
|
23
|
+
def amount = decimal :amount
|
|
24
|
+
|
|
25
|
+
# @return [Quote, nil] quote the job was won with, where it came back beside the job.
|
|
26
|
+
def quote = record Quote, :quote
|
|
27
|
+
|
|
28
|
+
# @return [Array<Line>] lines the work is billed as, empty where none came back.
|
|
29
|
+
def lines = records Line, :lines
|
|
30
|
+
|
|
31
|
+
# @return [Location, nil] where the work happens, where it came back beside the job.
|
|
32
|
+
def location = record Location, :location
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module Company
|
|
2
|
+
# Somebody who asked the business for work and is not a customer yet.
|
|
3
|
+
class Lead < Resource
|
|
4
|
+
# What every lead reads, by the vocabulary's names.
|
|
5
|
+
def self.attributes = %i[id]
|
|
6
|
+
|
|
7
|
+
# @return [Customer, nil] customer the lead was filed for, where they came back beside it.
|
|
8
|
+
def customer = record Customer, :customer
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Company
|
|
2
|
+
# One line of a document: how many of a thing, what it is called, and what it comes to.
|
|
3
|
+
class Line < Resource
|
|
4
|
+
# What every line reads, by the vocabulary's names.
|
|
5
|
+
def self.attributes = %i[id name description quantity amount]
|
|
6
|
+
|
|
7
|
+
# @return [String, nil] what the line is called.
|
|
8
|
+
def name = attribute :name
|
|
9
|
+
|
|
10
|
+
# @return [String, nil] what the line says beyond what it is called.
|
|
11
|
+
def description = attribute :description
|
|
12
|
+
|
|
13
|
+
# A whole quantity reads as an Integer -- `3 Faucets` rather than `3.0 Faucets` -- and a
|
|
14
|
+
# fraction keeps its point, since rounding it would lie about what was billed.
|
|
15
|
+
# @return [Integer, Float, nil] how many of it the document is for.
|
|
16
|
+
def quantity = whole attribute(:quantity)
|
|
17
|
+
|
|
18
|
+
# @return [BigDecimal, nil] what the line comes to, in dollars.
|
|
19
|
+
def amount = decimal :amount
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def whole(number) = number && ((number % 1).zero? ? number.to_i : number)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Company
|
|
2
|
+
# Where work happens: one address the business drives to.
|
|
3
|
+
class Location < Resource
|
|
4
|
+
# What every location reads, by the vocabulary's names.
|
|
5
|
+
def self.attributes = %i[id street city zip latitude longitude]
|
|
6
|
+
|
|
7
|
+
# @return [String, nil] street, number included.
|
|
8
|
+
def street = attribute :street
|
|
9
|
+
|
|
10
|
+
# @return [String, nil] town.
|
|
11
|
+
def city = attribute :city
|
|
12
|
+
|
|
13
|
+
# @return [String, nil] ZIP code.
|
|
14
|
+
def zip = attribute :zip
|
|
15
|
+
|
|
16
|
+
# @return [Numeric, nil] how far north the place is.
|
|
17
|
+
def latitude = attribute :latitude
|
|
18
|
+
|
|
19
|
+
# @return [Numeric, nil] how far east the place is.
|
|
20
|
+
def longitude = attribute :longitude
|
|
21
|
+
|
|
22
|
+
# @return [Customer, nil] whose place it is, where they came back beside the location.
|
|
23
|
+
def customer = record Customer, :customer
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module Company
|
|
2
|
+
# A price the business sent a customer, which a job names where it was won with one.
|
|
3
|
+
class Quote < Resource
|
|
4
|
+
# What every quote reads, by the vocabulary's names.
|
|
5
|
+
def self.attributes = %i[id amount]
|
|
6
|
+
|
|
7
|
+
# @return [BigDecimal, nil] what the quote comes to, in dollars.
|
|
8
|
+
def amount = decimal :amount
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Company
|
|
2
|
+
# One stop of a job: when the work is scheduled to happen, where the job does.
|
|
3
|
+
class Visit < Resource
|
|
4
|
+
# What every visit reads, by the vocabulary's names.
|
|
5
|
+
def self.attributes = %i[id description starts_at ends_at anytime]
|
|
6
|
+
|
|
7
|
+
# @return [String, nil] what the stop is, in the words of whoever booked it.
|
|
8
|
+
def description = attribute :description
|
|
9
|
+
|
|
10
|
+
# @return [Time, nil] moment the visit is booked to start.
|
|
11
|
+
def starts_at = time :starts_at
|
|
12
|
+
|
|
13
|
+
# @return [Time, nil] moment the visit is booked to end.
|
|
14
|
+
def ends_at = time :ends_at
|
|
15
|
+
|
|
16
|
+
# @return [Boolean, nil] whether the visit may happen any time that day rather than at an hour.
|
|
17
|
+
def anytime? = attribute :anytime
|
|
18
|
+
|
|
19
|
+
# @return [Job, nil] job the stop belongs to, where it came back beside the visit.
|
|
20
|
+
def job = record Job, :job
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/company/version.rb
CHANGED
data/lib/company.rb
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
|
+
require 'bigdecimal'
|
|
2
|
+
require 'time'
|
|
3
|
+
|
|
1
4
|
require 'company/version'
|
|
2
5
|
|
|
3
|
-
#
|
|
4
|
-
|
|
5
|
-
|
|
6
|
+
# Only the Active Support files whose methods are used: a name a platform holds nothing for
|
|
7
|
+
# arrives as readily empty as null, a node reads under either kind of key, the lines of a job
|
|
8
|
+
# join into one sentence, and a moment plus a duration is a moment.
|
|
9
|
+
require 'active_support'
|
|
10
|
+
require 'active_support/core_ext/object/blank'
|
|
11
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
|
12
|
+
require 'active_support/core_ext/array/conversions'
|
|
13
|
+
require 'active_support/core_ext/numeric/time'
|
|
14
|
+
|
|
15
|
+
require 'company/error'
|
|
16
|
+
require 'company/errors/throttled'
|
|
17
|
+
require 'company/phone'
|
|
18
|
+
require 'company/resource'
|
|
19
|
+
require 'company/collection'
|
|
20
|
+
require 'company/collections/leads'
|
|
21
|
+
require 'company/account'
|
|
22
|
+
require 'company/resources/business'
|
|
23
|
+
require 'company/resources/customer'
|
|
24
|
+
require 'company/resources/location'
|
|
25
|
+
require 'company/resources/line'
|
|
26
|
+
require 'company/resources/lead'
|
|
27
|
+
require 'company/resources/quote'
|
|
28
|
+
require 'company/resources/visit'
|
|
29
|
+
require 'company/resources/job'
|
metadata
CHANGED
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: company
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Claudio Baccigalupo
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
-
dependencies:
|
|
12
|
-
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activesupport
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
description: The records a field-service business holds, whichever platform holds
|
|
27
|
+
them
|
|
13
28
|
email:
|
|
14
29
|
- claudiob@users.noreply.github.com
|
|
15
30
|
executables: []
|
|
@@ -20,6 +35,21 @@ files:
|
|
|
20
35
|
- LICENSE.txt
|
|
21
36
|
- README.md
|
|
22
37
|
- lib/company.rb
|
|
38
|
+
- lib/company/account.rb
|
|
39
|
+
- lib/company/collection.rb
|
|
40
|
+
- lib/company/collections/leads.rb
|
|
41
|
+
- lib/company/error.rb
|
|
42
|
+
- lib/company/errors/throttled.rb
|
|
43
|
+
- lib/company/phone.rb
|
|
44
|
+
- lib/company/resource.rb
|
|
45
|
+
- lib/company/resources/business.rb
|
|
46
|
+
- lib/company/resources/customer.rb
|
|
47
|
+
- lib/company/resources/job.rb
|
|
48
|
+
- lib/company/resources/lead.rb
|
|
49
|
+
- lib/company/resources/line.rb
|
|
50
|
+
- lib/company/resources/location.rb
|
|
51
|
+
- lib/company/resources/quote.rb
|
|
52
|
+
- lib/company/resources/visit.rb
|
|
23
53
|
- lib/company/version.rb
|
|
24
54
|
homepage: https://github.com/claudiob/company
|
|
25
55
|
licenses:
|
|
@@ -36,7 +66,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
36
66
|
requirements:
|
|
37
67
|
- - ">="
|
|
38
68
|
- !ruby/object:Gem::Version
|
|
39
|
-
version:
|
|
69
|
+
version: 3.2.0
|
|
40
70
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
41
71
|
requirements:
|
|
42
72
|
- - ">="
|