zugpferd 0.1.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.
@@ -0,0 +1,48 @@
1
+ require "bigdecimal"
2
+
3
+ module Zugpferd
4
+ module Model
5
+ # Document-level allowance (BG-20) or charge (BG-21).
6
+ #
7
+ # @example Create a charge
8
+ # charge = AllowanceCharge.new(charge_indicator: true, amount: "50.00")
9
+ # charge.charge? # => true
10
+ #
11
+ # @example Create an allowance
12
+ # allowance = AllowanceCharge.new(charge_indicator: false, amount: "10.00")
13
+ # allowance.allowance? # => true
14
+ class AllowanceCharge
15
+ # @return [Boolean] +true+ for charge (BG-21), +false+ for allowance (BG-20)
16
+ # @return [String, nil] BT-97/BT-104 Allowance/charge reason
17
+ # @return [String, nil] BT-98/BT-105 Allowance/charge reason code
18
+ # @return [BigDecimal] BT-92/BT-99 Allowance/charge amount
19
+ # @return [BigDecimal, nil] BT-93/BT-100 Base amount
20
+ # @return [BigDecimal, nil] BT-94/BT-101 Percentage
21
+ # @return [String, nil] BT-95/BT-102 Tax category code
22
+ # @return [BigDecimal, nil] BT-96/BT-103 Tax rate
23
+ # @return [String, nil] Currency code
24
+ attr_accessor :charge_indicator, :reason, :reason_code,
25
+ :amount, :base_amount, :multiplier_factor,
26
+ :tax_category_code, :tax_percent, :currency_code
27
+
28
+ # @param charge_indicator [Boolean] +true+ for charge, +false+ for allowance
29
+ # @param amount [String, BigDecimal] Allowance/charge amount
30
+ # @param rest [Hash] additional attributes set via accessors
31
+ def initialize(charge_indicator:, amount:, **rest)
32
+ @charge_indicator = charge_indicator
33
+ @amount = BigDecimal(amount.to_s)
34
+ rest.each { |k, v| public_send(:"#{k}=", v) }
35
+ end
36
+
37
+ # @return [Boolean] whether this is a charge
38
+ def charge?
39
+ charge_indicator
40
+ end
41
+
42
+ # @return [Boolean] whether this is an allowance
43
+ def allowance?
44
+ !charge_indicator
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,16 @@
1
+ module Zugpferd
2
+ module Model
3
+ # Contact information (BG-6 / BG-9).
4
+ class Contact
5
+ # @return [String, nil] BT-41/BT-56 Contact name
6
+ # @return [String, nil] BT-42/BT-57 Telephone
7
+ # @return [String, nil] BT-43/BT-58 Email address
8
+ attr_accessor :name, :telephone, :email
9
+
10
+ # @param attrs [Hash] attributes set via accessors
11
+ def initialize(**attrs)
12
+ attrs.each { |k, v| public_send(:"#{k}=", v) }
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,51 @@
1
+ require "bigdecimal"
2
+
3
+ module Zugpferd
4
+ module Model
5
+ # The root invoice document (BG-0).
6
+ #
7
+ # @example
8
+ # invoice = Invoice.new(number: "INV-001", issue_date: Date.today)
9
+ # invoice.seller = TradeParty.new(name: "Seller GmbH")
10
+ class Invoice
11
+ # @return [String] BT-1 Invoice number
12
+ # @return [Date] BT-2 Issue date
13
+ # @return [Date, nil] BT-9 Payment due date
14
+ # @return [String] BT-3 Invoice type code (default: "380")
15
+ # @return [String] BT-5 Document currency code (default: "EUR")
16
+ # @return [String, nil] BT-10 Buyer reference
17
+ # @return [String, nil] BT-24 Specification identifier
18
+ # @return [String, nil] BT-23 Business process type
19
+ # @return [String, nil] BT-22 Invoice note
20
+ # @return [TradeParty, nil] BG-4 Seller party
21
+ # @return [TradeParty, nil] BG-7 Buyer party
22
+ # @return [Array<LineItem>] BG-25 Invoice lines
23
+ # @return [TaxBreakdown, nil] BG-23 VAT breakdown
24
+ # @return [MonetaryTotals, nil] BG-22 Document totals
25
+ # @return [PaymentInstructions, nil] BG-16 Payment information
26
+ # @return [Array<AllowanceCharge>] BG-20/BG-21 Document-level allowances and charges
27
+ attr_accessor :number, :issue_date, :due_date, :type_code,
28
+ :currency_code, :buyer_reference, :customization_id,
29
+ :profile_id, :note, :seller, :buyer, :line_items,
30
+ :tax_breakdown, :monetary_totals, :payment_instructions,
31
+ :allowance_charges
32
+
33
+ # @param number [String] BT-1 Invoice number
34
+ # @param issue_date [Date] BT-2 Issue date
35
+ # @param type_code [String] BT-3 Invoice type code
36
+ # @param currency_code [String] BT-5 Document currency code
37
+ # @param rest [Hash] additional attributes set via accessors
38
+ def initialize(number:, issue_date:, type_code: "380",
39
+ currency_code: "EUR", **rest)
40
+ @number = number
41
+ @issue_date = issue_date
42
+ @type_code = type_code
43
+ @currency_code = currency_code
44
+ @line_items = []
45
+ @allowance_charges = []
46
+ @tax_breakdown = nil
47
+ rest.each { |k, v| public_send(:"#{k}=", v) }
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,23 @@
1
+ module Zugpferd
2
+ module Model
3
+ # Item information (BG-31).
4
+ class Item
5
+ # @return [String] BT-153 Item name
6
+ # @return [String, nil] BT-154 Item description
7
+ # @return [String, nil] BT-155 Seller's item identifier
8
+ # @return [String, nil] BT-151 Invoiced item VAT category code
9
+ # @return [BigDecimal, nil] BT-152 Invoiced item VAT rate
10
+ # @return [Array] BT-158 Item classification codes
11
+ attr_accessor :name, :description, :sellers_identifier,
12
+ :tax_category, :tax_percent, :classification_codes
13
+
14
+ # @param name [String] BT-153 Item name
15
+ # @param rest [Hash] additional attributes set via accessors
16
+ def initialize(name:, **rest)
17
+ @name = name
18
+ @classification_codes = []
19
+ rest.each { |k, v| public_send(:"#{k}=", v) }
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,32 @@
1
+ require "bigdecimal"
2
+
3
+ module Zugpferd
4
+ module Model
5
+ # An invoice line (BG-25).
6
+ class LineItem
7
+ # @return [String] BT-126 Line identifier
8
+ # @return [BigDecimal] BT-129 Invoiced quantity
9
+ # @return [String] BT-130 Unit of measure code
10
+ # @return [BigDecimal] BT-131 Line extension amount
11
+ # @return [String, nil] BT-127 Invoice line note
12
+ # @return [Item, nil] BG-31 Item information
13
+ # @return [Price, nil] BG-29 Price details
14
+ attr_accessor :id, :invoiced_quantity, :unit_code,
15
+ :line_extension_amount, :note, :item, :price
16
+
17
+ # @param id [String] BT-126 Line identifier
18
+ # @param invoiced_quantity [String, BigDecimal] BT-129 Quantity
19
+ # @param unit_code [String] BT-130 Unit of measure code
20
+ # @param line_extension_amount [String, BigDecimal] BT-131 Line total
21
+ # @param rest [Hash] additional attributes set via accessors
22
+ def initialize(id:, invoiced_quantity:, unit_code:,
23
+ line_extension_amount:, **rest)
24
+ @id = id
25
+ @invoiced_quantity = BigDecimal(invoiced_quantity.to_s)
26
+ @unit_code = unit_code
27
+ @line_extension_amount = BigDecimal(line_extension_amount.to_s)
28
+ rest.each { |k, v| public_send(:"#{k}=", v) }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,35 @@
1
+ require "bigdecimal"
2
+
3
+ module Zugpferd
4
+ module Model
5
+ # Document level monetary totals (BG-22).
6
+ class MonetaryTotals
7
+ # @return [BigDecimal] BT-106 Sum of invoice line net amounts
8
+ # @return [BigDecimal] BT-109 Invoice total without VAT
9
+ # @return [BigDecimal] BT-112 Invoice total with VAT
10
+ # @return [BigDecimal] BT-115 Amount due for payment
11
+ # @return [BigDecimal, nil] BT-113 Paid amount
12
+ # @return [BigDecimal, nil] BT-114 Rounding amount
13
+ # @return [BigDecimal, nil] BT-107 Sum of allowances on document level
14
+ # @return [BigDecimal, nil] BT-108 Sum of charges on document level
15
+ attr_accessor :line_extension_amount, :tax_exclusive_amount,
16
+ :tax_inclusive_amount, :payable_amount,
17
+ :prepaid_amount, :payable_rounding_amount,
18
+ :allowance_total_amount, :charge_total_amount
19
+
20
+ # @param line_extension_amount [String, BigDecimal] BT-106
21
+ # @param tax_exclusive_amount [String, BigDecimal] BT-109
22
+ # @param tax_inclusive_amount [String, BigDecimal] BT-112
23
+ # @param payable_amount [String, BigDecimal] BT-115
24
+ # @param rest [Hash] additional attributes set via accessors
25
+ def initialize(line_extension_amount:, tax_exclusive_amount:,
26
+ tax_inclusive_amount:, payable_amount:, **rest)
27
+ @line_extension_amount = BigDecimal(line_extension_amount.to_s)
28
+ @tax_exclusive_amount = BigDecimal(tax_exclusive_amount.to_s)
29
+ @tax_inclusive_amount = BigDecimal(tax_inclusive_amount.to_s)
30
+ @payable_amount = BigDecimal(payable_amount.to_s)
31
+ rest.each { |k, v| public_send(:"#{k}=", v) }
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,29 @@
1
+ module Zugpferd
2
+ module Model
3
+ # Payment instructions (BG-16) including payment card (BG-18)
4
+ # and direct debit (BG-19) information.
5
+ class PaymentInstructions
6
+ # @return [String] BT-81 Payment means type code
7
+ # @return [String, nil] BT-83 Remittance information
8
+ # @return [String, nil] BT-84 Payment account identifier (IBAN)
9
+ # @return [String, nil] BT-82 Payment terms note
10
+ # @return [String, nil] BT-87 Payment card primary account number
11
+ # @return [String, nil] BT-88 Payment card holder name
12
+ # @return [String, nil] Card network ID (UBL-only, required in CardAccount)
13
+ # @return [String, nil] BT-89 Mandate reference identifier
14
+ # @return [String, nil] BT-90 Bank assigned creditor identifier
15
+ # @return [String, nil] BT-91 Debited account identifier
16
+ attr_accessor :payment_means_code, :payment_id, :account_id, :note,
17
+ :card_account_id, :card_holder_name, :card_network_id,
18
+ :mandate_reference, :creditor_reference_id,
19
+ :debited_account_id
20
+
21
+ # @param payment_means_code [String] BT-81 Payment means type code
22
+ # @param rest [Hash] additional attributes set via accessors
23
+ def initialize(payment_means_code:, **rest)
24
+ @payment_means_code = payment_means_code
25
+ rest.each { |k, v| public_send(:"#{k}=", v) }
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,19 @@
1
+ module Zugpferd
2
+ module Model
3
+ # Postal address (BG-5 / BG-8).
4
+ class PostalAddress
5
+ # @return [String, nil] BT-35/BT-50 Street name
6
+ # @return [String, nil] BT-37/BT-52 City name
7
+ # @return [String, nil] BT-38/BT-53 Postal zone
8
+ # @return [String] BT-40/BT-55 Country code
9
+ attr_accessor :street_name, :city_name, :postal_zone, :country_code
10
+
11
+ # @param country_code [String] BT-40/BT-55 Country code (ISO 3166-1 alpha-2)
12
+ # @param rest [Hash] additional attributes set via accessors
13
+ def initialize(country_code:, **rest)
14
+ @country_code = country_code
15
+ rest.each { |k, v| public_send(:"#{k}=", v) }
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ require "bigdecimal"
2
+
3
+ module Zugpferd
4
+ module Model
5
+ # Price details (BG-29).
6
+ class Price
7
+ # @return [BigDecimal] BT-146 Item net price
8
+ # @return [BigDecimal, nil] BT-149 Item price base quantity
9
+ attr_accessor :amount, :base_quantity
10
+
11
+ # @param amount [String, BigDecimal] BT-146 Item net price
12
+ # @param rest [Hash] additional attributes set via accessors
13
+ def initialize(amount:, **rest)
14
+ @amount = BigDecimal(amount.to_s)
15
+ rest.each { |k, v| public_send(:"#{k}=", v) }
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ require "bigdecimal"
2
+
3
+ module Zugpferd
4
+ module Model
5
+ # VAT breakdown (BG-23).
6
+ class TaxBreakdown
7
+ # @return [BigDecimal] BT-110 Invoice total VAT amount
8
+ # @return [String] Tax currency code
9
+ # @return [Array<TaxSubtotal>] Individual VAT category breakdowns
10
+ attr_accessor :tax_amount, :currency_code, :subtotals
11
+
12
+ # @param tax_amount [String, BigDecimal] BT-110 Total VAT amount
13
+ # @param currency_code [String] Tax currency code
14
+ # @param rest [Hash] additional attributes set via accessors
15
+ def initialize(tax_amount:, currency_code:, **rest)
16
+ @tax_amount = BigDecimal(tax_amount.to_s)
17
+ @currency_code = currency_code
18
+ @subtotals = []
19
+ rest.each { |k, v| public_send(:"#{k}=", v) }
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,32 @@
1
+ require "bigdecimal"
2
+
3
+ module Zugpferd
4
+ module Model
5
+ # A single VAT category breakdown within {TaxBreakdown}.
6
+ class TaxSubtotal
7
+ # @return [BigDecimal] BT-116 VAT category taxable amount
8
+ # @return [BigDecimal] BT-117 VAT category tax amount
9
+ # @return [String] BT-118 VAT category code
10
+ # @return [BigDecimal, nil] BT-119 VAT category rate
11
+ # @return [String] Currency code
12
+ # @return [String, nil] BT-120 VAT exemption reason text
13
+ # @return [String, nil] BT-121 VAT exemption reason code
14
+ attr_accessor :taxable_amount, :tax_amount, :category_code,
15
+ :percent, :currency_code,
16
+ :exemption_reason, :exemption_reason_code
17
+
18
+ # @param taxable_amount [String, BigDecimal] BT-116
19
+ # @param tax_amount [String, BigDecimal] BT-117
20
+ # @param category_code [String] BT-118
21
+ # @param currency_code [String] Currency code
22
+ # @param rest [Hash] additional attributes set via accessors
23
+ def initialize(taxable_amount:, tax_amount:, category_code:, currency_code:, **rest)
24
+ @taxable_amount = BigDecimal(taxable_amount.to_s)
25
+ @tax_amount = BigDecimal(tax_amount.to_s)
26
+ @category_code = category_code
27
+ @currency_code = currency_code
28
+ rest.each { |k, v| public_send(:"#{k}=", v) }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,32 @@
1
+ module Zugpferd
2
+ module Model
3
+ # A seller (BG-4) or buyer (BG-7) party.
4
+ #
5
+ # @example
6
+ # party = TradeParty.new(name: "Seller GmbH")
7
+ # party.vat_identifier = "DE123456789"
8
+ class TradeParty
9
+ # @return [String] BT-27/BT-44 Legal name
10
+ # @return [String, nil] BT-28/BT-45 Trading name
11
+ # @return [String, nil] BT-29/BT-46 Party identifier
12
+ # @return [String, nil] BT-30/BT-47 Legal registration identifier
13
+ # @return [String, nil] BT-33 Company legal form
14
+ # @return [String, nil] BT-31/BT-48 VAT identifier
15
+ # @return [String, nil] BT-34/BT-49 Electronic address
16
+ # @return [String, nil] BT-34-1/BT-49-1 Electronic address scheme
17
+ # @return [PostalAddress, nil] BG-5/BG-8 Postal address
18
+ # @return [Contact, nil] BG-6/BG-9 Contact information
19
+ attr_accessor :name, :trading_name, :identifier,
20
+ :legal_registration_id, :legal_form, :vat_identifier,
21
+ :electronic_address, :electronic_address_scheme,
22
+ :postal_address, :contact
23
+
24
+ # @param name [String] BT-27/BT-44 Legal name
25
+ # @param rest [Hash] additional attributes set via accessors
26
+ def initialize(name:, **rest)
27
+ @name = name
28
+ rest.each { |k, v| public_send(:"#{k}=", v) }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,135 @@
1
+ module Zugpferd
2
+ module UBL
3
+ module Mapping
4
+ NS = {
5
+ "ubl" => "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2",
6
+ "cac" => "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2",
7
+ "cbc" => "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2",
8
+ }.freeze
9
+
10
+ # Invoice (BG-0)
11
+ INVOICE = {
12
+ number: "cbc:ID",
13
+ issue_date: "cbc:IssueDate",
14
+ due_date: "cbc:DueDate",
15
+ type_code: "cbc:InvoiceTypeCode",
16
+ currency_code: "cbc:DocumentCurrencyCode",
17
+ buyer_reference: "cbc:BuyerReference",
18
+ customization_id: "cbc:CustomizationID",
19
+ profile_id: "cbc:ProfileID",
20
+ note: "cbc:Note",
21
+ }.freeze
22
+
23
+ # Seller (BG-4)
24
+ SELLER = "cac:AccountingSupplierParty/cac:Party"
25
+ # Buyer (BG-7)
26
+ BUYER = "cac:AccountingCustomerParty/cac:Party"
27
+
28
+ # TradeParty fields
29
+ PARTY = {
30
+ name: "cac:PartyLegalEntity/cbc:RegistrationName",
31
+ trading_name: "cac:PartyName/cbc:Name",
32
+ identifier: "cac:PartyIdentification/cbc:ID",
33
+ legal_registration_id: "cac:PartyLegalEntity/cbc:CompanyID",
34
+ legal_form: "cac:PartyLegalEntity/cbc:CompanyLegalForm",
35
+ vat_identifier: "cac:PartyTaxScheme[cac:TaxScheme/cbc:ID='VAT']/cbc:CompanyID",
36
+ electronic_address: "cbc:EndpointID",
37
+ }.freeze
38
+
39
+ # PostalAddress (BG-5 / BG-8)
40
+ POSTAL_ADDRESS = "cac:PostalAddress"
41
+ ADDRESS = {
42
+ street_name: "cbc:StreetName",
43
+ city_name: "cbc:CityName",
44
+ postal_zone: "cbc:PostalZone",
45
+ country_code: "cac:Country/cbc:IdentificationCode",
46
+ }.freeze
47
+
48
+ # Contact (BG-6 / BG-9)
49
+ CONTACT = "cac:Contact"
50
+ CONTACT_FIELDS = {
51
+ name: "cbc:Name",
52
+ telephone: "cbc:Telephone",
53
+ email: "cbc:ElectronicMail",
54
+ }.freeze
55
+
56
+ # PaymentMeans (BG-16)
57
+ PAYMENT_MEANS = "cac:PaymentMeans"
58
+ PAYMENT = {
59
+ payment_means_code: "cbc:PaymentMeansCode",
60
+ payment_id: "cbc:PaymentID",
61
+ account_id: "cac:PayeeFinancialAccount/cbc:ID",
62
+ card_account_id: "cac:CardAccount/cbc:PrimaryAccountNumberID",
63
+ card_network_id: "cac:CardAccount/cbc:NetworkID",
64
+ card_holder_name: "cac:CardAccount/cbc:HolderName",
65
+ mandate_reference: "cac:PaymentMandate/cbc:ID",
66
+ debited_account_id: "cac:PaymentMandate/cac:PayerFinancialAccount/cbc:ID",
67
+ }.freeze
68
+ PAYMENT_TERMS_NOTE = "cac:PaymentTerms/cbc:Note"
69
+
70
+ # TaxTotal (BG-23)
71
+ TAX_TOTAL = "cac:TaxTotal"
72
+ TAX_SUBTOTAL = "cac:TaxSubtotal"
73
+ TAX = {
74
+ taxable_amount: "cbc:TaxableAmount",
75
+ tax_amount: "cbc:TaxAmount",
76
+ category_code: "cac:TaxCategory/cbc:ID",
77
+ percent: "cac:TaxCategory/cbc:Percent",
78
+ exemption_reason: "cac:TaxCategory/cbc:TaxExemptionReason",
79
+ exemption_reason_code: "cac:TaxCategory/cbc:TaxExemptionReasonCode",
80
+ }.freeze
81
+
82
+ # LegalMonetaryTotal (BG-22)
83
+ MONETARY_TOTAL = "cac:LegalMonetaryTotal"
84
+ TOTALS = {
85
+ line_extension_amount: "cbc:LineExtensionAmount",
86
+ tax_exclusive_amount: "cbc:TaxExclusiveAmount",
87
+ tax_inclusive_amount: "cbc:TaxInclusiveAmount",
88
+ prepaid_amount: "cbc:PrepaidAmount",
89
+ payable_rounding_amount: "cbc:PayableRoundingAmount",
90
+ allowance_total_amount: "cbc:AllowanceTotalAmount",
91
+ charge_total_amount: "cbc:ChargeTotalAmount",
92
+ payable_amount: "cbc:PayableAmount",
93
+ }.freeze
94
+
95
+ # AllowanceCharge (BG-20 / BG-21)
96
+ ALLOWANCE_CHARGE = "cac:AllowanceCharge"
97
+ ALLOWANCE_CHARGE_FIELDS = {
98
+ charge_indicator: "cbc:ChargeIndicator",
99
+ reason: "cbc:AllowanceChargeReason",
100
+ reason_code: "cbc:AllowanceChargeReasonCode",
101
+ amount: "cbc:Amount",
102
+ base_amount: "cbc:BaseAmount",
103
+ multiplier_factor: "cbc:MultiplierFactorNumeric",
104
+ tax_category_code: "cac:TaxCategory/cbc:ID",
105
+ tax_percent: "cac:TaxCategory/cbc:Percent",
106
+ }.freeze
107
+
108
+ # InvoiceLine (BG-25)
109
+ INVOICE_LINE = "cac:InvoiceLine"
110
+ LINE = {
111
+ id: "cbc:ID",
112
+ invoiced_quantity: "cbc:InvoicedQuantity",
113
+ unit_code: "cbc:InvoicedQuantity/@unitCode",
114
+ line_extension_amount: "cbc:LineExtensionAmount",
115
+ note: "cbc:Note",
116
+ }.freeze
117
+
118
+ # Item (BG-31)
119
+ ITEM = "cac:Item"
120
+ ITEM_FIELDS = {
121
+ name: "cbc:Name",
122
+ description: "cbc:Description",
123
+ sellers_identifier: "cac:SellersItemIdentification/cbc:ID",
124
+ tax_category: "cac:ClassifiedTaxCategory/cbc:ID",
125
+ tax_percent: "cac:ClassifiedTaxCategory/cbc:Percent",
126
+ }.freeze
127
+
128
+ # Price (BG-29)
129
+ PRICE = "cac:Price"
130
+ PRICE_FIELDS = {
131
+ amount: "cbc:PriceAmount",
132
+ }.freeze
133
+ end
134
+ end
135
+ end