Is it possible to restrict the OMS to "all or nothing"?

#1

Hello,

i am continuing evaluation of spryker possibilities. I am playing around with the OMS state machine and ran into the following problem.

If an order with multiple items was placed and should manually be forwarded to the “paid” status, is it possible to avoid a manual status change for a single item from the order?
The background is that some ERP Systems just accept the order with all its items at once. Single items therefore can not be processed by the ERP systems or rather later paid items cant be added to an earlier placed order in the ERP.

It would be a bit easier if no user action would be necessary, because you could just apply an automatic state change for all items programmatically when the order arrives. But with a manual action, there is a state change button next to each item.

Whats a good approach?

Thanks

#2

Hi Ingo,

Thank you for your question.
I think this topic is covered in general in these two documents:
https://documentation.spryker.com/state_machine_cookbook/state-machine-cookbook.htm
https://documentation.spryker.com/state_machine_cookbook/state-machine-cookbook-2.htm.

And specifically your question seems to be covered here - https://documentation.spryker.com/state_machine_cookbook/state-machine-cookbook.htm#events.

Feel free to ask additional questions if this doesn’t help you.

Best regards,
Valerii

#3

Thanks for your help.

I tried to build something similar with the help of the docs. They say, that you can check conditions before a state changes over. So i built up a simpel state machine which uses a “Ready for export” state … now i want to check that whenever all these items from this order has the “Ready for export” state, the order should go over into the real “Export” State

    <process name="Order" main="true">
    <states>
        <state name="new" reserved="true"/>
        <state name="prepared for export" reserved="true"/>
        <state name="exported" reserved="true"/>
    </states>

    <transitions>
        <transition happy="true">
            <source>new</source>
            <target>prepared for export</target>
            <event>prepare for export</event>
        </transition>
        <transition happy="true" condition="Oms/IsCompleteOrder">
            <source>prepared for export</source>
            <target>exported</target>
            <event>export</event>
        </transition>
    </transitions>

    <events>
        <event name="export" onEnter="true" manual="false" command="Oms/Export"/>
        <event name="prepare for export" manual="true"/>
    </events>
</process>

As you can see, the “prepare for export” event can be triggered manually for all items. When it’s in the “prepared for export” state it checks if all other items are also in this state. When no, it stays in the state, if yes, this item and all other items should go over into the “export” state (including the export command)

The first problem: Even if i say “manual=false”, you still can trigger the export manually (button is shown next to each item)

The second problem: How to retrigger the condition check for all the other items after the last not “ready for export” item is now in “ready for export” state?

How to solve this?

Best regards

#4

Hi Jim!
For first just clarifying - state machine works only per item.
About your first problem - you need to remove “manual” section completely.
About your second question:
I think you can use transition without any events just with condition. For example:

<transition happy="true" condition="Oms/IsReadyForExport">
    <source>ready for export</source>
    <target>exported</target>
</transition>

In this case your item will be moved to target state only if condition is true. It will check infinitely on each cron run. In condition you can put any logic even checking all the other items by selecting them from data base.
I hope this helps.

Best regards,
Aleksey Kotsuba

#5

Hi Aleksey,

thanks for your response.
I’ll try to remove the manual section.
One question right now: When removing the event for the transition, how can i execute the export command (which is bound to the event)?

Best regards

#6

Hi Jim!
I think you can use different transitions for check if all items are now ready for export and execute export. I don’t really understand why you need manual event prepare for export but if it useful you can use intermediate state:

<process name="Order" main="true">
<states>
    <state name="new" reserved="true"/>
    <state name="prepared for export" reserved="true"/>
    <state name="ready for export" reserved="true"/>
    <state name="exported" reserved="true"/>
</states>

<transitions>
    <transition happy="true">
        <source>new</source>
        <target>prepared for export</target>
        <event>prepare for export</event>
    </transition>
    <transition happy="true" condition="Oms/IsReadyForExport">
        <source>prepared for export</source>
        <target>ready for export</target>
    </transition>
    <transition happy="true" condition="Oms/IsCompleteOrder">
        <source>prepared for export</source>
        <target>exported</target>
        <event>export</event>
    </transition>
</transitions>

<events>
    <event name="export" onEnter="true" command="Oms/Export"/>
    <event name="prepare for export" manual="true"/>
</events>

In this case your items will be moved to prepared for export state from new state by manually executing prepare for export. Item will be moved form prepared for export to ready for export state only if IsReadyForExport condition is true. And finally Export event will be triggered automatically and if IsCompleteOrder condition is true item goes to exported state.
You can try to use this example if it represents your business logic.

Best regards,
Aleksey Kotsuba.

#7

I don’t really understand why you need manual event prepare for export

Thats because i can’t figure out a simpler solution to ensure, that all items gets exported at once. The best solution to me would be to deactivate the state changing button next to each single item in the backend and only allow one button for the whole order with the label “Export”. This “Export” button is just active if all items were paid.
But as far as i know until here, that is not possible because the statemachine works only for each single item.

To summarize the whole process i want

  1. Order comes in -> status for each item: “new”
  2. Order gets paid -> set manual status for each item: “paid” (that would be okay, because you can use the “set paid for all” button for the order but you can also pay each item manually)
  3. Order gets exported -> Create one export file with all items in it, that means, the export should only happen when all items from this order were payed
  4. All items are now in state “exported”. No item has a different state than this.

Transition from “Paid” to “Exported” is ideally automatic (execute automatic the export after the last unpaid item was payed)

It may be, that i think too complicated :wink:

Thanks for patience anyway

Best regards

#8

As I said above you can use additional state to make sure that all items are paid. And after that use onEnter event with command to execute export.

Best regards,
Aleksey Kotsuba

#9

Hi,

i think i am close to a solution.

    <process name="Order" main="true">
    <states>
        <state name="new" reserved="true"/>
        <state name="order completed" reserved="true"/>
        <state name="prepared for export" reserved="true">
            <flag>prepared</flag>
        </state>
        <state name="exported" reserved="true"/>
    </states>

    <transitions>
        <transition>
            <source>new</source>
            <target>prepared for export</target>
            <event>prepare for export</event>
        </transition>
        <transition condition="Oms/IsCompleteOrder">
            <source>prepared for export</source>
            <target>order completed</target>
        </transition>
        <transition happy="true">
            <source>order completed</source>
            <target>exported</target>
            <event>export</event>
        </transition>
    </transitions>

    <events>
        <event name="export" onEnter="true" command="Oms/Export"/>
        <event name="prepare for export" manual="true"/>
    </events>
</process>

The problem now seems, that the oms:check-condition do not pick up my condition. If i set a breakpoint in the IsCompleteOrderPlugin Class, it will never reached (even a simple die() will not be executed, just to eliminate debugging problems)

Every item in the state machine looks like this at the moment, so why the check-condition is not executed for that?

I expect, that the IsCompleteOrderPlugin condition will be checked for each item and moved to the “order completed” state. Entering this state, the export gets automatically triggered.

Whats the problem?

Best regards

#10

Hi Jim!
Have you added your plugin into OmsDependencyProvider?
Just like that:

namespace Pyz\Zed\Oms;

class OmsDependencyProvider extends SprykerOmsDependencyProvider
{
    public function provideBusinessLayerDependencies(Container $container): Container
    {
        $container = parent::provideBusinessLayerDependencies($container);
        $container = $this->extendCommandPlugins($container);
        $container = $this->extendConditionPlugins($container);

        return $container;
    }

    protected function extendCommandPlugins(Container $container): Container
    {
        $container->extend(self::COMMAND_PLUGINS, function (CommandCollectionInterface $commandCollection) {
        $commandCollection->add(new ExportPlugin(), 'Oms/Export');

        return $commandCollection;
        });
    }

    protected function extendConditionPlugins(Container $container): Container
    {
        $container->extend(OmsDependencyProvider::CONDITION_PLUGINS, function (ConditionCollectionInterface $conditionCollection) {
            $conditionCollection->add(new IsCompleteOrderPlugin(), 'Oms/IsCompleteOrderPlugin');

            return $conditionCollection;
        });

        return $container;
    }

Best regards,
Aleksey Kotsuba

#11

Hi @aleksey.kotsuba

Yup, i added it … the check worked before when it was initiated via an event tag. But without event tag it does not seem to work.

My OmsDependencyProvider

    public function provideBusinessLayerDependencies(Container $container)
{
    $container = parent::provideBusinessLayerDependencies($container);
    $container->extend(self::COMMAND_PLUGINS, function (CommandCollectionInterface $commandCollection) {
        $commandCollection->add(new TriggerOrderExportProcessCommand(), 'Oms/Export');

        return $commandCollection;
    });

    $container->extend(self::CONDITION_PLUGINS, function (ConditionCollectionInterface $conditionCollection) {
       $conditionCollection->add(new IsCompleteOrderPlugin(), 'Oms/IsCompleteOrder');
       return $conditionCollection;
    });

    return $container;
} 

Best regards

#12

OmsDependencyProvider looks good. So condition should work. I won’t be able to debug it in general way because it runs every minute by cron. But you can use console oms:check-condition console command to debug.
Please check if your cron runs.

Best regards,
Aleksey Kotsuba

#13

I already ran

XDEBUG_CONFIG="remote_host=10.10.0.1" PHP_IDE_CONFIG="serverName=zed" console oms:check-condition

Debugger stops in OmsDependencyProvider (where the condition is added), but does not stop in IsCompleteOrderPlugin Class. So generally the debugging and adding of the condition works, but will not picked up by the console command … :thinking:

#14

And what about condition itself? Could you please try to return just true in check method and see if item moved to the next point?
About debugging: there can be issue with line numbers where point is placed. Please try to disable opcache for cli in php.ini and try again.

Best reagrds,
Aleksey Kotsuba

#15

Still the same issue with return true … i already placed a die() in this line before … no success …

#16

Ok, i found the error.
I debugged a little bit sql statements and found that on oms:check-condition there is a join with spy_oms_order_process.name property. It tried to match “Order” but in my case there was only an entry with “Shop”.
That was certainly caused by my renaming-the-process action. Its high probably, that the record for the process was already created and i renamed the process name in the xml file afterwards. So it never could match. After renaming the process name in the database it works like a charm.

Just for my knowledge … the filename of the process (e.g. “Shop01.xml”) is never used for creating any database entries? If not, it also could be, that my filename was the reason cause it was “Shop.xml” and the process inside named “Order”. Until now i thought this should be independent from each other. Isn’t it?

Best regards

#17

I have another question for this topic. The oms:check-condition is now checking if all order items are in “order complete” state. If yes, my export will be triggered. In theory, the problem now is that for each item the whole export is triggered. I want to avoid that.
How to limit the export to one time (for the whole order) ? The oms:check-condition would execute my command for each item and triggers the export now which will end for all items in the same singe xml file in my case …
How to prevent that?

Second Question: Is the event and the condition check synchronous? Lets say, the event is executing a middleware connected process pipeline. Does the check condition wait until this pipeline was executed?

Best regards