--- /dev/null
+<?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';
+ }
+}
--- /dev/null
+<?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);
+ }
+}
--- /dev/null
+<?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',
+ ];
+ }
+}
--- /dev/null
+<?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>
--- /dev/null
+/* 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
+ });
+});