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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -1
  3. data/Gemfile.lock +44 -40
  4. data/README.md +53 -24
  5. data/lib/moco/client.rb +58 -0
  6. data/lib/moco/connection.rb +45 -22
  7. data/lib/moco/entities/activity.rb +31 -1
  8. data/lib/moco/entities/catalog_service.rb +54 -0
  9. data/lib/moco/entities/comment.rb +61 -0
  10. data/lib/moco/entities/company.rb +57 -2
  11. data/lib/moco/entities/contact.rb +56 -0
  12. data/lib/moco/entities/custom_property.rb +49 -0
  13. data/lib/moco/entities/deal.rb +38 -2
  14. data/lib/moco/entities/deal_category.rb +27 -0
  15. data/lib/moco/entities/employment.rb +55 -0
  16. data/lib/moco/entities/expense.rb +37 -2
  17. data/lib/moco/entities/expense_template.rb +39 -0
  18. data/lib/moco/entities/fixed_cost.rb +30 -0
  19. data/lib/moco/entities/holiday.rb +33 -2
  20. data/lib/moco/entities/hourly_rate.rb +33 -0
  21. data/lib/moco/entities/internal_hourly_rate.rb +32 -0
  22. data/lib/moco/entities/invoice.rb +81 -1
  23. data/lib/moco/entities/invoice_bookkeeping_export.rb +33 -0
  24. data/lib/moco/entities/invoice_payment.rb +51 -0
  25. data/lib/moco/entities/invoice_reminder.rb +51 -0
  26. data/lib/moco/entities/offer.rb +122 -0
  27. data/lib/moco/entities/offer_approval.rb +42 -0
  28. data/lib/moco/entities/payment_schedule.rb +48 -0
  29. data/lib/moco/entities/planning_entry.rb +43 -2
  30. data/lib/moco/entities/presence.rb +34 -2
  31. data/lib/moco/entities/profile.rb +24 -0
  32. data/lib/moco/entities/project.rb +76 -10
  33. data/lib/moco/entities/project_contract.rb +50 -0
  34. data/lib/moco/entities/project_group.rb +38 -0
  35. data/lib/moco/entities/purchase.rb +90 -0
  36. data/lib/moco/entities/purchase_bookkeeping_export.rb +34 -0
  37. data/lib/moco/entities/purchase_budget.rb +47 -0
  38. data/lib/moco/entities/purchase_category.rb +38 -0
  39. data/lib/moco/entities/purchase_draft.rb +25 -0
  40. data/lib/moco/entities/purchase_payment.rb +51 -0
  41. data/lib/moco/entities/receipt.rb +55 -0
  42. data/lib/moco/entities/recurring_expense.rb +55 -0
  43. data/lib/moco/entities/reports/absences.rb +16 -0
  44. data/lib/moco/entities/reports/cashflow.rb +16 -0
  45. data/lib/moco/entities/reports/finance.rb +16 -0
  46. data/lib/moco/entities/reports/utilization.rb +16 -0
  47. data/lib/moco/entities/schedule.rb +39 -2
  48. data/lib/moco/entities/tag.rb +30 -0
  49. data/lib/moco/entities/tagging.rb +27 -0
  50. data/lib/moco/entities/task.rb +25 -2
  51. data/lib/moco/entities/task_template.rb +38 -0
  52. data/lib/moco/entities/unit.rb +36 -0
  53. data/lib/moco/entities/user.rb +50 -2
  54. data/lib/moco/entities/user_role.rb +29 -0
  55. data/lib/moco/entities/vat_code_purchase.rb +29 -0
  56. data/lib/moco/entities/vat_code_sale.rb +29 -0
  57. data/lib/moco/entities/web_hook.rb +32 -2
  58. data/lib/moco/entities/work_time_adjustment.rb +51 -0
  59. data/lib/moco/version.rb +1 -1
  60. data/lib/moco.rb +51 -1
  61. data/moco.gemspec +38 -0
  62. 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
- # Provides methods for planning entry-specific associations
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
- # Provides methods for presence-specific operations and associations
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