rodoo 0.1.0 → 0.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/.rubocop.yml +4 -0
- data/CHANGELOG.md +5 -0
- data/README.md +68 -0
- data/lib/rodoo/models/accounting_entry.rb +110 -0
- data/lib/rodoo/models/attachment.rb +117 -0
- data/lib/rodoo/models/tax.rb +7 -0
- data/lib/rodoo/version.rb +1 -1
- data/lib/rodoo.rb +2 -0
- metadata +18 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 87923ea6d949a5082fdf435534be6e9785c655b2bc37533784c8389c3288661b
|
|
4
|
+
data.tar.gz: 33009601846b3655494da2ee9f03c1e1ec87896d964094b03491f9ce692e07d8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 50bf69658ddddcf3f3b8a82f1998ebeda996ec656cfb5647c6a6c9d9e1ef2ba60349b585e861ceb44b4af69ab8b7c01c6050b5f47e58f796c89ffe5ea2074e1a
|
|
7
|
+
data.tar.gz: 7c0e9f254bc0a0c71474e29b8568fef331f78855cfac1a1956400dfc200914a5dcced7b1f440b167abb73cf646244628238c8a260ed6cf61183b5a6400751dd0
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
|
@@ -149,6 +149,73 @@ contact.destroy
|
|
|
149
149
|
contact.destroyed? # => true
|
|
150
150
|
```
|
|
151
151
|
|
|
152
|
+
### Attachments
|
|
153
|
+
|
|
154
|
+
Attach PDF files to accounting entries (invoices, credit notes, journal entries):
|
|
155
|
+
|
|
156
|
+
```ruby
|
|
157
|
+
invoice = Rodoo::ProviderInvoice.find(42)
|
|
158
|
+
|
|
159
|
+
# Attach from file path (sets as main attachment by default)
|
|
160
|
+
invoice.attach_pdf("/path/to/invoice.pdf")
|
|
161
|
+
|
|
162
|
+
# Attach with custom filename
|
|
163
|
+
invoice.attach_pdf("/path/to/document.pdf", filename: "vendor_invoice.pdf")
|
|
164
|
+
|
|
165
|
+
# Attach without setting as main attachment
|
|
166
|
+
invoice.attach_pdf("/path/to/supporting.pdf", set_as_main: false)
|
|
167
|
+
|
|
168
|
+
# Attach from base64 data
|
|
169
|
+
invoice.attach_pdf_from_base64(base64_content, filename: "invoice.pdf")
|
|
170
|
+
|
|
171
|
+
# List attachments
|
|
172
|
+
invoice.attachments
|
|
173
|
+
invoice.attachments(mimetype: "application/pdf")
|
|
174
|
+
|
|
175
|
+
# Get the main attachment
|
|
176
|
+
invoice.main_attachment
|
|
177
|
+
|
|
178
|
+
# Set a different attachment as main
|
|
179
|
+
invoice.set_main_attachment(attachment_id)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
#### With Rails ActiveStorage
|
|
183
|
+
|
|
184
|
+
When using ActiveStorage in a Rails application:
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
# Rails model with ActiveStorage attachment
|
|
188
|
+
class Invoice < ApplicationRecord
|
|
189
|
+
has_one_attached :pdf_document
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Sync to Odoo
|
|
193
|
+
rails_invoice = Invoice.find(123)
|
|
194
|
+
odoo_invoice = Rodoo::ProviderInvoice.find(42)
|
|
195
|
+
|
|
196
|
+
rails_invoice.pdf_document.open do |tempfile|
|
|
197
|
+
odoo_invoice.attach_pdf(tempfile, filename: rails_invoice.pdf_document.filename.to_s)
|
|
198
|
+
end
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### Direct Attachment model usage
|
|
202
|
+
|
|
203
|
+
```ruby
|
|
204
|
+
# Create attachment for any record
|
|
205
|
+
Rodoo::Attachment.create_for(
|
|
206
|
+
record, "/path/to/file.pdf", filename: "doc.pdf", mimetype: "application/pdf"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Create from base64
|
|
210
|
+
Rodoo::Attachment.create_from_base64(
|
|
211
|
+
record, base64_data, filename: "doc.pdf", mimetype: "application/pdf"
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# Find attachments for a record
|
|
215
|
+
Rodoo::Attachment.for_record(record)
|
|
216
|
+
Rodoo::Attachment.for_record(record, mimetype: "application/pdf")
|
|
217
|
+
```
|
|
218
|
+
|
|
152
219
|
### Available models
|
|
153
220
|
|
|
154
221
|
Rodoo includes pre-built models for common Odoo objects:
|
|
@@ -159,6 +226,7 @@ Rodoo includes pre-built models for common Odoo objects:
|
|
|
159
226
|
| `Rodoo::Project` | `project.project` |
|
|
160
227
|
| `Rodoo::AnalyticAccount` | `account.analytic.account` |
|
|
161
228
|
| `Rodoo::AnalyticPlan` | `account.analytic.plan` |
|
|
229
|
+
| `Rodoo::Attachment` | `ir.attachment` |
|
|
162
230
|
| `Rodoo::AccountingEntry` | `account.move` (all types) |
|
|
163
231
|
| `Rodoo::CustomerInvoice` | `account.move` (move_type: out_invoice) |
|
|
164
232
|
| `Rodoo::ProviderInvoice` | `account.move` (move_type: in_invoice) |
|
|
@@ -16,9 +16,16 @@ module Rodoo
|
|
|
16
16
|
# @example Using the base class to query all types
|
|
17
17
|
# all_entries = Rodoo::AccountingEntry.where([["date", ">", "2025-01-01"]])
|
|
18
18
|
#
|
|
19
|
+
# @example Attaching a PDF
|
|
20
|
+
# invoice = Rodoo::ProviderInvoice.find(42)
|
|
21
|
+
# invoice.attach_pdf("/path/to/invoice.pdf")
|
|
22
|
+
#
|
|
19
23
|
class AccountingEntry < Model
|
|
20
24
|
model_name "account.move"
|
|
21
25
|
|
|
26
|
+
PDF_MIMETYPE = "application/pdf"
|
|
27
|
+
private_constant :PDF_MIMETYPE
|
|
28
|
+
|
|
22
29
|
# Subclasses override this to specify their move_type
|
|
23
30
|
#
|
|
24
31
|
# @return [String, nil] The move_type value for this class
|
|
@@ -49,5 +56,108 @@ module Rodoo
|
|
|
49
56
|
end
|
|
50
57
|
super(scoped_attrs)
|
|
51
58
|
end
|
|
59
|
+
|
|
60
|
+
# Attach a PDF file to this record
|
|
61
|
+
#
|
|
62
|
+
# @param file_path_or_io [String, IO, #read] File path or IO-like object
|
|
63
|
+
# @param filename [String, nil] The filename (derived from path if not provided)
|
|
64
|
+
# @param set_as_main [Boolean] Whether to set this as the main attachment (default: true)
|
|
65
|
+
# @return [Attachment] The created attachment
|
|
66
|
+
#
|
|
67
|
+
# @example Attach from file path (sets as main by default)
|
|
68
|
+
# invoice.attach_pdf("/path/to/invoice.pdf")
|
|
69
|
+
#
|
|
70
|
+
# @example Attach without setting as main
|
|
71
|
+
# invoice.attach_pdf("/path/to/supporting.pdf", set_as_main: false)
|
|
72
|
+
#
|
|
73
|
+
def attach_pdf(file_path_or_io, filename: nil, set_as_main: true)
|
|
74
|
+
resolved_filename = filename || derive_filename(file_path_or_io)
|
|
75
|
+
attachment = Attachment.create_for(
|
|
76
|
+
self, file_path_or_io, filename: resolved_filename, mimetype: PDF_MIMETYPE
|
|
77
|
+
)
|
|
78
|
+
set_main_attachment(attachment) if set_as_main
|
|
79
|
+
attachment
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Attach a PDF from base64-encoded data
|
|
83
|
+
#
|
|
84
|
+
# @param base64_data [String] The base64-encoded PDF data
|
|
85
|
+
# @param filename [String] The filename for the attachment
|
|
86
|
+
# @param set_as_main [Boolean] Whether to set this as the main attachment (default: true)
|
|
87
|
+
# @return [Attachment] The created attachment
|
|
88
|
+
#
|
|
89
|
+
# @example
|
|
90
|
+
# invoice.attach_pdf_from_base64(base64_content, filename: "invoice.pdf")
|
|
91
|
+
#
|
|
92
|
+
def attach_pdf_from_base64(base64_data, filename:, set_as_main: true)
|
|
93
|
+
attachment = Attachment.create_from_base64(
|
|
94
|
+
self, base64_data, filename: filename, mimetype: PDF_MIMETYPE
|
|
95
|
+
)
|
|
96
|
+
set_main_attachment(attachment) if set_as_main
|
|
97
|
+
attachment
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Set the main attachment for this record (visible in Odoo's side panel)
|
|
101
|
+
#
|
|
102
|
+
# @param attachment_or_id [Attachment, Integer] The attachment or its ID
|
|
103
|
+
# @return [self]
|
|
104
|
+
#
|
|
105
|
+
# @example With an Attachment object
|
|
106
|
+
# invoice.set_main_attachment(attachment)
|
|
107
|
+
#
|
|
108
|
+
# @example With an attachment ID
|
|
109
|
+
# invoice.set_main_attachment(123)
|
|
110
|
+
#
|
|
111
|
+
# rubocop:disable Naming/AccessorMethodName
|
|
112
|
+
def set_main_attachment(attachment_or_id)
|
|
113
|
+
attachment_id = attachment_or_id.is_a?(Attachment) ? attachment_or_id.id : attachment_or_id
|
|
114
|
+
update(message_main_attachment_id: attachment_id)
|
|
115
|
+
end
|
|
116
|
+
# rubocop:enable Naming/AccessorMethodName
|
|
117
|
+
|
|
118
|
+
# List attachments for this record
|
|
119
|
+
#
|
|
120
|
+
# @param mimetype [String, nil] Optional MIME type filter
|
|
121
|
+
# @return [Array<Attachment>] Array of attachments
|
|
122
|
+
#
|
|
123
|
+
# @example Get all attachments
|
|
124
|
+
# invoice.attachments
|
|
125
|
+
#
|
|
126
|
+
# @example Get only PDF attachments
|
|
127
|
+
# invoice.attachments(mimetype: "application/pdf")
|
|
128
|
+
#
|
|
129
|
+
def attachments(mimetype: nil)
|
|
130
|
+
Attachment.for_record(self, mimetype: mimetype)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Get the main attachment for this record
|
|
134
|
+
#
|
|
135
|
+
# @return [Attachment, nil] The main attachment or nil if not set
|
|
136
|
+
#
|
|
137
|
+
# @example
|
|
138
|
+
# main = invoice.main_attachment
|
|
139
|
+
#
|
|
140
|
+
def main_attachment
|
|
141
|
+
main_id = self[:message_main_attachment_id]
|
|
142
|
+
return nil unless main_id
|
|
143
|
+
|
|
144
|
+
# Odoo returns [id, name] for many2one fields
|
|
145
|
+
attachment_id = main_id.is_a?(Array) ? main_id.first : main_id
|
|
146
|
+
return nil unless attachment_id
|
|
147
|
+
|
|
148
|
+
Attachment.find(attachment_id)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
private
|
|
152
|
+
|
|
153
|
+
def derive_filename(file_path_or_io)
|
|
154
|
+
if file_path_or_io.respond_to?(:path)
|
|
155
|
+
File.basename(file_path_or_io.path)
|
|
156
|
+
elsif file_path_or_io.is_a?(String)
|
|
157
|
+
File.basename(file_path_or_io)
|
|
158
|
+
else
|
|
159
|
+
"attachment.pdf"
|
|
160
|
+
end
|
|
161
|
+
end
|
|
52
162
|
end
|
|
53
163
|
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "base64"
|
|
4
|
+
|
|
5
|
+
module Rodoo
|
|
6
|
+
# Wrapper for Odoo's ir.attachment model.
|
|
7
|
+
#
|
|
8
|
+
# Provides methods for creating and querying file attachments linked to Odoo records.
|
|
9
|
+
#
|
|
10
|
+
# @example Create an attachment from a file path
|
|
11
|
+
# invoice = Rodoo::ProviderInvoice.find(42)
|
|
12
|
+
# Rodoo::Attachment.create_for(
|
|
13
|
+
# invoice, "/path/to/file.pdf", filename: "invoice.pdf", mimetype: "application/pdf"
|
|
14
|
+
# )
|
|
15
|
+
#
|
|
16
|
+
# @example Create an attachment from base64 data
|
|
17
|
+
# Rodoo::Attachment.create_from_base64(
|
|
18
|
+
# invoice, base64_data, filename: "doc.pdf", mimetype: "application/pdf"
|
|
19
|
+
# )
|
|
20
|
+
#
|
|
21
|
+
# @example List attachments for a record
|
|
22
|
+
# Rodoo::Attachment.for_record(invoice)
|
|
23
|
+
# Rodoo::Attachment.for_record(invoice, mimetype: "application/pdf")
|
|
24
|
+
#
|
|
25
|
+
class Attachment < Model
|
|
26
|
+
model_name "ir.attachment"
|
|
27
|
+
|
|
28
|
+
# Create an attachment for a record from a file path or IO object
|
|
29
|
+
#
|
|
30
|
+
# @param record [Model] The Odoo record to attach the file to
|
|
31
|
+
# @param file_path_or_io [String, IO, #read] File path or IO-like object
|
|
32
|
+
# @param filename [String] The filename for the attachment
|
|
33
|
+
# @param mimetype [String] The MIME type of the file
|
|
34
|
+
# @return [Attachment] The created attachment
|
|
35
|
+
#
|
|
36
|
+
# @example From file path
|
|
37
|
+
# Rodoo::Attachment.create_for(
|
|
38
|
+
# invoice, "/path/to/file.pdf", filename: "invoice.pdf", mimetype: "application/pdf"
|
|
39
|
+
# )
|
|
40
|
+
#
|
|
41
|
+
# @example From IO object
|
|
42
|
+
# File.open("/path/to/file.pdf", "rb") do |f|
|
|
43
|
+
# Rodoo::Attachment.create_for(
|
|
44
|
+
# invoice, f, filename: "invoice.pdf", mimetype: "application/pdf"
|
|
45
|
+
# )
|
|
46
|
+
# end
|
|
47
|
+
#
|
|
48
|
+
def self.create_for(record, file_path_or_io, filename:, mimetype:)
|
|
49
|
+
data = read_file_data(file_path_or_io)
|
|
50
|
+
base64_data = Base64.strict_encode64(data)
|
|
51
|
+
create_from_base64(record, base64_data, filename: filename, mimetype: mimetype)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Create an attachment for a record from base64-encoded data
|
|
55
|
+
#
|
|
56
|
+
# @param record [Model] The Odoo record to attach the file to
|
|
57
|
+
# @param base64_data [String] The base64-encoded file data
|
|
58
|
+
# @param filename [String] The filename for the attachment
|
|
59
|
+
# @param mimetype [String] The MIME type of the file
|
|
60
|
+
# @return [Attachment] The created attachment
|
|
61
|
+
#
|
|
62
|
+
# @example
|
|
63
|
+
# Rodoo::Attachment.create_from_base64(
|
|
64
|
+
# invoice, base64_content, filename: "doc.pdf", mimetype: "application/pdf"
|
|
65
|
+
# )
|
|
66
|
+
#
|
|
67
|
+
def self.create_from_base64(record, base64_data, filename:, mimetype:)
|
|
68
|
+
attrs = {
|
|
69
|
+
name: filename,
|
|
70
|
+
type: "binary",
|
|
71
|
+
datas: base64_data,
|
|
72
|
+
res_model: record.class.model_name,
|
|
73
|
+
res_id: record.id,
|
|
74
|
+
mimetype: mimetype
|
|
75
|
+
}
|
|
76
|
+
ids = execute("create", vals_list: [attrs])
|
|
77
|
+
# Don't call find() - reading ir.attachment fails when Odoo tries to return
|
|
78
|
+
# the binary datas field. Return an instance with the known attributes.
|
|
79
|
+
new(attrs.except(:datas).merge(id: ids.first))
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Find all attachments for a record
|
|
83
|
+
#
|
|
84
|
+
# @param record [Model] The Odoo record to find attachments for
|
|
85
|
+
# @param mimetype [String, nil] Optional MIME type filter
|
|
86
|
+
# @return [Array<Attachment>] Array of attachments
|
|
87
|
+
#
|
|
88
|
+
# @example Get all attachments
|
|
89
|
+
# Rodoo::Attachment.for_record(invoice)
|
|
90
|
+
#
|
|
91
|
+
# @example Get only PDF attachments
|
|
92
|
+
# Rodoo::Attachment.for_record(invoice, mimetype: "application/pdf")
|
|
93
|
+
#
|
|
94
|
+
def self.for_record(record, mimetype: nil)
|
|
95
|
+
domain = [
|
|
96
|
+
["res_model", "=", record.class.model_name],
|
|
97
|
+
["res_id", "=", record.id]
|
|
98
|
+
]
|
|
99
|
+
domain << ["mimetype", "=", mimetype] if mimetype
|
|
100
|
+
where(domain)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Read file data from a path or IO object
|
|
104
|
+
#
|
|
105
|
+
# @param file_path_or_io [String, IO, #read] File path or IO-like object
|
|
106
|
+
# @return [String] Binary file data
|
|
107
|
+
# @api private
|
|
108
|
+
def self.read_file_data(file_path_or_io)
|
|
109
|
+
if file_path_or_io.respond_to?(:read)
|
|
110
|
+
file_path_or_io.read
|
|
111
|
+
else
|
|
112
|
+
File.binread(file_path_or_io)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
private_class_method :read_file_data
|
|
116
|
+
end
|
|
117
|
+
end
|
data/lib/rodoo/version.rb
CHANGED
data/lib/rodoo.rb
CHANGED
|
@@ -12,6 +12,7 @@ module Rodoo
|
|
|
12
12
|
autoload :AccountingEntryLine, "rodoo/models/accounting_entry_line"
|
|
13
13
|
autoload :AnalyticAccount, "rodoo/models/analytic_account"
|
|
14
14
|
autoload :AnalyticPlan, "rodoo/models/analytic_plan"
|
|
15
|
+
autoload :Attachment, "rodoo/models/attachment"
|
|
15
16
|
autoload :CustomerCreditNote, "rodoo/models/customer_credit_note"
|
|
16
17
|
autoload :CustomerInvoice, "rodoo/models/customer_invoice"
|
|
17
18
|
autoload :JournalEntry, "rodoo/models/journal_entry"
|
|
@@ -20,6 +21,7 @@ module Rodoo
|
|
|
20
21
|
autoload :Project, "rodoo/models/project"
|
|
21
22
|
autoload :ProviderCreditNote, "rodoo/models/provider_credit_note"
|
|
22
23
|
autoload :ProviderInvoice, "rodoo/models/provider_invoice"
|
|
24
|
+
autoload :Tax, "rodoo/models/tax"
|
|
23
25
|
|
|
24
26
|
@configuration = nil
|
|
25
27
|
@connection = nil
|
metadata
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rodoo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rodrigo Serrano
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
-
dependencies:
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: base64
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
12
26
|
description: This gem implements a wrapper to interact with Odoo's API in Ruby. The
|
|
13
27
|
API used is Odoo's 'external JSON-2 API' introduced in Odoo v19.
|
|
14
28
|
email:
|
|
@@ -35,6 +49,7 @@ files:
|
|
|
35
49
|
- lib/rodoo/models/accounting_entry_line.rb
|
|
36
50
|
- lib/rodoo/models/analytic_account.rb
|
|
37
51
|
- lib/rodoo/models/analytic_plan.rb
|
|
52
|
+
- lib/rodoo/models/attachment.rb
|
|
38
53
|
- lib/rodoo/models/contact.rb
|
|
39
54
|
- lib/rodoo/models/customer_credit_note.rb
|
|
40
55
|
- lib/rodoo/models/customer_invoice.rb
|
|
@@ -43,6 +58,7 @@ files:
|
|
|
43
58
|
- lib/rodoo/models/project.rb
|
|
44
59
|
- lib/rodoo/models/provider_credit_note.rb
|
|
45
60
|
- lib/rodoo/models/provider_invoice.rb
|
|
61
|
+
- lib/rodoo/models/tax.rb
|
|
46
62
|
- lib/rodoo/version.rb
|
|
47
63
|
homepage: https://github.com/dekuple/rodoo
|
|
48
64
|
licenses:
|