io-complyance-unify-sdk 3.0.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/CHANGELOG.md +26 -0
- data/README.md +595 -0
- data/lib/complyance/circuit_breaker.rb +99 -0
- data/lib/complyance/persistent_queue_manager.rb +474 -0
- data/lib/complyance/retry_strategy.rb +198 -0
- data/lib/complyance_sdk/config/retry_config.rb +127 -0
- data/lib/complyance_sdk/config/sdk_config.rb +212 -0
- data/lib/complyance_sdk/exceptions/circuit_breaker_open_error.rb +14 -0
- data/lib/complyance_sdk/exceptions/sdk_exception.rb +93 -0
- data/lib/complyance_sdk/generators/config_generator.rb +67 -0
- data/lib/complyance_sdk/generators/install_generator.rb +22 -0
- data/lib/complyance_sdk/generators/templates/complyance_initializer.rb +36 -0
- data/lib/complyance_sdk/http/authentication_middleware.rb +43 -0
- data/lib/complyance_sdk/http/client.rb +223 -0
- data/lib/complyance_sdk/http/logging_middleware.rb +153 -0
- data/lib/complyance_sdk/jobs/base_job.rb +63 -0
- data/lib/complyance_sdk/jobs/process_document_job.rb +92 -0
- data/lib/complyance_sdk/jobs/sidekiq_job.rb +165 -0
- data/lib/complyance_sdk/middleware/rack_middleware.rb +39 -0
- data/lib/complyance_sdk/models/country.rb +205 -0
- data/lib/complyance_sdk/models/country_policy_registry.rb +159 -0
- data/lib/complyance_sdk/models/document_type.rb +52 -0
- data/lib/complyance_sdk/models/environment.rb +144 -0
- data/lib/complyance_sdk/models/logical_doc_type.rb +228 -0
- data/lib/complyance_sdk/models/mode.rb +47 -0
- data/lib/complyance_sdk/models/operation.rb +47 -0
- data/lib/complyance_sdk/models/policy_result.rb +145 -0
- data/lib/complyance_sdk/models/purpose.rb +52 -0
- data/lib/complyance_sdk/models/source.rb +104 -0
- data/lib/complyance_sdk/models/source_ref.rb +130 -0
- data/lib/complyance_sdk/models/unify_request.rb +208 -0
- data/lib/complyance_sdk/models/unify_response.rb +198 -0
- data/lib/complyance_sdk/queue/persistent_queue_manager.rb +609 -0
- data/lib/complyance_sdk/railtie.rb +29 -0
- data/lib/complyance_sdk/retry/circuit_breaker.rb +159 -0
- data/lib/complyance_sdk/retry/retry_manager.rb +108 -0
- data/lib/complyance_sdk/retry/retry_strategy.rb +225 -0
- data/lib/complyance_sdk/version.rb +5 -0
- data/lib/complyance_sdk.rb +935 -0
- metadata +322 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ComplyanceSDK
|
|
4
|
+
module Jobs
|
|
5
|
+
# Sidekiq-specific job implementation
|
|
6
|
+
class SidekiqJob
|
|
7
|
+
# Include Sidekiq::Job if available
|
|
8
|
+
if defined?(Sidekiq)
|
|
9
|
+
include Sidekiq::Job
|
|
10
|
+
|
|
11
|
+
# Configure Sidekiq options
|
|
12
|
+
sidekiq_options queue: :complyance_sdk,
|
|
13
|
+
retry: 3,
|
|
14
|
+
backtrace: true,
|
|
15
|
+
dead: false
|
|
16
|
+
|
|
17
|
+
# Custom retry logic for Sidekiq
|
|
18
|
+
sidekiq_retry_in do |count, exception|
|
|
19
|
+
case exception
|
|
20
|
+
when ComplyanceSDK::Exceptions::NetworkError
|
|
21
|
+
# Exponential backoff for network errors
|
|
22
|
+
(count ** 2) + 15 + (rand(30) * (count + 1))
|
|
23
|
+
when ComplyanceSDK::Exceptions::APIError
|
|
24
|
+
# Only retry certain API errors
|
|
25
|
+
if exception.status_code && [429, 500, 502, 503, 504].include?(exception.status_code)
|
|
26
|
+
(count ** 2) + 10 + (rand(10) * (count + 1))
|
|
27
|
+
else
|
|
28
|
+
:kill # Don't retry other API errors
|
|
29
|
+
end
|
|
30
|
+
when ComplyanceSDK::Exceptions::ConfigurationError,
|
|
31
|
+
ComplyanceSDK::Exceptions::ValidationError
|
|
32
|
+
:kill # Don't retry configuration or validation errors
|
|
33
|
+
else
|
|
34
|
+
# Default exponential backoff
|
|
35
|
+
(count ** 2) + 15 + (rand(30) * (count + 1))
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Process a document using Sidekiq
|
|
41
|
+
#
|
|
42
|
+
# @param request_data [Hash] The UnifyRequest data
|
|
43
|
+
# @param callback_url [String, nil] Optional callback URL for results
|
|
44
|
+
# @param callback_headers [Hash] Optional headers for callback
|
|
45
|
+
def perform(request_data, callback_url = nil, callback_headers = {})
|
|
46
|
+
unless ComplyanceSDK.configured?
|
|
47
|
+
raise ComplyanceSDK::Exceptions::ConfigurationError.new(
|
|
48
|
+
"ComplyanceSDK is not configured for Sidekiq jobs"
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
client = ComplyanceSDK::HTTP::Client.new(ComplyanceSDK.configuration)
|
|
53
|
+
|
|
54
|
+
log_info("Starting Sidekiq document processing", {
|
|
55
|
+
document_type: request_data["document_type"],
|
|
56
|
+
country: request_data["country"],
|
|
57
|
+
jid: jid
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
begin
|
|
61
|
+
response = client.post("/api/v1/unify", request_data)
|
|
62
|
+
|
|
63
|
+
log_info("Sidekiq document processed successfully", {
|
|
64
|
+
response_status: response["status"],
|
|
65
|
+
submission_id: response.dig("data", "submission_id"),
|
|
66
|
+
jid: jid
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
# Send callback if provided
|
|
70
|
+
if callback_url
|
|
71
|
+
send_callback(callback_url, callback_headers, {
|
|
72
|
+
status: "success",
|
|
73
|
+
data: response,
|
|
74
|
+
request_id: request_data["metadata"]&.dig("request_id"),
|
|
75
|
+
job_id: jid
|
|
76
|
+
})
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
response
|
|
80
|
+
rescue => error
|
|
81
|
+
log_error("Sidekiq document processing failed", {
|
|
82
|
+
error_class: error.class.name,
|
|
83
|
+
error_message: error.message,
|
|
84
|
+
jid: jid
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
# Send error callback if provided
|
|
88
|
+
if callback_url
|
|
89
|
+
send_callback(callback_url, callback_headers, {
|
|
90
|
+
status: "error",
|
|
91
|
+
error: {
|
|
92
|
+
code: error.respond_to?(:code) ? error.code : "unknown_error",
|
|
93
|
+
message: error.message
|
|
94
|
+
},
|
|
95
|
+
request_id: request_data["metadata"]&.dig("request_id"),
|
|
96
|
+
job_id: jid
|
|
97
|
+
})
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
raise error
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
def jid
|
|
107
|
+
@jid ||= defined?(Sidekiq) ? (bid || "unknown") : "no-sidekiq"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def send_callback(url, headers, payload)
|
|
111
|
+
require "net/http"
|
|
112
|
+
require "uri"
|
|
113
|
+
require "json"
|
|
114
|
+
|
|
115
|
+
uri = URI(url)
|
|
116
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
117
|
+
http.use_ssl = uri.scheme == "https"
|
|
118
|
+
|
|
119
|
+
request = Net::HTTP::Post.new(uri)
|
|
120
|
+
request["Content-Type"] = "application/json"
|
|
121
|
+
headers.each { |key, value| request[key] = value }
|
|
122
|
+
request.body = payload.to_json
|
|
123
|
+
|
|
124
|
+
response = http.request(request)
|
|
125
|
+
|
|
126
|
+
log_info("Sidekiq callback sent", {
|
|
127
|
+
callback_url: url,
|
|
128
|
+
callback_status: response.code,
|
|
129
|
+
payload_status: payload[:status],
|
|
130
|
+
jid: jid
|
|
131
|
+
})
|
|
132
|
+
rescue => error
|
|
133
|
+
log_warn("Sidekiq callback failed", {
|
|
134
|
+
callback_url: url,
|
|
135
|
+
error: error.message,
|
|
136
|
+
jid: jid
|
|
137
|
+
})
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def log_info(message, context = {})
|
|
141
|
+
log_message("INFO", message, context)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def log_warn(message, context = {})
|
|
145
|
+
log_message("WARN", message, context)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def log_error(message, context = {})
|
|
149
|
+
log_message("ERROR", message, context)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def log_message(level, message, context = {})
|
|
153
|
+
if defined?(Sidekiq) && Sidekiq.logger
|
|
154
|
+
full_message = "ComplyanceSDK Sidekiq Job: #{message}"
|
|
155
|
+
full_message += " Context: #{context}" unless context.empty?
|
|
156
|
+
Sidekiq.logger.send(level.downcase.to_sym, full_message)
|
|
157
|
+
elsif defined?(Rails) && Rails.logger
|
|
158
|
+
full_message = "ComplyanceSDK Sidekiq Job: #{message}"
|
|
159
|
+
full_message += " Context: #{context}" unless context.empty?
|
|
160
|
+
Rails.logger.send(level.downcase.to_sym, full_message)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ComplyanceSDK
|
|
4
|
+
module Middleware
|
|
5
|
+
# Rack middleware for ComplyanceSDK integration
|
|
6
|
+
class RackMiddleware
|
|
7
|
+
def initialize(app, options = {})
|
|
8
|
+
@app = app
|
|
9
|
+
@options = options
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call(env)
|
|
13
|
+
# Add ComplyanceSDK context to the request
|
|
14
|
+
env["complyance_sdk.configured"] = ComplyanceSDK.configured?
|
|
15
|
+
env["complyance_sdk.environment"] = ComplyanceSDK.configuration&.environment
|
|
16
|
+
|
|
17
|
+
# Add request ID for tracing
|
|
18
|
+
env["complyance_sdk.request_id"] = generate_request_id
|
|
19
|
+
|
|
20
|
+
status, headers, response = @app.call(env)
|
|
21
|
+
|
|
22
|
+
# Add ComplyanceSDK headers to response if configured
|
|
23
|
+
if ComplyanceSDK.configured? && @options[:add_headers]
|
|
24
|
+
headers["X-ComplyanceSDK-Version"] = ComplyanceSDK::VERSION
|
|
25
|
+
headers["X-ComplyanceSDK-Environment"] = ComplyanceSDK.configuration.environment.to_s
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
[status, headers, response]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def generate_request_id
|
|
34
|
+
require "securerandom"
|
|
35
|
+
SecureRandom.uuid
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ComplyanceSDK
|
|
4
|
+
module Models
|
|
5
|
+
# Country enumeration for supported countries
|
|
6
|
+
class Country
|
|
7
|
+
# Supported countries
|
|
8
|
+
SA = :sa # Saudi Arabia
|
|
9
|
+
MY = :my # Malaysia
|
|
10
|
+
AE = :ae # United Arab Emirates
|
|
11
|
+
SG = :sg # Singapore
|
|
12
|
+
EG = :eg # Egypt
|
|
13
|
+
IN = :in # India
|
|
14
|
+
PH = :ph # Philippines
|
|
15
|
+
TH = :th # Thailand
|
|
16
|
+
VN = :vn # Vietnam
|
|
17
|
+
ID = :id # Indonesia
|
|
18
|
+
BD = :bd # Bangladesh
|
|
19
|
+
LK = :lk # Sri Lanka
|
|
20
|
+
PK = :pk # Pakistan
|
|
21
|
+
NP = :np # Nepal
|
|
22
|
+
MM = :mm # Myanmar
|
|
23
|
+
KH = :kh # Cambodia
|
|
24
|
+
LA = :la # Laos
|
|
25
|
+
BN = :bn # Brunei
|
|
26
|
+
MV = :mv # Maldives
|
|
27
|
+
BT = :bt # Bhutan
|
|
28
|
+
|
|
29
|
+
# All supported countries
|
|
30
|
+
ALL_COUNTRIES = [
|
|
31
|
+
SA, MY, AE, SG, EG, IN, PH, TH, VN, ID,
|
|
32
|
+
BD, LK, PK, NP, MM, KH, LA, BN, MV, BT
|
|
33
|
+
].freeze
|
|
34
|
+
|
|
35
|
+
class << self
|
|
36
|
+
# Check if a country is valid
|
|
37
|
+
#
|
|
38
|
+
# @param country [Symbol] The country code to validate
|
|
39
|
+
# @return [Boolean] True if valid, false otherwise
|
|
40
|
+
def valid?(country)
|
|
41
|
+
ALL_COUNTRIES.include?(country)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Parse a string to a country symbol
|
|
45
|
+
#
|
|
46
|
+
# @param str [String] The string to parse
|
|
47
|
+
# @return [Symbol] The country symbol
|
|
48
|
+
# @raise [ArgumentError] If the string is invalid
|
|
49
|
+
def from_string(str)
|
|
50
|
+
return str if str.is_a?(Symbol)
|
|
51
|
+
|
|
52
|
+
country = str.to_s.downcase.to_sym
|
|
53
|
+
|
|
54
|
+
unless valid?(country)
|
|
55
|
+
raise ArgumentError, "Invalid country: #{str}. Valid countries: #{ALL_COUNTRIES.join(', ')}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
country
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Convert a country to string format
|
|
62
|
+
#
|
|
63
|
+
# @param country [Symbol] The country
|
|
64
|
+
# @return [String] The string representation
|
|
65
|
+
def to_string(country)
|
|
66
|
+
country.to_s.upcase
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Get the default tax authority for a country
|
|
70
|
+
#
|
|
71
|
+
# @param country [Symbol] The country
|
|
72
|
+
# @return [String, nil] The tax authority name or nil if not mapped
|
|
73
|
+
def default_tax_authority(country)
|
|
74
|
+
case country
|
|
75
|
+
when SA
|
|
76
|
+
'ZATCA'
|
|
77
|
+
when MY
|
|
78
|
+
'LHDN'
|
|
79
|
+
when AE
|
|
80
|
+
'FTA'
|
|
81
|
+
when SG
|
|
82
|
+
'IRAS'
|
|
83
|
+
when EG
|
|
84
|
+
'ETA'
|
|
85
|
+
when IN
|
|
86
|
+
'GSTN'
|
|
87
|
+
when PH
|
|
88
|
+
'BIR'
|
|
89
|
+
when TH
|
|
90
|
+
'RD'
|
|
91
|
+
when VN
|
|
92
|
+
'GDT'
|
|
93
|
+
when ID
|
|
94
|
+
'DJP'
|
|
95
|
+
else
|
|
96
|
+
nil
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Get the full country name
|
|
101
|
+
#
|
|
102
|
+
# @param country [Symbol] The country code
|
|
103
|
+
# @return [String] The full country name
|
|
104
|
+
def full_name(country)
|
|
105
|
+
case country
|
|
106
|
+
when SA
|
|
107
|
+
'Saudi Arabia'
|
|
108
|
+
when MY
|
|
109
|
+
'Malaysia'
|
|
110
|
+
when AE
|
|
111
|
+
'United Arab Emirates'
|
|
112
|
+
when SG
|
|
113
|
+
'Singapore'
|
|
114
|
+
when EG
|
|
115
|
+
'Egypt'
|
|
116
|
+
when IN
|
|
117
|
+
'India'
|
|
118
|
+
when PH
|
|
119
|
+
'Philippines'
|
|
120
|
+
when TH
|
|
121
|
+
'Thailand'
|
|
122
|
+
when VN
|
|
123
|
+
'Vietnam'
|
|
124
|
+
when ID
|
|
125
|
+
'Indonesia'
|
|
126
|
+
when BD
|
|
127
|
+
'Bangladesh'
|
|
128
|
+
when LK
|
|
129
|
+
'Sri Lanka'
|
|
130
|
+
when PK
|
|
131
|
+
'Pakistan'
|
|
132
|
+
when NP
|
|
133
|
+
'Nepal'
|
|
134
|
+
when MM
|
|
135
|
+
'Myanmar'
|
|
136
|
+
when KH
|
|
137
|
+
'Cambodia'
|
|
138
|
+
when LA
|
|
139
|
+
'Laos'
|
|
140
|
+
when BN
|
|
141
|
+
'Brunei'
|
|
142
|
+
when MV
|
|
143
|
+
'Maldives'
|
|
144
|
+
when BT
|
|
145
|
+
'Bhutan'
|
|
146
|
+
else
|
|
147
|
+
country.to_s.upcase
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Check if country is allowed for production environments
|
|
152
|
+
# Based on the Java SDK's validation rules
|
|
153
|
+
#
|
|
154
|
+
# @param country [Symbol] The country
|
|
155
|
+
# @param environment [Symbol] The environment
|
|
156
|
+
# @return [Boolean] True if allowed
|
|
157
|
+
def allowed_for_environment?(country, environment)
|
|
158
|
+
case environment
|
|
159
|
+
when :dev, :test, :stage, :local
|
|
160
|
+
# Development environments allow all countries
|
|
161
|
+
true
|
|
162
|
+
when :sandbox, :simulation, :production
|
|
163
|
+
# Production environments have restrictions
|
|
164
|
+
case country
|
|
165
|
+
when SA
|
|
166
|
+
# SA is allowed in all production environments
|
|
167
|
+
true
|
|
168
|
+
when MY
|
|
169
|
+
# MY is allowed in SANDBOX and PRODUCTION only (not SIMULATION)
|
|
170
|
+
environment != :simulation
|
|
171
|
+
else
|
|
172
|
+
# Other countries are blocked in production environments
|
|
173
|
+
false
|
|
174
|
+
end
|
|
175
|
+
else
|
|
176
|
+
# Unknown environment, default to allowing
|
|
177
|
+
true
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Get validation error message for country/environment combination
|
|
182
|
+
#
|
|
183
|
+
# @param country [Symbol] The country
|
|
184
|
+
# @param environment [Symbol] The environment
|
|
185
|
+
# @return [String, nil] Error message or nil if allowed
|
|
186
|
+
def validation_error_message(country, environment)
|
|
187
|
+
return nil if allowed_for_environment?(country, environment)
|
|
188
|
+
|
|
189
|
+
case environment
|
|
190
|
+
when :simulation
|
|
191
|
+
if country == MY
|
|
192
|
+
"MY (Malaysia) is not allowed in SIMULATION environment. Use SANDBOX or PRODUCTION."
|
|
193
|
+
else
|
|
194
|
+
"Only SA and MY are allowed for #{environment.to_s.upcase}. Use DEV/TEST/STAGE for other countries."
|
|
195
|
+
end
|
|
196
|
+
when :sandbox, :production
|
|
197
|
+
"Only SA and MY are allowed for #{environment.to_s.upcase}. Use DEV/TEST/STAGE for other countries."
|
|
198
|
+
else
|
|
199
|
+
"Country #{country.to_s.upcase} is not allowed for environment #{environment.to_s.upcase}."
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'logical_doc_type'
|
|
4
|
+
require_relative 'policy_result'
|
|
5
|
+
require_relative 'document_type'
|
|
6
|
+
require_relative 'country'
|
|
7
|
+
|
|
8
|
+
module ComplyanceSDK
|
|
9
|
+
module Models
|
|
10
|
+
# Registry for country-specific document type policies
|
|
11
|
+
# Maps logical document types to base document types and meta configuration flags
|
|
12
|
+
class CountryPolicyRegistry
|
|
13
|
+
class << self
|
|
14
|
+
# Evaluate a logical document type for a specific country
|
|
15
|
+
#
|
|
16
|
+
# @param country [Symbol] The country code
|
|
17
|
+
# @param logical_type [Symbol] The logical document type
|
|
18
|
+
# @return [PolicyResult] The policy result
|
|
19
|
+
# @raise [ComplyanceSDK::Exceptions::ValidationError] If the combination is not allowed
|
|
20
|
+
def evaluate(country, logical_type)
|
|
21
|
+
# Validate inputs
|
|
22
|
+
unless Country.valid?(country)
|
|
23
|
+
raise ComplyanceSDK::Exceptions::ValidationError.new(
|
|
24
|
+
"Invalid country: #{country}. Valid countries: #{Country::ALL_COUNTRIES.join(', ')}"
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
unless LogicalDocType.valid?(logical_type)
|
|
29
|
+
raise ComplyanceSDK::Exceptions::ValidationError.new(
|
|
30
|
+
"Invalid logical document type: #{logical_type}. Valid types: #{LogicalDocType::ALL_TYPES.join(', ')}"
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Get policy for the logical type (country-agnostic for now)
|
|
35
|
+
policy = create_policy_for_logical_type(logical_type)
|
|
36
|
+
|
|
37
|
+
if policy.nil?
|
|
38
|
+
raise ComplyanceSDK::Exceptions::ValidationError.new(
|
|
39
|
+
"Document type not allowed for country: #{country}. Logical type: #{logical_type}"
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
policy
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
# Create meta configuration flags map
|
|
49
|
+
#
|
|
50
|
+
# @param is_export [Boolean] Export flag
|
|
51
|
+
# @param is_self_billed [Boolean] Self-billed flag
|
|
52
|
+
# @param is_third_party [Boolean] Third party flag
|
|
53
|
+
# @param is_nominal_supply [Boolean] Nominal supply flag
|
|
54
|
+
# @param is_summary [Boolean] Summary flag
|
|
55
|
+
# @param is_b2b [Boolean] B2B flag
|
|
56
|
+
# @param is_prepayment [Boolean] Prepayment flag
|
|
57
|
+
# @param is_adjusted [Boolean] Adjusted flag
|
|
58
|
+
# @param is_receipt [Boolean] Receipt flag
|
|
59
|
+
# @return [Hash] The configuration flags
|
|
60
|
+
def create_config_map(is_export: false, is_self_billed: false, is_third_party: false,
|
|
61
|
+
is_nominal_supply: false, is_summary: false, is_b2b: false,
|
|
62
|
+
is_prepayment: false, is_adjusted: false, is_receipt: false)
|
|
63
|
+
{
|
|
64
|
+
isExport: is_export,
|
|
65
|
+
isSelfBilled: is_self_billed,
|
|
66
|
+
isThirdParty: is_third_party,
|
|
67
|
+
isNominal: is_nominal_supply, # Changed from isNominalSupply to isNominal to match Java
|
|
68
|
+
isSummary: is_summary,
|
|
69
|
+
isB2B: is_b2b,
|
|
70
|
+
isPrepayment: is_prepayment,
|
|
71
|
+
isAdjusted: is_adjusted,
|
|
72
|
+
isReceipt: is_receipt
|
|
73
|
+
}
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Get document type string for a logical type
|
|
77
|
+
#
|
|
78
|
+
# @param logical_type [Symbol] The logical document type
|
|
79
|
+
# @return [String] The document type string
|
|
80
|
+
def get_document_type_string(logical_type)
|
|
81
|
+
LogicalDocType.invoice_data_document_type(logical_type)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Get meta config document type for a logical type
|
|
85
|
+
#
|
|
86
|
+
# @param logical_type [Symbol] The logical document type
|
|
87
|
+
# @return [String] The meta config document type
|
|
88
|
+
def get_meta_config_document_type(logical_type)
|
|
89
|
+
LogicalDocType.meta_config_document_type(logical_type)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Create policy result for a logical document type
|
|
93
|
+
#
|
|
94
|
+
# @param logical_type [Symbol] The logical document type
|
|
95
|
+
# @return [PolicyResult] The policy result
|
|
96
|
+
def create_policy_for_logical_type(logical_type)
|
|
97
|
+
document_type = get_document_type_string(logical_type)
|
|
98
|
+
type_name = logical_type.to_s
|
|
99
|
+
|
|
100
|
+
# Determine B2B vs B2C based on TAX_INVOICE vs SIMPLIFIED_TAX_INVOICE
|
|
101
|
+
is_b2b = if type_name.start_with?('simplified_tax_invoice')
|
|
102
|
+
false # B2C
|
|
103
|
+
elsif type_name.start_with?('tax_invoice')
|
|
104
|
+
true # B2B
|
|
105
|
+
else
|
|
106
|
+
# For legacy types, set appropriate defaults
|
|
107
|
+
# Legacy types - determine B2B based on context
|
|
108
|
+
# case logical_type
|
|
109
|
+
# when LogicalDocType::INVOICE,
|
|
110
|
+
# LogicalDocType::EXPORT_INVOICE,
|
|
111
|
+
# LogicalDocType::EXPORT_THIRD_PARTY_INVOICE,
|
|
112
|
+
# LogicalDocType::THIRD_PARTY_INVOICE,
|
|
113
|
+
# LogicalDocType::SELF_BILLED_INVOICE,
|
|
114
|
+
# LogicalDocType::NOMINAL_SUPPLY_INVOICE,
|
|
115
|
+
# LogicalDocType::SUMMARY_INVOICE
|
|
116
|
+
# true
|
|
117
|
+
# else
|
|
118
|
+
# false
|
|
119
|
+
# end
|
|
120
|
+
true # Default for non-simplified types
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Determine other flags based on type name
|
|
124
|
+
is_export = type_name.include?('export')
|
|
125
|
+
is_self_billed = type_name.include?('self_billed')
|
|
126
|
+
is_third_party = type_name.include?('third_party')
|
|
127
|
+
is_nominal_supply = type_name.include?('nominal_supply')
|
|
128
|
+
is_summary = type_name.include?('summary')
|
|
129
|
+
is_prepayment = type_name.include?('prepayment')
|
|
130
|
+
is_adjusted = type_name.include?('adjusted')
|
|
131
|
+
is_receipt = type_name.include?('receipt')
|
|
132
|
+
|
|
133
|
+
config = create_config_map(
|
|
134
|
+
is_export: is_export,
|
|
135
|
+
is_self_billed: is_self_billed,
|
|
136
|
+
is_third_party: is_third_party,
|
|
137
|
+
is_nominal_supply: is_nominal_supply,
|
|
138
|
+
is_summary: is_summary,
|
|
139
|
+
is_b2b: is_b2b,
|
|
140
|
+
is_prepayment: is_prepayment,
|
|
141
|
+
is_adjusted: is_adjusted,
|
|
142
|
+
is_receipt: is_receipt
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Determine base DocumentType
|
|
146
|
+
base_type = if type_name.include?('credit_note')
|
|
147
|
+
DocumentType::CREDIT_NOTE
|
|
148
|
+
elsif type_name.include?('debit_note')
|
|
149
|
+
DocumentType::DEBIT_NOTE
|
|
150
|
+
else
|
|
151
|
+
DocumentType::TAX_INVOICE
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
PolicyResult.new(base_type, config, document_type)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ComplyanceSDK
|
|
4
|
+
module Models
|
|
5
|
+
# Document type enumeration
|
|
6
|
+
module DocumentType
|
|
7
|
+
# Tax invoice document type
|
|
8
|
+
TAX_INVOICE = :tax_invoice
|
|
9
|
+
|
|
10
|
+
# Credit note document type
|
|
11
|
+
CREDIT_NOTE = :credit_note
|
|
12
|
+
|
|
13
|
+
# Debit note document type
|
|
14
|
+
DEBIT_NOTE = :debit_note
|
|
15
|
+
|
|
16
|
+
# Get all valid document types
|
|
17
|
+
#
|
|
18
|
+
# @return [Array<Symbol>] All valid document types
|
|
19
|
+
def self.all
|
|
20
|
+
[TAX_INVOICE, CREDIT_NOTE, DEBIT_NOTE]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Check if a document type is valid
|
|
24
|
+
#
|
|
25
|
+
# @param document_type [Symbol, String] The document type to check
|
|
26
|
+
# @return [Boolean] True if valid, false otherwise
|
|
27
|
+
def self.valid?(document_type)
|
|
28
|
+
return false if document_type.nil?
|
|
29
|
+
all.include?(document_type.to_sym)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Convert string to document type symbol
|
|
33
|
+
#
|
|
34
|
+
# @param document_type [String, Symbol] The document type
|
|
35
|
+
# @return [Symbol, nil] The document type symbol or nil if invalid
|
|
36
|
+
def self.normalize(document_type)
|
|
37
|
+
return nil if document_type.nil?
|
|
38
|
+
|
|
39
|
+
case document_type.to_s.downcase.gsub(/[-_\s]/, '')
|
|
40
|
+
when "taxinvoice", "invoice"
|
|
41
|
+
TAX_INVOICE
|
|
42
|
+
when "creditnote", "credit"
|
|
43
|
+
CREDIT_NOTE
|
|
44
|
+
when "debitnote", "debit"
|
|
45
|
+
DEBIT_NOTE
|
|
46
|
+
else
|
|
47
|
+
document_type.to_sym if valid?(document_type)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|