Project is currenly live on mhmt.gq/manage
This is an order management application. Used MySQL as a database but it doesnt matter as long as you use laravel. You can migrate to any database using migration files.
All the datas are being sent to client side and VueJS is parsing data and passing them to html. So structure is like Laravel->Vue App-> HTML view
And for CRUD functions to Server-Side I used Ajax requests with Axios. Also I included relevant unit tests.
Users | Products | Orders |
---|---|---|
id | id | id |
name | name | product |
password | price | user |
created_at | price | |
Address | updated_at | total_price |
created_at | created_at | |
updated_at | updated_at |
- in orders table, product and user field are storing IDs' of them. Not the names
- price is constant that stores the price of product when the order created and total_price field are being calculated in controller. It could be also auto calculated in Model or in Database.
- app\Http\Controllers\orderController.php
- Add Request to Database
- Return price total price and id values to client
public function store(orderRequest $request) {
$request->calc(); // calculations done on request class
$new = Order::create($request->all()); // creating our order into database
return $new['price'] . "," . $new['total_price'] . "," . $new['user'] . "," . $new['product'] . "," . $new['id']; // values to client
}
- resources\views\manage.blade.php
- orders array is being showed to user in HTML simulatenously.
- After Sending post request, add new element to orders array.
createorder: function() {
var order = this.order;
axios.post('/addorder', { // sending post request to addorder route so it will add data to database using controller
user: order.user,
product: order.product,
quantity: order.quantity
})
.then(function (response) { // we make sure that our data received by server
// used unshift instead of push because I want to add it to top of the list
orders.unshift({ // adding new order to order array so it will be shown on the screen
id: response.data.split(",")[4],
user: users[findKey(order.user,users)].name,
product: products[findKey(order.product,products)].name,
quantity: order.quantity,
price: response.data.split(",")[0], // also here price and total price calculated in server and returned to us as a response
total: response.data.split(",")[1],
userId: response.data.split(",")[2],
productId: response.data.split(",")[3],
date: new Date()
});
})
- app\Http\Controllers\orderController.php
- Return new price and total price values
public function update(orderRequest $request, Order $order) {
$new = $request->calc()->all(); // Price Calculations
$order = Order::findorFail($new['id']);
$order['product'] = $new['product'];
$order['user'] = $new['user'];
$order['quantity'] = $new['quantity'];
$order['price'] = $new['price'];
$order['total_price'] = $new['total_price'];
$order->save();
return $order['price'] . "," . $order['total_price']; // Return of new price and Recalculated Total price
}
- resources\views\manage.blade.php
- Removing the element from orders array
- when we got the success response, adding it to its place(same order) with response values
updateorder: function () { // function that will run when the save button clicked after edits done
var order = this.order; // order object is sent by vue routes as params
var key = findorderKey(order.id); // we find order and delete
orders.splice(key, 1);
axios.post('/edit', { // here we are sending a post request to edit route which will run edit function in controller
id:order.id,
user: order.userId,
product: order.productId,
quantity: order.quantity
})
.then(function (response) {
console.log(response);
orders.splice(key, 0, { // after we make sure that we have the data edited in DB we are creating new object in orders with
id: order.id, // values we sent to database
user: users[findKey(order.userId,users)].name,
userId:order.userId, //splice will create data on screen on where it used to be so sort wont change
product: products[findKey(order.productId,products)].name,
productId: order.productId,
quantity: order.quantity,
price: response.data.split(",")[0], // here price and total price sent us from server
total: response.data.split(",")[1], // calculations on client-side are risky and manipulative
date: order.date
});
- app\Http\Controllers\orderController.php
- Delete order record which has the requests' id value
public function destroy(Order $order, Request $request) {
return Order::destroy(Request::all()); // Destroy the data with requested id
}
- resources\views\manage.blade.php
- Remove it from Orders Array
- Post request to /destroy route using id as data
remove (index) { //this is the function that work when we click on delete button
orders.splice(findKey(index,orders), 1); // deletes order from orders array, so it will be deleted from the screen
axios.post('/destroy', { //sending id of order that we want to delete, to destroy route and it will be deleted from DB too
id:index
})
- Filtering is running only in client-side, because we don't have pagination and we have all the data with us so we can just filter it on client side
- resources\views\manage.blade.php
- HTML gets data from orders but it get it's from filteredorders not just orders
<tr v-for="order in filteredorders">
- So it will return us a filtered data and as you can see there is a && sign so we can filter by date and the keyword at the same time
computed: { // this is filtering system
filteredorders: function () { // view gets orders from filteredorders, not orders.
return this.orders.filter(function (order) { //so this anoymous function will return data by date and(&&) keywords
return ( new Date(order.date).getDate() >= this.rDate ) && ( this.searchKey=='' ||order.user.toLowerCase().indexOf(this.searchKey.toLowerCase()) !== -1 || order.product.toLowerCase().indexOf(this.searchKey.toLowerCase()) !== -1);
},this);
}
}
- Let's take a look in our keywords filtering first I use toLowerCase function because I want my search to be case insensitive then I use indexOf function to see if any order's user has any part of my searchkey if there's no match it will return -1 so, if not equals to -1 it matches
- Done same thing for the product
- That's Right, so to prevent that data mismatch, In orders array we have both IDs and names as data unlike its database field
@foreach($orders as $order)
{id: {{$order->id}}, user: users[finduserKey({{$order->user}})].name, product: products[findproductKey({{$order->product}})].name, price: {{$order->price}}, quantity: {{$order->quantity}}, total:{{$order->total_price}}, userId:{{$order->user}}, productId:{{$order->product}} , date:"{{$order->created_at}}"},
@endforeach
- As you can see in the codeblock above, finduserKey function help us gets user's key by using user's id so we can get user's name from its object
-
We will get day numbers from date functions and we will filter dates by comparing them(>=)
-
rDate is reference day if we want to see todays order we will set reference day to -today and make its value equals to today's day number value
if we want to see last seven days' orders we can set our reference Date's day number value to today - 7
and if we want to see all orders we can just set rDate to 0 so all day numbers will be greater than its value
change(){ // this method is being used for filtering orders by date
this.date = this.options[this.i++%3]; // change text to next element e.g. last 7 days -> today
if (this.i%3==1) {
this.rDate = new Date().getDate() - 7 ; // we will see the orders that have greater date value than the reference Date
} // so in this if state reference date is today - 7 days, because we want last 7 days
if (this.i%3==2) {
this.rDate = new Date().getDate(); // reference date is today which will show us orders only added today
}
if (this.i%3==0) {
this.rDate = 0 ; // reference date is 0 which is lower than all date value so it will show us all orders
}
}
- tests\Unit\MainTest.php
- I usually code tests with Python but laravel has this lib in it, so I wanted to try
- I was going to include front-end browser tests but I remembered that they are not called Unit tests
- In this test file in general we check
- Add Requests
- Update Requests
- Delete Requests
- Price Calculations
- Seeing the right View
- Request Validations
/** @test */
public function total_price_calculation() // This test checks if the price calculation is correct
{ // Also checks the creation data on the database
$response = $this->post('addorder', [
'user' => '1',
'product' => '1',
'quantity' => '8'
]);
$this->assertDatabaseHas('orders', [
'user' => '1',
'product' => '1',
'quantity' => '8',
'total_price' => '14.4'
]);
}
/** @test */
public function exceptional_stuation_test() // Test checks if the exceptional stuation are being done correctly
{
$response = $this->post('addorder', [
'user' => '1',
'product' => '2',
'quantity' => '30'
]);
$this->assertDatabaseHas('orders', [
'user' => '1',
'product' => '2',
'quantity' => '30',
'total_price' => '38.4'
]);
}
/** @test */
public function edit_orders_test() // Checking if the edits and calculations are correct in database
{
$id = \App\Order::all()->last()->id -1;
$response = $this->post('edit', [
'id' => $id,
'user' => '1',
'product' => '2',
'quantity' => '300'
]);
$this->assertDatabaseHas('orders', [
'id' => $id,
'user' => '1',
'product' => '2',
'quantity' => '300',
'total_price' => '384'
]);
}
/** @test */
public function deletion_test() // Asserting that deletions works
{
$id = \App\Order::all()->last()->id; // delete the last added item(s) so Test Database will stay clear and ok
$response = $this->post('destroy', [ //first deletion
'id' => $id
]);
$response = $this->post('destroy', [ //reason of this deletion is, deleting the two records we added above to keep test DB clear
'id' => $id - 1
]); // I usually run my all tests at once so I didnt hesitate using this
$this->assertDatabaseMissing('orders', [
'id' => $id
]);
}
/** @test */
public function non_existing_user_post() // we want to see if an user with unknown id can add records with fake product to database
{
$user = \App\User::all()->last()->id + 10; // I got 10 more of latest users' id because I want to generate an non existing id
$product = \App\Product::all()->last()->id + 10 ; // and we know that IDs are incremental not generated randomly.
$response = $this->post('addorder', [
'user' => $user,
'product' => $product,
'quantity' => '1'
]);
$this->assertDatabaseMissing('orders', [
'user' => $user,
'product' => $product
]);
}
/** @test */
public function non_existing_product_edit() // Check if is it possible to edit an order with non existing product id
{
$response = $this->post('addorder', [
'id' => '43',
'user' => '2',
'product' => '3',
'quantity' => '0'
]);
$this->assertDatabaseMissing('orders', [
'user' => '2',
'product' => '3',
'quantity' => '0'
]);
}
/** @test */
public function display_manage_orders_page() // chech if the manage view returns to user proceeds to /manage
{
$response = $this->get('/manage');
$response->assertStatus(200);
$response->assertViewIs('manage');
}
- Date's day number gets value only in between 0-31 so start of every month Date filtering won't work correctly.
- When a new order added, its time value format is different than others make them all in same format.
- Users only needs to be sended to client when add or edit function called by client it may reduce the load on server side <- listing orders will be problem in this case server-side reorganizations will be needed
- Pagination and Filtering on server-side might be needed in case of large scale data recorded
"Thanks for your precious time" -Mehmet Can