Verification of payments and ceiling of trainings in Laravel - php

My goal is that when I encode a student for a payment. He (student) can follow 2 trainings per payment. (this is the ceiling - 1 payment = 2 trainings)
In my form Payment, I encode a student for example Menier.
The student is entitled to two trainings, (this is the ceiling)
In my form Training: I encode 2 trainings for the student Menier.
My first question: how can I block the number of trainings by two?
(so, if I encode another training, it must block!)
My second queston, if I encode 2 payments for the same student. The student is entitled to 4 trainings. How to create this type of algorithm?
Here is my code for now, I know it's not a lot...
Edit 05-10-2019 - Controller Training
public function store(Request $request)
{
$request->validate([
'date_seance' => 'required',
'hour_start' => 'required',
'hour_end' => 'required',
'fk_motorbike' => 'required',
'fk_former' => 'required',
'fk_student' => 'required',
'fk_typeseance' => 'required'
]);
$date_seance = $request->get('date_seance');
$hour_start = $request->get('hour_start');
$hour_end = $request->get('hour_end');
$fk_motorbike = $request->get('fk_motorbike');
$fk_student = $request->get('fk_student');
$fk_former = $request->get('fk_former');
$fk_typeseance = $request->get('fk_typeseance');
$payments = Payment::where('fk_student', $request->get('fk_student'))->first();
if(!isset($payments)){
return redirect()->route('trainings.index')
->with('error', 'No payment, no training! ');
}
$thisStudentsTrainings = Training::where('fk_student', $fk_student)->get();
if(count($thisStudentsTrainings) >= 2){
return redirect()->route('trainings.index')
->with('error', 'The ceiling is 2 trainings! ');
}
$thisStudentsPayments = Payment::where('fk_student', $request->get('fk_student'))->get();
if( (count($thisStudentsPayments) * 2) < count($thisStudentsTrainings) ) {
return redirect()->route('trainings.index')
->with('error', 'test!');
}
else{
Training::create($request->all());
return redirect()->route('trainings.index')
->with('success', 'Add');
}
}
Do you have an idea of how I could solve my problems, I am still a beginner in laravel.
For Watercayman
Thank you for your time.

This is not too bad to do. Since the payment is not directly associated with a specific training (ie you have a credit system), you can do this pretty easily with a couple of queries.
My first question: how can I block the number of trainings by two?
Start with the basics and find the number of trainings in the database for this student:
$thisStudentsTrainings = Training::where('fk_student', $fk_student)->get();
Or you can come in from the reverse for this simply query:
$student = Student::with('trainings')->get();
$thisStudentsTrainings = $student->trainings;
Then, to limit to two trainings (without payment consideration yet):
if(count($thisStudentsTrainings) >= 2){ too many trainings }
Now that you have a count of trainings, if you also want to make sure they have a payment in the system, lets get the payments:
$thisStudentsPayments = Payment::where('fk_student', $request->get('fk_student'))->get();
To check if they have paid for trainings, you now have both pieces of data that you need. You just have to figure out if they have paid for the right amount of trainings based on 2 payments = 1 training. So:
if( (count($thisStudentsPayments) * 2) < count($thisStudentsTrainings) ) {
// They have not made enough payments!
}
My second queston, if I encode 2 payments for the same student. The student is entitled to 4 trainings. How to create this type of algorithm?
The above will work for 2 or 4 or whatever you want.
Now, if you want to enforce a max of 2 trainings per each payment, we can check on this too. BUT, this is starting to get a little complex or circular in the logic. If you can avoid this, it will be a lot easier. But, let's check on the max of 2 per payment, which is just the adding an equals check, AFTER the one above:
if( (count($thisStudentsTrainings) >= count($thisStudentsPayments) * 2) {
// They are at their limit of trainings based on their payments!
// Note we include >= so that if they have purchased 2 trainings,
// this blocks them from a 3rd until they pay again.
}
This should solve your issue. However, you didn't ask, but I assume you don't want a student to allow a training if they have already used up a payment. IE if they've taken a training and they have 'spent their credit', they should not be allowed to take the training. If this is important to you, I suggest that in another part of your program, you write to the database when a payment has been consumed. So - if a student uses 2 trainings and has paid for them, maybe a boolean field on the Payment model spent (or something to indicate the payment is no longer valid). You could also remove the payment from the system if you don't need historical data. But, assuming you do, and you use $payment->spent, you can still do the above algorithm, just add the spent line to the query something like:
$student = Student::with(['trainings' => function($query){
$query->where('spent', 0)
}])->get();
Then all the rest should be the same. This isn't cut & paste, but I think now that you have separated out payments and trainings, this should be a pretty easy solve to understand based on the above. :)

Related

Parallel transactions on database lead to incorrect result, laravel

My application is implemented with php/laravel and MySQL database. In this system, users can deposit money to wallet. I have a wallet_balance column in the users table. When users deposit funds into their wallet, I update that column as follows:
public function topup(Request $request)
{
$user = auth()->user();
$deposit_amount = $request->deposit_amount;
//e.g.
//$deposit_amount = 100
//$user->wallet_balance = 50
$user->wallet_balance = $user->wallet_balance + $deposit_amount;
$user->save();
// After saving I expect $user->wallet_balance to be 150 which works perfectly.
}
There are some services that users are charged where money is deducted from their wallet (in another function). For example:
public function chargeService(Request $request)
{
$user = User::findOrFail($request->user_id);
$service_fee = $request->service_fee;
//$service_fee = 30
//$user->wallet_balance = 150
$user->wallet_balance = $user->wallet_balance - $service_fee;
$user->save();
// After saving I expect $user->wallet_balance to be 120 which works perfectly.
}
After the two transactions, the user's wallet balance should be 120. However, on very rare cases the two transactions might happen concurrently. That means in the deposit transaction, the initial wallet balance is 50 and also for the service fee transaction the initial wallet balance is 50 (because they queried the database at the same time before any of the two updated the wallet balance). Here is the danger. The first transaction will have the resulting wallet balance as 150 (50 + 100), and the second will have it as 20 (50 - 30). This leaves the wallet balance of the user as either 150 or 20, depending on which operation updates the user's wallet balance last.
Now, my question is, how can I approach my wallet system so as to avoid this stinking issue. I will appreciate so much guys.
You can suggest a better way to phrase the question.
The main issue you're facing is that the User state that is loaded in PHP doesn't reflect what's in your database. You should either execute a query against the database:
$params = ['fee' => 30, 'user_id' => $request->user_id];
DB::statement('UPDATE users SET wallet_balance=wallet_balance-:fee WHERE id=:user_id', $params);
or lock the record for updating:
try {
DB::beginTransaction();
// it is important to get the state from the
// database at the time of locking
$user = User::query()->lockForUpdate()->findOrFail($request->user_id);
$user->wallet_balance = $user->wallet_balance - $service_fee;
$user->save();
DB::commit();
} catch (Throwable $e) {
DB::rollBack();
}
You have to lock the row of table using raw mysql query and i dont know if eloquent has some features for that type of query but you can dig in.
Have a look at this article to get a clear picture https://medium.com/#ibraheemabukaff/locking-rows-in-mysql-e84fd3bbb8cd

How can I customize the filter of the product grid on the admin side in order to filter by RANGE?

Hope you are all good and in great shape.
TLDR: How can I filter my product grid on the admin side using a WEIGHT RANGE?
I am a newbie in Magento and I have been doing some progress over the last months on the basics of customizing Magento to our small company. I had a request from the sales team which I am struggling to get to term. We sell a kind of product which has a different weight to every item and the final price of the product is calculated by multiplying a variable rate by the weight. It makes the weight a very important attribute in the system not only for the sales/logistics, but also for the client's decision making. Therefore, I need to filter the product catalog grid on the admin side by weight, but not by a specific number, instead, I need a RANGE of weight. (From ...kg to ...kg) I managed to create the field, but I am struggling on the part were the action happens. Please take a look on the screenshots below:
Weight range in filters
Weight in columns
This is the structure of the module that I created:
module structure
And here is the code on the file AddWeightFieldToCollection.php which I am struggling with:
<?php
namespace Diamwill\WeightFilter\Ui\DataProvider\Product;
use Magento\Framework\Data\Collection;
use Magento\Ui\DataProvider\AddFieldToCollectionInterface;
class AddWeightFieldToCollection implements AddFieldToCollectionInterface
{
public function addField(Collection $collection, $field, $alias = null)
{
$mg_eav_attribute = $collection->getResource()->getTable('mg_eav_attribute');
$mg_catalog_product_entity_decimal = $collection->getResource()->getTable('mg_catalog_product_entity_decimal');
$collection->getSelect()->joinLeft(
['soa' => $mg_eav_attribute],
'soa.entity_type_id = 4 AND soa.attribute_code = \'weight\'',
null
);
$collection->getSelect()->joinLeft(
['dcrt' => $mg_catalog_product_entity_decimal],
'soa.attribute_id = dcrt.attribute_id',
['code']
);
$collection->joinField(
'weight_range',
'mg_catalog_product_entity',
'value',
'mg_catalog_product_entity.entity_id = soa.entity_id AND soa.store_id = 0',
null,
'left'
);
}
}
Basically, I know that I have to join the product tables to get weight, which in SQL terms would be something like this:
SELECT
w1.value AS 'weight'
FROM
mg_catalog_product_entity AS prod
LEFT JOIN
mg_catalog_product_entity_decimal w1 ON
prod.entity_id = w1.entity_id
AND w1.store_id = 0
AND w1.attribute_id = (SELECT
attribute_id
FROM
mg_eav_attribute
WHERE
attribute_code = 'weight'
AND entity_type_id = (SELECT
entity_type_id
FROM
mg_eav_entity_type
WHERE
entity_type_code = 'catalog_product'));
Which I am not capable of linking with the Collection class and apply to the column, in order to filter. Can someone help me with the code? I am happy to read a tutorial to learn or to get a certain book/article with more information on the topic.
Please, I do not want to buy a plugin/module in order to do that. I would like to learn how to do it.
Thanks to anyone who can help me.
Have a great day!

Codeigniter Unilevel MLM earning Distribution

I am here to have some help from you.
I am making a Unilevel MLM using Codeigniter
and now I can sucessfully add new member
But the problem is I need to distribute the earnings to other level
after a new member is successfully Added
See pic below:
Distribution of earnings
I need to distribute like the image above.
I hope you can help me with this guys.
Okay, I have a solution for you. The process i used is based on my understanding of the question.
So this is it, first i checked for a registration post, if a post request is made, i use the referral id from the post to fetch the number of registrations tied to that referral id that has not been given awarded the 100 earning. If the count of the result of this query is equal to 4, i loop through all of them and give them the earning of 100 and update their paid status to reflect that they have been paid then i insert the record, else i just insert the record.
So too much text, lets see the code
//this is the controller code
//first check for post of registration
if($_POST){
//kindly do your form validation here
$register = array(
"name" => $this->input->post('name'),
"refid" => $this->input->post('refID')
);
//during post, get the referral id from the post
$refID = $this->input->post('refID');
//before registering, use referral id to get the referred that have not been given earnings yet
$thereffered = $this->referral_m->getReferred($refID);
//check for the number of registration
if(count($thereffered) == 4){
//then get their ids and give them their earnings and update them to paid
foreach($thereffered as $referred){
$earnings = array(
"userID" => $referred->id,
"amount" => 100
);
$paid = array(
"paid" => 1
);
//give earning
$this->referral_m->giveEarning($earnings); //this adds the user id to earning table and give it an amount of 100
$this->referral_m->updateUser($paid, $referred->id); //this updates the user with the paid status
}
//then proceed to register the new record
$this->referral_m->register($register);
}else{
//register the new record
$this->referral_m->register($register);
}
//redirect after registration
redirect();
}else{
//load view here
}
This is how the model looks like
function getReferred($refID){
return $this->db->get_where('referral', array("refid" => $refID, "paid" => '0'))->result();
}
function giveEarning($record){
$this->db->insert('earnings', $record);
}
function register($array){
$this->db->insert('referral', $array);
}
function updateUser($array, $id){
$this->db->where('id', $id);
$this->db->update('referral', $array);
}
From the model, you would discover that i created 2 database tables, I assume you already have those tables created, just use the logic to update your code. If you find any difficulty, kindly comment lets sort it out

Laravel 4.2 - storing in database is very slow for large amount of data

I'm building an application that handles several thousand apartments per month.
This means several inserts, updates and selects in different tables per apartment per month.
I've used in-built laravel/eloquent syntax, nothing in raw sql, as Im not familiar with it very well.
I have a button that generates the info for a month for all apartments. I wasnt very pleased of having to put all that in one action but it was required of me.
Now, every time I click the button and generate the info it gets harder and harder to process as the database fills each month. I assume its normal considering the situation. Could I get an advice on how to optimize this? I have to wait like 30 seconds - 1 minute for it to finish.... which is not ok...
This is how my controller basically runs (this is the main storing function, which calls several other functions):
// define month
$month = '01-04-2017';
// identify all payment categories (like water, electricity, maintenance etc)
// all these must be treated separately as they are calculated in very different ways
$payment_categories = PaymentCategories::get();
$payments_array = [];
foreach($payment_categories as $payment_category){
$total = Transactions::where('month', $month)->where('payment_category_id', $payment_category->id)->sum('amount');
$payments_array[$payment_category->id] = $total;
}
$apartments = Apartments::get();
foreach($apartaments as $apartment){
$this->storeMainData($apartment);
// each of these categories must be treates separately as they are calculated in very different ways
foreach($payment_categories as $payment_category){
$total_amount = $payments_array[$payment_category->id];
if($payment_category->id == '1'){
$this->storePaymentCategory1($apartment, $payment_category, $month, $total_amount);
}
// there are aprox. 10 categories of payments which dont have a common pattern so they must be treated separately
// these functions below work similarly to storePaymentCategory1()
if($payment_category->id == '2'){
$this->storePaymentCategory2($apartment, $payment_category, $month, $total_amount);
}
if($payment_category->id == '3'){
$this->storePaymentCategory3($apartment, $payment_category, $month, $total_amount);
}
if($payment_category->id == '4'){
$this->storePaymentCategory4($apartment, $payment_category, $month, $total_amount);
}
// ...
// NEXT I HAVE A FUNCTION FOR GENERATING THE PDF RECEIPT
// NEXT I HAVE ANOTHER FUNCTION TO GENERATE THE INVOICE, IF THE APARTMENT REQUIRES ONE
}
}
Next I have the storeMainData() function:
$validator = Validator::make(Input::all(), MainData::$rules, MainData::$messages);
if($validator->passes()){
$check = MainData::where('month', $month)->where('apartament_id', $apartament->id)->first();
if(count($check)>0){
$store_main_data = $check;
}
else{
$store_main_data = new MainData;
}
//main data stuff (like apartment receipt data etc...)
$store_main_data->save();
}
else{
return Redirect::back()
->withErrors($validator);
}
Next I have the storePaymentCategory1() function (which is an example of how other payment categories work as well):
// NOW WE MUST APPLY: total amount stored in the payment category / apartment percentage = due payment for the apartment;
// identify the percentage
$percentage_type = $payment_category->percentage_id;
$percentage = Percentages::where('percentage_type_id', $percentage_type)->where('apartment_id', $apartment->id)->first();
// verify if the percentage type is applied to this apartment
$calculated_apartments = Percentages::where('percentage_type_id', $percentage_type)->get();
$calculated_apartments_array = [];
foreach($calculated_apartments as $calculated_apartment){
array_push($calculated_apartments_array, $calculated_apartment->apartment_id);
}
if(in_array($apartment->id, $calculated_apartments_array)){
$validator = Validator::make(Input::all(), DuePayments::$rules, DuePayments::$messages);
if($validator->passes()){
$check = DuePayments::where('month', $month)->where('apartment_id', $apartment->id)->where('payment_category_id', $payment_category->id)->first();
if(count($check)>0){
$store_due_payment = $check;
}
else{
$store_due_payment = new DuePayments;
}
// store due payment stuff
$store_due_payment->save();
}
else{
return Redirect::back()
->withErrors($validator);
}
}

Build vote system using Laravel 5

I'm trying to build voting system for my web page (Laravel 5). Now, any user can vote multiple times, I want to fix it.
Here is my code in php:
public function addVote()
{
$bikepoint = $this->bikepoint->find($id);
$userid = \Sentry::getUser()->id;
$votesid = \DB::table('bikepoints')->select('votesuid')->where('votesuid', 'LIKE' , $userid)->get();
if (...) {
$bikepoint->votes = $bikepoint->votes + 1;
$bikepoint->votesuid = $bikepoint->votesuid . $userid . '; ';
echo 'Success!';
}
else {
echo 'Fail!';
}
$bikepoint->save();
}
My DB table:
id title votes votesuid
1 point1 2 93; 22;
2 point2 3 92; 28; 47;
3 point3 45 ...
4 point4 32 ...
5 point5 12 ...
So when user click the "Add vote" button, a function adds its ID to the votesuid field and one vote to the votes field.
I want to check if the user has already voted by checking if his ID is placed in the votesuid field. And, if so, the user cannot vote anymore.
For example, users with IDs 92, 28 and 47 should not be able to vote another time.
As suggested by others in the comments, yes, you do need to change your system so that each vote has it's own record on a table.
So create a Votes table and a Laravel model class for it, and give your Users model class a hasMany() relationship to Votes.
That's actually not too much work, is it?
But now then the solution to your question becomes trivially easy -- ie:
$votes = Sentry::getUser()->votes;
As things stand, you would need to do sub-string LIKE queries, which are much harder to write, and will also give you major performance problems when your DB starts getting bigger, so yes, you definitely need to be refactoring it now.

Categories