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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +36 -1
- data/Gemfile.lock +47 -43
- data/README.md +63 -24
- data/copy_project.rb +337 -0
- data/lib/moco/client.rb +58 -0
- data/lib/moco/connection.rb +45 -22
- data/lib/moco/entities/activity.rb +31 -1
- data/lib/moco/entities/catalog_service.rb +54 -0
- data/lib/moco/entities/comment.rb +61 -0
- data/lib/moco/entities/company.rb +57 -2
- data/lib/moco/entities/contact.rb +56 -0
- data/lib/moco/entities/custom_property.rb +49 -0
- data/lib/moco/entities/deal.rb +38 -2
- data/lib/moco/entities/deal_category.rb +27 -0
- data/lib/moco/entities/employment.rb +55 -0
- data/lib/moco/entities/expense.rb +37 -2
- data/lib/moco/entities/expense_template.rb +39 -0
- data/lib/moco/entities/fixed_cost.rb +30 -0
- data/lib/moco/entities/holiday.rb +33 -2
- data/lib/moco/entities/hourly_rate.rb +33 -0
- data/lib/moco/entities/internal_hourly_rate.rb +32 -0
- data/lib/moco/entities/invoice.rb +81 -1
- data/lib/moco/entities/invoice_bookkeeping_export.rb +33 -0
- data/lib/moco/entities/invoice_payment.rb +51 -0
- data/lib/moco/entities/invoice_reminder.rb +51 -0
- data/lib/moco/entities/offer.rb +122 -0
- data/lib/moco/entities/offer_approval.rb +42 -0
- data/lib/moco/entities/payment_schedule.rb +48 -0
- data/lib/moco/entities/planning_entry.rb +43 -2
- data/lib/moco/entities/presence.rb +34 -2
- data/lib/moco/entities/profile.rb +24 -0
- data/lib/moco/entities/project.rb +76 -2
- data/lib/moco/entities/project_contract.rb +50 -0
- data/lib/moco/entities/project_group.rb +38 -0
- data/lib/moco/entities/purchase.rb +90 -0
- data/lib/moco/entities/purchase_bookkeeping_export.rb +34 -0
- data/lib/moco/entities/purchase_budget.rb +47 -0
- data/lib/moco/entities/purchase_category.rb +38 -0
- data/lib/moco/entities/purchase_draft.rb +25 -0
- data/lib/moco/entities/purchase_payment.rb +51 -0
- data/lib/moco/entities/receipt.rb +55 -0
- data/lib/moco/entities/recurring_expense.rb +55 -0
- data/lib/moco/entities/reports/absences.rb +16 -0
- data/lib/moco/entities/reports/cashflow.rb +16 -0
- data/lib/moco/entities/reports/finance.rb +16 -0
- data/lib/moco/entities/reports/utilization.rb +16 -0
- data/lib/moco/entities/schedule.rb +39 -2
- data/lib/moco/entities/tag.rb +30 -0
- data/lib/moco/entities/tagging.rb +27 -0
- data/lib/moco/entities/task.rb +25 -2
- data/lib/moco/entities/task_template.rb +38 -0
- data/lib/moco/entities/unit.rb +36 -0
- data/lib/moco/entities/user.rb +50 -2
- data/lib/moco/entities/user_role.rb +29 -0
- data/lib/moco/entities/vat_code_purchase.rb +29 -0
- data/lib/moco/entities/vat_code_sale.rb +29 -0
- data/lib/moco/entities/web_hook.rb +32 -2
- data/lib/moco/entities/work_time_adjustment.rb +51 -0
- data/lib/moco/sync.rb +112 -6
- data/lib/moco/version.rb +1 -1
- data/lib/moco-ruby.rb +6 -0
- data/lib/moco.rb +51 -1
- data/moco.gemspec +5 -3
- data/sync_activity.rb +8 -2
- 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
|
|
5
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|