This is the 16th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

I. Order details management

1.1 Creating an Order Controller

Run the commandphp artisan make:controller Web/OrderControllerCreate order controller:Write order management-related methods:


      

namespace App\Http\Controllers\Web;

use App\Http\Controllers\BaseController;
use App\Models\Cart;
use App\Models\Order;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class OrderController extends BaseController
{
	/** * Order list */
    public function index(Request $request) {
        // Status query
        $status = $request->query('status');// According to the title
        $title = $request->query('title');

        $order = Order::where('user_id', auth('api')->id())
            ->when($status.function ($query) use ($status) {
                $query->where('status'.$status);
            })
            ->when($status.function ($query) use ($title) {
                $query->whereHas('goods'.function ($query) use($title) {
                    $query->where('title'.'like'."%{$title}%");
                }); // Add a custom constraint to the association
            })
            ->paginate(3);
        return $this->response->paginator($order.new OrderTransformer());
    }
	
    /** * preview the order */
    public function preview()
    {
        // Address data
        // TODO temporarily simulates address data
        $address= [['id'= >1.'name'= >'Joe'.'address'= >'xiamen'.'phone'= >'123123'],
            ['id'= >2.'name'= >'bill'.'address'= >'xiamen'.'phone'= >'123123'],
            ['id'= >3.'name'= >'Wang Wu'.'address'= >'xiamen'.'phone'= >'123123']];// Shopping cart data
        $carts = Cart::where('user_id', auth('api')->id())
            ->where('is_checked'.1)
            ->with('goods:id,cove,title')
            ->get();

        // Return data
        return $this->response->array([
            'address'= >$address.'carts'= >$carts,]); }/** * submit the order */
    public function store(Request $request)
    {
        // Verify the address field
        $request->validate([
            'address_id'= >'required' // TODO addresses exist :addresses,id
        ], [
            'address_id.required'= > ['Harvest address cannot be empty']]);// Process the inserted data
        $user_id = auth('api')->id();
        $order_no = date('YmdHis') . rand(100000.999999);
        $amount = 0;
        $cartsQuery = Cart::where('user_id'.$user_id)
            ->where('is_checked'.1);

        $carts = $cartsQuery->with('goods:id,price')->get();

        // The order details to be inserted
        $insertData = [];


        foreach ($carts as $key= >$cart) {
        	// If an item is out of stock, prompt the user to choose again
            if ($cart->goods->stock < $cart->num) {
                return $this->response->errorBadRequest($cart->goods->title.'Stock is insufficient, please select another item! ');
            }
            $insertData[] = [
                'goods_id'= >$cart->goods_id,
                'price'= >$cart->goods->price,
                'num'= >$cart->num
            ];
            $amount+ =$cart->goods->price * $cart->num;
        }

        try {
            DB::beginTransaction(); // Start the transaction
            // Generate the order
            $order = Order::create([
                'user_id'= >$user_id.'order_no'= >$order_no.'address_id'= >$request->input('address_id'),
                'amount'= >$amount
            ]);

            // Generate details of the order
            $order->orderDetails()->createMany($insertData);

            // Delete the cart data that has already been settled
            $cartsQuery->delete();
            
            // Subtract the inventory of goods
            foreach($carts as $cart) {
                Good::where('id'.$cart->goods_id)->decrement('stock'.$cart->num);
            }
            DB::commit(); // Commit only if there is no exception
            return $this->response->created();
        } catch (\Exception $e) {
            DB::rollBack(); // Abnormal transaction data rollback occurred
            throw $e; // Throw an exception}}// Order details
    public function show(Order $order) {
        return $this->response->item($order.new OrderTransformer());
    }
	/** * confirm receipt of goods */
    public function confirm(Order $order) {
        if ($order->status ! =3) {
            return $this->response->errorBadRequest('Order status abnormal! ');
        }
       try {
            DB::beginTransaction();
            $order->status = 4;
            $order->save();

            $orderDetails = $order->orderDetails;
            
            // Increase the sales of all items under the order
            foreach($orderDetails as $orderDetail) {
                // Update product sales
                Good::where('id'.$orderDetail->goods_id)->increment('sales'.$orderDetail->num);
            }

            DB::commit();
        } catch (\Exception $e) {
            DB::rollBack();
            throw $e;
        }
        return $this->response->noContent(); }}Copy the code

1. We haven’t done address management API yet, so the address is simulated temporarily. 2.

Since we are inserting data into the order table, we also write a batch assignable field to the order table:Since data is also inserted into the order detail table, we also need to insert batchable fields into the order detail table:


We then write remote one-to-many to the order model so that we can skip the intermediate order detail table and get the item data directly from the order model:

    /** ** order remote one-to-many, associated goods */
    public function goods() {
        return $this->hasManyThrough(
            Good::class, // The final associated model
            OrderDetails::class, // The intermediate model
            'order_id'.// The foreign key associated with the intermediate model
            'id'.// The foreign key of the final correlation model
            'id'.// This model is associated with the intermediate model
            'goods_id' // The intermediate table and the final model are associated with a key
        );
    }
Copy the code

Add item data to OrderTransformer. PHP:

    /**
     * 商品数据
     */
    public function includeGoods(Order $order) {
        return $this->collection($order->goods, new GoodTransformer());
    }
Copy the code

1. The task scheduling order is outdated, unpaid and cancelled

Since placing an order will reduce inventory, in order to prevent some people from placing malicious orders and reducing inventory, we can do a task scheduling and make an order how long it will be invalid. Enter the command:crontab -e, enter 1 and press Enter:Enter in the editor* * * * * /home/vagrant/code/shopProjectApi/artisan schedule:run >> /dev/null 2>&1. The absolute path to your project. thencontroller + xThe inputySave and exit. Then, inApp\Console\KernelIn the input:

// Check the order status regularly. If the order is not paid within 10 minutes, it will be invalid
        // Real projects usually use long links, because it can be real-time long links, when the time is up, directly void, and task scheduling is not so accurate before the same time display
        // Help us perform tasks at a certain time like backup data...
        $schedule->call(function () {
            // info('hellp');
            $orders = Order::where('status'.1)
                    ->where('created_at'.'<', date('Y-m-d H:i:s', time()-600))
                    ->with('orderDetails.goods') // Get the associated item
                    ->get();

            // Loop the order, modify the order status, restore the inventory
            try {
                DB::beginTransaction(); // Open the door

                foreach($orders as $order) {
                    $order->status = 5;
                    $order->save();

                    // Restore the inventory
                    foreach($order->orderDetails as $details) {
                        $details->goods->increment('stock'.$details->num);
                    }
                }

                Db::commit();
            } catch (\Exception $e) {
                DB::rollBack();
                Log::error($e);
            }

        })->everyMinute();
Copy the code

When the order expires, the order status will be set to 5 and the item quantity will be rolled back:


1.2 Creating an Order Route

        /** ** order */
        // Order preview page
        $api->get('order/preview', [OrderController::class, 'preview']);

        // Submit the order
        $api->post('order', [OrderController::class, 'store']);

		// Order details
        $api->get('order/{order}', [OrderController::class, 'show']);

		// Order list
        $api->get('orders', [OrderController::class, 'index']);
		
		// Confirm receipt of goods
        $api->patch('orders/{order}/confirm', [OrderController::class, 'confirm']);
Copy the code


1.3 Test Effect

During the test, we found that the error was reported, because the order number was set as the order sheet when we were building itintegerSo let’s change the type of this field. Run the command firstcomposer require doctrine/dbal.Then run:php artisan make:migration modify_order_no_column_in_orders_table --table=ordersCreate a migration file that modifies this field:Then write:$table - > string (" order_no ") - > comment (' order number) - > change (); The good type has been modified.


Next test:

You can see that the order is generated and the data in the shopping cart is gone.

If you find this article helpful on your way to learning PHP, please follow me to like and comment on it. Thank you, your blog is definitely another support for me to write.