How to prevent overlapping time periods during validation? - php

I have a membership that for any given day, can only have 3 users assigned to it.
user
start_date
end_date
User1
2023-01-01
2023-01-30
User2
2023-01-01
2023-01-25
User3
2023-01-26
2023-01-30
This set of records would be fine because on any given day there is not more than 3 users. My issue arises when I try to add:
user
start_date
end_date
User4
2023-01-01
2023-01-30
Using SELECT * FROM members WHERE start_date >= '2023-01-01' AND end_date <= '2023-01-30' returns 3 records like it should. I'm having a hard time trying to figure out how to make sure that none of these overlap to exceed 3.
I'm after advice on how I can achieve this using php, but pseudocode would work too

Loop over the date range in each result record, and count how many entries you got for any given date in an array. If you only need the maximum number of overlapping days, simply get the maximum of the array values. (If you need to know the specific dates where it would go over the limit - then you can loop the array.)
$rows = [
['2023-01-01', '2023-01-30'],
['2023-01-01', '2023-01-25'],
['2023-01-26', '2023-01-30'],
];
$countByDates = [];
foreach($rows as $row) { // this is going to be a classic while loop
// over your database result in your actual use case
$start = strtotime($row[0]); // access rather by column name than array index
$end = strtotime($row[1]);
do {
$date = date('Y-m-d', $start);
$countByDates[$date] = ($countByDates[$date] ?? 0) + 1;
$start = strtotime('+1 day', $start);
} while($start <= $end);
}
print_r($countByDates);
echo "maximum number of daily bookings: ", max($countByDates);
Result:
Array
(
[2023-01-01] => 2
[2023-01-02] => 2
[2023-01-03] => 2
[2023-01-04] => 2
[2023-01-05] => 2
[2023-01-06] => 2
[2023-01-07] => 2
[2023-01-08] => 2
[2023-01-09] => 2
[2023-01-10] => 2
[2023-01-11] => 2
[2023-01-12] => 2
[2023-01-13] => 2
[2023-01-14] => 2
[2023-01-15] => 2
[2023-01-16] => 2
[2023-01-17] => 2
[2023-01-18] => 2
[2023-01-19] => 2
[2023-01-20] => 2
[2023-01-21] => 2
[2023-01-22] => 2
[2023-01-23] => 2
[2023-01-24] => 2
[2023-01-25] => 1
[2023-01-26] => 2
[2023-01-27] => 2
[2023-01-28] => 2
[2023-01-29] => 2
[2023-01-30] => 2
)
maximum number of daily bookings: 2
https://3v4l.org/1W3WD

Related

Points Ranking in Laravel where points are equal

I am trying to work out how to correct display the rank of points to show a leaderboard.
I currently have the following in my Modal
public function getPointrankAttribute(){
$year = Carbon::parse(Carbon::now())->year;
$pointrank = Points::query()
->select('member_id')->selectRaw('SUM(value) as TotalPoints')
->where('Year','=', $year)
->groupBy('member_id')
->orderByDesc('Totalpoints')
->get();
return $pointrank->search(function($points){
return $points->member_id == $this->id;
}) + 1;
}
This works well, however it is using the row position in the table to show the rank, so the first record is 1st and the 2nd record shows as 2nd.
However the issue arises when members have the same total points, because under this the rank is based on position on in the table
See the following example
|member_id|totalpoints|
| 12 | 20 |
| 3 | 10 |
| 10 | 10 |
| 5 | 5 |
In this example based on my current solution
Member 12 = 1st, Member 3 = 2nd, Member 10 = 3rd, Member 5 = 4th
What I want is the following
Member 12 = 1st, Member 3 = 2nd, Member 10 = 2nd, Member 5 = 4th
It would be awesome if I could also add "=" as well when we have more than 1 member sharing that position (as in Member 3 and 10 in this case)
Thanks in advance
To get rank for your users you will need to assign rank to distinct total points first, using laravel collection helper you can create separate ranks collection irrespective of member ids like
$collection =collect(
[
[
"member_id" => 12,
"totalpoints" => 20
],
[
"member_id" => 3,
"totalpoints" => 10
],
[
"member_id" => 10,
"totalpoints" => 10
],
[
"member_id" => 5,
"totalpoints" => 5
],
]
);
$ranks = $collection->unique('totalpoints')
->values()
->mapWithKeys(function ($item, $index) {
return [$item['totalpoints'] => $index + 1];
});
Make sure the original collection should be ordered based on total points.
The above will create an array of distinct points as keys and values as ranks based on total points like
Array
(
[20] => 1
[10] => 2
[5] => 3
)
Now you can search your members based on id from original collection and get their respective rank from ranks array like
$id = 10;
$ranks[$collection->firstWhere("member_id", $id)["totalpoints"]];
There is another way to get ranks for your records directly from database if the vendor database supports window functions like RANK()
What I want is the following :Member 12 = 1st, Member 3 = 2nd, Member 10 = 2nd, Member 5 = 4th
To achieve your desired results you just need to remove ->values() from above code so that your results are not re indexed
$ranks = $collection->unique('totalpoints')
->mapWithKeys(function ($item, $index) {
return [$item['totalpoints'] => $index + 1];
});
Array
(
[20] => 1
[10] => 2
[5] => 4
)
DEMO

How do we calculate monthly sale in codeigniter?

I have trouble with some calculation in Codeigniter. Please check attached image below
https://imgur.com/HuSBkXJ
That data fetching from the database , the sale column is fine but I having trouble with monthly sale
1st day of every month sale (10) and monthly sale (10) are same, but from second day sale is correct(8), monthly sale has to be 1st day sale + second day sale(10+8 = 18). for third day sale is fine, monthly sale should be (18+20=38). the calculation going like this up to end of the month, Same process again stating from next month
How do we calculate in codeigniter, The above image i have created in excel sheet for demonstration purpose.
Model view
public function fetchSaleData()
{
$this->db->join('stock_shop','stock_shop.shop_id = stock_sale.shopId');
$this->db->join('stock_products','stock_products.product_id = stock_sale.productID');
$thisMonth=date('m');
$this->db->where('MONTH(DateOfSale)',$thisMonth);
$query= $this->db->get("stock_sale");
return $query->result_array();
}
controller view
public function salesView()
{
$data['sales']=$this->Query_sale->fetchSaleData();
$data['shops']=$this->Query_sale->fetchShop();
$this->load->view('stock/shop_sales_view',$data);
}
since you're not showing table structures or any other insight, I'll only suggest an approach based on my own code which you may feel free to adapt to your specific needs, along with the assumptions I make along the way
Assumption #1: the model returns an array in which each element contains the date and the sold units on such date
This would look like this
+-----------------+
| array data |
+------------+----+
| 2020-01-01 | 10 |
+------------+----+
| 2020-01-02 | 20 |
+------------+----+
| 2020-01-03 | 5 |
+------------+----+
| 2020-01-04 | 8 |
+------------+----+
On array form, this may look like this:
$ar = array(
0 => array(
'date' => '2020-01-01',
'units_sold' => 10,
),
1 => array(
'date' => '2020-01-02',
'units_sold' => 20,
),
2 => array(
'date' => '2020-01-03',
'units_sold' => 5,
),
3 => array(
'date' => '2020-01-04',
'units_sold' => 8,
),
);
and so on until the end of the month. Lets call the returned fields date and units_sold as in the above example
now that you have the data loaded in a variable ($ar) you can loop through it and keep tabs on the accumulated total on each loop.
$accum = 0; // keep track of accumulated total
$i = 0; // keep track of array index
foreach ($ar as $a)
{
$accum += $a['sales'];
$ar[$i]['accum'] = $accum;
$i++;
}
Upon running, this code will:
Iteration 0: For 2020-01-01, sales=10, accumulated=10
Iteration 1: For 2020-01-02, sales 20, accumulated=30
Iteration 2: For 2020-01-03, sales 5, accumulated=35
...and so on until the month is over
The above can be verified with a print_r or var_dump (I use print_r... like this: print print_r($ar,true);):
print_r output
Array
(
[0] => Array
(
[date] => 2020-01-01
[sales] => 10
[accum] => 10
)
[1] => Array
(
[date] => 2020-01-02
[sales] => 20
[accum] => 30
)
[2] => Array
(
[date] => 2020-01-03
[sales] => 5
[accum] => 35
)
[3] => Array
(
[date] => 2020-01-04
[sales] => 8
[accum] => 43
)
)
So, now that you've succesfully pushed a whole new element in your result array, you can send it over to the view and display it in tabular form as you need

Creating booking overview (table) from MySQL Database - Combining arrays

I am desperate because of my lack of programming experience.
I am working on a small WordPress plugin, a room booking system.
Now my problem is to create the daily overview.
In the database, there is a table with all bookings looking like this:
--------------------------------------------------------------
|id|user_id| starttime | endtime |room|type|
---+-------+-------------------+-------------------+----+-----
|1 | 101|2017-08-24 11:00:00|2017-08-24 12:30:00|12 | 4 |
|2 | ...
The overview / calendar / schedule for every day should be made as an HTML-Table using Bootstrap and look like the following:
Time Conference Room Meeting Room Lobby
07:00 Mr. T
08:00 Mr. T
09:00 C.J.P. Mrs. L.
10:00
11:00
12:00 Mrs. M
So the free timeslots should be empty columns.
To achieve this, I started using a function to create all starttimes:
$times_array = array();
$timing_interval = get_option('timing');
for ($hours = get_option('starttime'); $hours < get_option('endtime'); $hours++) {
for ($m = 0; $m < 60; $m += $timing_interval) {
$starttime_html = sprintf('%02u:%02u', $hours, $m);
$times_array[$starttime_html]='';
}
}
Afterwards I created an array containing the already saved bookings:
$booking_array = array();
$statement = $wpdb->get_results( $wpdb->prepare("SELECT {$mpbs_prefix}_bookings.id, user_id, wp_users.user_nicename AS username_nice, starttime, facility_id, roomname, equipment_1, {$mpbs_prefix}_equipment.name AS eq_1_name, equipment_2 FROM {$mpbs_prefix}_bookings, {$mpbs_prefix}_facilities, {$mpbs_prefix}_equipment, wp_users WHERE user_id = wp_users.ID AND equipment_1 = {$mpbs_prefix}_equipment.id AND {$mpbs_prefix}_facilities.id = {$mpbs_prefix}_bookings.facility_id AND starttime >= '2017-08-22 00:00:00'"),ARRAY_A);
$bookingrow = 0;
foreach($statement as $actualbookings) {
$starttime_currentday = explode(" ",$statement[$bookingrow]['starttime']);
$starttime_curr_hm = substr($starttime_currentday[1],0,5);
$booking_array[$bookingrow] =
array('starttime_of_booking'=>$starttime_curr_hm, 'facliId'=>$statement[$bookingrow]['facility_id'],'userid'=> $statement[$bookingrow]['username_nice'], 'horse_1_name' => $statement[$bookingrow]['horse_1_name']);
$bookingrow++;
}
As there are a lot of rooms (facilities) in different categories, I chose a tabbed navigation from Bootstrap.
Therefore I created a function to "draw" the table using information from three directions:
The column heads directly from the database
the starting times created in PHP (see above)
the already booked times and facilities saved in my $booking_array
As the HTML-table is created row wise, I thought it would be a good approach to check if the starttime is also available in the $booking_array and if yes to add a cell containing the username.
So far that worked, but now I wanted to have all free slots as empty cells. So I tried a lot but always end up in a table with hundreds of empty rows.
In my last approach I used a function I found here, to check if the starttime is not in my multidimensional associative array ($booking_array) and to add the empty cell:
$drawTable = function($funcFormCat, $funcFacId) use ($mpbs_overview_category_query, $times_array, $booking_array) {
echo "<table class='table table-hover'>";
echo "<thead>";
//echo "<th class='text-center'></th>";
foreach ($mpbs_overview_category_query as $overview_formcategories) {
if ($overview_formcategories->formcategory == $funcFormCat && $overview_formcategories->facility_id == $funcFacId) {
echo "<th class='text-center' id='$overview_formcategories->facility_id'>" . $overview_formcategories->facility ."</th>";
}
}
echo "</thead>";
foreach ($times_array as $timeskey => $timesvalue) {
echo "<tr>";
// echo "<td class='text-center'>.</td>";
for ($booking_array_id = 0; $booking_array_id < count($booking_array); $booking_array_id++ ) {
if ($booking_array[$booking_array_id]['starttime_of_booking'] == $timeskey && $booking_array[$booking_array_id]['facliId'] == $funcFacId ) {
echo "<td class='text-center'>" . $booking_array[$booking_array_id]['username'] . "</td>";
echo "</tr>";
}
elseif (in_array_r($timeskey, $booking_array)==false) {
echo "<td class='text-center'>**</td>";
echo "</tr>";
}
}
}
My arrays do contain the following format:
$times_array
Array ( [07:00] => [07:30] => [08:00] => [08:30] => [09:00] => [09:30] => [10:00] => [10:30] => [11:00] => [11:30] => [12:00] => [12:30] => [13:00] => [13:30] => [14:00] => [14:30] => [15:00] => [15:30] => [16:00] => [16:30] => [17:00] => [17:30] => [18:00] => [18:30] => [19:00] => [19:30] => [20:00] => [20:30] => [21:00] => [21:30] => )
$booking_array
Array ( [0] => Array ( [starttime_of_booking] => 08:00 [facliId] => 6 [userid] => Mr T [equipment] => Beamer ) [1] => Array ( [starttime_of_booking] => 14:00 [facliId] => 4 [userid] => Mrs. L [equipment] => Flipchart ) [2] => Array ( [starttime_of_booking] => 14:00 [facliId] => 9 [userid] => Mrs. M [equipment] => TV-Set ) [3] => Array ( [starttime_of_booking] => 13:00 [facliId] => 8 [userid] => C.J.P. [equipment] => Stuff )
As I am trying around for hours now and do not get a correct overview, I would be very happy for any hints, tips or suggestions!
Thanks in advance.
Regards
Lars
To clarify, what I want to achieve, I add some more information here:
What I want is to create a HTML-Table, which is populated from the 3 sources:
At first he table's heading should be made by a SQL-query to get the room names and their ids. (<thead><th id='facility_id'>room1</th><th id='facility_id'>room2</th id='facility_id'>...</thead>
Afterwards in the 1st data row in the 1st column the 1st starttime coming from $times_array should be printed. ("<td>".$timeskey."</td>").
All remaining cells should also be formatted as HTML-Cells.
Now comes the tricky part (for me), as I want to populate those cells, where already a booking was made.
For example the conference room is booked from 07:00 to 09:00 by Mr.T.
To achieve this without Ajax, I created the array $booking_array with the data from my bookings database table.
Now I am stuck on writing a function, which draws my HTML-Table with all necessary information at the right place.
A <tr> tag is printed to open a new row. In the next step a cell with the starttime is added. And now I want to iterate through my $bookings_array for any booking starting at this time and printing the username to the corresponding column.
As I did not get that to work, in one table, I tried to achieve it by creating a single table for each room containing the starttimes and the column with the booking data / free slots. But the table gets hundreds of rows.

Get Daily sales from database - days without sales should show 0 - sales of last 30 days

I need to create a graph of statistics for sales from the last 30 days. On some days there are no sales so I have to show 0 on these days.
MySQL
SELECT COUNT(*) as total, DAY(FROM_UNIXTIME(tmstmp)) AS soldDay
FROM tl_voucher_create
WHERE sold = 1
GROUP BY soldDay
ORDER BY tmstmp DESC 0,30
PHP array of the result
Array
(
[0] => Array
(
[total] => 1
[soldDay] => 24
)
[1] => Array
(
[total] => 1
[soldDay] => 13
)
[2] => Array
(
[total] => 4
[soldDay] => 3
)
[3] => Array
(
[total] => 2
[soldDay] => 23
)
Using a loop, I put the values into a new array:
for ($x = $day; $x < 31; $x++) {
if (isset($this->_["soldDaily"][$x]["soldDay"])) {
$total[$x] = $this->_["soldDaily"][$x]["total"];
} else {
$total[$x] = 0;
}
}
How can I make the correct output with the values for the last 30 days, in the correct order?
Thanks in advance
Base your query on the actual date:
SELECT COUNT(*) AS total, DATE(FROM_UNIXTIME(tmstmp)) AS sold_date
FROM tl_voucher_create
WHERE sold = 1
AND sold_date BETWEEN DATE(NOW() - INTERVAL 30 DAY) AND CURDATE()
GROUP BY sold_date
ORDER BY sold_date DESC
Assuming the result from your query looks like this:
$data = [
[
'total' => 7,
'sold_date' => '2017-02-28'
],
[
'total' => 19,
'sold_date' => '2017-02-27'
],
[
'total' => 8,
'sold_date' => '2017-02-24'
],
[
'total' => 5,
'sold_date' => '2017-02-22'
],
[
'total' => 12,
'sold_date' => '2017-02-21'
]
];
You now need to base your PHP on an actual date object rather than just a numeric 30-iteration loop, otherwise you will run into various problems and inaccuracies in your reporting because dates are not as simple as just looping from 1 to 30 (months have different numbers of days, you cannot easily cross back into the previous month, daylight savings issues, dates are different across timezones, etc.)
// Organise the array by date
$dates = [];
foreach ($data as $day) {
$dates[$day['sold_date']] = $day['total'];
}
// Loop through the last 30 days and match each iteration with the data
$d = new DateTime();
for ($i = 0; $i < 30; $i++) {
$date = $d->format('Y-m-d');
// If there's no data for the specified date, use zero
$total = isset($dates[$date]) ? $dates[$date] : 0;
echo '<p>' . $total . ' sold on ' . $date . '</p>';
$d->modify('-1 day');
}

store monthly date into array from the selected date

Assume that selected date from Canlender is 02/09/2011. To store weekly date into array from 20/09/2011 is
for($i=0; $i<7; $i++)
{
$WeeklyDate[] = date("Y-m-d", strtotime(2011-09-02) - 86400*$i);
}
My question is how to store monthly date into array from the selected date.
Many thanks
---Update----------
The final result of monthlyDate should look like the following:
$monthlyDate= array{2011-08-03, 2011-08-04, 2011-08-05, 2011-08-06, 2011-08-07 ....2011-08-31, 2011-09-01, 2011-09-02}
First, calculate the number of days in a month using cal_days_in_month and then proceed as you are doing with weeks eg:
$days = cal_days_in_month(CAL_GREGORIAN, 9, 2011);
for($i = 0; $i <= $days; $i++)
{
$MonthlyDate[] = date("Y-m-d", strtotime(2011-09-02) - 86400*$i);
}
Notice that CAL_GREGORIAN is a built-in constant.
Working Example
Whenever programs are incrementing a date using 86400 there is a risk of unexpected output because of DST.
By using strtotime() with a unit larger than hours (like days, weeks, months, etc.) preventing any DST hiccups. Note: a DateTime object approach can be used but for this case, it is unnecessary overhead.
The following is an adjusted form of a one-liner date range function I developed.
Here is the online demo for this case.
function getDatesFromRange($a,$b,$x=0,$dates=[]){
while(end($dates)!=$b && $x=array_push($dates,date("Y-m-d",strtotime("$a +$x day"))));
return $dates;
}
$date='2011-09-02';
$monthlyDate=getDatesFromRange(date("Y-m-d",strtotime("$date -1 month +1 day")),$date);
var_export($monthlyDate);
output as desired/expected:
array (
0 => '2011-08-03',
1 => '2011-08-04',
2 => '2011-08-05',
3 => '2011-08-06',
4 => '2011-08-07',
5 => '2011-08-08',
6 => '2011-08-09',
7 => '2011-08-10',
8 => '2011-08-11',
9 => '2011-08-12',
10 => '2011-08-13',
11 => '2011-08-14',
12 => '2011-08-15',
13 => '2011-08-16',
14 => '2011-08-17',
15 => '2011-08-18',
16 => '2011-08-19',
17 => '2011-08-20',
18 => '2011-08-21',
19 => '2011-08-22',
20 => '2011-08-23',
21 => '2011-08-24',
22 => '2011-08-25',
23 => '2011-08-26',
24 => '2011-08-27',
25 => '2011-08-28',
26 => '2011-08-29',
27 => '2011-08-30',
28 => '2011-08-31',
29 => '2011-09-01',
30 => '2011-09-02',
)

Categories