How can I update related models in yii 2 - yii2

I have 3 tables to manage my stock.
Items_table (item_id,label,stock...) column stock has 0 default value.
orders_table (order_id,at_date,status) column status has 0 default value.
orders_items_table(orderitem_id,order_id,item_id,quantity) manage orders details.
what I want to do is to update order 'status' 1 and update Items 'stock' with quantity from orders_items_table by concerned item_id.
here is my actionValidate
public function actionValidate($id)
{
$model = $this->findModel($id);
$query = new purchases::find()->where('purchase_id' = :id);
echo $query->createCommand()->sql;
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $model->purchase_id]);
}
return $this->render('validate', [
'model' => $model,
]);
}

So you should end up with three models.
Step 1: create three ActiveRecord model I will create one for you only
class Order extends \yii\db\ActiveRecord
{
public static function tableName()
{
return 'orders';
}
public function rules()
{
return [
[['id','status'], 'integer'],
];
}
public function attributeLabels()
{
return [
'id' => Yii::t('app', 'Order Id'),
'status' => Yii::t('app', 'Order Status'),
'created_at' => Yii::t('app', 'Order Date'),
];
}
public function getOrderItems()
{
return $this->hasMany(OrderItem::className(), ['order_id' => 'id']);
}
public function getItems()
{
return $this->hasMany(Item::className(), ['id' => 'item_id'])
->via('orderItems');
}
}
Now you need to create OrderItem model, OrderItem hasOne Order and hasOne Item. After that create Item model that have many OrderItem and Orders.
Step 2: create your controller and the following
$order = Order::findOne($order_id);
if( ! is_null($order) ){
//we found the order
foreach ($order->orderItems as $orderItem) {
//get the item
$item = $orderItem->item;
$item->stock = $item->stock - $orderItem->quantity;
//save the item
$item->save();
}
//update the order
$order->status = 1;
$order->save();
}
That code just to show you how to do the logic don't forget you need to do validation and data integrity.
see more Active Record

Related

YII2 Call to a member function isAttributeRequired() on a non-object

I am new with Yii2, im trying to make sales transaction, but i have problem for dropdownlist in detail section.
for this module, i have 3 tables :
1. for header, table salesheader
2. for detail, table salesdetail
3. for item, table item
this is my code :
on controller :
public function actionUpdate($id)
{
$model = $this->findModel($id); //find appropriate record in salesheader
//$modeldetail = salesdetail::findModel($id);
$modeldetail = new salesdetail();
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['index']);
} else {
$item = item::find()->select(["concat('[',code,'] - ', name) as item", "id"])->indexBy('id')->column();
$detail = salesdetail::find()->select(["*"])->all(); //find appropriate record in salesdetail
return $this->render('update', [
'model' => $model,
'modeldetail' => $modeldetail,
'item' => $item,
'detail' => $detail
]);
}
}
on salesheader model
namespace app\models;
use Yii;
class salesheader extends \yii\db\ActiveRecord
{
public static function tableName()
{
return 'salesheader';
}
public function rules()
{
return [
[['partnerId', 'date', 'term', 'name'], 'required'],
[['name', 'invNumber'], 'string'],
[['partnerId', 'term'], 'integer']
];
}
public function attributeLabels()
{
return [
'partnerId' => 'Customer',
'date' => 'Date',
'invNumber' => 'Inv. Number',
'term' => 'Term',
'name' => 'Name'
];
}
on salesdetail model
namespace app\models;
use Yii;
class salesdetail extends \yii\db\ActiveRecord
{
public static function tableName()
{
return 'salesdetail';
}
public function rules()
{
return [
[['itemId', 'qty', 'price'], 'required'],
[['unit'], 'string']
];
}
/**
* #inheritdoc
*/
public function attributeLabels()
{
return [
'itemId' => 'Item',
];
}
}
on my view
<?= $form->field($detail, 'itemId')->dropDownList($item)->label(false) ?>
and when open the page in browser, i got this error "Call to a member function isAttributeRequired() on a non-object"
and if i change this line on controller
//$modeldetail = salesdetail::findModel($id);
$modeldetail = new salesdetail();
to this one
$modeldetail = salesdetail::findModel($id);
//$modeldetail = new salesdetail();
then the error would be "Call to undefined method app\models\salesdetail::findModel()"
can somebody tell me what am i doing wrong ?
and how to get appropriate data from salesdetail based on salesheader and put it on dropdownlist ?
Thank you for ur help

How to save current user_id to database (YII2)

I try to save current user_id to the education table in database. However,the data of user_id is not filled. This is my code.
At model
public function rules()
{
return [
[['year', 'fieldstudy', 'institute', 'grade'], 'required'],
[['user_id'], 'integer'],
[['year', 'fieldstudy', 'institute', 'grade'], 'string', 'max' => 255],
];
}
public function attributeLabels()
{
return [
'education_id' => 'Education ID',
'user_id' => 'User ID',
'year' => 'Year',
'fieldstudy' => 'Fieldstudy',
'institute' => 'Institute',
'grade' => 'Grade',
];
}
public function getUser()
{
return $this->hasOne(User::className(), ['user_id' => 'user_id']);
}
At controller
public function actionCreate()
{
$model = new Education();
$model->user_id =Yii::$app->user->id;
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $model->education_id]);
} else {
return $this->render('create', [
'model' => $model,
]);
}
}
How can I solve my problem and fix my code? Thanks
update Yii::$app->user->id to Yii::$app->user->identity->id.
public function actionCreate()
{
$model = new Education();
if ($model->load(Yii::$app->request->post())) {
$model->user_id =Yii::$app->user->identity->id;
if($model->save()){
return $this->redirect(['view', 'id' => $model->education_id]);
}
}
return $this->render('create', [
'model' => $model,
]);
}
You have to check two things
Check whether the user is logged in.
Use Yii2 debugger to see whether we are getting the id value of the logged in user by the code Yii::$app->user->id or Yii::$app->user->id
Use Yii2 debugger to check whether the correct user id value we are getting by using the code
Yii::info("User id=".Yii::$app->user->id);
Full code you have to try in the controller is given below
public function actionCreate() {
$model = new Education();
//checking whether we are getting the logged in user id value
Yii::info("User id=".Yii::$app->user->id);
$model->user_id = Yii::$app->user->id;
if ($model->load(Yii::$app->request->post()) && $model->save()) {
//checking here the saved user id value in table
Yii::info("checking User id after saving model=".$model->user_id);
return $this->redirect(['view', 'id' => $model->education_id]);
} else {
return $this->render('create', [
'model' => $model,
]);
}
}
Now after running the application you can check using Yii2 debugger the values that are set in the user id in various places.

Yii2 remove unique validator

I have an AR model, it has the following rules:
/**
* #inheritdoc
*/
public function rules() {
return [
[['category_id', 'source', 'url', 'title', 'thumbs', 'duration', 'status', 'created_at'], 'required'],
[['category_id', 'status', 'views', 'ratings', 'created_at'], 'integer'],
[['rating'], 'double'],
[['source', 'url', 'title', 'slug'], 'string', 'max' => 255],
[['url'], 'unique', 'on' => 'create'],
[['category_id'], 'exist', 'skipOnError' => true, 'targetClass' => Category::className(), 'targetAttribute' => ['category_id' => 'id']],
];
}
I want to do a soft delete so I have the following.
/**
* Deletes an existing Video model.
* If deletion is successful, the browser will be redirected to the 'index' page.
* #param integer $id
* #return mixed
*/
public function actionDelete($id)
{
$model = $this->findModel($id);
$model->status = 0;
//var_dump($model->validate());
//var_dump($model->getErrors());die;
$model->save();
return $this->redirect(['index']);
}
But unfortunately I cannot change the status of model, because the validation says that (The url xxxxxxx has been taken) so I went to the PostgreSql, and I checked the records, but unfortunately only the updating record has this value! So in my mind the Yii2 unique validatios is bad. I would like to remove the unique validator, but it seems it is impossible. Because I commented out the uniqure row in the rule array, but it did not help me. I restarted the machine, but I do not know, it seems Yii2 want always check the url is unique or not.
You can use scenario
public function scenarios()
{
$scenarios = parent::scenarios();
$scenarios['soft_delete'] = ['status',]; //Scenario Values Only Accepted
return $scenarios;
}
public function actionDelete($id)
{
$model = $this->findModel($id);
$model->status = 0;
$model->scenario = 'soft_delete';
//var_dump($model->validate());
//var_dump($model->getErrors());die;
$model->save();
return $this->redirect(['index']);
}
or another way is suppress validation for this action
public function actionDelete($id)
{
$model = $this->findModel($id);
$model->status = 0;
//var_dump($model->validate());
//var_dump($model->getErrors());die;
$model->save(false);
return $this->redirect(['index']);
}

Yii2: What is the correct way to define relationships among multiple tables?

In a controller I have the following code:
public function actionView($id)
{
$query = new Query;
$query->select('*')
->from('table_1 t1')
->innerJoin('table_2 t2', 't2.t1_id = t1.id')
->innerJoin('table_3 t3', 't2.t3_id = t3.id')
->innerJoin('table_4 t4', 't3.t4_id = t4.id')
->andWhere('t1.id = ' . $id);
$rows = $query->all();
return $this->render('view', [
'model' => $this->findModel($id),
'rows' => $rows,
]);
}
See the db schema: https://github.com/AntoninSlejska/yii-test/blob/master/example/sql/example-schema.png
In the view view.php are displayed data from tables_2-4, which are related to table_1:
foreach($rows as $row) {
echo $row['t2_field_1'];
echo $row['t2_field_2'];
...
}
See: Yii2 innerJoin()
and: http://www.yiiframework.com/doc-2.0/yii-db-query.html
It works, but I'm not sure, if it is the most correct Yii2's way.
I tried to define the relations in the model TableOne:
public function getTableTwoRecords()
{
return $this->hasMany(TableTwo::className(), ['t1_id' => 'id']);
}
public function getTableThreeRecords()
{
return $this->hasMany(TableThree::className(), ['id' => 't3_id'])
->via('tableTwoRecords');
}
public function getTableFourRecords()
{
return $this->hasMany(TableFour::className(), ['id' => 't4_id'])
->via('tableThreeRecords');
}
and then to join the records in the controller TableOneController:
$records = TableOne::find()
->innerJoinWith(['tableTwoRecords'])
->innerJoinWith(['tableThreeRecords'])
->innerJoinWith(['tableFourRecords'])
->all();
but it doesn't work. If I join only the first three tables, then it works. If I add the fourth table, then I receive the following error message: "Getting unknown property: frontend\models\TableOne::t3_id"
If I change the function getTableFourRecords() in this way:
public function getTableFourRecords()
{
return $this->hasOne(TableThree::className(), ['t4_id' => 'id']);
}
then I receive this error message: "SQLSTATE[42S22]: Column not found: 1054 Unknown column 'table_4.t4_id' in 'on clause'
The SQL being executed was: SELECT table_1.* FROM table_1 INNER JOIN table_2 ON table_1.id = table_2.t1_id INNER JOIN table_3 ON table_2.t3_id = table_3.id INNER JOIN table_4 ON table_1.id = table_4.t4_id"
You should have to define key value pair in the relation eg:
class Customer extends ActiveRecord
{
public function getOrders()
{
return $this->hasMany(Order::className(), ['customer_id' => 'id']); // Always KEY => VALUE pair this relation relate to hasMany relation
}
}
class Order extends ActiveRecord
{
public function getCustomer()
{
return $this->hasOne(Customer::className(), ['id' => 'customer_id']);
// Always KEY => VALUE pair this relation relate to hasOne relation
}
}
Now in your forth relation use:
public function getTableFourRecords()
{
return $this->hasOne(TableThree::className(), ['id' => 't4_id']);
}
You can read more on ActiveRecord here
Based on the answer of softark the simplest solution can look like this:
Model TableOne:
public function getTableTwoRecords()
{
return $this->hasMany(TableTwo::className(), ['t1_id' => 'id']);
}
Model TableTwo:
public function getTableThreeRecord()
{
return $this->hasOne(TableThree::className(), ['id' => 't3_id']);
}
Model TableThree:
public function getTableFourRecord()
{
return $this->hasOne(TableFour::className(), ['id' => 't4_id']);
}
Controller TableOneController:
public function actionView($id)
{
return $this->render('view', [
'model' => $this->findModel($id),
]);
}
The view table-one/view.php:
foreach ($model->tableTwoRecords as $record) {
echo ' Table 2 >> ';
echo ' ID: ' . $record->id;
echo ' T1 ID: ' . $record->t1_id;
echo ' T3 ID: ' . $record->t3_id;
echo ' Table 3 >> ';
echo ' ID: ' . $record->tableThreeRecord->id;
echo ' T4 ID: ' . $record->tableThreeRecord->t4_id;
echo ' Table 4 >> ';
echo ' ID: ' . $record->tableThreeRecord->tableFourRecord->id;
echo ' <br>';
}
A solution based on the GridView is also possible.
Model TableTwo:
public function getTableOneRecord()
{
return $this->hasOne(TableOne::className(), ['id' => 't1_id']);
}
public function getTableThreeRecord()
{
return $this->hasOne(TableThree::className(), ['id' => 't3_id']);
}
public function getTableFourRecord()
{
return $this->hasOne(TableFour::className(), ['id' => 't4_id'])
->via('tableThreeRecord');
}
The function actionView in TableOneController, which was generated with Gii for the model TableTwo was edited:
use app\models\TableTwo;
use app\models\TableTwoSearch;
...
public function actionView($id)
{
$searchModel = new TableTwoSearch([
't1_id' => $id, // the data have to be filtered by the id of the displayed record
]);
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
return $this->render('view', [
'model' => $this->findModel($id),
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
]);
}
and also the views/table-one/view.php:
echo GridView::widget([
'dataProvider' => $dataProvider,
'columns' => [
'id',
't1_id',
'tableOneRecord.id',
't3_id',
'tableThreeRecord.id',
'tableThreeRecord.t4_id',
'tableFourRecord.id',
],
]);
See the code on Github.

moving a row to another table but specific columns using active record

What should be my model function structure if I delete an entire row from a table and insert two column values of the row to another table? I want to use active record.
function foo()
{
$this->db->truncate('to');
$query = $this->db->get('from')->result(); // get first table
foreach($query as $row) // loop over results
{
$this->db->insert('to', $row); // insert each row to another table
}
}
this is moving the entire row. I just want two specific column. What should I do?
Edit
Is it the right way?
public function refund()
{
$id = $this->uri->segment(4);
$data['accounts'] = $this->accounts_model->get_accounts_data_by_id($id);
foreach ($accounts as $row)
{
$data = array(
'barcode' => $row->barcode,
'refunded_amount' => $row->refunded_amount,
);
}
$this->db-insert('refund',$data);
}
Controller:
class Controllername extends CI_Controller
{
public function __construct()
{
parent::__construct();
$this->load->model('your_model');
}
public function somename()
{
$id = 6; // Row id to delete
$this->your_model->delete_table1($id);
$data = array(
array(
'col1' => 'val1',
'col2' => 'val2',
'col3' => 'val3',
),
array(
'col1' => 'val1',
'col2' => 'val2',
'col3' => 'val3',
),
);
$this->your_model->insert_table2($data);
}
}
Model:
class Your_model extends CI_Model
{
public function __construct()
{
parent::__construct();
$this->db = $this->load->database('default',true);
}
public function delete_table1($id)
{
$this->db->delete('table1', array('id' => $id));
}
public function insert_table2($data)
{
$this->db->insert_batch('table2', $data);
}
}
Explanation:
1) Created a function in which you call a model function provided with the $id parameter of the row to be deleted from table1.
2) The second model function called inserts 2 rows to table2 provided the array as argument and used insert_batch() active record functionality.
3) Before doing the above tasks, don't forget to load the model in the Controller constructer.
Inside the loop :
$insert = array(
"column_name_1" => $row->wanted_column_1,
"column_name_2" => $row->wanted_column_2
);
$this->db->insert('to', $insert);
EDIT :
$accounts is not defined in your function.
try instead :
foreach ($data['accounts'] as $row)
{
$data = array(
'barcode' => $row->barcode,
'refunded_amount' => $row->refunded_amount
);
$this->db->insert('refund',$data);
}