Skip to content

Commit 3fadfdd

Browse files
committed
feat: Add billingReferences mapping and enhance invoice mappers/validators
- Integrated billingReferences mapping into InvoiceMapper via mapBillingReferences(). - Fixed allowanceCharges mapping to correctly iterate over taxCategories. - Added new mappers: AdditionalDocumentMapper, CustomerMapper, InvoiceLineMapper, ItemMapper, PaymentMeansMapper, PriceMapper, SupplierMapper. - Introduced InvoiceAmountValidator for validating monetary totals and invoice line calculations. - Updated unit and integration tests for invoice mappers and validators. - Added GitHub Actions workflow for invoice validation using the Fatoora SDK. - Minor improvements and bug fixes across Delivery, LegalMonetaryTotal, and Invoice classes.
1 parent bf0a71e commit 3fadfdd

37 files changed

+3413
-493
lines changed

.github/workflows/validate_fatoora.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
- name: Generator Invoice
2323
run: |
2424
cd examples
25-
php GeneratorInvoice.php
25+
php GeneratorStandard_Invoice.php
2626
php SigningInvoices.php
2727
2828
- name: ☕ Install OpenJDK 11.0.26
@@ -86,6 +86,6 @@ jobs:
8686
cd ../Apps
8787
chmod +x fatoora
8888
# ✅ Validate Invoice using Fatoora
89-
fatoora -validate -invoice ../../../examples/output/unsigned_invoice.xml
90-
fatoora -validate -invoice ../../../examples/output/signed_invoice.xml
89+
fatoora -validate -invoice ../../../examples/output/GeneratorSimplified_Invoice_Signed.xml
90+
fatoora -validate -invoice ../../../examples/output/GeneratorStandard_Invoice_Signed.xml
9191
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
require __DIR__ . '/../../vendor/autoload.php';
3+
4+
use Saleh7\Zatca\CertificateBuilder;
5+
use Saleh7\Zatca\Exceptions\CertificateBuilderException;
6+
7+
8+
// Usage example with random data:
9+
try {
10+
(new CertificateBuilder())
11+
// The Organization Identifier must be 15 digits, starting andending with 3
12+
->setOrganizationIdentifier('399999999900003')
13+
// string $solutionName .. The solution provider name
14+
// string $model .. The model of the unit the stamp is being generated for
15+
// string $serialNumber .. # If you have multiple devices each should have a unique serial number
16+
->setSerialNumber('POS', 'A1', '98765')
17+
->setCommonName('مؤسسة وقت الاستجابة') // The common name to be used in the certificate
18+
->setCountryName('SA') // The Country name must be Two chars only
19+
->setOrganizationName('مؤسسة وقت الاستجابة') // The name of your organization
20+
->setOrganizationalUnitName('IT Department') // Organizational unit
21+
->setAddress('1234 Main St, Riyadh') // Address
22+
// # Four digits, each digit acting as a bool. The order is as follows: Standard Invoice, Simplified, future use, future use
23+
->setInvoiceType(1000)
24+
->setProduction(false) // true = Production | false = Testing
25+
->setBusinessCategory('Technology') // Your business category like food, real estate, etc
26+
// Generate and save the certificate and private key
27+
->generateAndSave('output/certificate.csr', 'output/private.pem');
28+
29+
echo "Certificate and private key saved.\n";
30+
} catch (CertificateBuilderException $e) {
31+
echo "Error: " . $e->getMessage() . "\n";
32+
exit(1);
33+
}

examples/RequestComplianceCertificate.php examples/Certificates/RequestComplianceCertificate.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?php
2-
require __DIR__ . '/../vendor/autoload.php';
2+
require __DIR__ . '/../../vendor/autoload.php';
33

44
use Saleh7\Zatca\ZatcaAPI;
55
use Saleh7\Zatca\Exceptions\ZatcaApiException;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"certificate": "MIIB6zCCAZGgAwIBAgIGAZVJOn6LMAoGCCqGSM49BAMCMBUxEzARBgNVBAMMCmVJbnZvaWNpbmcwHhcNMjUwMjI3MjEwNTIwWhcNMzAwMjI3MjEwMDAwWjA\/MQswCQYDVQQDDAIgIDELMAkGA1UECgwCICAxFjAUBgNVBAsMDUlUIERlcGFydG1lbnQxCzAJBgNVBAYTAlNBMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEQIflB0ZIktA5UXwfXYHunm9mwsLchDgRuuXbGgGNyfTpM+hlr791lIpZbHXrqASxUjXj9qAoqbLFrUsiRtrexqOBpTCBojAMBgNVHRMBAf8EAjAAMIGRBgNVHREEgYkwgYakgYMwgYAxGzAZBgNVBAQMEjEtUE9TfDItQTF8My05ODc2NTEfMB0GCgmSJomT8ixkAQEMDzM5OTk5OTk5OTkwMDAwMzENMAsGA1UEDAwEMTAwMDEcMBoGA1UEGgwTMTIzNCBNYWluIFN0IFJpeWFkaDETMBEGA1UEDwwKVGVjaG5vbG9neTAKBggqhkjOPQQDAgNIADBFAiEA+jX7SpRecySqDwLtP2CNcww3J8gifRWMRQXq9PwxqKoCIGYC8teTZ8B8sIiLDfSfJC3AC+h7RHZWYSmIBP7k7iBK",
3+
"secret": "prNqoTQfYN5cifxl2zZ06zyFpAzHhzLdY2JviFGj0EE=",
4+
"requestId": "1234567890123"
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-----BEGIN CERTIFICATE REQUEST-----
2+
MIIBxzCCAW0CAQAwPzELMAkGA1UEAwwCICAxCzAJBgNVBAoMAiAgMRYwFAYDVQQL
3+
DA1JVCBEZXBhcnRtZW50MQswCQYDVQQGEwJTQTBWMBAGByqGSM49AgEGBSuBBAAK
4+
A0IABECH5QdGSJLQOVF8H12B7p5vZsLC3IQ4Ebrl2xoBjcn06TPoZa+/dZSKWWx1
5+
66gEsVI14/agKKmyxa1LIkba3saggc4wgcsGCSqGSIb3DQEJDjGBvTCBujAkBgkr
6+
BgEEAYI3FAIEFxMVUFJFWkFUQ0EtQ29kZS1TaWduaW5nMIGRBgNVHREEgYkwgYak
7+
gYMwgYAxGzAZBgNVBAQMEjEtUE9TfDItQTF8My05ODc2NTEfMB0GCgmSJomT8ixk
8+
AQEMDzM5OTk5OTk5OTkwMDAwMzENMAsGA1UEDAwEMTAwMDEcMBoGA1UEGgwTMTIz
9+
NCBNYWluIFN0IFJpeWFkaDETMBEGA1UEDwwKVGVjaG5vbG9neTAKBggqhkjOPQQD
10+
AgNIADBFAiBzXb/5Br4UuKJmcsVSr2yPuuOTQybq3xLDKi6AsG45fAIhAKdX5B2E
11+
rcZTjPbJYos+E4Rn0yMwbAIBOCBtdaHBCe06
12+
-----END CERTIFICATE REQUEST-----
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgKHTICNJRTkepNLfbQv2/
3+
a99o2Fs3DnFQB+jMqtDMlB2hRANCAARAh+UHRkiS0DlRfB9dge6eb2bCwtyEOBG6
4+
5dsaAY3J9Okz6GWvv3WUillsdeuoBLFSNeP2oCipssWtSyJG2t7G
5+
-----END PRIVATE KEY-----

examples/GeneratorCertificate.php

-31
This file was deleted.
+241
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
<?php
2+
require __DIR__ . '/../vendor/autoload.php';
3+
4+
use Saleh7\Zatca\{
5+
SignatureInformation,UBLDocumentSignatures,ExtensionContent,UBLExtension,UBLExtensions,Signature,InvoiceType,AdditionalDocumentReference,
6+
TaxScheme,PartyTaxScheme,Address,LegalEntity,Delivery,Party,PaymentMeans,TaxCategory,
7+
AllowanceCharge,TaxSubTotal,TaxTotal,LegalMonetaryTotal,ClassifiedTaxCategory,Item,Price,InvoiceLine,
8+
GeneratorInvoice,Invoice,UnitCode,OrderReference,BillingReference,Contract,Attachment
9+
};
10+
11+
// --- Signature Information & UBL Document Signatures ---
12+
$signatureInfo = (new SignatureInformation)
13+
->setReferencedSignatureID("urn:oasis:names:specification:ubl:signature:Invoice")
14+
->setID('urn:oasis:names:specification:ubl:signature:1');
15+
16+
$ublDocSignatures = (new UBLDocumentSignatures)
17+
->setSignatureInformation($signatureInfo);
18+
19+
$extensionContent = (new ExtensionContent)
20+
->setUBLDocumentSignatures($ublDocSignatures);
21+
22+
$ublExtension = (new UBLExtension)
23+
->setExtensionURI('urn:oasis:names:specification:ubl:dsig:enveloped:xades')
24+
->setExtensionContent($extensionContent);
25+
26+
// Default UBL Extensions Default
27+
$ublExtensions = (new UBLExtensions)
28+
->setUBLExtensions([$ublExtension]);
29+
30+
// --- Signature Default ---
31+
$signature = (new Signature)
32+
->setId("urn:oasis:names:specification:ubl:signature:Invoice")
33+
->setSignatureMethod("urn:oasis:names:specification:ubl:dsig:enveloped:xades");
34+
35+
// --- Invoice Type ---
36+
$invoiceType = (new InvoiceType())
37+
->setInvoice('simplified') // 'standard' or 'simplified'
38+
->setInvoiceType('invoice') // 'invoice', 'debit', or 'credit', 'prepayment'
39+
->setIsThirdParty(false) // Third-party transaction
40+
->setIsNominal(false) // Nominal transaction
41+
->setIsExportInvoice(false) // Export invoice
42+
->setIsSummary(false) // Summary invoice
43+
->setIsSelfBilled(false); // Self-billed invoice
44+
45+
// add Attachment
46+
$attachment = (new Attachment())
47+
->setBase64Content('NWZlY2ViNjZmZmM4NmYzOGQ5NTI3ODZjNmQ2OTZjNzljMmRiYzIzOWRkNGU5MWI0NjcyOWQ3M2EyN2ZiNTdlOQ==',
48+
'base64',
49+
'text/plain'
50+
);
51+
52+
// --- Additional Document References ---
53+
$additionalDocs = [];
54+
55+
// icv = Invoice counter value
56+
$additionalDocs[] = (new AdditionalDocumentReference())
57+
->setId('ICV')
58+
->setUUID("10"); //Invoice counter value
59+
60+
// pih = Previous Invoice Hash
61+
$additionalDocs[] = (new AdditionalDocumentReference())
62+
->setId('PIH')
63+
->setAttachment($attachment); // Previous Invoice Hash
64+
65+
// qr = QR Code Default
66+
$additionalDocs[] = (new AdditionalDocumentReference())
67+
->setId('QR');
68+
69+
// --- Tax Scheme & Party Tax Schemes ---
70+
$taxScheme = (new TaxScheme())
71+
->setId("VAT");
72+
73+
// --- Legal Entity Company ---
74+
$legalEntityCompany = (new LegalEntity())
75+
->setRegistrationName('Maximum Speed Tech Supply');
76+
77+
// --- Party Tax Scheme Company ---
78+
$partyTaxSchemeCompany = (new PartyTaxScheme())
79+
->setTaxScheme($taxScheme)
80+
->setCompanyId('399999999900003');
81+
82+
// --- Address Company ---
83+
$addressCompany = (new Address())
84+
->setStreetName('Prince Sultan')
85+
->setBuildingNumber("2322")
86+
->setCitySubdivisionName('Al-Murabba')
87+
->setCityName('Riyadh')
88+
->setPostalZone('23333')
89+
->setCountry('SA');
90+
91+
// --- Supplier Company ---
92+
$supplierCompany = (new Party())
93+
->setPartyIdentification("1010010000")
94+
->setPartyIdentificationId("CRN")
95+
->setLegalEntity($legalEntityCompany)
96+
->setPartyTaxScheme($partyTaxSchemeCompany)
97+
->setPostalAddress($addressCompany);
98+
99+
100+
// --- Legal Entity Customer ---
101+
$legalEntityCustomer = (new LegalEntity())
102+
->setRegistrationName('Fatoora Samples');
103+
104+
// --- Party Tax Scheme Customer ---
105+
$partyTaxSchemeCustomer = (new PartyTaxScheme())
106+
->setTaxScheme($taxScheme)
107+
->setCompanyId('399999999800003');
108+
109+
// --- Address Customer ---
110+
$addressCustomer = (new Address())
111+
->setStreetName('Salah Al-Din')
112+
->setBuildingNumber("1111")
113+
->setCitySubdivisionName('Al-Murooj')
114+
->setCityName('Riyadh')
115+
->setPostalZone('12222')
116+
->setCountry('SA');
117+
118+
// --- Supplier Customer ---
119+
$supplierCustomer = (new Party())
120+
->setLegalEntity($legalEntityCustomer)
121+
->setPartyTaxScheme($partyTaxSchemeCustomer)
122+
->setPostalAddress($addressCustomer);
123+
124+
// --- Payment Means ---
125+
$paymentMeans = (new PaymentMeans())
126+
->setPaymentMeansCode("10");
127+
128+
129+
// --- array of Tax Category Discount ---
130+
$taxCategoryDiscount = [];
131+
132+
// --- Tax Category Discount ---
133+
$taxCategoryDiscount[] = (new TaxCategory())
134+
->setPercent(15)
135+
->setTaxScheme($taxScheme);
136+
137+
// --- Allowance Charge (for Invoice Line) ---
138+
$allowanceCharges = [];
139+
$allowanceCharges[] = (new AllowanceCharge())
140+
->setChargeIndicator(false)
141+
->setAllowanceChargeReason('discount')
142+
->setAmount(0.00)
143+
->setTaxCategory($taxCategoryDiscount);// Tax Category Discount
144+
145+
// --- Tax Category ---
146+
$taxCategorySubTotal = (new TaxCategory())
147+
->setPercent(15)
148+
->setTaxScheme($taxScheme);
149+
150+
// --- Tax Sub Total ---
151+
$taxSubTotal = (new TaxSubTotal())
152+
->setTaxableAmount(4)
153+
->setTaxAmount(0.6)
154+
->setTaxCategory($taxCategorySubTotal);
155+
156+
// --- Tax Total ---
157+
$taxTotal = (new TaxTotal())
158+
->addTaxSubTotal($taxSubTotal)
159+
->setTaxAmount(0.6);
160+
161+
// --- Legal Monetary Total ---
162+
$legalMonetaryTotal = (new LegalMonetaryTotal())
163+
->setLineExtensionAmount(4)// Total amount of the invoice
164+
->setTaxExclusiveAmount(4) // Total amount without tax
165+
->setTaxInclusiveAmount(4.60) // Total amount with tax
166+
->setPrepaidAmount(0) // Prepaid amount
167+
->setPayableAmount(4.60) // Amount to be paid
168+
->setAllowanceTotalAmount(0); // Total amount of allowances
169+
170+
// --- Classified Tax Category ---
171+
$classifiedTax = (new ClassifiedTaxCategory())
172+
->setPercent(15)
173+
->setTaxScheme($taxScheme);
174+
175+
// --- Item (Product) ---
176+
$productItem = (new Item())
177+
->setName('Product') // Product name
178+
->setClassifiedTaxCategory($classifiedTax); // Classified tax category
179+
180+
// --- Allowance Charge (for Price) ---
181+
$allowanceChargesPrice = [];
182+
$allowanceChargesPrice[] = (new AllowanceCharge())
183+
->setChargeIndicator(true)
184+
->setAllowanceChargeReason('discount')
185+
->setAmount(0.00);
186+
187+
// --- Price ---
188+
$price = (new Price())
189+
->setUnitCode(UnitCode::UNIT)
190+
->setAllowanceCharges($allowanceChargesPrice)
191+
->setPriceAmount(2);
192+
193+
// --- Invoice Line Tax Total ---
194+
$lineTaxTotal = (new TaxTotal())
195+
->setTaxAmount(0.60)
196+
->setRoundingAmount(4.60);
197+
198+
// --- Invoice Line(s) ---
199+
$invoiceLine = (new InvoiceLine())
200+
->setUnitCode("PCE")
201+
->setId(1)
202+
->setItem($productItem)
203+
->setLineExtensionAmount(4)
204+
->setPrice($price)
205+
->setTaxTotal($lineTaxTotal)
206+
->setInvoicedQuantity(2);
207+
$invoiceLines = [$invoiceLine];
208+
209+
210+
// Invoice
211+
$invoice = (new Invoice())
212+
->setUBLExtensions($ublExtensions)
213+
->setUUID('3cf5ee18-ee25-44ea-a444-2c37ba7f28be')
214+
->setId('SME00023')
215+
->setIssueDate(new DateTime('2024-09-07 17:41:08'))
216+
->setIssueTime(new DateTime('2024-09-07 17:41:08'))
217+
->setInvoiceType($invoiceType)
218+
->setNote('ABC')->setlanguageID('ar')
219+
->setInvoiceCurrencyCode('SAR') // Currency code (ISO 4217)
220+
->setTaxCurrencyCode('SAR') // Tax currency code (ISO 4217)
221+
->setAdditionalDocumentReferences($additionalDocs) // Additional document references
222+
->setAccountingSupplierParty($supplierCompany)// Supplier company
223+
->setAccountingCustomerParty($supplierCustomer) // Customer company
224+
->setPaymentMeans($paymentMeans)// Payment means
225+
->setAllowanceCharges($allowanceCharges)// Allowance charges
226+
->setTaxTotal($taxTotal)// Tax total
227+
->setLegalMonetaryTotal($legalMonetaryTotal)// Legal monetary total
228+
->setInvoiceLines($invoiceLines)// Invoice lines
229+
->setSignature($signature);
230+
231+
232+
try {
233+
// Generate the XML (default currency 'SAR')
234+
// Save the XML to an output file
235+
GeneratorInvoice::invoice($invoice)->saveXMLFile('GeneratorSimplified_Invoice.xml');
236+
237+
} catch (\Exception $e) {
238+
// Log error message and exit
239+
echo "An error occurred: " . $e->getMessage() . "\n";
240+
exit(1);
241+
}

examples/GeneratorInvoice.php examples/GeneratorStandard_Invoice.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@
215215
try {
216216
// Generate the XML (default currency 'SAR')
217217
// Save the XML to an output file
218-
$xmlOutput = GeneratorInvoice::invoice($invoice)->saveXMLFile();
218+
$xmlOutput = GeneratorInvoice::invoice($invoice)->saveXMLFile('GeneratorStandard_Invoice.xml');
219219

220220
} catch (\Exception $e) {
221221
// Log error message and exit

0 commit comments

Comments
 (0)