Commit 29263ec6 by Mac Stephens

add row validation, saving

parent f6aacf29
......@@ -3,6 +3,16 @@ object FTimeEntries: TFTimeEntries
Height = 480
ElementFont = efCSS
OnCreate = WebFormCreate
object lblValidationMessage: TWebLabel
Left = 90
Top = 32
Width = 69
Height = 15
ElementID = 'lbl_validation_message'
ElementFont = efCSS
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object edtWeekOf: TWebEdit
Left = 71
Top = 80
......
......@@ -23,5 +23,6 @@
<button id="btn_add_entry" class="btn btn-sm btn-success text-nowrap">Add Entry</button>
</div>
</div>
<div id="lbl_validation_message" class="alert alert-danger py-1 px-2 mb-2 d-none small"></div>
<div id="time_entries_table_host" class="flex-grow-1 min-h-0 overflow-auto"></div>
</div>
......@@ -24,6 +24,7 @@ type
xdwdsTimeEntriessummary: TStringField;
xdwdsTimeEntriescategoryDesc: TStringField;
btnAddEntry: TWebButton;
lblValidationMessage: TWebLabel;
procedure edtWeekOfChange(Sender: TObject);
procedure edtStartDateChange(Sender: TObject);
procedure edtEndDateChange(Sender: TObject);
......@@ -39,6 +40,10 @@ type
FUpdatingDates: Boolean;
FPendingScrollTop: Integer;
FPendingScrollLeft: Integer;
FActiveRowIndex: Integer;
FPendingEntryId: string;
FLastMouseDownRowIndex: Integer;
FLastMouseDownTime: Double;
[async] procedure LoadTimeEntries;
[async] function AddTimeEntry: Boolean;
procedure RenderTable;
......@@ -55,6 +60,19 @@ type
procedure SetTimeEntriesLabel(const AName: string);
procedure CaptureTableScroll;
procedure RestoreTableScroll;
procedure EditorInput(Event: TJSEvent);
procedure RowFocusOut(Event: TJSEvent);
procedure RowClick(Event: TJSEvent);
function ValidateRow(AIndex: Integer): Boolean;
procedure ApplyPendingEntryFocus;
procedure ApplyRowValidation(AIndex: Integer);
procedure ClearRowValidation(AIndex: Integer);
procedure ShowRowValidationMessage(AIndex: Integer; const AMessage: string);
procedure HideRowValidationMessage;
[async] procedure SaveRow(AIndex: Integer);
procedure SetTopControlsEnabled(AEnabled: Boolean);
function GetTargetRowIndex(ATarget: TJSHTMLElement): Integer;
procedure DocumentMouseDown(Event: TJSEvent);
public
end;
......@@ -81,6 +99,12 @@ begin
FUpdatingDates := False;
FPendingScrollTop := 0;
FPendingScrollLeft := 0;
FActiveRowIndex := -1;
FPendingEntryId := '';
FLastMouseDownRowIndex := -1;
FLastMouseDownTime := 0;
document.addEventListener('mousedown', TJSEventHandler(@DocumentMouseDown));
payload := AuthService.TokenPayload;
if Assigned(payload) then
......@@ -113,6 +137,7 @@ begin
LoadTimeEntries;
end;
function TFTimeEntries.HtmlEncode(const s: string): string;
begin
Result := s;
......@@ -123,6 +148,7 @@ begin
Result := StringReplace(Result, '''', '&#39;', [rfReplaceAll]);
end;
function TFTimeEntries.IsoToDate(const AValue: string): TDateTime;
var
yearValue: Integer;
......@@ -217,8 +243,7 @@ var
el: TJSHTMLElement;
begin
el := TJSHTMLElement(document.getElementById('lbl_time_total_rows'));
if Assigned(el) then
el.innerText := 'Total Rows: ' + IntToStr(ARowCount);
el.innerText := 'Total Rows: ' + IntToStr(ARowCount);
end;
procedure TFTimeEntries.SetTimeEntriesLabel(const AName: string);
......@@ -231,8 +256,7 @@ begin
displayName := 'User';
el := TJSHTMLElement(document.getElementById('lbl_time_entries_title'));
if Assigned(el) then
el.innerText := displayName + '''s Time Entries';
el.innerText := displayName + '''s Time Entries';
end;
[async] procedure TFTimeEntries.LoadTimeEntries;
......@@ -265,41 +289,29 @@ begin
end;
end;
if not Assigned(response.Result) then
Exit;
resultObj := TJSObject(response.Result);
if resultObj.hasOwnProperty('userName') then
begin
userNameValue := string(resultObj['userName']);
if userNameValue <> '' then
SetTimeEntriesLabel(userNameValue);
end
userNameValue := string(resultObj['userName']);
if userNameValue <> '' then
SetTimeEntriesLabel(userNameValue)
else
SetTimeEntriesLabel(FUserName);
rowCount := StrToIntDef(string(resultObj['count']), 0);
SetTotalRowsLabel(rowCount);
if resultObj.hasOwnProperty('taskOptions') then
FTaskOptions := TJSArray(resultObj['taskOptions'])
else
FTaskOptions := TJSArray.new;
if resultObj.hasOwnProperty('categoryOptions') then
FCategoryOptions := TJSArray(resultObj['categoryOptions'])
else
FCategoryOptions := TJSArray.new;
FTaskOptions := TJSArray(resultObj['taskOptions']);
FCategoryOptions := TJSArray(resultObj['categoryOptions']);
itemsArray := TJSArray(resultObj['items']);
if not Assigned(itemsArray) then
itemsArray := TJSArray.new;
xdwdsTimeEntries.Close;
xdwdsTimeEntries.SetJsonData(itemsArray);
xdwdsTimeEntries.Open;
FActiveRowIndex := -1;
HideRowValidationMessage;
SetTopControlsEnabled(True);
RenderTable;
finally
Utils.HideSpinner('spinner');
......@@ -331,6 +343,8 @@ begin
[FUserId, edtWeekOf.Text]
));
FPendingEntryId := JS.toString(response.Result);
console.log('AddTimeEntry response=' + string(TJSJSON.stringify(response.Result)));
Result := True;
except
......@@ -368,7 +382,7 @@ var
function TextInput(const FieldName, Value: string; const AIdx: Integer): string;
begin
Result :=
'<input class="form-control form-control-sm cell-input time-editor w-100" readonly ' +
'<input class="form-control form-control-sm cell-input time-editor w-100" ' +
'data-idx="' + IntToStr(AIdx) + '" data-field="' + FieldName + '" ' +
'value="' + HtmlEncode(Value) + '">';
end;
......@@ -380,7 +394,7 @@ var
dateValue := Utils.NormalizeDateValue(Value);
Result :=
'<input type="date" class="form-control form-control-sm cell-input time-editor w-100" readonly ' +
'<input type="date" class="form-control form-control-sm cell-input time-editor w-100" ' +
'data-idx="' + IntToStr(AIdx) + '" data-field="' + FieldName + '" ' +
'value="' + HtmlEncode(dateValue) + '">';
end;
......@@ -421,7 +435,7 @@ var
'data-display="" ' +
'data-trigger-id="' + triggerId + '"></button>';
if Assigned(Items) then
for i := 0 to Items.length - 1 do
begin
optionObj := TJSObject(Items[i]);
......@@ -445,15 +459,13 @@ var
function SummaryTextArea(const FieldName, Value: string; const AIdx: Integer): string;
begin
Result :=
'<textarea rows="1" class="form-control form-control-sm cell-textarea time-textarea time-editor w-100" readonly ' +
'<textarea rows="1" class="form-control form-control-sm cell-textarea time-textarea time-editor w-100" ' +
'data-idx="' + IntToStr(AIdx) + '" data-field="' + FieldName + '" ' +
'style="min-height:31px; height:31px; overflow:hidden; resize:none;">' + HtmlEncode(Value) + '</textarea>';
end;
begin
host := TJSHTMLElement(document.getElementById('time_entries_table_host'));
if not Assigned(host) then
Exit;
html :=
'<div class="time-vscroll">' +
......@@ -486,7 +498,7 @@ begin
hoursText := FormatFloat('0.##', xdwdsTimeEntrieshours.AsFloat);
html := html +
'<tr data-entry-id="' + IntToStr(xdwdsTimeEntriesentryId.AsInteger) + '" data-task-id="' + xdwdsTimeEntriestaskId.AsString + '">' +
'<tr class="time-row-selectable" data-idx="' + IntToStr(rowIdx) + '" data-entry-id="' + IntToStr(xdwdsTimeEntriesentryId.AsInteger) + '" data-task-id="' + xdwdsTimeEntriestaskId.AsString + '">' +
TdNowrap(DateInput('taskDate', xdwdsTimeEntriestaskDate.AsString, rowIdx)) +
TdNowrap(SelectList('taskId', xdwdsTimeEntriestaskId.AsString, xdwdsTimeEntriestaskDisplay.AsString, rowIdx, FTaskOptions, 'taskId', 'taskDisplay')) +
TdNowrap(HoursInput('hours', hoursText, rowIdx)) +
......@@ -506,6 +518,7 @@ begin
EnableAutoGrowTextAreas;
EnableColumnResize;
RestoreTableScroll;
ApplyPendingEntryFocus;
end;
procedure TFTimeEntries.BindTableEditors;
......@@ -518,15 +531,202 @@ begin
nodes := document.querySelectorAll('.time-editor');
console.log('BindTableEditors: time-editor count=' + IntToStr(nodes.length));
for i := 0 to nodes.length - 1 do
begin
el := TJSHTMLElement(nodes.item(i));
el.addEventListener('input', TJSEventHandler(@EditorInput));
end;
nodes := document.querySelectorAll('.time-dd-item');
console.log('BindTableEditors: time-dd-item count=' + IntToStr(nodes.length));
for i := 0 to nodes.length - 1 do
begin
el := TJSHTMLElement(nodes.item(i));
el.addEventListener('click', TJSEventHandler(@DropdownItemClick));
end;
nodes := document.querySelectorAll('.time-row-selectable');
console.log('BindTableEditors: time-row-selectable count=' + IntToStr(nodes.length));
for i := 0 to nodes.length - 1 do
begin
el := TJSHTMLElement(nodes.item(i));
el.addEventListener('click', TJSEventHandler(@RowClick));
el.addEventListener('focusout', TJSEventHandler(@RowFocusOut));
end;
end;
procedure TFTimeEntries.EditorInput(Event: TJSEvent);
var
el: TJSHTMLElement;
idx: Integer;
idxStr: string;
fieldName: string;
newVal: string;
begin
if not xdwdsTimeEntries.Active then
Exit;
el := TJSHTMLElement(Event.target);
idxStr := string(el.getAttribute('data-idx'));
fieldName := string(el.getAttribute('data-field'));
idx := StrToIntDef(idxStr, -1);
if (idx < 0) or (fieldName = '') then
Exit;
FActiveRowIndex := idx;
GotoRowIndex(idx);
if xdwdsTimeEntries.Eof then
Exit;
newVal := string(TJSObject(el)['value']);
xdwdsTimeEntries.Edit;
if SameText(fieldName, 'taskDate') then
xdwdsTimeEntriestaskDate.AsString := newVal
else if SameText(fieldName, 'hours') then
begin
if Trim(newVal) = '' then
xdwdsTimeEntrieshours.Clear
else
xdwdsTimeEntrieshours.AsFloat := StrToFloatDef(newVal, 0);
end
else if SameText(fieldName, 'taskTime') then
xdwdsTimeEntriestaskTime.AsString := newVal
else if SameText(fieldName, 'summary') then
xdwdsTimeEntriessummary.AsString := newVal
else
xdwdsTimeEntries.FieldByName(fieldName).AsString := newVal;
xdwdsTimeEntries.Post;
el.setAttribute('data-unsaved-data', '1');
if ValidateRow(idx) then
begin
ClearRowValidation(idx);
SetTopControlsEnabled(True);
end
else
SetTopControlsEnabled(False);
end;
[async] procedure TFTimeEntries.SaveRow(AIndex: Integer);
var
saveObj: TJSObject;
response: TXDataClientResponse;
rowEl: TJSHTMLElement;
nodes: TJSNodeList;
i: Integer;
el: TJSHTMLElement;
begin
if not xdwdsTimeEntries.Active then
Exit;
GotoRowIndex(AIndex);
if xdwdsTimeEntries.Eof then
Exit;
if not ValidateRow(AIndex) then
begin
ApplyRowValidation(AIndex);
ShowRowValidationMessage(AIndex, 'Complete required fields before leaving this row.');
Exit;
end;
saveObj := TJSObject.new;
saveObj['entryId'] := xdwdsTimeEntriesentryId.AsString;
saveObj['userId'] := FUserId;
saveObj['taskDate'] := xdwdsTimeEntriestaskDate.AsString;
saveObj['taskId'] := xdwdsTimeEntriestaskId.AsString;
saveObj['hours'] := xdwdsTimeEntrieshours.AsFloat;
saveObj['taskTime'] := xdwdsTimeEntriestaskTime.AsString;
saveObj['category'] := xdwdsTimeEntriescategory.AsString;
saveObj['summary'] := xdwdsTimeEntriessummary.AsString;
try
response := await(xdwcTimeEntries.RawInvokeAsync(
'ITimeEntryService.SaveTimeEntry',
[saveObj]
));
console.log('SaveRow response=' + string(TJSJSON.stringify(response.Result)));
rowEl := TJSHTMLElement(document.querySelector('tr[data-idx="' + IntToStr(AIndex) + '"]'));
nodes := rowEl.querySelectorAll('[data-unsaved-data="1"]');
for i := 0 to nodes.length - 1 do
begin
el := TJSHTMLElement(nodes.item(i));
el.removeAttribute('data-unsaved-data');
end;
ClearRowValidation(AIndex);
except
on E: EXDataClientRequestException do
begin
console.log('SaveRow ERROR: ' + E.ErrorResult.ErrorMessage);
Utils.ShowErrorModal(E.ErrorResult.ErrorMessage);
end;
end;
end;
procedure TFTimeEntries.RowClick(Event: TJSEvent);
var
rowEl: TJSHTMLElement;
idx: Integer;
begin
rowEl := TJSHTMLElement(Event.currentTarget);
idx := StrToIntDef(string(rowEl.getAttribute('data-idx')), -1);
if idx < 0 then
Exit;
FActiveRowIndex := idx;
end;
procedure TFTimeEntries.RowFocusOut(Event: TJSEvent);
var
rowEl: TJSHTMLElement;
idx: Integer;
begin
rowEl := TJSHTMLElement(Event.currentTarget);
window.setTimeout(
procedure
begin
idx := StrToIntDef(string(rowEl.getAttribute('data-idx')), -1);
if idx < 0 then
Exit;
if (FLastMouseDownRowIndex = idx) and ((TJSDate.now - FLastMouseDownTime) < 500) then
Exit;
if Assigned(document.activeElement) and rowEl.contains(document.activeElement) then
Exit;
if not ValidateRow(idx) then
begin
FActiveRowIndex := idx;
ApplyRowValidation(idx);
ShowRowValidationMessage(idx, 'Complete required fields before leaving this row.');
SetTopControlsEnabled(False);
Exit;
end;
ClearRowValidation(idx);
SetTopControlsEnabled(True);
if Assigned(rowEl.querySelector('[data-unsaved-data="1"]')) then
SaveRow(idx);
end,
0
);
end;
......@@ -580,16 +780,126 @@ begin
xdwdsTimeEntries.Post;
if triggerId <> '' then
FActiveRowIndex := idx;
btn := TJSHTMLElement(document.getElementById(triggerId));
btn.setAttribute('data-unsaved-data', '1');
labelEl := TJSHTMLElement(btn.querySelector('.time-dd-label'));
labelEl.textContent := newDisplay;
btn.focus;
if ValidateRow(idx) then
begin
btn := TJSHTMLElement(document.getElementById(triggerId));
if Assigned(btn) then
begin
labelEl := TJSHTMLElement(btn.querySelector('.time-dd-label'));
if Assigned(labelEl) then
labelEl.textContent := newDisplay;
end;
ClearRowValidation(idx);
SetTopControlsEnabled(True);
end
else
SetTopControlsEnabled(False);
end;
function TFTimeEntries.ValidateRow(AIndex: Integer): Boolean;
var
hasDate: Boolean;
hasTask: Boolean;
hasHours: Boolean;
hasCategory: Boolean;
hasSummary: Boolean;
begin
Result := False;
if not xdwdsTimeEntries.Active then
Exit;
GotoRowIndex(AIndex);
if xdwdsTimeEntries.Eof then
Exit;
hasDate := Trim(xdwdsTimeEntriestaskDate.AsString) <> '';
hasTask := Trim(xdwdsTimeEntriestaskId.AsString) <> '';
if xdwdsTimeEntrieshours.IsNull then
hasHours := False
else
hasHours := xdwdsTimeEntrieshours.AsFloat > 0;
hasCategory := Trim(xdwdsTimeEntriescategory.AsString) <> '';
hasSummary := Trim(xdwdsTimeEntriessummary.AsString) <> '';
Result := hasDate and hasTask and hasHours and hasCategory and hasSummary;
end;
procedure TFTimeEntries.ApplyPendingEntryFocus;
var
rowEl: TJSHTMLElement;
idx: Integer;
firstEditor: TJSHTMLElement;
begin
if FPendingEntryId = '' then
Exit;
rowEl := TJSHTMLElement(document.querySelector('tr[data-entry-id="' + FPendingEntryId + '"]'));
if not Assigned(rowEl) then
begin
FPendingEntryId := '';
Exit;
end;
idx := StrToIntDef(string(rowEl.getAttribute('data-idx')), -1);
if idx < 0 then
begin
FPendingEntryId := '';
Exit;
end;
FActiveRowIndex := idx;
SetTopControlsEnabled(False);
firstEditor := TJSHTMLElement(rowEl.querySelector('[data-field="taskDate"]'));
if Assigned(firstEditor) then
firstEditor.focus;
FPendingEntryId := '';
end;
procedure TFTimeEntries.ApplyRowValidation(AIndex: Integer);
var
rowEl: TJSHTMLElement;
hoursInvalid: Boolean;
procedure SetInvalid(const AFieldName: string; AInvalid: Boolean);
var
fieldEl: TJSHTMLElement;
begin
fieldEl := TJSHTMLElement(rowEl.querySelector('[data-field="' + AFieldName + '"]'));
if AInvalid then
fieldEl.classList.add('is-invalid')
else
fieldEl.classList.remove('is-invalid');
end;
begin
if not xdwdsTimeEntries.Active then
Exit;
GotoRowIndex(AIndex);
if xdwdsTimeEntries.Eof then
Exit;
rowEl := TJSHTMLElement(document.querySelector('tr[data-idx="' + IntToStr(AIndex) + '"]'));
if xdwdsTimeEntrieshours.IsNull then
hoursInvalid := True
else
hoursInvalid := xdwdsTimeEntrieshours.AsFloat <= 0;
SetInvalid('taskDate', Trim(xdwdsTimeEntriestaskDate.AsString) = '');
SetInvalid('taskId', Trim(xdwdsTimeEntriestaskId.AsString) = '');
SetInvalid('hours', hoursInvalid);
SetInvalid('category', Trim(xdwdsTimeEntriescategory.AsString) = '');
SetInvalid('summary', Trim(xdwdsTimeEntriessummary.AsString) = '');
end;
......@@ -605,7 +915,6 @@ begin
editor.style.height = 'auto';
editor.style.height = editor.scrollHeight + 'px';
};
fit();
editor.addEventListener('input', fit);
});
......@@ -613,6 +922,79 @@ begin
end;
end;
procedure TFTimeEntries.ClearRowValidation(AIndex: Integer);
var
rowEl: TJSHTMLElement;
nodes: TJSNodeList;
i: Integer;
el: TJSHTMLElement;
begin
rowEl := TJSHTMLElement(document.querySelector('tr[data-idx="' + IntToStr(AIndex) + '"]'));
nodes := rowEl.querySelectorAll('.is-invalid');
for i := 0 to nodes.length - 1 do
begin
el := TJSHTMLElement(nodes.item(i));
el.classList.remove('is-invalid');
end;
SetTopControlsEnabled(True);
HideRowValidationMessage;
end;
procedure TFTimeEntries.ShowRowValidationMessage(AIndex: Integer; const AMessage: string);
var
rowEl: TJSHTMLElement;
firstInvalidEl: TJSHTMLElement;
begin
lblValidationMessage.Caption := AMessage;
lblValidationMessage.ElementHandle.classList.remove('d-none');
rowEl := TJSHTMLElement(document.querySelector('tr[data-idx="' + IntToStr(AIndex) + '"]'));
firstInvalidEl := TJSHTMLElement(rowEl.querySelector('.is-invalid'));
firstInvalidEl.focus;
end;
procedure TFTimeEntries.HideRowValidationMessage;
begin
lblValidationMessage.Caption := '';
lblValidationMessage.ElementHandle.classList.add('d-none');
end;
function TFTimeEntries.GetTargetRowIndex(ATarget: TJSHTMLElement): Integer;
var
el: TJSHTMLElement;
idxText: string;
begin
Result := -1;
el := ATarget;
while Assigned(el) do
begin
idxText := string(el.getAttribute('data-idx'));
if idxText <> '' then
begin
Result := StrToIntDef(idxText, -1);
if Result >= 0 then
Exit;
end;
el := TJSHTMLElement(el.parentElement);
end;
end;
procedure TFTimeEntries.DocumentMouseDown(Event: TJSEvent);
begin
FLastMouseDownRowIndex := GetTargetRowIndex(TJSHTMLElement(Event.target));
FLastMouseDownTime := TJSDate.now;
end;
procedure TFTimeEntries.EnableColumnResize;
begin
asm
......@@ -665,6 +1047,7 @@ begin
end;
end;
[async] procedure TFTimeEntries.btnAddEntryClick(Sender: TObject);
begin
Utils.ShowSpinner('spinner');
......@@ -679,6 +1062,7 @@ begin
end;
end;
procedure TFTimeEntries.CaptureTableScroll;
begin
asm
......@@ -690,6 +1074,7 @@ begin
end;
end;
procedure TFTimeEntries.RestoreTableScroll;
begin
asm
......@@ -701,4 +1086,12 @@ begin
end;
end;
procedure TFTimeEntries.SetTopControlsEnabled(AEnabled: Boolean);
begin
edtWeekOf.Enabled := AEnabled;
edtStartDate.Enabled := AEnabled;
edtEndDate.Enabled := AEnabled;
btnAddEntry.Enabled := AEnabled;
end;
end.
......@@ -44,4 +44,9 @@
.time-table .dropdown-menu {
z-index: 1055;
}
.time-dd-toggle.is-invalid {
border-color: var(--bs-danger) !important;
padding-right: calc(1.5em + .75rem);
}
\ No newline at end of file
......@@ -1142,4 +1142,68 @@ object ApiDatabase: TApiDatabase
Value = nil
end>
end
object uqSaveTimeEntry: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'UPDATE time_items'
'SET'
' TASK_DATE = :TASK_DATE,'
' TASK_ID = :TASK_ID,'
' HOURS = :HOURS,'
' TASK_TIME = :TASK_TIME,'
' CATEGORY = :CATEGORY,'
' SUMMARY = :SUMMARY,'
' MODIFY_DATE = now(),'
' MODIFIED_BY = :MODIFIED_BY'
'WHERE ENTRY_ID = :ENTRY_ID'
' AND USER_ID = :USER_ID')
Left = 544
Top = 424
ParamData = <
item
DataType = ftUnknown
Name = 'TASK_DATE'
Value = nil
end
item
DataType = ftUnknown
Name = 'TASK_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'HOURS'
Value = nil
end
item
DataType = ftUnknown
Name = 'TASK_TIME'
Value = nil
end
item
DataType = ftUnknown
Name = 'CATEGORY'
Value = nil
end
item
DataType = ftUnknown
Name = 'SUMMARY'
Value = nil
end
item
DataType = ftUnknown
Name = 'MODIFIED_BY'
Value = nil
end
item
DataType = ftUnknown
Name = 'ENTRY_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'USER_ID'
Value = nil
end>
end
end
......@@ -89,6 +89,7 @@ type
uqProjectReportedUsersTASK_ITEM_USER_ID: TStringField;
uqProjectReportedUsersNAME: TStringField;
uqAddTimeEntry: TUniQuery;
uqSaveTimeEntry: TUniQuery;
procedure DataModuleCreate(Sender: TObject);
procedure uqUsersCalcFields(DataSet: TDataSet);
private
......
......@@ -48,11 +48,24 @@ type
destructor Destroy; override;
end;
TTimeEntrySave = class
public
entryId: string;
userId: string;
taskDate: string;
taskId: string;
hours: Double;
taskTime: string;
category: string;
summary: string;
end;
[ServiceContract, Model(API_MODEL)]
ITimeEntryService = interface(IInvokable)
['{B18BCD1E-B19A-4D25-BBA9-50A24FC4C690}']
[HttpGet] function GetTimeEntries(userId, startDate, endDate: string): TTimeEntriesResponse;
[HttpPost] function AddTimeEntry(userId, taskDate: string): string;
[HttpPost] function SaveTimeEntry(Item: TTimeEntrySave): Boolean;
end;
implementation
......
......@@ -31,6 +31,7 @@ type
public
function GetTimeEntries(userId, startDate, endDate: string): TTimeEntriesResponse;
function AddTimeEntry(userId, taskDate: string): string;
function SaveTimeEntry(Item: TTimeEntrySave): Boolean;
end;
implementation
......@@ -429,6 +430,38 @@ begin
end;
function TTimeEntryService.SaveTimeEntry(Item: TTimeEntrySave): Boolean;
var
taskDateValue: TDateTime;
begin
Logger.Log(4, Format('TimeEntryService.SaveTimeEntry - ENTRY_ID="%s" USER_ID="%s"', [Item.entryId, Item.userId]));
taskDateValue := ParseIsoDate(Item.taskDate);
apiDB.uqSaveTimeEntry.Close;
apiDB.uqSaveTimeEntry.ParamByName('ENTRY_ID').AsString := Item.entryId;
apiDB.uqSaveTimeEntry.ParamByName('USER_ID').AsString := Item.userId;
apiDB.uqSaveTimeEntry.ParamByName('TASK_DATE').AsDateTime := taskDateValue;
apiDB.uqSaveTimeEntry.ParamByName('TASK_ID').AsString := Item.taskId;
apiDB.uqSaveTimeEntry.ParamByName('HOURS').AsFloat := Item.hours;
if Trim(Item.taskTime) = '' then
apiDB.uqSaveTimeEntry.ParamByName('TASK_TIME').Clear
else
apiDB.uqSaveTimeEntry.ParamByName('TASK_TIME').AsString := Item.taskTime;
apiDB.uqSaveTimeEntry.ParamByName('CATEGORY').AsString := Item.category;
apiDB.uqSaveTimeEntry.ParamByName('SUMMARY').AsString := Item.summary;
apiDB.uqSaveTimeEntry.ParamByName('MODIFIED_BY').AsString := Item.userId;
apiDB.uqSaveTimeEntry.ExecSQL;
Result := True;
Logger.Log(4, 'TimeEntryService.SaveTimeEntry - saved ENTRY_ID=' + Item.entryId);
end;
initialization
RegisterServiceType(TTimeEntryService);
......
......@@ -2,7 +2,7 @@
MemoLogLevel=4
FileLogLevel=4
webClientVersion=0.8.9
LogFileNum=219
LogFileNum=222
[Database]
Server=192.168.102.131
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment