SilverStripe. Search by date-range in ModelAdmin - search

I have date-property in my DataObject.
How can I search by date-range in ModelAdmin?
For example: "search all items where date is more than 2007-13-01 and less than 2007-17-01"
or "search all items where date is between 2007-13-01 and 2007-17-01"
For now I can search only with GreaterTranFilter or with LessThanFilter, but not with both.
class MyObject extends DataObject {
private static $db = [
"Date" => "Date",
];
private static $summary_fields = [
"Date" => "Date",
];
private static $searchable_fields = [
"Date" => [
"field" => "DateField",
"filter" => "GreaterThanFilter",
"title" => 'Date from ...'
],
];
}
Additionally search field must use a calendar(datepicker)
DateField:
default_config:
showcalendar: true
Can you give an example how to search by date-range?

There is a WithinRangeFilter, but it's not going to get you very far if you're using configuration only. This is something you really need to implement procedurally.
Add the range filters by overloading getSearchContext(), then overload getList() and check the q request param for the date ranges, and apply them to the list.
public function getSearchContext()
{
$context = parent::getSearchContext();
$context->getFields()->push(DateField::create('q[Start]','Start'));
$context->getFields()->push(DateField::create('q[End]','End'));
return $context;
}
public function getList()
{
$list = parent::getList();
$params = $this->getRequest()->requestVar('q');
$filters = [];
if(isset($params['Start'])) {
$filters['Date:LessThanOrEqual'] = $params['Start'];
}
if(isset($params['End'])) {
$filters['Date:GreaterThanOrEqual'] = $params['End'];
}
return $list->filter($filters);
}

Related

How to import products with variations in Shopware 6

I'm trying to import products from an XML with variations.
The import for the products works so far but it doesn't create the variations.
Here is my code (simplified):
/**
* #return int
* #throws \Exception
*/
public function execute()
{
// avoid reaching memory limit
ini_set('memory_limit', '-1');
// set tax id
$this->setTaxId();
if (empty($this->taxId)) {
return 1;
}
// read products from import xml file
$importProducts = $this->loadProducts();
$csvBatch = array_chunk($importProducts, self::BATCH);
$productNumbers = [];
foreach ($csvBatch as $products) {
$productNumbers[] = $this->processImportProducts($products, false);
}
$this->deleteProducts(array_merge(...$productNumbers));
return 0;
}
/**
* #param $productsData
* #param $progressBar
* #return array
*/
private function processImportProducts($productsData, $progressBar)
{
$products = [];
$productNumbers = [];
foreach ($productsData as $product) {
$products[$product['SKU']['#cdata']] = $this->importProducts($product, $progressBar);
$productNumbers[] = $product['SKU']['#cdata'];
}
// upsert product
try {
$this->cleanProductProperties($products, $this->context);
$this->productRepository->upsert(array_values($products), $this->context);
} catch (WriteException $exception) {
$this->logger->info(' ');
$this->logger->info('<error>Products could not be imported. Message: '. $exception->getMessage() .'</error>');
}
unset($products);
return $productNumbers;
}
/**
* #param $product
* #param $progressBar
* #return array
*/
private function importProducts($product, $progressBar)
{
...
$productData = [
'id' => $productId,
'productNumber' => $productNumber,
'price' => [
[
'currencyId' => Defaults::CURRENCY,
'net' => !empty($product['net']) ? $product['net'] : 0,
'gross' => !empty($product['net']) ? $product['net'] : 0,
'linked' => true
]
],
'stock' => 99999,
'unit' => [
'id' => '3fff95a8077b4f5ba3d1d2a41cb53fab'
],
'unitId' => '3fff95a8077b4f5ba3d1d2a41cb53fab',
'taxId' => $this->taxId,
'name' => $productNames,
'description' => $productDescriptions
];
if(isset($product['Variations'])) {
$variationIds = $product['Variations']['#cdata'] ?? '';
$productData['variation'] = [$this->getProductVariationIds($variationIds)];
}
return $productData;
}
/**
* Get product variation ids
*
* #param string $productVariations
* #return string
*/
private function getProductVariationIds($productVariations)
{
$productVariationIds = explode(',', $productVariations);
// get product variationIds in form of a string list
$ids = $this->productRepository->search(
(new Criteria())->addFilter(new EqualsAnyFilter('productNumber', $productVariationIds)),
$this->context
)->getIds();
return implode(',', $ids);
}
It loads correctly the ids but nothing happen. Also no error.
Anyone an idea how to import variations as well?
The variation field is not meant to be persisted or to create variants of a product. It has the Runtime flag, meaning it's not an actual database column but processed during runtime.
You have to create/update variants just like you create the parent product. Additionally you have to set the parentId and the options. The latter being associations to property_group_option, which you'll have to create first.
So in addition to your existing payload when creating parent products, you'll have to add this data to the variants:
$productData = [
// ...
'parentId' => '...'
'options' => [
['id' => '...'],
['id' => '...'],
['id' => '...'],
// ...
],
];
Finally you'll have to create the product_configurator_setting records. That's one record for each option used across all variants. Also the productId for the records has to be the one of the parent product.
$repository = $this->container->get('product_configurator_setting.repository');
$configuratorSettings = [];
foreach ($options as $option) {
$configuratorSetting = [
'optionId' => $option['id'],
'productId' => $parentId,
];
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('productId', $parentId));
$criteria->addFilter(new EqualsFilter('optionId', $option['id']));
$id = $repository->searchIds($criteria, $context)->firstId();
// if the configurator setting already exists, update or skip
if ($id) {
$configuratorSetting['id'] = $id;
}
$configuratorSettings[] = $configuratorSetting;
}
$repository->upsert(configuratorSettings, $context);
Just as an addition to make things easier. When creating a product with variants you can just update the configuratorSettings of the parent/father/main-product (whatever you call it).
Then Shopware6 will go and create the variant products automatically. Also the uuids of the children are created automatically. So if need to keep track of these you have to query them after the creation process.
But for a fast creation this might be much faster, if you have a lot of variants the only "variation" are the options. So no special images or texts.

Shopware: Store custom field for customer

I added this custom field to my customer and to the register form in storefront/component/account/register.html.twig:
<input type="checkbox" class="custom-control-input" id="alumni" name="custom_kw_dau" value="1">
The field is type checkbox. It works fine in the backend but it is not filled during customer registration.
You have to manually store it. Subscribe to event and add the field to customFields in the output like this:
public static function getSubscribedEvents(): array
{
return [
CustomerEvents::MAPPING_REGISTER_CUSTOMER => 'addCustomField'
];
}
public function addCustomField(DataMappingEvent $event): bool
{
$inputData = $event->getInput();
$outputData = $event->getOutput();
$custom_field = (bool)$inputData->get('custom_kw_dau', false);
$outputData['customFields'] = array('custom_kw_dau' => $custom_field);
$event->setOutput($outputData);
return true;
}
Yeah you need to subscribe to the event - but I have done it like this instead of the above event, and it works fine as well.
public static function getSubscribedEvents(): array
{
return [
CustomerRegisterEvent::class => 'onRegister',
GuestCustomerRegisterEvent::class => 'onRegister'
];
}
public function onRegister(CustomerRegisterEvent $event): void
{
$request = $this->requestStack->getCurrentRequest();
if ($request) {
$params = $request->request->all();
$customer = $event->getCustomer();
$data = [
'id' => $customer->getId(),
'customFields' => [
'your_field' => $params['your_field']
]
];
$this->customerRepository->update([$data], $event->getContext());
}
}
But I think the answer above might be more suitable as it does not require any additional services.

In EasyAdmin 3 configureActions method how do I get the current entity?

I wish to add an action to another to the index action with a predefined filter.
To build the filter I need the get current entity in the configureActions method.
public function configureActions(Actions $actions): Actions
{
parent::configureActions($actions);
$adminUrlGenerator = $this->get(AdminUrlGenerator::class);
$url = $adminUrlGenerator
->setController(SiteCrudController::class)
->setAction(Action::INDEX)
->set('filters', [
'agent' => [
'comparison' => '=',
'value' => 2194, // How to get current entity here??
]
])
->generateUrl()
;
$viewRelatesSites = Action::new('viewRelatedSites', 'Sites', 'fa fa-file-invoice')
->linkToUrl($url)
;
$actions->add(Action::DETAIL, $viewRelatesSites);
$actions->add(Action::EDIT, $viewRelatesSites);
return $actions;
}
}
How can I get the entity here?
To get the current entity the AdminContext is needed.
The best place to get this with the entity set is in a BeforeCrudActionEvent.
final class AgentCrudActionEventListen implements EventSubscriberInterface
{
private AdminUrlGenerator $adminUrlGenerator;
private AdminContextProvider $adminContextProvider;
public function __construct(AdminUrlGenerator $adminUrlGenerator, AdminContextProvider $adminContextProvider)
{
$this->adminUrlGenerator = $adminUrlGenerator;
$this->adminContextProvider = $adminContextProvider;
}
public static function getSubscribedEvents(): array
{
return [
BeforeCrudActionEvent::class => 'onBeforeCrudActionEvent',
];
}
public function onBeforeCrudActionEvent(BeforeCrudActionEvent $event): void
{
$crud = $event->getAdminContext()->getCrud();
if ($crud->getControllerFqcn() !== AgentCrudController::class) {
return;
}
$entity = $this->adminContextProvider->getContext()->getEntity();
if (!$entity) {
return;
}
$url = $this->adminUrlGenerator
->setController(SiteCrudController::class)
->setAction(Action::INDEX)
->set('filters', [
'agent' => [
'comparison' => '=',
'value' => $entity->getPrimaryKeyValue(),
]
])
->generateUrl()
;
$viewRelatedSites = Action::new('viewRelatedSites', 'Sites', 'fa fa-file-invoice')
->linkToUrl($url)
;
$actions = $crud->getActionsConfig();
$actions->appendAction(Action::DETAIL, $viewRelatedSites->getAsDto());
$actions->appendAction(Action::EDIT, $viewRelatedSites->getAsDto());
}
}
After a day looking for the current entity into my AbstractCrudController, I found this :
$currentEntity = $this->getContext()->getEntity()->getInstance();
Since EasyAdmin 3 events are discouraged per the docs:
Starting from EasyAdmin 3.0 everything is defined with PHP. That's why it's easier to customize backend behavior overloading PHP classes and methods and calling to your own services. However, the events still remain in case you want to use them.
public function configureActions(Actions $actions): Actions
{
// Create your action
$viewRelatedSites = Action::new('viewRelatedSites', 'Sites', 'fa fa-file-invoice');
//set the link using a string or a callable (function like its being used here)
$viewRelatedSites->linkToUrl(function($entity) {
$adminUrlGenerator = $this->get(AdminUrlGenerator::class);
$url = $adminUrlGenerator
->setController(SiteCrudController::class)
->setAction(Action::INDEX)
->set('filters', [
'agent' => [
'comparison' => '=',
'value' => $entity->getId()
]
])
->generateUrl();
return $url;
});
$actions->add(Crud::PAGE_INDEX, $viewRelatedSites);
return $actions;
}

How to make all cells wrap text and have complete all borders in Laravel Excel

I want to make export results with Laravel Excel, but the results did not match what I wanted.
what I want is like this
I want all cells to be wrapped in text and have a complete border.
This is my code:
class SdgsExportView2 implements FromView, WithTitle {
protected $id_perangkat_daerah;
function __construct($id_perangkat_daerah) {
$this->id_perangkat_daerah = $id_perangkat_daerah;
}
public function view(): View
{
$data_sdgs = DataSdgs::where('id_perangkat_daerah',$this->id_perangkat_daerah)
->with('indikator.target.sdgs')
->with('detail_data_sdgs.kegiatan.program.perangkat_daerah')
->get();
return view('template.exportMatrik2', [
'data_sdgs' => $data_sdgs
]);
}
public function registerEvents(): array
{
return [
AfterSheet::class => function(AfterSheet $event) {
$cellRange = 'A1:W100'; // All headers
$event->sheet->getDelegate()->getStyle($cellRange)->getFont()->setSize(14);
$event->sheet->getDelegate()->getStyle($cellRange)->getAlignment()->setWrapText(true);
}
];
}
public function title() : string
{
return 'MATRIK 2';
}
}
I really hope for your help
I know it is a bit late to answer (after 8 months), but to add border format to your code is relatively simple.
In your event function AfterSheet::class => function(AfterSheet $event) you need to add following code:
$styleHeader = [
'borders' => [
'allBorders' => [
'borderStyle' => 'thin',
'color' => ['rgb' => '808080']
],
]
];
$event->sheet->getStyle("A1:C1")->applyFromArray($styleHeader);
And replace "A1:C1" with your header range.
For me I have added event by extends WithEvents and use
use Maatwebsite\Excel\Concerns\WithEvents;
Then I apply like this....
public function registerEvents(): array
{
$alphabetRange = range('A', 'Z');
$alphabet = $alphabetRange[$this->totalValue+6]; // returns Alphabet
$totalRow = (count($this->attributeSets) * 3) + count($this->allItems)+1;
$cellRange = 'A1:'.$alphabet.$totalRow;
return [
AfterSheet::class => function(AfterSheet $event) use($cellRange) {
$event->sheet->getStyle($cellRange)->applyFromArray([
'borders' => [
'allBorders' => [
'borderStyle' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN,
'color' => ['argb' => '000000'],
],
],
])->getAlignment()->setWrapText(true);
},
];
}
For Details or more you can follow The doc https://docs.laravel-excel.com/3.1/getting-started/
#Zahid answer little bit update for v3.1
public function registerEvents(): array
{
return [
AfterSheet::class => function(AfterSheet $event) {
$alphabet = $event->sheet->getHighestDataColumn();
$totalRow = $event->sheet->getHighestDataRow();
$cellRange = 'A7:'.$alphabet.$totalRow;
$event->sheet->getStyle($cellRange)->applyFromArray([
'borders' => [
'allBorders' => [
'borderStyle' => Border::BORDER_HAIR,
],
],
])->getAlignment()->setWrapText(true);
},
];
}
I think this might help you what you looking for : phpspreadsheet simpler
There is a method
// $titleName : pass data that you wants to set (not array)
// $mergeTill : pass count of array dataset (Int)
public function setTitle($titleName, $mergeTill, $startCell ,$styleName = "")
go through this example given link

The bot doesn't understand the value when I use the property Describe

I am using the sample "SandwichOrder" code. When I use the property "Describe" to change the item value the bot doesn't understand the setted value.
public enum LengthOptions
{
[Describe("Test 1")]
SixInch = 1,
[Describe("Test 2")]
FootLong = 2
};
This is the output:
It's the problem how FormFlow handles the feedback after user's selection, the result is actually right the type of LengthOptions. Since we're not able to modify the source code of BotBuilder SDK, here is a workaround to solve this problem: we try to override the feedback of this item in FormFlow, and here is the code when building the FormDialog:
...
.Field(nameof(Length),
validate: async (state, response) =>
{
var result = new ValidateResult { IsValid = true, Value = response };
var value = (LengthOptions)response;
result.Feedback = "Your selection means " + value;
return result;
})
...
The Length property in above code can be defined like this:
public enum LengthOptions
{
[Describe("Test 1")]
SixInch = 1,
[Describe("Test 2")]
FootLong = 2
};
public LengthOptions? Length { get; set; }
Here is the test result:
What #Grace Feng mentioned is one way to do that. Another simpler way would be to add the Terms decoration to LengthOptions each item.
So the code would be :
public enum LengthOptions
{
[Terms(new string[] { "Test 1" })]
[Describe("Test 1")]
SixInch = 1,
[Terms(new string[] { "Test 2" })]
[Describe("Test 2")]
FootLong = 2
};
Now your bot will automatically understand the value of "Test 1" as SixInch and "Test 2" as FootLong

Resources