From 66abc3137a905acad43b1ccce6c5344d53d5668d Mon Sep 17 00:00:00 2001 From: marina Date: Mon, 24 Feb 2025 13:32:13 +0300 Subject: [PATCH] =?utf8?q?ERP-302=20=D0=A0=D0=B5=D0=B4=D0=B0=D0=BA=D1=82?= =?utf8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=B1=D1=83?= =?utf8?q?=D0=BA=D0=B5=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- erp24/records/BouquetComposition.php | 4 +- erp24/records/BouquetCompositionProducts.php | 121 +++++++++++++++++++ erp24/views/bouquet/_product_list.php | 15 +-- 3 files changed, 131 insertions(+), 9 deletions(-) diff --git a/erp24/records/BouquetComposition.php b/erp24/records/BouquetComposition.php index 28e751f1..fceb1aa3 100644 --- a/erp24/records/BouquetComposition.php +++ b/erp24/records/BouquetComposition.php @@ -176,7 +176,7 @@ class BouquetComposition extends ActiveRecord BouquetCompositionMatrixTypeHistory::updateMatrixType( $this->id, - isset($data['matrix_type_id']) ? (int) $data['matrix_type_id'] : null + isset($data['matrix_type_id']) ? (int)$data['matrix_type_id'] : null ); $this->processFiles(); @@ -413,7 +413,7 @@ class BouquetComposition extends ActiveRecord } sort($prices); - $middle = (int) ($count / 2); + $middle = (int)($count / 2); $median = ($count % 2 === 0) ? ($prices[$middle - 1] + $prices[$middle]) / 2 : $prices[$middle]; diff --git a/erp24/records/BouquetCompositionProducts.php b/erp24/records/BouquetCompositionProducts.php index 14179d59..4156da48 100644 --- a/erp24/records/BouquetCompositionProducts.php +++ b/erp24/records/BouquetCompositionProducts.php @@ -176,4 +176,125 @@ class BouquetCompositionProducts extends ActiveRecord { return $this->hasOne(Products1c::class, ['id' => 'product_guid']); } + + public function getProfitability(): float + { + // Получаем среднюю себестоимость за последние 2 недели + $averageCost = SelfCostProduct::find() + ->where(['product_guid' => $this->product_guid]) + ->andWhere(['>=', 'date', date('Y-m-d', strtotime('-14 days'))]) + ->average('price'); + + if ($averageCost === null) { + return 0; + } + + // Получаем актуальные розничные цены (РЦ) за последние 2 недели + $retailPrices = PricesDynamic::find() + ->where(['product_id' => $this->product_guid]) + ->andWhere(['active' => 1]) // Только активные цены + ->andWhere(['<=', 'date_from', date('Y-m-d')]) // Цена начала действия + ->andWhere(['>', 'date_to', date('Y-m-d')]) // Цена еще актуальна + ->orderBy('date_from ASC') + ->asArray() + ->all(); + + if (empty($retailPrices)) { + return 0; + } + + $profitMargins = []; + + foreach ($retailPrices as $price) { + if ($price['price'] > 0) { + $grossProfit = $price['price'] - $averageCost; + $profitability = ($grossProfit / $price['price']) * 100; + $profitMargins[] = $profitability; + } + } + + if (empty($profitMargins)) { + return 0; + } + + // Сортируем массив рентабельности для вычисления медианы + sort($profitMargins); + $count = count($profitMargins); + + // Вычисляем медиану рентабельности + $medianProfitability = ($count % 2) ? + $profitMargins[floor($count / 2)] : + (($profitMargins[$count / 2 - 1] + $profitMargins[$count / 2]) / 2); + + return round($medianProfitability, 2); + } + + public function getBuildPercentage(): float + { + $totalAssemblies = BouquetCompositionProducts::find()->count(); + $assembliesWithProduct = BouquetCompositionProducts::find() + ->where(['product_guid' => $this->product_guid]) + ->count(); + + return $totalAssemblies ? round(($assembliesWithProduct / $totalAssemblies) * 100, 2) : 0; + } + + public function getAverageNumberOfPieces(): float + { + $totalCount = BouquetCompositionProducts::find() + ->where(['product_guid' => $this->product_guid]) + ->sum('count'); + + $assembliesWithProduct = BouquetCompositionProducts::find() + ->where(['product_guid' => $this->product_guid]) + ->count(); + + return $assembliesWithProduct ? round($totalCount / $assembliesWithProduct, 2) : 0; + } + + public function getWriteOffPercentage(): float + { + // Получаем GUID продуктов из состава букетов + $productGuids = BouquetCompositionProducts::find() + ->select('product_guid') + ->where(['product_guid' => $this->product_guid]) + ->column(); + + if (empty($productGuids)) { + return 0; + } + + // Находим ID номенклатуры по полученным GUID + $productIds = Products1cNomenclature::find() + ->select('id') + ->where(['id' => $productGuids]) // Исправлено: в номенклатуре ID = product_guid из букетов + ->column(); + + if (empty($productIds)) { + return 0; + } + + // Считаем сумму списаний за последний месяц по найденным товарам + $writeOffs = WriteOffs::find() + ->where(['>=', 'date', date('Y-m-01', strtotime('-1 month'))]) + ->andWhere(new Expression(" + EXISTS ( + SELECT 1 FROM jsonb_array_elements(items::jsonb) item + WHERE item->>'product_id' IN (:productIds) + ) + ", [':productIds' => implode("','", $productIds)])) + ->sum('summ'); + + + $sales = SalesProducts::find() + ->alias('sp') + ->innerJoin('sales s', 'CAST(s.id AS VARCHAR) = sp.check_id') // Приведение типов в другую сторону + ->where(['>=', 's.date', date('Y-m-01', strtotime('-1 month'))]) // Фильтр по дате + ->andWhere(['sp.product_id' => $productIds]) // Фильтр по продукту + ->sum('sp.summ'); + + return $sales ? round(($writeOffs / $sales) * 100, 2) : 0; + } + + } \ No newline at end of file diff --git a/erp24/views/bouquet/_product_list.php b/erp24/views/bouquet/_product_list.php index bc12b4c5..dd5c89d3 100644 --- a/erp24/views/bouquet/_product_list.php +++ b/erp24/views/bouquet/_product_list.php @@ -2,7 +2,7 @@
Название
Кол-во
% списания
-
мрж-ть
+
рен-ть
% в сборке
ср.шт. в сборке
@@ -12,15 +12,16 @@ use yii\helpers\Html; use yii\helpers\Url; use yii_app\records\BouquetComposition; + use yii_app\records\BouquetCompositionProducts; foreach ($bouquetCompositionProducts as $product) { ?>
-
product->name ?>
-
count ?>
-
10%
-
30%
-
10%
-
3.2%
+
product->name) ?>
+
count) ?>
+
getWriteOffPercentage()) ?>
+
getProfitability())?>
+
getBuildPercentage()) ?>%
+
getAverageNumberOfPieces()) ?>
-- 2.39.5