I need to check if an order has already some shipment set. The only data I can use is the increment id of the order. I'm getting an instance of a model order, but I don't see a way I can get a shipment instance.
I'm using this code:
$order = Mage::getModel('sales/order')
->loadByIncrementId($order_increment_id);
But how can I get a shipment instance? I know that I can call Mage::getModel('sales/order_shipment')->loadByIncrementId($shipment_increment_id) but how do I get the shipment increment id?
Assume that the person who wrote this might have also needed to do what you need to do. Generally, when Magento objects have a one to many relationship you can find a method to load the many on the one.
You've got a class alias sales/order.
This corresponds to Mage_Sales_Model_Order (in a stock installation).
You can find this class at app/code/core/Mage/Sales/Model/Order.php.
If you examine this class, there are 7 methods with the word "ship" in them
function canShip
function setShippingAddress
function getShippingAddress
function getShip
function getShipmentsCollection
function hasShip
function prepareShip
Of those 7, only the semantics of getShipmentsCollection indicate a method for grabbing an order's shipments. So try
foreach($order->getShipmentsCollection() as $shipment)
{
var_dump(get_class($shipment));
//var_dump($shipment->getData());
}
Or take a look at the source for getShipmentsCollection
public function getShipmentsCollection()
{
if (empty($this->_shipments)) {
if ($this->getId()) {
$this->_shipments = Mage::getResourceModel('sales/order_shipment_collection')
->setOrderFilter($this)
->load();
} else {
return false;
}
}
return $this->_shipments;
}
Just to make it complete Mage_Sales_Model_Order has public method:hasShipments()which returns number of shipments and internally uses mentioned getShipmentsCollection().
Related
I have two models customer and orders. They are already fecthed separately
$customers = customer::all();
$orders = orders::all();
customerID=1 has orderID : 1, 2,4 customerID=2 has orderID : 3,5,9
They are related (hasMany, belongsTo) but the problem is inside my for a certain reason they are separated but I want to send them as response in API using toJson or ToArray as one data having the orders nested to their correct customers.
How can I achieve that linking to have at the end one variable $customersWithOrders that should be transformed to JSON ?
I am using laravel 5.5
I don't know what the context is. Defining relationships as other answers mentioned is a good solution.
In addition, I recently read a pretty good article about this specific scenario.
So you can also do something like this, if you have already retrieved customers and orders:
$customers = Customer::all();
$orders = Order::all();
return $customers->each(function ($customers) use ($orders) {
$customer->setRelation('orders', $orders->where('customer_id', $customer->id));
});
If you already have a relation you just use it. For example, in model Customer.php:
public function orders()
{
return $this->hasMany(Order::class);
}
Then you'd get customer orders by calling $customer->orders
If you already have defined relations, you can simply fetch data with eager loading
// in customer model
public function orders()
{
return $this->hasMany(orders::class, 'orderID');
}
// in controller
$customersWithOrders = customer::with('orders')->get();
return response()->json(['customersWithOrders' => $customersWithOrders]);
// in js
for (let customer in response.customersWithOrders){
let orders = customer.orders
}
I have a scope on my Supplier model that returns results where active = true.
This works great when creating new entries, as I only want the user to see active suppliers.
Current entries may have an inactive supplier; When I edit it, I want to see all active Suppliers, plus the current supplier (if it is inactive)
I have this code in my controller:
$suppliers = Supplier::active()->get();
if (!$suppliers->contains('id', $record->supplier->id))
{
$suppliers->add(Supplier::find($record->supplier->id));
}
Two questions: Is this the correct way to do this? Should this code be in my controller or should I have it somewhere else? (perhaps a scope but I wouldn't know how to code that).
Edit:
Thanks for the help guys. I have applied advice from each of the answers and refactored my code into a new scope:
public function scopeActiveIncluding($query, Model $model = null)
{
$query->where('active', 1);
if ($model && !$model->supplier->active)
{
$query->orWhere('id', $model->supplier->id);
}
}
What you've written will work, but the Collection::contains function can potentially be pretty slow if the collection is large.
Since you have the id, I would probably make the following change:
$suppliers = Supplier::active()->get();
$supplier = Supplier::find($record->supplier->id);
if (!$supplier->active) {
$suppliers->add($supplier);
}
Of course, the downside to this is that you may be making an unnecessary query on the database.
So you have to consider:
is the record's supplier more likely to be active or inactive?
is the size of the collection of active suppliers large enough to justify another (potentially wasted) call to the database?
Make the choice that makes the most sense, based on what you know of your application's data.
As for the second question, if you will only need this specific set of suppliers in this one part of your application, then the controller is a good place for this code.
If, however, you will need this particular set of suppliers in other parts of your application, you should probably move this code elsewhere. In that case, it might make sense to create a function on the the related model (whatever type $record is...) that returns that model's suppliers set. Something like:
public function getSuppliers()
{
$suppliers = Supplier::active()->get();
$supplier = $this->supplier;
if (!$supplier->active) {
$suppliers->add($supplier);
}
return $suppliers;
}
I saw #Vince's answer about 1st question, and I'm agree with him.
About 2nd question:
Write scope in Supplier model like this:
public function scopeActive($query){
$query->where('active', 1); // for boolean type
}
For good practice, you need to write the logic parts in services like "App\Services\SupplierService.php". And there write the function you want:
public function activeSuppliersWithCurrent($record) {
$suppliers = Supplier::active()->get();
$supplier = Supplier::find($record->supplier->id);
if (!$supplier->active) {
$suppliers->add($supplier);
}
}
In your SupplierController's constructor inject the instance of that service and use the function, for example:
use App\Servives\SupplierService;
protected $supplierService = null;
public function __construct(SupplierService $supplierService) {
$this->supplierService = $supplierService;
}
public function getActiveSuppliersWithCurrent(...) {
$result = $this->supplierService->activeSuppliersWithCurrent($record);
}
As you can see, later you will not need to change anything in controller. If you'll need to change for example the query of suppliers selection, you will just have to change something only in service. This way will make your code blocks separated and shorter.
Also the sense for this pattern: you don't need to access the models from controller. All logic related with models will implemented in services.
For other projects you can grab only services or only controllers, and implement another part differently. But in that case if you had all codes in controller, that will prevent you to grab the portions of necessary codes, cuz may you don't remember what doing each blocks...
You could add a where clause to the query to also find that id.
$suppliers = Supplier::active()->orWhere('id', $record->supplier->id)->get();
You could potentially slide this into the active scope by passing the 'id' as an argument.
public function scopeActive($query, $id = null)
{
$query->where('active', true);
if ($id) {
$query->orWhere('id', $id);
}
}
Supplier::active($record->supplier->id)->get();
Or make another scope that does this.
I have one to many relation - Entry can have many Visits.
In my Entry model I have the following methods:
public function visits() {
return $this->hasMany ('Visit', 'entry_id','id');
}
public function visitsCount() {
return $this->hasMany('Visit', 'entry_id','id')
->selectRaw('SUM(number) as count')
->groupBy('entry_id');
}
In Blade I can get number of visits for my entry using:
{{$entry->visits()->count() }}
or
{{ $entry->visitsCount()->first()->count }}
If I want to create accessor for getting number of visits I can define:
public function getNrVisitsAttribute()
{
$related = $this->visitsCount()->first();
return ($related) ? $related->count : 0;
}
and now I can use:
{{ $entry->nr_visits }}
Questions:
In some examples I saw defining such relation this way:
public function getNrVisitsAttribute()
{
if (!array_key_exists('visitsCount', $this->relations)) {
$this->load('visitsCount');
}
$related = $this->getRelation('visitsCount')->first();
return ($related) ? $related->count : 0;
}
Question is: what's the difference between this and the "simple method" I showed at the beginning? Is it quicker/use less resource or ... ?
Why this method doesn't work in this case? $related is null so accessor return 0 whereas using "simple method" it returns correct number of visits
I've tried also changing in visitsCount method relationship from hasMany to hasOne but it doesn't change anything.
1 Your relation won't work because you didn't select the foreign key:
public function visitsCount() {
// also use hasOne here
return $this->hasOne('Visit', 'entry_id','id')
->selectRaw('entry_id, SUM(number) as count')
->groupBy('entry_id');
}
2 Your accessor should have the same name as the relation in order to make sense (that's why I created those accessors in the first place):
public function getVisitsCountAttribute()
{
if ( ! array_key_exists('visitsCount', $this->relations)) $this->load('visitsCount');
$related = $this->getRelation('visitsCount');
return ($related) ? $related->count : 0;
}
This accessor is just a handy way to call the count this way:
$entry->visitsCount;
instead of
$entry->visitsCount->count;
// or in your case with hasMany
$entry->visitsCount->first()->count;
So it has nothing to do with performance.
Also mind that it is not defining the relation differently, it requires the relation to be defined like above.
Assuming your schema reflects one record / model per visit in your visits table, The best method would be to get rid of the visitsCount() relation and only use $entry->visits->count() to retrieve the number of visits to the entry.
The reason for this is that once this relation is loaded, it will simply count the models in the collection instead of re-querying for them (if using a separate relationship)
If your concern is overhead and unnecessary queries: My suggestion would be to eager-load these models in a base controller somewhere as children of the user object and cache it, so the only time you really need to re-query for any of it is when there have been changes.
BaseController:
public function __construct(){
if(!Cache::has('user-'.Auth::user()->id)){
$this->user = User::with('entries.visits')->find(Auth::user()->id);
Cache::put('user-'.Auth::user()->id, $this->user, 60);
} else {
$this->user = Cache::get('user-'.Auth::user()->id);
}
}
Then set up an observer on your Entry model to flush the user cache on save. Another possibility if you are using Memcached or Reddis would be to use cache tags so you don't have to flush the whole user's cache every time an Entry model is added or modified.
Of course, this also assumes that each Entry is related to a user, however, if it isn't and you need to use Entry alone as the parent, the same logic could apply, by moving the Cache class calls in your EntryController
I am new to Magento.
What's the proper way to check if an order with a given increment id already exists ?
The obvious way:
$order = Mage::getModel('sales/order')->loadByIncrementId($reservedOrderId);
if ($order) {
Mage::log('already have order with id ' . $reservedOrderId);
return $order;
}
does not work, because I get a new and empty model instance back.
What's the correct way in magento to see if I have no such model for that id ?
The most common approach I've seen in core code just load()s a model and checks if there was a primary key assigned. In your case this would look like the following - note the very slight adjustment to the logical condition ($object->getId() vs. $object):
$order = Mage::getModel('sales/order')->loadByIncrementId($reservedOrderId);
if ($order->getId()) {
Mage::log('already have order with id ' . $reservedOrderId);
return $order;
}
It's a simple mistake, but remember that a call to load data on a Magento data model will always return the object instance. It's only if there is a result from the storage backend that the object would be decorated with data and therefore a primary key.
In my experience there are two ways to do this:
if ($order->hasData()) {
// order already exists
}
or, by using a collection;
$collection = Mage::getModel('sales/order')->getCollection()->addFieldToFilter('increment_id', $reservedOrderId);
if ($collection->count()) {
// order already exists
}
In your case, probably best to use the first one.
There's multiple ways to approach this. First, since you know the increment ID to expect, you could check for it after you get your model back
$increment_id = '100000002';
$order = Mage::getModel('sales/order')->loadByIncrementId($increment_id);
if($order->getIncrementId() == $increment_id)
{
var_dump("Increment IDs match, that means there's an order");
}
else
{
var_dump("Increment IDs don't match, that means there's no order");
}
Similarly, although there's a model returned even if there's no match, you could check that model's data — an empty array means nothing was loaded
$increment_id = '100000002';
$order = Mage::getModel('sales/order')->loadByIncrementId($increment_id);
if($order->getData())
{
var_dump("Data array means there's an order");
}
else
{
var_dump("Empty data array means there's no order");
}
Finally, you can load a collection with an increment id filter, and check how many items it contains
$increment_id = '100000002';
$c = Mage::getModel('sales/order')->getCollection()
->addFieldToFilter('increment_id',$increment_id);
if(count($c) > 0)
{
var_dump("A collection with more than zero items means the order exists");
}
else
{
var_dump("An empty collection means it does not");
}
I prefer the last approach for a simple "does/does-not" exists check, as a collection doesn't trigger a model's after load method which means it's theoretically more performant. That said, no approach is more valid than the other — just try to use the same technique everywhere for more readable code.
I am looking for a solution to storing product attributes with each order. Essentially I have a need for storing a unique item lot number for each product that can then be searched on the front end to find out which orders contained products from a specific lot. My initial thought was to do this with product attributes and store the attribute with each product in an order.
Does anybody have a better solution or can point me in the right direction for implementing this solution?
Edit: Still looking for a solution to this
I agree the lot number should be a product attribute but you don't need to store it twice. Just join it with order table when you later need it - That way the information is kept up to date (unless you want to know what it was at the time of ordering, in which case this is all wrong).
Unfortunately the order tables are flattened, not EAV, so don't handle attributes so well. The collection's joinAttribute() method is a stub. You can get around that with this query patterns library (self promotion disclaimer; I'm using it here because I wrote the attribute functions and don't want to redo the work) and then extending it with a class specific to you.
class Knectar_Select_Product_Lot extends Knectar_Select_Product
{
public function __construct()
{
parent::__construct();
$this->joinAttribute('lots', 'catalog_product', 'lot_number',
'products.entity_id', 0, 'lot_number'
);
}
public static function enhance(Varien_Db_Select $select, $tableName, $condition, $columns = null, $type = self::LEFT_JOIN)
{
$select->_join(
$type,
array($tableName => new self()),
$condition,
$columns ? $columns : 'lot_number'
);
}
}
The enhance() function does the hard bit, you only need to call it and filter by it's column.
$orderItems = Mage::getResourceModel('sales/order_item_collection');
Knectar_Select_Product_Lot::enhance($orderItems->getSelect, 'lots', 'lots.product_id = main_table.product_id');
$orderItems->addFieldToFilter('lot_number', 'ABC123');
If you are using the collection in an admin grid then it's column filters will do addFieldToFilter() for you.