moco-ruby 1.1.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 +56 -1
- data/Gemfile.lock +45 -40
- data/README.md +98 -25
- data/lib/moco/client.rb +65 -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 +70 -1
- data/lib/moco/entities/invoice_attachment.rb +34 -0
- 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/letter_paper.rb +23 -0
- data/lib/moco/entities/offer.rb +111 -0
- data/lib/moco/entities/offer_approval.rb +42 -0
- data/lib/moco/entities/offer_attachment.rb +34 -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 +85 -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/session.rb +58 -0
- 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/entities.rb +5 -5
- data/lib/moco/sync.rb +7 -7
- data/lib/moco/version.rb +1 -1
- data/lib/moco.rb +55 -1
- data/moco.gemspec +38 -0
- data/sync_activity.rb +1 -1
- metadata +51 -8
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
# Represents a MOCO project contract/staff assignment
|
|
5
|
+
# (Projekte / Personal) - assigning users to projects
|
|
6
|
+
#
|
|
7
|
+
# == Required attributes for create:
|
|
8
|
+
# user_id - Integer, user to assign to project
|
|
9
|
+
#
|
|
10
|
+
# == Optional attributes:
|
|
11
|
+
# billable - Boolean, whether user's time is billable
|
|
12
|
+
# active - Boolean, whether assignment is active
|
|
13
|
+
# budget - Float, hours budget for this user on project
|
|
14
|
+
# hourly_rate - Float, billing rate for this user on project
|
|
15
|
+
#
|
|
16
|
+
# == Read-only attributes:
|
|
17
|
+
# id, firstname, lastname, created_at, updated_at
|
|
18
|
+
#
|
|
19
|
+
# == Access via project:
|
|
20
|
+
# project = moco.projects.find(123)
|
|
21
|
+
# project.contracts # via nested API
|
|
22
|
+
#
|
|
23
|
+
# == Example:
|
|
24
|
+
# # Assign user to project with budget
|
|
25
|
+
# moco.post("projects/123/contracts", {
|
|
26
|
+
# user_id: 456,
|
|
27
|
+
# budget: 100,
|
|
28
|
+
# hourly_rate: 150.0,
|
|
29
|
+
# billable: true
|
|
30
|
+
# })
|
|
31
|
+
#
|
|
32
|
+
# == Note:
|
|
33
|
+
# Cannot delete assignment if user has tracked hours.
|
|
34
|
+
# user_id cannot be changed after creation.
|
|
35
|
+
#
|
|
36
|
+
class ProjectContract < BaseEntity
|
|
37
|
+
# Associations
|
|
38
|
+
def project
|
|
39
|
+
association(:project)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def user
|
|
43
|
+
association(:user)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def to_s
|
|
47
|
+
"Contract ##{id}"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
# Represents a MOCO project group for organizing projects
|
|
5
|
+
#
|
|
6
|
+
# == Required attributes for create:
|
|
7
|
+
# name - String, group name
|
|
8
|
+
#
|
|
9
|
+
# == Read-only attributes:
|
|
10
|
+
# id, created_at, updated_at
|
|
11
|
+
#
|
|
12
|
+
# == Example:
|
|
13
|
+
# # Create a project group
|
|
14
|
+
# moco.project_groups.create(name: "Website Projects")
|
|
15
|
+
#
|
|
16
|
+
# # Get projects in a group
|
|
17
|
+
# group = moco.project_groups.find(123)
|
|
18
|
+
# group.projects # => Array of Project objects
|
|
19
|
+
#
|
|
20
|
+
# == Note:
|
|
21
|
+
# To assign a project to a group, set project_group_id when
|
|
22
|
+
# creating/updating the project.
|
|
23
|
+
#
|
|
24
|
+
class ProjectGroup < BaseEntity
|
|
25
|
+
def self.entity_path
|
|
26
|
+
"projects/groups"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Get projects in this group
|
|
30
|
+
def projects
|
|
31
|
+
has_many(:projects, :project_group_id)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def to_s
|
|
35
|
+
name.to_s
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
# Represents a MOCO purchase/expense (Ausgaben)
|
|
5
|
+
#
|
|
6
|
+
# == Required attributes for create:
|
|
7
|
+
# date - String, "YYYY-MM-DD" format
|
|
8
|
+
# currency - String, valid currency code (e.g., "EUR", "CHF", "USD")
|
|
9
|
+
# payment_method - String, one of:
|
|
10
|
+
# "bank_transfer", "direct_debit", "credit_card",
|
|
11
|
+
# "paypal", "cash", "bank_transfer_swiss_qr_esr"
|
|
12
|
+
# items - Array of item hashes (at least one required):
|
|
13
|
+
# { title: "Item", total: 100.0, tax: 7.7, tax_included: true }
|
|
14
|
+
#
|
|
15
|
+
# == Optional attributes:
|
|
16
|
+
# title - String, purchase title (auto-generated from items if omitted)
|
|
17
|
+
# due_date - String, "YYYY-MM-DD" payment due date
|
|
18
|
+
# service_period_from - String, "YYYY-MM-DD" service period start
|
|
19
|
+
# service_period_to - String, "YYYY-MM-DD" service period end
|
|
20
|
+
# status - String, "pending" (Inbox) or "archived" (Archive)
|
|
21
|
+
# company_id - Integer, supplier company ID
|
|
22
|
+
# user_id - Integer, responsible user ID
|
|
23
|
+
# receipt_identifier - String, supplier's invoice number
|
|
24
|
+
# info - String, free text notes
|
|
25
|
+
# iban - String, bank account for payment
|
|
26
|
+
# reference - String, payment reference
|
|
27
|
+
# custom_property_values - Hash, custom field values
|
|
28
|
+
# tags - Array of Strings, labels
|
|
29
|
+
# file - Hash, { filename: "doc.pdf", base64: "..." }
|
|
30
|
+
#
|
|
31
|
+
# == Item attributes:
|
|
32
|
+
# title - String, item description
|
|
33
|
+
# total - Float, item total amount
|
|
34
|
+
# tax - Float, tax percentage (e.g., 7.7)
|
|
35
|
+
# tax_included - Boolean, whether total includes tax
|
|
36
|
+
# category_id - Integer, purchase category ID
|
|
37
|
+
#
|
|
38
|
+
# == Read-only attributes:
|
|
39
|
+
# id, identifier, net_total, gross_total, payments (Array),
|
|
40
|
+
# approval_status, file_url, company (Hash), user (Hash),
|
|
41
|
+
# created_at, updated_at
|
|
42
|
+
#
|
|
43
|
+
# == Example:
|
|
44
|
+
# moco.purchases.create(
|
|
45
|
+
# date: "2024-01-15",
|
|
46
|
+
# currency: "EUR",
|
|
47
|
+
# payment_method: "bank_transfer",
|
|
48
|
+
# company_id: 456,
|
|
49
|
+
# items: [
|
|
50
|
+
# { title: "Office supplies", total: 119.0, tax: 19.0, tax_included: true }
|
|
51
|
+
# ],
|
|
52
|
+
# tags: ["Office"]
|
|
53
|
+
# )
|
|
54
|
+
#
|
|
55
|
+
class Purchase < BaseEntity
|
|
56
|
+
# Assign purchase to a project expense
|
|
57
|
+
def assign_to_project(project_id:, project_expense_id: nil)
|
|
58
|
+
payload = { project_id: }
|
|
59
|
+
payload[:project_expense_id] = project_expense_id if project_expense_id
|
|
60
|
+
|
|
61
|
+
client.post("purchases/#{id}/assign_to_project", payload)
|
|
62
|
+
reload
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Update purchase status (pending/archived)
|
|
66
|
+
def update_status(status)
|
|
67
|
+
client.patch("purchases/#{id}/update_status", { status: })
|
|
68
|
+
reload
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Store/upload a document for this purchase
|
|
72
|
+
def store_document(file_data)
|
|
73
|
+
client.patch("purchases/#{id}/store_document", file_data)
|
|
74
|
+
self
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Associations
|
|
78
|
+
def company
|
|
79
|
+
association(:company)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def user
|
|
83
|
+
association(:user)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def to_s
|
|
87
|
+
"#{title} (#{date})"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
# Represents a MOCO purchase bookkeeping export
|
|
5
|
+
# (Ausgaben / Buchhaltungsexporte)
|
|
6
|
+
# Exports purchase data for accounting systems
|
|
7
|
+
#
|
|
8
|
+
# == Read-only attributes:
|
|
9
|
+
# id, from, to, file_url, user (Hash),
|
|
10
|
+
# created_at, updated_at
|
|
11
|
+
#
|
|
12
|
+
# == Filtering:
|
|
13
|
+
# moco.purchase_bookkeeping_exports.all
|
|
14
|
+
# moco.purchase_bookkeeping_exports.where(from: "2024-01-01", to: "2024-01-31")
|
|
15
|
+
#
|
|
16
|
+
# == Note:
|
|
17
|
+
# Bookkeeping exports are generated via MOCO's finance interface.
|
|
18
|
+
# This endpoint provides read-only access to export records.
|
|
19
|
+
#
|
|
20
|
+
class PurchaseBookkeepingExport < BaseEntity
|
|
21
|
+
# Custom path since it's nested under purchases
|
|
22
|
+
def self.entity_path
|
|
23
|
+
"purchases/bookkeeping_exports"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def user
|
|
27
|
+
association(:user, "User")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def to_s
|
|
31
|
+
"PurchaseBookkeepingExport #{id} (#{from} - #{to})"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
# Represents a MOCO purchase budget (Ausgaben – Budgets)
|
|
5
|
+
# Read-only budget tracking for expense categories
|
|
6
|
+
#
|
|
7
|
+
# == Read-only attributes:
|
|
8
|
+
# id, title, year, target, exhausted, remaining,
|
|
9
|
+
# created_at, updated_at
|
|
10
|
+
#
|
|
11
|
+
# == Helper methods:
|
|
12
|
+
# remaining_percentage - Percentage of budget remaining
|
|
13
|
+
# exhausted_percentage - Percentage of budget used
|
|
14
|
+
#
|
|
15
|
+
# == Example:
|
|
16
|
+
# budgets = moco.purchase_budgets.all
|
|
17
|
+
# budgets.each do |budget|
|
|
18
|
+
# puts "#{budget.title}: #{budget.remaining_percentage}% remaining"
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# == Note:
|
|
22
|
+
# Purchase budgets are configured in MOCO's admin interface.
|
|
23
|
+
# This endpoint provides read-only access for tracking.
|
|
24
|
+
#
|
|
25
|
+
class PurchaseBudget < BaseEntity
|
|
26
|
+
# Custom path since it's nested under purchases
|
|
27
|
+
def self.entity_path
|
|
28
|
+
"purchases/budgets"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def to_s
|
|
32
|
+
"PurchaseBudget #{id}: #{title} (#{year})"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def remaining_percentage
|
|
36
|
+
return 0 if target.to_f.zero?
|
|
37
|
+
|
|
38
|
+
(remaining.to_f / target.to_f * 100).round(1)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def exhausted_percentage
|
|
42
|
+
return 0 if target.to_f.zero?
|
|
43
|
+
|
|
44
|
+
(exhaused.to_f / target.to_f * 100).round(1)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
# Represents a MOCO purchase category (Ausgaben – Kategorien)
|
|
5
|
+
# Read-only expense categories for organizing purchases
|
|
6
|
+
#
|
|
7
|
+
# == Read-only attributes:
|
|
8
|
+
# id, name, credit_account, active, created_at, updated_at
|
|
9
|
+
#
|
|
10
|
+
# == Example:
|
|
11
|
+
# # List all categories
|
|
12
|
+
# moco.purchase_categories.all
|
|
13
|
+
#
|
|
14
|
+
# # Use category when creating purchase
|
|
15
|
+
# moco.purchases.create(
|
|
16
|
+
# # ... other fields ...
|
|
17
|
+
# items: [{
|
|
18
|
+
# title: "Travel",
|
|
19
|
+
# total: 250.0,
|
|
20
|
+
# tax: 7.7,
|
|
21
|
+
# category_id: 123 # from purchase_categories
|
|
22
|
+
# }]
|
|
23
|
+
# )
|
|
24
|
+
#
|
|
25
|
+
# == Note:
|
|
26
|
+
# Purchase categories are configured in MOCO's admin interface.
|
|
27
|
+
# This endpoint provides read-only access.
|
|
28
|
+
#
|
|
29
|
+
class PurchaseCategory < BaseEntity
|
|
30
|
+
def self.entity_path
|
|
31
|
+
"purchases/categories"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def to_s
|
|
35
|
+
name.to_s
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
# Represents a MOCO purchase draft
|
|
5
|
+
# Auto-created draft purchases from document scanning
|
|
6
|
+
#
|
|
7
|
+
# == Read-only attributes:
|
|
8
|
+
# id, title, date, company (Hash), file_url,
|
|
9
|
+
# items (Array), created_at, updated_at
|
|
10
|
+
#
|
|
11
|
+
# == Note:
|
|
12
|
+
# Purchase drafts are created automatically when documents
|
|
13
|
+
# are uploaded/scanned in MOCO's inbox.
|
|
14
|
+
# Convert to actual purchases via the MOCO interface.
|
|
15
|
+
#
|
|
16
|
+
class PurchaseDraft < BaseEntity
|
|
17
|
+
def self.entity_path
|
|
18
|
+
"purchases/drafts"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_s
|
|
22
|
+
"Draft ##{id}"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
# Represents a MOCO purchase payment record (Ausgaben / Zahlungen)
|
|
5
|
+
# For tracking payments made for purchases
|
|
6
|
+
#
|
|
7
|
+
# == Required attributes for create:
|
|
8
|
+
# date - String, "YYYY-MM-DD" payment date
|
|
9
|
+
# total - Float, payment amount
|
|
10
|
+
#
|
|
11
|
+
# == Optional attributes:
|
|
12
|
+
# purchase_id - Integer, purchase being paid (required unless description set)
|
|
13
|
+
# description - String, payment description (required if no purchase_id)
|
|
14
|
+
#
|
|
15
|
+
# == Read-only attributes:
|
|
16
|
+
# id, purchase (Hash), created_at, updated_at
|
|
17
|
+
#
|
|
18
|
+
# == Example:
|
|
19
|
+
# moco.purchase_payments.create(
|
|
20
|
+
# date: "2024-01-20",
|
|
21
|
+
# purchase_id: 456,
|
|
22
|
+
# total: 1500.0
|
|
23
|
+
# )
|
|
24
|
+
#
|
|
25
|
+
# == Bulk create:
|
|
26
|
+
# moco.post("purchases/payments/bulk", {
|
|
27
|
+
# bulk_data: [
|
|
28
|
+
# { date: "2024-01-20", purchase_id: 123, total: 500 },
|
|
29
|
+
# { date: "2024-01-21", description: "Salaries", total: 10000 }
|
|
30
|
+
# ]
|
|
31
|
+
# })
|
|
32
|
+
#
|
|
33
|
+
# == Filtering:
|
|
34
|
+
# moco.purchase_payments.where(purchase_id: 456)
|
|
35
|
+
# moco.purchase_payments.where(date_from: "2024-01-01", date_to: "2024-01-31")
|
|
36
|
+
#
|
|
37
|
+
class PurchasePayment < BaseEntity
|
|
38
|
+
# Custom path since it's nested under purchases
|
|
39
|
+
def self.entity_path
|
|
40
|
+
"purchases/payments"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def purchase
|
|
44
|
+
association(:purchase, "Purchase")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_s
|
|
48
|
+
"PurchasePayment #{id}: #{total} on #{date}"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
# Represents a MOCO receipt/expense claim (Spesen)
|
|
5
|
+
#
|
|
6
|
+
# == Required attributes for create:
|
|
7
|
+
# date - String, "YYYY-MM-DD" format
|
|
8
|
+
# title - String, receipt description (e.g., "Team lunch")
|
|
9
|
+
# currency - String, valid currency code (e.g., "EUR", "CHF")
|
|
10
|
+
# items - Array of item hashes (at least one required):
|
|
11
|
+
# { vat_code_id: 186, gross_total: 99.90 }
|
|
12
|
+
#
|
|
13
|
+
# == Optional attributes:
|
|
14
|
+
# project_id - Integer, project to associate with
|
|
15
|
+
# info - String, additional notes
|
|
16
|
+
# billable - Boolean, whether expense is billable to customer
|
|
17
|
+
# attachment - Hash, { filename: "receipt.pdf", base64: "..." }
|
|
18
|
+
#
|
|
19
|
+
# == Item attributes:
|
|
20
|
+
# vat_code_id - Integer, VAT code ID (required)
|
|
21
|
+
# gross_total - Float, total amount including tax (required)
|
|
22
|
+
# purchase_category_id - Integer, expense category
|
|
23
|
+
#
|
|
24
|
+
# == Read-only attributes:
|
|
25
|
+
# id, pending, user (Hash), project (Hash), refund_request (Hash),
|
|
26
|
+
# attachment_url, created_at, updated_at
|
|
27
|
+
#
|
|
28
|
+
# == Example:
|
|
29
|
+
# moco.receipts.create(
|
|
30
|
+
# date: "2024-01-15",
|
|
31
|
+
# title: "Client lunch",
|
|
32
|
+
# currency: "EUR",
|
|
33
|
+
# project_id: 123,
|
|
34
|
+
# billable: true,
|
|
35
|
+
# items: [
|
|
36
|
+
# { vat_code_id: 186, gross_total: 85.50 }
|
|
37
|
+
# ]
|
|
38
|
+
# )
|
|
39
|
+
#
|
|
40
|
+
# == Filtering:
|
|
41
|
+
# moco.receipts.where(from: "2024-01-01", to: "2024-01-31")
|
|
42
|
+
# moco.receipts.where(project_id: 123)
|
|
43
|
+
# moco.receipts.where(user_id: 456)
|
|
44
|
+
#
|
|
45
|
+
class Receipt < BaseEntity
|
|
46
|
+
# Associations
|
|
47
|
+
def user
|
|
48
|
+
association(:user)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def to_s
|
|
52
|
+
"#{title} (#{date})"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
# Represents a MOCO project recurring expense
|
|
5
|
+
# (Wiederkehrende Zusatzleistungen) for ongoing services like hosting
|
|
6
|
+
#
|
|
7
|
+
# == Required attributes for create:
|
|
8
|
+
# start_date - String, "YYYY-MM-DD" when recurring starts
|
|
9
|
+
# period - String, recurrence interval:
|
|
10
|
+
# "weekly", "biweekly", "monthly", "quarterly",
|
|
11
|
+
# "biannual", "annual"
|
|
12
|
+
# title - String, expense name (e.g., "Monthly Hosting")
|
|
13
|
+
# quantity - Float, number of units per period
|
|
14
|
+
# unit - String, unit type (e.g., "Server", "License")
|
|
15
|
+
# unit_price - Float, price per unit
|
|
16
|
+
# unit_cost - Float, internal cost per unit
|
|
17
|
+
#
|
|
18
|
+
# == Optional attributes:
|
|
19
|
+
# finish_date - String, "YYYY-MM-DD" when to stop (null = unlimited)
|
|
20
|
+
# description - String, detailed description
|
|
21
|
+
# billable - Boolean, whether to invoice (default: true)
|
|
22
|
+
# budget_relevant - Boolean, count toward budget (default: false)
|
|
23
|
+
# service_period_direction - String, "none", "forward", "backward"
|
|
24
|
+
# custom_properties - Hash, custom field values
|
|
25
|
+
#
|
|
26
|
+
# == Read-only attributes:
|
|
27
|
+
# id, price, cost, currency, recur_next_date, project (Hash),
|
|
28
|
+
# revenue_category (Hash), created_at, updated_at
|
|
29
|
+
#
|
|
30
|
+
# == Example:
|
|
31
|
+
# moco.post("projects/123/recurring_expenses", {
|
|
32
|
+
# start_date: "2024-01-01",
|
|
33
|
+
# period: "monthly",
|
|
34
|
+
# title: "Web Hosting",
|
|
35
|
+
# quantity: 1,
|
|
36
|
+
# unit: "Server",
|
|
37
|
+
# unit_price: 99.0,
|
|
38
|
+
# unit_cost: 50.0,
|
|
39
|
+
# billable: true
|
|
40
|
+
# })
|
|
41
|
+
#
|
|
42
|
+
# == Note:
|
|
43
|
+
# start_date and period cannot be modified after creation.
|
|
44
|
+
#
|
|
45
|
+
class RecurringExpense < BaseEntity
|
|
46
|
+
# Associations
|
|
47
|
+
def project
|
|
48
|
+
association(:project)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def to_s
|
|
52
|
+
"RecurringExpense ##{id} - #{title}"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
module Reports
|
|
5
|
+
# Represents a MOCO absences report (read-only)
|
|
6
|
+
class Absences < BaseEntity
|
|
7
|
+
def self.entity_path
|
|
8
|
+
"report/absences"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def to_s
|
|
12
|
+
"AbsencesReport"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
module Reports
|
|
5
|
+
# Represents a MOCO cashflow report (read-only)
|
|
6
|
+
class Cashflow < BaseEntity
|
|
7
|
+
def self.entity_path
|
|
8
|
+
"report/cashflow"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def to_s
|
|
12
|
+
"CashflowReport"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
module Reports
|
|
5
|
+
# Represents a MOCO finance report (read-only)
|
|
6
|
+
class Finance < BaseEntity
|
|
7
|
+
def self.entity_path
|
|
8
|
+
"report/finance"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def to_s
|
|
12
|
+
"FinanceReport"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
module Reports
|
|
5
|
+
# Represents a MOCO utilization report (read-only)
|
|
6
|
+
class Utilization < BaseEntity
|
|
7
|
+
def self.entity_path
|
|
8
|
+
"report/utilization"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def to_s
|
|
12
|
+
"UtilizationReport"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -1,8 +1,45 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module MOCO
|
|
4
|
-
# Represents a MOCO schedule entry
|
|
5
|
-
#
|
|
4
|
+
# Represents a MOCO schedule entry (absence/time-off)
|
|
5
|
+
# Note: For project planning, use PlanningEntry instead
|
|
6
|
+
#
|
|
7
|
+
# == Required attributes for create:
|
|
8
|
+
# date - String, "YYYY-MM-DD" date of absence
|
|
9
|
+
# absence_code - Integer, type of absence:
|
|
10
|
+
# 1 = unplannable absence
|
|
11
|
+
# 2 = public holiday
|
|
12
|
+
# 3 = sick day
|
|
13
|
+
# 4 = holiday/vacation
|
|
14
|
+
# 5 = other absence
|
|
15
|
+
#
|
|
16
|
+
# == Optional attributes:
|
|
17
|
+
# user_id - Integer, user ID (default: current user)
|
|
18
|
+
# am - Boolean, morning absence (default: true)
|
|
19
|
+
# pm - Boolean, afternoon absence (default: true)
|
|
20
|
+
# comment - String, comment/note
|
|
21
|
+
# symbol - Integer, 1-6 for half day visualization
|
|
22
|
+
#
|
|
23
|
+
# == Read-only attributes:
|
|
24
|
+
# id, assignment (Hash), user (Hash), created_at, updated_at
|
|
25
|
+
#
|
|
26
|
+
# == Example:
|
|
27
|
+
# # Full day vacation
|
|
28
|
+
# moco.schedules.create(
|
|
29
|
+
# date: "2024-01-15",
|
|
30
|
+
# absence_code: 4,
|
|
31
|
+
# user_id: 123,
|
|
32
|
+
# comment: "Annual leave"
|
|
33
|
+
# )
|
|
34
|
+
#
|
|
35
|
+
# # Half day sick (morning only)
|
|
36
|
+
# moco.schedules.create(
|
|
37
|
+
# date: "2024-01-16",
|
|
38
|
+
# absence_code: 3,
|
|
39
|
+
# am: true,
|
|
40
|
+
# pm: false
|
|
41
|
+
# )
|
|
42
|
+
#
|
|
6
43
|
class Schedule < BaseEntity
|
|
7
44
|
# Associations
|
|
8
45
|
def user
|
|
@@ -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
|