I got an idea to change the way I currently schedule appointments. Currently, I have an officehours table with 1 record per appointment time per day, as such weekday, appt_time, type_id, provider_id
I was thinking it might be better to have the officehours table instead have 1 record per day instead, with opening_time, closing_time, weekday, provider_id
I also have a closed table, which has open(time), close(time), closed (boolean), provider_id.
Then I have the appts table which saves appt_date, appt_time, client_id, appt_type_id, provider_id, etc
What I did in the past was create arrays of upcoming dates, booked appointments, officehours, and closures. I'd loop through the dates, and filter the arrays to match and run my logic. Anything that wasn't booked or closed would be pushed into an available array and I'd display that. Seems complicated, and it was.
I was wondering if it would be better to use Carbon dates, starting with the first day of the current week, and adding a day until 2 weeks of appointments is done. It's a lot of nested loops, though.
for each day, I have each provider (loop) and for each provider I'd use Carbon to start with the opening time and add the appointment if available and skip 10 minutes or the length of the appointment that is already booked before moving on to the next time.
This way I was using Eqoquent Collections instead of standard arrays, and I'm feeling as though they are slower and more difficult to work with but I'll stick it out if someone who knows better can help me understand.
Anyone have a great idea of how to make this happen in a great way (dare I say best)? I have a hard time simplifying things.
My old code:
public function index(Request $request)
{
$type = str_contains($request->path(), 'calendar') ? 1 : 2;
$doc = str_contains($request->path(), 'drdave') ? 2 : 1;
// getDates($displayWeeks, $daysInWeek) default 2, 4
$settings = Setting::where('type','public')->first();
$days = CalendarController::getDates($settings->weeks_visible ?: 2, $settings->days_in_week ?: 6);
$display = CalendarController::getApptCalendarData($days, $type, $doc);
return view('calendar', ['display'=>$display, 'daysInWeek'=>$settings->days_in_week, 'type'=>$type]);
}
// returns a list of days to be displayed on the calendar
public function getDates($displayWeeks = null, $daysInWeek = null) {
$today = date('Y-m-d',strtotime("today")); // today’s date
$thisday = date('w',strtotime("today")); // today’s weekday
$lastmon = date('m/d/Y',strtotime("last monday")); // last monday
$nextmon = date('m/d/Y',strtotime("next monday")); // next monday
$now = date("h:i:s"); // current time
$days = [];
// DETERMINE STARTING MONDAY
if ($thisday < 1 || $thisday > 6) // today is Sun, Fri, Sat
{
$day0 = date('m/d/Y',strtotime('-1 day', strtotime($nextmon)));
} elseif ($thisday > 1) // today is Tue, Wed, Thu, Fri
{
$day0 = date('m/d/Y',strtotime('-1 day', strtotime($lastmon)));
} else {
$day0 = date('m/d/Y',strtotime('-1 day', strtotime($today)));
}
// GENERATE LIST OF DATES
$displayWeeks = $displayWeeks ? $displayWeeks : 2; // # of weeks shown
// 4 = M-TH, 5 = M-F, 6 = M-Sat, 7 = full week
$daysInWeek = $daysInWeek ? $daysInWeek : 4;
$showSunday = $daysInWeek === 7 ? true : false;
// create array of dates to display
for ($w = 1; $w <= $displayWeeks; $w++) {
for ( $i = 0; $i < $daysInWeek; $i++ ) {
$x = $showSunday === false ? $i + 1 : $i;
$day = strftime("%Y-%m-%d", strtotime( '+' . $x .' day', strtotime($day0)));
array_push($days, $day);
}
// change to the next week
$day0 = strftime("%Y-%m-%d", strtotime( '+7 days', strtotime($day0)));;
}
return $days;
}
// returns list of appointments for doc (1 for Mel, 2 for Dave)
public function getApptCalendarData($days, $type = 0, $doc = 1) {
$start = $days[0];
$end = $days[count($days)-1];
// $closed = Closed::select('*')->where(['closed_date' >='$start', 'closed_date' <= '$end'])->orderBy('closed_date','ASC');
$closed = DB::select("SELECT AM_PM_DAY, closed_date, reason, open, close, date_format(open,'%l:%i %p') as opentime, date_format(close,'%l:%i %p') as closetime from closed WHERE closed_date >='$start' AND closed_date <= '$end' ORDER BY closed_date ASC");
$appointments = DB::select("SELECT appt_date, appt_time, appt_note, appt_reminder, reminder_cell, date_format(appt_time,'%l:%i %p') as time, patient_id, patient.id, patient.nickname, month(appt_date), year(appt_date), appt_type_id, appt_type.id, appt_type.appt_abbr, appt_type.appt_type_name, appt.id as appt_id, appt_status_id FROM appt, patient, appt_type WHERE appt.patient_id = patient.id and appt_date >= '$start' AND appt_date <= '$end' AND appt.appt_type_id = appt_type.id AND appt_status_id NOT IN ( '" . implode( "', '" , $this->RECALL ) . "' ) ORDER BY appt_date, appt_time ASC");
$available = $type > 0 ? DB::select("SELECT appt_time, dayslot, doctor_id, type, date_format(appt_time,'%l:%i %p') as time from officehour WHERE type = '$type' ORDER BY dayslot asc, appt_time asc") : DB::select("SELECT appt_time, dayslot, doctor_id, type, date_format(appt_time,'%l:%i %p') as time from officehour ORDER BY dayslot asc, appt_time asc");
$display = [];
// create an array of objects to display of date, closedInfo, appointments, and availability for each day
foreach($days as $day) {
$item = new class{};
$item->day = $day;
$item->isAdmin = false;
$item->today = date('Y-m-d',strtotime("today")) === date('Y-m-d', strtotime($day)); // today’s date
$item->show = date('Y-m-d', strtotime($day)) >= date('Y-m-d', strtotime('today'));
$item->displayDay = date("l F jS", strtotime($day));
$dayofweek = date('N',strtotime($day)); // for dayslot of office hours
$c = $closed;
$closedToday = array_filter(
$c,
function ($e) use (&$day) {
return $e->closed_date == $day;
}
);
$item->isclosed = count($closedToday) === 0 ? false : true;
if($item->isclosed === true) {
$item->closedInfo = head($closedToday);
$item->closedInfo->message = $item->closedInfo->AM_PM_DAY === 'DAY' ? "Closed Today" : ($item->closedInfo->AM_PM_DAY === 'AM' ? 'Morning Closed' : 'Afternoon/Evening Closed');
}
// filter booked appointments for this day
$existing = array_filter(
$appointments,
function ($e) use (&$day) {
return $e->appt_date == $day;
}
);
// LIST TIMES BOOKED FOR COMPArISON
$existing_times = array_column($existing, 'appt_time');
// office hours for this day of the week
$item->hours = array_filter(
$available,
function ($e) use (&$dayofweek) {
return $e->dayslot == $dayofweek;
}
);
// DISPLAY ARRAYS
$item->availability = [];
$item->adminAppts = [];
$appts = [];
// CHECK FOR AVAILABLE OR BOOKED, AND FILL LISTS
foreach($item->hours as $hour) {
// IS THE TIME BOOKED?
$freetime = (in_Array($hour->appt_time, $existing_times) === true) ? false : true;
// ADD the existing appt to the list
if($freetime === false && count($existing) > 0) {
// add this item to the admin appts list
$pushed = array_shift($existing);
array_push($appts, $pushed);
}
// CHECK IF TIME IS OPEN
$opentime = $item->isclosed === false ? true : false;
if($item->isclosed === true) {
$openAMPMDAY = (($item->closedInfo->AM_PM_DAY !== substr($hour->time, -2)) && ($item->closedInfo->AM_PM_DAY !== 'DAY'));
$openHOURS = (($hour->appt_time >= $item->closedInfo->open && $hour->appt_time <= $item->closedInfo->close));
$opentime = ($openHOURS === true && $openAMPMDAY === true);
}
// ADD THE OPEN TIME TO THE LIST
if($freetime === true && $opentime === true ) {
array_push($appts, $hour);
}
}
// ADD ANY REMAINING APPOINTMENTS TO THE LIST
foreach($existing as $remaining) {
array_push($appts, $remaining);
}
// sort the appointments by time
usort($appts, fn($a, $b) => strcmp($a->appt_time, $b->appt_time));
// split the appointments into 2 lists for column display
$count = ceil(count($appts) /2);
array_push($item->adminAppts, array_splice($appts, 0, $count, true));
array_push($item->adminAppts, array_diff_key($appts, $item->adminAppts));
// array_push($item->adminAppts, $session);
array_push($display, $item);
}
return $display;
}
To give you an idea of how it looks, https://adjust-me.com/calendar for existing, or https://adjust-me.com/newbies for new people. What it's NOT doing is displaying the closure info for each doctor with their appointments, the labeling sucks, and I need separate schedules depending on the type of appointment.
I was hoping to make things simpler for clients and allow them to book any time in the range, but newbies are 60 minutes so I'd need to switch from 1 record per time slot to make that work. That said I don't want it to lag.
There are 2 columns. 1st one with values in date format DD.MM.YYYY, 2nd one with integer values. How to produce the sum of the numbers from the second column according to the conditions of matching the month from the first? For ex Im interesting in sum of the values that match MONTH () = 11?
Suppose that your original data is in a sheet called Sheet1, that you have headers in A1 and B1 and that the data set you posted is in A2:B14 with nothing written in the cells A15:B. In a new sheet, enter this formula in cell A1:
=ArrayFormula(QUERY({DATE(YEAR(Sheet1!A2:A),MONTH(Sheet1!A2:A),1),B2:B},"Select Col1, SUM(Col2) WHERE Col2 Is Not Null GROUP BY Col1 LABEL Col1 'MONTH', SUM(Col2) 'TOTAL' FORMAT Col1 'mmm yyyy'"))
This QUERY will result in two headers (which you can change within the LABEL section of the QUERY if you like) and all results for all months/years present in the data set.
The QUERY acts on a virtual array which first converts all dates in Sheet1!A2:A into new DATEs based on the month and year from the original dates but all falling on the first of the month. This allows the SUMs to be grouped in the QUERY. Then the QUERY's FORMAT section converts those normalized dates to show you just the month and year portion.
Adjust the sheet name and referenced ranges to fit the sheet name and data range of your actual data.
NOTE (after learning that OP's locale is Russia):
This version will work for your locale...
=ArrayFormula(QUERY({DATE(YEAR(Sheet1!A2:A); MONTH(Sheet1!A2:A);1) \ Sheet1!B2:B}; "Select Col1, SUM(Col2) WHERE Col2 Is Not Null GROUP BY Col1 LABEL Col1 'MONTH', SUM(Col2) 'TOTAL' FORMAT Col1 'mmm yyyy'"))
Monthly Sums
function myfunk() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet0');
const vs = sh.getRange(2,1,sh.getLastRow() - 1, 2).getValues();
let sums = {pA:[]};
vs.forEach((r => {
let p = r[0].split('.').filter((e,i) => i > 0).reduce((a,c,idx) => {
if(idx > 0) a += '.';
a += c;
return a;
});
if(!sums.hasOwnProperty(p)) {
sums[p]= r[1];
sums.pA.push(p);
} else {
sums[p] += r[1]
}
}));
let html = '<style> td,th{border: 1px solid black;}</style><table><tr><th>Month</th><th>Sum</th></tr>';
sums.pA.forEach(p => {
html += `<tr><td>${p}</td><td>${sums[p]}</td></tr>`
});
SpreadsheetApp.getUi().showModelessDialog(HtmlService.createHtmlOutput(html),'Monthly Sums')
}
Data:
Sums Dialog:
Added Monthly Sums to osh
function myfunk() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet0');
const osh = ss.getSheetByName('Sheet1');
osh.clear();
const vs = sh.getRange(2,1,sh.getLastRow() - 1, 2).getValues();
let sums = {pA:[]};
vs.forEach((r => {
let p = r[0].split('.').filter((e,i) => i > 0).reduce((a,c,idx) => {
if(idx > 0) a += '.';
a += c;
return a;
});
if(!sums.hasOwnProperty(p)) {
sums[p]= r[1];
sums.pA.push(p);
} else {
sums[p] += r[1]
}
}));
let html = '<style> td,th{border: 1px solid black;}</style><table><tr><th>Month</th><th>Sum</th></tr>';
let arr = [['Month','Sums']];
sums.pA.forEach(p => {
html += `<tr><td>${p}</td><td>${sums[p]}</td></tr>`
arr.push([`${p}`,`${sums[p]}`])
});
SpreadsheetApp.getUi().showModelessDialog(HtmlService.createHtmlOutput(html),'Monthly Sums');
osh.getRange(1,1,arr.length,arr[0].length).setValues(arr);
}
I have the following sql query
SELECT statusId, statusName,sum(durationSeconds)/3600 as duration
FROM status
where date_local >=date
and durationSeconds > 0
group by statusId
order by duration desc;
I'm trying to do the same using EF core.
var result = await context.status
.Where(e => e.ShiftdateLocal >= date && e.Durationseconds > 0)
.Select(e => new LiveStatusProductionViewModel
{ StatusId = e.statusId, StatusName = e.statusName, Duration = e.Durationseconds / 3600 })
//.GroupBy(e => e.Duration)
.OrderByDescending(e => e.Duration)
.ToListAsync();
What am I doing wrong? How do I achieve the same result as on mysql?
You have did only half of work, added GroupBy but not added correct projection.
var result = await context.status
.Where(e => e.ShiftdateLocal >= date && e.Durationseconds > 0)
.GroupBy(e => new { e.statusId, e.statusName })
.Select(g => new LiveStatusProductionViewModel
{
StatusId = g.Key.statusId,
StatusName = g.Key.statusName,
Duration = g.Sum(x => x.Durationseconds / 3600)
})
.OrderByDescending(e => e.Duration)
.ToListAsync();
In Google Sheets, I have a spreadsheet called Events/Incidents which staff from various branches populate. I want Column B to automatically generate a unique ID based on the year in column A and the previously populated event. Given that there could be several events on a particular day, rows in column A could have duplicate dates.
The following is an example of what I am looking for in column B:
There can be no duplicates. Would really appreciate some help with either code or formula.
There are my thoughts https://github.com/contributorpw/google-apps-script-snippets/blob/master/snippets/spreadsheet_autoid/autoid.js
The main function gets a sheet and makes the magic
/**
*
* #param {GoogleAppsScript.Spreadsheet.Sheet} sheet
*/
function autoid_(sheet) {
var data = sheet.getDataRange().getValues();
if (data.length < 2) return;
var indexId = data[0].indexOf('ID');
var indexDate = data[0].indexOf('DATE');
if (indexId < 0 || indexDate < 0) return;
var id = data.reduce(
function(p, row) {
var year =
row[indexDate] && row[indexDate].getTime
? row[indexDate].getFullYear() % 100
: '-';
if (!Object.prototype.hasOwnProperty.call(p.indexByGroup, year)) {
p.indexByGroup[year] = [];
}
var match = ('' + row[indexId]).match(/(\d+)-(\d+)/);
var idVal = row[indexId];
if (match && match.length > 1) {
idVal = match[2];
p.indexByGroup[year].push(+idVal);
}
p.ids.push(idVal);
p.years.push(year);
return p;
},
{ indexByGroup: {}, ids: [], years: [] }
);
// Logger.log(JSON.stringify(id, null, ' '));
var newId = data
.map(function(row, i) {
if (row[indexId] !== '') return [row[indexId]];
if (isNumeric(id.years[i])) {
var lastId = Math.max.apply(
null,
id.indexByGroup[id.years[i]].filter(function(e) {
return isNumeric(e);
})
);
lastId = lastId === -Infinity ? 1 : lastId + 1;
id.indexByGroup[id.years[i]].push(lastId);
return [
Utilities.formatString(
'%s-%s',
id.years[i],
('000000000' + lastId).slice(-3)
)
];
}
return [''];
})
.slice(1);
sheet.getRange(2, indexId + 1, newId.length).setValues(newId);
}
I think it can be simplified in the feature.
There is an easier way to generate unique values that works for me, pick a #, then do +1. Ctrl C, then Ctrl shift V to paste back and remove the formula. Now you are left with thousands of unique IDs.
This is a manual solution but you can do an entire database in a matter of seconds every once in a while.
here is my query:
List<string> kwList = GetFilterKeywords(); // returns NULL none keyword selected
var res = from d in ctx.Books
where (kwList == null || kwList.Contains(d.Name))
select d;
Looks like it is not legit to add where clause if kwList is NULL. So my question is: Is there any way to add more where clauses to the same query in IF/ELSE IF construction?
I mean:
var res = from d in ctx.Books
select d;
if (kwList != null)
{
res.Where(d => kwList.Contains(d.Name);
}
var res = ctx.Books; // no need to write select
if (kwList != null)
res = res.Where(x => kwList.Contains(x.Name));
foreach (d in res) {
...
}
You can use the tertiary operator
var res = kwList == null ? ctx.Books : ctx.Books.Where(x => kwList.Contains(x.Name));
If you want to modify the initial linq query in subsequent case statements, make sure to reassign the initial query to the modified:
var res = ctx.Books;
if (a == b)
{
// reassign here
res = res.Where(x => kwList.Contains(x.Name));
}
else if (a == c)
res = res.Where(x => x.Id == y);