-
Notifications
You must be signed in to change notification settings - Fork 118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add variant support to PropertyAdd plugin #252
base: master
Are you sure you want to change the base?
Conversation
Using codes only leads to the situation that it now works for variant articles but not for selection products any more. You need to fetch all products by their ID or their code. |
If I update the existing expression to following: $expr = [
$search->combine('||', [
$search->compare('==', 'product.id', array_unique($productIds)),
$search->compare('==', 'product.code', array_unique($productCodes)),
]),
$search->getConditions()
];
$search->setConditions($search->combine('&&', $expr)); And the The Or, would you just filter the list of products by code first, followed by id if null is returned? |
It's expected that it returns two products for ordered variant articles (the article and the selection product) and we want to get properties from both if they exists. You can shorten the code a bit:
|
That makes sense now, I was ignoring the possibility the article could still have properties that might need to be stored against the order product. This now introduces more questions :) Which properties should take priority? For eg. an article and selection will have the same property options. If one of these is a radio group, either yes/no then there is always a value. This would be stored as two json objects, and result in two comma separated values when printed out in the back office under the order. I'll make the changes for now and we can review. |
If both, selection products and variant articles has the same property type assigned, then article properties should have priority - but I wouldn't care much about that scenario yet |
@@ -121,18 +121,19 @@ public function update( \Aimeos\MW\Observer\Publisher\Iface $order, string $acti | |||
if( !is_array( $value ) ) | |||
{ | |||
\Aimeos\MW\Common\Base::checkClass( \Aimeos\MShop\Order\Item\Base\Product\Iface::class, $value ); | |||
return $this->addAttributes( $value, $this->getProductItems( [$value->getProductId()] ), $types ); | |||
return $this->addAttributes( $value, $this->getProductItems( [$value->getProductId()], [$value->getProductCode()]), $types ); | |||
} | |||
|
|||
$list = []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be better to use $ids
and $codes
instead of a two-dimensional array
foreach( $value as $orderProduct ) | ||
{ | ||
\Aimeos\MW\Common\Base::checkClass( \Aimeos\MShop\Order\Item\Base\Product\Iface::class, $orderProduct ); | ||
$list[] = $orderProduct->getProductId(); | ||
} | ||
\Aimeos\MW\Common\Base::checkClass(\Aimeos\MShop\Order\Item\Base\Product\Iface::class, $orderProduct); | ||
$list['code'][] = $orderProduct->getProductCode(); | ||
$list['id'][] = $orderProduct->getProductId(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can shorten this to:
$ids = map( $value )->getProductId();
$codes = map( $value )->getProductCode();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we still need to keep the checks for orderProduct(s)? ie. can do you want me to also remove the loop?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't think they are really necessary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can shorten this to:
$ids = map( $value )->getProductId(); $codes = map( $value )->getProductCode();
This is sorcery...it's pretty cool to be able to call a method on an array of objects directly from the list.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
jQuery style ;-)
protected function getProductItems( array $productCodes ) : \Aimeos\Map | ||
{ | ||
$manager = \Aimeos\MShop::create( $this->getContext(), 'product' ); | ||
$search = $manager->filter( true ); | ||
$expr = [ | ||
$search->compare( '==', 'product.id', array_unique( $productIds ) ), | ||
$search->compare( '==', 'product.code', array_unique( $productCodes ) ), | ||
$search->getConditions(), | ||
]; | ||
$search->setConditions( $search->and( $expr ) ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't use both, IDs and codes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad, I never copied my changes over, im copying my edits from editor to the browser.
Im trying to find more info on how I can run the test suite using phing, I just setup the environment so it has some data to work against.
if($products->count() > 1) { | ||
$variant = $products->find( | ||
function ( \Aimeos\MShop\Product\Item\Iface $item ) use ( $orderProduct ) : bool { | ||
return $item->getCode() === $orderProduct->getProductCode(); | ||
} | ||
); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's more efficient if you use $map = $products->col( null, 'product.code' );
first and then access the products by code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! Im used to using laravel collections and find myself digging through your docs for functions that are similar that I can use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Map methods are very close to Laravel, only if standard PHP uses another name (array_column()
instead of Laravel's pluck()
), then the Map methods follow the PHP naming.
Im commiting blind in the browser again, if there is anything I miss im really going to need to spend some time to understand and setup phing.
$ids = map( $value )->getProductId()->toArray(); | ||
$codes = map( $value )->getProductCode()->toArray(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use ->unique()
already here and pass the Map objects if you change the method signature of getProductsItems() to iterable
instead of array
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did do that initially, but my mistake was type hinting as Aimeos\Map, which would have failed for the call we make on the addAttributes
method outside of the loop.
$search->add( $search->or( [ | ||
$search->is( 'product.id', '==', array_unique( $productIds ) ), | ||
$search->is( 'product.code', '==', array_unique( $productCodes ) ), | ||
] ) ); | ||
|
||
return $manager->search( $search, ['product/property'] ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could save a lot of $products->col( null, 'product.code' )
mappings in addAttributes()
if we already return the merged properties already here. For that, we would need an array of product.code/product.id pairs, search for both like now and return a product.id/propertiy items map. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The method should probably change to reflect what it returns then.
getProductItems
should probably become getProductProperties
.
I guess we would also want to filter the attributes by the types configured here also, which means passing the types to method.
Do you not think passing an array of code/ids into getProductProperties
is less explicit than the current approach, as the filter will need to extract the keys and values for the relevant columns to filter on?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can also pass IDs and codes separately and return a map of product.code as keys and property items as values.
Your are right, we should rename the method, getProperties()
is shorter and would fit too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to return the map keyed as you described?
The changes ive made locally within the now labelled getProperties
function, are to accept the types.
I map over the types and return a new map that includes the properties.
It returns something like the following:
Aimeos\Map {#1780
#list: array:3 [
"package-weight" => Aimeos\Map {#1778
#list: array:1 [
182 => "0.4"
]
}
"package-net-weight" => Aimeos\Map {#1777
#list: array:1 [
190 => "0.4"
]
}
"package-dangerous" => Aimeos\Map {#1772
#list: array:2 [
191 => "0"
192 => "0"
]
}
]
}
This is then passed to addAttributes
replacing the existing $products
parameter, and I iterate over this and attach to the $orderProduct
.
The tests are still passing for me with these changes - but could be a false positive due to coverage. I can publish my changes If you like?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that should work
if ( ($attrItem = $orderProduct->getAttributeItem($type, 'product/property')) === null ) { | ||
$attrItem = $this->orderAttrManager->create(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can shorten this to:
$attrItem = $orderProduct->getAttributeItem( $type, 'product/property' ) ?: $this->orderAttrManager->create();
foreach( $types as $type ) | ||
{ | ||
$list = $product->getProperties( $type ); | ||
$properties->each(function ( $attributes, $type ) use ( &$orderProduct ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is $type
comming from? &
isn't necessary for objects
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$type
is the key in the properties map, it's the type the user configures in the admin for the plugin.
An example of the properties passed into addAttributes
is below.
Array
(
[package-width] => Aimeos\Map Object
(
[list:protected] => Array
(
[7] => 15.0
)
[sep:protected] => /
)
)
$items = $manager->search( $search, ['product/property'] ); | ||
|
||
return map( $types )->map( function ( $type ) use ( $items ) { | ||
if( !( $properties = $items->getProperties($type)->collapse() )->empty() ) { | ||
return [ $type => $properties ]; | ||
} | ||
} )->filter()->collapse(1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be sufficient to get a product code index and the properties as list of items:
return $manager->search( $search, ['product/property'] )->col( null, 'product.code' )->getProperties( $types );
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getProperties
requires a string not an array, does it accept comma separated values? If so we would need to implode $types
.
That should work. |
I thought so too initially, but after I thought about it - it couldn't be :) We're mapping over the products, and returning properties by type, but this will return early if there is more than a single type as we are not returning a collection of properties by the types given. For eg. the below would only return a map of properties belonging to the type 'one'
I will come back to this when I have some more time, for me what I originally wrote before the refactoring was working and has enabled me to continue. |
@iammart Any news on this topic? |
Following on from the discussion on the below thread, I have updated the plugin to query on product code(s) not product ID.
This enables product attributes to be stored on order products that have types of
selection
orbundle
.https://aimeos.org/help/viewtopic.php?p=15713#p15713