Repository: horstoeko/zugferd (php)

Issue:
[BUG] ZugferdSettings::addSpecialDecimalPlacesMap doesn't work for BasisPeriodMeasure
By setting `ZugferdSettings::addSpecialDecimalPlacesMap('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradePaymentTerms/ram:ApplicableTradePaymentDiscountTerms/ram:BasisPeriodMeasure',0);`
I still get an result like `<ram:BasisPeriodMeasure unitCode="DAY">14.0</ram:BasisPeriodMeasure>` 

I can't validate my invoices with an BasisPeriodMeasure having decimals. 
The function `ZugferdSettings::addSpecialDecimalPlacesMap()` works fine on different fields, but somehow not on the BasisPeriodMeasure

 Sample code: 
 ZugferdSettings::addSpecialDecimalPlacesMap('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradePaymentTerms/ram:ApplicableTradePaymentDiscountTerms/ram:BasisPeriodMeasure',0);
$document = ZugferdDocumentBuilder::CreateNew(ZugferdProfiles::PROFILE_EXTENDED);
$document->addDocumentPaymentTerm("Fälligkeitsdatum: 05.12.24 ; Skonto 3% bis zum 19.11.24",\DateTime::createFromFormat("d.m.y", "19.11.24"))
    ->addDiscountTermsToPaymentTerms(3,\DateTime::createFromFormat("d.m.y", "05.11.24"), 14, "DAY",2123.53,63.71)

Pull request description:
#169 Handle Decimals of the measure type
# Description

Handle the decimal places of a measure. For that new methods are added to ZugferdSettings:

*  getMeasureDecimals
*  setMeasureDecimals

Added handling of measure-decimals to ``ZugferdTypesHandler``

Fixes #169 

Patch:
diff --git a/examples/ExtendedSimple.php b/examples/ExtendedSimple.php
new file mode 100644
index 0000000..67c7ff9
--- /dev/null
+++ b/examples/ExtendedSimple.php
@@ -0,0 +1,55 @@
+<?php
+
+use horstoeko\zugferd\ZugferdProfiles;
+use horstoeko\zugferd\ZugferdSettings;
+use horstoeko\zugferd\ZugferdDocumentBuilder;
+use horstoeko\zugferd\codelists\ZugferdPaymentMeans;
+
+require dirname(__FILE__) . "/../vendor/autoload.php";
+
+ZugferdSettings::addSpecialDecimalPlacesMap('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradePaymentTerms/ram:ApplicableTradePaymentDiscountTerms/ram:BasisPeriodMeasure', 0);
+
+$document = ZugferdDocumentBuilder::CreateNew(ZugferdProfiles::PROFILE_EXTENDED);
+$document
+    ->setDocumentInformation("471102", "380", \DateTime::createFromFormat("Ymd", "20180305"), "EUR")
+    ->addDocumentNote('Rechnung gemäß Bestellung vom 01.03.2018.')
+    ->addDocumentNote('Lieferant GmbH' . PHP_EOL . 'Lieferantenstraße 20' . PHP_EOL . '80333 München' . PHP_EOL . 'Deutschland' . PHP_EOL . 'Geschäftsführer: Hans Muster' . PHP_EOL . 'Handelsregisternummer: H A 123' . PHP_EOL . PHP_EOL, null, 'REG')
+    ->setDocumentSupplyChainEvent(\DateTime::createFromFormat('Ymd', '20180305'))
+    ->addDocumentPaymentMean(ZugferdPaymentMeans::UNTDID_4461_58, null, null, null, null, null, "DE12500105170648489890", null, null, null)
+    ->setDocumentSeller("Lieferant GmbH", "549910")
+    ->addDocumentSellerGlobalId("4000001123452", "0088")
+    ->addDocumentSellerTaxRegistration("FC", "201/113/40209")
+    ->addDocumentSellerTaxRegistration("VA", "DE123456789")
+    ->setDocumentSellerAddress("Lieferantenstraße 20", "", "", "80333", "München", "DE")
+    ->setDocumentSellerContact("Heinz Mükker", "Buchhaltung", "+49-111-2222222", "+49-111-3333333", "info@lieferant.de")
+    ->setDocumentBuyer("Kunden AG Mitte", "GE2020211")
+    ->setDocumentBuyerReference("34676-342323")
+    ->setDocumentBuyerAddress("Kundenstraße 15", "", "", "69876", "Frankfurt", "DE")
+    ->addDocumentTax("S", "VAT", 275.0, 19.25, 7.0)
+    ->addDocumentTax("S", "VAT", 198.0, 37.62, 19.0)
+    ->setDocumentSummation(529.87, 529.87, 473.00, 0.0, 0.0, 473.00, 56.87, null, 0.0)
+    ->addDocumentPaymentTerm("Zahlbar innerhalb 30 Tagen netto bis 04.04.2018, 3% Skonto innerhalb 10 Tagen bis 15.03.2018")
+    ->addNewPosition("1")
+    ->setDocumentPositionNote("Bemerkung zu Zeile 1")
+    ->setDocumentPositionProductDetails("Trennblätter A4", "", "TB100A4", null, "0160", "4012345001235")
+    ->addDocumentPositionProductCharacteristic("Farbe", "Gelb")
+    ->addDocumentPositionProductClassification("ClassCode", "ClassName", "ListId", "ListVersionId")
+    ->setDocumentPositionProductOriginTradeCountry("CN")
+    ->setDocumentPositionGrossPrice(9.9000)
+    ->setDocumentPositionNetPrice(9.9000)
+    ->setDocumentPositionQuantity(20, "H87")
+    ->addDocumentPositionTax('S', 'VAT', 19)
+    ->setDocumentPositionLineSummation(198.0)
+    ->addNewPosition("2")
+    ->setDocumentPositionNote("Bemerkung zu Zeile 2")
+    ->setDocumentPositionProductDetails("Joghurt Banane", "", "ARNR2", null, "0160", "4000050986428")
+    ->addDocumentPositionProductCharacteristic("Suesstoff", "Nein")
+    ->addDocumentPositionProductClassification("ClassCode", "ClassName", "ListId", "ListVersionId")
+    ->SetDocumentPositionGrossPrice(5.5000)
+    ->SetDocumentPositionNetPrice(5.5000)
+    ->SetDocumentPositionQuantity(50, "H87")
+    ->AddDocumentPositionTax('S', 'VAT', 7)
+    ->SetDocumentPositionLineSummation(275.0)
+    ->addDocumentPaymentTerm("Fälligkeitsdatum: 05.12.24 ; Skonto 3% bis zum 19.11.24", \DateTime::createFromFormat("d.m.y", "19.11.24"))
+    ->addDiscountTermsToPaymentTerms(3, \DateTime::createFromFormat("d.m.y", "05.11.24"), 14, "DAY", 2123.53, 63.71)
+    ->writeFile(dirname(__FILE__) . "/factur-x.xml");
diff --git a/src/ZugferdSettings.php b/src/ZugferdSettings.php
index 63e4ca8..5ee7941 100644
--- a/src/ZugferdSettings.php
+++ b/src/ZugferdSettings.php
@@ -43,6 +43,13 @@ class ZugferdSettings
      */
     protected static $percentDecimals = 2;
 
+    /**
+     * The number of decimals for measure values
+     *
+     * @var integer
+     */
+    protected static $measureDecimals = 2;
+
     /**
      * The decimal separator
      *
@@ -141,6 +148,27 @@ class ZugferdSettings
         static::$percentDecimals = $percentDecimals;
     }
 
+    /**
+     * Get the number of decimals to use for measure values
+     *
+     * @return integer
+     */
+    public static function getMeasureDecimals(): int
+    {
+        return static::$measureDecimals;
+    }
+
+    /**
+     * Set the number of decimals to use for measure values
+     *
+     * @param  integer $measureDecimals
+     * @return void
+     */
+    public static function setMeasureDecimals(int $measureDecimals): void
+    {
+        static::$measureDecimals = $measureDecimals;
+    }
+
     /**
      * Get the decimal separator
      *
diff --git a/src/jms/ZugferdTypesHandler.php b/src/jms/ZugferdTypesHandler.php
index 0b36a5d..6f3a663 100644
--- a/src/jms/ZugferdTypesHandler.php
+++ b/src/jms/ZugferdTypesHandler.php
@@ -9,10 +9,7 @@
 
 namespace horstoeko\zugferd\jms;
 
-use DOMElement;
-use DOMText;
 use horstoeko\zugferd\ZugferdSettings;
-use JMS\Serializer\Context;
 use JMS\Serializer\GraphNavigator;
 use JMS\Serializer\Handler\SubscribingHandlerInterface;
 use JMS\Serializer\XmlSerializationVisitor;
@@ -147,6 +144,30 @@ class ZugferdTypesHandler implements SubscribingHandlerInterface
                 'type' => 'horstoeko\zugferd\entities\extended\udt\IndicatorType',
                 'method' => 'serializeIndicatorType'
             ],
+            [
+                'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
+                'format' => 'xml',
+                'type' => 'horstoeko\zugferd\entities\basic\udt\MeasureType',
+                'method' => 'serializeMeasureType'
+            ],
+            [
+                'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
+                'format' => 'xml',
+                'type' => 'horstoeko\zugferd\entities\basicwl\udt\MeasureType',
+                'method' => 'serializeMeasureType'
+            ],
+            [
+                'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
+                'format' => 'xml',
+                'type' => 'horstoeko\zugferd\entities\en16931\udt\MeasureType',
+                'method' => 'serializeMeasureType'
+            ],
+            [
+                'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
+                'format' => 'xml',
+                'type' => 'horstoeko\zugferd\entities\extended\udt\MeasureType',
+                'method' => 'serializeMeasureType'
+            ],
         ];
     }
 
@@ -225,6 +246,33 @@ class ZugferdTypesHandler implements SubscribingHandlerInterface
         return $node;
     }
 
+    /**
+     * Serialize a meassure value
+     * The valze will be serialized (by default) with a precission of 2 digits
+     *
+     * @param XmlSerializationVisitor $visitor
+     * @param mixed                   $data
+     */
+    public function serializeMeasureType(XmlSerializationVisitor $visitor, $data)
+    {
+        $node = $visitor->getDocument()->createTextNode(
+            number_format(
+                $data->value(),
+                ZugferdSettings::getSpecialDecimalPlacesMap($visitor->getCurrentNode()->getNodePath(), ZugferdSettings::getMeasureDecimals()),
+                ZugferdSettings::getDecimalSeparator(),
+                ZugferdSettings::getThousandsSeparator()
+            )
+        );
+
+        if ($data->getUnitCode() != null) {
+            $attr = $visitor->getDocument()->createAttribute("unitCode");
+            $attr->value = $data->getUnitCode();
+            $visitor->getCurrentNode()->appendChild($attr);
+        }
+
+        return $node;
+    }
+
     /**
      * Serialize a inditcator
      * False and true values will be serialized correctly (false won't be serialized


Test patch:
diff --git a/tests/testcases/SettingsTest.php b/tests/testcases/SettingsTest.php
index c068c67..1874be5 100644
--- a/tests/testcases/SettingsTest.php
+++ b/tests/testcases/SettingsTest.php
@@ -17,6 +17,7 @@ class SettingsTest extends TestCase
         ZugferdSettings::setAmountDecimals(2);
         ZugferdSettings::setQuantityDecimals(2);
         ZugferdSettings::setPercentDecimals(2);
+        ZugferdSettings::setMeasureDecimals(2);
         ZugferdSettings::setDecimalSeparator(".");
         ZugferdSettings::setThousandsSeparator("");
         ZugferdSettings::setIccProfileFilename("sRGB_v4_ICC.icc");
@@ -61,6 +62,19 @@ class SettingsTest extends TestCase
         $this->assertEquals(3, $property->getValue());
     }
 
+    public function testMeasureDecimals(): void
+    {
+        $this->assertEquals(2, ZugferdSettings::getMeasureDecimals());
+
+        ZugferdSettings::setMeasureDecimals(3);
+
+        $this->assertEquals(3, ZugferdSettings::getMeasureDecimals());
+
+        $property = $this->getPrivatePropertyFromClassname(ZugferdSettings::class, "measureDecimals");
+
+        $this->assertEquals(3, $property->getValue());
+    }
+
     public function testDecimalSeparator(): void
     {
         $this->assertEquals(".", ZugferdSettings::getDecimalSeparator());