moco-ruby 1.1.0 → 1.2.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 +36 -1
- data/Gemfile.lock +44 -40
- data/README.md +53 -24
- data/lib/moco/client.rb +58 -0
- data/lib/moco/connection.rb +45 -22
- data/lib/moco/entities/activity.rb +31 -1
- data/lib/moco/entities/catalog_service.rb +54 -0
- data/lib/moco/entities/comment.rb +61 -0
- data/lib/moco/entities/company.rb +57 -2
- data/lib/moco/entities/contact.rb +56 -0
- data/lib/moco/entities/custom_property.rb +49 -0
- data/lib/moco/entities/deal.rb +38 -2
- data/lib/moco/entities/deal_category.rb +27 -0
- data/lib/moco/entities/employment.rb +55 -0
- data/lib/moco/entities/expense.rb +37 -2
- data/lib/moco/entities/expense_template.rb +39 -0
- data/lib/moco/entities/fixed_cost.rb +30 -0
- data/lib/moco/entities/holiday.rb +33 -2
- data/lib/moco/entities/hourly_rate.rb +33 -0
- data/lib/moco/entities/internal_hourly_rate.rb +32 -0
- data/lib/moco/entities/invoice.rb +81 -1
- data/lib/moco/entities/invoice_bookkeeping_export.rb +33 -0
- data/lib/moco/entities/invoice_payment.rb +51 -0
- data/lib/moco/entities/invoice_reminder.rb +51 -0
- data/lib/moco/entities/offer.rb +122 -0
- data/lib/moco/entities/offer_approval.rb +42 -0
- data/lib/moco/entities/payment_schedule.rb +48 -0
- data/lib/moco/entities/planning_entry.rb +43 -2
- data/lib/moco/entities/presence.rb +34 -2
- data/lib/moco/entities/profile.rb +24 -0
- data/lib/moco/entities/project.rb +76 -10
- data/lib/moco/entities/project_contract.rb +50 -0
- data/lib/moco/entities/project_group.rb +38 -0
- data/lib/moco/entities/purchase.rb +90 -0
- data/lib/moco/entities/purchase_bookkeeping_export.rb +34 -0
- data/lib/moco/entities/purchase_budget.rb +47 -0
- data/lib/moco/entities/purchase_category.rb +38 -0
- data/lib/moco/entities/purchase_draft.rb +25 -0
- data/lib/moco/entities/purchase_payment.rb +51 -0
- data/lib/moco/entities/receipt.rb +55 -0
- data/lib/moco/entities/recurring_expense.rb +55 -0
- data/lib/moco/entities/reports/absences.rb +16 -0
- data/lib/moco/entities/reports/cashflow.rb +16 -0
- data/lib/moco/entities/reports/finance.rb +16 -0
- data/lib/moco/entities/reports/utilization.rb +16 -0
- data/lib/moco/entities/schedule.rb +39 -2
- data/lib/moco/entities/tag.rb +30 -0
- data/lib/moco/entities/tagging.rb +27 -0
- data/lib/moco/entities/task.rb +25 -2
- data/lib/moco/entities/task_template.rb +38 -0
- data/lib/moco/entities/unit.rb +36 -0
- data/lib/moco/entities/user.rb +50 -2
- data/lib/moco/entities/user_role.rb +29 -0
- data/lib/moco/entities/vat_code_purchase.rb +29 -0
- data/lib/moco/entities/vat_code_sale.rb +29 -0
- data/lib/moco/entities/web_hook.rb +32 -2
- data/lib/moco/entities/work_time_adjustment.rb +51 -0
- data/lib/moco/version.rb +1 -1
- data/lib/moco.rb +51 -1
- data/moco.gemspec +38 -0
- metadata +47 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7527d151ba5f10b9406932d5f6e488d73897fab49ef9ab9a89341166adde7d42
|
|
4
|
+
data.tar.gz: ce1efd8024e78ab0541229172735ac27c8ec4595eeaf86d3183bc09d72b59504
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ecad34b5401422a64351c4814c262ad9328bde6cdfa096f2295ae58b5aa56aa015dbd2c06905a6c212a6efa3b13236c45df12dd8534c15861d61dbb5071630bd
|
|
7
|
+
data.tar.gz: fe46f175939e575b1b63459a10b2ababb5137e4fae30230e59ea45185c996f6e52e9b56ecc11184a2a8fc8a7c28deda74d134594a0940cc3358d7100b4cd735b
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [1.2.0] - 2026-01-14
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Complete MOCO API coverage with 48+ entity types
|
|
9
|
+
- New entities: Offer, Contact, Tagging, DealCategory, CatalogService, CustomProperty,
|
|
10
|
+
ExpenseTemplate, FixedCost, HourlyRate, InternalHourlyRate, TaskTemplate, UserRole,
|
|
11
|
+
VatCodeSale, VatCodePurchase, Profile, WorkTimeAdjustment, ProjectContract,
|
|
12
|
+
PaymentSchedule, RecurringExpense, InvoicePayment, InvoiceReminder, OfferApproval,
|
|
13
|
+
PurchaseCategory, PurchaseDraft, ProjectGroup, InvoiceBookkeepingExport,
|
|
14
|
+
PurchaseBookkeepingExport, PurchaseBudget, PurchasePayment
|
|
15
|
+
- Inline attribute documentation for all entity classes
|
|
16
|
+
- Reports API support via `moco.reports.absences`, `.cashflow`, `.finance`, `.utilization`
|
|
17
|
+
- Integration tests for all entity types
|
|
18
|
+
- GitHub Actions CI for tests and auto-release
|
|
19
|
+
- Ruby 4.0 support
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- Debug output now correctly shows request body for POST/PUT/PATCH requests
|
|
23
|
+
|
|
24
|
+
## [1.1.0] - 2025-11-15
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
- Support for limited-permission accounts with auto-create missing tasks
|
|
28
|
+
- `--default-task` flag for limited-permission accounts
|
|
29
|
+
- `copy_project` tool for copying projects between MOCO instances
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
- Enable auto-require for Bundler
|
|
33
|
+
- Loosen dependency constraints for Rails 8 compatibility
|
|
34
|
+
|
|
35
|
+
### Security
|
|
36
|
+
- Update rexml to 3.4.4 to fix CVE-2025-58767
|
|
37
|
+
|
|
5
38
|
## [1.0.0] - 2025-10-08
|
|
6
39
|
|
|
7
40
|
### Fixed
|
|
@@ -125,7 +158,9 @@
|
|
|
125
158
|
## [0.1.0] - 2024-02-27
|
|
126
159
|
- Initial release
|
|
127
160
|
|
|
128
|
-
[Unreleased]: https://github.com/starsong-consulting/moco-ruby/compare/v1.
|
|
161
|
+
[Unreleased]: https://github.com/starsong-consulting/moco-ruby/compare/v1.2.0...HEAD
|
|
162
|
+
[1.2.0]: https://github.com/starsong-consulting/moco-ruby/compare/v1.1.0...v1.2.0
|
|
163
|
+
[1.1.0]: https://github.com/starsong-consulting/moco-ruby/compare/v1.0.0...v1.1.0
|
|
129
164
|
[1.0.0]: https://github.com/starsong-consulting/moco-ruby/compare/v1.0.0.beta...v1.0.0
|
|
130
165
|
[1.0.0.beta]: https://github.com/starsong-consulting/moco-ruby/compare/v1.0.0.alpha...v1.0.0.beta
|
|
131
166
|
[1.0.0.alpha]: https://github.com/starsong-consulting/moco-ruby/compare/v1.0.0.alpha-initial...v1.0.0.alpha
|
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.2.0)
|
|
5
5
|
activesupport (>= 7.0)
|
|
6
6
|
faraday (>= 2.0)
|
|
7
7
|
fuzzy_match (~> 2.1.0)
|
|
@@ -9,59 +9,62 @@ PATH
|
|
|
9
9
|
GEM
|
|
10
10
|
remote: https://rubygems.org/
|
|
11
11
|
specs:
|
|
12
|
-
activesupport (
|
|
12
|
+
activesupport (8.1.2)
|
|
13
13
|
base64
|
|
14
|
-
benchmark (>= 0.3)
|
|
15
14
|
bigdecimal
|
|
16
15
|
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
17
16
|
connection_pool (>= 2.2.5)
|
|
18
17
|
drb
|
|
19
18
|
i18n (>= 1.6, < 2)
|
|
19
|
+
json
|
|
20
20
|
logger (>= 1.4.2)
|
|
21
21
|
minitest (>= 5.1)
|
|
22
22
|
securerandom (>= 0.3)
|
|
23
23
|
tzinfo (~> 2.0, >= 2.0.5)
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
uri (>= 0.13.1)
|
|
25
|
+
addressable (2.8.8)
|
|
26
|
+
public_suffix (>= 2.0.2, < 8.0)
|
|
26
27
|
ast (2.4.3)
|
|
27
|
-
base64 (0.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
crack (1.0.0)
|
|
28
|
+
base64 (0.3.0)
|
|
29
|
+
bigdecimal (4.0.1)
|
|
30
|
+
concurrent-ruby (1.3.6)
|
|
31
|
+
connection_pool (3.0.2)
|
|
32
|
+
crack (1.0.1)
|
|
33
33
|
bigdecimal
|
|
34
34
|
rexml
|
|
35
35
|
dotenv (2.8.1)
|
|
36
|
-
drb (2.2.
|
|
37
|
-
faraday (2.
|
|
38
|
-
faraday-net_http (>= 2.0, < 3.
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
drb (2.2.3)
|
|
37
|
+
faraday (2.14.0)
|
|
38
|
+
faraday-net_http (>= 2.0, < 3.5)
|
|
39
|
+
json
|
|
40
|
+
logger
|
|
41
|
+
faraday-net_http (3.4.2)
|
|
42
|
+
net-http (~> 0.5)
|
|
41
43
|
fuzzy_match (2.1.0)
|
|
42
|
-
hashdiff (1.1
|
|
43
|
-
i18n (1.14.
|
|
44
|
+
hashdiff (1.2.1)
|
|
45
|
+
i18n (1.14.8)
|
|
44
46
|
concurrent-ruby (~> 1.0)
|
|
45
|
-
json (2.
|
|
46
|
-
language_server-protocol (3.17.0.
|
|
47
|
+
json (2.18.0)
|
|
48
|
+
language_server-protocol (3.17.0.5)
|
|
47
49
|
lint_roller (1.1.0)
|
|
48
50
|
logger (1.7.0)
|
|
49
|
-
minitest (
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
minitest (6.0.1)
|
|
52
|
+
prism (~> 1.5)
|
|
53
|
+
net-http (0.9.1)
|
|
54
|
+
uri (>= 0.11.1)
|
|
55
|
+
parallel (1.27.0)
|
|
56
|
+
parser (3.3.10.0)
|
|
54
57
|
ast (~> 2.4.1)
|
|
55
58
|
racc
|
|
56
|
-
power_assert (
|
|
57
|
-
prism (1.
|
|
58
|
-
public_suffix (
|
|
59
|
+
power_assert (3.0.1)
|
|
60
|
+
prism (1.7.0)
|
|
61
|
+
public_suffix (7.0.2)
|
|
59
62
|
racc (1.8.1)
|
|
60
63
|
rainbow (3.1.1)
|
|
61
|
-
rake (13.
|
|
62
|
-
regexp_parser (2.
|
|
64
|
+
rake (13.3.1)
|
|
65
|
+
regexp_parser (2.11.3)
|
|
63
66
|
rexml (3.4.4)
|
|
64
|
-
rubocop (1.
|
|
67
|
+
rubocop (1.82.1)
|
|
65
68
|
json (~> 2.3)
|
|
66
69
|
language_server-protocol (~> 3.17.0.2)
|
|
67
70
|
lint_roller (~> 1.1.0)
|
|
@@ -69,23 +72,23 @@ GEM
|
|
|
69
72
|
parser (>= 3.3.0.2)
|
|
70
73
|
rainbow (>= 2.2.2, < 4.0)
|
|
71
74
|
regexp_parser (>= 2.9.3, < 3.0)
|
|
72
|
-
rubocop-ast (>= 1.
|
|
75
|
+
rubocop-ast (>= 1.48.0, < 2.0)
|
|
73
76
|
ruby-progressbar (~> 1.7)
|
|
74
77
|
unicode-display_width (>= 2.4.0, < 4.0)
|
|
75
|
-
rubocop-ast (1.
|
|
78
|
+
rubocop-ast (1.49.0)
|
|
76
79
|
parser (>= 3.3.7.2)
|
|
77
|
-
prism (~> 1.
|
|
80
|
+
prism (~> 1.7)
|
|
78
81
|
ruby-progressbar (1.13.0)
|
|
79
82
|
securerandom (0.4.1)
|
|
80
|
-
test-unit (3.
|
|
83
|
+
test-unit (3.7.7)
|
|
81
84
|
power_assert
|
|
82
85
|
tzinfo (2.0.6)
|
|
83
86
|
concurrent-ruby (~> 1.0)
|
|
84
|
-
unicode-display_width (3.
|
|
85
|
-
unicode-emoji (~> 4.
|
|
86
|
-
unicode-emoji (4.0
|
|
87
|
-
uri (1.
|
|
88
|
-
webmock (3.
|
|
87
|
+
unicode-display_width (3.2.0)
|
|
88
|
+
unicode-emoji (~> 4.1)
|
|
89
|
+
unicode-emoji (4.2.0)
|
|
90
|
+
uri (1.1.1)
|
|
91
|
+
webmock (3.26.1)
|
|
89
92
|
addressable (>= 2.8.0)
|
|
90
93
|
crack (>= 0.3.2)
|
|
91
94
|
hashdiff (>= 0.4.0, < 2.0.0)
|
|
@@ -93,6 +96,7 @@ GEM
|
|
|
93
96
|
PLATFORMS
|
|
94
97
|
arm64-darwin-22
|
|
95
98
|
arm64-darwin-23
|
|
99
|
+
x86_64-linux
|
|
96
100
|
|
|
97
101
|
DEPENDENCIES
|
|
98
102
|
dotenv (~> 2.8)
|
data/README.md
CHANGED
|
@@ -167,25 +167,54 @@ puts "Billable tasks: #{billable_tasks.map(&:name).join(', ')}"
|
|
|
167
167
|
dev_task = project.tasks.find_by(name: "Development")
|
|
168
168
|
```
|
|
169
169
|
|
|
170
|
+
### Profile
|
|
171
|
+
|
|
172
|
+
Access the current user's profile:
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
175
|
+
profile = moco.profile
|
|
176
|
+
puts "Logged in as: #{profile.firstname} #{profile.lastname}"
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Reports
|
|
180
|
+
|
|
181
|
+
Access read-only report endpoints:
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
# Absences report
|
|
185
|
+
absences = moco.reports.absences(year: 2024)
|
|
186
|
+
|
|
187
|
+
# Utilization report (requires date range)
|
|
188
|
+
utilization = moco.reports.utilization(from: "2024-01-01", to: "2024-12-31")
|
|
189
|
+
|
|
190
|
+
# Financial reports
|
|
191
|
+
cashflow = moco.reports.cashflow(from: "2024-01-01", to: "2024-03-31")
|
|
192
|
+
finance = moco.reports.finance(from: "2024-01-01", to: "2024-03-31")
|
|
193
|
+
```
|
|
194
|
+
|
|
170
195
|
### Supported Entities
|
|
171
196
|
|
|
172
197
|
The gem supports all MOCO API entities with a Ruby-esque interface:
|
|
173
198
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
199
|
+
**Core:**
|
|
200
|
+
`Project`, `Activity`, `User`, `Company`, `Task`, `Invoice`, `Deal`, `Expense`, `WebHook`, `Schedule`, `Presence`, `Holiday`, `PlanningEntry`
|
|
201
|
+
|
|
202
|
+
**Business:**
|
|
203
|
+
`Contact`, `Offer`, `Purchase`, `Receipt`, `Comment`, `Tag`, `Tagging`, `DealCategory`, `ProjectGroup`, `Unit`
|
|
204
|
+
|
|
205
|
+
**Account Settings:**
|
|
206
|
+
`CatalogService`, `CustomProperty`, `ExpenseTemplate`, `FixedCost`, `HourlyRate`, `InternalHourlyRate`, `TaskTemplate`, `UserRole`
|
|
207
|
+
|
|
208
|
+
**Financial:**
|
|
209
|
+
`VatCodeSale`, `VatCodePurchase`, `PurchaseCategory`, `PurchaseDraft`, `PurchaseBudget`, `PurchasePayment`
|
|
210
|
+
|
|
211
|
+
**Bookkeeping:**
|
|
212
|
+
`InvoiceBookkeepingExport`, `PurchaseBookkeepingExport`
|
|
213
|
+
|
|
214
|
+
**Nested Resources:**
|
|
215
|
+
`Employment`, `WorkTimeAdjustment`, `ProjectContract`, `PaymentSchedule`, `RecurringExpense`, `InvoicePayment`, `InvoiceReminder`, `OfferApproval`
|
|
216
|
+
|
|
217
|
+
Access them via the client using their plural, snake_case names (e.g., `moco.planning_entries`, `moco.vat_code_sales`).
|
|
189
218
|
|
|
190
219
|
## Utilities
|
|
191
220
|
|
|
@@ -246,16 +275,16 @@ After checking out the repo, run `bin/setup` to install dependencies.
|
|
|
246
275
|
|
|
247
276
|
### Running Tests
|
|
248
277
|
|
|
249
|
-
The gem includes
|
|
278
|
+
The gem includes unit tests (mocked) and integration tests (live API):
|
|
250
279
|
|
|
251
280
|
```bash
|
|
252
|
-
#
|
|
253
|
-
ruby test/test_v2_api.rb
|
|
254
|
-
ruby test/
|
|
255
|
-
ruby test/test_holidays_expenses.rb # Holidays & Expenses tests (requires .env)
|
|
281
|
+
# Unit tests (mocked, fast)
|
|
282
|
+
bundle exec ruby -Ilib -Itest test/test_v2_api.rb
|
|
283
|
+
bundle exec ruby -Ilib -Itest test/test_new_entities.rb
|
|
256
284
|
|
|
257
|
-
#
|
|
258
|
-
ruby test/
|
|
285
|
+
# Integration tests (requires .env credentials)
|
|
286
|
+
bundle exec ruby -Ilib -Itest test/test_integration.rb
|
|
287
|
+
bundle exec ruby -Ilib -Itest test/test_comprehensive.rb
|
|
259
288
|
```
|
|
260
289
|
|
|
261
290
|
For integration tests, create a `.env` file with your test instance credentials:
|
|
@@ -264,7 +293,7 @@ MOCO_API_TEST_SUBDOMAIN=your-test-subdomain
|
|
|
264
293
|
MOCO_API_TEST_API_KEY=your-test-api-key
|
|
265
294
|
```
|
|
266
295
|
|
|
267
|
-
**Note:** The MOCO API has rate limits
|
|
296
|
+
**Note:** The MOCO API has rate limits. The gem automatically retries rate-limited requests with exponential backoff.
|
|
268
297
|
|
|
269
298
|
### Installation
|
|
270
299
|
|
|
@@ -278,4 +307,4 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/starso
|
|
|
278
307
|
|
|
279
308
|
## License
|
|
280
309
|
|
|
281
|
-
The gem is available as open source under the terms of the [
|
|
310
|
+
The gem is available as open source under the terms of the [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0).
|
data/lib/moco/client.rb
CHANGED
|
@@ -37,6 +37,16 @@ module MOCO
|
|
|
37
37
|
name.to_s == ActiveSupport::Inflector.pluralize(name.to_s)
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
+
# Get the current user's profile (singleton resource)
|
|
41
|
+
def profile
|
|
42
|
+
Profile.new(self, get("profile"))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Reports namespace for read-only report endpoints
|
|
46
|
+
def reports
|
|
47
|
+
@reports ||= ReportsProxy.new(self)
|
|
48
|
+
end
|
|
49
|
+
|
|
40
50
|
# Delegate HTTP methods to connection
|
|
41
51
|
%i[get post put patch delete].each do |method|
|
|
42
52
|
define_method(method) do |path, params = {}|
|
|
@@ -44,4 +54,52 @@ module MOCO
|
|
|
44
54
|
end
|
|
45
55
|
end
|
|
46
56
|
end
|
|
57
|
+
|
|
58
|
+
# Proxy for accessing report endpoints
|
|
59
|
+
class ReportsProxy
|
|
60
|
+
def initialize(client)
|
|
61
|
+
@client = client
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Get absences report
|
|
65
|
+
# @param year [Integer] optional year filter
|
|
66
|
+
# @param active [Boolean] optional active status filter
|
|
67
|
+
def absences(year: nil, active: nil)
|
|
68
|
+
params = {}
|
|
69
|
+
params[:year] = year if year
|
|
70
|
+
params[:active] = active unless active.nil?
|
|
71
|
+
@client.get("report/absences", params)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Get cashflow report
|
|
75
|
+
# @param from [String] start date (YYYY-MM-DD)
|
|
76
|
+
# @param to [String] end date (YYYY-MM-DD)
|
|
77
|
+
# @param term [String] optional search term
|
|
78
|
+
def cashflow(from: nil, to: nil, term: nil)
|
|
79
|
+
params = {}
|
|
80
|
+
params[:from] = from if from
|
|
81
|
+
params[:to] = to if to
|
|
82
|
+
params[:term] = term if term
|
|
83
|
+
@client.get("report/cashflow", params)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Get finance report
|
|
87
|
+
# @param from [String] start date (YYYY-MM-DD)
|
|
88
|
+
# @param to [String] end date (YYYY-MM-DD)
|
|
89
|
+
# @param term [String] optional search term
|
|
90
|
+
def finance(from: nil, to: nil, term: nil)
|
|
91
|
+
params = {}
|
|
92
|
+
params[:from] = from if from
|
|
93
|
+
params[:to] = to if to
|
|
94
|
+
params[:term] = term if term
|
|
95
|
+
@client.get("report/finance", params)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Get utilization report
|
|
99
|
+
# @param from [String] start date (YYYY-MM-DD) - required
|
|
100
|
+
# @param to [String] end date (YYYY-MM-DD) - required
|
|
101
|
+
def utilization(from:, to:)
|
|
102
|
+
@client.get("report/utilization", { from:, to: })
|
|
103
|
+
end
|
|
104
|
+
end
|
|
47
105
|
end
|
data/lib/moco/connection.rb
CHANGED
|
@@ -22,34 +22,57 @@ module MOCO
|
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
# Maximum retries for rate-limited requests
|
|
26
|
+
MAX_RETRIES = 3
|
|
27
|
+
# Base delay between retries (seconds)
|
|
28
|
+
RETRY_DELAY = 1.0
|
|
29
|
+
|
|
25
30
|
# Define methods for HTTP verbs (get, post, put, patch, delete)
|
|
26
31
|
# These methods send the request and return the raw parsed JSON response body.
|
|
27
32
|
%w[get post put patch delete].each do |http_method|
|
|
28
33
|
define_method(http_method) do |path, params = {}|
|
|
29
|
-
|
|
30
|
-
if @debug
|
|
31
|
-
full_url = @conn.build_url(path, params).to_s
|
|
32
|
-
warn "[DEBUG] Fetching URL: #{http_method.upcase} #{full_url}"
|
|
33
|
-
end
|
|
34
|
-
response = @conn.send(http_method, path, params)
|
|
34
|
+
retries = 0
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
36
|
+
loop do
|
|
37
|
+
begin
|
|
38
|
+
# Log request if debug is enabled
|
|
39
|
+
if @debug
|
|
40
|
+
if %w[post put patch].include?(http_method)
|
|
41
|
+
# For body methods, show the JSON that will be sent
|
|
42
|
+
warn "[DEBUG] #{http_method.upcase} #{@conn.url_prefix}/#{path}"
|
|
43
|
+
warn "[DEBUG] Body: #{params.to_json}" unless params.empty?
|
|
44
|
+
else
|
|
45
|
+
# For query methods, show the full URL with params
|
|
46
|
+
full_url = @conn.build_url(path, params).to_s
|
|
47
|
+
warn "[DEBUG] #{http_method.upcase} #{full_url}"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
response = @conn.send(http_method, path, params)
|
|
46
52
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
# Handle rate limiting with automatic retry
|
|
54
|
+
if response.status == 429 && retries < MAX_RETRIES
|
|
55
|
+
retries += 1
|
|
56
|
+
# Get Retry-After header or use exponential backoff
|
|
57
|
+
retry_after = response.headers["Retry-After"]&.to_f || (RETRY_DELAY * (2**retries))
|
|
58
|
+
warn "[RATE LIMITED] Waiting #{retry_after}s before retry #{retries}/#{MAX_RETRIES}..." if @debug
|
|
59
|
+
sleep(retry_after)
|
|
60
|
+
next
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Raise an error for non-successful responses
|
|
64
|
+
unless response.success?
|
|
65
|
+
# Attempt to parse error details from the body, otherwise use status/reason
|
|
66
|
+
error_details = response.body.is_a?(Hash) ? response.body["message"] : response.body
|
|
67
|
+
raise "MOCO API Error: #{response.status} #{response.reason_phrase}. Details: #{error_details}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
return response.body
|
|
71
|
+
rescue Faraday::Error => e
|
|
72
|
+
# Wrap Faraday errors
|
|
73
|
+
raise "Faraday Connection Error: #{e.message}"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
53
76
|
end
|
|
54
77
|
end
|
|
55
78
|
|
|
@@ -2,7 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
module MOCO
|
|
4
4
|
# Represents a MOCO activity (time entry)
|
|
5
|
-
#
|
|
5
|
+
#
|
|
6
|
+
# == Required attributes for create:
|
|
7
|
+
# date - String, "YYYY-MM-DD" format (e.g., "2024-01-15")
|
|
8
|
+
# project_id - Integer, ID of the project
|
|
9
|
+
# task_id - Integer, ID of the task within the project
|
|
10
|
+
#
|
|
11
|
+
# == Optional attributes:
|
|
12
|
+
# seconds - Integer, duration in seconds (3600 = 1 hour)
|
|
13
|
+
# hours - Float, duration in hours (alternative to seconds)
|
|
14
|
+
# description - String, description of the work done
|
|
15
|
+
# billable - Boolean, whether the activity is billable (default: true or project setting)
|
|
16
|
+
# tag - String, any tag (e.g., "RMT-123")
|
|
17
|
+
# remote_service - String, external service name. Allowed: "trello", "jira", "asana",
|
|
18
|
+
# "basecamp", "wunderlist", "basecamp2", "basecamp3", "toggl", "mite",
|
|
19
|
+
# "github", "youtrack"
|
|
20
|
+
# remote_id - String, ID in the external service (e.g., "PRJ-2342")
|
|
21
|
+
# remote_url - String, URL to the external ticket/issue
|
|
22
|
+
#
|
|
23
|
+
# == Read-only attributes (returned by API):
|
|
24
|
+
# id, billed, invoice_id, project (Hash), task (Hash), customer (Hash),
|
|
25
|
+
# user (Hash), hourly_rate, timer_started_at, created_at, updated_at
|
|
26
|
+
#
|
|
27
|
+
# == Example:
|
|
28
|
+
# moco.activities.create(
|
|
29
|
+
# date: "2024-01-15",
|
|
30
|
+
# project_id: 123456,
|
|
31
|
+
# task_id: 234567,
|
|
32
|
+
# seconds: 3600,
|
|
33
|
+
# description: "Implemented feature X"
|
|
34
|
+
# )
|
|
35
|
+
#
|
|
6
36
|
class Activity < BaseEntity
|
|
7
37
|
# Instance methods for activity-specific operations
|
|
8
38
|
def start_timer
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
# Represents a MOCO catalog service (Leistungskatalog)
|
|
5
|
+
# Pre-defined service templates for offers/invoices
|
|
6
|
+
#
|
|
7
|
+
# == Required attributes for create:
|
|
8
|
+
# title - String, catalog entry name
|
|
9
|
+
#
|
|
10
|
+
# == Optional attributes:
|
|
11
|
+
# items - Array of item hashes, service line items
|
|
12
|
+
#
|
|
13
|
+
# == Item types:
|
|
14
|
+
# { type: "title", title: "Section" }
|
|
15
|
+
# { type: "description", description: "Details..." }
|
|
16
|
+
# { type: "item", title: "Service", quantity: 10, unit: "h", unit_price: 150.0, net_total: 1500.0 }
|
|
17
|
+
# { type: "item", title: "Fixed Fee", net_total: 500.0 } # lump sum (quantity=0)
|
|
18
|
+
# { type: "subtotal", part: true } # subtotal for section
|
|
19
|
+
# { type: "separator" }
|
|
20
|
+
# { type: "page-break" }
|
|
21
|
+
#
|
|
22
|
+
# == Item attributes:
|
|
23
|
+
# title - String, item title
|
|
24
|
+
# description - String, item description
|
|
25
|
+
# quantity - Float, number of units (0 for lump sum)
|
|
26
|
+
# unit - String, unit type (e.g., "h", "pieces")
|
|
27
|
+
# unit_price - Float, price per unit
|
|
28
|
+
# net_total - Float, total price for this item
|
|
29
|
+
# unit_cost - Float, internal cost per unit
|
|
30
|
+
# optional - Boolean, mark as optional
|
|
31
|
+
# additional - Boolean, mark as additional service
|
|
32
|
+
#
|
|
33
|
+
# == Read-only attributes:
|
|
34
|
+
# id, items (Array), created_at, updated_at
|
|
35
|
+
#
|
|
36
|
+
# == Example:
|
|
37
|
+
# moco.catalog_services.create(
|
|
38
|
+
# title: "Web Development Package",
|
|
39
|
+
# items: [
|
|
40
|
+
# { type: "item", title: "Setup", net_total: 1200.0 },
|
|
41
|
+
# { type: "item", title: "Development", quantity: 40, unit: "h", unit_price: 150.0, net_total: 6000.0 }
|
|
42
|
+
# ]
|
|
43
|
+
# )
|
|
44
|
+
#
|
|
45
|
+
class CatalogService < BaseEntity
|
|
46
|
+
def self.entity_path
|
|
47
|
+
"account/catalog_services"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def to_s
|
|
51
|
+
name.to_s
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
# Represents a MOCO comment/note (Notizen)
|
|
5
|
+
# Comments can be attached to various entities
|
|
6
|
+
#
|
|
7
|
+
# == Required attributes for create:
|
|
8
|
+
# commentable_id - Integer, ID of the entity to attach comment to
|
|
9
|
+
# commentable_type - String, entity type:
|
|
10
|
+
# "Project", "Contact", "Company", "Deal", "User", "Unit",
|
|
11
|
+
# "Invoice", "Offer", "Expense", "Receipt", "Purchase",
|
|
12
|
+
# "DeliveryNote", "OfferConfirmation", "InvoiceReminder",
|
|
13
|
+
# "InvoiceDeletion", "InvoiceBookkeepingExport",
|
|
14
|
+
# "RecurringExpense", "ReceiptRefundRequest",
|
|
15
|
+
# "PurchaseBookkeepingExport", "PurchaseDraft"
|
|
16
|
+
# text - String, comment text (plain text or HTML)
|
|
17
|
+
#
|
|
18
|
+
# == Optional attributes:
|
|
19
|
+
# attachment_filename - String, filename for attachment
|
|
20
|
+
# attachment_content - String, base64-encoded file content
|
|
21
|
+
# created_at - String, timestamp for data migration
|
|
22
|
+
#
|
|
23
|
+
# == Read-only attributes:
|
|
24
|
+
# id, manual, user (Hash - creator), created_at, updated_at
|
|
25
|
+
#
|
|
26
|
+
# == Allowed HTML tags in text:
|
|
27
|
+
# div, strong, em, u, pre, ul, ol, li, br
|
|
28
|
+
#
|
|
29
|
+
# == Example:
|
|
30
|
+
# # Add comment to a project
|
|
31
|
+
# moco.comments.create(
|
|
32
|
+
# commentable_id: 123,
|
|
33
|
+
# commentable_type: "Project",
|
|
34
|
+
# text: "<div>Project kickoff on <strong>Jan 15</strong></div>"
|
|
35
|
+
# )
|
|
36
|
+
#
|
|
37
|
+
# == Filtering:
|
|
38
|
+
# moco.comments.where(commentable_type: "Project", commentable_id: 123)
|
|
39
|
+
# moco.comments.where(user_id: 456)
|
|
40
|
+
# moco.comments.where(manual: true) # user-created only
|
|
41
|
+
#
|
|
42
|
+
class Comment < BaseEntity
|
|
43
|
+
# Bulk create comments
|
|
44
|
+
# @param client [MOCO::Client] the client instance
|
|
45
|
+
# @param comments [Array<Hash>] array of comment attributes
|
|
46
|
+
# @return [Array<Comment>] created comments
|
|
47
|
+
def self.bulk_create(client, comments)
|
|
48
|
+
response = client.post("comments/bulk", { bulk: comments })
|
|
49
|
+
response.map { |data| new(client, data) }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Associations
|
|
53
|
+
def user
|
|
54
|
+
association(:user)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def to_s
|
|
58
|
+
text.to_s.truncate(50)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|