query($sql); $totalPrices = []; $multiProdTotals = []; while($row = $database->fetchArray($query)){ if(!isset($totalPrices[$row['idPackage']])){ $totalPrices[$row['idPackage']] = 0; } $tempTotal = $this->calculateTotalPrice($row['isPriceRecurring'], $row['unitCostPrice'], $row['payPeriod'], $row['quantity']); if(intval($row['hasMultipleRealProducts']) === 1){ if(!isset($multiProdTotals[$row['idPackage']][$row['idCategory']])){ $multiProdTotals[$row['idPackage']][$row['idCategory']] = 0; } $multiProdTotals[$row['idPackage']][$row['idCategory']] = $this->getMultiProductsTotal($multiProdTotals[$row['idPackage']][$row['idCategory']], $tempTotal); }else{ $totalPrices[$row['idPackage']] += $tempTotal; } } foreach ($multiProdTotals as $idPackage => $categories) { foreach($categories as $categoryTotal){ $totalPrices[$idPackage] += $categoryTotal; } } $sql = "SELECT plb.idPackage, plb.payMargin FROM ".TABLES['broker_commission_split']." plb INNER JOIN ( SELECT DISTINCT idPackage FROM ".TABLES['rel_package_products']." WHERE $whereSql ) affectedPackages ON plb.idPackage=affectedPackages.idPackage WHERE plb.payMargin!=0"; $query = $database->query($sql); $invalidPackages = []; $validPackages = []; while($row = $database->fetchArray($query)){ if($row['payMargin'] < $totalPrices[$row['idPackage']]){ $invalidPackages[] = $row['idPackage']; }else{ $validPackages[] = $row['idPackage']; } } if(count($invalidPackages) > 0){ $sqlUpd = "UPDATE ".TABLES['packages']." SET status='high-cost' WHERE id IN(".implode(',', $invalidPackages).") AND status!='not-available'"; $query = $database->query($sqlUpd); return $database->affectedRows(); } if(count($validPackages) > 0){ $sqlUpd = "UPDATE ".TABLES['packages']." SET status='available' WHERE id IN(".implode(',', $validPackages).") AND status='high-cost'"; $query = $database->query($sqlUpd); return $database->affectedRows(); } return 0; } public function getPackageProductPrices($idPackage, $sumBy = 'category'){ global $database; $sql = "SELECT scp.unitCostPrice AS unitCostPrice, scp.unitVatCost AS unitVatCost, scp.isPriceRecurring AS isPriceRecurring, scp.payPeriod AS payPeriod, pc.category AS productCategory, pt.type AS productType, rpp.quantity AS quantity FROM ".TABLES['suppliers_countries_products']." scp INNER JOIN ".TABLES['product_categories']." pc ON pc.id=scp.idProductCategory INNER JOIN ".TABLES['product_types']." pt ON pt.id = pc.idType INNER JOIN ".TABLES['rel_package_products']." rpp ON rpp.idProduct=scp.idProduct INNER JOIN (SELECT rpp_last.idPackage, MAX(rpp_last.packageInstance) AS maxInstance FROM ".TABLES['rel_package_products']." rpp_last GROUP BY rpp_last.idPackage) last_instance ON last_instance.idPackage = rpp.idPackage AND last_instance.maxInstance = rpp.packageInstance WHERE rpp.idPackage=$idPackage"; $query = $database->query($sql); $data['productsPrices'] = []; while($row = $database->fetchArray($query)) { $productKey = $sumBy === 'type' ? $row['productType'] : $row['productCategory']; if(!isset($data['productsPrices'][$productKey])){ $data['productsPrices'][$productKey]['totalUnitCost'] = 0; $data['productsPrices'][$productKey]['totalVatCost'] = 0; } unset($totalCategoryUnitCost); unset($totalCategoryVatCost); $totalCategoryUnitCost =& $data['productsPrices'][$productKey]['totalUnitCost']; $totalCategoryVatCost =& $data['productsPrices'][$productKey]['totalVatCost']; if($sumBy === 'type' && $row['productType'] === 'service') { $totalUnitCost = $this->calculateTotalPrice(0, $row['unitCostPrice'], $row['payPeriod'], $row['quantity']); $totalVatCost = $this->calculateTotalPrice(0, $row['unitVatCost'], $row['payPeriod'], $row['quantity']); if($row['isPriceRecurring'] == 1) { if(!isset($data['productsPrices'][$row['productType']]['recurringPrice'])) { $data['productsPrices'][$row['productType']]['recurringPrice'] = 0; $data['productsPrices'][$row['productType']]['recurringVatPrice'] = 0; } $data['productsPrices'][$row['productType']]['recurringPrice'] += $totalUnitCost; $data['productsPrices'][$row['productType']]['recurringVatPrice'] += $totalVatCost; } else { if(!isset($data['productsPrices'][$row['productType']]['recurringPrice'])) { $data['productsPrices'][$row['productType']]['fixedPrice'] = 0; $data['productsPrices'][$row['productType']]['fixedVatPrice'] = 0; } $data['productsPrices'][$row['productType']]['fixedPrice'] += $totalUnitCost; $data['productsPrices'][$row['productType']]['fixedVatPrice'] += $totalVatCost; } } else { $totalUnitCost = $this->calculateTotalPrice($row['isPriceRecurring'], $row['unitCostPrice'], $row['payPeriod'], $row['quantity']); $totalVatCost = $this->calculateTotalPrice($row['isPriceRecurring'], $row['unitVatCost'], $row['payPeriod'], $row['quantity']); } if($row['productType'] === 'installation'){ $totalCategoryUnitCost = $this->getMultiProductsTotal($totalCategoryUnitCost, $totalUnitCost); $totalCategoryVatCost = $this->getMultiProductsTotal($totalCategoryVatCost, $totalVatCost); } else { $totalCategoryUnitCost += $totalUnitCost; $totalCategoryVatCost += $totalVatCost; } } return $data['productsPrices']; } public function getCommissionSplit($idPackage){ global $database; $sql = "SELECT bcs.brokerSplit as broker, bcs.commercialLeadSplit as commercialLead, bcs.payMargin FROM ".TABLES['broker_commission_split']." bcs WHERE idPackage=$idPackage"; $commissionSplit = $database->fetchResultArray($sql); return !empty($commissionSplit) ? $commissionSplit[0] : []; } /** * get product prices list and comissions * @param INT $idPackage id of the package * @param BOOLEAN $getOnlySelectedTpes if true will return just prices set by the broker * @param String $sumBy type to sum by 'category' or 'type' * @param Float $interestRate interest rate to calculate the prices with * @return json list of prices and list of comission */ public function getPriceTypes($idPackage, $getOnlySelectedTpes = false, $sumBy = 'category', $interestRate = -1){ global $database; $joinType = $getOnlySelectedTpes ? "INNER JOIN" : "LEFT OUTER JOIN"; $data = []; if($interestRate !== -1) { $interestRateValue = $interestRate; } else { $interestRate = new InterestRate(); $interestRateValue = $interestRate->getInterestRate()['interestRate']; } $interestRatePercentage = $interestRateValue ? floatval($interestRateValue) / 100 : 0; $data['interestRate'] = $interestRateValue; $data['priceTypes'] = []; $sql = "SELECT pt.id as idPayType, pt.payType, pt.packagePayPeriod, pt.servicesContractPeriod, pt.maxContractPeriod, pt.periodUnit, plb.minimalFixedPrice, plb.principalAmount, plb.minimalServicesPrice FROM ".TABLES['payment_types']." pt $joinType (SELECT idPaymentType, minimalFixedPrice, principalAmount, minimalServicesPrice FROM ".TABLES['price_list_broker']." WHERE idPackage=$idPackage) plb ON pt.id=plb.idPaymentType"; $query = $database->query($sql); while($row = $database->fetchArray($query)){ $row['minimalRecurentPrice'] = $row['packagePayPeriod'] > 0 ? $this->PMT($interestRatePercentage, $row['packagePayPeriod'], $row['principalAmount']) : 0; $row['minimalRecurentPrice'] = number_format($row['minimalRecurentPrice'], 2, '.', ''); $data['priceTypes'][] = $row; } $data['productsPrices'] = $this->getPackageProductPrices($idPackage, $sumBy); $data['commissionSplit'] = $this->getCommissionSplit($idPackage); return $data; } public function getCommercialLeadPrices($idPackage = 0, $idUserCommercialLead = 0){ global $database, $user; $idPackage = $database->escapeValue($idPackage); $whereSql = ""; $clPrices = []; if($idPackage){ $whereSql .= " AND plcl.idPackage=$idPackage"; } if($idUserCommercialLead){ $whereSql .= " AND cl.idUser=$idUserCommercialLead"; } $sql = "SELECT plcl.idPackage, plcl.idPaymentType, pt.payType, IFNULL(plcl.idCustomer, 0) as idCustomer, plcl.fixedExtra , plcl.recurentExtra, plcl.servicesExtra, plcl.visibleToCustomer, pt.packagePayPeriod FROM ".TABLES['price_list_commercial_lead']." plcl INNER JOIN ".TABLES['payment_types']." pt ON pt.id = plcl.idPaymentType INNER JOIN ".TABLES['commercial_leads']." cl ON cl.id=plcl.idCommercialLead WHERE 1=1 $whereSql"; $query = $database->query($sql); while($row = $database->fetchArray($query)){ $clPrices[$row['idPackage']][$row['idCustomer']][$row['idPaymentType']] = $row; } return $clPrices; } /** * validate values to insert/update broker prices * @param INT $idPackage id of package * @param Object $price object containtg the info for prices * @return Array error message array or empty array if no error */ public function validateBrokerPrices($idPackage, $price){ global $database; $data =[]; $max_value = pow(10, 13); if(filter_var($idPackage, FILTER_VALIDATE_INT) === false || $idPackage == 0){ $data['messages'][] = [ 'code' => 'error', 'message' => 'INVALID_PACKAGE_ID' ]; return $data; } if(filter_var($price->idPayType, FILTER_VALIDATE_INT) === false || $price->idPayType == 0){ $data['messages'][] = [ 'code' => 'error', 'message' => 'INVALID_PAY_TYPE_ID' ]; return $data; } $checkMessage = $database->invalidNumber('minimalFixedPrice', $price->minimalFixedPrice, 0, $max_value); if($checkMessage){ $data['messages'][] = $checkMessage; } $checkMessage = $database->invalidNumber('principalAmount', $price->principalAmount, 0, $max_value); if($checkMessage){ $data['messages'][] = $checkMessage; } $checkMessage = $database->invalidNumber('minimalServicesPrice', $price->minimalServicesPrice, 0, $max_value); if($checkMessage){ $data['messages'][] = $checkMessage; } return $data; } /** * validate values for commission split * @param Obect $commissionSplit info for commissison split * @return Array error message array or empty array if no error */ public function validateComissionSplit($commissionSplit){ global $database; $data =[]; $max_value = 100; $checkMessage = $database->invalidNumber('brokerSplit', $commissionSplit->broker, 0, $max_value); if($checkMessage){ $data['messages'][] = $checkMessage; } $checkMessage = $database->invalidNumber('commercialLeadSplit', $commissionSplit->commercialLead, 0, $max_value); if($checkMessage){ $data['messages'][] = $checkMessage; } return $data; } /** * update values for broker prices and comissions * @param INT $idPackage id for package * @param Objects Array $prices Array with prices to be inserted/updated * @param Objects Array $commissionSplit Array with commission to be inserted/updated * @return Array Update message */ public function updateBrokerPricesAndCommission($idPackage, $prices, $commissionSplit){ global $database; $idPackage = $database->escapeValue($idPackage); $prices = json_decode($prices); $commissionSplit = json_decode($commissionSplit); $valuesSql = ""; $idsToNotDelete = ""; $data = []; if(empty($prices)){ $data['messages'][] = [ 'code' => 'error', 'message' => 'ONE_PRICE_REQUIRED' ]; return $data; } foreach ($prices as $price) { $price->idPayType = $database->escapeValue($price->idPayType); $price->minimalFixedPrice = $price->minimalFixedPrice ? $database->escapeValue($price->minimalFixedPrice) : 0; $price->principalAmount = $price->principalAmount ? $database->escapeValue($price->principalAmount) : 0; $price->minimalServicesPrice =$price->minimalServicesPrice ? $database->escapeValue($price->minimalServicesPrice) : 0; $data = array_merge($data, $this->validateBrokerPrices($idPackage, $price)); $valuesSql .= "(".$idPackage.", ".$database->escapeValue($price->idPayType).", ".$price->minimalFixedPrice.", ".$price->principalAmount.", ".$price->minimalServicesPrice." ),"; $idsToNotDelete .= $price->idPayType.","; } if(!empty($data)){ return $data; } if($valuesSql !== ""){ $valuesSql = rtrim($valuesSql, ','); $idsToNotDelete = rtrim($idsToNotDelete, ','); $sql = "INSERT INTO ".TABLES['price_list_broker']." (idPackage, idPaymentType, minimalFixedPrice, principalAmount, minimalServicesPrice) VALUES $valuesSql ON DUPLICATE KEY UPDATE minimalFixedPrice=VALUES(minimalFixedPrice), principalAmount=VALUES(principalAmount), minimalServicesPrice=VALUES(minimalServicesPrice)"; $query = $database->query($sql); $updatedPrices = $database->affectedRows(); $sql = "DELETE FROM ".TABLES['price_list_broker']." WHERE idPackage=$idPackage AND idPaymentType NOT IN($idsToNotDelete)"; $query = $database->query($sql); $deletePrices = $database->affectedRows(); if($updatedPrices === 0 && $deletePrices === 0){ $data['messages'][] = [ 'code' => 'warning', 'message' => 'NO_CHANGES_PRICES' ]; }else{ $data['messages'][] = [ 'code' => 'success', 'message' => 'BROKER_PRICES_UPDATED' ]; } } $data = array_merge($data, $this->validateComissionSplit($commissionSplit)); $commissionSplit->broker = $commissionSplit->broker ? $database->escapeValue($commissionSplit->broker) : 0; $commissionSplit->commercialLead = $commissionSplit->commercialLead ? $database->escapeValue($commissionSplit->commercialLead) : 0; $commissionSplit->payMargin = $commissionSplit->payMargin ? $database->escapeValue($commissionSplit->payMargin) : 0; $sql = "INSERT INTO ".TABLES['broker_commission_split']." (idPackage, brokerSplit, commercialLeadSplit, payMargin) VALUES(".$database->escapeValue($idPackage).", ".$commissionSplit->broker.", ".$commissionSplit->commercialLead.", ".$commissionSplit->payMargin." ) ON DUPLICATE KEY UPDATE brokerSplit=VALUES(brokerSplit), commercialLeadSplit=VALUES(commercialLeadSplit), payMargin=VALUES(payMargin) "; $query = $database->query($sql); if($database->affectedRows() < 1){ $data['messages'][] = [ 'code' => 'warning', 'message' => 'NO_CHANGES_COMMISSION_SPLIT' ]; }else{ $data['messages'][] = [ 'code' => 'success', 'message' => 'COMMISSION_SPLIT_UPDATED' ]; } $priceChange = $this->checkPackagePriceMargin(0, $idPackage); if($priceChange > 0){ $data['messages'][] = [ 'code' => 'warning', 'message' => 'PACKAGES_HIGH_PRICE_CHANGED' ]; } return $data; } public function getPackagesAvailablePayTypes(){ global $database; $data = []; $sql = "SELECT plb.idPackage, plb.idPaymentType, pt.payType FROM ".TABLES['price_list_broker']." plb INNER JOIN ".TABLES['payment_types']." pt ON pt.id=plb.idPaymentType"; $query = $database->query($sql); while($row = $database->fetchArray($query)){ $data[$row['idPackage']][] = $row; } return $data; } public function getClDefaultPrices($idPackage){ global $database, $user; $data = []; $sql = "SELECT d.idPackage, d.idPaymentType, d.fixedExtra, d.recurentExtra, d.servicesExtra FROM ".TABLES['price_list_commercial_lead']." d INNER JOIN ".TABLES['commercial_leads']." cl ON cl.id=d.idCommercialLead WHERE idUser=".$user->getUserId()." AND idPackage=$idPackage AND d.idCustomer IS NULL"; $query = $database->query($sql); while($row = $database->fetchArray($query)){ $data[$row['idPaymentType']] = $row; } return $data; } public function calculatePackageTotalCost($productsPrices){ $total = 0; foreach ($productsPrices as $category) { $total += $category['totalUnitCost']; } return $total; } /** * Copy of Excel's PMT function. * Credit: http://thoughts-of-laszlo.blogspot.nl/2012/08/complete-formula-behind-excels-pmt.html * * @param double $interest The interest rate for the loan. * @param int $num_of_payments The total number of payments for the loan in months. * @param double $PV The present value, or the total amount that a series of future payments is worth now; * Also known as the principal. * @param double $FV The future value, or a cash balance you want to attain after the last payment is made. * If fv is omitted, it is assumed to be 0 (zero), that is, the future value of a loan is 0. * @param int $Type Optional, defaults to 0. The number 0 (zero) or 1 and indicates when payments are due. * 0 = At the end of period * 1 = At the beginning of the period * * @return float */ public function PMT($interest, $num_of_payments, $PV, $FV = 0.00, $Type = 0){ /*$interest = $interest / 12; $xp=pow((1+$interest),$num_of_payments); return ($PV* $interest*$xp/($xp-1)+$interest/($xp-1)*$FV)* ($Type==0 ? 1 : 1/($interest+1));*/ $rates = [ 24 => 4.282, 30 => 3.451, 36 => 2.896, 42 => 2.500, 48 => 2.223, 54 => 2.025, 60 => 1.834 ]; $interest = isset($rates[$num_of_payments]) ? $rates[$num_of_payments] : 10; return round($PV * ($interest / 100)); } private function calculateMargin($payType, $totalCost){ $totalGain = $payType['minimalFixedPrice'] + $payType['principalAmount']; return $totalGain - $totalCost; } public function getPriceMargins($commisisonPercent, $payType, $totalCost){ $margin = $this->calculateMargin($payType, $totalCost); return [ 'margin' => $margin, 'clMargin' => ($margin * $commisisonPercent['commercialLead'] / 100), 'brokerMargin' => ($margin * $commisisonPercent['broker'] / 100) ]; } /** * calculate fixed mortage based on principal amount * @param HashArray $payType pay type hash array (principalAmount and packagePayPeriod keys required) * @param Float $clMargin commercial lead margin * @param Float $interestRate interest rate percent value * @return Float fixed mortage formated to two decimals */ public function getRecurrentPriceMortage($payType, $clMargin, $interestRate){ $newPrincipalAmount = $payType['principalAmount'] - $clMargin; $interestRate = $interestRate / 100; $fixedMortage = $this->PMT($interestRate, $payType['packagePayPeriod'], $newPrincipalAmount); return number_format($fixedMortage, 2, '.', ''); } }