Thanks in advance for any help. I have an Invoice Model which has a one-to-many relationship with the payment model and when I loop through an invoice's payments to add all the $payment->net and subtract it from the $invoice->cost to see the balance that is left. The payment that was just made in the same call doesn't appear in $invoice->payments, it feels like its cached.
Invoice.php
public function payments()
{
return $this->hasMany('App\Payment');
}
public function net() :float
{
$payments = $this->payments;
$net = $this->cost;
foreach ($payments as $payment) {
$net += $payment->net;
}
return $net;
}
public function balance() :float
{
return ($this->cost - $this->net());
}
Payment.php
public function invoice()
{
return $this->belongsTo('App\Invoice');
}
PaymentController.php
$invoice = Invoice::findOrFail($id);
// Check ownership
if(!$this->getCurrentUser()->isSuperuser() && $this->getCurrentUser()->id === $invoice->user_id) {
throw new ModelNotFoundException();
}
$payment = new Payment($request->all());
$payment->ref = Payment::generateRef($invoice->id, $request->input('type'));
// Check for overpayment
if($invoice->balance() < $payment->net) {
throw new BadInputException('Payment exceeds balance.');
}
if($payment = $invoice->payments()->save($payment)) {
if($invoice->balance() == 0) {
$invoice->status = Invoice::CLOSED;
$invoice->save();
}
}
return $payment;
This is not the solution I was looking for but I don't like to expend too much time in little problems like this. I'll try to understand why is the model caching later.
The first time I run $invoice->balance() the value gets cached and the function won't take into consideration the new payment made when evaluating the new balance.
so the next time I ran $invoice->balance() i just manually subtract the new net payment.
if($invoice->balance() - $payment->net == 0) {
$invoice->status = Invoice::CLOSED;
$invoice->save();
}
Related
I have a model ProductOffer inside of it I use afterSave to generate the coupon.
Right now the status is null and in aftersave I want to update it.
public function afterSave($insert, $changedAttributes) {
if (floatval($this->offer) >= floatval($this->product->threshold_price)) {
$coupon = false;
$createCoupon = "";
$ctr = 1;
while ($coupon == false) {
$createCoupon = $this->createCoupon(
"Offer for " . $this->customer_name . ' #' . $this->id,
$this->product->sale_price - $this->offer,
$this->product_id
);
if ($createCoupon || $ctr > 3) {
$coupon = true;
}
$ctr++;
}
$this->status = self::STATUS_ACCEPTED_COUPON_GENERATED;
$this->coupon_code = $createCoupon->code;
// todo this
// echo "Accepted automatically then send email to customer as the same time to merchant email";
} else {
$this->status = self::STATUS_REJECTED;
}
return parent::afterSave($insert, $changedAttributes);
}
So here at afterSave I want to update the status of record and save the coupon code.
What I wan't to do is simply like this.
public function afterSave($insert, $changedAttributes) {
// So basically I want to update the status in afterSave
$this->status = "What ever value rejected or accepted it depends of the outcome of generating coupon";
$this->coupon = "AddTheCoupon";
// Save or Update
$this->save();
return parent::afterSave($insert, $changedAttributes);
}
But It seems not working for me and if you going to analyze it, it seems to do endless updating of the data since every save() it will pass through the afterSave().
Is there other way to do it?
Thanks!
You should use the updateAttributes method, which skips all the events.
See reference updateAttributes(['some_field']).
/** After record is saved
*/
public function afterSave($insert, $changedAttributes)
{
parent::afterSave($insert, $changedAttributes);
$this->some_field = 'new_value';
$this->updateAttributes(['some_field']);
}
I have three models in my laravel project, Step, Diploma and Pupil. This is what the relations look like:
class Pupil {
function steps() {
return $this->belongsToMany(Step::class);
}
function diplomas() {
return $this->belongsToMany(Diploma::class);
}
}
class Step {
function diploma() {
return $this->belongsTo(Diploma::class);
}
}
class Diploma {
function steps() {
return $this->hasMany(Step::class);
}
}
Now I have a form where the admin can check boxes of which steps a pupil has accomplished, which I then save by doing $pupil->steps()->sync($request['steps']);. Now what I want to do is find the diplomas of which all the steps have been accomplished and sync them too. But for some reason I can't figure out how to build that query. Is this clear? Would anyone like to help?
edit I now have this, but it's not as clean as I would like:
class Pupil {
public function hasCompleted(array $completedSteps)
{
$this->steps()->sync($completedSteps);
$diplomas = [];
foreach(Diploma::all() as $diploma) {
// First see how many steps does a diploma have...
$c1 = Step::where('diploma_id', $diploma->id)->count();
// Then see how many of those we completed
$c2 = Step::where('diploma_id', $diploma->id)->whereIn('id', $completedSteps)->count();
// If that's equal, then we can add the diploma.
if ($c1 === $c2) $diplomas[] = $diploma->id;
}
$this->diplomas()->sync($diplomas);
}
}
Instead of syncing the diplomas, as the steps could change in the future, what about pulling the diplomas via the completed steps when you need to know the diplomas?
class Pupil {
public function hasCompleted(array $completedSteps) {
$this->steps()->sync($completedSteps);
}
public function getCompletedDiplomas() {
$steps = $this->steps;
$stepIds = // get the step ids
$diplomaIdsFromSteps = // get the diploma Ids from the $steps
$potentialDiplomas = Diploma::with('steps')->whereIn('id', $diplomaIdsFromSteps)->get();
$diplomas = [];
foreach($potentialDiplomas as $d) {
$diplomaSteps = $d->steps;
$diplomaStepIds = // get unique diploma ids from $diplomaSteps
if(/* all $diplomaStepIds are in $stepIds */) {
$diplomas[] = $d;
}
}
return $diplomas;
}
}
I haven't completed all the code since I'm unable to test it right now. However, I hope this points you in the right direction.
I'm trying to learn how to do unit testing in general but specifically the project I'm working on is built with CakePHP. I have this parentNode() method in my user model taken directly from the Simple Acl Controlled Application tutorial.
public function parentNode() {
if (!$this->id && empty($this->data)) {
return null;
}
if (isset($this->data['User']['group_id'])) {
$groupId = $this->data['User']['group_id'];
} else {
$groupId = $this->field('group_id');
}
if (!$groupId) {
return null; // not tested
} else {
return array('Group' => array('id' => $groupId));
}
}
I wrote the following tests
public function testParentNodeHasNoUserDataOrId() {
unset($this->User->id);
unset($this->User->data);
$this->assertNull($this->User->parentNode());
}
public function testParentNodeWithGroupIDInUserData() {
$this->User->data['User']['group_id'] = 1;
$this->assertEquals(array('Group'=>array('id'=>1)),$this->User->parentNode());
}
public function testParentNodeWithoutGroupIDInUserData() {
$this->User->id = 1;
unset($this->User->data['User']['group_id']);
$this->assertEquals(array('Group'=>array('id'=>1)), $this->User->parentNode());
}
and they all seem to work. My code coverage report however shows that I'm not testing the return null; in the if(!$groupId) block. I can't figure out how to test that line.
As far as I can tell it will never execute. If my User model has no id and no data it returns null in the first if block. if I cheat a bit and give the user some fake data $this->field('group_id') is still returning group 1 (I think it should return false instead but it doesn't)
So when unit testing do you have to test everything? How could I test for the return null if(!$groupId)? If there's code that will never execute should I just remove it?
Thanks!
I figured out how I could test for the "impossible" scenario with the following test
public function testParentNodeWhenUserHasNoGroupId() {
$this->User->id = 1;
$user = $this->User->read();
$user['User']['group_id'] = 0;
$this->User->save($user);
$this->assertNull($this->User->parentNode());
}
I am trying to get the subtotal amount on checkout success page. It works good for registred users:
$_order = Mage::getModel('sales/order')->loadByIncrementId($this->getOrderId());
$amount = $_order->getData('subtotal');
$total = number_format($amount, 2);
But when the order is processed by a guest, $total is empty.
What can be done?
P.S.: I am using Magento 1.6.1 CE
Taken from Magento's app/code/core/Mage/Checkout/Block/Onepage.php:
if (!$this->isCustomerLoggedIn()) {
return $this->getQuote()->getShippingAddress();
} else {
return Mage::getModel('sales/quote_address');
}
I am 99% sure you can do exactly the same with getOrder() and with Mage_Checkout_Block_Success =)
Note: the isCustomerLoggedIn() method is defined at Mage_Checkout_Block_Onepage_Abstract which is not inherited by Mage_Checkout_Block_Success. So, you could simply use its implementation:
public function isCustomerLoggedIn()
{
return Mage::getSingleton('customer/session')->isLoggedIn();
}
E.g. your code now shall look like this:
if (!Mage::getSingleton('customer/session')->isLoggedIn()) {
$order = Mage::getSingleton('checkout/session')->getOrder();
} else {
$order = Mage::getModel('sales/order')->loadByIncrementId($this->getOrderId());
}
Sorry for saying non-sense things before...
I have users' table users, where I store information like post_count and so on. I want to have ~50 badges and it is going to be even more than that in future.
So, I want to have a page where member of website could go and take the badge, not automatically give him it like in SO. And after he clicks a button called smth like "Take 'Made 10 posts' badge" the system checks if he has posted 10 posts and doesn't have this badge already, and if it's ok, give him the badge and insert into the new table the badge's id and user_id that member couldn't take it twice.
But I have so many badges, so do I really need to put so many if's to check for all badges? What would be your suggestion on this? How can I make it more optimal if it's even possible?
Thank you.
optimal would be IMHO the the following:
have an object for the user with functions that return user specific attributes/metrics that you initialise with the proper user id (you probably wanna make this a singleton/static for some elements...):
<?
class User {
public function initUser($id) {
/* initialise the user. maby load all metrics now, or if they
are intensive on demand when the functions are called.
you can cache them in a class variable*/
}
public function getPostCount() {
// return number of posts
}
public function getRegisterDate() {
// return register date
}
public function getNumberOfLogins() {
// return the number of logins the user has made over time
}
}
?>
have a badge object that is initialised with an id/key and loads dependencies from your database:
<?
class Badge {
protected $dependencies = array();
public function initBadge($id) {
$this->loadDependencies($id);
}
protected function loadDependencies() {
// load data from mysql and store it into dependencies like so:
$dependencies = array(array(
'value' => 300,
'type' => 'PostCount',
'compare => 'greater',
),...);
$this->dependencies = $dependencies;
}
public function getDependencies() {
return $this->dependencies;
}
}
?>
then you could have a class that controls the awarding of batches (you can also do it inside user...)
and checks dependencies and prints failed dependencies etc...
<?
class BadgeAwarder {
protected $badge = null;
protected $user = null;
public function awardBadge($userid,$badge) {
if(is_null($this->badge)) {
$this->badge = new Badge; // or something else for strange freaky badges, passed by $badge
}
$this->badge->initBadge($badge);
if(is_null($this->user)) {
$this->user = new User;
$this->user->initUser($userid);
}
$allowed = $this->checkDependencies();
if($allowed === true) {
// grant badge, print congratulations
} else if(is_array($failed)) {
// sorry, you failed tu full fill thef ollowing dependencies: print_r($failed);
} else {
echo "error?";
}
}
protected function checkDependencies() {
$failed = array();
foreach($this->badge->getDependencies() as $depdency) {
$value = call_user_func(array($this->badge, 'get'.$depdency['type']));
if(!$this->compare($value,$depdency['value'],$dependency['compare'])) {
$failed[] = $dependency;
}
}
if(count($failed) > 0) {
return $failed;
} else {
return true;
}
}
protected function compare($val1,$val2,$operator) {
if($operator == 'greater') {
return ($val1 > $val2);
}
}
}
?>
you can extend to this class if you have very custom batches that require weird calculations.
hope i brought you on the right track.
untested andp robably full of syntax errors.
welcome to the world of object oriented programming. still wanna do this?
Maybe throw the information into a table and check against that? If it's based on the number of posts, have fields for badge_name and post_count and check that way?