migration to new Repo

This commit is contained in:
rowell_m_soriano 2026-07-30 10:31:04 +08:00
commit 405c7b3a6d
318 changed files with 26295 additions and 0 deletions

363
.gitignore vendored Normal file
View File

@ -0,0 +1,363 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd

View File

@ -0,0 +1,11 @@
using LSFE.Infrastructure.Dto.Account;
namespace LSFE.Domain.Contracts
{
public interface INavigationService
{
Task<NavigationResponseDto> GetUserNavigationAsync(string userId);
Task<List<NavigationItemDto>> GetNavigationByRoleIdsAsync(List<string> roleIds);
Task<List<NavigationItemDto>> GetNavigationByRoleAsync(string roleId);
}
}

View File

@ -0,0 +1,24 @@
using LSFE.Infrastructure.Dto.Doctor;
using LSFE.Infrastructure.Entities.Doctor;
using LSFE.Infrastructure.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Domain.Contracts.Doctor
{
public interface IDoctorRepo
{
Task<Response> SoftDeleteAsync(int doctorId);
Task<List<DoctorDto>> GetDoctor(DoctorDto dto);
Task<List<DoctorDto>> GetMyDoctor(DoctorDto dto);
Task<List<Infrastructure.Entities.Doctor.Doctor>> GetAllDoctor(DoctorDto dto);
Task<List<DoctorCoverageToday>> GetDoctorCoverageToday(DoctorDto dto);
Task<Response> PostPutDoctor(DoctorDto dto);
Task<Response> ApproveDoctor(int doctorId, byte newStatus,string userName);
Task<List<ForDoctorApprovalDto>> GetPendingApprovals(DoctorDto doctor);
}
}

View File

@ -0,0 +1,28 @@
using LSFE.Infrastructure.Dto;
using LSFE.Infrastructure.Entities;
using LSFE.Infrastructure.Model;
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Domain.Contracts
{
public interface IAccountService
{
Task<List<RoleDto>> GetUserRole();
Task<List<UserWithRoleDto>> GetUsers(string company);
Task<List<Departments>> GetDepartment();
Task<AppsUsers?> GetUserById(string userId);
Task<string> CreateToken(AppsUsers user);
Task<Response> PostPutDepartmentAsync(Departments dto);
Task<Response> RegisterUserAsync(RegisterUserDto dto);
Task<Response> UpdateUserAsync(UpdateUserDto dto);
Task<Response> AddUserToRoleAsync(string userId, string roleName);
Task<Response> AddClaimToUserAsync(string userId, Claim claim);
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Domain.Contracts
{
public interface IGenericRepository<T> where T : class
{
Task<T?> GetByIdAsync(int id);
Task<IEnumerable<T>> GetAllAsync();
Task AddAsync(T entity);
void Update(T entity);
void Delete(T entity);
}
}

View File

@ -0,0 +1,46 @@
using LSFE.Domain.Contracts.Doctor;
using LSFE.Domain.Contracts.Maintenance;
using LSFE.Domain.Contracts.Planning;
using LSFE.Domain.Contracts.Reports;
using LSFE.Domain.Contracts.TimeKeeping;
using LSFE.Domain.Services;
using LSFE.Domain.Services.TimeKeeping;
using LSFE.Infrastructure.Entities.Admin;
using LSFE.Infrastructure.Entities.Doctor;
using LSFE.Infrastructure.Entities.Maintenance;
using LSFE.Infrastructure.Entities.Planning;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Domain.Contracts
{
public interface IUnitOfWork : IDisposable
{
IErrorLogger ErrorLogger { get; }
IDoctorRepo Doctors { get; }
IAttendanceRepo Attendances { get; }
IPlanningRepo Planning { get; }
IReportsRepo Reports { get; }
IMaintenanceRepo<Areas> Areas { get; }
IMaintenanceRepo<Districts> Districts { get; }
IMaintenanceRepo<Divisions> Divisions { get; }
IMaintenanceRepo<Territories> Territories { get; }
IMaintenanceRepo<InstitutionCategory> InstitutionCategories { get; }
IMaintenanceRepo<Institutions> Institutions { get; }
IMaintenanceRepo<Products> Products { get; }
IMaintenanceRepo<Specialization> Specializations { get; }
IMaintenanceRepo<Inventory> Inventory { get; }
IMaintenanceRepo<InventoryTransaction> InventoryTransactions { get; }
IMaintenanceRepo<ProductTransaction> ProductTransactions { get; }
IMaintenanceRepo<MRRawPlan> MRRawPlans { get; }
IMaintenanceRepo<CatchUpCall> CatchUpCalls { get; }
IMaintenanceRepo<MRPlanDetail> MRPlanDetails { get; }
IMaintenanceRepo<MRPlanHeader> MRPlanHeaders { get; }
IMaintenanceRepo<UserDevice> UserDevices { get; }
Task<int> SaveChangesAsync();
}
}

View File

@ -0,0 +1,25 @@
using LSFE.Infrastructure.Model;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Domain.Contracts.Maintenance
{
public interface IMaintenanceRepo<TEntity> where TEntity : class
{
Task<List<TEntity>> GetAllAsync(params Expression<Func<TEntity, object>>[] includeProperties);
Task<TEntity?> GetByIdAsync(object id);
Task<TEntity?> FindAsync(Expression<Func<TEntity, bool>> predicate);
Task<bool> AnyAsync(Expression<Func<TEntity, bool>> predicate);
IQueryable<TEntity> Query();
Task<IEnumerable<TEntity>> FindAllAsync(Expression<Func<TEntity, bool>> predicate);
Task AddRangeAsync(IEnumerable<TEntity> entities);
Task<Response> AddOrUpdateAsync(TEntity entity);
Task<Response> SoftDeleteAsync(int id);
Task<Response> DeleteAsync(Guid id);
}
}

View File

@ -0,0 +1,33 @@
using LSFE.Infrastructure.Dto;
using LSFE.Infrastructure.Dto.Doctor;
using LSFE.Infrastructure.Dto.Planning;
using LSFE.Infrastructure.Entities.Maintenance;
using LSFE.Infrastructure.Entities.Planning;
using LSFE.Infrastructure.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Domain.Contracts.Planning
{
public interface IPlanningRepo
{
Task<Response> SoftDeleteAsync(int doctorId);
Task<List<MRRawPlan>> GetPlan(PlanningDto dto);
Task<List<DoctorById>> GetDoctorById(DoctorDto dto);
Task<List<DoctorWithPlan>> GetDoctorWithPlan(DoctorDto dto);
Task<List<PendingApproval>> GetPendingApprovals(PlanningDto dto);
Task<List<MRRawPlan>> GetAllMissReschedule(CredentialDto dto);
Task<List<MRRawPlan>> GetAllAppointmentToday(CredentialDto dto);
Task<List<MRRawPlansDto>> GetMRRawPlans(PlanByUserIdRequest dto);
Task<List<InventoryTransaction>> GetSampleTransaction(Guid mrRawPlanId);
Task<List<ProductTransaction>> GetProductTransaction(Guid mrRawPlanId);
Task<Response> PostPutPlan(PlanningDto dto);
Task<Response> CopyMonthlyMRRawPlans(PlanningDto dto);
Task<Response> ApprovePlan(ApprovePlanDto dto);
}
}

View File

@ -0,0 +1,19 @@
using LSFE.Infrastructure.Dto.Reports;
using LSFE.Infrastructure.Entities.Reports;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Domain.Contracts.Reports
{
public interface IReportsRepo
{
Task<List<CallRateReport>> GetCallRateReport(ReportsDto dto);
Task<List<CallReachReport>> GetCallReachReport(ReportsDto dto);
Task<List<CatchUpCallReport>> GetCatchUpCallReport(ReportsDto dto);
Task<List<ActualCoverageReport>> GetActualCoverageReport(ReportsDto dto);
Task<bool> PutMissedCall(ReportsDto dto);
}
}

View File

@ -0,0 +1,21 @@
using DocumentFormat.OpenXml.Office2010.ExcelAc;
using LSFE.Infrastructure.Dto;
using LSFE.Infrastructure.Dto.TimeKeeping;
using LSFE.Infrastructure.Entities.TimeKeeping;
using LSFE.Infrastructure.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Domain.Contracts.TimeKeeping
{
public interface IAttendanceRepo
{
Task<List<Infrastructure.Entities.TimeKeeping.AttendanceDto>>
GetAttendance(Infrastructure.Dto.TimeKeeping.AttendanceDto dto);
Task<List<Attendance>> GetAttendanceToday(CredentialDto dto);
Task<Response> PostPutAttendance(Infrastructure.Dto.TimeKeeping.AttendanceDto dto);
}
}

View File

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Domain.Helpers
{
public static class ContentTypeHelper
{
public static string GetContentType(string fileName)
{
var extension = Path.GetExtension(fileName).ToLowerInvariant();
return extension switch
{
".pdf" => "application/pdf",
".doc" => "application/msword",
".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
".pptx" => "application/vnd.openxmlformats-officedocument.presentationml.presentation",
".xls" => "application/vnd.ms-excel",
".xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
".png" => "image/png",
".jpg" or ".jpeg" => "image/jpeg",
".gif" => "image/gif",
".txt" => "text/plain",
_ => "application/octet-stream"
};
}
public static string ValidateFile(string filePath, string uploadsPath)
{
var message = "";
// Security: Prevent directory traversal attacks
var fullPath = Path.GetFullPath(filePath);
if (!fullPath.StartsWith(Path.GetFullPath(uploadsPath)))
{
message="Invalid file path";
}
if (!System.IO.File.Exists(filePath))
{
message="File not found";
}
return message;
}
}
}

View File

@ -0,0 +1,40 @@
using ClosedXML.Excel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Domain.Helpers
{
public class ExcelTemplateValidator
{
private static readonly string[] REQUIRED_HEADERS = new[]
{
"BRAND", "January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December",
"Year", "Medrep_Name"
};
public static (bool isValid, List<string> errors) ValidateHeaders(IXLWorksheet worksheet)
{
var errors = new List<string>();
var isValid = true;
for (int col = 1; col <= REQUIRED_HEADERS.Length; col++)
{
var headerValue = worksheet.Cell(1, col).Value.ToString()?.Trim();
var expectedHeader = REQUIRED_HEADERS[col - 1];
if (string.IsNullOrEmpty(headerValue) ||
!headerValue.Equals(expectedHeader, StringComparison.OrdinalIgnoreCase))
{
errors.Add($"Column {col}: Expected '{expectedHeader}' but found '{headerValue}'");
isValid = false;
}
}
return (isValid, errors);
}
}
}

View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ClosedXML" Version="0.105.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.20" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.20" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LSFE.Infrastructure\LSFE.Infrastructure.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,170 @@
using LSFE.Domain.Contracts;
using LSFE.Infrastructure.Database;
using LSFE.Infrastructure.Dto.Account;
using LSFE.Infrastructure.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using System.Data;
namespace LSFE.Domain.Services.Account
{
public class NavigationService : INavigationService
{
private readonly LSFEDbContext _context;
private readonly UserManager<AppsUsers> _userManager;
public NavigationService(LSFEDbContext context, UserManager<AppsUsers> userManager)
{
_context = context;
_userManager = userManager;
}
public async Task<NavigationResponseDto> GetUserNavigationAsync(string userId)
{
try
{
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
return new NavigationResponseDto
{
Success = false,
Message = "User not found or inactive",
Data = new List<NavigationItemDto>()
};
}
// Get user roles using UserManager
var roles = await _userManager.GetRolesAsync(user);
if (!roles.Any())
{
return new NavigationResponseDto
{
Success = false,
Message = "User has no assigned roles",
Data = new List<NavigationItemDto>()
};
}
// Get role IDs from role names
var roleIds = await _context.Roles
.Where(r => roles.Contains(r.Name))
.Select(r => r.Id)
.ToListAsync();
// Get navigation for all user roles
var navigationItems = await GetNavigationByRoleIdsAsync(roleIds);
return new NavigationResponseDto
{
Success = true,
Data = navigationItems,
Message = "Navigation retrieved successfully"
};
}
catch (Exception ex)
{
return new NavigationResponseDto
{
Success = false,
Message = $"Error retrieving navigation: {ex.Message}",
Data = new List<NavigationItemDto>()
};
}
}
public async Task<List<NavigationItemDto>> GetNavigationByRoleIdsAsync(List<string> roleIds)
{
try
{
var roleIdsParam = string.Join(",", roleIds);
using (var command = _context.Database.GetDbConnection().CreateCommand())
{
command.CommandText = "GetUserNavigation";
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add(new SqlParameter("@RoleIds", roleIdsParam));
await _context.Database.OpenConnectionAsync();
using (var result = await command.ExecuteReaderAsync())
{
var rootModules = new List<NavigationItemDto>();
var childModules = new List<NavigationItemDto>();
// Read root modules
while (await result.ReadAsync())
{
rootModules.Add(new NavigationItemDto
{
Id = result["Id"].ToString(),
Label = result["Label"].ToString(),
Icon = result["Icon"].ToString(),
Route = result["Route"].ToString(),
Badge = result["Badge"]?.ToString(),
Permissions = new PermissionsDto
{
CanView = Convert.ToBoolean(result["CanView"]),
CanCreate = Convert.ToBoolean(result["CanCreate"]),
CanEdit = Convert.ToBoolean(result["CanEdit"]),
CanDelete = Convert.ToBoolean(result["CanDelete"])
},
Children = new List<NavigationItemDto>()
});
}
// Read child modules
if (await result.NextResultAsync())
{
while (await result.ReadAsync())
{
childModules.Add(new NavigationItemDto
{
Id = result["Id"].ToString(),
Label = result["Label"].ToString(),
Icon = result["Icon"].ToString(),
Route = result["Route"].ToString(),
ParentModuleId = result["ParentModuleId"] != DBNull.Value
? Convert.ToInt32(result["ParentModuleId"])
: (int?)null,
Permissions = new PermissionsDto
{
CanView = Convert.ToBoolean(result["CanView"]),
CanCreate = Convert.ToBoolean(result["CanCreate"]),
CanEdit = Convert.ToBoolean(result["CanEdit"]),
CanDelete = Convert.ToBoolean(result["CanDelete"])
}
});
}
}
// Attach children to parents
foreach (var parent in rootModules)
{
var children = childModules
.Where(c => c.ParentModuleId.ToString() == parent.Id)
.ToList();
parent.Children = children.Any() ? children : null;
}
return rootModules;
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error in GetNavigationByRoleIdsAsync: {ex.Message}");
throw;
}
}
// Optional: Add method to get single role navigation (for backwards compatibility)
public async Task<List<NavigationItemDto>> GetNavigationByRoleAsync(string roleId)
{
return await GetNavigationByRoleIdsAsync(new List<string> { roleId });
}
}
}

View File

@ -0,0 +1,459 @@
using LSFE.Domain.Contracts;
using LSFE.Infrastructure.Database;
using LSFE.Infrastructure.Dto;
using LSFE.Infrastructure.Entities;
using LSFE.Infrastructure.Entities.Auth;
using LSFE.Infrastructure.Model;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Domain.Services
{
public class AccountService : IAccountService
{
private readonly UserManager<AppsUsers> _userManager;
private readonly IConfiguration _configuration;
private readonly RoleManager<Role> _roleManager;
private readonly LSFEDbContext _context;
public AccountService(UserManager<AppsUsers> userManager, IConfiguration configuration,
RoleManager<Role> roleManager , LSFEDbContext context )
{
_userManager = userManager;
_configuration = configuration;
_roleManager = roleManager;
_context = context;
}
public async Task<Response> AddUserToRoleAsync(string userId, string roleName)
{
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
return new Response
{
Success = false,
Message = "User not found",
MessCode = 0
};
// Ensure role exists
if (!await _roleManager.RoleExistsAsync(roleName))
{
var roleResult = await _roleManager.CreateAsync(new Role());
if (!roleResult.Succeeded)
return new Response
{
Success = false,
Message = roleResult.ToString(),
MessCode = 0
};
}
await _userManager.AddToRoleAsync(user, roleName);
return new Response
{
Success = true,
Message = "Add user role successfully!",
MessCode = 1
};
}
public async Task<Response> AddClaimToUserAsync(string userId, Claim claim)
{
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
return new Response
{
Success = false,
Message = "User not found",
MessCode = 0
};
await _userManager.AddClaimAsync(user, claim);
return new Response
{
Success = true,
Message = "Success",
MessCode = 1
};
}
public async Task<Response> RegisterUserAsync(RegisterUserDto dto)
{
try
{
// 🔍 1. Validate required fields
if (string.IsNullOrWhiteSpace(dto.UserName) ||
string.IsNullOrWhiteSpace(dto.Email) ||
string.IsNullOrWhiteSpace(dto.Password))
{
return new Response
{
Success = false,
Message = "Username, Email, and Password are required.",
MessCode = 0
};
}
// 🧩 2. Check if username or email already exists
var existingUserByName = await _userManager.FindByNameAsync(dto.UserName);
if (existingUserByName != null)
{
return new Response
{
Success = false,
Message = $"Username '{dto.UserName}' is already taken.",
MessCode = 0
};
}
var existingUserByEmail = await _userManager.FindByEmailAsync(dto.Email);
if (existingUserByEmail != null)
{
return new Response
{
Success = false,
Message = $"Email '{dto.Email}' is already registered.",
MessCode = 0
};
}
// 🏢 3. Optional: Validate Department
if (dto.DepartmentId.HasValue)
{
var departmentExists = await _context.Departments
.AnyAsync(d => d.DepartmentId == dto.DepartmentId && d.IsActive);
if (!departmentExists)
{
return new Response
{
Success = false,
Message = "Invalid Department selected.",
MessCode = 0
};
}
}
// 🧍‍♂️ 4. Create new user
var user = new AppsUsers
{
UserName = dto.UserName,
Email = dto.Email,
FullName = dto.FullName,
EmployeeId = dto.EmployeeId,
Company = dto.Company,
DepartmentId = dto.DepartmentId,
Address = dto.Address,
PhoneNumber = dto.PhoneNumber,
CreatedDate = DateTime.UtcNow,
UpdatedDate = DateTime.UtcNow,
CreatedBy = dto.UpdatedBy,
UpdatedBy = dto.UpdatedBy,
EmailConfirmed = true // optional: mark as verified by admin
};
var createResult = await _userManager.CreateAsync(user, dto.Password);
if (!createResult.Succeeded)
{
var errors = string.Join("; ", createResult.Errors.Select(e => e.Description));
return new Response
{
Success = false,
Message = $"Failed to create user: {errors}",
MessCode = 0
};
}
// 🛡️ 5. Assign Role if provided
if (!string.IsNullOrWhiteSpace(dto.RoleId))
{
var role = await _roleManager.FindByIdAsync(dto.RoleId);
if (role == null)
{
return new Response
{
Success = false,
Message = $"Role with ID '{dto.RoleId}' does not exist.",
MessCode = 0
};
}
await _userManager.AddToRoleAsync(user, role.Name);
// optional: add user claim for role
await _userManager.AddClaimAsync(user, new Claim("RoleName", role.Name));
}
return new Response
{
Success = true,
Message = "User registered successfully!",
MessCode = 1
};
}
catch (Exception ex)
{
return new Response
{
Success = false,
Message = $"Unexpected error: {ex.Message}",
MessCode = 0
};
}
}
public async Task<string> CreateToken(AppsUsers user)
{
var authClaims = await BuildClaims(user);
var authSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(_configuration["JWT:Secret"]));
var token = new JwtSecurityToken(
issuer: _configuration["JWT:ValidIssuer"],
audience: _configuration["JWT:ValidAudience"],
expires: DateTime.UtcNow.AddMinutes(30),
claims: authClaims,
signingCredentials: new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256)
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
private async Task<List<Claim>> BuildClaims(AppsUsers user)
{
var roles = await _userManager.GetRolesAsync(user);
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.NameIdentifier, user.Id),
new Claim("FullName", user.FullName ?? ""),
new Claim("Company", user.Company ?? ""),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
claims.AddRange(roles.Select(r => new Claim(ClaimTypes.Role, r)));
return claims;
}
public async Task<AppsUsers?> GetUserById(string userId)
{
return await _userManager.Users.FirstOrDefaultAsync(u => u.Id == userId);
}
public async Task<Response> UpdateUserAsync(UpdateUserDto dto)
{
using var transaction = await _context.Database.BeginTransactionAsync();
try
{
var user = await _userManager.FindByIdAsync(dto.Id.ToString());
if (user == null)
return new Response { Success = false, Message = "User not found.", MessCode = 0 };
// --- Update basic info ---
user.Email = dto.Email;
user.FullName = dto.FullName;
user.EmployeeId = dto.EmployeeId;
user.Company = dto.Company;
user.DepartmentId = dto.DepartmentId;
user.Address = dto.Address;
user.PhoneNumber = dto.PhoneNumber;
user.UpdatedBy = dto.UpdatedBy;
user.UpdatedDate = DateTime.UtcNow;
// --- Update password (if provided) ---
if (!string.IsNullOrWhiteSpace(dto.Password))
{
var passwordHasher = new PasswordHasher<AppsUsers>();
user.PasswordHash = passwordHasher.HashPassword(user, dto.Password);
}
var updateResult = await _userManager.UpdateAsync(user);
if (!updateResult.Succeeded)
return new Response
{
Success = false,
Message = string.Join(", ", updateResult.Errors.Select(e => e.Description)),
MessCode = 0
};
// --- Update roles ---
if (!string.IsNullOrEmpty(dto.RoleName))
{
var currentRoles = await _userManager.GetRolesAsync(user);
if (currentRoles.Any())
await _userManager.RemoveFromRolesAsync(user, currentRoles);
if (await _roleManager.RoleExistsAsync(dto.RoleName))
await _userManager.AddToRoleAsync(user, dto.RoleName);
else
return new Response
{
Success = false,
Message = $"Role '{dto.RoleName}' not found.",
MessCode = 0
};
}
// --- Update claims ---
if (!string.IsNullOrEmpty(dto.RoleName))
{
var existingClaims = await _userManager.GetClaimsAsync(user);
var existingRoleClaim = existingClaims.FirstOrDefault(c => c.Type == "Role");
if (existingRoleClaim != null)
await _userManager.RemoveClaimAsync(user, existingRoleClaim);
await _userManager.AddClaimAsync(user, new Claim("Role", dto.RoleName));
}
await transaction.CommitAsync();
return new Response
{
Success = true,
Message = "User updated successfully!",
MessCode = 1
};
}
catch (Exception ex)
{
await transaction.RollbackAsync();
return new Response
{
Success = false,
Message = $"Updating user failed: {ex.Message}",
MessCode = 0
};
}
}
public async Task<List<UserWithRoleDto>> GetUsers(string company)
{
try
{
var users = await _userManager.Users
.Where(u=>u.Company== company)
.Include(u => u.Department)
.ToListAsync();
var roles = await _roleManager.Roles.ToListAsync(); // cache all roles once
var result = new List<UserWithRoleDto>();
foreach (var user in users)
{
var roleNames = await _userManager.GetRolesAsync(user);
// Map role names to RoleId + RoleName
var userRoles = roles
.Where(r => roleNames.Contains(r.Name))
.Select(r => new RoleDto
{
RoleId = r.Id,
RoleName = r.Name!
})
.ToList();
result.Add(new UserWithRoleDto
{
Id = user.Id,
EmployeeId = user.EmployeeId,
Company = user.Company,
Address = user.Address,
UpdatedDate = user.UpdatedDate,
UpdatedBy = user.UpdatedBy,
Email = user.Email,
FullName = user.FullName,
RoleName = userRoles[0].RoleName,
CreatedBy = user.CreatedBy,
CreatedDate = user.CreatedDate,
UserName = user.UserName,
DepartmentId = user.DepartmentId,
RoleId = userRoles[0].RoleId,
Department = user.Department,
AccessFailedCount = user.AccessFailedCount,
PhoneNumber = user.PhoneNumber,
LockoutEnd = user.LockoutEnd,
LockoutEnabled = user.LockoutEnabled,
UserRoles = userRoles
});
}
return result;
}
catch (Exception ex)
{
ex.ToString();
throw;
}
}
public async Task<Response> PostPutDepartmentAsync(Departments dto)
{
var existing = await _context.Departments
.FirstOrDefaultAsync(d => d.DepartmentId == dto.DepartmentId);
if (existing == null)
{
var duplicate = await _context.Departments
.AnyAsync(d => d.DepartmentName == dto.DepartmentName
|| d.DepartmentCode == dto.DepartmentCode);
if (duplicate)
{
return new Response
{
Success = false,
Message = "Department name or code already exists.",
MessCode = 0
};
}
_context.Departments.Add(dto);
await _context.SaveChangesAsync();
return new Response
{
Success = true,
Message = "Department created successfully.",
MessCode = 1
};
}
else
{
existing.DepartmentName = dto.DepartmentName;
existing.DepartmentCode = dto.DepartmentCode;
existing.IsActive = dto.IsActive;
_context.Departments.Update(existing);
await _context.SaveChangesAsync();
return new Response
{
Success = true,
Message = "Department updated successfully.",
MessCode = 1
};
}
}
public async Task<List<Departments>> GetDepartment()
{
return await _context.Departments.ToListAsync();
}
public async Task<List<RoleDto>> GetUserRole()
{
return await _roleManager.Roles
.Select(r => new RoleDto
{
RoleId = r.Id,
RoleName = r.Name
})
.ToListAsync();
}
}
}

View File

@ -0,0 +1,489 @@
using LSFE.Domain.Contracts.Doctor;
using LSFE.Infrastructure.Database;
using LSFE.Infrastructure.Dto.Doctor;
using LSFE.Infrastructure.Entities.Doctor;
using LSFE.Infrastructure.Model;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Domain.Services.Doctor
{
public class DoctorRepo : GenericRepository<Infrastructure.Entities.Doctor.Doctor>, IDoctorRepo
{
private readonly LSFEDbContext _context;
public DoctorRepo(LSFEDbContext context) : base(context)
{
_context = context;
}
public async Task<Response> SoftDeleteAsync(int doctorId)
{
var response = new Response();
try
{
var existingDoctor = await GetByIdAsync(doctorId);
if (existingDoctor == null)
{
response.Success = false;
response.Message = $"Doctor with ID {doctorId} not found.";
response.MessCode = 0;
return response;
}
existingDoctor.IsActive = false;
Update(existingDoctor);
await _context.SaveChangesAsync();
response.Success = true;
response.Data = existingDoctor;
response.Message = "Doctor deactivated successfully.";
response.MessCode = 3; // code for delete/soft delete
}
catch (Exception ex)
{
response.Success = false;
response.Message = ex.Message;
response.MessCode = 0;
}
return response;
}
public async Task<List<Infrastructure.Entities.Doctor.Doctor>> GetAllDoctor(DoctorDto dto)
{
var allItems = await _context.Doctors
.FromSqlRaw("EXEC GetAllDoctor @UserId",
new SqlParameter("@UserId", dto.UserId))
.ToListAsync();
return allItems ?? new List<Infrastructure.Entities.Doctor.Doctor>();
}
public async Task<List<DoctorDto>> GetDoctor(DoctorDto dto)
{
var query =
from d in _context.Doctors
join i in _context.Institutions
on d.InstitutionId equals i.InstitutionId into di
from i in di.DefaultIfEmpty()
join s in _context.Specializations
on d.SpecializationId equals s.SpecializationId into si
from s in si.DefaultIfEmpty()
join dis in _context.Districts
on d.DistrictId equals dis.DistrictId into dist
from dis in dist.DefaultIfEmpty()
join fda in _context.ForDoctorApprovals
on d.DoctorId equals fda.DoctorId
join u in _context.Users
on d.UserId equals u.Id
where d.UserId == dto.UserId
&& (dto.DoctorId == 0 || d.DoctorId == dto.DoctorId)
select new DoctorDto
{
DoctorId = d.DoctorId,
FirstName = d.FirstName,
MiddleInitial = d.MiddleInitial,
LastName = d.LastName,
EmailAddress = d.EmailAddress,
PhoneNo = d.PhoneNo,
SpecializationId = d.SpecializationId,
LicenseNo = d.LicenseNo,
Address = d.Address,
BirthDate = d.BirthDate,
MaxVisit = d.MaxVisit,
MedRepName=u.UserName,
Status = fda.Status,
IsTerritorial= d.IsTerritorial,
InstitutionId = fda.InstitutionId,
InstitutionName = i != null ? i.InstitutionName : null,
SpecializationName = s != null ? s.SpecializationName : null,
DistrictId = fda != null ? dis.DistrictId : 0,
DistrictName = dis != null ? dis.DistrictName : null
};
return await query.ToListAsync();
}
public async Task<List<DoctorDto>> GetMyDoctor(DoctorDto dto)
{
var query =
from d in _context.Doctors
join i in _context.Institutions
on d.InstitutionId equals i.InstitutionId into di
from i in di.DefaultIfEmpty()
join s in _context.Specializations
on d.SpecializationId equals s.SpecializationId into si
from s in si.DefaultIfEmpty()
join dis in _context.Districts
on d.DistrictId equals dis.DistrictId into dist
from dis in dist.DefaultIfEmpty()
join u in _context.Users
on d.UserId equals u.Id
where d.UserId == dto.UserId
select new DoctorDto
{
DoctorId = d.DoctorId,
FirstName = d.FirstName,
MiddleInitial = d.MiddleInitial,
LastName = d.LastName,
EmailAddress = d.EmailAddress,
PhoneNo = d.PhoneNo,
SpecializationId = d.SpecializationId,
LicenseNo = d.LicenseNo,
Address = d.Address,
BirthDate = d.BirthDate,
MaxVisit = d.MaxVisit,
Status = d.Status,
MedRepName = u.UserName,
IsTerritorial = d.IsTerritorial,
InstitutionId = d.InstitutionId,
InstitutionName = i != null ? i.InstitutionName : null,
SpecializationName = s != null ? s.SpecializationName : null,
DistrictId = dis != null ? dis.DistrictId : 0,
DistrictName = dis != null ? dis.DistrictName : null
};
return await query.ToListAsync();
}
public async Task<Response> PostPutDoctor(DoctorDto dto)
{
var response = new Response();
try
{
Infrastructure.Entities.Doctor.Doctor? existingDoctor = null;
ForDoctorApproval? existingApproval = null;
// Check if DoctorId exists
if (dto.DoctorId > 0)
{
existingDoctor = await GetByIdAsync(dto.DoctorId);
existingApproval = await _context.ForDoctorApprovals
.FirstOrDefaultAsync(x => x.DoctorId == dto.DoctorId);
}
int doctorId = dto.DoctorId;
// Step 1: Insert/Update in main Doctors table first to get DoctorId
if (existingDoctor == null)
{
// CREATE new doctor in main table with Status = 1 (For DSM Approval)
var newDoctor = MapToNewDoctor(dto);
newDoctor.Status = 1; // Always start with "For DSM Approval"
newDoctor.IsActive = false; // Not active until approved
await AddAsync(newDoctor);
await _context.SaveChangesAsync(); // Save to get DoctorId
doctorId = newDoctor.DoctorId; // Get the auto-generated DoctorId
}
else
{
// UPDATE existing doctor - keep current status, don't change it here
// The doctor table will only be updated when status = 3 (Approved)
// For now, we just ensure it exists
doctorId = existingDoctor.DoctorId;
}
// Step 2: Insert/Update in ForDoctorApproval staging table
if (existingApproval == null)
{
// INSERT into ForDoctorApproval
var newApproval = new ForDoctorApproval
{
ForDoctorApprovalId = Guid.NewGuid(),
DoctorId = doctorId, // Use the DoctorId from main table
FirstName = dto.FirstName,
MiddleInitial = dto.MiddleInitial,
LastName = dto.LastName,
MaxVisit = dto.MaxVisit,
SpecializationId = dto.SpecializationId,
UserId = dto.UserId,
StageLevelId = dto.StageLevelId,
DistrictId = dto.DistrictId,
BirthDate = dto.BirthDate,
EmailAddress = dto.EmailAddress,
Address = dto.Address,
PhoneNo = dto.PhoneNo,
LicenseNo = dto.LicenseNo,
InstitutionId = dto.InstitutionId,
IsTerritorial = dto.IsTerritorial,
Status = 1, // Always "For DSM Approval" when submitted
CreatedDate = DateTime.UtcNow,
UpdatedDate = DateTime.UtcNow,
CreatedBy = dto.UserName
};
await _context.ForDoctorApprovals.AddAsync(newApproval);
await _context.SaveChangesAsync();
return new Response
{
Success = true,
Data = new { DoctorId = doctorId, Approval = newApproval },
Message = "Doctor submitted for approval successfully.",
MessCode = 1
};
}
else
{
// UPDATE existing ForDoctorApproval
existingApproval.FirstName = dto.FirstName;
existingApproval.MiddleInitial = dto.MiddleInitial;
existingApproval.LastName = dto.LastName;
existingApproval.MaxVisit = dto.MaxVisit;
existingApproval.DistrictId = dto.DistrictId;
existingApproval.SpecializationId = dto.SpecializationId;
existingApproval.BirthDate = dto.BirthDate;
existingApproval.EmailAddress = dto.EmailAddress;
existingApproval.Address = dto.Address;
existingApproval.PhoneNo = dto.PhoneNo;
existingApproval.LicenseNo = dto.LicenseNo;
existingApproval.InstitutionId = dto.InstitutionId;
existingApproval.IsTerritorial = dto.IsTerritorial;
existingApproval.UserId = dto.UserId;
existingApproval.StageLevelId = dto.StageLevelId;
existingApproval.Status = 1; // Reset to "For DSM Approval"
existingApproval.UpdatedDate = DateTime.UtcNow;
existingApproval.UpdatedBy = dto.UpdatedBy;
_context.ForDoctorApprovals.Update(existingApproval);
await _context.SaveChangesAsync();
return new Response
{
Success = true,
Data = new { DoctorId = doctorId, Approval = existingApproval },
Message = "Doctor information updated and resubmitted for approval.",
MessCode = 2
};
}
}
catch (Exception ex)
{
return new Response
{
Success = false,
Message = ex.Message,
MessCode = 0
};
}
}
public async Task<Response> ApproveDoctor(int doctorId, byte newStatus, string updatedBy)
{
var response = new Response();
try
{
// Get the approval record from staging table
var approval = await _context.ForDoctorApprovals
.FirstOrDefaultAsync(x => x.DoctorId == doctorId);
if (approval == null)
{
return new Response
{
Success = false,
Message = "Approval record not found for this doctor.",
MessCode = 0
};
}
// Get the doctor from main table
var doctor = await GetByIdAsync(doctorId);
if (doctor == null)
{
return new Response
{
Success = false,
Message = "Doctor not found in main table.",
MessCode = 0
};
}
// Validate status progression
// Status flow: 1 (For DSM) -> 2 (For NSM) -> 3 (Approved) or 0 (Denied at any step)
if (newStatus != 0 && newStatus != 2 && newStatus != 3)
{
return new Response
{
Success = false,
Message = "Invalid status. Use 0 (Denied), 2 (For NSM Approval), or 3 (Approved).",
MessCode = 0
};
}
// Update status in ForDoctorApproval staging table
approval.Status = newStatus;
approval.UpdatedDate = DateTime.UtcNow;
_context.ForDoctorApprovals.Update(approval);
// Handle different status scenarios
if (newStatus==3) // APPROVED - Copy from staging to main table
{
// Apply all changes from ForDoctorApproval to Doctors table
doctor.FirstName = approval.FirstName;
doctor.MiddleInitial = approval.MiddleInitial;
doctor.LastName = approval.LastName;
doctor.MaxVisit = approval.MaxVisit;
doctor.DistrictId = approval.DistrictId;
doctor.SpecializationId = approval.SpecializationId;
doctor.StageLevelId = approval.StageLevelId;
doctor.BirthDate = approval.BirthDate;
doctor.EmailAddress = approval.EmailAddress;
doctor.Address = approval.Address;
doctor.PhoneNo = approval.PhoneNo;
doctor.LicenseNo = approval.LicenseNo;
doctor.InstitutionId = approval.InstitutionId;
doctor.IsTerritorial = approval.IsTerritorial;
doctor.Status = 3;
doctor.IsActive = true; // Activate the doctor
doctor.UpdatedDate = DateTime.UtcNow;
doctor.UpdatedBy = updatedBy;
Update(doctor);
// Optionally, you can delete or archive the approval record
// _context.ForDoctorApprovals.Remove(approval);
}
else if (newStatus == 0) // DENIED
{
// Update doctor status to denied
doctor.Status = 0;
doctor.IsActive = false;
doctor.UpdatedDate= DateTime.UtcNow;
doctor.UpdatedBy = updatedBy;
Update(doctor);
}
else if (newStatus == 2) // FOR NSM APPROVAL
{
// Just update the staging table status, keep doctor status as is
doctor.Status = 2; // Update main table to show it's in NSM review
doctor.UpdatedDate = DateTime.UtcNow;
doctor.UpdatedBy = updatedBy;
Update(doctor);
}
await _context.SaveChangesAsync();
string statusMessage = newStatus switch
{
0 => "denied",
2 => "forwarded to NSM for approval",
3 => "approved and activated in the system",
_ => "updated"
};
return new Response
{
Success = true,
Data = new { Doctor = doctor, Approval = approval },
Message = $"Doctor has been {statusMessage}.",
MessCode = 2
};
}
catch (Exception ex)
{
return new Response
{
Success = false,
Message = ex.Message,
MessCode = 0
};
}
}
public async Task<List<ForDoctorApprovalDto>> GetPendingApprovals(DoctorDto dto)
{
var query =
from fda in _context.ForDoctorApprovals
join d in _context.Doctors
on fda.DoctorId equals d.DoctorId into fdaDoc
from d in fdaDoc.DefaultIfEmpty()
join i in _context.Institutions
on fda.InstitutionId equals i.InstitutionId into fdai
from i in fdai.DefaultIfEmpty()
join dis in _context.Districts
on fda.DistrictId equals dis.DistrictId into fdaDist
from dis in fdaDist.DefaultIfEmpty()
join u in _context.Users
on d.UserId equals u.Id into user
from u in user.DefaultIfEmpty()
where fda.Status == 1 || u.Company == dto.Company
select new ForDoctorApprovalDto
{
ForDoctorApprovalId = fda.ForDoctorApprovalId,
DoctorId = fda.DoctorId,
FirstName = fda.FirstName,
MiddleInitial = fda.MiddleInitial,
LastName = fda.LastName,
EmailAddress = fda.EmailAddress,
PhoneNo = fda.PhoneNo,
SpecializationId = fda.SpecializationId,
LicenseNo = fda.LicenseNo,
Address = fda.Address,
BirthDate = fda.BirthDate,
MaxVisit = fda.MaxVisit,
MedRepName=u.UserName,
Status = fda.Status,
InstitutionId = fda.InstitutionId,
InstitutionName = i != null ? i.InstitutionName : null,
DistrictId = fda.DistrictId,
DistrictName = dis != null ? dis.DistrictName : null,
UserId = fda.UserId,
StageLevelId = fda.StageLevelId,
IsTerritorial = fda.IsTerritorial
};
return await query.ToListAsync();
}
private Infrastructure.Entities.Doctor.Doctor MapToNewDoctor(DoctorDto dto)
{
return new Infrastructure.Entities.Doctor.Doctor
{
FirstName = dto.FirstName,
MiddleInitial = dto.MiddleInitial,
LastName = dto.LastName,
MaxVisit = dto.MaxVisit,
SpecializationId = dto.SpecializationId,
UserId = dto.UserId,
StageLevelId = dto.StageLevelId,
DistrictId = dto.DistrictId,
BirthDate = dto.BirthDate,
EmailAddress = dto.EmailAddress,
Address = dto.Address,
PhoneNo = dto.PhoneNo,
LicenseNo = dto.LicenseNo,
InstitutionId = dto.InstitutionId,
IsTerritorial = dto.IsTerritorial,
Status = dto.Status,
IsActive = true,
CreatedDate = DateTime.UtcNow,
UpdatedDate = DateTime.UtcNow,
CreatedBy = dto.UserName,
UpdatedBy = dto.UserName,
};
}
public async Task<List<DoctorCoverageToday>> GetDoctorCoverageToday(DoctorDto dto)
{
var allItems = await _context.DoctorCoverageTodays
.FromSqlRaw("EXEC GetDoctorCoverageToday @UserId,@MRPlanDate",
new SqlParameter("@UserId", dto.UserId),
new SqlParameter("@MRPlanDate", dto.MRPlanDate))
.ToListAsync();
return allItems ?? new List<DoctorCoverageToday>();
}
}
}

View File

@ -0,0 +1,31 @@
using LSFE.Domain.Contracts.Doctor;
using LSFE.Infrastructure.Database;
using LSFE.Infrastructure.Entities;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Domain.Services
{
public interface IErrorLogger
{
Task LogErrorAsync(ErrorLogs error);
}
public class ErrorLogger : GenericRepository<Infrastructure.Entities.ErrorLogs>, IErrorLogger
{
private readonly LSFEDbContext _context;
public ErrorLogger(LSFEDbContext context) : base(context)
{
_context = context;
}
public async Task LogErrorAsync(ErrorLogs error)
{
await _dbContext.AddAsync(error);
await _dbContext.SaveChangesAsync();
}
}
}

View File

@ -0,0 +1,632 @@
using Azure.Core;
using ClosedXML.Excel;
using DocumentFormat.OpenXml.Office2016.Excel;
using LSFE.Domain.Contracts;
using LSFE.Domain.Helpers;
using LSFE.Infrastructure.Entities;
using LSFE.Infrastructure.Entities.Maintenance;
using LSFE.Infrastructure.Model;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Domain.Services
{
public class ExcelInventoryService
{
private readonly IUnitOfWork _unitOfWork;
private readonly ILogger<ExcelInventoryService> _logger;
private const int BATCH_SIZE = 100;
public readonly UserManager<AppsUsers> _userManager;
public ExcelInventoryService(IUnitOfWork unitOfWork, ILogger<ExcelInventoryService> logger,
UserManager<AppsUsers> userManager)
{
_unitOfWork = unitOfWork;
_logger = logger;
_userManager = userManager;
}
public async Task<ExcelUploadResponse> ProcessExcelFileAsync(IFormFile file, string userId, string userName)
{
var response = new ExcelUploadResponse();
try
{
// Use ArrayPool for better memory management
var buffer = System.Buffers.ArrayPool<byte>.Shared.Rent((int)file.Length);
try
{
using var fileStream = file.OpenReadStream();
await fileStream.ReadAsync(buffer.AsMemory(0, (int)file.Length));
using var stream = new MemoryStream(buffer, 0, (int)file.Length, writable: false);
using var workbook = new XLWorkbook(stream);
var worksheet = workbook.Worksheets.FirstOrDefault();
if (worksheet == null)
{
response.Success = false;
response.Message = "No worksheet found in the Excel file";
response.MessCode = 0;
return response;
}
// Validate headers
var (isValid, errors) = ExcelTemplateValidator.ValidateHeaders(worksheet);
if (!isValid)
{
response.Success = false;
response.Message = "Invalid Excel template. Please use the correct template.";
response.Errors = errors;
response.MessCode = 0;
return response;
}
var rowCount = worksheet.LastRowUsed()?.RowNumber() ?? 0;
response.TotalRows = rowCount - 1;
// Calculate current year and month once for performance
var currentDate = DateTime.Now;
var currentYear = currentDate.Year;
var currentMonth = currentDate.Month;
// Pre-load existing inventory data to avoid N+1 query problem
var existingInventory = await LoadExistingInventoryAsync(worksheet, rowCount);
// Pre-load and validate all MedRep names from the Excel file
var validMedReps = await ValidateMedRepNamesAsync(worksheet, rowCount, response);
if (!validMedReps.Any())
{
response.Success = false;
response.Message = "No valid MedRep users found in the Excel file.";
response.MessCode = 0;
return response;
}
// Process in batches
var inventoriesToInsert = new List<Inventory>();
var summaryDict = new Dictionary<string, InventorySummary>();
for (int row = 2; row <= rowCount; row++)
{
try
{
var brandName = worksheet.Cell(row, 1).Value.ToString()?.Trim();
var year = worksheet.Cell(row, 14).Value.ToString()?.Trim();
var medRepName = worksheet.Cell(row, 15).Value.ToString()?.Trim();
if (string.IsNullOrWhiteSpace(brandName))
{
response.SkippedRows++;
continue;
}
// Validate MedRep Name exists in the system
if (string.IsNullOrWhiteSpace(medRepName))
{
response.Errors.Add($"Row {row}: MedRep Name is required.");
response.SkippedRows++;
continue;
}
if (!validMedReps.Contains(medRepName.ToLower()))
{
response.Errors.Add($"Row {row}: Invalid MedRep User Name '{medRepName}'. User does not exist in the system.");
response.SkippedRows++;
continue;
}
if (!int.TryParse(year, out int cycleYear))
{
response.Errors.Add($"Row {row}: Invalid year '{year}'");
response.SkippedRows++;
continue;
}
// Validate year is not in the past
if (cycleYear < currentYear)
{
response.Errors.Add($"Row {row}: Year {cycleYear} is in the past. Only current month onwards is allowed.");
response.SkippedRows++;
continue;
}
// Process each month
for (byte month = 1; month <= 12; month++)
{
var cellValue = worksheet.Cell(row, month + 1).Value.ToString()?.Trim();
if (!int.TryParse(cellValue, out int qty) || qty <= 0)
{
continue;
}
// Validate date is not in the past (current month or future only)
if (cycleYear == currentYear && month < currentMonth)
{
_logger.LogDebug($"Row {row}, Month {month}: Skipping past date - {month}/{cycleYear}");
response.Errors.Add($"Row {row}, Month {month}: Date {month}/{cycleYear} is in the past. Only {currentMonth}/{currentYear} onwards is allowed.");
response.SkippedRows++;
continue;
}
// Check if record exists using in-memory lookup (much faster)
var key = $"{brandName}|{month}|{cycleYear}|{medRepName}";
if (existingInventory.Contains(key))
{
_logger.LogDebug($"Row {row}, Month {month}: Skipping duplicate - {key}");
continue; // Skip duplicates
}
// Also check if we already added this in current batch
if (inventoriesToInsert.Any(x =>
x.Description == brandName &&
x.CycleMonth == month &&
x.CycleYear == cycleYear &&
x.MedRepName == medRepName))
{
_logger.LogDebug($"Row {row}, Month {month}: Skipping - already in current batch");
continue;
}
var inventory = new Inventory
{
InventoryId = Guid.NewGuid(),
UserId = userId,
Description = brandName,
QtyIn = qty,
QtyOut = 0,
QtyBalance = qty,
CycleMonth = month,
CycleYear = cycleYear,
MedRepName = medRepName,
CreatedBy = userName,
CreatedDate = DateTime.Now,
IsActive = true
};
inventoriesToInsert.Add(inventory);
response.ProcessedRows++;
// Update summary
var summaryKey = $"{brandName}|{medRepName}";
if (!summaryDict.TryGetValue(summaryKey, out var summary))
{
summary = new InventorySummary
{
Brand = brandName,
TotalQty = 0,
MedRepName = medRepName
};
summaryDict[summaryKey] = summary;
}
summary.TotalQty += qty;
}
// Batch insert to reduce database round trips
if (inventoriesToInsert.Count >= BATCH_SIZE)
{
await _unitOfWork.Inventory.AddRangeAsync(inventoriesToInsert);
await _unitOfWork.SaveChangesAsync();
inventoriesToInsert.Clear();
}
}
catch (Exception ex)
{
response.Errors.Add($"Row {row}: {ex.Message}");
response.SkippedRows++;
_logger.LogError(ex, $"Error processing row {row}");
}
}
// Insert remaining items
if (inventoriesToInsert.Any())
{
await _unitOfWork.Inventory.AddRangeAsync(inventoriesToInsert);
await _unitOfWork.SaveChangesAsync();
}
response.InsertedItems = summaryDict.Values.ToList();
response.Success = true;
response.MessCode = 1;
response.Message = $"Successfully processed {response.ProcessedRows} inventory records. Skipped {response.SkippedRows} duplicates/invalid rows.";
_logger.LogInformation($"Excel processing completed: {response.ProcessedRows} processed, {response.SkippedRows} skipped, {response.TotalRows} total rows");
}
finally
{
// Return buffer to pool
System.Buffers.ArrayPool<byte>.Shared.Return(buffer);
}
}
catch (Exception ex)
{
response.Success = false;
response.Message = $"Error processing Excel file: {ex.Message}";
response.MessCode = 0;
response.Errors.Add(ex.ToString());
_logger.LogError(ex, "Error processing Excel file");
}
return response;
}
/// <summary>
/// Validates all MedRep names from the Excel file against the UserManager
/// Returns a HashSet of valid (lowercase) usernames for O(1) lookup during processing
/// </summary>
private async Task<HashSet<string>> ValidateMedRepNamesAsync(IXLWorksheet worksheet, int rowCount, ExcelUploadResponse response)
{
var validMedReps = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var uniqueMedRepNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
// Collect unique MedRep names from Excel
for (int row = 2; row <= rowCount; row++)
{
var medRepName = worksheet.Cell(row, 15).Value.ToString()?.Trim();
if (!string.IsNullOrWhiteSpace(medRepName))
{
uniqueMedRepNames.Add(medRepName);
}
}
// Validate each unique MedRep name against UserManager
foreach (var medRepName in uniqueMedRepNames)
{
var user = await _userManager.FindByNameAsync(medRepName.ToLower());
if (user != null)
{
validMedReps.Add(medRepName.ToLower());
_logger.LogDebug($"MedRep '{medRepName}' validated successfully");
}
else
{
_logger.LogWarning($"MedRep '{medRepName}' not found in the system");
}
}
_logger.LogInformation($"Validated {validMedReps.Count} out of {uniqueMedRepNames.Count} unique MedRep names");
return validMedReps;
}
/// <summary>
/// Pre-loads existing inventory records from database to avoid N+1 queries
/// This validates against ALL records in the inventory table (from all users)
/// to prevent duplicate uploads
/// </summary>
private async Task<HashSet<string>> LoadExistingInventoryAsync(IXLWorksheet worksheet, int rowCount)
{
var brandNames = new HashSet<string>();
var years = new HashSet<int>();
var medRepNames = new HashSet<string>();
var months = new HashSet<byte> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
// Collect unique brands, years, and med rep names from Excel file being uploaded
for (int row = 2; row <= rowCount; row++)
{
var brandName = worksheet.Cell(row, 1).Value.ToString()?.Trim();
var yearStr = worksheet.Cell(row, 14).Value.ToString()?.Trim();
var medRepName = worksheet.Cell(row, 15).Value.ToString()?.Trim();
if (!string.IsNullOrWhiteSpace(brandName))
{
brandNames.Add(brandName);
}
if (int.TryParse(yearStr, out int year))
{
years.Add(year);
}
if (!string.IsNullOrWhiteSpace(medRepName))
{
medRepNames.Add(medRepName);
}
}
if (!brandNames.Any() || !years.Any())
{
return new HashSet<string>();
}
// Single query to load ALL matching records from inventory table
var existingRecords = await _unitOfWork.Inventory
.FindAllAsync(inv =>
brandNames.Contains(inv.Description) &&
months.Contains(inv.CycleMonth) &&
years.Contains(inv.CycleYear) &&
medRepNames.Contains(inv.MedRepName) &&
inv.IsActive == true);
// Create lookup set with composite key: Brand|Month|Year|MedRepName
var existingKeys = new HashSet<string>(
existingRecords.Select(inv =>
$"{inv.Description}|{inv.CycleMonth}|{inv.CycleYear}|{inv.MedRepName}"));
_logger.LogInformation($"Loaded {existingKeys.Count} existing inventory records for validation");
return existingKeys;
}
}
}
/*#region
public async Task<ExcelUploadResponse> ProcessExcelFileAsync(IFormFile file, string userId, string userName)
{
var response = new ExcelUploadResponse();
try
{
// Use ArrayPool for better memory management
var buffer = System.Buffers.ArrayPool<byte>.Shared.Rent((int)file.Length);
try
{
using var fileStream = file.OpenReadStream();
await fileStream.ReadAsync(buffer.AsMemory(0, (int)file.Length));
using var stream = new MemoryStream(buffer, 0, (int)file.Length, writable: false);
using var workbook = new XLWorkbook(stream);
var worksheet = workbook.Worksheets.FirstOrDefault();
if (worksheet == null)
{
response.Success = false;
response.Message = "No worksheet found in the Excel file";
response.MessCode = 0;
return response;
}
// Validate headers
var (isValid, errors) = ExcelTemplateValidator.ValidateHeaders(worksheet);
if (!isValid)
{
response.Success = false;
response.Message = "Invalid Excel template. Please use the correct template.";
response.Errors = errors;
response.MessCode = 0;
return response;
}
var user = await _userManager.FindByNameAsync(request.UserName.ToLower());
if (user == null)
{
response.Success = false;
response.Message = "Invalid MedRep User Name.";
response.Errors = errors;
response.MessCode = 0;
return response;
}
var rowCount = worksheet.LastRowUsed()?.RowNumber() ?? 0;
response.TotalRows = rowCount - 1;
// Calculate current year and month once for performance
var currentDate = DateTime.Now;
var currentYear = currentDate.Year;
var currentMonth = currentDate.Month;
// Pre-load existing inventory data to avoid N+1 query problem
var existingInventory = await LoadExistingInventoryAsync(worksheet, rowCount);
// Process in batches
var inventoriesToInsert = new List<Inventory>();
var summaryDict = new Dictionary<string, InventorySummary>();
for (int row = 2; row <= rowCount; row++)
{
try
{
var brandName = worksheet.Cell(row, 1).Value.ToString()?.Trim();
var year = worksheet.Cell(row, 14).Value.ToString()?.Trim();
var medRepName = worksheet.Cell(row, 15).Value.ToString()?.Trim();
if (string.IsNullOrWhiteSpace(brandName))
{
response.SkippedRows++;
continue;
}
if (!int.TryParse(year, out int cycleYear))
{
response.Errors.Add($"Row {row}: Invalid year '{year}'");
response.SkippedRows++;
continue;
}
// Validate year is not in the past
if (cycleYear < currentYear)
{
response.Errors.Add($"Row {row}: Year {cycleYear} is in the past. Only current month onwards is allowed.");
response.SkippedRows++;
continue;
}
// Process each month
for (byte month = 1; month <= 12; month++)
{
var cellValue = worksheet.Cell(row, month + 1).Value.ToString()?.Trim();
if (!int.TryParse(cellValue, out int qty) || qty <= 0)
{
continue;
}
// Validate date is not in the past (current month or future only)
if (cycleYear == currentYear && month < currentMonth)
{
_logger.LogDebug($"Row {row}, Month {month}: Skipping past date - {month}/{cycleYear}");
response.Errors.Add($"Row {row}, Month {month}: Date {month}/{cycleYear} is in the past. Only {currentMonth}/{currentYear} onwards is allowed.");
response.SkippedRows++;
continue;
}
// Check if record exists using in-memory lookup (much faster)
// Include MedRepName in key because same brand can be distributed by different reps
var key = $"{brandName}|{month}|{cycleYear}|{medRepName}";
if (existingInventory.Contains(key))
{
_logger.LogDebug($"Row {row}, Month {month}: Skipping duplicate - {key}");
continue; // Skip duplicates
}
// Also check if we already added this in current batch (prevent duplicates within same file)
if (inventoriesToInsert.Any(x =>
x.Description == brandName &&
x.CycleMonth == month &&
x.CycleYear == cycleYear &&
x.MedRepName == medRepName))
{
_logger.LogDebug($"Row {row}, Month {month}: Skipping - already in current batch");
continue;
}
var inventory = new Inventory
{
InventoryId = Guid.NewGuid(),
UserId = userId,
Description = brandName,
QtyIn = qty,
QtyOut = 0,
QtyBalance = qty,
CycleMonth = month,
CycleYear = cycleYear,
MedRepName = medRepName,
CreatedBy = userName,
CreatedDate = DateTime.Now,
IsActive = true
};
inventoriesToInsert.Add(inventory);
response.ProcessedRows++;
// Update summary
var summaryKey = $"{brandName}|{medRepName}";
if (!summaryDict.TryGetValue(summaryKey, out var summary))
{
summary = new InventorySummary
{
Brand = brandName,
TotalQty = 0,
MedRepName = medRepName
};
summaryDict[summaryKey] = summary;
}
summary.TotalQty += qty;
}
// Batch insert to reduce database round trips
if (inventoriesToInsert.Count >= BATCH_SIZE)
{
await _unitOfWork.Inventory.AddRangeAsync(inventoriesToInsert);
await _unitOfWork.SaveChangesAsync();
inventoriesToInsert.Clear();
}
}
catch (Exception ex)
{
response.Errors.Add($"Row {row}: {ex.Message}");
response.SkippedRows++;
_logger.LogError(ex, $"Error processing row {row}");
}
}
// Insert remaining items
if (inventoriesToInsert.Any())
{
await _unitOfWork.Inventory.AddRangeAsync(inventoriesToInsert);
await _unitOfWork.SaveChangesAsync();
}
response.InsertedItems = summaryDict.Values.ToList();
response.Success = true;
response.MessCode = 1;
response.Message = $"Successfully processed {response.ProcessedRows} inventory records. Skipped {response.SkippedRows} duplicates/invalid rows.";
_logger.LogInformation($"Excel processing completed: {response.ProcessedRows} processed, {response.SkippedRows} skipped, {response.TotalRows} total rows");
}
finally
{
// Return buffer to pool
System.Buffers.ArrayPool<byte>.Shared.Return(buffer);
}
}
catch (Exception ex)
{
response.Success = false;
response.Message = $"Error processing Excel file: {ex.Message}";
response.MessCode = 0;
response.Errors.Add(ex.ToString());
_logger.LogError(ex, "Error processing Excel file");
}
return response;
}
/// <summary>
/// Pre-loads existing inventory records from database to avoid N+1 queries
/// This validates against ALL records in the inventory table (from all users)
/// to prevent duplicate uploads
/// </summary>
private async Task<HashSet<string>> LoadExistingInventoryAsync(IXLWorksheet worksheet, int rowCount)
{
var brandNames = new HashSet<string>();
var years = new HashSet<int>();
var medRepNames = new HashSet<string>();
var months = new HashSet<byte> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; // All possible months
// Collect unique brands, years, and med rep names from Excel file being uploaded
for (int row = 2; row <= rowCount; row++)
{
var brandName = worksheet.Cell(row, 1).Value.ToString()?.Trim();
var yearStr = worksheet.Cell(row, 14).Value.ToString()?.Trim();
var medRepName = worksheet.Cell(row, 15).Value.ToString()?.Trim();
if (!string.IsNullOrWhiteSpace(brandName))
{
brandNames.Add(brandName);
}
if (int.TryParse(yearStr, out int year))
{
years.Add(year);
}
if (!string.IsNullOrWhiteSpace(medRepName))
{
medRepNames.Add(medRepName);
}
}
if (!brandNames.Any() || !years.Any())
{
return new HashSet<string>();
}
// Single query to load ALL matching records from inventory table
// This includes records uploaded by ANY user (current or previous uploads)
// Filters by Brand, Month, Year, and MedRepName to ensure precise duplicate detection
var existingRecords = await _unitOfWork.Inventory
.FindAllAsync(inv =>
brandNames.Contains(inv.Description) &&
months.Contains(inv.CycleMonth) &&
years.Contains(inv.CycleYear) &&
medRepNames.Contains(inv.MedRepName) &&
inv.IsActive == true); // Only check active records
// Create lookup set with composite key: Brand|Month|Year|MedRepName
// This allows O(1) existence checks during row processing
// MedRepName is included because the same brand can be handled by different reps
var existingKeys = new HashSet<string>(
existingRecords.Select(inv =>
$"{inv.Description}|{inv.CycleMonth}|{inv.CycleYear}|{inv.MedRepName}"));
_logger.LogInformation($"Loaded {existingKeys.Count} existing inventory records for validation");
return existingKeys;
}
#endregion*/

View File

@ -0,0 +1,40 @@
using LSFE.Domain.Contracts;
using LSFE.Infrastructure.Database;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Domain.Services
{
public class GenericRepository<TEntity> where TEntity : class
{
protected readonly LSFEDbContext _dbContext;
public GenericRepository(LSFEDbContext context)
{
_dbContext = context;
}
public async Task<TEntity?> GetByIdAsync(int id) => await _dbContext.Set<TEntity>().FindAsync(id);
public async Task<List<TEntity>> GetAllAsync() => await _dbContext.Set<TEntity>().ToListAsync();
public async Task AddAsync(TEntity entity)
{
await _dbContext.Set<TEntity>().AddAsync(entity);
}
public void Update(TEntity entity)
{
_dbContext.Entry(entity).State = EntityState.Modified;
}
public void Remove(TEntity entity)
{
_dbContext.Set<TEntity>().Remove(entity);
}
}
}

View File

@ -0,0 +1,135 @@
using LSFE.Domain.Contracts.Maintenance;
using LSFE.Infrastructure.Database;
using LSFE.Infrastructure.Dto.Maintenance;
using LSFE.Infrastructure.Model;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Domain.Services.Maintenance
{
public class MaintenanceRepo<TEntity> : IMaintenanceRepo<TEntity> where TEntity : class
{
private readonly LSFEDbContext _context;
private readonly DbSet<TEntity> _dbSet;
public MaintenanceRepo(LSFEDbContext context)
{
_context = context;
_dbSet = _context.Set<TEntity>();
}
public async Task<bool> AnyAsync(Expression<Func<TEntity, bool>> predicate)
{
return await _dbSet.AnyAsync(predicate);
}
public IQueryable<TEntity> Query()
{
return _context.Set<TEntity>().AsQueryable();
}
public async Task<List<TEntity>> GetAllAsync(params Expression<Func<TEntity, object>>[] includeProperties)
{
IQueryable<TEntity> query = _context.Set<TEntity>();
if (includeProperties != null)
{
foreach (var includeProperty in includeProperties)
{
query = query.Include(includeProperty);
}
}
return await query.ToListAsync();
}
public async Task<Response> AddOrUpdateAsync(TEntity entity)
{
try
{
var keyProperty = _context.Model.FindEntityType(typeof(TEntity))?
.FindPrimaryKey()?.Properties.First();
if (keyProperty == null)
throw new InvalidOperationException($"No key defined for {typeof(TEntity).Name}");
var keyValue = keyProperty.PropertyInfo?.GetValue(entity);
var existing = await _context.Set<TEntity>().FindAsync(keyValue);
if (existing == null)
{
await _context.Set<TEntity>().AddAsync(entity);
}
else
{
_context.Entry(existing).CurrentValues.SetValues(entity);
_context.Entry(existing).State = EntityState.Modified;
}
return new Response { Success = true, MessCode = 1 };
}
catch (Exception ex)
{
return new Response
{
Success = false,
Message = ex.ToString(),
MessCode = 0
};
}
}
public async Task<Response> SoftDeleteAsync(int id)
{
var entity = await _context.Set<TEntity>().FindAsync(id);
if (entity == null)
return new Response { Success = false, Message = "Not found" };
var prop = typeof(TEntity).GetProperty("IsActive");
if (prop != null)
prop.SetValue(entity, false);
return new Response { Success = true };
}
public async Task<TEntity?> FindAsync(Expression<Func<TEntity, bool>> predicate)
{
return await _context.Set<TEntity>().FirstOrDefaultAsync(predicate);
}
public async Task<TEntity?> GetByIdAsync(object id)
{
return await _context.Set<TEntity>().FindAsync(id);
}
public async Task<IEnumerable<TEntity>> FindAllAsync(Expression<Func<TEntity, bool>> predicate)
{
return await _context.Set<TEntity>()
.Where(predicate)
.AsNoTracking()
.ToListAsync();
}
public async Task AddRangeAsync(IEnumerable<TEntity> entities)
{
if (entities == null || !entities.Any())
{
return;
}
await _context.Set<TEntity>().AddRangeAsync(entities);
}
public async Task<Response> DeleteAsync(Guid id)
{
var entity = await _context.Set<TEntity>().FindAsync(id);
if (entity == null)
return new Response { Success = false, Message = "Not found" };
_context.Set<TEntity>().Remove(entity);
await _context.SaveChangesAsync();
return new Response { Success = true };
}
}
}

View File

@ -0,0 +1,27 @@
using Microsoft.Data.SqlClient;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Domain.Services
{
public static class OutputParamMessage
{
public static (SqlParameter MessCode, SqlParameter Message) CreateOutputParams()
{
var messCode = new SqlParameter("@MessCode", SqlDbType.TinyInt) { Direction = ParameterDirection.Output };
var message = new SqlParameter("@Message", SqlDbType.VarChar, 8000) { Direction = ParameterDirection.Output };
return (messCode, message);
}
public static (SqlParameter MessCode, SqlParameter Message, SqlParameter planId) CreateOutputParamsPlanId()
{
var messCode = new SqlParameter("@MessCode", SqlDbType.TinyInt) { Direction = ParameterDirection.Output };
var message = new SqlParameter("@Message", SqlDbType.VarChar, 8000) { Direction = ParameterDirection.Output };
var planId = new SqlParameter("@MRRawPlanIdOutPut", SqlDbType.VarChar, 8000) { Direction = ParameterDirection.Output };
return (messCode, message, planId);
}
}
}

View File

@ -0,0 +1,667 @@
using LSFE.Domain.Contracts.Planning;
using LSFE.Infrastructure.Database;
using LSFE.Infrastructure.Dto;
using LSFE.Infrastructure.Dto.Doctor;
using LSFE.Infrastructure.Dto.Planning;
using LSFE.Infrastructure.Entities.Maintenance;
using LSFE.Infrastructure.Entities.Planning;
using LSFE.Infrastructure.Model;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using Microsoft.VisualBasic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using static LSFE.Domain.Services.OutputParamMessage;
namespace LSFE.Domain.Services.Planning
{
public class PlanningRepo : GenericRepository<MRRawPlan>, IPlanningRepo
{
private readonly LSFEDbContext _context;
public PlanningRepo(LSFEDbContext context) : base(context)
{
_context = context;
}
#region Post Put
public async Task<Response> SoftDeleteAsync(int doctorId)
{
var response = new Response();
try
{
var existingDoctor = await GetByIdAsync(doctorId);
if (existingDoctor == null)
{
response.Success = false;
response.Message = $"Doctor with ID {doctorId} not found.";
response.MessCode = 0;
return response;
}
Update(existingDoctor);
await _context.SaveChangesAsync();
response.Success = true;
response.Data = existingDoctor;
response.Message = "Doctor deactivated successfully.";
response.MessCode = 3; // code for delete/soft delete
}
catch (Exception ex)
{
response.Success = false;
response.Message = ex.Message;
response.MessCode = 0;
}
return response;
}
public async Task<Response> PostPutPlan(PlanningDto dto)
{
// Input validation
if (dto == null)
return new Response { Success = false, Message = "Invalid request data", MessCode = 0 };
var (messCode, message, planId) = CreateOutputParamsPlanId();
using var transaction = await _context.Database.BeginTransactionAsync();
try
{
// Execute the stored procedure
await _context.Database.ExecuteSqlRawAsync(
"EXEC PostPutPlan @UserId,@DoctorId,@CallDate,@CallType,@SLP," +
"@IsJoinCall,@IsLiterature,@MRPlanDetailId,@MRRawPlanId,@IsUpdateDelete,@MessCode OUTPUT,@Message OUTPUT,@MRRawPlanIdOutPut OUTPUT",
new SqlParameter("@UserId", dto.UserId),
new SqlParameter("@DoctorId", dto.DoctorId),
new SqlParameter("@CallDate", dto.CallDate),
new SqlParameter("@CallType", dto.SLP ?? "N/A"),
new SqlParameter("@SLP", dto.SLP ?? "N/A"),
new SqlParameter("@IsJoinCall", false),
new SqlParameter("@IsLiterature", dto.IsLiterature),
new SqlParameter("@MRPlanDetailId",
string.IsNullOrEmpty(dto.MRPlanDetailId) || dto.MRPlanDetailId == "N/A"
? (object)DBNull.Value
: Guid.Parse(dto.MRPlanDetailId)),
new SqlParameter("@MRRawPlanId",
string.IsNullOrEmpty(dto.MRRawPlanId) || dto.MRRawPlanId == "N/A"
? (object)DBNull.Value
: Guid.Parse(dto.MRRawPlanId)),
new SqlParameter("@IsUpdateDelete", dto.IsUpdateDelete),
messCode,
message,
planId);
var code = (byte)(messCode.Value ?? 0);
var msg = message.Value?.ToString() ?? "No response message.";
var mrRawPlanId = planId.Value?.ToString() ?? string.Empty;
var success = code != 0;
// If stored procedure failed, rollback and return
if (!success)
{
await transaction.RollbackAsync();
return new Response { Success = false, Message = msg, MessCode = code };
}
// Validate that we have a valid plan ID for insert/update operations
if (string.IsNullOrEmpty(mrRawPlanId) && dto.IsUpdateDelete != 1)
{
await transaction.RollbackAsync();
return new Response { Success = false, Message = "You cannot create more than one visit in a week!", MessCode = 0 };
}
// Get username for audit trail
var username = await _context.Users
.Where(u => u.Id == dto.UserId)
.Select(u => u.UserName)
.FirstOrDefaultAsync();
if (string.IsNullOrEmpty(username))
{
await transaction.RollbackAsync();
return new Response { Success = false, Message = "User not found", MessCode = 0 };
}
var now = DateTime.Now;
DateOnly today = DateOnly.FromDateTime(DateTime.Now);
// Handle inventory based on operation type
if (dto.IsUpdateDelete == 1 || dto.IsUpdateDelete == 2) // UPDATE/DELETE
{
if (!IsSameMonth(dto.CallDate, today))
{
msg = await HandleInventoryDelete(dto, msg);
msg = await HandlePromosDelete(dto, msg);
msg = await HandleInventoryInsert(dto, msg, username, now, mrRawPlanId);
msg = await HandlePromosInsert(dto, msg, username, now, mrRawPlanId);
}
}
else if (dto.IsUpdateDelete == 2) // UPDATE
{
if (!IsSameMonth(dto.CallDate, today))
{
msg = await HandleInventoryUpdate(dto, msg, username, now);
msg = await HandlePromosUpdate(dto, msg, username, now, mrRawPlanId);
}
}
else // INSERT (new plan)
{
msg = await HandleInventoryInsert(dto, msg, username, now, mrRawPlanId);
msg = await HandlePromosInsert(dto, msg, username, now, mrRawPlanId);
}
await _context.SaveChangesAsync();
await transaction.CommitAsync();
return new Response
{
Success = true,
Message = msg,
MessCode = code
};
}
catch (Exception ex)
{
await transaction.RollbackAsync();
return new Response
{
Success = false,
Message = $"Error processing plan: {ex.Message}",
MessCode = 0
};
}
}
private bool IsSameMonth(DateOnly date, DateOnly reference)
{
return date.Year == reference.Year && date.Month == reference.Month;
}
private async Task<string> HandleInventoryDelete(PlanningDto dto, string msg)
{
if (string.IsNullOrEmpty(dto.MRRawPlanId))
return msg + " Cannot delete inventory: Missing MRRawPlanId.";
var mrRawPlanGuid = Guid.Parse(dto.MRRawPlanId);
// Get all inventory transactions (ONE query only)
var transactions = await _context.InventoryTransactions
.Where(t => t.MRRawPlanId == mrRawPlanGuid)
.ToListAsync();
if (transactions.Count == 0)
return msg + " No linked inventory transactions found.";
// Collect inventory IDs for batch query
var inventoryIds = transactions
.Select(t => t.InventoryId)
.Distinct()
.ToList();
// Load inventories in ONE query
var inventories = await _context.Inventory
.Where(i => inventoryIds.Contains(i.InventoryId))
.ToDictionaryAsync(i => i.InventoryId);
// Process each transaction update
foreach (var tx in transactions)
{
if (!inventories.TryGetValue(tx.InventoryId, out var inventory))
continue; // if missing, skip safely
inventory.QtyOut -= tx.QtyIn; // revert qty out
inventory.QtyBalance += tx.QtyIn; // restore qty
if (inventory.QtyOut < 0)
inventory.QtyOut = 0; // safety check
_context.Inventory.Update(inventory);
}
// Remove all transaction records
_context.InventoryTransactions.RemoveRange(transactions);
return msg + $" Deleted {transactions.Count} record(s).";
}
private async Task<string> HandleInventoryUpdate(PlanningDto dto, string msg,
string username, DateTime now)
{
if (dto.SelectedSamples == null || !dto.SelectedSamples.Any())
return msg;
if (string.IsNullOrEmpty(dto.MRRawPlanId))
{
return msg + " Cannot update inventory: Missing MRRawPlanId.";
}
var mrRawPlanGuid = Guid.Parse(dto.MRRawPlanId);
foreach (var sample in dto.SelectedSamples)
{
var inventoryTransaction = await _context.InventoryTransactions
.FirstOrDefaultAsync(i => i.InventoryId == sample.InventoryId
&& i.MRRawPlanId == mrRawPlanGuid);
var inventory = await _context.Inventory
.FirstOrDefaultAsync(i => i.InventoryId == sample.InventoryId);
if (inventory == null)
{
msg += $" {sample.InventoryId}: Inventory not found.";
continue;
}
if (inventoryTransaction == null)
{
msg += $" {inventory.Description}: No existing transaction found.";
continue;
}
// Calculate quantity difference
int newQtyBalance = inventory.QtyIn - sample.SelectedQty;
//Update the new qty balance
inventory.QtyBalance = newQtyBalance;
// Validate balance
if (inventory.QtyBalance < 0)
{
msg += $" {inventory.Description}: Insufficient stock for update.";
continue;
}
if (inventory.QtyBalance > inventory.QtyIn)
{
msg += $" {inventory.Description}: Balance exceeds original quantity.";
continue;
}
// Update transaction
inventoryTransaction.AppointmentDate = dto.CallDate;
inventoryTransaction.QtyOut = sample.SelectedQty;
inventoryTransaction.QtyIn = sample.SelectedQty;
inventoryTransaction.UpdatedBy = username;
inventoryTransaction.UpdatedDate = now;
_context.InventoryTransactions.Update(inventoryTransaction);
_context.Inventory.Update(inventory);
msg += $" {inventory.Description}: Updated from {sample.OriginalQty} to {sample.SelectedQty} units.";
}
return msg;
}
private async Task<string> HandleInventoryInsert(PlanningDto dto, string msg,
string username, DateTime now, string mrRawPlanId)
{
if (dto.SelectedSamples == null || !dto.SelectedSamples.Any())
return msg;
if (!Guid.TryParse(mrRawPlanId, out var planGuid))
{
return msg + " Invalid plan ID for inventory insert.";
}
var inventoryWarnings = new List<string>();
foreach (var sample in dto.SelectedSamples)
{
var inventory = await _context.Inventory
.FirstOrDefaultAsync(i => i.InventoryId == sample.InventoryId);
if (inventory == null)
{
inventoryWarnings.Add($"{sample.InventoryId}: Not found");
continue;
}
// Calculate quantity difference
int newQtyBalance = inventory.QtyIn - sample.SelectedQty;
// Create transaction
var inventoryTransaction = new InventoryTransaction
{
InventoryId = sample.InventoryId,
DoctorId = dto.DoctorId,
Description = sample.Description ?? inventory.Description ?? "Sample",
MRRawPlanId = planGuid,
AppointmentDate = dto.CallDate,
CreatedDate = now,
QtyIn = sample.SelectedQty,
QtyOut = sample.SelectedQty,
UserId = dto.UserId,
CreatedBy = username,
IsActive = true
};
_context.InventoryTransactions.Add(inventoryTransaction);
// Deduct from inventory
inventory.QtyBalance = newQtyBalance;
inventory.QtyOut = sample.SelectedQty;
inventory.UpdatedBy = username;
inventory.UpdatedDate = now;
_context.Inventory.Update(inventory);
}
if (inventoryWarnings.Any())
{
msg += $" (Inventory warnings: {string.Join("; ", inventoryWarnings)})";
}
return msg;
}
private async Task<string> HandlePromosDelete(PlanningDto dto, string msg)
{
if (dto.SelectedPromos == null || !dto.SelectedPromos.Any())
return msg;
if (string.IsNullOrEmpty(dto.MRRawPlanId))
{
return msg + " Cannot delete promos: Missing MRRawPlanId.";
}
var mrRawPlanGuid = Guid.Parse(dto.MRRawPlanId);
var promosToDelete = await _context.ProductTransactions
.Where(pt => pt.MRRawPlanId == mrRawPlanGuid)
.ToListAsync();
_context.ProductTransactions.RemoveRange(promosToDelete);
return msg + $" Deleted {promosToDelete.Count} promo(s).";
}
private async Task<string> HandlePromosUpdate(PlanningDto dto, string msg,
string username, DateTime now, string mrRawPlanId)
{
if (dto.SelectedPromos == null || !dto.SelectedPromos.Any())
return msg;
if (string.IsNullOrEmpty(dto.MRRawPlanId))
{
return msg + " Cannot update promos: Missing MRRawPlanId.";
}
var mrRawPlanGuid = Guid.Parse(dto.MRRawPlanId);
// Delete existing promos
var existingPromos = await _context.ProductTransactions
.Where(pt => pt.MRRawPlanId == mrRawPlanGuid)
.ToListAsync();
_context.ProductTransactions.RemoveRange(existingPromos);
// Insert new promos
foreach (var product in dto.SelectedPromos)
{
var productTransaction = new ProductTransaction
{
ProductId = product.ProductId,
DoctorId = dto.DoctorId,
ProductName = product.ProductName,
AppointmentDate = dto.CallDate,
MRRawPlanId = mrRawPlanGuid,
CreatedDate = now,
CreatedBy = username,
UserId = dto.UserId,
IsActive = true
};
_context.ProductTransactions.Add(productTransaction);
}
return msg + $" Updated promos: removed {existingPromos.Count}, added {dto.SelectedPromos.Count}.";
}
private async Task<string> HandlePromosInsert(PlanningDto dto, string msg,
string username, DateTime now, string mrRawPlanId)
{
if (dto.SelectedPromos == null || !dto.SelectedPromos.Any())
return msg;
if (!Guid.TryParse(mrRawPlanId, out var planGuid))
{
return msg + " Invalid plan ID for promo insert.";
}
foreach (var product in dto.SelectedPromos)
{
var productTransaction = new ProductTransaction
{
ProductId = product.ProductId,
DoctorId = dto.DoctorId,
ProductName= product.ProductName,
AppointmentDate = dto.CallDate,
MRRawPlanId = planGuid,
CreatedDate = now,
CreatedBy = username,
UserId = dto.UserId,
IsActive = true
};
_context.ProductTransactions.Add(productTransaction);
}
return msg;
}
public async Task<Response> ApprovePlan(ApprovePlanDto dto)
{
if (dto == null || dto.AppointmentIds == null || !dto.AppointmentIds.Any())
return new Response { Success = false, Message = "No appointments selected", MessCode = 0 };
// Filter out empty GUIDs
var guidList = dto.AppointmentIds.Where(id => id != Guid.Empty).ToList();
if (!guidList.Any())
return new Response { Success = false, Message = "No valid appointment ids", MessCode = 0 };
using var tx = await _context.Database.BeginTransactionAsync();
try
{
var plans = await _context.MRRawPlans
.Where(p => guidList.Contains(p.MRRawPlanId))
.ToListAsync();
if (!plans.Any())
return new Response { Success = false, Message = "No matching appointments found", MessCode = 0 };
int approvedCount = 0;
int alreadyApproved = 0;
var now = DateTime.UtcNow;
var userIdOrSystem = dto.UpdatedBy ?? "System";
foreach (var plan in plans)
{
if (plan.IsApproved == true)
{
alreadyApproved++;
continue;
}
plan.IsApproved = true;
plan.ApprovedBy = userIdOrSystem;
plan.ApprovedDate = now;
approvedCount++;
}
if (approvedCount > 0)
{
_context.MRRawPlans.UpdateRange(plans.Where(p => p.IsApproved == true));
await _context.SaveChangesAsync();
}
await tx.CommitAsync();
return new Response
{
Success = true,
Message = $"{approvedCount} approved, {alreadyApproved} already approved.",
MessCode = 1
};
}
catch (Exception ex)
{
await tx.RollbackAsync();
return new Response { Success = false, Message = $"Error: {ex.Message}", MessCode = 0 };
}
}
public async Task<Response> CopyMonthlyMRRawPlans(PlanningDto dto)
{
try
{
var (messCode, message) = CreateOutputParams();
// Execute the stored procedure
await _context.Database.ExecuteSqlRawAsync(
"EXEC CopyMonthlyMRRawPlans @MonthPlanCopy,@NewMonthPlan,@UserId,@MessCode OUTPUT,@Message OUTPUT",
new SqlParameter("@MonthPlanCopy", dto.MonthPlanCopy),
new SqlParameter("@NewMonthPlan", dto.NewMonthPlan),
new SqlParameter("@UserId", dto.UserId),
messCode,
message);
return new Response
{
Success = true,
Message = message.Value.ToString(),
MessCode = (byte)(messCode.Value ?? 0)
};
}
catch (Exception ex)
{
return new Response
{
Success = true,
Message = ex.ToString() ?? ex.InnerException.ToString(),
MessCode = 0
};
}
}
#endregion
#region Get
public async Task<List<DoctorWithPlan>> GetDoctorWithPlan(DoctorDto dto)
{
string spName = dto.IsPlanning
? "GetDoctorWithPlan"
: "GetDoctorWithPlanStatus";
var allItems = await _context.DoctorWithPlan
.FromSqlRaw(
$"EXEC {spName} @UserId, @MRPlanDate",
new SqlParameter("@UserId", dto.UserId),
new SqlParameter("@MRPlanDate", dto.MRPlanDate))
.ToListAsync();
return allItems ?? new List<DoctorWithPlan>();
}
public async Task<List<MRRawPlan>> GetPlan(PlanningDto dto)
{
var allItems = await _context.MRRawPlans
.FromSqlRaw("EXEC GetPlan @UserId",
new SqlParameter("@UserId", dto.UserId))
.ToListAsync();
return allItems ?? new List<MRRawPlan>();
}
public async Task<List<DoctorById>> GetDoctorById(DoctorDto dto)
{
var allItems = await _context.DoctorByIds
.FromSqlRaw("EXEC GetDoctorById @UserId,@DoctorId,@MRPlanDate",
new SqlParameter("@UserId", dto.UserId),
new SqlParameter("@DoctorId", dto.DoctorId),
new SqlParameter("@MRPlanDate", dto.MRPlanDate))
.ToListAsync();
return allItems ?? new List<DoctorById>();
}
public async Task<List<PendingApproval>> GetPendingApprovals(PlanningDto dto)
{
var mrPlanDate = GetValidDate(dto.MRPlanDate);
var allItems = await _context.PendingApprovals
.FromSqlRaw(
"EXEC GetPendingApprovals @UserId,@DoctorId,@MRPlanDate",
new SqlParameter("@UserId", dto.UserId),
new SqlParameter("@DoctorId", dto.DoctorId),
new SqlParameter("@MRPlanDate", mrPlanDate)
)
.ToListAsync();
return allItems ?? new List<PendingApproval>();
}
private static DateTime GetValidDate(object? input)
{
if (input is DateTime dt)
return dt;
if (input is string s && DateTime.TryParse(s, out var parsed))
return parsed;
return DateTime.UtcNow;
}
public async Task<List<MRRawPlan>> GetAllAppointmentToday(CredentialDto dto)
{
var allItems = await _context.MRRawPlans
.FromSqlRaw("EXEC GetAllAppointmentToday @UserId",
new SqlParameter("@UserId", dto.UserId))
.ToListAsync();
return allItems ?? new List<MRRawPlan>();
}
public async Task<List<MRRawPlan>> GetAllMissReschedule(CredentialDto dto)
{
var allItems = await _context.MRRawPlans
.FromSqlRaw("EXEC GetAllMissReschedule @UserId,@IsMissed,@IsReschedule",
new SqlParameter("@UserId", dto.UserId),
new SqlParameter("@IsMissed", dto.IsMissed),
new SqlParameter("@IsReschedule", dto.IsReschedule))
.ToListAsync();
return allItems ?? new List<MRRawPlan>();
}
public async Task<List<InventoryTransaction>> GetSampleTransaction(Guid mrRawPlanId)
{
var allItems = await _context.InventoryTransactions
.FromSqlRaw("EXEC GetSampleTransaction @MRRawPlanId",
new SqlParameter("@MRRawPlanId", mrRawPlanId)).ToListAsync();
return allItems ?? new List<InventoryTransaction>();
}
public async Task<List<ProductTransaction>> GetProductTransaction(Guid mrRawPlanId)
{
var allItems = await _context.ProductTransactions
.FromSqlRaw("EXEC GetProductTransaction @MRRawPlanId",
new SqlParameter("@MRRawPlanId", mrRawPlanId)).ToListAsync();
return allItems ?? new List<ProductTransaction>();
}
public async Task<List<MRRawPlansDto>> GetMRRawPlans(PlanByUserIdRequest dto)
{
var plans = await _context.MRRawPlansDto
.FromSqlRaw("EXEC GetMRRawPlans @UserId",
new SqlParameter("@UserId", dto.UserId)).ToListAsync();
return plans ?? new List<MRRawPlansDto>();
}
#endregion
}
}

View File

@ -0,0 +1,130 @@
using LSFE.Domain.Contracts.Reports;
using LSFE.Infrastructure.Database;
using LSFE.Infrastructure.Dto.Reports;
using LSFE.Infrastructure.Entities.Reports;
using LSFE.Infrastructure.Model;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Domain.Services.Reports
{
public class ReportsRepo : IReportsRepo
{
private readonly LSFEDbContext _context;
public ReportsRepo(LSFEDbContext context)
{
_context = context;
}
protected Task<(DateTime StartDate, DateTime EndDate)>
GetCurrentWholeMonth()
{
var now = DateTime.UtcNow;
var startDate = new DateTime(
now.Year,
now.Month,
1,
0, 0, 0,
DateTimeKind.Utc
);
var endDate = startDate.AddMonths(1);
return Task.FromResult((startDate, endDate));
}
public async Task<List<CallRateReport>> GetCallRateReport(ReportsDto dto)
{
var (startDate, endDate) = await GetCurrentWholeMonth();
var allItems = await _context.CallRateReports
.FromSqlRaw(
"EXEC GetCallRateReport @UserId,@StartDate,@EndDate,@Period",
new SqlParameter("@UserId", dto.UserId),
new SqlParameter("@StartDate", dto.StartDate ?? startDate),
new SqlParameter("@EndDate", dto.EndDate ?? endDate),
new SqlParameter("@Period", dto.Period ?? "monthly")).ToListAsync();
return allItems ?? new List<CallRateReport>();
}
public async Task<List<CallReachReport>> GetCallReachReport(ReportsDto dto)
{
var (startDate, endDate) = await GetCurrentWholeMonth();
var allItems = await _context.CallReachReports
.FromSqlRaw(
"EXEC GetCallReachReport @UserId,@StartDate,@EndDate,@Period",
new SqlParameter("@UserId", dto.UserId),
new SqlParameter("@StartDate", dto.StartDate ?? startDate),
new SqlParameter("@EndDate", dto.EndDate ?? endDate),
new SqlParameter("@Period", dto.Period ?? "monthly")).ToListAsync();
return allItems ?? new List<CallReachReport>();
}
public async Task<List<CatchUpCallReport>> GetCatchUpCallReport(ReportsDto dto)
{
var (startDate, endDate) = await GetCurrentWholeMonth();
var allItems = await _context.CatchUpCallReports
.FromSqlRaw(
"EXEC GetCatchUpCallReport @UserId,@StartDate,@EndDate,@Period",
new SqlParameter("@UserId", dto.UserId),
new SqlParameter("@StartDate", dto.StartDate ?? startDate),
new SqlParameter("@EndDate", dto.EndDate ?? endDate),
new SqlParameter("@Period", dto.Period ?? "monthly")).ToListAsync();
return allItems ?? new List<CatchUpCallReport>();
}
public async Task<List<ActualCoverageReport>> GetActualCoverageReport(ReportsDto dto)
{
var (startDate, endDate) = await GetCurrentWholeMonth();
var allItems = await _context.ActualCoverageReports
.FromSqlRaw(
"EXEC GetActualCoverageReport @UserId,@StartDate,@EndDate,@Period",
new SqlParameter("@UserId", dto.UserId),
new SqlParameter("@StartDate", dto.StartDate ?? startDate),
new SqlParameter("@EndDate", dto.EndDate ?? endDate),
new SqlParameter("@Period", dto.Period ?? "monthly")).ToListAsync();
return allItems ?? new List<ActualCoverageReport>();
}
public async Task<bool> PutMissedCall(ReportsDto dto)
{
try
{
var catchUp = await _context.CatchUpCalls
.SingleOrDefaultAsync(c => c.MRRawPlanId == dto.MRRawPlanId);
if (catchUp != null)
{
catchUp.MissedReason = dto.MissedReason;
}
var plan = await _context.MRRawPlans
.SingleOrDefaultAsync(c => c.MRRawPlanId == dto.MRRawPlanId);
if (plan != null)
{
plan.MissedReason = dto.MissedReason;
}
await _context.SaveChangesAsync();
return plan?.MissedReason != null;
}
catch (Exception ex)
{
ex.ToString();
throw;
}
}
}
}

View File

@ -0,0 +1,177 @@
using LSFE.Infrastructure.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
namespace LSFE.Domain.Services.Security
{
public interface IRoleAuthorizationCache
{
Task<List<string>> GetAllowedRoleIdsAsync(string controller);
Task<bool> UserHasAccessAsync(string userId, string controller);
Task<List<string>> GetUserRoleIdsAsync(string userId);
void InvalidateCache(string controller);
void InvalidateUserCache(string userId);
void InvalidateAllCache();
}
public class RoleAuthorizationCache : IRoleAuthorizationCache
{
private readonly IMemoryCache _cache;
private readonly LSFEDbContext _dbContext;
private readonly ILogger<RoleAuthorizationCache> _logger;
private const string CONTROLLER_ROLES_PREFIX = "controller_roles_";
private const string USER_ROLES_PREFIX = "user_roles_";
private const int CACHE_DURATION_MINUTES = 30;
public RoleAuthorizationCache(
IMemoryCache cache,
LSFEDbContext dbContext,
ILogger<RoleAuthorizationCache> logger)
{
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <summary>
/// Get all role IDs that have access to a specific controller (CACHED)
/// </summary>
public async Task<List<string>> GetAllowedRoleIdsAsync(string controller)
{
if (string.IsNullOrWhiteSpace(controller))
throw new ArgumentException("Controller name cannot be null or empty", nameof(controller));
var cacheKey = $"{CONTROLLER_ROLES_PREFIX}{controller}";
return await _cache.GetOrCreateAsync(cacheKey, async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(CACHE_DURATION_MINUTES);
entry.SetPriority(CacheItemPriority.High);
_logger.LogInformation("Cache MISS: Loading role permissions for controller: {Controller}", controller);
var roleIds = await _dbContext.AuthorizeRoles
.Where(ar => ar.IsActive && ar.Controller == controller)
.Select(ar => ar.RoleId)
.Distinct()
.ToListAsync();
_logger.LogInformation("Cached {Count} roles for controller: {Controller}", roleIds.Count, controller);
return roleIds ?? new List<string>();
});
}
/// <summary>
/// Get all role IDs for a specific user (CACHED)
/// </summary>
public async Task<List<string>> GetUserRoleIdsAsync(string userId)
{
if (string.IsNullOrWhiteSpace(userId))
throw new ArgumentException("User ID cannot be null or empty", nameof(userId));
var cacheKey = $"{USER_ROLES_PREFIX}{userId}";
return await _cache.GetOrCreateAsync(cacheKey, async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(CACHE_DURATION_MINUTES);
entry.SetPriority(CacheItemPriority.High);
_logger.LogInformation("Cache MISS: Loading roles for user: {UserId}", userId);
var userRoleIds = await _dbContext.UserRoles
.Where(ur => ur.UserId == userId)
.Select(ur => ur.RoleId)
.ToListAsync();
_logger.LogInformation("Cached {Count} roles for user: {UserId}", userRoleIds.Count, userId);
return userRoleIds ?? new List<string>();
});
}
/// <summary>
/// Check if a specific user has access to a controller (FULLY CACHED)
/// </summary>
public async Task<bool> UserHasAccessAsync(string userId, string controller)
{
if (string.IsNullOrWhiteSpace(userId))
throw new ArgumentException("User ID cannot be null or empty", nameof(userId));
if (string.IsNullOrWhiteSpace(controller))
throw new ArgumentException("Controller name cannot be null or empty", nameof(controller));
try
{
// Get allowed role IDs from cache (or database if cache miss)
var allowedRoleIds = await GetAllowedRoleIdsAsync(controller);
if (!allowedRoleIds.Any())
{
_logger.LogWarning("No roles configured for controller: {Controller}", controller);
return false;
}
// Get user's role IDs from cache (or database if cache miss)
var userRoleIds = await GetUserRoleIdsAsync(userId);
if (!userRoleIds.Any())
{
_logger.LogWarning("User {UserId} has no roles assigned", userId);
return false;
}
// Check if user has any of the required roles (IN-MEMORY operation, no database!)
var hasAccess = userRoleIds.Any(userRole => allowedRoleIds.Contains(userRole));
_logger.LogDebug("User {UserId} access to {Controller}: {HasAccess}", userId, controller, hasAccess);
return hasAccess;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error checking access for user {UserId} on controller {Controller}",
userId, controller);
throw;
}
}
/// <summary>
/// Invalidate cache for a specific controller
/// </summary>
public void InvalidateCache(string controller)
{
if (string.IsNullOrWhiteSpace(controller))
return;
var cacheKey = $"{CONTROLLER_ROLES_PREFIX}{controller}";
_cache.Remove(cacheKey);
_logger.LogInformation("Cache invalidated for controller: {Controller}", controller);
}
/// <summary>
/// Invalidate cache for a specific user (call this when user roles change)
/// </summary>
public void InvalidateUserCache(string userId)
{
if (string.IsNullOrWhiteSpace(userId))
return;
var cacheKey = $"{USER_ROLES_PREFIX}{userId}";
_cache.Remove(cacheKey);
_logger.LogInformation("Cache invalidated for user: {UserId}", userId);
}
/// <summary>
/// Invalidate all controller role caches
/// </summary>
public void InvalidateAllCache()
{
_logger.LogWarning("All cache invalidation requested - consider implementing a cache key tracking system");
}
}
}

View File

@ -0,0 +1,108 @@
using LSFE.Domain.Contracts.Planning;
using LSFE.Domain.Contracts.TimeKeeping;
using LSFE.Infrastructure.Database;
using LSFE.Infrastructure.Dto;
using LSFE.Infrastructure.Dto.TimeKeeping;
using LSFE.Infrastructure.Entities.Planning;
using LSFE.Infrastructure.Entities.TimeKeeping;
using LSFE.Infrastructure.Model;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Domain.Services.TimeKeeping
{
public class AttendanceRepo : GenericRepository<Attendance>, IAttendanceRepo
{
private readonly LSFEDbContext _context;
public AttendanceRepo(LSFEDbContext context) : base(context)
{
_context = context;
}
public async Task<List<Infrastructure.Entities.TimeKeeping.AttendanceDto>>
GetAttendance(Infrastructure.Dto.TimeKeeping.AttendanceDto dto)
{
var now = DateTime.UtcNow;
var startDate = new DateTime(now.Year,now.Month,1,0, 0, 0,DateTimeKind.Utc);
var endDate = startDate.AddMonths(1);
var attendance = await _context.AttendanceDto
.FromSqlRaw(
"EXEC GetAttendance @UserId,@StartDate,@EndDate,@Period",
new SqlParameter("@UserId", dto.UserId),
new SqlParameter("@StartDate", dto.StartDate ?? startDate),
new SqlParameter("@EndDate", dto.EndDate ?? endDate),
new SqlParameter("@Period", dto.Period ?? "monthly")).ToListAsync();
return attendance ?? new List<Infrastructure.Entities.TimeKeeping.AttendanceDto>();
}
public async Task<List<Attendance>> GetAttendanceToday(CredentialDto dto)
{
var attendance = await _context.Attendances
.FromSqlRaw("EXEC GetAttendanceToday @UserId",
new SqlParameter("@UserId", dto.UserId))
.ToListAsync();
return attendance ?? new List<Attendance>();
}
public async Task<Response> PostPutAttendance(Infrastructure.Dto.TimeKeeping.AttendanceDto dto)
{
try
{
// Query by AppAttendanceId (the mobile app's ID), not AttendanceId (the DB auto-increment)
var logs = await _context.Attendances
.FirstOrDefaultAsync(a => a.UserId == dto.UserId &&
a.AppAttendanceId == dto.AppAttendanceId);
if (logs == null)
{
// Create new record - don't set AttendanceId, let the DB auto-increment it
var attendance = new Attendance
{
AppAttendanceId = dto.AppAttendanceId,
TimeIn = dto.TimeIn,
UserId = dto.UserId,
TimeOut = dto.TimeOut,
TimeInLocation = dto.TimeInLocation,
TimeOutLocation = dto.TimeOutLocation,
SignatureFileNameIn = dto.SignatureFileNameIn,
};
await _context.AddAsync(attendance);
await _context.SaveChangesAsync(); // MUST SAVE!
}
else
{
// Update existing record
logs.TimeIn = dto.TimeIn;
logs.TimeOut = dto.TimeOut;
logs.TimeInLocation = dto.TimeInLocation;
logs.TimeOutLocation = dto.TimeOutLocation;
logs.SignatureFileNameOut = dto.SignatureFileNameOut;
await _context.SaveChangesAsync();
}
return new Response
{
Message = "Sync Successfully",
MessCode = 1,
Success = true
};
}
catch (Exception ex)
{
// Log the actual error
Debug.WriteLine($"PostPutAttendance Error: {ex.ToString()}");
throw; // Re-throw so the controller can log it
}
}
}
}

View File

@ -0,0 +1,86 @@
using LSFE.Domain.Contracts;
using LSFE.Domain.Contracts.Doctor;
using LSFE.Domain.Contracts.Maintenance;
using LSFE.Domain.Contracts.Planning;
using LSFE.Domain.Contracts.Reports;
using LSFE.Domain.Contracts.TimeKeeping;
using LSFE.Domain.Services.Doctor;
using LSFE.Domain.Services.Maintenance;
using LSFE.Domain.Services.Planning;
using LSFE.Domain.Services.Reports;
using LSFE.Domain.Services.TimeKeeping;
using LSFE.Infrastructure.Database;
using LSFE.Infrastructure.Entities.Admin;
using LSFE.Infrastructure.Entities.Doctor;
using LSFE.Infrastructure.Entities.Maintenance;
using LSFE.Infrastructure.Entities.Planning;
using LSFE.Infrastructure.Entities.TimeKeeping;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Domain.Services
{
public class UnitOfWork : IUnitOfWork
{
private readonly LSFEDbContext _context;
public IErrorLogger ErrorLogger { get; }
public IPlanningRepo Planning { get; }
public IDoctorRepo Doctors { get; }
public IAttendanceRepo Attendances {get; }
public IMaintenanceRepo<Areas> Areas { get; }
public IMaintenanceRepo<Districts> Districts { get; }
public IMaintenanceRepo<Divisions> Divisions { get; }
public IMaintenanceRepo<Territories> Territories { get; }
public IMaintenanceRepo<Institutions> Institutions { get; }
public IMaintenanceRepo<InstitutionCategory> InstitutionCategories { get; }
public IMaintenanceRepo<Products> Products { get; }
public IMaintenanceRepo<Specialization> Specializations { get; }
public IMaintenanceRepo<Inventory> Inventory { get; }
public IMaintenanceRepo<InventoryTransaction> InventoryTransactions { get; }
public IMaintenanceRepo<ProductTransaction> ProductTransactions { get; }
public IMaintenanceRepo<MRRawPlan> MRRawPlans { get; }
public IMaintenanceRepo<CatchUpCall> CatchUpCalls { get; }
public IMaintenanceRepo<MRPlanDetail> MRPlanDetails { get; }
public IMaintenanceRepo<MRPlanHeader> MRPlanHeaders { get; }
public IMaintenanceRepo<UserDevice> UserDevices { get; }
public IReportsRepo Reports { get; }
public UnitOfWork(LSFEDbContext context)
{
_context = context;
ErrorLogger = new ErrorLogger(_context);
Doctors = new DoctorRepo(_context);
Planning = new PlanningRepo(_context);
Reports = new ReportsRepo(_context);
Attendances = new AttendanceRepo(_context);
Areas = new MaintenanceRepo<Areas>(_context);
Districts = new MaintenanceRepo<Districts>(_context);
Divisions = new MaintenanceRepo<Divisions>(_context);
Territories = new MaintenanceRepo<Territories>(_context);
Institutions = new MaintenanceRepo<Institutions>(_context);
InstitutionCategories = new MaintenanceRepo<InstitutionCategory>(_context);
Products = new MaintenanceRepo<Products>(_context);
Specializations = new MaintenanceRepo<Specialization>(_context);
Inventory = new MaintenanceRepo<Inventory>(_context);
InventoryTransactions = new MaintenanceRepo<InventoryTransaction>(_context);
ProductTransactions = new MaintenanceRepo<ProductTransaction>(_context);
MRRawPlans= new MaintenanceRepo<MRRawPlan>(_context);
CatchUpCalls = new MaintenanceRepo<CatchUpCall>(_context);
MRPlanDetails = new MaintenanceRepo<MRPlanDetail>(_context);
MRPlanHeaders = new MaintenanceRepo<MRPlanHeader>(_context);
UserDevices = new MaintenanceRepo<UserDevice>(_context);
}
public async Task<int> SaveChangesAsync()
{
return await _context.SaveChangesAsync();
}
public void Dispose()
{
_context.Dispose();
}
}
}

View File

@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Converter
{
public class GuidListConverter : JsonConverter<List<Guid>>
{
public override List<Guid> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var list = new List<Guid>();
if (reader.TokenType != JsonTokenType.StartArray)
throw new JsonException();
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndArray)
return list;
if (reader.TokenType == JsonTokenType.String)
{
var str = reader.GetString();
if (Guid.TryParse(str, out var guid))
list.Add(guid);
}
else if (reader.TokenType == JsonTokenType.PropertyName)
{
// Handle object format if needed
}
}
return list;
}
public override void Write(Utf8JsonWriter writer, List<Guid> value, JsonSerializerOptions options)
{
writer.WriteStartArray();
foreach (var guid in value)
writer.WriteStringValue(guid.ToString());
writer.WriteEndArray();
}
}
}

View File

@ -0,0 +1,154 @@
using LSFE.Infrastructure.Dto.Planning;
using LSFE.Infrastructure.Entities;
using LSFE.Infrastructure.Entities.Admin;
using LSFE.Infrastructure.Entities.Auth;
using LSFE.Infrastructure.Entities.Doctor;
using LSFE.Infrastructure.Entities.Maintenance;
using LSFE.Infrastructure.Entities.Planning;
using LSFE.Infrastructure.Entities.Reports;
using LSFE.Infrastructure.Entities.TimeKeeping;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Database
{
public class LSFEDbContext : IdentityDbContext<AppsUsers, Role, string>
{
public LSFEDbContext(DbContextOptions<LSFEDbContext> options) : base(options) { }
public DbSet<Doctor> Doctors { get; set; }
public DbSet<ForDoctorApproval> ForDoctorApprovals { get; set; }
public DbSet<Departments> Departments { get; set; }
public DbSet<Attachment> Attachments { get; set; }
public DbSet<Areas> Areas { get; set; }
public DbSet<Districts> Districts { get; set; }
public DbSet<Institutions> Institutions { get; set; }
public DbSet<Specialization> Specializations { get; set; }
public DbSet<InstitutionCategory> InstitutionCategories { get; set; }
public DbSet<Products> Products { get; set; }
public DbSet<ProductTransaction> ProductTransactions { get; set; }
public DbSet<InventoryTransaction> InventoryTransactions { get; set; }
public DbSet<Territories> Territories { get; set; }
public DbSet<Divisions> Divisions { get; set; }
public DbSet<MRRawPlan> MRRawPlans { get; set; }
public DbSet<MRRawPlansDto> MRRawPlansDto { get; set; }
public DbSet<CatchUpCall> CatchUpCalls { get; set; }
public DbSet<Inventory> Inventory { get; set; }
public DbSet<CatchUpCallReport> CatchUpCallReports { get; set; }
public DbSet<CallRateReport> CallRateReports { get; set; }
public DbSet<CallReachReport> CallReachReports { get; set; }
public DbSet<ActualCoverageReport> ActualCoverageReports { get; set; }
public virtual DbSet<DoctorWithPlan> DoctorWithPlan { get; set; }
public DbSet<DoctorCoverageToday> DoctorCoverageTodays { get; set; }
public virtual DbSet<PendingApproval> PendingApprovals { get; set; }
public virtual DbSet<DoctorById> DoctorByIds { get; set; }
public DbSet<MRPlanDetail> MRPlanDetails { get; set; }
public DbSet<MRPlanHeader> MRPlanHeaders { get; set; }
public DbSet<Attendance> Attendances { get; set; }
public virtual DbSet<Entities.TimeKeeping.AttendanceDto> AttendanceDto { get; set; }
public DbSet<ErrorLogs> ErrorLogs { get; set; }
public DbSet<UserDevice> UserDevices { get; set; }
public DbSet<AuthorizeRoles> AuthorizeRoles { get; internal set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ActualCoverageReport>(entity =>
{
entity.ToTable("ActualCoverageReport");
entity.HasNoKey();
});
modelBuilder.Entity<Areas>()
.Property(d => d.AreaId)
.ValueGeneratedOnAdd();
modelBuilder.Entity<Divisions>()
.Property(d => d.DivisionId)
.ValueGeneratedOnAdd();
modelBuilder.Entity<Doctor>()
.HasOne(d => d.Institution)
.WithMany(i => i.Doctors)
.HasForeignKey(d => d.InstitutionId);
modelBuilder.Entity<Doctor>()
.HasOne(d => d.Districts)
.WithMany(i => i.Doctors)
.HasForeignKey(d => d.DistrictId);
modelBuilder.Entity<Districts>(b =>
{
b.ToTable("Districts");
b.HasOne(u => u.Areas)
.WithMany()
.HasForeignKey(u => u.AreaId)
.IsRequired(false);
});
modelBuilder.Entity<Institutions>(b =>
{
b.ToTable("Institutions");
b.HasOne(u => u.Territories)
.WithMany()
.HasForeignKey(u => u.TerritoryId)
.IsRequired(false);
});
modelBuilder.Entity<Territories>(b =>
{
b.ToTable("Territories");
b.HasOne(u => u.Districts)
.WithMany()
.HasForeignKey(u => u.DistrictId)
.IsRequired(false);
});
modelBuilder.Entity<AppsUsers>(b =>
{
b.ToTable("Users");
b.HasOne(u => u.Attachment)
.WithMany()
.HasForeignKey(u => u.AttachmentId)
.IsRequired(false);
b.HasOne(u => u.Department)
.WithMany()
.HasForeignKey(u => u.DepartmentId)
.IsRequired(false);
});
modelBuilder.Entity<Attachment>(b =>
{
b.ToTable("Attachment");
b.Property(d => d.AttachmentId)
.ValueGeneratedOnAdd();
});
modelBuilder.Entity<Departments>(b =>
{
b.ToTable("Departments");
});
modelBuilder.Entity<IdentityUserClaim<string>>(b =>
{
b.ToTable("UserClaims");
});
modelBuilder.Entity<IdentityUserToken<string>>(b =>
{
b.ToTable("UserTokens");
});
modelBuilder.Entity<Role>(b =>
{
b.ToTable("Roles");
});
modelBuilder.Entity<IdentityUserRole<string>>(b =>
{
b.ToTable("UserRoles");
});
}
}
}

View File

@ -0,0 +1,29 @@
using System.Security;
namespace LSFE.Infrastructure.Dto.Account
{
public class NavigationItemDto
{
public required string Id { get; set; }
public required string Label { get; set; }
public required string Icon { get; set; }
public string? Badge { get; set; }
public required string Route { get; set; }
public PermissionsDto? Permissions { get; set; }
public List<NavigationItemDto>? Children { get; set; }
public int? ParentModuleId { get; set; }
}
public class PermissionsDto
{
public bool CanView { get; set; }
public bool CanCreate { get; set; }
public bool CanEdit { get; set; }
public bool CanDelete { get; set; }
}
public class NavigationResponseDto
{
public bool Success { get; set; }
public List<NavigationItemDto>? Data { get; set; }
public string? Message { get; set; }
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Dto.Account
{
public class UserClaimsDto
{
public string UserId { get; init; } = default!;
public string UserName { get; init; } = default!;
public string FullName { get; init; } = default!;
public string Company { get; init; } = default!;
public IReadOnlyList<string> Roles { get; init; } = [];
}
}

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Dto.Admin
{
public class DeviceInfoDto
{
public string? DeviceId { get; set; }
public string? DeviceModel { get; set; }
public string? Platform { get; set; }
public string? Version { get; set; }
public string? Manufacturer { get; set; }
public string? Name { get; set; }
public string? DeviceType { get; set; }
public string? VersionString { get; set; }
public string? UserName { get; set; }
}
}

View File

@ -0,0 +1,19 @@
using LSFE.Infrastructure.Converter;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Dto
{
public class ApprovePlanDto
{
[JsonConverter(typeof(GuidListConverter))]
public List<Guid> AppointmentIds { get; set; } = new();
public string? UserId { get; set; }
public string? MRPlanDate { get; set; }
public string? UpdatedBy { get; set; }
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Dto
{
public class CredentialDto
{
public string? UserRole { get; set; }
public string? UserId { get; set; }
public bool IsMissed { get; set; }
public bool IsReschedule { get; set; }
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Dto.Doctor
{
public class CoverageDto
{
public string? UpdatedBy { get; set; }
public string? UserId { get; set; }
}
}

View File

@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Dto.Doctor
{
public class DoctorDto
{
public int DoctorId { get; set; } = 0;
public string? MiddleInitial { get; set; }
public string? LastName { get; set; }
public string? FirstName { get; set; }
public string? Company { get; set; }
public byte MaxVisit { get; set; }
public string? UserId { get; set; }
public byte StageLevelId { get; set; }
public DateTime? BirthDate { get; set; }
public string? EmailAddress { get; set; }
public int InstitutionId { get; set; }
public byte Status { get; set; }
public bool IsActive { get; set; }
public string? PhoneNo { get; set; } = "N/A";
public string? MedRepName { get; set; }
public byte SpecializationId { get; set; }
public string? SpecializationName { get; set; }
public string? LicenseNo { get; set; }
public string? Address { get; set; }
public string? InstitutionName { get; set; }
public int? DistrictId { get; set; }
public string? DistrictName { get; set; }
public string? MRPlanDate { get; set; }
public bool IsTerritorial { get; set; }
public bool IsPlanning { get; set; }
public bool IsCatchUpCall { get; set; }=false;
public string? UserName { get; set; }
public string? UpdatedBy { get; set; }
}
}

View File

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Dto.Doctor
{
public class ForDoctorApprovalDto
{
public Guid ForDoctorApprovalId { get; set; }
public int DoctorId { get; set; }
public string? FirstName { get; set; }
public string? MiddleInitial { get; set; }
public string? LastName { get; set; }
public string? EmailAddress { get; set; }
public string? PhoneNo { get; set; }
public byte SpecializationId { get; set; }
public string? LicenseNo { get; set; }
public string? Address { get; set; }
public DateTime? BirthDate { get; set; }
public byte MaxVisit { get; set; }
public byte Status { get; set; }
public int InstitutionId { get; set; }
public string? InstitutionName { get; set; }
public int? DistrictId { get; set; }
public string? DistrictName { get; set; }
public string? UserId { get; set; }
public byte StageLevelId { get; set; }
public bool IsTerritorial { get; set; }
public string? MedRepName { get; set; }
}
}

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Dto.Maintenance
{
public class InventoryDto
{
public Guid InventoryId { get; set; }
public int DoctorId { get; set; }
public string? UserId { get; set; }
public string? UpdatedBy { get; set; }
public string? UserRole { get; set; }
public int QtyOut { get; set; }
public int QtyBalance { get; set; }
public byte CycleMonth { get; set; }
public int CycleYear { get; set; }
public DateTime UpdatedDate { get; set; }
}
}

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Dto.Maintenance
{
public class ProductDto
{
public Guid Id { get; set; }
public string? UserId { get; set; }
public string? UpdatedBy { get; set; }
public string? UserRole { get; set; }
}
}

View File

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Dto.Maintenance
{
public class ProductTransactionDto
{
public Guid ProductTransId { get; set; }
public int ProductId { get; set; }
public string? ProductName { get; set; }
public int DoctorId { get; set; }
public Guid MRRawPlanId { get; set; }
public DateOnly? AppointmentDate { get; set; }
public DateTime CreatedDate { get; set; }
public string? CreatedBy { get; set; }
public string? UserId { get; set; }
public bool IsActive { get; set; }
public string? ProductLink { get; set; }
}
}

View File

@ -0,0 +1,43 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Dto.Planning
{
[Keyless]
public class MRRawPlansDto
{
public Guid MRRawPlanId { get; set; }
public Guid? MRPlanDetailId { get; set; }
public int DoctorId { get; set; }
public byte? VisitCount { get; set; }
public DateOnly? CallDate { get; set; }
public DateOnly? ActualCallDate { get; set; }
public DateTime? StartTime { get; set; }
public DateTime? EndTime { get; set; }
public string? Longitude { get; set; }
public string? Latitude { get; set; }
public string? Location { get; set; }
public bool IsSignature { get; set; }
public string? SignatureFileName { get; set; }
public bool IsMissed { get; set; }
public string? MissedReason { get; set; }
public bool IsDoctorProxy { get; set; }
public bool IsReschedule { get; set; }
public string? RescheduleReason { get; set; }
public string? CallType { get; set; }
public bool IsJoinCall { get; set; }
public byte? CycleId { get; set; }
public bool IsApproved { get; set; }
public bool IsRecovered { get; set; }
public string? SLP { get; set; }
public string? ApprovedBy { get; set; }
public DateTime? ApprovedDate { get; set; }
public string? UserId { get; set; }
public byte Status { get; set; }
public bool IsCatchUpCall { get; set; } = false;
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Dto.Planning
{
public class PlanByUserIdRequest
{
public string? UserId { get; set; }
}
}

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Dto.Planning.Request
{
public class PlanForApprovalRequest
{
}
}

View File

@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Dto
{
public class PlanningDto
{
public class SelectedSampleDto
{
public Guid InventoryId { get; set; }
public string? Description { get; set; }
public int MaxQty { get; set; }
public int OriginalQty { get; set; }
public int QtyBalance { get; set; }
public int SelectedQty { get; set; }
}
public class SelectedPromoDto
{
public int ProductId { get; set; }
public string? ProductName { get; set; }
}
public List<SelectedSampleDto> SelectedSamples { get; set; } = new();
public List<SelectedPromoDto> SelectedPromos { get; set; } = new();
public string? MRPlanDetailId { get; set; }=null;
public int DoctorId { get; set; }
[DataType(DataType.Date)]
public DateOnly PlanDate { get; set; }
public int DivisionId { get; set; }
public bool IsActive { get; set; }
public string? MRRawPlanId { get; set; } = null;
public int ProductId { get; set; }
[DataType(DataType.Date)]
public DateOnly CallDate { get; set; }
public string? RescheduleReason { get; set; }
public string? CallType { get; set; }
public byte CycleId { get; set; }
public bool IsApproved { get; set; } = false;
public bool IsRecovered { get; set; } = false;
public bool IsLiterature { get; set; }
public string? SLP { get; set; }
public byte IsUpdateDelete { get; set; }
public string? MRPlanDate { get; set; }
public string? UpdatedBy { get; set; }
public string? UserId { get; set; }
public byte CycleMonth { get; set; }
public int CycleYear { get; set; }
public DateOnly MonthPlanCopy { get; set; }
public DateOnly NewMonthPlan { get; set; }
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Dto
{
public class RegisterUserDto
{
public string? UserName { get; set; }
public string? Email { get; set; }
public string? Password { get; set; }
public string? FullName { get; set; }
public string? Company { get; set; }
public int? DepartmentId { get; set; }
public string? Address { get; set; }
public int? AttachmentId { get; set; } = 1;
public string? EmployeeId { get; set; }
public string? RoleId { get; set; }
public string? PhoneNumber { get; set; }
public string? RoleName { get; set; }
public string? UpdatedBy { get; set; }
}
}

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Dto.Reports
{
public class ReportsDto
{
public required string UserId { get; set; }
public string? MedRepName { get; set; }
public string? Company { get; set; }
public DateOnly CallMonth { get; set; }
public Guid MRRawPlanId { get; set; }
public string? MissedReason { get; set; }
public string? Period { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Dto
{
public class RoleDto
{
public string? RoleId { get; set; }
public string? RoleName { get; set; }
}
}

View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Dto.TimeKeeping
{
public class AttendanceDto
{
public int AttendanceId { get; set; }
public string? AppAttendanceId { get; set; }
public DateTime? TimeIn { get; set; }
public DateTime? TimeOut { get; set; }
public string? UserId { get; set; }
public string? TimeInLocation { get; set; }
public string? TimeOutLocation { get; set; }
public string? SignatureFileNameIn { get; set; }
public string? SignatureFileNameOut { get; set; }
public string? SignatureBase64 { get; set; }
public string? MedRepName { get; set; }
public DateTime? EndDate { get; set; }
public DateTime? StartDate { get; set; }
public string? Period { get; set; }
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Dto
{
public class UpdateUserDto
{
public string? Id { get; set; }
public string? UserName { get; set; }
public string? Email { get; set; }
public string? Password { get; set; }
public string? FullName { get; set; }
public string? RoleId { get; set; }
public string? RoleName { get; set; }
public string? Company { get; set; }
public int? DepartmentId { get; set; }
public string? Address { get; set; }
public string? EmployeeId { get; set; }
public string? UpdatedBy { get; set; }
public string? PhoneNumber { get; set; }
}
}

View File

@ -0,0 +1,18 @@
using LSFE.Infrastructure.Entities;
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Dto
{
public class UserWithRoleDto : AppsUsers
{
public string? RoleName { get; set; }
public Departments? Departments { get; set; }
public List<RoleDto>? UserRoles { get; set; }
public string? RoleId { get; set; }
}
}

View File

@ -0,0 +1,35 @@
using LSFE.Infrastructure.Model;
using LSFE.Infrastructure.Model.Admin;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities.Admin
{
[Table("UserDevices")]
public class UserDevice
{
[Key]
public int UserDeviceId { get; set; }
public string? UserId { get; set; }
public string? DeviceId { get; set; }
public string? DeviceModel { get; set; }
public string? DevicePlatform { get; set; }
public string? DeviceManufacturer { get; set; }
public string? DeviceVersion { get; set; }
public string? AppVersion { get; set; }
public bool IsAuthorized { get; set; }
public bool IsActive { get; set; }
public DateTime? DeactivatedDate { get; set; }
public string? DeactivatedBy { get; set; }
public string? DeactivationReason { get; set; }
public DateTime RegisteredDate { get; set; }
public string? RegisteredBy { get; set; }
//public DateTime LastLoginDate { get; set; }
public string? Notes { get; set; }
}
}

View File

@ -0,0 +1,32 @@
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Net.Mail;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities
{
public class AppsUsers : IdentityUser
{
public string? FullName { get; set; }
public string? Company { get; set; }
public int? DepartmentId { get; set; }
[ForeignKey("DepartmentId")]
public Departments? Department { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime UpdatedDate { get; set; }
public string? CreatedBy { get; set; }
public string? UpdatedBy { get; set; }
public string? Address { get; set; }
public int? AttachmentId { get; set; }
[ForeignKey("AttachmentId")]
public Attachment? Attachment { get; set; }
public string? EmployeeId { get; set; }
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities
{
[Table("Attachments")]
public class Attachment
{
[Key]
public int AttachmentId { get; set; }
public string? URL { get; set; }
public string? FileName { get; set; }
}
}

View File

@ -0,0 +1,16 @@
using System.ComponentModel.DataAnnotations;
namespace LSFE.Infrastructure.Entities.Auth
{
public class AuthorizeRoles
{
[Key]
public int AuthorizeRoleId { get; set; }
public string? RoleId { get; set; }
public string? Name { get; set; }
public string? Controller { get; set; }
public bool IsActive { get; set; }
public DateTime CreatedAt { get; set; }
public string? CreatedBy { get; set; }
}
}

View File

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace LSFE.Infrastructure.Entities.Auth
{
[Table("Modules")]
public class Module
{
[Key]
public int ModuleId { get; set; }
[Required]
[MaxLength(50)]
public required string ModuleName { get; set; }
[Required]
[MaxLength(50)]
public required string ModuleKey { get; set; }
[Required]
[MaxLength(50)]
public required string IconName { get; set; }
public int DisplayOrder { get; set; }
public bool IsActive { get; set; } = true;
// CHANGE: Make this nullable for root modules
public int? ParentModuleId { get; set; }
[MaxLength(200)]
public required string RouteURL { get; set; }
[MaxLength(200)]
public string? Description { get; set; }
public DateTime CreatedDate { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
[ForeignKey("ParentModuleId")]
public virtual Module? ParentModule { get; set; }
public virtual ICollection<Module>? ChildModules { get; set; }
public virtual ICollection<RoleModulePermission>? RoleModulePermissions { get; set; }
}
}

View File

@ -0,0 +1,10 @@
using Microsoft.AspNetCore.Identity;
namespace LSFE.Infrastructure.Entities.Auth
{
public class Role : IdentityRole
{
public virtual ICollection<RoleModulePermission> RoleModulePermissions { get; set; }
= new List<RoleModulePermission>();
}
}

View File

@ -0,0 +1,40 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace LSFE.Infrastructure.Entities.Auth
{
[Table("RoleModulePermissions")]
public class RoleModulePermission
{
[Key]
public int PermissionId { get; set; }
[Required]
public string RoleId { get; set; } = default!;
[Required]
public int ModuleId { get; set; }
public bool CanView { get; set; } = false;
public bool CanCreate { get; set; } = false;
public bool CanEdit { get; set; } = false;
public bool CanDelete { get; set; } = false;
[MaxLength(500)]
public string? BadgeQuery { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
// Navigation Properties
[ForeignKey("RoleId")]
public virtual Role? Role { get; set; }
[ForeignKey("ModuleId")]
public virtual Module? Module { get; set; }
}
}

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities
{
public class BaseLogEntity
{
public DateTime CreatedDate { get; set; }= DateTime.Now;
public DateTime UpdatedDate { get; set; }
public string? CreatedBy { get; set; }
public string? UpdatedBy { get;set; }
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities
{
[Table("Departments")]
public class Departments
{
[Key]
public int DepartmentId { get; set; }
public string? DepartmentCode { get; set; }
public string? DepartmentName { get; set; }
public bool IsActive { get; set; }
}
}

View File

@ -0,0 +1,39 @@
using LSFE.Infrastructure.Entities.Maintenance;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities.Doctor
{
[Table("Doctors")]
public class Doctor : BaseLogEntity
{
[Key]
public int DoctorId { get; set; }
public string? MiddleInitial { get; set; }
public string? LastName { get; set; }
public string? FirstName { get; set; }
public byte MaxVisit { get; set; }
public byte SpecializationId { get; set; }
public string? UserId { get; set; }
public byte StageLevelId { get; set; }
public DateTime? BirthDate { get; set; }
public string? EmailAddress { get; set; }
public string? LicenseNo { get; set; }
public string? PhoneNo { get; set; }
public int InstitutionId { get; set; }
[ForeignKey(nameof(InstitutionId))]
public Institutions? Institution { get; set; }
public byte Status { get; set; }
public bool IsActive { get; set; }
public bool IsTerritorial { get; set; }
public string? Address { get; set; }
public int? DistrictId { get; set; }
[ForeignKey(nameof(DistrictId))]
public Districts? Districts { get; set; }
}
}

View File

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities.Doctor
{
public class DoctorCoverageToday
{
[Key]
public int DoctorId { get; set; }
public Guid MRRawPlanId { get; set; }
public string? MiddleInitial { get; set; }
public string? LastName { get; set; }
public string? FirstName { get; set; }
public byte MaxVisit { get; set; }
public string? SpecializationName { get; set; }
public DateTime BirthDate { get; set; }
public string? EmailAddress { get; set; }
public string? LicenseNo { get; set; }
public string? PhoneNo { get; set; }
public int InstitutionId { get; set; }
public string? InstitutionName { get; set; }
public byte Status { get; set; }
public bool IsTerritorial { get; set; }
public string? Address { get; set; }
public string? DistrictName { get; set; }
public string? AreaName { get; set; }
}
}

View File

@ -0,0 +1,39 @@
using LSFE.Infrastructure.Entities.Maintenance;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities.Doctor
{
[Table("ForDoctorApproval")]
public class ForDoctorApproval : BaseLogEntity
{
[Key]
public Guid ForDoctorApprovalId { get; set; }
public int DoctorId { get; set; }
public string? MiddleInitial { get; set; }
public string? LastName { get; set; }
public string? FirstName { get; set; }
public byte MaxVisit { get; set; }
public byte SpecializationId { get; set; }
public string? UserId { get; set; }
public byte StageLevelId { get; set; }
public DateTime? BirthDate { get; set; }
public string? EmailAddress { get; set; }
public string? LicenseNo { get; set; }
public string? PhoneNo { get; set; }
public int InstitutionId { get; set; }
[ForeignKey(nameof(InstitutionId))]
public Institutions? Institution { get; set; }
public bool IsTerritorial { get; set; }
public string? Address { get; set; }
public byte Status { get; set; }
public int? DistrictId { get; set; }
[ForeignKey(nameof(DistrictId))]
public Districts? Districts { get; set; }
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities.Doctor
{
[Table("Specialization")]
public class Specialization
{
[Key]
public byte SpecializationId { get; set; }
public string? SpecializationName { get; set; }
public int CompanyId { get; set; }
public bool IsActive { get; set; }
}
}

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities
{
[Table("ErrorLogs")]
public class ErrorLogs
{
[Key]
public int Id { get; set; }
public string? Message { get; set; }
public string? CreatedBy { get; set; }
public string? Application { get; set; }
public DateTime CreatedDate { get; set; }= DateTime.UtcNow;
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities.Maintenance
{
[Table("Areas")]
public class Areas
{
[Key]
public int AreaId { get; set; }
public string? AreaName { get; set; }
public bool IsActive { get; set; }
}
}

View File

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities.Maintenance
{
[Table("Districts")]
public class Districts
{
[Key]
public int DistrictId { get; set; }
public string? DistrictName { get; set; }
public int AreaId { get; set; }
[ForeignKey("AreaId")]
public Areas? Areas { get; set; }
public bool IsActive { get; set; }
public ICollection<Doctor.Doctor> Doctors { get; set; } = new List<Doctor.Doctor>();
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities.Maintenance
{
[Table("Divisions")]
public class Divisions
{
[Key]
public int DivisionId { get; set; }
public string? DivisionName { get; set; }
public bool IsActive { get; set; }
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities.Maintenance
{
[Table("InstitutionCategory")]
public class InstitutionCategory
{
[Key]
public int InstitutionCategoryId { get; set; }
public string? InstitutionCategoryCode { get; set; }
public string? InstitutionCategoryName { get; set; }
public bool IsActive { get; set; }
}
}

View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities.Maintenance
{
[Table("Institutions")]
public class Institutions
{
[Key]
public int InstitutionId { get; set; }
public string? InstitutionName { get; set; }
public int TerritoryId { get; set; }
[ForeignKey("TerritoryId")]
public Territories? Territories { get; set; }
public bool IsActive { get; set; }
public int InstitutionCategoryId { get; set; }
[ForeignKey("InstitutionCategoryId")]
public InstitutionCategory? InstitutionCategory { get; set; }
public ICollection<Doctor.Doctor> Doctors { get; set; } = new List<Doctor.Doctor>();
}
}

View File

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities.Maintenance
{
[Table("Inventory")]
public class Inventory
{
[Key]
public Guid InventoryId { get; set; }
public string? UserId { get; set; }
public string? Description { get; set; }
public int QtyIn { get; set; }
public int QtyOut { get; set; }
public int QtyBalance { get; set; }
public byte CycleMonth { get; set; }
public int CycleYear { get; set; }
public string? MedRepName { get; set; }
public string? CreatedBy { get; set; }
public DateTime CreatedDate { get; set; }
public string? UpdatedBy { get; set; }
public DateTime? UpdatedDate { get; set; }
public bool IsActive { get; set; }
}
}

View File

@ -0,0 +1,32 @@
using LSFE.Infrastructure.Model;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities.Maintenance
{
[Table("InventoryTransaction")]
public class InventoryTransaction
{
[Key]
public Guid InventoryTransId { get; set; }
public Guid InventoryId { get; set; }
public Guid MRRawPlanId { get; set; }
public int DoctorId { get; set; }
public string? Description { get; set; }
public DateOnly? AppointmentDate { get; set; }
public DateTime CreatedDate { get; set; }
public int QtyIn { get; set; }
public int QtyOut { get; set; }
public string? UserId { get; set; }
public string? CreatedBy { get; set; }
public bool IsActive { get; set; }
public string? UpdatedBy { get; set; }
public DateTime? UpdatedDate { get; set; }
public byte IconEmojiId { get; set; }
}
}

View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities.Maintenance
{
[Table("ProductTransaction")]
public class ProductTransaction
{
[Key]
public Guid ProductTransId { get; set; }
public int ProductId { get; set; }
public string? ProductName { get; set; }
public int DoctorId { get; set; }
public Guid MRRawPlanId { get; set; }
public DateOnly AppointmentDate { get; set; }
public DateTime CreatedDate { get; set; }
public string? CreatedBy { get; set; }
public string? UserId { get; set; }
public bool IsActive { get; set; }
}
}

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities.Maintenance
{
[Table("Products")]
public class Products
{
[Key]
public int ProductId { get; set; }
public string? ProductName { get; set; }
public string? ProductLink { get; set; }
public string? UserId { get; set; }
public bool IsActive { get; set; }
}
}

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities.Maintenance
{
[Table("Territories")]
public class Territories
{
[Key]
public int TerritoryId { get; set; }
public string? TerritoryName { get; set; }
public int DistrictId { get; set; }
[ForeignKey("DistrictId")]
public Districts? Districts { get; set; }
public bool IsActive { get; set; }
}
}

View File

@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities.Planning
{
[Table("CatchUpCalls")]
public class CatchUpCall
{
[Key]
public Guid CatchUpCallId { get; set; }
public Guid MRRawPlanId { get; set; }
public Guid? MRPlanDetailId { get; set; }
public byte? VisitCount { get; set; }
public DateOnly? CallDate { get; set; }
public DateOnly? ActualCallDate { get; set; }
public DateTime? StartTime { get; set; }
public DateTime? EndTime { get; set; }
public string? Longitude { get; set; }
public string? Latitude { get; set; }
public string? Location { get; set; }
public bool IsSignature { get; set; } = false;
public string? SignatureFileName { get; set; }
public bool IsMissed { get; set; } = false;
public string? MissedReason { get; set; }
public bool IsDoctorProxy { get; set; } = false;
public bool IsReschedule { get; set; } = false;
public string? RescheduleReason { get; set; }
public string? CallType { get; set; }
public bool IsJoinCall { get; set; } = false;
public byte? CycleId { get; set; }
public bool IsApproved { get; set; } = false;
public bool IsRecovered { get; set; } = false;
public bool IsLiterature { get; set; }
public bool IsCatchUpCall { get; set; }
public string? SLP { get; set; }
public byte Status { get; set; }
}
}

View File

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities.Planning
{
public class DoctorById
{
[Key]
public string? MRRawPlanId { get; set; }
public Guid MRPlanDetailId { get; set; }
public int DoctorId { get; set; }
public string? MRFullName { get; set; }
public string? InstitutionName { get; set; }
public Guid MRPlanHeaderId { get; set; }
public string? MRPlanDate { get; set; }
public string? DivisionName { get; set; }
public int DivisionId { get; set; }
public string? FirstName { get; set; }
public string? MiddleInitial { get; set; }
public string? LastName { get; set; }
public string? TerritoryName { get; set; }
public string? SpecializationName { get; set; }
public bool IsLiterature { get; set; }
public bool IsApproved { get; set; }
public byte MaxVisit { get; set; }
public string? Status { get; set; }
}
}

View File

@ -0,0 +1,30 @@
using LSFE.Infrastructure.Entities.Doctor;
using LSFE.Infrastructure.Entities.Maintenance;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities.Planning
{
public class DoctorWithPlan
{
[Key]
public int DoctorId { get; set; }
public string? MRFullName { get; set; }
public string? InstitutionName { get; set; }
public Guid MRPlanHeaderId { get; set; }
public Guid MRPlanDetailId { get; set; }
public string? MRPlanDate { get; set; }
public int TerritoryId { get; set; }
public string? TerritoryName { get; set; }
public string? FirstName { get; set; }
public string? MiddleInitial { get; set; }
public string? LastName { get; set; }
public string? SpecializationName { get; set; }
public byte MaxVisit { get; set; }
public string? Status { get; set; }
}
}

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities.Planning
{
[Table("MRPlanDetails")]
public class MRPlanDetail
{
[Key]
public Guid MRPlanDetailId { get; set; }
public Guid MRPlanHeaderId { get; set; }
public byte MaxVisit { get; set; }
public int DoctorId { get; set; }
public int TerritoryId { get; set; }
public int DivisionId { get; set; }
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities.Planning
{
[Table("MRPlanHeader")]
public class MRPlanHeader
{
[Key]
public Guid MRPlanHeaderId { get; set; }
public string? MRUserId { get; set; }
public bool IsActive { get; set; }
}
}

View File

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Reflection.Metadata;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities.Planning
{
[Table("MRRawPlans")]
public class MRRawPlan
{
[Key]
public Guid MRRawPlanId { get; set; }
public Guid? MRPlanDetailId { get; set; }
public byte? VisitCount { get; set; }
public DateOnly? CallDate { get; set; }
public DateOnly? ActualCallDate { get; set; }
public DateTime? StartTime { get; set; }
public DateTime? EndTime { get; set; }
public string? Longitude { get; set; }
public string? Latitude { get; set; }
public string? Location { get; set; }
public bool IsSignature { get; set; } = false;
public string? SignatureFileName { get; set; }
public bool IsMissed { get; set; } = false;
public string? MissedReason { get; set; }
public bool IsDoctorProxy { get; set; } = false;
public bool IsReschedule { get; set; } = false;
public string? RescheduleReason { get; set; }
public string? CallType { get; set; }
public bool IsJoinCall { get; set; } = false;
public byte? CycleId { get; set; }
public bool IsApproved { get; set; } = false;
public bool IsRecovered { get; set; } = false;
public bool IsLiterature { get; set; }
public string? SLP { get; set; }
public string? ApprovedBy { get; set; }
public DateTime? ApprovedDate { get; set; }
public byte Status { get; set; }
public bool IsCatchUpCall { get; set; }
}
}

View File

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities.Planning
{
public class PendingApproval
{
[Key]
public Guid MRRawPlanId { get; set; }
public Guid MRPlanDetailId { get; set; }
public int DoctorId { get; set; }
public string? MRFullName { get; set; }
public string? InstitutionName { get; set; }
public Guid MRPlanHeaderId { get; set; }
public string? MRPlanDate { get; set; }
public string? DivisionName { get; set; }
public int DivisionId { get; set; }
public string? TerritoryName { get; set; }
public string? FirstName { get; set; }
public string? MiddleInitial { get; set; }
public string? LastName { get; set; }
public string? SpecializationName { get; set; }
public byte MaxVisit { get; set; }
public string? Status { get; set; }
}
}

View File

@ -0,0 +1,43 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities.Reports
{
[Keyless]
public class ActualCoverageReport
{
public Guid MRRawPlanId { get; set; }
public Guid? MRPlanDetailId { get; set; }
public DateOnly? CallDate { get; set; }
public DateOnly? ActualCallDate { get; set; }
public DateTime? StartTime { get; set; }
public DateTime? EndTime { get; set; }
public string? Longitude { get; set; }
public string? Latitude { get; set; }
public string? Location { get; set; }
public bool IsSignature { get; set; } = false;
public string? SignatureFileName { get; set; }
public bool IsMissed { get; set; } = false;
public string? MissedReason { get; set; }
public bool IsDoctorProxy { get; set; } = false;
public bool IsReschedule { get; set; } = false;
public string? RescheduleReason { get; set; }
public string? CallType { get; set; }
public bool IsApproved { get; set; } = false;
public bool IsRecovered { get; set; } = false;
public bool IsLiterature { get; set; }
public string? SLP { get; set; }
public string? ApprovedBy { get; set; }
public DateTime? ApprovedDate { get; set; }
public bool IsCatchUpCall { get; set; }
public string? MedRepName { get; set; }
public string? Company { get; set; }
public string? DoctorName { get; set; }
public string? TerritoryName { get; set; }
}
}

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities.Reports
{
public class CallRateReport
{
[Key]
public required string MRUserId { get; set; }
public string? MedRepName { get; set; }
public string? Company { get; set; }
public int TotalTargetCall { get; set; }
public int TotalActualCallRate { get; set; }
public int TotalMissedCall { get; set; }
public int TotalReschedule { get; set; }
public decimal TotalCallRatePercentage { get; set; }
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities.Reports
{
public class CallReachReport
{
[Key]
public required string MRUserId { get; set; }
public string? MedRepName { get; set; }
public string? Company { get; set; }
public int TotalTargetCall { get; set; }
public int TotalActualCallReach { get; set; }
public decimal TotalCallReachPercentage { get; set; }
}
}

View File

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities.Reports
{
public class CatchUpCallReport
{
[Key]
public Guid CatchUpCallId { get; set; }
public Guid MRRawPlanId { get; set; }
public Guid? MRPlanDetailId { get; set; }
public byte? VisitCount { get; set; }
public DateOnly? ActualCallDate { get; set; }
public DateTime? StartTime { get; set; }
public DateTime? EndTime { get; set; }
public string? Longitude { get; set; }
public string? Latitude { get; set; }
public string? Location { get; set; }
public string? MedRepName { get; set; }
public string? DoctorName { get; set; }
public string? Company { get; set; }
public string? MissedReason { get; set; }
public DateOnly OriginalCallDate { get; set; }
public DateOnly CatchUpCallDate { get; set; }
}
}

View File

@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace LSFE.Infrastructure.Entities.TimeKeeping
{
[Table("Attendance")]
public class Attendance
{
[Key]
public int AttendanceId { get; set; }
public string? AppAttendanceId { get; set; }
public DateTime? TimeIn { get; set; }
public DateTime? TimeOut { get; set; }
public string? UserId { get; set; }
public string? TimeInLocation { get; set; }
public string? TimeOutLocation { get; set; }
public string? SignatureFileNameIn { get; set; }
public string? SignatureFileNameOut { get; set; }
}
}

View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Entities.TimeKeeping
{
public class AttendanceDto
{
[Key]
public int AttendanceId { get; set; }
public string? AppAttendanceId { get; set; }
public DateTime? TimeIn { get; set; }
public DateTime? TimeOut { get; set; }
public string? UserId { get; set; }
public string? TimeInLocation { get; set; }
public string? TimeOutLocation { get; set; }
public string? SignatureFileNameIn { get; set; }
public string? SignatureFileNameOut { get; set; }
public string? MedRepName { get; set; }
}
}

View File

@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="8.0.23" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.20" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.20" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.20">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.20" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.20">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Features" Version="8.0.23" />
</ItemGroup>
<ItemGroup>
<Folder Include="Dto\Planning\Response\" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,363 @@
// <auto-generated />
using System;
using LSFE.Infrastructure.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace LSFE.Infrastructure.Migrations
{
[DbContext(typeof(LSFEDbContext))]
[Migration("20250920072412_iniitialCreate")]
partial class iniitialCreate
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.20")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("LSFE.Infrastructure.Entities.AppsUsers", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("Address")
.HasColumnType("nvarchar(max)");
b.Property<int?>("AttachmentId")
.HasColumnType("int");
b.Property<string>("Company")
.HasColumnType("nvarchar(max)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("CreatedBy")
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("CreatedDate")
.HasColumnType("datetime2");
b.Property<int?>("DepartmentId")
.HasColumnType("int");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("bit");
b.Property<string>("FullName")
.HasColumnType("nvarchar(max)");
b.Property<bool>("LockoutEnabled")
.HasColumnType("bit");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetimeoffset");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("PhoneNumber")
.HasColumnType("nvarchar(max)");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("bit");
b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("bit");
b.Property<string>("UpdatedBy")
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("UpdatedDate")
.HasColumnType("datetime2");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("AttachmentId");
b.HasIndex("DepartmentId");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("Users", (string)null);
});
modelBuilder.Entity("LSFE.Infrastructure.Entities.Attachment", b =>
{
b.Property<int>("AttachmentId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("AttachmentId"));
b.Property<string>("FileName")
.HasColumnType("nvarchar(max)");
b.Property<string>("URL")
.HasColumnType("nvarchar(max)");
b.HasKey("AttachmentId");
b.ToTable("Attachment", (string)null);
});
modelBuilder.Entity("LSFE.Infrastructure.Entities.Departments", b =>
{
b.Property<int>("DepartmentId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("DepartmentId"));
b.Property<string>("Department")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsActive")
.HasColumnType("bit");
b.HasKey("DepartmentId");
b.ToTable("Departments", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("Roles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("UserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderKey")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("RoleId")
.HasColumnType("nvarchar(450)");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("UserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("Name")
.HasColumnType("nvarchar(450)");
b.Property<string>("Value")
.HasColumnType("nvarchar(max)");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("UserTokens", (string)null);
});
modelBuilder.Entity("LSFE.Infrastructure.Entities.AppsUsers", b =>
{
b.HasOne("LSFE.Infrastructure.Entities.Attachment", "Attachment")
.WithMany()
.HasForeignKey("AttachmentId");
b.HasOne("LSFE.Infrastructure.Entities.Departments", "Department")
.WithMany()
.HasForeignKey("DepartmentId");
b.Navigation("Attachment");
b.Navigation("Department");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("LSFE.Infrastructure.Entities.AppsUsers", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("LSFE.Infrastructure.Entities.AppsUsers", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LSFE.Infrastructure.Entities.AppsUsers", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("LSFE.Infrastructure.Entities.AppsUsers", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,287 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LSFE.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class iniitialCreate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Attachment",
columns: table => new
{
AttachmentId = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
URL = table.Column<string>(type: "nvarchar(max)", nullable: true),
FileName = table.Column<string>(type: "nvarchar(max)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Attachment", x => x.AttachmentId);
});
migrationBuilder.CreateTable(
name: "Departments",
columns: table => new
{
DepartmentId = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Department = table.Column<string>(type: "nvarchar(max)", nullable: true),
IsActive = table.Column<bool>(type: "bit", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Departments", x => x.DepartmentId);
});
migrationBuilder.CreateTable(
name: "Roles",
columns: table => new
{
Id = table.Column<string>(type: "nvarchar(450)", nullable: false),
Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
NormalizedName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
ConcurrencyStamp = table.Column<string>(type: "nvarchar(max)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Roles", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Users",
columns: table => new
{
Id = table.Column<string>(type: "nvarchar(450)", nullable: false),
FullName = table.Column<string>(type: "nvarchar(max)", nullable: true),
Company = table.Column<string>(type: "nvarchar(max)", nullable: true),
DepartmentId = table.Column<int>(type: "int", nullable: true),
CreatedDate = table.Column<DateTime>(type: "datetime2", nullable: false),
UpdatedDate = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatedBy = table.Column<string>(type: "nvarchar(max)", nullable: true),
UpdatedBy = table.Column<string>(type: "nvarchar(max)", nullable: true),
Address = table.Column<string>(type: "nvarchar(max)", nullable: true),
AttachmentId = table.Column<int>(type: "int", nullable: true),
UserName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
NormalizedUserName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
Email = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
NormalizedEmail = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
EmailConfirmed = table.Column<bool>(type: "bit", nullable: false),
PasswordHash = table.Column<string>(type: "nvarchar(max)", nullable: true),
SecurityStamp = table.Column<string>(type: "nvarchar(max)", nullable: true),
ConcurrencyStamp = table.Column<string>(type: "nvarchar(max)", nullable: true),
PhoneNumber = table.Column<string>(type: "nvarchar(max)", nullable: true),
PhoneNumberConfirmed = table.Column<bool>(type: "bit", nullable: false),
TwoFactorEnabled = table.Column<bool>(type: "bit", nullable: false),
LockoutEnd = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: true),
LockoutEnabled = table.Column<bool>(type: "bit", nullable: false),
AccessFailedCount = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Users", x => x.Id);
table.ForeignKey(
name: "FK_Users_Attachment_AttachmentId",
column: x => x.AttachmentId,
principalTable: "Attachment",
principalColumn: "AttachmentId");
table.ForeignKey(
name: "FK_Users_Departments_DepartmentId",
column: x => x.DepartmentId,
principalTable: "Departments",
principalColumn: "DepartmentId");
});
migrationBuilder.CreateTable(
name: "RoleClaims",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
RoleId = table.Column<string>(type: "nvarchar(450)", nullable: false),
ClaimType = table.Column<string>(type: "nvarchar(max)", nullable: true),
ClaimValue = table.Column<string>(type: "nvarchar(max)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_RoleClaims", x => x.Id);
table.ForeignKey(
name: "FK_RoleClaims_Roles_RoleId",
column: x => x.RoleId,
principalTable: "Roles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "UserLogins",
columns: table => new
{
LoginProvider = table.Column<string>(type: "nvarchar(450)", nullable: false),
ProviderKey = table.Column<string>(type: "nvarchar(450)", nullable: false),
ProviderDisplayName = table.Column<string>(type: "nvarchar(max)", nullable: true),
UserId = table.Column<string>(type: "nvarchar(450)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_UserLogins", x => new { x.LoginProvider, x.ProviderKey });
table.ForeignKey(
name: "FK_UserLogins_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "UserClaims",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
UserId = table.Column<string>(type: "nvarchar(450)", nullable: false),
ClaimType = table.Column<string>(type: "nvarchar(max)", nullable: true),
ClaimValue = table.Column<string>(type: "nvarchar(max)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_UserClaims", x => x.Id);
table.ForeignKey(
name: "FK_UserClaims_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "UserRoles",
columns: table => new
{
UserId = table.Column<string>(type: "nvarchar(450)", nullable: false),
RoleId = table.Column<string>(type: "nvarchar(450)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_UserRoles", x => new { x.UserId, x.RoleId });
table.ForeignKey(
name: "FK_UserRoles_Roles_RoleId",
column: x => x.RoleId,
principalTable: "Roles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_UserRoles_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "UserTokens",
columns: table => new
{
UserId = table.Column<string>(type: "nvarchar(450)", nullable: false),
LoginProvider = table.Column<string>(type: "nvarchar(450)", nullable: false),
Name = table.Column<string>(type: "nvarchar(450)", nullable: false),
Value = table.Column<string>(type: "nvarchar(max)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_UserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
table.ForeignKey(
name: "FK_UserTokens_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_RoleClaims_RoleId",
table: "RoleClaims",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "IX_UserLogins_UserId",
table: "UserLogins",
column: "UserId");
migrationBuilder.CreateIndex(
name: "RoleNameIndex",
table: "Roles",
column: "NormalizedName",
unique: true,
filter: "[NormalizedName] IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_UserClaims_UserId",
table: "UserClaims",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_UserRoles_RoleId",
table: "UserRoles",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "EmailIndex",
table: "Users",
column: "NormalizedEmail");
migrationBuilder.CreateIndex(
name: "IX_Users_AttachmentId",
table: "Users",
column: "AttachmentId");
migrationBuilder.CreateIndex(
name: "IX_Users_DepartmentId",
table: "Users",
column: "DepartmentId");
migrationBuilder.CreateIndex(
name: "UserNameIndex",
table: "Users",
column: "NormalizedUserName",
unique: true,
filter: "[NormalizedUserName] IS NOT NULL");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "RoleClaims");
migrationBuilder.DropTable(
name: "UserLogins");
migrationBuilder.DropTable(
name: "UserClaims");
migrationBuilder.DropTable(
name: "UserRoles");
migrationBuilder.DropTable(
name: "UserTokens");
migrationBuilder.DropTable(
name: "Roles");
migrationBuilder.DropTable(
name: "Users");
migrationBuilder.DropTable(
name: "Attachment");
migrationBuilder.DropTable(
name: "Departments");
}
}
}

View File

@ -0,0 +1,360 @@
// <auto-generated />
using System;
using LSFE.Infrastructure.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace LSFE.Infrastructure.Migrations
{
[DbContext(typeof(LSFEDbContext))]
partial class LSFEDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.20")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("LSFE.Infrastructure.Entities.AppsUsers", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("Address")
.HasColumnType("nvarchar(max)");
b.Property<int?>("AttachmentId")
.HasColumnType("int");
b.Property<string>("Company")
.HasColumnType("nvarchar(max)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("CreatedBy")
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("CreatedDate")
.HasColumnType("datetime2");
b.Property<int?>("DepartmentId")
.HasColumnType("int");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("bit");
b.Property<string>("FullName")
.HasColumnType("nvarchar(max)");
b.Property<bool>("LockoutEnabled")
.HasColumnType("bit");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetimeoffset");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("PhoneNumber")
.HasColumnType("nvarchar(max)");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("bit");
b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("bit");
b.Property<string>("UpdatedBy")
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("UpdatedDate")
.HasColumnType("datetime2");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("AttachmentId");
b.HasIndex("DepartmentId");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("Users", (string)null);
});
modelBuilder.Entity("LSFE.Infrastructure.Entities.Attachment", b =>
{
b.Property<int>("AttachmentId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("AttachmentId"));
b.Property<string>("FileName")
.HasColumnType("nvarchar(max)");
b.Property<string>("URL")
.HasColumnType("nvarchar(max)");
b.HasKey("AttachmentId");
b.ToTable("Attachment", (string)null);
});
modelBuilder.Entity("LSFE.Infrastructure.Entities.Departments", b =>
{
b.Property<int>("DepartmentId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("DepartmentId"));
b.Property<string>("Department")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsActive")
.HasColumnType("bit");
b.HasKey("DepartmentId");
b.ToTable("Departments", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("Roles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("UserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderKey")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("RoleId")
.HasColumnType("nvarchar(450)");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("UserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("Name")
.HasColumnType("nvarchar(450)");
b.Property<string>("Value")
.HasColumnType("nvarchar(max)");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("UserTokens", (string)null);
});
modelBuilder.Entity("LSFE.Infrastructure.Entities.AppsUsers", b =>
{
b.HasOne("LSFE.Infrastructure.Entities.Attachment", "Attachment")
.WithMany()
.HasForeignKey("AttachmentId");
b.HasOne("LSFE.Infrastructure.Entities.Departments", "Department")
.WithMany()
.HasForeignKey("DepartmentId");
b.Navigation("Attachment");
b.Navigation("Department");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("LSFE.Infrastructure.Entities.AppsUsers", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("LSFE.Infrastructure.Entities.AppsUsers", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LSFE.Infrastructure.Entities.AppsUsers", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("LSFE.Infrastructure.Entities.AppsUsers", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Model.Admin
{
public class AdminVerificationRequest
{
public string? AdminUserName { get; set; }
public string? AdminPassword { get; set; }
}
public class RevokeDeviceRequest
{
public string? AdminPassword { get; set; }
public string? UserId { get; set; }
public string? DeviceId { get; set; }
public string? Reason { get; set; }
}
/*public class UserDevice
{
public int Id { get; set; }
public string? UserId { get; set; }
public string? DeviceId { get; set; }
public string? DeviceModel { get; set; }
public string? DevicePlatform { get; set; }
public string? DeviceVersion { get; set; }
public string? DeviceManufacturer { get; set; }
public string? AppVersion { get; set; }
public bool IsAuthorized { get; set; }
public bool IsActive { get; set; }
public DateTime RegisteredDate { get; set; }
public string? RegisteredBy { get; set; }
public DateTime? LastLoginDate { get; set; }
public DateTime? DeactivatedDate { get; set; }
public string? DeactivatedBy { get; set; }
public string? DeactivationReason { get; set; }
public string? Notes { get; set; }
}*/
}

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Model.Admin
{
public class AdminVerificationResponse
{
public bool Success { get; set; }
public string? Data { get; set; }
public string? Message { get; set; }
public int MessCode { get; set; }
}
}

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Model.Admin
{
public class DeviceInfo
{
public string? DeviceId { get; set; }
public string? DeviceModel { get; set; }
public string? Platform { get; set; }
public string? Version { get; set; }
public string? Manufacturer { get; set; }
public string? Name { get; set; }
public string? DeviceType { get; set; }
public string? VersionString { get; set; }
public string? UserName { get; set; }
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Model.Admin
{
public class DeviceRegistrationRequest
{
public string? MedRepUsername { get; set; }
public string? MedRepPassword { get; set; }
public string? DeviceId { get; set; }
public string? DeviceModel { get; set; }
public string? DevicePlatform { get; set; }
public string? DeviceVersion { get; set; }
public string? DeviceManufacturer { get; set; }
public string? AppVersion { get; set; }
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Model.Admin
{
public class DeviceRegistrationResponse
{
public bool Success { get; set; }
public string? Message { get; set; }
public string? UserId { get; set; }
public string? DeviceId { get; set; }
public DateTime RegisteredDate { get; set; }
public HttpStatusCode StatusCode { get; set; }
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Model
{
public class ExcelUploadResponse : Response
{
public int TotalRows { get; set; }
public int ProcessedRows { get; set; }
public int SkippedRows { get; set; }
public List<string> Errors { get; set; } = new List<string>();
public List<InventorySummary> InsertedItems { get; set; } = new List<InventorySummary>();
}
}

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Model
{
public class InventorySummary
{
public string? Brand { get; set; }
public int TotalQty { get; set; }
public string? MedRepName { get; set; }
}
}

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Model
{
public class Response
{
public object? Data { get; set; }
public bool Success { get; set; }
public string? Message { get; set; }
public byte MessCode { get; set; }
}
}

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LSFE.Infrastructure.Model
{
public class Users
{
public string? UserName { get; set; }
public string? Password { get; set; }
}
}

22
LSFE.MobApp/App.xaml Normal file
View File

@ -0,0 +1,22 @@
<?xml version = "1.0" encoding = "UTF-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:converters="clr-namespace:LSFE.MobApp.Converters"
xmlns:local="clr-namespace:LSFE.MobApp"
x:Class="LSFE.MobApp.App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
<converters:InverseBoolConverter x:Key="InverseBoolConverter" />
<converters:InverseBoolConverter x:Key="IsNotNullConverter" />
<converters:InverseBoolConverter x:Key="IsNotNullOrEmptyConverter" />
<converters:InverseBoolConverter x:Key="ColorToLightColorConverter" />
</ResourceDictionary>
</Application.Resources>
</Application>

Some files were not shown because too many files have changed in this diff Show More