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
data/lib/moco/client.rb
CHANGED
|
@@ -37,6 +37,16 @@ module MOCO
|
|
|
37
37
|
name.to_s == ActiveSupport::Inflector.pluralize(name.to_s)
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
+
# Get the current user's profile (singleton resource)
|
|
41
|
+
def profile
|
|
42
|
+
Profile.new(self, get("profile"))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Reports namespace for read-only report endpoints
|
|
46
|
+
def reports
|
|
47
|
+
@reports ||= ReportsProxy.new(self)
|
|
48
|
+
end
|
|
49
|
+
|
|
40
50
|
# Delegate HTTP methods to connection
|
|
41
51
|
%i[get post put patch delete].each do |method|
|
|
42
52
|
define_method(method) do |path, params = {}|
|
|
@@ -44,4 +54,52 @@ module MOCO
|
|
|
44
54
|
end
|
|
45
55
|
end
|
|
46
56
|
end
|
|
57
|
+
|
|
58
|
+
# Proxy for accessing report endpoints
|
|
59
|
+
class ReportsProxy
|
|
60
|
+
def initialize(client)
|
|
61
|
+
@client = client
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Get absences report
|
|
65
|
+
# @param year [Integer] optional year filter
|
|
66
|
+
# @param active [Boolean] optional active status filter
|
|
67
|
+
def absences(year: nil, active: nil)
|
|
68
|
+
params = {}
|
|
69
|
+
params[:year] = year if year
|
|
70
|
+
params[:active] = active unless active.nil?
|
|
71
|
+
@client.get("report/absences", params)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Get cashflow report
|
|
75
|
+
# @param from [String] start date (YYYY-MM-DD)
|
|
76
|
+
# @param to [String] end date (YYYY-MM-DD)
|
|
77
|
+
# @param term [String] optional search term
|
|
78
|
+
def cashflow(from: nil, to: nil, term: nil)
|
|
79
|
+
params = {}
|
|
80
|
+
params[:from] = from if from
|
|
81
|
+
params[:to] = to if to
|
|
82
|
+
params[:term] = term if term
|
|
83
|
+
@client.get("report/cashflow", params)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Get finance report
|
|
87
|
+
# @param from [String] start date (YYYY-MM-DD)
|
|
88
|
+
# @param to [String] end date (YYYY-MM-DD)
|
|
89
|
+
# @param term [String] optional search term
|
|
90
|
+
def finance(from: nil, to: nil, term: nil)
|
|
91
|
+
params = {}
|
|
92
|
+
params[:from] = from if from
|
|
93
|
+
params[:to] = to if to
|
|
94
|
+
params[:term] = term if term
|
|
95
|
+
@client.get("report/finance", params)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Get utilization report
|
|
99
|
+
# @param from [String] start date (YYYY-MM-DD) - required
|
|
100
|
+
# @param to [String] end date (YYYY-MM-DD) - required
|
|
101
|
+
def utilization(from:, to:)
|
|
102
|
+
@client.get("report/utilization", { from:, to: })
|
|
103
|
+
end
|
|
104
|
+
end
|
|
47
105
|
end
|
data/lib/moco/connection.rb
CHANGED
|
@@ -22,34 +22,57 @@ module MOCO
|
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
# Maximum retries for rate-limited requests
|
|
26
|
+
MAX_RETRIES = 3
|
|
27
|
+
# Base delay between retries (seconds)
|
|
28
|
+
RETRY_DELAY = 1.0
|
|
29
|
+
|
|
25
30
|
# Define methods for HTTP verbs (get, post, put, patch, delete)
|
|
26
31
|
# These methods send the request and return the raw parsed JSON response body.
|
|
27
32
|
%w[get post put patch delete].each do |http_method|
|
|
28
33
|
define_method(http_method) do |path, params = {}|
|
|
29
|
-
|
|
30
|
-
if @debug
|
|
31
|
-
full_url = @conn.build_url(path, params).to_s
|
|
32
|
-
warn "[DEBUG] Fetching URL: #{http_method.upcase} #{full_url}"
|
|
33
|
-
end
|
|
34
|
-
response = @conn.send(http_method, path, params)
|
|
34
|
+
retries = 0
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
36
|
+
loop do
|
|
37
|
+
begin
|
|
38
|
+
# Log request if debug is enabled
|
|
39
|
+
if @debug
|
|
40
|
+
if %w[post put patch].include?(http_method)
|
|
41
|
+
# For body methods, show the JSON that will be sent
|
|
42
|
+
warn "[DEBUG] #{http_method.upcase} #{@conn.url_prefix}/#{path}"
|
|
43
|
+
warn "[DEBUG] Body: #{params.to_json}" unless params.empty?
|
|
44
|
+
else
|
|
45
|
+
# For query methods, show the full URL with params
|
|
46
|
+
full_url = @conn.build_url(path, params).to_s
|
|
47
|
+
warn "[DEBUG] #{http_method.upcase} #{full_url}"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
response = @conn.send(http_method, path, params)
|
|
46
52
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
# Handle rate limiting with automatic retry
|
|
54
|
+
if response.status == 429 && retries < MAX_RETRIES
|
|
55
|
+
retries += 1
|
|
56
|
+
# Get Retry-After header or use exponential backoff
|
|
57
|
+
retry_after = response.headers["Retry-After"]&.to_f || (RETRY_DELAY * (2**retries))
|
|
58
|
+
warn "[RATE LIMITED] Waiting #{retry_after}s before retry #{retries}/#{MAX_RETRIES}..." if @debug
|
|
59
|
+
sleep(retry_after)
|
|
60
|
+
next
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Raise an error for non-successful responses
|
|
64
|
+
unless response.success?
|
|
65
|
+
# Attempt to parse error details from the body, otherwise use status/reason
|
|
66
|
+
error_details = response.body.is_a?(Hash) ? response.body["message"] : response.body
|
|
67
|
+
raise "MOCO API Error: #{response.status} #{response.reason_phrase}. Details: #{error_details}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
return response.body
|
|
71
|
+
rescue Faraday::Error => e
|
|
72
|
+
# Wrap Faraday errors
|
|
73
|
+
raise "Faraday Connection Error: #{e.message}"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
53
76
|
end
|
|
54
77
|
end
|
|
55
78
|
|
|
@@ -2,7 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
module MOCO
|
|
4
4
|
# Represents a MOCO activity (time entry)
|
|
5
|
-
#
|
|
5
|
+
#
|
|
6
|
+
# == Required attributes for create:
|
|
7
|
+
# date - String, "YYYY-MM-DD" format (e.g., "2024-01-15")
|
|
8
|
+
# project_id - Integer, ID of the project
|
|
9
|
+
# task_id - Integer, ID of the task within the project
|
|
10
|
+
#
|
|
11
|
+
# == Optional attributes:
|
|
12
|
+
# seconds - Integer, duration in seconds (3600 = 1 hour)
|
|
13
|
+
# hours - Float, duration in hours (alternative to seconds)
|
|
14
|
+
# description - String, description of the work done
|
|
15
|
+
# billable - Boolean, whether the activity is billable (default: true or project setting)
|
|
16
|
+
# tag - String, any tag (e.g., "RMT-123")
|
|
17
|
+
# remote_service - String, external service name. Allowed: "trello", "jira", "asana",
|
|
18
|
+
# "basecamp", "wunderlist", "basecamp2", "basecamp3", "toggl", "mite",
|
|
19
|
+
# "github", "youtrack"
|
|
20
|
+
# remote_id - String, ID in the external service (e.g., "PRJ-2342")
|
|
21
|
+
# remote_url - String, URL to the external ticket/issue
|
|
22
|
+
#
|
|
23
|
+
# == Read-only attributes (returned by API):
|
|
24
|
+
# id, billed, invoice_id, project (Hash), task (Hash), customer (Hash),
|
|
25
|
+
# user (Hash), hourly_rate, timer_started_at, created_at, updated_at
|
|
26
|
+
#
|
|
27
|
+
# == Example:
|
|
28
|
+
# moco.activities.create(
|
|
29
|
+
# date: "2024-01-15",
|
|
30
|
+
# project_id: 123456,
|
|
31
|
+
# task_id: 234567,
|
|
32
|
+
# seconds: 3600,
|
|
33
|
+
# description: "Implemented feature X"
|
|
34
|
+
# )
|
|
35
|
+
#
|
|
6
36
|
class Activity < BaseEntity
|
|
7
37
|
# Instance methods for activity-specific operations
|
|
8
38
|
def start_timer
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
# Represents a MOCO catalog service (Leistungskatalog)
|
|
5
|
+
# Pre-defined service templates for offers/invoices
|
|
6
|
+
#
|
|
7
|
+
# == Required attributes for create:
|
|
8
|
+
# title - String, catalog entry name
|
|
9
|
+
#
|
|
10
|
+
# == Optional attributes:
|
|
11
|
+
# items - Array of item hashes, service line items
|
|
12
|
+
#
|
|
13
|
+
# == Item types:
|
|
14
|
+
# { type: "title", title: "Section" }
|
|
15
|
+
# { type: "description", description: "Details..." }
|
|
16
|
+
# { type: "item", title: "Service", quantity: 10, unit: "h", unit_price: 150.0, net_total: 1500.0 }
|
|
17
|
+
# { type: "item", title: "Fixed Fee", net_total: 500.0 } # lump sum (quantity=0)
|
|
18
|
+
# { type: "subtotal", part: true } # subtotal for section
|
|
19
|
+
# { type: "separator" }
|
|
20
|
+
# { type: "page-break" }
|
|
21
|
+
#
|
|
22
|
+
# == Item attributes:
|
|
23
|
+
# title - String, item title
|
|
24
|
+
# description - String, item description
|
|
25
|
+
# quantity - Float, number of units (0 for lump sum)
|
|
26
|
+
# unit - String, unit type (e.g., "h", "pieces")
|
|
27
|
+
# unit_price - Float, price per unit
|
|
28
|
+
# net_total - Float, total price for this item
|
|
29
|
+
# unit_cost - Float, internal cost per unit
|
|
30
|
+
# optional - Boolean, mark as optional
|
|
31
|
+
# additional - Boolean, mark as additional service
|
|
32
|
+
#
|
|
33
|
+
# == Read-only attributes:
|
|
34
|
+
# id, items (Array), created_at, updated_at
|
|
35
|
+
#
|
|
36
|
+
# == Example:
|
|
37
|
+
# moco.catalog_services.create(
|
|
38
|
+
# title: "Web Development Package",
|
|
39
|
+
# items: [
|
|
40
|
+
# { type: "item", title: "Setup", net_total: 1200.0 },
|
|
41
|
+
# { type: "item", title: "Development", quantity: 40, unit: "h", unit_price: 150.0, net_total: 6000.0 }
|
|
42
|
+
# ]
|
|
43
|
+
# )
|
|
44
|
+
#
|
|
45
|
+
class CatalogService < BaseEntity
|
|
46
|
+
def self.entity_path
|
|
47
|
+
"account/catalog_services"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def to_s
|
|
51
|
+
name.to_s
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MOCO
|
|
4
|
+
# Represents a MOCO comment/note (Notizen)
|
|
5
|
+
# Comments can be attached to various entities
|
|
6
|
+
#
|
|
7
|
+
# == Required attributes for create:
|
|
8
|
+
# commentable_id - Integer, ID of the entity to attach comment to
|
|
9
|
+
# commentable_type - String, entity type:
|
|
10
|
+
# "Project", "Contact", "Company", "Deal", "User", "Unit",
|
|
11
|
+
# "Invoice", "Offer", "Expense", "Receipt", "Purchase",
|
|
12
|
+
# "DeliveryNote", "OfferConfirmation", "InvoiceReminder",
|
|
13
|
+
# "InvoiceDeletion", "InvoiceBookkeepingExport",
|
|
14
|
+
# "RecurringExpense", "ReceiptRefundRequest",
|
|
15
|
+
# "PurchaseBookkeepingExport", "PurchaseDraft"
|
|
16
|
+
# text - String, comment text (plain text or HTML)
|
|
17
|
+
#
|
|
18
|
+
# == Optional attributes:
|
|
19
|
+
# attachment_filename - String, filename for attachment
|
|
20
|
+
# attachment_content - String, base64-encoded file content
|
|
21
|
+
# created_at - String, timestamp for data migration
|
|
22
|
+
#
|
|
23
|
+
# == Read-only attributes:
|
|
24
|
+
# id, manual, user (Hash - creator), created_at, updated_at
|
|
25
|
+
#
|
|
26
|
+
# == Allowed HTML tags in text:
|
|
27
|
+
# div, strong, em, u, pre, ul, ol, li, br
|
|
28
|
+
#
|
|
29
|
+
# == Example:
|
|
30
|
+
# # Add comment to a project
|
|
31
|
+
# moco.comments.create(
|
|
32
|
+
# commentable_id: 123,
|
|
33
|
+
# commentable_type: "Project",
|
|
34
|
+
# text: "<div>Project kickoff on <strong>Jan 15</strong></div>"
|
|
35
|
+
# )
|
|
36
|
+
#
|
|
37
|
+
# == Filtering:
|
|
38
|
+
# moco.comments.where(commentable_type: "Project", commentable_id: 123)
|
|
39
|
+
# moco.comments.where(user_id: 456)
|
|
40
|
+
# moco.comments.where(manual: true) # user-created only
|
|
41
|
+
#
|
|
42
|
+
class Comment < BaseEntity
|
|
43
|
+
# Bulk create comments
|
|
44
|
+
# @param client [MOCO::Client] the client instance
|
|
45
|
+
# @param comments [Array<Hash>] array of comment attributes
|
|
46
|
+
# @return [Array<Comment>] created comments
|
|
47
|
+
def self.bulk_create(client, comments)
|
|
48
|
+
response = client.post("comments/bulk", { bulk: comments })
|
|
49
|
+
response.map { |data| new(client, data) }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Associations
|
|
53
|
+
def user
|
|
54
|
+
association(:user)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def to_s
|
|
58
|
+
text.to_s.truncate(50)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -1,8 +1,63 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module MOCO
|
|
4
|
-
# Represents a MOCO company (customer)
|
|
5
|
-
#
|
|
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
|
data/lib/moco/entities/deal.rb
CHANGED
|
@@ -1,8 +1,44 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module MOCO
|
|
4
|
-
# Represents a MOCO deal
|
|
5
|
-
#
|
|
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
|
-
#
|
|
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
|