PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.4
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.4
1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 trunk 1.2.0 All 47 releases
fluent-cart / app / Hooks / CLI / OrderCloneCommand.php

OrderCloneCommand.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.6.4, at app/Hooks/CLI/OrderCloneCommand.php

328 lines 11.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\App\Hooks\CLI;
4
5 use FluentCart\App\Models\Order;
6 use FluentCart\App\Models\OrderAddress;
7 use FluentCart\App\Models\OrderItem;
8 use FluentCart\App\Models\AppliedCoupon;
9 use FluentCart\App\Models\OrderMeta;
10 use FluentCart\App\Services\DateTime\DateTime;
11 use FluentCartPro\App\Modules\Licensing\Models\License;
12 use FluentCartPro\App\Modules\Subscriptions\Models\Subscription;
13
14 class OrderCloneCommand
15 {
16 /**
17 * Clone orders with specified date range
18 *
19 * ## OPTIONS
20 *
21 * [--count=<number>]
22 * : Number of orders to clone
23 * ---
24 * default: 10
25 * ---
26 *
27 * [--start-date=<date>]
28 * : Start date for cloned orders (YYYY-MM-DD)
29 * ---
30 * default: 30 days ago
31 * ---
32 *
33 * [--end-date=<date>]
34 * : End date for cloned orders (YYYY-MM-DD)
35 * ---
36 * default: today
37 * ---
38 *
39 * [--source-order-id=<id>]
40 * : Specific order ID to clone (if not provided, random orders will be selected)
41 *
42 * ## EXAMPLES
43 *
44 * wp fluent_cart clone_orders --count=50
45 * wp fluent_cart clone_orders --count=25 --start-date=2024-01-01 --end-date=2024-12-31
46 * wp fluent_cart clone_orders --source-order-id=123 --count=5
47 */
48 public function clone_orders($args, $assoc_args)
49 {
50 $count = isset($assoc_args['count']) ? (int)$assoc_args['count'] : 10;
51 $startDate = isset($assoc_args['start-date']) ? $assoc_args['start-date'] : gmdate('Y-m-d', strtotime('-30 days'));
52 $endDate = isset($assoc_args['end-date']) ? $assoc_args['end-date'] : gmdate('Y-m-d');
53 $sourceOrderId = isset($assoc_args['source-order-id']) ? (int)$assoc_args['source-order-id'] : null;
54
55 \WP_CLI::line("Starting order cloning process...");
56 \WP_CLI::line("Count: {$count}");
57 \WP_CLI::line("Date range: {$startDate} to {$endDate}");
58
59 try {
60 $results = $this->cloneOrders($count, $startDate, $endDate, $sourceOrderId);
61
62 \WP_CLI::success("Successfully cloned {$results['success']} orders");
63 if ($results['failed'] > 0) {
64 \WP_CLI::warning("Failed to clone {$results['failed']} orders");
65 }
66
67 } catch (\Exception $e) {
68 \WP_CLI::error("Order cloning failed: " . $e->getMessage());
69 }
70 }
71
72 private function cloneOrders($count, $startDate, $endDate, $sourceOrderId = null)
73 {
74 $results = ['success' => 0, 'failed' => 0];
75
76 // Get source orders
77 $sourceOrders = $this->getSourceOrders($sourceOrderId, $count);
78
79 if (empty($sourceOrders)) {
80 throw new \Exception('No source orders found to clone');
81 }
82
83 $progress = \WP_CLI\Utils\make_progress_bar('Cloning Orders', $count);
84
85 for ($i = 0; $i < $count; $i++) {
86 $sourceOrder = $sourceOrders[$i % count($sourceOrders)];
87 $randomDate = $this->getRandomDateInRange($startDate, $endDate);
88
89 if ($sourceOrder instanceof Order) {
90 $sourceOrder = $sourceOrder->toArray();
91 }
92 try {
93 $this->cloneSingleOrder($sourceOrder, $randomDate);
94 $results['success']++;
95 } catch (\Exception $e) {
96 $results['failed']++;
97 \WP_CLI::debug("Failed to clone order {$sourceOrder->id}: " . $e->getMessage());
98 }
99
100 $progress->tick();
101 }
102
103 $progress->finish();
104 return $results;
105 }
106
107 private function getSourceOrders($sourceOrderId, $count)
108 {
109 $query = Order::with([
110 'billing_address',
111 'shipping_address',
112 'order_items',
113 'appliedCoupons',
114 'customer'
115 ]);
116
117 if ($sourceOrderId) {
118 return [$query->findOrFail($sourceOrderId)];
119 }
120
121 return $query->inRandomOrder()->limit(min($count, 50))->get();
122 }
123
124 private function cloneSingleOrder($sourceOrder, $targetDate)
125 {
126 global $wpdb;
127 //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
128 $wpdb->query('START TRANSACTION');
129
130 try {
131 // Clone main order
132 $newOrder = $this->cloneOrderRecord($sourceOrder, $targetDate);
133
134 // Clone order items
135 $this->cloneOrderItems($sourceOrder, $newOrder);
136
137 // Clone addresses
138 $this->cloneOrderAddresses($sourceOrder, $newOrder);
139
140 // Clone applied coupons
141 $this->cloneAppliedCoupons($sourceOrder, $newOrder);
142
143 // Clone order meta/actions
144 $this->cloneOrderMeta($sourceOrder, $newOrder);
145
146 // Clone licenses if exists
147 $this->cloneLicenses($sourceOrder, $newOrder);
148
149 // Clone subscriptions if exists
150 $this->cloneSubscriptions($sourceOrder, $newOrder);
151 //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
152 $wpdb->query('COMMIT');
153
154 } catch (\Exception $e) {
155 //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
156 $wpdb->query('ROLLBACK');
157 throw $e;
158 }
159 }
160
161 private function cloneOrderRecord($sourceOrder, $targetDate)
162 {
163
164 $orderData = $sourceOrder;
165 unset($orderData['id']);
166
167 // Generate new identifiers
168 $orderData['uuid'] = md5(time() . wp_generate_uuid4());
169 $orderData['receipt_number'] = $this->getNextReceiptNumber();
170 $orderData['invoice_no'] = 'FC-' . $orderData['receipt_number'];
171
172 // Set target date
173 $orderData['created_at'] = DateTime::anyTimeToGmt($targetDate)->format('Y-m-d H:i:s');
174 $orderData['updated_at'] = $orderData['created_at'];
175
176 $order = Order::create($orderData);
177
178 $order->load('customer');
179
180 if ($order->customer) {
181 $order->customer->recountStat();
182 }
183
184 return $order;
185 }
186
187 private function cloneOrderAddresses($sourceOrder, $newOrder)
188 {
189 foreach (['billing_address', 'shipping_address'] as $addressType) {
190 if (!empty($sourceOrder[$addressType])) {
191 $addressData = $sourceOrder[$addressType];
192 unset($addressData['id']);
193 $addressData['order_id'] = $newOrder->id;
194 $addressData['created_at'] = $newOrder->created_at;
195 $addressData['updated_at'] = $newOrder->created_at;
196
197 OrderAddress::create($addressData);
198 }
199 }
200 }
201
202 private function cloneOrderItems($sourceOrder, $newOrder)
203 {
204 if (!empty($sourceOrder['order_items'])) {
205 foreach ($sourceOrder['order_items'] as $item) {
206 $itemData = $item;
207 unset($itemData['id']);
208 $itemData['order_id'] = $newOrder->id;
209 $itemData['created_at'] = $newOrder->created_at;
210 $itemData['updated_at'] = $newOrder->created_at;
211
212 OrderItem::create($itemData);
213 }
214 }
215 }
216
217 private function cloneAppliedCoupons($sourceOrder, $newOrder)
218 {
219 if (!empty($sourceOrder['appliedCoupons'])) {
220 $couponIds = [];
221
222 foreach ($sourceOrder['appliedCoupons'] as $coupon) {
223 $couponData = is_array($coupon) ? $coupon : $coupon->toArray();
224 unset($couponData['id']);
225 $couponData['order_id'] = $newOrder->id;
226 $couponData['created_at'] = $newOrder->created_at;
227 $couponData['updated_at'] = $newOrder->created_at;
228
229 AppliedCoupon::create($couponData);
230
231 // Collect coupon IDs for incrementing use_count
232 if (!empty($couponData['coupon_id'])) {
233 $couponIds[] = $couponData['coupon_id'];
234 }
235 }
236
237 // Increment use_count for all used coupons
238 if (!empty($couponIds)) {
239 \FluentCart\App\Models\Coupon::whereIn('id', array_unique($couponIds))
240 ->increment('use_count', 1);
241 }
242 }
243 }
244
245 private function cloneOrderMeta($sourceOrder, $newOrder)
246 {
247 $metas = OrderMeta::where('order_id', $sourceOrder['id'])->get();
248
249 foreach ($metas as $meta) {
250 OrderMeta::create([
251 'order_id' => $newOrder->id,
252 //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
253 'meta_key' => $meta->meta_key,
254 //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
255 'meta_value' => $meta->meta_value,
256 'created_at' => $newOrder->created_at,
257 'updated_at' => $newOrder->created_at
258 ]);
259 }
260 }
261
262 private function cloneLicenses($sourceOrder, $newOrder)
263 {
264 if (!class_exists(License::class)) {
265 return;
266 }
267
268 $licenses = License::where('order_id', $sourceOrder['id'])->get();
269
270 foreach ($licenses as $license) {
271 $licenseData = $license->toArray();
272 unset($licenseData['id']);
273 $licenseData['order_id'] = $newOrder->id;
274 $licenseData['license_key'] = wp_generate_uuid4();
275 $licenseData['created_at'] = $newOrder->created_at;
276 $licenseData['updated_at'] = $newOrder->created_at;
277
278 License::create($licenseData);
279 }
280 }
281
282 private function cloneSubscriptions($sourceOrder, $newOrder)
283 {
284 if (!class_exists(Subscription::class)) {
285 return;
286 }
287
288 $subscriptions = Subscription::where('parent_order_id', $sourceOrder['id'])->get();
289
290 foreach ($subscriptions as $subscription) {
291 $subData = $subscription->toArray();
292 unset($subData['id']);
293 $subData['parent_order_id'] = $newOrder->id;
294 $subData['created_at'] = $newOrder->created_at;
295 $subData['updated_at'] = $newOrder->created_at;
296
297 // Adjust billing dates relative to new order date
298 if ($subData['next_billing_date']) {
299 $interval = $subData['billing_interval'] ?? 'month';
300 $subData['next_billing_date'] = gmdate('Y-m-d H:i:s',
301 strtotime($newOrder->created_at . " +1 {$interval}"));
302 }
303
304 Subscription::create($subData);
305 }
306 }
307
308 private function getRandomDateInRange($startDate, $endDate)
309 {
310 $startTimestamp = strtotime($startDate);
311 $endTimestamp = strtotime($endDate);
312 $randomTimestamp = wp_rand($startTimestamp, $endTimestamp);
313
314 // Add random time within the day
315 $randomTime = wp_rand(0, 86399); // 0 to 23:59:59
316
317 return gmdate('Y-m-d H:i:s', $randomTimestamp + $randomTime);
318 }
319
320 private function getNextReceiptNumber()
321 {
322 global $wpdb;
323 //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
324 $lastNumber = $wpdb->get_var("SELECT MAX(receipt_number) FROM {$wpdb->prefix}fct_orders");
325 return ($lastNumber ?? 1000) + 1;
326 }
327 }
328