first commit

This commit is contained in:
2026-04-28 15:13:50 +02:00
commit a95acc355b
63745 changed files with 9487948 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
<?php
namespace WPO\IPS\EDI\Syntaxes\Cii\Abstracts;
use WPO\IPS\EDI\Interfaces\FormatInterface;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
abstract class AbstractCiiFormat implements FormatInterface {
public string $syntax = 'cii';
/**
* Get the format context
*
* @return string
*/
abstract public function get_context(): string;
/**
* Get the format type code
*
* @return string
*/
abstract public function get_type_code(): string;
/**
* Get the format root element
*
* @return string
*/
abstract public function get_root_element(): string;
/**
* Get the format additional attributes
*
* @return array
*/
abstract public function get_additional_attributes(): array;
/**
* Get the format namespaces
*
* @return array
*/
abstract public function get_namespaces(): array;
/**
* Get the format structure
*
* @return array
*/
abstract public function get_structure(): array;
}

View File

@@ -0,0 +1,85 @@
<?php
namespace WPO\IPS\EDI\Syntaxes\Cii\Abstracts;
use WPO\IPS\EDI\Abstracts\AbstractHandler;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
abstract class AbstractCiiHandler extends AbstractHandler {
/**
* Get the date format code for CII documents.
*
* @return string The default date format code.
*/
public function get_date_format_code(): string {
return apply_filters( 'wpo_ips_edi_cii_document_date_format_code', '102', $this );
}
/**
* Validate CII date format.
*
* @param string $value The date value to validate.
* @param string $format The date format (102, 610, or 616).
* @return bool True if valid, false otherwise.
*/
public function validate_date_format( string $value, string $format = '102' ): bool {
$allowed_formats = array( '102', '610', '616' );
if ( ! in_array( $format, $allowed_formats, true ) ) {
return false;
}
switch ( $format ) {
case '102': // YYYYMMDD
if ( strlen( $value ) !== 8 || ! ctype_digit( $value ) ) {
return false;
}
$year = (int) substr( $value, 0, 4 );
$month = (int) substr( $value, 4, 2 );
$day = (int) substr( $value, 6, 2 );
return checkdate( $month, $day, $year );
case '610': // YYYYMM
if ( strlen( $value ) !== 6 || ! ctype_digit( $value ) ) {
return false;
}
$year = (int) substr( $value, 0, 4 );
$month = (int) substr( $value, 4, 2 );
return ( $year > 0 && $month >= 1 && $month <= 12 );
case '616': // YYYYWW
if ( strlen( $value ) !== 6 || ! ctype_digit( $value ) ) {
return false;
}
$year = (int) substr( $value, 0, 4 );
$week = (int) substr( $value, 4, 2 );
return ( $year > 0 && $week >= 1 && $week <= 53 );
}
return false;
}
/**
* Converts a CII date format code to a PHP date format.
*
* @param string $code CII date format code (e.g. 102, 610, 616).
* @return string PHP-compatible date format string.
*/
public function get_php_date_format_from_code( string $code ): string {
switch ( $code ) {
case '102': // Full date: YYYYMMDD
return 'Ymd';
case '610': // Year + Month: YYYYMM
return 'Ym';
case '616': // Year + Week: YYYYWW (ISO)
return 'oW';
default:
return 'Ymd'; // Fallback
}
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace WPO\IPS\EDI\Syntaxes\Cii\Formats\CiiD16B\Handlers\SupplyChainTradeTransaction;
use WPO\IPS\EDI\Syntaxes\Cii\Handlers\SupplyChainTradeTransaction\ApplicableHeaderTradeSettlementHandler as BaseApplicableHeaderTradeSettlementHandler;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class ApplicableHeaderTradeSettlementHandler extends BaseApplicableHeaderTradeSettlementHandler {
/**
* Handle the data and return the formatted output.
*
* @param array $data The data to be handled.
* @param array $options Additional options for handling.
* @return array
*/
public function handle( array $data, array $options = array() ): array {
$applicable_header_trade_settlement = array(
'name' => 'ram:ApplicableHeaderTradeSettlement',
'value' => array_filter( array(
$this->get_payment_reference(),
$this->get_invoice_currency_code(),
$this->get_payment_means(),
$this->get_trade_tax(),
$this->get_payment_terms(),
$this->get_monetary_summation(),
) ),
);
$data[] = apply_filters( 'wpo_ips_edi_cii_applicable_header_trade_settlement', $applicable_header_trade_settlement, $data, $options, $this );
return $data;
}
/**
* Get the invoice currency code.
*
* @return array|null
*/
public function get_invoice_currency_code(): ?array {
$currency = $this->document->order->get_currency();
$invoice_currency_code = array(
'name' => 'ram:InvoiceCurrencyCode',
'value' => wpo_ips_edi_sanitize_string( $currency ),
);
return apply_filters( 'wpo_ips_edi_cii_invoice_currency_code', $invoice_currency_code, $this );
}
}

View File

@@ -0,0 +1,127 @@
<?php
namespace WPO\IPS\EDI\Syntaxes\Cii\Formats\CiiD16B;
use WPO\IPS\EDI\Syntaxes\Cii\Abstracts\AbstractCiiFormat;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Invoice extends AbstractCiiFormat {
public string $type = 'invoice';
public string $slug = 'cii-invoice-d16b';
public string $name = 'CII Invoice D16B';
/**
* Get the context
*
* @return string
*/
public function get_context(): string {
return 'urn:cen.eu:en16931:2017';
}
/**
* Get the type code
*
* @return string
*/
public function get_type_code(): string {
return '380';
}
/**
* Get the root element
*
* @return string
*/
public function get_root_element(): string {
return 'rsm:CrossIndustryInvoice';
}
/**
* Get the additional attributes
*
* @return array
*/
public function get_additional_attributes(): array {
return array();
}
/**
* Get the namespaces
*
* @return array
*/
public function get_namespaces(): array {
return array(
'rsm' => 'urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100',
'qdt' => 'urn:un:unece:uncefact:data:standard:QualifiedDataType:100',
'udt' => 'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100',
'ram' => 'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100',
);
}
/**
* Get the structure
*
* @return array
*/
public function get_structure(): array {
return array(
// Exchanged Document Context
'exchanged_document_context' => array(
'enabled' => true,
'handler' => \WPO\IPS\EDI\Syntaxes\Cii\Handlers\ExchangedDocumentContextHandler::class,
),
// Exchanged Document
'exchanged_document' => array(
'enabled' => true,
'handler' => \WPO\IPS\EDI\Syntaxes\Cii\Handlers\ExchangedDocumentHandler::class,
),
// Header Trade Delivery
'header_trade_delivery' => array(
'enabled' => false,
'handler' => \WPO\IPS\EDI\Syntaxes\Cii\Handlers\HeaderTradeDeliveryHandler::class,
),
// Supply Chain Trade Transaction
'included_supply_chain_trade_line_item' => array(
'enabled' => true,
'handler' => \WPO\IPS\EDI\Syntaxes\Cii\Handlers\SupplyChainTradeTransaction\IncludedSupplyChainTradeLineItemHandler::class,
'options' => array(
'root' => 'rsm:SupplyChainTradeTransaction',
),
),
'applicable_header_trade_agreement' => array(
'enabled' => true,
'handler' => \WPO\IPS\EDI\Syntaxes\Cii\Handlers\SupplyChainTradeTransaction\ApplicableHeaderTradeAgreementHandler::class,
'options' => array(
'root' => 'rsm:SupplyChainTradeTransaction',
),
),
'applicable_header_trade_delivery' => array(
'enabled' => true,
'handler' => \WPO\IPS\EDI\Syntaxes\Cii\Handlers\SupplyChainTradeTransaction\ApplicableHeaderTradeDeliveryHandler::class,
'options' => array(
'root' => 'rsm:SupplyChainTradeTransaction',
),
),
'applicable_header_trade_settlement' => array(
'enabled' => true,
'handler' => \WPO\IPS\EDI\Syntaxes\Cii\Formats\CiiD16B\Handlers\SupplyChainTradeTransaction\ApplicableHeaderTradeSettlementHandler::class,
'options' => array(
'root' => 'rsm:SupplyChainTradeTransaction',
),
),
);
}
}

View File

@@ -0,0 +1,136 @@
<?php
namespace WPO\IPS\EDI\Syntaxes\Cii\Formats\FacturX1p0;
use WPO\IPS\EDI\Interfaces\HybridFormatInterface;
use WPO\IPS\EDI\Syntaxes\Cii\Formats\CiiD16B\Invoice as CiiD16BInvoice;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Invoice extends CiiD16BInvoice implements HybridFormatInterface {
public string $slug = 'factur-x-invoice-1p0';
public string $name = 'Factur-X Invoice 1.0';
/**
* Get RDF metadata for embedding XML in PDF/A-3.
*
* Schema: https://www.pdflib.com/fileadmin/pdf-knowledge-base/zugferd/Factur-X_extension_schema.xmp
*
* @return string RDF metadata.
*/
public function get_rdf_metadata(): string {
// FacturX actual description
$rdf = sprintf( '<rdf:Description rdf:about="" xmlns:%s="%s">', $this->get_prefix(), $this->get_namespace() ) . "\n";
$rdf .= sprintf( ' <%s:DocumentType>%s</%s:DocumentType>', $this->get_prefix(), strtoupper( $this->get_document_type() ), $this->get_prefix() ) . "\n";
$rdf .= sprintf( ' <%s:DocumentFileName>%s</%s:DocumentFileName>', $this->get_prefix(), $this->get_document_filename(), $this->get_prefix() ) . "\n";
$rdf .= sprintf( ' <%s:Version>%s</%s:Version>', $this->get_prefix(), $this->get_version(), $this->get_prefix() ) . "\n";
$rdf .= sprintf( ' <%s:ConformanceLevel>%s</%s:ConformanceLevel>', $this->get_prefix(), strtoupper( $this->get_conformance_level() ), $this->get_prefix() ) . "\n";
$rdf .= '</rdf:Description>' . "\n\n";
// PDF/A Extension schema
$rdf .= '<rdf:Description rdf:about=""' . "\n";
$rdf .= ' xmlns:pdfaExtension="http://www.aiim.org/pdfa/ns/extension/"' . "\n";
$rdf .= ' xmlns:pdfaSchema="http://www.aiim.org/pdfa/ns/schema#"' . "\n";
$rdf .= ' xmlns:pdfaProperty="http://www.aiim.org/pdfa/ns/property#">' . "\n";
$rdf .= ' <pdfaExtension:schemas>' . "\n";
$rdf .= ' <rdf:Bag>' . "\n";
$rdf .= ' <rdf:li rdf:parseType="Resource">' . "\n";
$rdf .= ' <pdfaSchema:schema>Factur-X PDFA Extension Schema</pdfaSchema:schema>' . "\n";
$rdf .= ' <pdfaSchema:namespaceURI>' . $this->get_namespace() . '</pdfaSchema:namespaceURI>' . "\n";
$rdf .= ' <pdfaSchema:prefix>' . $this->get_prefix() . '</pdfaSchema:prefix>' . "\n";
$rdf .= ' <pdfaSchema:property>' . "\n";
$rdf .= ' <rdf:Seq>' . "\n";
$rdf .= ' <rdf:li rdf:parseType="Resource">' . "\n";
$rdf .= ' <pdfaProperty:name>DocumentFileName</pdfaProperty:name>' . "\n";
$rdf .= ' <pdfaProperty:valueType>Text</pdfaProperty:valueType>' . "\n";
$rdf .= ' <pdfaProperty:category>external</pdfaProperty:category>' . "\n";
$rdf .= ' <pdfaProperty:description>name of the embedded XML invoice file</pdfaProperty:description>' . "\n";
$rdf .= ' </rdf:li>' . "\n";
$rdf .= ' <rdf:li rdf:parseType="Resource">' . "\n";
$rdf .= ' <pdfaProperty:name>DocumentType</pdfaProperty:name>' . "\n";
$rdf .= ' <pdfaProperty:valueType>Text</pdfaProperty:valueType>' . "\n";
$rdf .= ' <pdfaProperty:category>external</pdfaProperty:category>' . "\n";
$rdf .= ' <pdfaProperty:description>INVOICE</pdfaProperty:description>' . "\n";
$rdf .= ' </rdf:li>' . "\n";
$rdf .= ' <rdf:li rdf:parseType="Resource">' . "\n";
$rdf .= ' <pdfaProperty:name>Version</pdfaProperty:name>' . "\n";
$rdf .= ' <pdfaProperty:valueType>Text</pdfaProperty:valueType>' . "\n";
$rdf .= ' <pdfaProperty:category>external</pdfaProperty:category>' . "\n";
$rdf .= ' <pdfaProperty:description>The actual version of the Factur-X XML schema</pdfaProperty:description>' . "\n";
$rdf .= ' </rdf:li>' . "\n";
$rdf .= ' <rdf:li rdf:parseType="Resource">' . "\n";
$rdf .= ' <pdfaProperty:name>ConformanceLevel</pdfaProperty:name>' . "\n";
$rdf .= ' <pdfaProperty:valueType>Text</pdfaProperty:valueType>' . "\n";
$rdf .= ' <pdfaProperty:category>external</pdfaProperty:category>' . "\n";
$rdf .= ' <pdfaProperty:description>The conformance level of the embedded Factur-X data</pdfaProperty:description>' . "\n";
$rdf .= ' </rdf:li>' . "\n";
$rdf .= ' </rdf:Seq>' . "\n";
$rdf .= ' </pdfaSchema:property>' . "\n";
$rdf .= ' </rdf:li>' . "\n";
$rdf .= ' </rdf:Bag>' . "\n";
$rdf .= ' </pdfaExtension:schemas>' . "\n";
$rdf .= '</rdf:Description>' . "\n";
return $rdf;
}
/**
* Get the filename for this format.
*
* @return string The filename.
*/
public function get_document_filename(): string {
return 'factur-x.xml';
}
/**
* Get the slug for this format.
*
* @return string The slug.
*/
public function get_document_type(): string {
return strtoupper( $this->type );
}
/**
* Get the conformance level for this format.
*
* - Can be: MINIMUM, BASIC WL, EN 16931
*
* @return string The conformance level.
*/
public function get_conformance_level(): string {
return 'EN 16931';
}
/**
* Get the version of this format.
*
* @return string The version of the format.
*/
public function get_version(): string {
return '1.0'; // not 1p0
}
/**
* Get the prefix for this format.
*
* @return string The prefix.
*/
public function get_prefix(): string {
return 'fx';
}
/**
* Get the namespace for this format.
*
* @return string The namespace.
*/
public function get_namespace(): string {
return 'urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#';
}
}

View File

@@ -0,0 +1,136 @@
<?php
namespace WPO\IPS\EDI\Syntaxes\Cii\Formats\Zugferd1p0;
use WPO\IPS\EDI\Interfaces\HybridFormatInterface;
use WPO\IPS\EDI\Syntaxes\Cii\Formats\CiiD16B\Invoice as CiiD16BInvoice;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Invoice extends CiiD16BInvoice implements HybridFormatInterface {
public string $slug = 'zugferd-invoice-1p0';
public string $name = 'ZUGFeRD Invoice 1.0';
/**
* Get RDF metadata for embedding XML in PDF/A-3.
*
* Schema: https://www.pdflib.com/fileadmin/pdf-knowledge-base/zugferd/ZUGFeRD1_extension_schema.xmp
*
* @return string RDF metadata.
*/
public function get_rdf_metadata(): string {
// ZUGFeRD actual description
$rdf = sprintf( '<rdf:Description rdf:about="" xmlns:%s="%s">', $this->get_prefix(), $this->get_namespace() ) . "\n";
$rdf .= sprintf( ' <%s:DocumentType>%s</%s:DocumentType>', $this->get_prefix(), strtoupper( $this->get_document_type() ), $this->get_prefix() ) . "\n";
$rdf .= sprintf( ' <%s:DocumentFileName>%s</%s:DocumentFileName>', $this->get_prefix(), $this->get_document_filename(), $this->get_prefix() ) . "\n";
$rdf .= sprintf( ' <%s:Version>%s</%s:Version>', $this->get_prefix(), $this->get_version(), $this->get_prefix() ) . "\n";
$rdf .= sprintf( ' <%s:ConformanceLevel>%s</%s:ConformanceLevel>', $this->get_prefix(), strtoupper( $this->get_conformance_level() ), $this->get_prefix() ) . "\n";
$rdf .= '</rdf:Description>' . "\n\n";
// PDF/A Extension Schema
$rdf .= '<rdf:Description rdf:about=""' . "\n";
$rdf .= ' xmlns:pdfaExtension="http://www.aiim.org/pdfa/ns/extension/"' . "\n";
$rdf .= ' xmlns:pdfaSchema="http://www.aiim.org/pdfa/ns/schema#"' . "\n";
$rdf .= ' xmlns:pdfaProperty="http://www.aiim.org/pdfa/ns/property#">' . "\n";
$rdf .= ' <pdfaExtension:schemas>' . "\n";
$rdf .= ' <rdf:Bag>' . "\n";
$rdf .= ' <rdf:li rdf:parseType="Resource">' . "\n";
$rdf .= ' <pdfaSchema:schema>ZUGFeRD PDFA Extension Schema</pdfaSchema:schema>' . "\n";
$rdf .= ' <pdfaSchema:namespaceURI>' . $this->get_namespace() . '</pdfaSchema:namespaceURI>' . "\n";
$rdf .= ' <pdfaSchema:prefix>' . $this->get_prefix() . '</pdfaSchema:prefix>' . "\n";
$rdf .= ' <pdfaSchema:property>' . "\n";
$rdf .= ' <rdf:Seq>' . "\n";
$rdf .= ' <rdf:li rdf:parseType="Resource">' . "\n";
$rdf .= ' <pdfaProperty:name>DocumentFileName</pdfaProperty:name>' . "\n";
$rdf .= ' <pdfaProperty:valueType>Text</pdfaProperty:valueType>' . "\n";
$rdf .= ' <pdfaProperty:category>external</pdfaProperty:category>' . "\n";
$rdf .= ' <pdfaProperty:description>name of the embedded XML invoice file</pdfaProperty:description>' . "\n";
$rdf .= ' </rdf:li>' . "\n";
$rdf .= ' <rdf:li rdf:parseType="Resource">' . "\n";
$rdf .= ' <pdfaProperty:name>DocumentType</pdfaProperty:name>' . "\n";
$rdf .= ' <pdfaProperty:valueType>Text</pdfaProperty:valueType>' . "\n";
$rdf .= ' <pdfaProperty:category>external</pdfaProperty:category>' . "\n";
$rdf .= ' <pdfaProperty:description>INVOICE</pdfaProperty:description>' . "\n";
$rdf .= ' </rdf:li>' . "\n";
$rdf .= ' <rdf:li rdf:parseType="Resource">' . "\n";
$rdf .= ' <pdfaProperty:name>Version</pdfaProperty:name>' . "\n";
$rdf .= ' <pdfaProperty:valueType>Text</pdfaProperty:valueType>' . "\n";
$rdf .= ' <pdfaProperty:category>external</pdfaProperty:category>' . "\n";
$rdf .= ' <pdfaProperty:description>The actual version of the ZUGFeRD XML schema</pdfaProperty:description>' . "\n";
$rdf .= ' </rdf:li>' . "\n";
$rdf .= ' <rdf:li rdf:parseType="Resource">' . "\n";
$rdf .= ' <pdfaProperty:name>ConformanceLevel</pdfaProperty:name>' . "\n";
$rdf .= ' <pdfaProperty:valueType>Text</pdfaProperty:valueType>' . "\n";
$rdf .= ' <pdfaProperty:category>external</pdfaProperty:category>' . "\n";
$rdf .= ' <pdfaProperty:description>The conformance level of the embedded ZUGFeRD data</pdfaProperty:description>' . "\n";
$rdf .= ' </rdf:li>' . "\n";
$rdf .= ' </rdf:Seq>' . "\n";
$rdf .= ' </pdfaSchema:property>' . "\n";
$rdf .= ' </rdf:li>' . "\n";
$rdf .= ' </rdf:Bag>' . "\n";
$rdf .= ' </pdfaExtension:schemas>' . "\n";
$rdf .= '</rdf:Description>' . "\n";
return $rdf;
}
/**
* Get the filename for this format.
*
* @return string The filename.
*/
public function get_document_filename(): string {
return 'ZUGFeRD_Invoice.xml';
}
/**
* Get the slug for this format.
*
* @return string The slug.
*/
public function get_document_type(): string {
return strtoupper( $this->type );
}
/**
* Get the conformance level for this format.
*
* - Can be: BASIC, BASIC WL, COMFORT, EXTENDED
*
* @return string The conformance level.
*/
public function get_conformance_level(): string {
return 'BASIC'; // EN 16931 not available
}
/**
* Get the version of this format.
*
* @return string The version of the format.
*/
public function get_version(): string {
return '1p0'; // not 1.0
}
/**
* Get the prefix for this format.
*
* @return string The prefix.
*/
public function get_prefix(): string {
return 'zf';
}
/**
* Get the namespace for this format.
*
* @return string The namespace.
*/
public function get_namespace(): string {
return 'urn:ferd:pdfa:CrossIndustryDocument:invoice:1p0#';
}
}

View File

@@ -0,0 +1,136 @@
<?php
namespace WPO\IPS\EDI\Syntaxes\Cii\Formats\Zugferd2p0;
use WPO\IPS\EDI\Interfaces\HybridFormatInterface;
use WPO\IPS\EDI\Syntaxes\Cii\Formats\CiiD16B\Invoice as CiiD16BInvoice;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Invoice extends CiiD16BInvoice implements HybridFormatInterface {
public string $slug = 'zugferd-invoice-2p0';
public string $name = 'ZUGFeRD Invoice 2.0';
/**
* Get RDF metadata for embedding XML in PDF/A-3.
*
* Schema: https://www.pdflib.com/fileadmin/pdf-knowledge-base/zugferd/ZUGFeRD2_extension_schema.xmp
*
* @return string RDF metadata.
*/
public function get_rdf_metadata(): string {
// ZUGFeRD actual description
$rdf = sprintf( '<rdf:Description rdf:about="" xmlns:%s="%s">', $this->get_prefix(), $this->get_namespace() ) . "\n";
$rdf .= sprintf( ' <%s:DocumentType>%s</%s:DocumentType>', $this->get_prefix(), strtoupper( $this->get_document_type() ), $this->get_prefix() ) . "\n";
$rdf .= sprintf( ' <%s:DocumentFileName>%s</%s:DocumentFileName>', $this->get_prefix(), $this->get_document_filename(), $this->get_prefix() ) . "\n";
$rdf .= sprintf( ' <%s:Version>%s</%s:Version>', $this->get_prefix(), $this->get_version(), $this->get_prefix() ) . "\n";
$rdf .= sprintf( ' <%s:ConformanceLevel>%s</%s:ConformanceLevel>', $this->get_prefix(), strtoupper( $this->get_conformance_level() ), $this->get_prefix() ) . "\n";
$rdf .= '</rdf:Description>' . "\n\n";
// PDF/A Extension schema
$rdf .= '<rdf:Description rdf:about=""' . "\n";
$rdf .= ' xmlns:pdfaExtension="http://www.aiim.org/pdfa/ns/extension/"' . "\n";
$rdf .= ' xmlns:pdfaSchema="http://www.aiim.org/pdfa/ns/schema#"' . "\n";
$rdf .= ' xmlns:pdfaProperty="http://www.aiim.org/pdfa/ns/property#">' . "\n";
$rdf .= ' <pdfaExtension:schemas>' . "\n";
$rdf .= ' <rdf:Bag>' . "\n";
$rdf .= ' <rdf:li rdf:parseType="Resource">' . "\n";
$rdf .= ' <pdfaSchema:schema>ZUGFeRD PDFA Extension Schema</pdfaSchema:schema>' . "\n";
$rdf .= ' <pdfaSchema:namespaceURI>' . $this->get_namespace() . '</pdfaSchema:namespaceURI>' . "\n";
$rdf .= ' <pdfaSchema:prefix>' . $this->get_prefix() . '</pdfaSchema:prefix>' . "\n";
$rdf .= ' <pdfaSchema:property>' . "\n";
$rdf .= ' <rdf:Seq>' . "\n";
$rdf .= ' <rdf:li rdf:parseType="Resource">' . "\n";
$rdf .= ' <pdfaProperty:name>DocumentFileName</pdfaProperty:name>' . "\n";
$rdf .= ' <pdfaProperty:valueType>Text</pdfaProperty:valueType>' . "\n";
$rdf .= ' <pdfaProperty:category>external</pdfaProperty:category>' . "\n";
$rdf .= ' <pdfaProperty:description>name of the embedded XML invoice file</pdfaProperty:description>' . "\n";
$rdf .= ' </rdf:li>' . "\n";
$rdf .= ' <rdf:li rdf:parseType="Resource">' . "\n";
$rdf .= ' <pdfaProperty:name>DocumentType</pdfaProperty:name>' . "\n";
$rdf .= ' <pdfaProperty:valueType>Text</pdfaProperty:valueType>' . "\n";
$rdf .= ' <pdfaProperty:category>external</pdfaProperty:category>' . "\n";
$rdf .= ' <pdfaProperty:description>INVOICE</pdfaProperty:description>' . "\n";
$rdf .= ' </rdf:li>' . "\n";
$rdf .= ' <rdf:li rdf:parseType="Resource">' . "\n";
$rdf .= ' <pdfaProperty:name>Version</pdfaProperty:name>' . "\n";
$rdf .= ' <pdfaProperty:valueType>Text</pdfaProperty:valueType>' . "\n";
$rdf .= ' <pdfaProperty:category>external</pdfaProperty:category>' . "\n";
$rdf .= ' <pdfaProperty:description>The actual version of the ZUGFeRD XML schema</pdfaProperty:description>' . "\n";
$rdf .= ' </rdf:li>' . "\n";
$rdf .= ' <rdf:li rdf:parseType="Resource">' . "\n";
$rdf .= ' <pdfaProperty:name>ConformanceLevel</pdfaProperty:name>' . "\n";
$rdf .= ' <pdfaProperty:valueType>Text</pdfaProperty:valueType>' . "\n";
$rdf .= ' <pdfaProperty:category>external</pdfaProperty:category>' . "\n";
$rdf .= ' <pdfaProperty:description>The conformance level of the embedded ZUGFeRD data</pdfaProperty:description>' . "\n";
$rdf .= ' </rdf:li>' . "\n";
$rdf .= ' </rdf:Seq>' . "\n";
$rdf .= ' </pdfaSchema:property>' . "\n";
$rdf .= ' </rdf:li>' . "\n";
$rdf .= ' </rdf:Bag>' . "\n";
$rdf .= ' </pdfaExtension:schemas>' . "\n";
$rdf .= '</rdf:Description>' . "\n";
return $rdf;
}
/**
* Get the filename for this format.
*
* @return string The filename.
*/
public function get_document_filename(): string {
return 'zugferd-invoice.xml';
}
/**
* Get the slug for this format.
*
* @return string The slug.
*/
public function get_document_type(): string {
return strtoupper( $this->type );
}
/**
* Get the conformance level for this format.
*
* - Can be: MINIMUM, BASIC WL, EN 16931
*
* @return string The conformance level.
*/
public function get_conformance_level(): string {
return 'EN 16931';
}
/**
* Get the version of this format.
*
* @return string The version of the format.
*/
public function get_version(): string {
return '2p0'; // not 2.0
}
/**
* Get the prefix for this format.
*
* @return string The prefix.
*/
public function get_prefix(): string {
return 'zf';
}
/**
* Get the namespace for this format.
*
* @return string The namespace.
*/
public function get_namespace(): string {
return 'urn:zugferd:pdfa:CrossIndustryDocument:invoice:2p0#';
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace WPO\IPS\EDI\Syntaxes\Cii\Handlers;
use WPO\IPS\EDI\Syntaxes\Cii\Abstracts\AbstractCiiHandler;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class ExchangedDocumentContextHandler extends AbstractCiiHandler {
/**
* Handle the data and return the formatted output.
*
* @param array $data The data to be handled.
* @param array $options Additional options for handling.
* @return array
*/
public function handle( array $data, array $options = array() ): array {
$exchanged_document_context = array(
'name' => 'rsm:ExchangedDocumentContext',
'value' => array(
array(
'name' => 'ram:GuidelineSpecifiedDocumentContextParameter',
'value' => array(
array(
'name' => 'ram:ID',
'value' => $this->document->format_document->get_context(),
),
),
),
),
);
$data[] = apply_filters( 'wpo_ips_edi_cii_exchanged_document_context', $exchanged_document_context, $data, $options, $this );
return $data;
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace WPO\IPS\EDI\Syntaxes\Cii\Handlers;
use WPO\IPS\EDI\Syntaxes\Cii\Abstracts\AbstractCiiHandler;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class ExchangedDocumentHandler extends AbstractCiiHandler {
/**
* Handle the data and return the formatted output.
*
* @param array $data The data to be handled.
* @param array $options Additional options for handling.
* @return array
*/
public function handle( array $data, array $options = array() ): array {
$date_format_code = $this->get_date_format_code();
$php_date_format = $this->get_php_date_format_from_code( $date_format_code );
$number_instance = $this->document->order_document->get_number();
$date_instance = $this->document->order_document->get_date();
$exchanged_document = array(
'name' => 'rsm:ExchangedDocument',
'value' => array(
array(
'name' => 'ram:ID',
'value' => ! empty( $number_instance ) ? $number_instance->get_formatted() : '',
),
array(
'name' => 'ram:TypeCode',
'value' => $this->document->get_type_code(),
),
array(
'name' => 'ram:IssueDateTime',
'value' => array(
array(
'name' => 'udt:DateTimeString',
'value' => ! empty( $date_instance ) ? $date_instance->date_i18n( $php_date_format ) : '',
'attributes' => array(
'format' => $date_format_code,
),
),
),
),
),
);
$data[] = apply_filters( 'wpo_ips_edi_cii_exchanged_document', $exchanged_document, $data, $options, $this );
return $data;
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace WPO\IPS\EDI\Syntaxes\Cii\Handlers;
use WPO\IPS\EDI\Syntaxes\Cii\Abstracts\AbstractCiiHandler;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class HeaderTradeDeliveryHandler extends AbstractCiiHandler {
/**
* Handle the data and return the formatted output.
*
* @param array $data The data to be handled.
* @param array $options Additional options for handling.
* @return array
*/
public function handle( array $data, array $options = array() ): array {
$order = $this->document->order;
$delivery_date = apply_filters( 'wpo_ips_edi_cii_delivery_date', null, $order, $this );
if ( empty( $delivery_date ) ) {
return $data;
}
$date_format_code = $this->get_date_format_code();
$php_date_format = $this->get_php_date_format_from_code( $date_format_code );
$delivery_date = $this->normalize_date( $delivery_date, $php_date_format );
if ( ! $this->validate_date_format( $delivery_date, $date_format_code ) ) {
wpo_ips_edi_log(
sprintf(
'CII ApplicableHeaderTradeDelivery: Invalid delivery date format for %s in order %d.',
$delivery_date,
$order->get_id()
),
'error'
);
return $data;
}
$delivery_node = array(
'name' => 'ram:ApplicableHeaderTradeDelivery',
'value' => array(
array(
'name' => 'ram:ActualDeliverySupplyChainEvent',
'value' => array(
array(
'name' => 'ram:OccurrenceDateTime',
'value' => array(
'name' => 'udt:DateTimeString',
'value' => $delivery_date,
'attributes' => array(
'format' => $date_format_code,
),
),
),
),
),
),
);
$data[] = apply_filters( 'wpo_ips_edi_cii_header_trade_delivery', $delivery_node, $data, $options, $this );
return $data;
}
}

View File

@@ -0,0 +1,259 @@
<?php
namespace WPO\IPS\EDI\Syntaxes\Cii\Handlers\SupplyChainTradeTransaction;
use WPO\IPS\EDI\Syntaxes\Cii\Abstracts\AbstractCiiHandler;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class ApplicableHeaderTradeAgreementHandler extends AbstractCiiHandler {
/**
* Handle the data and return the formatted output.
*
* @param array $data The data to be handled.
* @param array $options Additional options for handling.
* @return array
*/
public function handle( array $data, array $options = array() ): array {
$applicable_header_trade_agreement = array(
'name' => 'ram:ApplicableHeaderTradeAgreement',
'value' => array_filter( array(
$this->get_seller_trade_party(),
$this->get_buyer_trade_party(),
$this->get_contract_referenced_document(),
) ),
);
$data[] = apply_filters( 'wpo_ips_edi_cii_applicable_header_trade_agreement', $applicable_header_trade_agreement, $data, $options, $this );
return $data;
}
/**
* Get the seller trade party details.
*
* @return array|null
*/
public function get_seller_trade_party(): ?array {
$seller = apply_filters( 'wpo_ips_edi_cii_seller_data', array(
'name' => wpo_ips_edi_sanitize_string( $this->get_supplier_identifiers_data( 'shop_name' ) ),
'vat_number' => $this->get_supplier_identifiers_data( 'vat_number' ),
'postcode' => $this->get_supplier_identifiers_data( 'shop_address_postcode' ),
'address_line' => wpo_ips_edi_sanitize_string( $this->get_supplier_identifiers_data( 'shop_address_line_1' ) ),
'city' => wpo_ips_edi_sanitize_string( $this->get_supplier_identifiers_data( 'shop_address_city' ) ),
'country_code' => wpo_ips_edi_sanitize_string( $this->get_supplier_identifiers_data( 'shop_address_country' ) ),
), $this );
$name = wpo_ips_edi_sanitize_string( (string) ( $seller['name'] ?? '' ) );
$vat_number = (string) ( $seller['vat_number'] ?? '' );
if ( empty( $name ) ) {
wpo_ips_edi_log( 'CII ApplicableHeaderTradeAgreementHandler: Seller name is empty. Please check your shop settings.', 'error' );
return null;
}
if ( empty( $vat_number ) ) {
wpo_ips_edi_log( 'CII ApplicableHeaderTradeAgreementHandler: VAT number is empty. Please check your shop settings.', 'error' );
return null;
}
$postcode = (string) ( $seller['postcode'] ?? '' );
$address_line_1 = (string) ( $seller['address_line'] ?? '' );
$address_city = (string) ( $seller['city'] ?? '' );
$country_code = (string) ( $seller['country_code'] ?? '' );
$seller_trade_party = array(
'name' => 'ram:SellerTradeParty',
'value' => array(
array(
'name' => 'ram:Name',
'value' => $name,
),
array(
'name' => 'ram:SpecifiedLegalOrganization',
'value' => array(
array(
'name' => 'ram:ID',
'value' => $vat_number,
),
),
),
array(
'name' => 'ram:PostalTradeAddress',
'value' => array(
array(
'name' => 'ram:PostcodeCode',
'value' => $postcode,
),
array(
'name' => 'ram:LineOne',
'value' => $address_line_1,
),
array(
'name' => 'ram:CityName',
'value' => $address_city,
),
array(
'name' => 'ram:CountryID',
'value' => $country_code,
),
),
),
array(
'name' => 'ram:SpecifiedTaxRegistration',
'value' => array(
array(
'name' => 'ram:ID',
'value' => $vat_number,
'attributes' => array(
'schemeID' => 'VA',
),
),
),
),
),
);
return apply_filters( 'wpo_ips_edi_cii_seller_trade_party', $seller_trade_party, $this );
}
/**
* Get the buyer trade party details.
*
* @return array|null
*/
public function get_buyer_trade_party(): ?array {
$order = \wpo_ips_edi_get_parent_order( $this->document->order );
$customer_party_name = $order ? $order->get_formatted_billing_full_name() : '';
$billing_company = $order ? $order->get_billing_company() : '';
$vat_number = $this->get_order_customer_vat_number( $order );
if ( ! empty( $billing_company ) ) {
$customer_party_name = $billing_company;
}
// Buyer Name
$buyer_trade_party = array(
'name' => 'ram:BuyerTradeParty',
'value' => array(
array(
'name' => 'ram:Name',
'value' => wpo_ips_edi_sanitize_string( $customer_party_name ),
),
),
);
// Legal Organization (if company)
if ( ! empty( $billing_company ) ) {
$legal_organization = array();
if ( ! empty( $vat_number ) ) {
$legal_organization[] = array(
'name' => 'ram:ID',
'value' => $vat_number,
);
} else {
wpo_ips_edi_log(
sprintf(
'CII ApplicableHeaderTradeAgreementHandler: VAT number is empty for buyer in order %d.',
$order->get_id()
),
'error'
);
}
$legal_organization[] = array(
'name' => 'ram:TradingBusinessName',
'value' => wpo_ips_edi_sanitize_string( $billing_company ),
);
$buyer_trade_party['value'][] = array(
'name' => 'ram:SpecifiedLegalOrganization',
'value' => $legal_organization,
);
}
$postcode = $order->get_billing_postcode() ?: '';
$address_line_1 = wpo_ips_edi_sanitize_string( $order->get_billing_address_1() ?: '' );
$address_line_2 = wpo_ips_edi_sanitize_string( $order->get_billing_address_2() ?: '' );
$address_city = wpo_ips_edi_sanitize_string( $order->get_billing_city() ?: '' );
$country_code = $order->get_billing_country() ?: '';
// Postal Address
$buyer_trade_party['value'][] = array(
'name' => 'ram:PostalTradeAddress',
'value' => array(
array(
'name' => 'ram:PostcodeCode',
'value' => $postcode,
),
array(
'name' => 'ram:LineOne',
'value' => $address_line_1,
),
array(
'name' => 'ram:LineTwo',
'value' => $address_line_2,
),
array(
'name' => 'ram:CityName',
'value' => $address_city,
),
array(
'name' => 'ram:CountryID',
'value' => $country_code,
),
),
);
// VAT number
if ( ! empty( $vat_number ) ) {
$buyer_trade_party['value'][] = array(
'name' => 'ram:SpecifiedTaxRegistration',
'value' => array(
array(
'name' => 'ram:ID',
'value' => $vat_number,
'attributes' => array(
'schemeID' => 'VA',
),
),
),
);
}
return apply_filters( 'wpo_ips_edi_cii_buyer_trade_party', $buyer_trade_party, $this );
}
/**
* Get the contract referenced document.
*
* @return array|null
*/
public function get_contract_referenced_document(): ?array {
$order = $this->document->order;
$reference_id = apply_filters( 'wpo_ips_edi_cii_contract_reference_id', null, $order, $this );
if ( empty( $reference_id ) ) {
return null;
}
$contract_document = array(
'name' => 'ram:ContractReferencedDocument',
'value' => array(
array(
'name' => 'ram:IssuerAssignedID',
'value' => wpo_ips_edi_sanitize_string( $reference_id ),
),
),
);
return apply_filters( 'wpo_ips_edi_cii_contract_referenced_document', $contract_document, $this );
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace WPO\IPS\EDI\Syntaxes\Cii\Handlers\SupplyChainTradeTransaction;
use WPO\IPS\EDI\Syntaxes\Cii\Abstracts\AbstractCiiHandler;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class ApplicableHeaderTradeDeliveryHandler extends AbstractCiiHandler {
/**
* Handle the data and return the formatted output.
*
* @param array $data The data to be handled.
* @param array $options Additional options for handling.
* @return array
*/
public function handle( array $data, array $options = array() ): array {
$applicable_header_trade_delivery = array(
'name' => 'ram:ApplicableHeaderTradeDelivery',
'value' => array(),
);
$data[] = apply_filters( 'wpo_ips_edi_cii_applicable_header_trade_delivery', $applicable_header_trade_delivery, $data, $options, $this );
return $data;
}
}

View File

@@ -0,0 +1,320 @@
<?php
namespace WPO\IPS\EDI\Syntaxes\Cii\Handlers\SupplyChainTradeTransaction;
use WPO\IPS\EDI\Syntaxes\Cii\Abstracts\AbstractCiiHandler;
use WPO\IPS\EDI\Standards\EN16931;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class ApplicableHeaderTradeSettlementHandler extends AbstractCiiHandler {
/**
* Handle the data and return the formatted output.
*
* @param array $data The data to be handled.
* @param array $options Additional options for handling.
* @return array
*/
public function handle( array $data, array $options = array() ): array {
$applicable_header_trade_settlement = array(
'name' => 'ram:ApplicableHeaderTradeSettlement',
'value' => array_filter( array(
$this->get_payment_reference(),
$this->get_payment_means(),
$this->get_trade_tax(),
$this->get_payment_terms(),
$this->get_monetary_summation(),
) ),
);
$data[] = apply_filters( 'wpo_ips_edi_cii_applicable_header_trade_settlement', $applicable_header_trade_settlement, $data, $options, $this );
return $data;
}
/**
* Get the payment reference for the order.
*
* @return array|null
*/
public function get_payment_reference(): ?array {
$order = $this->document->order;
$reference = $order->get_order_number(); // Default to WooCommerce order number
$reference = apply_filters( 'wpo_ips_edi_cii_payment_reference', $reference, $order, $this );
if ( empty( $reference ) ) {
wpo_ips_edi_log(
sprintf(
'CII ApplicableHeaderTradeSettlementHandler: Payment reference is empty in order %d.',
$order->get_id()
),
'error'
);
return null;
}
$payment_reference = array(
'name' => 'ram:PaymentReference',
'value' => wpo_ips_edi_sanitize_string( $reference ),
);
return apply_filters( 'wpo_ips_edi_cii_payment_reference', $payment_reference, $this );
}
/**
* Get the payment means data for the order.
*
* @return array|null
*/
public function get_payment_means(): ?array {
$payment = $this->get_payment_means_data();
// If no usable payment data, exit early
if ( empty( $payment['type_code'] ) ) {
wpo_ips_edi_log(
sprintf(
'CII ApplicableHeaderTradeSettlementHandler: No usable payment means data found in order %d.',
$this->document->order->get_id()
),
'error'
);
return null;
}
$payment_means = array(
'name' => 'ram:SpecifiedTradeSettlementPaymentMeans',
'value' => array(
array(
'name' => 'ram:TypeCode',
'value' => $payment['type_code'],
),
),
);
// Add account
if ( ! empty( $payment['iban'] ) || ! empty( $payment['account_number'] ) ) {
$name = ! empty( $payment['iban'] ) ? 'ram:IBANID' : 'ram:ProprietaryID';
$value = ! empty( $payment['iban'] )
? strtoupper( preg_replace( '/\s+/', '', $payment['iban'] ) )
: $payment['account_number'];
$payment_means['value'][] = array(
'name' => 'ram:PayeePartyCreditorFinancialAccount',
'value' => array(
array(
'name' => $name,
'value' => $value,
),
),
);
}
// Add transaction ID if applicable (e.g., PayPal, Stripe)
if ( ! empty( $payment['transaction_id'] ) ) {
$payment_means['value'][] = array(
'name' => 'ram:Information',
'value' => wpo_ips_edi_sanitize_string( $payment['transaction_id'] ),
);
}
// Add BIC if available (only relevant when IBAN is used)
if ( ! empty( $payment['bic'] ) ) {
$payment_means['value'][] = array(
'name' => 'ram:PayeeSpecifiedCreditorFinancialInstitution',
'value' => array(
array(
'name' => 'ram:BICID',
'value' => strtoupper( $payment['bic'] ),
),
),
);
}
return apply_filters( 'wpo_ips_edi_cii_payment_means', $payment_means, $this );
}
/**
* Get the trade tax data for the order.
*
* @return array|null
*/
public function get_trade_tax(): ?array {
$tax_reasons = EN16931::get_vatex();
$grouped_tax_data = $this->get_grouped_order_tax_data();
$trade_tax = array();
foreach ( $grouped_tax_data as $item ) {
$percent = (float) ( $item['percentage'] ?? 0 );
$category = strtoupper( (string) ( $item['category'] ?? '' ) );
$reason = strtoupper( (string) ( $item['reason'] ?? 'NONE' ) );
$scheme = strtoupper( (string) ( $item['scheme'] ?? 'VAT' ) );
$basis_raw = (float) ( $item['total_ex'] ?? 0 );
$basis = wc_round_tax_total( $basis_raw );
$tax = wc_round_tax_total( $basis_raw * $percent / 100 );
// Skip empty non-Z groups.
$is_z = ( 'Z' === $category );
if ( 0.0 === $basis && 0.0 === $tax && ! $is_z ) {
continue;
}
$node = array(
'name' => 'ram:ApplicableTradeTax',
'value' => array(
array(
'name' => 'ram:CalculatedAmount',
'value' => $this->format_decimal( $tax ),
),
array(
'name' => 'ram:TypeCode',
'value' => $scheme ?: 'VAT',
),
array(
'name' => 'ram:BasisAmount',
'value' => $this->format_decimal( $basis ),
),
array(
'name' => 'ram:CategoryCode',
'value' => $category ?: 'Z',
),
array(
'name' => 'ram:RateApplicablePercent',
'value' => $this->format_decimal( $percent, 1 ),
),
),
);
// Only emit exemption data for 0% non-Z with an explicit reason.
if ( 0.0 === $percent && 'Z' !== $category && 'NONE' !== $reason ) {
$reason_mapped = ! empty( $tax_reasons[ $reason ] ) ? $tax_reasons[ $reason ] : $reason;
$node['value'][] = array(
'name' => 'ram:ExemptionReasonCode',
'value' => $reason_mapped,
);
$node['value'][] = array(
'name' => 'ram:ExemptionReason',
'value' => $reason_mapped,
);
}
$trade_tax[] = $node;
}
if ( empty( $trade_tax ) ) {
return null;
}
return apply_filters( 'wpo_ips_edi_cii_trade_tax', $trade_tax, $this );
}
/**
* Get the payment terms for the order.
*
* @return array|null
*/
public function get_payment_terms(): ?array {
$due_date_timestamp = is_callable( array( $this->document->order_document, 'get_due_date' ) )
? $this->document->order_document->get_due_date()
: 0;
if ( empty( $due_date_timestamp ) ) {
return null;
}
$date_format_code = $this->get_date_format_code();
$php_date_format = $this->get_php_date_format_from_code( $date_format_code );
$due_date = $this->normalize_date( $due_date_timestamp, $php_date_format );
if ( ! $this->validate_date_format( $due_date, $date_format_code ) ) {
wpo_ips_edi_log( 'CII ApplicableHeaderTradeSettlementHandler: Invalid due date format.', 'error' );
return null;
}
$payment_terms = array(
'name' => 'ram:SpecifiedTradePaymentTerms',
'value' => array(
array(
'name' => 'ram:DueDateDateTime',
'value' => array(
array(
'name' => 'udt:DateTimeString',
'value' => $due_date,
'attributes' => array(
'format' => $date_format_code,
),
),
),
),
),
);
return apply_filters( 'wpo_ips_edi_cii_payment_terms', $payment_terms, $this );
}
/**
* Get the monetary summation for the order.
*
* @return array|null
*/
public function get_monetary_summation(): ?array {
$totals = $this->get_order_payment_totals( $this->document->order );
$currency = $this->document->order->get_currency();
$line_total = isset( $totals['lines_net'] )
? $totals['lines_net']
: $totals['total_exc_tax'];
$monetary_summation = array(
'name' => 'ram:SpecifiedTradeSettlementHeaderMonetarySummation',
'value' => array(
array(
'name' => 'ram:LineTotalAmount',
'value' => $this->format_decimal( $line_total ),
),
array(
'name' => 'ram:TaxBasisTotalAmount',
'value' => $this->format_decimal( $totals['total_exc_tax'] ),
),
array(
'name' => 'ram:TaxTotalAmount',
'value' => $this->format_decimal( $totals['total_tax'] ),
'attributes' => array(
'currencyID' => $currency,
),
),
array(
'name' => 'ram:GrandTotalAmount',
'value' => $this->format_decimal( $totals['total_inc_tax'] ),
),
),
);
// Only output TotalPrepaidAmount when there is an actual prepayment.
if ( $totals['prepaid_amount'] > 0 ) {
$monetary_summation['value'][] = array(
'name' => 'ram:TotalPrepaidAmount',
'value' => $this->format_decimal( $totals['prepaid_amount'] ),
);
}
// Only include RoundingAmount when materially non-zero.
if ( abs( $totals['rounding_diff'] ) >= 0.01 ) {
$monetary_summation['value'][] = array(
'name' => 'ram:RoundingAmount',
'value' => $this->format_decimal( $totals['rounding_diff'] ),
);
}
$monetary_summation['value'][] = array(
'name' => 'ram:DuePayableAmount',
'value' => $this->format_decimal( $totals['payable_amount'] ),
);
return apply_filters( 'wpo_ips_edi_cii_monetary_summation', $monetary_summation, $this );
}
}

View File

@@ -0,0 +1,208 @@
<?php
namespace WPO\IPS\EDI\Syntaxes\Cii\Handlers\SupplyChainTradeTransaction;
use WPO\IPS\EDI\Syntaxes\Cii\Abstracts\AbstractCiiHandler;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class IncludedSupplyChainTradeLineItemHandler extends AbstractCiiHandler {
/**
* Handle the data and return the formatted output.
*
* @param array $data The data to be handled.
* @param array $options Additional options for handling.
* @return array
*/
public function handle( array $data, array $options = array() ): array {
$order = $this->document->order;
$items = $order->get_items( array( 'line_item', 'fee', 'shipping' ) );
foreach ( $items as $item_id => $item ) {
// Resolve tax meta for this line
$meta = $this->resolve_item_tax_meta( $item );
$tax_nodes = array(
array(
'name' => 'ram:ApplicableTradeTax',
'value' => array(
array(
'name' => 'ram:TypeCode',
'value' => $meta['scheme'],
),
array(
'name' => 'ram:CategoryCode',
'value' => $meta['category'],
),
array(
'name' => 'ram:RateApplicablePercent',
'value' => $this->format_decimal( $meta['percentage'], 1 ),
),
),
),
);
// Price parts
$parts = $this->compute_item_price_parts( $item, false );
// Round gross/net units first (numeric), then derive discount, then recompute net.
$gross_unit_f = (float) $this->format_decimal( $parts['gross_unit'], 2 );
$net_unit_f = (float) $this->format_decimal( $parts['net_unit'], 2 );
$unit_discount_f = $gross_unit_f - $net_unit_f;
if ( $unit_discount_f < 0 ) {
$unit_discount_f = 0.0;
}
$unit_discount_f = (float) $this->format_decimal( $unit_discount_f, 2 );
// Recompute net from gross - discount to guarantee equality in XML.
$net_unit_f = $gross_unit_f - $unit_discount_f;
$gross_unit = $this->format_decimal( $gross_unit_f, 2 );
$net_unit = $this->format_decimal( $net_unit_f, 2 );
$unit_discount = $this->format_decimal( $unit_discount_f, 2 );
$price_children = array(
array(
'name' => 'ram:ChargeAmount',
'value' => $net_unit,
),
array(
'name' => 'ram:BasisQuantity',
'value' => 1,
'attributes' => array(
'unitCode' => 'C62'
)
),
);
// Only products can have a price-level discount (already reflected in net price)
// if ( $unit_discount > 0 && is_a( $item, 'WC_Order_Item_Product' ) ) {
// $price_children[] = array(
// 'name' => 'ram:AppliedTradeAllowanceCharge',
// 'value' => array(
// array(
// 'name' => 'ram:ChargeIndicator',
// 'value' => array(
// array(
// 'name' => 'udt:Indicator',
// 'value' => 'false'
// ),
// ),
// ),
// array(
// 'name' => 'ram:ActualAmount',
// 'value' => $unit_discount,
// ),
// array(
// 'name' => 'ram:BasisAmount',
// 'value' => $gross_unit,
// ),
// ),
// );
// }
// Build SpecifiedTradeProduct
$product_value = array(
array(
'name' => 'ram:Name',
'value' => wpo_ips_edi_sanitize_string( $item->get_name() ),
),
);
// Optionally append ApplicableProductCharacteristic from meta
if ( wpo_ips_edi_include_item_meta() ) {
$meta_rows = $this->get_item_meta( $item );
if ( ! empty( $meta_rows ) ) {
foreach ( $meta_rows as $row ) {
$product_value[] = array(
'name' => 'ram:ApplicableProductCharacteristic',
'value' => array(
array(
'name' => 'ram:Description',
'value' => $row['name'],
),
array(
'name' => 'ram:Value',
'value' => $row['value'],
),
),
);
}
}
}
$quantity_value = $parts['qty'];
// Use Woos net_total for the line total amount.
$net_line_total_f = (float) $this->format_decimal( $parts['net_total'], 2 );
$net_line_total = $this->format_decimal( $net_line_total_f, 2 );
$line_item = array(
'name' => 'ram:IncludedSupplyChainTradeLineItem',
'value' => array(
array(
'name' => 'ram:AssociatedDocumentLineDocument',
'value' => array(
array(
'name' => 'ram:LineID',
'value' => $item_id,
),
),
),
array(
'name' => 'ram:SpecifiedTradeProduct',
'value' => $product_value,
),
array(
'name' => 'ram:SpecifiedLineTradeAgreement',
'value' => array(
array(
'name' => 'ram:NetPriceProductTradePrice',
'value' => $price_children,
),
),
),
array(
'name' => 'ram:SpecifiedLineTradeDelivery',
'value' => array(
array(
'name' => 'ram:BilledQuantity',
'value' => $quantity_value,
'attributes' => array(
'unitCode' => 'C62',
),
),
),
),
array(
'name' => 'ram:SpecifiedLineTradeSettlement',
'value' => array_merge(
$tax_nodes,
array(
array(
'name' => 'ram:SpecifiedTradeSettlementLineMonetarySummation',
'value' => array(
array(
'name' => 'ram:LineTotalAmount',
'value' => $net_line_total,
),
),
),
)
),
),
),
);
$data[] = apply_filters( 'wpo_ips_edi_cii_included_supply_chain_trade_line_item', $line_item, $data, $options, $item, $this );
}
return $data;
}
}

View File

@@ -0,0 +1,2 @@
<?php
// Silence is golden.