Query for getting OrderLineItems in Shopware 6 fails due to missing column 'order_line_item.order.order_line_item_version_id' - shopware

I'm trying to get all OrderLineItems in Shopware 6.4.18.1, that are a product of a certain manufacturer and:
paid for (if prepayment was used)
or either:
paid partially
payment in progress
payment open
The following code:
<?php
declare(strict_types=1);
namespace MyBundle\App\Query\OrderLineItem;
use Shopware\Core\Checkout\Payment\Cart\PaymentHandler\PrePayment;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityCollection;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\AndFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\NorFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\OrFilter;
class OrderLineItemQuery
{
public const PAID_STATE = 'paid';
public const PAID_PARTIALLY_STATE = 'paid_partially';
public const IN_PROGRESS_STATE = 'in_progress';
public const OPEN_STATE = 'open';
protected EntityRepository $orderLineItemRepository;
public function __construct(
EntityRepository $orderLineItemRepository
) {
$this->orderLineItemRepository = $orderLineItemRepository;
}
public function getOrderLineItemsByManufacturer(string $manufacturerId): EntityCollection
{
$criteria = new Criteria();
// default associations needed for following procedures
$criteria->addAssociations([
'order.deliveries.shippingOrderAddress.country',
'order.deliveries.shippingOrderAddress.salutation',
'order.orderCustomer.customer.salutation',
'order.salesChannel',
'order.transactions.stateMachineState',
'order.transactions.paymentMethod',
'product.manufacturer',
]);
$criteria->addFilter(
// only take manufacturer products
new EqualsFilter('product.manufacturer.id', $manufacturerId),
// exclude unpaid prepayment orders
new OrFilter([
new EqualsFilter('order.transactions.stateMachineState.technicalName', self::PAID_STATE),
// removing the filters inside here fixes the problem
new AndFilter([
new NorFilter([
new EqualsFilter('order.transactions.paymentMethod.handlerIdentifier', PrePayment::class),
]),
new OrFilter([
new EqualsFilter('order.transactions.stateMachineState.technicalName', self::PAID_PARTIALLY_STATE),
new EqualsFilter('order.transactions.stateMachineState.technicalName', self::IN_PROGRESS_STATE),
new EqualsFilter('order.transactions.stateMachineState.technicalName', self::OPEN_STATE),
]),
]),
])
);
return $this->orderLineItemRepository->search($criteria, Context::createDefaultContext())->getEntities();
}
}
But it only returns the Error:
In Exception.php line 18:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'order_line_item.order.order_line_item_version_id' in 'field list'
Removing everything inside the AndFilter seems to remove the error, but I need to exclude the OrderLineItems from orders where prepayment was choosen as payment method, but not yet paid.
How should I change the query to make it work?

I think the problem here might be a circular reference. Since you're querying mostly associations of the order, I'd recommend basing your query on the order table by using order.repository instead. By passively adding the lineItems association, you can then iterate over the OrderEntity instances and collect all corresponding line items by calling getLineItems().
$criteria = new Criteria();
// default associations needed for following procedures
$criteria->addAssociations([
'deliveries.shippingOrderAddress.country',
'deliveries.shippingOrderAddress.salutation',
'orderCustomer.customer.salutation',
'salesChannel',
'transactions.stateMachineState',
'transactions.paymentMethod',
]);
/**
* Get an association `lineItems`. (is added by calling the getter as well)
* Add a filter specifically for that association.
* Optionally, if you need `product` and `manufacturer` data,
* add them as additional associations.
*/
$criteria->getAssociation('lineItems')
->addFilter(
new EqualsFilter('product.manufacturer.id', $manufacturerId)
)
->addAssociation('product.manufacturer');
$criteria->addFilter(
new OrFilter([
new EqualsFilter('transactions.stateMachineState.technicalName', self::PAID_STATE),
new AndFilter([
new NorFilter([
new EqualsFilter('transactions.paymentMethod.handlerIdentifier', PrePayment::class),
]),
new OrFilter([
new EqualsFilter('transactions.stateMachineState.technicalName', self::PAID_PARTIALLY_STATE),
new EqualsFilter('transactions.stateMachineState.technicalName', self::IN_PROGRESS_STATE),
new EqualsFilter('transactions.stateMachineState.technicalName', self::OPEN_STATE),
]),
]),
])
);
$orders = $this->orderRepository->search($criteria, Context::createDefaultContext())->getEntities();
$lineItems = new OrderLineItemCollection();
/** #var OrderEntity $order */
foreach ($orders as $order) {
$lineItems->merge($order->getLineItems());
}
return $lineItems;

Related

How can I access the names of variant products in Shopware 6?

How can I get the name of a variant product via the product repository? So for example t-shirt L. Or only the option.
This is my code:
$cri2 = new Criteria();
$cri2->addFilter(new EqualsFilter('parentId', $itemProductId));
$cri2->addFilter(new EqualsFilter("active", 1));
$cri2->addFilter(new RangeFilter('stock', [ 'gt' => 0 ]));
I need the name of this option:
I believe the confusion is here:
Just note that this is not the name of the variant, it's just it's options concatenated as preview.
You can see inn the picture below that the actual name is null & therefore inherits the parents' name:
If you change the name from the variant product, then it will not be null anymore.
To create an actual name you will have to manually pull its options using associations. Here is an example of a query you might be looking for. After you get the data, just create the name yourself out of the variation data. High chance that this is how Shopware6 does it in the picture provided.
$criteria = (new Criteria()
// loads non-child products
->addFilter(new EqualsFilter('product.parentId', null))
->addAssociations([
'options',
'variation',
'children',
'children.options',
'children.properties',
'children.properties.group'
])
When you want to search for a specific name you can use
$cri2->addFilter(new EqualsFilter('name', 't-shirt L'));
When you want to search for the option name instead you can use
$cri2->addFilter(new EqualsFilter('options.option.name', 't-shirt L'));
When you don't want to search by name, but only what to get the name of a given product:
$product = $this->productRepository->search($cri2, $context)->getFirst();
echo $product->getName();
The finished code is:
$cri = new Criteria();
$cri->addFilter(new EqualsFilter('parentId', "[YOUR PARENT ID]"));
// Add options to associations
$cri->addAssociations([
'options'
]);
// Loop through variant products
foreach ($productRepository->search($cri, $context)->getElements() as &$variantRawItem) {
// Map all options to array
$options = array_map(function ($n) {
return $n->get('translated')['name'];
}, $variantRawItem->get('options')->getElements());
// Create the options title
$optionsTitle = implode(', ', $options);
// $optionsTitle is now e.g.: L, Lila
}

On an afterSubmit when we creating a copy of one inventory item (name with '-c') ,The original ID of item link should come in a field on a copy order

I tried this above, here I am getting a null value only from my previous record.
Kindly give some guidance to solve my questions.
thanks in advance.
/**
*#NApiVersion 2.0
*#NScriptType UserEventScript
*/
define(["N/url", "N/record", "N/runtime"], function (url, record, runtime) {
function afterSubmit(context){
var recordobj = context.newRecord;
var prevItemrecord= context.oldRecord;
var Itemname = recordobj.getValue({fieldId:'itemid'});
var prevItemname = prevItemrecord.getValue({fieldId : 'itemid'});
var Type=context.type;
var checkbox=recordobj.getValue({fieldId:'custitem17'});
if(Type== context.UserEventType.CREATE)
if((Itemname=prevItemname+'-c')&&(checkbox=true))
record.submitFields({
type: recordobj.type,
id: recordobj.id,
values:{custitem_item_link:prevItemname}
});
}
return{
afterSubmit:afterSubmit
}
});
This is my code
On create there is no old record.
Since you are trying to update the same record as was just created you are better off having this in a beforeSubmit event script
if((Itemname=prevItemname+'-c')&&(checkbox=true)) this is an error
if((Itemname == prevItemname+'-c') && checkbox) is more what you need
If you are trying to capture a copy operation you can set that up in the beforeLoad event that you use in the beforeSubmit event.
function beforeLoad(ctx){
if(ctx.type == ctx.UserEventType.COPY){
if(ctx.form){
ctx.form.addField({
id:'custpage_original_item',
label:'Copied Item',
type:ui.FieldType.SELECT,
source:'item'
}).updateDisplayType({
displayType:ui.FieldDisplayType.HIDDEN
}).defaultValue = ctx.request.parameters.id;
// your naming makes me wonder if you are trying to link to the
// source item rather than just saving a reference to the source
// item's name
/*
* Using the original item's name like below is closer to what you
* posted but I think by the time the script runs the itemid field
* has been cleared.
* ctx.form.addField({
* id:'custpage_original_item_name',
* label:'Copied Item Name',
* type:ui.FieldType.TEXT
* }).updateDisplayType({
* displayType:ui.FieldDisplayType.HIDDEN
* }).defaultValue = ctx.newRecord.getValue({fieldId:'itemid'});
*/
}
}
}
function beforeSubmit(ctx){
if(ctx.type == ctx.UserEventType.CREATE){
const itemRec = ctx.newRecord;
if(itemRec.getValue({fieldId:'custitem17'})){
const sourceId = itemRec.getValue({fieldId:'custpage_original_item'})
if(sourceId){
itemRec.setValue({
fieldId:'custitem_item_link:prevItemname',
value:sourceId
});
/* or use a search function to look up the original item's name
* and then test the new item's name.
*/
}
}
}
}

Is there any way to create matrix items using suite script?

Basically my requirement is to create a matrix item through the script. I'm wondering if is there any way to create a matrix item through Restlet or any Workflow. I succeeded creating the parent item with some specific attributes but it seems after submitting the record there is no child items getting created.
Bellow is the code snippet what I'm using right now.
var record= nlapiCreateRecord('serviceitem');
record.setFieldValue('name', 'Matrix Parent Record');
record.setFieldValue('matrixtype', 'PARENT');
record.setFieldValue('custitem_matrix_op1', '2');
record.setFieldValue('custitem_matrix_op2', '3');
var id=nlapiSubmitRecord(record);
Any help or suggestions would be appreciated.
Thank You.
Little example:
var parent = nlapiCreateRecord('noninventoryitem');
parent.setFieldValue('matrixtype', 'PARENT');
parent.setFieldValue('itemid', 'zzz ttt');
parent.setFieldValue('subsidiary', 2);// internalid for subs.
parent.setFieldValue('taxschedule', 4);// internalid for N/A in my account
parent.setFieldValues('itemoptions', ['CUSTCOL_LLL_EVENTLOCATION_OPT']);//option to be shown at PDP
parent.setFieldValue('custitem_event_location', 11);// particular option id (see in your list)
var parentid = nlapiSubmitRecord(parent);
var child = nlapiCreateRecord('noninventoryitem');
child.setFieldValue('matrixtype', 'CHILD');
child.setFieldValue('parent', parentid);
child.setFieldValue('itemid', 'zzz ttt child');
child.setFieldValue('taxschedule', 4);// internalid for N/A in my account
child.setFieldValues('itemoptions', ['CUSTCOL_LLL_EVENTLOCATION_OPT']);// same as in parent record
child.setFieldValue('matrixoptioncustitem_event_location', 11);// same as in parent record
var childid = nlapiSubmitRecord(child );
It will create a matrix with one child item.
Do not forget to set up additional fields like price and "display in web store" (isonline field).
After creating the parent item, you need to create child item as well,In the child item set the parent item internal ID & submit the record.
but here there is a drawback. because of static sub list, am not able to append the child items to parent in the Matrix sub list.
Using Suite Talk you could do something like this
/** Create Sweaters as matrix items.
* First create the parent - no matrix properties except "Matrix Type" is Parent
* Second create the matrix children with a combination of sizes and colors.
* This can be done in a single addList (as shown).
*/
//Define mrr method
public static RecordRef mrr(String internalId)
{
RecordRef toRet = new RecordRef();
toRet.setInternalId(internalId);
return toRet;
}
// Define makeListOrRecordRef method
public static ListOrRecordRef makeListOrRecordRef(String sTypeId, String internalId, String sName)
{
ListOrRecordRef toRet = new ListOrRecordRef();
toRet.setInternalId(internalId);
toRet.setName(sName);
toRet.setTypeId(sTypeId);
return toRet;
}
public void testMatrixSample() throws Exception
{
// Color is a Custom List of TypeId/RecType 1 that has already been created. 1,2,3 represent the
// internalIds of Red, Green, Blue
ListOrRecordRef[] colorArray = new
ListOrRecordRef[] {makeListOrRecordRef("1","1","Red"), makeListOrRecordRef("1","2","Green"),
makeListOrRecordRef("1","3","Blue")}; // Representing red, green and blue
// Size is a CustomList of TypeId/RecType 2 that has already been created
ListOrRecordRef[] sizeArray = new ListOrRecordRef[]{makeListOrRecordRef("2","2","Large"),makeListOrRecordRef("2","3","Small")};
//Representing large and small
InventoryItem[] toSubmit = new InventoryItem[1+colorArray.length*sizeArray.length];
toSubmit[0] = new InventoryItem();
toSubmit[0].setExternalId("parentSweater");
toSubmit[0].setItemId("sweater");
toSubmit[0].setMatrixType(ItemMatrixType._parent);
// set other fields on the Parent
for (int i=0;i<colorArray.length*sizeArray.length;i++)
{
toSubmit[i+1] = new InventoryItem();
toSubmit[i+1].setMatrixType(ItemMatrixType._child);
// mrr Creates a recordRef given an internal and externalId, the latter of which we specify.
// This makes it so we can submit all the records at once
toSubmit[i+1].setParent(mrr((String)null,"parentSweater"));
// "sweater-large-red","sweater-large-green"...
toSubmit[i+1].setItemId("sweater-"+colorArray[i%3].getName() + "-" +
sizeArray[i % 2].getName());
// set externalId so it's easier to find later
toSubmit[i+1].setExternalId(toSubmit[i+1].getItemId());
// CUSTITEM_COLOR,SIZE are the names of the Item Custom Fields, applied to
//InventoryItem that were setup as a Matrix types.
SelectCustomFieldRef colorRef = new SelectCustomFieldRef();
colorRef.setInternalId("CUSTITEM_COLOR");
colorRef.setValue(colorArray[i%3]);
SelectCustomFieldRef sizeRef = new SelectCustomFieldRef();
sizeRef.setInternalId("CUSTITEM_SIZE");
sizeRef.setValue(sizeArray[i%2]);
toSubmit[i+1].setMatrixOptionList(new MatrixOptionList(new
SelectCustomFieldRef[]{colorRef,sizeRef}));
// Set other matrix item child files
//....
}
WriteResponseList wr = c.getPort().addList(toSubmit);
}

Symfony2 Entity form type with a specific query_buider

Context
In my case, I've some orders with "discount vouchers" (discount). A discount can be use on under different conditions. For instance, discounts have an expired date, can be used by a limited number of customers, can be dedicated to a user, ...
Each discount can be attached to several order.
In my backoffice, I want to add to order create form a field "Discount" with a list of discount available but only right discounts.
What I made
An entity "order" with a field manyToMany
/**
* #ORM\ManyToMany(targetEntity="PATH\MyBundle\Entity\Discount", inversedBy="orders")
* #ORM\JoinTable(name="shop_discounts_orders",
* joinColumns={#ORM\JoinColumn(name="order_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="discount_id", referencedColumnName="id")}
* )
*/
private $discounts;
An entity "discounts" with a field manyToMany
/**
* #ORM\ManyToMany(targetEntity="PATH\MyBundle\Entity\Order", mappedBy="discounts")
*/
private $orders;
A form OrderType with a field discounts
$builder->add('discounts', 'entity',
array( 'label' => 'Discount vouchers',
'required' => false,
'expanded' => true,
'class' => 'PATH\MyBundle\Entity\Discount',
'property' => 'title',
'multiple' => true,
'query_builder' => function(EntityRepository $er) use ($params) {
return $er->getQuerySelectType($params);
},
));
With this solution, I can return specific discount defined by my request in my entity repository. It's good for expired date condition for instance.
What I would like
I'd like to filter results in the checkbox list. In fact, I want limit usage of the discount to a dedicated user, limit to a list of products, or limit the number of usage... And these condition cannot be done by a simple sql request.
I try to create special Type. My idea is to have an array of entities Discount and load a choice list... After that, I create a dataTransformer but It doesn't work !
Thank's for your ideas !
You could use the $options from public function buildForm(FormBuilderInterface $builder, array $options) to pass your user and product for instance. With those 2 informations you could refine your list of discount (in your query)
if you do so you need to add them in the setDefaultValue
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'user_discount' => null,
'product_discount' => null,
));
}
and in your controller:
$form = $this->formFactory->create(new YourFormType(), $entity, array(
'user_discount' => $this->getUser(),
'product_discount' => $product,
));
I found a solution and explain it if someone have the same issue as me.
Create a custom Type
My custom type is inspired by Symfony\Bridge\Doctrine\Form\Type\DoctrineType
class DiscountOrderType extends AbstractType
{
// overide choiceList callback
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$choiceListCache =& $this->choiceListCache;
$type = $this;
$choiceList = function (Options $options) use (&$choiceListCache, &$time, $container) {
[[ Copy paste same as Doctrine type ]]
// Create your own choiceList class (EntityChoiceList)
if (!isset($choiceListCache[$hash])) {
$choiceListCache[$hash] = new DiscountChoiceList(
$options['em'],
$options['class'],
$options['property'],
$options['loader'],
$options['choices'],
$options['group_by']
);
// If you want add container
$choiceListCache[$hash]->setContainer($container);
}
return $choiceListCache[$hash];
};
$resolver->setDefaults(array(
'choice_list' => $choiceList,
));
}
Create a custom EntityChoiceList
My custom type is inspired by Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList
class EntityChoiceList extends ObjectChoiceList
{
protected function load()
{
if ($this->entityLoader) {
$entities = $this->entityLoader->getEntities();
} else {
$entities = $this->em->getRepository($this->class)->findAll();
}
// You have access to the entities in the choice list
// Add your custom code here to manipulate the choice list
// you can do some check not properly possible with sql request (http requests on each result, ...) before add it in choice list
// you can add some custom cache rules, ...
// if you use gedmon and want apply a "join" with translate table, you can add $query->setHint(\Doctrine\ORM\Query::HINT_CUSTOM_OUTPUT_WALKER, 'Gedmo\\Translatable\\Query\\TreeWalker\\TranslationWalker'); before playing request...
// Possibilities are infinite
// FOR INSTANCE : you already want unset first entity of the result
if (isset($entities[0])) {
unset($entities[0]);
}
// END OF CUSTOM CODE
try {
// The second parameter $labels is ignored by ObjectChoiceList
// The third parameter $preferredChoices is currently not supported
parent::initialize($entities, array(), array());
} catch (StringCastException $e) {
throw new StringCastException(str_replace('argument $labelPath', 'option "property"', $e->getMessage()), null, $e);
}
$this->loaded = true;
}
Of course you can try to extend symfony class for beautyfull code ;).
Thank's to #maxwell2022 for your help !

how to index cck field of type text area in solr: drupal6

I've created a cck filed of type textarea with name filed_desc, how do i get this field to index in solr.
i found this article http://acquia.com/blog/understanding-apachesolr-cck-api, i have tried this but it is not indexing the filed, can somebody help.
<?php
// $Id$
/**
* Implementation of hook_apachesolr_cck_fields_alter
*/
function example_apachesolr_cck_fields_alter(&$mappings) {
// either for all CCK of a given field_type and widget option
// 'filefield' is here the CCK field_type. Correlates to $field['field_type']
$mappings['text'] = array(
'text_textarea' => array('callback' => 'example_callback', 'index_type' => 'string'),
);
}
/**
* A function that gets called during indexing.
* #node The current node being indexed
* #fieldname The current field being indexed
*
* #return an array of arrays. Each inner array is a value, and must be
* keyed 'value' => $value
*/
function example_callback($node, $fieldname) {
$fields = array();
foreach ($node->$fieldname as $field) {
// In this case we are indexing the filemime type. While this technically
// makes it possible that we could search for nodes based on the mime type
// of their file fields, the real purpose is to have facet blocks during
// searching.
$fields[] = array('value' => $field['field_desc']);
}
return $fields;
}
?>
I am currently working on adding this in a nice, pretty, generic way. If you really need this working now, take a look at this issue on Drupal.org. My code is currently living at GitHub, though hopefully I can get this included upstream and make a release of it.
Hope that helps!
Per field mapping is easier to control.
alter function:
$mappings['per-field']['field_specialities'] = array(
'index_type' => 'string',
'callback' => 'ge_search_apachesolr_field_specialities_callback'
);
callback:
function ge_search_apachesolr_field_specialities_callback($node, $fieldname)
{
$fields = array();
foreach($node->$fieldname as $field) {
$fields[] = array('value' => $field['value']);
}
return $fields;
}

Resources