OpenXE/classes/Modules/CopperSurcharge/Service/CopperSurchargeCalculator.php
2021-05-21 08:49:41 +02:00

934 lines
31 KiB
PHP

<?php
declare(strict_types=1);
namespace Xentral\Modules\CopperSurcharge\Service;
use DateTimeImmutable;
use DateTimeInterface;
use Xentral\Modules\CopperSurcharge\Data\CopperSurchargeData;
use Xentral\Modules\CopperSurcharge\Data\DocumentPositionData;
use Xentral\Modules\CopperSurcharge\Exception\InvalidDateFormatException;
use Xentral\Modules\CopperSurcharge\Wrapper\DocumentPositionWrapper;
use Xentral\Modules\CopperSurcharge\Wrapper\DocumentPositionWrapperInterface;
final class CopperSurchargeCalculator
{
/** @var RawMaterialGateway $rawMaterialGateway */
private $rawMaterialGateway;
/** @var PurchasePriceGateway $purchasePriceGateway */
private $purchasePriceGateway;
/** @var DocumentPositionWrapper $documentPositionWrapper */
private $documentPositionWrapper;
/** @var CopperSurchargeData $config */
private $config;
/** @var DocumentService $documentService */
private $documentService;
/** @var DocumentGateway $documentGateway */
private $documentGateway;
/**
* @param PurchasePriceGateway $purchasePriceGateway
* @param RawMaterialGateway $rawMaterialGateway
* @param DocumentPositionWrapperInterface $documentPositionWrapper
* @param DocumentService $documentService
* @param DocumentGateway $documentGateway
* @param CopperSurchargeData $copperSurchargeConfig
*/
public function __construct(
PurchasePriceGateway $purchasePriceGateway,
RawMaterialGateway $rawMaterialGateway,
DocumentPositionWrapperInterface $documentPositionWrapper,
DocumentService $documentService,
DocumentGateway $documentGateway,
CopperSurchargeData $copperSurchargeConfig
) {
$this->purchasePriceGateway = $purchasePriceGateway;
$this->rawMaterialGateway = $rawMaterialGateway;
$this->documentPositionWrapper = $documentPositionWrapper;
$this->documentService = $documentService;
$this->config = $copperSurchargeConfig;
$this->documentGateway = $documentGateway;
}
/**
* @param string $docType
* @param int $docId
* @param array|DocumentPositionData[] $possibleCopperPositions
* @param array $copperPositionsInPartsList
*
* @return int
*/
public function handleCopperSurchargePositions(
string $docType,
int $docId,
array $possibleCopperPositions,
array $copperPositionsInPartsList
): int {
$calcDate = $this->evaluateCalcDate($docType, $docId);
if ($this->config->getSurchargePositionType() === CopperSurchargeData::POSITION_TYPE_ALWAYS) {
$this->createManyPositions(
$docType,
$docId,
$possibleCopperPositions,
$copperPositionsInPartsList,
$calcDate
);
return 0;
} elseif ($this->config->getSurchargePositionType() === CopperSurchargeData::POSITION_TYPE_ONETIME) {
$this->createSinglePosition(
$docType,
$docId,
$possibleCopperPositions,
$copperPositionsInPartsList,
$calcDate
);
return 1;
} else {
$this->createGroupPositions($docType, $docId, $copperPositionsInPartsList, $calcDate);
return 2;
}
}
/**
* Because there is no connection between positions,
* all surcharge positions get deleted and recreated in the next step
*
* @param string $docType
* @param int $docId
*/
public function resetDocument(string $docType, int $docId): void
{
$this->resetBetweenPositions($docId, $docType, $this->config->getCopperSurchargeArticleId());
$this->documentService->deleteCopperSurchargePositions(
$docType,
$docId,
$this->config->getCopperSurchargeArticleId()
);
$this->documentService->updatePositionSorts($docType, $docId);
}
/**
* @param string $docType
* @param int $docId
* @param array|DocumentPositionData[] $copperPositions
* @param array $copperPositionsInPartsList
* @param DateTimeInterface $calcDate
*
*/
private function createManyPositions(
string $docType,
int $docId,
array $copperPositions,
array $copperPositionsInPartsList,
DateTimeInterface $calcDate
): void {
foreach ($copperPositions as $position) {
$amount = $this->calcAmount($docType, $position->getPositionId(), $position->getArticleId());
$copperBase = $this->getCopperBase($position->getArticleId());
$price = $this->calculateCopperSurchargePrice(
$copperBase,
$amount,
$calcDate
);
$newPosId = $this->addCopperSurchargePosition(
$docType,
$docId,
$price,
$amount,
$copperBase,
$position->getCurrency(),
$calcDate
);
$this->documentService->updatePositionSort($docType, $docId, $position->getPositionId(), $newPosId);
}
if (empty($copperPositionsInPartsList)) {
return;
}
foreach ($copperPositionsInPartsList as $partListPosition) {
$partListData = $this->evaluateSurchargeDataForPartList(
$partListPosition['article_id'],
$partListPosition['pos_id'],
$calcDate,
$docType,
$partListPosition['amount']
);
$newPosId = $this->addCopperSurchargePosition(
$docType,
$docId,
$partListData['price'],
$partListData['amount'],
$partListData['copper_base'],
$partListPosition['currency'],
$calcDate
);
$this->documentService->updatePositionSort($docType, $docId, $partListData['position_id'], $newPosId);
}
}
/**
* @param int $partListHeadId
* @param int $positionId
* @param DateTimeInterface $calcDate
* @param string $docType
* @param float $partListPositionAmount
*
* @return array
*/
private function evaluateSurchargeDataForPartList(
int $partListHeadId,
int $positionId,
DateTimeInterface $calcDate,
string $docType,
float $partListPositionAmount
): array {
$copperArticles =
$this->getCopperArticlesFromPartList(
$partListHeadId,
$this->config->getSurchargeMaintenanceType(),
$this->config->getCopperNumberOption(),
$this->config->getCopperSurchargeArticleId()
);
$amount = 0;
$price = 0.0;
$copperBase = 0.0;
foreach ($copperArticles as $copperArticle) {
$amount += $copperArticle['amount'];
$copperBase = $this->getCopperBase($copperArticle['article_id']);
$price += $this->calculateCopperSurchargePrice(
$copperBase,
$amount,
$calcDate
);
}
return [
'amount' => $amount * $partListPositionAmount,
'price' => $price * $partListPositionAmount,
'copper_base' => $copperBase,
'position_id' => $this->documentGateway->evaluatePartListLastPositionId($docType, $positionId),
];
}
/**
* @param string $docType
* @param int $docId
* @param float $price
* @param float $amount
* @param float $copperBase
* @param $currency
* @param DateTimeInterface $calcDate
*
* @return int
*/
private function addCopperSurchargePosition(
string $docType,
int $docId,
float $price,
float $amount,
float $copperBase,
$currency,
DateTimeInterface $calcDate
): int {
$copperSurchargeArticleId = $this->config->getCopperSurchargeArticleId();
$articleData = $this->documentGateway->getArticleData($copperSurchargeArticleId);
$description = $this->findCopperSurchargeArticleDescription(
$amount,
$copperBase,
$price,
$articleData,
$calcDate
);
return $this->documentPositionWrapper->addPositionManuallyWithPrice(
$docType,
$docId,
$copperSurchargeArticleId,
$articleData,
1,
$price,
$currency,
$description
);
}
/**
* @param float $amount
* @param float $copperBase
* @param float $price
* @param array $articleData
* @param DateTimeInterface $calcDate
*
* @return string
*/
private function findCopperSurchargeArticleDescription(
float $amount,
float $copperBase,
float $price,
array $articleData,
DateTimeInterface $calcDate
): string {
$description = $articleData['description'];
$delPrice = $this->purchasePriceGateway->getDelCopperPriceByDate(
$calcDate,
$this->config->getCopperSurchargeArticleId()
);
$price = number_format($price, 2, ",", ".");
$delPrice = number_format($delPrice, 2, ",", ".");
$copperBase = number_format($copperBase, 2, ",", ".");
$amount = str_replace('.', ',', $amount);
$description = str_replace('{NETPRICE}', $price, $description);
$description = str_replace('{ARTIKELNUMMER}', $articleData['number'], $description);
$description = str_replace('{ARTIKELNAME}', $articleData['name_de'], $description);
$description = str_replace('{COPPERBASIS}', $copperBase, $description);
$description = str_replace('{COPPERNUMBER}', $amount, $description);
$description = str_replace('{DELVALUE}', $delPrice, $description);
return $description;
}
/**
* @param string $docType
* @param int $docId
* @param array|DocumentPositionData[] $copperPositions
* @param array $copperPositionsInPartsList
* @param DateTimeInterface $calcDate
*
* @return int
*/
private function createSinglePosition(
string $docType,
int $docId,
array $copperPositions,
array $copperPositionsInPartsList,
DateTimeInterface $calcDate
): int {
$price = 0.0;
$totalAmount = 0.0;
$currency = 'EUR';
$copperBase = 0.0;
foreach ($copperPositions as $position) {
$currency = $position->getCurrency();
$amount = $this->calcAmount($docType, $position->getPositionId(), $position->getArticleId());
$totalAmount += $amount;
$copperBase = $this->getCopperBase($position->getArticleId());
$price += $this->calculateCopperSurchargePrice(
$copperBase,
$amount,
$calcDate
);
}
if (!empty($copperPositionsInPartsList)) {
foreach ($copperPositionsInPartsList as $position) {
$partListData = $this->evaluateSurchargeDataForPartList(
$position['article_id'],
$position['pos_id'],
$calcDate,
$docType,
$position['amount']
);
$totalAmount += $partListData['amount'];
$price += $partListData['price'];
$copperBase = $partListData['copper_base'];
}
}
$newPosId = 0;
if ($price > 0) {
$newPosId = $this->addCopperSurchargePosition(
$docType,
$docId,
$price,
$totalAmount,
$copperBase,
$currency,
$calcDate
);
}
return $newPosId;
}
/**
* @param string $docType
* @param int $docId
* @param array $copperPositionsInPartsList
* @param DateTimeInterface $calcDate
*/
private function createGroupPositions(
string $docType,
int $docId,
array $copperPositionsInPartsList,
DateTimeInterface $calcDate
): void {
if ($this->config->getSurchargeMaintenanceType() === CopperSurchargeData::SURCHARGE_MAINTENANCE_TYPE_APP) {
$positions = $this->rawMaterialGateway->findAllPositionsForGrouped(
$docType,
$docId,
$this->config->getCopperSurchargeArticleId()
);
} else {
$positions = $this->documentGateway->findAllPositionsForGrouped(
$docType,
$docId,
$this->config->getCopperNumberOption()
);
}
$price = 0.0;
$totalAmount = 0.0;
$currency = 'EUR';
$prev = null;
$copperBase = 0.0;
$lastSort = 0;
foreach ($positions as $key => $position) {
if ((bool)$position['is_copper']) {
$amount = $this->calcAmount($docType, (int)$position['pos_id'], (int)$position['article_id']);
$copperBase = $this->getCopperBase($position['article_id']);
$price += $this->calculateCopperSurchargePrice(
$copperBase,
$amount,
$calcDate
);
$currency = $position['currency'];
$totalAmount += $amount;
}
if ($position['between_type'] === 'gruppe') {
if (!empty($copperPositionsInPartsList)) {
$partListElements = $this->getElementsFromPartListBetweenSorts(
$copperPositionsInPartsList,
$lastSort,
$position['sort']
);
foreach ($partListElements as $partListElement) {
$partListKey = $partListElement['part_list_key'];
$partListData = $this->evaluateSurchargeDataForPartList(
$partListElement['article_id'],
$position['pos_id'],
$calcDate,
$docType,
$partListElement['amount']
);
$totalAmount += $partListData['amount'];
$price += $partListData['price'];
$copperBase = $partListData['copper_base'];
unset($copperPositionsInPartsList[$partListKey]);
}
}
if ($price > 0.0) {
$newPosId = $this->addCopperSurchargePosition(
$docType,
$docId,
$price,
$totalAmount,
$copperBase,
$currency,
$calcDate
);
if (!empty($prev)) {
$this->documentService->updatePositionSort($docType, $docId, (int)$prev['pos_id'], $newPosId);
}
$price = 0.0;
$totalAmount = 0.0;
$lastSort = $position['sort'];
}
}
$prev = $position;
}
if (!empty($copperPositionsInPartsList)) {
foreach ($copperPositionsInPartsList as $partListElement) {
$partListData = $this->evaluateSurchargeDataForPartList(
$partListElement['article_id'],
$partListElement['pos_id'],
$calcDate,
$docType,
$partListElement['amount']
);
$totalAmount += $partListData['amount'];
$price += $partListData['price'];
$copperBase = $partListData['copper_base'];
}
}
if ($price > 0.0) {
$this->addCopperSurchargePosition(
$docType,
$docId,
$price,
$totalAmount,
$copperBase,
$currency,
$calcDate
);
}
}
/**
* @param float $copperBase
* @param float $amount
* @param DateTimeInterface $calcDate
*
* @return float
*/
private function calculateCopperSurchargePrice(
float $copperBase,
float $amount,
DateTimeInterface $calcDate
): float {
$delPrice = $this->purchasePriceGateway->getDelCopperPriceByDate(
$calcDate,
$this->config->getCopperSurchargeArticleId()
);
$perCent = $this->config->getSurchargeDeliveryCosts() / 100;
return (($delPrice + ($delPrice * $perCent)) - $copperBase) * $amount / 100;
}
/**
* @param string $docType
* @param int $positionId
* @param int $positionArticleId
*
* @return float
*/
private function calcAmount(string $docType, int $positionId, int $positionArticleId): float
{
if ($this->config->getSurchargeMaintenanceType() === CopperSurchargeData::SURCHARGE_MAINTENANCE_TYPE_APP) {
$amount = $this->rawMaterialGateway->getRawMaterialAmount(
$positionArticleId,
$this->config->getCopperSurchargeArticleId()
);
} else {
$articleId = $this->documentGateway->getArticleIdByPositionId($docType, $positionId);
$amount = $this->documentGateway->getArticleCopperNumber(
$articleId,
$this->config->getCopperNumberOption()
);
}
$documentAmount = $this->documentGateway->getPositionAmount($docType, $positionId);
$amount *= $documentAmount;
return (float)$amount;
}
/**
* @param int $positionArticleId
*
* @return float
*/
private function getCopperBase(int $positionArticleId): float
{
$copperBase = $this->config->getSurchargeCopperBaseStandard();
$articleCopperBaseField = $this->config->getSurchargeCopperBase();
if (!empty($articleCopperBaseField)) {
$copperBaseTemp = $this->documentGateway->getArticleCopperBase($positionArticleId, $articleCopperBaseField);
if (!empty($copperBaseTemp)) {
$copperBase = $copperBaseTemp;
}
}
return $copperBase;
}
/**
* @param string $doctype
* @param int $docId
*
* @throws InvalidDateFormatException
* @return DateTimeInterface
*/
private function evaluateCalcDate(string $doctype, int $docId): DateTimeInterface
{
$orderOfferId = 0;
if ($doctype === 'auftrag') {
$orderOfferId = $this->documentGateway->findOrderOfferId($docId);
}
if (
$doctype === 'rechnung' &&
$this->config->getSurchargeInvoice() === CopperSurchargeData::INVOICE_CREATE_POS_BY_DELIVERY_DATE
) {
$calcDate = $this->documentGateway->findDeliveryDate($docId);
if (empty($calcDate)) {
$calcDate = new DateTimeImmutable();
}
} elseif (
$doctype === 'rechnung' &&
$this->config->getSurchargeInvoice() === CopperSurchargeData::INVOICE_CREATE_POS_BY_ORDER_DATE
) {
$orderId = $this->documentGateway->findInvoiceOrderId($docId);
if (empty($orderId)) {
$calcDate = new DateTimeImmutable();
} else {
$calcDate = $this->documentGateway->getCalcDate('auftrag', $orderId);
}
} elseif ($doctype === 'rechnung' &&
$this->config->getSurchargeInvoice() === CopperSurchargeData::INVOICE_CREATE_POS_BY_INVOICE_DATE) {
$calcDate = $this->documentGateway->getCalcDate($doctype, $docId);
} elseif ($doctype === 'rechnung' &&
$this->config->getSurchargeInvoice() === CopperSurchargeData::INVOICE_CREATE_POS_BY_OFFER_DATE) {
$offerId = $this->documentGateway->findInvoiceOfferId($docId);
if (!empty($offerId)) {
$calcDate = $this->documentGateway->getCalcDate('angebot', $offerId);
} else {
$calcDate = new DateTimeImmutable();
}
} elseif (
$doctype === 'auftrag' &&
$orderOfferId !== 0 &&
$this->config->getSurchargeDocumentConversion() === CopperSurchargeData::DOCUMENT_CONVERSION_FROM_OFFER
) {
$calcDate = $this->documentGateway->getCalcDate('angebot', $orderOfferId);
} else {
$calcDate = new DateTimeImmutable();
}
return $calcDate;
}
/**
* @param int $docId
* @param string $docType
* @param int $copperSurchargeArticleId
*/
private function resetBetweenPositions(int $docId, string $docType, int $copperSurchargeArticleId): void
{
if ($this->config->getSurchargeMaintenanceType() === CopperSurchargeData::SURCHARGE_MAINTENANCE_TYPE_APP) {
$positions = $this->rawMaterialGateway->findAllPositionsForGrouped(
$docType,
$docId,
$copperSurchargeArticleId
);
} else {
$positions = $this->documentGateway->findAllPositionsForGrouped(
$docType,
$docId,
$this->config->getCopperNumberOption()
);
}
$offset = 0;
foreach ($positions as $position) {
if ((int)$position['article_id'] === $copperSurchargeArticleId) {
$offset--;
}
if ((int)$position['pos_type'] === 2 && $offset < 0) {
$this->documentService->updateBetweenSort(
(int)$position['between_id'],
(int)$position['sort'] + $offset
);
}
}
}
/**
* @param string $docType
* @param int $docId
*/
public function deleteRemainingCopperSurchargeArticles(string $docType, int $docId): void
{
if ($this->config->getSurchargeMaintenanceType() === CopperSurchargeData::SURCHARGE_MAINTENANCE_TYPE_ARTICLE) {
$hasCopperArticles =
$this->documentGateway->hasCopperArticles(
$docType,
$docId,
$this->config->getCopperNumberOption()
);
} else {
$hasCopperArticles =
$this->rawMaterialGateway->hasCopperArticles(
$docType,
$docId,
$this->config->getCopperSurchargeArticleId()
);
}
if (!$hasCopperArticles) {
$this->documentService->deleteCopperSurchargePositions(
$docType,
$docId,
$this->config->getCopperSurchargeArticleId()
);
}
}
/**
* @param string $docType
* @param int $posId
*/
public function updatePositionContributionMargin(string $docType, int $posId)
{
$this->documentService->updatePositionContributionMargin($docType, $posId, 0);
}
/**
* @param string $docType
* @param int $posId
*/
public function updatePositionPurchasePrice(string $docType, int $posId)
{
$this->documentService->updatePositionPurchasePrice($docType, $posId, 0.0);
}
/**
* @param string $docType
*
* @param int $docTypeId
*
* @return array|DocumentPositionData[]
*/
public function findPositionsForMaintenanceApp(string $docType, int $docTypeId): array
{
$data = $this->rawMaterialGateway->findPositions(
$docType,
$docTypeId,
$this->config->getCopperSurchargeArticleId()
);
return $this->transformToDocumentPositionData($data);
}
/**
* @param string $docType
*
* @param int $docTypeId
*
* @return array|DocumentPositionData[]
*/
public function findPositionsForMaintenanceArticle(string $docType, int $docTypeId): array
{
$data = $this->documentGateway->findPositions(
$docType,
$docTypeId,
$this->config->getCopperNumberOption()
);
return $this->transformToDocumentPositionData($data);
}
/**
* @param array $copperPositionsRaw
*
* @return array|DocumentPositionData[]
*/
private function transformToDocumentPositionData(array $copperPositionsRaw): array
{
$documentPositions = [];
foreach ($copperPositionsRaw as $position) {
$data = new DocumentPositionData(
(int)$position['pos_id'],
(int)$position['article_id'],
(string)$position['currency']
);
$documentPositions[] = $data;
}
return $documentPositions;
}
/**
* @param int $doctypeId
* @param string $doctype
*/
public function updateCopperSurchargeArticles(int $doctypeId, string $doctype)
{
$copperSurchargeArticleId = $this->config->getCopperSurchargeArticleId();
$surchargePositions = $this->documentGateway->findCopperSurchargeArticlePositionIds(
$doctype,
$doctypeId,
$copperSurchargeArticleId
);
if (!empty($surchargePositions)) {
foreach ($surchargePositions as $surchargePosition) {
$this->updatePositionContributionMargin($doctype, (int)$surchargePosition['pos_id']);
$this->updatePositionPurchasePrice($doctype, (int)$surchargePosition['pos_id']);
}
}
}
/**
* @param array $copperPositionsInPartsList
* @param int $lastSort
* @param int $nextSort
*
* @return array
*/
private function getElementsFromPartListBetweenSorts(
array $copperPositionsInPartsList,
int $lastSort,
int $nextSort
): array {
$result = [];
foreach ($copperPositionsInPartsList as $partListKey => $position) {
$currentSort = $position['sort'];
if ($currentSort > $lastSort && $currentSort <= $nextSort) {
$position['part_list_key'] = $partListKey;
$result[] = $position;
}
}
return $result;
}
/**
* @param int $partListHeadId
* @param int $surchargeMaintenanceType
* @param string $copperNumberOption
* @param int $copperArticleId
*
* @return array
*/
private function getCopperArticlesFromPartList(
int $partListHeadId,
int $surchargeMaintenanceType,
string $copperNumberOption,
int $copperArticleId
): array {
$result = [];
$childElements = $this->documentGateway->getAllPartListChildElements($partListHeadId);
foreach ($childElements as $childElement) {
if ($surchargeMaintenanceType === CopperSurchargeData::SURCHARGE_MAINTENANCE_TYPE_ARTICLE) {
$possibleArticle = $this->documentGateway->findPossibleCopperArticle(
$childElement['id'],
$copperNumberOption
);
} else {
$possibleArticle = $this->rawMaterialGateway->findPossibleCopperArticle(
$childElement['id'],
$copperArticleId
);
}
if (empty($possibleArticle)) {
continue;
}
$result[] = [
'article_id' => $possibleArticle['article_id'],
'amount' => $possibleArticle['amount'] * $childElement['amount'],
];
}
return $result;
}
/**
* @param string $docType
* @param int $docTypeId
*
* @return array
*/
public function findPositionsForMaintenanceAppInPartsList(
string $docType,
int $docTypeId
): array {
$result = [];
$copperArticleId = $this->config->getCopperSurchargeArticleId();
$headArticles = $this->documentGateway->findPartListHeadArticles($docType, $docTypeId);
foreach ($headArticles as $headArticle) {
$childElements = $this->documentGateway->getAllPartListChildElements($headArticle['id']);
$hasCopper = false;
foreach ($childElements as $childElement) {
if (!$hasCopper) {
$hasCopper = !empty(
$this->rawMaterialGateway->findPossibleCopperArticle(
$childElement['id'],
$copperArticleId
)
);
}
}
if ($hasCopper) {
$result[] = [
'article_id' => $headArticle['id'],
'sort' => $headArticle['sort'],
'pos_id' => $headArticle['pos_id'],
'currency' => $headArticle['currency'],
'amount' => (float)$headArticle['amount'],
];
}
}
return $result;
}
/**
* @param string $docType
* @param int $docTypeId
*
* @return array
*/
public function findPositionsForMaintenanceArticleInPartsList(
string $docType,
int $docTypeId
): array {
$result = [];
$copperNumberOption = $this->config->getCopperNumberOption();
$headArticles = $this->documentGateway->findPartListHeadArticles($docType, $docTypeId);
foreach ($headArticles as $headArticle) {
$childElements = $this->documentGateway->getAllPartListChildElements((int)$headArticle['id']);
$hasCopper = false;
foreach ($childElements as $childElement) {
if (!$hasCopper) {
$hasCopper = !empty(
$this->documentGateway->findPossibleCopperArticle(
$childElement['id'],
$copperNumberOption
)
);
}
}
if ($hasCopper) {
$result[] = [
'article_id' => $headArticle['id'],
'sort' => $headArticle['sort'],
'pos_id' => $headArticle['pos_id'],
'currency' => $headArticle['currency'],
'amount' => (float)$headArticle['amount'],
];
}
}
return $result;
}
}