get_id();
} elseif ( isset( $post_or_order->ID ) ) {
$order_id = (int) $post_or_order->ID;
}
echo '
';
$this->render_panel( $order_id );
echo '
';
}
/**
* @param int $order_id Order ID.
* @return void
*/
private function render_panel( $order_id ) {
// Defense-in-depth: hide risk panel from non-admin roles.
// WooCommerce does not restrict admin order editor URLs by role,
// so any logged-in user could access this page via direct URL.
if ( ! Capabilities::current_user_can_manage() ) {
return;
}
$row = ( new OrderRiskRepository() )->get_current( $order_id );
if ( ! $row ) {
echo '' . esc_html__( 'Not evaluated yet.', 'marginbeacon' ) . '
';
return;
}
if ( isset( $_GET['aksmb_ack'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$ack = wp_unslash( $_GET['aksmb_ack'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Exact equality gate only.
$ok = is_string( $ack ) && '1' === $ack;
$msg = isset( $_GET['aksmb_msg'] ) ? sanitize_text_field( rawurldecode( wp_unslash( $_GET['aksmb_msg'] ) ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
printf(
'',
esc_attr( $ok ? 'success' : 'error' ),
esc_html( $msg ? $msg : ( $ok ? __( 'Saved.', 'marginbeacon' ) : __( 'Failed.', 'marginbeacon' ) ) )
);
}
$dual = DualContextPresenter::from_row( $row );
$badge = $row['risk_badge'] ? (string) $row['risk_badge'] : '—';
$summary = $dual['summary'];
$order = function_exists( 'wc_get_order' ) ? wc_get_order( $order_id ) : null;
$refunded = OrderRefundLifecycle::should_show_label( $row, $order );
if ( $refunded && ! OrderRefundLifecycle::row_is_marked_refunded( $row ) ) {
echo '' . esc_html__( 'Refunded', 'marginbeacon' ) . '';
}
echo '';
echo '- ' . esc_html__( 'Presentation', 'marginbeacon' ) . '' . esc_html( PresentationState::label( (string) $row['presentation_state'] ) ) . '
';
echo '- ' . esc_html__( 'Badge', 'marginbeacon' ) . '';
if ( $row['risk_badge'] ) {
$cov = OrderRefundLifecycle::coverage_from_row( $row );
$shipping_excluded = ( 'safe' === $row['risk_badge'] && FreeShippingScope::coverage_excludes_shipping( $cov ) );
$badge_label = $shipping_excluded ? FreeShippingScope::safe_label() : Severity::label( (string) $row['risk_badge'] );
$badge_class = 'aksmb-badge aksmb-badge--' . sanitize_html_class( (string) $row['risk_badge'] );
if ( $shipping_excluded || strlen( $badge_label ) > 18 ) {
$badge_class .= ' aksmb-badge--wrap';
}
printf(
'%2$s',
esc_attr( $badge_class ),
esc_html( $badge_label )
);
} else {
echo esc_html( $badge );
}
echo '
';
if ( PaymentResultState::is_preliminary( PaymentResultState::from_row( $row ) ) ) {
echo '- ' . esc_html__( 'Payment', 'marginbeacon' ) . '' . esc_html( PaymentResultState::preliminary_note() ) . '
';
}
if ( $refunded ) {
echo '- ' . esc_html__( 'Refund', 'marginbeacon' ) . '' . esc_html( OrderRefundLifecycle::label() ) . '
';
}
if ( $summary ) {
echo '- ' . esc_html__( 'Dual context', 'marginbeacon' ) . '' . esc_html( $summary ) . '
';
}
echo '- ' . esc_html__( 'Signal', 'marginbeacon' ) . '' . esc_html( $this->signal_label( $row ) ) . '
';
echo '- ' . esc_html__( 'Origin', 'marginbeacon' ) . '' . esc_html( $this->origin_label( $row ) ) . '
';
echo '- ' . esc_html__( 'Revenue', 'marginbeacon' ) . '' . esc_html( null === $row['revenue'] ? '—' : (string) $row['revenue'] ) . '
';
echo '- ' . esc_html__( 'COGS', 'marginbeacon' ) . '' . esc_html( null === $row['cogs'] ? '—' : (string) $row['cogs'] ) . '
';
$reason_text = ReasonPresenter::from_row( $row );
echo '- ' . esc_html__( 'Reason', 'marginbeacon' ) . '' . esc_html( $reason_text ? $reason_text : '—' ) . '
';
echo '- ' . esc_html__( 'Formula', 'marginbeacon' ) . '' . esc_html( (string) $row['formula_version'] ) . '
';
echo '
';
$this->render_recalculation_notice( $row );
$this->render_retry_form( $order_id, $row );
$this->render_acknowledgement_forms( $order_id, $row );
/**
* Pro actions under order risk panel (e.g. View price history).
*
* @param array $row Current order risk snapshot.
* @param int $order_id Order ID.
*/
do_action( 'aksmb_order_risk_pro_actions', $row, $order_id );
echo '' . esc_html__( 'MarginBeacon calculates gross product margin estimates only. Results do not constitute financial or accounting advice. Acknowledgement never changes severity.', 'marginbeacon' ) . '
';
}
/**
* Secondary recalculation state while last completed result remains visible (PRD FR-055).
*
* @param array $row Snapshot.
* @return void
*/
private function render_recalculation_notice( array $row ) {
$state = isset( $row['recalculation_state'] ) ? (string) $row['recalculation_state'] : '';
if ( '' === $state ) {
return;
}
$messages = array(
'queued' => __( 'Recalculation queued — last completed result shown until replacement finishes.', 'marginbeacon' ),
'running' => __( 'Recalculation running — last completed result shown until replacement finishes.', 'marginbeacon' ),
'failed' => __( 'Recalculation failed — last completed result shown.', 'marginbeacon' ),
);
$msg = isset( $messages[ $state ] ) ? $messages[ $state ] : '';
if ( ! $msg ) {
return;
}
$error = ! empty( $row['recalculation_error_code'] ) ? (string) $row['recalculation_error_code'] : '';
$class = 'failed' === $state ? 'notice-error' : 'notice-info';
echo '';
echo esc_html( $msg );
if ( $error ) {
echo ' ' . esc_html( $error ) . '';
}
echo '
';
}
/**
* Failed retry + Free basic refresh.
*
* @param int $order_id Order ID.
* @param array $row Current snapshot.
* @return void
*/
private function render_retry_form( $order_id, array $row ) {
/**
* Whether to show Free basic refresh (Pro may replace with Recalculate risk).
*
* @param bool $show Default true.
* @param array $row Snapshot.
* @param int $order_id Order ID.
*/
if ( ! apply_filters( 'aksmb_order_risk_show_basic_refresh', true, $row, $order_id ) ) {
return;
}
$order = function_exists( 'wc_get_order' ) ? wc_get_order( $order_id ) : null;
if ( OrderRefundLifecycle::should_show_label( $row, $order ) ) {
return; // Never refresh into refund-adjusted economics.
}
$presentation = isset( $row['presentation_state'] ) ? (string) $row['presentation_state'] : '';
$processing = isset( $row['processing_state'] ) ? (string) $row['processing_state'] : '';
$recalc = isset( $row['recalculation_state'] ) ? (string) $row['recalculation_state'] : '';
$is_failed = ( 'failed' === $presentation || 'failed' === $processing || 'failed' === $recalc );
$is_pending = ( 'pending' === $presentation || in_array( $processing, array( 'queued', 'running' ), true ) )
|| in_array( $recalc, array( 'queued', 'running' ), true );
$signal = isset( $row['promotion_signal'] ) ? (string) $row['promotion_signal'] : 'none';
$can_refresh = false;
if ( $is_failed ) {
$can_refresh = true;
} elseif ( ! $is_pending && 'completed' === $processing && 'none' !== $signal ) {
$can_refresh = true;
} elseif ( ! $is_pending && in_array( $presentation, array( 'financial_result', 'unsupported' ), true ) ) {
$can_refresh = true;
}
if ( ! $can_refresh ) {
return;
}
$label = $is_failed
? __( 'Retry evaluation', 'marginbeacon' )
: __( 'Refresh evaluation', 'marginbeacon' );
?>
get_items( 'line_item' ) as $item ) {
$product_id = (int) $item->get_product_id();
$variation_id = (int) $item->get_variation_id();
$key = $variation_id ? 'v' . $variation_id : 'p' . $product_id;
if ( isset( $seen[ $key ] ) ) {
continue;
}
$seen[ $key ] = true;
$active = $svc->active_for_catalog( $product_id, $variation_id );
if ( ! $active && ! $offer_new ) {
continue;
}
$entries[] = array(
'name' => $item->get_name(),
'active' => $active,
'scope' => $variation_id ? AcknowledgementService::SCOPE_VARIATION : AcknowledgementService::SCOPE_PRODUCT,
'entity' => $variation_id ? $variation_id : $product_id,
);
}
foreach ( $order->get_coupon_codes() as $code ) {
$coupon = new \WC_Coupon( (string) $code );
if ( ! $coupon->get_id() ) {
continue;
}
$coupon_id = (int) $coupon->get_id();
$key = 'c' . $coupon_id;
if ( isset( $seen[ $key ] ) ) {
continue;
}
$seen[ $key ] = true;
$active = $svc->active_for_coupon( $coupon_id );
if ( ! $active && ! $offer_new ) {
continue;
}
$entries[] = array(
/* translators: %s: coupon code */
'name' => sprintf( __( 'Coupon: %s', 'marginbeacon' ), $coupon->get_code() ),
'active' => $active,
'scope' => AcknowledgementService::SCOPE_COUPON,
'entity' => $coupon_id,
);
}
if ( empty( $entries ) ) {
return;
}
echo '';
echo '
' . esc_html__( 'Acknowledged loss leader', 'marginbeacon' ) . '
';
echo '
' . esc_html__( 'Mark intentional below-cost items. Severity stays Critical/High/Warning; only dashboard noise and work-list grouping change.', 'marginbeacon' ) . '
';
foreach ( $entries as $entry ) {
echo '
';
echo '
' . esc_html( $entry['name'] ) . '
';
if ( $entry['active'] ) {
echo '
';
echo '' . esc_html__( 'Acknowledged', 'marginbeacon' ) . '';
echo '
';
if ( ! empty( $entry['active']['reason'] ) ) {
echo '
' . esc_html__( 'Reason', 'marginbeacon' ) . ': ' . esc_html( (string) $entry['active']['reason'] ) . '
';
}
echo '
';
} else {
echo '
';
}
echo '
';
}
echo '
';
}
/**
* @param array $row Risk row.
* @return string
*/
private function signal_label( array $row ) {
$signal = isset( $row['promotion_signal'] ) ? (string) $row['promotion_signal'] : 'none';
$conf = isset( $row['promotion_signal_confidence'] ) ? (string) $row['promotion_signal_confidence'] : 'none';
$signal_labels = array(
'coupon' => __( 'Coupon', 'marginbeacon' ),
'sale_price' => __( 'Sale price', 'marginbeacon' ),
'both' => __( 'Coupon + sale', 'marginbeacon' ),
'none' => __( 'None', 'marginbeacon' ),
);
$conf_labels = array(
'verified' => __( 'Verified', 'marginbeacon' ),
'suspected' => __( 'Suspected', 'marginbeacon' ),
'none' => __( 'None', 'marginbeacon' ),
);
$left = isset( $signal_labels[ $signal ] ) ? $signal_labels[ $signal ] : $signal;
$right = isset( $conf_labels[ $conf ] ) ? $conf_labels[ $conf ] : $conf;
return $left . ' / ' . $right;
}
/**
* @param array $row Risk row.
* @return string
*/
private function origin_label( array $row ) {
$origin = ! empty( $row['evaluation_origin'] ) ? (string) $row['evaluation_origin'] : 'realtime';
if ( 'recent_preview' === $origin ) {
return __( 'Recent orders preview', 'marginbeacon' );
}
if ( 'realtime' === $origin ) {
return __( 'Realtime', 'marginbeacon' );
}
return ucfirst( str_replace( '_', ' ', $origin ) );
}
}