Comment implémenter un workflow d'approbation de commandes personnalisé pour les boutiques B2B
How to Implement a Custom "Order Approval Workflow" for B2B Stores
Running a B2B store on Magento 2? You probably deal with complex purchasing processes where commandes need approval before processing. Unlike B2C, B2B transactions often involve mulconseille decision-makers—managers, procurement teams, or finance departments—who need to avis and approve purchases before they’re finalized.
Magento 2’s default setup doesn’t include a built-in commande approval flux de travail, but with some personnalisation, you can create a seamless approval process tailored to your entreprise needs. Dans ce guide, nous’ll walk through comment implement a custom commande approval flux de travail étape par étape.
Why You Need an Order Approval Workflow
Avant diving into the code, let’s understand why this fonctionnalité is crucial for B2B stores:
- Prevents Unauthorized Purchases: Ensures only approved commandes proceed.
- Multi-Level Approvals: Allows different roles (e.g., managers, finance) to avis commandes.
- Budget Control: Restricts spending beyond predefined limits.
- Audit Trail: Keeps track of who approved what and when.
Step 1: Setting Up the Database Structure
Premièrement, we need a way to store approval requests. We’ll create a new table de base de données to track commande approvals.
// File: app/code/YourVendor/OrderApproval/Setup/InstallSchema.php
use Magento\Framework\DB\Ddl\Table;
use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
class InstallSchema implements InstallSchemaInterface
{
public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
{
$installer = $setup;
$installer->startSetup();
$table = $installer->getConnection()->newTable(
$installer->getTable('order_approval_requests')
)->addColumn(
'approval_id',
Table::TYPE_INTEGER,
null,
['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true],
'Approval ID'
)->addColumn(
'order_id',
Table::TYPE_INTEGER,
null,
['unsigned' => true, 'nullable' => false],
'Order ID'
)->addColumn(
'status',
Table::TYPE_TEXT,
20,
['nullable' => false, 'default' => 'pending'],
'Approval Status (pending/approved/rejected)'
)->addColumn(
'approver_id',
Table::TYPE_INTEGER,
null,
['unsigned' => true, 'nullable' => true],
'Admin User ID who approved/rejected'
)->addColumn(
'comments',
Table::TYPE_TEXT,
'64k',
['nullable' => true],
'Approver Comments'
)->addColumn(
'created_at',
Table::TYPE_TIMESTAMP,
null,
['nullable' => false, 'default' => Table::TIMESTAMP_INIT],
'Creation Time'
)->addColumn(
'updated_at',
Table::TYPE_TIMESTAMP,
null,
['nullable' => false, 'default' => Table::TIMESTAMP_INIT_UPDATE],
'Update Time'
)->addForeignKey(
$installer->getFkName(
'order_approval_requests',
'order_id',
'sales_order',
'entity_id'
),
'order_id',
$installer->getTable('sales_order'),
'entity_id',
Table::ACTION_CASCADE
)->setComment(
'Order Approval Requests'
);
$installer->getConnection()->createTable($table);
$installer->endSetup();
}
}
Step 2: Creating the Approval Logic
Ensuite, we’ll create an observateur that triggers when an commande is placed and creates an approval request.
// File: app/code/YourVendor/OrderApproval/Observer/OrderPlaceAfter.php
namespace YourVendor\OrderApproval\Observer;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use YourVendor\OrderApproval\Model\ApprovalFactory;
class OrderPlaceAfter implements ObserverInterface
{
protected $approvalFactory;
public function __construct(ApprovalFactory $approvalFactory)
{
$this->approvalFactory = $approvalFactory;
}
public function execute(Observer $observer)
{
$order = $observer->getEvent()->getOrder();
// Skip approval for certain customer groups or small orders
if ($order->getCustomerGroupId() == 1 || $order->getGrandTotal() < 1000) {
return;
}
$approval = $this->approvalFactory->create();
$approval->setOrderId($order->getId())
->setStatus('pending')
->save();
// Send email notification to approvers
$this->sendApprovalEmail($order);
}
protected function sendApprovalEmail($order)
{
// Email logic here
}
}
Step 3: Building the Admin Approval Interface
Now we need a way for admins to avis and approve commandes. Nous allons add a new grid in the Magento admin.
// File: app/code/YourVendor/OrderApproval/Block/Adminhtml/Approval/Grid.php
namespace YourVendor\OrderApproval\Block\Adminhtml\Approval;
class Grid extends \Magento\Backend\Block\Widget\Grid\Extended
{
protected $_approvalCollectionFactory;
public function __construct(
\Magento\Backend\Block\Template\Context $context,
\Magento\Backend\Helper\Data $backendHelper,
\YourVendor\OrderApproval\Model\ResourceModel\Approval\CollectionFactory $approvalCollectionFactory,
array $data = []
) {
$this->_approvalCollectionFactory = $approvalCollectionFactory;
parent::__construct($context, $backendHelper, $data);
}
protected function _construct()
{
parent::_construct();
$this->setId('approvalGrid');
$this->setDefaultSort('created_at');
$this->setDefaultDir('DESC');
$this->setSaveParametersInSession(true);
$this->setUseAjax(true);
}
protected function _prepareCollection()
{
$collection = $this->_approvalCollectionFactory->create();
$this->setCollection($collection);
return parent::_prepareCollection();
}
protected function _prepareColumns()
{
$this->addColumn('approval_id', [
'header' => __('ID'),
'index' => 'approval_id',
'type' => 'number'
]);
$this->addColumn('order_id', [
'header' => __('Order #'),
'index' => 'order_id',
'renderer' => \YourVendor\OrderApproval\Block\Adminhtml\Approval\Renderer\Order::class
]);
$this->addColumn('status', [
'header' => __('Status'),
'index' => 'status',
'type' => 'options',
'options' => [
'pending' => __('Pending'),
'approved' => __('Approved'),
'rejected' => __('Rejected')
]
]);
// Add more columns as needed
return parent::_prepareColumns();
}
public function getGridUrl()
{
return $this->getUrl('*/*/grid', ['_current' => true]);
}
}
Step 4: Implementing Approval Actions
Now let's create the contrôleur that handles approval/rejection actions:
// File: app/code/YourVendor/OrderApproval/Controller/Adminhtml/Approval/Approve.php
namespace YourVendor\OrderApproval\Controller\Adminhtml\Approval;
class Approve extends \Magento\Backend\App\Action
{
protected $approvalFactory;
protected $orderRepository;
protected $messageManager;
public function __construct(
\Magento\Backend\App\Action\Context $context,
\YourVendor\OrderApproval\Model\ApprovalFactory $approvalFactory,
\Magento\Sales\Api\OrderRepositoryInterface $orderRepository
) {
parent::__construct($context);
$this->approvalFactory = $approvalFactory;
$this->orderRepository = $orderRepository;
$this->messageManager = $context->getMessageManager();
}
public function execute()
{
$approvalId = $this->getRequest()->getParam('id');
$approval = $this->approvalFactory->create()->load($approvalId);
if (!$approval->getId()) {
$this->messageManager->addErrorMessage(__('Approval request not found.'));
return $this->_redirect('*/*/');
}
try {
$approval->setStatus('approved')
->setApproverId($this->_auth->getUser()->getId())
->save();
$order = $this->orderRepository->get($approval->getOrderId());
$order->setState(\Magento\Sales\Model\Order::STATE_PROCESSING)
->setStatus('processing')
->save();
$this->messageManager->addSuccessMessage(__('Order has been approved.'));
} catch (\Exception $e) {
$this->messageManager->addErrorMessage($e->getMessage());
}
return $this->_redirect('*/*/');
}
}
Step 5: Customizing the Customer Experience
Let's modify the frontend to show statut de commande and approval information to clients:
// File: app/code/YourVendor/OrderApproval/Block/Order/ApprovalStatus.php
namespace YourVendor\OrderApproval\Block\Order;
class ApprovalStatus extends \Magento\Framework\View\Element\Template
{
protected $approvalFactory;
public function __construct(
\Magento\Framework\View\Element\Template\Context $context,
\YourVendor\OrderApproval\Model\ApprovalFactory $approvalFactory,
array $data = []
) {
$this->approvalFactory = $approvalFactory;
parent::__construct($context, $data);
}
public function getApprovalStatus($orderId)
{
$approval = $this->approvalFactory->create()
->getCollection()
->addFieldToFilter('order_id', $orderId)
->getFirstItem();
if ($approval->getId()) {
return $approval->getStatus();
}
return 'approved'; // Default if no approval needed
}
}
Step 6: Adding Email Notifications
Communication is clé in approval flux de travails. Let's set up e-mail notifications:
// File: app/code/YourVendor/OrderApproval/Model/Email/Sender.php
namespace YourVendor\OrderApproval\Model\Email;
class Sender
{
protected $transportBuilder;
protected $storeManager;
protected $inlineTranslation;
public function __construct(
\Magento\Framework\Mail\Template\TransportBuilder $transportBuilder,
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Magento\Framework\Translate\Inline\StateInterface $inlineTranslation
) {
$this->transportBuilder = $transportBuilder;
$this->storeManager = $storeManager;
$this->inlineTranslation = $inlineTranslation;
}
public function sendApprovalRequestEmail($order, $approvers)
{
$this->inlineTranslation->suspend();
$storeId = $this->storeManager->getStore()->getId();
$templateVars = [
'order' => $order,
'approve_url' => $this->getApprovalUrl($order->getId())
];
foreach ($approvers as $approver) {
$transport = $this->transportBuilder
->setTemplateIdentifier('order_approval_request')
->setTemplateOptions(['area' => 'frontend', 'store' => $storeId])
->setTemplateVars($templateVars)
->setFrom('general')
->addTo($approver->getEmail(), $approver->getName())
->getTransport();
$transport->sendMessage();
}
$this->inlineTranslation->resume();
}
}
Advanced Customizations
Une fois you have the basic flux de travail working, consider these enhancements:
- Multi-Level Approvals: Create sequential approval étapes for different departments.
- Approval Rules: Set rules basé sur commande amount, groupe de clients, or product category.
- Partial Approvals: Allow approvers to modify quantities before approval.
- Escalation Rules: Automatically escalate if approval takes too long.
Testing Your Workflow
Avant going live, thoroughly test:
- Place commandes that should and shouldn't require approval
- Test approval/rejection from admin
- Verify e-mail notifications
- Check statut de commande updates
- Test with mulconseille approvers if applicable
Conclusion
Implementing a custom commande approval flux de travail in Magento 2 requires several composants working together: database tracking, admin interfaces, frontend displays, and e-mail notifications. Tandis que this guide covers the fundamentals, you can extend it further to match your specific entreprise processes.
For entreprisees that prefer ready-made solutions, check out Magefine's Order Approval Extension which offers these fonctionnalités and more prêt à l'emploi.
Have you implemented an approval flux de travail in your Magento store? Share your experiences in the comments!