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 / database / Seeder / OrderSeeder.php

OrderSeeder.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.6.4, at database/Seeder/OrderSeeder.php

299 lines 12.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\Database\Seeder;
4
5 use FluentCart\Api\StoreSettings;
6 use FluentCart\App\Helpers\CustomerHelper;
7 use FluentCart\App\Models\AppliedCoupon;
8 use FluentCart\App\Models\Coupon;
9 use FluentCart\App\Models\Customer;
10 use FluentCart\App\Models\Order;
11 use FluentCart\App\Models\OrderItem;
12 use FluentCart\App\Models\OrderTransaction;
13 use FluentCart\App\Models\Product;
14 use FluentCart\App\Services\DateTime\DateTime;
15 use FluentCart\Faker\Factory;
16 use FluentCart\Framework\Support\Arr;
17
18 class OrderSeeder
19 {
20 public static function seed($count, $assoc_args = [])
21 {
22 $db = \FluentCart\App\App::getInstance('db');
23
24 $faker = Factory::create();
25 $faker->addProvider(new \FluentCart\Database\Seeder\ProductNameProvide($faker));
26
27 $customerIds = Customer::query()->select('id')->orderBy('id', 'desc')->take($count / 2)->pluck('id');
28 $products = Product::with('variants')->take(100)->get()->toArray();
29 $orderCount = Order::query()->count();
30 $orderItems = [];
31 $orderTransactions = [];
32 // Accumulated across the whole batch, like the two above: the insert
33 // happens once after the loop.
34 $appliedCouponData = [];
35 $coupons = Coupon::query()->where('status', 'active')->get()->toArray();
36
37 if (defined('WP_CLI') && WP_CLI) {
38 $progress = \WP_CLI\Utils\make_progress_bar('%CSeeding Orders', $count);
39 }
40
41 $storeSettings = new StoreSettings();
42
43 // How far back seeded orders may be dated. Reports filter on
44 // o.created_at, so `--days=60` puts the whole batch inside a report's
45 // date window. Defaults to the historic 450-day spread.
46 $maxAgeDays = isset($assoc_args['days']) ? absint($assoc_args['days']) : 0;
47 if (!$maxAgeDays) {
48 $maxAgeDays = 450;
49 }
50
51 for ($i = 0; $i < $count; $i++) {
52 $totalPrice = 0;
53 $discountTotal = 0;
54 $tempOrderItems = [];
55 $createdDate = $faker->dateTimeBetween('-' . $maxAgeDays . ' days', 'now')->format('Y-m-d H:i:s');
56 $createdDateGmt = DateTime::anyTimeToGmt($createdDate);
57
58 $fulfilmentType = $faker->randomElement(['physical', 'digital']);
59
60 // Filter only products that have at least one variant with the selected fulfilment type
61 $filteredProducts = array_filter($products, function ($product) use ($fulfilmentType) {
62 return !empty(array_filter($product['variants'], function ($variant) use ($fulfilmentType) {
63 return isset($variant['fulfillment_type']) && $variant['fulfillment_type'] === $fulfilmentType;
64 }));
65 });
66
67 // If no product matches the type, fallback to all products
68 $availableProducts = count($filteredProducts) > 0 ? array_values($filteredProducts) : $products;
69
70 // Randomize
71 $randomizeProduct = count($availableProducts) < 2
72 ? $availableProducts
73 : array_map(function ($index) use ($availableProducts) {
74 return $availableProducts[$index];
75 }, (array)array_rand($availableProducts, min(wp_rand(2, 5), count($availableProducts))));
76
77 // Randomly pick a coupon
78 $appliedCoupon = $faker->randomElement($coupons ?? []);
79 $couponType = Arr::get($appliedCoupon, 'type');
80 $couponAmount = (int)Arr::get($appliedCoupon, 'amount', 0);
81
82 foreach ($randomizeProduct as $key => $productIndex) {
83 $product = $products[$key];
84 $variant = $faker->randomElement($product['variants']);
85 $quantity = wp_rand(1, 3);
86 $itemPrice = $variant['item_price'] ?? 0;
87 $subTotalPrice = $quantity * $itemPrice;
88
89 $lineDiscount = 0;
90 if ($couponType === 'percentage' && $couponAmount > 0) {
91 $lineDiscount = intval(($couponAmount / 100) * $subTotalPrice);
92 }
93
94 $discountTotal += $lineDiscount;
95 $totalPrice += $subTotalPrice;
96
97 $tempOrderItems[] = [
98 'post_title' => $product['post_title'],
99 'title' => $variant['variation_title'] ?? '',
100 'fulfillment_type' => $variant['fulfillment_type'],
101 'payment_type' => $variant['payment_type'],
102 'quantity' => $quantity,
103 'unit_price' => $itemPrice,
104 'line_total' => $subTotalPrice - $lineDiscount,
105 'subtotal' => $subTotalPrice,
106 'post_id' => $product['ID'],
107 'object_id' => $variant['id'],
108 'discount_total' => $lineDiscount,
109 'cart_index' => $i + 1,
110 'created_at' => $createdDateGmt,
111 ];
112 }
113
114 $paymentMethod = $faker->randomElement(['offline_payment', 'online_payment']);
115 $paymentMethodTitle = $faker->randomElement(['Cash on Delivery', 'PayPal', 'Stripe']);
116 $customerId = $faker->randomElement($customerIds);
117
118 $paymentStatus = $faker->randomElement([
119 'pending',
120 'paid',
121 'partially_paid',
122 'refunded',
123 'partially_refunded',
124 'failed',
125 ]);
126
127 switch ($paymentStatus) {
128 case 'paid':
129 if ($fulfilmentType === 'digital') {
130 $status = 'completed';
131 } else {
132 $status = $faker->randomElement(['completed', 'processing']);
133 }
134 break;
135
136 case 'partially_paid':
137 $status = 'processing';
138 break;
139
140 case 'partially_refunded':
141 $status = 'completed';
142 break;
143
144 case 'refunded':
145 $status = 'canceled';
146 break;
147
148 default:
149 // For pending, failed, authorized
150 $status = $faker->randomElement(['on-hold', 'canceled', 'failed']);
151 break;
152 }
153
154 $refundedAt = $faker->dateTimeBetween($createdDate, 'now')->format('Y-m-d H:i:s');
155 $currency = 'USD';
156 $shippingTax = 0;
157 $taxTotal = 0;
158 $orderDiscountTotal = 0;
159 $totalRefund = 0;
160 $totalPaid = 0;
161 $completedAt = null;
162 $totalAmount = $totalPrice - $shippingTax - $taxTotal - $orderDiscountTotal - $discountTotal;
163 if ($status === 'completed') {
164 $endDate = (new DateTime($createdDateGmt))->modify('+3 days')->format('Y-m-d H:i:s');
165 $completedAt = $faker->dateTimeBetween($createdDateGmt, $endDate)->format('Y-m-d H:i:s');
166 }
167
168 if ($paymentStatus === 'paid') {
169 $totalPaid = $totalAmount;
170 } elseif ($paymentStatus === 'refunded') {
171 $totalPaid = $totalAmount;
172 $totalRefund = $totalPaid;
173 } elseif ($paymentStatus === 'partially_refunded') {
174 $totalPaid = $totalAmount;
175 $refundPercentage = wp_rand(20, 30) / 100;
176 $totalRefund = round($totalPaid * $refundPercentage, 2);
177 } elseif ($paymentStatus === 'partially_paid') {
178 $paymentPercentage = wp_rand(50, 60) / 100;
179 $totalPaid = round($totalAmount * $paymentPercentage, 2);
180 $totalRefund = 0;
181 }
182
183 $orderData = [
184 'parent_id' => 0,
185 'invoice_no' => $storeSettings->getInvoicePrefix() . ($orderCount + $i + 1) . $storeSettings->getInvoiceSuffix(),
186 'receipt_number' => ($orderCount + $i + 1),
187 'customer_id' => $customerId,
188 'payment_method' => $paymentMethod,
189 'payment_method_title' => $paymentMethodTitle,
190 'payment_status' => $paymentStatus,
191 'currency' => $currency,
192 'subtotal' => $totalPrice,
193 'shipping_tax' => $shippingTax,
194 'fulfillment_type' => $fulfilmentType,
195 'tax_total' => $taxTotal,
196 'manual_discount_total' => $orderDiscountTotal,
197 'coupon_discount_total' => $discountTotal,
198 'total_amount' => $totalPrice - $shippingTax - $taxTotal - $orderDiscountTotal - $discountTotal,
199 'total_paid' => $totalPaid,
200 'status' => $status,
201 'created_at' => $createdDateGmt,
202 'total_refund' => $totalRefund,
203 'refunded_at' => $refundedAt,
204 'completed_at' => $completedAt,
205 ];
206
207 $order = Order::query()->create($orderData);
208
209 switch ($paymentStatus) {
210 case 'paid':
211 case 'partially_paid':
212 case 'authorized':
213 $transactionStatus = 'completed';
214 break;
215
216 case 'refunded':
217 $transactionStatus = 'refunded';
218 break;
219
220 case 'partially_refunded':
221 $transactionStatus = 'partially_refunded';
222 break;
223
224 case 'pending':
225 $transactionStatus = 'pending';
226 break;
227
228 case 'failed':
229 $transactionStatus = 'failed';
230 break;
231
232 default:
233 $transactionStatus = 'pending';
234 break;
235 }
236
237
238 $orderTransactions[] = [
239 'order_id' => $order->id,
240 'order_type' => $order->type,
241 'payment_method' => $paymentMethod,
242 'payment_mode' => 'test',
243 'status' => $transactionStatus,
244 'currency' => $currency,
245 'total' => $totalPrice,
246 'created_at' => $createdDateGmt,
247 ];
248
249 if (!empty($appliedCoupon) && $discountTotal > 0) {
250 // fct_applied_coupons has no customer_id column (see
251 // AppliedCouponsMigrator) — including it made every seed run die
252 // with "Unknown column 'customer_id' in 'field list'" before the
253 // order operations were ever seeded.
254 $appliedCouponData[] = [
255 'order_id' => $order->id,
256 'coupon_id' => $appliedCoupon['id'],
257 'code' => $appliedCoupon['code'],
258 'amount' => $discountTotal,
259 'created_at' => $createdDateGmt,
260 ];
261 }
262
263
264 foreach ($tempOrderItems as $index => $item) {
265 $tempOrderItems[$index]['order_id'] = $order->id;
266 }
267
268 $orderItems = array_merge($orderItems, $tempOrderItems);
269
270 if (defined('WP_CLI') && WP_CLI) {
271 if ($i !== $count - 1) {
272 $progress->tick();
273 }
274 } else {
275 echo wp_kses_post( sprintf(
276 /* translators: %d: order ID */
277 __('Inserting Order %1$s<br>', 'fluent-cart'),
278 esc_html($i + 1)
279 ) );
280 }
281 }
282
283 OrderTransaction::query()->insert($orderTransactions);
284 OrderItem::query()->insert($orderItems);
285 if ($appliedCouponData) {
286 AppliedCoupon::query()->insert($appliedCouponData);
287 }
288
289 (new CustomerHelper)->calculateCustomerStats();
290
291 if (defined('WP_CLI') && WP_CLI) {
292 $progress->tick();
293 $progress->finish();
294 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
295 echo \WP_CLI::colorize('%n');
296 }
297 }
298 }
299