Fixes+
This commit is contained in:
parent
3a0019a1b9
commit
d598a9214f
@ -9,7 +9,7 @@ PSC\Shop\EntityBundle\Entity\Product:
|
|||||||
pos: <numberBetween(1, 200)>
|
pos: <numberBetween(1, 200)>
|
||||||
notEdit: false
|
notEdit: false
|
||||||
private: false
|
private: false
|
||||||
taxClass: 19
|
mwert: 19
|
||||||
price: 0
|
price: 0
|
||||||
set_config: '{}'
|
set_config: '{}'
|
||||||
shop: '@shop_1'
|
shop: '@shop_1'
|
||||||
@ -23,7 +23,7 @@ PSC\Shop\EntityBundle\Entity\Product:
|
|||||||
notEdit: false
|
notEdit: false
|
||||||
pos: <numberBetween(1, 200)>
|
pos: <numberBetween(1, 200)>
|
||||||
private: false
|
private: false
|
||||||
taxClass: 19
|
mwert: 19
|
||||||
price: 0
|
price: 0
|
||||||
set_config: '{}'
|
set_config: '{}'
|
||||||
shop: '@shop_1'
|
shop: '@shop_1'
|
||||||
@ -37,7 +37,7 @@ PSC\Shop\EntityBundle\Entity\Product:
|
|||||||
notEdit: false
|
notEdit: false
|
||||||
pos: <numberBetween(1, 200)>
|
pos: <numberBetween(1, 200)>
|
||||||
private: false
|
private: false
|
||||||
taxClass: 19
|
mwert: 19
|
||||||
price: 0
|
price: 0
|
||||||
set_config: '\[{"article_id": "7996f00d-a5e6-4915-970d-9f7c4287cd92"}, {"article_id": "7996f00d-a5e6-4915-970d-9f7c4287cd91"} ]'
|
set_config: '\[{"article_id": "7996f00d-a5e6-4915-970d-9f7c4287cd92"}, {"article_id": "7996f00d-a5e6-4915-970d-9f7c4287cd91"} ]'
|
||||||
shop: '@shop_1'
|
shop: '@shop_1'
|
||||||
@ -51,7 +51,7 @@ PSC\Shop\EntityBundle\Entity\Product:
|
|||||||
type: 2
|
type: 2
|
||||||
pos: <numberBetween(1, 200)>
|
pos: <numberBetween(1, 200)>
|
||||||
private: false
|
private: false
|
||||||
taxClass: 19
|
mwert: 19
|
||||||
price: 20
|
price: 20
|
||||||
set_config: '{}'
|
set_config: '{}'
|
||||||
shop: '@shop_1'
|
shop: '@shop_1'
|
||||||
@ -65,7 +65,7 @@ PSC\Shop\EntityBundle\Entity\Product:
|
|||||||
pos: <numberBetween(1, 200)>
|
pos: <numberBetween(1, 200)>
|
||||||
notEdit: false
|
notEdit: false
|
||||||
private: false
|
private: false
|
||||||
taxClass: 19
|
mwert: 19
|
||||||
set_config: '{}'
|
set_config: '{}'
|
||||||
shop: '@shop_1'
|
shop: '@shop_1'
|
||||||
calcXml: >
|
calcXml: >
|
||||||
@ -116,7 +116,7 @@ PSC\Shop\EntityBundle\Entity\Product:
|
|||||||
pos: <numberBetween(1, 200)>
|
pos: <numberBetween(1, 200)>
|
||||||
notEdit: false
|
notEdit: false
|
||||||
private: false
|
private: false
|
||||||
taxClass: 19
|
mwert: 19
|
||||||
set_config: '{}'
|
set_config: '{}'
|
||||||
shop: '@shop_1'
|
shop: '@shop_1'
|
||||||
calcXml: >
|
calcXml: >
|
||||||
|
|||||||
@ -34,4 +34,39 @@ class VoucherItemRepository extends EntityRepository
|
|||||||
->getQuery()->execute();
|
->getQuery()->execute();
|
||||||
return $isDeleted;
|
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);
|
$order->addTax($order->getShipping()->getCalcPrice()->tax);
|
||||||
foreach ($order->getPositions() as $position) {
|
foreach ($order->getPositions() as $position) {
|
||||||
$position->getProduct()->setShopUuid($order->getShop()->getUuid());
|
$position->getProduct()->setShopUuid($order->getShop()->getUuid());
|
||||||
if (
|
if ($this->productTypeRegistry->getProductType($position->getProduct()->getSpecialProductTypeObject()->getTyp())) {
|
||||||
$this->productTypeRegistry->getProductType($position->getProduct()->getSpecialProductTypeObject()->getTyp())
|
|
||||||
) {
|
|
||||||
$specialProductTransformer = $this->productTypeRegistry
|
$specialProductTransformer = $this->productTypeRegistry
|
||||||
->getProductType($position->getProduct()->getSpecialProductTypeObject()->getTyp())
|
->getProductType($position->getProduct()->getSpecialProductTypeObject()->getTyp())
|
||||||
->getProducer();
|
->getProducer();
|
||||||
@ -77,7 +75,6 @@ readonly class Calc
|
|||||||
$specialProductTransformer->calcPriceForOrderPosition($position);
|
$specialProductTransformer->calcPriceForOrderPosition($position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$priceNet = $priceNet->plus(Money::ofMinor($position->getPrice()->getAllNet(), 'EUR'));
|
$priceNet = $priceNet->plus(Money::ofMinor($position->getPrice()->getAllNet(), 'EUR'));
|
||||||
$priceVat = $priceVat->plus(Money::ofMinor($position->getPrice()->getAllVat(), 'EUR'));
|
$priceVat = $priceVat->plus(Money::ofMinor($position->getPrice()->getAllVat(), 'EUR'));
|
||||||
$priceGross = $priceGross->plus(Money::ofMinor($position->getPrice()->getAllGross(), '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\Document\Country;
|
||||||
use PSC\Shop\EntityBundle\Repository\CountryRepository;
|
use PSC\Shop\EntityBundle\Repository\CountryRepository;
|
||||||
use PSC\Shop\OrderBundle\Model\Order\Tax;
|
use PSC\Shop\OrderBundle\Model\Order\Tax;
|
||||||
|
use PSC\System\SettingsBundle\Service\Shop;
|
||||||
|
|
||||||
class VatCalc
|
class VatCalc
|
||||||
{
|
{
|
||||||
|
public function __construct(
|
||||||
public function __construct(private readonly CountryRepository $countryRepository)
|
private readonly CountryRepository $countryRepository,
|
||||||
{
|
private readonly Shop $shopService,
|
||||||
}
|
) {}
|
||||||
|
|
||||||
public function calcVat(\PSC\Shop\OrderBundle\Model\Base $order): void
|
public function calcVat(\PSC\Shop\OrderBundle\Model\Base $order): void
|
||||||
{
|
{
|
||||||
|
|
||||||
$country = strtoupper($order->getDeliveryAddress()->getCountry());
|
$country = strtoupper($order->getDeliveryAddress()->getCountry());
|
||||||
$ustid = $order->getDeliveryAddress()->getUstid();
|
$ustid = $order->getDeliveryAddress()->getUstid();
|
||||||
|
|
||||||
if($country == '') {
|
if ($country == '') {
|
||||||
$country = 'DE';
|
$country = 'DE';
|
||||||
}
|
}
|
||||||
|
if ($order->getShop()->getId() == 0) {
|
||||||
/**
|
$shop = $this->shopService->getShopByUid($order->getShop()->getUuid());
|
||||||
* @var Country $countryDoc
|
$countryDoc = $this->countryRepository->findOneBy([
|
||||||
*/
|
'code' => $country,
|
||||||
$countryDoc = $this->countryRepository->findOneBy(['code' => $country]);
|
'shop' => $shop->getId(),
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$countryDoc = $this->countryRepository->findOneBy([
|
||||||
|
'code' => $country,
|
||||||
|
'shop' => $order->getShop()->getId(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$useVat = true;
|
$useVat = true;
|
||||||
if($ustid != "") {
|
if ($ustid != '') {
|
||||||
$useVat = $countryDoc->isWithTaxWithUstNr();
|
$useVat = $countryDoc->isWithTaxWithUstNr();
|
||||||
}else{
|
} else {
|
||||||
$useVat = $countryDoc->isWithTaxWithoutUstNr();
|
$useVat = $countryDoc->isWithTaxWithoutUstNr();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!$useVat) {
|
if (!$useVat) {
|
||||||
$order->getPayment()->getCalcPrice()->vat = 0;
|
$order->getPayment()->getCalcPrice()->vat = 0;
|
||||||
$order->getPayment()->getCalcPrice()->gross = $order->getPayment()->getCalcPrice()->net;
|
$order->getPayment()->getCalcPrice()->gross = $order->getPayment()->getCalcPrice()->net;
|
||||||
$order->getPayment()->getCalcPrice()->tax = new Tax();
|
$order->getPayment()->getCalcPrice()->tax = new Tax();
|
||||||
@ -44,7 +51,7 @@ class VatCalc
|
|||||||
$order->getShipping()->getCalcPrice()->gross = $order->getPayment()->getCalcPrice()->net;
|
$order->getShipping()->getCalcPrice()->gross = $order->getPayment()->getCalcPrice()->net;
|
||||||
$order->getShipping()->getCalcPrice()->tax = new Tax();
|
$order->getShipping()->getCalcPrice()->tax = new Tax();
|
||||||
|
|
||||||
foreach($order->getPositions() as $position) {
|
foreach ($order->getPositions() as $position) {
|
||||||
$position->getPrice()->setVat(0);
|
$position->getPrice()->setVat(0);
|
||||||
$position->getPrice()->setGross(0);
|
$position->getPrice()->setGross(0);
|
||||||
$position->getPrice()->setAllVat($position->getPrice()->getNet());
|
$position->getPrice()->setAllVat($position->getPrice()->getNet());
|
||||||
@ -57,5 +64,4 @@ class VatCalc
|
|||||||
$order->setTaxes([]);
|
$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;
|
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\Component\ApiBundle\Model\Shop;
|
||||||
use PSC\Shop\ContactBundle\Model\Address;
|
use PSC\Shop\ContactBundle\Model\Address;
|
||||||
use PSC\Shop\OrderBundle\Model\Order\Position;
|
use PSC\Shop\OrderBundle\Model\Order\Position;
|
||||||
|
use PSC\Shop\OrderBundle\Model\Order\Position\Price;
|
||||||
use PSC\Shop\OrderBundle\Service\Calc;
|
use PSC\Shop\OrderBundle\Service\Calc;
|
||||||
use PSC\Shop\PaymentBundle\Model\Payment;
|
use PSC\Shop\PaymentBundle\Model\Payment;
|
||||||
use PSC\Shop\ProductBundle\Model\Product;
|
use PSC\Shop\ProductBundle\Model\Product;
|
||||||
use PSC\Shop\ProductBundle\Model\ProductSpecialObject;
|
use PSC\Shop\ProductBundle\Model\ProductSpecialObject;
|
||||||
use PSC\Shop\ShippingBundle\Model\Shipping;
|
use PSC\Shop\ShippingBundle\Model\Shipping;
|
||||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
|
use Tests\RefreshDatabaseTrait;
|
||||||
|
|
||||||
class ImportCalcTest extends KernelTestCase
|
class ImportCalcTest extends KernelTestCase
|
||||||
{
|
{
|
||||||
@ -25,12 +25,12 @@ class ImportCalcTest extends KernelTestCase
|
|||||||
$container = static::getContainer();
|
$container = static::getContainer();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Calc $calcService
|
* @var Calc $calcService
|
||||||
*/
|
*/
|
||||||
$calcService = $container->get(Calc::class);
|
$calcService = $container->get(Calc::class);
|
||||||
|
|
||||||
$shop = new Shop();
|
$shop = new Shop();
|
||||||
$shop->setUuid('shop1');
|
$shop->setUuid('771a1176-d531-48ed-93b8-eec1fd4b917f');
|
||||||
|
|
||||||
$order = new \PSC\Shop\OrderBundle\Model\Order();
|
$order = new \PSC\Shop\OrderBundle\Model\Order();
|
||||||
$order->setShop($shop);
|
$order->setShop($shop);
|
||||||
@ -57,7 +57,7 @@ class ImportCalcTest extends KernelTestCase
|
|||||||
|
|
||||||
$specialProductSettingsW1 = new ProductSpecialObject();
|
$specialProductSettingsW1 = new ProductSpecialObject();
|
||||||
$specialProductSettingsW1->setCount(1);
|
$specialProductSettingsW1->setCount(1);
|
||||||
$specialProductSettingsW1->setNet(25.5*100);
|
$specialProductSettingsW1->setNet(25.5 * 100);
|
||||||
$specialProductSettingsW1->setCent(true);
|
$specialProductSettingsW1->setCent(true);
|
||||||
$productW1->setSpecialProductTypeObject($specialProductSettingsW1);
|
$productW1->setSpecialProductTypeObject($specialProductSettingsW1);
|
||||||
$price1 = new Price();
|
$price1 = new Price();
|
||||||
|
|||||||
@ -3,14 +3,14 @@
|
|||||||
namespace Plugins\System\PSC\XmlCalc\Api;
|
namespace Plugins\System\PSC\XmlCalc\Api;
|
||||||
|
|
||||||
use PSC\Shop\ContactBundle\Model\AccountType;
|
use PSC\Shop\ContactBundle\Model\AccountType;
|
||||||
use PSC\Shop\EntityBundle\Repository\JobRepository;
|
|
||||||
use Tests\RefreshDatabaseTrait;
|
|
||||||
use PSC\Shop\ContactBundle\Repository\ContactRepository;
|
use PSC\Shop\ContactBundle\Repository\ContactRepository;
|
||||||
use PSC\Shop\EntityBundle\Entity\Shop;
|
use PSC\Shop\EntityBundle\Entity\Shop;
|
||||||
|
use PSC\Shop\EntityBundle\Repository\JobRepository;
|
||||||
use PSC\Shop\EntityBundle\Repository\ShopRepository;
|
use PSC\Shop\EntityBundle\Repository\ShopRepository;
|
||||||
use PSC\Shop\PaymentBundle\Repository\PaymentRepository;
|
use PSC\Shop\PaymentBundle\Repository\PaymentRepository;
|
||||||
use PSC\Shop\ShippingBundle\Repository\ShippingRepository;
|
use PSC\Shop\ShippingBundle\Repository\ShippingRepository;
|
||||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||||
|
use Tests\RefreshDatabaseTrait;
|
||||||
|
|
||||||
class CreateOrderFromExistingProductTest extends WebTestCase
|
class CreateOrderFromExistingProductTest extends WebTestCase
|
||||||
{
|
{
|
||||||
@ -33,14 +33,14 @@ class CreateOrderFromExistingProductTest extends WebTestCase
|
|||||||
'/api/order/create',
|
'/api/order/create',
|
||||||
[
|
[
|
||||||
'shop' => [
|
'shop' => [
|
||||||
'uuid' => (string)$shop->getUuid()
|
'uuid' => (string) $shop->getUuid(),
|
||||||
],
|
],
|
||||||
'type' => 2,
|
'type' => 2,
|
||||||
'shipping' => [
|
'shipping' => [
|
||||||
'uid' => $shippingRepository->findOneBy(['title' => 'Abholung vor Ort'])->getUid()
|
'uid' => $shippingRepository->findOneBy(['title' => 'Abholung vor Ort'])->getUid(),
|
||||||
],
|
],
|
||||||
'payment' => [
|
'payment' => [
|
||||||
'uid' => $paymentRepository->findOneBy(['title' => 'Bar bei Abholung'])->getUid()
|
'uid' => $paymentRepository->findOneBy(['title' => 'Bar bei Abholung'])->getUid(),
|
||||||
],
|
],
|
||||||
'draft' => false,
|
'draft' => false,
|
||||||
'deliveryAddress' => [
|
'deliveryAddress' => [
|
||||||
@ -49,7 +49,7 @@ class CreateOrderFromExistingProductTest extends WebTestCase
|
|||||||
'street' => 'Chausseestr.',
|
'street' => 'Chausseestr.',
|
||||||
'houseNumber' => '24',
|
'houseNumber' => '24',
|
||||||
'zip' => '17506',
|
'zip' => '17506',
|
||||||
'city' => 'Gribow'
|
'city' => 'Gribow',
|
||||||
],
|
],
|
||||||
'invoiceAddress' => [
|
'invoiceAddress' => [
|
||||||
'firstname' => 'Thomas',
|
'firstname' => 'Thomas',
|
||||||
@ -57,7 +57,7 @@ class CreateOrderFromExistingProductTest extends WebTestCase
|
|||||||
'street' => 'Chausseestr.',
|
'street' => 'Chausseestr.',
|
||||||
'houseNumber' => '24',
|
'houseNumber' => '24',
|
||||||
'zip' => '17400',
|
'zip' => '17400',
|
||||||
'city' => 'Berlin'
|
'city' => 'Berlin',
|
||||||
],
|
],
|
||||||
'positions' => [
|
'positions' => [
|
||||||
[
|
[
|
||||||
@ -68,14 +68,14 @@ class CreateOrderFromExistingProductTest extends WebTestCase
|
|||||||
'specialProductTypeObject' => [
|
'specialProductTypeObject' => [
|
||||||
'typ' => 6,
|
'typ' => 6,
|
||||||
'params' => [
|
'params' => [
|
||||||
'auflage' => 100
|
'auflage' => 100,
|
||||||
],
|
],
|
||||||
]
|
],
|
||||||
]
|
],
|
||||||
]
|
],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
['HTTP_apiKey' => $shop->getApiKey()]
|
['HTTP_apiKey' => $shop->getApiKey()],
|
||||||
);
|
);
|
||||||
self::assertSame(200, $client->getResponse()->getStatusCode());
|
self::assertSame(200, $client->getResponse()->getStatusCode());
|
||||||
|
|
||||||
@ -86,9 +86,9 @@ class CreateOrderFromExistingProductTest extends WebTestCase
|
|||||||
'POST',
|
'POST',
|
||||||
'/api/order/getonebyuuid',
|
'/api/order/getonebyuuid',
|
||||||
[
|
[
|
||||||
'uuid' => $data['uuid'],
|
'uuid' => $data['uuid'],
|
||||||
],
|
],
|
||||||
['HTTP_apiKey' => $shop->getApiKey()]
|
['HTTP_apiKey' => $shop->getApiKey()],
|
||||||
);
|
);
|
||||||
|
|
||||||
self::assertSame(200, $client->getResponse()->getStatusCode());
|
self::assertSame(200, $client->getResponse()->getStatusCode());
|
||||||
@ -117,17 +117,17 @@ class CreateOrderFromExistingProductTest extends WebTestCase
|
|||||||
'/api/order/create',
|
'/api/order/create',
|
||||||
[
|
[
|
||||||
'shop' => [
|
'shop' => [
|
||||||
'uuid' => (string)$shop->getUuid()
|
'uuid' => (string) $shop->getUuid(),
|
||||||
],
|
],
|
||||||
'type' => 2,
|
'type' => 2,
|
||||||
'shipping' => [
|
'shipping' => [
|
||||||
'uid' => $shippingRepository->findOneBy(['title' => 'Abholung vor Ort'])->getUid()
|
'uid' => $shippingRepository->findOneBy(['title' => 'Abholung vor Ort'])->getUid(),
|
||||||
],
|
],
|
||||||
'payment' => [
|
'payment' => [
|
||||||
'uid' => $paymentRepository->findOneBy(['title' => 'Bar bei Abholung'])->getUid()
|
'uid' => $paymentRepository->findOneBy(['title' => 'Bar bei Abholung'])->getUid(),
|
||||||
],
|
],
|
||||||
'contact' => [
|
'contact' => [
|
||||||
'accountType' => AccountType::COMPANY
|
'accountType' => AccountType::COMPANY,
|
||||||
],
|
],
|
||||||
'draft' => false,
|
'draft' => false,
|
||||||
'deliveryAddress' => [
|
'deliveryAddress' => [
|
||||||
@ -136,7 +136,7 @@ class CreateOrderFromExistingProductTest extends WebTestCase
|
|||||||
'street' => 'Chausseestr.',
|
'street' => 'Chausseestr.',
|
||||||
'houseNumber' => '24',
|
'houseNumber' => '24',
|
||||||
'zip' => '17506',
|
'zip' => '17506',
|
||||||
'city' => 'Gribow'
|
'city' => 'Gribow',
|
||||||
],
|
],
|
||||||
'invoiceAddress' => [
|
'invoiceAddress' => [
|
||||||
'firstname' => 'Thomas',
|
'firstname' => 'Thomas',
|
||||||
@ -144,7 +144,7 @@ class CreateOrderFromExistingProductTest extends WebTestCase
|
|||||||
'street' => 'Chausseestr.',
|
'street' => 'Chausseestr.',
|
||||||
'houseNumber' => '24',
|
'houseNumber' => '24',
|
||||||
'zip' => '17400',
|
'zip' => '17400',
|
||||||
'city' => 'Berlin'
|
'city' => 'Berlin',
|
||||||
],
|
],
|
||||||
'positions' => [
|
'positions' => [
|
||||||
[
|
[
|
||||||
@ -155,14 +155,14 @@ class CreateOrderFromExistingProductTest extends WebTestCase
|
|||||||
'specialProductTypeObject' => [
|
'specialProductTypeObject' => [
|
||||||
'typ' => 6,
|
'typ' => 6,
|
||||||
'params' => [
|
'params' => [
|
||||||
'auflage' => 100
|
'auflage' => 100,
|
||||||
],
|
],
|
||||||
]
|
],
|
||||||
]
|
],
|
||||||
]
|
],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
['HTTP_apiKey' => $shop->getApiKey()]
|
['HTTP_apiKey' => $shop->getApiKey()],
|
||||||
);
|
);
|
||||||
|
|
||||||
self::assertSame(200, $client->getResponse()->getStatusCode());
|
self::assertSame(200, $client->getResponse()->getStatusCode());
|
||||||
@ -174,9 +174,9 @@ class CreateOrderFromExistingProductTest extends WebTestCase
|
|||||||
'POST',
|
'POST',
|
||||||
'/api/order/getonebyuuid',
|
'/api/order/getonebyuuid',
|
||||||
[
|
[
|
||||||
'uuid' => $data['uuid'],
|
'uuid' => $data['uuid'],
|
||||||
],
|
],
|
||||||
['HTTP_apiKey' => $shop->getApiKey()]
|
['HTTP_apiKey' => $shop->getApiKey()],
|
||||||
);
|
);
|
||||||
|
|
||||||
self::assertSame(200, $client->getResponse()->getStatusCode());
|
self::assertSame(200, $client->getResponse()->getStatusCode());
|
||||||
@ -187,6 +187,4 @@ class CreateOrderFromExistingProductTest extends WebTestCase
|
|||||||
$jobs = static::getContainer()->get(JobRepository::class);
|
$jobs = static::getContainer()->get(JobRepository::class);
|
||||||
self::assertCount(0, $jobs->findBy(['data.order' => $data['uuid']]));
|
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 string $voucherCode = '';
|
||||||
private ?\DateTime $expirationDate = null;
|
private ?\DateTime $expirationDate = null;
|
||||||
|
|
||||||
|
// VoucherBundle integration
|
||||||
|
private ?int $linkedVoucherUid = null;
|
||||||
|
|
||||||
public function getName(): string
|
public function getName(): string
|
||||||
{
|
{
|
||||||
return 'Gutscheinprodukt';
|
return 'Gutscheinprodukt';
|
||||||
@ -46,6 +49,7 @@ class ProductSpecialObject implements IProductTypeObject
|
|||||||
'voucherCode' => $this->voucherCode,
|
'voucherCode' => $this->voucherCode,
|
||||||
'expirationDate' => $this->expirationDate?->format('Y-m-d H:i:s'),
|
'expirationDate' => $this->expirationDate?->format('Y-m-d H:i:s'),
|
||||||
'validityMonths' => $this->validityMonths,
|
'validityMonths' => $this->validityMonths,
|
||||||
|
'linkedVoucherUid' => $this->linkedVoucherUid,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,4 +173,14 @@ class ProductSpecialObject implements IProductTypeObject
|
|||||||
{
|
{
|
||||||
$this->expirationDate = $expirationDate;
|
$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(
|
public function __construct(
|
||||||
private readonly EntityManagerInterface $entityManager,
|
private readonly EntityManagerInterface $entityManager,
|
||||||
private readonly CodeGenerator $codeGenerator,
|
private readonly CodeGenerator $codeGenerator,
|
||||||
|
private readonly \Plugin\Custom\PSC\Gutschein\Service\VoucherCodeDistributor $voucherCodeDistributor,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function setProduct(Product $product): void
|
public function setProduct(Product $product): void
|
||||||
@ -136,11 +137,54 @@ class Producer implements IUiProducer, IProducerHydrateModel
|
|||||||
|
|
||||||
public function calcPriceForOrderPosition(Position $position): void
|
public function calcPriceForOrderPosition(Position $position): void
|
||||||
{
|
{
|
||||||
// Generate unique voucher code for this order position
|
|
||||||
$voucherCode = $this->codeGenerator->generate();
|
|
||||||
|
|
||||||
/** @var ProductSpecialObject $specProd */
|
/** @var ProductSpecialObject $specProd */
|
||||||
$specProd = $position->getProduct()->getSpecialProductTypeObject();
|
$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->setVoucherCode($voucherCode);
|
||||||
$specProd->setVoucherAmount($this->voucherAmount);
|
$specProd->setVoucherAmount($this->voucherAmount);
|
||||||
$specProd->setRecipientName($this->recipientName);
|
$specProd->setRecipientName($this->recipientName);
|
||||||
|
|||||||
@ -17,3 +17,7 @@ services:
|
|||||||
Plugin\Custom\PSC\Gutschein\Form\Field\Calc:
|
Plugin\Custom\PSC\Gutschein\Form\Field\Calc:
|
||||||
tags:
|
tags:
|
||||||
- { name: psc.backend.custom.fields, productType: 9 }
|
- { 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="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
{{ form_label(form.gutschein.validityMonths) }}
|
{{ form_label(form.validityMonths) }}
|
||||||
{{ form_widget(form.gutschein.validityMonths) }}
|
{{ form_widget(form.validityMonths) }}
|
||||||
<small class="form-text text-muted">Gültigkeitsdauer ab Kaufdatum</small>
|
<small class="form-text text-muted">Gültigkeitsdauer ab Kaufdatum</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -16,22 +16,22 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
{{ form_label(form.gutschein.minAmount) }}
|
{{ form_label(form.minAmount) }}
|
||||||
{{ form_widget(form.gutschein.minAmount) }}
|
{{ form_widget(form.minAmount) }}
|
||||||
<small class="form-text text-muted">Mindestbetrag</small>
|
<small class="form-text text-muted">Mindestbetrag</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
{{ form_label(form.gutschein.defaultAmount) }}
|
{{ form_label(form.defaultAmount) }}
|
||||||
{{ form_widget(form.gutschein.defaultAmount) }}
|
{{ form_widget(form.defaultAmount) }}
|
||||||
<small class="form-text text-muted">Vorauswahl</small>
|
<small class="form-text text-muted">Vorauswahl</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
{{ form_label(form.gutschein.maxAmount) }}
|
{{ form_label(form.maxAmount) }}
|
||||||
{{ form_widget(form.gutschein.maxAmount) }}
|
{{ form_widget(form.maxAmount) }}
|
||||||
<small class="form-text text-muted">Maximalbetrag</small>
|
<small class="form-text text-muted">Maximalbetrag</small>
|
||||||
</div>
|
</div>
|
||||||
</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()) {
|
if ($productDoc->getCustom4()) {
|
||||||
$prodSpec->setValidityMonths((int)$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(
|
public function toDb(
|
||||||
@ -50,5 +54,9 @@ class Product implements IProductTransformer
|
|||||||
$productDoc->setCustom2((string)$prodSpec->getMaxAmount());
|
$productDoc->setCustom2((string)$prodSpec->getMaxAmount());
|
||||||
$productDoc->setCustom3((string)$prodSpec->getDefaultAmount());
|
$productDoc->setCustom3((string)$prodSpec->getDefaultAmount());
|
||||||
$productDoc->setCustom4((string)$prodSpec->getValidityMonths());
|
$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 Product $product;
|
||||||
private Engine $engine;
|
private Engine $engine;
|
||||||
private EntityManagerInterface $entityManager;
|
private EntityManagerInterface $entityManager;
|
||||||
private null|Contact $contact = null;
|
private ?Contact $contact = null;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
Shop $shopService,
|
Shop $shopService,
|
||||||
@ -73,7 +73,6 @@ class Producer implements IUiProducer, IProducerHydrateModel, ICalcNeedContact
|
|||||||
* @var ProductSpecialObject $specProd
|
* @var ProductSpecialObject $specProd
|
||||||
*/
|
*/
|
||||||
$specProd = $this->product->getSpecialProductTypeObject();
|
$specProd = $this->product->getSpecialProductTypeObject();
|
||||||
|
|
||||||
$priceObj = Money::ofMinor($this->engine->getPrice() * 100, 'EUR');
|
$priceObj = Money::ofMinor($this->engine->getPrice() * 100, 'EUR');
|
||||||
|
|
||||||
$price = new Price();
|
$price = new Price();
|
||||||
@ -152,6 +151,9 @@ class Producer implements IUiProducer, IProducerHydrateModel, ICalcNeedContact
|
|||||||
$engine->loadString($product->getCalcXml());
|
$engine->loadString($product->getCalcXml());
|
||||||
$engine->setFormulas($product->getShop()->getFormel());
|
$engine->setFormulas($product->getShop()->getFormel());
|
||||||
$engine->setParameters($product->getShop()->getParameter());
|
$engine->setParameters($product->getShop()->getParameter());
|
||||||
|
|
||||||
|
$spec = $this->product->getSpecialProductTypeObject();
|
||||||
|
$spec->setTaxClass($product->getMwert() * 100);
|
||||||
} elseif ($this->product->getUuid()) {
|
} elseif ($this->product->getUuid()) {
|
||||||
/**
|
/**
|
||||||
* @var \PSC\Shop\EntityBundle\Entity\Product $product
|
* @var \PSC\Shop\EntityBundle\Entity\Product $product
|
||||||
@ -169,6 +171,9 @@ class Producer implements IUiProducer, IProducerHydrateModel, ICalcNeedContact
|
|||||||
$engine->loadString($product->getCalcXml());
|
$engine->loadString($product->getCalcXml());
|
||||||
$engine->setFormulas($product->getShop()->getFormel());
|
$engine->setFormulas($product->getShop()->getFormel());
|
||||||
$engine->setParameters($product->getShop()->getParameter());
|
$engine->setParameters($product->getShop()->getParameter());
|
||||||
|
$spec = $this->product->getSpecialProductTypeObject();
|
||||||
|
|
||||||
|
$spec->setTaxClass($product->getMwert() * 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->engine = $engine;
|
$this->engine = $engine;
|
||||||
@ -248,7 +253,7 @@ class Producer implements IUiProducer, IProducerHydrateModel, ICalcNeedContact
|
|||||||
return $temp;
|
return $temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setContact(null|Contact $contact): void
|
public function setContact(?Contact $contact): void
|
||||||
{
|
{
|
||||||
$this->contact = $contact;
|
$this->contact = $contact;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user