]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
[ERP-272] план продаж и списания
authorAlexander Smirnov <fredeom@mail.ru>
Fri, 27 Dec 2024 06:14:43 +0000 (09:14 +0300)
committerAlexander Smirnov <fredeom@mail.ru>
Fri, 27 Dec 2024 06:14:43 +0000 (09:14 +0300)
erp24/controllers/SalesWriteOffsPlanController.php [new file with mode: 0644]
erp24/migrations/m241225_131333_create_table_sales_write_offs_plan.php [new file with mode: 0755]
erp24/records/SalesWriteOffsPlan.php [new file with mode: 0644]
erp24/views/sales-write-offs-plan/index.php [new file with mode: 0644]
erp24/web/js/sales-write-offs-plan/index.js [new file with mode: 0644]

diff --git a/erp24/controllers/SalesWriteOffsPlanController.php b/erp24/controllers/SalesWriteOffsPlanController.php
new file mode 100644 (file)
index 0000000..0afdd71
--- /dev/null
@@ -0,0 +1,78 @@
+<?php
+
+namespace app\controllers;
+
+use Yii;
+use yii\base\DynamicModel;
+use yii\helpers\Json;
+use yii\web\Controller;
+use yii_app\records\CityStore;
+use yii_app\records\Sales;
+use yii_app\records\SalesWriteOffsPlan;
+
+class SalesWriteOffsPlanController extends Controller
+{
+    public function actionIndex() {
+        $model = DynamicModel::validateData([
+            'year' => date('Y'),
+            'month' => date('m'),
+        ], [
+            [['year', 'month'], 'safe']
+        ]);
+
+        $model->load(Yii::$app->request->get());
+
+        $prevMonthStart = date("Y-m-01", strtotime("-1 month", strtotime($model->year . "-" . $model->month . "-01")));
+        $prevMonthEnd = date("Y-m-t", strtotime("-1 month", strtotime($model->year . "-" . $model->month . "-01")));
+
+        $years = [];
+        for ($i = 3; $i >= 0; $i--) {
+            $year = date("Y") - $i;
+            $years [$year] = $year;
+        }
+
+        $stores = CityStore::find()->andWhere(['visible' => '1'])->all();
+        $salesWriteOffsPlan = SalesWriteOffsPlan::find()->where(['year' => $model->year, 'month' => $model->month])
+            ->indexBy('store_id')->asArray()->all();
+
+        $sales = Sales::find()->select([
+            "sum(CASE WHEN operation='Продажа' THEN summ ELSE (CASE WHEN operation='Возврат' THEN -summ ELSE 0 END) END) as total",
+            'store_id'])
+            ->where(['between', 'date', $prevMonthStart, $prevMonthEnd])
+            ->groupBy(['store_id'])
+            ->indexBy('store_id')
+            ->asArray()->all();
+
+        return $this->render('index', compact('model', 'years', 'stores',
+            'salesWriteOffsPlan', 'sales'));
+    }
+
+    public function actionSaveFields() {
+        $year = Yii::$app->request->post('year');
+        $month = Yii::$app->request->post('month');
+        $storeId = Yii::$app->request->post('store_id');
+        $total_sales_plan = Yii::$app->request->post('total_sales_plan');
+//        $total_sales_fact = Yii::$app->request->post('total_sales_fact');
+        $write_offs_plan = Yii::$app->request->post('write_offs_plan');
+        $offline_sales_plan = Yii::$app->request->post('offline_sales_plan');
+        $online_sales_shop_plan = Yii::$app->request->post('online_sales_shop_plan');
+        $online_sales_marketplace_plan = Yii::$app->request->post('online_sales_marketplace_plan');
+        $salesWriteOffsPlan = SalesWriteOffsPlan::find()->where(['year' => $year, 'month' => $month, 'store_id' => $storeId])->one();
+        if (!$salesWriteOffsPlan) {
+            $salesWriteOffsPlan = new SalesWriteOffsPlan;
+            $salesWriteOffsPlan->year = $year;
+            $salesWriteOffsPlan->month = $month;
+            $salesWriteOffsPlan->store_id = $storeId;
+        }
+        $salesWriteOffsPlan->total_sales_plan = $total_sales_plan;
+        $salesWriteOffsPlan->write_offs_plan = $write_offs_plan;
+        $salesWriteOffsPlan->offline_sales_plan = $offline_sales_plan;
+        $salesWriteOffsPlan->online_sales_shop_plan = $online_sales_shop_plan;
+        $salesWriteOffsPlan->online_sales_marketplace_plan = $online_sales_marketplace_plan;
+        $salesWriteOffsPlan->save();
+        if ($salesWriteOffsPlan->getErrors()) {
+            throw new \Exception(Json::encode($salesWriteOffsPlan->getErrors()));
+        }
+        return 'ok';
+    }
+}
diff --git a/erp24/migrations/m241225_131333_create_table_sales_write_offs_plan.php b/erp24/migrations/m241225_131333_create_table_sales_write_offs_plan.php
new file mode 100755 (executable)
index 0000000..6d05f5f
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+
+use yii\db\Migration;
+
+/**
+ * Class m241225_131333_create_table_sales_write_offs_plan
+ */
+class m241225_131333_create_table_sales_write_offs_plan extends Migration
+{
+    const TABLE_NAME = 'erp24.sales_write_offs_plan';
+    /**
+     * {@inheritdoc}
+     */
+    public function safeUp()
+    {
+        $this->createTable(self::TABLE_NAME, [
+            'id' => $this->primaryKey(),
+            'year' => $this->integer()->notNull()->comment('Год создания отчёта'),
+            'month' => $this->integer()->notNull()->comment('Месяц создания отчёта'),
+            'store_id' => $this->integer()->notNull()->comment('id магазина в ERP'),
+            'total_sales_plan' => $this->float()->null()->comment('План продаж'),
+            'write_offs_plan' => $this->float()->null()->comment('План списания'),
+            'offline_sales_plan' => $this->float()->null()->comment('План продаж офлайн'),
+            'online_sales_shop_plan' => $this->float()->null()->comment('План online продаж магазина'),
+            'online_sales_marketplace_plan' => $this->float()->null()->comment('План online продаж маркетплейса'),
+        ]);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function safeDown()
+    {
+        $this->dropTable(self::TABLE_NAME);
+    }
+}
diff --git a/erp24/records/SalesWriteOffsPlan.php b/erp24/records/SalesWriteOffsPlan.php
new file mode 100644 (file)
index 0000000..a48c94d
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+
+namespace yii_app\records;
+
+use Yii;
+
+/**
+ * This is the model class for table "sales_write_offs_plan".
+ *
+ * @property int $id
+ * @property int $year Год создания отчёта
+ * @property int $month Месяц создания отчёта
+ * @property int $store_id id магазина в ERP
+ * @property float|null $total_sales_plan План продаж
+ * @property float|null $write_offs_plan План списания
+ * @property float|null $offline_sales_plan План продаж офлайн
+ * @property float|null $online_sales_shop_plan План online продаж магазина
+ * @property float|null $online_sales_marketplace_plan План online продаж маркетплейса
+ */
+class SalesWriteOffsPlan extends \yii\db\ActiveRecord
+{
+    /**
+     * {@inheritdoc}
+     */
+    public static function tableName()
+    {
+        return 'sales_write_offs_plan';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rules()
+    {
+        return [
+            [['year', 'month', 'store_id'], 'required'],
+            [['year', 'month', 'store_id'], 'default', 'value' => null],
+            [['year', 'month', 'store_id'], 'integer'],
+            [['total_sales_plan', 'write_offs_plan', 'offline_sales_plan', 'online_sales_shop_plan', 'online_sales_marketplace_plan'], 'number'],
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function attributeLabels()
+    {
+        return [
+            'id' => 'ID',
+            'year' => 'Year',
+            'month' => 'Month',
+            'store_id' => 'Store ID',
+            'total_sales_plan' => 'Total Sales Plan',
+            'write_offs_plan' => 'Write Offs Plan',
+            'offline_sales_plan' => 'Offline Sales Plan',
+            'online_sales_shop_plan' => 'Online Sales Shop Plan',
+            'online_sales_marketplace_plan' => 'Online Sales Marketplace Plan',
+        ];
+    }
+}
diff --git a/erp24/views/sales-write-offs-plan/index.php b/erp24/views/sales-write-offs-plan/index.php
new file mode 100644 (file)
index 0000000..76cca00
--- /dev/null
@@ -0,0 +1,118 @@
+<?php
+
+use yii\helpers\Html;
+use yii\base\DynamicModel;
+use yii\widgets\ActiveForm;
+
+use dosamigos\datetimepicker\DateTimePicker;
+
+use yii_app\helpers\HtmlHelper;
+use yii_app\records\SalesWriteOffsPlan;
+
+/* @var $model DynamicModel */
+/* @var $years array */
+/* @var $stores array */
+/* @var $salesWriteOffsPlan SalesWriteOffsPlan[] */
+/* @var $sales array */
+
+$this->registerJsFile('/js/sales-write-offs-plan/index.js', ['position' => \yii\web\View::POS_END]);
+
+function colorScheme1($p) {
+    $p = round(100 * $p);
+    if ($p <= 90) { return 'red'; }
+    if ($p < 99) { return '#aaaa00'; }
+    return 'green';
+}
+
+function colorScheme2($p) {
+    $p = round(100 * $p, 1);
+    if ($p > 10) { return 'red'; }
+    if ($p > 9) { return '#aaaa00'; }
+    return 'green';
+}
+
+?>
+
+<div class="salesWriteOffsPlanIndex m-5">
+
+    <?php $form = ActiveForm::begin([
+        'method' => 'GET',
+        'action' => '/sales-write-offs-plan'
+    ]) ?>
+
+    <div class="row">
+        <div class="col-1">
+            <?= $form->field($model, 'year')->dropDownList($years, ['onchange' => 'this.form.submit();'])->label(false) ?>
+        </div>
+    </div>
+    <div class="row">
+        <div class="col-1">
+            <?= $form->field($model, 'month')->dropDownList(HtmlHelper::getMonthNames(), ['onchange' => 'this.form.submit();'])->label(false) ?>
+        </div>
+    </div>
+
+    <?php ActiveForm::end() ?>
+
+    <table id="storePlans">
+        <thead>
+            <tr><th rowspan="3" class="text-center align-middle border">Магазины</th><th colspan="4" class="text-center border">Total</th><th colspan="2" class="text-center border">Оффлайн торговля</th><th colspan="4" class="text-center border">Онлайн торговля</th></tr>
+            <tr><th colspan="4" class="text-center border">Оффлайн + онлайн торговля</th><th colspan="2" class="text-center border">Магазин</th><th colspan="2" class="text-center border">Интернет-магазин</th><th colspan="2" class="text-center border">Маркетплейсы</th></tr>
+            <tr><th class="text-center border">Продажи</th><th class="text-center border">К прошлому месяцу</th><th class="text-center border">Списания</th><th class="text-center border">К Плану</th><th class="text-center border">Цель</th><th class="text-center border">К прошлому месяцу</th><th class="text-center border">Цель</th><th class="text-center border">К прошлому месяцу</th><th class="text-center border">Цель</th><th class="text-center border">К прошлому месяцу</th></tr>
+        </thead>
+        <tbody>
+            <?php foreach ($stores as $store): ?>
+                <?php
+                    $total_sales_fact = $sales[$store->id]['total'] ?? 0;
+                    $total_sales_plan = $salesWriteOffsPlan[$store->id]['total_sales_plan'] ?? 0;
+                    $p1 = $total_sales_fact > 0 ? $total_sales_plan / $total_sales_fact : 0;
+
+                    $write_offs_plan = $salesWriteOffsPlan[$store->id]['write_offs_plan'] ?? 0;
+                    $p2 = $total_sales_plan > 0 ? $write_offs_plan / $total_sales_plan : 0;
+
+                    $offline_sales_plan = $salesWriteOffsPlan[$store->id]['offline_sales_plan'] ?? 0;
+                    $p3 = $total_sales_fact > 0 ? $offline_sales_plan / $total_sales_fact : 0;
+
+                    $online_sales_shop_plan = $salesWriteOffsPlan[$store->id]['online_sales_shop_plan'] ?? 0;
+                    $p4 = $total_sales_fact > 0 ? $online_sales_shop_plan / $total_sales_fact : 0;
+
+                    $online_sales_marketplace_plan = $salesWriteOffsPlan[$store->id]['online_sales_marketplace_plan'] ?? 0;
+                    $p5 = $total_sales_fact > 0 ? $online_sales_marketplace_plan / $total_sales_fact : 0;
+                ?>
+                <tr>
+                    <td><?= $store->name ?><?= Html::hiddenInput('store_id', $store->id) ?></td>
+                    <td><?= Html::textInput('total_sales_plan', $total_sales_plan, ['type' => 'number', 'readonly' => true]) ?></td>
+                    <td>
+                        <?= Html::hiddenInput('total_sales_fact', $total_sales_fact) ?>
+                        <span style="color:<?= colorScheme1($p1) ?>">
+                            <?= number_format(round($p1 * 100), 0, '.', ' ') ?>
+                        </span>%
+                    </td>
+                    <td><?= Html::textInput('write_offs_plan', $write_offs_plan, ['type' => 'number', 'onchange' => 'editField(this);']) ?></td>
+                    <td>
+                        <span style="color:<?= colorScheme2($p2) ?>">
+                            <?= number_format(round($p2 * 100), 1, '.', ' ') ?>
+                        </span>%
+                    </td>
+                    <td><?= Html::textInput('offline_sales_plan', $offline_sales_plan, ['type' => 'number', 'onchange' => 'editField(this);']) ?></td>
+                    <td>
+                        <span style="color:<?= colorScheme1($p3) ?>">
+                            <?= number_format(round($p3 * 100), 0, '.', ' ') ?>
+                        </span>%
+                    </td>
+                    <td><?= Html::textInput('online_sales_shop_plan', $online_sales_shop_plan, ['type' => 'number', 'onchange' => 'editField(this);']) ?></td>
+                    <td>
+                        <span style="color:<?= colorScheme1($p4) ?>">
+                            <?= number_format(round($p4 * 100), 0, '.', ' ') ?>
+                        </span>%
+                    </td>
+                    <td><?= Html::textInput('online_sales_marketplace_plan', $online_sales_marketplace_plan, ['type' => 'number', 'onchange' => 'editField(this);']) ?></td>
+                    <td>
+                        <span style="color:<?= colorScheme1($p5) ?>">
+                            <?= number_format(round($p5 * 100), 0, '.', ' ') ?>
+                        </span>%
+                    </td>
+                </tr>
+            <?php endforeach; ?>
+        </tbody>
+    </table>
+</div>
diff --git a/erp24/web/js/sales-write-offs-plan/index.js b/erp24/web/js/sales-write-offs-plan/index.js
new file mode 100644 (file)
index 0000000..3296989
--- /dev/null
@@ -0,0 +1,68 @@
+/* jshint esversion: 6 */
+
+const param25 = $('meta[name=csrf-param]').attr('content');
+const token25 = $('meta[name=csrf-token]').attr('content');
+
+function isNumeric(value) {
+    return /^-?\d+\.?\d*$/.test(value);
+}
+
+function editField(zis) {
+    const tr = zis.parentNode.parentNode;
+    const store_id = tr.querySelector("[name=store_id]").value;
+    const total_sales_plan = tr.querySelector("[name=total_sales_plan]");
+    const total_sales_fact = tr.querySelector("[name=total_sales_fact]");
+    const write_offs_plan = tr.querySelector("[name=write_offs_plan]");
+    const offline_sales_plan = tr.querySelector("[name=offline_sales_plan]");
+    const online_sales_shop_plan = tr.querySelector("[name=online_sales_shop_plan]");
+    const online_sales_marketplace_plan = tr.querySelector("[name=online_sales_marketplace_plan]");
+    const editFields = [
+        total_sales_plan,
+        total_sales_fact,
+        write_offs_plan,
+        offline_sales_plan,
+        online_sales_shop_plan,
+        online_sales_marketplace_plan,
+    ];
+    let succ = true;
+    editFields.forEach((el) => {
+       if (!isNumeric(el.value)) {
+           succ = false;
+           alert(el.value);
+           console.log(el);
+       }
+    });
+    if (succ) {
+        total_sales_plan.value = +offline_sales_plan.value + (+online_sales_shop_plan.value) + (+online_sales_marketplace_plan.value);
+        $.ajax({
+            method: "POST",
+            url: '/sales-write-offs-plan/save-fields',
+            data: {
+                year: document.querySelector('#dynamicmodel-year').value,
+                month: document.querySelector('#dynamicmodel-month').value,
+                store_id,
+                total_sales_plan : total_sales_plan.value,
+                total_sales_fact : total_sales_fact.value,
+                write_offs_plan : write_offs_plan.value,
+                offline_sales_plan : offline_sales_plan.value,
+                online_sales_shop_plan : online_sales_shop_plan.value,
+                online_sales_marketplace_plan : online_sales_marketplace_plan.value,
+                [param25]: token25
+            },
+            dataType: "text",
+            success: function(data) {
+                console.log(data);
+            },
+        });
+    }
+}
+
+$(document).ready(() => {
+    $('#storePlans').DataTable({
+        sorting: false,
+        info: false,
+        paging: false,
+        searching: true,
+        language: data_table_language
+    });
+});