Class: UnovaFacturX::XmlGenerator

Inherits:
Object
  • Object
show all
Defined in:
lib/unova_factur_x/xml_generator.rb

Instance Method Summary collapse

Constructor Details

#initialize(document, type: :invoice, devise: "EUR", validate: true) ⇒ Nokogiri::XML::Document

Générateur de fichiers XML de type CrossIndustryInvoice pour facture électronique

Cette classe permet de générer dynamiquement un fichier XML structuré selon le standard CII (EN16931), à partir d’un objet ruby. Elle supporte les factures simples (type 380) ainsi que les avoirs (type 381). Les montants fournis doivent être arithmétiquement cohérents, aucune correction automatique n’est effectuée. Aussi, pour plus de simplicité, tous les attributs de la facture/du crédit sont attendus en String.

Examples:

# Générer un XML de facture standard
xml = XmlGenerator.new(invoice).call

# Générer un XML pour un avoir
xml = XmlGenerator.new(credit, type: :credit).call

# Configurer la monnaie utilisée sur la facture
xml = XmlGenerator.new(invoice, devise: "USD") # 'EUR' par défaut

# Exemple de hash pour une facture (Même chose pour un avoir /!\ Ne pas mettre les valeurs de l'avoir en négatif /!\) :
document = {
  id: "Numéro unique de facture (BT-1) [OBLIGATOIRE]",
  issue_date: "Date d'émission format YYYYMMDD (BT-2) [OBLIGATOIRE]",

  seller: {
    name: "Nom légal du vendeur (BT-27) [OBLIGATOIRE]",
    legal_id: "Identifiant légal (SIREN/SIRET) (BT-30) [OPTIONNEL]",
    vat_number: "Numéro TVA avec préfixe pays acheteur (ex: FR123...) (BT-31) [OPTIONNEL]",
    address: {
      line1: "Rue (BT-35) [OBLIGATOIRE]",
      line2: "Complément adresse [OPTIONNEL]",
      postcode: "Code postal (BT-38) [OBLIGATOIRE]",
      city: "Ville (BT-37) [OBLIGATOIRE]",
      country: "Code pays ISO 3166-1 alpha-2 (BT-40) [OBLIGATOIRE]",
    }
  },

  # [BLOC OBLIGATOIRE]
  buyer: {
    id: "Identifiant interne client (BT-46) [OPTIONNEL]",
    name: "Nom légal du client (BT-44) [OBLIGATOIRE]",
    vat_number: "Numéro TVA avec préfixe pays acheteur (ex: FR123...) (BT-48) [OPTIONNEL]",
    contact: { # [OPTIONNEL]
      name: "Nom du contact client (BT-56) [OPTIONNEL]",
    },
    address: {
      line1: "Rue (BT-50) [OBLIGATOIRE]",
      line2: "Complément adresse [OPTIONNEL]",
      postcode: "Code postal (BT-53) [OBLIGATOIRE]",
      city: "Ville (BT-52) [OBLIGATOIRE]",
      country: "Code pays ISO 3166-1 alpha-2 (BT-55) [OBLIGATOIRE]",
    }
  },

  # [BLOC OPTIONNEL]
  delivery: {
    gln: "Identifiant GLN (schemeID 0088) (BT-71) [OPTIONNEL]",
    gln_scheme: "0088: GLN (GS1), 0002: SIRENE (France), 9906: SIRET, 9915: TVA intracom FR, 0060:	DUNS [OPTIONNEL | OBLIGATOIRE SI GLN]",
    date: "Date réelle de livraison format YYYYMMDD (BT-72) [OPTIONNEL]",
    address: {
      line1: "Rue livraison (BT-75) [OPTIONNEL]",
      line2: "Complément adresse livraison [OPTIONNEL]",
      postcode: "Code postal livraison (BT-75) [OPTIONNEL]",
      city: "Ville livraison (BT-74) [OPTIONNEL]",
      country: "Code pays ISO 3166-1 alpha-2 (BT-76) [OPTIONNEL]",
    }
  },

  # [BLOC OBLIGATOIRE] (minimum 1 item)
  items: [
    {
      line_id: "Numéro de ligne (BT-126) [OBLIGATOIRE]",
      seller_assigned_id: "Identifiant interne produit (BT-155) [OPTIONNEL]",
      name: "Désignation produit/service (BT-153) [OBLIGATOIRE]",
      quantity: "Quantité (BT-129) [OBLIGATOIRE]",
      unit_code: "Code unité UN/ECE Rec20 (ex: H87, C62, DAY) (BT-130) [OBLIGATOIRE]",
      price_ht: "Prix unitaire net HT (BT-146) [OBLIGATOIRE]",
      vat_rate: "Taux TVA (BT-152) [OBLIGATOIRE]",
      vat_category: "Catégorie TVA (S, Z, E, AE...) (BT-151) [OBLIGATOIRE]",
      discount: { # [OPTIONNEL]
        total_amount: "Montant de la remise applicable à la ligne de facture (BT-136) [OPTIONNEL sauf si discount]",
        percentage: "Pourcentage de remise applicable à la ligne de facture (BT-138) [OPTIONNEL sauf si discount]",
        # reason OU reason_code [OBLIGATOIRE] si bloc présent
        reason: "Motif de la remise applicable à la ligne de facture (BT-139) [OPTIONNEL sauf si discount]",
        reason_code: "Code de motif de la remise applicable à la ligne de facture (BT-140) [OPTIONNEL sauf si discount]"
      }
      line_total: "Montant net de la ligne HT = Quantité × Prix unitaire net (BT-131)"
    }
  ],

  # [BLOC OBLIGATOIRE]
  payment_means: {
    type_code: "Code UNCL 4461 (30 = virement) (BT-81) [OBLIGATOIRE]",
    iban: "IBAN bénéficiaire (BT-84) [OBLIGATOIRE si virement]",
  },

  # [BLOC OBLIGATOIRE]
  vat_breakdown: [
    {
      vat_category: "Catégorie TVA (BT-118) [OBLIGATOIRE]",
      vat_rate: "Taux TVA % (BT-119) [OBLIGATOIRE]",
      taxable_amount: "Base HT pour ce taux (BT-116) [OBLIGATOIRE]",
      tax_amount: "Montant TVA pour ce taux (BT-117) [OBLIGATOIRE]",
      # exemption_reason OU exemption_reason_code [OBLIGATOIRE] si vat_category = "E" (Exempt)
      exemption_reason: "Motif d'exonération de la TVA (BT-120)",
      exemption_reason_code: "Code de motif d'exonération de la TVA (BT-121)"
    }
  ],

  # [BLOC OPTIONNEL]
  discount: [ # Ce bloc est un tableau avec un item par taux de TVA d'item. Il doit donc avoir la même longueur que le bloc vat_breakdown
    {
      vat_category: "Catégorie TVA (BT-118) [OBLIGATOIRE si le bloc est présent]",
      vat_rate: "Taux TVA % (BT-119) [OBLIGATOIRE si le bloc est présent]",
      total_amount: "Montant total de la remise pour le taux de TVA [OBLIGATOIRE si percentage présent]",
      percentage: "% de remise au niveau du document si la remise est en % (BT-94) [OPTIONNEL]",
      # reason OU reason_code [OBLIGATOIRE] si bloc présent
      reason: "Motif de la remise au niveau du document (BT-97)",
      reason_code: "Code de motif de la remise au niveau du document (BT-98)",
    }
  ],

  # [BLOC OBLIGATOIRE]
  totals: {
    line_total_ht: "Total HT lignes (BT-106) [OBLIGATOIRE]",
    total_discount: "Somme des remises au niveau du document (BT-107) [OBLIGATOIRE si bloc discount présent]",
    tax_basis_total_ht: "Total bases taxables (BT-109) [OBLIGATOIRE]",
    tax_total: "Total TVA (BT-110) [OBLIGATOIRE]",
    grand_total_ttc: "Total TTC (BT-112) [OBLIGATOIRE]",
    amount_due: "Montant à payer (BT-115) [OPTIONNEL]",
    # due_date OU description [OBLIGATOIRE] si amount_due est défini et positif
    due_date: "Date due du paiement format YYYYMMDD (BT-9)",
    description: "Termes du paiement (BT-20)"
  }
}

Parameters:

  • document (Hash)

    Un hash représentant une facture ou un crédit.

  • type (Symbol) (defaults to: :invoice)

    Type de document : ‘:invoice` pour une facture normale, `:credit` pour un avoir. (`:invoice` par défaut)

  • devise (String) (defaults to: "EUR")

    Code ISO 4217



141
142
143
144
145
146
# File 'lib/unova_factur_x/xml_generator.rb', line 141

def initialize(document, type: :invoice, devise: "EUR", validate: true)
  @document = document
  @type = type
  @devise = devise
  @validate = validate
end

Instance Method Details

#callObject



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/unova_factur_x/xml_generator.rb', line 148

def call
  builder = ::Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
    xml['rsm'].CrossIndustryInvoice(
      'xmlns:rsm' => 'urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100',
      'xmlns:udt' => 'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100',
      'xmlns:qdt' => 'urn:un:unece:uncefact:data:standard:QualifiedDataType:100',
      'xmlns:ram' => 'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100'
    ) do
      build_exchanged_document_context(xml)
      build_exchanged_document(xml)
      build_supply_chain_trade_transaction(xml)
    end
  end

  validate_with_xslt!(builder.doc) if @validate

  # Retourne le XML
  builder.doc
end