moco-ruby 1.2.0 → 1.3.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/.rubocop.yml +1 -0
- data/CHANGELOG.md +20 -0
- data/Gemfile.lock +11 -10
- data/README.md +47 -3
- data/lib/moco/client.rb +7 -0
- data/lib/moco/entities/invoice.rb +3 -14
- data/lib/moco/entities/invoice_attachment.rb +34 -0
- data/lib/moco/entities/letter_paper.rb +23 -0
- data/lib/moco/entities/offer.rb +3 -14
- data/lib/moco/entities/offer_attachment.rb +34 -0
- data/lib/moco/entities/project.rb +14 -5
- data/lib/moco/entities/session.rb +58 -0
- data/lib/moco/entities.rb +5 -5
- data/lib/moco/sync.rb +7 -7
- data/lib/moco/version.rb +1 -1
- data/lib/moco.rb +4 -0
- data/sync_activity.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a32219e92cf0b5cc11f885486ec9a0afce63d39d2a2de95f3d5225b5521020e5
|
|
4
|
+
data.tar.gz: fa20563a9ff63d9b412540b99beecdab249ee7dca31608d23a47a82926062f27
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 63995cff5c2e36f43619a43e04208460d8aa3a899f06a8b4e0acc673655af550ac40f472519096a8da21d73d850705a4d4a10d788287eb6cac1a461d614c4139
|
|
7
|
+
data.tar.gz: 7be74a0104d6bfffd3d3bb93f6658db539ec1727e108795f69a63c1b38447d0876eb21187c0557936fa0b70d7457a4150bccd9eed3ba04e8df6f3d32c59179ef
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [1.3.0] - 2026-05-23
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- New entities to cover remaining MOCO API v1 resources: `LetterPaper`
|
|
9
|
+
(read-only letterhead listing), `InvoiceAttachment`, `OfferAttachment`,
|
|
10
|
+
and `Session` (API key exchange/verification).
|
|
11
|
+
- `MOCO::Session.create(subdomain:, email:, password:)` exchanges credentials
|
|
12
|
+
for an API key without requiring an existing `Client`.
|
|
13
|
+
- `moco.session.verify` confirms the configured API key and returns the user
|
|
14
|
+
identity.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- `Invoice#attachments` and `Offer#attachments` now return a
|
|
18
|
+
`NestedCollectionProxy` of typed `InvoiceAttachment` / `OfferAttachment`
|
|
19
|
+
entities, replacing the previous raw `add_attachment` / `delete_attachment`
|
|
20
|
+
helpers. Use `invoice.attachments.create(attachment: { filename:, base64: })`
|
|
21
|
+
and `invoice.attachments.find(id).destroy` instead.
|
|
22
|
+
- Documentation URLs updated from `hundertzehn.github.io/mocoapp-api-docs`
|
|
23
|
+
(legacy) to `docs.mocoapp.com/api/docs/v1` (current OpenAPI reference).
|
|
24
|
+
|
|
5
25
|
## [1.2.0] - 2026-01-14
|
|
6
26
|
|
|
7
27
|
### Added
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
moco-ruby (1.
|
|
4
|
+
moco-ruby (1.3.0)
|
|
5
5
|
activesupport (>= 7.0)
|
|
6
6
|
faraday (>= 2.0)
|
|
7
7
|
fuzzy_match (~> 2.1.0)
|
|
@@ -9,7 +9,7 @@ PATH
|
|
|
9
9
|
GEM
|
|
10
10
|
remote: https://rubygems.org/
|
|
11
11
|
specs:
|
|
12
|
-
activesupport (8.1.
|
|
12
|
+
activesupport (8.1.3)
|
|
13
13
|
base64
|
|
14
14
|
bigdecimal
|
|
15
15
|
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
@@ -22,11 +22,11 @@ GEM
|
|
|
22
22
|
securerandom (>= 0.3)
|
|
23
23
|
tzinfo (~> 2.0, >= 2.0.5)
|
|
24
24
|
uri (>= 0.13.1)
|
|
25
|
-
addressable (2.
|
|
25
|
+
addressable (2.9.0)
|
|
26
26
|
public_suffix (>= 2.0.2, < 8.0)
|
|
27
27
|
ast (2.4.3)
|
|
28
28
|
base64 (0.3.0)
|
|
29
|
-
bigdecimal (4.
|
|
29
|
+
bigdecimal (4.1.2)
|
|
30
30
|
concurrent-ruby (1.3.6)
|
|
31
31
|
connection_pool (3.0.2)
|
|
32
32
|
crack (1.0.1)
|
|
@@ -34,21 +34,22 @@ GEM
|
|
|
34
34
|
rexml
|
|
35
35
|
dotenv (2.8.1)
|
|
36
36
|
drb (2.2.3)
|
|
37
|
-
faraday (2.14.
|
|
37
|
+
faraday (2.14.2)
|
|
38
38
|
faraday-net_http (>= 2.0, < 3.5)
|
|
39
39
|
json
|
|
40
40
|
logger
|
|
41
|
-
faraday-net_http (3.4.
|
|
41
|
+
faraday-net_http (3.4.3)
|
|
42
42
|
net-http (~> 0.5)
|
|
43
43
|
fuzzy_match (2.1.0)
|
|
44
44
|
hashdiff (1.2.1)
|
|
45
45
|
i18n (1.14.8)
|
|
46
46
|
concurrent-ruby (~> 1.0)
|
|
47
|
-
json (2.
|
|
47
|
+
json (2.19.5)
|
|
48
48
|
language_server-protocol (3.17.0.5)
|
|
49
49
|
lint_roller (1.1.0)
|
|
50
50
|
logger (1.7.0)
|
|
51
|
-
minitest (6.0.
|
|
51
|
+
minitest (6.0.6)
|
|
52
|
+
drb (~> 2.0)
|
|
52
53
|
prism (~> 1.5)
|
|
53
54
|
net-http (0.9.1)
|
|
54
55
|
uri (>= 0.11.1)
|
|
@@ -57,8 +58,8 @@ GEM
|
|
|
57
58
|
ast (~> 2.4.1)
|
|
58
59
|
racc
|
|
59
60
|
power_assert (3.0.1)
|
|
60
|
-
prism (1.
|
|
61
|
-
public_suffix (7.0.
|
|
61
|
+
prism (1.9.0)
|
|
62
|
+
public_suffix (7.0.5)
|
|
62
63
|
racc (1.8.1)
|
|
63
64
|
rainbow (3.1.1)
|
|
64
65
|
rake (13.3.1)
|
data/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/rb/moco-ruby)
|
|
4
4
|
|
|
5
|
-
A Ruby Gem to interact with the [MOCO API](https://
|
|
5
|
+
A Ruby Gem to interact with the [MOCO API](https://docs.mocoapp.com/api/docs/v1). This gem provides a modern, Ruby-esque interface (`MOCO::Client`) for interacting with the MOCO API.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -176,6 +176,47 @@ profile = moco.profile
|
|
|
176
176
|
puts "Logged in as: #{profile.firstname} #{profile.lastname}"
|
|
177
177
|
```
|
|
178
178
|
|
|
179
|
+
### Sessions
|
|
180
|
+
|
|
181
|
+
Exchange email/password for an API key, or verify an existing key:
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
# Exchange credentials for an API key (no Client needed)
|
|
185
|
+
session = MOCO::Session.create(
|
|
186
|
+
subdomain: "your-subdomain",
|
|
187
|
+
email: "you@example.com",
|
|
188
|
+
password: "secret"
|
|
189
|
+
)
|
|
190
|
+
api_key = session["api_key"]
|
|
191
|
+
|
|
192
|
+
# Verify the configured API key for an existing client
|
|
193
|
+
identity = moco.session.verify
|
|
194
|
+
puts "Authenticated as user #{identity['id']} (#{identity['uuid']})"
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Invoice / Offer Attachments
|
|
198
|
+
|
|
199
|
+
Attachments are nested under the parent document and use base64-encoded uploads:
|
|
200
|
+
|
|
201
|
+
```ruby
|
|
202
|
+
require "base64"
|
|
203
|
+
|
|
204
|
+
invoice = moco.invoices.find(123)
|
|
205
|
+
invoice.attachments.all
|
|
206
|
+
invoice.attachments.create(
|
|
207
|
+
attachment: {
|
|
208
|
+
filename: "appendix.pdf",
|
|
209
|
+
base64: Base64.strict_encode64(File.read("appendix.pdf"))
|
|
210
|
+
}
|
|
211
|
+
)
|
|
212
|
+
invoice.attachments.find(42).destroy
|
|
213
|
+
|
|
214
|
+
# Offer attachments work the same way:
|
|
215
|
+
moco.offers.find(123).attachments.create(
|
|
216
|
+
attachment: { filename: "quote-details.pdf", base64: ... }
|
|
217
|
+
)
|
|
218
|
+
```
|
|
219
|
+
|
|
179
220
|
### Reports
|
|
180
221
|
|
|
181
222
|
Access read-only report endpoints:
|
|
@@ -212,9 +253,12 @@ The gem supports all MOCO API entities with a Ruby-esque interface:
|
|
|
212
253
|
`InvoiceBookkeepingExport`, `PurchaseBookkeepingExport`
|
|
213
254
|
|
|
214
255
|
**Nested Resources:**
|
|
215
|
-
`Employment`, `WorkTimeAdjustment`, `ProjectContract`, `PaymentSchedule`, `RecurringExpense`, `InvoicePayment`, `InvoiceReminder`, `OfferApproval`
|
|
256
|
+
`Employment`, `WorkTimeAdjustment`, `ProjectContract`, `PaymentSchedule`, `RecurringExpense`, `InvoicePayment`, `InvoiceReminder`, `OfferApproval`, `InvoiceAttachment`, `OfferAttachment`
|
|
257
|
+
|
|
258
|
+
**Misc:**
|
|
259
|
+
`LetterPaper` (read-only), `Session` (for API key exchange/verification)
|
|
216
260
|
|
|
217
|
-
Access them via the client using their plural, snake_case names (e.g., `moco.planning_entries`, `moco.vat_code_sales`).
|
|
261
|
+
Access them via the client using their plural, snake_case names (e.g., `moco.planning_entries`, `moco.vat_code_sales`, `moco.letter_papers`). Attachments are accessed via the parent: `invoice.attachments`, `offer.attachments`.
|
|
218
262
|
|
|
219
263
|
## Utilities
|
|
220
264
|
|
data/lib/moco/client.rb
CHANGED
|
@@ -42,6 +42,13 @@ module MOCO
|
|
|
42
42
|
Profile.new(self, get("profile"))
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
# Session helper for verifying the configured API key.
|
|
46
|
+
# Use MOCO::Session.create(subdomain:, email:, password:) to exchange
|
|
47
|
+
# credentials for an API key without a Client.
|
|
48
|
+
def session
|
|
49
|
+
@session ||= Session.new(self)
|
|
50
|
+
end
|
|
51
|
+
|
|
45
52
|
# Reports namespace for read-only report endpoints
|
|
46
53
|
def reports
|
|
47
54
|
@reports ||= ReportsProxy.new(self)
|
|
@@ -90,21 +90,10 @@ module MOCO
|
|
|
90
90
|
self
|
|
91
91
|
end
|
|
92
92
|
|
|
93
|
-
#
|
|
93
|
+
# Fetches attachments for this invoice as a NestedCollectionProxy.
|
|
94
|
+
# Supports .all, .find(id), .create(attachment: { filename:, base64: }), and .destroy.
|
|
94
95
|
def attachments
|
|
95
|
-
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# Add an attachment to the invoice
|
|
99
|
-
def add_attachment(file_data)
|
|
100
|
-
client.post("invoices/#{id}/attachments", file_data)
|
|
101
|
-
self
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
# Delete an attachment from the invoice
|
|
105
|
-
def delete_attachment(attachment_id)
|
|
106
|
-
client.delete("invoices/#{id}/attachments/#{attachment_id}")
|
|
107
|
-
self
|
|
96
|
+
MOCO::NestedCollectionProxy.new(client, self, :attachments, "InvoiceAttachment")
|
|
108
97
|
end
|
|
109
98
|
|
|
110
99
|
# Fetches payments for this invoice
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
# Represents an attachment on a MOCO invoice
|
|
5
|
+
#
|
|
6
|
+
# == Required attributes for create:
|
|
7
|
+
# attachment - Hash with the following keys:
|
|
8
|
+
# filename - String, file name including extension (e.g., "appendix.pdf")
|
|
9
|
+
# base64 - String, base64-encoded file content
|
|
10
|
+
#
|
|
11
|
+
# == Read-only attributes:
|
|
12
|
+
# id, title, created_at, updated_at
|
|
13
|
+
#
|
|
14
|
+
# == Usage:
|
|
15
|
+
# invoice = moco.invoices.find(123)
|
|
16
|
+
# invoice.attachments.all
|
|
17
|
+
# invoice.attachments.create(
|
|
18
|
+
# attachment: {
|
|
19
|
+
# filename: "appendix.pdf",
|
|
20
|
+
# base64: Base64.strict_encode64(File.read("appendix.pdf"))
|
|
21
|
+
# }
|
|
22
|
+
# )
|
|
23
|
+
# invoice.attachments.find(42).destroy
|
|
24
|
+
#
|
|
25
|
+
# == Note:
|
|
26
|
+
# The API only supports list (GET), create (POST), and delete (DELETE).
|
|
27
|
+
# Update is not available - delete and re-upload to replace.
|
|
28
|
+
#
|
|
29
|
+
class InvoiceAttachment < BaseEntity
|
|
30
|
+
def to_s
|
|
31
|
+
title.to_s
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
# Represents a MOCO letter paper (letterhead template used on invoices/offers PDFs)
|
|
5
|
+
# Read-only listing of letterheads configured in the MOCO account.
|
|
6
|
+
#
|
|
7
|
+
# == Read-only attributes:
|
|
8
|
+
# id, name, active, template, file, created_at, updated_at
|
|
9
|
+
#
|
|
10
|
+
# == Usage:
|
|
11
|
+
# moco.letter_papers.all
|
|
12
|
+
#
|
|
13
|
+
# == Note:
|
|
14
|
+
# The API only exposes a list endpoint (GET /letter_papers).
|
|
15
|
+
# Use a letter paper's `id` as `letter_paper_id` when fetching
|
|
16
|
+
# invoice/offer PDFs (e.g. GET /invoices/{id}.pdf?letter_paper_id=...).
|
|
17
|
+
#
|
|
18
|
+
class LetterPaper < BaseEntity
|
|
19
|
+
def to_s
|
|
20
|
+
name.to_s
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/moco/entities/offer.rb
CHANGED
|
@@ -85,21 +85,10 @@ module MOCO
|
|
|
85
85
|
reload
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
-
#
|
|
88
|
+
# Fetches attachments for this offer as a NestedCollectionProxy.
|
|
89
|
+
# Supports .all, .find(id), .create(attachment: { filename:, base64: }), and .destroy.
|
|
89
90
|
def attachments
|
|
90
|
-
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# Add an attachment to the offer
|
|
94
|
-
def add_attachment(file_data)
|
|
95
|
-
client.post("offers/#{id}/attachments", file_data)
|
|
96
|
-
self
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
# Delete an attachment from the offer
|
|
100
|
-
def delete_attachment(attachment_id)
|
|
101
|
-
client.delete("offers/#{id}/attachments/#{attachment_id}")
|
|
102
|
-
self
|
|
91
|
+
MOCO::NestedCollectionProxy.new(client, self, :attachments, "OfferAttachment")
|
|
103
92
|
end
|
|
104
93
|
|
|
105
94
|
# Associations
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
# Represents an attachment on a MOCO offer
|
|
5
|
+
#
|
|
6
|
+
# == Required attributes for create:
|
|
7
|
+
# attachment - Hash with the following keys:
|
|
8
|
+
# filename - String, file name including extension (e.g., "appendix.pdf")
|
|
9
|
+
# base64 - String, base64-encoded file content
|
|
10
|
+
#
|
|
11
|
+
# == Read-only attributes:
|
|
12
|
+
# id, title, created_at, updated_at
|
|
13
|
+
#
|
|
14
|
+
# == Usage:
|
|
15
|
+
# offer = moco.offers.find(123)
|
|
16
|
+
# offer.attachments.all
|
|
17
|
+
# offer.attachments.create(
|
|
18
|
+
# attachment: {
|
|
19
|
+
# filename: "appendix.pdf",
|
|
20
|
+
# base64: Base64.strict_encode64(File.read("appendix.pdf"))
|
|
21
|
+
# }
|
|
22
|
+
# )
|
|
23
|
+
# offer.attachments.find(42).destroy
|
|
24
|
+
#
|
|
25
|
+
# == Note:
|
|
26
|
+
# The API only supports list (GET), create (POST), and delete (DELETE).
|
|
27
|
+
# Update is not available - delete and re-upload to replace.
|
|
28
|
+
#
|
|
29
|
+
class OfferAttachment < BaseEntity
|
|
30
|
+
def to_s
|
|
31
|
+
title.to_s
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -82,15 +82,24 @@ module MOCO
|
|
|
82
82
|
MOCO::NestedCollectionProxy.new(client, self, :expenses, "Expense")
|
|
83
83
|
end
|
|
84
84
|
|
|
85
|
-
# Fetches tasks associated with this project.
|
|
86
|
-
#
|
|
87
|
-
# Data is fetched lazily when accessed (e.g., .all, .first, .each).
|
|
88
|
-
# Note: Embedded tasks from projects.assigned are available via attributes[:tasks]
|
|
89
|
-
# but may have incomplete fields compared to the dedicated endpoint.
|
|
85
|
+
# Fetches tasks associated with this project via the API.
|
|
86
|
+
# Returns a NestedCollectionProxy for lazy loading and CRUD operations.
|
|
90
87
|
def tasks
|
|
91
88
|
MOCO::NestedCollectionProxy.new(client, self, :tasks, "Task")
|
|
92
89
|
end
|
|
93
90
|
|
|
91
|
+
# Returns embedded tasks from the projects/assigned response, or nil.
|
|
92
|
+
# These have fewer fields than the full API response but avoid an
|
|
93
|
+
# extra API call, useful for limited-permission accounts.
|
|
94
|
+
def embedded_tasks
|
|
95
|
+
embedded = attributes[:tasks]
|
|
96
|
+
if embedded.is_a?(Array) && embedded.all? { |t| t.is_a?(MOCO::Task) }
|
|
97
|
+
embedded
|
|
98
|
+
else
|
|
99
|
+
[]
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
94
103
|
# Fetches contracts associated with this project.
|
|
95
104
|
def contracts
|
|
96
105
|
MOCO::NestedCollectionProxy.new(client, self, :contracts, "ProjectContract")
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faraday"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module MOCO
|
|
7
|
+
# Represents a MOCO API session for authentication.
|
|
8
|
+
#
|
|
9
|
+
# The `/session` endpoint exchanges email/password credentials for an
|
|
10
|
+
# API key, and can verify an existing key.
|
|
11
|
+
#
|
|
12
|
+
# == Creating an API key (POST /session):
|
|
13
|
+
# result = MOCO::Session.create(
|
|
14
|
+
# subdomain: "your-account",
|
|
15
|
+
# email: "you@example.com",
|
|
16
|
+
# password: "secret"
|
|
17
|
+
# )
|
|
18
|
+
# result["api_key"] # => "6f95f9a0..."
|
|
19
|
+
# result["user_id"] # => 933590696
|
|
20
|
+
#
|
|
21
|
+
# == Verifying an existing key (GET /session):
|
|
22
|
+
# identity = moco.session.verify
|
|
23
|
+
# identity["id"] # => 933590696
|
|
24
|
+
# identity["uuid"] # => "7a60719d-..."
|
|
25
|
+
#
|
|
26
|
+
# == Note:
|
|
27
|
+
# `create` does not require an existing Client - it uses a temporary
|
|
28
|
+
# unauthenticated connection. `verify` uses the Client's configured
|
|
29
|
+
# API key.
|
|
30
|
+
#
|
|
31
|
+
class Session
|
|
32
|
+
class << self
|
|
33
|
+
# Exchange email/password for an API key. Does not require a Client.
|
|
34
|
+
# Returns a Hash: { "api_key" => "...", "user_id" => ... }
|
|
35
|
+
def create(subdomain:, email:, password:)
|
|
36
|
+
conn = Faraday.new(url: "https://#{subdomain}.mocoapp.com/api/v1") do |f|
|
|
37
|
+
f.request :json
|
|
38
|
+
f.response :json
|
|
39
|
+
end
|
|
40
|
+
response = conn.post("session", { email:, password: })
|
|
41
|
+
raise MOCO::Error, "Authentication failed: #{response.status}" unless response.success?
|
|
42
|
+
|
|
43
|
+
response.body
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
attr_reader :client
|
|
48
|
+
|
|
49
|
+
def initialize(client)
|
|
50
|
+
@client = client
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Verify the configured API key. Returns the identity Hash or raises on 401.
|
|
54
|
+
def verify
|
|
55
|
+
client.get("session")
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
data/lib/moco/entities.rb
CHANGED
|
@@ -44,7 +44,7 @@ module MOCO
|
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
# https://
|
|
47
|
+
# https://docs.mocoapp.com/api/docs/v1#tag/projects.html
|
|
48
48
|
# @deprecated Use MOCO::Project from entities/project.rb instead
|
|
49
49
|
class Project < BaseEntity
|
|
50
50
|
attr_accessor :id, :active, :name, :customer, :tasks
|
|
@@ -54,7 +54,7 @@ module MOCO
|
|
|
54
54
|
end
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
-
# https://
|
|
57
|
+
# https://docs.mocoapp.com/api/docs/v1#tag/project_tasks.html
|
|
58
58
|
# @deprecated Use MOCO::Task from entities/task.rb instead
|
|
59
59
|
class Task < BaseEntity
|
|
60
60
|
attr_accessor :id, :active, :name, :project_id, :billable
|
|
@@ -64,7 +64,7 @@ module MOCO
|
|
|
64
64
|
end
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
-
# https://
|
|
67
|
+
# https://docs.mocoapp.com/api/docs/v1#tag/activities.html
|
|
68
68
|
# @deprecated Use MOCO::Activity from entities/activity.rb instead
|
|
69
69
|
class Activity < BaseEntity
|
|
70
70
|
attr_accessor :id, :active, :date, :description, :project, :task, :seconds, :hours, :billable, :billed, :user,
|
|
@@ -79,13 +79,13 @@ module MOCO
|
|
|
79
79
|
end
|
|
80
80
|
end
|
|
81
81
|
|
|
82
|
-
# https://
|
|
82
|
+
# https://docs.mocoapp.com/api/docs/v1#tag/companies.html
|
|
83
83
|
# @deprecated Use MOCO::Company from entities/company.rb instead
|
|
84
84
|
class Customer < BaseEntity
|
|
85
85
|
attr_accessor :id, :name
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
-
# https://
|
|
88
|
+
# https://docs.mocoapp.com/api/docs/v1#tag/users.html
|
|
89
89
|
# @deprecated Use MOCO::User from entities/user.rb instead
|
|
90
90
|
class User < BaseEntity
|
|
91
91
|
attr_accessor :id, :firstname, :lastname
|
data/lib/moco/sync.rb
CHANGED
|
@@ -364,7 +364,7 @@ module MOCO
|
|
|
364
364
|
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
|
365
365
|
|
|
366
366
|
def fetch_assigned_projects
|
|
367
|
-
# Use .projects.assigned for
|
|
367
|
+
# Use .projects.assigned for both source and target to get embedded tasks
|
|
368
368
|
source_filters = @filters.fetch(:source, {}).merge(active: "true")
|
|
369
369
|
# Get the proxy, then fetch all results into the instance variable
|
|
370
370
|
@source_projects = @source.projects.assigned.where(source_filters).all
|
|
@@ -372,19 +372,19 @@ module MOCO
|
|
|
372
372
|
@source_projects.each do |project|
|
|
373
373
|
debug_log " Source Project: #{project.id} - #{project.name} (#{project.identifier})"
|
|
374
374
|
debug_log " Tasks:"
|
|
375
|
-
project.
|
|
375
|
+
project.embedded_tasks.each do |task|
|
|
376
376
|
debug_log " Task: #{task.id} - #{task.name}"
|
|
377
377
|
end
|
|
378
378
|
end
|
|
379
379
|
|
|
380
380
|
target_filters = @filters.fetch(:target, {}).merge(active: "true")
|
|
381
381
|
# Get the proxy, then fetch all results into the instance variable
|
|
382
|
-
@target_projects = @target.projects.where(target_filters).all
|
|
382
|
+
@target_projects = @target.projects.assigned.where(target_filters).all
|
|
383
383
|
debug_log "Found #{@target_projects.size} target projects:"
|
|
384
384
|
@target_projects.each do |project|
|
|
385
385
|
debug_log " Target Project: #{project.id} - #{project.name} (#{project.identifier})"
|
|
386
386
|
debug_log " Tasks:"
|
|
387
|
-
project.
|
|
387
|
+
project.embedded_tasks.each do |task|
|
|
388
388
|
debug_log " Task: #{task.id} - #{task.name}"
|
|
389
389
|
end
|
|
390
390
|
end
|
|
@@ -401,7 +401,7 @@ module MOCO
|
|
|
401
401
|
@project_mapping[source_project.id] = target_project
|
|
402
402
|
debug_log "Mapped source project #{source_project.id} (#{source_project.name}) to target project #{target_project.id} (#{target_project.name})"
|
|
403
403
|
|
|
404
|
-
target_project.
|
|
404
|
+
target_project.embedded_tasks.each do |target_task|
|
|
405
405
|
source_task = match_task(target_task, source_project)
|
|
406
406
|
if source_task
|
|
407
407
|
@task_mapping[source_task.id] = target_task
|
|
@@ -520,7 +520,7 @@ module MOCO
|
|
|
520
520
|
return @default_task_cache[target_project.id] if @default_task_cache.key?(target_project.id)
|
|
521
521
|
|
|
522
522
|
# Search for the default task in the target project
|
|
523
|
-
default_task = target_project.
|
|
523
|
+
default_task = target_project.embedded_tasks.find { |task| task.name == @default_task_name }
|
|
524
524
|
|
|
525
525
|
# Cache the result (even if nil)
|
|
526
526
|
@default_task_cache[target_project.id] = default_task
|
|
@@ -545,7 +545,7 @@ module MOCO
|
|
|
545
545
|
|
|
546
546
|
def match_task(target_task, source_project)
|
|
547
547
|
# Get tasks from the source project (embedded in projects.assigned response)
|
|
548
|
-
tasks = source_project.
|
|
548
|
+
tasks = source_project.embedded_tasks
|
|
549
549
|
|
|
550
550
|
# Only proceed if we have tasks to match against
|
|
551
551
|
return nil if tasks.empty?
|
data/lib/moco/version.rb
CHANGED
data/lib/moco.rb
CHANGED
|
@@ -63,7 +63,11 @@ require_relative "moco/entities/recurring_expense"
|
|
|
63
63
|
require_relative "moco/entities/invoice_payment"
|
|
64
64
|
require_relative "moco/entities/invoice_reminder"
|
|
65
65
|
require_relative "moco/entities/invoice_bookkeeping_export"
|
|
66
|
+
require_relative "moco/entities/invoice_attachment"
|
|
66
67
|
require_relative "moco/entities/offer_approval"
|
|
68
|
+
require_relative "moco/entities/offer_attachment"
|
|
69
|
+
require_relative "moco/entities/letter_paper"
|
|
70
|
+
require_relative "moco/entities/session"
|
|
67
71
|
require_relative "moco/entities/purchase_category"
|
|
68
72
|
require_relative "moco/entities/purchase_draft"
|
|
69
73
|
require_relative "moco/entities/purchase_payment"
|
data/sync_activity.rb
CHANGED
|
@@ -94,7 +94,7 @@ syncer = MOCO::Sync.new(
|
|
|
94
94
|
syncer.source_projects.each do |project|
|
|
95
95
|
if syncer.project_mapping[project.id]
|
|
96
96
|
puts "✅ Project #{project} --> #{syncer.project_mapping[project.id]}"
|
|
97
|
-
project.
|
|
97
|
+
project.embedded_tasks.each do |task|
|
|
98
98
|
if syncer.task_mapping[task.id]
|
|
99
99
|
puts " ✅ Task #{task} --> #{syncer.task_mapping[task.id]}"
|
|
100
100
|
else
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: moco-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Teal Bauer
|
|
@@ -95,11 +95,14 @@ files:
|
|
|
95
95
|
- lib/moco/entities/hourly_rate.rb
|
|
96
96
|
- lib/moco/entities/internal_hourly_rate.rb
|
|
97
97
|
- lib/moco/entities/invoice.rb
|
|
98
|
+
- lib/moco/entities/invoice_attachment.rb
|
|
98
99
|
- lib/moco/entities/invoice_bookkeeping_export.rb
|
|
99
100
|
- lib/moco/entities/invoice_payment.rb
|
|
100
101
|
- lib/moco/entities/invoice_reminder.rb
|
|
102
|
+
- lib/moco/entities/letter_paper.rb
|
|
101
103
|
- lib/moco/entities/offer.rb
|
|
102
104
|
- lib/moco/entities/offer_approval.rb
|
|
105
|
+
- lib/moco/entities/offer_attachment.rb
|
|
103
106
|
- lib/moco/entities/payment_schedule.rb
|
|
104
107
|
- lib/moco/entities/planning_entry.rb
|
|
105
108
|
- lib/moco/entities/presence.rb
|
|
@@ -120,6 +123,7 @@ files:
|
|
|
120
123
|
- lib/moco/entities/reports/finance.rb
|
|
121
124
|
- lib/moco/entities/reports/utilization.rb
|
|
122
125
|
- lib/moco/entities/schedule.rb
|
|
126
|
+
- lib/moco/entities/session.rb
|
|
123
127
|
- lib/moco/entities/tag.rb
|
|
124
128
|
- lib/moco/entities/tagging.rb
|
|
125
129
|
- lib/moco/entities/task.rb
|
|
@@ -161,7 +165,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
161
165
|
- !ruby/object:Gem::Version
|
|
162
166
|
version: '0'
|
|
163
167
|
requirements: []
|
|
164
|
-
rubygems_version: 4.0.
|
|
168
|
+
rubygems_version: 4.0.10
|
|
165
169
|
specification_version: 4
|
|
166
170
|
summary: A Ruby Gem to interact with the MOCO API.
|
|
167
171
|
test_files: []
|