diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ccb5d1a..5b5ce45 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -8,6 +8,7 @@ jobs:
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.experimental || false }}
strategy:
+ fail-fast: false
matrix:
php-version: ['7.1', '7.2', '7.3', '7.4']
include:
@@ -26,7 +27,7 @@ jobs:
with:
php-version: ${{ matrix.php-version }}
coverage: none
- extensions: ast
+ extensions: ast-stable
# Setup Composer
- name: Setup Composer
@@ -35,7 +36,7 @@ jobs:
# Run static analyzer
- name: Run static analyzer
if: ${{ success() && matrix.php-version != '7.1' }}
- run: vendor/bin/phan
+ run: vendor/bin/phan --color --no-progress-bar
# Run tests
- name: Run tests
@@ -51,9 +52,8 @@ jobs:
# Deploy documentation
- name: Deploy documentation
if: ${{ success() && matrix.deploy || false }}
- uses: JamesIves/github-pages-deploy-action@3.7.1
+ uses: JamesIves/github-pages-deploy-action@4.1.4
with:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- BRANCH: gh-pages
- FOLDER: site
- CLEAN: true # Automatically remove deleted files from the deploy branch
+ branch: gh-pages
+ folder: site
+ clean: true # Automatically remove deleted files from the deploy branch
diff --git a/.phan/config.php b/.phan/config.php
index bbbd20c..4be53c2 100644
--- a/.phan/config.php
+++ b/.phan/config.php
@@ -1,20 +1,42 @@
null,
// A list of directories that should be parsed for class and
@@ -26,15 +48,9 @@
// your application should be included in this list.
'directory_list' => [
'src',
- 'vendor/josemmo/uxml'
+ 'vendor',
],
- // A regex used to match every file name that you want to
- // exclude from parsing. Actual value will exclude every
- // "test", "tests", "Test" and "Tests" folders found in
- // "vendor/" directory.
- 'exclude_file_regex' => '@^vendor/.*/(tests?|Tests?)/@',
-
// A directory list that defines files that will be excluded
// from static analysis, but whose class and method
// information should be included.
@@ -47,28 +63,128 @@
// should be added to both the `directory_list`
// and `exclude_analysis_directory_list` arrays.
'exclude_analysis_directory_list' => [
- 'vendor/'
+ 'vendor',
],
- // Set to true in order to attempt to detect unused variables.
- // `dead_code_detection` will also enable unused variable detection.
+ // If enabled, Phan will warn if **any** type in a method invocation's object
+ // is definitely not an object,
+ // or if **any** type in an invoked expression is not a callable.
+ // Setting this to true will introduce numerous false positives
+ // (and reveal some bugs).
+ 'strict_method_checking' => true,
+
+ // If enabled, Phan will warn if **any** type in the argument's union type
+ // cannot be cast to a type in the parameter's expected union type.
+ // Setting this to true will introduce numerous false positives
+ // (and reveal some bugs).
+ 'strict_param_checking' => true,
+
+ // If enabled, Phan will warn if **any** type in a property assignment's union type
+ // cannot be cast to a type in the property's declared union type.
+ // Setting this to true will introduce numerous false positives
+ // (and reveal some bugs).
+ // (For self-analysis, Phan has a large number of suppressions and file-level suppressions, due to \ast\Node being difficult to type check)
+ 'strict_property_checking' => true,
+
+ // If enabled, Phan will warn if **any** type in a returned value's union type
+ // cannot be cast to the declared return type.
+ // Setting this to true will introduce numerous false positives
+ // (and reveal some bugs).
+ // (For self-analysis, Phan has a large number of suppressions and file-level suppressions, due to \ast\Node being difficult to type check)
+ 'strict_return_checking' => true,
+
+ // If enabled, Phan will warn if **any** type of the object expression for a property access
+ // does not contain that property.
+ 'strict_object_checking' => true,
+
+ // If enabled, check all methods that override a
+ // parent method to make sure its signature is
+ // compatible with the parent's. This check
+ // can add quite a bit of time to the analysis.
+ // This will also check if final methods are overridden, etc.
+ 'analyze_signature_compatibility' => true,
+
+ // If true, check to make sure the return type declared
+ // in the doc-block (if any) matches the return type
+ // declared in the method signature.
+ 'check_docblock_signature_return_type_match' => true,
+
+ // If true, check to make sure the param types declared
+ // in the doc-block (if any) matches the param types
+ // declared in the method signature.
+ 'check_docblock_signature_param_type_match' => true,
+
+ // Set to true in order to attempt to detect dead
+ // (unreferenced) code. Keep in mind that the
+ // results will only be a guess given that classes,
+ // properties, constants and methods can be referenced
+ // as variables (like `$class->$property` or
+ // `$class->$method()`) in ways that we're unable
+ // to make sense of.
//
- // This has a few known false positives, e.g. for loops or branches.
- 'unused_variable_detection' => true,
+ // To more aggressively detect dead code,
+ // you may want to set `dead_code_detection_prefer_false_negative` to `false`.
+ 'dead_code_detection' => true,
+
+ // Set to true in order to attempt to detect redundant and impossible conditions.
+ //
+ // This has some false positives involving loops,
+ // variables set in branches of loops, and global variables.
+ 'redundant_condition_detection' => true,
+
+ // Set to true in order to attempt to detect error-prone truthiness/falsiness checks.
+ //
+ // This is not suitable for all codebases.
+ 'error_prone_truthy_condition_detection' => true,
+
+ // Enable or disable support for generic templated
+ // class types.
+ 'generic_types_enabled' => true,
// If enabled, warn about throw statement where the exception types
// are not documented in the PHPDoc of functions, methods, and closures.
'warn_about_undocumented_throw_statements' => true,
- // If enabled (and warn_about_undocumented_throw_statements is enabled),
- // warn about function/closure/method calls that have (at)throws
- // without the invoking method documenting that exception.
+ // If enabled (and `warn_about_undocumented_throw_statements` is enabled),
+ // Phan will warn about function/closure/method invocations that have `@throws`
+ // that aren't caught or documented in the invoking method.
'warn_about_undocumented_exceptions_thrown_by_invoked_functions' => true,
- // A list of plugin files to execute
- // NOTE: values can be the base name without the extension for plugins bundled with Phan (E.g. 'AlwaysReturnPlugin')
- // or relative/absolute paths to the plugin (Relative to the project root).
+ // The minimum severity level to report on. This can be
+ // set to Issue::SEVERITY_LOW, Issue::SEVERITY_NORMAL or
+ // Issue::SEVERITY_CRITICAL.
+ 'minimum_severity' => Issue::SEVERITY_NORMAL,
+
+ // Add any issue types (such as `'PhanUndeclaredMethod'`)
+ // to this list to inhibit them from being reported.
+ 'suppress_issue_types' => [
+ 'PhanUnreferencedClass',
+ 'PhanUnreferencedPublicMethod',
+ ],
+
+ // A list of plugin files to execute.
+ // Plugins which are bundled with Phan can be added here by providing their name
+ // (e.g. 'AlwaysReturnPlugin')
+ //
+ // Documentation about available bundled plugins can be found
+ // at https://github.com/phan/phan/tree/v4/.phan/plugins
+ //
+ // Alternately, you can pass in the full path to a PHP file
+ // with the plugin's implementation.
+ // (e.g. 'vendor/phan/phan/.phan/plugins/AlwaysReturnPlugin.php')
'plugins' => [
+ 'AlwaysReturnPlugin', // Checks if a function, closure or method unconditionally returns.
+ 'DollarDollarPlugin',
+ 'DuplicateArrayKeyPlugin',
+ 'DuplicateExpressionPlugin',
+ 'EmptyStatementListPlugin',
+ 'InlineHTMLPlugin',
+ 'LoopVariableReusePlugin',
'PreferNamespaceUsePlugin',
- ]
+ 'PregRegexCheckerPlugin',
+ 'PrintfCheckerPlugin',
+ 'SleepCheckerPlugin',
+ 'UnreachableCodePlugin', // Checks for syntactically unreachable statements in the global scope or function bodies.
+ 'UseReturnValuePlugin',
+ ],
];
diff --git a/README.md b/README.md
index 0a7e1fb..e51662f 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,20 @@
-# European Invoicing (eInvoicing)
-[![Build Status](https://github.com/josemmo/einvoicing/workflows/CI/badge.svg)](https://github.com/josemmo/einvoicing/actions)
-[![Latest Version](https://img.shields.io/packagist/v/josemmo/einvoicing?include_prereleases)](https://packagist.org/packages/josemmo/einvoicing)
-[![Minimum PHP Version](https://img.shields.io/packagist/php-v/josemmo/einvoicing)](#installation)
-[![License](https://img.shields.io/github/license/josemmo/einvoicing)](LICENSE)
-[![Documentation](https://img.shields.io/badge/online-docs-blueviolet)](https://josemmo.github.io/einvoicing/)
+
+
+
+
+
+
+
+
+
+## About
eInvoicing is a PHP library for creating and reading electronic invoices according to the [eInvoicing Directive and European standard](https://ec.europa.eu/cefdigital/wiki/display/CEFDIGITAL/eInvoicing).
It aims to be 100% compliant with [EN 16931](https://ec.europa.eu/cefdigital/wiki/x/kwFVBg) as well as with the most popular CIUS and extensions, such as [PEPPOL BIS](https://docs.peppol.eu/poacc/billing/3.0/bis/).
-> ⚠️ WARNING: This library is almost ready for production. Some features may not be available yet. ⚠️
-
## Installation
First of all, make sure your environment meets the following requirements:
@@ -24,9 +28,72 @@ composer require josemmo/einvoicing
```
## Usage
-For a quick guide on how to get started, visit the documentation website at
+For a proper quick start guide, visit the documentation website at
[https://josemmo.github.io/einvoicing/](https://josemmo.github.io/einvoicing/).
+### Importing invoice documents
+```php
+use Einvoicing\Exceptions\ValidationException;
+use Einvoicing\Readers\UblReader;
+
+$reader = new UblReader();
+$document = file_get_contents(__DIR__ . "/example.xml");
+$inv = $reader->import($document);
+try {
+ $inv->validate();
+} catch (ValidationException $e) {
+ // Invoice is not EN 16931 complaint
+}
+```
+
+### Exporting invoice documents
+```php
+use Einvoicing\Identifier;
+use Einvoicing\Invoice;
+use Einvoicing\InvoiceLine;
+use Einvoicing\Party;
+use Einvoicing\Presets;
+use Einvoicing\Writers\UblWriter;
+
+// Create PEPPOL invoice instance
+$inv = new Invoice(Presets\Peppol::class);
+$inv->setNumber('F-202000012')
+ ->setIssueDate(new DateTime('2020-11-01'))
+ ->setDueDate(new DateTime('2020-11-30'));
+
+// Set seller
+$seller = new Party();
+$seller->setElectronicAddress(new Identifier('9482348239847239874', '0088'))
+ ->setCompanyId(new Identifier('AH88726', '0183'))
+ ->setName('Seller Name Ltd.')
+ ->setTradingName('Seller Name')
+ ->setVatNumber('ESA00000000')
+ ->setAddress(['Fake Street 123', 'Apartment Block 2B'])
+ ->setCity('Springfield')
+ ->setCountry('DE');
+$inv->setSeller($seller);
+
+// Set buyer
+$buyer = new Party();
+$buyer->setElectronicAddress(new Identifier('ES12345', '0002'))
+ ->setName('Buyer Name Ltd.')
+ ->setCountry('FR');
+$inv->setBuyer($buyer);
+
+// Add a product line
+$line = new InvoiceLine();
+$line->setName('Product Name')
+ ->setPrice(100)
+ ->setVatRate(16)
+ ->setQuantity(1);
+$inv->addLine($line);
+
+// Export invoice to a UBL document
+header('Content-Type: text/xml');
+$writer = new UblWriter();
+echo $writer->export($inv);
+```
+
## Roadmap
These are the expected features for the library and how's it going so far:
diff --git a/composer.json b/composer.json
index 7e36479..d698634 100644
--- a/composer.json
+++ b/composer.json
@@ -29,12 +29,12 @@
},
"require": {
"php": ">=7.1",
- "josemmo/uxml": "^0.1.1"
+ "josemmo/uxml": "^0.1.2"
},
"require-dev": {
"ext-openssl": "*",
- "phan/phan": "^4",
+ "phan/phan": "^5.0",
"phpdocumentor/reflection": "^4.0",
- "symfony/phpunit-bridge": "^5.2"
+ "symfony/phpunit-bridge": "^5.3"
}
}
diff --git a/composer.lock b/composer.lock
index 78fed34..c8809c1 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,20 +4,20 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "7aac792b4c23a2b38656f2b6798fa17f",
+ "content-hash": "1cc14bd61534b65cbd3dd84fdc0c7465",
"packages": [
{
"name": "josemmo/uxml",
- "version": "v0.1.1",
+ "version": "v0.1.2",
"source": {
"type": "git",
"url": "https://github.com/josemmo/uxml.git",
- "reference": "60b59961a3c7144f03c4d9971f04277dd9c0085d"
+ "reference": "be11039e103ad1a3d801ea67c03fdc30756f7992"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/josemmo/uxml/zipball/60b59961a3c7144f03c4d9971f04277dd9c0085d",
- "reference": "60b59961a3c7144f03c4d9971f04277dd9c0085d",
+ "url": "https://api.github.com/repos/josemmo/uxml/zipball/be11039e103ad1a3d801ea67c03fdc30756f7992",
+ "reference": "be11039e103ad1a3d801ea67c03fdc30756f7992",
"shasum": ""
},
"require": {
@@ -25,8 +25,8 @@
"php": ">=7.1"
},
"require-dev": {
- "phan/phan": "^4",
- "symfony/phpunit-bridge": "^5.2"
+ "phan/phan": "^5.0",
+ "symfony/phpunit-bridge": "^5.3"
},
"type": "library",
"autoload": {
@@ -52,9 +52,9 @@
],
"support": {
"issues": "https://github.com/josemmo/uxml/issues",
- "source": "https://github.com/josemmo/uxml/tree/v0.1.1"
+ "source": "https://github.com/josemmo/uxml/tree/v0.1.2"
},
- "time": "2021-05-25T17:54:56+00:00"
+ "time": "2021-08-07T09:49:41+00:00"
}
],
"packages-dev": [
@@ -141,21 +141,21 @@
},
{
"name": "composer/xdebug-handler",
- "version": "2.0.1",
+ "version": "2.0.2",
"source": {
"type": "git",
"url": "https://github.com/composer/xdebug-handler.git",
- "reference": "964adcdd3a28bf9ed5d9ac6450064e0d71ed7496"
+ "reference": "84674dd3a7575ba617f5a76d7e9e29a7d3891339"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/964adcdd3a28bf9ed5d9ac6450064e0d71ed7496",
- "reference": "964adcdd3a28bf9ed5d9ac6450064e0d71ed7496",
+ "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/84674dd3a7575ba617f5a76d7e9e29a7d3891339",
+ "reference": "84674dd3a7575ba617f5a76d7e9e29a7d3891339",
"shasum": ""
},
"require": {
"php": "^5.3.2 || ^7.0 || ^8.0",
- "psr/log": "^1.0"
+ "psr/log": "^1 || ^2 || ^3"
},
"require-dev": {
"phpstan/phpstan": "^0.12.55",
@@ -185,7 +185,7 @@
"support": {
"irc": "irc://irc.freenode.org/composer",
"issues": "https://github.com/composer/xdebug-handler/issues",
- "source": "https://github.com/composer/xdebug-handler/tree/2.0.1"
+ "source": "https://github.com/composer/xdebug-handler/tree/2.0.2"
},
"funding": [
{
@@ -201,24 +201,24 @@
"type": "tidelift"
}
],
- "time": "2021-05-05T19:37:51+00:00"
+ "time": "2021-07-31T17:03:58+00:00"
},
{
"name": "felixfbecker/advanced-json-rpc",
- "version": "v3.2.0",
+ "version": "v3.2.1",
"source": {
"type": "git",
"url": "https://github.com/felixfbecker/php-advanced-json-rpc.git",
- "reference": "06f0b06043c7438959dbdeed8bb3f699a19be22e"
+ "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/06f0b06043c7438959dbdeed8bb3f699a19be22e",
- "reference": "06f0b06043c7438959dbdeed8bb3f699a19be22e",
+ "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447",
+ "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447",
"shasum": ""
},
"require": {
- "netresearch/jsonmapper": "^1.0 || ^2.0",
+ "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0",
"php": "^7.1 || ^8.0",
"phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0"
},
@@ -244,29 +244,29 @@
"description": "A more advanced JSONRPC implementation",
"support": {
"issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues",
- "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.0"
+ "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1"
},
- "time": "2021-01-10T17:48:47+00:00"
+ "time": "2021-06-11T22:34:44+00:00"
},
{
"name": "microsoft/tolerant-php-parser",
- "version": "v0.0.23",
+ "version": "v0.1.1",
"source": {
"type": "git",
"url": "https://github.com/microsoft/tolerant-php-parser.git",
- "reference": "1d76657e3271754515ace52501d3e427eca42ad0"
+ "reference": "6a965617cf484355048ac6d2d3de7b6ec93abb16"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/microsoft/tolerant-php-parser/zipball/1d76657e3271754515ace52501d3e427eca42ad0",
- "reference": "1d76657e3271754515ace52501d3e427eca42ad0",
+ "url": "https://api.github.com/repos/microsoft/tolerant-php-parser/zipball/6a965617cf484355048ac6d2d3de7b6ec93abb16",
+ "reference": "6a965617cf484355048ac6d2d3de7b6ec93abb16",
"shasum": ""
},
"require": {
- "php": ">=7.0"
+ "php": ">=7.2"
},
"require-dev": {
- "phpunit/phpunit": "^6.4"
+ "phpunit/phpunit": "^8.5.15"
},
"type": "library",
"autoload": {
@@ -289,22 +289,22 @@
"description": "Tolerant PHP-to-AST parser designed for IDE usage scenarios",
"support": {
"issues": "https://github.com/microsoft/tolerant-php-parser/issues",
- "source": "https://github.com/microsoft/tolerant-php-parser/tree/v0.0.23"
+ "source": "https://github.com/microsoft/tolerant-php-parser/tree/v0.1.1"
},
- "time": "2020-09-13T17:29:12+00:00"
+ "time": "2021-07-16T21:28:12+00:00"
},
{
"name": "netresearch/jsonmapper",
- "version": "v2.1.0",
+ "version": "v4.0.0",
"source": {
"type": "git",
"url": "https://github.com/cweiske/jsonmapper.git",
- "reference": "e0f1e33a71587aca81be5cffbb9746510e1fe04e"
+ "reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/e0f1e33a71587aca81be5cffbb9746510e1fe04e",
- "reference": "e0f1e33a71587aca81be5cffbb9746510e1fe04e",
+ "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d",
+ "reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d",
"shasum": ""
},
"require": {
@@ -312,10 +312,10 @@
"ext-pcre": "*",
"ext-reflection": "*",
"ext-spl": "*",
- "php": ">=5.6"
+ "php": ">=7.1"
},
"require-dev": {
- "phpunit/phpunit": "~4.8.35 || ~5.7 || ~6.4 || ~7.0",
+ "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0",
"squizlabs/php_codesniffer": "~3.5"
},
"type": "library",
@@ -340,22 +340,22 @@
"support": {
"email": "cweiske@cweiske.de",
"issues": "https://github.com/cweiske/jsonmapper/issues",
- "source": "https://github.com/cweiske/jsonmapper/tree/master"
+ "source": "https://github.com/cweiske/jsonmapper/tree/v4.0.0"
},
- "time": "2020-04-16T18:48:43+00:00"
+ "time": "2020-12-01T19:48:11+00:00"
},
{
"name": "nikic/php-parser",
- "version": "v4.10.5",
+ "version": "v4.12.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "4432ba399e47c66624bc73c8c0f811e5c109576f"
+ "reference": "6608f01670c3cc5079e18c1dab1104e002579143"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4432ba399e47c66624bc73c8c0f811e5c109576f",
- "reference": "4432ba399e47c66624bc73c8c0f811e5c109576f",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6608f01670c3cc5079e18c1dab1104e002579143",
+ "reference": "6608f01670c3cc5079e18c1dab1104e002579143",
"shasum": ""
},
"require": {
@@ -396,22 +396,22 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v4.10.5"
+ "source": "https://github.com/nikic/PHP-Parser/tree/v4.12.0"
},
- "time": "2021-05-03T19:11:20+00:00"
+ "time": "2021-07-21T10:44:31+00:00"
},
{
"name": "phan/phan",
- "version": "4.0.6",
+ "version": "5.0.0",
"source": {
"type": "git",
"url": "https://github.com/phan/phan.git",
- "reference": "4caaa97195dcea549021cb773a5dc30bb46d1fab"
+ "reference": "f36b6b9a2f4143a25f35ce94d712ceb0527e9d90"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phan/phan/zipball/4caaa97195dcea549021cb773a5dc30bb46d1fab",
- "reference": "4caaa97195dcea549021cb773a5dc30bb46d1fab",
+ "url": "https://api.github.com/repos/phan/phan/zipball/f36b6b9a2f4143a25f35ce94d712ceb0527e9d90",
+ "reference": "f36b6b9a2f4143a25f35ce94d712ceb0527e9d90",
"shasum": ""
},
"require": {
@@ -421,23 +421,25 @@
"ext-json": "*",
"ext-tokenizer": "*",
"felixfbecker/advanced-json-rpc": "^3.0.4",
- "microsoft/tolerant-php-parser": "0.0.23",
- "netresearch/jsonmapper": "^1.6.0|^2.0|^3.0",
+ "microsoft/tolerant-php-parser": "^0.1.0",
+ "netresearch/jsonmapper": "^1.6.0|^2.0|^3.0|^4.0",
"php": "^7.2.0|^8.0.0",
"sabre/event": "^5.0.3",
"symfony/console": "^3.2|^4.0|^5.0",
"symfony/polyfill-mbstring": "^1.11.0",
- "symfony/polyfill-php80": "^1.20.0"
+ "symfony/polyfill-php80": "^1.20.0",
+ "tysonandre/var_representation_polyfill": "^0.0.2"
},
"require-dev": {
"phpunit/phpunit": "^8.5.0"
},
"suggest": {
- "ext-ast": "Needed for parsing ASTs (unless --use-fallback-parser is used). 1.0.1+ is needed, 1.0.10+ is recommended.",
+ "ext-ast": "Needed for parsing ASTs (unless --use-fallback-parser is used). 1.0.1+ is needed, 1.0.14+ is recommended.",
"ext-iconv": "Either iconv or mbstring is needed to ensure issue messages are valid utf-8",
"ext-igbinary": "Improves performance of polyfill when ext-ast is unavailable",
"ext-mbstring": "Either iconv or mbstring is needed to ensure issue messages are valid utf-8",
- "ext-tokenizer": "Needed for fallback/polyfill parser support and file/line-based suppressions."
+ "ext-tokenizer": "Needed for fallback/polyfill parser support and file/line-based suppressions.",
+ "ext-var_representation": "Suggested for converting values to strings in issue messages"
},
"bin": [
"phan",
@@ -473,9 +475,9 @@
],
"support": {
"issues": "https://github.com/phan/phan/issues",
- "source": "https://github.com/phan/phan/tree/4.0.6"
+ "source": "https://github.com/phan/phan/tree/5.0.0"
},
- "time": "2021-05-19T23:27:16+00:00"
+ "time": "2021-08-01T18:17:28+00:00"
},
{
"name": "phpdocumentor/reflection",
@@ -857,27 +859,29 @@
},
{
"name": "symfony/console",
- "version": "v5.2.8",
+ "version": "v5.3.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "864568fdc0208b3eba3638b6000b69d2386e6768"
+ "reference": "51b71afd6d2dc8f5063199357b9880cea8d8bfe2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/864568fdc0208b3eba3638b6000b69d2386e6768",
- "reference": "864568fdc0208b3eba3638b6000b69d2386e6768",
+ "url": "https://api.github.com/repos/symfony/console/zipball/51b71afd6d2dc8f5063199357b9880cea8d8bfe2",
+ "reference": "51b71afd6d2dc8f5063199357b9880cea8d8bfe2",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
+ "symfony/deprecation-contracts": "^2.1",
"symfony/polyfill-mbstring": "~1.0",
"symfony/polyfill-php73": "^1.8",
- "symfony/polyfill-php80": "^1.15",
+ "symfony/polyfill-php80": "^1.16",
"symfony/service-contracts": "^1.1|^2",
"symfony/string": "^5.1"
},
"conflict": {
+ "psr/log": ">=3",
"symfony/dependency-injection": "<4.4",
"symfony/dotenv": "<5.1",
"symfony/event-dispatcher": "<4.4",
@@ -885,10 +889,10 @@
"symfony/process": "<4.4"
},
"provide": {
- "psr/log-implementation": "1.0"
+ "psr/log-implementation": "1.0|2.0"
},
"require-dev": {
- "psr/log": "~1.0",
+ "psr/log": "^1|^2",
"symfony/config": "^4.4|^5.0",
"symfony/dependency-injection": "^4.4|^5.0",
"symfony/event-dispatcher": "^4.4|^5.0",
@@ -934,7 +938,74 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v5.2.8"
+ "source": "https://github.com/symfony/console/tree/v5.3.6"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-07-27T19:10:22+00:00"
+ },
+ {
+ "name": "symfony/deprecation-contracts",
+ "version": "v2.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/deprecation-contracts.git",
+ "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627",
+ "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.4-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ },
+ "autoload": {
+ "files": [
+ "function.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "A generic function and convention to trigger deprecation notices",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.0"
},
"funding": [
{
@@ -950,30 +1021,30 @@
"type": "tidelift"
}
],
- "time": "2021-05-11T15:45:21+00:00"
+ "time": "2021-03-23T23:28:01+00:00"
},
{
"name": "symfony/phpunit-bridge",
- "version": "v5.2.9",
+ "version": "v5.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/phpunit-bridge.git",
- "reference": "ea24e42c1ee04792f5d814da6f0814b20ece2907"
+ "reference": "bc368b765a651424b19f5759953ce2873e7d448b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/ea24e42c1ee04792f5d814da6f0814b20ece2907",
- "reference": "ea24e42c1ee04792f5d814da6f0814b20ece2907",
+ "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/bc368b765a651424b19f5759953ce2873e7d448b",
+ "reference": "bc368b765a651424b19f5759953ce2873e7d448b",
"shasum": ""
},
"require": {
- "php": ">=5.5.9"
+ "php": ">=7.1.3",
+ "symfony/deprecation-contracts": "^2.1"
},
"conflict": {
- "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0|<6.4,>=6.0|9.1.2"
+ "phpunit/phpunit": "<7.5|9.1.2"
},
"require-dev": {
- "symfony/deprecation-contracts": "^2.1",
"symfony/error-handler": "^4.4|^5.0"
},
"suggest": {
@@ -1017,7 +1088,7 @@
"description": "Provides utilities for PHPUnit, especially user deprecation notices management",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/phpunit-bridge/tree/v5.2.9"
+ "source": "https://github.com/symfony/phpunit-bridge/tree/v5.3.4"
},
"funding": [
{
@@ -1033,20 +1104,20 @@
"type": "tidelift"
}
],
- "time": "2021-05-16T13:07:46+00:00"
+ "time": "2021-07-15T21:37:44+00:00"
},
{
"name": "symfony/polyfill-ctype",
- "version": "v1.22.1",
+ "version": "v1.23.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
- "reference": "c6c942b1ac76c82448322025e084cadc56048b4e"
+ "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e",
- "reference": "c6c942b1ac76c82448322025e084cadc56048b4e",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce",
+ "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce",
"shasum": ""
},
"require": {
@@ -1058,7 +1129,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "1.22-dev"
+ "dev-main": "1.23-dev"
},
"thanks": {
"name": "symfony/polyfill",
@@ -1096,7 +1167,7 @@
"portable"
],
"support": {
- "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.1"
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0"
},
"funding": [
{
@@ -1112,20 +1183,20 @@
"type": "tidelift"
}
],
- "time": "2021-01-07T16:49:33+00:00"
+ "time": "2021-02-19T12:13:01+00:00"
},
{
"name": "symfony/polyfill-intl-grapheme",
- "version": "v1.22.1",
+ "version": "v1.23.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
- "reference": "5601e09b69f26c1828b13b6bb87cb07cddba3170"
+ "reference": "16880ba9c5ebe3642d1995ab866db29270b36535"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5601e09b69f26c1828b13b6bb87cb07cddba3170",
- "reference": "5601e09b69f26c1828b13b6bb87cb07cddba3170",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/16880ba9c5ebe3642d1995ab866db29270b36535",
+ "reference": "16880ba9c5ebe3642d1995ab866db29270b36535",
"shasum": ""
},
"require": {
@@ -1137,7 +1208,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "1.22-dev"
+ "dev-main": "1.23-dev"
},
"thanks": {
"name": "symfony/polyfill",
@@ -1177,7 +1248,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.22.1"
+ "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.23.1"
},
"funding": [
{
@@ -1193,20 +1264,20 @@
"type": "tidelift"
}
],
- "time": "2021-01-22T09:19:47+00:00"
+ "time": "2021-05-27T12:26:48+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
- "version": "v1.22.1",
+ "version": "v1.23.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
- "reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248"
+ "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/43a0283138253ed1d48d352ab6d0bdb3f809f248",
- "reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8",
+ "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8",
"shasum": ""
},
"require": {
@@ -1218,7 +1289,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "1.22-dev"
+ "dev-main": "1.23-dev"
},
"thanks": {
"name": "symfony/polyfill",
@@ -1261,7 +1332,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.22.1"
+ "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.23.0"
},
"funding": [
{
@@ -1277,20 +1348,20 @@
"type": "tidelift"
}
],
- "time": "2021-01-22T09:19:47+00:00"
+ "time": "2021-02-19T12:13:01+00:00"
},
{
"name": "symfony/polyfill-mbstring",
- "version": "v1.22.1",
+ "version": "v1.23.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "5232de97ee3b75b0360528dae24e73db49566ab1"
+ "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/5232de97ee3b75b0360528dae24e73db49566ab1",
- "reference": "5232de97ee3b75b0360528dae24e73db49566ab1",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9174a3d80210dca8daa7f31fec659150bbeabfc6",
+ "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6",
"shasum": ""
},
"require": {
@@ -1302,7 +1373,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "1.22-dev"
+ "dev-main": "1.23-dev"
},
"thanks": {
"name": "symfony/polyfill",
@@ -1341,7 +1412,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.1"
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.1"
},
"funding": [
{
@@ -1357,20 +1428,20 @@
"type": "tidelift"
}
],
- "time": "2021-01-22T09:19:47+00:00"
+ "time": "2021-05-27T12:26:48+00:00"
},
{
"name": "symfony/polyfill-php73",
- "version": "v1.22.1",
+ "version": "v1.23.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php73.git",
- "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2"
+ "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/a678b42e92f86eca04b7fa4c0f6f19d097fb69e2",
- "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2",
+ "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fba8933c384d6476ab14fb7b8526e5287ca7e010",
+ "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010",
"shasum": ""
},
"require": {
@@ -1379,7 +1450,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "1.22-dev"
+ "dev-main": "1.23-dev"
},
"thanks": {
"name": "symfony/polyfill",
@@ -1420,7 +1491,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-php73/tree/v1.22.1"
+ "source": "https://github.com/symfony/polyfill-php73/tree/v1.23.0"
},
"funding": [
{
@@ -1436,20 +1507,20 @@
"type": "tidelift"
}
],
- "time": "2021-01-07T16:49:33+00:00"
+ "time": "2021-02-19T12:13:01+00:00"
},
{
"name": "symfony/polyfill-php80",
- "version": "v1.22.1",
+ "version": "v1.23.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
- "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91"
+ "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dc3063ba22c2a1fd2f45ed856374d79114998f91",
- "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91",
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/1100343ed1a92e3a38f9ae122fc0eb21602547be",
+ "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be",
"shasum": ""
},
"require": {
@@ -1458,7 +1529,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "1.22-dev"
+ "dev-main": "1.23-dev"
},
"thanks": {
"name": "symfony/polyfill",
@@ -1503,7 +1574,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-php80/tree/v1.22.1"
+ "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.1"
},
"funding": [
{
@@ -1519,7 +1590,7 @@
"type": "tidelift"
}
],
- "time": "2021-01-07T16:49:33+00:00"
+ "time": "2021-07-28T13:41:28+00:00"
},
{
"name": "symfony/service-contracts",
@@ -1602,16 +1673,16 @@
},
{
"name": "symfony/string",
- "version": "v5.2.8",
+ "version": "v5.3.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "01b35eb64cac8467c3f94cd0ce2d0d376bb7d1db"
+ "reference": "bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/01b35eb64cac8467c3f94cd0ce2d0d376bb7d1db",
- "reference": "01b35eb64cac8467c3f94cd0ce2d0d376bb7d1db",
+ "url": "https://api.github.com/repos/symfony/string/zipball/bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1",
+ "reference": "bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1",
"shasum": ""
},
"require": {
@@ -1665,7 +1736,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v5.2.8"
+ "source": "https://github.com/symfony/string/tree/v5.3.3"
},
"funding": [
{
@@ -1681,7 +1752,58 @@
"type": "tidelift"
}
],
- "time": "2021-05-10T14:56:10+00:00"
+ "time": "2021-06-27T11:44:38+00:00"
+ },
+ {
+ "name": "tysonandre/var_representation_polyfill",
+ "version": "0.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/TysonAndre/var_representation_polyfill.git",
+ "reference": "3f17999ee1f257319ddc6721dd26ebbc5d175f33"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/TysonAndre/var_representation_polyfill/zipball/3f17999ee1f257319ddc6721dd26ebbc5d175f33",
+ "reference": "3f17999ee1f257319ddc6721dd26ebbc5d175f33",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": "^7.2.0|^8.0.0"
+ },
+ "require-dev": {
+ "phan/phan": "^4.0",
+ "phpunit/phpunit": "^8.5.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "VarRepresentation\\": "src/VarRepresentation"
+ },
+ "files": [
+ "src/var_representation.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Tyson Andre"
+ }
+ ],
+ "description": "Polyfill for var_representation",
+ "keywords": [
+ "var_export",
+ "var_representation"
+ ],
+ "support": {
+ "issues": "https://github.com/TysonAndre/var_representation_polyfill/issues",
+ "source": "https://github.com/TysonAndre/var_representation_polyfill/tree/0.0.2"
+ },
+ "time": "2021-06-26T18:55:02+00:00"
},
{
"name": "webmozart/assert",
diff --git a/docs/favicon.png b/docs/favicon.png
new file mode 100644
index 0000000..6028c76
Binary files /dev/null and b/docs/favicon.png differ
diff --git a/docs/getting-started/creating-custom-presets.md b/docs/getting-started/creating-custom-presets.md
index 9663598..1cfc622 100644
--- a/docs/getting-started/creating-custom-presets.md
+++ b/docs/getting-started/creating-custom-presets.md
@@ -91,7 +91,7 @@ class CustomPreset extends AbstractPreset {
public function setupInvoice(Invoice $invoice) {
$invoice->setRoundingMatrix([
"line/netAmount" => 4,
- null => 2
+ "" => 2
]);
}
}
diff --git a/docs/logo-white.svg b/docs/logo-white.svg
new file mode 100644
index 0000000..2f504a7
--- /dev/null
+++ b/docs/logo-white.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/docs/logo.svg b/docs/logo.svg
new file mode 100644
index 0000000..cb20b35
--- /dev/null
+++ b/docs/logo.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/mkdocs.yml b/mkdocs.yml
index 28273cb..6e0835e 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -1,5 +1,5 @@
# Project configuration
-site_name: eInvoicing
+site_name: European Invoicing (eInvoicing)
site_url: https://josemmo.github.io/einvoicing/
repo_name: josemmo/einvoicing
repo_url: https://github.com/josemmo/einvoicing
@@ -8,11 +8,11 @@ edit_uri: ""
# Theme customization
theme:
name: material
- icon:
- repo: fontawesome/brands/github
+ logo: logo-white.svg
+ favicon: favicon.png
palette:
scheme: slate
- primary: light blue
+ primary: blue
accent: yellow
features:
- navigation.tabs
diff --git a/src/Invoice.php b/src/Invoice.php
index 2777517..101d614 100644
--- a/src/Invoice.php
+++ b/src/Invoice.php
@@ -31,6 +31,8 @@ class Invoice {
protected $taxPointDate = null;
protected $note = null;
protected $buyerReference = null;
+ protected $purchaseOrderReference = null;
+ protected $salesOrderReference = null;
protected $paidAmount = 0;
protected $roundingAmount = 0;
protected $seller = null;
@@ -72,7 +74,7 @@ public function __construct(?string $preset=null) {
* @return int Number of decimal places
*/
public function getDecimals(string $field): int {
- return $this->roundingMatrix[$field] ?? $this->roundingMatrix[null] ?? self::DEFAULT_DECIMALS;
+ return $this->roundingMatrix[$field] ?? $this->roundingMatrix[''] ?? self::DEFAULT_DECIMALS;
}
@@ -287,6 +289,46 @@ public function setBuyerReference(?string $buyerReference): self {
}
+ /**
+ * Get purchase order reference
+ * @return string|null Purchase order reference
+ */
+ public function getPurchaseOrderReference(): ?string {
+ return $this->purchaseOrderReference;
+ }
+
+
+ /**
+ * Set purchase order reference
+ * @param string|null $purchaseOrderReference Purchase order reference
+ * @return self Invoice instance
+ */
+ public function setPurchaseOrderReference(?string $purchaseOrderReference): self {
+ $this->purchaseOrderReference = $purchaseOrderReference;
+ return $this;
+ }
+
+
+ /**
+ * Get sales order reference
+ * @return string|null Sales order reference
+ */
+ public function getSalesOrderReference(): ?string {
+ return $this->salesOrderReference;
+ }
+
+
+ /**
+ * Set sales order reference
+ * @param string|null $salesOrderReference Sales order reference
+ * @return self Invoice instance
+ */
+ public function setSalesOrderReference(?string $salesOrderReference): self {
+ $this->salesOrderReference = $salesOrderReference;
+ return $this;
+ }
+
+
/**
* Get invoice prepaid amount
* NOTE: may be rounded according to the CIUS specification
diff --git a/src/InvoiceLine.php b/src/InvoiceLine.php
index f6a9d3a..fe3bf55 100644
--- a/src/InvoiceLine.php
+++ b/src/InvoiceLine.php
@@ -192,19 +192,19 @@ public function setSellerIdentifier(?string $identifier): self {
/**
* Get quantity
- * @return int|float Quantity
+ * @return float Quantity
*/
- public function getQuantity() {
+ public function getQuantity(): float {
return $this->quantity;
}
/**
* Set quantity
- * @param int|float $quantity Quantity
- * @return self Invoice line instance
+ * @param float $quantity Quantity
+ * @return self Invoice line instance
*/
- public function setQuantity($quantity): self {
+ public function setQuantity(float $quantity): self {
$this->quantity = $quantity;
return $this;
}
@@ -241,11 +241,11 @@ public function getPrice(): ?float {
/**
* Set price
- * @param float $price Price
- * @param int|float|null $baseQuantity Base quantity
- * @return self Invoice line instance
+ * @param float $price Price
+ * @param float|null $baseQuantity Base quantity
+ * @return self Invoice line instance
*/
- public function setPrice(float $price, $baseQuantity=null): self {
+ public function setPrice(float $price, ?float $baseQuantity=null): self {
$this->price = $price;
if ($baseQuantity !== null) {
$this->setBaseQuantity($baseQuantity);
@@ -256,19 +256,19 @@ public function setPrice(float $price, $baseQuantity=null): self {
/**
* Get base quantity
- * @return int|float Base quantity
+ * @return float Base quantity
*/
- public function getBaseQuantity() {
+ public function getBaseQuantity(): float {
return $this->baseQuantity;
}
/**
* Set base quantity
- * @param int|float $baseQuantity Base quantity
- * @return self Invoice line instance
+ * @param float $baseQuantity Base quantity
+ * @return self Invoice line instance
*/
- public function setBaseQuantity($baseQuantity): self {
+ public function setBaseQuantity(float $baseQuantity): self {
$this->baseQuantity = $baseQuantity;
return $this;
}
diff --git a/src/Models/InvoiceTotals.php b/src/Models/InvoiceTotals.php
index fd0aef8..05992f2 100644
--- a/src/Models/InvoiceTotals.php
+++ b/src/Models/InvoiceTotals.php
@@ -2,6 +2,7 @@
namespace Einvoicing\Models;
use Einvoicing\Invoice;
+use Einvoicing\Traits\VatTrait;
use function array_values;
use function round;
@@ -88,7 +89,7 @@ static public function fromInvoice(Invoice $inv): InvoiceTotals {
foreach ($inv->getLines() as $line) {
$lineNetAmount = $line->getNetAmount($inv->getDecimals('line/netAmount')) ?? 0;
$totals->netAmount += $lineNetAmount;
- self::updateVatMap($vatMap, $line->getVatCategory(), $line->getVatRate(), $lineNetAmount);
+ self::updateVatMap($vatMap, $line, $lineNetAmount);
}
// Apply allowance and charge totals
@@ -96,12 +97,12 @@ static public function fromInvoice(Invoice $inv): InvoiceTotals {
foreach ($inv->getAllowances() as $item) {
$allowanceAmount = $item->getEffectiveAmount($totals->netAmount, $allowancesChargesDecimals);
$totals->allowancesAmount += $allowanceAmount;
- self::updateVatMap($vatMap, $item->getVatCategory(), $item->getVatRate(), -$allowanceAmount);
+ self::updateVatMap($vatMap, $item, -$allowanceAmount);
}
foreach ($inv->getCharges() as $item) {
$chargeAmount = $item->getEffectiveAmount($totals->netAmount, $allowancesChargesDecimals);
$totals->chargesAmount += $chargeAmount;
- self::updateVatMap($vatMap, $item->getVatCategory(), $item->getVatRate(), $chargeAmount);
+ self::updateVatMap($vatMap, $item, $chargeAmount);
}
// Calculate VAT amounts
@@ -127,18 +128,34 @@ static public function fromInvoice(Invoice $inv): InvoiceTotals {
/**
* Update VAT map
- * @param array &$vatMap VAT map reference
- * @param string $category VAT category
- * @param int|null $rate VAT rate
- * @param float $addTaxableAmount Taxable amount to add
+ * @param VatBreakdown[string] &$vatMap VAT map reference
+ * @param VatTrait $item Item instance
+ * @param float|null $rate VAT rate
+ * @param float $addTaxableAmount Taxable amount to add
*/
- static private function updateVatMap(array &$vatMap, string $category, ?int $rate, float $addTaxableAmount) {
+ static private function updateVatMap(array &$vatMap, $item, float $addTaxableAmount) {
+ $category = $item->getVatCategory();
+ $rate = $item->getVatRate();
$key = "$category:$rate";
+
+ // Initialize VAT breakdown
if (!isset($vatMap[$key])) {
$vatMap[$key] = new VatBreakdown();
$vatMap[$key]->category = $category;
$vatMap[$key]->rate = $rate;
}
+
+ // Update exemption reason (last item overwrites previous ones)
+ $exemptionReasonCode = $item->getVatExemptionReasonCode();
+ $exemptionReason = $item->getVatExemptionReason();
+ if ($exemptionReasonCode !== null) {
+ $vatMap[$key]->exemptionReasonCode = $exemptionReasonCode;
+ }
+ if ($exemptionReason !== null) {
+ $vatMap[$key]->exemptionReason = $exemptionReason;
+ }
+
+ // Increase taxable amount
$vatMap[$key]->taxableAmount += $addTaxableAmount;
}
}
diff --git a/src/Models/VatBreakdown.php b/src/Models/VatBreakdown.php
index 61ed98f..26e85a4 100644
--- a/src/Models/VatBreakdown.php
+++ b/src/Models/VatBreakdown.php
@@ -10,10 +10,22 @@ class VatBreakdown {
/**
* VAT rate as a percentage
- * @var int|null
+ * @var float|null
*/
public $rate;
+ /**
+ * VAT exemption reason code
+ * @var string|null
+ */
+ public $exemptionReasonCode = null;
+
+ /**
+ * VAT exemption reason as text
+ * @var string|null
+ */
+ public $exemptionReason = null;
+
/**
* Sum of all taxable amounts
* @var float
diff --git a/src/Presets/AbstractPreset.php b/src/Presets/AbstractPreset.php
index 18a584b..e391499 100644
--- a/src/Presets/AbstractPreset.php
+++ b/src/Presets/AbstractPreset.php
@@ -25,6 +25,6 @@ public function getRules(): array {
* @param Invoice $invoice Invoice instance
*/
public function setupInvoice(Invoice $invoice) {
- $invoice->setRoundingMatrix([null => 2]);
+ $invoice->setRoundingMatrix(['' => 2]);
}
}
diff --git a/src/Presets/Peppol.php b/src/Presets/Peppol.php
index 39de6c7..ac0c29a 100644
--- a/src/Presets/Peppol.php
+++ b/src/Presets/Peppol.php
@@ -3,6 +3,8 @@
use Einvoicing\Invoice;
+// @phan-file-suppress PhanPossiblyNonClassMethodCall
+
/**
* PEPPOL BIS Billing 3.0
* @author OpenPEPPOL
@@ -23,6 +25,11 @@ public function getSpecification(): string {
public function getRules(): array {
$res = [];
+ $res['PEPPOL-EN16931-R003'] = static function(Invoice $inv) {
+ if ($inv->getBuyerReference() !== null) return;
+ if ($inv->getPurchaseOrderReference() !== null) return;
+ return "A buyer reference or purchase order reference MUST be provided.";
+ };
$res['PEPPOL-EN16931-R061'] = static function(Invoice $inv) {
if ($inv->getPayment() === null) return;
if ($inv->getPayment()->getMandate() === null) return;
diff --git a/src/Readers/UblReader.php b/src/Readers/UblReader.php
index 19e562b..d0f69d4 100644
--- a/src/Readers/UblReader.php
+++ b/src/Readers/UblReader.php
@@ -13,6 +13,7 @@
use Einvoicing\Payments\Mandate;
use Einvoicing\Payments\Payment;
use Einvoicing\Payments\Transfer;
+use Einvoicing\Traits\VatTrait;
use Einvoicing\Writers\UblWriter;
use InvalidArgumentException;
use UXML\UXML;
@@ -32,7 +33,7 @@ public function import(string $document): Invoice {
$cac = UblWriter::NS_CAC;
$cbc = UblWriter::NS_CBC;
- // BT-24: Specification indentifier
+ // BT-24: Specification identifier
$specificationNode = $xml->get("{{$cbc}}CustomizationID");
if ($specificationNode !== null) {
$specification = $specificationNode->asText();
@@ -45,6 +46,26 @@ public function import(string $document): Invoice {
}
}
+ // Index tax exemption reasons
+ /** @var array */
+ $taxExemptions = [];
+ foreach ($xml->getAll("{{$cac}}TaxTotal/{{$cac}}TaxSubtotal/{{$cac}}TaxCategory") as $node) {
+ $exemptionReasonCodeNode = $node->get("{{$cbc}}TaxExemptionReasonCode");
+ $exemptionReasonNode = $node->get("{{$cbc}}TaxExemptionReason");
+ if ($exemptionReasonCodeNode === null && $exemptionReasonNode === null) continue;
+
+ // Get tax subtotal key
+ $category = $node->get("{{$cbc}}ID")->asText(); // @phan-suppress-current-line PhanPossiblyNonClassMethodCall
+ $rate = (float) $node->get("{{$cbc}}Percent")->asText(); // @phan-suppress-current-line PhanPossiblyNonClassMethodCall
+ $key = "$category:$rate";
+
+ // Save reasons
+ $taxExemptions[$key] = [
+ "code" => ($exemptionReasonCodeNode === null) ? null : $exemptionReasonCodeNode->asText(),
+ "reason" => ($exemptionReasonNode === null) ? null : $exemptionReasonNode->asText(),
+ ];
+ }
+
// BT-23: Business process type
$businessProcessNode = $xml->get("{{$cbc}}ProfileID");
if ($businessProcessNode !== null) {
@@ -108,6 +129,18 @@ public function import(string $document): Invoice {
// BG-14: Invoice period
$this->parsePeriodFields($xml, $invoice);
+ // BT-13: Purchase order reference
+ $purchaseOrderReferenceNode = $xml->get("{{$cac}}OrderReference/{{$cbc}}ID");
+ if ($purchaseOrderReferenceNode !== null) {
+ $invoice->setPurchaseOrderReference($purchaseOrderReferenceNode->asText());
+ }
+
+ // BT-14: Sales order reference
+ $salesOrderReferenceNode = $xml->get("{{$cac}}OrderReference/{{$cbc}}SalesOrderID");
+ if ($salesOrderReferenceNode !== null) {
+ $invoice->setSalesOrderReference($salesOrderReferenceNode->asText());
+ }
+
// Seller node
$sellerNode = $xml->get("{{$cac}}AccountingSupplierParty/{{$cac}}Party");
if ($sellerNode !== null) {
@@ -138,12 +171,24 @@ public function import(string $document): Invoice {
// Allowances and charges
foreach ($xml->getAll("{{$cac}}AllowanceCharge") as $node) {
- $this->addAllowanceOrCharge($invoice, $node);
+ $this->addAllowanceOrCharge($invoice, $node, $taxExemptions);
+ }
+
+ // BT-113: Paid amount
+ $paidAmountNode = $xml->get("{{$cac}}LegalMonetaryTotal/{{$cbc}}PrepaidAmount");
+ if ($paidAmountNode !== null) {
+ $invoice->setPaidAmount((float) $paidAmountNode->asText());
+ }
+
+ // BT-114: Rounding amount
+ $roundingAmountNode = $xml->get("{{$cac}}LegalMonetaryTotal/{{$cbc}}PayableRoundingAmount");
+ if ($roundingAmountNode !== null) {
+ $invoice->setRoundingAmount((float) $roundingAmountNode->asText());
}
// Invoice lines
foreach ($xml->getAll("{{$cac}}InvoiceLine") as $node) {
- $invoice->addLine($this->parseInvoiceLine($node));
+ $invoice->addLine($this->parseInvoiceLine($node, $taxExemptions));
}
return $invoice;
@@ -525,10 +570,11 @@ private function parsePaymentMandateNode(UXML $xml): Mandate {
/**
* Set VAT attributes
- * @param AllowanceOrCharge|InvoiceLine $target Target instance
- * @param UXML $xml XML node
+ * @param VatTrait $target Target instance
+ * @param UXML $xml XML node
+ * @param array &$taxExemptions Tax exemption reasons
*/
- private function setVatAttributes($target, UXML $xml) {
+ private function setVatAttributes($target, UXML $xml, array $taxExemptions) {
$cbc = UblWriter::NS_CBC;
// Tax category
@@ -542,21 +588,27 @@ private function setVatAttributes($target, UXML $xml) {
if ($taxRateNode !== null) {
$target->setVatRate((float) $taxRateNode->asText());
}
+
+ // Tax exemption reasons
+ $key = "{$target->getVatCategory()}:{$target->getVatRate()}";
+ $target->setVatExemptionReasonCode($taxExemptions[$key]['code'] ?? null);
+ $target->setVatExemptionReason($taxExemptions[$key]['reason'] ?? null);
}
/**
* Add allowance or charge
- * @param Invoice|InvoiceLine $target Target instance
- * @param UXML $xml XML node
+ * @param Invoice|InvoiceLine $target Target instance
+ * @param UXML $xml XML node
+ * @param array &$taxExemptions Tax exemption reasons
*/
- private function addAllowanceOrCharge($target, UXML $xml) {
+ private function addAllowanceOrCharge($target, UXML $xml, array &$taxExemptions) {
$allowanceOrCharge = new AllowanceOrCharge();
$cac = UblWriter::NS_CAC;
$cbc = UblWriter::NS_CBC;
// Add instance to invoice
- if ($xml->get("{{$cbc}}ChargeIndicator")->asText() === "true") {
+ if ($xml->get("{{$cbc}}ChargeIndicator")->asText() === "true") { // @phan-suppress-current-line PhanPossiblyNonClassMethodCall
$target->addCharge($allowanceOrCharge);
} else {
$target->addAllowance($allowanceOrCharge);
@@ -577,7 +629,7 @@ private function addAllowanceOrCharge($target, UXML $xml) {
// Amount
$factorNode = $xml->get("{{$cbc}}MultiplierFactorNumeric");
if ($factorNode === null) {
- $amount = (float) $xml->get("{{$cbc}}Amount")->asText();
+ $amount = (float) $xml->get("{{$cbc}}Amount")->asText(); // @phan-suppress-current-line PhanPossiblyNonClassMethodCall
$allowanceOrCharge->setAmount($amount);
} else {
$percent = (float) $factorNode->asText();
@@ -587,17 +639,18 @@ private function addAllowanceOrCharge($target, UXML $xml) {
// VAT attributes
$vatNode = $xml->get("{{$cac}}TaxCategory");
if ($vatNode !== null) {
- $this->setVatAttributes($allowanceOrCharge, $vatNode);
+ $this->setVatAttributes($allowanceOrCharge, $vatNode, $taxExemptions);
}
}
/**
* Parse invoice line
- * @param UXML $xml XML node
- * @return InvoiceLine Invoice line instance
+ * @param UXML $xml XML node
+ * @param array &$taxExemptions Tax exemption reasons
+ * @return InvoiceLine Invoice line instance
*/
- private function parseInvoiceLine(UXML $xml): InvoiceLine {
+ private function parseInvoiceLine(UXML $xml, array &$taxExemptions): InvoiceLine {
$line = new InvoiceLine();
$cac = UblWriter::NS_CAC;
$cbc = UblWriter::NS_CBC;
@@ -632,7 +685,7 @@ private function parseInvoiceLine(UXML $xml): InvoiceLine {
// Allowances and charges
foreach ($xml->getAll("{{$cac}}AllowanceCharge") as $node) {
- $this->addAllowanceOrCharge($line, $node);
+ $this->addAllowanceOrCharge($line, $node, $taxExemptions);
}
// BT-154: Item description
@@ -692,7 +745,7 @@ private function parseInvoiceLine(UXML $xml): InvoiceLine {
// VAT attributes
$vatNode = $xml->get("{{$cac}}Item/{{$cac}}ClassifiedTaxCategory");
if ($vatNode !== null) {
- $this->setVatAttributes($line, $vatNode);
+ $this->setVatAttributes($line, $vatNode, $taxExemptions);
}
// BG-32: Item attributes
diff --git a/src/Traits/InvoiceValidationTrait.php b/src/Traits/InvoiceValidationTrait.php
index 25bc50c..d50a4b3 100644
--- a/src/Traits/InvoiceValidationTrait.php
+++ b/src/Traits/InvoiceValidationTrait.php
@@ -6,6 +6,8 @@
use function array_merge;
use function in_array;
+// @phan-file-suppress PhanPossiblyNonClassMethodCall
+
trait InvoiceValidationTrait {
/**
* Validate invoice
@@ -97,11 +99,6 @@ private function getDefaultRules(): array {
if ($line->getPrice() < 0) return "The Item net price (BT-146) shall NOT be negative";
}
};
- $res['BR-28'] = static function(Invoice $inv) {
- foreach ($inv->getLines() as $line) {
- if ($line->getNetAmount() < 0) return "The Item gross price (BT-148) shall NOT be negative";
- }
- };
$res['BR-31'] = static function(Invoice $inv) {
foreach ($inv->getAllowances() as $allowance) {
if ($allowance->getAmount() === null) {
diff --git a/src/Traits/VatTrait.php b/src/Traits/VatTrait.php
index 36e2754..8e39969 100644
--- a/src/Traits/VatTrait.php
+++ b/src/Traits/VatTrait.php
@@ -4,6 +4,8 @@
trait VatTrait {
protected $vatCategory = "S"; // TODO: add constants
protected $vatRate = null;
+ protected $vatExemptionReasonCode = null;
+ protected $vatExemptionReason = null;
/**
* Get VAT category code
@@ -43,4 +45,44 @@ public function setVatRate(?float $rate): self {
$this->vatRate = $rate;
return $this;
}
+
+
+ /**
+ * Get VAT exemption reason code
+ * @return string|null VAT exemption reason code
+ */
+ public function getVatExemptionReasonCode(): ?string {
+ return $this->vatExemptionReasonCode;
+ }
+
+
+ /**
+ * Set VAT exemption reason code
+ * @param string|null $reasonCode VAT exemption reason code
+ * @return self This instance
+ */
+ public function setVatExemptionReasonCode(?string $reasonCode): self {
+ $this->vatExemptionReasonCode = $reasonCode;
+ return $this;
+ }
+
+
+ /**
+ * Get VAT exemption reason
+ * @return string|null VAT exemption reason expressed as text
+ */
+ public function getVatExemptionReason(): ?string {
+ return $this->vatExemptionReason;
+ }
+
+
+ /**
+ * Set VAT exemption reason
+ * @param string|null $reason VAT exemption reason expressed as text
+ * @return self This instance
+ */
+ public function setVatExemptionReason(?string $reason): self {
+ $this->vatExemptionReason = $reason;
+ return $this;
+ }
}
diff --git a/src/Writers/UblWriter.php b/src/Writers/UblWriter.php
index 458f5b2..b026e5b 100644
--- a/src/Writers/UblWriter.php
+++ b/src/Writers/UblWriter.php
@@ -29,7 +29,7 @@ public function export(Invoice $invoice): string {
'xmlns:cbc' => self::NS_CBC
]);
- // BT-24: Specification indentifier
+ // BT-24: Specification identifier
$specificationIdentifier = $invoice->getSpecification();
if ($specificationIdentifier !== null) {
$xml->add('cbc:CustomizationID', $specificationIdentifier);
@@ -92,6 +92,9 @@ public function export(Invoice $invoice): string {
// BG-14: Invoice period
$this->addPeriodNode($xml, $invoice);
+ // Order reference node
+ $this->addOrderReferenceNode($xml, $invoice);
+
// Seller node
$seller = $invoice->getSeller();
if ($seller !== null) {
@@ -138,7 +141,7 @@ public function export(Invoice $invoice): string {
// Invoice lines
$lines = $invoice->getLines();
foreach ($lines as $i=>$line) {
- $this->addLineNode($xml, $line, $i+1, $invoice);
+ $this->addLineNode($xml, $line, $i+1, $invoice); // @phan-suppress-current-line PhanPartialTypeMismatchArgument
}
return $xml->asXML();
@@ -177,12 +180,36 @@ private function addPeriodNode(UXML $parent, $source) {
}
// Period end date
- if ($startDate !== null) {
+ if ($endDate !== null) {
$xml->add('cbc:EndDate', $endDate->format('Y-m-d'));
}
}
+ /**
+ * Add order reference node
+ * @param UXML $parent Parent element
+ * @param Invoice $invoice Invoice instance
+ */
+ private function addOrderReferenceNode(UXML $parent, Invoice $invoice) {
+ $purchaseOrderReference = $invoice->getPurchaseOrderReference();
+ $salesOrderReference = $invoice->getSalesOrderReference();
+ if ($purchaseOrderReference === null && $salesOrderReference === null) return;
+
+ $orderReferenceNode = $parent->add('cac:OrderReference');
+
+ // BT-13: Purchase order reference
+ if ($purchaseOrderReference !== null) {
+ $orderReferenceNode->add('cbc:ID', $purchaseOrderReference);
+ }
+
+ // BT-14: Sales order reference
+ if ($salesOrderReference !== null) {
+ $orderReferenceNode->add('cbc:SalesOrderID', $salesOrderReference);
+ }
+ }
+
+
/**
* Add amount node
* @param UXML $parent Parent element
@@ -197,12 +224,17 @@ private function addAmountNode(UXML $parent, string $name, float $amount, string
/**
* Add VAT node
- * @param UXML $parent Parent element
- * @param string $name New node name
- * @param string $category VAT category
- * @param int|null $rate VAT rate
+ * @param UXML $parent Parent element
+ * @param string $name New node name
+ * @param string $category VAT category
+ * @param float|null $rate VAT rate
+ * @param string|null $exemptionReasonCode VAT exemption reason code
+ * @param string|null $exemptionReason VAT exemption reason as text
*/
- private function addVatNode(UXML $parent, string $name, string $category, ?int $rate) {
+ private function addVatNode(
+ UXML $parent, string $name, string $category, ?float $rate,
+ ?string $exemptionReasonCode=null, ?string $exemptionReason=null
+ ) {
$xml = $parent->add($name);
// VAT category
@@ -213,6 +245,16 @@ private function addVatNode(UXML $parent, string $name, string $category, ?int $
$xml->add('cbc:Percent', (string) $rate);
}
+ // Exemption reason code
+ if ($exemptionReasonCode !== null) {
+ $xml->add('cbc:TaxExemptionReasonCode', $exemptionReasonCode);
+ }
+
+ // Exemption reason (as text)
+ if ($exemptionReason !== null) {
+ $xml->add('cbc:TaxExemptionReason', $exemptionReason);
+ }
+
// Tax scheme
$xml->add('cac:TaxScheme')->add('cbc:ID', 'VAT');
}
@@ -589,7 +631,7 @@ private function addAllowanceOrCharge(
// Amount
$baseAmount = $atDocumentLevel ?
$invoice->getTotals()->netAmount :
- $line->getNetAmount($invoice->getDecimals('line/netAmount')) ?? 0;
+ $line->getNetAmount($invoice->getDecimals('line/netAmount')) ?? 0; // @phan-suppress-current-line PhanPossiblyNonClassMethodCall
$amount = $item->getEffectiveAmount($baseAmount, $invoice->getDecimals('line/allowanceChargeAmount'));
$this->addAmountNode($xml, 'cbc:Amount', $amount, $invoice->getCurrency());
@@ -621,7 +663,8 @@ private function addTaxTotalNode(UXML $parent, InvoiceTotals $totals) {
$vatBreakdownNode = $xml->add('cac:TaxSubtotal');
$this->addAmountNode($vatBreakdownNode, 'cbc:TaxableAmount', $item->taxableAmount, $totals->currency);
$this->addAmountNode($vatBreakdownNode, 'cbc:TaxAmount', $item->taxAmount, $totals->currency);
- $this->addVatNode($vatBreakdownNode, 'cac:TaxCategory', $item->category, $item->rate);
+ $this->addVatNode($vatBreakdownNode, 'cac:TaxCategory', $item->category, $item->rate,
+ $item->exemptionReasonCode, $item->exemptionReason);
}
}
@@ -633,7 +676,7 @@ private function addTaxTotalNode(UXML $parent, InvoiceTotals $totals) {
*/
private function addDocumentTotalsNode(UXML $parent, InvoiceTotals $totals) {
$xml = $parent->add('cac:LegalMonetaryTotal');
-
+
// Build totals matrix
$totalsMatrix = [
"cbc:LineExtensionAmount" => $totals->netAmount,
diff --git a/tests/Integration/IntegrationTest.php b/tests/Integration/IntegrationTest.php
index 6124032..331b8e6 100644
--- a/tests/Integration/IntegrationTest.php
+++ b/tests/Integration/IntegrationTest.php
@@ -45,4 +45,8 @@ public function testCanRecreatePeppolBaseExample(): void {
public function testCanRecreatePeppolVatExample(): void {
$this->importAndExportInvoice(__DIR__ . "/peppol-vat-s.xml");
}
+
+ public function testCanRecreatePeppolAllowanceExample(): void {
+ $this->importAndExportInvoice(__DIR__ . "/peppol-allowance.xml");
+ }
}
diff --git a/tests/Integration/peppol-allowance.xml b/tests/Integration/peppol-allowance.xml
new file mode 100644
index 0000000..2fe6b63
--- /dev/null
+++ b/tests/Integration/peppol-allowance.xml
@@ -0,0 +1,323 @@
+
+
+
+ urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0
+ urn:fdc:peppol.eu:2017:poacc:billing:01:1.0
+ Snippet1
+ 2017-11-13
+ 2017-12-01
+ 380
+ Please note we have a new phone number: 22 22 22 22
+ 2017-12-01
+ EUR
+ 4025:123:4343
+ 0150abc
+
+ 2017-12-01
+ 2017-12-31
+
+
+
+ 7300010000001
+
+ 99887766
+
+
+ SupplierTradingName Ltd.
+
+
+ Main street 1
+ Postbox 123
+ London
+ GB 123 EW
+
+ GB
+
+
+
+ GB1232434
+
+ VAT
+
+
+
+ SupplierOfficialName Ltd
+ GB983294
+
+
+
+
+
+ 4598375937
+
+ 4598375937
+
+
+ BuyerTradingName AS
+
+
+ Hovedgatan 32
+ Po box 878
+ Stockholm
+ 456 34
+ Södermalm
+
+ SE
+
+
+
+ SE4598375937
+
+ VAT
+
+
+
+ Buyer Official Name
+ 39937423947
+
+
+ Lisa Johnson
+ 23434234
+ lj@buyer.se
+
+
+
+
+ 2017-11-01
+
+ 7300010000001
+
+ Delivery street 2
+ Building 56
+ Stockholm
+ 21234
+ Södermalm
+
+ Gate 15
+
+
+ SE
+
+
+
+
+
+ Delivery party Name
+
+
+
+
+ 30
+ Snippet1
+
+ IBAN32423940
+ AccountName
+
+ BIC324098
+
+
+
+
+ Payment within 10 days, 2% discount
+
+
+ false
+ 95
+ Discount
+ 200
+
+ S
+ 25
+
+ VAT
+
+
+
+
+ true
+ CG
+ Cleaning
+ 20
+ 1189.8
+ 5949
+
+ S
+ 25
+
+ VAT
+
+
+
+
+ 1484.7
+
+ 5938.8
+ 1484.7
+
+ S
+ 25
+
+ VAT
+
+
+
+
+ 1000
+ 0
+
+ E
+ 0
+ Reason for tax exempt
+
+ VAT
+
+
+
+
+
+ 5949
+ 6938.8
+ 8423.5
+ 200
+ 1189.8
+ 1000
+ 7423.5
+
+
+ 1
+ Testing note on line level
+ 10
+ 4040
+ Konteringsstreng
+
+ false
+ 95
+ Discount
+ 101
+
+
+ true
+ CG
+ Cleaning
+ 1
+ 40.4
+ 4040
+
+
+ Description of item
+ item name
+
+ 97iugug876
+
+
+ NO
+
+
+ 09348023
+
+
+ S
+ 25
+
+ VAT
+
+
+
+
+ 410
+
+
+
+ 2
+ Testing note on line level
+ 10
+ 1000
+ Konteringsstreng
+
+ 2017-12-01
+ 2017-12-05
+
+
+ 124
+
+
+ Description of item
+ item name
+
+ 97iugug876
+
+
+ 86776
+
+
+ E
+ 0
+
+ VAT
+
+
+
+ AdditionalItemName
+ AdditionalItemValue
+
+
+
+ 200
+ 2
+
+
+
+ 3
+ Testing note on line level
+ 10
+ 909
+ Konteringsstreng
+
+ 2017-12-01
+ 2017-12-05
+
+
+ 124
+
+
+ false
+ 95
+ Discount
+ 101
+
+
+ true
+ CG
+ Charge
+ 1
+ 9.09
+ 909
+
+
+ Description of item
+ item name
+
+ 97iugug876
+
+
+ 86776
+
+
+ S
+ 25
+
+ VAT
+
+
+
+ AdditionalItemName
+ AdditionalItemValue
+
+
+
+ 100
+
+
+
diff --git a/tests/Integration/peppol-base.xml b/tests/Integration/peppol-base.xml
index 3a3f7ad..dd1e96b 100644
--- a/tests/Integration/peppol-base.xml
+++ b/tests/Integration/peppol-base.xml
@@ -12,6 +12,9 @@
EUR
4025:123:4343
0150abc
+
+ 854777
+
9482348239847239874
diff --git a/tests/InvoiceTest.php b/tests/InvoiceTest.php
index 92bd17f..b01c856 100644
--- a/tests/InvoiceTest.php
+++ b/tests/InvoiceTest.php
@@ -17,7 +17,7 @@ final class InvoiceTest extends TestCase {
private $line;
protected function setUp(): void {
- $this->invoice = (new Invoice)->setRoundingMatrix([null => 2]);
+ $this->invoice = (new Invoice)->setRoundingMatrix(['' => 2]);
$this->line = new InvoiceLine();
}
@@ -56,7 +56,7 @@ public function testDecimalMatrixIsUsed(): void {
$this->invoice->setRoundingMatrix([
"invoice/paidAmount" => 4,
"line/netAmount" => 8,
- null => 3
+ "" => 3
])->setPaidAmount(123.456789)
->setRoundingAmount(987.654321)
->addLine((new InvoiceLine)->setPrice(12.121212121))
diff --git a/tests/Models/InvoiceTotalsTest.php b/tests/Models/InvoiceTotalsTest.php
new file mode 100644
index 0000000..7f6b551
--- /dev/null
+++ b/tests/Models/InvoiceTotalsTest.php
@@ -0,0 +1,63 @@
+invoice = new Invoice();
+ }
+
+ public function testClassConstructors(): void {
+ $line = (new InvoiceLine())
+ ->setName('Test Line')
+ ->setPrice(100);
+ $this->invoice->addLine($line);
+
+ $totalsA = InvoiceTotals::fromInvoice($this->invoice);
+ $totalsB = $this->invoice->getTotals();
+ $this->assertInstanceOf(InvoiceTotals::class, $totalsA);
+ $this->assertInstanceOf(InvoiceTotals::class, $totalsB);
+ $this->assertEquals(100, $totalsA->payableAmount);
+ $this->assertEquals(100, $totalsB->payableAmount);
+ }
+
+ public function testVatExemptionReasons(): void {
+ $firstLine = (new InvoiceLine())
+ ->setName('Line #1')
+ ->setVatCategory('E')
+ ->setVatExemptionReasonCode('VATEX-EU-O')
+ ->setVatExemptionReason('Not subject to VAT');
+ $secondLine = (new InvoiceLine())
+ ->setName('Line #2')
+ ->setVatCategory('E')
+ ->setVatRate(0)
+ ->setVatExemptionReasonCode('VATEX-EU-132-1P');
+ $thirdLine = (clone $firstLine)
+ ->setName('Line #3');
+ $allowance = (new AllowanceOrCharge())
+ ->setReason('Allowance')
+ ->setAmount(100)
+ ->setVatCategory('E')
+ ->setVatExemptionReason('Another reason expressed as text');
+
+ $this->invoice
+ ->addLine($firstLine)
+ ->addLine($secondLine)
+ ->addLine($thirdLine)
+ ->addAllowance($allowance);
+ $totals = $this->invoice->getTotals();
+
+ $this->assertEquals('VATEX-EU-O', $totals->vatBreakdown[0]->exemptionReasonCode);
+ $this->assertEquals('Another reason expressed as text', $totals->vatBreakdown[0]->exemptionReason);
+ $this->assertEquals(null, $totals->vatBreakdown[1]->exemptionReason);
+ $this->assertEquals('VATEX-EU-132-1P', $totals->vatBreakdown[1]->exemptionReasonCode);
+ }
+}
diff --git a/tests/Readers/UblReaderTest.php b/tests/Readers/UblReaderTest.php
index 0c001db..b2135e8 100644
--- a/tests/Readers/UblReaderTest.php
+++ b/tests/Readers/UblReaderTest.php
@@ -17,6 +17,7 @@ protected function setUp(): void {
public function testCanReadInvoice(): void {
$invoice = $this->reader->import(file_get_contents(self::DOCUMENT_PATH));
+ $invoice->validate();
$totals = $invoice->getTotals();
$this->assertEquals(1300, $totals->netAmount);
$this->assertEquals(1325, $totals->taxExclusiveAmount);
diff --git a/tests/Traits/VatTraitTest.php b/tests/Traits/VatTraitTest.php
index 1a841f2..3cddfd0 100644
--- a/tests/Traits/VatTraitTest.php
+++ b/tests/Traits/VatTraitTest.php
@@ -18,4 +18,16 @@ public function testCanReadAndWriteRate(): void {
$this->line->setVatRate(0);
$this->assertEquals(0, $this->line->getVatRate());
}
+
+ public function testCanReadAndWriteExemptions(): void {
+ $category = "E";
+ $reason = "Supply of transport services for sick or injured persons";
+ $reasonCode = "VATEX-EU-132-1P";
+ $this->line->setVatCategory($category);
+ $this->assertEquals($category, $this->line->getVatCategory());
+ $this->line->setVatExemptionReason($reason);
+ $this->assertEquals($reason, $this->line->getVatExemptionReason());
+ $this->line->setVatExemptionReasonCode($reasonCode);
+ $this->assertEquals($reasonCode, $this->line->getVatExemptionReasonCode());
+ }
}