@@ -33,6 +33,22 @@ class Order extends Generator {
3333 */
3434 const SECOND_REFUND_MAX_DAYS = 30 ;
3535
36+ /**
37+ * Pre-generated coupon flags for exact ratio distribution in batch mode.
38+ * Each element is a boolean: true = apply coupon, false = skip.
39+ *
40+ * @var array|null
41+ */
42+ protected static $ batch_coupon_flags = null ;
43+
44+ /**
45+ * Pre-generated refund flags for exact ratio distribution in batch mode.
46+ * Each element is a string: 'none', 'full', 'partial', or 'multi'.
47+ *
48+ * @var array|null
49+ */
50+ protected static $ batch_refund_flags = null ;
51+
3652 /**
3753 * Return a new order.
3854 *
@@ -115,20 +131,26 @@ public static function generate( $save = true, $assoc_args = array() ) {
115131
116132 // Handle --coupon-ratio parameter
117133 if ( isset ( $ assoc_args ['coupon-ratio ' ] ) ) {
118- $ coupon_ratio = floatval ( $ assoc_args ['coupon-ratio ' ] );
134+ // Use exact ratio flag if in batch mode
135+ if ( null !== self ::$ batch_coupon_flags && ! empty ( self ::$ batch_coupon_flags ) ) {
136+ $ include_coupon = array_shift ( self ::$ batch_coupon_flags );
137+ } else {
138+ // Fall back to probabilistic approach for single order generation
139+ $ coupon_ratio = floatval ( $ assoc_args ['coupon-ratio ' ] );
119140
120- // Validate ratio is between 0.0 and 1.0
121- if ( $ coupon_ratio < 0.0 || $ coupon_ratio > 1.0 ) {
122- $ coupon_ratio = max ( 0.0 , min ( 1.0 , $ coupon_ratio ) );
123- }
141+ // Validate ratio is between 0.0 and 1.0
142+ if ( $ coupon_ratio < 0.0 || $ coupon_ratio > 1.0 ) {
143+ $ coupon_ratio = max ( 0.0 , min ( 1.0 , $ coupon_ratio ) );
144+ }
124145
125- // Apply coupon based on ratio
126- if ( $ coupon_ratio >= 1.0 ) {
127- $ include_coupon = true ;
128- } elseif ( $ coupon_ratio > 0 && wp_rand ( 1 , 100 ) <= ( $ coupon_ratio * 100 ) ) {
129- $ include_coupon = true ;
130- } else {
131- $ include_coupon = false ;
146+ // Apply coupon based on ratio
147+ if ( $ coupon_ratio >= 1.0 ) {
148+ $ include_coupon = true ;
149+ } elseif ( $ coupon_ratio > 0 && wp_rand ( 1 , 100 ) <= ( $ coupon_ratio * 100 ) ) {
150+ $ include_coupon = true ;
151+ } else {
152+ $ include_coupon = false ;
153+ }
132154 }
133155 }
134156
@@ -163,29 +185,43 @@ public static function generate( $save = true, $assoc_args = array() ) {
163185
164186 // Handle --refund-ratio parameter for completed orders
165187 if ( isset ( $ assoc_args ['refund-ratio ' ] ) && 'completed ' === $ status ) {
166- $ refund_ratio = floatval ( $ assoc_args ['refund-ratio ' ] );
167-
168- // Validate ratio is between 0.0 and 1.0
169- if ( $ refund_ratio < 0.0 || $ refund_ratio > 1.0 ) {
170- $ refund_ratio = max ( 0.0 , min ( 1.0 , $ refund_ratio ) );
171- }
172-
173- $ should_refund = false ;
188+ $ refund_type = 'none ' ;
189+
190+ // Use exact ratio flag if in batch mode
191+ if ( null !== self ::$ batch_refund_flags && ! empty ( self ::$ batch_refund_flags ) ) {
192+ $ refund_type = array_shift ( self ::$ batch_refund_flags );
193+ } else {
194+ // Fall back to probabilistic approach for single order generation
195+ $ refund_ratio = floatval ( $ assoc_args ['refund-ratio ' ] );
196+
197+ // Validate ratio is between 0.0 and 1.0
198+ if ( $ refund_ratio < 0.0 || $ refund_ratio > 1.0 ) {
199+ $ refund_ratio = max ( 0.0 , min ( 1.0 , $ refund_ratio ) );
200+ }
174201
175- if ( $ refund_ratio >= 1.0 ) {
176- // Always refund if ratio is 1.0 or higher
177- $ should_refund = true ;
178- } elseif ( $ refund_ratio > 0 && wp_rand ( 1 , 100 ) <= ( $ refund_ratio * 100 ) ) {
179- // Use random chance for ratios between 0 and 1
180- $ should_refund = true ;
202+ if ( $ refund_ratio >= 1.0 ) {
203+ // Always refund if ratio is 1.0 or higher
204+ $ refund_type = 'full ' ;
205+ } elseif ( $ refund_ratio > 0 && wp_rand ( 1 , 100 ) <= ( $ refund_ratio * 100 ) ) {
206+ // Use random chance for ratios between 0 and 1
207+ // Split evenly between full and partial
208+ $ refund_type = (bool ) wp_rand ( 0 , 1 ) ? 'full ' : 'partial ' ;
209+
210+ // 25% chance for multi-partial
211+ if ( 'partial ' === $ refund_type && wp_rand ( 1 , 100 ) <= self ::SECOND_REFUND_PROBABILITY ) {
212+ $ refund_type = 'multi ' ;
213+ }
214+ }
181215 }
182216
183- if ( $ should_refund ) {
184- // Create first refund with date within 2 months of completion
185- $ first_refund = self ::create_refund ( $ order );
186-
187- // Some partial refunds get a second refund (always partial)
188- if ( $ first_refund && wp_rand ( 1 , 100 ) <= self ::SECOND_REFUND_PROBABILITY ) {
217+ // Process refund based on type
218+ if ( 'full ' === $ refund_type ) {
219+ self ::create_refund ( $ order );
220+ } elseif ( 'partial ' === $ refund_type ) {
221+ self ::create_refund ( $ order , true );
222+ } elseif ( 'multi ' === $ refund_type ) {
223+ $ first_refund = self ::create_refund ( $ order , true );
224+ if ( $ first_refund ) {
189225 self ::create_refund ( $ order , true , $ first_refund );
190226 }
191227 }
@@ -218,13 +254,19 @@ public static function batch( $amount, array $args = array() ) {
218254 return $ amount ;
219255 }
220256
257+ // Initialize exact ratio flags for deterministic distribution
258+ self ::init_ratio_flags ( $ amount , $ args );
259+
221260 $ order_ids = array ();
222261
223262 for ( $ i = 1 ; $ i <= $ amount ; $ i ++ ) {
224263 $ order = self ::generate ( true , $ args );
225264 $ order_ids [] = $ order ->get_id ();
226265 }
227266
267+ // Clear ratio flags after batch generation
268+ self ::clear_ratio_flags ();
269+
228270 return $ order_ids ;
229271 }
230272
@@ -737,4 +779,67 @@ protected static function calculate_refund_date( $order, $previous_refund = null
737779 $ random_days = wp_rand ( 0 , $ max_days );
738780 return date ( 'Y-m-d H:i:s ' , strtotime ( $ base_date ->date ( 'Y-m-d H:i:s ' ) ) + ( $ random_days * DAY_IN_SECONDS ) );
739781 }
782+
783+ /**
784+ * Initialize exact ratio flags for batch generation.
785+ * Creates pre-generated arrays for exact distribution of coupons and refunds.
786+ *
787+ * @param int $count Number of orders to generate.
788+ * @param array $args Arguments containing ratio parameters.
789+ * @return void
790+ */
791+ protected static function init_ratio_flags ( $ count , $ args ) {
792+ // Initialize coupon flags if coupon-ratio is set
793+ if ( isset ( $ args ['coupon-ratio ' ] ) ) {
794+ $ coupon_ratio = floatval ( $ args ['coupon-ratio ' ] );
795+ $ coupon_ratio = max ( 0.0 , min ( 1.0 , $ coupon_ratio ) );
796+
797+ $ num_with_coupons = (int ) round ( $ count * $ coupon_ratio );
798+ $ num_without = $ count - $ num_with_coupons ;
799+
800+ // Create array with exact counts
801+ self ::$ batch_coupon_flags = array_merge (
802+ array_fill ( 0 , $ num_with_coupons , true ),
803+ array_fill ( 0 , $ num_without , false )
804+ );
805+
806+ // Shuffle for randomness
807+ shuffle ( self ::$ batch_coupon_flags );
808+ }
809+
810+ // Initialize refund flags if refund-ratio is set
811+ if ( isset ( $ args ['refund-ratio ' ] ) && isset ( $ args ['status ' ] ) && 'completed ' === $ args ['status ' ] ) {
812+ $ refund_ratio = floatval ( $ args ['refund-ratio ' ] );
813+ $ refund_ratio = max ( 0.0 , min ( 1.0 , $ refund_ratio ) );
814+
815+ $ total_refunds = (int ) round ( $ count * $ refund_ratio );
816+
817+ // Split refunds: 50% full, 25% single partial, 25% multi-partial
818+ $ num_full = (int ) round ( $ total_refunds * 0.5 );
819+ $ num_partial = (int ) round ( $ total_refunds * 0.25 );
820+ $ num_multi = $ total_refunds - $ num_full - $ num_partial ; // Remainder goes to multi
821+ $ num_none = $ count - $ total_refunds ;
822+
823+ // Create array with exact counts
824+ self ::$ batch_refund_flags = array_merge (
825+ array_fill ( 0 , $ num_full , 'full ' ),
826+ array_fill ( 0 , $ num_partial , 'partial ' ),
827+ array_fill ( 0 , $ num_multi , 'multi ' ),
828+ array_fill ( 0 , $ num_none , 'none ' )
829+ );
830+
831+ // Shuffle for randomness
832+ shuffle ( self ::$ batch_refund_flags );
833+ }
834+ }
835+
836+ /**
837+ * Clear ratio flags after batch generation is complete.
838+ *
839+ * @return void
840+ */
841+ protected static function clear_ratio_flags () {
842+ self ::$ batch_coupon_flags = null ;
843+ self ::$ batch_refund_flags = null ;
844+ }
740845}
0 commit comments