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
@@ -1,8 +1,63 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MOCO
4
- # Represents a MOCO company (customer)
5
- # Provides methods for company-specific associations
4
+ # Represents a MOCO company (customer, supplier, or organization)
5
+ #
6
+ # == Required attributes for create:
7
+ # name - String, company name (e.g., "Acme Corp")
8
+ # type - String, one of: "customer", "supplier", "organization"
9
+ # currency - String, 3-letter code (e.g., "EUR") - required for customers only
10
+ #
11
+ # == Optional attributes (all types):
12
+ # identifier - String, company identifier (e.g., "K-123") - required if manual numbering
13
+ # country_code - String, ISO Alpha-2 code in uppercase (e.g., "DE", "CH", "US")
14
+ # vat_identifier - String, EU VAT ID (e.g., "DE123456789")
15
+ # website - String, company website URL
16
+ # phone - String, phone number
17
+ # fax - String, fax number
18
+ # email - String, main email address
19
+ # billing_email_cc - String, CC for billing emails
20
+ # billing_notes - String, notes for billing
21
+ # address - String, full address (use \n for line breaks)
22
+ # info - String, additional information
23
+ # tags - Array of Strings, e.g., ["Network", "Print"]
24
+ # custom_properties - Hash, e.g., {"UID": "123-ABC"}
25
+ # user_id - Integer, responsible person (user ID)
26
+ # footer - String, HTML footer for invoices
27
+ # alternative_correspondence_language - Boolean, use alternative language
28
+ #
29
+ # == Customer-specific attributes:
30
+ # customer_tax - Float, tax rate for customer (e.g., 19.0)
31
+ # default_invoice_due_days - Integer, payment terms (e.g., 30)
32
+ # debit_number - Integer, for bookkeeping (e.g., 10000)
33
+ #
34
+ # == Supplier-specific attributes:
35
+ # bank_owner - String, bank account holder name
36
+ # iban - String, bank account IBAN
37
+ # bank_bic - String, bank BIC/SWIFT code
38
+ # supplier_tax - Float, tax rate for supplier
39
+ # credit_number - Integer, for bookkeeping (e.g., 70000)
40
+ #
41
+ # == Read-only attributes (returned by API):
42
+ # id, intern, projects (Array), user (Hash), created_at, updated_at
43
+ #
44
+ # == Example:
45
+ # # Create a customer
46
+ # moco.companies.create(
47
+ # name: "Acme Corp",
48
+ # type: "customer",
49
+ # currency: "EUR",
50
+ # country_code: "DE",
51
+ # email: "info@acme.com"
52
+ # )
53
+ #
54
+ # # Create a supplier
55
+ # moco.companies.create(
56
+ # name: "Office Supplies Inc",
57
+ # type: "supplier",
58
+ # iban: "DE89370400440532013000"
59
+ # )
60
+ #
6
61
  class Company < BaseEntity
7
62
  # Associations
8
63
  def projects
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MOCO
4
+ # Represents a MOCO contact (person/Ansprechpartner)
5
+ # Contacts are people associated with companies
6
+ #
7
+ # == Required attributes for create:
8
+ # lastname - String, last name (e.g., "Muster")
9
+ # gender - String, one of: "F" (female), "M" (male), "U" (unknown/diverse)
10
+ #
11
+ # == Optional attributes:
12
+ # firstname - String, first name
13
+ # company_id - Integer, associated company ID
14
+ # user_id - Integer, responsible user ID (default: current user)
15
+ # title - String, title (e.g., "Dr.", "Prof.")
16
+ # job_position - String, job title (e.g., "Account Manager")
17
+ # mobile_phone - String, mobile phone number
18
+ # work_phone - String, work phone number
19
+ # work_fax - String, work fax number
20
+ # work_email - String, work email address
21
+ # work_address - String, work address (use \n for line breaks)
22
+ # home_email - String, personal email
23
+ # home_address - String, home address
24
+ # birthday - String, "YYYY-MM-DD" format (e.g., "1990-05-22")
25
+ # info - String, additional notes
26
+ # tags - Array of Strings, e.g., ["VIP", "Newsletter"]
27
+ #
28
+ # == Read-only attributes (returned by API):
29
+ # id, avatar_url, company (Hash), created_at, updated_at
30
+ #
31
+ # == Example:
32
+ # moco.contacts.create(
33
+ # firstname: "John",
34
+ # lastname: "Doe",
35
+ # gender: "M",
36
+ # company_id: 123456,
37
+ # work_email: "john.doe@example.com",
38
+ # job_position: "CTO"
39
+ # )
40
+ #
41
+ class Contact < BaseEntity
42
+ # Override entity_path to match API path
43
+ def self.entity_path
44
+ "contacts/people"
45
+ end
46
+
47
+ # Associations
48
+ def company
49
+ association(:company)
50
+ end
51
+
52
+ def to_s
53
+ "#{firstname} #{lastname}"
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MOCO
4
+ # Represents a MOCO custom property/field definition (Eigene Felder)
5
+ #
6
+ # == Required attributes for create:
7
+ # name - String, field name (e.g., "Purchase Order Number")
8
+ # kind - String, field type:
9
+ # "String", "Textarea", "Link", "Boolean",
10
+ # "Select", "MultiSelect", "Date"
11
+ # entity - String, entity type this field applies to:
12
+ # "Project", "Customer", "Deal", etc.
13
+ #
14
+ # == Optional attributes:
15
+ # placeholder - String, placeholder text for input
16
+ # placeholder_alt - String, placeholder in alternative language
17
+ # print_on_invoice - Boolean, show on invoices
18
+ # print_on_offer - Boolean, show on offers
19
+ # print_on_timesheet - Boolean, show on timesheets
20
+ # notification_enabled - Boolean, send notification for Date fields
21
+ # api_only - Boolean, hide from UI (API access only)
22
+ # defaults - Array, options for Select/MultiSelect types
23
+ #
24
+ # == Read-only attributes:
25
+ # id, name_alt, created_at, updated_at
26
+ #
27
+ # == Example:
28
+ # # Create a dropdown field
29
+ # moco.custom_properties.create(
30
+ # name: "Project Type",
31
+ # kind: "Select",
32
+ # entity: "Project",
33
+ # defaults: ["Website", "Mobile App", "API Integration"],
34
+ # print_on_invoice: true
35
+ # )
36
+ #
37
+ # == Note:
38
+ # `kind` and `entity` cannot be changed after creation.
39
+ #
40
+ class CustomProperty < BaseEntity
41
+ def self.entity_path
42
+ "account/custom_properties"
43
+ end
44
+
45
+ def to_s
46
+ name.to_s
47
+ end
48
+ end
49
+ end
@@ -1,8 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MOCO
4
- # Represents a MOCO deal
5
- # Provides methods for deal-specific associations
4
+ # Represents a MOCO deal/lead
5
+ #
6
+ # == Required attributes for create:
7
+ # name - String, deal name (e.g., "Website Relaunch")
8
+ # currency - String, 3-letter code (e.g., "EUR", "USD")
9
+ # money - Float/Integer, deal value (e.g., 25000)
10
+ # reminder_date - String, "YYYY-MM-DD" format for follow-up
11
+ # user_id - Integer, responsible user ID
12
+ # deal_category_id - Integer, deal category/stage ID
13
+ #
14
+ # == Optional attributes:
15
+ # company_id - Integer, associated company ID
16
+ # person_id - Integer, associated contact ID
17
+ # info - String, additional information
18
+ # status - String, one of: "potential", "pending", "won", "lost", "dropped"
19
+ # (default: "pending")
20
+ # closed_on - String, "YYYY-MM-DD" when deal was closed
21
+ # service_period_from - String, "YYYY-MM-DD" (must be 1st of month)
22
+ # service_period_to - String, "YYYY-MM-DD" (must be last of month)
23
+ # tags - Array of Strings, e.g., ["Important", "Q1"]
24
+ # custom_properties - Hash, e.g., {"Source": "Website"}
25
+ #
26
+ # == Read-only attributes (returned by API):
27
+ # id, user (Hash), company (Hash), person (Hash), category (Hash),
28
+ # created_at, updated_at
29
+ #
30
+ # == Example:
31
+ # moco.deals.create(
32
+ # name: "New Website Project",
33
+ # currency: "EUR",
34
+ # money: 50000,
35
+ # reminder_date: "2024-02-01",
36
+ # user_id: 123,
37
+ # deal_category_id: 456,
38
+ # company_id: 789,
39
+ # status: "pending"
40
+ # )
41
+ #
6
42
  class Deal < BaseEntity
7
43
  # Associations
8
44
  def company
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MOCO
4
+ # Represents a MOCO deal category/stage (Akquise-Stufen)
5
+ #
6
+ # == Required attributes for create:
7
+ # name - String, category name (e.g., "Contact", "Qualified", "Proposal")
8
+ # probability - Integer, win probability percentage (0-100)
9
+ #
10
+ # == Read-only attributes:
11
+ # id, created_at, updated_at
12
+ #
13
+ # == Example:
14
+ # moco.deal_categories.create(
15
+ # name: "Qualified Lead",
16
+ # probability: 25
17
+ # )
18
+ #
19
+ # == Note:
20
+ # Categories cannot be deleted if deals are using them.
21
+ #
22
+ class DealCategory < BaseEntity
23
+ def to_s
24
+ name.to_s
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MOCO
4
+ # Represents a MOCO user employment/work schedule record
5
+ # Access via: moco.employments.where(user_id: 123) or user.employments
6
+ #
7
+ # == Required attributes for create:
8
+ # user_id - Integer, the user this employment belongs to
9
+ # pattern - Hash, weekly work schedule:
10
+ # { "am": [0, 4, 4, 4, 4], "pm": [0, 4, 4, 4, 4] }
11
+ # Arrays represent Mon-Fri morning/afternoon hours
12
+ #
13
+ # == Optional attributes:
14
+ # from - String, "YYYY-MM-DD" when employment starts (default: today)
15
+ # to - String, "YYYY-MM-DD" when employment ends (null = ongoing)
16
+ #
17
+ # == Read-only attributes:
18
+ # id, weekly_target_hours, user (Hash), created_at, updated_at
19
+ #
20
+ # == Example:
21
+ # # Create full-time employment (8h/day Mon-Fri)
22
+ # moco.employments.create(
23
+ # user_id: 123,
24
+ # pattern: {
25
+ # am: [4, 4, 4, 4, 4],
26
+ # pm: [4, 4, 4, 4, 4]
27
+ # },
28
+ # from: "2024-01-01"
29
+ # )
30
+ #
31
+ # # Create part-time (4h/day Tue-Thu)
32
+ # moco.employments.create(
33
+ # user_id: 123,
34
+ # pattern: {
35
+ # am: [0, 4, 4, 4, 0],
36
+ # pm: [0, 0, 0, 0, 0]
37
+ # },
38
+ # from: "2024-01-01"
39
+ # )
40
+ #
41
+ class Employment < BaseEntity
42
+ def self.entity_path
43
+ "users/employments"
44
+ end
45
+
46
+ # Associations
47
+ def user
48
+ association(:user)
49
+ end
50
+
51
+ def to_s
52
+ "Employment ##{id} (#{from} - #{self.to || 'present'})"
53
+ end
54
+ end
55
+ end
@@ -1,8 +1,43 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MOCO
4
- # Represents a MOCO expense
5
- # Provides methods for expense-specific operations and associations
4
+ # Represents a MOCO project expense (additional service)
5
+ # Expenses are typically accessed via project: project.expenses.create(...)
6
+ #
7
+ # == Required attributes for create:
8
+ # date - String, "YYYY-MM-DD" expense date
9
+ # title - String, expense title (e.g., "Hosting XS")
10
+ # quantity - Float/Integer, quantity (e.g., 3)
11
+ # unit - String, unit label (e.g., "months", "pieces", "hours")
12
+ # unit_price - Float, price per unit charged to customer
13
+ # unit_cost - Float, cost per unit (your cost)
14
+ #
15
+ # == Optional attributes:
16
+ # description - String, detailed description
17
+ # billable - Boolean, whether expense is billable (default: true)
18
+ # budget_relevant - Boolean, whether counts toward budget (default: false)
19
+ # service_period_from - String, "YYYY-MM-DD" service period start
20
+ # service_period_to - String, "YYYY-MM-DD" service period end
21
+ # user_id - Integer, responsible user ID (default: current user)
22
+ # custom_properties - Hash, e.g., {"Type": "Infrastructure"}
23
+ # file - Hash, { filename: "receipt.pdf", base64: "..." }
24
+ #
25
+ # == Read-only attributes:
26
+ # id, price, cost, currency, billed, invoice_id, project (Hash),
27
+ # company (Hash), created_at, updated_at
28
+ #
29
+ # == Example:
30
+ # project = moco.projects.find(123)
31
+ # project.expenses.create(
32
+ # date: "2024-01-15",
33
+ # title: "Cloud Hosting",
34
+ # quantity: 1,
35
+ # unit: "month",
36
+ # unit_price: 99.0,
37
+ # unit_cost: 49.0,
38
+ # billable: true
39
+ # )
40
+ #
6
41
  class Expense < BaseEntity
7
42
  # Override entity_path to use the global expenses endpoint
8
43
  # Note: Expenses can also be accessed via projects/{id}/expenses
@@ -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