--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace yii_app\records;
+
+use Yii;
+use yii\behaviors\BlameableBehavior;
+use yii\behaviors\TimestampBehavior;
+use yii\db\ActiveQuery;
+use yii\db\ActiveRecord;
+use yii\db\Expression;
+
+/**
+ * Справочник маркировок.
+ * Код уникальный (3-12 символов, A-Z0-9-).
+ * UNIQUE (producer_id, plantation_id, product_name).
+ *
+ * @property int $id
+ * @property string $code Код маркировки (SCH-RP-RN50)
+ * @property int $producer_id
+ * @property int $plantation_id
+ * @property string $product_name
+ * @property int $supplier_id
+ * @property bool $is_active
+ * @property int $created_by
+ * @property int|null $updated_by
+ * @property string $created_at
+ * @property string|null $updated_at
+ *
+ * @property-read Producer $producer
+ * @property-read Plantation $plantation
+ * @property-read Supplier $supplier
+ *
+ * @see https://itriteil.atlassian.net/browse/ERP-320
+ */
+class Marking extends ActiveRecord
+{
+ public const CODE_REGEX = '/^[A-Z0-9\-]{3,12}$/D';
+
+ public static function tableName(): string
+ {
+ return '{{%erp24.markings}}';
+ }
+
+ public function behaviors(): array
+ {
+ return [
+ [
+ 'class' => TimestampBehavior::class,
+ 'createdAtAttribute' => 'created_at',
+ 'updatedAtAttribute' => 'updated_at',
+ 'value' => new Expression('NOW()'),
+ ],
+ [
+ 'class' => BlameableBehavior::class,
+ 'createdByAttribute' => 'created_by',
+ 'updatedByAttribute' => 'updated_by',
+ 'defaultValue' => null,
+ ],
+ ];
+ }
+
+ public function rules(): array
+ {
+ return [
+ [['code', 'producer_id', 'plantation_id', 'product_name', 'supplier_id'], 'required'],
+ ['code', 'string', 'max' => 50],
+ [
+ 'code',
+ 'match',
+ 'pattern' => self::CODE_REGEX,
+ 'message' => 'Код: 3-12 символов, только A-Z, 0-9 и дефис',
+ ],
+ ['code', 'filter', 'filter' => 'strtoupper'],
+ ['code', 'unique', 'message' => 'Маркировка с таким кодом уже существует'],
+ ['product_name', 'string', 'max' => 200],
+ [['producer_id', 'plantation_id', 'supplier_id'], 'integer'],
+ [
+ 'producer_id',
+ 'exist',
+ 'targetClass' => Producer::class,
+ 'targetAttribute' => 'id',
+ 'message' => 'Производитель не найден',
+ ],
+ [
+ 'plantation_id',
+ 'exist',
+ 'targetClass' => Plantation::class,
+ 'targetAttribute' => 'id',
+ 'message' => 'Плантация не найдена',
+ ],
+ [
+ 'supplier_id',
+ 'exist',
+ 'targetClass' => Supplier::class,
+ 'targetAttribute' => 'id',
+ 'message' => 'Поставщик не найден',
+ ],
+ [
+ ['product_name'],
+ 'unique',
+ 'targetAttribute' => ['producer_id', 'plantation_id', 'product_name'],
+ 'message' => 'Маркировка с такой комбинацией производителя, плантации и названия уже существует',
+ ],
+ ['is_active', 'boolean'],
+ ['is_active', 'default', 'value' => true],
+ ];
+ }
+
+ public function attributeLabels(): array
+ {
+ return [
+ 'id' => 'ID',
+ 'code' => 'Код',
+ 'producer_id' => 'Производитель',
+ 'plantation_id' => 'Плантация',
+ 'product_name' => 'Название',
+ 'supplier_id' => 'Поставщик',
+ 'is_active' => 'Статус',
+ 'created_by' => 'Создал',
+ 'updated_by' => 'Обновил',
+ 'created_at' => 'Создано',
+ 'updated_at' => 'Обновлено',
+ ];
+ }
+
+ /* --- Scopes --- */
+
+ public static function findActive(): ActiveQuery
+ {
+ return static::find()->where(['is_active' => true]);
+ }
+
+ /* --- Relations --- */
+
+ public function getProducer(): ActiveQuery
+ {
+ return $this->hasOne(Producer::class, ['id' => 'producer_id']);
+ }
+
+ public function getPlantation(): ActiveQuery
+ {
+ return $this->hasOne(Plantation::class, ['id' => 'plantation_id']);
+ }
+
+ public function getSupplier(): ActiveQuery
+ {
+ return $this->hasOne(Supplier::class, ['id' => 'supplier_id']);
+ }
+
+ /* --- Бейджи --- */
+
+ public function getStatusBadge(): string
+ {
+ if ($this->is_active) {
+ return '<span class="badge-status-active">Активна</span>';
+ }
+ return '<span class="badge-status-inactive">Неактивна</span>';
+ }
+
+ /* --- Подсчёт маппингов --- */
+
+ /**
+ * Количество привязанных маппингов (через junction mapping_markings).
+ */
+ public function getMappingsCount(): int
+ {
+ return (int)Yii::$app->db->createCommand(
+ 'SELECT COUNT(*) FROM {{%erp24.mapping_markings}} WHERE marking_id = :mid',
+ [':mid' => $this->id]
+ )->queryScalar();
+ }
+
+ /* --- Деактивация --- */
+
+ /**
+ * Деактивация маркировки. Блокируется при наличии привязанных маппингов.
+ *
+ * @throws \RuntimeException если есть привязанные маппинги
+ */
+ public function deactivate(): void
+ {
+ $count = $this->getMappingsCount();
+ if ($count > 0) {
+ throw new \RuntimeException(sprintf(
+ 'Нельзя деактивировать маркировку: есть %d привязанных маппингов. Сначала отвяжите их.',
+ $count
+ ));
+ }
+
+ $this->is_active = false;
+ $this->save(false);
+ }
+}