Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions core/components/minishop3/lexicon/en/default.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,5 @@
$_lang['ms3_mgr_order_recalc_invalid_mode'] = 'Invalid order cost recalculation mode.';
$_lang['ms3_mgr_order_recalc_manual_delivery_missing'] = 'Manual delivery cost (manual_delivery_cost) is required in manual mode.';
$_lang['ms3_order_cost_recalc_success'] = 'Order cost recalculated';
$_lang['ms3_order_finalize_cost_recalc_required'] =
'Recalculate order cost before finalizing: the selected delivery or payment requires manual cost or force_provider mode.';
2 changes: 2 additions & 0 deletions core/components/minishop3/lexicon/en/vue.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,8 @@
$_lang['ms3_order_finalize_confirm_desc'] = 'After finalization, the order will receive a number, status will change to "New", and notifications will be sent.';
$_lang['ms3_order_finalized'] = 'Order successfully finalized';
$_lang['ms3_order_finalize_error'] = 'Error finalizing order';
$_lang['ms3_order_finalize_cost_recalc_required'] =
'Recalculate order cost before finalizing: the selected delivery or payment requires manual cost or force_provider mode.';
$_lang['ms3_order_is_draft'] = 'Draft';
$_lang['ms3_order_err_validation'] = 'Order data validation error';
$_lang['ms3_order_err_products'] = 'Order has no products';
Expand Down
2 changes: 2 additions & 0 deletions core/components/minishop3/lexicon/ru/default.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,5 @@
$_lang['ms3_mgr_order_recalc_invalid_mode'] = 'Недопустимый режим пересчёта стоимости заказа.';
$_lang['ms3_mgr_order_recalc_manual_delivery_missing'] = 'В режиме manual обязательно укажите manual_delivery_cost (стоимость доставки).';
$_lang['ms3_order_cost_recalc_success'] = 'Стоимость заказа пересчитана';
$_lang['ms3_order_finalize_cost_recalc_required'] =
'Перед оформлением пересчитайте стоимость заказа: для выбранных доставки или оплаты нужен ручной расчёт или force_provider.';
2 changes: 2 additions & 0 deletions core/components/minishop3/lexicon/ru/vue.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,8 @@
$_lang['ms3_order_finalize_confirm_desc'] = 'После оформления заказ получит номер, статус изменится на «Новый», и будут отправлены уведомления.';
$_lang['ms3_order_finalized'] = 'Заказ успешно оформлен';
$_lang['ms3_order_finalize_error'] = 'Ошибка при оформлении заказа';
$_lang['ms3_order_finalize_cost_recalc_required'] =
'Перед оформлением пересчитайте стоимость заказа: для выбранных доставки или оплаты нужен ручной расчёт или force_provider.';
$_lang['ms3_order_is_draft'] = 'Черновик';
$_lang['ms3_order_err_validation'] = 'Ошибка валидации данных заказа';
$_lang['ms3_order_err_products'] = 'В заказе нет товаров';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@
use MODX\Revolution\modX;

/**
* Explicit manager-side recomputation of order totals from persisted msOrderProducts
* and configured delivery/payment methods (without mutating unrelated order fields).
* Recomputation of order totals from persisted msOrderProducts and configured delivery/payment.
*
* Used by manager «Пересчитать стоимость» ({@see calculateBreakdown()} / {@see recalculate()})
* and draft finalize ({@see OrderFinalizeService}).
*
* External delivery/payment provider classes are not invoked in {@see self::MODE_AUTO};
* callers should use manual delivery cost or {@see self::MODE_FORCE_PROVIDER}.
*
* Payment commission is reflected only in aggregated {@see msOrder cost}, not stored in a column.
* Default handler math lives in {@see OrderPersistedCostRules}.
*/
class ManagerOrderCostRecalculator
{
Expand Down Expand Up @@ -67,19 +70,17 @@ public function calculateProductTotals(msOrder $order): array
}

/**
* Compute cost breakdown without persisting the order.
*
* Shared by manager recalculate and draft finalize so both paths use the same rules.
*
* @param array<string, mixed> $options
* @return array{success: bool, message?: string, data?: array}
*/
public function recalculate(msOrder $order, array $options = []): array
public function calculateBreakdown(msOrder $order, array $options = []): array
{
$mode = (string) ($options['mode'] ?? self::MODE_AUTO);

$totals = $this->calculateProductTotals($order);
$cartCost = $totals['cart_cost'];
$orderWeight = $totals['weight'];

$warnings = [];

if ($mode !== self::MODE_AUTO && $mode !== self::MODE_MANUAL && $mode !== self::MODE_FORCE_PROVIDER) {
return $this->ms3->utils->error('ms3_mgr_order_recalc_invalid_mode');
}
Expand All @@ -88,14 +89,19 @@ public function recalculate(msOrder $order, array $options = []): array
return $this->ms3->utils->error('ms3_mgr_order_recalc_manual_delivery_missing');
}

$totals = $this->calculateProductTotals($order);
$cartCost = $totals['cart_cost'];
$orderWeight = $totals['weight'];

$warnings = [];

$prevDeliveryCost = round((float) $order->get('delivery_cost'), 6);
$deliveryResult = $this->resolveDeliveryCost($order, $cartCost, $orderWeight, $prevDeliveryCost, $mode, $options);
if (!$deliveryResult['success']) {
return $deliveryResult;
}

$warnings = array_merge($warnings, $deliveryResult['warnings']);

$deliveryCost = $deliveryResult['delivery_cost'];

$paymentBase = round($cartCost + $deliveryCost, 6);
Expand All @@ -105,13 +111,43 @@ public function recalculate(msOrder $order, array $options = []): array
}

$warnings = array_merge($warnings, $paymentResult['warnings']);

$paymentFee = $paymentResult['payment_fee'];

/** @var OrderService $orderService */
$orderService = $this->modx->services->get('ms3_order_service');
$cost = round($orderService->clampComputedTotal($order, $cartCost, $deliveryCost, $paymentFee), 6);

return $this->ms3->utils->success('', [
'breakdown' => [
'cart_cost' => $cartCost,
'weight' => $orderWeight,
'delivery_cost' => $deliveryCost,
'payment_cost' => $paymentFee,
'cost' => $cost,
],
'warnings' => $warnings,
]);
}

/**
* @param array<string, mixed> $options
* @return array{success: bool, message?: string, data?: array}
*/
public function recalculate(msOrder $order, array $options = []): array
{
$result = $this->calculateBreakdown($order, $options);
if (!$result['success']) {
return $result;
}

$breakdown = $result['data']['breakdown'];
$warnings = $result['data']['warnings'];
$cartCost = $breakdown['cart_cost'];
$orderWeight = $breakdown['weight'];
$deliveryCost = $breakdown['delivery_cost'];
$paymentFee = $breakdown['payment_cost'];
$cost = $breakdown['cost'];

$before = [
'cart_cost' => (float)$order->get('cart_cost'),
'delivery_cost' => (float)$order->get('delivery_cost'),
Expand Down Expand Up @@ -327,32 +363,18 @@ protected function resolvePaymentFee(msOrder $order, float $paymentBase, string

protected function calculateDefaultDeliveryCost(msDelivery $delivery, float $cartCost, float $orderWeight): float
{
$freeDeliveryAmount = (float)$delivery->get('free_delivery_amount');

if ($freeDeliveryAmount > 0 && $cartCost >= $freeDeliveryAmount) {
return 0.0;
}

$deliveryCost = 0.0;
$weightPrice = (float)$delivery->get('weight_price');
$weightPrice = (float) $delivery->get('weight_price');
if ($weightPrice < 0) {
$this->modx->log(
modX::LOG_LEVEL_ERROR,
'[ManagerOrderCostRecalculator] Invalid weight_price for delivery #' . $delivery->get(
'id'
) . ': ' . $weightPrice,
);
$weightPrice = 0;
}

$deliveryCost += $weightPrice * $orderWeight;

$addPrice = $delivery->get('price');
if (empty($addPrice)) {
return round($deliveryCost, 6);
}

if (PriceAdjustment::isPercent($addPrice)) {
if (!empty($addPrice) && PriceAdjustment::isPercent($addPrice)) {
$percent = PriceAdjustment::getPercent($addPrice);
if (!PriceAdjustment::isAllowedPercent($percent)) {
$this->modx->log(
Expand All @@ -363,12 +385,16 @@ protected function calculateDefaultDeliveryCost(msDelivery $delivery, float $car
$percent
)
);

return round($deliveryCost, 6);
}
}

return round($deliveryCost + PriceAdjustment::calculate($cartCost, $addPrice), 6);
return OrderPersistedCostRules::calculateDefaultDeliveryCost(
(float) $delivery->get('free_delivery_amount'),
$weightPrice,
$addPrice,
$cartCost,
$orderWeight
);
}

/**
Expand All @@ -377,11 +403,7 @@ protected function calculateDefaultDeliveryCost(msDelivery $delivery, float $car
protected function calculateDefaultPaymentCommission(msPayment $payment, float $baseCost): float
{
$addPrice = $payment->get('price');
if (empty($addPrice)) {
return 0.0;
}

if (PriceAdjustment::isPercent($addPrice)) {
if (!empty($addPrice) && PriceAdjustment::isPercent($addPrice)) {
$percent = PriceAdjustment::getPercent($addPrice);
if (!PriceAdjustment::isAllowedPercent($percent)) {
$this->modx->log(
Expand All @@ -392,12 +414,10 @@ protected function calculateDefaultPaymentCommission(msPayment $payment, float $
$percent
)
);

return 0.0;
}
}

return round(PriceAdjustment::calculate($baseCost, $addPrice), 6);
return OrderPersistedCostRules::calculateDefaultPaymentCommission($addPrice, $baseCost);
}

protected function isSimpleDelivery(msDelivery $delivery): bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,11 @@ public function finalize(int $orderId, array $options = []): array
$order->set('cost', $costResult['data']['total_cost']);
$order->set('cart_cost', $costResult['data']['cart_cost']);
$order->set('delivery_cost', $costResult['data']['delivery_cost']);
$order->save();
$order->set('weight', $costResult['data']['weight']);

if (!$order->save()) {
return $this->error('ms3_err_unknown');
}

// Event: before create order (same as frontend)
$response = $this->ms3->utils->invokeEvent('msOnBeforeCreateOrder', [
Expand Down Expand Up @@ -380,50 +384,37 @@ protected function validateDeliveryRequiredFields(msOrder $order): array
*/
protected function calculateCosts(msOrder $order): array
{
// Calculate cart cost from order products
$cartCost = 0;
$weight = 0;
$recalculator = new ManagerOrderCostRecalculator($this->modx, $this->ms3);
$result = $recalculator->calculateBreakdown($order);

$products = $this->modx->getIterator(msOrderProduct::class, [
'order_id' => $order->get('id'),
]);

foreach ($products as $product) {
$cartCost += (float) $product->get('cost');
$weight += (float) $product->get('weight') * (int) $product->get('count');
if (!$result['success']) {
return $result;
}

// Calculate delivery cost
$deliveryCost = 0;
$deliveryId = (int) $order->get('delivery_id');
$breakdown = $result['data']['breakdown'];
$warnings = $result['data']['warnings'] ?? [];

if ($deliveryId > 0) {
/** @var msDelivery $delivery */
$delivery = $this->modx->getObject(msDelivery::class, $deliveryId);
if ($delivery) {
// Use delivery's getCost method if available, otherwise use fixed price
$deliveryCost = (float) $delivery->get('price');

// Check for weight-based pricing
$weightPrice = (float) $delivery->get('weight_price');
if ($weightPrice > 0 && $weight > 0) {
$deliveryCost += $weight * $weightPrice;
}
}
}
if ($warnings !== []) {
$this->modx->log(
modX::LOG_LEVEL_WARN,
'[OrderFinalizeService] Cost calculation warnings for order #'
. $order->get('id')
. ': '
. implode(', ', $warnings)
);

// Update order weight
$order->set('weight', $weight);
return $this->error('ms3_order_finalize_cost_recalc_required', [
'warnings' => $warnings,
]);
}

/** @var OrderService $orderService */
$orderService = $this->modx->services->get('ms3_order_service');
$totalCost = $orderService->clampComputedTotal($order, (float) $cartCost, (float) $deliveryCost, 0.0);
$order->set('weight', $breakdown['weight']);

return $this->success('', [
'cart_cost' => $cartCost,
'delivery_cost' => $deliveryCost,
'total_cost' => $totalCost,
'weight' => $weight,
'cart_cost' => $breakdown['cart_cost'],
'delivery_cost' => $breakdown['delivery_cost'],
'total_cost' => $breakdown['cost'],
'weight' => $breakdown['weight'],
]);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

namespace MiniShop3\Services\Order;

use MiniShop3\Utils\PriceAdjustment;

/**
* Pure cost rules for orders built from persisted msOrderProducts (manager recalculate, draft finalize).
*
* Shared by {@see ManagerOrderCostRecalculator} and smoke tests; no MODX I/O.
*/
final class OrderPersistedCostRules
{
public static function calculateDefaultDeliveryCost(
float $freeDeliveryAmount,
float $weightPrice,
mixed $addPrice,
float $cartCost,
float $orderWeight
): float {
if ($freeDeliveryAmount > 0 && $cartCost >= $freeDeliveryAmount) {
return 0.0;
}

if ($weightPrice < 0) {
$weightPrice = 0.0;
}

$deliveryCost = $weightPrice * $orderWeight;

if (empty($addPrice)) {
return round($deliveryCost, 6);
}

if (PriceAdjustment::isPercent($addPrice)) {
$percent = PriceAdjustment::getPercent($addPrice);
if (!PriceAdjustment::isAllowedPercent($percent)) {
return round($deliveryCost, 6);
}
}

return round($deliveryCost + PriceAdjustment::calculate($cartCost, $addPrice), 6);
}

/**
* Surcharge only (excluding base), aligned with {@see \MiniShop3\Controllers\Payment\Payment::getCost()}.
*/
public static function calculateDefaultPaymentCommission(mixed $addPrice, float $baseCost): float
{
if (empty($addPrice)) {
return 0.0;
}

if (PriceAdjustment::isPercent($addPrice)) {
$percent = PriceAdjustment::getPercent($addPrice);
if (!PriceAdjustment::isAllowedPercent($percent)) {
return 0.0;
}
}

return round(PriceAdjustment::calculate($baseCost, $addPrice), 6);
}
}
Loading
Loading