PluginProbe
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO / 1.4.17
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO v1.4.17
1.4.17 1.4.16 1.4.15 1.4.14 1.4.13 1.4.12 1.4.11 1.4.10 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.54 1.3.53 1.3.52 1.3.51 1.3.50 1.3.49 1.3.48 All 180 releases
← All changes | app/Intents/IntentHelper.php +85 -25 1.4.61.4.17 View file →
@@ -493,8 +493,37 @@
493 493 }
494 494
495 495 $discountable_units = $this->grouped_discount_budget( $rule, $total_qualifying_units );
496 496
497 + /**
498 + * A flat amount is charged for the pooled group as a whole.
499 + *
500 + * Pooling exists so the cart together can reach the tier minimum, so
501 + * the rule qualifies for the group rather than for each line that
502 + * contributed quantity. The Calc layer returns the rule value whole
503 + * for every line it is handed, because a flat amount does not scale
504 + * with units, so spreading it across lines multiplied the discount by
505 + * the line count: a flat 10 came out as 20 over two lines.
506 + *
507 + * It is applied to the first eligible line carrying the entire pooled
508 + * budget, which is what lets a recursive rule still charge once per
509 + * bundle: the Calc layer derives the bundle count from that quantity.
510 + * Per unit types keep every line, since their amount is meant to
511 + * scale with the units.
512 + */
513 + if ( in_array( $rule['discount_type'], array( 'fixed', 'fixed_price' ), true ) ) {
514 + $first_line = reset( $eligible_lines );
515 +
516 + if ( ! empty( $first_line['item'] ) && $discountable_units > 0 ) {
517 + $item = $first_line['item'];
518 + $item['disco_forced_qty'] = $discountable_units;
519 +
520 + $this->apply_rule_to_discount( $discounts, $campaign, $rule, $item, $cart, $total_applicable_qty );
521 + }
522 +
523 + continue;
524 + }
525 +
497 526 // Hand out the discountable units across the eligible lines in cart order.
498 527 foreach ( $eligible_lines as $eligible_line ) {
499 528 if ( $discountable_units <= 0 ) {
500 529 break;
@@ -652,17 +681,10 @@
652 681 * Get a discount limit form campaign.
653 682 *
654 683 * Compare with total product meta and apply discount
655 684 */
656 - $discount_limit = $intent->campaign->discount_max_user;
657 -
658 - // Only count applied orders when a limit is actually configured.
659 - if ( ! empty( $discount_limit ) && $discount_limit >= 0 ) {
660 - $total_applied_campaign = ( new UserLimit )->disco_get_total_applied_campaign( $intent->campaign->id );
661 -
662 - if ( $total_applied_campaign >= $discount_limit ) {
663 - continue;
664 - }
685 + if ( ( new UserLimit )->disco_is_limit_reached( $intent->campaign ) ) {
686 + continue;
665 687 }
666 688
667 689 $items = $this->get_items_for_discount( $cart, $intent->campaign );
668 690 $get_discounts = $intent->get_discounts( $items, $cart );
@@ -670,13 +692,19 @@
670 692 if ( empty( $get_discounts ) ) {
671 693 continue;
672 694 }
673 695
674 - // Only stage campaigns whose discount actually applied to the cart.
675 - ( new UserLimit )->disco_start_session_on_checkout( $intent->campaign->id );
676 -
677 696 foreach ( $get_discounts as $item_id => $discount ) {
697 + /**
698 + * Keep the campaign alongside its amount at the same index.
699 + *
700 + * Staging cannot happen yet: several campaigns can offer an amount
701 + * for the same item and only one of them survives the min/max
702 + * reduction below. Crediting every campaign here would consume the
703 + * usage limit of campaigns whose discount never reached the cart.
704 + */
678 705 $discounts[ $item_id ]['discounts'][] = max( $discount['discounts'] );
706 + $discounts[ $item_id ]['campaigns'][] = (int) $intent->campaign->id;
679 707 }
680 708 }
681 709
682 710 // Check if the discounts are empty.
@@ -683,13 +711,27 @@
683 711 if ( empty( $discounts ) ) {
684 712 return false;
685 713 }
686 714
715 + $applied_campaign_ids = array();
716 +
687 717 // Get the min or max discount amount for each item.
688 718 foreach ( $discounts as $item_id => $discount ) {
689 - $discounts[ $item_id ] = $this->min_max_average( $discount['discounts'] );
719 + $winning_amount = $this->min_max_average( $discount['discounts'] );
720 + $winning_index = array_search( $winning_amount, $discount['discounts'], true );
721 +
722 + if ( false !== $winning_index && isset( $discount['campaigns'][ $winning_index ] ) ) {
723 + $applied_campaign_ids[ $discount['campaigns'][ $winning_index ] ] = true;
724 + }
725 +
726 + $discounts[ $item_id ] = $winning_amount;
690 727 }
691 728
729 + // Stage only the campaigns that actually won an item.
730 + foreach ( array_keys( $applied_campaign_ids ) as $applied_campaign_id ) {
731 + ( new UserLimit )->disco_start_session_on_checkout( $applied_campaign_id );
732 + }
733 +
692 734 return $discounts;
693 735 }
694 736
695 737 /**
@@ -768,9 +810,10 @@
768 810 if ( empty( $intents ) ) {
769 811 return false;
770 812 }
771 813
772 - $discounts = array();
814 + $discounts = array();
815 + $candidate_campaign_ids = array();
773 816
774 817 // Loop through the intents.
775 818 foreach ( $intents as $intent ) {
776 819 /**
@@ -777,17 +820,10 @@
777 820 * Get a discount limit form campaign.
778 821 *
779 822 * Compare with total product meta and apply discount
780 823 */
781 - $discount_limit = $intent->campaign->discount_max_user;
782 -
783 - // Only count applied orders when a limit is actually configured.
784 - if ( ! empty( $discount_limit ) && $discount_limit >= 0 ) {
785 - $total_applied_campaign = ( new UserLimit )->disco_get_total_applied_campaign( $intent->campaign->id );
786 -
787 - if ( $total_applied_campaign >= $discount_limit ) {
788 - continue;
789 - }
824 + if ( ( new UserLimit )->disco_is_limit_reached( $intent->campaign ) ) {
825 + continue;
790 826 }
791 827
792 828 $items = $this->get_items_for_discount( $cart, $intent->campaign );
793 829 $get_discounts = $intent->get_discounts( $items, $cart );
@@ -795,10 +831,18 @@
795 831 if ( empty( $get_discounts ) ) {
796 832 continue;
797 833 }
798 834
799 - // Only stage campaigns whose discount actually applied to the cart.
800 - ( new UserLimit )->disco_start_session_on_checkout( $intent->campaign->id );
835 + /**
836 + * Collect the campaign now, stage it later.
837 + *
838 + * What this method finally grants is decided well below: the rewards
839 + * are unioned across campaigns and a BuyXGetY (products) campaign can
840 + * then override the whole result authoritatively. Crediting a campaign
841 + * here would consume its usage limit even when the final result grants
842 + * it nothing.
843 + */
844 + $candidate_campaign_ids[] = (int) $intent->campaign->id;
801 845
802 846 foreach ( $get_discounts as $item_id => $discount ) {
803 847 $discounts[ $item_id ]['discounts'][] = max( $discount['discounts'] ); // phpcs:ignore
804 848 $discounts[ $item_id ]['free'] = $discount['free']; // phpcs:ignore
@@ -885,8 +929,24 @@
885 929 $discounts['get_qty'] = empty( $reward_map ) ? 0 : max( $reward_map ); // phpcs:ignore
886 930 $discounts['free'] = ! empty( $reward_map ); // phpcs:ignore
887 931 $discounts['bogo_type'] = $bogo_type;
888 932 $discounts['free_item_selection'] = $intent->campaign->get_free_item_selection();
933 +
934 + /**
935 + * This branch is authoritative for the whole result, so the campaign
936 + * that owns it is the only one that can be credited.
937 + */
938 + $candidate_campaign_ids = array( (int) $intent->campaign->id );
939 + }
940 +
941 + /**
942 + * Stage the campaigns only once the result is final, and only when it
943 + * actually grants free items.
944 + */
945 + if ( ! empty( $discounts['free'] ) ) {
946 + foreach ( array_unique( $candidate_campaign_ids ) as $applied_campaign_id ) {
947 + ( new UserLimit )->disco_start_session_on_checkout( $applied_campaign_id );
948 + }
889 949 }
890 950
891 951 return $discounts;
892 952 }