rodoo 0.1.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 +7 -0
- data/.env.example +2 -0
- data/.rubocop.yml +32 -0
- data/CHANGELOG.md +5 -0
- data/CLAUDE.md +25 -0
- data/LICENSE.txt +21 -0
- data/Odoo_API.md +424 -0
- data/README.md +230 -0
- data/Rakefile +12 -0
- data/lib/rodoo/configuration.rb +42 -0
- data/lib/rodoo/connection.rb +128 -0
- data/lib/rodoo/domain_builder.rb +62 -0
- data/lib/rodoo/errors.rb +31 -0
- data/lib/rodoo/model.rb +276 -0
- data/lib/rodoo/models/accounting_entry.rb +53 -0
- data/lib/rodoo/models/accounting_entry_line.rb +24 -0
- data/lib/rodoo/models/analytic_account.rb +7 -0
- data/lib/rodoo/models/analytic_plan.rb +7 -0
- data/lib/rodoo/models/contact.rb +18 -0
- data/lib/rodoo/models/customer_credit_note.rb +15 -0
- data/lib/rodoo/models/customer_invoice.rb +15 -0
- data/lib/rodoo/models/journal_entry.rb +15 -0
- data/lib/rodoo/models/product.rb +18 -0
- data/lib/rodoo/models/project.rb +18 -0
- data/lib/rodoo/models/provider_credit_note.rb +15 -0
- data/lib/rodoo/models/provider_invoice.rb +15 -0
- data/lib/rodoo/version.rb +5 -0
- data/lib/rodoo.rb +45 -0
- metadata +72 -0
data/lib/rodoo/model.rb
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rodoo
|
|
4
|
+
# Base class for Odoo models. Provides both class-level query methods and instance-level persistence.
|
|
5
|
+
#
|
|
6
|
+
# @example Defining a model
|
|
7
|
+
# class Contact < Rodoo::Model
|
|
8
|
+
# model_name "res.partner"
|
|
9
|
+
# end
|
|
10
|
+
#
|
|
11
|
+
# @example Querying
|
|
12
|
+
# contact = Rodoo::Contact.find(42)
|
|
13
|
+
# contacts = Rodoo::Contact.where([["is_company", "=", true]])
|
|
14
|
+
# all = Rodoo::Contact.all(limit: 10)
|
|
15
|
+
#
|
|
16
|
+
# @example Creating
|
|
17
|
+
# contact = Rodoo::Contact.create(name: "Acme Corp")
|
|
18
|
+
#
|
|
19
|
+
# @example Building and saving
|
|
20
|
+
# contact = Rodoo::Contact.new(name: "Draft")
|
|
21
|
+
# contact.email = "draft@example.com"
|
|
22
|
+
# contact.save
|
|
23
|
+
#
|
|
24
|
+
class Model
|
|
25
|
+
# ============================================
|
|
26
|
+
# Class-level configuration and query methods
|
|
27
|
+
# ============================================
|
|
28
|
+
|
|
29
|
+
# Sets or gets the Odoo model name for this model
|
|
30
|
+
#
|
|
31
|
+
# @param name [String, nil] The Odoo model name (e.g., "res.partner")
|
|
32
|
+
# @return [String] The model name
|
|
33
|
+
def self.model_name(name = nil)
|
|
34
|
+
if name
|
|
35
|
+
@odoo_model_name = name
|
|
36
|
+
else
|
|
37
|
+
@odoo_model_name || (superclass.respond_to?(:model_name) ? superclass.model_name : nil)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Find a single record by ID
|
|
42
|
+
#
|
|
43
|
+
# @param id [Integer] The record ID
|
|
44
|
+
# @return [Model] The found record
|
|
45
|
+
# @raise [Rodoo::NotFoundError] If the record doesn't exist
|
|
46
|
+
#
|
|
47
|
+
# @example
|
|
48
|
+
# contact = Rodoo::Contact.find(42)
|
|
49
|
+
# contact.name # => "Acme Corp"
|
|
50
|
+
#
|
|
51
|
+
def self.find(id)
|
|
52
|
+
result = execute("read", ids: [id])
|
|
53
|
+
raise NotFoundError, "#{model_name} with id=#{id} not found" if result.nil? || result.empty?
|
|
54
|
+
|
|
55
|
+
new(result.first)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Search for records with flexible query syntax
|
|
59
|
+
#
|
|
60
|
+
# @param conditions [Array, Hash, String, nil] Query conditions
|
|
61
|
+
# @param fields [Array<String>, nil] Fields to retrieve
|
|
62
|
+
# @param limit [Integer, nil] Maximum records to return
|
|
63
|
+
# @param offset [Integer, nil] Number of records to skip
|
|
64
|
+
# @return [Array<Model>] Array of matching records
|
|
65
|
+
#
|
|
66
|
+
# @example Keyword arguments (equality)
|
|
67
|
+
# Rodoo::Contact.where(name: "Acme", is_company: true)
|
|
68
|
+
#
|
|
69
|
+
# @example String condition
|
|
70
|
+
# Rodoo::Contact.where("credit_limit > 1000")
|
|
71
|
+
#
|
|
72
|
+
# @example Array of strings
|
|
73
|
+
# Rodoo::Contact.where(["credit_limit > 1000", "active = true"])
|
|
74
|
+
#
|
|
75
|
+
# @example Raw Odoo domain (array of arrays)
|
|
76
|
+
# Rodoo::Contact.where([["is_company", "=", true]], limit: 10)
|
|
77
|
+
#
|
|
78
|
+
def self.where(conditions = nil, fields: nil, limit: nil, offset: nil, **attrs)
|
|
79
|
+
domain = DomainBuilder.build(conditions, attrs)
|
|
80
|
+
|
|
81
|
+
params = { domain: domain }
|
|
82
|
+
params[:fields] = fields if fields
|
|
83
|
+
params[:limit] = limit if limit
|
|
84
|
+
params[:offset] = offset if offset
|
|
85
|
+
|
|
86
|
+
execute("search_read", params).map { |record| new(record) }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Fetch all records (optionally limited)
|
|
90
|
+
#
|
|
91
|
+
# @param fields [Array<String>, nil] Fields to retrieve
|
|
92
|
+
# @param limit [Integer, nil] Maximum records to return
|
|
93
|
+
# @return [Array<Model>] Array of records
|
|
94
|
+
#
|
|
95
|
+
# @example
|
|
96
|
+
# all_contacts = Rodoo::Contact.all(limit: 100)
|
|
97
|
+
#
|
|
98
|
+
def self.all(fields: nil, limit: nil)
|
|
99
|
+
where([], fields: fields, limit: limit)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Find a single record by attribute conditions
|
|
103
|
+
#
|
|
104
|
+
# @param conditions [Array, Hash, String, nil] Query conditions (same as where)
|
|
105
|
+
# @return [Model, nil] The first matching record or nil if not found
|
|
106
|
+
#
|
|
107
|
+
# @example Find by keyword arguments
|
|
108
|
+
# contact = Rodoo::Contact.find_by(email: "john@example.com")
|
|
109
|
+
#
|
|
110
|
+
# @example Find by multiple conditions
|
|
111
|
+
# contact = Rodoo::Contact.find_by(name: "Acme Corp", is_company: true)
|
|
112
|
+
#
|
|
113
|
+
# @example Find by string condition
|
|
114
|
+
# contact = Rodoo::Contact.find_by("credit_limit > 1000")
|
|
115
|
+
#
|
|
116
|
+
# @example Find by raw domain
|
|
117
|
+
# contact = Rodoo::Contact.find_by([["name", "ilike", "%acme%"]])
|
|
118
|
+
#
|
|
119
|
+
def self.find_by(conditions = nil, **attrs)
|
|
120
|
+
where(conditions, limit: 1, **attrs).first
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Find a single record by attribute conditions, raising if not found
|
|
124
|
+
#
|
|
125
|
+
# @param conditions [Array, Hash, String, nil] Query conditions (same as where)
|
|
126
|
+
# @return [Model] The first matching record
|
|
127
|
+
# @raise [Rodoo::NotFoundError] If no matching record is found
|
|
128
|
+
#
|
|
129
|
+
# @example Find by email (raises if not found)
|
|
130
|
+
# contact = Rodoo::Contact.find_by!(email: "john@example.com")
|
|
131
|
+
#
|
|
132
|
+
def self.find_by!(conditions = nil, **attrs)
|
|
133
|
+
record = find_by(conditions, **attrs)
|
|
134
|
+
return record if record
|
|
135
|
+
|
|
136
|
+
raise NotFoundError, "#{model_name} matching #{conditions.inspect} #{attrs.inspect} not found"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Create a new record in Odoo
|
|
140
|
+
#
|
|
141
|
+
# @param attrs [Hash] Attributes for the new record
|
|
142
|
+
# @return [Model] The created record with its ID
|
|
143
|
+
#
|
|
144
|
+
# @example
|
|
145
|
+
# contact = Rodoo::Contact.create(name: "New Contact", email: "new@example.com")
|
|
146
|
+
# contact.id # => 123
|
|
147
|
+
#
|
|
148
|
+
def self.create(attrs)
|
|
149
|
+
ids = execute("create", vals_list: [attrs])
|
|
150
|
+
find(ids.first)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Execute an Odoo method via the JSON-2 API
|
|
154
|
+
#
|
|
155
|
+
# @param method [String] The method to call (e.g., "search_read")
|
|
156
|
+
# @param params [Hash] The method parameters
|
|
157
|
+
# @return [Object] The response data
|
|
158
|
+
def self.execute(method, params = {})
|
|
159
|
+
Rodoo.connection.execute(model_name, method, params)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# ============================================
|
|
163
|
+
# Instance attributes and lifecycle
|
|
164
|
+
# ============================================
|
|
165
|
+
|
|
166
|
+
def initialize(attributes = {})
|
|
167
|
+
@attributes = (attributes || {}).transform_keys(&:to_sym)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def [](key)
|
|
171
|
+
@attributes[key.to_sym]
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def []=(key, value)
|
|
175
|
+
@attributes[key.to_sym] = value
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def to_h
|
|
179
|
+
@attributes.dup
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def persisted?
|
|
183
|
+
!id.nil?
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Save the record to Odoo
|
|
187
|
+
#
|
|
188
|
+
# Creates a new record if unpersisted, updates if persisted.
|
|
189
|
+
#
|
|
190
|
+
# @return [self]
|
|
191
|
+
def save
|
|
192
|
+
if persisted?
|
|
193
|
+
update(to_h.except(:id))
|
|
194
|
+
else
|
|
195
|
+
result = self.class.create(to_h)
|
|
196
|
+
self.id = result.id
|
|
197
|
+
end
|
|
198
|
+
self
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Update specific attributes on a persisted record
|
|
202
|
+
#
|
|
203
|
+
# @param attrs [Hash] Attributes to update
|
|
204
|
+
# @return [self]
|
|
205
|
+
# @raise [Rodoo::Error] If the record hasn't been persisted
|
|
206
|
+
def update(attrs)
|
|
207
|
+
raise Error, "Cannot update a record that hasn't been persisted" unless persisted?
|
|
208
|
+
|
|
209
|
+
normalized = attrs.transform_keys(&:to_sym)
|
|
210
|
+
self.class.execute("write", ids: [id], vals: normalized)
|
|
211
|
+
normalized.each { |key, value| self[key] = value }
|
|
212
|
+
self
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Reload the record from Odoo
|
|
216
|
+
#
|
|
217
|
+
# @return [self]
|
|
218
|
+
# @raise [Rodoo::Error] If the record hasn't been persisted
|
|
219
|
+
def reload
|
|
220
|
+
raise Error, "Cannot reload a record that hasn't been persisted" unless persisted?
|
|
221
|
+
|
|
222
|
+
fresh = self.class.find(id)
|
|
223
|
+
fresh.to_h.each { |key, value| self[key] = value }
|
|
224
|
+
self
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Permanently delete the record from Odoo
|
|
228
|
+
#
|
|
229
|
+
# @return [self] The deleted record (frozen)
|
|
230
|
+
# @raise [Rodoo::Error] If the record hasn't been persisted
|
|
231
|
+
#
|
|
232
|
+
# @example
|
|
233
|
+
# contact = Rodoo::Contact.find(42)
|
|
234
|
+
# contact.destroy
|
|
235
|
+
# contact.destroyed? # => true
|
|
236
|
+
#
|
|
237
|
+
def destroy
|
|
238
|
+
raise Error, "Cannot destroy a record that hasn't been persisted" unless persisted?
|
|
239
|
+
|
|
240
|
+
self.class.execute("unlink", ids: [id])
|
|
241
|
+
@destroyed = true
|
|
242
|
+
freeze
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Check if this record has been destroyed
|
|
246
|
+
#
|
|
247
|
+
# @return [Boolean]
|
|
248
|
+
def destroyed?
|
|
249
|
+
@destroyed == true
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def inspect
|
|
253
|
+
"#<#{self.class.name} id=#{id.inspect} #{inspectable_attributes}>"
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
private
|
|
257
|
+
|
|
258
|
+
def respond_to_missing?(_method_name, _include_private = false)
|
|
259
|
+
true
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def method_missing(method_name, *args, &)
|
|
263
|
+
attr_name = method_name.to_s
|
|
264
|
+
|
|
265
|
+
if attr_name.end_with?("=")
|
|
266
|
+
@attributes[attr_name.delete_suffix("=").to_sym] = args.first
|
|
267
|
+
else
|
|
268
|
+
@attributes[method_name]
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def inspectable_attributes
|
|
273
|
+
to_h.except(:id).map { |k, v| "#{k}=#{v.inspect}" }.join(" ")
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rodoo
|
|
4
|
+
# Base class for Odoo accounting entries (account.move).
|
|
5
|
+
#
|
|
6
|
+
# In Odoo, customer invoices, provider invoices, credit notes, and journal entries
|
|
7
|
+
# are all stored in the same model (account.move) and differentiated by the move_type field.
|
|
8
|
+
#
|
|
9
|
+
# This class provides the base functionality, while subclasses automatically filter
|
|
10
|
+
# and set the appropriate move_type.
|
|
11
|
+
#
|
|
12
|
+
# @example Using a specific subclass
|
|
13
|
+
# invoice = Rodoo::CustomerInvoice.create(partner_id: 42)
|
|
14
|
+
# bills = Rodoo::ProviderInvoice.where([["state", "=", "posted"]])
|
|
15
|
+
#
|
|
16
|
+
# @example Using the base class to query all types
|
|
17
|
+
# all_entries = Rodoo::AccountingEntry.where([["date", ">", "2025-01-01"]])
|
|
18
|
+
#
|
|
19
|
+
class AccountingEntry < Model
|
|
20
|
+
model_name "account.move"
|
|
21
|
+
|
|
22
|
+
# Subclasses override this to specify their move_type
|
|
23
|
+
#
|
|
24
|
+
# @return [String, nil] The move_type value for this class
|
|
25
|
+
def self.default_move_type
|
|
26
|
+
nil
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Search for records, automatically scoped to the move_type
|
|
30
|
+
#
|
|
31
|
+
# @param conditions [Array, Hash, String, nil] Query conditions
|
|
32
|
+
# @param options [Hash] Additional options (fields, limit, offset)
|
|
33
|
+
# @return [Array<AccountingEntry>] Array of matching records
|
|
34
|
+
def self.where(conditions = nil, **options)
|
|
35
|
+
domain = DomainBuilder.build(conditions, options.except(:fields, :limit, :offset))
|
|
36
|
+
domain = [["move_type", "=", default_move_type]] + domain if default_move_type
|
|
37
|
+
super(domain, **options.slice(:fields, :limit, :offset))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Create a new record, automatically setting the move_type
|
|
41
|
+
#
|
|
42
|
+
# @param attrs [Hash] Attributes for the new record
|
|
43
|
+
# @return [AccountingEntry] The created record
|
|
44
|
+
def self.create(attrs)
|
|
45
|
+
scoped_attrs = if default_move_type
|
|
46
|
+
{ move_type: default_move_type }.merge(attrs)
|
|
47
|
+
else
|
|
48
|
+
attrs
|
|
49
|
+
end
|
|
50
|
+
super(scoped_attrs)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rodoo
|
|
4
|
+
# Accounting entry line (account.move.line) - individual debit/credit lines within an accounting entry.
|
|
5
|
+
#
|
|
6
|
+
# Every accounting entry (invoice, bill, journal entry) has at least two lines:
|
|
7
|
+
# one for the debit side and one for the credit side.
|
|
8
|
+
#
|
|
9
|
+
# @example Find lines for a specific entry
|
|
10
|
+
# lines = Rodoo::AccountingEntryLine.where([["move_id", "=", 42]])
|
|
11
|
+
#
|
|
12
|
+
# @example Find all receivable lines
|
|
13
|
+
# receivables = Rodoo::AccountingEntryLine.where([["account_type", "=", "asset_receivable"]])
|
|
14
|
+
#
|
|
15
|
+
# @example Get line details
|
|
16
|
+
# line = Rodoo::AccountingEntryLine.find(123)
|
|
17
|
+
# line.debit # => 1000.0
|
|
18
|
+
# line.credit # => 0.0
|
|
19
|
+
# line.balance # => 1000.0
|
|
20
|
+
#
|
|
21
|
+
class AccountingEntryLine < Model
|
|
22
|
+
model_name "account.move.line"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rodoo
|
|
4
|
+
# Contact model for Odoo's res.partner (contacts/companies)
|
|
5
|
+
#
|
|
6
|
+
# @example Find a contact
|
|
7
|
+
# contact = Rodoo::Contact.find(42)
|
|
8
|
+
#
|
|
9
|
+
# @example Search for companies
|
|
10
|
+
# companies = Rodoo::Contact.where([["is_company", "=", true]])
|
|
11
|
+
#
|
|
12
|
+
# @example Create a contact
|
|
13
|
+
# contact = Rodoo::Contact.create(name: "Acme Corp", is_company: true)
|
|
14
|
+
#
|
|
15
|
+
class Contact < Model
|
|
16
|
+
model_name "res.partner"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rodoo
|
|
4
|
+
# Customer credit note / refund (move_type: out_refund)
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# credit_note = Rodoo::CustomerCreditNote.create(partner_id: 42)
|
|
8
|
+
# credit_notes = Rodoo::CustomerCreditNote.where([["state", "=", "posted"]])
|
|
9
|
+
#
|
|
10
|
+
class CustomerCreditNote < AccountingEntry
|
|
11
|
+
def self.default_move_type
|
|
12
|
+
"out_refund"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rodoo
|
|
4
|
+
# Customer invoice (move_type: out_invoice)
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# invoice = Rodoo::CustomerInvoice.create(partner_id: 42)
|
|
8
|
+
# invoices = Rodoo::CustomerInvoice.where([["state", "=", "posted"]])
|
|
9
|
+
#
|
|
10
|
+
class CustomerInvoice < AccountingEntry
|
|
11
|
+
def self.default_move_type
|
|
12
|
+
"out_invoice"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rodoo
|
|
4
|
+
# Journal entry (move_type: entry)
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# entry = Rodoo::JournalEntry.create(journal_id: 1)
|
|
8
|
+
# entries = Rodoo::JournalEntry.where([["state", "=", "posted"]])
|
|
9
|
+
#
|
|
10
|
+
class JournalEntry < AccountingEntry
|
|
11
|
+
def self.default_move_type
|
|
12
|
+
"entry"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rodoo
|
|
4
|
+
# Product model for Odoo's product.product (product variants)
|
|
5
|
+
#
|
|
6
|
+
# @example Find a product
|
|
7
|
+
# product = Rodoo::Product.find(42)
|
|
8
|
+
#
|
|
9
|
+
# @example Search for active products
|
|
10
|
+
# products = Rodoo::Product.where([["active", "=", true]])
|
|
11
|
+
#
|
|
12
|
+
# @example Create a product
|
|
13
|
+
# product = Rodoo::Product.create(name: "Widget", list_price: 9.99)
|
|
14
|
+
#
|
|
15
|
+
class Product < Model
|
|
16
|
+
model_name "product.product"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rodoo
|
|
4
|
+
# Project model for Odoo's project.project table.
|
|
5
|
+
#
|
|
6
|
+
# @example Find a project by ID
|
|
7
|
+
# project = Rodoo::Project.find(42)
|
|
8
|
+
#
|
|
9
|
+
# @example Search for projects
|
|
10
|
+
# project = Rodoo::Project.where([["is_company", "=", true]])
|
|
11
|
+
#
|
|
12
|
+
# @example Create a project
|
|
13
|
+
# project = Rodoo::Project.create(name: "my_project", account_id: analytic_account_id, allow_billable: true)
|
|
14
|
+
#
|
|
15
|
+
class Project < Model
|
|
16
|
+
model_name "project.project"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rodoo
|
|
4
|
+
# Provider/vendor credit note / refund (move_type: in_refund)
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# refund = Rodoo::ProviderCreditNote.create(partner_id: 42)
|
|
8
|
+
# refunds = Rodoo::ProviderCreditNote.where([["state", "=", "posted"]])
|
|
9
|
+
#
|
|
10
|
+
class ProviderCreditNote < AccountingEntry
|
|
11
|
+
def self.default_move_type
|
|
12
|
+
"in_refund"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rodoo
|
|
4
|
+
# Provider/vendor invoice (move_type: in_invoice)
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# bill = Rodoo::ProviderInvoice.create(partner_id: 42)
|
|
8
|
+
# bills = Rodoo::ProviderInvoice.where([["state", "=", "posted"]])
|
|
9
|
+
#
|
|
10
|
+
class ProviderInvoice < AccountingEntry
|
|
11
|
+
def self.default_move_type
|
|
12
|
+
"in_invoice"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
data/lib/rodoo.rb
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "rodoo/version"
|
|
4
|
+
require_relative "rodoo/configuration"
|
|
5
|
+
require_relative "rodoo/connection"
|
|
6
|
+
require_relative "rodoo/errors"
|
|
7
|
+
require_relative "rodoo/domain_builder"
|
|
8
|
+
require_relative "rodoo/model"
|
|
9
|
+
|
|
10
|
+
module Rodoo
|
|
11
|
+
autoload :AccountingEntry, "rodoo/models/accounting_entry"
|
|
12
|
+
autoload :AccountingEntryLine, "rodoo/models/accounting_entry_line"
|
|
13
|
+
autoload :AnalyticAccount, "rodoo/models/analytic_account"
|
|
14
|
+
autoload :AnalyticPlan, "rodoo/models/analytic_plan"
|
|
15
|
+
autoload :CustomerCreditNote, "rodoo/models/customer_credit_note"
|
|
16
|
+
autoload :CustomerInvoice, "rodoo/models/customer_invoice"
|
|
17
|
+
autoload :JournalEntry, "rodoo/models/journal_entry"
|
|
18
|
+
autoload :Contact, "rodoo/models/contact"
|
|
19
|
+
autoload :Product, "rodoo/models/product"
|
|
20
|
+
autoload :Project, "rodoo/models/project"
|
|
21
|
+
autoload :ProviderCreditNote, "rodoo/models/provider_credit_note"
|
|
22
|
+
autoload :ProviderInvoice, "rodoo/models/provider_invoice"
|
|
23
|
+
|
|
24
|
+
@configuration = nil
|
|
25
|
+
@connection = nil
|
|
26
|
+
|
|
27
|
+
def self.configuration
|
|
28
|
+
@configuration ||= Configuration.new
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.configure
|
|
32
|
+
yield(configuration)
|
|
33
|
+
@connection = nil # Reset connection when configuration changes
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.reset!
|
|
37
|
+
@configuration = Configuration.new
|
|
38
|
+
@connection = nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.connection
|
|
42
|
+
configuration.validate!
|
|
43
|
+
@connection ||= Connection.new(configuration)
|
|
44
|
+
end
|
|
45
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rodoo
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Rodrigo Serrano
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: This gem implements a wrapper to interact with Odoo's API in Ruby. The
|
|
13
|
+
API used is Odoo's 'external JSON-2 API' introduced in Odoo v19.
|
|
14
|
+
email:
|
|
15
|
+
- rodrigo.serrano@dekuple.es
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- ".env.example"
|
|
21
|
+
- ".rubocop.yml"
|
|
22
|
+
- CHANGELOG.md
|
|
23
|
+
- CLAUDE.md
|
|
24
|
+
- LICENSE.txt
|
|
25
|
+
- Odoo_API.md
|
|
26
|
+
- README.md
|
|
27
|
+
- Rakefile
|
|
28
|
+
- lib/rodoo.rb
|
|
29
|
+
- lib/rodoo/configuration.rb
|
|
30
|
+
- lib/rodoo/connection.rb
|
|
31
|
+
- lib/rodoo/domain_builder.rb
|
|
32
|
+
- lib/rodoo/errors.rb
|
|
33
|
+
- lib/rodoo/model.rb
|
|
34
|
+
- lib/rodoo/models/accounting_entry.rb
|
|
35
|
+
- lib/rodoo/models/accounting_entry_line.rb
|
|
36
|
+
- lib/rodoo/models/analytic_account.rb
|
|
37
|
+
- lib/rodoo/models/analytic_plan.rb
|
|
38
|
+
- lib/rodoo/models/contact.rb
|
|
39
|
+
- lib/rodoo/models/customer_credit_note.rb
|
|
40
|
+
- lib/rodoo/models/customer_invoice.rb
|
|
41
|
+
- lib/rodoo/models/journal_entry.rb
|
|
42
|
+
- lib/rodoo/models/product.rb
|
|
43
|
+
- lib/rodoo/models/project.rb
|
|
44
|
+
- lib/rodoo/models/provider_credit_note.rb
|
|
45
|
+
- lib/rodoo/models/provider_invoice.rb
|
|
46
|
+
- lib/rodoo/version.rb
|
|
47
|
+
homepage: https://github.com/dekuple/rodoo
|
|
48
|
+
licenses:
|
|
49
|
+
- MIT
|
|
50
|
+
metadata:
|
|
51
|
+
homepage_uri: https://github.com/dekuple/rodoo
|
|
52
|
+
source_code_uri: https://github.com/dekuple/rodoo
|
|
53
|
+
changelog_uri: https://github.com/dekuple/rodoo/blob/main/CHANGELOG.md
|
|
54
|
+
rubygems_mfa_required: 'true'
|
|
55
|
+
rdoc_options: []
|
|
56
|
+
require_paths:
|
|
57
|
+
- lib
|
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
59
|
+
requirements:
|
|
60
|
+
- - ">="
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: 3.1.0
|
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
68
|
+
requirements: []
|
|
69
|
+
rubygems_version: 3.6.7
|
|
70
|
+
specification_version: 4
|
|
71
|
+
summary: Odoo API wrapper (using the modern JSON-2 API)
|
|
72
|
+
test_files: []
|