Class: Nfe::Resources::ProductInvoices

Inherits:
AbstractResource show all
Defined in:
lib/nfe/resources/product_invoices.rb,
sig/nfe/resources/product_invoices.rbs

Overview

Product invoices (NF-e) resource for the :cte host family (+https://api.nfse.io/v2/...+). Full NF-e lifecycle: issue, list, retrieve, cancel, correction letters (CC-e), disablement (inutilização) and file downloads.

Emission is asynchronous (HTTP 202, queued; completion via webhook): #create/#create_with_state_tax return either a ProductInvoicePending or a ProductInvoiceIssued. There is no +create_and_wait+/+create_batch+ in v1.0 — poll manually with #retrieve + FlowStatus.terminal?.

NOTE: unlike the other invoice resources, the download methods return a NfeFileResource (a URI to the file), NOT raw bytes.

Instance Attribute Summary

Attributes inherited from AbstractResource

#client

Instance Method Summary collapse

Methods inherited from AbstractResource

#build_list_page, #build_multipart_body, #cursor_list_page, #delete, #dig_key, #download, #full_path, #get, #handle_async_response, #hydrate, #hydrate_list, #initialize, #multipart_part, #page_list_page, #parse_json, #post, #put, #unwrap, #upload_multipart

Constructor Details

This class inherits a constructor from Nfe::Resources::AbstractResource

Instance Method Details

#api_familySymbol

Returns:

  • (Symbol)


29
30
31
# File 'lib/nfe/resources/product_invoices.rb', line 29

def api_family
  :cte
end

#api_versionString

The :cte host serves the v2 API; paths embed /v2 explicitly, so no version segment is auto-prefixed.

Returns:

  • (String)


35
36
37
# File 'lib/nfe/resources/product_invoices.rb', line 35

def api_version
  ""
end

#base_path(company_id) ⇒ String

Parameters:

  • company_id (String)

Returns:

  • (String)


213
214
215
# File 'lib/nfe/resources/product_invoices.rb', line 213

def base_path(company_id)
  "/v2/companies/#{company_id}/productinvoices"
end

#build_pending(response) ⇒ Nfe::Resources::ProductInvoicePending

Build a Nfe::Resources::ProductInvoicePending from a 202 response, or raise when the Location header is missing/unparsable.



270
271
272
273
274
275
276
277
278
279
280
# File 'lib/nfe/resources/product_invoices.rb', line 270

def build_pending(response)
  location = response.location
  invoice_id = extract_invoice_id(location)
  if location.nil? || location.empty? || invoice_id.nil?
    raise Nfe::InvoiceProcessingError.new(
      "Resposta 202 sem Location utilizável: não é possível identificar a NF-e em processamento.",
      status_code: response.status, response_headers: response.headers
    )
  end
  ProductInvoicePending.new(invoice_id: invoice_id, location: location)
end

#cancel(company_id:, invoice_id:, reason: nil) ⇒ Hash

Cancel a product invoice (async); reason forwarded as a query param.

Parameters:

  • company_id (String)
  • invoice_id (String)
  • reason (String, nil) (defaults to: nil)
  • company_id: (String)
  • invoice_id: (String)
  • reason: (String, nil) (defaults to: nil)

Returns:

  • (Hash)

    the cancellation resource payload.



107
108
109
110
111
112
# File 'lib/nfe/resources/product_invoices.rb', line 107

def cancel(company_id:, invoice_id:, reason: nil)
  cid = Nfe::IdValidator.company_id(company_id)
  iid = Nfe::IdValidator.invoice_id(invoice_id)
  response = delete("#{base_path(cid)}/#{iid}", query: { reason: reason }.compact)
  parse_json(response.body) || {}
end

#create(company_id:, data:, idempotency_key: nil, request_options: nil) ⇒ ProductInvoicePending, ProductInvoiceIssued

Issue a product invoice (NF-e). Returns a discriminated result: a Nfe::Resources::ProductInvoicePending on HTTP 202 or a Nfe::Resources::ProductInvoiceIssued on 201.

Parameters:

  • company_id (String)
  • data (Hash)

    invoice payload (camelCase keys per the API).

  • idempotency_key (String, nil) (defaults to: nil)

    sent as the Idempotency-Key header.

  • request_options (Nfe::RequestOptions, nil) (defaults to: nil)

    per-call overrides.

  • company_id: (String)
  • data: (Hash[untyped, untyped])
  • idempotency_key: (String, nil) (defaults to: nil)
  • request_options: (Nfe::RequestOptions, nil) (defaults to: nil)

Returns:



49
50
51
52
53
54
55
# File 'lib/nfe/resources/product_invoices.rb', line 49

def create(company_id:, data:, idempotency_key: nil, request_options: nil)
  cid = Nfe::IdValidator.company_id(company_id)
  response = post(base_path(cid), body: json_body(data), headers: json_headers,
                                  idempotency_key: idempotency_key,
                                  request_options: request_options)
  discriminate(response)
end

#create_with_state_tax(company_id:, state_tax_id:, data:, idempotency_key: nil, request_options: nil) ⇒ ProductInvoicePending, ProductInvoiceIssued

Issue a product invoice scoped to a state tax registration.

Parameters:

  • company_id (String)
  • state_tax_id (String)
  • data (Hash)
  • idempotency_key (String, nil) (defaults to: nil)
  • request_options (Nfe::RequestOptions, nil) (defaults to: nil)
  • company_id: (String)
  • state_tax_id: (String)
  • data: (Hash[untyped, untyped])
  • idempotency_key: (String, nil) (defaults to: nil)
  • request_options: (Nfe::RequestOptions, nil) (defaults to: nil)

Returns:



65
66
67
68
69
70
71
72
# File 'lib/nfe/resources/product_invoices.rb', line 65

def create_with_state_tax(company_id:, state_tax_id:, data:, idempotency_key: nil, request_options: nil)
  cid = Nfe::IdValidator.company_id(company_id)
  sid = Nfe::IdValidator.state_tax_id(state_tax_id)
  response = post("/v2/companies/#{cid}/statetaxes/#{sid}/productinvoices",
                  body: json_body(data), headers: json_headers,
                  idempotency_key: idempotency_key, request_options: request_options)
  discriminate(response)
end

#cursor_query(options) ⇒ Hash[Symbol, untyped]

Parameters:

  • options (Hash[Symbol, untyped])

Returns:

  • (Hash[Symbol, untyped])


231
232
233
234
235
236
# File 'lib/nfe/resources/product_invoices.rb', line 231

def cursor_query(options)
  {
    startingAfter: options[:starting_after], endingBefore: options[:ending_before],
    limit: options[:limit], q: options[:q]
  }.compact
end

#disable(company_id:, invoice_id:, reason: nil) ⇒ Hash

Disable (inutilizar) a single invoice (async). reason optional.

Parameters:

  • company_id: (String)
  • invoice_id: (String)
  • reason: (String, nil) (defaults to: nil)

Returns:

  • (Hash)

    the cancellation resource payload.



193
194
195
196
197
198
# File 'lib/nfe/resources/product_invoices.rb', line 193

def disable(company_id:, invoice_id:, reason: nil)
  cid = Nfe::IdValidator.company_id(company_id)
  iid = Nfe::IdValidator.invoice_id(invoice_id)
  response = post("#{base_path(cid)}/#{iid}/disablement", query: { reason: reason }.compact)
  parse_json(response.body) || {}
end

#disable_range(company_id:, data:) ⇒ Hash

Disable a range of invoice numbers (single number = same begin/last).

Parameters:

  • company_id (String)
  • data (Hash)

    { environment, serie, state, begin_number, last_number, reason? }.

  • company_id: (String)
  • data: (Hash[untyped, untyped])

Returns:

  • (Hash)

    the disablement resource payload.



205
206
207
208
209
# File 'lib/nfe/resources/product_invoices.rb', line 205

def disable_range(company_id:, data:)
  cid = Nfe::IdValidator.company_id(company_id)
  response = post("#{base_path(cid)}/disablement", body: json_body(data), headers: json_headers)
  parse_json(response.body) || {}
end

#discriminate(response) ⇒ Nfe::Resources::ProductInvoicePending, Nfe::Resources::ProductInvoiceIssued

Interpret the emission response into the discriminated value object.



262
263
264
265
266
# File 'lib/nfe/resources/product_invoices.rb', line 262

def discriminate(response)
  return build_pending(response) if response.status == 202

  ProductInvoiceIssued.new(resource: hydrate(Nfe::ProductInvoice, parse_json(response.body)))
end

#download_correction_letter_pdf(company_id:, invoice_id:) ⇒ Nfe::NfeFileResource

CC-e DANFE PDF file resource (URI, not bytes).

Parameters:

  • company_id: (String)
  • invoice_id: (String)

Returns:



179
180
181
# File 'lib/nfe/resources/product_invoices.rb', line 179

def download_correction_letter_pdf(company_id:, invoice_id:)
  file_resource(company_id: company_id, invoice_id: invoice_id, segment: "correctionletter/pdf")
end

#download_correction_letter_xml(company_id:, invoice_id:) ⇒ Nfe::NfeFileResource

CC-e XML file resource (URI, not bytes).

Parameters:

  • company_id: (String)
  • invoice_id: (String)

Returns:



186
187
188
# File 'lib/nfe/resources/product_invoices.rb', line 186

def download_correction_letter_xml(company_id:, invoice_id:)
  file_resource(company_id: company_id, invoice_id: invoice_id, segment: "correctionletter/xml")
end

#download_epec_xml(company_id:, invoice_id:) ⇒ Nfe::NfeFileResource

Contingency authorization (EPEC) XML file resource (URI, not bytes).

Parameters:

  • company_id: (String)
  • invoice_id: (String)

Returns:



155
156
157
# File 'lib/nfe/resources/product_invoices.rb', line 155

def download_epec_xml(company_id:, invoice_id:)
  file_resource(company_id: company_id, invoice_id: invoice_id, segment: "xml-epec")
end

#download_pdf(company_id:, invoice_id:, force: nil) ⇒ Nfe::NfeFileResource

DANFE PDF file resource (URI, not bytes). force regenerates the PDF.

Parameters:

  • company_id: (String)
  • invoice_id: (String)
  • force: (Boolean, nil) (defaults to: nil)

Returns:



133
134
135
136
# File 'lib/nfe/resources/product_invoices.rb', line 133

def download_pdf(company_id:, invoice_id:, force: nil)
  file_resource(company_id: company_id, invoice_id: invoice_id,
                segment: "pdf", query: { force: force }.compact)
end

#download_rejection_xml(company_id:, invoice_id:) ⇒ Nfe::NfeFileResource

Rejection XML file resource (URI, not bytes).

Parameters:

  • company_id: (String)
  • invoice_id: (String)

Returns:



148
149
150
# File 'lib/nfe/resources/product_invoices.rb', line 148

def download_rejection_xml(company_id:, invoice_id:)
  file_resource(company_id: company_id, invoice_id: invoice_id, segment: "xml-rejection")
end

#download_xml(company_id:, invoice_id:) ⇒ Nfe::NfeFileResource

Authorized NF-e XML file resource (URI, not bytes).

Parameters:

  • company_id: (String)
  • invoice_id: (String)

Returns:



141
142
143
# File 'lib/nfe/resources/product_invoices.rb', line 141

def download_xml(company_id:, invoice_id:)
  file_resource(company_id: company_id, invoice_id: invoice_id, segment: "xml")
end

#extract_invoice_id(location) ⇒ String?

Parameters:

  • location (String, nil)

Returns:

  • (String, nil)


282
283
284
285
286
287
# File 'lib/nfe/resources/product_invoices.rb', line 282

def extract_invoice_id(location)
  return nil if location.nil?

  match = location.match(%r{productinvoices/([a-z0-9-]+)}i)
  match ? match[1] : nil
end

#file_resource(company_id:, invoice_id:, segment:, query: {}) ⇒ Nfe::NfeFileResource?

Parameters:

  • company_id: (String)
  • invoice_id: (String)
  • segment: (String)
  • query: (Hash[untyped, untyped]) (defaults to: {})

Returns:



254
255
256
257
258
259
# File 'lib/nfe/resources/product_invoices.rb', line 254

def file_resource(company_id:, invoice_id:, segment:, query: {})
  cid = Nfe::IdValidator.company_id(company_id)
  iid = Nfe::IdValidator.invoice_id(invoice_id)
  response = get("#{base_path(cid)}/#{iid}/#{segment}", query: query)
  hydrate(Nfe::NfeFileResource, parse_json(response.body))
end

#json_body(data) ⇒ String

Parameters:

  • data (Object)

Returns:

  • (String)


221
222
223
# File 'lib/nfe/resources/product_invoices.rb', line 221

def json_body(data)
  JSON.generate(data)
end

#json_headersHash[String, String]

Returns:

  • (Hash[String, String])


217
218
219
# File 'lib/nfe/resources/product_invoices.rb', line 217

def json_headers
  { "Content-Type" => "application/json" }
end

#list(company_id:, environment:, **options) ⇒ Nfe::ListResponse

List product invoices (cursor-style). environment is REQUIRED.

Parameters:

  • company_id (String)
  • environment (String)

    "Production" or "Test" (required).

  • options (Hash)

    starting_after, ending_before, limit, q.

  • company_id: (String)
  • environment: (String)

Returns:

Raises:



81
82
83
84
85
86
87
# File 'lib/nfe/resources/product_invoices.rb', line 81

def list(company_id:, environment:, **options)
  cid = Nfe::IdValidator.company_id(company_id)
  require_environment(environment)
  query = cursor_query(options).merge(environment: environment)
  response = get(base_path(cid), query: query)
  hydrate_list(Nfe::ProductInvoice, parse_json(response.body), wrapper_key: "productInvoices")
end

#list_events(company_id:, invoice_id:, limit: nil, starting_after: nil) ⇒ Nfe::ListResponse

List fiscal events of an invoice (cursor-style).

Parameters:

  • company_id: (String)
  • invoice_id: (String)
  • limit: (Integer, nil) (defaults to: nil)
  • starting_after: (String, nil) (defaults to: nil)

Returns:



125
126
127
128
# File 'lib/nfe/resources/product_invoices.rb', line 125

def list_events(company_id:, invoice_id:, limit: nil, starting_after: nil)
  sub_list(company_id: company_id, invoice_id: invoice_id, segment: "events",
           limit: limit, starting_after: starting_after, wrapper_key: "events")
end

#list_items(company_id:, invoice_id:, limit: nil, starting_after: nil) ⇒ Nfe::ListResponse

List items of an invoice (cursor-style).

Parameters:

  • company_id: (String)
  • invoice_id: (String)
  • limit: (Integer, nil) (defaults to: nil)
  • starting_after: (String, nil) (defaults to: nil)

Returns:



117
118
119
120
# File 'lib/nfe/resources/product_invoices.rb', line 117

def list_items(company_id:, invoice_id:, limit: nil, starting_after: nil)
  sub_list(company_id: company_id, invoice_id: invoice_id, segment: "items",
           limit: limit, starting_after: starting_after, wrapper_key: "items")
end

#require_environment(environment) ⇒ void

This method returns an undefined value.

Parameters:

  • environment (Object)

Raises:



225
226
227
228
229
# File 'lib/nfe/resources/product_invoices.rb', line 225

def require_environment(environment)
  return unless environment.nil? || environment.to_s.strip.empty?

  raise Nfe::InvalidRequestError, "environment é obrigatório (Production ou Test)"
end

#retrieve(company_id:, invoice_id:) ⇒ Nfe::ProductInvoice

Retrieve a product invoice by id.

Parameters:

  • company_id (String)
  • invoice_id (String)
  • company_id: (String)
  • invoice_id: (String)

Returns:



94
95
96
97
98
99
# File 'lib/nfe/resources/product_invoices.rb', line 94

def retrieve(company_id:, invoice_id:)
  cid = Nfe::IdValidator.company_id(company_id)
  iid = Nfe::IdValidator.invoice_id(invoice_id)
  response = get("#{base_path(cid)}/#{iid}")
  hydrate(Nfe::ProductInvoice, parse_json(response.body))
end

#send_correction_letter(company_id:, invoice_id:, reason:) ⇒ Hash

Send a correction letter (CC-e). reason must be 15..1000 chars; the length is validated client-side before any HTTP request.

Parameters:

  • company_id (String)
  • invoice_id (String)
  • reason (String)
  • company_id: (String)
  • invoice_id: (String)
  • reason: (String)

Returns:

  • (Hash)

    the cancellation/CC-e resource payload.

Raises:



167
168
169
170
171
172
173
174
# File 'lib/nfe/resources/product_invoices.rb', line 167

def send_correction_letter(company_id:, invoice_id:, reason:)
  cid = Nfe::IdValidator.company_id(company_id)
  iid = Nfe::IdValidator.invoice_id(invoice_id)
  validate_correction_reason(reason)
  response = put("#{base_path(cid)}/#{iid}/correctionletter",
                 body: json_body({ reason: reason }), headers: json_headers)
  parse_json(response.body) || {}
end

#sub_list(company_id:, invoice_id:, segment:, limit:, starting_after:, wrapper_key:) ⇒ Nfe::ListResponse

Parameters:

  • company_id: (String)
  • invoice_id: (String)
  • segment: (String)
  • limit: (Object)
  • starting_after: (Object)
  • wrapper_key: (String)

Returns:



246
247
248
249
250
251
252
# File 'lib/nfe/resources/product_invoices.rb', line 246

def sub_list(company_id:, invoice_id:, segment:, limit:, starting_after:, wrapper_key:)
  cid = Nfe::IdValidator.company_id(company_id)
  iid = Nfe::IdValidator.invoice_id(invoice_id)
  query = { limit: limit, startingAfter: starting_after }.compact
  response = get("#{base_path(cid)}/#{iid}/#{segment}", query: query)
  hydrate_list(Nfe::ProductInvoice, parse_json(response.body), wrapper_key: wrapper_key)
end

#validate_correction_reason(reason) ⇒ void

This method returns an undefined value.

Parameters:

  • reason (Object)

Raises:



238
239
240
241
242
243
244
# File 'lib/nfe/resources/product_invoices.rb', line 238

def validate_correction_reason(reason)
  length = reason.to_s.length
  return if length.between?(15, 1000)

  raise Nfe::InvalidRequestError,
        "motivo da carta de correção deve conter entre 15 e 1000 caracteres"
end