Module: Cpro::Facturx

Defined in:
lib/cpro/facturx.rb

Overview

Inspection minimale d'un PDF/A-3 Factur-X, sans dépendance à un analyseur PDF : de quoi attraper le mauvais fichier avant de le déposer, pas de quoi valider la facture.

Constant Summary collapse

ENTETE_PDF =
"%PDF"
RACINE_CII =
"CrossIndustryInvoice"

Class Method Summary collapse

Class Method Details

.inflate(flux) ⇒ Object



47
48
49
50
51
# File 'lib/cpro/facturx.rb', line 47

def inflate(flux)
  Zlib::Inflate.inflate(flux)
rescue Zlib::Error
  nil
end

.montant_total(contenu) ⇒ Object

Montant TTC déclaré par le XML embarqué (BT-112), à comparer par l'appelant à ce qu'il attend. La gem ne lit pas le PDF lisible et ne peut donc rien confronter elle-même.



39
40
41
42
43
44
45
# File 'lib/cpro/facturx.rb', line 39

def montant_total(contenu)
  document = xml(contenu) or return nil
  valeur = document[%r{<ram:GrandTotalAmount[^>]*>([^<]+)</ram:GrandTotalAmount>}, 1]
  valeur && BigDecimal(valeur.strip)
rescue ArgumentError
  nil
end

.pdf?(contenu) ⇒ Boolean

Returns:

  • (Boolean)


15
16
17
# File 'lib/cpro/facturx.rb', line 15

def pdf?(contenu)
  contenu.to_s.byteslice(0, ENTETE_PDF.bytesize) == ENTETE_PDF
end

.xml(contenu) ⇒ Object

Les pièces jointes d'un PDF/A-3 sont presque toujours compressées en FlateDecode : on décompresse les flux jusqu'à retrouver la racine CII. Un XML stocké sans compression échapperait à cette recherche, d'où xml_absent? qui ne conclut que sur le cas certain.



22
23
24
25
26
27
28
29
30
31
# File 'lib/cpro/facturx.rb', line 22

def xml(contenu)
  octets = contenu.to_s.b
  octets.enum_for(:scan, /stream\r?\n/).each do
    debut = Regexp.last_match.end(0)
    fin = octets.index("endstream", debut) || next
    decompresse = inflate(octets[debut...fin]) || next
    return decompresse if decompresse.include?(RACINE_CII)
  end
  nil
end

.xml?(contenu) ⇒ Boolean

Returns:

  • (Boolean)


33
34
35
# File 'lib/cpro/facturx.rb', line 33

def xml?(contenu)
  !xml(contenu).nil?
end