moco-ruby 1.1.0 → 1.3.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/.rubocop.yml +1 -0
- data/CHANGELOG.md +56 -1
- data/Gemfile.lock +45 -40
- data/README.md +98 -25
- data/lib/moco/client.rb +65 -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 +70 -1
- data/lib/moco/entities/invoice_attachment.rb +34 -0
- 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/letter_paper.rb +23 -0
- data/lib/moco/entities/offer.rb +111 -0
- data/lib/moco/entities/offer_approval.rb +42 -0
- data/lib/moco/entities/offer_attachment.rb +34 -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 +85 -10
- 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/session.rb +58 -0
- 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/entities.rb +5 -5
- data/lib/moco/sync.rb +7 -7
- data/lib/moco/version.rb +1 -1
- data/lib/moco.rb +55 -1
- data/moco.gemspec +38 -0
- data/sync_activity.rb +1 -1
- metadata +51 -8
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
# Represents an attachment on a MOCO invoice
|
|
5
|
+
#
|
|
6
|
+
# == Required attributes for create:
|
|
7
|
+
# attachment - Hash with the following keys:
|
|
8
|
+
# filename - String, file name including extension (e.g., "appendix.pdf")
|
|
9
|
+
# base64 - String, base64-encoded file content
|
|
10
|
+
#
|
|
11
|
+
# == Read-only attributes:
|
|
12
|
+
# id, title, created_at, updated_at
|
|
13
|
+
#
|
|
14
|
+
# == Usage:
|
|
15
|
+
# invoice = moco.invoices.find(123)
|
|
16
|
+
# invoice.attachments.all
|
|
17
|
+
# invoice.attachments.create(
|
|
18
|
+
# attachment: {
|
|
19
|
+
# filename: "appendix.pdf",
|
|
20
|
+
# base64: Base64.strict_encode64(File.read("appendix.pdf"))
|
|
21
|
+
# }
|
|
22
|
+
# )
|
|
23
|
+
# invoice.attachments.find(42).destroy
|
|
24
|
+
#
|
|
25
|
+
# == Note:
|
|
26
|
+
# The API only supports list (GET), create (POST), and delete (DELETE).
|
|
27
|
+
# Update is not available - delete and re-upload to replace.
|
|
28
|
+
#
|
|
29
|
+
class InvoiceAttachment < BaseEntity
|
|
30
|
+
def to_s
|
|
31
|
+
title.to_s
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -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,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
# Represents a MOCO letter paper (letterhead template used on invoices/offers PDFs)
|
|
5
|
+
# Read-only listing of letterheads configured in the MOCO account.
|
|
6
|
+
#
|
|
7
|
+
# == Read-only attributes:
|
|
8
|
+
# id, name, active, template, file, created_at, updated_at
|
|
9
|
+
#
|
|
10
|
+
# == Usage:
|
|
11
|
+
# moco.letter_papers.all
|
|
12
|
+
#
|
|
13
|
+
# == Note:
|
|
14
|
+
# The API only exposes a list endpoint (GET /letter_papers).
|
|
15
|
+
# Use a letter paper's `id` as `letter_paper_id` when fetching
|
|
16
|
+
# invoice/offer PDFs (e.g. GET /invoices/{id}.pdf?letter_paper_id=...).
|
|
17
|
+
#
|
|
18
|
+
class LetterPaper < BaseEntity
|
|
19
|
+
def to_s
|
|
20
|
+
name.to_s
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
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
|
+
# Fetches attachments for this offer as a NestedCollectionProxy.
|
|
89
|
+
# Supports .all, .find(id), .create(attachment: { filename:, base64: }), and .destroy.
|
|
90
|
+
def attachments
|
|
91
|
+
MOCO::NestedCollectionProxy.new(client, self, :attachments, "OfferAttachment")
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Associations
|
|
95
|
+
def company
|
|
96
|
+
association(:customer, "Company")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def project
|
|
100
|
+
association(:project)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def deal
|
|
104
|
+
association(:deal)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def to_s
|
|
108
|
+
"#{identifier} - #{title}"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
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,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
# Represents an attachment on a MOCO offer
|
|
5
|
+
#
|
|
6
|
+
# == Required attributes for create:
|
|
7
|
+
# attachment - Hash with the following keys:
|
|
8
|
+
# filename - String, file name including extension (e.g., "appendix.pdf")
|
|
9
|
+
# base64 - String, base64-encoded file content
|
|
10
|
+
#
|
|
11
|
+
# == Read-only attributes:
|
|
12
|
+
# id, title, created_at, updated_at
|
|
13
|
+
#
|
|
14
|
+
# == Usage:
|
|
15
|
+
# offer = moco.offers.find(123)
|
|
16
|
+
# offer.attachments.all
|
|
17
|
+
# offer.attachments.create(
|
|
18
|
+
# attachment: {
|
|
19
|
+
# filename: "appendix.pdf",
|
|
20
|
+
# base64: Base64.strict_encode64(File.read("appendix.pdf"))
|
|
21
|
+
# }
|
|
22
|
+
# )
|
|
23
|
+
# offer.attachments.find(42).destroy
|
|
24
|
+
#
|
|
25
|
+
# == Note:
|
|
26
|
+
# The API only supports list (GET), create (POST), and delete (DELETE).
|
|
27
|
+
# Update is not available - delete and re-upload to replace.
|
|
28
|
+
#
|
|
29
|
+
class OfferAttachment < BaseEntity
|
|
30
|
+
def to_s
|
|
31
|
+
title.to_s
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
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
|
|
@@ -1,8 +1,40 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module MOCO
|
|
4
|
-
# Represents a MOCO presence entry
|
|
5
|
-
#
|
|
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
|
|
@@ -30,19 +82,42 @@ module MOCO
|
|
|
30
82
|
MOCO::NestedCollectionProxy.new(client, self, :expenses, "Expense")
|
|
31
83
|
end
|
|
32
84
|
|
|
33
|
-
# Fetches tasks associated with this project.
|
|
85
|
+
# Fetches tasks associated with this project via the API.
|
|
86
|
+
# Returns a NestedCollectionProxy for lazy loading and CRUD operations.
|
|
34
87
|
def tasks
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
88
|
+
MOCO::NestedCollectionProxy.new(client, self, :tasks, "Task")
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Returns embedded tasks from the projects/assigned response, or nil.
|
|
92
|
+
# These have fewer fields than the full API response but avoid an
|
|
93
|
+
# extra API call, useful for limited-permission accounts.
|
|
94
|
+
def embedded_tasks
|
|
95
|
+
embedded = attributes[:tasks]
|
|
96
|
+
if embedded.is_a?(Array) && embedded.all? { |t| t.is_a?(MOCO::Task) }
|
|
97
|
+
embedded
|
|
98
|
+
else
|
|
99
|
+
[]
|
|
40
100
|
end
|
|
101
|
+
end
|
|
41
102
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
103
|
+
# Fetches contracts associated with this project.
|
|
104
|
+
def contracts
|
|
105
|
+
MOCO::NestedCollectionProxy.new(client, self, :contracts, "ProjectContract")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Fetches payment schedules associated with this project.
|
|
109
|
+
def payment_schedules
|
|
110
|
+
MOCO::NestedCollectionProxy.new(client, self, :payment_schedules, "PaymentSchedule")
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Fetches recurring expenses associated with this project.
|
|
114
|
+
def recurring_expenses
|
|
115
|
+
MOCO::NestedCollectionProxy.new(client, self, :recurring_expenses, "RecurringExpense")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Get the project group
|
|
119
|
+
def project_group
|
|
120
|
+
association(:project_group)
|
|
46
121
|
end
|
|
47
122
|
|
|
48
123
|
def to_s
|