RUN apk add --no-cache zlib libpng icu \
&& apk add --no-cache --virtual .deps zlib-dev libpng-dev icu-dev \
- && docker-php-ext-install -j$(nproc) gd mysqli pdo pdo_mysql intl calendar opcache \
+ && docker-php-ext-install -j$(nproc) gd mysqli pdo pdo_mysql intl calendar mbstring opcache \
&& apk del .deps
RUN apk --no-cache --update --repository http://dl-cdn.alpinelinux.org/alpine/v$ALPINE_VERSION/main/ add \
#add xdebug
RUN apk add --no-cache $PHPIZE_DEPS \
- && pecl install xdebug \
+ && pecl install xdebug-3.1.6 \
&& docker-php-ext-enable xdebug \
&& apk del $PHPIZE_DEPS
WORKDIR /www
--- /dev/null
+<?php
+
+namespace yii_app\forms;
+
+use yii\base\Model;
+use yii\data\ActiveDataProvider;
+use yii_app\records\EmployeePayment;
+
+/**
+ * EmployeePaymentSearch represents the model behind the search form of `yii_app\records\EmployeePayment`.
+ */
+class EmployeePaymentSearch extends EmployeePayment
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function rules()
+ {
+ return [
+ [['id', 'admin_id', 'admin_group_id', 'creator_id'], 'integer'],
+ [['date'], 'safe'],
+ [['monthly_salary', 'daily_payment'], 'number'],
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function scenarios()
+ {
+ // bypass scenarios() implementation in the parent class
+ return Model::scenarios();
+ }
+
+ /**
+ * Creates data provider instance with search query applied
+ *
+ * @param array $params
+ *
+ * @return ActiveDataProvider
+ */
+ public function search($params)
+ {
+ $query = EmployeePayment::find();
+
+ // add conditions that should always apply here
+
+ $dataProvider = new ActiveDataProvider([
+ 'query' => $query,
+ ]);
+
+ $this->load($params);
+
+ if (!$this->validate()) {
+ // uncomment the following line if you do not want to return any records when validation fails
+ // $query->where('0=1');
+ return $dataProvider;
+ }
+
+ // grid filtering conditions
+ $query->andFilterWhere([
+ 'id' => $this->id,
+ 'admin_id' => $this->admin_id,
+ 'admin_group_id' => $this->admin_group_id,
+ 'date' => $this->date,
+ 'monthly_salary' => $this->monthly_salary,
+ 'daily_payment' => $this->daily_payment,
+ 'creator_id' => $this->creator_id,
+ ]);
+
+ return $dataProvider;
+ }
+}
--- /dev/null
+<?php
+
+namespace yii_app\forms;
+
+use yii\base\Model;
+use yii\web\UploadedFile;
+class MultipleUploadForm extends Model
+{
+ /**
+ * @var UploadedFile[] files uploaded
+ */
+ public $imageFiles;
+ /**
+ * @return array the validation rules.
+ */
+ public function rules()
+ {
+ return [
+ [['imageFiles'], 'file', 'extensions' => 'png, jpg', 'maxFiles' => 10, 'skipOnEmpty' => false],
+ ];
+ }
+
+ public function upload()
+ {
+ if ($this->validate()) {
+ foreach ($this->imageFiles as $file) {
+
+ $test = 33;
+// $file->saveAs('uploads/' . $file->baseName . '.' . $file->extension);
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+namespace yii_app\forms;
+
+use Yii;
+use yii\base\Model;
+use yii_app\records\Files;
+
+class UploadImageForm extends Model {
+ public $image;
+ public function rules() {
+ return [
+ [['image'], 'file', 'skipOnEmpty' => false, 'extensions' => 'jpg, png'],
+ ];
+ }
+ public function upload($entity, $entity_id) {
+ if ($this->validate()) {
+ $target_dir = 'uploads/' . Yii::$app->user->id . '/' . date("Y") . "/" . date("m") . "/" . date("d");
+ if (!is_dir($target_dir)) {
+ mkdir($target_dir, 0777, true);
+ }
+ $targetFile = $target_dir . "/" . rand(1000, 9999) . $this->image->baseName . '.' . $this->image->extension;
+ $this->image->saveAs($targetFile);
+
+ $fileRecord = new Files;
+ $fileRecord->created_at = date("Y-m-d H:i:s");
+ $fileRecord->entity_id = $entity_id;
+ $fileRecord->entity = $entity;
+ $fileRecord->file_type = 'image';
+ $fileRecord->url = '/' . $targetFile;
+ $fileRecord->save();
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+declare(strict_types = 1);
+
+namespace yii_app\forms\device;
+
+use yii\base\Model;
+use yii_app\records\AdminDesktop;
+use yii_app\records\CityStore;
+
+/**
+ * Форма добавления слотов
+ * @package yii_app\forms\timetable
+ */
+class AddDeviceForm extends Model
+{
+ public $storeId = null;
+ public $typeId = null;
+ public $name = null;
+
+ protected $device;
+
+ public function init()
+ {
+ $this->device = new AdminDesktop();
+ }
+ public function rules(): array
+ {
+ return [
+ [['storeId'], 'integer'],
+ [['name'], 'string'],
+ [['name'], 'default', 'value' => 'Рабочий ПК'],
+ [['storeId'], 'in', 'range' => array_keys(self::stores())],
+ [['typeId'], 'in', 'range' => array_keys(AdminDesktop::deviceTypes())],
+ ];
+ }
+
+ public function attributeLabels()
+ {
+ return [
+ 'storeId' => 'Магазин',
+ 'typeId' => 'Тип устройства',
+ 'name' => 'Название устройства',
+ ];
+ }
+ public static function stores() : array
+ {
+ return CityStore::find()
+ ->select(['name', 'id'])
+ ->indexBy('id')
+ ->cache(3600)
+ ->column();
+ }
+ public static function devices()
+ {
+ return AdminDesktop::deviceTypes();
+ }
+
+ public function getStoreName()
+ {
+ return self::stores()[$this->storeId] ?? 'вне магазинов';
+ }
+
+ public function save(): bool
+ {
+ if (!$this->validate()) {
+ return false;
+ }
+ $this->device->name = $this->name;
+ $this->device->store_id = $this->storeId;
+ $this->device->date_add = date('Y-m-d H:i:s');
+ $this->device->type_id = $this->typeId;
+ $this->device->device_info = \Yii::$app->getRequest()->getUserAgent();
+ $this->device->lasttime = date('Y-m-d H:i:s');
+ $this->device->keygen = \Yii::$app->getSecurity()->generateRandomString();
+ $this->device->ip = \Yii::$app->getRequest()->getRemoteIP();
+ $this->device->admin_id = $_SESSION['admin_id'];
+ if (!$this->device->save()) {
+ $this->addErrors($this->device->getErrors());
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * @return AdminDesktop
+ */
+ public function getDevice()
+ {
+ return $this->device;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+declare(strict_types = 1);
+
+namespace yii_app\forms\device;
+
+use yii\base\Model;
+use yii_app\records\AdminDesktop;
+use yii_app\records\CityStore;
+
+/**
+ * Форма добавления слотов
+ * @package yii_app\forms\timetable
+ */
+class SelectDeviceForm extends Model
+{
+ public $id = null;
+
+ public function rules(): array
+ {
+ return [
+ [['id'], 'integer'],
+ [['id'], 'in', 'range' => array_keys(self::devices())],
+ ];
+ }
+
+ public static function devices()
+ {
+ static $all = [];
+ if ($all) {
+ return $all;
+ }
+ return $all = AdminDesktop::find()->indexBy('id')->all();
+ }
+
+ public function attributeLabels()
+ {
+ return [
+ 'id' => 'Устройство',
+ ];
+ }
+
+ /**
+ * @return array|\yii\db\ActiveRecord|null
+ */
+ public function getDevice()
+ {
+ return AdminDesktop::find()->andWhere(['id' => $this->id])->one();
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+namespace yii_app\forms\infoTable;
+
+use yii\base\Model;
+
+class ChartManagementForm extends Model
+{
+ public $date_start;
+
+ public $date_end;
+
+ public $mode_group;
+
+ public $mode_select_matrix_category;
+
+ public function rules()
+ {
+ return [
+ [[ 'date_start', 'date_end'], 'safe'],
+ [['mode_select_matrix_category', 'mode_group'], 'integer'],
+ ['date_start', 'default', 'value' => date("Y-m-d", strtotime('today -14 day'))],
+ ['date_end', 'default', 'value' => date("Y-m-d", strtotime('now'))],
+ ['mode_select_matrix_category', 'default', 'value' => 0],
+ ['mode_group', 'default', 'value' => 0],
+ ['date_start', 'validateDate'],
+ ];
+ }
+
+ public function attributeLabels()
+ {
+ return [
+ 'date_start' => 'Дата начала',
+ 'date_end' => 'Дата окончания',
+ 'mode_select_matrix_category' => 'Определить категории матрицы'
+ ];
+ }
+
+ public function validateDate()
+ {
+ if (strtotime($this->date_start) > strtotime($this->date_end)) {
+ $this->addError('date_start', 'Дата начала не может быть больше даты окончания');
+ $this->addError('date_end', 'Дата окончания не может быть меньше даты начала');
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+namespace yii_app\forms\infoTable;
+
+class Charts extends \yii\base\Model
+{
+
+ public $store_id;
+
+ public $date_start;
+
+ public $date_end;
+
+ public function rules()
+ {
+ return [
+ [['store_id', 'date_start', 'date_end'], 'safe'],
+ ['date_start', 'default', 'value' => date("Y-m-d", strtotime('first day of this month'))],
+ ['date_end', 'default', 'value' => date("Y-m-d", strtotime('last day of this month'))],
+ ['date_start', 'validateDate'],
+ ];
+ }
+
+ public function attributeLabels()
+ {
+ return [
+ 'date_start' => 'Дата начала',
+ 'date_end' => 'Дата окончания'
+ ];
+ }
+
+ public function validateDate()
+ {
+ if (strtotime($this->date_start) > strtotime($this->date_end)) {
+ $this->addError('date_start', 'Дата начала не может быть больше даты окончания');
+ $this->addError('date_end', 'Дата окончания не может быть меньше даты начала');
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+declare(strict_types = 1);
+
+namespace yii_app\forms\infoTable;
+
+use yii\base\Model;
+
+class YearWeekSearchForm extends Model
+{
+ public string $year;
+ public string $week;
+ public string $dateFrom;
+ public string $dateTo;
+ public string $store_id;
+
+ public function rules(): array
+ {
+ return [
+ [['year', 'week', 'dateFrom', 'dateTo', 'store_id'], 'string'],
+ [['year', 'week'], 'required'],
+ ];
+ }
+ public function __construct()
+ {
+ $this->year = date("Y", strtotime(date("Y-m-d",time())));
+
+ $this->week = date("W", strtotime(date("Y-m-d",time())));
+ }
+
+
+ public function attributeLabels()
+ {
+ return [
+ 'year' => 'Год',
+ 'week' => 'Неделя',
+ 'store_id' => 'Магазин',
+ ];
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+declare(strict_types = 1);
+
+namespace yii_app\forms\lesson;
+
+use yii\base\Model;
+
+class LessonForm extends Model
+{
+ public $lesson_id = null;
+ public $name = null;
+ public $description = null;
+ public $status = 0;
+ public $content = null;
+ public $video_url = null;
+
+ public $obligatory_time = 0;
+ public $shuffle_polls = 0;
+ public $min_percent = 100;
+ public $max_attempts = 10;
+ public $recommended_time = 0;
+ public $attempt_delay = 0;
+ public $max_time = 60;
+ public $success_ball = 10;
+ public $fail_ball = 10;
+
+ public function rules(): array
+ {
+ return [
+ [['name', 'video_url'], 'required'],
+ [['lesson_id', 'min_percent', 'max_attempts', 'recommended_time', 'status', 'attempt_delay', 'max_time',
+ 'success_ball', 'fail_ball', 'shuffle_polls', 'obligatory_time'], 'integer'],
+ [['name','description','content','video_url'], 'string'],
+ ];
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+declare(strict_types = 1);
+
+namespace yii_app\forms\log;
+
+use yii\base\Model;
+
+/**
+ * Фильтр ошибок
+ * @package yii_app\forms\timetable
+ */
+class ErrorLogSearchModel extends Model
+{
+ public $id;
+ public $ip;
+ public $line;
+ public $col;
+ public $level;
+ public $category;
+
+ public function rules()
+ {
+ return [
+ [['id'], 'integer'],
+ [['ip', 'category'], 'string'],
+ [['line', 'col','level'], 'string'],
+ ];
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+declare(strict_types = 1);
+
+namespace yii_app\forms\payroll;
+
+use yii\base\Model;
+
+class YearMonthSearchForm extends Model
+{
+ public string $year;
+ public string $month;
+ public string $dateFrom;
+ public string $dateTo;
+ public string $store_id;
+
+ public function rules(): array
+ {
+ return [
+ [['year', 'month', 'dateFrom', 'dateTo', 'store_id'], 'string'],
+ [['year', 'month'], 'required'],
+ ];
+ }
+ public function __construct()
+ {
+ $dayPastCurrentMonth = (int) date("d");
+ $modifier = "";
+ if ($dayPastCurrentMonth < 8) {
+ $modifier = " -1 months";
+ }
+ $this->year = date("Y", strtotime(date("Y-m-d",time()) . $modifier));
+
+ $this->month = date("m", strtotime(date("Y-m-d",time()) . $modifier));
+ }
+
+
+ public function attributeLabels()
+ {
+ return [
+ 'year' => 'Год',
+ 'month' => 'Месяц',
+ 'dateFrom' => 'dateFrom',
+ 'store_id' => 'Магазин',
+ ];
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+namespace yii_app\forms\planStore;
+
+use yii\base\Model;
+
+class AddPlanStore extends Model
+{
+ public $store_id;
+
+ public $date_str;
+
+ public $year;
+
+ public $month;
+
+ public static $month_name = [
+ 1 => 'Январь',
+ 'Февраль',
+ 'Март',
+ 'Апрель',
+ 'Май',
+ 'Июнь',
+ 'Июль',
+ 'Август',
+ 'Сентябрь',
+ 'Октябрь',
+ 'Ноябрь',
+ 'Декабрь'
+ ];
+ public function rules()
+ {
+ return [
+ [['store_id', 'year', 'month'], 'required'],
+ [['store_id', 'year', 'month'], 'integer'],
+ ['date_str', 'string']
+ ];
+ }
+
+ public function attributeLabels()
+ {
+ return [
+ 'store_id' => 'Магазин',
+ 'date_str' => 'Дата'
+ ];
+ }
+
+ public function updateDate () {
+ $this->year = intval(date('Y', strtotime($this->date_str)));
+ $this->month = intval(date('m', strtotime($this->date_str)));
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+namespace yii_app\forms\support;
+
+use yii_app\records\Task;
+
+class TaskSupportCreateForm extends Task
+{
+ public function rules()
+ {
+ return [
+ [['name', 'description', 'entity_type', 'task_type_id', 'duration', 'alert_level_id', 'motivation_id'], 'required'],
+ [['description',], 'string'],
+ [['name'], 'string', 'max' => 255],
+ [['name'], 'string', 'min' => 3],
+ [['entity_id', 'function_entity_id'], 'string', 'max' => 36],
+ [['files'], 'file', 'skipOnEmpty' => true, 'maxFiles' => 20],
+ ];
+ }
+
+ public function attributeLabels()
+ {
+ return [
+ 'description' => 'Описание',
+ 'entity_type' => 'Тип сущности',
+ 'entity_id' => 'Магазин',
+ 'name' => 'Название'
+ ];
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+declare(strict_types = 1);
+
+namespace yii_app\forms\timetable;
+
+use yii\base\Model;
+use yii\db\Expression;
+use yii_app\records\Admin;
+use yii_app\records\AdminGroup;
+use yii_app\records\CityStore;
+use yii_app\records\Shift;
+use yii_app\records\Timetable;
+use yii_app\records\TimetablePlan;
+
+/**
+ * Форма добавления слотов
+ * @property-read Shift $shift
+ * @package yii_app\forms\timetable
+ */
+class AddForm extends Model
+{
+ public $storeId = null;
+ public $date = null;
+ public $timeStart = null;
+ public $timeEnd = null;
+ public $shiftId = null;
+ public $adminIds = null;
+ public $slotTypeId = null;
+ public $comment = '';
+
+ public function rules(): array
+ {
+ return [
+ [['storeId', 'shiftId'], 'integer'],
+ [['storeId'], 'in', 'range' => array_keys(self::stores())],
+ [['date'], 'date', 'format' => 'php:Y-m-d'],
+ [['timeStart', 'timeEnd', 'adminIds'], 'required'],
+ [['timeStart', 'timeEnd'], 'date', 'format' => 'php:H:i:s'],
+ [['shiftId'], 'in', 'range' => array_keys(Shift::all())],
+ [['adminIds'], 'each', 'rule' => ['in', 'range' => array_keys(self::admins())]],
+ [['slotTypeId'], 'in', 'range' => array_keys(Timetable::slotTypeName())],
+ ];
+ }
+
+ public function attributeLabels()
+ {
+ return [
+ 'adminIds' => 'Пользователи',
+ 'storeId' => 'Магазин',
+ 'timeStart' => 'Время начала',
+ 'timeEnd' => 'Время конца',
+ 'shiftId' => 'Смена',
+ 'slotTypeId' => 'Тип занятости',
+ ];
+ }
+ public static function stores() : array
+ {
+ return CityStore::find()
+ ->select(['name', 'id'])
+ ->andWhere(['visible' => 1])
+ ->indexBy('id')
+ ->cache(3600)
+ ->column();
+ }
+
+ public function getShift(): Shift
+ {
+ return Shift::all()[$this->shiftId];
+ }
+
+ public static function admins()
+ {
+ return Admin::find()
+ ->with('adminGroup')
+ ->andWhere([
+ 'group_id' => array_keys(AdminGroup::groupsWithShift()),
+ ])
+ ->orderBy([
+ 'group_id' => SORT_ASC,
+ 'name' => SORT_ASC
+ ])
+ ->indexBy('id')
+ ->all()
+ ;
+ }
+
+ public static function minutes(): array
+ {
+ return [0, 10, 20, 30, 40, 50];
+ }
+
+ public function getStoreName()
+ {
+ return self::stores()[$this->storeId] ?? 'вне магазинов';
+ }
+
+ public function save(): bool
+ {
+ if (!$this->validate()) {
+ return false;
+ }
+ $start = new \DateTime($this->date . ' ' .$this->timeStart);
+ $end = new \DateTime($this->date . ' ' .$this->timeEnd);
+ $minStart = new \DateTime($this->date . ' ' .$this->shift->start_time);
+ // shift crosses midnight
+ if ($start < $minStart) {
+ $start->modify('+1 day');
+ }
+ if ($end < $minStart) {
+ $end->modify('+1 day');
+ }
+
+ /** @var Admin[] $admins */
+ $admins = Admin::find()
+ ->with('adminGroup')
+ ->andWhere(['id' => $this->adminIds])
+ ->indexBy('id')
+ ->all();
+
+ $trx = TimetablePlan::getDb()->beginTransaction();
+ foreach ($this->adminIds as $adminId) {
+ $timeTable = new TimetablePlan([
+ 'shift_id' => $this->shiftId,
+ 'store_id' => $this->storeId,
+ 'date' => $this->date,
+ 'admin_id' => $adminId,
+ 'd_id' => $admins[$adminId]->adminGroup->id,
+ 'admin_id_add' => $_SESSION['admin_id'],
+ 'time_start' => $this->timeStart,
+ 'time_end' => $this->timeEnd,
+ 'datetime_start' => $start->format('Y-m-d H:i:s'),
+ 'datetime_end' => $end->format('Y-m-d H:i:s'),
+ 'slot_type_id' => $this->slotTypeId,
+ 'comment' => $this->comment,
+ 'status' => 0,
+ ]);
+ if (!$timeTable->save()) {
+ $this->addError('adminIds', implode("\n", $timeTable->getFirstErrors()));
+ $trx->rollBack();
+ return false;
+ }
+ }
+ $trx->commit();
+ return true;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+declare(strict_types = 1);
+
+namespace yii_app\forms\timetable;
+
+use yii\base\Model;
+
+class HolidaySearch extends Model
+{
+ public $dateFrom = null;
+ public $dateTo = null;
+
+ public function rules(): array
+ {
+ return [
+ [['dateFrom', 'dateTo'], 'string'],
+ [['dateFrom', 'dateTo'], 'date', 'format' => 'php:Y-m-d'],
+ [['dateFrom'], 'default', 'value' => function () {
+ return date('Y-01-01');
+ }],
+ [['dateTo'], 'default', 'value' => function () {
+ return date('Y-12-31');
+ }],
+ ];
+ }
+
+ public function attributeLabels()
+ {
+ return [
+ 'dateFrom' => 'Дата начала',
+ 'dateTo' => 'Дата конца',
+ ];
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+declare(strict_types = 1);
+
+namespace yii_app\forms\timetable;
+
+use yii\base\Model;
+use yii_app\records\CityStore;
+
+class IndexForm extends Model
+{
+ public $store_id = null;
+ public $start = null;
+ public $end = null;
+
+ public function rules(): array
+ {
+ return [
+ [['store_id'], 'integer'],
+ [['store_id'], 'in', 'range' => array_keys(self::stores())],
+ [['start', 'end'], 'date', 'format' => 'php:Y-m-d'],
+ [['start'], 'default', 'value' => date('Y-m-d', strtotime('-1 day'))],
+ [['end'], 'default', 'value' => date('Y-m-d', strtotime('+7 day'))],
+ ];
+ }
+
+ public function attributeLabels()
+ {
+ return [
+ 'store_id' => 'Магазин',
+ 'start' => 'Дата начала',
+ 'end' => 'Дата конца',
+ ];
+ }
+ public static function stores() : array
+ {
+ return CityStore::find()
+ ->select(['name', 'id'])
+ ->andWhere(['visible' => 1])
+ ->indexBy('id')
+ ->cache(3600)
+ ->column();
+ }
+
+ public function getStoreName()
+ {
+ return self::stores()[$this->store_id] ?? 'вне магазинов';
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+declare(strict_types = 1);
+
+namespace yii_app\forms\timetable;
+
+use yii\base\Model;
+use yii\helpers\ArrayHelper;
+use yii\web\UploadedFile;
+use yii_app\records\Admin;
+use yii_app\records\AdminCheckin;
+use yii_app\records\AdminDesktop;
+use yii_app\records\AdminGroup;
+use yii_app\records\CityStore;
+use yii_app\records\Shift;
+use yii_app\records\Timetable;
+use yii_app\records\TimetablePlan;
+
+/**
+ * Форма добавления слотов
+ * @property CityStore $store
+ * @property TimetablePlan $planSlot
+ * @package yii_app\forms\timetable
+ */
+class StartForm extends Model
+{
+ public $id;
+ public $admin_id;
+ public $type_id;
+ public $date;
+ public $time;
+ public $store_id;
+ public $ball;
+ public $comment = '';
+ /** @var UploadedFile */
+ public $photo;
+ public $d_id;
+ public $status = 0; // не проверено
+ public $lat;
+ public $lon;
+ /** @var int */
+ public $replaced_admin_id;
+ /** @var int */
+ public $plan_id;
+ /** @var int */
+ public $device_id;
+ public $checkin_id;
+
+ /** @var AdminCheckin */
+ public $checkinModel;
+ /** @var TimetablePlan */
+ private $planSlotModel;
+
+ public function isStart()
+ {
+ return $this->type_id == AdminCheckin::TYPE_START;
+ }
+
+ public function isEnd()
+ {
+ return $this->type_id == AdminCheckin::TYPE_END;
+ }
+
+ public function isAppear()
+ {
+ return $this->type_id == AdminCheckin::TYPE_APPEAR;
+ }
+
+ public static function ratings()
+ {
+ return [
+ 2 => 'Неудовлетворительно',
+ 3 => 'Удовлетворительно',
+ 4 => 'Хорошо',
+ 5 => 'Отлично',
+ ];
+ }
+
+ public function attributeLabels()
+ {
+ return [
+ 'id' => 'ID',
+ 'admin_id' => 'ID пользователя',
+ 'type_id' => 'Открытие/закрытие смены',
+ 'date' => 'Дата смены',
+ 'time' => 'Время',
+ 'store_id' => 'Место',
+ 'checkin_id' => 'ID отметки / открытия / закрытия смены',
+ 'ball' => 'Оценка',
+ 'comment' => 'Комментарий',
+ 'photo' => 'Селфи',
+ 'd_id' => 'Должность',
+ 'status' => 'Статус проверки',
+ 'replaced_admin_id' => 'Заменяемый сотрудник',
+ 'gps' => 'Координаты',
+ ];
+ }
+
+ public function init()
+ {
+ $this->checkinModel = new AdminCheckin();
+ }
+
+ public function rules(): array
+ {
+ return [
+ [['id', 'admin_id', 'replaced_admin_id', 'plan_id', 'checkin_id', 'store_id', 'type_id', 'd_id', 'ball', 'status'], 'integer'],
+ [['comment', 'date'], 'string'],
+ [['lat', 'lon'], 'number'],
+ [['admin_id', 'replaced_admin_id'], 'exist', 'targetClass' => Admin::class, 'targetAttribute' => 'id'],
+ [['admin_id', 'store_id', 'device_id', 'type_id', 'd_id', 'ball', 'date'], 'required'],
+ [['d_id'], 'exist', 'targetClass' => AdminGroup::class, 'targetAttribute' => 'id'],
+ [['store_id'], function() {
+ $targetAdminId = $this->replaced_admin_id ?: $this->admin_id;
+ /** @var Admin $targetAdmin */
+ $targetAdmin = Admin::find()->andWhere(['id' => $targetAdminId])->one();
+ if (!in_array($this->store_id, $targetAdmin->getStoreIds())) {
+ $this->addError('store_id', 'У пользователя нет доступа к этому магазину');
+ }
+ }],
+ [['status'], 'required', 'requiredValue' => AdminCheckin::STATUS_PENDING],
+ [['photo'], 'required', 'message' => 'Смена не может быть открыта без селфи'],
+ [['photo'], 'file', 'mimeTypes' => ['image/jpeg'], 'message' => 'Неправильный формат файла'],
+ [['store_id'], 'required', 'message' => 'Смена не может быть открыта без указания магазина'],
+ [['time'], function () {
+ $maxShiftDuration = max(ArrayHelper::getColumn(Shift::all(), 'duration'));
+ /** @var AdminCheckin $lastCheckin */
+ $lastCheckin = AdminCheckin::find()
+ ->andWhere(['admin_id' => $this->admin_id])
+ ->andWhere(['>', 'time', date('Y-m-d H:i:s', strtotime("-$maxShiftDuration hour"))])
+ ->orderBy(['time' => SORT_DESC])
+ ->limit(1)
+ ->one();
+ if (!$lastCheckin) {
+ return;
+ }
+ if ($lastCheckin->type_id == $this->type_id) {
+ if ($this->isStart()) {
+ $this->addError('time', 'Смена уже открывалась');
+ } else {
+ $this->addError('time', 'Смена уже закрывалась');
+ }
+ }
+ }],
+ [['admin_id'], function () {
+ /** @TODO check for power admins */
+ if ($this->admin_id != $_SESSION['admin_id']) {
+ $this->addError('admin_id', 'Неправильный пользователь');
+ }
+ }],
+ [['device_id'], function () {
+ /** @var AdminDesktop $device */
+ $device = AdminDesktop::find()->andWhere(['id' => $this->device_id])->one();
+ if (!$device) {
+ $this->addError('device_id', 'Неправильный идентификатор устройства');
+ }
+ if ($device->keygen != $_COOKIE['device_key']) {
+ $this->addError('device_id', 'Переданный идентификатор не совпадает с привязанным устройством');
+ }
+ }],
+ [['plan_id'], function () {
+ if (!$this->plan_id) {
+ return;
+ }
+ /** @var TimetablePlan $plan */
+ $plan = TimetablePlan::find()->andWhere(['id' => $this->plan_id])->one();
+ if (!$plan) {
+ $this->addError('plan_id', 'Неправильный номер записи плана');
+ }
+ if (!$plan->isWorkSlot()) {
+ $this->addError('plan_id', 'Нерабочий день по плану');
+ }
+ if ($this->date != $plan->date) {
+ $this->addError('date', 'Дата открытия не совпадает с планом');
+ }
+ $targetAdminId = $this->replaced_admin_id ?: $this->admin_id;
+ if ($targetAdminId != $plan->admin_id) {
+ $this->addError($this->replaced_admin_id ? 'replaced_admin_id' : 'admin_id', 'Выбран неправильный пользователь в плане');
+ }
+ if ($this->d_id != $plan->d_id) {
+ $this->addError('d_id', 'Выбранная должность не совпадает с планом');
+ }
+ if ($this->date != $plan->date) {
+ $this->addError('date', 'Дата не совпадает с планом');
+ }
+ }],
+ ];
+ }
+
+ public function beforeValidate()
+ {
+ $this->time = date('Y-m-d H:i:s');
+ return parent::beforeValidate();
+ }
+
+ public function save()
+ {
+ if (!$this->validate()) {
+ return false;
+ }
+ $filePath = $this->generateFilePath();
+ if ($this->hasErrors()) {
+ parent::afterValidate();
+ return false;
+ }
+
+ if (!$this->photo->saveAs($filePath)) {
+ $this->addError('photo', 'Невозможно загрузить файл фотографии');
+ parent::afterValidate();
+ return false;
+ }
+
+ $attributes = ['photo' => $filePath] + $this->getAttributes();
+ $this->checkinModel->setAttributes($attributes);
+ if (!$this->checkinModel->save()) {
+ $this->addErrors($this->checkinModel->getErrors());
+ return false;
+ }
+ return true;
+ }
+
+ private function generateFilePath(): string
+ {
+ $Y = date("Y");
+ $m = date("m");
+ if (!is_dir("data/admin/$Y/$m")) {
+ mkdir("data/admin/$Y/$m", 0777, true);
+ }
+ $fileName = $_SESSION["admin_id"] . '-' . date("YmdHis") . '.jpg';
+ return "data/admin/$Y/$m/$fileName";
+ }
+
+ public function getStore()
+ {
+ return CityStore::find()
+ ->andWhere(['id' => $this->store_id])
+ ->one()
+ ;
+ }
+
+ /**
+ * @return TimetablePlan
+ */
+ public function getPlanSlot()
+ {
+ if (!$this->planSlotModel) {
+ $this->planSlotModel = TimetablePlan::find()->andWhere(['id' => $this->plan_id])->one();
+ }
+ return $this->planSlotModel;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+declare(strict_types = 1);
+
+namespace yii_app\forms\timetable;
+
+use yii\base\Model;
+use yii\db\ActiveQuery;
+use yii\db\Expression;
+use yii_app\records\Admin;
+use yii_app\records\AdminGroup;
+use yii_app\records\CityStore;
+use yii_app\records\Holiday;
+use yii_app\records\Timetable;
+use yii_app\records\TimetableFact;
+use yii_app\records\TimetablePlan;
+
+class TabelSearchForm extends Model
+{
+ public $storeId = null;
+ public $start = null;
+ public $end = null;
+ public $adminGroupId = '';
+ public $adminId = '';
+ public $status = '';
+
+ public function rules(): array
+ {
+ return [
+ [['storeId', 'adminGroupId', 'status'], 'integer'],
+ [['storeId'], 'in', 'range' => array_keys(self::stores())],
+ [['adminGroupId'], 'in', 'range' => array_keys(AdminGroup::groupsWithShift())],
+ [['start', 'end'], 'date', 'format' => 'php:Y-m-d'],
+ [['status'], 'in' ,'range' => array_keys(Timetable::statuses())],
+ [['start'], 'default', 'value' => date('Y-m-01', strtotime('next month midnight'))],
+ [['end'], 'default', 'value' => date('Y-m-t', strtotime('next month midnight'))],
+ ];
+ }
+
+ public function attributeLabels()
+ {
+ return [
+ 'storeId' => 'Магазин',
+ 'start' => 'Дата начала',
+ 'end' => 'Дата конца',
+ 'adminGroupId' => 'Группа работников',
+ ];
+ }
+
+ public static function stores() : array
+ {
+ $stores = CityStore::find()
+ ->select(['name', 'id'])
+ ->andWhere(['id' => $_SESSION["store_arr_dostup"]])
+ ->indexBy('id')
+ ->cache(3600)
+ ->column();
+ natsort($stores);
+ return $stores;
+ }
+
+ public static function adminGroups(): array
+ {
+ static $groups;
+ if (isset($groups)) {
+ return $groups;
+ }
+ return \Yii::$app->getCache()->getOrSet('admin_groups_with_shifts', function () {
+ return AdminGroup::find()
+ ->with(['shift'])
+ ->andWhere(['NOT IN', 'id', [1, 2, -1]])
+ ->indexBy('id')
+ ->all();
+ });
+ }
+
+ /**
+ * @return Admin[]
+ */
+ public function admins()
+ {
+ $adminQuery = Admin::find()
+ ->select(['id', 'name', 'group_id', 'store_arr', 'avatarka'])
+ ->with(['adminGroup' => function (ActiveQuery $q) {
+ return $q->select(['id', 'name']);
+ }])
+ ->with('adminGroup.shift')
+ ->andWhere([
+ 'group_id' => array_keys(AdminGroup::groupsWithShift())
+ ])
+ ->andFilterWhere(['group_id' => $this->adminGroupId])
+ ->orderBy(['group_id' => SORT_ASC, 'name' => SORT_ASC])
+ ->indexBy('id');
+ if ($this->storeId) {
+ $adminQuery
+ ->andWhere(new Expression('FIND_IN_SET(:store_id, store_arr)', ['store_id' => (int) $this->storeId]));
+ }
+ return $adminQuery->all();
+ }
+
+ public function search($tabel)
+ {
+ $classes = [
+ Timetable::TABLE_PLAN => TimetablePlan::class,
+ Timetable::TABLE_FACT => TimetableFact::class,
+ ];
+ if (!isset($classes[$tabel])) {
+ throw new \Exception('Unknown type');
+ }
+ /** @var TimetablePlan | TimetableFact $class */
+ $class = $classes[$tabel];
+ return $class::find()
+ ->andWhere([
+ 'admin_group_id' => array_keys(AdminGroup::groupsWithShift()),
+ 'store_id' => array_keys(self::stores()),
+ ])
+ ->andFilterWhere([
+ 'admin_group_id' => $this->adminGroupId,
+ 'store_id' => $this->storeId,
+ 'status' => $this->status,
+ ])
+ ->andFilterWhere(['>=', 'date', $this->start])
+ ->andFilterWhere(['<=', 'date', $this->end])
+ ;
+ }
+
+ public function holidays()
+ {
+ return $this->validate(['start', 'end'], false) ? Holiday::find()
+ ->andFilterWhere(['>=', 'date', $this->start])
+ ->andFilterWhere(['<=', 'date', $this->end])
+ ->all() : []
+ ;
+ }
+
+ public function getDates(): array
+ {
+ $startDatetime = new \DateTime($this->start);
+ $endDatetime = new \DateTime($this->end);
+
+ $dates = [];
+ for ($date = clone $startDatetime; $date <= $endDatetime; $date->modify('+1 day')) {
+ $dates[] = clone $date;
+ }
+
+ return $dates;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+declare(strict_types = 1);
+
+namespace yii_app\forms\timetable;
+
+use yii\base\Model;
+use yii\db\ActiveQuery;
+use yii\db\Expression;
+use yii_app\records\Admin;
+use yii_app\records\AdminGroup;
+use yii_app\records\CityStore;
+use yii_app\records\Timetable;
+use yii_app\records\TimetableFact;
+use yii_app\records\TimetablePlan;
+
+class TabelSearchFormFact extends Model
+{
+ public $storeId = null;
+ public $start = null;
+ public $end = null;
+ public $adminGroupId = '';
+ public $adminId = '';
+ public $status = '';
+
+ public function rules(): array
+ {
+ return [
+ [['storeId', 'adminGroupId', 'status'], 'integer'],
+ [['storeId'], 'in', 'range' => array_keys(self::stores())],
+ [['adminGroupId'], 'in', 'range' => array_keys(AdminGroup::groupsWithShift())],
+ [['start', 'end'], 'date', 'format' => 'php:Y-m-d'],
+ [['status'], 'in' ,'range' => array_keys(Timetable::statuses())],
+ [['status'], 'default' ,'value' => Timetable::STATUS_PENDING],
+ [['start'], 'default', 'value' => date('Y-m-d', strtotime('-7 day'))],
+ [['end'], 'default', 'value' => date('Y-m-d', strtotime('+1 day'))],
+ ];
+ }
+
+ public function attributeLabels()
+ {
+ return [
+ 'storeId' => 'Магазин',
+ 'start' => 'Дата начала',
+ 'end' => 'Дата конца',
+ 'adminGroupId' => 'Группа работников',
+ 'status' => 'Статус проверки',
+ ];
+ }
+
+ public static function stores() : array
+ {
+ return CityStore::find()
+ ->select(['name', 'id'])
+ ->andWhere(['id' => $_SESSION["store_arr_dostup"]])
+ ->indexBy('id')
+ ->cache(3600)
+ ->column();
+ }
+
+ public static function adminGroups(): array
+ {
+ static $groups;
+ if (isset($groups)) {
+ return $groups;
+ }
+ return \Yii::$app->getCache()->getOrSet('admin_groups_with_shifts', function () {
+ return AdminGroup::find()
+ ->with(['shift'])
+ ->andWhere(['NOT IN', 'id', [1, 2, -1]])
+ ->indexBy('id')
+ ->all();
+ });
+ }
+
+ /**
+ * @return Admin[]
+ */
+ public function admins()
+ {
+ $adminQuery = Admin::find()
+ ->select(['id', 'name', 'group_id', 'store_arr'])
+ ->with(['adminGroup' => function (ActiveQuery $q) {
+ return $q->select(['id', 'name']);
+ }])
+ ->with('adminGroup.shift')
+ ->andWhere([
+ 'group_id' => array_keys(AdminGroup::groupsWithShift())
+ ])
+ ->andFilterWhere(['group_id' => $this->adminGroupId])
+ ->orderBy(['group_id' => SORT_ASC, 'name' => SORT_ASC])
+ ->indexBy('id');
+ if ($this->storeId) {
+ $adminQuery
+ ->andWhere(new Expression('FIND_IN_SET(:store_id, store_arr)', ['store_id' => (int) $this->storeId]));
+ }
+ return $adminQuery->all();
+ }
+
+ public function search($tabel)
+ {
+ $classes = [
+ Timetable::TABLE_PLAN => TimetablePlan::class,
+ Timetable::TABLE_FACT => TimetableFact::class,
+ ];
+ if (!isset($classes[$tabel])) {
+ throw new \Exception('Unknown type');
+ }
+ /** @var TimetablePlan | TimetableFact $class */
+ $class = $classes[$tabel];
+ return $class::find()
+ ->andWhere([
+ 'admin_group_id' => array_keys(AdminGroup::groupsWithShift()),
+ 'store_id' => array_keys(self::stores()),
+ ])
+ ->andFilterWhere([
+ 'admin_group_id' => $this->adminGroupId,
+ 'store_id' => $this->storeId,
+ 'status' => $this->status,
+ ])
+ ->andFilterWhere(['>=', 'date', $this->start])
+ ->andFilterWhere(['<=', 'date', $this->end])
+ ;
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+declare(strict_types = 1);
+
+namespace yii_app\forms\writeOffsErp;
+
+use yii\base\Model;
+
+class WriteOffsForm extends Model
+{
+ public $store_id;
+ public $modelsProducts;
+
+ public function rules(): array
+ {
+ return [
+ [['store_id',], 'integer'],
+ [['modelsProducts',], 'safe'],
+ [['store_id'], 'required'],
+ ];
+ }
+ public function __construct()
+ {
+
+ }
+
+
+ public function attributeLabels()
+ {
+ return [
+ 'store_id' => 'Магазин',
+ ];
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+declare(strict_types = 1);
+
+namespace yii_app\forms\writeOffsErp;
+
+use yii\base\Model;
+use yii_app\records\Balances;
+use yii_app\records\CityStore;
+use yii_app\records\Products1c;
+
+class WriteOffsProductsForm extends Model
+{
+ public $product_id;
+ public $cause_id;
+ public $store_id;
+ public $quantity;
+ public $images;
+
+
+ private static function checkProductBalance($productGuid, $storeId, $quantity)
+ {
+
+ $product = Products1c::getProduct1c($productGuid, 'products');
+
+ if (empty($product)) {
+ $error_text = 'Выбранный продукт ' . $productGuid . ' не найден в справочнике соответствия ERP с продуктами 1с';
+ } else {
+ $store = CityStore::getCityStoreById( (int) $storeId, true);
+
+ $storeGuid = $store['storeGuid']['export_val'];
+
+ $productBalance = self::getProductBalance($productGuid, $storeGuid);
+
+ $productName = $product['name'];
+ $storeName = $store['name'];
+
+ $error_text = '';
+ $error_header_text = 'В магазине "' . $storeName . '" товара "' . $productName . '" на остатках';
+
+ if (0 == $productBalance) {
+ $error_text .= $error_header_text . ' нет';
+ } else if ($productBalance < $quantity) {
+ $delta = $quantity - $productBalance;
+ $error_text .= $error_header_text . ' недостаточно для списания.';
+ $error_text .= ' Необходимое количество для списания ' . $quantity . ' шт. , сейчас в магазине числится ' . $productBalance . ' шт.';
+ $error_text .= ' Для списания нужно ещё ' . $delta . ' шт.';
+ }
+ }
+
+ return $error_text;
+
+ }
+
+ private static function getProductBalance($productGuid, $storeGuid)
+ {
+ $productCount = Balances::find()
+ ->select('quantity')
+ ->andWhere([
+ 'product_id' => $productGuid,
+ 'store_id' => $storeGuid, //'quantity'
+ ])->asArray()
+
+ ->scalar();
+
+
+ return $productCount;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function rules()
+ {
+ return [
+ [['product_id', 'quantity'], 'required'],
+ [['store_id', 'cause_id'], 'integer'],
+ [['quantity'], 'number'],
+ [['product_id'], 'string', 'max' => 36],
+ ['quantity', 'compare', 'compareValue' => 0, 'operator' => '>'],
+ ['product_id', 'custom_function_validation'],
+ ];
+ }
+ public function custom_function_validation($attribute, $params)
+ {
+ if (empty($this->quantity) || empty($this->store_id)) {
+ $errorText = 'Проверка по наличию товара под списание в магазине не пройдена';
+ $this->addError($attribute, $errorText);
+ } else {
+ $resultCheck = self::checkProductBalance($this->$attribute, $this->store_id, $this->quantity);
+
+ if (!empty($resultCheck)) {
+ $this->addError($attribute, $resultCheck);
+ }
+ }
+
+ }
+
+
+/**
+ * {@inheritdoc}
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'product_id' => 'Товар',
+ 'cause_id' => 'Причина списания',
+ 'quantity' => 'Количество',
+ ];
+ }
+
+}
\ No newline at end of file
$_SESSION['dostup_area'] = [];
$group = AdminGroup::findOne($user->group_id);
$_SESSION['admin_group_name'] = $group->name ?? "Какая-то...";
+ $_SESSION['name_group_admin'] = $group->name ?? "Какая-то...";
+
$_SESSION['status_dostup_arr'] = [];
$_SESSION['admin_group_add_arr'] = [];
$_SESSION['manager_id'] = $user->manager_id ?? null;
$_SESSION['content_dostup'] = $user->content_dostup ?? null;
- $_SESSION['name_group_admin'] = "Пользователь";
}
/**
--- /dev/null
+<?php
+
+namespace app\tests\functional;
+class PageListCest
+{
+ public function shiftSalesInterface(\FunctionalTester $I)
+ {
+ $I->amLoggedInAs(\app\models\User::findByUsername('admin'));
+ $I->amOnPage('/info-table/shift-sales');
+ $I->see('Login', 'h1');
+ }
+
+}
\ No newline at end of file
namespace tests\unit\models;
use app\models\User;
+use yii_app\records\Admin;
class UserTest extends \Codeception\Test\Unit
{
public function testFindUserById()
{
- verify($user = User::findIdentity(100))->notEmpty();
- verify($user->username)->equals('admin');
+ verify($user = Admin::findIdentity(1))->notEmpty();
+ verify($user->login_user)->equals('root');
verify(User::findIdentity(999))->empty();
}
var ADMIN_ID = " . (Yii::$app->user->id ?? 0) . ";
", \yii\web\View::POS_BEGIN, 'api2_menu_init_global_vars');
-//$this->registerJsFile('/js/site/get_menu.js', ['position' => \yii\web\View::POS_END]);
+$this->registerJsFile('/js/site/get_menu.js', ['position' => \yii\web\View::POS_END]);