Initial xentral_oss_20.3.c9ffacf

This commit is contained in:
Alex
2021-05-21 08:49:41 +02:00
parent 5406e4a551
commit 34e5ac43d9
18884 changed files with 2109867 additions and 0 deletions
@@ -0,0 +1,129 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\SubscriptionCycle;
use Xentral\Modules\SubscriptionCycle\Data\SubscriptionCycleArticleData;
use Xentral\Modules\SubscriptionCycle\Data\SubscriptionCycleAutoSubscriptionData;
use Xentral\Modules\SubscriptionCycle\Exception\AutoSubscriptionNotFoundException;
use Xentral\Modules\SubscriptionCycle\Exception\RuntimeException;
use Xentral\Modules\SubscriptionCycle\Exception\ValidationFailedException;
use Xentral\Modules\SubscriptionCycle\Service\SubscriptionCycleArticleService;
use Xentral\Modules\SubscriptionCycle\Service\SubscriptionCycleAutoSubscriptionGateway;
use Xentral\Modules\SubscriptionCycle\Service\SubscriptionCycleAutoSubscriptionService;
use Xentral\Modules\SubscriptionCycle\Wrapper\BusinessLetterWrapper;
final class AutoSubscriptionModule
{
/** @var SubscriptionCycleAutosubScriptionService $autosubscriptionService */
private $autoSubscriptionService;
/** @var SubscriptionCycleAutoSubscriptionGateway $autosubscriptionGateway */
private $autoSubscriptionGateway;
/** @var SubscriptionCycleArticleService $subscriptionCycleArticleService */
private $subscriptionCycleArticleService;
/** @var BusinessLetterWrapper $businessLetterWrapper */
private $businessLetterWrapper;
/**
* @param SubscriptionCycleAutoSubscriptionService $autoSubscriptionService
* @param SubscriptionCycleAutoSubscriptionGateway $autoSubscriptionGateway
* @param SubscriptionCycleArticleService $subscriptionCycleArticleService
* @param BusinessLetterWrapper $businessLetterWrapper
*/
public function __construct(
SubscriptionCycleAutoSubscriptionService $autoSubscriptionService,
SubscriptionCycleAutoSubscriptionGateway $autoSubscriptionGateway,
SubscriptionCycleArticleService $subscriptionCycleArticleService,
BusinessLetterWrapper $businessLetterWrapper
) {
$this->autoSubscriptionService = $autoSubscriptionService;
$this->autoSubscriptionGateway = $autoSubscriptionGateway;
$this->subscriptionCycleArticleService = $subscriptionCycleArticleService;
$this->businessLetterWrapper = $businessLetterWrapper;
}
/**
* @param SubscriptionCycleAutoSubscriptionData $autosubscription
*
* @throws ValidationFailedException
*/
public function saveNewAutoSubscription(SubscriptionCycleAutoSubscriptionData $autosubscription): void
{
$this->autoSubscriptionService->create($autosubscription);
}
/**
* @param SubscriptionCycleAutoSubscriptionData $autosubscription
*
* @throws ValidationFailedException
* @throws AutoSubscriptionNotFoundException
*/
public function updateAutoSubscription(SubscriptionCycleAutoSubscriptionData $autosubscription): void
{
$this->autoSubscriptionService->edit($autosubscription);
}
/**
* @param int $autoSubscriptionId
*
* @throws AutoSubscriptionNotFoundException
*
* @return SubscriptionCycleAutoSubscriptionData
*/
public function getAutoSubscriptionById(int $autoSubscriptionId): SubscriptionCycleAutoSubscriptionData
{
$autosubscription = $this->autoSubscriptionGateway->getById($autoSubscriptionId);
return $autosubscription;
}
/**
* @param int $autoSubscriptionId
*
* @throws AutoSubscriptionNotFoundException
* @throws RuntimeException
*/
public function deleteAutoSubscriptionById(int $autoSubscriptionId): void
{
$this->autoSubscriptionService->removeById($autoSubscriptionId);
}
/**
* @param int $docId
*/
public function createSubscription(int $docId): void
{
$data = $this->autoSubscriptionGateway->findAutoSubscriptionData($docId);
if (!empty($data)) {
foreach ($data as $d) {
$subscriptionArticle = SubscriptionCycleArticleData::fromArray($d);
$this->subscriptionCycleArticleService->create($subscriptionArticle);
if ($d['prevent_auto_dispatch'] == 1) {
$this->autoSubscriptionService->preventAutoDispatch($docId);
}
}
$this->businessLetterWrapper->sendBusinessLetter($data, $docId);
}
}
/**
* @param int $docId
*
* @return bool
*/
public function hasDocAutoSubscription(int $docId): bool
{
$data = $this->autoSubscriptionGateway->findAutoSubscriptionData($docId);
if (!empty($data)) {
return true;
}
return false;
}
}
@@ -0,0 +1,231 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\SubscriptionCycle;
use Aboabrechnung;
use Xentral\Components\SchemaCreator\Collection\SchemaCollection;
use Xentral\Components\SchemaCreator\Schema\TableSchema;
use Xentral\Components\SchemaCreator\Type;
use Xentral\Components\SchemaCreator\Index;
use Xentral\Core\DependencyInjection\ContainerInterface;
use Xentral\Modules\SubscriptionCycle\Scheduler\SubscriptionCycleFullTask;
use Xentral\Modules\SubscriptionCycle\Scheduler\SubscriptionCycleManualJobTask;
use Xentral\Modules\SubscriptionCycle\Scheduler\TaskMutexService;
use Xentral\Modules\SubscriptionCycle\Service\SubscriptionCycleArticleService;
use Xentral\Modules\SubscriptionCycle\Service\SubscriptionCycleAutoSubscriptionGateway;
use Xentral\Modules\SubscriptionCycle\Service\SubscriptionCycleAutoSubscriptionService;
use Xentral\Modules\SubscriptionCycle\Service\SubscriptionCycleArticleGateway;
use Xentral\Modules\SubscriptionCycle\Service\SubscriptionCycleCacheService;
use Xentral\Modules\SubscriptionCycle\Service\SubscriptionCycleJobService;
use Xentral\Modules\SubscriptionCycle\Wrapper\BusinessLetterWrapper;
final class Bootstrap
{
/**
* @return array
*/
public static function registerServices(): array
{
return [
'AutoSubscriptionModule' => 'onInitAutoSubscriptionModule',
'SubscriptionCycleCacheFiller' => 'onInitSubscriptionCycleCacheFiller',
'SubscriptionCycleManualJobTask' => 'onInitSubscriptionCycleManualJobTask',
'SubscriptionCycleJobService' => 'onInitSubscriptionCycleJobService',
'SubscriptionCycleFullTask' => 'onInitSubscriptionCycleFullTask',
'TaskMutexService' => 'onInitTaskMutexService',
];
}
/**
* @param ContainerInterface $container
*
* @return AutoSubscriptionModule
*/
public static function onInitAutoSubscriptionModule(ContainerInterface $container): AutoSubscriptionModule
{
return new AutoSubscriptionModule(
self::onInitSubscriptionCycleAutoSubscriptionService($container),
self::onInitSubscriptionCycleAutoSubscriptionGateway($container),
self::onInitSubscriptionCycleArticleService($container),
self::onInitBusinessLetterWrapper($container)
);
}
/**
* @param ContainerInterface $container
*
* @return SubscriptionCycleManualJobTask
*/
public static function onInitSubscriptionCycleManualJobTask(ContainerInterface $container
): SubscriptionCycleManualJobTask {
$legacyApp = $container->get('LegacyApplication');
$subscriptionCycleModule = $legacyApp->loadModule('rechnungslauf');
$subscriptionModule = new Aboabrechnung($legacyApp);
$subscriptionModule->cronjob = true;
return new SubscriptionCycleManualJobTask(
$legacyApp,
$container->get('Database'),
$container->get('TaskMutexService'),
$container->get('SubscriptionCycleJobService'),
$subscriptionCycleModule,
$subscriptionModule,
!empty($legacyApp->erp->GetKonfiguration('rechnungslauf_gruppen'))
);
}
/**
* @param ContainerInterface $container
*
* @return SubscriptionCycleJobService
*/
public static function onInitSubscriptionCycleJobService(ContainerInterface $container): SubscriptionCycleJobService
{
return new SubscriptionCycleJobService($container->get('Database'));
}
/**
* @param ContainerInterface $container
*
* @return SubscriptionCycleFullTask
*/
public static function onInitSubscriptionCycleFullTask(ContainerInterface $container): SubscriptionCycleFullTask
{
$legacyApp = $container->get('LegacyApplication');
$legacyApp->loadModule('rechnungslauf');
$subscriptionModule = new Aboabrechnung($legacyApp);
$subscriptionModule->cronjob = true;
return new SubscriptionCycleFullTask(
$legacyApp,
$container->get('Database'),
$container->get('TaskMutexService'),
$container->get('SubscriptionCycleJobService'),
$subscriptionModule,
!empty($legacyApp->erp->GetKonfiguration('rechnungslauf_cronjoborders')),
!empty($legacyApp->erp->GetKonfiguration('rechnungslauf_cronjobinvoices')),
(int)$legacyApp->erp->GetKonfiguration('rechnungslauf_cronjobprinter'),
(string)$legacyApp->erp->GetKonfiguration('rechnungslauf_cronjobemailprinter')
);
}
/**
* @param ContainerInterface $container
*
* @return TaskMutexService
*/
public static function onInitTaskMutexService(ContainerInterface $container): TaskMutexService
{
return new TaskMutexService($container->get('Database'));
}
/**
* @param ContainerInterface $container
*
* @return SubscriptionCycleAutoSubscriptionService
*/
private static function onInitSubscriptionCycleAutoSubscriptionService(ContainerInterface $container
): SubscriptionCycleAutoSubscriptionService {
return new SubscriptionCycleAutoSubscriptionService(
$container->get('Database')
);
}
/**
* @param ContainerInterface $container
*
* @return SubscriptionCycleAutoSubscriptionGateway
*/
private static function onInitSubscriptionCycleAutosubScriptionGateway(ContainerInterface $container
): SubscriptionCycleAutoSubscriptionGateway {
return new SubscriptionCycleAutoSubscriptionGateway(
$container->get('Database')
);
}
/**
* @param ContainerInterface $container
*
* @return SubscriptionCycleArticleService
*/
private static function onInitSubscriptionCycleArticleService(ContainerInterface $container
): SubscriptionCycleArticleService {
return new SubscriptionCycleArticleService(
$container->get('Database')
);
}
/**
* @param ContainerInterface $container
*
* @return BusinessLetterWrapper
*/
private static function onInitBusinessLetterWrapper(ContainerInterface $container): BusinessLetterWrapper
{
return new BusinessLetterWrapper(
$container->get('LegacyApplication'),
$container->get('SystemMailer'),
$container->get('EmailAccountGateway')
);
}
/**
* @param ContainerInterface $container
*
* @return SubscriptionCycleCacheService
*/
private static function onInitSubscriptionCycleCacheService(ContainerInterface $container
): SubscriptionCycleCacheService {
return new SubscriptionCycleCacheService(
$container->get('Database')
);
}
/**
* @param ContainerInterface $container
*
* @return SubscriptionCycleArticleGateway
*/
private static function onInitSubscriptionCycleCacheGateway(ContainerInterface $container
): SubscriptionCycleArticleGateway {
return new SubscriptionCycleArticleGateway(
$container->get('Database')
);
}
/**
* @param ContainerInterface $container
*
* @return SubscriptionCycleCacheFiller
*/
public static function onInitSubscriptionCycleCacheFiller(
ContainerInterface $container
): SubscriptionCycleCacheFiller {
return new SubscriptionCycleCacheFiller(
self::onInitSubscriptionCycleCacheGateway($container),
self::onInitSubscriptionCycleCacheService($container)
);
}
/**
* @param SchemaCollection $collection
*
* @return void
*/
public static function registerTableSchemas(SchemaCollection $collection): void
{
$subscriptionCycleJob = new TableSchema('subscription_cycle_job');
$subscriptionCycleJob->addColumn(Type\Integer::asAutoIncrement('id'));
$subscriptionCycleJob->addColumn(new Type\Integer('address_id'));
$subscriptionCycleJob->addColumn(new Type\Varchar('document_type', 32));
$subscriptionCycleJob->addColumn(new Type\Varchar('job_type', 32));
$subscriptionCycleJob->addColumn(new Type\Integer('printer_id'));
$subscriptionCycleJob->addColumn(new Type\Timestamp('created_at', 'CURRENT_TIMESTAMP'));
$subscriptionCycleJob->addIndex(new Index\Primary(['id']));
$subscriptionCycleJob->addIndex(new Index\Index(['address_id']));
$collection->add($subscriptionCycleJob);
}
}
@@ -0,0 +1,558 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\SubscriptionCycle\Data;
use Xentral\Modules\SubscriptionCycle\Exception\ValidationFailedException;
final class SubscriptionCycleArticleData
{
/** @var int $id */
private $id = 0;
/** @var int $sort */
private $sort = 0;
/** @var int $articleId */
private $articleId = 0;
/** @var string $articleName */
private $articleName = '';
/** @var string $articleNumber */
private $articleNumber = '';
/** @var float $amount */
private $amount = 0.0;
/** @var float $price */
private $price = 0.0;
/** @var string $taxClass */
private $taxClass = '';
/** @var float $discount */
private $discount = 0.0;
/** @var bool $cleared */
private $cleared = false;
/** @var string $startDate */
private $startDate = '0000-00-00';
/** @var string $deliveranceDate */
private $deliveranceDate = '0000-00-00';
/** @var string $clearedTill */
private $clearedTill = '0000-00-00';
/** @var bool $repeating */
private $repeating = false;
/** @var int $payCycle */
private $payCycle = 0;
/** @var string $clearedOn */
private $clearedOn = '0000-00-00';
/** @var int $invoiceId */
private $invoiceId = 0;
/** @var int $projectId */
private $projectId = 0;
/** @var int $adressId */
private $adressId = 0;
/** @var string $status */
private $status = 'angelegt';
/** @var string $text */
private $text = '';
/** @var string $logFile */
private $logFile = '0000-00-00';
/** @var string $description */
private $description = '';
/** @var string $document */
private $document = '';
/** @var string $priceType */
private $priceType = '';
/** @var string $endDate */
private $endDate = '0000-00-00';
/** @var int $createdBy */
private $createdBy = 0;
/** @var string $createdDate */
private $createdDate = '0000-00-00';
/** @var bool $expert */
private $expert = false;
/** @var string $currency */
private $currency = '';
/** @var bool $replaceDescription */
private $replaceDescription = false;
/** @var int $subscriptionCycleGroupId */
private $subscriptionCycleGroupId = 0;
private function __construct()
{
}
/**
* @param array $data
*
* @throws ValidationFailedException
*
* @return SubscriptionCycleArticleData
*/
public static function fromArray(array $data): SubscriptionCycleArticleData
{
$errors = self::validate($data);
if (!empty($errors)) {
throw ValidationFailedException::fromErrors($errors);
}
if (isset($data['id']) && !is_int($data['id'])) {
$data['id'] = (int)$data['id'];
}
if (isset($data['menge']) && !is_float($data['menge'])) {
$data['menge'] = (float)$data['menge'];
}
if (isset($data['preis']) && !is_float($data['preis'])) {
$data['preis'] = (float)$data['preis'];
}
if (isset($data['rabatt']) && !is_float($data['rabatt'])) {
$data['rabatt'] = (float)$data['rabatt'];
}
if (isset($data['abgerechnet']) && !is_bool($data['abgerechnet'])) {
$data['abgerechnet'] = (bool)$data['abgerechnet'];
}
if (isset($data['wiederholend']) && !is_bool($data['wiederholend'])) {
$data['wiederholend'] = (bool)$data['wiederholend'];
}
if (isset($data['beschreibungersetzten']) && !is_bool($data['beschreibungersetzten'])) {
$data['beschreibungersetzten'] = (bool)$data['beschreibungersetzten'];
}
if (isset($data['experte']) && !is_bool($data['experte'])) {
$data['experte'] = (bool)$data['experte'];
}
return self::fromDbState($data);
}
/**
* @param array $data
*
* @return array
*/
private static function validate(array $data): array
{
$errors = [];
if (!isset($data['artikel']) || empty($data['artikel'])) {
$errors['artikel'][] = 'Article_id is not set.';
}
if (!isset($data['menge']) || empty($data['menge'])) {
$errors['menge'][] = 'Amount is not set';
}
if (!isset($data['preis']) || empty($data['preis'])) {
$errors['preis'][] = 'Price is not set.';
}
if (!isset($data['dokument']) || empty($data['dokument'])) {
$errors['dokument'][] = 'Document is not set.';
}
if (!isset($data['preisart']) || empty($data['preisart'])) {
$errors['preisart'][] = 'Price-type is not set.';
}
return $errors;
}
/**
* @param array $data
*
* @return SubscriptionCycleArticleData
*/
public static function fromDbState(array $data): SubscriptionCycleArticleData
{
$instance = new self();
if (isset($data['id'])) {
$instance->id = $data['id'];
}
if (isset($data['sort'])) {
$instance->sort = $data['sort'];
}
if (isset($data['artikel'])) {
$instance->articleId = $data['artikel'];
}
if (isset($data['bezeichnung'])) {
$instance->articleName = $data['bezeichnung'];
}
if (isset($data['nummer'])) {
$instance->articleNumber = $data['nummer'];
}
if (isset($data['menge'])) {
$instance->amount = $data['menge'];
}
if (isset($data['preis'])) {
$instance->price = $data['preis'];
}
if (isset($data['steuerklasse'])) {
$instance->taxClass = $data['steuerklasse'];
}
if (isset($data['rabatt'])) {
$instance->discount = $data['rabatt'];
}
if (isset($data['abgerechnet'])) {
$instance->cleared = $data['abgerechnet'];
}
if (isset($data['startdatum'])) {
$instance->startDate = $data['startdatum'];
}
if (isset($data['lieferdatum'])) {
$instance->deliveranceDate = $data['lieferdatum'];
}
if (isset($data['abgerechnetbis'])) {
$instance->clearedTill = $data['abgerechnetbis'];
}
if (isset($data['wiederholend'])) {
$instance->repeating = $data['wiederholend'];
}
if (isset($data['zahlzyklus'])) {
$instance->payCycle = $data['zahlzyklus'];
}
if (isset($data['abgrechnetam'])) {
$instance->clearedOn = $data['abgrechnetam'];
}
if (isset($data['rechnung'])) {
$instance->invoiceId = $data['rechnung'];
}
if (isset($data['projekt'])) {
$instance->projectId = $data['projekt'];
}
if (isset($data['adresse'])) {
$instance->adressId = $data['adresse'];
}
if (isset($data['status'])) {
$instance->status = $data['status'];
}
if (isset($data['bemerkung'])) {
$instance->text = $data['bemerkung'];
}
if (isset($data['logdatei'])) {
$instance->logFile = $data['logdatei'];
}
if (isset($data['beschreibung'])) {
$instance->description = $data['beschreibung'];
}
if (isset($data['dokument'])) {
$instance->document = $data['dokument'];
}
if (isset($data['enddatum'])) {
$instance->endDate = $data['enddatum'];
}
if (isset($data['angelegtvon'])) {
$instance->createdBy = $data['angelegtvon'];
}
if (isset($data['angelegtam'])) {
$instance->createdDate = $data['angelegtam'];
}
if (isset($data['waehrung'])) {
$instance->currency = $data['waehrung'];
}
if (isset($data['beschreibungersetzten'])) {
$instance->replaceDescription = $data['beschreibungersetzten'];
}
if (isset($data['gruppe'])) {
$instance->subscriptionCycleGroupId = $data['gruppe'];
}
if (isset($data['preisart'])) {
$instance->priceType = $data['preisart'];
}
if (isset($data['experte'])) {
$instance->expert = $data['experte'];
}
return $instance;
}
/**
* @return int
*/
public function getId(): int
{
return $this->id;
}
/**
* @return int
*/
public function getSort(): int
{
return $this->sort;
}
/**
* @return int
*/
public function getArticleId(): int
{
return $this->articleId;
}
/**
* @return string
*/
public function getArticleName(): string
{
return $this->articleName;
}
/**
* @return string
*/
public function getArticleNumber(): string
{
return $this->articleNumber;
}
/**
* @return float
*/
public function getAmount(): float
{
return $this->amount;
}
/**
* @return float
*/
public function getPrice(): float
{
return $this->price;
}
/**
* @return string
*/
public function getTaxClass(): string
{
return $this->taxClass;
}
/**
* @return float
*/
public function getDiscount(): float
{
return $this->discount;
}
/**
* @return bool
*/
public function isCleared(): bool
{
return $this->cleared;
}
/**
* @return string
*/
public function getStartDate(): string
{
return $this->startDate;
}
/**
* @return string
*/
public function getDeliveranceDate(): string
{
return $this->deliveranceDate;
}
/**
* @return string
*/
public function getClearedTill(): string
{
return $this->clearedTill;
}
/**
* @return bool
*/
public function isRepeating(): bool
{
return $this->repeating;
}
/**
* @return int
*/
public function getPayCycle(): int
{
return $this->payCycle;
}
/**
* @return string
*/
public function getClearedOn(): string
{
return $this->clearedOn;
}
/**
* @return int
*/
public function getInvoiceId(): int
{
return $this->invoiceId;
}
/**
* @return int
*/
public function getProjectId(): int
{
return $this->projectId;
}
/**
* @return int
*/
public function getAdressId(): int
{
return $this->adressId;
}
/**
* @return string
*/
public function getStatus(): string
{
return $this->status;
}
/**
* @return string
*/
public function getText(): string
{
return $this->text;
}
/**
* @return string
*/
public function getLogFile(): string
{
return $this->logFile;
}
/**
* @return string
*/
public function getDescription(): string
{
return $this->description;
}
/**
* @return string
*/
public function getDocument(): string
{
return $this->document;
}
/**
* @return string
*/
public function getPriceType(): string
{
return $this->priceType;
}
/**
* @return string
*/
public function getEndDate(): string
{
return $this->endDate;
}
/**
* @return int
*/
public function getCreatedBy(): int
{
return $this->createdBy;
}
/**
* @return string
*/
public function getCreatedDate(): string
{
return $this->createdDate;
}
/**
* @return bool
*/
public function isExpert(): bool
{
return $this->expert;
}
/**
* @return string
*/
public function getCurrency(): string
{
return $this->currency;
}
/**
* @return bool
*/
public function isReplaceDescription(): bool
{
return $this->replaceDescription;
}
/**
* @return int
*/
public function getSubscriptionCycleGroupId(): int
{
return $this->subscriptionCycleGroupId;
}
}
@@ -0,0 +1,296 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\SubscriptionCycle\Data;
use Xentral\Modules\SubscriptionCycle\Exception\ValidationFailedException;
final class SubscriptionCycleAutoSubscriptionData
{
/** @var int $id */
private $id = 0;
/** @var int $projectId */
private $projectId = 0;
/** @var int $articleId */
private $articleId = 0;
/** @var string $priceCycle */
private $priceCycle = '';
/** @var string $documentType */
private $documentType = '';
/** @var int $subscriptionGroupId */
private $subscriptionGroupId = 0;
/** @var int $position */
private $position = 0;
/** @var string $firstDateType */
private $firstDateType = '';
/** @var bool $preventAutoDispatch */
private $preventAutoDispatch = true;
/** @var bool $autoEmailConfirmation */
private $autoEmailConfirmation = true;
/** @var int $businessLetterPatternId */
private $businessLetterPatternId = 0;
/** @var bool $addPdf */
private $addPdf = true;
private function __construct()
{
}
/**
* @param array $data
*
* @throws ValidationFailedException
*
* @return SubscriptionCycleAutoSubscriptionData
*/
public static function fromArray(array $data): SubscriptionCycleAutoSubscriptionData
{
$errors = self::validate($data);
if (!empty($errors)) {
throw ValidationFailedException::fromErrors($errors);
}
if (isset($data['id']) && !is_int($data['id'])) {
$data['id'] = (int)$data['id'];
}
if (isset($data['project_id']) && !is_int($data['project_id'])) {
$data['project_id'] = (int)$data['project_id'];
}
if (isset($data['article_id']) && !is_int($data['article_id'])) {
$data['article_id'] = (int)$data['article_id'];
}
if (isset($data['subscription_group_id']) && !is_int($data['subscription_group_id'])) {
$data['subscription_group_id'] = (int)$data['subscription_group_id'];
}
if (isset($data['position']) && !is_int($data['position'])) {
$data['position'] = (int)$data['position'];
}
if (isset($data['prevent_auto_dispatch']) && !is_bool($data['prevent_auto_dispatch'])) {
$data['prevent_auto_dispatch'] = (bool)$data['prevent_auto_dispatch'];
}
if (isset($data['auto_email_confirmation']) && !is_bool($data['auto_email_confirmation'])) {
$data['auto_email_confirmation'] = (bool)$data['auto_email_confirmation'];
}
if (isset($data['business_letter_pattern_id']) && !is_int($data['business_letter_pattern_id'])) {
$data['business_letter_pattern_id'] = (int)$data['business_letter_pattern_id'];
}
if (isset($data['add_pdf']) && !is_bool($data['add_pdf'])) {
$data['add_pdf'] = (bool)$data['add_pdf'];
}
return self::fromDbState($data);
}
/**
* @param array $data
*
* @return array
*/
private static function validate(array $data): array
{
$errors = [];
if (!isset($data['article_id']) || empty($data['article_id'])) {
$errors['article_id'][] = 'Article-id is not set.';
}
return $errors;
}
/**
* @param array $data
*
* @return SubscriptionCycleAutoSubscriptionData
*/
public static function fromDbState(array $data): SubscriptionCycleAutoSubscriptionData
{
$instance = new self();
if (isset($data['id'])) {
$instance->id = $data['id'];
}
if (isset($data['project_id'])) {
$instance->projectId = $data['project_id'];
}
if (isset($data['article_id'])) {
$instance->articleId = $data['article_id'];
}
if (isset($data['price_cycle'])) {
$instance->priceCycle = $data['price_cycle'];
}
if (isset($data['document_type'])) {
$instance->documentType = $data['document_type'];
}
if (isset($data['subscription_group_id'])) {
$instance->subscriptionGroupId = $data['subscription_group_id'];
}
if (isset($data['position'])) {
$instance->position = $data['position'];
}
if (isset($data['first_date_type'])) {
$instance->firstDateType = $data['first_date_type'];
}
if (isset($data['prevent_auto_dispatch'])) {
$instance->preventAutoDispatch = $data['prevent_auto_dispatch'];
}
if (isset($data['auto_email_confirmation'])) {
$instance->autoEmailConfirmation = $data['auto_email_confirmation'];
}
if (isset($data['business_letter_pattern_id'])) {
$instance->businessLetterPatternId = $data['business_letter_pattern_id'];
}
if (isset($data['add_pdf'])) {
$instance->addPdf = $data['add_pdf'];
}
return $instance;
}
/**
* @return int
*/
public function getId(): int
{
return $this->id;
}
/**
* @return int
*/
public function getProjectId(): int
{
return $this->projectId;
}
/**
* @return int
*/
public function getArticleId(): int
{
return $this->articleId;
}
/**
* @return string
*/
public function getPriceCycle(): string
{
return $this->priceCycle;
}
/**
* @return string
*/
public function getDocumentType(): string
{
return $this->documentType;
}
/**
* @return int
*/
public function getSubscriptionGroupId(): int
{
return $this->subscriptionGroupId;
}
/**
* @return int
*/
public function getPosition(): int
{
return $this->position;
}
/**
* @return string
*/
public function getFirstDateType(): string
{
return $this->firstDateType;
}
/**
* @return bool
*/
public function getPreventAutoDispatch(): bool
{
return $this->preventAutoDispatch;
}
/**
* @return bool
*/
public function getAutoEmailConfirmation(): bool
{
return $this->autoEmailConfirmation;
}
/**
* @return int
*/
public function getBusinessLetterPatternId(): int
{
return $this->businessLetterPatternId;
}
/**
* @return bool
*/
public function getAddPdf(): bool
{
return $this->addPdf;
}
/**
* @return array
*/
public function toArray(): array
{
return [
'id' => $this->id,
'project_id' => $this->projectId,
'article_id' => $this->articleId,
'price_cycle' => $this->priceCycle,
'document_type' => $this->documentType,
'subscription_group_id' => $this->subscriptionGroupId,
'position' => $this->position,
'first_date_type' => $this->firstDateType,
'prevent_auto_dispatch' => $this->preventAutoDispatch,
'auto_email_confirmation' => $this->autoEmailConfirmation,
'business_letter_pattern_id' => $this->businessLetterPatternId,
'add_pdf' => $this->addPdf,
];
}
}
@@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\SubscriptionCycle\Data;
use DateTimeImmutable;
use DateTimeInterface;
final class SubscriptionCycleCacheData
{
/** @var int $subscriptionArticleId */
private $subscriptionArticleId;
/** @var DateTimeInterface $startDate */
private $startDate;
/** @var DateTimeInterface $calculationBaseDate */
private $calculationBaseDate;
/** @var float $startMonthPriceFactor */
private $startMonthPriceFactor;
/** @var int $cyclesCount */
private $cyclesCount;
private function __construct()
{
}
/**
* @param array $data
*
* @return SubscriptionCycleCacheData
*/
public static function fromDbState(array $data): SubscriptionCycleCacheData
{
$cacheData = new SubscriptionCycleCacheData();
$cacheData->subscriptionArticleId = (int)$data['subscription_article_id'];
$cacheData->startDate = new DateTimeImmutable($data['start_date']);
$cacheData->cyclesCount = (int)$data['cycles_count'];
$cacheData->calculationBaseDate = new DateTimeImmutable($data['calculation_base_date']);
$cacheData->startMonthPriceFactor = (float)$data['start_month_price_factor'];
return $cacheData;
}
/**
* @return int
*/
public function getSubscriptionArticleId(): int
{
return $this->subscriptionArticleId;
}
/**
* @return DateTimeInterface
*/
public function getStartDate(): DateTimeInterface
{
return $this->startDate;
}
/**
* @return DateTimeInterface
*/
public function getCalculationBaseDate(): DateTimeInterface
{
return $this->calculationBaseDate;
}
/**
* @return float
*/
public function getStartMonthPriceFactor(): float
{
return $this->startMonthPriceFactor;
}
/**
* @return int
*/
public function getCyclesCount(): int
{
return $this->cyclesCount;
}
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\SubscriptionCycle\Exception;
use RuntimeException as SplRuntimeException;
final class AutoSubscriptionNotFoundException extends SplRuntimeException implements SubscriptionCycleExceptionInterface
{
}
@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\SubscriptionCycle\Exception;
final class InvalidArgumentException extends \InvalidArgumentException
{
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\SubscriptionCycle\Exception;
use RuntimeException as SplRuntimeException;
final class OrderNotFoundException extends SplRuntimeException implements SubscriptionCycleExceptionInterface
{
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\SubscriptionCycle\Exception;
use RuntimeException as SplRuntimeException;
final class RuntimeException extends SplRuntimeException implements SubscriptionCycleExceptionInterface
{
}
@@ -0,0 +1,9 @@
<?php
namespace Xentral\Modules\SubscriptionCycle\Exception;
use Xentral\Core\Exception\ModuleExceptionInterface;
interface SubscriptionCycleExceptionInterface extends ModuleExceptionInterface
{
}
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\SubscriptionCycle\Exception;
use RuntimeException;
final class ValidationFailedException extends RuntimeException implements SubscriptionCycleExceptionInterface
{
/** @var array $errors */
private $errors = [];
/**
* @param array $errors
*
* @return self
*/
public static function fromErrors(array $errors)
{
$errorString = '';
foreach ($errors as $propertyName => $propertyErrors) {
$errorString .= implode("\r\n", $propertyErrors);
}
$exception = new self('Validation failed with following errors: ' . "\n\n" . $errorString);
$exception->errors = $errors;
return $exception;
}
/**
* @return array
*/
public function getErrors()
{
return $this->errors;
}
}
@@ -0,0 +1,119 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\SubscriptionCycle\Scheduler;
use Aboabrechnung;
use ApplicationCore;
use Xentral\Components\Database\Database;
use Xentral\Modules\SubscriptionCycle\Service\SubscriptionCycleJobService;
use Xentral\Modules\SubscriptionCycle\SubscriptionModuleInterface;
final class SubscriptionCycleFullTask
{
/** @var ApplicationCore $app */
private $app;
/** @var Database $db */
private $db;
/** @var TaskMutexServiceInterface $taskMutexService */
private $taskMutexService;
/** @var SubscriptionModuleInterface $subscriptionModule */
private $subscriptionModule;
/** @var bool $isOrdersActive */
private $isOrdersActive;
/** @var bool $isInvoiceActive */
private $isInvoiceActive;
/** @var int|null $printerId */
private $printerId;
/** @var string $mailPrinter */
private $mailPrinter;
/** @var SubscriptionCycleJobService $cycleJobService */
private $cycleJobService;
/**
* SubscriptionCycleFullTask constructor.
*
* @param ApplicationCore $app
* @param Database $db
* @param SubscriptionCycleJobService $cycleJobService
* @param bool $isOrdersActive
* @param bool $isInvoiceActive
* @param int|null $printerId
* @param string $mailPrinter
*/
public function __construct(
ApplicationCore $app,
Database $db,
TaskMutexServiceInterface $taskMutexService,
SubscriptionCycleJobService $cycleJobService,
SubscriptionModuleInterface $subscriptionModule,
bool $isOrdersActive,
bool $isInvoiceActive,
?int $printerId,
string $mailPrinter
) {
$this->app = $app;
$this->db = $db;
$this->taskMutexService = $taskMutexService;
$this->subscriptionModule = $subscriptionModule;
$this->cycleJobService = $cycleJobService;
$this->isOrdersActive = $isOrdersActive;
$this->isInvoiceActive = $isInvoiceActive;
$this->printerId = $printerId;
$this->mailPrinter = $mailPrinter;
}
public function execute(): void
{
if ($this->taskMutexService->isTaskInstanceRunning('rechnungslauf')) {
return;
}
$this->taskMutexService->setMutex('rechnungslauf');
if (empty($this->isOrdersActive) && empty($this->isInvoiceActive)) {
return;
}
if ($this->isOrdersActive) {
$orderAddresses = array_map(
'intval',
array_keys((array)$this->subscriptionModule->GetRechnungsArray('auftrag'))
);
$addressIdsInJobs = $this->cycleJobService->getAddressIdsByDocumentType('auftrag');
$orderAddresses = array_diff($orderAddresses, $addressIdsInJobs);
foreach ($orderAddresses as $addressToAdd) {
$this->cycleJobService->create($addressToAdd, 'auftrag', $this->mailPrinter, $this->printerId);
}
unset($orderAddresses);
}
if ($this->isInvoiceActive) {
$invoiceAddresses = array_map(
'intval',
array_keys((array)$this->subscriptionModule->GetRechnungsArray('rechnung'))
);
$addressIdsInJobs = $this->cycleJobService->getAddressIdsByDocumentType('rechnung');
$invoiceAddresses = array_diff($invoiceAddresses, $addressIdsInJobs);
foreach ($invoiceAddresses as $addressToAdd) {
$this->cycleJobService->create($addressToAdd, 'rechnung', $this->mailPrinter, $this->printerId);
}
}
if (empty($this->isInvoiceActive)) {
return;
}
}
public function cleanup(): void
{
$this->taskMutexService->setMutex('rechnungslauf', false);
}
}
@@ -0,0 +1,200 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\SubscriptionCycle\Scheduler;
use ApplicationCore;
use DateTimeImmutable;
use DateTimeInterface;
use Exception;
use Xentral\Components\Database\Database;
use Xentral\Modules\SubscriptionCycle\Service\SubscriptionCycleJobService;
use Xentral\Modules\SubscriptionCycle\SubscriptionCycleModuleInterface;
use Xentral\Modules\SubscriptionCycle\SubscriptionModuleInterface;
final class SubscriptionCycleManualJobTask
{
/** @var ApplicationCore $app */
private $app;
/** @var Database $db */
private $db;
/** @var SubscriptionCycleJobService $cycleJobService */
private $cycleJobService;
/** @var TaskMutexServiceInterface $taskMutexService */
private $taskMutexService;
/** @var SubscriptionCycleModuleInterface $subscriptionCycleModule */
private $subscriptionCycleModule;
/** @var SubscriptionModuleInterface $subscriptionModule */
private $subscriptionModule;
/** @var bool $useGroups */
private $useGroups;
/**
* SubscriptionCycleManualJobTask constructor.
*
* @param ApplicationCore $app
* @param Database $db
* @param SubscriptionCycleJobService $cycleJobService
* @param SubscriptionCycleModuleInterface $subscriptionCycleModule
* @param bool $useGroups
*/
public function __construct(
ApplicationCore $app,
Database $db,
TaskMutexServiceInterface $taskMutexService,
SubscriptionCycleJobService $cycleJobService,
SubscriptionCycleModuleInterface $subscriptionCycleModule,
SubscriptionModuleInterface $subscriptionModule,
bool $useGroups
) {
$this->app = $app;
$this->db = $db;
$this->taskMutexService = $taskMutexService;
$this->cycleJobService = $cycleJobService;
$this->subscriptionCycleModule = $subscriptionCycleModule;
$this->subscriptionModule = $subscriptionModule;
$this->useGroups = $useGroups;
}
public function execute(): void
{
if ($this->taskMutexService->isTaskInstanceRunning('rechnungslauf_manual')) {
return;
}
$this->taskMutexService->setMutex('rechnungslauf_manual');
$jobs = $this->cycleJobService->listAll(100);
$simulatedDays = $this->getSimulatedDates($jobs);
if (empty($jobs)) {
return;
}
foreach (['auftrag', 'rechnung'] as $doctype) {
foreach ($simulatedDays as $simulatedDay) {
if ($simulatedDay === '') {
$simulatedDay = null;
} else {
try {
$simulatedDay = new DateTimeImmutable($simulatedDay);
} catch (Exception $exception) {
$simulatedDay = null;
}
}
$addresses = $this->getAddressesByTypeFromJobs($jobs, $doctype, $simulatedDay);
foreach ($jobs as $job) {
$job = $this->cycleJobService->getJob((int)$job['id']);
if (empty($job)) {
continue;
}
if ($job['document_type'] !== $doctype) {
continue;
}
if ($job['simulated_day'] === null && $simulatedDay !== null) {
continue;
}
if (
$job['simulated_day'] !== null
&& ($simulatedDay === null || $simulatedDay->format('Y-m-d') !== $job['simulated_day'])
) {
continue;
}
if (!in_array($job['address_id'], $addresses, false)) {
$this->cycleJobService->delete((int)$job['id']);
continue;
}
$simulatedDay = null;
if ($job['simulated_day'] !== null) {
try {
$simulatedDay = new DateTimeImmutable($job['simulated_day']);
} catch (Exception $exception) {
$simulatedDay = null;
}
}
if ($this->useGroups) {
$this->subscriptionCycleModule->generateAndSendSubscriptionCycleGroups(
$this->subscriptionModule,
[$job['address_id']],
$doctype,
$job['job_type'],
$job['printer_id'],
$simulatedDay
);
} else {
$this->subscriptionCycleModule->generateAndSendSubscriptionCycle(
$this->subscriptionModule,
[$job['address_id']],
$doctype,
$job['printer_id'],
$job['job_type'],
$simulatedDay
);
}
$this->cycleJobService->delete((int)$job['id']);
if ($this->taskMutexService->isTaskInstanceRunning('rechnungslauf')) {
return;
}
$this->taskMutexService->setMutex('rechnungslauf_manual');
}
}
}
}
public function cleanup(): void
{
$this->taskMutexService->setMutex('rechnungslauf_manual', false);
}
/**
* @param array $jobs
* @param string $documentType
* @param DateTimeInterface|null $simulatedDay
*
* @return array
*/
private function getAddressesByTypeFromJobs(
array $jobs,
string $documentType,
?DateTimeInterface $simulatedDay = null
): array {
$addresses = [];
foreach ($jobs as $job) {
if ($job['document_type'] === $documentType) {
$addresses[] = (int)$job['address_id'];
}
}
if (empty($addresses)) {
return [];
}
$addressesWithSubscriptions = array_keys(
(array)$this->subscriptionModule->GetRechnungsArray($documentType, true)
);
return array_intersect($addresses, $addressesWithSubscriptions);
}
/**
* get all Dates from Setting "Vergangenes Datum für Abrechnungserstellung" to calc old Subscription cycles
*
* @param array $jobs
*
* @return array
*/
private function getSimulatedDates(array $jobs): array
{
$simulatedDates = [];
foreach ($jobs as $job) {
$simulatedDates[] = (string)$job['simulated_day'];
}
return array_unique($simulatedDates);
}
}
@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\SubscriptionCycle\Scheduler;
use Xentral\Components\Database\Database;
class TaskMutexService implements TaskMutexServiceInterface
{
/** @var Database $db */
private $db;
/**
* TaskMutexService constructor.
*
* @param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* @param string $parameter
* @param bool $active
*/
public function setMutex(string $parameter, bool $active = true): void
{
$this->db->perform(
'UPDATE `prozessstarter` SET `mutex` = :mutex, `mutexcounter` = 0, `letzteausfuerhung` = NOW()
WHERE `parameter` = :parameter AND `aktiv` = 1',
['mutex' => (int)$active, 'parameter' => $parameter]
);
}
/**
* @param string $parameter
*
* @return bool
*/
public function isTaskInstanceRunning(string $parameter): bool
{
return (int)$this->db->fetchValue(
'SELECT COUNT(`id`) FROM `prozessstarter` WHERE `parameter` = :parameter AND `aktiv` = 1 AND `mutex` = 1',
['parameter' => $parameter]
) > 0;
}
}
@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\SubscriptionCycle\Scheduler;
interface TaskMutexServiceInterface
{
/**
* @param string $parameter
* @param bool $active
*/
public function setMutex(string $parameter, bool $active = true): void;
/**
* @param string $parameter
*
* @return bool
*/
public function isTaskInstanceRunning(string $parameter): bool;
}
@@ -0,0 +1,388 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\SubscriptionCycle\Service;
use DateTimeInterface;
use Xentral\Components\Database\Database;
use Xentral\Modules\SubscriptionCycle\Data\SubscriptionCycleCacheData;
final class SubscriptionCycleArticleGateway
{
/** @var Database */
private $db;
/**
* @param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* @param DateTimeInterface $nextFirstDay
*
* @return SubscriptionCycleCacheData[]
*/
public function findMonthlySubscriptionData(DateTimeInterface $nextFirstDay): array
{
$sql =
'SELECT abr.id AS `subscription_article_id`,
CASE
WHEN abr.abgerechnetbis != \'0000-00-00\' AND abr.abgerechnetbis = LAST_DAY(abr.abgerechnetbis)
THEN DATE_ADD(abr.abgerechnetbis, INTERVAL 1 DAY)
WHEN abr.abgerechnetbis != \'0000-00-00\'
THEN abr.abgerechnetbis
WHEN abr.startdatum = LAST_DAY(abr.startdatum)
THEN DATE_ADD(abr.startdatum, INTERVAL 1 DAY)
ELSE abr.startdatum
END AS `start_date`,
ROUND(ROUND(
DATEDIFF(
CASE
WHEN abr.abgerechnetbis != \'0000-00-00\' AND abr.abgerechnetbis >= :next_first_day
THEN DATE_ADD(LAST_DAY(abr.abgerechnetbis), INTERVAL 1 DAY)
WHEN abr.abgerechnetbis != \'0000-00-00\' AND abr.abgerechnetbis = LAST_DAY(abr.abgerechnetbis)
THEN DATE_SUB(DATE_ADD(:next_first_day, INTERVAL 1 MONTH), INTERVAL IF(abr.zahlzyklus < 1, 0, abr.zahlzyklus - 1) MONTH)
WHEN abr.abgerechnetbis != \'0000-00-00\' AND DAY(abr.abgerechnetbis) = 1
THEN DATE_SUB(:next_first_day, INTERVAL IF(abr.zahlzyklus < 1, 0, abr.zahlzyklus - 1) MONTH)
WHEN abr.abgerechnetbis != \'0000-00-00\'
THEN DATE_ADD(:next_first_day, INTERVAL IF(abr.zahlzyklus <= 1, 1, abr.zahlzyklus) MONTH)
WHEN abr.startdatum > :next_first_day
THEN DATE_ADD(LAST_DAY(abr.startdatum), INTERVAL 1 DAY)
WHEN abr.startdatum = :next_first_day AND DAY(abr.startdatum) = 1
THEN DATE_ADD(:next_first_day, INTERVAL 1 MONTH)
WHEN abr.startdatum < :next_first_day
THEN DATE_ADD(:next_first_day, INTERVAL 1 MONTH)
ELSE :next_first_day
END,
CASE
WHEN abr.abgerechnetbis != \'0000-00-00\' AND DAY(abr.abgerechnetbis) = 1
THEN abr.abgerechnetbis
WHEN abr.abgerechnetbis != \'0000-00-00\' AND abr.abgerechnetbis = LAST_DAY(abr.abgerechnetbis)
THEN DATE_ADD(LAST_DAY(abr.abgerechnetbis), INTERVAL 1 DAY)
WHEN abr.abgerechnetbis != \'0000-00-00\'
THEN DATE_SUB(DATE_ADD(LAST_DAY(abr.abgerechnetbis), INTERVAL 1 DAY), INTERVAL 1 MONTH)
WHEN DAY(abr.startdatum) = 1
THEN abr.startdatum
WHEN abr.startdatum = LAST_DAY(abr.startdatum)
THEN DATE_ADD(abr.startdatum, INTERVAL 1 DAY)
ELSE DATE_SUB(DATE_ADD(LAST_DAY(abr.startdatum), INTERVAL 1 DAY), INTERVAL 1 MONTH)
END
) / 30
,0) / IF(abr.zahlzyklus <= 1, 1, abr.zahlzyklus)) * IF(abr.zahlzyklus <= 1, 1, abr.zahlzyklus) AS `cycles_count`,
CASE
WHEN abr.abgerechnetbis != \'0000-00-00\' AND abr.abgerechnetbis = LAST_DAY(abr.abgerechnetbis)
THEN DATE_ADD(abr.abgerechnetbis, INTERVAL 1 DAY)
WHEN abr.abgerechnetbis != \'0000-00-00\'
THEN DATE_ADD(LAST_DAY(DATE_SUB(abr.abgerechnetbis,INTERVAL 1 MONTH)), INTERVAL 1 DAY)
WHEN DAY(abr.startdatum) = 1
THEN abr.startdatum
WHEN abr.startdatum = LAST_DAY(abr.startdatum)
THEN DATE_ADD(abr.startdatum, INTERVAL 1 DAY)
ELSE DATE_ADD(LAST_DAY(DATE_SUB(abr.startdatum,INTERVAL 1 MONTH)), INTERVAL 1 DAY)
END AS `calculation_base_date`,
CASE
WHEN abr.abgerechnetbis = \'0000-00-00\' AND DAY(abr.startdatum) != 1 AND abr.startdatum != LAST_DAY(abr.startdatum)
THEN ( IF(DAY(abr.startdatum) = 1,0,DAY(abr.startdatum)) / DAY(LAST_DAY(abr.startdatum)))
WHEN abr.abgerechnetbis = \'0000-00-00\'
THEN 0
WHEN abr.abgerechnetbis != LAST_DAY(abr.abgerechnetbis)
THEN (IF(DAY(abr.abgerechnetbis) = 1,0,DAY(abr.abgerechnetbis)) / DAY(LAST_DAY(abr.abgerechnetbis)))
ELSE
0
END
AS `start_month_price_factor`
FROM `abrechnungsartikel` AS `abr`
WHERE (abr.preisart = \'monat\' OR abr.preisart = \'\')
AND (abr.startdatum IS NULL OR abr.startdatum = \'0000-00-00\'
OR (
abr.startdatum <= :next_first_day
OR (abr.preisart = \'monat\' OR abr.preisart = \'\'
AND DATE_ADD(abr.startdatum, INTERVAL 1 DAY) < DATE_ADD(:next_first_day, INTERVAL 1 MONTH))
)
)
AND (
abr.abgerechnetbis = \'0000-00-00\'
OR (abr.abgerechnetbis < :next_first_day)
OR (abr.abgerechnetbis < DATE_SUB(DATE_ADD(:next_first_day, INTERVAL 1 MONTH), INTERVAL 1 DAY)
AND (abr.preisart = \'monat\' OR abr.preisart = \'\'))
)
AND (abr.enddatum = \'0000-00-00\' OR abr.enddatum >= DATE_SUB(:next_first_day, INTERVAL 1 DAY))';
return $this->convertArrayToSubscriptionCycleCacheData(
$this->db->fetchAll($sql, ['next_first_day' => $nextFirstDay->format('Y-m-d')])
);
}
/**
* @param DateTimeInterface $nextFirstDay
*
* @return SubscriptionCycleCacheData[]
*/
public function findCustomIntervalSubscriptionData(DateTimeInterface $nextFirstDay): array
{
$sql =
'SELECT
abr.id AS `subscription_article_id`,
IF(
abr.abgerechnetbis != \'0000-00-00\',
IF(
abr.abgerechnetbis = LAST_DAY(abr.abgerechnetbis),
DATE_ADD(abr.abgerechnetbis, INTERVAL 1 DAY),
abr.abgerechnetbis
),
IF(
abr.startdatum = LAST_DAY(abr.startdatum),
DATE_ADD(abr.startdatum, INTERVAL 1 DAY),
abr.startdatum
)
) AS `start_date`,
ROUND(
DATEDIFF(
IF(
abr.startdatum > :next_first_day,
DATE_ADD(LAST_DAY(abr.startdatum), INTERVAL 1 DAY),
:next_first_day
),
IF(
abr.abgerechnetbis != \'0000-00-00\',
IF(
abr.abgerechnetbis = LAST_DAY(abr.abgerechnetbis),
DATE_ADD(abr.abgerechnetbis, INTERVAL 1 DAY),
DATE_ADD(LAST_DAY(DATE_SUB(abr.abgerechnetbis,INTERVAL 1 MONTH)), INTERVAL 1 DAY)
),
IF(
abr.startdatum = LAST_DAY(abr.startdatum),
DATE_ADD(abr.startdatum, INTERVAL 1 DAY),
DATE_ADD(LAST_DAY(DATE_SUB(abr.startdatum,INTERVAL 1 MONTH)), INTERVAL 1 DAY)
)
)
) / 30
,0) AS `cycles_count`,
IF(
abr.abgerechnetbis != \'0000-00-00\',
IF(
abr.abgerechnetbis = LAST_DAY(abr.abgerechnetbis),
DATE_ADD(abr.abgerechnetbis, INTERVAL 1 DAY),
DATE_ADD(LAST_DAY(DATE_SUB(abr.abgerechnetbis,INTERVAL 1 MONTH)), INTERVAL 1 DAY)
),
IF(
abr.startdatum = LAST_DAY(abr.startdatum),
DATE_ADD(abr.startdatum, INTERVAL 1 DAY),
DATE_ADD(LAST_DAY(DATE_SUB(abr.startdatum,INTERVAL 1 MONTH)), INTERVAL 1 DAY)
)
) AS `calculation_base_date`,
0 AS `start_month_price_factor`
FROM `abrechnungsartikel` AS `abr`
WHERE abr.preisart = \'monatx\'';
return $this->convertArrayToSubscriptionCycleCacheData(
$this->db->fetchAll($sql, ['next_first_day' => $nextFirstDay->format('Y-m-d')])
);
}
/**
* @param DateTimeInterface $nextFirstDay
*
* @return SubscriptionCycleCacheData[]
*/
public function findYearlySubscriptionData(DateTimeInterface $nextFirstDay): array
{
$sql =
'SELECT
abr.id AS `subscription_article_id`,
IF(
abr.abgerechnetbis != \'0000-00-00\',
DATE_ADD(abr.abgerechnetbis, INTERVAL 1 DAY),
IF(
abr.startdatum = LAST_DAY(abr.startdatum),
DATE_ADD(abr.startdatum, INTERVAL 1 DAY),
abr.startdatum
)
) AS `start_date`,
ROUND(
DATEDIFF(
IF(
abr.startdatum > :next_first_day,
DATE_ADD(LAST_DAY(abr.startdatum), INTERVAL 1 DAY),
:next_first_day
),
IF(
abr.abgerechnetbis != \'0000-00-00\',
IF(
abr.abgerechnetbis = LAST_DAY(abr.abgerechnetbis),
DATE_ADD(abr.abgerechnetbis, INTERVAL 1 DAY),
DATE_ADD(LAST_DAY(DATE_SUB(abr.abgerechnetbis,INTERVAL 1 MONTH)), INTERVAL 1 DAY)
),
IF(
abr.startdatum = LAST_DAY(abr.startdatum),
DATE_ADD(abr.startdatum, INTERVAL 1 DAY),
DATE_ADD(LAST_DAY(DATE_SUB(abr.startdatum,INTERVAL 1 MONTH)), INTERVAL 1 DAY)
)
)
) / 365
,0) AS `cycles_count`,
IF(
abr.abgerechnetbis != \'0000-00-00\',
IF(
abr.abgerechnetbis = LAST_DAY(abr.abgerechnetbis),
DATE_ADD(abr.abgerechnetbis, INTERVAL 1 DAY),
DATE_ADD(LAST_DAY(DATE_SUB(abr.abgerechnetbis,INTERVAL 1 MONTH)), INTERVAL 1 DAY)
),
IF(
abr.startdatum = LAST_DAY(abr.startdatum),
DATE_ADD(abr.startdatum, INTERVAL 1 DAY),
DATE_ADD(LAST_DAY(DATE_SUB(abr.startdatum,INTERVAL 1 MONTH)), INTERVAL 1 DAY)
)
) AS `calculation_base_date`,
0 AS `start_month_price_factor`
FROM `abrechnungsartikel` AS `abr`
WHERE abr.preisart = \'jahr\'';
return $this->convertArrayToSubscriptionCycleCacheData(
$this->db->fetchAll($sql, ['next_first_day' => $nextFirstDay->format('Y-m-d')])
);
}
/**
* @param DateTimeInterface $date
*
* @return SubscriptionCycleCacheData[]
*/
public function findWeeklySubscriptionData(DateTimeInterface $date): array
{
$sql =
'SELECT
abr.id AS `subscription_article_id`,
IF(
abr.abgerechnetbis != \'0000-00-00\',
abr.abgerechnetbis,
abr.startdatum
) AS `start_date`,
(FLOOR(
DATEDIFF(
:date,
IF(
abr.abgerechnetbis != \'0000-00-00\',
IF(
abr.abgerechnetbis = LAST_DAY(abr.abgerechnetbis),
DATE_ADD(abr.abgerechnetbis, INTERVAL 1 DAY),
DATE_ADD(LAST_DAY(DATE_SUB(abr.abgerechnetbis,INTERVAL 1 MONTH)), INTERVAL 1 DAY)
),
IF(
abr.startdatum = LAST_DAY(abr.startdatum),
DATE_ADD(abr.startdatum, INTERVAL 1 DAY),
DATE_ADD(LAST_DAY(DATE_SUB(abr.startdatum,INTERVAL 1 MONTH)), INTERVAL 1 DAY)
)
)
)
/ (7 * abr.zahlzyklus)
)
* abr.zahlzyklus
) + abr.zahlzyklus AS `cycles_count`,
IF(
abr.abgerechnetbis != \'0000-00-00\',
abr.abgerechnetbis,
abr.startdatum
) AS `calculation_base_date`,
0 AS `start_month_price_factor`
FROM `abrechnungsartikel` AS `abr`
WHERE abr.preisart = \'wochen\'';
return $this->convertArrayToSubscriptionCycleCacheData(
$this->db->fetchAll($sql, ['date' => $date->format('Y-m-d')])
);
}
/**
* @param DateTimeInterface $date
*
* @return SubscriptionCycleCacheData[]
*/
public function find30DaysSubscriptionData(DateTimeInterface $date): array
{
$sql =
'SELECT
abr.id AS `subscription_article_id`,
IF(
abr.abgerechnetbis != \'0000-00-00\',
abr.abgerechnetbis,
abr.startdatum
) AS `start_date`,
(FLOOR(
DATEDIFF(
:date,
IF(
abr.abgerechnetbis != \'0000-00-00\',
IF(
abr.abgerechnetbis = LAST_DAY(abr.abgerechnetbis),
DATE_ADD(abr.abgerechnetbis, INTERVAL 1 DAY),
DATE_ADD(LAST_DAY(DATE_SUB(abr.abgerechnetbis,INTERVAL 1 MONTH)), INTERVAL 1 DAY)
),
IF(
abr.startdatum = LAST_DAY(abr.startdatum),
DATE_ADD(abr.startdatum, INTERVAL 1 DAY),
DATE_ADD(LAST_DAY(DATE_SUB(abr.startdatum,INTERVAL 1 MONTH)), INTERVAL 1 DAY)
)
)
)
/ (30 * abr.zahlzyklus)
)
* abr.zahlzyklus
) + abr.zahlzyklus AS `cycles_count`,
IF(
abr.abgerechnetbis != \'0000-00-00\',
abr.abgerechnetbis,
abr.startdatum
) AS `calculation_base_date`,
0 AS `start_month_price_factor`
FROM `abrechnungsartikel` AS `abr`
WHERE abr.preisart = \'30tage\'';
return $this->convertArrayToSubscriptionCycleCacheData(
$this->db->fetchAll($sql, ['date' => $date->format('Y-m-d')])
);
}
/**
* @return SubscriptionCycleCacheData[]
*/
public function findOneTimeSubscriptionData(): array
{
$sql =
'SELECT
abr.id AS `subscription_article_id`,
\'0000-00-00\' AS `start_date`,
0 AS `cycles_count`,
IF(
abr.abgerechnetbis != \'0000-00-00\',
abr.abgerechnetbis,
abr.startdatum
) AS `calculation_base_date`,
0 AS `start_month_price_factor`
FROM `abrechnungsartikel` AS `abr`
WHERE abr.preisart = \'einmalig\'';
return $this->convertArrayToSubscriptionCycleCacheData($this->db->fetchAll($sql));
}
/**
* @param array $result
*
* @return SubscriptionCycleCacheData[]
*/
private function convertArrayToSubscriptionCycleCacheData(array $result): array
{
$return = [];
if (!empty($result)) {
foreach ($result as $row) {
$return[] = SubscriptionCycleCacheData::fromDbState($row);
}
}
return $return;
}
}
@@ -0,0 +1,131 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\SubscriptionCycle\Service;
use Xentral\Components\Database\Database;
use Xentral\Modules\SubscriptionCycle\Data\SubscriptionCycleArticleData;
final class SubscriptionCycleArticleService
{
/** @var Database */
private $db;
/**
* @param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* @param SubscriptionCycleArticleData $article
*
* @return int
*/
public function create(SubscriptionCycleArticleData $article): int
{
$sql =
'INSERT INTO `abrechnungsartikel` (
`sort`,
`artikel`,
`bezeichnung`,
`nummer`,
`menge`,
`preis`,
`steuerklasse`,
`rabatt`,
`abgerechnet`,
`startdatum`,
`lieferdatum`,
`abgerechnetbis`,
`wiederholend`,
`zahlzyklus`,
`abgrechnetam`,
`rechnung`,
`projekt`,
`adresse`,
`status`,
`bemerkung`,
`beschreibung`,
`dokument`,
`preisart`,
`enddatum`,
`angelegtvon`,
`angelegtam`,
`experte`,
`waehrung`,
`beschreibungersetzten`,
`gruppe`
) VALUES (
:sort,
:articleId,
:articleName,
:articleNumber,
:amount,
:price,
:taxClass,
:discount,
:cleared,
:startDate,
:deliveranceDate,
:clearedTill,
:repeating,
:payCycle,
:clearedOn,
:invoiceId,
:projectId,
:adressId,
:status,
:text,
:description,
:document,
:priceType,
:endDate,
:createdBy,
:createdDate,
:expert,
:currency,
:replaceDescription,
:subscriptionCycleGroupId
)';
$values = [
'sort' => $article->getSort(),
'articleId' => $article->getArticleId(),
'articleName' => $article->getArticleName(),
'articleNumber' => $article->getArticleNumber(),
'amount' => $article->getAmount(),
'price' => $article->getPrice(),
'taxClass' => $article->getTaxClass(),
'discount' => $article->getDiscount(),
'cleared' => $article->isCleared(),
'startDate' => $article->getStartDate(),
'deliveranceDate' => $article->getDeliveranceDate(),
'clearedTill' => $article->getClearedTill(),
'repeating' => $article->isRepeating(),
'payCycle' => $article->getPayCycle(),
'clearedOn' => $article->getClearedOn(),
'invoiceId' => $article->getInvoiceId(),
'projectId' => $article->getProjectId(),
'adressId' => $article->getAdressId(),
'status' => $article->getStatus(),
'text' => $article->getText(),
'description' => $article->getDescription(),
'document' => $article->getDocument(),
'priceType' => $article->getPriceType(),
'endDate' => $article->getEndDate(),
'createdBy' => $article->getCreatedBy(),
'createdDate' => $article->getCreatedDate(),
'expert' => $article->isExpert(),
'currency' => $article->getCurrency(),
'replaceDescription' => $article->isReplaceDescription(),
'subscriptionCycleGroupId' => $article->getSubscriptionCycleGroupId(),
];
$this->db->perform($sql, $values);
return $this->db->lastInsertId();
}
}
@@ -0,0 +1,127 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\SubscriptionCycle\Service;
use Xentral\Components\Database\Database;
use Xentral\Modules\SubscriptionCycle\Data\SubscriptionCycleAutoSubscriptionData;
use Xentral\Modules\SubscriptionCycle\Exception\AutoSubscriptionNotFoundException;
final class SubscriptionCycleAutoSubscriptionGateway
{
/** @var Database */
private $db;
/**
* @param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* @param int $autoSubscriptionId
*
* @throws AutoSubscriptionNotFoundException
*
* @return SubscriptionCycleAutoSubscriptionData
*/
public function getById(int $autoSubscriptionId): SubscriptionCycleAutoSubscriptionData
{
$sql =
'SELECT
sca.id,
sca.project_id,
sca.article_id,
sca.price_cycle,
sca.document_type,
sca.subscription_group_id,
sca.position,
sca.first_date_type,
sca.prevent_auto_dispatch,
sca.auto_email_confirmation,
sca.business_letter_pattern_id,
sca.add_pdf
FROM `subscription_cycle_autosubscription` AS `sca`
WHERE sca.id = :id';
$data = $this->db->fetchRow($sql, ['id' => $autoSubscriptionId]);
if (empty($data)) {
throw new AutoSubscriptionNotFoundException('No data found for id: ' . $autoSubscriptionId);
}
return SubscriptionCycleAutoSubscriptionData::fromDbState($data);
}
/**
* @param int $docId
*
* @return array
*/
public function findAutoSubscriptionData(int $docId)
{
$sql =
'SELECT
ap.artikel,
art.nummer,
art.name_de AS `bezeichnung`,
ap.menge,
ap.preis,
ap.rabatt,
(CASE sca.first_date_type
WHEN \'monatsanfang\' THEN DATE_FORMAT(DATE_ADD(CURDATE(), INTERVAL 1 MONTH), \'%Y-%m-01\')
WHEN \'monatsmitte\' THEN (
IF(
DAY(CURDATE()) > 15,
DATE_FORMAT(DATE_ADD(CURDATE(), INTERVAL 1 MONTH), \'%Y-%m-15\'),
DATE_FORMAT(CURDATE(), \'%Y-%m-15\')
)
)
ELSE au.datum
END) AS `startdatum`,
1 AS `wiederholend`,
\'angelegt\' AS `status`,
(CASE sca.price_cycle
WHEN \'monatspreis\' THEN \'monat\'
WHEN \'jahrespreis\' THEN \'jahr\'
ELSE sca.price_cycle
END) AS `preisart`,
sca.position AS `sort`,
sca.project_id AS `projekt`,
NOW() AS `angelegtam`,
0 AS `experte`,
au.waehrung,
0 AS `beschreibungersetzten`,
art.umsatzsteuer AS `steuerklasse`,
1 AS `zahlzyklus`,
0 AS `rechnung`,
sca.document_type AS `dokument`,
sca.subscription_group_id AS `gruppe`,
0 AS `angelegtvon`,
0 AS `abgerechnet`,
sca.auto_email_confirmation,
sca.business_letter_pattern_id,
sca.add_pdf,
sca.prevent_auto_dispatch,
au.sprache,
gba.subjekt,
adr.email,
adr.abweichendeemailab,
au.adresse
FROM `auftrag_position` AS `ap`
INNER JOIN `auftrag` AS `au` ON au.id = ap.auftrag
INNER JOIN `artikel` AS `art` ON art.id = ap.artikel
INNER JOIN `adresse` AS `adr` ON au.adresse = adr.id
INNER JOIN `subscription_cycle_autosubscription` AS `sca` ON sca.article_id = ap.artikel
LEFT JOIN `geschaeftsbrief_vorlagen` AS `gba` ON gba.id = sca.business_letter_pattern_id
LEFT JOIN `abrechnungsartikel` AS `ara` ON ara.artikel = sca.article_id AND ara.adresse = au.adresse
WHERE au.id = :docId
AND IF(sca.project_id = 0, 1, au.projekt = sca.project_id)
AND ara.artikel IS NULL';
return $this->db->fetchAll($sql, ['docId' => $docId]);
}
}
@@ -0,0 +1,163 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\SubscriptionCycle\Service;
use Xentral\Components\Database\Database;
use Xentral\Modules\SubscriptionCycle\Data\SubscriptionCycleAutoSubscriptionData;
use Xentral\Modules\SubscriptionCycle\Exception\AutoSubscriptionNotFoundException;
use Xentral\Modules\SubscriptionCycle\Exception\OrderNotFoundException;
use Xentral\Modules\SubscriptionCycle\Exception\RuntimeException;
final class SubscriptionCycleAutoSubscriptionService
{
/** @var Database */
private $db;
/**
* @param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* @param SubscriptionCycleAutoSubscriptionData $autoSubscription
*
* @return int
*/
public function create(SubscriptionCycleAutoSubscriptionData $autoSubscription): int
{
$sql =
'INSERT INTO `subscription_cycle_autosubscription` (
`project_id`,
`article_id`,
`price_cycle`,
`document_type`,
`subscription_group_id`,
`position`,
`first_date_type`,
`prevent_auto_dispatch`,
`auto_email_confirmation`,
`business_letter_pattern_id`,
`add_pdf`
)
VALUES (
:projectId,
:articleId,
:priceCycle,
:documentType,
:subscriptionGroupId,
:position,
:firstDateType,
:preventAutoDispatch,
:autoEmailConfirmation,
:businessLetterPatternId,
:addPdf
)';
$values = [
'projectId' => $autoSubscription->getProjectId(),
'articleId' => $autoSubscription->getArticleId(),
'priceCycle' => $autoSubscription->getPriceCycle(),
'documentType' => $autoSubscription->getDocumentType(),
'subscriptionGroupId' => $autoSubscription->getSubscriptionGroupId(),
'position' => $autoSubscription->getPosition(),
'firstDateType' => $autoSubscription->getFirstDateType(),
'preventAutoDispatch' => $autoSubscription->getPreventAutoDispatch(),
'autoEmailConfirmation' => $autoSubscription->getAutoEmailConfirmation(),
'businessLetterPatternId' => $autoSubscription->getBusinessLetterPatternId(),
'addPdf' => $autoSubscription->getAddPdf(),
];
$this->db->perform($sql, $values);
return $this->db->lastInsertId();
}
/**
* @param SubscriptionCycleAutoSubscriptionData $autoSubscription
*
* @throws AutoSubscriptionNotFoundException
*/
public function edit(SubscriptionCycleAutoSubscriptionData $autoSubscription): void
{
if (empty($autoSubscription->getId())) {
throw new AutoSubscriptionNotFoundException('No ID is found for an update');
}
$sql =
'UPDATE `subscription_cycle_autosubscription`
SET
`project_id` = :projectId,
`article_id` = :articleId,
`price_cycle` = :priceCycle,
`document_type` = :documentType,
`subscription_group_id` = :subscriptionGroupId,
`position` = :position,
`first_date_type` = :firstDateType,
`prevent_auto_dispatch` = :preventAutoDispatch,
`auto_email_confirmation` = :autoEmailConfirmation,
`business_letter_pattern_id` = :businessLetterPatternId,
`add_pdf` = :addPdf
WHERE `id` = :id';
$values = [
'id' => $autoSubscription->getId(),
'projectId' => $autoSubscription->getProjectId(),
'articleId' => $autoSubscription->getArticleId(),
'priceCycle' => $autoSubscription->getPriceCycle(),
'documentType' => $autoSubscription->getDocumentType(),
'subscriptionGroupId' => $autoSubscription->getSubscriptionGroupId(),
'position' => $autoSubscription->getPosition(),
'firstDateType' => $autoSubscription->getFirstDateType(),
'preventAutoDispatch' => $autoSubscription->getPreventAutoDispatch(),
'autoEmailConfirmation' => $autoSubscription->getAutoEmailConfirmation(),
'businessLetterPatternId' => $autoSubscription->getBusinessLetterPatternId(),
'addPdf' => $autoSubscription->getAddPdf(),
];
$this->db->perform($sql, $values);
}
/**
* @param int $autosubscriptionId
*
* @throws AutoSubscriptionNotFoundException
* @throws RuntimeException
*/
public function removeById(int $autosubscriptionId): void
{
$sql = 'SELECT s.id FROM `subscription_cycle_autosubscription` AS `s` WHERE s.id = :id';
$data = $this->db->fetchRow($sql, ['id' => $autosubscriptionId]);
if (empty($data)) {
throw new AutoSubscriptionNotFoundException(
'The autosubscription with the following id does not exist:' . $autosubscriptionId
);
}
$sql = 'DELETE FROM `subscription_cycle_autosubscription` WHERE `id` = :id';
$numAffected = (int)$this->db->fetchAffected($sql, ['id' => $autosubscriptionId]);
if ($numAffected === 0) {
throw new RuntimeException('Autosubscription could not be deleted, id: ' . $autosubscriptionId);
}
}
/**
* @param int $orderId
*/
public function preventAutoDispatch(int $orderId): void
{
$sql = 'SELECT a.id FROM `auftrag` AS `a` WHERE a.id = :id';
$data = $this->db->fetchRow($sql, ['id' => $orderId]);
if (empty($data)) {
throw new OrderNotFoundException('The order with the following id does not exist:' . $orderId);
}
$sql = 'UPDATE `auftrag` SET `autoversand` = 0 WHERE `id` = :id';
$this->db->perform($sql, ['id' => (int)$orderId]);
}
}
@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\SubscriptionCycle\Service;
use Xentral\Components\Database\Database;
use Xentral\Modules\SubscriptionCycle\Data\SubscriptionCycleCacheData;
final class SubscriptionCycleCacheService
{
/** @var Database */
private $db;
/** @var string TABLE_NAME */
const TABLE_NAME = 'subscription_cycle_cache';
/**
* @param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* @param SubscriptionCycleCacheData[] $data
*/
public function createCacheEntries(array $data): void
{
if (!empty($data)) {
$insert = $this->db->insert()
->into(self::TABLE_NAME);
foreach ($data as $entry) {
$insert->addRow()
->cols(
[
'subscription_article_id' => $entry->getSubscriptionArticleId(),
'start_date' => $entry->getStartDate()->format('Y-m-d'),
'cycles_count' => $entry->getCyclesCount(),
'calculation_base_date' => $entry->getCalculationBaseDate()->format('Y-m-d'),
'start_month_price_factor' => $entry->getStartMonthPriceFactor(),
]
);
}
$insertSql = $insert->getStatement();
$values = $insert->getBindValues();
$this->db->perform($insertSql, $values);
}
}
public function emptyCache(): void
{
$sql = 'TRUNCATE `subscription_cycle_cache`';
$this->db->perform($sql);
}
}
@@ -0,0 +1,143 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\SubscriptionCycle\Service;
use DateTimeInterface;
use Xentral\Components\Database\Database;
use Xentral\Modules\SubscriptionCycle\Exception\InvalidArgumentException;
final class SubscriptionCycleJobService
{
private $db;
/**
* SubscriptionCycleJobService constructor.
*
* @param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* @param int $subscriptionCycleJobId
*/
public function delete(int $subscriptionCycleJobId): void
{
$this->db->perform(
'DELETE FROM `subscription_cycle_job` WHERE `id` = :subscription_cycle_job_id',
['subscription_cycle_job_id' => $subscriptionCycleJobId]
);
}
/**
* @param int $addressId
* @param string $documentType
*
* @throws InvalidArgumentException
*/
public function deleteJobsByAddressIdAndDoctype(int $addressId, string $documentType): void
{
$this->ensureDocumentType($documentType);
$this->db->perform(
'DELETE FROM `subscription_cycle_job` WHERE `address_id` = :address_id AND `document_type` = :document_type',
[
'address_id' => $addressId,
'document_type' => $documentType,
]
);
}
/**
* @param int $addressId
* @param string $documentType
* @param string|null $jobType
* @param int|null $printerId
* @param DateTimeInterface|null $simulatedDay
*
* @throws InvalidArgumentException
*
* @return int
*/
public function create(int $addressId, string $documentType, ?string $jobType, ?int $printerId, ?DateTimeInterface $simulatedDay = null): int
{
$this->ensureDocumentType($documentType);
$this->db->perform(
'INSERT INTO `subscription_cycle_job`
(`address_id`, `document_type`, `job_type`, `printer_id`, `created_at`, `simulated_day`)
VALUES (:address_id, :document_type, :job_type, :printer_id, NOW(), :simulated_day)',
[
'address_id' => $addressId,
'document_type' => $documentType,
'job_type' => $jobType,
'printer_id' => $printerId,
'simulated_day' => $simulatedDay === null ? null : $simulatedDay->format('Y-m-d'),
]
);
return (int)$this->db->lastInsertId();
}
/**
* @param int $subscriptionCycleJobId
*
* @return array
*/
public function getJob(int $subscriptionCycleJobId): array
{
return $this->db->fetchRow(
'SELECT * FROM `subscription_cycle_job` WHERE `id` = :subscription_cycle_job_id',
['subscription_cycle_job_id' => $subscriptionCycleJobId]
);
}
/**
* @param int|null $limit
*
* @return array
*/
public function listAll(?int $limit = null): array
{
if ($limit !== null) {
return $this->db->fetchAll('SELECT * FROM `subscription_cycle_job` LIMIT :limit', ['limit' => $limit]);
}
return $this->db->fetchAll('SELECT * FROM `subscription_cycle_job`');
}
/**
* @param string $documentType
*
* @throws InvalidArgumentException
*
* @return int[]
*/
public function getAddressIdsByDocumentType(string $documentType): array
{
$this->ensureDocumentType($documentType);
return array_map(
'intval',
$this->db->fetchCol(
'SELECT `address_id` FROM `subscription_cycle_job` WHERE `document_type` = :document_type',
['document_type' => $documentType]
)
);
}
/**
* @param string $documentType
*
* @throws InvalidArgumentException
*/
private function ensureDocumentType(string $documentType): void
{
if (!in_array($documentType, ['rechnung', 'auftrag'])) {
throw new InvalidArgumentException("{$documentType} is not a valid documentType");
}
}
}
@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\SubscriptionCycle;
use DateTimeInterface;
use Xentral\Modules\SubscriptionCycle\Service\SubscriptionCycleArticleGateway;
use Xentral\Modules\SubscriptionCycle\Service\SubscriptionCycleCacheService;
final class SubscriptionCycleCacheFiller
{
/** @var SubscriptionCycleArticleGateway $articleGateway */
private $articleGateway;
/** @var SubscriptionCycleCacheService $cacheService */
private $cacheService;
/**
* @param SubscriptionCycleArticleGateway $articleGateway
* @param SubscriptionCycleCacheService $cacheService
*/
public function __construct(
SubscriptionCycleArticleGateway $articleGateway,
SubscriptionCycleCacheService $cacheService
) {
$this->articleGateway = $articleGateway;
$this->cacheService = $cacheService;
}
/**
* @param DateTimeInterface $nextFirstDate
*/
public function generateCacheByNextFirstDate(DateTimeInterface $nextFirstDate): void
{
$monthlyData = $this->articleGateway->findMonthlySubscriptionData($nextFirstDate);
$customIntervalData = $this->articleGateway->findCustomIntervalSubscriptionData($nextFirstDate);
$yearlyData = $this->articleGateway->findYearlySubscriptionData($nextFirstDate);
$allData = array_merge($monthlyData, $customIntervalData, $yearlyData);
if (!empty($allData)) {
$this->cacheService->createCacheEntries($allData);
}
}
/**
* @param DateTimeInterface $currentDate
*/
public function generateCacheByCurrentDate(DateTimeInterface $currentDate): void
{
$weeklyData = $this->articleGateway->findWeeklySubscriptionData($currentDate);
$thirtyDaysData = $this->articleGateway->find30DaysSubscriptionData($currentDate);
$allData = array_merge($weeklyData, $thirtyDaysData);
if (!empty($allData)) {
$this->cacheService->createCacheEntries($allData);
}
}
public function generateCacheByOneTimeData(): void
{
$data = $this->articleGateway->findOneTimeSubscriptionData();
if (!empty($data)) {
$this->cacheService->createCacheEntries($data);
}
}
public function emptyCache()
{
$this->cacheService->emptyCache();
}
}
@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\SubscriptionCycle;
interface SubscriptionCycleModuleInterface
{
/**
* @param $subscription
* @param $customers
* @param $documentType
* @param $mailPrinter
* @param $printerId
* @param $simulatedDay
*
* @return mixed
*/
public function generateAndSendSubscriptionCycleGroups(
$subscription,
$customers,
$documentType,
$mailPrinter,
$printerId,
$simulatedDay = null
);
/**
* @param $subscription
* @param $customers
* @param $documentType
* @param $printerId
* @param $mailPrinter
* @param $simulatedDay
*
* @return mixed
*/
public function generateAndSendSubscriptionCycle(
$subscription,
$customers,
$documentType,
$printerId,
$mailPrinter,
$simulatedDay = null
);
}
@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\SubscriptionCycle;
use DateTimeInterface;
interface SubscriptionModuleInterface
{
/**
* @param int $customer
* @param string $documentType
*
* @return mixed
*/
public function RechnungKunde($customer, $documentType);
/**
* @param $customer
* @param $invoiceGroupKey
* @param $key
*
* @return mixed
*/
public function AuftragImportAbo($customer, $invoiceGroupKey, $key);
/**
* @param $customer
* @param $invoiceGroupKey
* @param $key
*
* @return mixed
*/
public function RechnungImportAbo($customer, $invoiceGroupKey, $key);
/**
* @param string $documentType
*
* @return array|null
*/
public function GetRechnungsArray($documentType);
}
@@ -0,0 +1,274 @@
<?php
namespace Xentral\Modules\SubscriptionCycle\Wrapper;
use ApplicationCore;
use AuftragPDF;
use Xentral\Components\Mailer\Data\EmailMessage;
use Xentral\Components\Mailer\Data\EmailRecipient;
use Xentral\Components\Mailer\Data\FileAttachment;
use Xentral\Modules\SubscriptionCycle\Exception\RuntimeException;
use Xentral\Modules\SystemMailer\Data\EmailBackupAccount;
use Xentral\Modules\SystemMailer\Service\EmailAccountGateway;
use Xentral\Modules\SystemMailer\SystemMailer;
final class BusinessLetterWrapper
{
/** @var ApplicationCore $app */
private $app;
/** @var SystemMailer $mailer */
private $mailer;
/** @var EmailAccountGateway $accountGateway */
private $accountGateway;
/**
* @param ApplicationCore $app
*/
public function __construct(ApplicationCore $app, SystemMailer $mailer, EmailAccountGateway $accountGateway)
{
$this->app = $app;
$this->mailer = $mailer;
$this->accountGateway = $accountGateway;
}
/**
* @param array $sendData
* @param int $orderId
*/
public function sendBusinessLetter(array $sendData, int $orderId): void
{
$dataSorted = [];
foreach ($sendData as $data) {
$autoEmailConfirmation = (bool)$data['auto_email_confirmation'];
if ($autoEmailConfirmation) {
$letterSubject = $data['subjekt'];
$dataSorted[$letterSubject][] = $data;
}
}
if (!empty($dataSorted)) {
foreach ($dataSorted as $letterSubject => $parts) {
$language = $parts[0]['sprache'];
$projectId = (int)$parts[0]['projekt'];
$isAddPdf = (bool)$parts[0]['add_pdf'];
if (empty($language)) {
$language = 'deutsch';
}
$files = [];
if ($isAddPdf) {
$path = $this->getMailPdf($orderId, $projectId);
if (!empty($path)) {
$attachment = new FileAttachment($path);
$files[] = $attachment;
}
}
$email = $data['abweichendeemailab'];
if (empty($recipient)) {
$email = $data['email'];
}
$recipient = new EmailRecipient($email);
$message = new EmailMessage(
$this->getMailSubject($letterSubject, $language, $projectId, $orderId, $parts),
$this->getMailContent($letterSubject, $language, $projectId, $orderId, $parts),
[$recipient],
null,
null,
$files
);
$account = $this->getBackupAccountData();
$mailresponse = $this->mailer->send($message, $account);
if (!empty($mailresponse)) {
throw new RuntimeException('Mail could not be send. More info in the logger');
}
}
}
}
/**
* @param string $letterSubject
* @param string $language
* @param int $projectId
* @param int $orderId
* @param array $articles
*
* @return string
*/
private function getMailSubject(
string $letterSubject,
string $language,
int $projectId,
int $orderId,
array $articles
): string {
$subject = $this->app->erp->GetGeschaeftsBriefBetreff(
$letterSubject,
$language,
$projectId,
'auftrag',
$orderId
);
$subject = $this->parseVars($subject, $orderId, $articles);
return $subject;
}
/**
* @param string $letterSubject
* @param string $language
* @param int $projectId
* @param int $orderId
* @param array $articles
*
* @return string
*/
private function getMailContent(
string $letterSubject,
string $language,
int $projectId,
int $orderId,
array $articles
): string {
$content = $this->app->erp->GetGeschaeftsBriefText(
$letterSubject,
$language,
$projectId,
'auftrag',
$orderId
);
$content = $this->parseVars($content, $orderId, $articles);
return $content;
}
/**
* @param string $text
* @param int $orderId
* @param array $articles
*
* @return string
*/
private function parseVars(string $text, int $orderId, array $articles): string
{
$text = (string)$this->app->erp->ParseUserVars('auftrag', $orderId, $text);
if (empty($text)) {
return '';
}
return $this->parseAutoAboVars($text, $articles);
}
/**
* @param string $text
* @param array $articles
*
* @return string
*/
private function parseAutoAboVars(string $text, array $articles): string
{
$replace = '';
foreach ($articles as $article) {
$lb = '';
if (!empty($replace)) {
$lb = PHP_EOL;
}
$replace .= $lb . $article['nummer'] . ' - ' . $article['bezeichnung'];
}
$text = str_replace('{ABOARTIKEL}', $replace, $text);
return $text;
}
/**
* @param int $orderId
* @param int $projectId
*
* @return string
*/
private function getMailPdf(int $orderId, int $projectId): string
{
$pdfclass = 'AuftragPDF';
if (class_exists('RechnungPDFCustom')) {
$pdfclass = 'AuftragPDFCustom';
}
/** @var AuftragPDF $pdfObject */
$pdfObject = new $pdfclass($this->app, $projectId);
$pdfObject->GetAuftrag($orderId);
$orderFile = $pdfObject->displayTMP();
$pdfObject->ArchiviereDocument();
return $orderFile;
}
/**
* @return EmailBackupAccount
*/
private function getBackupAccountData(): EmailBackupAccount
{
$senderEmail = (string)$this->app->erp->GetFirmaMail();
$senderName = (string)$this->app->erp->GetFirmaAbsender();
$account = $this->accountGateway->tryGetEmailAccountByEmail($senderEmail);
if (!empty($account)) {
return $account;
} else {
$data['id'] = 0;
$data['angezeigtername'] = $senderName;
$data['email'] = $senderEmail;
$data['internebeschreibung'] = '';
$data['benutzername'] = $this->app->erp->Firmendaten('benutzername');
$data['passwort'] = $this->app->erp->Firmendaten('passwort');
$data['server'] = $this->app->erp->Firmendaten('host');
$data['imap_sentfolder_aktiv'] = 0;
$data['imap_sentfolder'] = '';
$data['imap_port'] = 0;
$data['imap_type'] = 1;
$data['geschaeftsbriefvorlage'] = 0;
$data['autoresponder'] = 0;
$data['autoresponderbetreff'] = '';
$data['autorespondertext'] = '';
$data['autoresponder_blacklist'] = 0;
$data['projekt'] = 0;
$data['emailbackup'] = 0;
$data['loeschtage'] = 0;
$data['adresse'] = 0;
$data['firma'] = 1;
$data['geloescht'] = 0;
$data['ticket'] = 0;
$data['ticketloeschen'] = 0;
$data['ticketabgeschlossen'] = 0;
$data['ticketqueue'] = 0;
$data['ticketprojekt'] = 0;
$data['ticketemaileingehend'] = 0;
$data['abdatum'] = '0000-00-00';
$data['smtp_extra'] = 0;
$data['smtp'] = '';
$data['smtp_ssl'] = 0;
$data['smtp_port'] = 0;
$data['smtp_frommail'] = '';
$data['smtp_fromname'] = '';
$data['smtp_authtype'] = '';
$data['smtp_authparam'] = '';
$data['smtp_loglevel'] = 0;
$data['client_alias'] = '';
$data['eigenesignatur'] = empty($this->app->erp->Firmendaten('signatur')) ? 0 : 1;
$data['signatur'] = base64_decode($this->app->erp->Firmendaten('signatur'));
$data['mutex'] = 0;
return EmailBackupAccount::fromDbState($data);
}
}
}
@@ -0,0 +1,203 @@
var SubscriptionCycleAutoSubscription = (function ($) {
'use strict';
var me = {
isInitialized: false,
selector: {
newDialog: '#autosubscriptionnewdialog',
editDialog: '.autosubscriptioneditdialog',
deleteDialog: '.autosubscriptiondeletedialog',
newEdit: '#autosubscriptionnewedit',
delete: '#autosubscriptiondelete',
articleInput: '#article',
projectInput: '#project',
pricecycleSelect: '#pricecycle',
documenttypeSelect: '#documenttype',
subscriptiongroupSelect: '#subscriptiongroup',
positionInput: '#position',
firstdatetypeSelect: '#firstdatetype',
preventautodispatchCheck: '#preventautodispatch',
autoemailconfirmationCheck: '#autoemailconfirmation',
businessletterpatternInput: '#businessletterpattern',
businessletterrow: '.businessletter',
addpdf: '#addpdf',
deleteid: '#autosubscriptiondeleteid',
editid: '#autosubscriptioneditid',
msg: '#autosubscriptionmsg',
overviewTable: '#rechnungslaufautoabo',
autosubscriptionform: '#autosubscriptionform'
},
storage: {
$dialog: null,
$deleteDialog: null
},
init: function () {
if (me.isInitialized === true) {
return;
}
me.storage.$dialog = $(me.selector.newEdit);
me.storage.$deleteDialog = $(me.selector.delete);
me.dialogInit();
me.deleteDialogInit();
me.registerEvents();
me.isInitialized = true;
},
registerEvents: function () {
$(me.selector.newDialog).on('click', function (event) {
event.preventDefault();
me.dialogNewOpen();
});
$(me.selector.overviewTable).on('click', me.selector.editDialog,
function (event) {
event.preventDefault();
me.dialogEditOpen(this.id.replace('aae-', ''));
});
$(me.selector.overviewTable).on('click', me.selector.deleteDialog,
function (event) {
event.preventDefault();
me.dialogDeleteOpen(this.id.replace('aad-', ''));
});
$(me.selector.autoemailconfirmationCheck).on('click', function () {
if (me.storage.$dialog.find(me.selector.autoemailconfirmationCheck).prop('checked')) {
me.storage.$dialog.find(me.selector.businessletterrow).show();
} else {
me.storage.$dialog.find(me.selector.businessletterrow).hide();
}
});
},
deleteDialogInit: function () {
me.storage.$deleteDialog.dialog({
modal: true,
bgiframe: true,
closeOnEscape: false,
minWidth: 500,
minHeight: 110,
maxHeight: 200,
autoOpen: false,
open: function () {},
close: function () {}
});
},
dialogInit: function () {
me.storage.$dialog.dialog({
modal: true,
bgiframe: true,
closeOnEscape: false,
minWidth: 650,
minHeight: 450,
maxHeight: 500,
autoOpen: false,
open: function () {
$(me.selector.inputKey).trigger('focus');
},
close: function () {
me.dialogReset();
},
buttons:{
ABBRECHEN: function() {
me.dialogClose();
},
SPEICHERN: function() {
$(me.selector.autosubscriptionform).submit();
}
}
});
},
dialogNewOpen: function () {
me.dialogReset();
me.storage.$dialog.dialog('open');
},
dialogEditOpen: function (id) {
me.dialogReset();
$.ajax({
url: 'index.php?module=rechnungslauf&action=autoabo&cmd=editdata',
type: 'POST',
dataType: 'json',
data: {
id: id
},
success: function (data) {
if (data.error) {
me.storage.$dialog.find(me.selector.msg).text(data.error);
} else {
me.storage.$dialog.find(me.selector.editid).val(data.id);
me.storage.$dialog.find(me.selector.articleInput).val(data.article_name);
me.storage.$dialog.find(me.selector.projectInput).val(data.project_name);
me.storage.$dialog.find(me.selector.pricecycleSelect).val(data.price_cycle);
me.storage.$dialog.find(me.selector.documenttypeSelect).val(data.document_type);
me.storage.$dialog.find(me.selector.subscriptiongroupSelect).val(
data.subscription_group_id);
me.storage.$dialog.find(me.selector.positionInput).val(
data.position == 0 ? '' : data.position);
me.storage.$dialog.find(me.selector.firstdatetypeSelect).val(data.first_date_type);
me.storage.$dialog.find(me.selector.preventautodispatchCheck).prop('checked',
data.prevent_auto_dispatch);
me.storage.$dialog.find(me.selector.autoemailconfirmationCheck).prop('checked',
data.auto_email_confirmation);
me.storage.$dialog.find(me.selector.businessletterpatternInput).val(
data.business_letter_pattern_id);
me.storage.$dialog.find(me.selector.addpdf).prop('checked', data.add_pdf);
if(data.auto_email_confirmation){
me.storage.$dialog.find(me.selector.businessletterrow).show();
}
me.storage.$dialog.dialog('open');
}
},
beforeSend: function () {}
});
},
dialogDeleteOpen: function (id) {
me.storage.$deleteDialog.find(me.selector.deleteid).val(id);
me.storage.$deleteDialog.dialog('open');
},
dialogClose: function () {
me.storage.$dialog.dialog('close');
},
dialogReset: function () {
me.storage.$dialog.find(me.selector.editid).val(null);
me.storage.$dialog.find(me.selector.articleInput).val(null);
me.storage.$dialog.find(me.selector.projectInput).val(null);
me.storage.$dialog.find(me.selector.pricecycleSelect).val('monatspreis');
me.storage.$dialog.find(me.selector.documenttypeSelect).val('auftrag');
me.storage.$dialog.find(me.selector.subscriptiongroupSelect).val(0);
me.storage.$dialog.find(me.selector.positionInput).val('');
me.storage.$dialog.find(me.selector.firstdatetypeSelect).val('auftragsdatum');
me.storage.$dialog.find(me.selector.preventautodispatchCheck).prop('checked', false);
me.storage.$dialog.find(me.selector.autoemailconfirmationCheck).prop('checked', false);
me.storage.$dialog.find(me.selector.businessletterpatternInput).val('');
me.storage.$dialog.find(me.selector.addpdf).prop('checked', false);
me.storage.$dialog.find(me.selector.businessletterrow).hide();
}
};
return {
init: me.init
};
})(jQuery);
$(document).ready(function () {
SubscriptionCycleAutoSubscription.init();
});