From a64aa85d3339f44f8d84eb7880c1229f9c1db25b Mon Sep 17 00:00:00 2001 From: dibakor Date: Wed, 8 Jul 2026 18:15:24 +0600 Subject: [PATCH] add attendance endpoint --- .../MobileApp/MobileMasterDataRequest.cs | 26 +++++ .../MobileApp/MobileMasterDataResponse.cs | 17 +++ .../MobileApp/IMobileMasterDataService.cs | 3 + .../MobileApp/MobileMasterDataService.cs | 106 ++++++++++++++++++ .../Services/Systems/UserService.cs | 3 +- .../Mobile/MobileMasterDataController.cs | 54 +++++++++ 6 files changed, 208 insertions(+), 1 deletion(-) diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/MobileApp/MobileMasterDataRequest.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/MobileApp/MobileMasterDataRequest.cs index c26c939..0d2ee22 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/MobileApp/MobileMasterDataRequest.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/MobileApp/MobileMasterDataRequest.cs @@ -1,5 +1,6 @@  using DocumentFormat.OpenXml.Wordprocessing; +using System; using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; @@ -52,4 +53,29 @@ public class GetCustomersRequest : PagedRequest [StringLength(10, MinimumLength = 1, ErrorMessage = "Employee number must be between 1 and 10 characters.")] public string? EmployeeNumber { get; set; } +} + +public class GetAttendanceByEmpRequest +{ + [Required, NotNull, StringLength(maximumLength: 10, MinimumLength = 3, ErrorMessage = "EmployeeNumber must be between 3 and 10 characters.")] + public string EmployeeNumber { get; set; } + public DateTime? AttendanceDate { get; set; } +} + +public class UpsertAttendanceByEmpRequest +{ + [Required] + public DateTime AttendanceDate { get; set; } + [Required, NotNull, StringLength(maximumLength: 10, MinimumLength = 3, ErrorMessage = "EmployeeNumber must be between 3 and 10 characters.")] + public string EmployeeNumber { get; set; } + public DateTime? CheckInTime { get; set; } + public string? CheckInLatitude { get; set; } + public string? CheckInLongitude { get; set; } + public string? CheckInLocationName { get; set; } + public DateTime? CheckOutTime { get; set; } + public string? CheckOutLatitude { get; set; } + public string? CheckOutLongitude { get; set; } + public string? CheckOutLocationName { get; set; } + public bool IsCheckIn => CheckInTime.HasValue ? true : false; + public bool IsCheckOut => CheckOutTime.HasValue ? true : false; } \ No newline at end of file diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Responses/MobileApp/MobileMasterDataResponse.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Responses/MobileApp/MobileMasterDataResponse.cs index 830421a..b2f6578 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Responses/MobileApp/MobileMasterDataResponse.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Responses/MobileApp/MobileMasterDataResponse.cs @@ -131,4 +131,21 @@ public class GetCustomerResponse public string SalesOrgCode { get; set; } public string TeritoryCode { get; set; } public string PlantCode { get; set; } +} + + +public class GetAttendanceByEmpResponse +{ + public DateTime AttendanceDate { get; set; } + public string EmployeeNumber { get; set; } + public DateTime? CheckInTime { get; set; } + public string? CheckInLatitude { get; set; } + public string? CheckInLongitude { get; set; } + public string? CheckInLocationName { get; set; } + public DateTime? CheckOutTime { get; set; } + public string? CheckOutLatitude { get; set; } + public string? CheckOutLongitude { get; set; } + public string? CheckOutLocationName { get; set; } + public bool IsCheckIn => CheckInTime.HasValue ? true : false; + public bool IsCheckOut => CheckOutTime.HasValue ? true : false; } \ No newline at end of file diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Contracts/MobileApp/IMobileMasterDataService.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Contracts/MobileApp/IMobileMasterDataService.cs index 29cf963..aed700e 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Contracts/MobileApp/IMobileMasterDataService.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Contracts/MobileApp/IMobileMasterDataService.cs @@ -11,4 +11,7 @@ public interface IMobileMasterDataService Task> GetBrandsAsync(GetBrandsRequest request); Task> GetMaterialsAsync(GetMaterialsRequest request); Task> GetCustomersAsync(GetCustomersRequest request); + Task GetAttendanceByEmpAsync(GetAttendanceByEmpRequest request); + Task UpsertAttendanceByEmpAsync(UpsertAttendanceByEmpRequest request); + } diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/MobileApp/MobileMasterDataService.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/MobileApp/MobileMasterDataService.cs index a5a4e9f..f00a4b6 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/MobileApp/MobileMasterDataService.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/MobileApp/MobileMasterDataService.cs @@ -114,6 +114,9 @@ public class MobileMasterDataService : IMobileMasterDataService return response; } + + + public async Task> GetMaterialsAsync(GetMaterialsRequest request) { PagedResult response = new(); @@ -485,4 +488,107 @@ public class MobileMasterDataService : IMobileMasterDataService return response; } + + public async Task GetAttendanceByEmpAsync(GetAttendanceByEmpRequest request) + { + GetAttendanceByEmpResponse response = null; + + try + { + using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode); + try + { + SqlParameter[] p = + [ + SqlHelperExtension.CreateInParam(pName: "@EmployeeNumber", pType: SqlDbType.VarChar, pValue: request.EmployeeNumber), + SqlHelperExtension.CreateInParam(pName: "@AttendanceDate", pType: SqlDbType.Date, pValue: request.AttendanceDate) + ]; + + using (IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetAttendanceByEmployee", parameterValues: p)) + { + if (dr.Read()) + { + response = MapToResponse(dr); + } + dr.Close(); + } + tc.End(); + } + catch (Exception ie) + { + tc?.HandleError(); + throw DBCustomError.GenerateCustomError(ie); + } + } + catch (Exception ex) + { + throw; + } + + return response; + } + + public async Task UpsertAttendanceByEmpAsync(UpsertAttendanceByEmpRequest request) + { + GetAttendanceByEmpResponse response = new(); + + try + { + using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode); + try + { + SqlParameter[] p = + [ + SqlHelperExtension.CreateInParam(pName: "@AttendanceDate", pType: SqlDbType.Date, pValue: request.AttendanceDate), + SqlHelperExtension.CreateInParam(pName: "@EmployeeNumber", pType: SqlDbType.VarChar, pValue: request.EmployeeNumber), + SqlHelperExtension.CreateInParam(pName: "@CheckInTime", pType: SqlDbType.DateTime, pValue: request.CheckInTime), + SqlHelperExtension.CreateInParam(pName: "@CheckInLatitude", pType: SqlDbType.VarChar, pValue: request.CheckInLatitude), + SqlHelperExtension.CreateInParam(pName: "@CheckInLongitude", pType: SqlDbType.VarChar, pValue: request.CheckInLongitude), + SqlHelperExtension.CreateInParam(pName: "@CheckInLocationName", pType: SqlDbType.VarChar, pValue: request.CheckInLocationName), + SqlHelperExtension.CreateInParam(pName: "@CheckOutTime", pType: SqlDbType.DateTime, pValue: request.CheckOutTime), + SqlHelperExtension.CreateInParam(pName: "@CheckOutLatitude", pType: SqlDbType.VarChar, pValue: request.CheckOutLatitude), + SqlHelperExtension.CreateInParam(pName: "@CheckOutLongitude", pType: SqlDbType.VarChar, pValue: request.CheckOutLongitude), + SqlHelperExtension.CreateInParam(pName: "@CheckOutLocationName", pType: SqlDbType.VarChar, pValue: request.CheckOutLocationName) + ]; + + using (IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.UpsertAttendanceByEmployee", parameterValues: p)) + { + if (dr.Read()) + { + response = MapToResponse(dr); + } + dr.Close(); + } + tc.End(); + } + catch (Exception ie) + { + tc?.HandleError(); + throw DBCustomError.GenerateCustomError(ie); + } + } + catch (Exception ex) + { + throw; + } + + return response; + } + + private static GetAttendanceByEmpResponse MapToResponse(IDataReader dr) + { + return new GetAttendanceByEmpResponse + { + AttendanceDate = dr.GetDateTime(0), + EmployeeNumber = dr.GetString(1), + CheckInTime = dr.IsDBNull(2) ? null : dr.GetDateTime(2), + CheckInLatitude = dr.IsDBNull(3) ? null : dr.GetString(3), + CheckInLongitude = dr.IsDBNull(4) ? null : dr.GetString(4), + CheckInLocationName = dr.IsDBNull(5) ? null : dr.GetString(5), + CheckOutTime = dr.IsDBNull(6) ? null : dr.GetDateTime(6), + CheckOutLatitude = dr.IsDBNull(7) ? null : dr.GetString(7), + CheckOutLongitude = dr.IsDBNull(8) ? null : dr.GetString(8), + CheckOutLocationName = dr.IsDBNull(9) ? null : dr.GetString(9) + }; + } } diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Systems/UserService.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Systems/UserService.cs index 6271428..74350ae 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Systems/UserService.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Systems/UserService.cs @@ -675,7 +675,8 @@ namespace OnlineSalesAutoCrop.CoreAPI.Services.Services.Systems SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: user.UserId), SqlHelperExtension.CreateInParam(pName: "@LoginId", pType: SqlDbType.VarChar, pValue: user.LoginId, size: 30), SqlHelperExtension.CreateInParam(pName: "@IpAddress", pType: SqlDbType.VarChar, pValue: ipAddress, size: 20), - SqlHelperExtension.CreateInParam(pName: "@LoginTime", pType: SqlDbType.DateTime, pValue: DateTime.Now) + SqlHelperExtension.CreateInParam(pName: "@LoginTime", pType: SqlDbType.DateTime, pValue: DateTime.Now), + SqlHelperExtension.CreateInParam(pName: "@LogoutTime", pType: SqlDbType.DateTime, pValue: null) ]; _ = tc.ExecuteNonQuerySp(spName: "dbo.SaveAccessLog", parameterValues: p); diff --git a/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/Mobile/MobileMasterDataController.cs b/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/Mobile/MobileMasterDataController.cs index b18e250..41998a8 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/Mobile/MobileMasterDataController.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/Mobile/MobileMasterDataController.cs @@ -143,5 +143,59 @@ namespace OnlineSalesAutoCrop.CoreAPI.Controllers return StatusCode(StatusCodes.Status500InternalServerError, MobileResponseBase.Failure(ex.InnerException != null ? ex.InnerException.Message : ex.Message, StatusCodes.Status500InternalServerError)); } } + + + /// + /// Login using your credential data retrieve from SqlServer + /// + /// + /// + /// + /// If login successful Return Attendance List + [HttpGet("Attendance")] + [IgnoreAntiforgeryToken] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(GetAttendanceByEmpResponse))] + public async Task GetAttendance([FromQuery] GetAttendanceByEmpRequest request) + { + GetAttendanceByEmpResponse response = new(); + try + { + response = await _service.GetAttendanceByEmpAsync(request); + return Ok(MobileResponseBase.Success(response)); + } + catch (Exception ex) + { + string msg = $"Exception occur on Get Attendance endpoint with employee request - {request?.EmployeeNumber} -{request?.AttendanceDate}"; + _logger.LogError(exception: ex, message: msg); + return StatusCode(StatusCodes.Status500InternalServerError, MobileResponseBase.Failure(ex.InnerException != null ? ex.InnerException.Message : ex.Message, StatusCodes.Status500InternalServerError)); + } + } + + + /// + /// Login using your credential data retrieve from SqlServer + /// + /// + /// + /// + /// If login successful Return Attendance List + [HttpPost("Attendance")] + [IgnoreAntiforgeryToken] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(GetAttendanceByEmpResponse))] + public async Task UpsertAttendance([FromBody] UpsertAttendanceByEmpRequest request) + { + GetAttendanceByEmpResponse response = new(); + try + { + response = await _service.UpsertAttendanceByEmpAsync(request); + return Ok(MobileResponseBase.Success(response)); + } + catch (Exception ex) + { + string msg = $"Exception occur on Save Attendance endpoint with employee request - {request?.EmployeeNumber} -{request?.AttendanceDate}"; + _logger.LogError(exception: ex, message: msg); + return StatusCode(StatusCodes.Status500InternalServerError, MobileResponseBase.Failure(ex.InnerException != null ? ex.InnerException.Message : ex.Message, StatusCodes.Status500InternalServerError)); + } + } } } \ No newline at end of file