diff --git a/dist/onoffice_defaultview.min.js b/dist/onoffice_defaultview.min.js index 8182ccdab..df85bfcaa 100644 --- a/dist/onoffice_defaultview.min.js +++ b/dist/onoffice_defaultview.min.js @@ -1 +1 @@ -(function($){$((function(){$(document).ready((function(){$("#oo-galleryslide").slick({infinite:true,slidesToShow:1});$("#oo-similarframe").slick({infinite:true,arrows:false,dots:true,autoplay:true,slidesToShow:3,slidesToScroll:1,responsive:[{breakpoint:991,settings:{slidesToShow:2}},{breakpoint:575,settings:{slidesToShow:1}}]});applyGradientToSegments();function applyGradientToSegments(){const segments=$(".oo-details-energy-certificate .energy-certificate-container .segment");if(segments.length===0)return;const colors={start:[0,128,0],middle:[255,185,0],end:[255,0,0]};segments.each((function(index){const positionRatio=index/(segments.length-1);const isInInitialSegment=positionRatio<.5;const normalizedPosition=isInInitialSegment?positionRatio*2:(positionRatio-.5)*2;const color=calculateColor(isInInitialSegment?colors.start:colors.middle,isInInitialSegment?colors.middle:colors.end,normalizedPosition);const nextColor=calculateColor(isInInitialSegment?colors.start:colors.middle,isInInitialSegment?colors.middle:colors.end,normalizedPosition+1/(segments.length-1));$(this).css("background",`linear-gradient(to right, rgb(${color.join(",")}), rgb(${nextColor.join(",")}))`)}))}function calculateColor(start,end,positionRatio){return start.map(((startValue,i)=>Math.round(startValue+positionRatio*(end[i]-startValue))))}}))}))})(jQuery); \ No newline at end of file +(function($){$((function(){$(document).ready((function(){$("#oo-galleryslide").slick({infinite:true,slidesToShow:1});$("#oo-similarframe").slick({infinite:true,arrows:false,dots:true,autoplay:true,slidesToShow:3,slidesToScroll:1,responsive:[{breakpoint:991,settings:{slidesToShow:2}},{breakpoint:575,settings:{slidesToShow:1}}]});if($(".oo-costs-overview").length){initializeDonutChart()}function initializeDonutChart(){const colors=["#3F9DE4","#3ac411","#9C27B0","#D81B60","#FEC800"];const data=[];let totalCosts=0;$(".oo-costs-overview > div").each((function(index){const value=$(this).attr("data-value");const totalCostsValue=$(this).attr("total-costs-value");if(!isNaN(value)){data.push({value:value,color:colors[index]});$(this).find(".color-indicator").css("background-color",colors[index])}if(!isNaN(totalCostsValue)){totalCosts=totalCostsValue}}));let start=0;let gradientString="conic-gradient(";data.forEach((item=>{const percentage=item.value/totalCosts*100;const end=start+percentage;gradientString+=`${item.color} ${start.toFixed(2)}% ${end.toFixed(2)}%, `;start=end}));gradientString=gradientString.slice(0,-2)+")";$(".oo-donut-chart").css("background",gradientString)}applyGradientToSegments();function applyGradientToSegments(){const segments=$(".oo-details-energy-certificate .energy-certificate-container .segment");if(segments.length===0)return;const colors={start:[0,128,0],middle:[255,185,0],end:[255,0,0]};segments.each((function(index){const positionRatio=index/(segments.length-1);const isInInitialSegment=positionRatio<.5;const normalizedPosition=isInInitialSegment?positionRatio*2:(positionRatio-.5)*2;const color=calculateColor(isInInitialSegment?colors.start:colors.middle,isInInitialSegment?colors.middle:colors.end,normalizedPosition);const nextColor=calculateColor(isInInitialSegment?colors.start:colors.middle,isInInitialSegment?colors.middle:colors.end,normalizedPosition+1/(segments.length-1));$(this).css("background",`linear-gradient(to right, rgb(${color.join(",")}), rgb(${nextColor.join(",")}))`)}))}function calculateColor(start,end,positionRatio){return start.map(((startValue,i)=>Math.round(startValue+positionRatio*(end[i]-startValue))))}}))}))})(jQuery); \ No newline at end of file diff --git a/js/onoffice_defaultview.js b/js/onoffice_defaultview.js index 1f6c5e689..9eddb4d48 100644 --- a/js/onoffice_defaultview.js +++ b/js/onoffice_defaultview.js @@ -25,6 +25,7 @@ } }] }); + applyGradientToSegments(); function applyGradientToSegments() { diff --git a/plugin/DataView/DataDetailView.php b/plugin/DataView/DataDetailView.php index e98eab145..ece5e460c 100644 --- a/plugin/DataView/DataDetailView.php +++ b/plugin/DataView/DataDetailView.php @@ -66,6 +66,15 @@ class DataDetailView /** @var int */ const SHOW_MAIN_CONTACT_PERSON = '1'; + /** */ + const FIELD_TOTAL_COSTS_CALCULATOR = 'show_total_costs_calculator'; + + /** */ + const NOTARY_FEES = 1.5; + + /** */ + const LAND_REGISTER_ENTRY = 0.5; + /** @var string[] */ private $_fields = [ 'objekttitel', @@ -246,6 +255,9 @@ class DataDetailView ImageTypes::PASSPORTPHOTO ]; + /** @var bool */ + private $_showTotalCostsCalculator = false; + /** @var bool */ private $_showEnergyCertificate = false; @@ -265,6 +277,26 @@ class DataDetailView 'calculatedPrice' ]; + /** @var string[] */ + private $_propertyTransferTax = [ + 'Baden-Württemberg' => 5, + 'Bayern' => 3.5, + 'Berlin' => 6, + 'Brandenburg' => 6.5, + 'Bremen' => 5, + 'Hamburg' => 5.5, + 'Hessen' => 6, + 'Mecklenburg-Vorpommern' => 6, + 'Niedersachsen' => 5, + 'Nordrhein-Westfalen' => 6.5, + 'Rheinland-Pfalz' => 5, + 'Saarland' => 6.5, + 'Sachsen' => 5.5, + 'Sachsen-Anhalt' => 5, + 'Schleswig-Holstein' => 6.5, + 'Thüringen' => 5 + ]; + /** @var string */ private $_contactPerson = '0'; @@ -463,7 +495,6 @@ public function getShowEnergyCertificate(): bool public function setShowEnergyCertificate(bool $showEnergyCertificate) { $this->_showEnergyCertificate = $showEnergyCertificate; } - /** * @return array */ @@ -472,6 +503,18 @@ public function getListFieldsShowPriceOnRequest(): array return $this->_priceFields; } + /** @return array */ + public function getPropertyTransferTax(): array + { return $this->_propertyTransferTax; } + + /** @return bool */ + public function getShowTotalCostsCalculator(): bool + { return $this->_showTotalCostsCalculator; } + + /** @param bool $costsCalculator */ + public function setShowTotalCostsCalculator(bool $costsCalculator) + { $this->_showTotalCostsCalculator = $costsCalculator; } + /** @return array */ public function getContactImageTypes(): array { return $this->_contactImageTypes; } diff --git a/plugin/DataView/DataDetailViewHandler.php b/plugin/DataView/DataDetailViewHandler.php index b6744269a..84dea3d91 100644 --- a/plugin/DataView/DataDetailViewHandler.php +++ b/plugin/DataView/DataDetailViewHandler.php @@ -146,6 +146,7 @@ public function createDetailViewByValues(array $row): DataDetailView $pDataDetailView->setShowStatus($row['show_status'] ?? false); $pDataDetailView->setCustomLabels($row[DataDetailView::FIELD_CUSTOM_LABEL] ?? []); $pDataDetailView->setShowPriceOnRequest($row[DataDetailView::FIELD_PRICE_ON_REQUEST] ?? false); + $pDataDetailView->setShowTotalCostsCalculator($row[DataDetailView::FIELD_TOTAL_COSTS_CALCULATOR] ?? false); $pDataDetailView->setContactImageTypes($row['contact_image_types'] ?? []); $pDataDetailView->setShowEnergyCertificate($row['show_energy_certificate'] ?? false); $pDataDetailView->setContactPerson($row['contact_person'] ?? '0'); diff --git a/plugin/DonutChart.php b/plugin/DonutChart.php new file mode 100644 index 000000000..e999f76b1 --- /dev/null +++ b/plugin/DonutChart.php @@ -0,0 +1,92 @@ +. + * + */ + +namespace onOffice\WPlugin; + +use onOffice\WPlugin\EstateList; +/** + * + * @url http://www.onoffice.de + * @copyright 2003-2017, onOffice(R) GmbH + * + */ + class DonutChart + { + private $values; + private $valuesTitle; + private $colors = ['#3F9DE4', '#3ac411', '#9C27B0', '#D81B60', '#FEC800']; + + public function __construct(array $values, array $valuesTitle) + { + $this->values = $values; + $this->valuesTitle = $valuesTitle; + } + + private function toRadians($angle) + { + return $angle * pi() / 180; + } + + private function polarToCartesian($radius, $angle, $subtractGap = false) + { + $adjustedAngle = $this->toRadians($angle - 90); + if ($subtractGap) { + $adjustedAngle -= asin(0 / $radius); + } + $x = 300+ $radius * cos($adjustedAngle); + $y = 210 + $radius * sin($adjustedAngle); + + return sprintf('%0.2f,%0.2f', $x, $y); + } + + public function generateSVG() + { + $total = array_sum($this->values); + //$total = getTotalCostsData(); + $anglePerValue = 360 / $total; + $angleStart = 0; + + $svgContent = "\n"; + $svgContent .= "\n"; + + $counter = 0; + foreach ($this->values as $value) { + $angleDelta = $value * $anglePerValue; + $largeArcFlag = $angleDelta > 180 ? 1 : 0; + $angleEnd = $angleStart + $angleDelta; + + $path = [ + "M" . $this->polarToCartesian(190, $angleStart), + "L" . $this->polarToCartesian(130, $angleStart), + "A 130,130,0,{$largeArcFlag},1," . $this->polarToCartesian(130, $angleEnd, true), + "L" . $this->polarToCartesian(190, $angleEnd, true), + "A 190,190,0,{$largeArcFlag},0," . $this->polarToCartesian(190, $angleStart) + ]; + + $svgContent .= ''.$this->valuesTitle[$counter].'' . "\n"; + $angleStart = $angleEnd; + $counter++; + } + + $svgContent .= "\n"; + return $svgContent; + } + } \ No newline at end of file diff --git a/plugin/EstateList.php b/plugin/EstateList.php index e33814ff1..5dc75d7a2 100644 --- a/plugin/EstateList.php +++ b/plugin/EstateList.php @@ -59,6 +59,7 @@ use onOffice\WPlugin\WP\WPPluginChecker; use onOffice\WPlugin\Field\Collection\FieldsCollectionBuilderShort; use onOffice\WPlugin\Field\FieldParkingLot; +use onOffice\WPlugin\Field\CostsCalculator; class EstateList implements EstateListBase @@ -114,12 +115,16 @@ class EstateList /** @var Redirector */ private $_redirectIfOldUrl; + /** @var array */ + private $_totalCostsData = []; + /** @var FieldsCollection */ private $_pFieldsCollection; /** @var string */ private $_energyCertificate = ''; + /** * @param DataView $pDataView * @param EstateListEnvironment $pEnvironment @@ -247,6 +252,11 @@ private function loadRecords(int $currentPage) $estateParametersRaw['data'] []= 'vermarktungsart'; $estateParametersRaw['data'] []= 'preisAufAnfrage'; + if ($this->getShowTotalCostsCalculator()) { + $fields = ['kaufpreis', 'aussen_courtage', 'bundesland', 'waehrung']; + $estateParametersRaw['data'] = array_merge($estateParametersRaw['data'], $fields); + } + if ($this->getShowEnergyCertificate()) { $energyCertificateFields = ['energieausweistyp', 'energyClass']; $estateParametersRaw['data'] = array_merge($estateParametersRaw['data'], $energyCertificateFields); @@ -681,6 +691,15 @@ public function estateIterator($modifier = EstateViewFieldModifierTypes::MODIFIE $recordModified['vermarktungsstatus'] = $pEstateStatusLabel->getLabel($recordRaw); } + if ($this->getShowTotalCostsCalculator()) { + $externalCommission = $this->getExternalCommission($recordRaw['aussen_courtage'] ?? ''); + $propertyTransferTax = $this->_pDataView->getPropertyTransferTax(); + if (!empty((float) $recordRaw['kaufpreis']) && !empty($recordRaw['bundesland']) && $externalCommission !== null) { + $costsCalculator = $this->_pEnvironment->getContainer()->get(CostsCalculator::class); + $this->_totalCostsData = $costsCalculator->getTotalCosts($recordRaw, $propertyTransferTax, $externalCommission); + } + } + if ($modifier === EstateViewFieldModifierTypes::MODIFIER_TYPE_MAP && $this->_pDataView instanceof DataListView) { $recordModified['showGoogleMap'] = $this->getShowMapConfig(); } @@ -717,6 +736,7 @@ public function estateIterator($modifier = EstateViewFieldModifierTypes::MODIFIE foreach ($priceFields as $priceField) { $this->displayTextPriceOnRequest($recordModified, $priceField); } + $this->_totalCostsData = []; } } // do not show priceOnRequest as single Field @@ -725,6 +745,27 @@ public function estateIterator($modifier = EstateViewFieldModifierTypes::MODIFIE return $recordModified; } + /** + * @param string $externalCommission + * @return mixed + */ + private function getExternalCommission(string $externalCommission) + { + if (preg_match('/(\d+[,]?\d*)\s*%/', $externalCommission, $matches)) { + return floatval(str_replace(',', '.', $matches[1])); + } + + return null; + } + + /** + * @return array + */ + public function getTotalCostsData(): array + { + return $this->_totalCostsData; + } + /** * @param ArrayContainerEscape $recordModified * @param string $field @@ -1348,4 +1389,16 @@ public function getListViewId() return 'estate_detail'; } + + /** + * @return bool + */ + public function getShowTotalCostsCalculator(): bool + { + if ($this->_pDataView instanceof DataDetailView) { + return $this->_pDataView->getShowTotalCostsCalculator(); + } + + return false; + } } diff --git a/plugin/Field/CostsCalculator.php b/plugin/Field/CostsCalculator.php new file mode 100644 index 000000000..7ffe4adf1 --- /dev/null +++ b/plugin/Field/CostsCalculator.php @@ -0,0 +1,148 @@ +. + */ + +namespace onOffice\WPlugin\Field; + +use onOffice\WPlugin\DataView\DataDetailView; +use onOffice\SDK\onOfficeSDK; +use onOffice\WPlugin\API\APIClientActionGeneric; +use onOffice\WPlugin\SDKWrapper; + +class CostsCalculator +{ + /** @var SDKWrapper */ + private $_pSDKWrapper; + + public function __construct(SDKWrapper $_pSDKWrapper) + { + $this->_pSDKWrapper = $_pSDKWrapper; + } + + + /** + * @param array $recordRaw + * @param array $propertyTransferTax + * @param float $externalCommission + * @return array + */ + public function getTotalCosts(array $recordRaw, array $propertyTransferTax, float $externalCommission): array + { + $totalCostsData = $this->calculateRawCosts($recordRaw, $propertyTransferTax, $externalCommission); + $currencySymbol = $this->getCurrencySymbol(); + + if (empty($currencySymbol)) { + return []; + } + + $currency = $currencySymbol[$recordRaw['waehrung']]; + + return $this->formatPrice($totalCostsData, $currency); + } + + /** + * @param array $recordRaw + * @param array $propertyTransferTax + * @param float $externalCommission + * @return array + */ + private function calculateRawCosts(array $recordRaw, array $propertyTransferTax, float $externalCommission): array + { + $purchasePriceRaw = $recordRaw['kaufpreis']; + + $othersCosts = [ + 'bundesland' => $propertyTransferTax[$recordRaw['bundesland']], + 'aussen_courtage' => $externalCommission, + 'notary_fees' => DataDetailView::NOTARY_FEES, + 'land_register_entry' => DataDetailView::LAND_REGISTER_ENTRY + ]; + + $rawAllCosts = ['kaufpreis' => ['raw' => $purchasePriceRaw]]; + $totals = 0; + + foreach ($othersCosts as $key => $value) { + $calculatePrice = $this->calculatePrice($purchasePriceRaw, $value); + $rawAllCosts[$key] = ['raw' => $calculatePrice]; + $totals += $calculatePrice; + } + + $rawAllCosts['total_costs'] = ['raw' => $purchasePriceRaw + $totals]; + + return $rawAllCosts; + } + + /** + * @param float $price + * @param float $rate + * @return float + */ + private function calculatePrice(float $price, float $rate): float + { + return round($price * $rate / 100); + } + + /** + * @param array $totalCostsData + * @param string $currency + * @return array + */ + private function formatPrice(array $totalCostsData, string $currency): array + { + foreach ($totalCostsData as $key => $value) { + $totalCostsData[$key]['default'] = $this->formatCurrency($value['raw'], $currency); + } + + return $totalCostsData; + } + + /** + * @param float $amount + * @param string $currency + * @return string + */ + private function formatCurrency(float $amount, string $currency): string + { + $decimalPlaces = floor($amount) == $amount ? 0 : 2; + + return number_format($amount, $decimalPlaces, ',', '.') . ' ' . $currency; + } + + /** + * @return array + */ + + private function getCurrencySymbol(): array + { + $parameters = [ + 'labels' => true, + 'fieldList' => ['waehrung'], + 'language' => 'DEU', + 'modules' => [onOfficeSDK::MODULE_ESTATE], + ]; + + $pAPIClientAction = new APIClientActionGeneric + ($this->_pSDKWrapper, onOfficeSDK::ACTION_ID_GET, 'fields'); + $pAPIClientAction->setParameters($parameters); + $pAPIClientAction->addRequestToQueue(); + $this->_pSDKWrapper->sendRequests(); + $result = $pAPIClientAction->getResultRecords(); + + return $result[0]['elements']['waehrung']['permittedvalues'] ?? []; + } +} \ No newline at end of file diff --git a/plugin/Gui/AdminPageEstateDetail.php b/plugin/Gui/AdminPageEstateDetail.php index 70078a9d2..ea055a07a 100644 --- a/plugin/Gui/AdminPageEstateDetail.php +++ b/plugin/Gui/AdminPageEstateDetail.php @@ -110,6 +110,9 @@ class AdminPageEstateDetail /** */ const FORM_VIEW_SEARCH_FIELD_FOR_FIELD_LISTS_CONFIG = 'viewSearchFieldForFieldListsConfig'; + /** */ + const FORM_VIEW_TOTAL_COSTS_CALCULATOR = 'viewtotalcostscalculator'; + /** */ const FORM_VIEW_CONTACT_IMAGE_TYPES = 'viewcontactimagetypes'; @@ -266,6 +269,8 @@ private function generateMetaBoxes() $pFormDocumentTypes = $this->getFormModelByGroupSlug(self::FORM_VIEW_ADDITIONAL_MEDIA); $this->createMetaBoxByForm($pFormDocumentTypes, 'side'); + $pFormTotalCostsCalculator = $this->getFormModelByGroupSlug(self::FORM_VIEW_TOTAL_COSTS_CALCULATOR); + $this->createMetaBoxByForm($pFormTotalCostsCalculator, 'normal'); $pFormContactPerson = $this->getFormModelByGroupSlug(self::FORM_VIEW_CONTACT_PERSON); $this->createMetaBoxByForm($pFormContactPerson, 'side'); @@ -352,6 +357,14 @@ protected function buildForms() $pFormModelAccessControl->addInputModel( $pInputModelAccessControl ); $this->addFormModel( $pFormModelAccessControl ); + $pInputModelTotalCostsCalculator = $pFormModelBuilder->createInputModelTotalCostsCalculator(); + $pFormModelTotalCostsCalculator = new FormModel(); + $pFormModelTotalCostsCalculator->setPageSlug($this->getPageSlug()); + $pFormModelTotalCostsCalculator->setGroupSlug(self::FORM_VIEW_TOTAL_COSTS_CALCULATOR); + $pFormModelTotalCostsCalculator->setLabel(__('Total costs calculator', 'onoffice-for-wp-websites')); + $pFormModelTotalCostsCalculator->addInputModel($pInputModelTotalCostsCalculator); + $this->addFormModel($pFormModelTotalCostsCalculator); + $pInputModelDocumentTypes = $pFormModelBuilder->createInputModelExpose(); $pInputModelMovieLinks = $pFormModelBuilder->createInputModelMovieLinks(); $pInputModelOguloLinks = $pFormModelBuilder->createInputModelOguloLinks(); diff --git a/plugin/Model/FormModelBuilder/FormModelBuilderEstateDetailSettings.php b/plugin/Model/FormModelBuilder/FormModelBuilderEstateDetailSettings.php index 00168780f..6f22e0a0a 100644 --- a/plugin/Model/FormModelBuilder/FormModelBuilderEstateDetailSettings.php +++ b/plugin/Model/FormModelBuilder/FormModelBuilderEstateDetailSettings.php @@ -633,6 +633,39 @@ public function createInputModelContactPerson(): InputModelOption return $pInputModelContactPerson; } + /** + * @return InputModelOption + * @throws ExceptionInputModelMissingField + */ + public function createInputModelTotalCostsCalculator(): InputModelOption + { + $labelShowTotalCostsCalculator = __('Show total price calculator', 'onoffice-for-wp-websites'); + $pInputModelShowTotalCostsCalculator = $this->_pInputModelDetailViewFactory->create + (InputModelOptionFactoryDetailView::INPUT_SHOW_TOTAL_COSTS_CALCULATOR, $labelShowTotalCostsCalculator); + $pInputModelShowTotalCostsCalculator->setHtmlType(InputModelBase::HTML_TYPE_CHECKBOX); + $pInputModelShowTotalCostsCalculator->setValue($this->_pDataDetailView->getShowTotalCostsCalculator()); + $pInputModelShowTotalCostsCalculator->setValuesAvailable(1); + + $fields = ['kaufpreis', 'aussen_courtage', 'bundesland']; + $pFieldsCollection = $this->getFieldsCollection(); + $result = []; + foreach ($fields as $field) { + if ($pFieldsCollection->containsFieldByModule(onOfficeSDK::MODULE_ESTATE, $field)) { + $result[$field] = $pFieldsCollection->getFieldByKeyUnsafe($field)->getLabel(); + } else { + $result[$field] = $field; + } + } + + $textHint = sprintf(esc_html__( + 'The fields %1$s, %2$s and %3$s must be filled in onOffice enterprise so that output is possible. %4$s %4$s A standard value of 1.5%% and 0.5%% respectively is typically used to calculate the notary and land registry entry costs.', 'onoffice-for-wp-websites'), + ''.$result['kaufpreis'].'', ''.$result['aussen_courtage'].'', ''.$result['bundesland'].'', '
' + ); + $pInputModelShowTotalCostsCalculator->setHintHtml($textHint); + + return $pInputModelShowTotalCostsCalculator; + } + /** * * @return InputModelOption diff --git a/plugin/Model/InputModel/InputModelOptionFactoryDetailView.php b/plugin/Model/InputModel/InputModelOptionFactoryDetailView.php index 21b9f2fc1..71dcab8df 100644 --- a/plugin/Model/InputModel/InputModelOptionFactoryDetailView.php +++ b/plugin/Model/InputModel/InputModelOptionFactoryDetailView.php @@ -86,6 +86,9 @@ class InputModelOptionFactoryDetailView /** */ const INPUT_CONTACT_IMAGE_TYPES = 'contact_image_types'; + /** @var string */ + const INPUT_SHOW_TOTAL_COSTS_CALCULATOR = 'show_total_costs_calculator'; + /** @var string */ const INPUT_SHOW_ENERGY_CERTIFICATE = 'show_energy_certificate'; @@ -140,6 +143,9 @@ class InputModelOptionFactoryDetailView self::INPUT_CONTACT_PERSON => [ self::KEY_TYPE => InputModelOption::SETTING_TYPE_STRING ], + self::INPUT_SHOW_TOTAL_COSTS_CALCULATOR => [ + self::KEY_TYPE => InputModelOption::SETTING_TYPE_BOOLEAN + ], self::INPUT_SHOW_ENERGY_CERTIFICATE => [ self::KEY_TYPE => InputModelOption::SETTING_TYPE_BOOLEAN ], diff --git a/templates.dist/estate/default_detail.php b/templates.dist/estate/default_detail.php index 05d0e2d3b..0e2038358 100644 --- a/templates.dist/estate/default_detail.php +++ b/templates.dist/estate/default_detail.php @@ -20,8 +20,8 @@ */ use onOffice\WPlugin\EstateDetail; +use onOffice\WPlugin\DonutChart; use onOffice\WPlugin\ViewFieldModifier\EstateViewFieldModifierTypes; - /** * * Default template @@ -85,6 +85,8 @@ padding: 0 10px; } + +
resetEstateIterator(); @@ -270,6 +272,68 @@ function renderEnergyCertificate(string $energyCertificateType, array $energyCla
+ getTotalCostsData())) { + $totalCostsData = $pEstates->getTotalCostsData(); + + ?> +
+

+
+
+ generateSVG(); ?> +
+
+

+
+ +
+
getFieldLabel('kaufpreis')); ?>
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
getEstateUnits(); ?>
@@ -507,6 +571,7 @@ function headingLink($url, $title) ?> +
@@ -535,4 +600,4 @@ function headingLink($url, $title) text-decoration: none; color: #000; } - \ No newline at end of file + diff --git a/templates.dist/onoffice-style.css b/templates.dist/onoffice-style.css index b67373459..e84d696d2 100644 --- a/templates.dist/onoffice-style.css +++ b/templates.dist/onoffice-style.css @@ -222,7 +222,7 @@ font-weight: 700; } -.oo-detailsfreetext, .oo-detailsmap, .oo-area-butler { +.oo-detailsfreetext, .oo-detailsmap, .oo-area-butler, .oo-detailspricecalculator { padding-bottom: 15px; padding-top: 15px; border-bottom: 2px solid #efefef; @@ -279,6 +279,53 @@ width: 100%; } +.oo-costs-container { + width: 100%; + display: flex; + justify-content: flex-start; + align-items: flex-start; +} + +.oo-costs-container .oo-donut-chart { + width: 40%; +} + + + +.oo-costs-container .oo-costs-overview { + width: 40%; +} + +.oo-costs-container .oo-costs-overview > div { + margin-bottom: 5px; +} + +.oo-costs-container .oo-costs-overview .oo-price-label, +.oo-costs-container .oo-costs-overview .oo-costs-item { + display: flex; + align-items: center; +} + +.oo-costs-container .oo-costs-overview .oo-price-label { + width: 100%; + justify-content: space-between; +} + +.oo-costs-container .oo-costs-overview .oo-total-costs-label { + margin-left: 20px; +} + +.oo-costs-container .oo-costs-overview .color-indicator { + display: inline-block; + width: 14px; + height: 14px; + border-radius: 50%; + margin-right: 5px; + flex-basis: 14px; + flex-grow: 0; + flex-shrink: 0; +} + .oo-details-energy-certificate { position: relative; height: auto; @@ -363,6 +410,28 @@ z-index: 888; } +.oo-donut-chart-color0 { + fill:#3F9DE4; + background-color:#3F9DE4; +} + +.oo-donut-chart-color1 { + fill:#3ac411; + background-color:#3ac411; +} +.oo-donut-chart-color2 { + fill:#9C27B0; + background-color:#9C27B0; +} +.oo-donut-chart-color3 { + fill:#D81B60; + background-color:#D81B60; +} +.oo-donut-chart-color4 { + fill:#FEC800; + background-color:#FEC800; +} + #TB_overlay, #TB_window { z-index: 1050 !important; } @@ -380,6 +449,23 @@ .oo-details-sidebar { width: 50%; } + + .oo-costs-container { + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + flex-direction: column; + gap: 2rem; + } + + .oo-costs-container .oo-donut-chart { + width: 100%; + } + + .oo-costs-container .oo-costs-overview { + width: 100%; + } } @media only screen and (max-width: 700px) { @@ -392,4 +478,4 @@ .oo-listobject, .oo-searchformfield, .oo-details-sidebar { width: 100%; } -} +} \ No newline at end of file diff --git a/tests/TestClassCostsCalculator.php b/tests/TestClassCostsCalculator.php new file mode 100644 index 000000000..8ffb1f7f1 --- /dev/null +++ b/tests/TestClassCostsCalculator.php @@ -0,0 +1,114 @@ +. + * + */ + +namespace onOffice\tests; + +use DI\Container; +use WP_UnitTestCase; +use DI\ContainerBuilder; +use onOffice\WPlugin\Field\CostsCalculator; +use onOffice\SDK\onOfficeSDK; +use onOffice\WPlugin\SDKWrapper; + +/** + * + * @url http://www.onoffice.de + * @copyright 2003-2024, onOffice(R) GmbH + * + */ + +class TestClassCostsCalculator + extends WP_UnitTestCase +{ + /** @var Container */ + private $_pContainer; + + /** @var CostsCalculator */ + private $_pCostsCalculator = null; + + /** @var string[] */ + private $_totalCostsData = [ + 'kaufpreis' => [ + 'raw' => 123456.56, + 'default' => '123.456,56 €' + ], + 'bundesland' => [ + 'raw' => 4321, + 'default' => '4.321 €' + ], + 'aussen_courtage' => [ + 'raw' => 22222, + 'default' => '22.222 €' + ], + 'notary_fees' => [ + 'raw' => 1852, + 'default' => '1.852 €' + ], + 'land_register_entry' => [ + 'raw' => 617, + 'default' => '617 €' + ], + 'total_costs' => [ + 'raw' => 152468.56, + 'default' => '152.468,56 €' + ] + ]; + + /** + * @before + */ + public function prepare() + { + $pSDKWrapperMocker = new SDKWrapperMocker(); + $pContainerBuilder = new ContainerBuilder; + $pContainerBuilder->addDefinitions(ONOFFICE_DI_CONFIG_PATH); + $this->_pContainer = $pContainerBuilder->build(); + $dataGetFieldCurrency = json_decode + (file_get_contents(__DIR__.'/resources/ApiResponseGetFieldsCurrency.json'), true); + $responseGetFieldCurrency = $dataGetFieldCurrency['response']; + $parametersGetFieldCurrency = $dataGetFieldCurrency['parameters']; + + $pSDKWrapperMocker->addResponseByParameters + (onOfficeSDK::ACTION_ID_GET, 'fields', '', $parametersGetFieldCurrency, null, $responseGetFieldCurrency); + + $this->_pContainer->set(SDKWrapper::class, $pSDKWrapperMocker); + $this->_pCostsCalculator = $this->_pContainer->get(CostsCalculator::class); + } + + /** + * + */ + public function testGetShowMapConfig() + { + $recordRaw = [ + 'kaufpreis' => '123456.56', + 'bundesland' => 'Bayern', + 'waehrung' => 'EUR' + ]; + $propertyTransferTax = [ + 'Bayern' => 3.5 + ]; + $externalCommission = '18'; + + $totalCostsData = $this->_pCostsCalculator->getTotalCosts($recordRaw, $propertyTransferTax, $externalCommission); + $this->assertEquals($totalCostsData, $this->_totalCostsData); + } +} diff --git a/tests/TestClassDataDetailView.php b/tests/TestClassDataDetailView.php index ec06b861f..93f59f64f 100644 --- a/tests/TestClassDataDetailView.php +++ b/tests/TestClassDataDetailView.php @@ -80,6 +80,26 @@ class TestClassDataDetailView 'defaultemail', ]; + /** */ + const PROPERTY_TRANSFER_TAX = [ + 'Baden-Württemberg' => 5, + 'Bayern' => 3.5, + 'Berlin' => 6, + 'Brandenburg' => 6.5, + 'Bremen' => 5, + 'Hamburg' => 5.5, + 'Hessen' => 6, + 'Mecklenburg-Vorpommern' => 6, + 'Niedersachsen' => 5, + 'Nordrhein-Westfalen' => 6.5, + 'Rheinland-Pfalz' => 5, + 'Saarland' => 6.5, + 'Sachsen' => 5.5, + 'Sachsen-Anhalt' => 5, + 'Schleswig-Holstein' => 6.5, + 'Thüringen' => 5 + ]; + /** */ const DEFAULT_PICTURE_TYPES = [ ImageTypes::TITLE, @@ -113,6 +133,7 @@ public function testDefaultValues() $this->assertEquals(LinksTypes::LINKS_EMBEDDED, $pDataDetailView->getOguloLinks()); $this->assertEquals(LinksTypes::LINKS_DEACTIVATED, $pDataDetailView->getObjectLinks()); $this->assertEquals(LinksTypes::LINKS_DEACTIVATED, $pDataDetailView->getLinks()); + $this->assertEquals(self::PROPERTY_TRANSFER_TAX, $pDataDetailView->getPropertyTransferTax()); } /** @@ -146,6 +167,8 @@ public function testGetterSetter() $pDataDetailView->setShowPriceOnRequest(true); $this->assertEquals(true, $pDataDetailView->getShowPriceOnRequest()); $this->assertTrue($pDataDetailView->getShowStatus()); + $pDataDetailView->setShowTotalCostsCalculator(true); + $this->assertTrue($pDataDetailView->getShowTotalCostsCalculator()); } /** diff --git a/tests/TestClassEstateList.php b/tests/TestClassEstateList.php index 0f25ccb20..b4550d4d7 100644 --- a/tests/TestClassEstateList.php +++ b/tests/TestClassEstateList.php @@ -906,7 +906,82 @@ public function testGetShowMapConfig() /** * */ + public function testGetShowTotalCostsCalculator() + { + $totalCostsData = [ + 'kaufpreis' => [ + 'raw' => 123456.56, + 'default' => '123.456,56 €' + ], + 'bundesland' => [ + 'raw' => 4321, + 'default' => '4.321 €' + ], + 'aussen_courtage' => [ + 'raw' => 22222, + 'default' => '22.222 €' + ], + 'notary_fees' => [ + 'raw' => 1852, + 'default' => '1.852 €' + ], + 'land_register_entry' => [ + 'raw' => 617, + 'default' => '617 €' + ], + 'total_costs' => [ + 'raw' => 152468.56, + 'default' => '152.468,56 €' + ] + ]; + + $pDataDetailView = $this->getMockBuilder(DataDetailView::class) + ->setConstructorArgs([$this->_pContainer]) + ->setMethods(['getRecordsPerPage', + 'getSortby', + 'getSortorder', + 'getFilterId', + 'getFields', + 'getPictureTypes', + 'getAddressFields', + 'getFilterableFields', + 'getPageId', + 'getViewRestrict', + 'getShowPriceOnRequest', + 'getListFieldsShowPriceOnRequest', + 'getShowTotalCostsCalculator' + ]) + ->getMock(); + $pDataDetailView->method('getRecordsPerPage')->willReturn(5); + $pDataDetailView->method('getSortby')->willReturn('Id'); + $pDataDetailView->method('getSortorder')->willReturn('ASC'); + $pDataDetailView->method('getFilterId')->willReturn(12); + $pDataDetailView->method('getFields')->willReturn(['Id', 'objektart', 'objekttyp', 'objekttitel', 'objektbeschreibung', 'warmmiete', 'kaufpreis', 'erbpacht', 'nettokaltmiete', 'pacht', 'kaltmiete']); + $pDataDetailView->method('getPictureTypes')->willReturn(['Titelbild', 'Foto']); + $pDataDetailView->method('getAddressFields')->willReturn(['Vorname', 'Name']); + $pDataDetailView->method('getFilterableFields')->willReturn([GeoPosition::FIELD_GEO_POSITION]); + $pDataDetailView->method('getPageId')->willReturn(5); + $pDataDetailView->method('getViewRestrict')->willReturn(true); + $pDataDetailView->method('getShowPriceOnRequest')->willReturn(true); + $pDataDetailView->method('getListFieldsShowPriceOnRequest')->willReturn(['kaufpreis', 'erbpacht']); + $pDataDetailView->method('getShowTotalCostsCalculator')->willReturn(true); + + $pDataDetailViewHandler = $this->getMockBuilder(DataDetailViewHandler::class) + ->disableOriginalConstructor() + ->setMethods(['getDetailView']) + ->getMock(); + $pDataDetailViewHandler->method('getDetailView')->willReturn($pDataDetailView); + $this->_pEnvironment->method('getDataDetailViewHandler')->willReturn($pDataDetailViewHandler); + $this->_pEstateList = new EstateList($pDataDetailView, $this->_pEnvironment); + $this->_pEstateList->loadEstates(); + $this->_pEstateList->estateIterator(); + $this->assertEquals($totalCostsData, $this->_pEstateList->getTotalCostsData()); + } + + /** + * + */ public function testGetPermittedValues() { $this->_pEstateList->loadEstates(); @@ -935,11 +1010,23 @@ public function prepareEstateList() (file_get_contents(__DIR__.'/resources/ApiResponseGetIdsFromRelation.json'), true); $responseGetEstatePictures = json_decode (file_get_contents(__DIR__.'/resources/ApiResponseGetEstatePictures.json'), true); + $dataReadEstatesPublishedENGCostsCalculatorRaw = json_decode + (file_get_contents(__DIR__.'/resources/ApiResponseReadEstatesCostsCalculatorRaw.json'), true); + $responseReadEstatesCostsCalculatorRaw = $dataReadEstatesPublishedENGCostsCalculatorRaw['response']; + $parametersReadEstatesCostsCalculatorRaw = $dataReadEstatesPublishedENGCostsCalculatorRaw['parameters']; + $dataGetFieldCurrency = json_decode + (file_get_contents(__DIR__.'/resources/ApiResponseGetFieldsCurrency.json'), true); + $responseGetFieldCurrency = $dataGetFieldCurrency['response']; + $parametersGetFieldCurrency = $dataGetFieldCurrency['parameters']; $this->_pSDKWrapperMocker->addResponseByParameters (onOfficeSDK::ACTION_ID_READ, 'estate', '', $parametersReadEstate, null, $responseReadEstate); $this->_pSDKWrapperMocker->addResponseByParameters (onOfficeSDK::ACTION_ID_READ, 'estate', '', $parametersReadEstateRaw, null, $responseReadEstateRaw); + $this->_pSDKWrapperMocker->addResponseByParameters + (onOfficeSDK::ACTION_ID_READ, 'estate', '', $parametersReadEstatesCostsCalculatorRaw, null, $responseReadEstatesCostsCalculatorRaw); + $this->_pSDKWrapperMocker->addResponseByParameters + (onOfficeSDK::ACTION_ID_GET, 'fields', '', $parametersGetFieldCurrency, null, $responseGetFieldCurrency); unset($parametersReadEstate['georangesearch']); $this->_pSDKWrapperMocker->addResponseByParameters diff --git a/tests/TestClassFormModelBuilderEstateDetailSettings.php b/tests/TestClassFormModelBuilderEstateDetailSettings.php index 631c3f5c0..ac9e53d08 100644 --- a/tests/TestClassFormModelBuilderEstateDetailSettings.php +++ b/tests/TestClassFormModelBuilderEstateDetailSettings.php @@ -435,6 +435,21 @@ public function testCreateInputModelContactPerson() $this->assertEquals($pInputModelDB->getHintHtml(), 'The main contact person is the address data record of the broker type, which is stored in the first place in onOffice enterprise.'); } + /** + * @covers onOffice\WPlugin\Model\FormModelBuilder\FormModelBuilderEstateDetailSettings::createInputModelTotalCostsCalculator + * @covers onOffice\WPlugin\Model\FormModelBuilder\FormModelBuilderEstateDetailSettings::getFieldsCollection + */ + public function testCreateInputModelTotalCostsCalculator() + { + $pFormModelBuilderEstateDetailSettings = $this->_pFormModelBuilderEstateDetailSettings; + $pFormModelBuilderEstateDetailSettings->generate('test'); + $pInputModelOption = $pFormModelBuilderEstateDetailSettings->createInputModelTotalCostsCalculator(); + + $this->assertInstanceOf(InputModelOption::class, $pInputModelOption); + $this->assertNotEmpty($pInputModelOption->getValuesAvailable()); + $this->assertEquals($pInputModelOption->getHtmlType(), 'checkbox'); + } + /** * @covers onOffice\WPlugin\Model\FormModelBuilder\FormModelBuilderEstateDetailSettings::createInputModelShowEnergyCertificate */ diff --git a/tests/TestTemplateEstateDefaultDetail.php b/tests/TestTemplateEstateDefaultDetail.php index 051c8243a..200d5ea23 100644 --- a/tests/TestTemplateEstateDefaultDetail.php +++ b/tests/TestTemplateEstateDefaultDetail.php @@ -66,6 +66,7 @@ public function prepare() 'getEstateLinks', 'getLinkEmbedPlayers', 'getDetailView', + 'getTotalCostsData', 'getShowEnergyCertificate', 'getPermittedValues', 'getRawValues', @@ -95,6 +96,33 @@ public function prepare() 'baujahr' => 'testField', ]; + $totalCostsData = [ + 'kaufpreis' => [ + 'raw' => 123456.56, + 'default' => '123.456,56 €' + ], + 'bundesland' => [ + 'raw' => 4321, + 'default' => '4.321 €' + ], + 'aussen_courtage' => [ + 'raw' => 22222, + 'default' => '22.222 €' + ], + 'notary_fees' => [ + 'raw' => 1852, + 'default' => '1.852 €' + ], + 'land_register_entry' => [ + 'raw' => 617, + 'default' => '617 €' + ], + 'total_costs' => [ + 'raw' => 152468.56, + 'default' => '152.468,56 €' + ] + ]; + $estateDataRaw = [ 52 => [ 'id' => 52, @@ -115,6 +143,7 @@ public function prepare() ->will($this->returnCallback(function(string $field): string { return 'label-'.$field; })); + $this->_pEstate->method('getTotalCostsData')->willReturn($totalCostsData); $this->_pEstate->method('getRawValues') ->will($this->onConsecutiveCalls($pArrayContainerEstateDetailRaw, false)); diff --git a/tests/resources/ApiResponseGetFieldsCurrency.json b/tests/resources/ApiResponseGetFieldsCurrency.json new file mode 100644 index 000000000..18e6a9fa9 --- /dev/null +++ b/tests/resources/ApiResponseGetFieldsCurrency.json @@ -0,0 +1,47 @@ +{ + "parameters": { + "labels": true, + "fieldList": ["waehrung"], + "language": "DEU", + "modules": ["estate"] + }, + "response": { + "actionid": "urn:onoffice-de-ns:smart:2.5:smartml:action:get", + "resourceid": "", + "resourcetype": "fields", + "cacheable": true, + "identifier": "", + "data": { + "meta": { + "cntabsolute": null + }, + "records": [ + { + "id": "estate", + "type": "", + "elements": { + "label": "Adressen", + "waehrung": { + "type": "multiselect", + "length": null, + "permittedvalues": { + "EUR": "€", + "CZK": "Kč", + "TRY": "₺" + }, + "default": null, + "filters": [], + "dependencies": [], + "compoundFields": [], + "label": "Währung" + } + } + } + ] + }, + "status": { + "errorcode": 0, + "message": "OK" + } + } +} diff --git a/tests/resources/ApiResponseReadEstatesCostsCalculatorRaw.json b/tests/resources/ApiResponseReadEstatesCostsCalculatorRaw.json new file mode 100644 index 000000000..2fff2a404 --- /dev/null +++ b/tests/resources/ApiResponseReadEstatesCostsCalculatorRaw.json @@ -0,0 +1,101 @@ +{ + "parameters": { + "data": [ + "referenz", + "reserviert", + "verkauft", + "objekttitel", + "objektbeschreibung", + "exclusive", + "neu", + "top_angebot", + "preisreduktion", + "courtage_frei", + "objekt_des_tages", + "vermarktungsart", + "preisAufAnfrage", + "kaufpreis", + "aussen_courtage", + "bundesland", + "waehrung" + ], + "filter": { + "veroeffentlichen": [ + { + "op": "=", + "val": 1 + } + ], + "referenz": [ + { + "op": "=", + "val": 0 + } + ] + }, + "estatelanguage": "ENG", + "outputlanguage": "ENG", + "listlimit": 5, + "formatoutput": false, + "addMainLangId": true, + "listoffset": 0, + "sortby": "Id", + "sortorder": "ASC", + "filterid": 12 + }, + "response": { + "actionid": "urn:onoffice-de-ns:smart:2.5:smartml:action:read", + "resourceid": "", + "resourcetype": "estate", + "cacheable": true, + "identifier": "", + "data": { + "meta": { + "cntabsolute": 9 + }, + "records": [ + { + "id": 15, + "type": "estate", + "elements": { + "reserviert": "0", + "verkauft": "1", + "vermarktungsart": "kauf", + "referenz": "0", + "exclusive": "0", + "neu": "0", + "top_angebot": "0", + "preisreduktion": "0", + "courtage_frei": "0", + "objekt_des_tages": "0", + "virtualStreet": "", + "virtualHouseNumber": "", + "laengengrad": "0.00000", + "breitengrad": "0.00000", + "virtualLatitude": "0.00000", + "virtualLongitude": "0.00000", + "strasse": "", + "showGoogleMap": "1", + "hausnummer": "", + "objekttitel": "Name id 15", + "objektbeschreibung": "description test", + "ort": "", + "objektnr_extern": "DJ636", + "plz": "", + "land": "DEU", + "preisAufAnfrage": "0", + "mainLangId": null, + "kaufpreis": "123456.56", + "aussen_courtage": "aussen_courtage18%", + "bundesland": "Bayern", + "waehrung": "EUR" + } + } + ] + }, + "status": { + "errorcode": 0, + "message": "OK" + } + } +} diff --git a/tests/resources/templates/output_default_detail.html b/tests/resources/templates/output_default_detail.html index 7551eb7af..8e083822d 100644 --- a/tests/resources/templates/output_default_detail.html +++ b/tests/resources/templates/output_default_detail.html @@ -3,6 +3,8 @@ padding: 0 10px; } + +

flach begrüntes Grundstück

@@ -66,6 +68,66 @@

label-ausstatt_beschr

label-sonstige_angaben

Vereinbaren sie noch heute einen Besichtigungstermin
+
+

Total costs

+
+
+ + +123.456,56 € +4.321 € +22.222 € +1.852 € +617 € + +
+
+

Overview of costs

+
+ +
+
label-kaufpreis
+
123.456,56 €
+
+
+
+ +
+
Property transfer tax
+
4.321 €
+
+
+
+ +
+
Broker commission
+
22.222 €
+
+
+
+ +
+
Notary Fees
+
1.852 €
+
+
+
+ +
+
Land Register Entry
+
617 €
+
+
+
+
+
Total costs
+
152.468,56 €
+
+
+
+
+
+
Estate Units here
@@ -91,6 +153,7 @@

Documents

test ogulo link
+
Similar Estates here
@@ -103,4 +166,4 @@

Documents

text-decoration: none; color: #000; } - \ No newline at end of file +