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
@@ -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,12 +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
- # Don't cache the proxy - create a fresh one each time
36
- # This ensures we get fresh data when tasks are created/updated/deleted
37
91
  MOCO::NestedCollectionProxy.new(client, self, :tasks, "Task")
38
92
  end
39
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
+
40
114
  def to_s
41
115
  "Project #{identifier} \"#{name}\" (#{id})"
42
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
@@ -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