<?php
namespace WbfkExtensions\Documents;
use Shopware\Core\Checkout\Document\DocumentConfiguration;
use Shopware\Core\Checkout\Order\Aggregate\OrderAddress\OrderAddressEntity;
use Shopware\Core\Checkout\Order\Aggregate\OrderDelivery\OrderDeliveryEntity;
use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionEntity;
use Shopware\Core\Checkout\Order\OrderEntity;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Wbfk\DigitalInvoice\Core\Document\DigitalDocumentConfigExtension;
use Wbfk\DigitalInvoice\Event\GenerateDocumentConfigEvent;
use Wbfk\DigitalInvoice\Service\Document\DocumentService;
use Wbfk\DigitalInvoice\Service\Document\ZugferdService;
use Wbfk\DigitalInvoice\Service\Utils\Utils;
use WbfkExtensions\Service\TaxFreeProductService;
class DocumentConfigSubscriber implements EventSubscriberInterface
{
// ToDo: Change to a better (configurable) matching for payment method than the ID
//@formatter:off
private const paymentMethodIds = [
'invoice' => 'e29f15b5d53b4025baaf8ccb404aadbe',
'sepa' => 'fb6bbe50888942efbf43e04f502bddc5',
'direct' => '0280b3cfab6d4006afe3d5c46ef5f166',
];
//@formatter:on
public function __construct(
private readonly TranslatorInterface $translator,
protected readonly EntityRepository $customerRepository,
) {
}
public static function getSubscribedEvents(): array
{
return [
DocumentService::GENERATE_DOCUMENT_CONFIG_EVENT => 'generateDocumentConfig',
];
}
public function generateDocumentConfig(GenerateDocumentConfigEvent $event): void
{
$extConf = DigitalDocumentConfigExtension::fromDocumentConfig($event->documentConfiguration);
if (!$extConf) {
return;
}
$order = $event->order;
$langCode = $order->getLanguage()->getLocale()->getCode();
$shortDateFormatter = new \IntlDateFormatter($langCode, \IntlDateFormatter::SHORT, \IntlDateFormatter::NONE);
$dezimalFormatter = new \NumberFormatter($langCode, \NumberFormatter::DECIMAL);
/** @var string[] $cf */
$cf = $order->getCustomFields();
// Payment Target
/** @var ?OrderTransactionEntity $transaction */
$transaction = $order->getTransactions()->last();
if ($transaction && $transaction->getPaymentMethodId() === self::paymentMethodIds['invoice']) {
$paymentTarget = trim($cf['wbfk_invoice_payment_target'] ?? "14") ?: "14";
$bt20 = $this->translator->trans('documents.paymentTarget', ['%paymentTarget%' => $paymentTarget], null, $langCode);
$extConf->bt20[] = $bt20;
$extConf->pdfComments[] = $bt20;
}
// Skonto / QuickPayment Discount
$discountPercent = ((float)trim($cf['wbfk_invoice_discount'] ?? 0.0)) ?: 0.0;
$discountDays = ((int)trim($cf['wbfk_invoice_discount_days'] ?? 0)) ?: 0;
if ($discountPercent > 0 && $discountDays > 0) {
$extConf->bt20[] = '#SKONTO#TAGE='.$discountDays.'#PROZENT='.Utils::num2($discountPercent).'#';
$extConf->pdfComments[] = $this->translator->trans('documents.earlyPaymentDiscount', [
'%discountDays%' => $discountDays,
'%discountPercent%' => $dezimalFormatter->format($discountPercent),
], null, $langCode);
}
// Payment Received
if (
$transaction
&& $transaction->getPaymentMethodId() !== self::paymentMethodIds['invoice']
&& $transaction->getPaymentMethodId() !== self::paymentMethodIds['sepa']
&& $transaction->getPaymentMethodId() !== self::paymentMethodIds['direct']) {
$bt20 = $this->translator->trans('documents.paymentReceived', [
'%payment_date%' => $shortDateFormatter->format($order->getOrderDate()),
'%payment_method%' => $transaction->getPaymentMethod()->getName(),
], null, $langCode);
$extConf->bt20[] = $bt20;
$extConf->pdfComments[] = $bt20;
}
// Will be charged with SEPA
if ($transaction && $transaction->getPaymentMethodId() == self::paymentMethodIds['sepa']) {
$bt20 = $this->translator->trans('documents.willBeChargedWithSepa', [], null, $langCode);
$extConf->bt20[] = $bt20;
$extConf->pdfComments[] = $bt20;
}
// Our property till payment
if (
$transaction && (
$transaction->getPaymentMethodId() !== self::paymentMethodIds['invoice']
|| $transaction->getPaymentMethodId() !== self::paymentMethodIds['sepa']
|| $transaction->getPaymentMethodId() !== self::paymentMethodIds['direct']
)) {
$bt20 = $this->translator->trans('documents.remainOurPropertyTillPayment', [], null, $langCode);
$extConf->bt20[] = $bt20;
$extConf->pdfComments[] = $bt20;
}
// Leistungszeitraum
$extConf->pdfComments[] = $this->translator->trans('documents.serviceDateEqualInvoiceDate', [], null, $langCode);
$this->setTaxClassificationAndInvoiceNote($extConf, $event->documentConfiguration, $order);
// ToDo: Frage Oliver
// "priceInclVat": "* Grundpreise verstehen sich inkl. gesetzl. Mehrwertsteuer."
// hasBasePrice == true
}
private function setTaxClassificationAndInvoiceNote(DigitalDocumentConfigExtension $extConf, DocumentConfiguration $config, OrderEntity $order): void
{
$orderTaxState = $order->getTaxStatus();
if ($orderTaxState !== 'tax-free') {
$extConf->bt118 = ZugferdService::TAX_CODE_STD;
} elseif ($this->isUsInvoice($order)) {
$extConf->bt118 = ZugferdService::TAX_CODE_EXPORT;
$extConf->pdfComments[] = $this->translator->trans('documents.taxFreeUsArmyDelivery');
} elseif ($this->isIntraCommunityDelivery($config, $order)) {
if ($this->isServiceOnly($order)) {
$extConf->bt118 = ZugferdService::TAX_CODE_REVERSE;
$extConf->pdfComments[] = $this->translator->trans('documents.taxFreeReverseCharge');
} else {
$extConf->bt118 = ZugferdService::TAX_CODE_INTRA_COMMUNITY;
$extConf->pdfComments[] = $this->translator->trans('documents.intraCommunityDelivery');
}
} else {
$extConf->bt118 = ZugferdService::TAX_CODE_EXPORT;
$extConf->pdfComments[] = $this->translator->trans('documents.taxFreeExportDelivery');
}
}
/**
* Note! This does not test, if the order is tax-free, the customer is B2B or B2C, or if tyx-free is configured for the delivery country.
* This only test, if the delivery country is in the list of configured intra community countries.
*
* @param DocumentConfiguration $config
* @param OrderEntity $order
* @return bool
*/
private function isIntraCommunityDelivery(DocumentConfiguration $config, OrderEntity $order): bool
{
$deliveries = $order->getDeliveries();
if (empty($deliveries)) {
return false;
}
/** @var OrderDeliveryEntity $delivery */
$delivery = $deliveries->first();
/** @var OrderAddressEntity $shippingAddress */
$shippingAddress = $delivery->getShippingOrderAddress();
$country = $shippingAddress->getCountry();
if (!$country) {
return false;
}
$confArray = $config->getVars();
return \in_array($country->getId(), $confArray['deliveryCountries'], true);
}
protected function isUsInvoice(OrderEntity $order): bool
{
$criteria = new Criteria([$order->getOrderCustomer()->getCustomer()->getId()]);
$criteria->addAssociation('tags');
$customer = $this->customerRepository->search($criteria, Context::createDefaultContext())->first();
return $customer->getTags()->filterByProperty('name', TaxFreeProductService::TAX_FREE_TAG_NAME)->count() > 0;
}
protected function isServiceOnly(OrderEntity $order): bool
{
$lineItems = $order->getLineItems();
$isServiceOnly = true;
foreach ($lineItems as $lineItem) {
if ($lineItem->getGood()) {
$isServiceOnly = false;
break;
}
}
return $isServiceOnly;
}
}