Fixes+
This commit is contained in:
parent
3a0019a1b9
commit
d598a9214f
@ -9,7 +9,7 @@ PSC\Shop\EntityBundle\Entity\Product:
|
||||
pos: <numberBetween(1, 200)>
|
||||
notEdit: false
|
||||
private: false
|
||||
taxClass: 19
|
||||
mwert: 19
|
||||
price: 0
|
||||
set_config: '{}'
|
||||
shop: '@shop_1'
|
||||
@ -23,7 +23,7 @@ PSC\Shop\EntityBundle\Entity\Product:
|
||||
notEdit: false
|
||||
pos: <numberBetween(1, 200)>
|
||||
private: false
|
||||
taxClass: 19
|
||||
mwert: 19
|
||||
price: 0
|
||||
set_config: '{}'
|
||||
shop: '@shop_1'
|
||||
@ -37,7 +37,7 @@ PSC\Shop\EntityBundle\Entity\Product:
|
||||
notEdit: false
|
||||
pos: <numberBetween(1, 200)>
|
||||
private: false
|
||||
taxClass: 19
|
||||
mwert: 19
|
||||
price: 0
|
||||
set_config: '\[{"article_id": "7996f00d-a5e6-4915-970d-9f7c4287cd92"}, {"article_id": "7996f00d-a5e6-4915-970d-9f7c4287cd91"} ]'
|
||||
shop: '@shop_1'
|
||||
@ -51,7 +51,7 @@ PSC\Shop\EntityBundle\Entity\Product:
|
||||
type: 2
|
||||
pos: <numberBetween(1, 200)>
|
||||
private: false
|
||||
taxClass: 19
|
||||
mwert: 19
|
||||
price: 20
|
||||
set_config: '{}'
|
||||
shop: '@shop_1'
|
||||
@ -65,7 +65,7 @@ PSC\Shop\EntityBundle\Entity\Product:
|
||||
pos: <numberBetween(1, 200)>
|
||||
notEdit: false
|
||||
private: false
|
||||
taxClass: 19
|
||||
mwert: 19
|
||||
set_config: '{}'
|
||||
shop: '@shop_1'
|
||||
calcXml: >
|
||||
@ -116,7 +116,7 @@ PSC\Shop\EntityBundle\Entity\Product:
|
||||
pos: <numberBetween(1, 200)>
|
||||
notEdit: false
|
||||
private: false
|
||||
taxClass: 19
|
||||
mwert: 19
|
||||
set_config: '{}'
|
||||
shop: '@shop_1'
|
||||
calcXml: >
|
||||
|
||||
@ -34,4 +34,39 @@ class VoucherItemRepository extends EntityRepository
|
||||
->getQuery()->execute();
|
||||
return $isDeleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count unused codes for a voucher
|
||||
*
|
||||
* @param int $voucherUid
|
||||
* @return int
|
||||
*/
|
||||
public function countUnusedCodes(int $voucherUid): int
|
||||
{
|
||||
return (int) $this->createQueryBuilder('vi')
|
||||
->select('COUNT(vi.uid)')
|
||||
->where('vi.voucher = :voucherId')
|
||||
->andWhere('vi.used = false')
|
||||
->setParameter('voucherId', $voucherUid)
|
||||
->getQuery()
|
||||
->getSingleScalarResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find unused VoucherItems (for display only, not for locking)
|
||||
*
|
||||
* @param int $voucherUid
|
||||
* @param int $limit
|
||||
* @return array
|
||||
*/
|
||||
public function findUnusedCodes(int $voucherUid, int $limit = 100): array
|
||||
{
|
||||
return $this->createQueryBuilder('vi')
|
||||
->where('vi.voucher = :voucherId')
|
||||
->andWhere('vi.used = false')
|
||||
->setParameter('voucherId', $voucherUid)
|
||||
->setMaxResults($limit)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,9 +63,7 @@ readonly class Calc
|
||||
$order->addTax($order->getShipping()->getCalcPrice()->tax);
|
||||
foreach ($order->getPositions() as $position) {
|
||||
$position->getProduct()->setShopUuid($order->getShop()->getUuid());
|
||||
if (
|
||||
$this->productTypeRegistry->getProductType($position->getProduct()->getSpecialProductTypeObject()->getTyp())
|
||||
) {
|
||||
if ($this->productTypeRegistry->getProductType($position->getProduct()->getSpecialProductTypeObject()->getTyp())) {
|
||||
$specialProductTransformer = $this->productTypeRegistry
|
||||
->getProductType($position->getProduct()->getSpecialProductTypeObject()->getTyp())
|
||||
->getProducer();
|
||||
@ -77,7 +75,6 @@ readonly class Calc
|
||||
$specialProductTransformer->calcPriceForOrderPosition($position);
|
||||
}
|
||||
}
|
||||
|
||||
$priceNet = $priceNet->plus(Money::ofMinor($position->getPrice()->getAllNet(), 'EUR'));
|
||||
$priceVat = $priceVat->plus(Money::ofMinor($position->getPrice()->getAllVat(), 'EUR'));
|
||||
$priceGross = $priceGross->plus(Money::ofMinor($position->getPrice()->getAllGross(), 'EUR'));
|
||||
|
||||
@ -5,37 +5,44 @@ namespace PSC\Shop\OrderBundle\Service;
|
||||
use PSC\Shop\EntityBundle\Document\Country;
|
||||
use PSC\Shop\EntityBundle\Repository\CountryRepository;
|
||||
use PSC\Shop\OrderBundle\Model\Order\Tax;
|
||||
use PSC\System\SettingsBundle\Service\Shop;
|
||||
|
||||
class VatCalc
|
||||
{
|
||||
|
||||
public function __construct(private readonly CountryRepository $countryRepository)
|
||||
{
|
||||
}
|
||||
public function __construct(
|
||||
private readonly CountryRepository $countryRepository,
|
||||
private readonly Shop $shopService,
|
||||
) {}
|
||||
|
||||
public function calcVat(\PSC\Shop\OrderBundle\Model\Base $order): void
|
||||
{
|
||||
|
||||
$country = strtoupper($order->getDeliveryAddress()->getCountry());
|
||||
$ustid = $order->getDeliveryAddress()->getUstid();
|
||||
|
||||
if($country == '') {
|
||||
if ($country == '') {
|
||||
$country = 'DE';
|
||||
}
|
||||
|
||||
/**
|
||||
* @var Country $countryDoc
|
||||
*/
|
||||
$countryDoc = $this->countryRepository->findOneBy(['code' => $country]);
|
||||
if ($order->getShop()->getId() == 0) {
|
||||
$shop = $this->shopService->getShopByUid($order->getShop()->getUuid());
|
||||
$countryDoc = $this->countryRepository->findOneBy([
|
||||
'code' => $country,
|
||||
'shop' => $shop->getId(),
|
||||
]);
|
||||
} else {
|
||||
$countryDoc = $this->countryRepository->findOneBy([
|
||||
'code' => $country,
|
||||
'shop' => $order->getShop()->getId(),
|
||||
]);
|
||||
}
|
||||
|
||||
$useVat = true;
|
||||
if($ustid != "") {
|
||||
if ($ustid != '') {
|
||||
$useVat = $countryDoc->isWithTaxWithUstNr();
|
||||
}else{
|
||||
} else {
|
||||
$useVat = $countryDoc->isWithTaxWithoutUstNr();
|
||||
}
|
||||
|
||||
if(!$useVat) {
|
||||
if (!$useVat) {
|
||||
$order->getPayment()->getCalcPrice()->vat = 0;
|
||||
$order->getPayment()->getCalcPrice()->gross = $order->getPayment()->getCalcPrice()->net;
|
||||
$order->getPayment()->getCalcPrice()->tax = new Tax();
|
||||
@ -44,7 +51,7 @@ class VatCalc
|
||||
$order->getShipping()->getCalcPrice()->gross = $order->getPayment()->getCalcPrice()->net;
|
||||
$order->getShipping()->getCalcPrice()->tax = new Tax();
|
||||
|
||||
foreach($order->getPositions() as $position) {
|
||||
foreach ($order->getPositions() as $position) {
|
||||
$position->getPrice()->setVat(0);
|
||||
$position->getPrice()->setGross(0);
|
||||
$position->getPrice()->setAllVat($position->getPrice()->getNet());
|
||||
@ -57,5 +64,4 @@ class VatCalc
|
||||
$order->setTaxes([]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
139
src/new/tests/PSC/Shop/Order/Api/CreateVatTest.php
Normal file
139
src/new/tests/PSC/Shop/Order/Api/CreateVatTest.php
Normal file
@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\PSC\Shop\Order\Api;
|
||||
|
||||
use PSC\Shop\ContactBundle\Repository\ContactRepository;
|
||||
use PSC\Shop\EntityBundle\Entity\Shop;
|
||||
use PSC\Shop\EntityBundle\Repository\JobRepository;
|
||||
use PSC\Shop\EntityBundle\Repository\ShopRepository;
|
||||
use PSC\Shop\PaymentBundle\Repository\PaymentRepository;
|
||||
use PSC\Shop\ShippingBundle\Repository\ShippingRepository;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
use Tests\RefreshDatabaseTrait;
|
||||
|
||||
class CreateVatTest extends WebTestCase
|
||||
{
|
||||
use RefreshDatabaseTrait;
|
||||
|
||||
public function testCreateOrderDefault(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
|
||||
$shopRepository = static::getContainer()->get(ShopRepository::class);
|
||||
|
||||
/**
|
||||
* @var Shop $shop
|
||||
*/
|
||||
$shop = $shopRepository->findOneBy(['title' => 'Printchampion']);
|
||||
|
||||
$shippingRepository = static::getContainer()->get(ShippingRepository::class);
|
||||
|
||||
$paymentRepository = static::getContainer()->get(PaymentRepository::class);
|
||||
|
||||
$client->jsonRequest(
|
||||
'POST',
|
||||
'/api/order/create',
|
||||
[
|
||||
'shop' => [
|
||||
'uuid' => (string) $shop->getUuid(),
|
||||
],
|
||||
'type' => 2,
|
||||
'shipping' => [
|
||||
'uid' => $shippingRepository->findOneBy(['title' => 'Abholung vor Ort'])->getUid(),
|
||||
],
|
||||
'payment' => [
|
||||
'uid' => $paymentRepository->findOneBy(['title' => 'Bar'])->getUid(),
|
||||
],
|
||||
'draft' => false,
|
||||
'deliveryAddress' => [
|
||||
'firstname' => 'Thomas',
|
||||
'lastname' => 'Peterson',
|
||||
'street' => 'Chausseestr.',
|
||||
'houseNumber' => '24',
|
||||
'zip' => '17506',
|
||||
'city' => 'Gribow',
|
||||
],
|
||||
'invoiceAddress' => [
|
||||
'firstname' => 'Thomas',
|
||||
'lastname' => 'Peterson',
|
||||
'street' => 'Chausseestr.',
|
||||
'houseNumber' => '24',
|
||||
'zip' => '17400',
|
||||
'city' => 'Berlin',
|
||||
],
|
||||
'positions' => [
|
||||
[
|
||||
'count' => 1,
|
||||
'product' => [
|
||||
'title' => 'test XML',
|
||||
'specialProductTypeObject' => [
|
||||
'typ' => 6,
|
||||
'taxClass' => 700,
|
||||
'xml' => '<?xml version="1.0" encoding="utf-8"?>
|
||||
<kalkulation>
|
||||
<artikel>
|
||||
<name>Blocks A5 25blatt geleimt</name>
|
||||
<kommentar>kein</kommentar>
|
||||
|
||||
<option id="auflage" name="Auflage" type="Input" width="3" require="true" default="1">
|
||||
<auflage>
|
||||
<grenze formel="(10*5)">1-</grenze>
|
||||
</auflage>
|
||||
</option>
|
||||
|
||||
</artikel>
|
||||
</kalkulation>',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'count' => 1,
|
||||
'product' => [
|
||||
'title' => 'test Manual Position',
|
||||
'specialProductTypeObject' => [
|
||||
'typ' => 1,
|
||||
'cent' => true,
|
||||
'net' => 14500,
|
||||
'taxClass' => 700,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
['HTTP_apiKey' => $shop->getApiKey()],
|
||||
);
|
||||
|
||||
self::assertSame(200, $client->getResponse()->getStatusCode());
|
||||
|
||||
$data = json_decode($client->getResponse()->getContent(), true);
|
||||
|
||||
$client->jsonRequest(
|
||||
'POST',
|
||||
'/api/order/getonebyuuid',
|
||||
[
|
||||
'uuid' => $data['uuid'],
|
||||
],
|
||||
['HTTP_apiKey' => $shop->getApiKey()],
|
||||
);
|
||||
|
||||
$data = json_decode($client->getResponse()->getContent(), true);
|
||||
self::assertSame(20865, $data['gross']);
|
||||
self::assertSame('SAN-' . date('Ym') . '-1', $data['alias']);
|
||||
|
||||
self::assertSame('Berlin', $data['invoiceAddress']['city']);
|
||||
|
||||
self::assertSame('Gribow', $data['deliveryAddress']['city']);
|
||||
|
||||
self::assertSame('ShopMusterOrt', $data['senderAddress']['city']);
|
||||
self::assertSame('ShopMusterIban', $data['senderAddress']['iban']);
|
||||
|
||||
self::assertSame(200, $client->getResponse()->getStatusCode());
|
||||
|
||||
/**
|
||||
* @var JobRepository $jobs
|
||||
*/
|
||||
$jobs = static::getContainer()->get(JobRepository::class);
|
||||
|
||||
self::assertCount(0, $jobs->findBy(['data.order' => $data['uuid']]));
|
||||
}
|
||||
}
|
||||
@ -2,17 +2,17 @@
|
||||
|
||||
namespace App\Tests\PSC\Shop\Order\Service;
|
||||
|
||||
use PSC\Shop\OrderBundle\Model\Order\Position\Price;
|
||||
use Tests\RefreshDatabaseTrait;
|
||||
use PSC\Component\ApiBundle\Model\Shop;
|
||||
use PSC\Shop\ContactBundle\Model\Address;
|
||||
use PSC\Shop\OrderBundle\Model\Order\Position;
|
||||
use PSC\Shop\OrderBundle\Model\Order\Position\Price;
|
||||
use PSC\Shop\OrderBundle\Service\Calc;
|
||||
use PSC\Shop\PaymentBundle\Model\Payment;
|
||||
use PSC\Shop\ProductBundle\Model\Product;
|
||||
use PSC\Shop\ProductBundle\Model\ProductSpecialObject;
|
||||
use PSC\Shop\ShippingBundle\Model\Shipping;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Tests\RefreshDatabaseTrait;
|
||||
|
||||
class ImportCalcTest extends KernelTestCase
|
||||
{
|
||||
@ -25,12 +25,12 @@ class ImportCalcTest extends KernelTestCase
|
||||
$container = static::getContainer();
|
||||
|
||||
/**
|
||||
* @var Calc $calcService
|
||||
*/
|
||||
* @var Calc $calcService
|
||||
*/
|
||||
$calcService = $container->get(Calc::class);
|
||||
|
||||
$shop = new Shop();
|
||||
$shop->setUuid('shop1');
|
||||
$shop->setUuid('771a1176-d531-48ed-93b8-eec1fd4b917f');
|
||||
|
||||
$order = new \PSC\Shop\OrderBundle\Model\Order();
|
||||
$order->setShop($shop);
|
||||
@ -57,7 +57,7 @@ class ImportCalcTest extends KernelTestCase
|
||||
|
||||
$specialProductSettingsW1 = new ProductSpecialObject();
|
||||
$specialProductSettingsW1->setCount(1);
|
||||
$specialProductSettingsW1->setNet(25.5*100);
|
||||
$specialProductSettingsW1->setNet(25.5 * 100);
|
||||
$specialProductSettingsW1->setCent(true);
|
||||
$productW1->setSpecialProductTypeObject($specialProductSettingsW1);
|
||||
$price1 = new Price();
|
||||
@ -67,7 +67,7 @@ class ImportCalcTest extends KernelTestCase
|
||||
|
||||
$positionW1 = new Position();
|
||||
$positionW1->setProduct($productW1);
|
||||
$positionW1->setPrice($price1);
|
||||
$positionW1->setPrice($price1);
|
||||
|
||||
$order->addPosition($positionW1);
|
||||
|
||||
|
||||
@ -3,14 +3,14 @@
|
||||
namespace Plugins\System\PSC\XmlCalc\Api;
|
||||
|
||||
use PSC\Shop\ContactBundle\Model\AccountType;
|
||||
use PSC\Shop\EntityBundle\Repository\JobRepository;
|
||||
use Tests\RefreshDatabaseTrait;
|
||||
use PSC\Shop\ContactBundle\Repository\ContactRepository;
|
||||
use PSC\Shop\EntityBundle\Entity\Shop;
|
||||
use PSC\Shop\EntityBundle\Repository\JobRepository;
|
||||
use PSC\Shop\EntityBundle\Repository\ShopRepository;
|
||||
use PSC\Shop\PaymentBundle\Repository\PaymentRepository;
|
||||
use PSC\Shop\ShippingBundle\Repository\ShippingRepository;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
use Tests\RefreshDatabaseTrait;
|
||||
|
||||
class CreateOrderFromExistingProductTest extends WebTestCase
|
||||
{
|
||||
@ -33,14 +33,14 @@ class CreateOrderFromExistingProductTest extends WebTestCase
|
||||
'/api/order/create',
|
||||
[
|
||||
'shop' => [
|
||||
'uuid' => (string)$shop->getUuid()
|
||||
'uuid' => (string) $shop->getUuid(),
|
||||
],
|
||||
'type' => 2,
|
||||
'shipping' => [
|
||||
'uid' => $shippingRepository->findOneBy(['title' => 'Abholung vor Ort'])->getUid()
|
||||
'uid' => $shippingRepository->findOneBy(['title' => 'Abholung vor Ort'])->getUid(),
|
||||
],
|
||||
'payment' => [
|
||||
'uid' => $paymentRepository->findOneBy(['title' => 'Bar bei Abholung'])->getUid()
|
||||
'uid' => $paymentRepository->findOneBy(['title' => 'Bar bei Abholung'])->getUid(),
|
||||
],
|
||||
'draft' => false,
|
||||
'deliveryAddress' => [
|
||||
@ -49,7 +49,7 @@ class CreateOrderFromExistingProductTest extends WebTestCase
|
||||
'street' => 'Chausseestr.',
|
||||
'houseNumber' => '24',
|
||||
'zip' => '17506',
|
||||
'city' => 'Gribow'
|
||||
'city' => 'Gribow',
|
||||
],
|
||||
'invoiceAddress' => [
|
||||
'firstname' => 'Thomas',
|
||||
@ -57,7 +57,7 @@ class CreateOrderFromExistingProductTest extends WebTestCase
|
||||
'street' => 'Chausseestr.',
|
||||
'houseNumber' => '24',
|
||||
'zip' => '17400',
|
||||
'city' => 'Berlin'
|
||||
'city' => 'Berlin',
|
||||
],
|
||||
'positions' => [
|
||||
[
|
||||
@ -68,14 +68,14 @@ class CreateOrderFromExistingProductTest extends WebTestCase
|
||||
'specialProductTypeObject' => [
|
||||
'typ' => 6,
|
||||
'params' => [
|
||||
'auflage' => 100
|
||||
'auflage' => 100,
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
['HTTP_apiKey' => $shop->getApiKey()]
|
||||
['HTTP_apiKey' => $shop->getApiKey()],
|
||||
);
|
||||
self::assertSame(200, $client->getResponse()->getStatusCode());
|
||||
|
||||
@ -86,9 +86,9 @@ class CreateOrderFromExistingProductTest extends WebTestCase
|
||||
'POST',
|
||||
'/api/order/getonebyuuid',
|
||||
[
|
||||
'uuid' => $data['uuid'],
|
||||
'uuid' => $data['uuid'],
|
||||
],
|
||||
['HTTP_apiKey' => $shop->getApiKey()]
|
||||
['HTTP_apiKey' => $shop->getApiKey()],
|
||||
);
|
||||
|
||||
self::assertSame(200, $client->getResponse()->getStatusCode());
|
||||
@ -117,17 +117,17 @@ class CreateOrderFromExistingProductTest extends WebTestCase
|
||||
'/api/order/create',
|
||||
[
|
||||
'shop' => [
|
||||
'uuid' => (string)$shop->getUuid()
|
||||
'uuid' => (string) $shop->getUuid(),
|
||||
],
|
||||
'type' => 2,
|
||||
'shipping' => [
|
||||
'uid' => $shippingRepository->findOneBy(['title' => 'Abholung vor Ort'])->getUid()
|
||||
'uid' => $shippingRepository->findOneBy(['title' => 'Abholung vor Ort'])->getUid(),
|
||||
],
|
||||
'payment' => [
|
||||
'uid' => $paymentRepository->findOneBy(['title' => 'Bar bei Abholung'])->getUid()
|
||||
'uid' => $paymentRepository->findOneBy(['title' => 'Bar bei Abholung'])->getUid(),
|
||||
],
|
||||
'contact' => [
|
||||
'accountType' => AccountType::COMPANY
|
||||
'accountType' => AccountType::COMPANY,
|
||||
],
|
||||
'draft' => false,
|
||||
'deliveryAddress' => [
|
||||
@ -136,7 +136,7 @@ class CreateOrderFromExistingProductTest extends WebTestCase
|
||||
'street' => 'Chausseestr.',
|
||||
'houseNumber' => '24',
|
||||
'zip' => '17506',
|
||||
'city' => 'Gribow'
|
||||
'city' => 'Gribow',
|
||||
],
|
||||
'invoiceAddress' => [
|
||||
'firstname' => 'Thomas',
|
||||
@ -144,7 +144,7 @@ class CreateOrderFromExistingProductTest extends WebTestCase
|
||||
'street' => 'Chausseestr.',
|
||||
'houseNumber' => '24',
|
||||
'zip' => '17400',
|
||||
'city' => 'Berlin'
|
||||
'city' => 'Berlin',
|
||||
],
|
||||
'positions' => [
|
||||
[
|
||||
@ -155,14 +155,14 @@ class CreateOrderFromExistingProductTest extends WebTestCase
|
||||
'specialProductTypeObject' => [
|
||||
'typ' => 6,
|
||||
'params' => [
|
||||
'auflage' => 100
|
||||
'auflage' => 100,
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
['HTTP_apiKey' => $shop->getApiKey()]
|
||||
['HTTP_apiKey' => $shop->getApiKey()],
|
||||
);
|
||||
|
||||
self::assertSame(200, $client->getResponse()->getStatusCode());
|
||||
@ -174,9 +174,9 @@ class CreateOrderFromExistingProductTest extends WebTestCase
|
||||
'POST',
|
||||
'/api/order/getonebyuuid',
|
||||
[
|
||||
'uuid' => $data['uuid'],
|
||||
'uuid' => $data['uuid'],
|
||||
],
|
||||
['HTTP_apiKey' => $shop->getApiKey()]
|
||||
['HTTP_apiKey' => $shop->getApiKey()],
|
||||
);
|
||||
|
||||
self::assertSame(200, $client->getResponse()->getStatusCode());
|
||||
@ -187,6 +187,4 @@ class CreateOrderFromExistingProductTest extends WebTestCase
|
||||
$jobs = static::getContainer()->get(JobRepository::class);
|
||||
self::assertCount(0, $jobs->findBy(['data.order' => $data['uuid']]));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace Plugin\Custom\PSC\Gutschein\Form\Field;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PSC\Shop\EntityBundle\Entity\Product;
|
||||
use PSC\Shop\EntityBundle\Entity\Voucher;
|
||||
use PSC\Shop\EntityBundle\Repository\VoucherItemRepository;
|
||||
use PSC\System\PluginBundle\Form\Interfaces\Field;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
|
||||
class ProductSettings implements Field
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager
|
||||
) {}
|
||||
|
||||
public function getTemplate()
|
||||
{
|
||||
return '@PluginCustomPSCGutschein/form/field/product_settings.html.twig';
|
||||
}
|
||||
|
||||
public function getModule()
|
||||
{
|
||||
return Field::Product;
|
||||
}
|
||||
|
||||
public function formPreSubmit(FormEvent $event)
|
||||
{
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$product = $options['product'];
|
||||
|
||||
// Load all vouchers and build choices with code count
|
||||
$voucherRepo = $this->entityManager->getRepository(Voucher::class);
|
||||
/** @var VoucherItemRepository $voucherItemRepo */
|
||||
$voucherItemRepo = $this->entityManager->getRepository(\PSC\Shop\EntityBundle\Entity\VoucherItem::class);
|
||||
|
||||
$vouchers = $voucherRepo->findAll();
|
||||
$choices = ['-- Zufallscode generieren (Standard) --' => ''];
|
||||
|
||||
foreach ($vouchers as $voucher) {
|
||||
$unusedCount = $voucherItemRepo->countUnusedCodes($voucher->getUid());
|
||||
$label = sprintf(
|
||||
'%s (%d verfügbare Codes)',
|
||||
$voucher->getTitle(),
|
||||
$unusedCount
|
||||
);
|
||||
$choices[$label] = (string)$voucher->getUid();
|
||||
}
|
||||
|
||||
$currentLinkedVoucher = null;
|
||||
if ($product && isset($product->gutschein['linkedVoucherUid'])) {
|
||||
$currentLinkedVoucher = (string)$product->gutschein['linkedVoucherUid'];
|
||||
}
|
||||
|
||||
$builder->add('linkedVoucherUid', ChoiceType::class, [
|
||||
'required' => false,
|
||||
'label' => 'Verknüpfter Gutschein',
|
||||
'choices' => $choices,
|
||||
'data' => $currentLinkedVoucher,
|
||||
'help' => 'Wählen Sie einen Gutschein aus dem VoucherBundle, um vordefinierte Codes zu verwenden. Wenn kein Gutschein ausgewählt ist, wird ein Zufallscode generiert.',
|
||||
]);
|
||||
|
||||
return $builder;
|
||||
}
|
||||
|
||||
public function getGroup()
|
||||
{
|
||||
return \Plugin\Custom\PSC\Gutschein\Form\Group\Calc::GROUP_ID;
|
||||
}
|
||||
|
||||
public function formPostSetData(FormEvent $event)
|
||||
{
|
||||
}
|
||||
|
||||
public function formPostSubmit(FormEvent $event)
|
||||
{
|
||||
/** @var Product $product */
|
||||
$product = $event->getData();
|
||||
$form = $event->getForm();
|
||||
|
||||
if ($form->has('linkedVoucherUid')) {
|
||||
$linkedVoucherUid = $form->get('linkedVoucherUid')->getData();
|
||||
|
||||
// Get existing gutschein data
|
||||
$gutscheinData = $product->gutschein ?? [];
|
||||
|
||||
// Update or set linkedVoucherUid
|
||||
if (!empty($linkedVoucherUid)) {
|
||||
$gutscheinData['linkedVoucherUid'] = (int)$linkedVoucherUid;
|
||||
} else {
|
||||
// Remove if empty (use random generation)
|
||||
unset($gutscheinData['linkedVoucherUid']);
|
||||
}
|
||||
|
||||
// Save back to product
|
||||
$product->gutschein = $gutscheinData;
|
||||
}
|
||||
}
|
||||
|
||||
public function formPreSetData(FormEvent $event)
|
||||
{
|
||||
}
|
||||
|
||||
public function formSubmit(FormEvent $event)
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -25,6 +25,9 @@ class ProductSpecialObject implements IProductTypeObject
|
||||
private string $voucherCode = '';
|
||||
private ?\DateTime $expirationDate = null;
|
||||
|
||||
// VoucherBundle integration
|
||||
private ?int $linkedVoucherUid = null;
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Gutscheinprodukt';
|
||||
@ -46,6 +49,7 @@ class ProductSpecialObject implements IProductTypeObject
|
||||
'voucherCode' => $this->voucherCode,
|
||||
'expirationDate' => $this->expirationDate?->format('Y-m-d H:i:s'),
|
||||
'validityMonths' => $this->validityMonths,
|
||||
'linkedVoucherUid' => $this->linkedVoucherUid,
|
||||
];
|
||||
}
|
||||
|
||||
@ -169,4 +173,14 @@ class ProductSpecialObject implements IProductTypeObject
|
||||
{
|
||||
$this->expirationDate = $expirationDate;
|
||||
}
|
||||
|
||||
public function getLinkedVoucherUid(): ?int
|
||||
{
|
||||
return $this->linkedVoucherUid;
|
||||
}
|
||||
|
||||
public function setLinkedVoucherUid(?int $linkedVoucherUid): void
|
||||
{
|
||||
$this->linkedVoucherUid = $linkedVoucherUid;
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,6 +29,7 @@ class Producer implements IUiProducer, IProducerHydrateModel
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly CodeGenerator $codeGenerator,
|
||||
private readonly \Plugin\Custom\PSC\Gutschein\Service\VoucherCodeDistributor $voucherCodeDistributor,
|
||||
) {}
|
||||
|
||||
public function setProduct(Product $product): void
|
||||
@ -136,11 +137,54 @@ class Producer implements IUiProducer, IProducerHydrateModel
|
||||
|
||||
public function calcPriceForOrderPosition(Position $position): void
|
||||
{
|
||||
// Generate unique voucher code for this order position
|
||||
$voucherCode = $this->codeGenerator->generate();
|
||||
|
||||
/** @var ProductSpecialObject $specProd */
|
||||
$specProd = $position->getProduct()->getSpecialProductTypeObject();
|
||||
|
||||
$voucherCode = null;
|
||||
|
||||
// Try to get code from VoucherBundle if product is linked
|
||||
if ($specProd->getLinkedVoucherUid()) {
|
||||
try {
|
||||
$contact = $position->getOrder()?->getContact();
|
||||
$voucherItem = $this->voucherCodeDistributor->getNextAvailableCode(
|
||||
$specProd->getLinkedVoucherUid(),
|
||||
$contact
|
||||
);
|
||||
|
||||
if ($voucherItem) {
|
||||
// Successfully got code from VoucherBundle
|
||||
$voucherCode = $voucherItem->getCode();
|
||||
|
||||
error_log(sprintf(
|
||||
'[Gutschein] Distributed VoucherBundle code %s from Voucher %d for Product %s',
|
||||
$voucherCode,
|
||||
$specProd->getLinkedVoucherUid(),
|
||||
$position->getProduct()->getUuid()
|
||||
));
|
||||
} else {
|
||||
// No codes available - log warning
|
||||
error_log(sprintf(
|
||||
'[Gutschein] WARNING: No available codes in VoucherBundle Voucher %d for Product %s, using random generation',
|
||||
$specProd->getLinkedVoucherUid(),
|
||||
$position->getProduct()->getUuid()
|
||||
));
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Error accessing VoucherBundle - log and fall back
|
||||
error_log(sprintf(
|
||||
'[Gutschein] ERROR: Failed to access VoucherBundle (Voucher %d): %s, using random generation',
|
||||
$specProd->getLinkedVoucherUid(),
|
||||
$e->getMessage()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to random generation if no VoucherBundle code obtained
|
||||
if (!$voucherCode) {
|
||||
$voucherCode = $this->codeGenerator->generate();
|
||||
}
|
||||
|
||||
// Set voucher details
|
||||
$specProd->setVoucherCode($voucherCode);
|
||||
$specProd->setVoucherAmount($this->voucherAmount);
|
||||
$specProd->setRecipientName($this->recipientName);
|
||||
|
||||
@ -17,3 +17,7 @@ services:
|
||||
Plugin\Custom\PSC\Gutschein\Form\Field\Calc:
|
||||
tags:
|
||||
- { name: psc.backend.custom.fields, productType: 9 }
|
||||
|
||||
Plugin\Custom\PSC\Gutschein\Form\Field\ProductSettings:
|
||||
tags:
|
||||
- { name: psc.backend.custom.fields, productType: 9 }
|
||||
|
||||
@ -6,8 +6,8 @@
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
{{ form_label(form.gutschein.validityMonths) }}
|
||||
{{ form_widget(form.gutschein.validityMonths) }}
|
||||
{{ form_label(form.validityMonths) }}
|
||||
{{ form_widget(form.validityMonths) }}
|
||||
<small class="form-text text-muted">Gültigkeitsdauer ab Kaufdatum</small>
|
||||
</div>
|
||||
</div>
|
||||
@ -16,22 +16,22 @@
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
{{ form_label(form.gutschein.minAmount) }}
|
||||
{{ form_widget(form.gutschein.minAmount) }}
|
||||
{{ form_label(form.minAmount) }}
|
||||
{{ form_widget(form.minAmount) }}
|
||||
<small class="form-text text-muted">Mindestbetrag</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
{{ form_label(form.gutschein.defaultAmount) }}
|
||||
{{ form_widget(form.gutschein.defaultAmount) }}
|
||||
{{ form_label(form.defaultAmount) }}
|
||||
{{ form_widget(form.defaultAmount) }}
|
||||
<small class="form-text text-muted">Vorauswahl</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
{{ form_label(form.gutschein.maxAmount) }}
|
||||
{{ form_widget(form.gutschein.maxAmount) }}
|
||||
{{ form_label(form.maxAmount) }}
|
||||
{{ form_widget(form.maxAmount) }}
|
||||
<small class="form-text text-muted">Maximalbetrag</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5>VoucherBundle Integration</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
{{ form_label(form.linkedVoucherUid) }}
|
||||
{{ form_widget(form.linkedVoucherUid, {'attr': {'class': 'form-control'}}) }}
|
||||
{{ form_help(form.linkedVoucherUid) }}
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info mt-3">
|
||||
<strong>Wie funktioniert die Verknüpfung?</strong>
|
||||
<ul class="mb-0 mt-2">
|
||||
<li>Wählen Sie einen Gutschein aus dem VoucherBundle aus, um vordefinierte Codes zu verwenden</li>
|
||||
<li>Bei jedem Verkauf wird automatisch ein Code aus dem Voucher zugewiesen</li>
|
||||
<li>Der Code wird als "verwendet" markiert und mit dem Kunden verknüpft</li>
|
||||
<li>Wenn keine Codes mehr verfügbar sind, wird automatisch ein Zufallscode generiert</li>
|
||||
<li>Ohne Verknüpfung werden weiterhin Zufallscodes generiert (Standard-Verhalten)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{% if form.linkedVoucherUid.vars.value %}
|
||||
<div class="alert alert-success">
|
||||
<strong>Aktuell verknüpft:</strong> Dieser Gutschein verwendet Codes aus dem ausgewählten VoucherBundle-Gutschein.
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PrintshopCreator Suite
|
||||
*
|
||||
* @author Thomas Peterson <info@thomas-peterson.de>
|
||||
* @copyright 2012-2013 PrintshopCreator GmbH
|
||||
* @license Private
|
||||
* @link http://www.printshopcreator.de
|
||||
*/
|
||||
|
||||
namespace Plugin\Custom\PSC\Gutschein\Service;
|
||||
|
||||
use Doctrine\DBAL\LockMode;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PSC\Shop\EntityBundle\Entity\VoucherItem;
|
||||
use PSC\Shop\EntityBundle\Entity\Contact;
|
||||
|
||||
/**
|
||||
* VoucherCodeDistributor
|
||||
*
|
||||
* Service for atomic code distribution from VoucherBundle
|
||||
* Uses pessimistic locking to prevent race conditions
|
||||
*/
|
||||
class VoucherCodeDistributor
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get next available unused code from a Voucher
|
||||
* Uses pessimistic locking to prevent race conditions
|
||||
*
|
||||
* @param int $voucherUid
|
||||
* @param Contact|null $contact
|
||||
* @return VoucherItem|null
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getNextAvailableCode(int $voucherUid, ?Contact $contact = null): ?VoucherItem
|
||||
{
|
||||
$this->entityManager->beginTransaction();
|
||||
|
||||
try {
|
||||
// Use pessimistic write lock for atomic assignment
|
||||
$qb = $this->entityManager->createQueryBuilder();
|
||||
$voucherItem = $qb->select('vi')
|
||||
->from(VoucherItem::class, 'vi')
|
||||
->where('vi.voucher = :voucherId')
|
||||
->andWhere('vi.used = :used')
|
||||
->setParameter('voucherId', $voucherUid)
|
||||
->setParameter('used', false)
|
||||
->setMaxResults(1)
|
||||
->getQuery()
|
||||
->setLockMode(LockMode::PESSIMISTIC_WRITE)
|
||||
->getOneOrNullResult();
|
||||
|
||||
if (!$voucherItem) {
|
||||
$this->entityManager->rollback();
|
||||
return null;
|
||||
}
|
||||
|
||||
// Mark as used and link to contact
|
||||
$voucherItem->setUsed(true);
|
||||
if ($contact) {
|
||||
$voucherItem->setContact($contact);
|
||||
}
|
||||
$voucherItem->setUpdatedAt(new \DateTime());
|
||||
|
||||
$this->entityManager->flush();
|
||||
$this->entityManager->commit();
|
||||
|
||||
return $voucherItem;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->entityManager->rollback();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a Voucher has available codes
|
||||
*
|
||||
* @param int $voucherUid
|
||||
* @return bool
|
||||
*/
|
||||
public function hasAvailableCodes(int $voucherUid): bool
|
||||
{
|
||||
return $this->getRemainingCodeCount($voucherUid) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count of remaining unused codes
|
||||
*
|
||||
* @param int $voucherUid
|
||||
* @return int
|
||||
*/
|
||||
public function getRemainingCodeCount(int $voucherUid): int
|
||||
{
|
||||
$repo = $this->entityManager->getRepository(VoucherItem::class);
|
||||
return $repo->countUnusedCodes($voucherUid);
|
||||
}
|
||||
}
|
||||
@ -35,6 +35,10 @@ class Product implements IProductTransformer
|
||||
if ($productDoc->getCustom4()) {
|
||||
$prodSpec->setValidityMonths((int)$productDoc->getCustom4());
|
||||
}
|
||||
// Load linked VoucherBundle Voucher UID from custom5
|
||||
if ($productDoc->getCustom5()) {
|
||||
$prodSpec->setLinkedVoucherUid((int)$productDoc->getCustom5());
|
||||
}
|
||||
}
|
||||
|
||||
public function toDb(
|
||||
@ -50,5 +54,9 @@ class Product implements IProductTransformer
|
||||
$productDoc->setCustom2((string)$prodSpec->getMaxAmount());
|
||||
$productDoc->setCustom3((string)$prodSpec->getDefaultAmount());
|
||||
$productDoc->setCustom4((string)$prodSpec->getValidityMonths());
|
||||
// Save linked VoucherBundle Voucher UID to custom5
|
||||
$productDoc->setCustom5(
|
||||
$prodSpec->getLinkedVoucherUid() ? (string)$prodSpec->getLinkedVoucherUid() : ''
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ class Producer implements IUiProducer, IProducerHydrateModel, ICalcNeedContact
|
||||
private Product $product;
|
||||
private Engine $engine;
|
||||
private EntityManagerInterface $entityManager;
|
||||
private null|Contact $contact = null;
|
||||
private ?Contact $contact = null;
|
||||
|
||||
public function __construct(
|
||||
Shop $shopService,
|
||||
@ -73,7 +73,6 @@ class Producer implements IUiProducer, IProducerHydrateModel, ICalcNeedContact
|
||||
* @var ProductSpecialObject $specProd
|
||||
*/
|
||||
$specProd = $this->product->getSpecialProductTypeObject();
|
||||
|
||||
$priceObj = Money::ofMinor($this->engine->getPrice() * 100, 'EUR');
|
||||
|
||||
$price = new Price();
|
||||
@ -152,6 +151,9 @@ class Producer implements IUiProducer, IProducerHydrateModel, ICalcNeedContact
|
||||
$engine->loadString($product->getCalcXml());
|
||||
$engine->setFormulas($product->getShop()->getFormel());
|
||||
$engine->setParameters($product->getShop()->getParameter());
|
||||
|
||||
$spec = $this->product->getSpecialProductTypeObject();
|
||||
$spec->setTaxClass($product->getMwert() * 100);
|
||||
} elseif ($this->product->getUuid()) {
|
||||
/**
|
||||
* @var \PSC\Shop\EntityBundle\Entity\Product $product
|
||||
@ -169,6 +171,9 @@ class Producer implements IUiProducer, IProducerHydrateModel, ICalcNeedContact
|
||||
$engine->loadString($product->getCalcXml());
|
||||
$engine->setFormulas($product->getShop()->getFormel());
|
||||
$engine->setParameters($product->getShop()->getParameter());
|
||||
$spec = $this->product->getSpecialProductTypeObject();
|
||||
|
||||
$spec->setTaxClass($product->getMwert() * 100);
|
||||
}
|
||||
|
||||
$this->engine = $engine;
|
||||
@ -248,7 +253,7 @@ class Producer implements IUiProducer, IProducerHydrateModel, ICalcNeedContact
|
||||
return $temp;
|
||||
}
|
||||
|
||||
public function setContact(null|Contact $contact): void
|
||||
public function setContact(?Contact $contact): void
|
||||
{
|
||||
$this->contact = $contact;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user