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
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
# Represents a MOCO invoice payment record
|
|
5
|
+
# (Rechnungen / Zahlungen) for tracking received payments
|
|
6
|
+
#
|
|
7
|
+
# == Required attributes for create:
|
|
8
|
+
# date - String, "YYYY-MM-DD" payment date
|
|
9
|
+
# paid_total - Float, amount received
|
|
10
|
+
#
|
|
11
|
+
# == Optional attributes:
|
|
12
|
+
# invoice_id - Integer, invoice being paid (required unless description set)
|
|
13
|
+
# currency - String, payment currency (e.g., "EUR")
|
|
14
|
+
# partially_paid - Boolean, mark as partial payment
|
|
15
|
+
# description - String, payment description (required if no invoice_id)
|
|
16
|
+
#
|
|
17
|
+
# == Read-only attributes:
|
|
18
|
+
# id, invoice (Hash), paid_total_in_account_currency,
|
|
19
|
+
# created_at, updated_at
|
|
20
|
+
#
|
|
21
|
+
# == Example:
|
|
22
|
+
# moco.invoice_payments.create(
|
|
23
|
+
# date: "2024-01-20",
|
|
24
|
+
# invoice_id: 456,
|
|
25
|
+
# paid_total: 5000.0,
|
|
26
|
+
# currency: "EUR"
|
|
27
|
+
# )
|
|
28
|
+
#
|
|
29
|
+
# == Bulk create:
|
|
30
|
+
# moco.post("invoices/payments/bulk", {
|
|
31
|
+
# bulk_data: [
|
|
32
|
+
# { date: "2024-01-20", invoice_id: 123, paid_total: 1000 },
|
|
33
|
+
# { date: "2024-01-21", invoice_id: 456, paid_total: 2000 }
|
|
34
|
+
# ]
|
|
35
|
+
# })
|
|
36
|
+
#
|
|
37
|
+
# == Filtering:
|
|
38
|
+
# moco.invoice_payments.where(invoice_id: 123)
|
|
39
|
+
# moco.invoice_payments.where(date_from: "2024-01-01", date_to: "2024-01-31")
|
|
40
|
+
#
|
|
41
|
+
class InvoicePayment < BaseEntity
|
|
42
|
+
# Associations
|
|
43
|
+
def invoice
|
|
44
|
+
association(:invoice)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_s
|
|
48
|
+
"Payment ##{id} (#{date})"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
# Represents a MOCO invoice reminder (Mahnung / Zahlungserinnerung)
|
|
5
|
+
#
|
|
6
|
+
# == Required attributes for create:
|
|
7
|
+
# invoice_id - Integer, overdue invoice ID
|
|
8
|
+
#
|
|
9
|
+
# == Optional attributes:
|
|
10
|
+
# title - String, reminder title (uses default if omitted)
|
|
11
|
+
# text - String, reminder message (uses default if omitted)
|
|
12
|
+
# fee - Float, late payment fee
|
|
13
|
+
# date - String, "YYYY-MM-DD" reminder date
|
|
14
|
+
# due_date - String, "YYYY-MM-DD" new payment due date
|
|
15
|
+
#
|
|
16
|
+
# == Read-only attributes:
|
|
17
|
+
# id, status ("created" or "sent"), file_url, invoice (Hash),
|
|
18
|
+
# created_at, updated_at
|
|
19
|
+
#
|
|
20
|
+
# == Example:
|
|
21
|
+
# moco.invoice_reminders.create(
|
|
22
|
+
# invoice_id: 456,
|
|
23
|
+
# title: "Payment Reminder",
|
|
24
|
+
# text: "Please remit payment within 14 days.",
|
|
25
|
+
# fee: 25.0,
|
|
26
|
+
# date: "2024-02-01",
|
|
27
|
+
# due_date: "2024-02-15"
|
|
28
|
+
# )
|
|
29
|
+
#
|
|
30
|
+
# == Send by email:
|
|
31
|
+
# moco.post("invoice_reminders/123/send_email", {
|
|
32
|
+
# emails_to: "customer@example.com",
|
|
33
|
+
# subject: "Payment Reminder",
|
|
34
|
+
# text: "Please see attached reminder."
|
|
35
|
+
# })
|
|
36
|
+
#
|
|
37
|
+
# == Filtering:
|
|
38
|
+
# moco.invoice_reminders.where(invoice_id: 456)
|
|
39
|
+
# moco.invoice_reminders.where(date_from: "2024-01-01", date_to: "2024-01-31")
|
|
40
|
+
#
|
|
41
|
+
class InvoiceReminder < BaseEntity
|
|
42
|
+
# Associations
|
|
43
|
+
def invoice
|
|
44
|
+
association(:invoice)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_s
|
|
48
|
+
"Reminder ##{id} (#{date})"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
# Represents a MOCO offer/quote
|
|
5
|
+
#
|
|
6
|
+
# == Required attributes for create:
|
|
7
|
+
# recipient_address - String, full address (use \r\n for line breaks)
|
|
8
|
+
# date - String, "YYYY-MM-DD" offer date
|
|
9
|
+
# due_date - String, "YYYY-MM-DD" valid until date
|
|
10
|
+
# title - String, offer title (e.g., "Offer - Website Relaunch")
|
|
11
|
+
# tax - Float, tax rate percentage (e.g., 19.0)
|
|
12
|
+
# items - Array of Hashes, offer line items (see below)
|
|
13
|
+
#
|
|
14
|
+
# == Item types (for items array):
|
|
15
|
+
# { type: "title", title: "Section Title" }
|
|
16
|
+
# { type: "description", description: "Description text" }
|
|
17
|
+
# { type: "item", title: "Service", quantity: 10, unit: "h", unit_price: 150.0 }
|
|
18
|
+
# { type: "item", title: "Fixed Fee", net_total: 500.0 } # lump sum
|
|
19
|
+
# { type: "subtotal" }
|
|
20
|
+
# { type: "separator" }
|
|
21
|
+
# { type: "page-break" }
|
|
22
|
+
#
|
|
23
|
+
# == Optional attributes:
|
|
24
|
+
# company_id - Integer, customer company ID (set from project if project_id provided)
|
|
25
|
+
# deal_id - Integer, associated deal ID
|
|
26
|
+
# project_id - Integer, associated project ID
|
|
27
|
+
# currency - String, 3-letter code (required if no company/deal/project)
|
|
28
|
+
# salutation - String, greeting text
|
|
29
|
+
# footer - String, footer text
|
|
30
|
+
# discount - Float, discount percentage
|
|
31
|
+
# contact_id - Integer, customer contact ID
|
|
32
|
+
# change_address - String, "offer" or "customer"
|
|
33
|
+
# tags - Array of Strings
|
|
34
|
+
#
|
|
35
|
+
# == Read-only attributes:
|
|
36
|
+
# id, identifier, status, net_total, gross_total, created_at, updated_at
|
|
37
|
+
#
|
|
38
|
+
# == Example:
|
|
39
|
+
# moco.offers.create(
|
|
40
|
+
# deal_id: 123456,
|
|
41
|
+
# recipient_address: "Acme Corp\r\n123 Main St",
|
|
42
|
+
# date: "2024-01-15",
|
|
43
|
+
# due_date: "2024-02-15",
|
|
44
|
+
# title: "Offer - Website Relaunch",
|
|
45
|
+
# tax: 19.0,
|
|
46
|
+
# items: [
|
|
47
|
+
# { type: "title", title: "Development Services" },
|
|
48
|
+
# { type: "item", title: "Frontend Development", quantity: 40, unit: "h", unit_price: 150.0 },
|
|
49
|
+
# { type: "item", title: "Backend Development", quantity: 60, unit: "h", unit_price: 150.0 }
|
|
50
|
+
# ]
|
|
51
|
+
# )
|
|
52
|
+
#
|
|
53
|
+
class Offer < BaseEntity
|
|
54
|
+
# Update the offer status
|
|
55
|
+
def update_status(status)
|
|
56
|
+
client.put("offers/#{id}/update_status", { status: })
|
|
57
|
+
self
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Get the offer as PDF
|
|
61
|
+
def pdf
|
|
62
|
+
client.get("offers/#{id}.pdf")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Send the offer via email
|
|
66
|
+
def send_email(recipient:, subject:, text:, **options)
|
|
67
|
+
payload = {
|
|
68
|
+
recipient:,
|
|
69
|
+
subject:,
|
|
70
|
+
text:
|
|
71
|
+
}.merge(options)
|
|
72
|
+
|
|
73
|
+
client.post("offers/#{id}/send_email", payload)
|
|
74
|
+
self
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Assign offer to company, project, and/or deal
|
|
78
|
+
def assign(company_id: nil, project_id: nil, deal_id: nil)
|
|
79
|
+
payload = {}
|
|
80
|
+
payload[:company_id] = company_id if company_id
|
|
81
|
+
payload[:project_id] = project_id if project_id
|
|
82
|
+
payload[:deal_id] = deal_id if deal_id
|
|
83
|
+
|
|
84
|
+
client.put("offers/#{id}/assign", payload)
|
|
85
|
+
reload
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Get attachments for this offer
|
|
89
|
+
def attachments
|
|
90
|
+
client.get("offers/#{id}/attachments")
|
|
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
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Associations
|
|
106
|
+
def company
|
|
107
|
+
association(:customer, "Company")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def project
|
|
111
|
+
association(:project)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def deal
|
|
115
|
+
association(:deal)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def to_s
|
|
119
|
+
"#{identifier} - #{title}"
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
# Represents a MOCO offer customer approval (Kundenfreigabe)
|
|
5
|
+
# Allows customers to review and sign offers online
|
|
6
|
+
#
|
|
7
|
+
# == Read-only attributes:
|
|
8
|
+
# id, approval_url, offer_document_url, active,
|
|
9
|
+
# customer_full_name, customer_email, signature_url,
|
|
10
|
+
# signed_at, created_at, updated_at
|
|
11
|
+
#
|
|
12
|
+
# == Activation workflow:
|
|
13
|
+
# 1. Activate approval to generate shareable URL
|
|
14
|
+
# 2. Share offer_document_url with customer
|
|
15
|
+
# 3. Customer reviews and signs
|
|
16
|
+
# 4. Check signed_at to verify approval
|
|
17
|
+
#
|
|
18
|
+
# == Example:
|
|
19
|
+
# # Activate customer approval
|
|
20
|
+
# approval = moco.post("offers/123/customer_approval/activate")
|
|
21
|
+
#
|
|
22
|
+
# # Get approval status
|
|
23
|
+
# approval = moco.get("offers/123/customer_approval")
|
|
24
|
+
#
|
|
25
|
+
# # Deactivate (revoke access)
|
|
26
|
+
# moco.put("offers/123/customer_approval/deactivate")
|
|
27
|
+
#
|
|
28
|
+
# == Note:
|
|
29
|
+
# Check signed_at to determine if the customer has signed.
|
|
30
|
+
# Returns 404 if not yet activated.
|
|
31
|
+
#
|
|
32
|
+
class OfferApproval < BaseEntity
|
|
33
|
+
# Associations
|
|
34
|
+
def offer
|
|
35
|
+
association(:offer)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def to_s
|
|
39
|
+
"OfferApproval ##{id}"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
# Represents a MOCO project payment schedule entry
|
|
5
|
+
# (Geplante Abrechnungen) for fixed-price project milestones
|
|
6
|
+
#
|
|
7
|
+
# == Required attributes for create:
|
|
8
|
+
# net_total - Float, payment amount
|
|
9
|
+
# date - String, "YYYY-MM-DD" scheduled payment date
|
|
10
|
+
#
|
|
11
|
+
# == Optional attributes:
|
|
12
|
+
# title - String, milestone name (e.g., "First installment")
|
|
13
|
+
# description - String, milestone details (HTML allowed)
|
|
14
|
+
# checked - Boolean, mark as completed
|
|
15
|
+
#
|
|
16
|
+
# == Read-only attributes:
|
|
17
|
+
# id, project (Hash), billed, created_at, updated_at
|
|
18
|
+
#
|
|
19
|
+
# == Access methods:
|
|
20
|
+
# # All payment schedules across projects
|
|
21
|
+
# moco.payment_schedules.all
|
|
22
|
+
#
|
|
23
|
+
# # Filter by project
|
|
24
|
+
# moco.payment_schedules.where(project_id: 123)
|
|
25
|
+
#
|
|
26
|
+
# == Example:
|
|
27
|
+
# moco.post("projects/123/payment_schedules", {
|
|
28
|
+
# net_total: 5000.0,
|
|
29
|
+
# date: "2024-03-15",
|
|
30
|
+
# title: "Design Phase Complete"
|
|
31
|
+
# })
|
|
32
|
+
#
|
|
33
|
+
# == Filtering:
|
|
34
|
+
# moco.payment_schedules.where(from: "2024-01-01", to: "2024-12-31")
|
|
35
|
+
# moco.payment_schedules.where(checked: false) # unpaid only
|
|
36
|
+
# moco.payment_schedules.where(company_id: 456)
|
|
37
|
+
#
|
|
38
|
+
class PaymentSchedule < BaseEntity
|
|
39
|
+
# Associations
|
|
40
|
+
def project
|
|
41
|
+
association(:project)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def to_s
|
|
45
|
+
"PaymentSchedule ##{id} (#{date})"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -1,8 +1,49 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module MOCO
|
|
4
|
-
# Represents a MOCO planning entry
|
|
5
|
-
#
|
|
4
|
+
# Represents a MOCO planning entry (resource scheduling)
|
|
5
|
+
# For absences, use Schedule instead
|
|
6
|
+
#
|
|
7
|
+
# == Required attributes for create:
|
|
8
|
+
# project_id OR deal_id - Integer, must provide exactly one
|
|
9
|
+
# starts_on - String, "YYYY-MM-DD" start date
|
|
10
|
+
# ends_on - String, "YYYY-MM-DD" end date
|
|
11
|
+
# hours_per_day - Float/Integer, planned hours per day
|
|
12
|
+
#
|
|
13
|
+
# == Optional attributes:
|
|
14
|
+
# user_id - Integer, user ID (default: current user)
|
|
15
|
+
# task_id - Integer, task ID (only with project_id)
|
|
16
|
+
# comment - String, notes about the planning
|
|
17
|
+
# symbol - Integer, 1-10 for visual indicator:
|
|
18
|
+
# 1=home, 2=building, 3=car, 4=graduation cap, 5=cocktail,
|
|
19
|
+
# 6=bells, 7=baby carriage, 8=users, 9=moon, 10=info circle
|
|
20
|
+
# tentative - Boolean, true if this is a blocker/tentative
|
|
21
|
+
#
|
|
22
|
+
# == Read-only attributes:
|
|
23
|
+
# id, color, read_only, user (Hash), project (Hash), deal (Hash),
|
|
24
|
+
# series_id, series_repeat, created_at, updated_at
|
|
25
|
+
#
|
|
26
|
+
# == Example:
|
|
27
|
+
# # Plan user on project for a week
|
|
28
|
+
# moco.planning_entries.create(
|
|
29
|
+
# project_id: 123,
|
|
30
|
+
# task_id: 456,
|
|
31
|
+
# user_id: 789,
|
|
32
|
+
# starts_on: "2024-01-15",
|
|
33
|
+
# ends_on: "2024-01-19",
|
|
34
|
+
# hours_per_day: 6,
|
|
35
|
+
# comment: "Sprint planning"
|
|
36
|
+
# )
|
|
37
|
+
#
|
|
38
|
+
# # Plan user on deal (pre-sales)
|
|
39
|
+
# moco.planning_entries.create(
|
|
40
|
+
# deal_id: 123,
|
|
41
|
+
# starts_on: "2024-01-22",
|
|
42
|
+
# ends_on: "2024-01-22",
|
|
43
|
+
# hours_per_day: 4,
|
|
44
|
+
# tentative: true
|
|
45
|
+
# )
|
|
46
|
+
#
|
|
6
47
|
class PlanningEntry < BaseEntity
|
|
7
48
|
# Associations
|
|
8
49
|
def user
|
|
@@ -1,8 +1,40 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module MOCO
|
|
4
|
-
# Represents a MOCO presence entry
|
|
5
|
-
#
|
|
4
|
+
# Represents a MOCO presence entry (work time tracking)
|
|
5
|
+
#
|
|
6
|
+
# == Required attributes for create:
|
|
7
|
+
# date - String, "YYYY-MM-DD" date
|
|
8
|
+
# from - String, "HH:MM" start time (e.g., "08:00")
|
|
9
|
+
#
|
|
10
|
+
# == Optional attributes:
|
|
11
|
+
# to - String, "HH:MM" end time (e.g., "17:00"), can be blank for open entry
|
|
12
|
+
# is_home_office - Boolean, whether working from home (default: false)
|
|
13
|
+
#
|
|
14
|
+
# == Read-only attributes:
|
|
15
|
+
# id, user (Hash), created_at, updated_at
|
|
16
|
+
#
|
|
17
|
+
# == Class methods:
|
|
18
|
+
# Presence.touch(client) - Clock in/out (creates or closes presence)
|
|
19
|
+
#
|
|
20
|
+
# == Example:
|
|
21
|
+
# # Log work time
|
|
22
|
+
# moco.presences.create(
|
|
23
|
+
# date: "2024-01-15",
|
|
24
|
+
# from: "09:00",
|
|
25
|
+
# to: "17:30"
|
|
26
|
+
# )
|
|
27
|
+
#
|
|
28
|
+
# # Start work (open-ended)
|
|
29
|
+
# moco.presences.create(
|
|
30
|
+
# date: "2024-01-16",
|
|
31
|
+
# from: "08:30",
|
|
32
|
+
# is_home_office: true
|
|
33
|
+
# )
|
|
34
|
+
#
|
|
35
|
+
# # Clock in/out via touch
|
|
36
|
+
# MOCO::Presence.touch(moco)
|
|
37
|
+
#
|
|
6
38
|
class Presence < BaseEntity
|
|
7
39
|
# Define the specific API path for this entity as a class method
|
|
8
40
|
def self.entity_path
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
# Represents the current API user's profile
|
|
5
|
+
# Read-only singleton endpoint for the authenticated user
|
|
6
|
+
#
|
|
7
|
+
# == Read-only attributes:
|
|
8
|
+
# id, firstname, lastname, email, unit (Hash),
|
|
9
|
+
# created_at, updated_at
|
|
10
|
+
#
|
|
11
|
+
# == Usage:
|
|
12
|
+
# profile = moco.profile.get
|
|
13
|
+
# puts "Logged in as: #{profile.firstname} #{profile.lastname}"
|
|
14
|
+
#
|
|
15
|
+
# == Note:
|
|
16
|
+
# This returns information about the user who owns the API key.
|
|
17
|
+
# For other user information, use moco.users.
|
|
18
|
+
#
|
|
19
|
+
class Profile < BaseEntity
|
|
20
|
+
def to_s
|
|
21
|
+
"#{firstname} #{lastname}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -1,6 +1,58 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module MOCO
|
|
4
|
+
# Represents a MOCO project
|
|
5
|
+
#
|
|
6
|
+
# == Required attributes for create:
|
|
7
|
+
# name - String, project name (e.g., "Website Relaunch")
|
|
8
|
+
# currency - String, 3-letter currency code (e.g., "EUR", "USD", "CHF")
|
|
9
|
+
# start_date - String, "YYYY-MM-DD" format (required, must be 1st of month if retainer)
|
|
10
|
+
# finish_date - String, "YYYY-MM-DD" format (required, must be last of month if retainer)
|
|
11
|
+
# fixed_price - Boolean, true for fixed-price projects
|
|
12
|
+
# retainer - Boolean, true for retainer/recurring projects
|
|
13
|
+
# leader_id - Integer, user ID of the project leader
|
|
14
|
+
# customer_id - Integer, company ID of the customer
|
|
15
|
+
# identifier - String, project identifier (e.g., "P-123") - only required if manual numbering
|
|
16
|
+
#
|
|
17
|
+
# == Optional attributes:
|
|
18
|
+
# co_leader_id - Integer, user ID of co-leader
|
|
19
|
+
# deal_id - Integer, associated deal ID
|
|
20
|
+
# project_group_id - Integer, project group ID
|
|
21
|
+
# contact_id - Integer, primary contact ID
|
|
22
|
+
# secondary_contact_id - Integer, secondary contact ID
|
|
23
|
+
# billing_contact_id - Integer, billing contact ID
|
|
24
|
+
# billing_address - String, billing address (multiline with \n)
|
|
25
|
+
# billing_email_to - String, email for invoices
|
|
26
|
+
# billing_email_cc - String, CC email for invoices
|
|
27
|
+
# billing_notes - String, notes for billing
|
|
28
|
+
# billing_variant - String, "project", "task", or "user" (default: "project")
|
|
29
|
+
# setting_include_time_report - Boolean, include time report with invoices
|
|
30
|
+
# hourly_rate - Float/Integer, hourly rate (meaning depends on billing_variant)
|
|
31
|
+
# budget - Float/Integer, total budget
|
|
32
|
+
# budget_monthly - Float/Integer, monthly budget (required if retainer: true)
|
|
33
|
+
# budget_expenses - Float/Integer, expenses budget
|
|
34
|
+
# tags - Array of Strings, e.g., ["Print", "Digital"]
|
|
35
|
+
# custom_properties - Hash, e.g., {"PO-Number": "123-ABC"}
|
|
36
|
+
# info - String, additional info
|
|
37
|
+
#
|
|
38
|
+
# == Read-only attributes (returned by API):
|
|
39
|
+
# id, active, color, customer (Hash), leader (Hash), co_leader (Hash),
|
|
40
|
+
# deal (Hash), tasks (Array), contracts (Array), project_group (Hash),
|
|
41
|
+
# created_at, updated_at
|
|
42
|
+
#
|
|
43
|
+
# == Example:
|
|
44
|
+
# moco.projects.create(
|
|
45
|
+
# name: "Website Relaunch",
|
|
46
|
+
# currency: "EUR",
|
|
47
|
+
# start_date: "2024-01-01",
|
|
48
|
+
# finish_date: "2024-12-31",
|
|
49
|
+
# fixed_price: false,
|
|
50
|
+
# retainer: false,
|
|
51
|
+
# leader_id: 123456,
|
|
52
|
+
# customer_id: 789012,
|
|
53
|
+
# budget: 50000
|
|
54
|
+
# )
|
|
55
|
+
#
|
|
4
56
|
class Project < BaseEntity
|
|
5
57
|
def customer
|
|
6
58
|
# Use the association method to fetch the customer
|
|
@@ -31,20 +83,34 @@ module MOCO
|
|
|
31
83
|
end
|
|
32
84
|
|
|
33
85
|
# Fetches tasks associated with this project.
|
|
86
|
+
# Always returns a NestedCollectionProxy for consistent interface.
|
|
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.
|
|
34
90
|
def tasks
|
|
35
|
-
# If tasks are already embedded in the attributes (e.g., from projects.assigned),
|
|
36
|
-
# return them directly instead of making a new API call
|
|
37
|
-
embedded_tasks = attributes[:tasks]
|
|
38
|
-
if embedded_tasks.is_a?(Array) && embedded_tasks.all? { |t| t.is_a?(MOCO::Task) }
|
|
39
|
-
return embedded_tasks
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Otherwise, create a proxy for fetching tasks via API
|
|
43
|
-
# Don't cache the proxy - create a fresh one each time
|
|
44
|
-
# This ensures we get fresh data when tasks are created/updated/deleted
|
|
45
91
|
MOCO::NestedCollectionProxy.new(client, self, :tasks, "Task")
|
|
46
92
|
end
|
|
47
93
|
|
|
94
|
+
# Fetches contracts associated with this project.
|
|
95
|
+
def contracts
|
|
96
|
+
MOCO::NestedCollectionProxy.new(client, self, :contracts, "ProjectContract")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Fetches payment schedules associated with this project.
|
|
100
|
+
def payment_schedules
|
|
101
|
+
MOCO::NestedCollectionProxy.new(client, self, :payment_schedules, "PaymentSchedule")
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Fetches recurring expenses associated with this project.
|
|
105
|
+
def recurring_expenses
|
|
106
|
+
MOCO::NestedCollectionProxy.new(client, self, :recurring_expenses, "RecurringExpense")
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Get the project group
|
|
110
|
+
def project_group
|
|
111
|
+
association(:project_group)
|
|
112
|
+
end
|
|
113
|
+
|
|
48
114
|
def to_s
|
|
49
115
|
"Project #{identifier} \"#{name}\" (#{id})"
|
|
50
116
|
end
|
|
@@ -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
|