Skip to content

Commit

Permalink
Merge pull request #351 from qccoders/develop
Browse files Browse the repository at this point in the history
Prod deploy 1.3
  • Loading branch information
wburklund authored Jan 30, 2019
2 parents d6e2981 + 8e2e9e7 commit 4120bf2
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 24 deletions.
78 changes: 69 additions & 9 deletions api/QCVOC.Api/Scans/Controller/ScansController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,63 @@ public ScansController(ITripleKeyRepository<Scan> scanRepository, ISingleKeyRepo
private ISingleKeyRepository<Veteran> VeteranRepository { get; set; }
private ISingleKeyRepository<Service> ServiceRepository { get; set; }

/// <summary>
/// Returns a check in scan for the specified Veteran.
/// </summary>
/// <param name="eventId">The Id of the Event.</param>
/// <param name="id">Either the Veteran Id or Card Number of the Veteran.</param>
/// <returns>See attributes.</returns>
/// <response code="200">The Scan was retrieved successfully.</response>
/// <response code="400">The Veteran Id or Card Number is invalid.</response>
/// <response code="401">Unauthorized.</response>
/// <response code="404">The specified Card Number or Veteran Id doesn't match an enrolled Veteran, or the Veteran has not checked in to the specified event.</response>
/// <response code="500">The server encountered an error while processing the request.</response>
[HttpGet("{eventId}/{id}/checkin")]
[Authorize]
[ProducesResponseType(typeof(Scan), 200)]
[ProducesResponseType(400)]
[ProducesResponseType(401)]
[ProducesResponseType(404)]
[ProducesResponseType(typeof(Exception), 500)]
public IActionResult GetCheckIn(Guid eventId, string id)
{
if (string.IsNullOrEmpty(id))
{
return BadRequest($"The card or veteran ID is null or empty.");
}

var veteran = default(Veteran);

if (int.TryParse(id, out var cardNumber))
{
veteran = VeteranRepository
.GetAll(new VeteranFilters() { CardNumber = cardNumber })
.SingleOrDefault();
}
else if (Guid.TryParse(id, out var veteranId))
{
veteran = VeteranRepository.Get(veteranId);
}
else
{
return BadRequest($"The provided ID is neither a Card Number nor Veteran ID.");
}

if (veteran == default(Veteran))
{
return StatusCode(404, $"The specified Card Number or Veteran Id doesn't match an enrolled Veteran.");
}

var scan = ScanRepository.Get(eventId, veteran.Id, Guid.Empty);

if (scan == default(Scan))
{
return StatusCode(404, $"The Veteran has not checked in for this Event.");
}

return Ok(scan);
}

/// <summary>
/// Returns a list of Scans.
/// </summary>
Expand Down Expand Up @@ -85,28 +142,28 @@ public IActionResult GetAll([FromQuery]ScanFilters filters)
[ProducesResponseType(typeof(Exception), 500)]
public IActionResult Delete(Guid eventId, string id)
{
return Delete(eventId, id, null);
return Delete(eventId, Guid.Empty, id);
}

/// <summary>
/// Deletes a Service Scan.
/// </summary>
/// <param name="eventId">The Id of the Event.</param>
/// <param name="serviceId">The Id of the Service.</param>
/// <param name="id">Either the Veteran Id or Card Number of the Veteran.</param>
/// <param name="serviceId">The optional Service Id.</param>
/// <returns>See attributes.</returns>
/// <response code="204">The Scan was deleted successfully.</response>
/// <response code="400">The Veteran Id or Card Number is invalid.</response>
/// <response code="401">Unauthorized.</response>
/// <response code="404">The specified Card Number or Veteran Id doesn't match an enrolled Veteran.</response>
/// <response code="500">The server encountered an error while processing the request.</response>
[HttpDelete("{eventId}/{id}/{serviceId}")]
[HttpDelete("{eventId}/{serviceId}/{id}")]
[Authorize]
[ProducesResponseType(400)]
[ProducesResponseType(401)]
[ProducesResponseType(404)]
[ProducesResponseType(typeof(Exception), 500)]
public IActionResult Delete(Guid eventId, string id, Guid? serviceId = null)
public IActionResult Delete(Guid eventId, Guid serviceId, string id)
{
if (string.IsNullOrEmpty(id))
{
Expand Down Expand Up @@ -161,7 +218,7 @@ public IActionResult Delete(Guid eventId, string id, Guid? serviceId = null)
/// <response code="201">The Scan was recorded or updated.</response>
/// <response code="400">The specified Scan was invalid.</response>
/// <response code="401">Unauthorized.</response>
/// <response code="403">The Veteran has not checked in for the Event.</response>
/// <response code="403">The Veteran has not checked in for the Event, or did not check in with a guest and is attempting to use a service with a guest.</response>
/// <response code="404">The specified Veteran, Event or Service was invalid.</response>
/// <response code="409">The Scan has already been recorded.</response>
/// <response code="500">The server encountered an error while processing the request.</response>
Expand Down Expand Up @@ -219,18 +276,17 @@ public IActionResult Scan([FromBody]ScanRequest scan)
ServiceId = scan.ServiceId,
ScanById = User.GetId(),
ScanDate = DateTime.UtcNow,
PlusOne = scan.PlusOne,
};

// check in scan
if (scan.ServiceId == Guid.Empty)
{
scanRecord.PlusOne = scan.PlusOne;

if (existingCheckIn == default(Scan))
{
return CreateScan(scanRecord, veteran);
}
else if (existingCheckIn.PlusOne != scan.PlusOne)
else if (existingCheckIn.PlusOne != scanRecord.PlusOne)
{
return UpdateScan(scanRecord, veteran);
}
Expand All @@ -246,14 +302,18 @@ public IActionResult Scan([FromBody]ScanRequest scan)
return StatusCode(403, new ScanError(scanRecord, veteran, "The Veteran has not checked in for this Event."));
}

if (scanRecord.PlusOne && !existingCheckIn.PlusOne)
{
return StatusCode(403, new ScanError(scanRecord, veteran, "The Veteran did not check in with a +1."));
}

var previousServiceScan = previousScans.Where(s => s.ServiceId == scan.ServiceId).SingleOrDefault();

if (previousServiceScan != default(Scan))
{
return Conflict(new ScanError(previousServiceScan, veteran, "Duplicate Scan"));
}

scanRecord.PlusOne = existingCheckIn.PlusOne;
return CreateScan(scanRecord, veteran);
}

Expand Down
99 changes: 87 additions & 12 deletions web/src/scans/Scanner.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ const initialState = {
scanner: {
event: undefined,
service: undefined,
history: [],
},
scan: {
cardNumber: undefined,
Expand All @@ -95,7 +96,6 @@ const initialState = {
},
events: [],
services: [],
history: [],
plusOne: undefined,
historyDialog: {
open: false,
Expand All @@ -105,6 +105,7 @@ const initialState = {
},
plusOneDialog: {
open: false,
intent: undefined,
},
};

Expand All @@ -114,7 +115,24 @@ class Scanner extends Component {
componentDidMount = () => {
window.inputBarcodeScanner = this.handleBarcodeScanned;

this.fetchEvents('refreshApi');
let scanner = undefined;

try {
scanner = JSON.parse(sessionStorage.getItem('scanner'));

if (scanner === undefined || scanner.event === undefined || scanner.service === undefined || scanner.history === undefined) {
throw scanner;
}
} catch {
sessionStorage.removeItem('scanner');
}

if (scanner && scanner.event && scanner.service) {
this.setState({ scanner: scanner });
}
else {
this.fetchEvents('refreshApi');
}
}

handleBarcodeScanned = (barcode) => {
Expand All @@ -123,6 +141,35 @@ class Scanner extends Component {
let { event, service } = this.state.scanner;
let scan = { eventId: event && event.id, serviceId: service && service.id, cardNumber: barcode, plusOne: this.state.plusOne === undefined ? false : this.state.plusOne };

if (scan.serviceId !== CHECKIN_SERVICE_ID) {
this.setState({
scan: scan,
scanApi: { ...this.state.scanApi, isExecuting: true },
}, () => {
this.props.context.api.get('/v1/scans/' + scan.eventId + '/' + scan.cardNumber + '/checkin')
.then(response => {
if (response.data.plusOne) {
this.setState({
scanApi: { isExecuting: false, isErrored: false },
plusOneDialog: { open: true, intent: 'service' },
});
}
else {
this.sendScan(scan, barcode);
}
}, error => {
this.setState({ scanApi: { isExecuting: false, isErrored: true }}, () => {
this.handleScanResponse(barcode, error.response);
});
});
});
}
else {
this.sendScan(scan, barcode);
}
}

sendScan = (scan, barcode) => {
this.setState({
scan: initialState.scan,
scanDialog: { open: false },
Expand All @@ -143,7 +190,7 @@ class Scanner extends Component {

handleScanClick = () => {
if (this.state.scanner.service && this.state.scanner.service.id === CHECKIN_SERVICE_ID) {
this.setState({ plusOneDialog: { open: true }});
this.setState({ plusOneDialog: { open: true, intent: 'checkin' }});
}
else {
this.scan();
Expand All @@ -168,28 +215,50 @@ class Scanner extends Component {
}

handlePlusOneDialogClose = (result) => {
this.setState({ plusOneDialog: { open: false }, plusOne: result === undefined ? false : result }, () => {
let intent = this.state.plusOneDialog.intent;

this.setState({
plusOneDialog: { open: false, intent: undefined },
plusOne: result === undefined ? false : result,
}, () => {
if (result !== undefined) {
this.scan();
if (intent === 'checkin') {
// for checkin scans, +1 selection comes before card entry
this.scan();
}
else if (intent === 'service') {
// for service scans, +1 selection comes after card entry, and only if the veteran
// checked in with a +1.
this.sendScan({ ...this.state.scan, plusOne: result });
}
}
});
}

handleScanResponse = (cardNumber, response) => {
let scan = { cardNumber: cardNumber, status: response.status, response: response.data };

let history = this.state.history.slice(0);
history.unshift(scan);
let historyScan = JSON.parse(JSON.stringify(scan));
if (historyScan && historyScan.response && historyScan.response.veteran && historyScan.response.veteran.photoBase64) {
delete historyScan.response.veteran.photoBase64;
}

let history = this.state.scanner.history.slice(0);
history.unshift(historyScan);
history = history.slice(0, historyLimit);

this.setState({
scan: scan,
history: history,
scanner: { ...this.state.scanner, history: history },
}, () => {
sessionStorage.setItem('scanner', JSON.stringify(this.state.scanner));
});
}

resetScanner = (resolve) => {
this.setState({ ...initialState }, () => {
sessionStorage.removeItem('scanner');

this.fetchEvents('refreshApi')
.then(() => resolve());
});
Expand All @@ -201,8 +270,10 @@ class Scanner extends Component {

deleteScan = (scan) => {
this.setState({
history: this.state.history.filter(oldScan => oldScan.cardNumber !== scan.cardNumber),
scanner: { ...this.state.scanner, history: this.state.scanner.history.filter(oldScan => oldScan.cardNumber !== scan.cardNumber) },
}, () => {
sessionStorage.setItem('scanner', JSON.stringify(this.state.scanner));

if (this.state.scan.cardNumber === scan.cardNumber) {
this.clearLastScan();
}
Expand Down Expand Up @@ -290,12 +361,16 @@ class Scanner extends Component {
}

handleServiceItemClick = (service) => {
this.setState({ scanner: { ...this.state.scanner, service: service }});
let scanner = { ...this.state.scanner, service: service };

this.setState({ scanner: scanner }, () => {
sessionStorage.setItem('scanner', JSON.stringify(scanner));
});
}

render() {
let classes = this.props.classes;
let { loadApi, refreshApi, scanApi, scanner, scan, events, services, history, historyDialog, scanDialog, plusOneDialog } = this.state;
let { loadApi, refreshApi, scanApi, scanner, scan, events, services, historyDialog, scanDialog, plusOneDialog } = this.state;

let title = this.getTitle(scanner);

Expand Down Expand Up @@ -377,7 +452,7 @@ class Scanner extends Component {
<ScannerHistoryDialog
open={historyDialog.open}
service={scanner && scanner.service ? scanner.service.name : ''}
history={history}
history={scanner.history}
onDelete={this.deleteScan}
onClose={() => this.setState({ historyDialog: { open: false }})}
/>
Expand Down
10 changes: 9 additions & 1 deletion web/src/scans/ScannerHistoryDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,17 @@ class ScannerHistoryDialog extends Component {
let veteranId = scan.response.veteranId;
let serviceId = scan.response.serviceId;

let url = '/v1/scans/' + eventId + '/';

if (serviceId !== undefined) {
url = url + serviceId + '/';
}

url = url + veteranId;

return new Promise((resolve, reject) => {
this.setState({ api: { isExecuting: true}}, () => {
this.props.context.api.delete('/v1/scans/' + eventId + '/' + veteranId + (serviceId !== undefined ? '/' + serviceId : ''))
this.props.context.api.delete(url)
.then(response => {
this.setState({ api: { isExecuting: false, isErrored: false }}, () => {
this.props.onDelete(scan);
Expand Down
7 changes: 5 additions & 2 deletions web/src/scans/ScannerMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ class ScannerMenu extends Component {
<ArrowBack/>
</ListItemIcon>
<ListItemText>
Reset Event or Service
Reset Scanner
</ListItemText>
</MenuItem>
</Menu>
Expand All @@ -133,7 +133,10 @@ class ScannerMenu extends Component {
onConfirm={this.resetScanner}
onClose={this.handleConfirmDialogClose}
>
<p>Are you sure you want reset the Scanner?</p>
<div>
<p>Are you sure you want reset the Scanner?</p>
<p>This will clear the scan history and prompt you to select the Event and Service again.</p>
</div>
</ConfirmDialog>
</div>
);
Expand Down

0 comments on commit 4120bf2

Please sign in to comment.