Files
2026-04-28 15:13:50 +02:00

321 lines
8.8 KiB
PHP

<?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 );
}
}