moco-ruby 1.0.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -1
  3. data/Gemfile.lock +47 -43
  4. data/README.md +63 -24
  5. data/copy_project.rb +337 -0
  6. data/lib/moco/client.rb +58 -0
  7. data/lib/moco/connection.rb +45 -22
  8. data/lib/moco/entities/activity.rb +31 -1
  9. data/lib/moco/entities/catalog_service.rb +54 -0
  10. data/lib/moco/entities/comment.rb +61 -0
  11. data/lib/moco/entities/company.rb +57 -2
  12. data/lib/moco/entities/contact.rb +56 -0
  13. data/lib/moco/entities/custom_property.rb +49 -0
  14. data/lib/moco/entities/deal.rb +38 -2
  15. data/lib/moco/entities/deal_category.rb +27 -0
  16. data/lib/moco/entities/employment.rb +55 -0
  17. data/lib/moco/entities/expense.rb +37 -2
  18. data/lib/moco/entities/expense_template.rb +39 -0
  19. data/lib/moco/entities/fixed_cost.rb +30 -0
  20. data/lib/moco/entities/holiday.rb +33 -2
  21. data/lib/moco/entities/hourly_rate.rb +33 -0
  22. data/lib/moco/entities/internal_hourly_rate.rb +32 -0
  23. data/lib/moco/entities/invoice.rb +81 -1
  24. data/lib/moco/entities/invoice_bookkeeping_export.rb +33 -0
  25. data/lib/moco/entities/invoice_payment.rb +51 -0
  26. data/lib/moco/entities/invoice_reminder.rb +51 -0
  27. data/lib/moco/entities/offer.rb +122 -0
  28. data/lib/moco/entities/offer_approval.rb +42 -0
  29. data/lib/moco/entities/payment_schedule.rb +48 -0
  30. data/lib/moco/entities/planning_entry.rb +43 -2
  31. data/lib/moco/entities/presence.rb +34 -2
  32. data/lib/moco/entities/profile.rb +24 -0
  33. data/lib/moco/entities/project.rb +76 -2
  34. data/lib/moco/entities/project_contract.rb +50 -0
  35. data/lib/moco/entities/project_group.rb +38 -0
  36. data/lib/moco/entities/purchase.rb +90 -0
  37. data/lib/moco/entities/purchase_bookkeeping_export.rb +34 -0
  38. data/lib/moco/entities/purchase_budget.rb +47 -0
  39. data/lib/moco/entities/purchase_category.rb +38 -0
  40. data/lib/moco/entities/purchase_draft.rb +25 -0
  41. data/lib/moco/entities/purchase_payment.rb +51 -0
  42. data/lib/moco/entities/receipt.rb +55 -0
  43. data/lib/moco/entities/recurring_expense.rb +55 -0
  44. data/lib/moco/entities/reports/absences.rb +16 -0
  45. data/lib/moco/entities/reports/cashflow.rb +16 -0
  46. data/lib/moco/entities/reports/finance.rb +16 -0
  47. data/lib/moco/entities/reports/utilization.rb +16 -0
  48. data/lib/moco/entities/schedule.rb +39 -2
  49. data/lib/moco/entities/tag.rb +30 -0
  50. data/lib/moco/entities/tagging.rb +27 -0
  51. data/lib/moco/entities/task.rb +25 -2
  52. data/lib/moco/entities/task_template.rb +38 -0
  53. data/lib/moco/entities/unit.rb +36 -0
  54. data/lib/moco/entities/user.rb +50 -2
  55. data/lib/moco/entities/user_role.rb +29 -0
  56. data/lib/moco/entities/vat_code_purchase.rb +29 -0
  57. data/lib/moco/entities/vat_code_sale.rb +29 -0
  58. data/lib/moco/entities/web_hook.rb +32 -2
  59. data/lib/moco/entities/work_time_adjustment.rb +51 -0
  60. data/lib/moco/sync.rb +112 -6
  61. data/lib/moco/version.rb +1 -1
  62. data/lib/moco-ruby.rb +6 -0
  63. data/lib/moco.rb +51 -1
  64. data/moco.gemspec +5 -3
  65. data/sync_activity.rb +8 -2
  66. metadata +54 -14
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MOCO
4
+ # Represents a MOCO expense template (Zusatzleistungs-Katalog)
5
+ # Pre-defined templates for project additional services
6
+ #
7
+ # == Required attributes for create:
8
+ # title - String, template name (e.g., "Hosting L")
9
+ # unit - String, unit type (e.g., "month", "hours", "pieces")
10
+ # unit_price - Float, price per unit
11
+ # currency - String, currency code (e.g., "EUR")
12
+ #
13
+ # == Optional attributes:
14
+ # description - String, detailed description
15
+ # unit_cost - Float, internal cost per unit
16
+ #
17
+ # == Read-only attributes:
18
+ # id, revenue_category (Hash), created_at, updated_at
19
+ #
20
+ # == Example:
21
+ # moco.expense_templates.create(
22
+ # title: "Monthly Hosting",
23
+ # description: "Web hosting with monitoring and backup",
24
+ # unit: "month",
25
+ # unit_price: 50.0,
26
+ # unit_cost: 30.0,
27
+ # currency: "EUR"
28
+ # )
29
+ #
30
+ class ExpenseTemplate < BaseEntity
31
+ def self.entity_path
32
+ "account/expense_templates"
33
+ end
34
+
35
+ def to_s
36
+ name.to_s
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MOCO
4
+ # Represents a MOCO fixed cost entry (Fixkosten)
5
+ # Read-only access to company fixed costs for reporting
6
+ #
7
+ # == Read-only attributes:
8
+ # id, title, description, costs (Array of monthly amounts),
9
+ # created_at, updated_at
10
+ #
11
+ # == Costs array format:
12
+ # [{ year: 2024, month: 1, amount: 50000.0 }, ...]
13
+ #
14
+ # == Filtering:
15
+ # moco.fixed_costs.where(year: 2024)
16
+ #
17
+ # == Note:
18
+ # Fixed costs are configured in MOCO's admin interface.
19
+ # This endpoint provides read-only access for reporting.
20
+ #
21
+ class FixedCost < BaseEntity
22
+ def self.entity_path
23
+ "account/fixed_costs"
24
+ end
25
+
26
+ def to_s
27
+ name.to_s
28
+ end
29
+ end
30
+ end
@@ -1,8 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MOCO
4
- # Represents a MOCO holiday entry
5
- # Provides methods for holiday-specific associations
4
+ # Represents a MOCO user holiday/vacation entitlement record
5
+ # German: "Urlaubsanspruch"
6
+ #
7
+ # == Required attributes for create:
8
+ # user_id - Integer, the user this holiday entitlement belongs to
9
+ # year - Integer, the year (e.g., 2024)
10
+ # title - String, description (e.g., "Urlaubsanspruch 80%")
11
+ # days - Integer/Float, number of vacation days entitled
12
+ #
13
+ # == Optional attributes:
14
+ # creator_id - Integer, user who created this record
15
+ #
16
+ # == Read-only attributes:
17
+ # id, hours (auto-calculated from days), user (Hash), creator (Hash),
18
+ # created_at, updated_at
19
+ #
20
+ # == Example:
21
+ # # Create holiday entitlement for a user
22
+ # moco.holidays.create(
23
+ # user_id: 123,
24
+ # year: 2024,
25
+ # title: "Annual vacation entitlement",
26
+ # days: 25
27
+ # )
28
+ #
29
+ # == Filtering:
30
+ # moco.holidays.where(year: 2024)
31
+ # moco.holidays.where(user_id: 123)
32
+ #
33
+ # == Note:
34
+ # Holiday days are converted to hours using the user's daily hours setting.
35
+ # 10 days at 8h/day = 80 hours, 10 days at 5h/day = 50 hours.
36
+ #
6
37
  class Holiday < BaseEntity
7
38
  # Override entity_path to match API path
8
39
  def self.entity_path
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MOCO
4
+ # Represents MOCO hourly rates (Stundensätze)
5
+ # Read-only access to billing rates by task and user
6
+ #
7
+ # == Read-only structure:
8
+ # defaults_rates - Array of default rates per currency
9
+ # tasks - Array of tasks with their rates per currency
10
+ # users - Array of users with their rates per currency
11
+ #
12
+ # == Rate format:
13
+ # { currency: "EUR", hourly_rate: 150.0 }
14
+ #
15
+ # == Filtering:
16
+ # moco.hourly_rates.where(company_id: 123) # customer-specific rates
17
+ # moco.hourly_rates.where(include_archived_users: true)
18
+ #
19
+ # == Note:
20
+ # Hourly rates are configured in MOCO's admin interface.
21
+ # This endpoint provides read-only access.
22
+ # Without company_id, returns global default rates.
23
+ #
24
+ class HourlyRate < BaseEntity
25
+ def self.entity_path
26
+ "account/hourly_rates"
27
+ end
28
+
29
+ def to_s
30
+ "#{name} - #{rate}"
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MOCO
4
+ # Represents MOCO internal hourly rates (Interne Stundensätze)
5
+ # Internal cost rates per user for profitability calculations
6
+ #
7
+ # == Read-only structure (per user):
8
+ # id, full_name, rates (Array by year)
9
+ #
10
+ # == Rate format:
11
+ # { year: 2024, rate: 120.0 }
12
+ #
13
+ # == Filtering:
14
+ # moco.internal_hourly_rates.where(years: "2024")
15
+ # moco.internal_hourly_rates.where(years: "2023,2024")
16
+ # moco.internal_hourly_rates.where(unit_id: 123)
17
+ # moco.internal_hourly_rates.where(include_archived: true)
18
+ #
19
+ # == Updating rates:
20
+ # Use PATCH with year and rates array:
21
+ # { year: 2024, rates: [{ user_id: 123, rate: 140.0 }] }
22
+ #
23
+ class InternalHourlyRate < BaseEntity
24
+ def self.entity_path
25
+ "account/internal_hourly_rates"
26
+ end
27
+
28
+ def to_s
29
+ "#{name} - #{rate}"
30
+ end
31
+ end
32
+ end
@@ -2,7 +2,60 @@
2
2
 
3
3
  module MOCO
4
4
  # Represents a MOCO invoice
5
- # Provides methods for invoice-specific operations and associations
5
+ #
6
+ # == Required attributes for create:
7
+ # customer_id - Integer, customer company ID
8
+ # recipient_address - String, full address (use \n for line breaks)
9
+ # date - String, "YYYY-MM-DD" invoice date
10
+ # due_date - String, "YYYY-MM-DD" payment due date
11
+ # title - String, invoice title (e.g., "Invoice")
12
+ # tax - Float, tax rate percentage (e.g., 19.0)
13
+ # currency - String, 3-letter code (e.g., "EUR")
14
+ # items - Array of Hashes, invoice line items (see below)
15
+ #
16
+ # == Item types (for items array):
17
+ # { type: "title", title: "Section Title" }
18
+ # { type: "description", description: "Some description text" }
19
+ # { type: "item", title: "Service", quantity: 10, unit: "h", unit_price: 150.0 }
20
+ # { type: "item", title: "Fixed Fee", net_total: 500.0 } # lump sum
21
+ # { type: "subtotal" }
22
+ # { type: "separator" }
23
+ # { type: "page-break" }
24
+ #
25
+ # == Optional attributes:
26
+ # project_id - Integer, associated project ID
27
+ # status - String, "created" or "draft" (default: "created")
28
+ # service_period_from - String, "YYYY-MM-DD"
29
+ # service_period_to - String, "YYYY-MM-DD"
30
+ # change_address - String, "invoice", "project", or "customer"
31
+ # salutation - String, greeting text (HTML allowed)
32
+ # footer - String, footer text (HTML allowed)
33
+ # discount - Float, discount percentage
34
+ # cash_discount - Float, early payment discount percentage
35
+ # cash_discount_days - Integer, days for early payment discount
36
+ # tags - Array of Strings
37
+ # custom_properties - Hash
38
+ #
39
+ # == Read-only attributes:
40
+ # id, identifier, status, net_total, gross_total, payments, reminders,
41
+ # created_at, updated_at
42
+ #
43
+ # == Example:
44
+ # moco.invoices.create(
45
+ # customer_id: 123456,
46
+ # recipient_address: "Acme Corp\n123 Main St\n12345 City",
47
+ # date: "2024-01-15",
48
+ # due_date: "2024-02-15",
49
+ # title: "Invoice",
50
+ # tax: 19.0,
51
+ # currency: "EUR",
52
+ # items: [
53
+ # { type: "title", title: "Services January 2024" },
54
+ # { type: "item", title: "Development", quantity: 40, unit: "h", unit_price: 150.0 },
55
+ # { type: "item", title: "Project Management", quantity: 8, unit: "h", unit_price: 120.0 }
56
+ # ]
57
+ # )
58
+ #
6
59
  class Invoice < BaseEntity
7
60
  # Instance methods for invoice-specific operations
8
61
  def update_status(status)
@@ -37,6 +90,33 @@ module MOCO
37
90
  self
38
91
  end
39
92
 
93
+ # Get attachments for this invoice
94
+ def attachments
95
+ client.get("invoices/#{id}/attachments")
96
+ end
97
+
98
+ # Add an attachment to the invoice
99
+ def add_attachment(file_data)
100
+ client.post("invoices/#{id}/attachments", file_data)
101
+ self
102
+ end
103
+
104
+ # Delete an attachment from the invoice
105
+ def delete_attachment(attachment_id)
106
+ client.delete("invoices/#{id}/attachments/#{attachment_id}")
107
+ self
108
+ end
109
+
110
+ # Fetches payments for this invoice
111
+ def payments
112
+ MOCO::NestedCollectionProxy.new(client, self, :payments, "InvoicePayment")
113
+ end
114
+
115
+ # Fetches reminders for this invoice
116
+ def reminders
117
+ MOCO::NestedCollectionProxy.new(client, self, :reminders, "InvoiceReminder")
118
+ end
119
+
40
120
  # Associations
41
121
  def company
42
122
  association(:customer, "Company")
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MOCO
4
+ # Represents a MOCO invoice bookkeeping export (Buchhaltungsexporte)
5
+ # Exports invoice data for accounting systems
6
+ #
7
+ # == Read-only attributes:
8
+ # id, from, to, file_url, user (Hash),
9
+ # created_at, updated_at
10
+ #
11
+ # == Filtering:
12
+ # moco.invoice_bookkeeping_exports.all
13
+ # moco.invoice_bookkeeping_exports.where(from: "2024-01-01", to: "2024-01-31")
14
+ #
15
+ # == Note:
16
+ # Bookkeeping exports are generated via MOCO's finance interface.
17
+ # This endpoint provides read-only access to export records.
18
+ #
19
+ class InvoiceBookkeepingExport < BaseEntity
20
+ # Custom path since it's nested under invoices
21
+ def self.entity_path
22
+ "invoices/bookkeeping_exports"
23
+ end
24
+
25
+ def user
26
+ association(:user, "User")
27
+ end
28
+
29
+ def to_s
30
+ "InvoiceBookkeepingExport #{id} (#{from} - #{to})"
31
+ end
32
+ end
33
+ end
@@ -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