Risultato sottrazione tra due colonne in una terza

Ciao a tutti, mi sono imbattuto in questo problema: ho due colonne su cui in una ho la quantita’ disponibile e nella seconda la quantità utilizzata, nella terza dovrebbe restituire la quantita’ residua, quindi la sottrazione tra le prime due (sia valore positivo che negativo).
Ho fatto le seguenti implementazioni ma restituisce sempre (nessun valore) per ogni riga:

nel model Sistop.php
public function getDiff()
    {
        $this->diff = 0;

        if (is_numeric($this->qdsistop) && is_numeric($this->qusistop)) {
            $this->diff = $this->qdsistop - $this->qusistop;
        }

        return $this->diff;
    }
nel model SistopSearch.php
public function attributes()
    {
        return array_merge(parent::attributes(), ['diff']);
    }

   .......................
   
public function search($params)
    {
        $query = Sistop::find()->select('*, (`qdsistop` - `qusistop`) AS `diff`');
        // add conditions that should always apply here
        $dataProvider = new ActiveDataProvider([
            'query' => $query,
        ]);

        // enable sorting for the related columns
        $dataProvider->sort->attributes['diff'] = [
            'asc' => ['diff' => SORT_ASC],
            'desc' => ['diff' => SORT_DESC],
        ];

........

        if (is_numeric($this->diff)) {
            $query->having([
                'diff' => $this->diff,
            ]);
        }

Sul controller non ho effettuato alcuna modifica così come su views/sistop

Le colonne: ‘qdsistop’ e ‘qusistop’ sono INT(5).

Certamente le mie lacune in tal senso non mi permettono di trovare una soluzione che pensavo fosse facilmente deducibile ma non è cosi.

Grazie in anticipo.

Se stiamo parlando di una gridview, è sufficiente aggiungere questa colonna:

[
    'header' => 'Totale',
    'value' => function($data) { return $data->quantita_disponibilie - $data->quantita_utilizzata; } 
]

Grazie per avermi risposto, credo che il problema sia piu’ serio, la colonna che mi hai postato gentilmente lo aggiunta e su index viene visualizzata, ma non esegue l’operazione richiesta. Ti posto lo screenshot per capirci:

Alla riga 4 dovrebbe avere come risultato 8 invece è come se non “vedesse” la colonna centrale.
La colonna delle quantità è frutto di query con un un altro form che “pesca” con dropdownlist dai sistemi operativi disponibili in questa view.
Dovrei postare il codice credo.

La tabella del database è questa:

CREATE TABLE `sistop` (
  `id_sistop` int(11) NOT NULL,
  `elencosistop` varchar(255) DEFAULT NULL COMMENT 'Sistema Operativo',
  `licsistop` varchar(80) DEFAULT NULL COMMENT 'Licenza Uso',
  `qdsistop` int(5) DEFAULT NULL COMMENT 'Q.tà disp.',
  `qusistop` int(5) DEFAULT NULL COMMENT 'Q.tà util.',
  `created_at` timestamp NULL DEFAULT NULL COMMENT 'Data inserimento',
  `created_by` int(11) DEFAULT NULL COMMENT 'Inserito da',
  `updated_at` timestamp NULL DEFAULT NULL COMMENT 'Data modifica',
  `updated_by` int(11) DEFAULT NULL COMMENT 'Modificato da'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

e dovrei modificarla cosi credo:

CREATE TABLE `sistop` (
  `id_sistop` int(11) NOT NULL,
  `elencosistop` varchar(255) DEFAULT NULL COMMENT 'Sistema Operativo',
  `licsistop` varchar(80) DEFAULT NULL COMMENT 'Licenza Uso',
  `qdsistop` int(5) DEFAULT NULL COMMENT 'Q.tà disp.',
  `qusistop` int(5) DEFAULT NULL COMMENT 'Q.tà util.',
  `qrsistop` int(5) DEFAULT NULL COMMENT 'Q.tà rim.',
  `created_at` timestamp NULL DEFAULT NULL COMMENT 'Data inserimento',
  `created_by` int(11) DEFAULT NULL COMMENT 'Inserito da',
  `updated_at` timestamp NULL DEFAULT NULL COMMENT 'Data modifica',
  `updated_by` int(11) DEFAULT NULL COMMENT 'Modificato da'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Ecco controller,model e view.

app/controllers/SistopController
namespace app\controllers;

use Yii;
use app\models\Sistop;
use app\models\SistopSearch;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;

/**
 * SistopController implements the CRUD actions for Sistop model.
 */
class SistopController extends Controller
{
    /**
     * {@inheritdoc}
     */
    public function behaviors()
    {
        return [
            'verbs' => [
                'class' => VerbFilter::className(),
                'actions' => [
                    'delete' => ['POST'],
                ],
            ],
            'access' => [
                'class' => \yii\filters\AccessControl::className(),
                'rules' => [
                    [
                        'allow' => true,
                        'actions' => ['index', 'view'],
                        'roles' => ['view']
                    ],
                    [
                        'allow' => true,
                        'actions' => ['update'],
                        'roles' => ['update']
                        ],
                    [
                        'allow' => true,
                        'actions' => ['create'],
                        'roles' => ['create']
                    ],
                    [
                        'allow' => true,
                        'actions' => ['delete'],
                        'roles' => ['delete']
                    ],

                    [
                        'allow' => false
                    ]
                ]
            ]
        ];
    }
    /**
     * Lists all Sistop models.
     * @return mixed
     */
    public function actionIndex()
    {
        $searchModel = new SistopSearch();
        $dataProvider = $searchModel->search(Yii::$app->request->queryParams);

        return $this->render('index', [
            'searchModel' => $searchModel,
            'dataProvider' => $dataProvider,
        ]);
    }

    /**
     * Displays a single Sistop model.
     * @param integer $id
     * @return mixed
     * @throws NotFoundHttpException if the model cannot be found
     */
    public function actionView($id)
    {
        return $this->render('view', [
            'model' => $this->findModel($id),
        ]);
    }

    /**
     * Creates a new Sistop model.
     * If creation is successful, the browser will be redirected to the 'view' page.
     * @return mixed
     */
    public function actionCreate()
    {
        $model = new Sistop();

        if ($model->load(Yii::$app->request->post()) && $model->save()) {
            return $this->redirect(['view', 'id' => $model->id_sistop]);
        }

        return $this->render('create', [
            'model' => $model,
        ]);
    }

    /**
     * Updates an existing Sistop model.
     * If update is successful, the browser will be redirected to the 'view' page.
     * @param integer $id
     * @return mixed
     * @throws NotFoundHttpException if the model cannot be found
     */
    public function actionUpdate($id)
    {
        $model = $this->findModel($id);

        if ($model->load(Yii::$app->request->post()) && $model->save()) {
            return $this->redirect(['view', 'id' => $model->id_sistop]);
        }

        return $this->render('update', [
            'model' => $model,
        ]);
    }

    /**
     * Deletes an existing Sistop model.
     * If deletion is successful, the browser will be redirected to the 'index' page.
     * @param integer $id
     * @return mixed
     * @throws NotFoundHttpException if the model cannot be found
     */
    public function actionDelete($id)
    {
        $this->findModel($id)->delete();

        return $this->redirect(['index']);
    }

    /**
     * Finds the Sistop model based on its primary key value.
     * If the model is not found, a 404 HTTP exception will be thrown.
     * @param integer $id
     * @return Sistop the loaded model
     * @throws NotFoundHttpException if the model cannot be found
     */
    protected function findModel($id)
    {
        if (($model = Sistop::findOne($id)) !== null) {
            return $model;
        }

        throw new NotFoundHttpException(Yii::t('app', 'The requested page does not exist.'));
    }
}
app/models/Sistop.php
namespace app\models;

use Da\User\Model\User;
use Yii;
use yii\db\Expression;
use yii\behaviors\TimestampBehavior;
use yii\behaviors\BlameableBehavior;



/**
 * This is the model class for table "{{%sistop}}".
 *
 * @property int $id_sistop
 * @property string|null $elencosistop Sistema Operativo
 * @property string|null $licsistop Licenza Uso
 * @property int|null $qdsistop Q.tà disp.
 * @property int|null $qrsistop Q.tà rim.
 * @property string|null $created_at Data inserimento
 * @property int|null $created_by Inserito da
 * @property string|null $updated_at Data modifica
 * @property int|null $updated_by Modificato da
 *
 * @property Gestionalepc[] $gestionalepcs
 */
class Sistop extends \yii\db\ActiveRecord
{
    /**
     * {@inheritdoc}
     */
    public static function tableName()
    {
        return '{{%sistop}}';
    }

    public function behaviors()
    {
        return [
            [
                'class' => TimestampBehavior::className(),
                'createdAtAttribute' => 'created_at',
                'updatedAtAttribute' => 'updated_at',
                'value' => new Expression('NOW()'),
            ],
            [
                'class' => BlameableBehavior::className(),
                'createdByAttribute' => 'created_by',
                'updatedByAttribute' => 'updated_by',
            ],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function rules()
    {
        return [
            [['qdsistop', 'qusistop'], 'integer'],
            [['elencosistop'], 'string', 'max' => 255],
            [['elencosistop'], 'unique'],
            [['elencosistop'], 'required'],
            [['licsistop'], 'string', 'max' => 80],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function attributeLabels()
    {
        return [
            'id_sistop' => Yii::t('app', 'ID'),
            'elencosistop' => Yii::t('app', 'Sistema Operativo'),
            'licsistop' => Yii::t('app', 'Product Key'),
            'qdsistop' => Yii::t('app', 'Q.tà disp.'),
            'qusistop' => Yii::t('app', 'Q.tà util.'),
            'created_at' => Yii::t('app', 'Data inserimento'),
            'created_by' => Yii::t('app', 'Inserito da'),
            'updated_at' => Yii::t('app', 'Data modifica'),
            'updated_by' => Yii::t('app', 'Modificato da'),
        ];
    }

    public function getCreatore()
    {
        return $this->hasOne(User::className(), ['id' => 'created_by']);
    }

    public function getModificatore()
    {
        return $this->hasOne(User::className(), ['id' => 'updated_by']);
    }

    /**
     * Gets query for [[Gestionepcs]].
     *
     * @return \yii\db\ActiveQuery|GestionalepcQuery
     */
    public function getGestionalepcs()
    {
        return $this->hasMany(Gestionalepc::className(), ['os' => 'id_sistop']);
    }

    /**
     * {@inheritdoc}
     * @return SistopQuery the active query used by this AR class.
     */
    public static function find()
    {
        return new SistopQuery(get_called_class());
    }
}
app/models/SistopSearch.php
namespace app\models;

use yii\base\Model;
use yii\data\ActiveDataProvider;
use app\models\Sistop;

class SistopSearch extends Sistop
{
  
    public function rules()
    {
        return [
            [['id_sistop', 'qdsistop', 'qusistop'], 'integer'],
            [['elencosistop', 'licsistop'], 'safe'],
        ];
    }

    public function scenarios()
    {
        return Model::scenarios();
    }

    public function search($params)
    {
        $query = Sistop::find();
        
        // add conditions that should always apply here
        $dataProvider = new ActiveDataProvider([
            'query' => $query,
        ]);
        
        $this->load($params);
        if (!$this->validate()) {
            return $dataProvider;
        }
        // grid filtering conditions
        $query->andFilterWhere([
            'id_sistop' => $this->id_sistop,
            'qdsistop' => $this->qdsistop,
            'qusistop' => $this->qusistop,
            'created_at' => $this->created_at,
            'created_by' => $this->created_by,
            'updated_at' => $this->updated_at,
            'updated_by' => $this->updated_by,
        ]);
        
        
        $query->andFilterWhere(['like', 'elencosistop', $this->elencosistop])
            ->andFilterWhere(['like', 'licsistop', $this->licsistop]);
        return $dataProvider;
    }
}
app/views/sistop/index.php
use yii\helpers\Html;
use yii\widgets\DetailView;

/* @var $this yii\web\View */
/* @var $model app\models\Sistop */

$this->title = $model->elencosistop;
$this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Elenco Sistemi Operativi'), 'url' => ['index']];
$this->params['breadcrumbs'][] = $this->title;
\yii\web\YiiAsset::register($this);
?>
<div class="sistop-view">

    <h1><?= Html::encode($this->title) ?></h1>

    <p>
        <?= Html::a(Yii::t('app', 'Modifica'), ['update', 'id' => $model->id_sistop], ['class' => 'btn btn-primary']) ?>
        <?= Html::a(Yii::t('app', 'Elimina'), ['delete', 'id' => $model->id_sistop], [
            'class' => 'btn btn-danger',
            'data' => [
                'confirm' => Yii::t('app', 'Are you sure you want to delete this item?'),
                'method' => 'post',
            ],
        ]) ?>
        <?= Html::a(Yii::t('app', 'Aggiungi'), ['create'], ['class' => 'btn btn-success']) ?>
        <?= Html::a(Yii::t('app', 'Elenco Completo'), ['index'], ['class' => 'btn btn-warning']) ?>
    </p>

    <?= DetailView::widget([
        'model' => $model,
        'attributes' => [
            //'id_sistop',
            'elencosistop',
            'licsistop',
            'qdsistop',
            [
                'attribute' => 'qusistop',
                'value' => function ($sistopModel) {
                    return $sistopModel->getGestionalepcs()->count();
                }
            ],
            [
                'header' => 'Totale',
                'value' => function($data) { return $data->qdsistop - $data->qusistop; } 
            ]
            'created_at:datetime',
            'creatore.username',
            'updated_at:datetime',
            'modificatore.username',
        ],
    ]) ?>

</div>

Perché per la colonna disponibile usi la relazione, mentre poi per la Totale usi i campi del modello?

Ciao, la relazione la uso perché è collegata con il form gestionalepc e mi deve contare quanti pc usano un determinato o.s., mentre per il totale non ho pensato ad una relazione sinceramente perché mi sono accorto dopo che avrei avuto bisogno di quel dato e successivamente ho trovato difficoltà proprio perché non saprei come relazionarla e non ho aggiunto la colonna in mysql (‘qrsistop’->quantità rimaste per intenderci). Non so se sono riuscito a illustrare il problema correttamente. Ti ringrazio comunque in anticipo.

Forse non mi sono spiegato io… Se la terza colonna deve essere la differenza tra le due precedenti non lo stai facendo. La prima colonna è qdsistop, la seconda $sistopModel->getGestionalepcs()->count(), ma la terza fa la differenza tra due campi del db, che non sono (evidentemente) quelli che visualizzi.

Si @maxxer ti sei spiegato, nel frattempo ho ricreato la tabella in mysql comprendendo anche la colonna di quantità rimanenti in questo modo:

CREATE TABLE `sistop` (
  `id_sistop` int(11) NOT NULL,
  `elencosistop` varchar(255) DEFAULT NULL COMMENT 'Sistema Operativo',
  `licsistop` varchar(80) DEFAULT NULL COMMENT 'Licenza Uso',
  `qdsistop` int(5) NOT NULL DEFAULT '0' COMMENT 'Q.tà disp.', --> NOT NULL e 0 default
  `qusistop` int(5) NOT NULL DEFAULT '0' COMMENT 'Q.ta util.',  --> NOT NULL e 0 default
  `qrsistop` int(5) NOT NULL DEFAULT '0' COMMENT 'Q.tà rim.',  --> NOT NULL e 0 default
  `created_at` timestamp NULL DEFAULT NULL COMMENT 'Data inserimento',
  `created_by` int(11) DEFAULT NULL COMMENT 'Inserito da',
  `updated_at` timestamp NULL DEFAULT NULL COMMENT 'Data modifica',
  `updated_by` int(11) DEFAULT NULL COMMENT 'Modificato da'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Questo perche’ mi sono accorto che ‘qusistop’ restava NULL nella tabella mysql e ricreando la tabella con quelle modifiche funziona.
Nella view pero’ come dovrei “uniformare” le tre colonne se:

  • ‘qdsistop’ -> la decido io con il form;

  • ‘qusistop’ -> prende il dato da ‘gestionalepc’ dove sul model ho inserito

    public function afterSave($insert, $changedAttributes){
    if($insert){
    $this->sistop->updateCounters([‘qusistop’ => 1]);
    }
    }

e per farlo apparire su app\views\sistop\index.php ho messo la colonna

        [
            'attribute' => 'qusistop',
            'value' => function ($sistopModel) {
                return $sistopModel->getGestionalepcs()->count();
            }
        ],
  • ‘qrsistop’ -> differenza tra ‘qdsistop’ - ‘qrsistop’

Dovrei fare una cosa del genere:

[
    'header' =>  'Q.tà disp',
    'attribute' => 'qdsistop',
],
[
    'attribute' => 'qusistop',
    'value' => function ($sistopModel) {
        return $sistopModel->getGestionalepcs()->count();
    }
],
    [
        'attribute' =>  'qrsistop',
        'value' => 'qdsistop' - 'qusistop',
    ],

Ma restituisce l’errore su ‘qrsistop’

A non-numeric value encountered

Per curiosità ho provato anche creando variabili in questo modo e il risultato non cambia, il valore non numerico che causa l’errore non ci arrivo ancora

<?php
$disp = 'qdsistop';
$util = 'qusistop';
$rim = $disp - $util;

?>
<?= GridView::widget([
    'dataProvider' => $dataProvider,
    'filterModel' => $searchModel,
    'columns' => [
        ['class' => 'yii\grid\SerialColumn'],

        //'id_sistop',
        'elencosistop',
        'licsistop',
        [
            'header' =>  'Q.tà disp',
            'attribute' => 'qdsistop',
        ],
        [
            'attribute' => 'qusistop',
            'value' => function ($sistopModel) {
                return $sistopModel->getGestionalepcs()->count();
            }
        ],
        [
            'attribute' =>  'qrsistop',
            'value' => $rim,
        ],
        //'created_at:datetime',
        //[
        //    'attribute' => 'created_by',
        //    'value' => 'creatore.username',
        //],
        //'updated_at:datetime',
        // [
        //    'attribute' => 'updated_by',
        //    'value' => 'modificatore.username',
        //],
        ['class' => 'yii\grid\ActionColumn'],
    ],
]); ?>

Non so se ho capito male io la tua esigenza ma la colonna calcolata dovrebbe essere $data->qdsistop - $data->getGestionalepcs()->count()

Si @maxxer l’esigenza è quella, ma non mi riconosce la variabile $data

<?= GridView::widget([
    'dataProvider' => $dataProvider,
    'filterModel' => $searchModel,
    'columns' => [
        ['class' => 'yii\grid\SerialColumn'],

        //'id_sistop',
        'elencosistop',
        'licsistop',
        'qdsistop',
        [
            'attribute' => 'qusistop',
            'value' => function ($sistopModel) {
                return $sistopModel->getGestionalepcs()->count();
            }
        ],
        [
            'attribute' =>  'qrsistop',
            'value' => $data->qdsistop - $data->getGestionalepcs()->count()
        ],
        //'created_at:datetime',
        //[
        //    'attribute' => 'created_by',
        //    'value' => 'creatore.username',
        //],
        //'updated_at:datetime',
        // [
        //    'attribute' => 'updated_by',
        //    'value' => 'modificatore.username',
        //],
        ['class' => 'yii\grid\ActionColumn'],
    ],
]); ?>

Se commento

//‘value’ => $data->qdsistop - $data->getGestionalepcs()->count()

Q.tà disp. e Q.tà util. funzionano perfettamente sia se aggiungo un altra riga su gestionalepc o ne elimini una e anche in mysql la tabelle riporta fedelmente come da immagine della view. La Q.tà rim. non funziona e se la decommento mi restituisce l’errore sulla variabile $data.

$data dipende dal nome della variabile che dai nella funzione anonima…

function($data) { ... }

Grazie @maxxer ho risolto cosi:

    [
        'attribute' =>  'qrsistop',
        'value' => function ($data) {return $data->qdsistop - $data->getGestionalepcs()->count();
         }
    ],

e funziona sulla view, ma nella colonna mysql ‘qrsistop’ e ‘qusistop’ non si modifica.

Certo.

Ma se hai già i dati sul database in locale perché vuoi aggiornare il numero in questa tabella? Non ti conviene prenderlo sempre dalla relazione?

Si d’accordo, ti mostro la differenza attuale tra la view e la tabella sql che non si aggiorna.

Anche se cambio O.S. dal form ‘gestionalepc’ in mysql non si modificano i valori di qusistop e qrsistop e se cancello tutti i record dal form ‘gestionalepc’ gli stessi valori non si azzerano nella tabella mysql.
la parte view funziona cmq perfettamente se aggiungo o sottraggo e per questo ti ringrazio.

Ho capito che le due colonne non sono necessarie su mysql perché sono calcolate. Grazie @maxxer @fabriziocaldarelli