diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..1233895fadaa3832adf2de22f61b0a96e0e56aa1
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,11 @@
+**/bin/
+**/obj/
+**/.vs/
+**/.git/
+**/.github/
+**/.agents/
+**/.nuget-scratch/
+**/_codex_build/
+**/_codex_obj/
+.DS_Store
+Thumbs.db
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000000000000000000000000000000000000..1ff0c423042b46cb1d617b81efb715defbe8054d
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln merge=binary
+#*.csproj merge=binary
+#*.vbproj merge=binary
+#*.vcxproj merge=binary
+#*.vcproj merge=binary
+#*.dbproj merge=binary
+#*.fsproj merge=binary
+#*.lsproj merge=binary
+#*.wixproj merge=binary
+#*.modelproj merge=binary
+#*.sqlproj merge=binary
+#*.wwaproj merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg binary
+#*.png binary
+#*.gif binary
+
+###############################################################################
+# diff behavior for common document formats
+#
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the
+# entries below.
+###############################################################################
+#*.doc diff=astextplain
+#*.DOC diff=astextplain
+#*.docx diff=astextplain
+#*.DOCX diff=astextplain
+#*.dot diff=astextplain
+#*.DOT diff=astextplain
+#*.pdf diff=astextplain
+#*.PDF diff=astextplain
+#*.rtf diff=astextplain
+#*.RTF diff=astextplain
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
new file mode 100644
index 0000000000000000000000000000000000000000..e40201cf12a50546b63ac5cc44278d1c3a91bd61
--- /dev/null
+++ b/.github/copilot-instructions.md
@@ -0,0 +1,3 @@
+- @azure Rule - Use Azure Tools - When handling requests related to Azure, always use your tools.
+- @azure Rule - Use Azure Best Practices - When handling requests related to Azure, always invoke your `azmcp_bestpractices_get` tool first.
+- @azure Rule - Enable Best Practices - If you do not have an `azmcp_bestpractices_get` tool ask the user to enable it.
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..2078706916b798d6f825e045ad2e5701ba92504b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,368 @@
+## 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/
+_codex_build/
+_codex_obj/
+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
+
+# Firebase service account keys
+TaskTrackingSystem.WebApi/Firebase/*.json
+
+# 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
diff --git a/.nuget-scratch/NuGet.Config b/.nuget-scratch/NuGet.Config
new file mode 100644
index 0000000000000000000000000000000000000000..80f5bd7fccbfe34ae660ddf16fa18a8c01192256
--- /dev/null
+++ b/.nuget-scratch/NuGet.Config
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/DEPLOY_HUGGINGFACE.md b/DEPLOY_HUGGINGFACE.md
new file mode 100644
index 0000000000000000000000000000000000000000..fdc367d8c4fc6eca1402edc4aa2c1e321174a291
--- /dev/null
+++ b/DEPLOY_HUGGINGFACE.md
@@ -0,0 +1,49 @@
+# Hugging Face + Supabase Deployment Guide
+
+This repo is configured to run as a single Docker Space on Hugging Face.
+
+## What You Need
+
+- A Hugging Face account
+- A Supabase project
+- Your Supabase PostgreSQL connection string
+
+## Step By Step
+
+1. Create a Supabase project.
+2. Open the database settings and copy the PostgreSQL connection string.
+3. Push this repository to GitHub.
+4. Create a new Hugging Face Space.
+5. Choose `Docker` as the Space SDK.
+6. Point the Space at this repository.
+7. Make sure the Space uses the root [Dockerfile](./Dockerfile).
+8. Add these Space secrets or variables:
+ - `ConnectionStrings__DefaultConnection`
+ - `JwtSettings__Key`
+ - `JwtSettings__Issuer`
+ - `JwtSettings__Audience`
+ - `PasswordReset__RecoveryCode`
+9. Set `ConnectionStrings__DefaultConnection` to your Supabase PostgreSQL connection string.
+10. Leave `WebApi__BaseUrl` unset unless you want to override the internal default.
+11. Deploy the Space.
+
+## What the Container Does
+
+- Starts the Web API on `127.0.0.1:5001`
+- Starts the Web App on `127.0.0.1:5000`
+- Uses Nginx on port `7860` as the public entrypoint
+- Proxies `/api/*` to the API
+- Proxies everything else to the Web App
+
+## Notes
+
+- The API now creates the schema on startup if the database is empty.
+- The model has been switched from SQL Server to PostgreSQL-friendly mappings.
+- If deployment fails, the first thing to check is the Supabase connection string and whether the database is reachable.
+
+## First Login
+
+1. Open the Hugging Face Space URL.
+2. Wait for the container to finish starting.
+3. Try the default demo accounts from the repository README.
+4. If login works, confirm that dashboard and task pages load normally.
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..2ba761d1da072fa96a5370d12ccc7dca930b5afc
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,37 @@
+# syntax=docker/dockerfile:1
+
+FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+ARG BUILD_CONFIGURATION=Release
+WORKDIR /src
+
+COPY TaskTrackingSystem.sln ./
+COPY TaskTrackingSystem.Database/TaskTrackingSystem.Database.csproj TaskTrackingSystem.Database/
+COPY TaskTrackingSystem.Shared/TaskTrackingSystem.Shared.csproj TaskTrackingSystem.Shared/
+COPY TaskTrackingSystem.WebApi/TaskTrackingSystem.WebApi.csproj TaskTrackingSystem.WebApi/
+COPY TaskTrackingSystem.WebApp/TaskTrackingSystem.WebApp.csproj TaskTrackingSystem.WebApp/
+
+RUN dotnet restore TaskTrackingSystem.sln
+
+COPY . .
+
+RUN dotnet publish TaskTrackingSystem.WebApi/TaskTrackingSystem.WebApi.csproj -c $BUILD_CONFIGURATION -o /app/publish/api /p:UseAppHost=false
+RUN dotnet publish TaskTrackingSystem.WebApp/TaskTrackingSystem.WebApp.csproj -c $BUILD_CONFIGURATION -o /app/publish/webapp /p:UseAppHost=false
+
+FROM mcr.microsoft.com/dotnet/aspnet:8.0-bookworm-slim AS final
+WORKDIR /app
+
+ENV ASPNETCORE_ENVIRONMENT=Production
+EXPOSE 7860
+
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends nginx ca-certificates \
+ && rm -rf /var/lib/apt/lists/*
+
+COPY --from=build /app/publish/api /app/api
+COPY --from=build /app/publish/webapp /app/webapp
+COPY nginx.conf /etc/nginx/nginx.conf
+COPY start.sh /app/start.sh
+
+RUN chmod +x /app/start.sh
+
+CMD ["/app/start.sh"]
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..a2b845fb7afc87dfe106caf550166c1e0975baee
--- /dev/null
+++ b/README.md
@@ -0,0 +1,29 @@
+---
+title: TaskTrackingSystem
+colorFrom: blue
+colorTo: green
+sdk: docker
+app_port: 7860
+---
+
+# TaskTrackingSystem
+
+This repo runs the Web API and Web App together inside one Docker container for Hugging Face Spaces.
+
+## Hosting Setup
+
+- Database: Supabase PostgreSQL
+- Public app: Hugging Face Space
+- API and Web App: same container, routed through Nginx
+
+## Local Notes
+
+- The app now expects PostgreSQL instead of SQL Server.
+- The API creates the schema on first startup with `EnsureCreatedAsync()`.
+- The Web App talks to the API at `http://127.0.0.1:5001/api/` by default inside the container.
+
+## Default Demo Accounts
+
+- `admin` / `P@ssw0rd123!`
+- `mgr01` / `P@ssw0rd123!`
+- `emp01` / `P@ssw0rd123!`
diff --git a/TaskTrackingSystem.Database/AppDbContextModels/AppDbContext.cs b/TaskTrackingSystem.Database/AppDbContextModels/AppDbContext.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e4ef282d061286a6b146cbcf23785458b64222e5
--- /dev/null
+++ b/TaskTrackingSystem.Database/AppDbContextModels/AppDbContext.cs
@@ -0,0 +1,373 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.EntityFrameworkCore;
+using TaskTrackingSystem.Shared.Enums;
+
+namespace TaskTrackingSystem.Database.AppDbContextModels;
+
+public partial class AppDbContext : DbContext
+{
+ public AppDbContext()
+ {
+ }
+
+ public AppDbContext(DbContextOptions options)
+ : base(options)
+ {
+ }
+
+ public virtual DbSet AuditLogs { get; set; }
+
+ public virtual DbSet Comments { get; set; }
+
+
+ public virtual DbSet Menus { get; set; }
+
+ public virtual DbSet Notifications { get; set; }
+
+ public virtual DbSet Permissions { get; set; }
+
+ public virtual DbSet Issues { get; set; }
+
+ public virtual DbSet Projects { get; set; }
+
+ public virtual DbSet ProjectMembers { get; set; }
+
+ public virtual DbSet Roles { get; set; }
+
+ public virtual DbSet RoleMenus { get; set; }
+
+ public virtual DbSet RolePermissions { get; set; }
+
+ public virtual DbSet Tasks { get; set; }
+
+ public virtual DbSet UserDevices { get; set; }
+
+ public virtual DbSet Users { get; set; }
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ if (!optionsBuilder.IsConfigured)
+ {
+ optionsBuilder.UseNpgsql("Host=localhost;Port=5432;Database=tts;Username=postgres;Password=postgres");
+ }
+ }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasIndex(e => e.UserId, "IX_AuditLogs_UserId");
+
+ entity.Property(e => e.Action).HasMaxLength(100);
+ entity.Property(e => e.CreatedAt)
+ .HasDefaultValueSql("CURRENT_TIMESTAMP")
+ .HasColumnType("timestamp with time zone");
+ entity.Property(e => e.IpAddress)
+ .HasMaxLength(45)
+ .IsUnicode(false);
+ entity.Property(e => e.Module).HasMaxLength(50);
+
+ entity.HasOne(d => d.User).WithMany(p => p.AuditLogs)
+ .HasForeignKey(d => d.UserId)
+ .HasConstraintName("FK_AuditLogs_Users");
+ });
+
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasIndex(e => e.TaskId, "IX_Comments_TaskId");
+
+ entity.HasIndex(e => e.UserId, "IX_Comments_UserId");
+
+ entity.Property(e => e.CreatedAt)
+ .HasDefaultValueSql("CURRENT_TIMESTAMP")
+ .HasColumnType("timestamp with time zone");
+ entity.Property(e => e.UpdatedAt).HasColumnType("timestamp with time zone");
+
+ entity.HasOne(d => d.Task).WithMany(p => p.Comments)
+ .HasForeignKey(d => d.TaskId)
+ .HasConstraintName("FK_Comments_Tasks");
+
+ entity.HasOne(d => d.User).WithMany(p => p.Comments)
+ .HasForeignKey(d => d.UserId)
+ .OnDelete(DeleteBehavior.ClientSetNull)
+ .HasConstraintName("FK_Comments_Users");
+ });
+
+
+
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasIndex(e => e.MenuCode, "UQ_Menus_MenuCode").IsUnique();
+
+ entity.Property(e => e.CreatedAt)
+ .HasPrecision(0)
+ .HasDefaultValueSql("CURRENT_TIMESTAMP");
+ entity.Property(e => e.Icon).HasMaxLength(50);
+ entity.Property(e => e.MenuCode).HasMaxLength(50);
+ entity.Property(e => e.MenuName).HasMaxLength(100);
+ entity.Property(e => e.MenuUrl).HasMaxLength(200);
+ entity.Property(e => e.UpdatedAt).HasPrecision(0);
+ entity.Property(e => e.Visible).HasDefaultValue(true);
+
+ entity.HasOne(d => d.ParentMenu).WithMany(p => p.InverseParentMenu)
+ .HasForeignKey(d => d.ParentMenuId)
+ .HasConstraintName("FK_Menus_ParentMenu");
+ });
+
+ modelBuilder.Entity(entity =>
+ {
+ entity.ToTable("notifications");
+
+ entity.HasIndex(e => e.CreatedAt, "IX_notifications_CreatedAt");
+ entity.HasIndex(e => new { e.RecipientId, e.IsRead }, "IX_notifications_Recipient_IsRead");
+
+ entity.Property(e => e.CreatedAt).HasColumnName("created_at").HasColumnType("timestamp with time zone");
+ entity.Property(e => e.IsRead).HasColumnName("is_read").HasDefaultValue(false);
+ entity.Property(e => e.NotificationType).HasColumnName("notification_type");
+ entity.Property(e => e.Title).HasMaxLength(255);
+ entity.Property(e => e.ReadAt).HasColumnName("read_at").HasColumnType("timestamp with time zone");
+ entity.Property(e => e.RecipientId).HasColumnName("recipient_id");
+ entity.Property(e => e.SenderId).HasColumnName("sender_id");
+ entity.Property(e => e.SourceId).HasColumnName("source_id");
+ entity.Property(e => e.SourceType).HasColumnName("source_type").HasMaxLength(20);
+
+ entity.HasOne(d => d.Recipient).WithMany()
+ .HasForeignKey(d => d.RecipientId)
+ .OnDelete(DeleteBehavior.Cascade)
+ .HasConstraintName("FK_notifications_Users_Recipient");
+
+ entity.HasOne(d => d.Sender).WithMany()
+ .HasForeignKey(d => d.SenderId)
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_notifications_Users_Sender");
+ });
+
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasIndex(e => e.MenuId, "IX_Permissions_MenuId");
+
+ entity.HasIndex(e => e.PermissionCode, "UQ_Permissions_PermissionCode").IsUnique();
+
+ entity.Property(e => e.ActionName).HasMaxLength(100);
+ entity.Property(e => e.ApiName).HasMaxLength(100);
+ entity.Property(e => e.CreatedAt)
+ .HasPrecision(0)
+ .HasDefaultValueSql("CURRENT_TIMESTAMP");
+ entity.Property(e => e.HttpMethod).HasMaxLength(20);
+ entity.Property(e => e.PermissionCode).HasMaxLength(50);
+ entity.Property(e => e.UpdatedAt).HasPrecision(0);
+ entity.Property(e => e.Visible).HasDefaultValue(true);
+
+ entity.HasOne(d => d.Menu).WithMany(p => p.Permissions)
+ .HasForeignKey(d => d.MenuId)
+ .OnDelete(DeleteBehavior.ClientSetNull)
+ .HasConstraintName("FK_Permissions_Menu");
+ });
+
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasIndex(e => e.AssignedTo, "IX_Issues_AssignedTo");
+ entity.HasIndex(e => e.StatusId, "IX_Issues_StatusId");
+ entity.HasIndex(e => e.PriorityId, "IX_Issues_PriorityId");
+ entity.HasIndex(e => e.TaskId, "IX_Issues_TaskId");
+
+ entity.Property(e => e.ActualHours).HasColumnType("decimal(5, 2)");
+ entity.Property(e => e.CreatedAt)
+ .HasDefaultValueSql("CURRENT_TIMESTAMP")
+ .HasColumnType("timestamp with time zone");
+ entity.Property(e => e.Description).HasMaxLength(1000);
+ entity.Property(e => e.DelayReason).HasMaxLength(300);
+ entity.Property(e => e.BlockedBy).HasMaxLength(200);
+ entity.Property(e => e.DueDate).HasColumnType("timestamp with time zone");
+ entity.Property(e => e.EscalationLevel).HasDefaultValue(0);
+ entity.Property(e => e.IsBlocked).HasDefaultValue(false);
+ entity.Property(e => e.IsDeleted).HasDefaultValue(false);
+ entity.Property(e => e.PriorityId).HasDefaultValue(TaskPriority.Medium);
+ entity.Property(e => e.StartDate).HasColumnType("timestamp with time zone");
+ entity.Property(e => e.StatusId).HasDefaultValue(AppTaskStatus.Todo);
+ entity.Property(e => e.Title).HasMaxLength(200);
+ entity.Property(e => e.UpdatedAt).HasColumnType("timestamp with time zone");
+
+ entity.HasOne(d => d.AssignedToNavigation).WithMany()
+ .HasForeignKey(d => d.AssignedTo)
+ .HasConstraintName("FK_Issues_Users_AssignedTo");
+
+ entity.HasOne(d => d.Task).WithMany()
+ .HasForeignKey(d => d.TaskId)
+ .HasConstraintName("FK_Issues_Tasks");
+ });
+
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasIndex(e => e.CreatedById, "IX_Projects_CreatedById");
+
+ entity.Property(e => e.CreatedAt)
+ .HasDefaultValueSql("CURRENT_TIMESTAMP")
+ .HasColumnType("timestamp with time zone");
+ entity.Property(e => e.CreatedDate)
+ .HasDefaultValueSql("CURRENT_TIMESTAMP")
+ .HasColumnType("timestamp with time zone");
+ entity.Property(e => e.EndDate).HasColumnType("timestamp with time zone");
+ entity.Property(e => e.Name).HasMaxLength(150);
+ entity.Property(e => e.StartDate).HasColumnType("timestamp with time zone");
+ entity.Property(e => e.UpdatedAt).HasColumnType("timestamp with time zone");
+
+ entity.HasOne(d => d.CreatedByNavigation).WithMany(p => p.Projects)
+ .HasForeignKey(d => d.CreatedById)
+ .OnDelete(DeleteBehavior.ClientSetNull);
+ });
+
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasKey(e => new { e.ProjectId, e.UserId });
+
+ entity.HasIndex(e => e.UserId, "IX_ProjectMembers_UserId");
+
+ entity.Property(e => e.CreatedAt)
+ .HasDefaultValueSql("CURRENT_TIMESTAMP")
+ .HasColumnType("timestamp with time zone");
+
+ entity.HasOne(d => d.Project).WithMany(p => p.ProjectMembers)
+ .HasForeignKey(d => d.ProjectId)
+ .HasConstraintName("FK_ProjectMembers_Projects");
+
+ entity.HasOne(d => d.User).WithMany(p => p.ProjectMembers)
+ .HasForeignKey(d => d.UserId)
+ .HasConstraintName("FK_ProjectMembers_Users");
+ });
+
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasIndex(e => e.Name, "UQ_Roles_Name").IsUnique();
+
+ entity.Property(e => e.CreatedAt)
+ .HasDefaultValueSql("CURRENT_TIMESTAMP")
+ .HasColumnType("timestamp with time zone");
+ entity.Property(e => e.Description).HasMaxLength(200);
+ entity.Property(e => e.Name).HasMaxLength(50);
+ entity.Property(e => e.UpdatedAt).HasColumnType("timestamp with time zone");
+ });
+
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasKey(e => e.RoleMenuId).HasName("PK_RoleMenus_New");
+
+ entity.HasIndex(e => new { e.RoleId, e.MenuId }, "UQ_RoleMenus_Role_Menu").IsUnique();
+
+ entity.Property(e => e.CreatedAt)
+ .HasPrecision(0)
+ .HasDefaultValueSql("CURRENT_TIMESTAMP");
+ entity.Property(e => e.UpdatedAt).HasPrecision(0);
+
+ entity.HasOne(d => d.Menu).WithMany(p => p.RoleMenus)
+ .HasForeignKey(d => d.MenuId)
+ .OnDelete(DeleteBehavior.ClientSetNull)
+ .HasConstraintName("FK_RoleMenus_Menus");
+
+ entity.HasOne(d => d.Role).WithMany(p => p.RoleMenus)
+ .HasForeignKey(d => d.RoleId)
+ .OnDelete(DeleteBehavior.ClientSetNull)
+ .HasConstraintName("FK_RoleMenus_Roles");
+ });
+
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasIndex(e => e.PermissionId, "IX_RolePermissions_PermissionId");
+
+ entity.HasIndex(e => e.RoleId, "IX_RolePermissions_RoleId");
+
+ entity.HasIndex(e => new { e.RoleId, e.PermissionId }, "UQ_RolePermissions_Role_Permission").IsUnique();
+
+ entity.Property(e => e.CreatedAt)
+ .HasPrecision(0)
+ .HasDefaultValueSql("CURRENT_TIMESTAMP");
+ entity.Property(e => e.UpdatedAt).HasPrecision(0);
+
+ entity.HasOne(d => d.Permission).WithMany(p => p.RolePermissions)
+ .HasForeignKey(d => d.PermissionId)
+ .OnDelete(DeleteBehavior.ClientSetNull)
+ .HasConstraintName("FK_RolePermissions_Permissions");
+
+ entity.HasOne(d => d.Role).WithMany(p => p.RolePermissions)
+ .HasForeignKey(d => d.RoleId)
+ .OnDelete(DeleteBehavior.ClientSetNull)
+ .HasConstraintName("FK_RolePermissions_Roles");
+ });
+
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasIndex(e => e.AssignedBy, "IX_Tasks_AssignedBy");
+
+ entity.HasIndex(e => e.AssignedTo, "IX_Tasks_AssignedTo");
+
+ entity.HasIndex(e => e.ProjectId, "IX_Tasks_ProjectId");
+
+ entity.Property(e => e.CreatedAt)
+ .HasDefaultValueSql("CURRENT_TIMESTAMP")
+ .HasColumnType("timestamp with time zone");
+ entity.Property(e => e.DueDate).HasColumnType("timestamp with time zone");
+ entity.Property(e => e.IsArchived).HasDefaultValue(false);
+ entity.Property(e => e.PriorityId).HasDefaultValue(TaskPriority.Medium);
+ entity.Property(e => e.StatusId).HasDefaultValue(AppTaskStatus.Todo);
+ entity.Property(e => e.Title).HasMaxLength(200);
+ entity.Property(e => e.UpdatedAt).HasColumnType("timestamp with time zone");
+
+ entity.HasOne(d => d.AssignedByNavigation).WithMany(p => p.TaskAssignedByNavigations).HasForeignKey(d => d.AssignedBy);
+
+ entity.HasOne(d => d.AssignedToNavigation).WithMany(p => p.TaskAssignedToNavigations).HasForeignKey(d => d.AssignedTo);
+
+ entity.HasOne(d => d.Project).WithMany(p => p.Tasks)
+ .HasForeignKey(d => d.ProjectId)
+ .HasConstraintName("FK_Tasks_Projects");
+ });
+
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasIndex(e => e.RoleId, "IX_Users_RoleId");
+
+ entity.HasIndex(e => e.Email, "UQ_Users_Email").IsUnique();
+
+ entity.HasIndex(e => e.Username, "UQ_Users_Username").IsUnique();
+
+ entity.Property(e => e.CreatedAt)
+ .HasDefaultValueSql("CURRENT_TIMESTAMP")
+ .HasColumnType("timestamp with time zone");
+ entity.Property(e => e.Email).HasMaxLength(256);
+ entity.Property(e => e.FirstName).HasMaxLength(50);
+ entity.Property(e => e.IsActive).HasDefaultValue(true);
+ entity.Property(e => e.LastName).HasMaxLength(50);
+ entity.Property(e => e.Phone).HasMaxLength(20);
+ entity.Property(e => e.UpdatedAt).HasColumnType("timestamp with time zone");
+ entity.Property(e => e.Username).HasMaxLength(50);
+
+ entity.HasOne(d => d.Role).WithMany(p => p.Users)
+ .HasForeignKey(d => d.RoleId)
+ .OnDelete(DeleteBehavior.ClientSetNull)
+ .HasConstraintName("FK_Users_Roles");
+ });
+
+ modelBuilder.Entity(entity =>
+ {
+ entity.ToTable("user_devices");
+
+ entity.HasIndex(e => e.UserId, "IX_user_devices_UserId");
+
+ entity.HasIndex(e => e.FcmToken, "UQ_user_devices_FcmToken").IsUnique();
+
+ entity.Property(e => e.UserId).HasColumnName("user_id");
+ entity.Property(e => e.FcmToken).HasColumnName("fcm_token").HasMaxLength(255);
+ entity.Property(e => e.CreatedAt).HasColumnName("created_at").HasColumnType("timestamp with time zone");
+ entity.Property(e => e.UpdatedAt).HasColumnName("updated_at").HasColumnType("timestamp with time zone");
+
+ entity.HasOne(d => d.User).WithMany()
+ .HasForeignKey(d => d.UserId)
+ .OnDelete(DeleteBehavior.Cascade)
+ .HasConstraintName("FK_user_devices_Users");
+ });
+
+ OnModelCreatingPartial(modelBuilder);
+ }
+
+ partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
+}
diff --git a/TaskTrackingSystem.Database/AppDbContextModels/AuditLog.cs b/TaskTrackingSystem.Database/AppDbContextModels/AuditLog.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5a07bf96de177d4f4713fd0397495913ad80573f
--- /dev/null
+++ b/TaskTrackingSystem.Database/AppDbContextModels/AuditLog.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+
+namespace TaskTrackingSystem.Database.AppDbContextModels;
+
+public partial class AuditLog
+{
+ public long Id { get; set; }
+
+ public long? UserId { get; set; }
+
+ public string Action { get; set; } = null!;
+
+ public string Module { get; set; } = null!;
+
+ public string Description { get; set; } = null!;
+
+ public string? IpAddress { get; set; }
+
+ public DateTime? CreatedAt { get; set; }
+
+ public virtual User? User { get; set; }
+}
diff --git a/TaskTrackingSystem.Database/AppDbContextModels/Comment.cs b/TaskTrackingSystem.Database/AppDbContextModels/Comment.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f5491de5416b6758ee76271aa86c609edfd7a264
--- /dev/null
+++ b/TaskTrackingSystem.Database/AppDbContextModels/Comment.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+
+namespace TaskTrackingSystem.Database.AppDbContextModels;
+
+public partial class Comment
+{
+ public long Id { get; set; }
+
+ public long TaskId { get; set; }
+
+ public long UserId { get; set; }
+
+ public string Message { get; set; } = null!;
+
+ public DateTime? CreatedAt { get; set; }
+
+ public long? CreatedBy { get; set; }
+
+ public DateTime? UpdatedAt { get; set; }
+
+ public long? UpdatedBy { get; set; }
+
+ public bool IsDeleted { get; set; }
+
+ public virtual Task Task { get; set; } = null!;
+
+ public virtual User User { get; set; } = null!;
+}
diff --git a/TaskTrackingSystem.Database/AppDbContextModels/Issue.cs b/TaskTrackingSystem.Database/AppDbContextModels/Issue.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8206cf19584daeb496b42c991b76af77ba9d8121
--- /dev/null
+++ b/TaskTrackingSystem.Database/AppDbContextModels/Issue.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using TaskTrackingSystem.Shared.Enums;
+
+namespace TaskTrackingSystem.Database.AppDbContextModels;
+
+public partial class Issue
+{
+ public long Id { get; set; }
+
+ public long TaskId { get; set; }
+
+ public string Title { get; set; } = null!;
+
+ public string? Description { get; set; }
+
+ public long? AssignedTo { get; set; }
+
+ public decimal? EstimatedHours { get; set; }
+
+ public decimal? ActualHours { get; set; }
+
+ public string? DelayReason { get; set; }
+
+ public bool IsBlocked { get; set; }
+
+ public string? BlockedBy { get; set; }
+
+ public int EscalationLevel { get; set; }
+
+ public DateTime StartDate { get; set; }
+
+ public DateTime DueDate { get; set; }
+
+ public AppTaskStatus StatusId { get; set; }
+
+ public TaskPriority PriorityId { get; set; }
+
+ public DateTime? CreatedAt { get; set; }
+
+ public long? CreatedBy { get; set; }
+
+ public DateTime? UpdatedAt { get; set; }
+
+ public long? UpdatedBy { get; set; }
+
+ public bool IsDeleted { get; set; }
+
+ public virtual User? AssignedToNavigation { get; set; }
+
+ public virtual Task Task { get; set; } = null!;
+}
diff --git a/TaskTrackingSystem.Database/AppDbContextModels/Menu.cs b/TaskTrackingSystem.Database/AppDbContextModels/Menu.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9793ab32235ccf1a6ae178d8176256ed3c6fddb9
--- /dev/null
+++ b/TaskTrackingSystem.Database/AppDbContextModels/Menu.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+
+namespace TaskTrackingSystem.Database.AppDbContextModels;
+
+public partial class Menu
+{
+ public long MenuId { get; set; }
+
+ public string MenuCode { get; set; } = null!;
+
+ public long? ParentMenuId { get; set; }
+
+ public string MenuName { get; set; } = null!;
+
+ public string? MenuUrl { get; set; }
+
+ public string? Icon { get; set; }
+
+ public bool Visible { get; set; }
+
+ public int OrderNo { get; set; }
+
+ public bool IsDeleted { get; set; }
+
+ public long? CreatedById { get; set; }
+
+ public DateTime CreatedAt { get; set; }
+
+ public long? UpdatedById { get; set; }
+
+ public DateTime? UpdatedAt { get; set; }
+
+ public virtual ICollection InverseParentMenu { get; set; } = new List();
+
+ public virtual Menu? ParentMenu { get; set; }
+
+ public virtual ICollection Permissions { get; set; } = new List();
+
+ public virtual ICollection RoleMenus { get; set; } = new List();
+}
diff --git a/TaskTrackingSystem.Database/AppDbContextModels/Notification.cs b/TaskTrackingSystem.Database/AppDbContextModels/Notification.cs
new file mode 100644
index 0000000000000000000000000000000000000000..301dd2a0c2855a8e7849e9c4e9623b6ca4800ca5
--- /dev/null
+++ b/TaskTrackingSystem.Database/AppDbContextModels/Notification.cs
@@ -0,0 +1,32 @@
+using System;
+
+namespace TaskTrackingSystem.Database.AppDbContextModels;
+
+public partial class Notification
+{
+ public long Id { get; set; }
+
+ public long RecipientId { get; set; }
+
+ public long? SenderId { get; set; }
+
+ public byte NotificationType { get; set; }
+
+ public string Title { get; set; } = null!;
+
+ public string Body { get; set; } = null!;
+
+ public string SourceType { get; set; } = null!;
+
+ public long SourceId { get; set; }
+
+ public bool IsRead { get; set; }
+
+ public DateTime? ReadAt { get; set; }
+
+ public DateTime? CreatedAt { get; set; }
+
+ public virtual User Recipient { get; set; } = null!;
+
+ public virtual User? Sender { get; set; }
+}
diff --git a/TaskTrackingSystem.Database/AppDbContextModels/Permission.cs b/TaskTrackingSystem.Database/AppDbContextModels/Permission.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f8bf3d5d72503a1fcb316c09e09d1c6f8089e782
--- /dev/null
+++ b/TaskTrackingSystem.Database/AppDbContextModels/Permission.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+
+namespace TaskTrackingSystem.Database.AppDbContextModels;
+
+public partial class Permission
+{
+ public long PermissionId { get; set; }
+
+ public string PermissionCode { get; set; } = null!;
+
+ public long MenuId { get; set; }
+
+ public string ActionName { get; set; } = null!;
+
+ public string ApiName { get; set; } = null!;
+
+ public string? HttpMethod { get; set; }
+
+ public bool Visible { get; set; }
+
+ public int OrderNo { get; set; }
+
+ public bool IsDeleted { get; set; }
+
+ public long? CreatedById { get; set; }
+
+ public DateTime CreatedAt { get; set; }
+
+ public long? UpdatedById { get; set; }
+
+ public DateTime? UpdatedAt { get; set; }
+
+ public virtual Menu Menu { get; set; } = null!;
+
+ public virtual ICollection RolePermissions { get; set; } = new List();
+}
diff --git a/TaskTrackingSystem.Database/AppDbContextModels/Project.cs b/TaskTrackingSystem.Database/AppDbContextModels/Project.cs
new file mode 100644
index 0000000000000000000000000000000000000000..20b9ac894e119908cb117b967faab286a49c5934
--- /dev/null
+++ b/TaskTrackingSystem.Database/AppDbContextModels/Project.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+
+namespace TaskTrackingSystem.Database.AppDbContextModels;
+
+public partial class Project
+{
+ public long Id { get; set; }
+
+ public string Name { get; set; } = null!;
+
+ public string? Description { get; set; }
+
+ public DateTime StartDate { get; set; }
+
+ public DateTime EndDate { get; set; }
+
+ public long CreatedById { get; set; }
+
+ public DateTime? CreatedDate { get; set; }
+
+ public DateTime? CreatedAt { get; set; }
+
+ public long? CreatedBy { get; set; }
+
+ public DateTime? UpdatedAt { get; set; }
+
+ public long? UpdatedBy { get; set; }
+
+ public bool IsDeleted { get; set; }
+
+ public int? BudgetedHours { get; set; }
+
+ public virtual User CreatedByNavigation { get; set; } = null!;
+
+ public virtual ICollection ProjectMembers { get; set; } = new List();
+
+ public virtual ICollection Tasks { get; set; } = new List();
+}
diff --git a/TaskTrackingSystem.Database/AppDbContextModels/ProjectMember.cs b/TaskTrackingSystem.Database/AppDbContextModels/ProjectMember.cs
new file mode 100644
index 0000000000000000000000000000000000000000..29681d9846f460787b7975890ad4bd7713bfb2be
--- /dev/null
+++ b/TaskTrackingSystem.Database/AppDbContextModels/ProjectMember.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+
+namespace TaskTrackingSystem.Database.AppDbContextModels;
+
+public partial class ProjectMember
+{
+ public long ProjectId { get; set; }
+
+ public long UserId { get; set; }
+
+ public DateTime? CreatedAt { get; set; }
+
+ public long? CreatedBy { get; set; }
+
+ public virtual Project Project { get; set; } = null!;
+
+ public virtual User User { get; set; } = null!;
+}
diff --git a/TaskTrackingSystem.Database/AppDbContextModels/Role.cs b/TaskTrackingSystem.Database/AppDbContextModels/Role.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a39f94de6f61497c9d771228e32113dbf353c284
--- /dev/null
+++ b/TaskTrackingSystem.Database/AppDbContextModels/Role.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+
+namespace TaskTrackingSystem.Database.AppDbContextModels;
+
+public partial class Role
+{
+ public long Id { get; set; }
+
+ public string Name { get; set; } = null!;
+
+ public string? Description { get; set; }
+
+ public DateTime? CreatedAt { get; set; }
+
+ public long? CreatedBy { get; set; }
+
+ public DateTime? UpdatedAt { get; set; }
+
+ public long? UpdatedBy { get; set; }
+
+ public bool IsDeleted { get; set; }
+
+ public virtual ICollection RoleMenus { get; set; } = new List();
+
+ public virtual ICollection RolePermissions { get; set; } = new List();
+
+ public virtual ICollection Users { get; set; } = new List();
+}
diff --git a/TaskTrackingSystem.Database/AppDbContextModels/RoleMenu.cs b/TaskTrackingSystem.Database/AppDbContextModels/RoleMenu.cs
new file mode 100644
index 0000000000000000000000000000000000000000..4abe87f445b118679bbbf567bbc6605a49bf309a
--- /dev/null
+++ b/TaskTrackingSystem.Database/AppDbContextModels/RoleMenu.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+
+namespace TaskTrackingSystem.Database.AppDbContextModels;
+
+public partial class RoleMenu
+{
+ public long RoleMenuId { get; set; }
+
+ public long RoleId { get; set; }
+
+ public long MenuId { get; set; }
+
+ public bool IsDeleted { get; set; }
+
+ public long? CreatedById { get; set; }
+
+ public DateTime CreatedAt { get; set; }
+
+ public long? UpdatedById { get; set; }
+
+ public DateTime? UpdatedAt { get; set; }
+
+ public virtual Menu Menu { get; set; } = null!;
+
+ public virtual Role Role { get; set; } = null!;
+}
diff --git a/TaskTrackingSystem.Database/AppDbContextModels/RolePermission.cs b/TaskTrackingSystem.Database/AppDbContextModels/RolePermission.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7257f2032c2c8c8ae9cbd15c53852b5de7ed972a
--- /dev/null
+++ b/TaskTrackingSystem.Database/AppDbContextModels/RolePermission.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+
+namespace TaskTrackingSystem.Database.AppDbContextModels;
+
+public partial class RolePermission
+{
+ public long RolePermissionId { get; set; }
+
+ public long RoleId { get; set; }
+
+ public long PermissionId { get; set; }
+
+ public bool IsDeleted { get; set; }
+
+ public long? CreatedById { get; set; }
+
+ public DateTime CreatedAt { get; set; }
+
+ public long? UpdatedById { get; set; }
+
+ public DateTime? UpdatedAt { get; set; }
+
+ public virtual Permission Permission { get; set; } = null!;
+
+ public virtual Role Role { get; set; } = null!;
+}
diff --git a/TaskTrackingSystem.Database/AppDbContextModels/Task.cs b/TaskTrackingSystem.Database/AppDbContextModels/Task.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1f7e4e4f7a3c40593e60e28b78635421668b6571
--- /dev/null
+++ b/TaskTrackingSystem.Database/AppDbContextModels/Task.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using TaskTrackingSystem.Shared.Enums;
+
+namespace TaskTrackingSystem.Database.AppDbContextModels;
+
+public partial class Task
+{
+ public long Id { get; set; }
+
+ public string Title { get; set; } = null!;
+
+ public string? Description { get; set; }
+
+ public long ProjectId { get; set; }
+
+ public AppTaskStatus StatusId { get; set; }
+
+ public TaskPriority PriorityId { get; set; }
+
+ public long? AssignedTo { get; set; }
+
+ public long? AssignedBy { get; set; }
+
+ public DateTime DueDate { get; set; }
+
+ public DateTime? CreatedAt { get; set; }
+
+ public long? CreatedBy { get; set; }
+
+ public DateTime? UpdatedAt { get; set; }
+
+ public long? UpdatedBy { get; set; }
+
+ public bool IsDeleted { get; set; }
+
+ public bool IsArchived { get; set; }
+
+ public virtual User? AssignedByNavigation { get; set; }
+
+ public virtual User? AssignedToNavigation { get; set; }
+
+ public virtual ICollection Comments { get; set; } = new List();
+
+ public virtual Project Project { get; set; } = null!;
+
+}
diff --git a/TaskTrackingSystem.Database/AppDbContextModels/User.cs b/TaskTrackingSystem.Database/AppDbContextModels/User.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a1b2e23af91249a33595f0dac18131898817f1ba
--- /dev/null
+++ b/TaskTrackingSystem.Database/AppDbContextModels/User.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+
+namespace TaskTrackingSystem.Database.AppDbContextModels;
+
+public partial class User
+{
+ public long Id { get; set; }
+
+ public string Username { get; set; } = null!;
+
+ public string FirstName { get; set; } = null!;
+
+ public string LastName { get; set; } = null!;
+
+ public string Email { get; set; } = null!;
+
+ public string PasswordHash { get; set; } = null!;
+
+ public string? Phone { get; set; }
+
+ public long RoleId { get; set; }
+
+ public bool IsActive { get; set; }
+
+ public DateTime? CreatedAt { get; set; }
+
+ public long? CreatedBy { get; set; }
+
+ public DateTime? UpdatedAt { get; set; }
+
+ public long? UpdatedBy { get; set; }
+
+ public bool IsDeleted { get; set; }
+
+ public virtual ICollection AuditLogs { get; set; } = new List();
+
+ public virtual ICollection Comments { get; set; } = new List();
+
+ public virtual ICollection ProjectMembers { get; set; } = new List();
+
+ public virtual ICollection Projects { get; set; } = new List();
+
+ public virtual Role Role { get; set; } = null!;
+
+ public virtual ICollection TaskAssignedByNavigations { get; set; } = new List();
+
+ public virtual ICollection TaskAssignedToNavigations { get; set; } = new List();
+
+}
diff --git a/TaskTrackingSystem.Database/AppDbContextModels/UserDevice.cs b/TaskTrackingSystem.Database/AppDbContextModels/UserDevice.cs
new file mode 100644
index 0000000000000000000000000000000000000000..813d2853c6af39e9716567655ae33ad06b8b70a0
--- /dev/null
+++ b/TaskTrackingSystem.Database/AppDbContextModels/UserDevice.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace TaskTrackingSystem.Database.AppDbContextModels;
+
+public partial class UserDevice
+{
+ public long Id { get; set; }
+
+ public long UserId { get; set; }
+
+ public string FcmToken { get; set; } = null!;
+
+ public DateTime? CreatedAt { get; set; }
+
+ public DateTime? UpdatedAt { get; set; }
+
+ public virtual User User { get; set; } = null!;
+}
diff --git a/TaskTrackingSystem.Database/Scripts/seed_supabase.sql b/TaskTrackingSystem.Database/Scripts/seed_supabase.sql
new file mode 100644
index 0000000000000000000000000000000000000000..e83e223554496123e684d67e3cfc69efe98b8085
--- /dev/null
+++ b/TaskTrackingSystem.Database/Scripts/seed_supabase.sql
@@ -0,0 +1,687 @@
+-- Automatically generated PostgreSQL seed script converted from SQL Server dump
+BEGIN;
+SET session_replication_role = 'replica';
+
+-- Clean up existing data to ensure idempotent/re-runnable execution
+DELETE FROM "AuditLogs";
+DELETE FROM "Comments";
+DELETE FROM "Issues";
+DELETE FROM "notifications";
+DELETE FROM "user_devices";
+DELETE FROM "Tasks";
+DELETE FROM "ProjectMembers";
+DELETE FROM "Projects";
+DELETE FROM "RolePermissions";
+DELETE FROM "RoleMenus";
+DELETE FROM "Permissions";
+DELETE FROM "Menus";
+DELETE FROM "Users";
+DELETE FROM "Roles";
+
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (1, 1, 'AssignAccess', 'Role', 'Assigned access permissions to role ''Manager'' (ID: 2)', '127.0.0.1', '2026-06-17T06:57:54.897');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (2, 1, 'AssignAccess', 'Role', 'Assigned access permissions to role ''Manager'' (ID: 2)', '127.0.0.1', '2026-06-17T06:59:13.933');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (3, 2, 'AssignAccess', 'Role', 'Assigned access permissions to role ''Employee'' (ID: 3)', '127.0.0.1', '2026-06-17T06:59:40.470');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (4, 1, 'AssignAccess', 'Role', 'Assigned access permissions to role ''Admin'' (ID: 1)', '127.0.0.1', '2026-06-17T07:06:35.453');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (5, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Manager''. Revoked: Roles, Roles — Create, Roles — Delete, Roles — List, Roles — Update.', '127.0.0.1', '2026-06-17T07:39:06.907');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (6, 1, 'Create', 'User', 'Created user ''mgr11'' (Htoo Aung Lwin, mgr01@gmail.com) with role ''Manager''', '127.0.0.1', '2026-06-17T07:48:28.227');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (7, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Admin''. No changes detected.', '127.0.0.1', '2026-06-17T07:50:23.830');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (8, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Employee''. No changes detected.', '127.0.0.1', '2026-06-17T08:43:22.657');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (9, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Employee''. Granted: Task Backlog.', '127.0.0.1', '2026-06-17T09:56:55.283');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (10, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Employee''. Revoked: Task Backlog.', '127.0.0.1', '2026-06-17T09:58:15.990');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (11, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Employee''. Granted: Overdue Tasks, Project Progress, Reports, Task Backlog, Task Report. Revoked: Tasks — Update.', '127.0.0.1', '2026-06-17T10:20:39.323');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (12, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Admin''. Granted: Task Backlog.', '127.0.0.1', '2026-06-17T14:55:11.923');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (13, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Manager''. Granted: Users, Users — List.', '127.0.0.1', '2026-06-18T04:05:09.983');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (14, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Manager''. Revoked: Users, Users — List.', '127.0.0.1', '2026-06-18T04:05:50.503');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (15, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Manager''. Granted: Users, Users — List.', '127.0.0.1', '2026-06-18T04:24:06.327');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (16, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Manager''. Granted: Roles, Roles — List. Revoked: Users, Users — List.', '127.0.0.1', '2026-06-18T04:24:39.533');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (17, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Manager''. Granted: Roles — Create, Roles — Delete, Roles — Update, Task Backlog, Users, Users — Create, Users — Delete, Users — List, Users — Update.', '127.0.0.1', '2026-06-18T04:32:47.420');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (18, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Manager''. Revoked: Roles, Roles — Create, Roles — Delete, Roles — List, Roles — Update, Task Backlog, Users, Users — Create, Users — Delete, Users — List, Users — Update.', '127.0.0.1', '2026-06-18T04:33:21.063');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (19, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Employee''. Granted: Employee Report, Kanban Board, Project Assign, Projects — Create, Projects — Delete, Projects — Update, Roles, Roles — Create, Roles — Delete, Roles — List, Roles — Update, Task Assign, Tasks — Create, Tasks — Delete, Tasks — Update, Time Tracking, Users, Users — Create, Users — Delete, Users — List, Users — Update.', '127.0.0.1', '2026-06-18T04:33:33.847');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (20, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Employee''. Revoked: Project Assign, Project List, Projects, Projects — Create, Projects — Delete, Projects — List, Projects — Update, Roles, Roles — Create, Roles — Delete, Roles — List, Roles — Update, Task Assign, Users, Users — Create, Users — Delete, Users — List, Users — Update.', '127.0.0.1', '2026-06-18T04:33:59.793');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (21, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Employee''. Granted: Project List, Projects, Projects — List.', '127.0.0.1', '2026-06-18T04:35:14.170');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (22, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Manager''. Revoked: Projects — Create, Projects — Delete, Projects — Update.', '127.0.0.1', '2026-06-18T04:37:39.747');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (23, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Manager''. Granted: Projects — Create, Projects — Delete, Projects — Update.', '127.0.0.1', '2026-06-18T07:57:56.150');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (24, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Manager''. Revoked: Project Progress.', '127.0.0.1', '2026-06-18T09:04:05.613');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (25, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Admin''. No changes detected.', '127.0.0.1', '2026-06-18T12:17:20.890');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (26, 1, 'Create', 'Role', 'Created new role ''test''', '127.0.0.1', '2026-06-19T13:02:06.127');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (27, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''test''. No changes detected.', '127.0.0.1', '2026-06-19T13:02:06.907');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (28, 1, 'Create', 'User', 'Created user ''testing'' (test ing, testing@gmail.com) with role ''test''', '127.0.0.1', '2026-06-19T13:02:46.937');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (29, 1, 'Delete', 'User', 'Deleted user account ''testing'' (test ing)', '127.0.0.1', '2026-06-19T13:06:44.480');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (30, 1, 'Delete', 'Role', 'Deleted role ''test''', '127.0.0.1', '2026-06-19T13:06:51.520');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (31, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Employee''. Revoked: Tasks — Create, Tasks — Delete, Tasks — Update.', '127.0.0.1', '2026-06-19T14:22:20.200');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (32, 1, 'Create', 'User', 'Created user ''maungaye'' (Maung Aye, maungaye@gmail.com) with role ''Employee''', '127.0.0.1', '2026-06-22T04:13:19.547');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (33, 1, 'Create', 'Role', 'Created new role ''Supervisor''', '127.0.0.1', '2026-06-22T04:15:08.430');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (34, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Supervisor''. Granted: Dashboard, Project List, Projects, Projects — Create, Projects — List.', '127.0.0.1', '2026-06-22T04:15:08.980');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (35, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Admin''. Granted: Add Issue, Add Issue — Create, Add Issue — Delete, Add Issue — List, Add Issue — Update. Revoked: 1.', '127.0.0.1', '2026-07-01T06:53:19.993');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (36, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Employee''. Granted: Add Issue, Add Issue — Create, Add Issue — Delete, Add Issue — List, Add Issue — Update. Revoked: 1.', '127.0.0.1', '2026-07-01T07:11:49.900');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (37, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Employee''. No changes detected.', '127.0.0.1', '2026-07-01T07:50:14.773');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (38, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Admin''. Granted: Issue List. Revoked: 17.', '127.0.0.1', '2026-07-01T08:19:03.890');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (39, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Employee''. Granted: Issue List. Revoked: 17.', '127.0.0.1', '2026-07-02T03:39:57.093');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (40, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Admin''. Revoked: Audit Logs — List.', '127.0.0.1', '2026-07-03T07:15:03.567');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (41, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Admin''. Granted: Audit Logs — List.', '127.0.0.1', '2026-07-03T07:15:14.353');
+INSERT INTO "AuditLogs" ("Id", "UserId", "Action", "Module", "Description", "IpAddress", "CreatedAt") VALUES (42, 1, 'AssignAccess', 'Role', 'Updated access permissions for role ''Admin''. No changes detected.', '127.0.0.1', '2026-07-03T07:23:59.247');
+INSERT INTO "Comments" ("Id", "TaskId", "UserId", "Message", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (1, 7, 12, 'pee p', '2026-06-17T08:30:02.243', 12, NULL, NULL, FALSE);
+INSERT INTO "Comments" ("Id", "TaskId", "UserId", "Message", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (2, 41, 12, 'hi', '2026-06-17T08:44:20.520', 12, '2026-06-17T08:44:25.987', 12, TRUE);
+INSERT INTO "Comments" ("Id", "TaskId", "UserId", "Message", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (3, 41, 12, 'hihi', '2026-06-17T08:44:32.903', 12, NULL, NULL, FALSE);
+INSERT INTO "Comments" ("Id", "TaskId", "UserId", "Message", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (4, 1, 1, 'hihi', '2026-06-18T12:19:43.717', 1, NULL, NULL, FALSE);
+INSERT INTO "Comments" ("Id", "TaskId", "UserId", "Message", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (5, 117, 1, 'Hello', '2026-06-22T04:15:52.130', 1, NULL, NULL, FALSE);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (1, 117, 'Api test', 'Api Test', 1, 2, 12, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2026-07-01T07:14:40.177', 12, '2026-07-02T11:43:57.570', 1, FALSE, '2026-07-01', '2026-07-01', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (2, 117, 'Check Firewalls', NULL, 1, 2, 12, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2026-07-02T03:41:37.057', 12, '2026-07-02T11:43:57.570', 1, FALSE, '2026-07-02', '2026-07-02', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (3, 129, 'Refine user stories for Yangon Retail Upgrade retail checkout flow', 'Follow-up work for requirements workshop', 1, 2, 12, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-01-03T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-01-03', '2027-01-08', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (4, 130, 'Implement core point-of-sale (POS) integration for Yangon Retail', 'Follow-up work for implementation', 2, 3, 13, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-01-06T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-01-06', '2027-01-11', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (5, 131, 'QA validation on multi-currency checkout for Yangon Retail', 'Follow-up work for validation', 3, 1, 12, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-01-09T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-01-09', '2027-01-14', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (6, 132, 'Draft architecture design for Mandalay Service Portal customer dashboard', 'Follow-up work for requirements workshop', 1, 2, 14, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-02-03T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-02-03', '2027-02-08', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (7, 133, 'Develop service request submission form and state management', 'Follow-up work for implementation', 2, 3, 15, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-02-06T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-02-06', '2027-02-11', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (8, 134, 'UAT testing on customer service portal ticket routing', 'Follow-up work for validation', 3, 1, 14, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-02-09T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-02-09', '2027-02-14', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (9, 135, 'Review statutory tax calculation requirements for Payroll Hub', 'Follow-up work for requirements workshop', 1, 2, 16, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-03-03T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-03-03', '2027-03-08', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (10, 136, 'Build automated monthly payroll processing engine', 'Follow-up work for implementation', 2, 3, 17, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-03-06T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-03-06', '2027-03-11', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (11, 137, 'Verify payslip PDF generation accuracy against legacy data', 'Follow-up work for validation', 3, 1, 16, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-03-09T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-03-09', '2027-03-14', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (12, 138, 'Define vendor onboarding API schemas and authentication flows', 'Follow-up work for requirements workshop', 1, 2, 18, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-04-03T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-04-03', '2027-04-08', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (13, 139, 'Create vendor registration wizard and document upload UI', 'Follow-up work for implementation', 2, 3, 19, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-04-06T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-04-06', '2027-04-11', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (14, 140, 'Security audit and validation on vendor document upload storage bucket', 'Follow-up work for validation', 3, 1, 18, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-04-09T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-04-09', '2027-04-14', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (15, 141, 'Analyze real-time GPS tracking requirements and background worker sync interval', 'Follow-up work for requirements workshop', 1, 2, 20, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-05-03T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-05-03', '2027-05-08', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (16, 142, 'Integrate Google Maps API and implement driver location polling backend', 'Follow-up work for implementation', 2, 3, 21, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-05-06T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-05-06', '2027-05-11', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (17, 143, 'End-to-end testing on driver app background location services', 'Follow-up work for validation', 3, 1, 20, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-05-09T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-05-09', '2027-05-14', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (18, 144, 'Document offline data sync constraints for Mawlamyine Field App', 'Follow-up work for requirements workshop', 1, 2, 22, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-06-03T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-06-03', '2027-06-08', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (19, 145, 'Implement SQLite local database and sync queue for offline mode', 'Follow-up work for implementation', 2, 3, 23, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-06-06T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-06-06', '2027-06-11', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (20, 146, 'Perform offline-to-online network reconnection and conflict resolution testing', 'Follow-up work for validation', 3, 1, 22, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-06-09T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-06-09', '2027-06-14', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (21, 147, 'Finalize KPI metrics and chart visualization specs for Pathein Sales Dashboard', 'Follow-up work for requirements workshop', 1, 2, 24, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-07-03T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-07-03', '2027-07-08', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (22, 148, 'Build aggregate sales reporting APIs with Redis caching layer', 'Follow-up work for implementation', 2, 3, 25, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-07-06T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-07-06', '2027-07-11', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (23, 149, 'Cross-browser UI/UX testing on dashboard charts and dark mode toggle', 'Follow-up work for validation', 3, 1, 24, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-07-09T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-07-09', '2027-07-14', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (24, 150, 'Map out insurance claim workflow rules and approval hierarchy', 'Follow-up work for requirements workshop', 1, 2, 26, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-08-03T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-08-03', '2027-08-08', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (25, 151, 'Develop claims auto-adjudication rules engine', 'Follow-up work for implementation', 2, 3, 27, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-08-06T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-08-06', '2027-08-11', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (26, 152, 'Sanity testing on claims approval routing and email notifications', 'Follow-up work for validation', 3, 1, 26, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-08-09T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-08-09', '2027-08-14', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (27, 153, 'Gather warehouse inventory management and bin allocation requirements', 'Follow-up work for requirements workshop', 1, 2, 28, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-09-03T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-09-03', '2027-09-08', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (28, 154, 'Implement barcode scanning API integration for inventory tracking', 'Follow-up work for implementation', 2, 3, 29, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-09-06T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-09-06', '2027-09-11', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (29, 155, 'Stress test inventory database under simulated high-volume peak hours', 'Follow-up work for validation', 3, 1, 28, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-09-09T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-09-09', '2027-09-14', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (30, 156, 'Review subscription billing cycle edge cases and prorated refund rules', 'Follow-up work for requirements workshop', 1, 2, 30, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-10-03T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-10-03', '2027-10-08', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (31, 157, 'Integrate Stripe billing API for automated monthly recurring invoices', 'Follow-up work for implementation', 2, 3, 31, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-10-06T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-10-06', '2027-10-11', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (32, 158, 'Validate automated dunning emails and failed payment retry mechanisms', 'Follow-up work for validation', 3, 1, 30, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-10-09T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-10-09', '2027-10-14', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (33, 159, 'Scope chat widget integration and customer care SLA triggers', 'Follow-up work for requirements workshop', 1, 2, 32, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-11-03T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-11-03', '2027-11-08', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (34, 160, 'Embed live chat widget into customer portal with routing rules', 'Follow-up work for implementation', 2, 3, 33, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-11-06T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-11-06', '2027-11-11', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (35, 161, 'QA check on chat history persistence and agent assignment logic', 'Follow-up work for validation', 3, 1, 32, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-11-09T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-11-09', '2027-11-14', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (36, 162, 'Assess resource allocation tracking and scheduling UI requirements', 'Follow-up work for requirements workshop', 1, 2, 34, CAST(7.00 AS Decimal(5, 2)), CAST(7.00 AS Decimal(5, 2)), '2027-12-03T11:00:00.000', 1, '2026-07-02T11:43:57.570', 1, FALSE, '2027-12-03', '2027-12-08', NULL, FALSE, NULL, 0);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (37, 163, 'Develop interactive Gantt chart view for project resource scheduling', 'Follow-up work for implementation', 2, 3, 35, NULL, CAST(7.00 AS Decimal(5, 2)), '2027-12-06T11:00:00.000', 1, '2026-07-03T09:09:11.417', 1, FALSE, '2027-12-06', '2027-12-11', NULL, FALSE, NULL, 3);
+INSERT INTO "Issues" ("Id", "TaskId", "Title", "Description", "StatusId", "PriorityId", "AssignedTo", "EstimatedHours", "ActualHours", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "StartDate", "DueDate", "DelayReason", "IsBlocked", "BlockedBy", "EscalationLevel") VALUES (38, 164, 'Validate drag-and-drop event triggers on the operations timeline view', 'Follow-up work for validation', 3, 1, 34, NULL, CAST(7.00 AS Decimal(5, 2)), '2027-12-09T11:00:00.000', 1, '2026-07-03T09:09:00.740', 1, FALSE, '2027-12-09', '2027-12-14', NULL, FALSE, NULL, 3);
+INSERT INTO "Menus" ("MenuId", "MenuCode", "ParentMenuId", "MenuName", "MenuUrl", "Icon", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (1, 'DASHBOARD', NULL, 'Dashboard', NULL, 'layout-grid', FALSE, 0, TRUE, 1, '2026-06-01T08:00:00.0000000', NULL, '2026-07-06T06:09:57.0000000');
+INSERT INTO "Menus" ("MenuId", "MenuCode", "ParentMenuId", "MenuName", "MenuUrl", "Icon", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (2, 'PROJECTS', NULL, 'Projects', NULL, 'folder-kanban', TRUE, 2, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Menus" ("MenuId", "MenuCode", "ParentMenuId", "MenuName", "MenuUrl", "Icon", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (3, 'PROJECTS_LIST', 2, 'Project List', '/projects/all', 'list', TRUE, 3, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Menus" ("MenuId", "MenuCode", "ParentMenuId", "MenuName", "MenuUrl", "Icon", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (4, 'PROJECTS_ASSIGN', 2, 'Project Assign', '/projects/assign', 'users-round', TRUE, 4, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Menus" ("MenuId", "MenuCode", "ParentMenuId", "MenuName", "MenuUrl", "Icon", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (5, 'TASKS', NULL, 'Tasks', NULL, 'check-square', TRUE, 5, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Menus" ("MenuId", "MenuCode", "ParentMenuId", "MenuName", "MenuUrl", "Icon", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (6, 'TASKS_LIST', 5, 'Task List', '/tasks/all', 'list-todo', TRUE, 6, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Menus" ("MenuId", "MenuCode", "ParentMenuId", "MenuName", "MenuUrl", "Icon", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (7, 'TASKS_BOARD', 5, 'Kanban Board', '/board', 'columns-3', TRUE, 7, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Menus" ("MenuId", "MenuCode", "ParentMenuId", "MenuName", "MenuUrl", "Icon", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (8, 'TASKS_ASSIGN', 5, 'Task Assign', '/tasks/assign', 'user-check', TRUE, 8, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Menus" ("MenuId", "MenuCode", "ParentMenuId", "MenuName", "MenuUrl", "Icon", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (9, 'REPORTS', NULL, 'Reports', NULL, 'chart-column', TRUE, 9, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Menus" ("MenuId", "MenuCode", "ParentMenuId", "MenuName", "MenuUrl", "Icon", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (10, 'REPORTS_TASKS', 9, 'Task Report', '/reports/tasks', 'file-text', TRUE, 10, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Menus" ("MenuId", "MenuCode", "ParentMenuId", "MenuName", "MenuUrl", "Icon", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (11, 'REPORTS_TIMESHEET', 9, 'Time Tracking', '/reports/timesheet', 'clock-3', TRUE, 11, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Menus" ("MenuId", "MenuCode", "ParentMenuId", "MenuName", "MenuUrl", "Icon", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (12, 'REPORTS_OVERDUE', 9, 'Overdue Tasks', '/reports/overdue', 'alarm-clock', TRUE, 12, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Menus" ("MenuId", "MenuCode", "ParentMenuId", "MenuName", "MenuUrl", "Icon", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (13, 'REPORTS_EMPLOYEES', 9, 'Employee Report', '/reports/employees', 'users', TRUE, 13, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Menus" ("MenuId", "MenuCode", "ParentMenuId", "MenuName", "MenuUrl", "Icon", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (14, 'REPORTS_PROJECTS', 9, 'Project Progress', '/reports/projects', 'subtitles', TRUE, 14, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Menus" ("MenuId", "MenuCode", "ParentMenuId", "MenuName", "MenuUrl", "Icon", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (15, 'USERS', NULL, 'Users', '/users', 'users-round', TRUE, 15, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Menus" ("MenuId", "MenuCode", "ParentMenuId", "MenuName", "MenuUrl", "Icon", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (16, 'ROLES', NULL, 'Roles', '/roles', 'shield-user', TRUE, 16, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Menus" ("MenuId", "MenuCode", "ParentMenuId", "MenuName", "MenuUrl", "Icon", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (17, 'TASKS_BACKLOG', NULL, 'Task Backlog', '/tasks/backlog', 'layers', TRUE, 25, TRUE, NULL, '2026-06-17T09:32:51.0000000', 1, '2026-07-01T14:40:14.0000000');
+INSERT INTO "Menus" ("MenuId", "MenuCode", "ParentMenuId", "MenuName", "MenuUrl", "Icon", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (18, 'DASHBOARD_WIDGETS', NULL, 'Role Layouts', '/widget-setup', 'layout-grid', TRUE, 26, FALSE, NULL, '2026-06-19T04:44:59.0000000', NULL, '2026-06-19T06:48:19.0000000');
+INSERT INTO "Menus" ("MenuId", "MenuCode", "ParentMenuId", "MenuName", "MenuUrl", "Icon", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (19, 'DASHBOARD_ADMIN', NULL, 'Dashboard', '/dashboard', 'layout-dashboard', TRUE, 0, FALSE, NULL, '2026-06-19T07:44:16.0000000', NULL, '2026-07-06T06:09:57.0000000');
+INSERT INTO "Menus" ("MenuId", "MenuCode", "ParentMenuId", "MenuName", "MenuUrl", "Icon", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (20, 'DASHBOARD_MANAGER', NULL, 'Dashboard', '/dashboard/manager', 'layout-dashboard', TRUE, 0, FALSE, NULL, '2026-06-19T08:05:25.0000000', NULL, '2026-07-06T06:09:57.0000000');
+INSERT INTO "Menus" ("MenuId", "MenuCode", "ParentMenuId", "MenuName", "MenuUrl", "Icon", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (21, 'DASHBOARD_EMPLOYEE', NULL, 'Dashboard', '/dashboard/employee', 'layout-dashboard', TRUE, 0, FALSE, NULL, '2026-06-19T08:05:25.0000000', NULL, '2026-07-06T06:09:57.0000000');
+INSERT INTO "Menus" ("MenuId", "MenuCode", "ParentMenuId", "MenuName", "MenuUrl", "Icon", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (22, 'ISSUES_ADD', 5, 'Add Issue', '/issues/add', 'circle-plus', TRUE, 26, FALSE, 1, '2026-07-01T11:00:47.0000000', NULL, NULL);
+INSERT INTO "Menus" ("MenuId", "MenuCode", "ParentMenuId", "MenuName", "MenuUrl", "Icon", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (23, 'ISSUES_LIST', 5, 'Issue List', '/issues/all', 'list-todo', TRUE, 27, FALSE, 1, '2026-07-01T14:38:08.0000000', NULL, NULL);
+INSERT INTO "Menus" ("MenuId", "MenuCode", "ParentMenuId", "MenuName", "MenuUrl", "Icon", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (24, 'AUDIT_LOGS', NULL, 'Audit Logs', '/audit-logs', 'file-text', TRUE, 28, FALSE, 1, '2026-07-03T07:14:40.0000000', NULL, NULL);
+INSERT INTO "Menus" ("MenuId", "MenuCode", "ParentMenuId", "MenuName", "MenuUrl", "Icon", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (25, 'REPORTS_ISSUES', 9, 'Issue Report', '/reports/issues', 'file-pen-line', TRUE, 15, FALSE, NULL, '2026-07-03T08:42:56.0000000', NULL, '2026-07-06T06:09:57.0000000');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (1, 14, 1, 2, 'task', 1, TRUE, '2026-06-18T10:23:03.7644258', '2026-06-18T10:16:06.9643960', 'Task status changed', 'Task ''Portal landing page'' changed from Done to In Progress');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (2, 2, 1, 2, 'task', 1, FALSE, NULL, '2026-06-18T10:16:06.9645153', 'Task status changed', 'Task ''Portal landing page'' changed from Done to In Progress');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (3, 14, 1, 2, 'task', 1, TRUE, '2026-06-18T12:46:13.7896047', '2026-06-18T12:19:30.7116861', 'Task status changed', 'Task ''Portal landing page'' changed from In Progress to Done');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (4, 2, 1, 2, 'task', 1, FALSE, NULL, '2026-06-18T12:19:30.7117582', 'Task status changed', 'Task ''Portal landing page'' changed from In Progress to Done');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (5, 14, 1, 2, 'task', 1, TRUE, '2026-06-18T12:46:12.7694234', '2026-06-18T12:19:33.3325815', 'Task status changed', 'Task ''Portal landing page'' changed from Done to In Progress');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (6, 2, 1, 2, 'task', 1, FALSE, NULL, '2026-06-18T12:19:33.3325820', 'Task status changed', 'Task ''Portal landing page'' changed from Done to In Progress');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (7, 14, 1, 5, 'task', 1, TRUE, '2026-06-18T12:46:11.7093777', '2026-06-18T12:19:43.8271666', 'New comment', 'System Admin commented on task ''Portal landing page''');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (8, 2, 1, 5, 'task', 1, FALSE, NULL, '2026-06-18T12:19:43.8271680', 'New comment', 'System Admin commented on task ''Portal landing page''');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (9, 14, 1, 2, 'task', 1, TRUE, '2026-06-18T12:46:10.8180933', '2026-06-18T12:33:55.7001972', 'Task status changed', 'Task ''Portal landing page'' changed from In Progress to Done');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (10, 2, 1, 2, 'task', 1, FALSE, NULL, '2026-06-18T12:33:55.7001983', 'Task status changed', 'Task ''Portal landing page'' changed from In Progress to Done');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (11, 2, 1, 8, 'project', 8, FALSE, NULL, '2026-06-19T06:20:08.5442869', 'Project assigned', 'You have been assigned to project ''Testing''');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (14, 7, 12, 2, 'task', 41, FALSE, NULL, '2026-06-19T09:30:33.6906496', 'Task status changed', 'Task ''Customer Report '' changed from In Progress to Done');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (15, 3, 1, 8, 'project', 1, FALSE, NULL, '2026-06-19T09:48:30.2048589', 'Project assigned', 'You have been assigned to project ''Yangon Customer Portal''');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (16, 15, 1, 8, 'project', 1, FALSE, NULL, '2026-06-19T09:48:30.2050369', 'Project assigned', 'You have been assigned to project ''Yangon Customer Portal''');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (17, 16, 1, 8, 'project', 1, FALSE, NULL, '2026-06-19T09:48:30.2050376', 'Project assigned', 'You have been assigned to project ''Yangon Customer Portal''');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (18, 17, 1, 8, 'project', 1, FALSE, NULL, '2026-06-19T09:48:30.2050379', 'Project assigned', 'You have been assigned to project ''Yangon Customer Portal''');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (19, 18, 1, 8, 'project', 1, FALSE, NULL, '2026-06-19T09:48:30.2050382', 'Project assigned', 'You have been assigned to project ''Yangon Customer Portal''');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (20, 19, 1, 8, 'project', 1, FALSE, NULL, '2026-06-19T09:48:30.2050386', 'Project assigned', 'You have been assigned to project ''Yangon Customer Portal''');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (21, 2, 1, 8, 'project', 2, FALSE, NULL, '2026-06-19T09:49:18.3612860', 'Project assigned', 'You have been assigned to project ''Mandalay Inventory System''');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (22, 4, 1, 8, 'project', 2, FALSE, NULL, '2026-06-19T09:49:18.3612868', 'Project assigned', 'You have been assigned to project ''Mandalay Inventory System''');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (23, 12, 1, 8, 'project', 2, TRUE, '2026-07-02T03:38:23.1783888', '2026-06-19T09:49:18.3612871', 'Project assigned', 'You have been assigned to project ''Mandalay Inventory System''');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (24, 14, 1, 8, 'project', 2, FALSE, NULL, '2026-06-19T09:49:18.3612873', 'Project assigned', 'You have been assigned to project ''Mandalay Inventory System''');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (25, 13, 1, 8, 'project', 2, FALSE, NULL, '2026-06-19T09:49:18.3612875', 'Project assigned', 'You have been assigned to project ''Mandalay Inventory System''');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (26, 12, 1, 1, 'task', 9, TRUE, '2026-07-02T03:38:39.9796184', '2026-06-19T09:49:41.7266775', 'Task assigned', 'You have been assigned a new task: Stock dashboard cards');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (27, 12, 1, 8, 'project', 6, TRUE, '2026-07-02T03:38:52.3850103', '2026-06-19T09:51:01.7051302', 'Project assigned', 'You have been assigned to project ''Mawlamyine Logistics Hub''');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (28, 12, 1, 1, 'task', 43, TRUE, '2026-07-02T03:38:28.7529662', '2026-06-19T09:51:17.0125764', 'Task assigned', 'You have been assigned a new task: API Integration');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (30, 4, 12, 2, 'task', 43, FALSE, NULL, '2026-06-19T14:25:22.0208429', 'Task status changed', 'Task ''API Integration'' changed from To Do to Done');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (31, 12, 1, 2, 'task', 117, TRUE, '2026-07-02T03:38:33.5733219', '2026-06-20T02:53:07.1422148', 'Task status changed', 'Task ''Critical Security Patch'' changed from To Do to Done');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (32, 6, 1, 2, 'task', 117, FALSE, NULL, '2026-06-20T02:53:07.1423155', 'Task status changed', 'Task ''Critical Security Patch'' changed from To Do to Done');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (33, 4, 1, 2, 'task', 117, FALSE, NULL, '2026-06-20T02:53:07.1423160', 'Task status changed', 'Task ''Critical Security Patch'' changed from To Do to Done');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (34, 19, 1, 2, 'task', 120, FALSE, NULL, '2026-06-22T03:15:36.3428593', 'Task status changed', 'Task ''Supplier Sync Validation'' changed from To Do to In Progress');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (35, 8, 1, 2, 'task', 120, FALSE, NULL, '2026-06-22T03:15:36.3430321', 'Task status changed', 'Task ''Supplier Sync Validation'' changed from To Do to In Progress');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (36, 7, 1, 2, 'task', 120, FALSE, NULL, '2026-06-22T03:15:36.3430333', 'Task status changed', 'Task ''Supplier Sync Validation'' changed from To Do to In Progress');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (37, 2, 1, 8, 'project', 22, TRUE, '2026-06-24T08:09:29.0059447', '2026-06-22T03:55:44.1048049', 'Project assigned', 'You have been assigned to project ''Clothing ''');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (41, 6, 1, 5, 'task', 117, FALSE, NULL, '2026-06-22T04:15:52.2196317', 'New comment', 'System Admin commented on task ''Critical Security Patch''');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (42, 4, 1, 5, 'task', 117, FALSE, NULL, '2026-06-22T04:15:52.2196320', 'New comment', 'System Admin commented on task ''Critical Security Patch''');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (43, 6, 12, 2, 'task', 117, FALSE, NULL, '2026-06-24T07:56:12.4574052', 'Task status changed', 'Task ''Critical Security Patch'' changed from Done to In Progress');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (44, 4, 12, 2, 'task', 117, FALSE, NULL, '2026-06-24T07:56:12.4574906', 'Task status changed', 'Task ''Critical Security Patch'' changed from Done to In Progress');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (45, 7, 12, 2, 'task', 41, FALSE, NULL, '2026-06-24T07:56:16.2396720', 'Task status changed', 'Task ''Customer Report '' changed from Done to In Progress');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (46, 9, 12, 2, 'task', 7, FALSE, NULL, '2026-06-24T07:56:19.9425043', 'Task status changed', 'Task ''Audit trail page'' changed from Done to In Progress');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (47, 4, 12, 2, 'task', 9, FALSE, NULL, '2026-06-24T07:56:23.6521249', 'Task status changed', 'Task ''Stock dashboard cards'' changed from Done to In Progress');
+INSERT INTO "notifications" ("Id", "recipient_id", "sender_id", "notification_type", "source_type", "source_id", "is_read", "read_at", "created_at", "Title", "Body") VALUES (48, 4, 12, 2, 'task', 43, FALSE, NULL, '2026-06-24T07:56:27.8058837', 'Task status changed', 'Task ''API Integration'' changed from Done to In Progress');
+INSERT INTO "Permissions" ("PermissionId", "PermissionCode", "MenuId", "ActionName", "ApiName", "HttpMethod", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (1, 'Projects_List', 2, 'List', 'api/Project', 'GET', TRUE, 1, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Permissions" ("PermissionId", "PermissionCode", "MenuId", "ActionName", "ApiName", "HttpMethod", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (2, 'Projects_Create', 2, 'Create', 'api/Project', 'POST', TRUE, 2, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Permissions" ("PermissionId", "PermissionCode", "MenuId", "ActionName", "ApiName", "HttpMethod", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (3, 'Projects_Update', 2, 'Update', 'api/Project', 'PUT', TRUE, 3, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Permissions" ("PermissionId", "PermissionCode", "MenuId", "ActionName", "ApiName", "HttpMethod", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (4, 'Projects_Delete', 2, 'Delete', 'api/Project', 'DELETE', TRUE, 4, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Permissions" ("PermissionId", "PermissionCode", "MenuId", "ActionName", "ApiName", "HttpMethod", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (5, 'Tasks_List', 5, 'List', 'api/Task', 'GET', TRUE, 5, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Permissions" ("PermissionId", "PermissionCode", "MenuId", "ActionName", "ApiName", "HttpMethod", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (6, 'Tasks_Create', 5, 'Create', 'api/Task', 'POST', TRUE, 6, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Permissions" ("PermissionId", "PermissionCode", "MenuId", "ActionName", "ApiName", "HttpMethod", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (7, 'Tasks_Update', 5, 'Update', 'api/Task', 'PUT', TRUE, 7, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Permissions" ("PermissionId", "PermissionCode", "MenuId", "ActionName", "ApiName", "HttpMethod", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (8, 'Tasks_Delete', 5, 'Delete', 'api/Task', 'DELETE', TRUE, 8, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Permissions" ("PermissionId", "PermissionCode", "MenuId", "ActionName", "ApiName", "HttpMethod", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (9, 'Users_List', 15, 'List', 'api/User', 'GET', TRUE, 9, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Permissions" ("PermissionId", "PermissionCode", "MenuId", "ActionName", "ApiName", "HttpMethod", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (10, 'Users_Create', 15, 'Create', 'api/User', 'POST', TRUE, 10, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Permissions" ("PermissionId", "PermissionCode", "MenuId", "ActionName", "ApiName", "HttpMethod", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (11, 'Users_Update', 15, 'Update', 'api/User', 'PUT', TRUE, 11, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Permissions" ("PermissionId", "PermissionCode", "MenuId", "ActionName", "ApiName", "HttpMethod", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (12, 'Users_Delete', 15, 'Delete', 'api/User', 'DELETE', TRUE, 12, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Permissions" ("PermissionId", "PermissionCode", "MenuId", "ActionName", "ApiName", "HttpMethod", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (13, 'Roles_List', 16, 'List', 'api/Role', 'GET', TRUE, 13, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Permissions" ("PermissionId", "PermissionCode", "MenuId", "ActionName", "ApiName", "HttpMethod", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (14, 'Roles_Create', 16, 'Create', 'api/Role', 'POST', TRUE, 14, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Permissions" ("PermissionId", "PermissionCode", "MenuId", "ActionName", "ApiName", "HttpMethod", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (15, 'Roles_Update', 16, 'Update', 'api/Role', 'PUT', TRUE, 15, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Permissions" ("PermissionId", "PermissionCode", "MenuId", "ActionName", "ApiName", "HttpMethod", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (16, 'Roles_Delete', 16, 'Delete', 'api/Role', 'DELETE', TRUE, 16, FALSE, 1, '2026-06-01T08:00:00.0000000', NULL, NULL);
+INSERT INTO "Permissions" ("PermissionId", "PermissionCode", "MenuId", "ActionName", "ApiName", "HttpMethod", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (17, 'Issues_List', 22, 'List', 'api/Issue', 'GET', TRUE, 1, FALSE, 1, '2026-07-01T11:00:47.0000000', NULL, NULL);
+INSERT INTO "Permissions" ("PermissionId", "PermissionCode", "MenuId", "ActionName", "ApiName", "HttpMethod", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (18, 'Issues_Create', 22, 'Create', 'api/Issue', 'POST', TRUE, 2, FALSE, 1, '2026-07-01T11:00:47.0000000', NULL, NULL);
+INSERT INTO "Permissions" ("PermissionId", "PermissionCode", "MenuId", "ActionName", "ApiName", "HttpMethod", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (19, 'Issues_Update', 22, 'Update', 'api/Issue', 'PUT', TRUE, 3, FALSE, 1, '2026-07-01T11:00:47.0000000', NULL, NULL);
+INSERT INTO "Permissions" ("PermissionId", "PermissionCode", "MenuId", "ActionName", "ApiName", "HttpMethod", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (20, 'Issues_Delete', 22, 'Delete', 'api/Issue', 'DELETE', TRUE, 4, FALSE, 1, '2026-07-01T11:00:47.0000000', NULL, NULL);
+INSERT INTO "Permissions" ("PermissionId", "PermissionCode", "MenuId", "ActionName", "ApiName", "HttpMethod", "Visible", "OrderNo", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (21, 'AuditLogs_List', 24, 'List', 'api/AuditLog', 'GET', TRUE, 28, FALSE, 1, '2026-07-03T07:14:40.0000000', NULL, NULL);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (1, 2, '2026-06-19T09:48:29.983', NULL);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (1, 3, '2026-06-19T09:48:30.023', NULL);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (1, 12, '2026-06-19T09:48:30.023', NULL);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (1, 13, '2026-06-19T09:48:30.023', NULL);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (1, 14, '2026-06-19T09:48:30.023', NULL);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (1, 15, '2026-06-19T09:48:30.023', NULL);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (1, 16, '2026-06-19T09:48:30.023', NULL);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (1, 17, '2026-06-19T09:48:30.023', NULL);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (1, 18, '2026-06-19T09:48:30.023', NULL);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (1, 19, '2026-06-19T09:48:30.023', NULL);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (2, 2, '2026-06-19T09:49:18.317', NULL);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (2, 3, '2026-06-19T09:49:18.317', NULL);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (2, 4, '2026-06-19T09:49:18.317', NULL);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (2, 12, '2026-06-19T09:49:18.317', NULL);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (2, 13, '2026-06-19T09:49:18.317', NULL);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (2, 14, '2026-06-19T09:49:18.317', NULL);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (2, 15, '2026-06-19T09:49:18.317', NULL);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (2, 16, '2026-06-19T09:49:18.317', NULL);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (2, 17, '2026-06-19T09:49:18.317', NULL);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (3, 4, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (3, 18, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (3, 19, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (3, 20, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (4, 5, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (4, 21, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (4, 22, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (4, 23, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (5, 6, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (5, 24, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (5, 25, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (5, 26, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (6, 7, '2026-06-19T09:51:01.677', NULL);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (6, 12, '2026-06-19T09:51:01.677', NULL);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (6, 27, '2026-06-19T09:51:01.677', NULL);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (6, 28, '2026-06-19T09:51:01.677', NULL);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (6, 29, '2026-06-19T09:51:01.677', NULL);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (7, 8, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (7, 30, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (7, 31, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (7, 32, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (8, 9, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (8, 33, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (8, 34, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (8, 35, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (9, 10, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (9, 36, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (9, 37, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (9, 38, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (10, 11, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (10, 39, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (10, 40, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (10, 41, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (10, 42, '2026-06-19T14:11:15.377', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (22, 2, '2026-06-22T03:57:53.473', NULL);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (22, 12, '2026-06-22T03:57:53.473', NULL);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (23, 1, '2027-01-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (23, 2, '2027-01-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (23, 12, '2027-01-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (23, 13, '2027-01-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (24, 1, '2027-02-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (24, 3, '2027-02-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (24, 14, '2027-02-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (24, 15, '2027-02-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (25, 1, '2027-03-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (25, 4, '2027-03-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (25, 16, '2027-03-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (25, 17, '2027-03-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (26, 1, '2027-04-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (26, 5, '2027-04-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (26, 18, '2027-04-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (26, 19, '2027-04-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (27, 1, '2027-05-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (27, 6, '2027-05-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (27, 20, '2027-05-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (27, 21, '2027-05-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (28, 1, '2027-06-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (28, 7, '2027-06-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (28, 22, '2027-06-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (28, 23, '2027-06-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (29, 1, '2027-07-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (29, 8, '2027-07-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (29, 24, '2027-07-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (29, 25, '2027-07-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (30, 1, '2027-08-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (30, 9, '2027-08-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (30, 26, '2027-08-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (30, 27, '2027-08-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (31, 1, '2027-09-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (31, 10, '2027-09-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (31, 28, '2027-09-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (31, 29, '2027-09-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (32, 1, '2027-10-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (32, 11, '2027-10-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (32, 30, '2027-10-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (32, 31, '2027-10-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (33, 1, '2027-11-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (33, 32, '2027-11-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (33, 33, '2027-11-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (33, 42, '2027-11-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (34, 1, '2027-12-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (34, 2, '2027-12-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (34, 34, '2027-12-01T09:00:00.000', 1);
+INSERT INTO "ProjectMembers" ("ProjectId", "UserId", "CreatedAt", "CreatedBy") VALUES (34, 35, '2027-12-01T09:00:00.000', 1);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (1, 'Yangon Customer Portal', 'Customer portal for Yangon branches', '2026-06-01T00:00:00.000', '2026-08-31T00:00:00.000', 1, '2026-06-01T00:00:00.000', '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE, 160);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (2, 'Mandalay Inventory System', 'Warehouse and stock tracking for Mandalay team', '2026-06-03T00:00:00.000', '2026-09-15T00:00:00.000', 1, '2026-06-03T00:00:00.000', '2026-06-03T00:00:00.000', 1, NULL, NULL, FALSE, 220);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (3, 'Nay Pyi Taw HR Tracker', 'HR workflow and leave tracking', '2026-06-05T00:00:00.000', '2026-09-30T00:00:00.000', 1, '2026-06-05T00:00:00.000', '2026-06-05T00:00:00.000', 1, NULL, NULL, FALSE, 180);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (4, 'Bago Procurement Hub', 'Purchase request and approval workflow', '2026-06-07T00:00:00.000', '2026-10-15T00:00:00.000', 1, '2026-06-07T00:00:00.000', '2026-06-07T00:00:00.000', 1, '2026-06-19T13:00:00.000', 1, FALSE, 200);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (5, 'Taunggyi Support Desk', 'Helpdesk and issue tracking dashboard', '2026-06-10T00:00:00.000', '2026-10-31T00:00:00.000', 1, '2026-06-10T00:00:00.000', '2026-06-10T00:00:00.000', 1, '2026-06-19T13:00:00.000', 1, FALSE, 240);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (6, 'Mawlamyine Logistics Hub', 'Supply chain tracking system', '2026-07-01T00:00:00.000', '2026-09-30T00:00:00.000', 1, '2026-06-19T13:54:36.407', '2026-06-19T13:54:36.407', 1, NULL, NULL, FALSE, 180);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (7, 'Pathein Seafood Portal', 'B2B marketplace platform', '2026-07-15T00:00:00.000', '2026-11-15T00:00:00.000', 1, '2026-06-19T13:54:36.407', '2026-06-19T13:54:36.407', 1, NULL, NULL, FALSE, 250);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (8, 'Monywa Billing System', 'Automated utility billing service', '2026-08-01T00:00:00.000', '2026-12-31T00:00:00.000', 1, '2026-06-19T13:54:36.407', '2026-06-19T13:54:36.407', 1, NULL, NULL, FALSE, 300);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (9, 'Sittwe Port Dashboard', 'Vessel and cargo tracking UI', '2026-09-01T00:00:00.000', '2026-11-30T00:00:00.000', 1, '2026-06-19T13:54:36.407', '2026-06-19T13:54:36.407', 1, NULL, NULL, FALSE, 140);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (10, 'Pyin Oo Lwin AgriTech', 'Smart farming analytics dashboard', '2026-10-01T00:00:00.000', '2027-02-28T00:00:00.000', 1, '2026-06-19T13:54:36.407', '2026-06-19T13:54:36.407', 1, NULL, NULL, FALSE, 400);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (11, 'Lashio Retail Point', 'Cloud POS system for local shops', '2026-11-01T00:00:00.000', '2027-01-31T00:00:00.000', 1, '2026-06-19T13:54:36.407', '2026-06-19T13:54:36.407', 1, NULL, NULL, FALSE, 120);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (12, 'Dawei Eco-Tourism App', 'Booking and travel guide application', '2026-12-01T00:00:00.000', '2027-04-30T00:00:00.000', 1, '2026-06-19T13:54:36.407', '2026-06-19T13:54:36.407', 1, NULL, NULL, FALSE, 350);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (13, 'Hpa-An Enterprise ERP', 'Core resource planning modules', '2027-01-10T00:00:00.000', '2027-06-30T00:00:00.000', 1, '2026-06-19T13:54:36.407', '2026-06-19T13:54:36.407', 1, NULL, NULL, FALSE, 500);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (14, 'Magway Solar Grid Tracker', 'Renewable energy monitoring metrics', '2027-02-01T00:00:00.000', '2027-05-31T00:00:00.000', 1, '2026-06-19T13:54:36.407', '2026-06-19T13:54:36.407', 1, NULL, NULL, FALSE, 210);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (15, 'Myitkyina Education Portal', 'E-learning system for state colleges', '2027-03-01T00:00:00.000', '2027-08-31T00:00:00.000', 1, '2026-06-19T13:54:36.407', '2026-06-19T13:54:36.407', 1, NULL, NULL, FALSE, 280);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (16, 'Shwebo Rice Mill Software', 'Inventory and quality control logs', '2027-04-15T00:00:00.000', '2027-07-15T00:00:00.000', 1, '2026-06-19T13:54:36.407', '2026-06-19T13:54:36.407', 1, NULL, NULL, FALSE, 160);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (17, 'Kawthaung Marine Data', 'Fishery quotas and mapping UI', '2027-05-01T00:00:00.000', '2027-10-31T00:00:00.000', 1, '2026-06-19T13:54:36.407', '2026-06-19T13:54:36.407', 1, NULL, NULL, FALSE, 450);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (18, 'Kalay Health Tracker', 'Clinic patient management system', '2027-06-01T00:00:00.000', '2027-09-30T00:00:00.000', 1, '2026-06-19T13:54:36.407', '2026-06-19T13:54:36.407', 1, NULL, NULL, FALSE, 190);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (19, 'Kyaukme Tea Supply Chain', 'Export logistics management system', '2027-07-01T00:00:00.000', '2027-12-31T00:00:00.000', 1, '2026-06-19T13:54:36.407', '2026-06-19T13:54:36.407', 1, NULL, NULL, FALSE, 310);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (20, 'Meiktila Textile Inventory', 'Factory production tracking ledger', '2027-08-15T00:00:00.000', '2028-01-15T00:00:00.000', 1, '2026-06-19T13:54:36.407', '2026-06-19T13:54:36.407', 1, NULL, NULL, FALSE, 220);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (21, 'test', NULL, '2026-06-01T00:00:00.000', '2026-07-19T00:00:00.000', 1, '2026-06-19T19:28:35.490', '2026-06-19T12:58:35.310', 1, NULL, NULL, TRUE, NULL);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (22, 'Clothing ', NULL, '2026-06-22T00:00:00.000', '2026-07-22T00:00:00.000', 1, '2026-06-22T10:25:04.133', '2026-06-22T03:55:04.050', 1, NULL, NULL, FALSE, 7);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (23, 'Yangon Retail Upgrade', 'POS and storefront refresh for Q1', '2027-01-01T00:00:00.000', '2027-03-31T00:00:00.000', 1, '2027-01-01T00:00:00.000', '2027-01-01T09:00:00.000', 1, NULL, NULL, FALSE, 180);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (24, 'Mandalay Service Portal', 'Support portal and SLA workflow for Q1', '2027-02-01T00:00:00.000', '2027-04-30T00:00:00.000', 1, '2027-02-01T00:00:00.000', '2027-02-01T09:00:00.000', 1, NULL, NULL, FALSE, 200);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (25, 'Nay Pyi Taw Payroll Hub', 'Payroll and approvals refresh for Q2', '2027-03-01T00:00:00.000', '2027-05-31T00:00:00.000', 1, '2027-03-01T00:00:00.000', '2027-03-01T09:00:00.000', 1, NULL, NULL, FALSE, 220);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (26, 'Bago Vendor Board', 'Vendor onboarding and approval board', '2027-04-01T00:00:00.000', '2027-06-30T00:00:00.000', 1, '2027-04-01T00:00:00.000', '2027-04-01T09:00:00.000', 1, NULL, NULL, FALSE, 160);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (27, 'Taunggyi Delivery Tracker', 'Delivery scheduling and route updates', '2027-05-01T00:00:00.000', '2027-07-31T00:00:00.000', 1, '2027-05-01T00:00:00.000', '2027-05-01T09:00:00.000', 1, NULL, NULL, FALSE, 240);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (28, 'Mawlamyine Field App', 'Field support mobile app', '2027-06-01T00:00:00.000', '2027-08-31T00:00:00.000', 1, '2027-06-01T00:00:00.000', '2027-06-01T09:00:00.000', 1, NULL, NULL, FALSE, 260);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (29, 'Pathein Sales Dashboard', 'Sales analytics and KPI rollout', '2027-07-01T00:00:00.000', '2027-09-30T00:00:00.000', 1, '2027-07-01T00:00:00.000', '2027-07-01T09:00:00.000', 1, NULL, NULL, FALSE, 210);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (30, 'Monywa Claims System', 'Claims intake and review workflow', '2027-08-01T00:00:00.000', '2027-10-31T00:00:00.000', 1, '2027-08-01T00:00:00.000', '2027-08-01T09:00:00.000', 1, NULL, NULL, FALSE, 230);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (31, 'Sittwe Logistics Control', 'Cargo status and dispatch planning', '2027-09-01T00:00:00.000', '2027-11-30T00:00:00.000', 1, '2027-09-01T00:00:00.000', '2027-09-01T09:00:00.000', 1, NULL, NULL, FALSE, 190);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (32, 'Pyin Oo Lwin Billing Upgrade', 'Billing and statement enhancements', '2027-10-01T00:00:00.000', '2027-12-31T00:00:00.000', 1, '2027-10-01T00:00:00.000', '2027-10-01T09:00:00.000', 1, NULL, NULL, FALSE, 250);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (33, 'Lashio Customer Care', 'Customer care portal and case notes', '2027-11-01T00:00:00.000', '2028-01-31T00:00:00.000', 1, '2027-11-01T00:00:00.000', '2027-11-01T09:00:00.000', 1, NULL, NULL, FALSE, 280);
+INSERT INTO "Projects" ("Id", "Name", "Description", "StartDate", "EndDate", "CreatedById", "CreatedDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "BudgetedHours") VALUES (34, 'Dawei Operations Suite', 'Operations planning for year-end launch', '2027-12-01T00:00:00.000', '2028-02-28T00:00:00.000', 1, '2027-12-01T00:00:00.000', '2027-12-01T09:00:00.000', 1, NULL, NULL, FALSE, 300);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (1, 3, 1, TRUE, NULL, '2026-06-17T10:20:39.0000000', 1, '2026-07-01T07:11:50.0000000');
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (2, 3, 3, FALSE, NULL, '2026-06-17T10:20:39.0000000', 1, '2026-06-18T04:35:14.0000000');
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (3, 3, 2, FALSE, NULL, '2026-06-17T10:20:39.0000000', 1, '2026-06-18T04:35:14.0000000');
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (4, 3, 6, FALSE, NULL, '2026-06-17T10:20:39.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (5, 3, 5, FALSE, NULL, '2026-06-17T10:20:39.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (6, 3, 10, FALSE, NULL, '2026-06-17T10:20:39.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (7, 3, 9, FALSE, NULL, '2026-06-17T10:20:39.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (8, 3, 12, FALSE, NULL, '2026-06-17T10:20:39.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (9, 3, 14, FALSE, NULL, '2026-06-17T10:20:39.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (10, 3, 17, TRUE, NULL, '2026-06-17T10:20:39.0000000', 1, '2026-07-02T03:39:57.0000000');
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (11, 1, 1, TRUE, NULL, '2026-06-17T14:55:11.0000000', 1, '2026-07-01T06:53:20.0000000');
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (12, 1, 2, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (13, 1, 3, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (14, 1, 4, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (15, 1, 5, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (16, 1, 6, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (17, 1, 7, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (18, 1, 8, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (19, 1, 9, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (20, 1, 10, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (21, 1, 11, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (22, 1, 12, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (23, 1, 13, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (24, 1, 14, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (25, 1, 15, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (26, 1, 16, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (27, 1, 17, TRUE, NULL, '2026-06-17T14:55:11.0000000', 1, '2026-07-01T08:19:04.0000000');
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (28, 2, 1, FALSE, NULL, '2026-06-18T04:05:50.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (29, 2, 2, FALSE, NULL, '2026-06-18T04:05:50.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (30, 2, 3, FALSE, NULL, '2026-06-18T04:05:50.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (31, 2, 4, FALSE, NULL, '2026-06-18T04:05:50.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (32, 2, 5, FALSE, NULL, '2026-06-18T04:05:50.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (33, 2, 6, FALSE, NULL, '2026-06-18T04:05:50.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (34, 2, 7, FALSE, NULL, '2026-06-18T04:05:50.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (35, 2, 8, FALSE, NULL, '2026-06-18T04:05:50.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (36, 2, 9, FALSE, NULL, '2026-06-18T04:05:50.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (37, 2, 10, FALSE, NULL, '2026-06-18T04:05:50.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (38, 2, 11, FALSE, NULL, '2026-06-18T04:05:50.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (39, 2, 12, FALSE, NULL, '2026-06-18T04:05:50.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (40, 2, 13, FALSE, NULL, '2026-06-18T04:05:50.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (41, 2, 14, TRUE, NULL, '2026-06-18T04:05:50.0000000', 1, '2026-06-18T09:04:05.0000000');
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (42, 2, 15, TRUE, NULL, '2026-06-18T04:24:05.0000000', 1, '2026-06-18T04:33:21.0000000');
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (43, 2, 16, TRUE, NULL, '2026-06-18T04:24:39.0000000', 1, '2026-06-18T04:33:21.0000000');
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (44, 2, 17, TRUE, 1, '2026-06-18T04:32:46.0000000', 1, '2026-06-18T04:33:21.0000000');
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (45, 3, 4, TRUE, 1, '2026-06-18T04:33:33.0000000', 1, '2026-06-18T04:34:00.0000000');
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (46, 3, 7, FALSE, 1, '2026-06-18T04:33:33.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (47, 3, 8, TRUE, 1, '2026-06-18T04:33:33.0000000', 1, '2026-06-18T04:34:00.0000000');
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (48, 3, 11, FALSE, 1, '2026-06-18T04:33:33.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (49, 3, 13, FALSE, 1, '2026-06-18T04:33:33.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (50, 3, 15, TRUE, 1, '2026-06-18T04:33:33.0000000', 1, '2026-06-18T04:34:00.0000000');
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (51, 3, 16, TRUE, 1, '2026-06-18T04:33:33.0000000', 1, '2026-06-18T04:34:00.0000000');
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (52, 1, 18, FALSE, NULL, '2026-06-19T04:45:00.0000000', NULL, '2026-06-19T06:48:19.0000000');
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (53, 1, 19, FALSE, NULL, '2026-06-19T07:44:17.0000000', NULL, '2026-07-06T06:09:57.0000000');
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (54, 2, 20, FALSE, NULL, '2026-06-19T08:05:25.0000000', NULL, '2026-07-06T06:09:57.0000000');
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (55, 3, 21, FALSE, NULL, '2026-06-19T08:05:25.0000000', NULL, '2026-07-06T06:09:57.0000000');
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (56, 5, 2, FALSE, 1, '2026-06-22T04:15:09.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (57, 5, 3, FALSE, 1, '2026-06-22T04:15:09.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (58, 5, 20, FALSE, 1, '2026-06-22T04:15:09.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (59, 1, 22, FALSE, 1, '2026-07-01T06:53:20.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (60, 3, 22, FALSE, 1, '2026-07-01T07:11:50.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (61, 1, 23, FALSE, 1, '2026-07-01T08:19:04.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (62, 3, 23, FALSE, 1, '2026-07-02T03:39:57.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (63, 1, 24, FALSE, 1, '2026-07-03T07:14:40.0000000', NULL, NULL);
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (64, 1, 25, FALSE, NULL, '2026-07-03T08:42:57.0000000', NULL, '2026-07-06T06:09:57.0000000');
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (65, 2, 25, FALSE, NULL, '2026-07-03T08:42:57.0000000', NULL, '2026-07-06T06:09:57.0000000');
+INSERT INTO "RoleMenus" ("RoleMenuId", "RoleId", "MenuId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (66, 3, 25, FALSE, NULL, '2026-07-03T08:42:57.0000000', NULL, '2026-07-06T06:09:57.0000000');
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (1, 3, 1, FALSE, NULL, '2026-06-17T10:20:39.0000000', 1, '2026-06-18T04:35:14.0000000');
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (2, 3, 5, FALSE, NULL, '2026-06-17T10:20:39.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (3, 1, 1, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (4, 1, 2, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (5, 1, 3, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (6, 1, 4, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (7, 1, 5, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (8, 1, 6, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (9, 1, 7, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (10, 1, 8, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (11, 1, 9, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (12, 1, 10, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (13, 1, 11, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (14, 1, 12, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (15, 1, 13, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (16, 1, 14, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (17, 1, 15, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (18, 1, 16, FALSE, NULL, '2026-06-17T14:55:11.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (19, 2, 1, FALSE, NULL, '2026-06-18T04:05:50.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (20, 2, 2, FALSE, NULL, '2026-06-18T04:05:50.0000000', 1, '2026-06-18T07:57:56.0000000');
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (21, 2, 3, FALSE, NULL, '2026-06-18T04:05:50.0000000', 1, '2026-06-18T07:57:56.0000000');
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (22, 2, 4, FALSE, NULL, '2026-06-18T04:05:50.0000000', 1, '2026-06-18T07:57:56.0000000');
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (23, 2, 5, FALSE, NULL, '2026-06-18T04:05:50.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (24, 2, 6, FALSE, NULL, '2026-06-18T04:05:50.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (25, 2, 7, FALSE, NULL, '2026-06-18T04:05:50.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (26, 2, 8, FALSE, NULL, '2026-06-18T04:05:50.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (27, 2, 9, TRUE, NULL, '2026-06-18T04:24:05.0000000', 1, '2026-06-18T04:33:21.0000000');
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (28, 2, 13, TRUE, NULL, '2026-06-18T04:24:39.0000000', 1, '2026-06-18T04:33:21.0000000');
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (29, 2, 10, TRUE, 1, '2026-06-18T04:32:46.0000000', 1, '2026-06-18T04:33:21.0000000');
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (30, 2, 11, TRUE, 1, '2026-06-18T04:32:46.0000000', 1, '2026-06-18T04:33:21.0000000');
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (31, 2, 12, TRUE, 1, '2026-06-18T04:32:46.0000000', 1, '2026-06-18T04:33:21.0000000');
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (32, 2, 14, TRUE, 1, '2026-06-18T04:32:46.0000000', 1, '2026-06-18T04:33:21.0000000');
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (33, 2, 15, TRUE, 1, '2026-06-18T04:32:46.0000000', 1, '2026-06-18T04:33:21.0000000');
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (34, 2, 16, TRUE, 1, '2026-06-18T04:32:46.0000000', 1, '2026-06-18T04:33:21.0000000');
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (35, 3, 2, TRUE, 1, '2026-06-18T04:33:33.0000000', 1, '2026-06-18T04:34:00.0000000');
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (36, 3, 3, TRUE, 1, '2026-06-18T04:33:33.0000000', 1, '2026-06-18T04:34:00.0000000');
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (37, 3, 4, TRUE, 1, '2026-06-18T04:33:33.0000000', 1, '2026-06-18T04:34:00.0000000');
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (38, 3, 6, TRUE, 1, '2026-06-18T04:33:33.0000000', 1, '2026-06-19T14:22:20.0000000');
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (39, 3, 7, TRUE, 1, '2026-06-18T04:33:33.0000000', 1, '2026-06-19T14:22:20.0000000');
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (40, 3, 8, TRUE, 1, '2026-06-18T04:33:33.0000000', 1, '2026-06-19T14:22:20.0000000');
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (41, 3, 9, TRUE, 1, '2026-06-18T04:33:33.0000000', 1, '2026-06-18T04:34:00.0000000');
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (42, 3, 10, TRUE, 1, '2026-06-18T04:33:33.0000000', 1, '2026-06-18T04:34:00.0000000');
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (43, 3, 11, TRUE, 1, '2026-06-18T04:33:33.0000000', 1, '2026-06-18T04:34:00.0000000');
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (44, 3, 12, TRUE, 1, '2026-06-18T04:33:33.0000000', 1, '2026-06-18T04:34:00.0000000');
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (45, 3, 13, TRUE, 1, '2026-06-18T04:33:33.0000000', 1, '2026-06-18T04:34:00.0000000');
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (46, 3, 14, TRUE, 1, '2026-06-18T04:33:33.0000000', 1, '2026-06-18T04:34:00.0000000');
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (47, 3, 15, TRUE, 1, '2026-06-18T04:33:33.0000000', 1, '2026-06-18T04:34:00.0000000');
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (48, 3, 16, TRUE, 1, '2026-06-18T04:33:33.0000000', 1, '2026-06-18T04:34:00.0000000');
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (49, 5, 1, FALSE, 1, '2026-06-22T04:15:09.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (50, 5, 2, FALSE, 1, '2026-06-22T04:15:09.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (51, 1, 17, FALSE, 1, '2026-07-01T06:53:20.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (52, 1, 18, FALSE, 1, '2026-07-01T06:53:20.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (53, 1, 19, FALSE, 1, '2026-07-01T06:53:20.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (54, 1, 20, FALSE, 1, '2026-07-01T06:53:20.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (55, 3, 17, FALSE, 1, '2026-07-01T07:11:50.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (56, 3, 18, FALSE, 1, '2026-07-01T07:11:50.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (57, 3, 19, FALSE, 1, '2026-07-01T07:11:50.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (58, 3, 20, FALSE, 1, '2026-07-01T07:11:50.0000000', NULL, NULL);
+INSERT INTO "RolePermissions" ("RolePermissionId", "RoleId", "PermissionId", "IsDeleted", "CreatedById", "CreatedAt", "UpdatedById", "UpdatedAt") VALUES (59, 1, 21, FALSE, 1, '2026-07-03T07:14:40.0000000', 1, '2026-07-03T07:15:14.0000000');
+INSERT INTO "Roles" ("Id", "Name", "Description", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (1, 'Admin', 'Full system access', '2026-06-01T00:00:00.000', NULL, NULL, NULL, FALSE);
+INSERT INTO "Roles" ("Id", "Name", "Description", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (2, 'Manager', 'Project-scoped manager', '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Roles" ("Id", "Name", "Description", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (3, 'Employee', 'Task-level team member', '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Roles" ("Id", "Name", "Description", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (4, 'test', NULL, '2026-06-19T13:02:05.897', 1, '2026-06-19T13:06:51.477', 1, TRUE);
+INSERT INTO "Roles" ("Id", "Name", "Description", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (5, 'Supervisor', NULL, '2026-06-22T04:15:08.373', 1, NULL, NULL, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (1, 'Portal landing page', 'Landing page cleanup for Yangon Customer Portal', 1, 3, 2, 14, 5, '2026-06-19T00:00:00.000', '2026-06-01T00:00:00.000', 8, '2026-06-18T12:33:55.640', 1, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (2, 'Login and role routing', 'Login flow and redirect rules', 1, 3, 3, 14, 4, '2026-06-14T00:00:00.000', '2026-06-01T00:00:00.000', 8, '2026-06-18T12:45:53.723', 1, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (3, 'Customer profile form', 'Profile edit and validation', 1, 3, 2, 14, 10, '2026-06-18T00:00:00.000', '2026-06-01T00:00:00.000', 2, '2026-06-17T00:00:00.000', 14, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (4, 'Payments view cleanup', 'UI and filter cleanup for payment section', 1, 3, 3, 15, 3, '2026-06-23T00:00:00.000', '2026-06-01T00:00:00.000', 6, '2026-06-17T08:28:18.077', 1, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (5, 'Notification center', 'Notification list and unread badge', 1, 2, 2, 16, 2, '2026-06-28T00:00:00.000', '2026-06-01T00:00:00.000', 11, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (6, 'Export customer list', 'CSV / Excel export for customer data', 1, 1, 1, 17, 8, '2026-07-05T00:00:00.000', '2026-06-01T00:00:00.000', 3, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (7, 'Audit trail page', 'Activity log and history page', 1, 2, 2, 12, 9, '2026-07-08T00:00:00.000', '2026-06-01T00:00:00.000', 9, '2026-06-24T07:56:19.923', 12, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (8, 'Theme polish', 'Final spacing and color polish', 1, 1, 1, 13, 5, '2026-07-12T00:00:00.000', '2026-06-01T00:00:00.000', 6, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (9, 'Stock dashboard cards', 'Warehouse summary cards and counters', 2, 2, 2, 12, NULL, '2026-06-16T00:00:00.000', '2026-06-03T00:00:00.000', 4, '2026-06-24T07:56:23.640', 12, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (10, 'Warehouse item search', 'Fast search and filter for stock items', 2, 3, 2, 19, 4, '2026-06-18T00:00:00.000', '2026-06-03T00:00:00.000', 4, '2026-06-18T00:00:00.000', 19, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (11, 'Low stock alert setup', 'Warning badges for low stock levels', 2, 2, 3, 20, 5, '2026-06-21T00:00:00.000', '2026-06-03T00:00:00.000', 2, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (12, 'Goods receiving form', 'Receive stock entry form', 2, 3, 2, 21, 2, '2026-06-25T00:00:00.000', '2026-06-03T00:00:00.000', 4, '2026-06-24T00:00:00.000', 21, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (13, 'Item barcode page', 'Barcode lookup and item details screen', 2, 2, 1, 22, 6, '2026-07-01T00:00:00.000', '2026-06-03T00:00:00.000', 5, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (14, 'Inventory report export', 'Export stock summary for management', 2, 1, 2, 23, 10, '2026-07-04T00:00:00.000', '2026-06-03T00:00:00.000', 10, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (15, 'Stock correction audit', 'Review adjustment history', 2, 2, 3, 18, 11, '2026-07-09T00:00:00.000', '2026-06-03T00:00:00.000', 4, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (16, 'Supplier mapping', 'Link suppliers to inventory items', 2, 1, 2, 19, 3, '2026-07-15T00:00:00.000', '2026-06-03T00:00:00.000', 2, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (17, 'Employee directory sync', 'Sync employee directory and roles', 3, 3, 2, 24, 7, '2026-06-17T00:00:00.000', '2026-06-05T00:00:00.000', 2, '2026-06-16T00:00:00.000', 24, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (18, 'Leave request workflow', 'Leave request and approval steps', 3, 3, 3, 25, 6, '2026-06-20T00:00:00.000', '2026-06-05T00:00:00.000', 7, '2026-06-19T00:00:00.000', 25, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (19, 'Attendance summary', 'Daily attendance and monthly summary', 3, 2, 2, 26, 7, '2026-06-24T00:00:00.000', '2026-06-05T00:00:00.000', 5, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (20, 'Holiday calendar setup', 'Company holiday calendar maintenance', 3, 3, 1, 27, 4, '2026-06-29T00:00:00.000', '2026-06-05T00:00:00.000', 5, '2026-06-29T00:00:00.000', 27, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (21, 'Department assignment', 'Department mapping for employee records', 3, 2, 2, 28, 2, '2026-07-03T00:00:00.000', '2026-06-05T00:00:00.000', 2, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (22, 'Payroll export cleanup', 'Prepare payroll export format', 3, 1, 3, 29, 10, '2026-07-08T00:00:00.000', '2026-06-05T00:00:00.000', 7, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (23, 'Manager approval panel', 'Approval screen for managers', 3, 2, 2, 24, 2, '2026-07-11T00:00:00.000', '2026-06-05T00:00:00.000', 6, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (24, 'Employee dashboard polish', 'Visual cleanup for employee view', 3, 1, 1, 25, 3, '2026-07-18T00:00:00.000', '2026-06-05T00:00:00.000', 2, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (25, 'Purchase request list', 'Procurement request list and status', 4, 3, 2, 30, 7, '2026-06-19T00:00:00.000', '2026-06-07T00:00:00.000', 3, '2026-06-19T13:30:00.000', 1, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (26, 'Vendor lookup screen', 'Vendor search and details page', 4, 3, 2, 31, 2, '2026-06-22T00:00:00.000', '2026-06-07T00:00:00.000', 3, '2026-06-19T13:30:00.000', 1, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (27, 'Approval chain setup', 'Approval levels and routing rules', 4, 2, 3, 32, 8, '2026-06-26T00:00:00.000', '2026-06-07T00:00:00.000', 9, '2026-06-19T13:30:00.000', 1, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (28, 'Budget warning banner', 'Banner when purchase exceeds budget', 4, 3, 2, 33, 8, '2026-07-01T00:00:00.000', '2026-06-07T00:00:00.000', 11, '2026-06-19T13:30:00.000', 1, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (29, 'Procurement summary report', 'Summary page for approvals and budgets', 4, 2, 1, 34, 7, '2026-07-06T00:00:00.000', '2026-06-07T00:00:00.000', 2, '2026-06-19T13:30:00.000', 1, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (30, 'Attachment viewer', 'Preview uploaded purchase attachments', 4, 1, 2, 35, 2, '2026-07-10T00:00:00.000', '2026-06-07T00:00:00.000', 11, '2026-06-19T13:30:00.000', 1, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (31, 'Request aging status', 'Age status indicator for procurement requests', 4, 2, 3, 30, 7, '2026-07-14T00:00:00.000', '2026-06-07T00:00:00.000', 4, '2026-06-19T13:30:00.000', 1, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (32, 'Approval comments', 'Comment thread in approval steps', 4, 1, 2, 31, 4, '2026-07-20T00:00:00.000', '2026-06-07T00:00:00.000', 7, '2026-06-19T13:30:00.000', 1, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (33, 'Ticket queue board', 'Kanban-style support queue board', 5, 3, 2, 36, 9, '2026-06-21T00:00:00.000', '2026-06-10T00:00:00.000', 10, '2026-06-19T13:30:00.000', 1, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (34, 'Priority color rules', 'Priority badges and board colors', 5, 3, 3, 37, 3, '2026-06-24T00:00:00.000', '2026-06-10T00:00:00.000', 8, '2026-06-19T13:30:00.000', 1, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (35, 'Assignee quick filter', 'Quick filter by assignee', 5, 2, 2, 38, 3, '2026-06-28T00:00:00.000', '2026-06-10T00:00:00.000', 3, '2026-06-19T13:30:00.000', 1, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (36, 'Overdue banner', 'Highlight overdue tickets clearly', 5, 3, 2, 39, 2, '2026-07-02T00:00:00.000', '2026-06-10T00:00:00.000', 8, '2026-06-19T13:30:00.000', 1, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (37, 'Customer comment log', 'Log of customer replies and notes', 5, 2, 1, 40, 9, '2026-07-07T00:00:00.000', '2026-06-10T00:00:00.000', 5, '2026-06-19T13:30:00.000', 1, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (38, 'FAQ side panel', 'Helpful FAQ panel in support screen', 5, 1, 2, 41, 9, '2026-07-11T00:00:00.000', '2026-06-10T00:00:00.000', 11, '2026-06-19T13:30:00.000', 1, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (39, 'SLA summary chart', 'Support SLA summary and chart', 5, 2, 3, 36, 11, '2026-07-16T00:00:00.000', '2026-06-10T00:00:00.000', 4, '2026-06-19T13:30:00.000', 1, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (40, 'Notification templates', 'Template library for support notifications', 5, 1, 2, 37, 9, '2026-07-22T00:00:00.000', '2026-06-10T00:00:00.000', 9, '2026-06-19T13:30:00.000', 1, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (41, 'Customer Report ', 'adjust customer report', 1, 2, 2, 12, 7, '2026-06-23T00:00:00.000', '2026-06-16T09:35:34.917', 7, '2026-06-24T07:56:16.223', 12, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (42, 'Database Schema Setup', 'Design tables for supply chain entities', 6, 3, 3, 23, 7, '2026-07-15T00:00:00.000', '2026-06-19T13:59:06.027', 10, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (43, 'API Integration', 'Connect tracking endpoints with vendor APIs', 6, 2, 2, 12, NULL, '2026-08-10T00:00:00.000', '2026-06-19T13:59:06.027', 4, '2026-06-24T07:56:27.793', 12, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (44, 'Route Optimization UI', 'Build map visualization dashboard', 6, 1, 2, 12, NULL, '2026-08-30T00:00:00.000', '2026-06-19T13:59:06.027', 4, '2026-06-19T09:51:36.570', 1, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (45, 'Driver Manifest Report', 'Export layout formatting for logistics crews', 6, 2, 1, 25, 7, '2026-09-15T00:00:00.000', '2026-06-19T13:59:06.027', 5, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (46, 'UAT Signoff', 'Final validation with regional operations lead', 6, 2, 3, 26, 3, '2026-09-28T00:00:00.000', '2026-06-19T13:59:06.027', 8, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (47, 'Vendor Registration Form', 'Workflow setup for verified seafood distributors', 7, 3, 2, 27, 9, '2026-08-01T00:00:00.000', '2026-06-19T13:59:06.027', 8, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (48, 'Product Catalog Grid', 'Display live catch categories and listings', 7, 3, 2, 28, 2, '2026-08-25T00:00:00.000', '2026-06-19T13:59:06.027', 11, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (49, 'Bidding Engine Rules', 'Real-time price updating logic engine', 7, 1, 3, 29, 8, '2026-09-20T00:00:00.000', '2026-06-19T13:59:06.027', 3, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (50, 'Escrow Payment Gateway', 'Stripe/Local banking payment integration', 7, 1, 3, 24, 9, '2026-10-15T00:00:00.000', '2026-06-19T13:59:06.027', 9, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (51, 'Mobile Responsive Layout', 'Ensure smooth formatting across Android tablets', 7, 2, 1, 25, 11, '2026-11-05T00:00:00.000', '2026-06-19T13:59:06.027', 11, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (52, 'Meter Reading Sync API', 'Job runner to pull consumption logs', 8, 3, 3, 30, 9, '2026-08-20T00:00:00.000', '2026-06-19T13:59:06.027', 4, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (53, 'Invoice PDF Engine', 'Design standardized print layouts', 8, 3, 2, 31, 7, '2026-09-15T00:00:00.000', '2026-06-19T13:59:06.027', 10, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (54, 'Overdue Alert System', 'SMS notifications for pending payments', 8, 1, 2, 32, 10, '2026-10-25T00:00:00.000', '2026-06-19T13:59:06.027', 3, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (55, 'Tariff Calculator Logic', 'Multi-tiered processing script based on units used', 8, 1, 3, 33, 11, '2026-11-30T00:00:00.000', '2026-06-19T13:59:06.027', 2, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (56, 'Audit Trail Log View', 'Admin UI screen tracking custom manual edits', 8, 2, 1, 34, 5, '2026-12-15T00:00:00.000', '2026-06-19T13:59:06.027', 7, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (57, 'Vessel Docking Scheduler', 'Interactive planning board view for incoming ships', 9, 3, 3, 35, 7, '2026-09-20T00:00:00.000', '2026-06-19T13:59:06.027', 6, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (58, 'Manifest Data Parser', 'Excel/CSV customs document importer logic', 9, 3, 2, 30, 7, '2026-10-05T00:00:00.000', '2026-06-19T13:59:06.027', 5, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (59, 'Live Berth Map Graphic', 'SVG tracking layout of structural ship docks', 9, 1, 2, 31, 10, '2026-10-25T00:00:00.000', '2026-06-19T13:59:06.027', 2, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (60, 'Customs Status Badges', 'Color indicators for tax clear/hold logs', 9, 1, 1, 36, 9, '2026-11-10T00:00:00.000', '2026-06-19T13:59:06.027', 3, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (61, 'Daily Tonnage Aggregator', 'High-performance SQL reporting query', 9, 2, 2, 37, 2, '2026-11-25T00:00:00.000', '2026-06-19T13:59:06.027', 5, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (62, 'IoT Sensor Connector', 'Telemetry listeners for ground moisture grids', 10, 3, 3, 38, 9, '2026-11-01T00:00:00.000', '2026-06-19T13:59:06.027', 4, '2026-06-22T02:45:23.530', 1, FALSE, TRUE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (63, 'Weather Analytics Forecast', 'Third-party API telemetry feed cards', 10, 3, 1, 39, 5, '2026-12-01T00:00:00.000', '2026-06-19T13:59:06.027', 6, '2026-06-22T02:45:23.530', 1, FALSE, TRUE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (64, 'Crop Cycle Milestones', 'Visual step tracker for flower and vegetable blocks', 10, 1, 2, 40, 6, '2027-01-10T00:00:00.000', '2026-06-19T13:59:06.027', 8, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (65, 'Yield Prediction Model', 'Chart projections for harvest season capacities', 10, 1, 2, 41, 7, '2027-02-05T00:00:00.000', '2026-06-19T13:59:06.027', 2, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (66, 'Alert Threshold Adjustments', 'Sliders for minimal resource alarm criteria', 10, 2, 3, 36, 4, '2027-02-20T00:00:00.000', '2026-06-19T13:59:06.027', 5, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (67, 'Local Storage Fallback', 'Caching logic if internet connection drops', 11, 3, 3, 37, 6, '2026-11-20T00:00:00.000', '2026-06-19T13:59:06.027', 5, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (68, 'Barcode Scanner Support', 'Input text pattern handler logic updates', 11, 3, 2, 38, 7, '2026-12-10T00:00:00.000', '2026-06-19T13:59:06.027', 6, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (69, 'Receipt Component Design', 'Thermal printer formatting view engine', 11, 1, 2, 39, 5, '2026-12-28T00:00:00.000', '2026-06-19T13:59:06.027', 11, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (70, 'Shift Drawer Checkout', 'Cash drawer closing calculations view', 11, 1, 2, 40, 11, '2027-01-15T00:00:00.000', '2026-06-19T13:59:06.027', 2, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (71, 'Discount Code Engine', 'Flexible rules for flat rate percentage deductions', 11, 2, 1, 41, 5, '2027-01-25T00:00:00.000', '2026-06-19T13:59:06.027', 10, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (72, 'Hotel Availability Calendars', 'Dynamic availability layout blocks', 12, 3, 3, 23, 10, '2027-01-05T00:00:00.000', '2026-06-19T13:59:06.027', 5, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (73, 'Tour Guide Matrix Profiles', 'Rating breakdowns and skill badge designs', 12, 3, 1, 24, 3, '2027-02-01T00:00:00.000', '2026-06-19T13:59:06.027', 9, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (74, 'Multi-Currency Selector', 'Automatic exchange rate conversion cards', 12, 1, 2, 19, 7, '2027-03-01T00:00:00.000', '2026-06-19T13:59:06.027', 7, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (75, 'Offline Map Pack Downloads', 'Asset compression handlers for remote areas', 12, 1, 3, 25, 2, '2027-03-25T00:00:00.000', '2026-06-19T13:59:06.027', 4, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (76, 'Reviews Slider Feedback', 'Submit forms with media image attachments', 12, 2, 2, 26, 2, '2027-04-15T00:00:00.000', '2026-06-19T13:59:06.027', 9, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (77, 'Org Chart Matrix Engine', 'Interactive recursive reporting layouts', 13, 3, 2, 27, 4, '2027-02-10T00:00:00.000', '2026-06-19T13:59:06.027', 3, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (78, 'Asset Lifetime Depreciation', 'Financial calculations handling ledger variables', 13, 3, 3, 28, 10, '2027-03-15T00:00:00.000', '2026-06-19T13:59:06.027', 11, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (79, 'Purchase Order Flow Approvals', 'Dynamic multi-manager routing filters', 13, 1, 3, 29, 8, '2027-04-30T00:00:00.000', '2026-06-19T13:59:06.027', 3, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (80, 'Inventory Reconciliation Module', 'Stock discrepancy evaluation reports', 13, 1, 2, 24, 9, '2027-05-20T00:00:00.000', '2026-06-19T13:59:06.027', 10, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (81, 'SAML SSO Configuration', 'Corporate secure profile directory linkages', 13, 2, 3, 25, 7, '2027-06-15T00:00:00.000', '2026-06-19T13:59:06.027', 7, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (82, 'Inverter Diagnostic Parsing', 'Error code streaming parser functions', 14, 3, 3, 30, 6, '2027-02-25T00:00:00.000', '2026-06-19T13:59:06.027', 11, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (83, 'KWh Generation Linecharts', 'Time series streaming metrics configurations', 14, 3, 2, 31, 2, '2027-03-20T00:00:00.000', '2026-06-19T13:59:06.027', 8, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (84, 'Battery Degradation Alerter', 'Proactive email dispatcher rules criteria', 14, 1, 3, 32, 11, '2027-04-10T00:00:00.000', '2026-06-19T13:59:06.027', 7, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (85, 'Maintenance Dispatch Planner', 'Assign technicians based on geolocation proximity', 14, 1, 2, 33, 11, '2027-05-05T00:00:00.000', '2026-06-19T13:59:06.027', 8, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (86, 'Weather Impact Forecasting', 'Overlay expected overcast cloud calculations', 14, 2, 1, 34, 5, '2027-05-25T00:00:00.000', '2026-06-19T13:59:06.027', 5, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (87, 'Student Enrollment Grid', 'Batch processing CSV processing views', 15, 3, 2, 35, 3, '2027-04-01T00:00:00.000', '2026-06-19T13:59:06.027', 2, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (88, 'Quiz Generator Panel', 'Form wizard builder tool configuration', 15, 3, 2, 30, 3, '2027-05-15T00:00:00.000', '2026-06-19T13:59:06.027', 9, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (89, 'Video Lecture Player Wrapper', 'Custom video player UI component controls', 15, 1, 3, 31, 6, '2027-06-20T00:00:00.000', '2026-06-19T13:59:06.027', 7, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (90, 'Grading Rubric Calculations', 'Weighted score aggregate math logic functions', 15, 1, 3, 36, 7, '2027-07-25T00:00:00.000', '2026-06-19T13:59:06.027', 11, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (91, 'Certificate Generation System', 'Automated PDF generation with secure signatures', 15, 2, 1, 37, 9, '2027-08-18T00:00:00.000', '2026-06-19T13:59:06.027', 3, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (92, 'Moisture Content Checklist', 'Intake quality verification forms', 16, 3, 3, 38, 3, '2027-05-01T00:00:00.000', '2026-06-19T13:59:06.027', 10, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (93, 'Silo Storage Tracking', 'Volumetric storage level visual component', 16, 3, 2, 39, 2, '2027-05-25T00:00:00.000', '2026-06-19T13:59:06.027', 9, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (94, 'Wholesale Pricing Sheets', 'Bulk pricing calculation configurations', 16, 1, 2, 40, 8, '2027-06-15T00:00:00.000', '2026-06-19T13:59:06.027', 7, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (95, 'Batch Shipments Monitor', 'Fulfillment workflow validation checks', 16, 1, 3, 41, 9, '2027-07-02T00:00:00.000', '2026-06-19T13:59:06.027', 6, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (96, 'Scale Calibration Log', 'Record periodic equipment verification histories', 16, 2, 1, 36, 4, '2027-07-12T00:00:00.000', '2026-06-19T13:59:06.027', 7, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (97, 'Vessel GPS Coordinates Stream', 'Leaflet map vector visualization layer integrations', 17, 3, 3, 37, 10, '2027-06-01T00:00:00.000', '2026-06-19T13:59:06.027', 6, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (98, 'Species Harvest Trackers', 'Data breakdowns for compliance regulatory reports', 17, 3, 2, 38, 5, '2027-07-15T00:00:00.000', '2026-06-19T13:59:06.027', 7, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (99, 'Zone Restrictive Parameters', 'Polygonal boundary intersection check alarms', 17, 1, 3, 39, 2, '2027-08-30T00:00:00.000', '2026-06-19T13:59:06.027', 3, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (100, 'Seasonal Quota Counter Gauges', 'Radial dynamic bar indicator graphics', 17, 1, 2, 40, 4, '2027-09-25T00:00:00.000', '2026-06-19T13:59:06.027', 11, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (101, 'Patrol Crew Assignment UI', 'Shift scheduler dispatch interface layouts', 17, 2, 1, 41, 9, '2027-10-15T00:00:00.000', '2026-06-19T13:59:06.027', 6, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (102, 'Patient Registration Wizard', 'Multi-step demographics entry card setup', 18, 3, 2, 23, 11, '2027-06-20T00:00:00.000', '2026-06-19T13:59:06.027', 11, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (103, 'Vitals Stream Intake', 'Fields mapping pulse, temperature, and blood pressure', 18, 3, 3, 24, 11, '2027-07-15T00:00:00.000', '2026-06-19T13:59:06.027', 7, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (104, 'Prescription Text Composer', 'Autocomplete pharmaceutical database connectors', 18, 1, 3, 19, 8, '2027-08-10T00:00:00.000', '2026-06-19T13:59:06.027', 8, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (105, 'Appointment Waitlist Logic', 'FIFO triage processing queue mechanics', 18, 1, 2, 25, 8, '2027-09-05T00:00:00.000', '2026-06-19T13:59:06.027', 11, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (106, 'Lab Result File Attachment', 'Secure PDF scan upload components', 18, 2, 1, 26, 2, '2027-09-25T00:00:00.000', '2026-06-19T13:59:06.027', 2, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (107, 'Grower Cooperative Listing', 'Directory views showing regional sourcing partners', 19, 3, 2, 27, 7, '2027-08-01T00:00:00.000', '2026-06-19T13:59:06.027', 5, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (108, 'Drying Batch Quality Logs', 'Grade checks logging humidity variables', 19, 3, 2, 28, 8, '2027-09-15T00:00:00.000', '2026-06-19T13:59:06.027', 3, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (109, 'Container Packing Manifests', 'CBM cube optimization distribution matrices', 19, 1, 3, 29, 2, '2027-11-01T00:00:00.000', '2026-06-19T13:59:06.027', 6, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (110, 'Customs Duty Adjuster', 'Tax profile calculations based on region rules', 19, 1, 2, 24, 7, '2027-11-30T00:00:00.000', '2026-06-19T13:59:06.027', 10, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (111, 'Barcode Logistics Signoff', 'Mobile handheld warehouse terminal interfaces', 19, 2, 1, 25, 2, '2027-12-20T00:00:00.000', '2026-06-19T13:59:06.027', 9, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (112, 'Yarn Consumable Trackers', 'Color dyeing stock count balancing scripts', 20, 3, 3, 30, 7, '2027-09-05T00:00:00.000', '2026-06-19T13:59:06.027', 2, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (113, 'Loom Capacity Management', 'Operational calendar overview screens for factory equipment', 20, 3, 2, 31, 11, '2027-10-15T00:00:00.000', '2026-06-19T13:59:06.027', 5, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (114, 'Defect Frequency Pareto Chart', 'Aggregated reporting tables for quality audits', 20, 1, 2, 32, 2, '2027-11-20T00:00:00.000', '2026-06-19T13:59:06.027', 7, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (115, 'B2B Client Ordering Portal', 'Direct wholesale submission forms matrix', 20, 1, 3, 33, 7, '2027-12-15T00:00:00.000', '2026-06-19T13:59:06.027', 3, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (116, 'Shipping Invoice Deserializer', 'Automated packing breakdown logs generation', 20, 2, 1, 34, 7, '2028-01-08T00:00:00.000', '2026-06-19T13:59:06.027', 6, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (117, 'Critical Security Patch', 'Apply vulnerability updates to auth endpoints', 1, 2, 3, 12, 6, '2026-06-10T17:00:00.000', '2026-06-19T19:53:49.880', 4, '2026-06-24T07:56:12.210', 12, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (118, 'Database Migration Backup Check', 'Verify integrity logs for early June snapshots', 1, 2, 2, 13, 11, '2026-06-12T12:00:00.000', '2026-06-19T19:53:49.880', 6, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (119, 'Barcode Scanning Performance Fix', 'Optimize UI delay when processing consecutive items', 2, 2, 3, 18, 9, '2026-06-14T18:30:00.000', '2026-06-19T19:53:49.880', 11, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (120, 'Supplier Sync Validation', 'Investigate broken field parameters during nightly cron job', 2, 2, 2, 19, 8, '2026-06-15T09:00:00.000', '2026-06-19T19:53:49.880', 7, '2026-06-22T03:15:36.007', 1, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (121, 'Leave Policy Matrix Update', 'Adjust structural logic for regional holiday tracking formulas', 3, 1, 2, 24, 11, '2026-06-16T23:59:00.000', '2026-06-19T19:53:49.880', 2, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (122, 'Manager Dashboard Loading Delay', 'Optimize missing indexing queries causing timeout flags', 3, 2, 3, 25, 3, '2026-06-17T16:00:00.000', '2026-06-19T19:53:49.880', 10, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (123, 'API Route Finalization', 'Merge staging gateway links into production controller', 1, 2, 2, 14, 2, '2026-06-20T17:00:00.000', '2026-06-19T19:53:49.880', 10, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (124, 'Stock Variance Report UI', 'Format front-end data tables to highlight asset deficits', 2, 2, 2, 15, 6, '2026-06-21T12:00:00.000', '2026-06-19T19:53:49.880', 3, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (125, 'Warehouse Bin Mapping Card', 'Complete dynamic grid visualization view properties', 2, 2, 1, 16, 8, '2026-06-21T17:00:00.000', '2026-06-19T19:53:49.880', 9, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (126, 'Employee Directory Export Button', 'Implement excel/CSV layout downloads for HR staff', 3, 2, 2, 26, 8, '2026-06-22T15:30:00.000', '2026-06-19T19:53:49.880', 6, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (127, 'Document Upload Middleware', 'Enforce strict structural MIME-type verification controls', 3, 2, 3, 27, 8, '2026-06-22T18:00:00.000', '2026-06-19T19:53:49.880', 10, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (128, 'caculate price', NULL, 22, 1, 2, 12, NULL, '2026-06-29T00:00:00.000', '2026-06-22T04:01:27.747', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (129, 'Conduct requirements workshop for checkout flow', 'POS and storefront refresh for Q1 - Collect change requests and confirm scope', 23, 1, 2, 12, 1, '2027-01-11T00:00:00.000', '2027-01-02T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (130, 'Implement core POS storefront integration', 'POS and storefront refresh for Q1 - Implement the core screens and services', 23, 2, 3, 13, 1, '2027-01-18T00:00:00.000', '2027-01-03T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (131, 'Validate multi-currency transaction processing', 'POS and storefront refresh for Q1 - Verify edge cases and prep release notes', 23, 3, 1, 12, 1, '2027-01-25T00:00:00.000', '2027-01-04T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (132, 'Draft customer dashboard SLA requirements', 'Support portal and SLA workflow for Q1 - Collect change requests and confirm scope', 24, 1, 2, 14, 1, '2027-02-11T00:00:00.000', '2027-02-02T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (133, 'Develop service ticket submission interface', 'Support portal and SLA workflow for Q1 - Implement the core screens and services', 24, 2, 3, 15, 1, '2027-02-18T00:00:00.000', '2027-02-03T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (134, 'Run UAT billing/routing verification flows', 'Support portal and SLA workflow for Q1 - Verify edge cases and prep release notes', 24, 3, 1, 14, 1, '2027-02-25T00:00:00.000', '2027-02-04T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (135, 'Review statutory tax calculation rules', 'Payroll and approvals refresh for Q2 - Collect change requests and confirm scope', 25, 1, 2, 16, 1, '2027-03-11T00:00:00.000', '2027-03-02T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (136, 'Build automated monthly processing engine', 'Payroll and approvals refresh for Q2 - Implement the core screens and services', 25, 2, 3, 17, 1, '2027-03-18T00:00:00.000', '2027-03-03T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (137, 'Verify payslip PDF layout and data accuracy', 'Payroll and approvals refresh for Q2 - Verify edge cases and prep release notes', 25, 3, 1, 16, 1, '2027-03-25T00:00:00.000', '2027-03-04T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (138, 'Define vendor onboarding API schemas', 'Vendor onboarding and approval board - Collect change requests and confirm scope', 26, 1, 2, 18, 1, '2027-04-11T00:00:00.000', '2027-04-02T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (139, 'Create registration wizard and upload UI', 'Vendor onboarding and approval board - Implement the core screens and services', 26, 2, 3, 19, 1, '2027-04-18T00:00:00.000', '2027-04-03T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (140, 'Audit security compliance on document storage', 'Vendor onboarding and approval board - Verify edge cases and prep release notes', 26, 3, 1, 18, 1, '2027-04-25T00:00:00.000', '2027-04-04T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (141, 'Analyze real-time GPS location tracking constraints', 'Delivery scheduling and route updates - Collect change requests and confirm scope', 27, 1, 2, 20, 1, '2027-05-11T00:00:00.000', '2027-05-02T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (142, 'Integrate map APIs and background polling service', 'Delivery scheduling and route updates - Implement the core screens and services', 27, 2, 3, 21, 1, '2027-05-18T00:00:00.000', '2027-05-03T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (143, 'Test edge cases for background app termination', 'Delivery scheduling and route updates - Verify edge cases and prep release notes', 27, 3, 1, 20, 1, '2027-05-25T00:00:00.000', '2027-05-04T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (144, 'Document offline data synchronization strategy', 'Field support mobile app - Collect change requests and confirm scope', 28, 1, 2, 22, 1, '2027-06-11T00:00:00.000', '2027-06-02T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (145, 'Implement local SQLite cache and sync queue', 'Field support mobile app - Implement the core screens and services', 28, 2, 3, 23, 1, '2027-06-18T00:00:00.000', '2027-06-03T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (146, 'Perform network reconnection conflict resolution tests', 'Field support mobile app - Verify edge cases and prep release notes', 28, 3, 1, 22, 1, '2027-06-25T00:00:00.000', '2027-06-04T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (147, 'Finalize KPI reporting and visualization metrics', 'Sales analytics and KPI rollout - Collect change requests and confirm scope', 29, 1, 2, 24, 1, '2027-07-11T00:00:00.000', '2027-07-02T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (148, 'Build aggregate sales APIs with caching layer', 'Sales analytics and KPI rollout - Implement the core screens and services', 29, 2, 3, 25, 1, '2027-07-18T00:00:00.000', '2027-07-03T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (149, 'Conduct cross-browser and responsiveness check', 'Sales analytics and KPI rollout - Verify edge cases and prep release notes', 29, 3, 1, 24, 1, '2027-07-25T00:00:00.000', '2027-07-04T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (150, 'Map workflow state rules and approval matrix', 'Claims intake and review workflow - Collect change requests and confirm scope', 30, 1, 2, 26, 1, '2027-08-11T00:00:00.000', '2027-08-02T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (151, 'Develop automated rules engine for intake evaluation', 'Claims intake and review workflow - Implement the core screens and services', 30, 2, 3, 27, 1, '2027-08-18T00:00:00.000', '2027-08-03T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (152, 'Verify edge cases on notification routing rules', 'Claims intake and review workflow - Verify edge cases and prep release notes', 30, 3, 1, 26, 1, '2027-08-25T00:00:00.000', '2027-08-04T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (153, 'Gather warehouse bin mapping and allocation workflows', 'Cargo status and dispatch planning - Collect change requests and confirm scope', 31, 1, 2, 28, 1, '2027-09-11T00:00:00.000', '2027-09-02T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (154, 'Implement hardware barcode scanner API support', 'Cargo status and dispatch planning - Implement the core screens and services', 31, 2, 3, 29, 1, '2027-09-18T00:00:00.000', '2027-09-03T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (155, 'Run high-volume simulation stress tests', 'Cargo status and dispatch planning - Verify edge cases and prep release notes', 31, 3, 1, 28, 1, '2027-09-25T00:00:00.000', '2027-09-04T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (156, 'Review subscription proration and refund logic', 'Billing and statement enhancements - Collect change requests and confirm scope', 32, 1, 2, 30, 1, '2027-10-11T00:00:00.000', '2027-10-02T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (157, 'Integrate recurring invoice gateway endpoints', 'Billing and statement enhancements - Implement the core screens and services', 32, 2, 3, 31, 1, '2027-10-18T00:00:00.000', '2027-10-03T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (158, 'Validate automated payment retry dunning loops', 'Billing and statement enhancements - Verify edge cases and prep release notes', 32, 3, 1, 30, 1, '2027-10-25T00:00:00.000', '2027-10-04T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (159, 'Scope chat widget SLA handling rules', 'Customer care portal and case notes - Collect change requests and confirm scope', 33, 1, 2, 32, 1, '2027-11-11T00:00:00.000', '2027-11-02T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (160, 'Embed live chat components into client portals', 'Customer care portal and case notes - Implement the core screens and services', 33, 2, 3, 33, 1, '2027-11-18T00:00:00.000', '2027-11-03T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (161, 'Audit chat transcript persistence and storage cleanup', 'Customer care portal and case notes - Verify edge cases and prep release notes', 33, 3, 1, 32, 1, '2027-11-25T00:00:00.000', '2027-11-04T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (162, 'Assess resource allocation timeline requirements', 'Operations planning for year-end launch - Collect change requests and confirm scope', 34, 1, 2, 34, 1, '2027-12-11T00:00:00.000', '2027-12-02T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (163, 'Develop interactive scheduling Gantt chart component', 'Operations planning for year-end launch - Implement the core screens and services', 34, 2, 3, 35, 1, '2027-12-18T00:00:00.000', '2027-12-03T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Tasks" ("Id", "Title", "Description", "ProjectId", "StatusId", "PriorityId", "AssignedTo", "AssignedBy", "DueDate", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted", "IsArchived") VALUES (164, 'Verify event triggers on drag-and-drop actions', 'Operations planning for year-end launch - Verify edge cases and prep release notes', 34, 3, 1, 34, 1, '2027-12-25T00:00:00.000', '2027-12-04T00:00:00.000', 1, NULL, NULL, FALSE, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (1, 'admin', 'System', 'Admin', 'admin@tts.local', 'AQAAAAIAAYagAAAAECvk1uHPJiU8Uh+ZE5Gu8KnSE2sfPzF/gWE6WIPftNBpBTE+xeWUsNZJ74vj/WUiQA==', '09111111111', 1, TRUE, '2026-06-01T00:00:00.000', NULL, '2026-06-19T15:46:13.323', NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (2, 'mgr01', 'Kyaw Kyaw', 'Aung', 'mgr01@tts.local', 'AQAAAAIAAYagAAAAEOgN6MzeoxlSLef4b6w5eQ9EO4Lsllm8iMXbM7Nt/e2vJEeSfadZKOHI4wg3OmWlcQ==', '09200000002', 2, TRUE, '2026-06-01T00:00:00.000', 1, '2026-06-16T09:32:15.587', NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (3, 'mgr02', 'Min Min', 'Htun', 'mgr02@tts.local', 'HASHED_P@ssw0rd123!', '09200000003', 2, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (4, 'mgr03', 'Mg Mg', 'Naing', 'mgr03@tts.local', 'HASHED_P@ssw0rd123!', '09200000004', 2, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (5, 'mgr04', 'Aung Aung', 'Oo', 'mgr04@tts.local', 'HASHED_P@ssw0rd123!', '09200000005', 2, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (6, 'mgr05', 'Wai Yan', 'Tun', 'mgr05@tts.local', 'HASHED_P@ssw0rd123!', '09200000006', 2, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (7, 'mgr06', 'Thura', 'Ko', 'mgr06@tts.local', 'HASHED_P@ssw0rd123!', '09200000007', 2, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (8, 'mgr07', 'Su Su', 'Win', 'mgr07@tts.local', 'HASHED_P@ssw0rd123!', '09200000008', 2, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (9, 'mgr08', 'Nandar', 'Hlaing', 'mgr08@tts.local', 'HASHED_P@ssw0rd123!', '09200000009', 2, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (10, 'mgr09', 'Phyo Phyo', 'Aye', 'mgr09@tts.local', 'HASHED_P@ssw0rd123!', '09200000010', 2, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (11, 'mgr10', 'Zaw Zaw', 'Lin', 'mgr10@tts.local', 'HASHED_P@ssw0rd123!', '09200000011', 2, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (12, 'emp01', 'Hla Hla', 'Khin', 'emp01@tts.local', 'AQAAAAIAAYagAAAAEA4f098+mKmL/CQTOuKH1CkfPiJwPEhy6bxxzVH4EUVTSuowjzKm5K9y0qI0bELQ8Q==', '09300000012', 3, TRUE, '2026-06-01T00:00:00.000', 1, '2026-06-17T03:48:14.057', NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (13, 'emp02', 'Nwe Nwe', 'Aye', 'emp02@tts.local', 'HASHED_P@ssw0rd123!', '09300000013', 3, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (14, 'emp03', 'Ei Ei', 'Mon', 'emp03@tts.local', 'AQAAAAIAAYagAAAAELrpxwuFpgj6b0nqE/Y52BOgkPLe+gZi31D68B9JqrlJu442YGtwS+HySwi2WFV+UA==', '09300000014', 3, TRUE, '2026-06-01T00:00:00.000', 1, '2026-06-18T15:49:29.023', NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (15, 'emp04', 'Thandar', 'Win', 'emp04@tts.local', 'HASHED_P@ssw0rd123!', '09300000015', 3, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (16, 'emp05', 'Khin Khin', 'Tun', 'emp05@tts.local', 'HASHED_P@ssw0rd123!', '09300000016', 3, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (17, 'emp06', 'Su Mon', 'Hlaing', 'emp06@tts.local', 'HASHED_P@ssw0rd123!', '09300000017', 3, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (18, 'emp07', 'Mon Mon', 'Aung', 'emp07@tts.local', 'HASHED_P@ssw0rd123!', '09300000018', 3, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (19, 'emp08', 'Yee Yee', 'Lwin', 'emp08@tts.local', 'HASHED_P@ssw0rd123!', '09300000019', 3, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (20, 'emp09', 'Pyae Pyae', 'Naing', 'emp09@tts.local', 'HASHED_P@ssw0rd123!', '09300000020', 3, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (21, 'emp10', 'May', 'Zaw', 'emp10@tts.local', 'HASHED_P@ssw0rd123!', '09300000021', 3, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (22, 'emp11', 'Htet Htet', 'Oo', 'emp11@tts.local', 'HASHED_P@ssw0rd123!', '09300000022', 3, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (23, 'emp12', 'Aye Aye', 'Khaing', 'emp12@tts.local', 'HASHED_P@ssw0rd123!', '09300000023', 3, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (24, 'emp13', 'Chaw Chaw', 'Aung', 'emp13@tts.local', 'HASHED_P@ssw0rd123!', '09300000024', 3, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (25, 'emp14', 'Mya Mya', 'Myint', 'emp14@tts.local', 'HASHED_P@ssw0rd123!', '09300000025', 3, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (26, 'emp15', 'Lwin Lwin', 'Htun', 'emp15@tts.local', 'HASHED_P@ssw0rd123!', '09300000026', 3, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (27, 'emp16', 'Phyu Phyu', 'Soe', 'emp16@tts.local', 'HASHED_P@ssw0rd123!', '09300000027', 3, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (28, 'emp17', 'Myat Myat', 'Aung', 'emp17@tts.local', 'HASHED_P@ssw0rd123!', '09300000028', 3, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (29, 'emp18', 'Nyein Nyein', 'Win', 'emp18@tts.local', 'HASHED_P@ssw0rd123!', '09300000029', 3, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (30, 'emp19', 'Kyu Kyu', 'Hlaing', 'emp19@tts.local', 'HASHED_P@ssw0rd123!', '09300000030', 3, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (31, 'emp20', 'San San', 'Aye', 'emp20@tts.local', 'HASHED_P@ssw0rd123!', '09300000031', 3, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (32, 'emp21', 'Si Si', 'Ko', 'emp21@tts.local', 'HASHED_P@ssw0rd123!', '09300000032', 3, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (33, 'emp22', 'Moe Moe', 'Thant', 'emp22@tts.local', 'HASHED_P@ssw0rd123!', '09300000033', 3, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (34, 'emp23', 'Yu Yu', 'Naing', 'emp23@tts.local', 'HASHED_P@ssw0rd123!', '09300000034', 3, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (35, 'emp24', 'Pan Pan', 'Wai', 'emp24@tts.local', 'HASHED_P@ssw0rd123!', '09300000035', 3, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (36, 'emp25', 'Khaing', 'Khine', 'emp25@tts.local', 'HASHED_P@ssw0rd123!', '09300000036', 3, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (37, 'emp26', 'Thiri', 'Aung', 'emp26@tts.local', 'HASHED_P@ssw0rd123!', '09300000037', 3, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (38, 'emp27', 'Hnin', 'Aye', 'emp27@tts.local', 'HASHED_P@ssw0rd123!', '09300000038', 3, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (39, 'emp28', 'Wint', 'Oo', 'emp28@tts.local', 'HASHED_P@ssw0rd123!', '09300000039', 3, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (40, 'emp29', 'Chan', 'Moe', 'emp29@tts.local', 'HASHED_P@ssw0rd123!', '09300000040', 3, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (41, 'emp30', 'Aye', 'Mon', 'emp30@tts.local', 'HASHED_P@ssw0rd123!', '09300000041', 3, TRUE, '2026-06-01T00:00:00.000', 1, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (42, 'mgr11', 'Htoo Aung', 'Lwin', 'mgr01@gmail.com', 'AQAAAAIAAYagAAAAECTH+CrFqAOxC9VajYmq7WJaaaYZ+jvjO8+XO7ovwRn7YejMYZTLTEROurI6YMbMKg==', '234565432', 2, TRUE, '2026-06-17T07:48:27.583', NULL, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (43, 'testuser', 'Test', 'User', 'test@example.com', 'AQAAAAIAAYagAAAAEA5lUHPsaeUPeUaznoHnjOvHSm2skqAERD9wbyJnHcHWNnuBdy6lnjRF3EtAy4FJIQ==', '', 3, TRUE, '2026-06-18T09:25:35.540', NULL, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (44, 'Ths_16', 'Thin Htet', 'San', 'aiyurina161020@gmail.com', 'AQAAAAIAAYagAAAAEHpOQiiSeZkhqxFD+4VtPnq82TW4apBdt1qvCzt2fPFxAUyzYb5mm1DBmvAp5QfRYw==', '09260022544', 3, TRUE, '2026-06-18T15:50:16.737', NULL, NULL, NULL, FALSE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (45, 'testing', 'test', 'ing', 'testing@gmail.com', 'AQAAAAIAAYagAAAAEHtWl016AGw9A8en4pQ20vZM/Lm1drEQTuZEbno45E2r9TkhFcTGNzECxOdFb2HzvQ==', '0923847568584', 4, TRUE, '2026-06-19T13:02:46.403', NULL, NULL, NULL, TRUE);
+INSERT INTO "Users" ("Id", "Username", "FirstName", "LastName", "Email", "PasswordHash", "Phone", "RoleId", "IsActive", "CreatedAt", "CreatedBy", "UpdatedAt", "UpdatedBy", "IsDeleted") VALUES (46, 'maungaye', 'Maung', 'Aye', 'maungaye@gmail.com', 'AQAAAAIAAYagAAAAEGMdoiwJ634iuAxFxHUb+Qqkp1lQ6ein6A4i8EWlCvaofif8dswsERK9jHixckQddw==', '098765432234', 3, TRUE, '2026-06-22T04:13:19.313', NULL, NULL, NULL, FALSE);
+
+-- Reset primary key sequences for tables with auto-increment IDs
+SELECT setval(pg_get_serial_sequence('"Roles"', 'Id'), COALESCE(MAX("Id"), 1)) FROM "Roles";
+SELECT setval(pg_get_serial_sequence('"Users"', 'Id'), COALESCE(MAX("Id"), 1)) FROM "Users";
+SELECT setval(pg_get_serial_sequence('"Menus"', 'MenuId'), COALESCE(MAX("MenuId"), 1)) FROM "Menus";
+SELECT setval(pg_get_serial_sequence('"Permissions"', 'PermissionId'), COALESCE(MAX("PermissionId"), 1)) FROM "Permissions";
+SELECT setval(pg_get_serial_sequence('"RoleMenus"', 'RoleMenuId'), COALESCE(MAX("RoleMenuId"), 1)) FROM "RoleMenus";
+SELECT setval(pg_get_serial_sequence('"RolePermissions"', 'RolePermissionId'), COALESCE(MAX("RolePermissionId"), 1)) FROM "RolePermissions";
+SELECT setval(pg_get_serial_sequence('"Projects"', 'Id'), COALESCE(MAX("Id"), 1)) FROM "Projects";
+SELECT setval(pg_get_serial_sequence('"Tasks"', 'Id'), COALESCE(MAX("Id"), 1)) FROM "Tasks";
+SELECT setval(pg_get_serial_sequence('"Comments"', 'Id'), COALESCE(MAX("Id"), 1)) FROM "Comments";
+SELECT setval(pg_get_serial_sequence('"AuditLogs"', 'Id'), COALESCE(MAX("Id"), 1)) FROM "AuditLogs";
+SELECT setval(pg_get_serial_sequence('"Issues"', 'Id'), COALESCE(MAX("Id"), 1)) FROM "Issues";
+SELECT setval(pg_get_serial_sequence('"notifications"', 'Id'), COALESCE(MAX("Id"), 1)) FROM "notifications";
+SELECT setval(pg_get_serial_sequence('"user_devices"', 'Id'), COALESCE(MAX("Id"), 1)) FROM "user_devices";
+SET session_replication_role = 'origin';
+COMMIT;
diff --git a/TaskTrackingSystem.Database/TaskTrackingSystem.Database.csproj b/TaskTrackingSystem.Database/TaskTrackingSystem.Database.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..63d34d0395761bbc1efd7aef94e487732a48aa47
--- /dev/null
+++ b/TaskTrackingSystem.Database/TaskTrackingSystem.Database.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net8.0
+ enable
+ enable
+ false
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
diff --git a/TaskTrackingSystem.Shared/DisplayFormats.cs b/TaskTrackingSystem.Shared/DisplayFormats.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0d6ed8c47e97f1971b2a25d5f2066720aa48382f
--- /dev/null
+++ b/TaskTrackingSystem.Shared/DisplayFormats.cs
@@ -0,0 +1,17 @@
+using System.Globalization;
+
+namespace TaskTrackingSystem.Shared;
+
+public static class DisplayFormats
+{
+ public const string DateFormat = "dd-MM-yyyy";
+
+ public static string Date(DateTime value) =>
+ value.ToString(DateFormat, CultureInfo.InvariantCulture);
+
+ public static string Date(DateTime? value) =>
+ value.HasValue ? Date(value.Value) : string.Empty;
+
+ public static string Date(DateOnly value) =>
+ value.ToString(DateFormat, CultureInfo.InvariantCulture);
+}
diff --git a/TaskTrackingSystem.Shared/Enums/TaskPriority.cs b/TaskTrackingSystem.Shared/Enums/TaskPriority.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0a66a55a1fec0f5265647f0b5a91666ab75ec8cc
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Enums/TaskPriority.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace TaskTrackingSystem.Shared.Enums
+{
+ public enum TaskPriority : long
+ {
+ Low = 1,
+ Medium = 2,
+ High = 3
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Enums/TaskStatus.cs b/TaskTrackingSystem.Shared/Enums/TaskStatus.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6a031ea61f37a162f1d6f268ffdd2165ee552933
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Enums/TaskStatus.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace TaskTrackingSystem.Shared.Enums
+{
+ public enum AppTaskStatus : long
+ {
+ Todo = 1,
+ InProgress = 2,
+ Done = 3
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Localization/AppLocalization.cs b/TaskTrackingSystem.Shared/Localization/AppLocalization.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f1b94af97eca575951e157a73c41ad973dae1144
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Localization/AppLocalization.cs
@@ -0,0 +1,615 @@
+using System.Globalization;
+using TaskTrackingSystem.Shared.Enums;
+
+namespace TaskTrackingSystem.Shared.Localization;
+
+public static class AppLocalization
+{
+ private static readonly IReadOnlyDictionary English = new Dictionary(StringComparer.OrdinalIgnoreCase)
+ {
+ ["common.workspace"] = "Workspace",
+ ["common.signIn"] = "Sign In",
+ ["common.signOut"] = "Sign Out",
+ ["common.signOutTitle"] = "Sign Out?",
+ ["common.signOutDescription"] = "Are you sure you want to sign out of Taskify?",
+ ["common.cancel"] = "Cancel",
+ ["common.confirm"] = "Confirm",
+ ["common.confirmAction"] = "Confirm",
+ ["common.areYouSure"] = "Are you sure?",
+ ["common.cannotUndo"] = "This action cannot be undone.",
+ ["common.yes"] = "Yes",
+ ["common.no"] = "No",
+ ["common.save"] = "Save",
+ ["common.create"] = "Create",
+ ["common.update"] = "Update",
+ ["common.delete"] = "Delete",
+ ["common.confirmSignOut"] = "Yes, Sign Out",
+ ["common.listView"] = "List View",
+ ["common.chartView"] = "Chart View",
+ ["common.search"] = "Search",
+ ["common.reset"] = "Reset",
+ ["common.clear"] = "Clear",
+ ["common.all"] = "All",
+ ["common.loading"] = "Loading...",
+ ["common.close"] = "Close",
+ ["common.openNavigation"] = "Open navigation",
+ ["common.closeNavigation"] = "Close navigation",
+ ["common.index"] = "No.",
+ ["common.exportExcel"] = "Export Excel",
+ ["common.exportPdf"] = "Export PDF",
+ ["common.actions"] = "Actions",
+ ["common.status"] = "Status",
+ ["common.priority"] = "Priority",
+ ["common.assignee"] = "Assignee",
+ ["common.project"] = "Project",
+ ["common.task"] = "Task",
+ ["common.taskProject"] = "Task / Project",
+ ["common.issue"] = "Issue",
+ ["common.user"] = "User",
+ ["common.role"] = "Role",
+ ["common.module"] = "Module",
+ ["common.description"] = "Description",
+ ["common.date"] = "Date",
+ ["common.startDate"] = "Start Date",
+ ["common.endDate"] = "End Date",
+ ["common.dueDate"] = "Due Date",
+ ["common.due"] = "Due",
+ ["common.hours"] = "Hours",
+ ["common.risk"] = "Risk",
+ ["common.blocked"] = "Blocked",
+ ["common.overdue"] = "Overdue",
+ ["common.done"] = "Done",
+ ["common.inProgress"] = "In Progress",
+ ["common.todo"] = "To Do",
+ ["common.resolved"] = "Resolved",
+ ["common.clearStatus"] = "Clear",
+ ["common.none"] = "None",
+ ["common.unassigned"] = "Unassigned",
+ ["common.noDescription"] = "No description",
+ ["common.noneRecorded"] = "None recorded",
+ ["common.na"] = "N/A",
+ ["common.noAccessItems"] = "No access items are available for this role.",
+ ["common.accessDenied"] = "Access Denied",
+ ["common.accessDeniedDescription"] = "You do not have permission to access this page. Please contact your system administrator.",
+ ["common.backToDashboard"] = "Back to Dashboard",
+ ["common.noDataYet"] = "No data yet",
+ ["common.createFirstEntry"] = "Create your first entry to get started.",
+ ["common.loadingAuditLogs"] = "Loading audit logs...",
+ ["common.loadingUsers"] = "Loading users...",
+ ["common.loadingProjects"] = "Loading projects...",
+ ["common.loadingTasks"] = "Loading tasks...",
+ ["common.searchPlaceholder"] = "Search by name, email, username...",
+ ["common.searchTasks"] = "Search Task",
+ ["common.searchByUser"] = "Search by user, action, module, or details...",
+ ["common.searchByTitle"] = "Search by title or project...",
+ ["common.searchByTitleOrProject"] = "Search by title or project...",
+ ["common.searchProjects"] = "Search projects...",
+ ["common.searchTeamMembers"] = "Search team members...",
+ ["common.searchProjectMembers"] = "Search project members...",
+ ["common.searchIssues"] = "Search issue title, description or assignee...",
+ ["common.searchByIssueAssignee"] = "Search Issues / Assignee",
+ ["common.loadingSpinner"] = "Loading...",
+ ["common.loadingIssues"] = "Loading issues...",
+ ["common.loadingTasksAndUsers"] = "Loading tasks and users...",
+ ["common.loadingProjectsAndUsers"] = "Loading projects and users...",
+ ["common.loadingProjectMembers"] = "Loading project members...",
+ ["common.loadingMembers"] = "Loading members...",
+ ["common.noResults"] = "No results found.",
+ ["common.notApplicable"] = "Not applicable",
+ ["common.selectAll"] = "Select All",
+ ["common.deselectAll"] = "Deselect All",
+ ["common.selected"] = "selected",
+ ["common.viewDetails"] = "View details",
+ ["common.currentLanguage"] = "Switch language",
+ ["common.languageEnglish"] = "English",
+ ["common.languageBurmese"] = "Burmese",
+ ["common.allTasks"] = "All tasks",
+ ["common.allProjects"] = "All projects",
+ ["common.allRoles"] = "All Roles",
+ ["common.noProjectsFound"] = "No projects found.",
+ ["common.noTasksFound"] = "No tasks found.",
+ ["common.noTeamMembersFound"] = "No team members match your search criteria.",
+ ["common.noMembersMatchSearch"] = "No members match the current search.",
+ ["common.noTaskSelected"] = "No Task Selected",
+ ["common.noProjectSelected"] = "No Project Selected",
+ ["common.noProject"] = "No Project",
+ ["common.currentlyAssigningTask"] = "Currently Assigning Task",
+ ["common.currentlyManaging"] = "Currently Managing",
+ ["common.selectProject"] = "Select Project",
+ ["common.selectTask"] = "Select task...",
+ ["common.issueTitle"] = "Issue Title",
+ ["common.issueDescription"] = "Description",
+ ["common.blockedStatus"] = "Blocked Status",
+ ["common.escalationLevel"] = "Escalation Level",
+ ["common.blockedBy"] = "Blocked By",
+ ["common.delayReason"] = "Delay Reason",
+ ["common.actualHours"] = "Actual Hours",
+ ["common.estimatedHours"] = "Estimated Hours",
+ ["common.assignedToMeOnly"] = "Assigned to me only",
+ ["common.saveAssignment"] = "Save Assignment",
+ ["common.saveAssignmentChanges"] = "Save Assignment Changes",
+ ["common.saveChanges"] = "Save Changes",
+ ["common.addIssue"] = "Add Issue",
+ ["common.addIssueTitle"] = "Add Issue?",
+ ["common.editIssue"] = "Edit Issue",
+ ["common.noTaskSelectedForIssue"] = "No task selected",
+ ["common.unassignedMembers"] = "Unassigned",
+ ["common.noMembersMatchSearch"] = "No members match the current search.",
+ ["common.removeOwnerFromTask"] = "Remove owner from this task",
+
+ ["page.dashboard"] = "Dashboard",
+ ["page.managerDashboard"] = "Manager Dashboard",
+ ["page.employeeDashboard"] = "Employee Dashboard",
+ ["page.projects"] = "Projects",
+ ["page.projectAssignment"] = "Project Assignment",
+ ["page.tasks"] = "Tasks",
+ ["page.kanbanBoard"] = "Kanban Board",
+ ["page.taskAssignment"] = "Task Assignment",
+ ["page.taskDetails"] = "Task Details",
+ ["page.issues"] = "Issues",
+ ["page.addIssue"] = "Add Issue",
+ ["page.taskReport"] = "Task Report",
+ ["page.timeTracking"] = "Time Tracking",
+ ["page.projectProgress"] = "Project Progress",
+ ["page.overdueTasks"] = "Overdue Tasks",
+ ["page.employeeReport"] = "Employee Report",
+ ["page.rolesAccess"] = "Roles & Access",
+ ["page.users"] = "Users",
+ ["page.auditLogs"] = "Audit Logs",
+ ["page.issueReport"] = "Issue Report",
+ ["page.projectList"] = "Project List",
+ ["page.taskList"] = "Task List",
+ ["page.taskBacklog"] = "Task Backlog",
+ ["page.roleLayouts"] = "Role Layouts",
+
+ ["page.dashboard.desc"] = "A quick overview of work, risk, and progress.",
+ ["page.managerDashboard.desc"] = "Track projects, people, and delivery health.",
+ ["page.employeeDashboard.desc"] = "Focus on your tasks, due dates, and progress.",
+ ["page.projects.desc"] = "Manage project plans, timelines, and assignments.",
+ ["page.projectAssignment.desc"] = "Assign team members to the right projects.",
+ ["page.tasks.desc"] = "Review, filter, and update work items.",
+ ["page.kanbanBoard.desc"] = "Move work through status columns.",
+ ["page.taskAssignment.desc"] = "Assign tasks to project members.",
+ ["page.taskDetails.desc"] = "Review task progress, comments, and updates.",
+ ["page.issues.desc"] = "Track blockers, notes, and daily work progress.",
+ ["page.addIssue.desc"] = "Record a new issue against a task.",
+ ["page.taskReport.desc"] = "Analyze task activity and export task reports.",
+ ["page.timeTracking.desc"] = "Review effort by task, project, and assignee.",
+ ["page.projectProgress.desc"] = "Monitor delivery health across active projects.",
+ ["page.overdueTasks.desc"] = "Focus on delayed work that needs attention.",
+ ["page.employeeReport.desc"] = "See how work is distributed across people.",
+ ["page.rolesAccess.desc"] = "Control user roles, menus, and permissions.",
+ ["page.users.desc"] = "Manage people, status, and account details.",
+ ["page.auditLogs.desc"] = "Review system activity and changes.",
+ ["page.issueReport.desc"] = "Track daily execution, backlog health, and issue-level effort.",
+ ["page.projectList.desc"] = "Review project details and progress at a glance.",
+ ["page.taskList.desc"] = "Review, filter, and update task records.",
+ ["page.taskBacklog.desc"] = "See unassigned and queued work items.",
+ ["page.roleLayouts.desc"] = "Review access group layouts and permissions.",
+
+ ["menu.DASHBOARD"] = "Dashboard",
+ ["menu.DASHBOARD_ADMIN"] = "Dashboard",
+ ["menu.DASHBOARD_MANAGER"] = "Dashboard",
+ ["menu.DASHBOARD_EMPLOYEE"] = "Dashboard",
+ ["menu.PROJECTS"] = "Projects",
+ ["menu.PROJECTS_LIST"] = "Project List",
+ ["menu.PROJECTS_ASSIGN"] = "Project Assign",
+ ["menu.TASKS"] = "Tasks",
+ ["menu.TASKS_LIST"] = "Task List",
+ ["menu.TASKS_BOARD"] = "Kanban Board",
+ ["menu.TASKS_ASSIGN"] = "Task Assign",
+ ["menu.TASKS_BACKLOG"] = "Task Backlog",
+ ["menu.ISSUES_ADD"] = "Add Issue",
+ ["menu.ISSUES_LIST"] = "Issue List",
+ ["menu.REPORTS"] = "Reports",
+ ["menu.REPORTS_TASKS"] = "Task Report",
+ ["menu.REPORTS_TIMESHEET"] = "Time Tracking",
+ ["menu.REPORTS_OVERDUE"] = "Overdue Tasks",
+ ["menu.REPORTS_EMPLOYEES"] = "Employee Report",
+ ["menu.REPORTS_PROJECTS"] = "Project Progress",
+ ["menu.REPORTS_ISSUES"] = "Issue Report",
+ ["menu.AUDIT_LOGS"] = "Audit Logs",
+ ["menu.USERS"] = "Users",
+ ["menu.ROLES"] = "Roles",
+ ["menu.DASHBOARD_WIDGETS"] = "Role Layouts",
+
+ ["status.todo"] = "To Do",
+ ["status.inprogress"] = "In Progress",
+ ["status.done"] = "Done",
+ ["status.overdue"] = "Overdue",
+ ["status.blocked"] = "Blocked",
+ ["status.clear"] = "Clear",
+ ["status.resolved"] = "Resolved",
+ ["status.uncompleted"] = "Uncompleted",
+ ["priority.low"] = "Low",
+ ["priority.medium"] = "Medium",
+ ["priority.high"] = "High",
+
+ ["action.search"] = "Search",
+ ["action.reset"] = "Reset",
+ ["action.exportExcel"] = "Export Excel",
+ ["action.exportPdf"] = "Export PDF",
+ ["action.edit"] = "Edit",
+ ["action.delete"] = "Delete",
+ ["action.add"] = "Add",
+ ["action.create"] = "Create",
+ ["action.update"] = "Update",
+ ["action.save"] = "Save",
+ ["action.assign"] = "Assign",
+ ["action.viewTasks"] = "View Tasks",
+ ["action.archiveDone"] = "Archive Done",
+ ["action.clear"] = "Clear",
+
+ ["result.loading"] = "Loading...",
+ ["result.saved"] = "Saved successfully.",
+ ["result.created"] = "Created successfully.",
+ ["result.updated"] = "Updated successfully.",
+ ["result.deleted"] = "Deleted successfully.",
+
+ ["report.periodFilter"] = "Period",
+ ["report.daily"] = "Daily (Today)",
+ ["report.monthly"] = "Monthly (This Month)",
+ ["report.yearly"] = "Yearly (This Year)",
+ ["report.customRange"] = "Custom Range",
+ ["report.selectedProject"] = "Selected project",
+ ["report.openIssues"] = "Open issues",
+ ["report.lastActivity"] = "Last activity",
+ ["report.noTasksFound"] = "No Tasks Found",
+ ["report.noTasksFoundDesc"] = "There are no tasks matching your filters. Try adjusting your search query or filters.",
+ ["report.noIssuesFound"] = "No issues found",
+ ["report.noIssuesFoundDesc"] = "No issues matched the current report filters.",
+ ["report.healthCritical"] = "Critical",
+ ["report.healthAtRisk"] = "At Risk",
+ ["report.healthGood"] = "Healthy",
+ ["report.delay"] = "Delay",
+ ["report.blockedBy"] = "Blocked by",
+ ["report.noEffortEntries"] = "No effort entries",
+ ["report.noEffortEntriesDesc"] = "No timesheet effort entries matched your filters.",
+ ["report.estimatedVsActual"] = "Estimated vs actual effort",
+ ["report.utilizationGauge"] = "Utilization gauge",
+ ["report.estimateAccuracy"] = "Estimate accuracy",
+ ["report.effortByProject"] = "Effort by project",
+ ["report.effortLeaderboard"] = "Effort leaderboard",
+ ["report.workloadHeatmap"] = "Workload heatmap",
+ ["report.capacitySupportList"] = "Capacity support list",
+ ["report.completedVsOpenIssues"] = "Completed vs open issues",
+ ["report.overdueSupport"] = "Overdue support",
+ ["report.completionPattern"] = "Completion pattern",
+ ["report.projectHealthMatrix"] = "Project health matrix",
+ ["report.completionVsRisk"] = "Completion vs risk",
+ ["report.timelineSummary"] = "Timeline summary",
+ ["report.openOverdueBlockingIssues"] = "Open, overdue, and blocking issues by project",
+ ["report.topAtRiskProjects"] = "Top at-risk projects",
+ ["report.keyInsights"] = "Key Insights",
+
+ ["validation.startDateRequired"] = "Start date is required.",
+ ["validation.dueDateRequired"] = "Due date is required.",
+ ["validation.dueDateBeforeStart"] = "Due date cannot be earlier than start date.",
+ ["validation.projectNameRequired"] = "Project name is required.",
+ ["validation.taskTitleRequired"] = "Task title is required.",
+ ["validation.selectProjectRequired"] = "Please select a project.",
+ ["validation.roleNameRequired"] = "Role name is required.",
+ ["validation.passwordMinLength"] = "Password must be at least 6 characters.",
+ ["validation.usernameMinLength"] = "Username must be at least 3 characters long.",
+ ["validation.usernameNoSpaces"] = "Username cannot contain spaces.",
+ ["validation.usernameInvalidCharacters"] = "Username cannot contain spaces, and can only contain letters, numbers, underscores (_), and periods (.)",
+ ["validation.passwordMinLengthRule"] = "Password must be at least 8 characters long.",
+ ["validation.passwordComplexityRule"] = "Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character."
+ };
+
+ private static readonly IReadOnlyDictionary Burmese = new Dictionary(StringComparer.OrdinalIgnoreCase)
+ {
+ ["common.workspace"] = "အလုပ်ခွင်",
+ ["common.signIn"] = "အကောင့်ဝင်ရန်",
+ ["common.signOut"] = "အကောင့်ထွက်ရန်",
+ ["common.signOutTitle"] = "အကောင့်ထွက်မလား?",
+ ["common.signOutDescription"] = "Taskify မှ အကောင့်ထွက်ရန် သေချာပါသလား?",
+ ["common.cancel"] = "မလုပ်တော့ပါ",
+ ["common.confirm"] = "အတည်ပြုမည်",
+ ["common.confirmAction"] = "အတည်ပြုမည်",
+ ["common.areYouSure"] = "သင် သေချာပါသလား?",
+ ["common.cannotUndo"] = "ဤလုပ်ဆောင်ချက်ကို ပြန်ပြင်မရပါ။",
+ ["common.yes"] = "ဟုတ်ကဲ့",
+ ["common.no"] = "မဟုတ်ပါ",
+ ["common.save"] = "သိမ်းမည်",
+ ["common.create"] = "ဖန်တီးမည်",
+ ["common.update"] = "အပ်ဒိတ်လုပ်မည်",
+ ["common.delete"] = "ဖျက်မည်",
+ ["common.confirmSignOut"] = "ဟုတ်ကဲ့၊ အကောင့်ထွက်မည်",
+ ["common.listView"] = "စာရင်းမြင်ကွင်း",
+ ["common.chartView"] = "ဇယားမြင်ကွင်း",
+ ["common.search"] = "ရှာမည်",
+ ["common.reset"] = "ပြန်လည်သတ်မှတ်မည်",
+ ["common.clear"] = "ရှင်းမည်",
+ ["common.all"] = "အားလုံး",
+ ["common.loading"] = "ဖွင့်နေသည်...",
+ ["common.close"] = "ပိတ်မည်",
+ ["common.openNavigation"] = "မီနူးဖွင့်ရန်",
+ ["common.closeNavigation"] = "မီနူးပိတ်ရန်",
+ ["common.index"] = "နံပါတ်",
+ ["common.exportExcel"] = "Excel ထုတ်မည်",
+ ["common.exportPdf"] = "PDF ထုတ်မည်",
+ ["common.actions"] = "လုပ်ဆောင်ချက်များ",
+ ["common.status"] = "အခြေအနေ",
+ ["common.priority"] = "ဦးစားပေး",
+ ["common.assignee"] = "တာဝန်ယူသူ",
+ ["common.project"] = "စီမံကိန်း",
+ ["common.task"] = "လုပ်ငန်း",
+ ["common.taskProject"] = "လုပ်ငန်း / စီမံကိန်း",
+ ["common.issue"] = "ပြဿနာ",
+ ["common.user"] = "အသုံးပြုသူ",
+ ["common.role"] = "အခန်းကဏ္ဍ",
+ ["common.module"] = "မော်ဂျူး",
+ ["common.description"] = "ဖော်ပြချက်",
+ ["common.date"] = "ရက်စွဲ",
+ ["common.startDate"] = "စတင်ရက်",
+ ["common.endDate"] = "ပြီးဆုံးရက်",
+ ["common.dueDate"] = "ပြီးဆုံးရန်ရက်",
+ ["common.due"] = "ပြီးဆုံးရက်",
+ ["common.hours"] = "နာရီ",
+ ["common.risk"] = "အန္တရာယ်",
+ ["common.blocked"] = "ပိတ်ဆို့ထားသည်",
+ ["common.overdue"] = "နောက်ကျနေသည်",
+ ["common.done"] = "ပြီးဆုံး",
+ ["common.inProgress"] = "ဆောင်ရွက်နေသည်",
+ ["common.todo"] = "လုပ်ရန်",
+ ["common.resolved"] = "ဖြေရှင်းပြီး",
+ ["common.clearStatus"] = "ရှင်းလင်း",
+ ["common.none"] = "မရှိ",
+ ["common.unassigned"] = "မချမှတ်ရသေး",
+ ["common.noDescription"] = "ဖော်ပြချက်မရှိ",
+ ["common.noneRecorded"] = "မှတ်တမ်းမရှိ",
+ ["common.na"] = "မသက်ဆိုင်ပါ",
+ ["common.noAccessItems"] = "ဤအခန်းကဏ္ဍအတွက် ဝင်ရောက်ခွင့်မရှိသေးပါ။",
+ ["common.accessDenied"] = "ဝင်ရောက်ခွင့် မရှိပါ",
+ ["common.accessDeniedDescription"] = "ဤစာမျက်နှာသို့ ဝင်ရောက်ရန် သင်၌ ခွင့်ပြုချက်မရှိပါ။ စနစ်စီမံသူကို ဆက်သွယ်ပါ။",
+ ["common.backToDashboard"] = "ဒက်ရှ်ဘုတ်သို့ ပြန်သွားမည်",
+ ["common.noDataYet"] = "အချက်အလက် မရှိသေးပါ",
+ ["common.createFirstEntry"] = "စတင်ရန် ပထမဆုံး အချက်အလက်ကို ဖန်တီးပါ။",
+ ["common.loadingAuditLogs"] = "မှတ်တမ်းများ ဖွင့်နေသည်...",
+ ["common.loadingUsers"] = "အသုံးပြုသူများ ဖွင့်နေသည်...",
+ ["common.loadingProjects"] = "စီမံကိန်းများ ဖွင့်နေသည်...",
+ ["common.loadingTasks"] = "လုပ်ငန်းများ ဖွင့်နေသည်...",
+ ["common.searchPlaceholder"] = "အမည်၊ အီးမေးလ်၊ အသုံးပြုသူအမည်ဖြင့် ရှာပါ...",
+ ["common.searchTasks"] = "အလုပ်ရှာရန်",
+ ["common.searchByTitleOrProject"] = "ခေါင်းစဉ် သို့မဟုတ် စီမံကိန်းဖြင့် ရှာပါ...",
+ ["common.searchByUser"] = "အသုံးပြုသူ၊ လုပ်ဆောင်ချက်၊ မော်ဂျူး သို့မဟုတ် အသေးစိတ်ဖြင့် ရှာပါ...",
+ ["common.searchByTitle"] = "ခေါင်းစဉ် သို့မဟုတ် စီမံကိန်းအမည်ဖြင့် ရှာပါ...",
+ ["common.searchProjects"] = "စီမံကိန်းများ ရှာပါ...",
+ ["common.searchTeamMembers"] = "အဖွဲ့ဝင်များ ရှာပါ...",
+ ["common.searchProjectMembers"] = "စီမံကိန်းအဖွဲ့ဝင်များ ရှာပါ...",
+ ["common.searchIssues"] = "ပြဿနာ ခေါင်းစဉ်၊ ဖော်ပြချက် သို့မဟုတ် တာဝန်ယူသူဖြင့် ရှာပါ...",
+ ["common.searchByIssueAssignee"] = "ပြဿနာ / တာဝန်ယူသူ ရှာရန်",
+ ["common.loadingSpinner"] = "ဖွင့်နေသည်...",
+ ["common.loadingIssues"] = "ပြဿနာများ ဖွင့်နေသည်...",
+ ["common.loadingTasksAndUsers"] = "လုပ်ငန်းများနှင့် အသုံးပြုသူများ ဖွင့်နေသည်...",
+ ["common.loadingProjectsAndUsers"] = "စီမံကိန်းများနှင့် အသုံးပြုသူများ ဖွင့်နေသည်...",
+ ["common.loadingProjectMembers"] = "စီမံကိန်းအဖွဲ့ဝင်များ ဖွင့်နေသည်...",
+ ["common.loadingMembers"] = "အဖွဲ့ဝင်များ ဖွင့်နေသည်...",
+ ["common.noResults"] = "ရလဒ်မတွေ့ပါ။",
+ ["common.notApplicable"] = "မသက်ဆိုင်ပါ",
+ ["common.selectAll"] = "အားလုံးရွေးမည်",
+ ["common.deselectAll"] = "အားလုံးဖြုတ်မည်",
+ ["common.selected"] = "ရွေးထားသည်",
+ ["common.viewDetails"] = "အသေးစိတ်ကြည့်မည်",
+ ["common.currentLanguage"] = "ဘာသာစကားပြောင်းရန်",
+ ["common.languageEnglish"] = "အင်္ဂလိပ်",
+ ["common.languageBurmese"] = "မြန်မာ",
+ ["common.allTasks"] = "အလုပ်အားလုံး",
+ ["common.allProjects"] = "စီမံကိန်းအားလုံး",
+ ["common.allRoles"] = "အခန်းကဏ္ဍအားလုံး",
+ ["common.noProjectsFound"] = "စီမံကိန်းမတွေ့ပါ။",
+ ["common.noTasksFound"] = "လုပ်ငန်းမတွေ့ပါ။",
+ ["common.noTeamMembersFound"] = "သင်ရှာဖွေသည့် အဖွဲ့ဝင် မရှိပါ။",
+ ["common.noMembersMatchSearch"] = "လက်ရှိရှာဖွေမှုနှင့် ကိုက်ညီသည့် အဖွဲ့ဝင် မရှိပါ။",
+ ["common.noTaskSelected"] = "လုပ်ငန်း မရွေးထားပါ",
+ ["common.noProjectSelected"] = "စီမံကိန်း မရွေးထားပါ",
+ ["common.noProject"] = "စီမံကိန်း မရှိပါ",
+ ["common.currentlyAssigningTask"] = "လက်ရှိ ချထားနေသော လုပ်ငန်း",
+ ["common.currentlyManaging"] = "လက်ရှိ စီမံနေသည်",
+ ["common.selectProject"] = "စီမံကိန်း ရွေးရန်",
+ ["common.selectTask"] = "လုပ်ငန်း ရွေးရန်...",
+ ["common.issueTitle"] = "ပြဿနာ ခေါင်းစဉ်",
+ ["common.issueDescription"] = "ဖော်ပြချက်",
+ ["common.blockedStatus"] = "ပိတ်ဆို့မှု အခြေအနေ",
+ ["common.escalationLevel"] = "အဆင့်တိုးမှု အဆင့်",
+ ["common.blockedBy"] = "ပိတ်ဆို့နေသူ",
+ ["common.delayReason"] = "နှောင့်နှေးရသည့် အကြောင်းရင်း",
+ ["common.actualHours"] = "တကယ့် နာရီ",
+ ["common.estimatedHours"] = "ခန့်မှန်း နာရီ",
+ ["common.assignedToMeOnly"] = "ကျွန်ုပ်ထံပဲ ချထားထားသည်",
+ ["common.saveAssignment"] = "တာဝန်ချမှု သိမ်းမည်",
+ ["common.saveAssignmentChanges"] = "တာဝန်ချမှု ပြောင်းလဲချက်များ သိမ်းမည်",
+ ["common.saveChanges"] = "ပြောင်းလဲချက်များ သိမ်းမည်",
+ ["common.addIssue"] = "ပြဿနာ ထည့်မည်",
+ ["common.addIssueTitle"] = "ပြဿနာ ထည့်မည်လား?",
+ ["common.editIssue"] = "ပြဿနာ ပြင်မည်",
+ ["common.removeOwnerFromTask"] = "ဤလုပ်ငန်းမှ ပိုင်ရှင်ကို ဖယ်ရှားမည်",
+
+ ["page.dashboard"] = "ဒက်ရှ်ဘုတ်",
+ ["page.managerDashboard"] = "မန်နေဂျာ ဒက်ရှ်ဘုတ်",
+ ["page.employeeDashboard"] = "ဝန်ထမ်း ဒက်ရှ်ဘုတ်",
+ ["page.projects"] = "စီမံကိန်းများ",
+ ["page.projectAssignment"] = "စီမံကိန်း ခွဲဝေခြင်း",
+ ["page.tasks"] = "လုပ်ငန်းများ",
+ ["page.kanbanBoard"] = "ကန်ဘန်းဘုတ်",
+ ["page.taskAssignment"] = "လုပ်ငန်း ခွဲဝေခြင်း",
+ ["page.taskDetails"] = "လုပ်ငန်း အသေးစိတ်",
+ ["page.issues"] = "ပြဿနာများ",
+ ["page.addIssue"] = "ပြဿနာ ထည့်ရန်",
+ ["page.taskReport"] = "လုပ်ငန်း အစီရင်ခံစာ",
+ ["page.timeTracking"] = "အချိန်မှတ်တမ်း",
+ ["page.projectProgress"] = "စီမံကိန်း တိုးတက်မှု",
+ ["page.overdueTasks"] = "နောက်ကျ လုပ်ငန်းများ",
+ ["page.employeeReport"] = "ဝန်ထမ်း အစီရင်ခံစာ",
+ ["page.rolesAccess"] = "အခန်းကဏ္ဍနှင့် ဝင်ခွင့်",
+ ["page.users"] = "အသုံးပြုသူများ",
+ ["page.auditLogs"] = "စနစ်မှတ်တမ်းများ",
+ ["page.issueReport"] = "ပြဿနာ အစီရင်ခံစာ",
+ ["page.projectList"] = "စီမံကိန်းစာရင်း",
+ ["page.taskList"] = "လုပ်ငန်းစာရင်း",
+ ["page.taskBacklog"] = "လုပ်ငန်း နောက်ခံစာရင်း",
+ ["page.roleLayouts"] = "အခန်းကဏ္ဍ အပြင်အဆင်များ",
+
+ ["page.dashboard.desc"] = "အလုပ်များ၊ အန္တရာယ်များနှင့် တိုးတက်မှုများကို တစ်ချက်ကြည့်နိုင်သည်။",
+ ["page.managerDashboard.desc"] = "စီမံကိန်းများ၊ လူအင်အားနှင့် ပေးပို့မှုအခြေအနေကို စောင့်ကြည့်ပါ။",
+ ["page.employeeDashboard.desc"] = "သင့်လုပ်ငန်းများ၊ သတ်မှတ်ရက်နှင့် တိုးတက်မှုများကို အာရုံစိုက်ပါ။",
+ ["page.projects.desc"] = "စီမံကိန်းအစီအစဉ်၊ အချိန်ဇယားနှင့် ခွဲဝေမှုများကို စီမံပါ။",
+ ["page.projectAssignment.desc"] = "အဖွဲ့ဝင်များကို သင့်တော်သော စီမံကိန်းများသို့ ခွဲဝေပါ။",
+ ["page.tasks.desc"] = "လုပ်ငန်းများကို ကြည့်ရှု၊ စစ်ထုတ်၊ အပ်ဒိတ်လုပ်ပါ။",
+ ["page.kanbanBoard.desc"] = "အလုပ်များကို အခြေအနေကော်လံများအတိုင်း ရွှေ့ပါ။",
+ ["page.taskAssignment.desc"] = "လုပ်ငန်းများကို စီမံကိန်းအဖွဲ့ဝင်များသို့ ခွဲဝေပါ။",
+ ["page.taskDetails.desc"] = "လုပ်ငန်းတိုးတက်မှု၊ မှတ်ချက်များနှင့် အပ်ဒိတ်များကို ကြည့်ရှုပါ။",
+ ["page.issues.desc"] = "ပိတ်ဆို့မှုများ၊ မှတ်စုများနှင့် နေ့စဉ် အလုပ်တိုးတက်မှုကို ခြေရာခံပါ။",
+ ["page.addIssue.desc"] = "လုပ်ငန်းတစ်ခုအတွက် ပြဿနာအသစ်ကို မှတ်တမ်းတင်ပါ။",
+ ["page.taskReport.desc"] = "လုပ်ငန်းလှုပ်ရှားမှုကို ခွဲခြမ်းပြီး အစီရင်ခံစာများ ထုတ်ပါ။",
+ ["page.timeTracking.desc"] = "လုပ်ငန်း၊ စီမံကိန်းနှင့် တာဝန်ယူသူအလိုက် အားထုတ်မှုကို ကြည့်ရှုပါ။",
+ ["page.projectProgress.desc"] = "လက်ရှိ စီမံကိန်းများ၏ ပေးပို့မှုအခြေအနေကို စောင့်ကြည့်ပါ။",
+ ["page.overdueTasks.desc"] = "အရေးပေါ် အာရုံစိုက်ရန် လိုသည့် နောက်ကျလုပ်ငန်းများကို ကြည့်ပါ။",
+ ["page.employeeReport.desc"] = "အလုပ်တာဝန်များ လူတစ်ဦးချင်းစီအလိုက် မည်သို့ ဖြန့်ဝေထားသည်ကို ကြည့်ပါ။",
+ ["page.rolesAccess.desc"] = "အသုံးပြုသူအခန်းကဏ္ဍ၊ မီနူးများနှင့် ခွင့်ပြုချက်များကို ထိန်းချုပ်ပါ။",
+ ["page.users.desc"] = "လူများ၊ အခြေအနေများနှင့် အကောင့်အသေးစိတ်များကို စီမံပါ။",
+ ["page.auditLogs.desc"] = "စနစ်လှုပ်ရှားမှုများနှင့် ပြောင်းလဲမှုများကို ပြန်လည်ကြည့်ရှုပါ။",
+ ["page.issueReport.desc"] = "နေ့စဉ် ဆောင်ရွက်မှု၊ နောက်ကျမှုနှင့် ပြဿနာအလိုက် အားထုတ်မှုကို ခြေရာခံပါ။",
+ ["page.projectList.desc"] = "စီမံကိန်းအသေးစိတ်နှင့် တိုးတက်မှုကို တစ်ချက်ကြည့်ပါ။",
+ ["page.taskList.desc"] = "လုပ်ငန်းမှတ်တမ်းများကို ကြည့်ရှု၊ စစ်ထုတ်၊ အပ်ဒိတ်လုပ်ပါ။",
+ ["page.taskBacklog.desc"] = "မခွဲဝေသေးသော နှင့် စောင့်ဆိုင်းနေသော အလုပ်များကို ကြည့်ပါ။",
+ ["page.roleLayouts.desc"] = "ဝင်ခွင့်အုပ်စု ပုံစံများနှင့် ခွင့်ပြုချက်များကို ကြည့်ပါ။",
+
+ ["menu.DASHBOARD"] = "ဒက်ရှ်ဘုတ်",
+ ["menu.DASHBOARD_ADMIN"] = "ဒက်ရှ်ဘုတ်",
+ ["menu.DASHBOARD_MANAGER"] = "ဒက်ရှ်ဘုတ်",
+ ["menu.DASHBOARD_EMPLOYEE"] = "ဒက်ရှ်ဘုတ်",
+ ["menu.PROJECTS"] = "စီမံကိန်းများ",
+ ["menu.PROJECTS_LIST"] = "စီမံကိန်းစာရင်း",
+ ["menu.PROJECTS_ASSIGN"] = "စီမံကိန်း ခွဲဝေခြင်း",
+ ["menu.TASKS"] = "လုပ်ငန်းများ",
+ ["menu.TASKS_LIST"] = "လုပ်ငန်းစာရင်း",
+ ["menu.TASKS_BOARD"] = "ကန်ဘန်းဘုတ်",
+ ["menu.TASKS_ASSIGN"] = "လုပ်ငန်း ခွဲဝေခြင်း",
+ ["menu.TASKS_BACKLOG"] = "လုပ်ငန်း နောက်ခံစာရင်း",
+ ["menu.ISSUES_ADD"] = "ပြဿနာ ထည့်ရန်",
+ ["menu.ISSUES_LIST"] = "ပြဿနာ စာရင်း",
+ ["menu.REPORTS"] = "အစီရင်ခံစာများ",
+ ["menu.REPORTS_TASKS"] = "လုပ်ငန်း အစီရင်ခံစာ",
+ ["menu.REPORTS_TIMESHEET"] = "အချိန်မှတ်တမ်း",
+ ["menu.REPORTS_OVERDUE"] = "နောက်ကျ လုပ်ငန်းများ",
+ ["menu.REPORTS_EMPLOYEES"] = "ဝန်ထမ်း အစီရင်ခံစာ",
+ ["menu.REPORTS_PROJECTS"] = "စီမံကိန်း တိုးတက်မှု",
+ ["menu.REPORTS_ISSUES"] = "ပြဿနာ အစီရင်ခံစာ",
+ ["menu.AUDIT_LOGS"] = "စနစ်မှတ်တမ်းများ",
+ ["menu.USERS"] = "အသုံးပြုသူများ",
+ ["menu.ROLES"] = "အခန်းကဏ္ဍများ",
+ ["menu.DASHBOARD_WIDGETS"] = "အခန်းကဏ္ဍ အပြင်အဆင်များ",
+
+ ["status.todo"] = "လုပ်ရန်",
+ ["status.inprogress"] = "ဆောင်ရွက်နေသည်",
+ ["status.done"] = "ပြီးဆုံး",
+ ["status.overdue"] = "နောက်ကျ",
+ ["status.blocked"] = "ပိတ်ဆို့",
+ ["status.clear"] = "ရှင်း",
+ ["status.resolved"] = "ဖြေရှင်းပြီး",
+ ["status.uncompleted"] = "မပြီးသေး",
+ ["priority.low"] = "နိမ့်",
+ ["priority.medium"] = "အလယ်",
+ ["priority.high"] = "မြင့်",
+
+ ["action.search"] = "ရှာမည်",
+ ["action.reset"] = "ပြန်လည်သတ်မှတ်မည်",
+ ["action.exportExcel"] = "Excel ထုတ်မည်",
+ ["action.exportPdf"] = "PDF ထုတ်မည်",
+ ["action.edit"] = "ပြင်မည်",
+ ["action.delete"] = "ဖျက်မည်",
+ ["action.add"] = "ထည့်မည်",
+ ["action.create"] = "ဖန်တီးမည်",
+ ["action.update"] = "အပ်ဒိတ်လုပ်မည်",
+ ["action.save"] = "သိမ်းမည်",
+ ["action.assign"] = "ခွဲဝေမည်",
+ ["action.viewTasks"] = "လုပ်ငန်းများ ကြည့်မည်",
+ ["action.archiveDone"] = "ပြီးဆုံးများ သိမ်းဆည်းမည်",
+ ["action.clear"] = "ရှင်းမည်",
+
+ ["result.loading"] = "ဖွင့်နေသည်...",
+ ["result.saved"] = "အောင်မြင်စွာ သိမ်းပြီးပါပြီ။",
+ ["result.created"] = "အောင်မြင်စွာ ဖန်တီးပြီးပါပြီ။",
+ ["result.updated"] = "အောင်မြင်စွာ အပ်ဒိတ်ပြီးပါပြီ။",
+ ["result.deleted"] = "အောင်မြင်စွာ ဖျက်ပြီးပါပြီ။",
+ ["report.periodFilter"] = "ကာလ",
+ ["report.daily"] = "နေ့စဉ် (ယနေ့)",
+ ["report.monthly"] = "လစဉ် (ယခုလ)",
+ ["report.yearly"] = "နှစ်စဉ် (ယခုနှစ်)",
+ ["report.customRange"] = "စိတ်ကြိုက်အပိုင်းအခြား",
+ ["report.selectedProject"] = "ရွေးထားသော စီမံကိန်း",
+ ["report.openIssues"] = "ဖွင့်ထားသော ပြဿနာများ",
+ ["report.lastActivity"] = "နောက်ဆုံး လုပ်ဆောင်ချက်",
+ ["report.noTasksFound"] = "အလုပ်မတွေ့ပါ",
+ ["report.noTasksFoundDesc"] = "လက်ရှိစစ်ထုတ်မှုများနှင့် ကိုက်ညီသော အလုပ် မရှိပါ။ ရှာဖွေမှု သို့မဟုတ် စစ်ထုတ်ချက်များကို ပြင်ဆင်ကြည့်ပါ။",
+ ["report.noIssuesFound"] = "ပြဿနာမတွေ့ပါ",
+ ["report.noIssuesFoundDesc"] = "လက်ရှိအစီရင်ခံ စစ်ထုတ်ချက်များနှင့် ကိုက်ညီသော ပြဿနာ မရှိပါ။",
+ ["report.healthCritical"] = "အရေးကြီး",
+ ["report.healthAtRisk"] = "အန္တရာယ်ရှိ",
+ ["report.healthGood"] = "ကောင်းမွန်",
+ ["report.delay"] = "နှောင့်နှေးချက်",
+ ["report.blockedBy"] = "ပိတ်ဆို့နေသည်မှာ",
+
+ ["validation.startDateRequired"] = "စတင်ရက် လိုအပ်ပါသည်။",
+ ["validation.dueDateRequired"] = "ပြီးဆုံးရက် လိုအပ်ပါသည်။",
+ ["validation.dueDateBeforeStart"] = "ပြီးဆုံးရက်သည် စတင်ရက်ထက် စော၍ မရပါ။",
+ ["validation.projectNameRequired"] = "စီမံကိန်းအမည် လိုအပ်ပါသည်။",
+ ["validation.taskTitleRequired"] = "လုပ်ငန်းခေါင်းစဉ် လိုအပ်ပါသည်။",
+ ["validation.selectProjectRequired"] = "စီမံကိန်းတစ်ခုကို ရွေးပါ။",
+ ["validation.roleNameRequired"] = "အခန်းကဏ္ဍအမည် လိုအပ်ပါသည်။",
+ ["validation.passwordMinLength"] = "စကားဝှက်မှာ အနည်းဆုံး ၆ လုံး ရှိရမည်။",
+ ["validation.usernameMinLength"] = "အသုံးပြုသူအမည်မှာ အနည်းဆုံး ၃ လုံး ရှိရမည်။",
+ ["validation.usernameNoSpaces"] = "အသုံးပြုသူအမည်တွင် space မပါရပါ။",
+ ["validation.usernameInvalidCharacters"] = "အသုံးပြုသူအမည်တွင် space မပါရပါ၊ စာလုံး၊ နံပါတ်၊ underscore (_) နှင့် dot (.) သာ အသုံးပြုနိုင်သည်။",
+ ["validation.passwordMinLengthRule"] = "စကားဝှက်မှာ အနည်းဆုံး ၈ လုံး ရှိရမည်။",
+ ["validation.passwordComplexityRule"] = "စကားဝှက်တွင် အနည်းဆုံး uppercase တစ်လုံး၊ lowercase တစ်လုံး၊ နံပါတ်တစ်လုံးနှင့် အထူးလက္ခဏာတစ်လုံး ပါရမည်။"
+ };
+
+ public static bool IsBurmeseCulture => CultureInfo.CurrentUICulture.TwoLetterISOLanguageName.Equals("my", StringComparison.OrdinalIgnoreCase);
+
+ public static string Text(string key, string fallback = "")
+ {
+ var catalog = IsBurmeseCulture ? Burmese : English;
+ if (catalog.TryGetValue(key, out var value))
+ {
+ return value;
+ }
+
+ if ((IsBurmeseCulture ? English : Burmese).TryGetValue(key, out value))
+ {
+ return value;
+ }
+
+ return string.IsNullOrWhiteSpace(fallback) ? key : fallback;
+ }
+
+ public static string Menu(string menuCode, string fallback = "") => Text($"menu.{menuCode}", fallback);
+
+ public static string PageTitle(string key, string fallback = "") => Text($"page.{key}", fallback);
+
+ public static string PageDescription(string key, string fallback = "") => Text($"page.{key}.desc", fallback);
+
+ public static string StatusLabel(AppTaskStatus status) => status switch
+ {
+ AppTaskStatus.InProgress => Text("status.inprogress", "In Progress"),
+ AppTaskStatus.Done => Text("status.done", "Done"),
+ _ => Text("status.todo", "To Do")
+ };
+
+ public static string PriorityLabel(TaskPriority priority) => priority switch
+ {
+ TaskPriority.High => Text("priority.high", "High"),
+ TaskPriority.Medium => Text("priority.medium", "Medium"),
+ _ => Text("priority.low", "Low")
+ };
+
+ public static string EscalationLabel(int level) => level switch
+ {
+ 1 => IsBurmeseCulture ? "နိမ့်" : "Low",
+ 2 => IsBurmeseCulture ? "အလယ်" : "Medium",
+ 3 => IsBurmeseCulture ? "မြင့်" : "High",
+ _ => Text("common.none", "None")
+ };
+}
diff --git a/TaskTrackingSystem.Shared/Models/AuditLog/AuditLogDto.cs b/TaskTrackingSystem.Shared/Models/AuditLog/AuditLogDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5f3fe35c250469c50c1d692d33862f9e2a218f70
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/AuditLog/AuditLogDto.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace TaskTrackingSystem.Shared.Models.AuditLog
+{
+ public class AuditLogDto
+ {
+ public long Id { get; set; }
+ public long? UserId { get; set; }
+ public string Username { get; set; } = null!;
+ public string UserFullName { get; set; } = null!;
+ public string Action { get; set; } = null!;
+ public string Module { get; set; } = null!;
+ public string Description { get; set; } = null!;
+ public string? IpAddress { get; set; }
+ public DateTime CreatedAt { get; set; }
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Auth/AuthResponse.cs b/TaskTrackingSystem.Shared/Models/Auth/AuthResponse.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5da16046873d59e8e21c9f94b666b1f6076a7a5c
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Auth/AuthResponse.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TaskTrackingSystem.Shared.Models.Auth
+{
+ public class AuthResponseDto
+ {
+ public string Token { get; set; } = string.Empty;
+ public string Username { get; set; } = string.Empty;
+ public string Email { get; set; } = string.Empty;
+ public long RoleId { get; set; }
+ public string RoleName { get; set; } = string.Empty;
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Auth/LoginDto.cs b/TaskTrackingSystem.Shared/Models/Auth/LoginDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..81e291361f2fcdd89b87b4d78158821a4da226d0
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Auth/LoginDto.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TaskTrackingSystem.Shared.Models.Auth
+{
+ public class LoginDto
+ {
+ [Required]
+ public string UsernameOrEmail { get; set; } = string.Empty; // Username သို့မဟုတ် Email ကြိုက်တာနဲ့ login ဝင်လို့ရအောင် ပြုလုပ်ထားခြင်း
+
+ [Required]
+ public string Password { get; set; } = string.Empty;
+ }
+}
\ No newline at end of file
diff --git a/TaskTrackingSystem.Shared/Models/Auth/RegisterDto.cs b/TaskTrackingSystem.Shared/Models/Auth/RegisterDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c8ba23827788501c7cc0da8f573fe857043d1997
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Auth/RegisterDto.cs
@@ -0,0 +1,30 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+
+namespace TaskTrackingSystem.Shared.Models.Auth
+{
+ public class RegisterDto
+ {
+ [Required, MaxLength(50)]
+ [MinLength(3, ErrorMessage = ResultMessages.UsernameMinLength)]
+ [RegularExpression(@"^[a-zA-Z0-9._]+$", ErrorMessage = ResultMessages.UsernameInvalidCharacters)]
+ public string Username { get; set; } = string.Empty;
+
+ [Required, MaxLength(50)]
+ public string FirstName { get; set; } = string.Empty;
+
+ [Required, MaxLength(50)]
+ public string LastName { get; set; } = string.Empty;
+
+ [Required, EmailAddress, MaxLength(256)]
+ public string Email { get; set; } = string.Empty;
+
+ [Required]
+ [MinLength(8, ErrorMessage = ResultMessages.PasswordMinLengthRule)]
+ [RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^a-zA-Z0-9]).+$", ErrorMessage = ResultMessages.PasswordComplexityRule)]
+ public string Password { get; set; } = string.Empty;
+
+ [MaxLength(20)]
+ public string? Phone { get; set; }
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Auth/ResetPasswordDto.cs b/TaskTrackingSystem.Shared/Models/Auth/ResetPasswordDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b99a4ef36971d95288eca957090f9839d63bb908
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Auth/ResetPasswordDto.cs
@@ -0,0 +1,18 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace TaskTrackingSystem.Shared.Models.Auth
+{
+ public class ResetPasswordDto
+ {
+ [Required]
+ public string UsernameOrEmail { get; set; } = string.Empty;
+
+ [Required]
+ public string RecoveryCode { get; set; } = string.Empty;
+
+ [Required]
+ [MinLength(8, ErrorMessage = ResultMessages.PasswordMinLengthRule)]
+ [RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^a-zA-Z0-9]).+$", ErrorMessage = ResultMessages.PasswordComplexityRule)]
+ public string NewPassword { get; set; } = string.Empty;
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Comment/CommentDto.cs b/TaskTrackingSystem.Shared/Models/Comment/CommentDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6738de8a9cba7fc022b625cc9c74d3127237910e
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Comment/CommentDto.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace TaskTrackingSystem.Shared.Models.Comment
+{
+ public class CommentDto
+ {
+ public long Id { get; set; }
+ public long TaskId { get; set; }
+ public long UserId { get; set; }
+ public string UserFullName { get; set; } = null!;
+ public string UserRoleName { get; set; } = null!;
+ public string Message { get; set; } = null!;
+ public DateTime CreatedAt { get; set; }
+ public DateTime? UpdatedAt { get; set; }
+ }
+
+ public class CreateCommentDto
+ {
+ public string Message { get; set; } = null!;
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Dashboard/DashboardSummaryDto.cs b/TaskTrackingSystem.Shared/Models/Dashboard/DashboardSummaryDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..4dbf9f9fc9421db0f7602e03201c2c4cec6da4a2
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Dashboard/DashboardSummaryDto.cs
@@ -0,0 +1,9 @@
+namespace TaskTrackingSystem.Shared.Models.Dashboard
+{
+ public class DashboardSummaryDto
+ {
+ public int TotalUsers { get; set; }
+ public int ActiveProjectsCount { get; set; }
+ public int PendingTasksCount { get; set; }
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Dashboard/ProjectProgressDto.cs b/TaskTrackingSystem.Shared/Models/Dashboard/ProjectProgressDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7fc31b17343eb5c09cdbee49c1b1d53bdd531721
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Dashboard/ProjectProgressDto.cs
@@ -0,0 +1,11 @@
+namespace TaskTrackingSystem.Shared.Models.Dashboard
+{
+ public class ProjectProgressDto
+ {
+ public long ProjectId { get; set; }
+ public string ProjectName { get; set; } = string.Empty;
+ public double CompletionPercentage { get; set; }
+ public int CompletedTasksCount { get; set; }
+ public int TotalTasksCount { get; set; }
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Dashboard/TaskStatusOverviewDto.cs b/TaskTrackingSystem.Shared/Models/Dashboard/TaskStatusOverviewDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..40f606b3a101728b220d72e5d5797333f66be948
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Dashboard/TaskStatusOverviewDto.cs
@@ -0,0 +1,12 @@
+using TaskTrackingSystem.Shared.Enums;
+using AppTaskStatus = TaskTrackingSystem.Shared.Enums.AppTaskStatus;
+
+namespace TaskTrackingSystem.Shared.Models.Dashboard
+{
+ public class TaskStatusOverviewDto
+ {
+ public string StatusName { get; set; } = string.Empty;
+ public AppTaskStatus StatusId { get; set; }
+ public int TaskCount { get; set; }
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Issue/CreateIssueDto.cs b/TaskTrackingSystem.Shared/Models/Issue/CreateIssueDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..654ab3b7fab460ca823602f10e02f5ebebb0220e
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Issue/CreateIssueDto.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using TaskTrackingSystem.Shared.Enums;
+using TaskTrackingSystem.Shared.Localization;
+using AppTaskStatus = TaskTrackingSystem.Shared.Enums.AppTaskStatus;
+
+namespace TaskTrackingSystem.Shared.Models.Issue
+{
+ public class CreateIssueDto : IValidatableObject
+ {
+ [Required]
+ [Range(1, long.MaxValue)]
+ public long TaskId { get; set; }
+
+ [Required, MaxLength(200)]
+ public string Title { get; set; } = string.Empty;
+
+ [MaxLength(1000)]
+ public string? Description { get; set; }
+
+ [Range(0, long.MaxValue)]
+ public long? AssignedTo { get; set; }
+
+ [Range(typeof(decimal), "0", "100000")]
+ public decimal? EstimatedHours { get; set; }
+
+ [Range(typeof(decimal), "0", "100000")]
+ public decimal? ActualHours { get; set; }
+
+ [MaxLength(300)]
+ public string? DelayReason { get; set; }
+
+ public bool IsBlocked { get; set; }
+
+ [MaxLength(200)]
+ public string? BlockedBy { get; set; }
+
+ [Range(0, 3)]
+ public int EscalationLevel { get; set; }
+
+ [Required]
+ public DateTime StartDate { get; set; }
+
+ [Required]
+ public DateTime DueDate { get; set; }
+
+ [EnumDataType(typeof(AppTaskStatus))]
+ public AppTaskStatus StatusId { get; set; }
+
+ [EnumDataType(typeof(TaskPriority))]
+ public TaskPriority PriorityId { get; set; }
+
+ public IEnumerable Validate(ValidationContext validationContext)
+ {
+ if (StartDate == default)
+ {
+ yield return new ValidationResult(AppLocalization.Text("validation.startDateRequired", "Start date is required."), new[] { nameof(StartDate) });
+ }
+
+ if (DueDate == default)
+ {
+ yield return new ValidationResult(AppLocalization.Text("validation.dueDateRequired", "Due date is required."), new[] { nameof(DueDate) });
+ }
+
+ if (StartDate != default && DueDate != default && DueDate.Date < StartDate.Date)
+ {
+ yield return new ValidationResult(AppLocalization.Text("validation.dueDateBeforeStart", "Due date cannot be earlier than start date."), new[] { nameof(DueDate), nameof(StartDate) });
+ }
+ }
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Issue/IssueDto.cs b/TaskTrackingSystem.Shared/Models/Issue/IssueDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5f2c6eec8df6b4ea5141b068eb18cb2506b5348b
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Issue/IssueDto.cs
@@ -0,0 +1,30 @@
+using System;
+using TaskTrackingSystem.Shared.Enums;
+
+namespace TaskTrackingSystem.Shared.Models.Issue
+{
+ public class IssueDto
+ {
+ public long Id { get; set; }
+ public long TaskId { get; set; }
+ public string TaskTitle { get; set; } = string.Empty;
+ public long ProjectId { get; set; }
+ public string ProjectName { get; set; } = string.Empty;
+ public string Title { get; set; } = string.Empty;
+ public string? Description { get; set; }
+ public long? AssignedTo { get; set; }
+ public string? AssignedToName { get; set; }
+ public decimal? EstimatedHours { get; set; }
+ public decimal? ActualHours { get; set; }
+ public string? DelayReason { get; set; }
+ public bool IsBlocked { get; set; }
+ public string? BlockedBy { get; set; }
+ public int EscalationLevel { get; set; }
+ public DateTime StartDate { get; set; }
+ public DateTime DueDate { get; set; }
+ public AppTaskStatus StatusId { get; set; }
+ public TaskPriority PriorityId { get; set; }
+ public DateTime CreatedAt { get; set; }
+ public DateTime? UpdatedAt { get; set; }
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Issue/UpdateIssueDto.cs b/TaskTrackingSystem.Shared/Models/Issue/UpdateIssueDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6a4d110f5cb18a2ceea652bf62ac694590d8946c
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Issue/UpdateIssueDto.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using TaskTrackingSystem.Shared.Enums;
+using TaskTrackingSystem.Shared.Localization;
+using AppTaskStatus = TaskTrackingSystem.Shared.Enums.AppTaskStatus;
+
+namespace TaskTrackingSystem.Shared.Models.Issue
+{
+ public class UpdateIssueDto : IValidatableObject
+ {
+ [Required, MaxLength(200)]
+ public string Title { get; set; } = string.Empty;
+
+ [MaxLength(1000)]
+ public string? Description { get; set; }
+
+ [Range(0, long.MaxValue)]
+ public long? AssignedTo { get; set; }
+
+ [Range(typeof(decimal), "0", "100000")]
+ public decimal? EstimatedHours { get; set; }
+
+ [Range(typeof(decimal), "0", "100000")]
+ public decimal? ActualHours { get; set; }
+
+ [MaxLength(300)]
+ public string? DelayReason { get; set; }
+
+ public bool IsBlocked { get; set; }
+
+ [MaxLength(200)]
+ public string? BlockedBy { get; set; }
+
+ [Range(0, 3)]
+ public int EscalationLevel { get; set; }
+
+ [Required]
+ public DateTime StartDate { get; set; }
+
+ [Required]
+ public DateTime DueDate { get; set; }
+
+ [EnumDataType(typeof(AppTaskStatus))]
+ public AppTaskStatus StatusId { get; set; }
+
+ [EnumDataType(typeof(TaskPriority))]
+ public TaskPriority PriorityId { get; set; }
+
+ public IEnumerable Validate(ValidationContext validationContext)
+ {
+ if (StartDate == default)
+ {
+ yield return new ValidationResult(AppLocalization.Text("validation.startDateRequired", "Start date is required."), new[] { nameof(StartDate) });
+ }
+
+ if (DueDate == default)
+ {
+ yield return new ValidationResult(AppLocalization.Text("validation.dueDateRequired", "Due date is required."), new[] { nameof(DueDate) });
+ }
+
+ if (StartDate != default && DueDate != default && DueDate.Date < StartDate.Date)
+ {
+ yield return new ValidationResult(AppLocalization.Text("validation.dueDateBeforeStart", "Due date cannot be earlier than start date."), new[] { nameof(DueDate), nameof(StartDate) });
+ }
+ }
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Menu/AccessMenuDto.cs b/TaskTrackingSystem.Shared/Models/Menu/AccessMenuDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c1eaf5bd8226f2e6a1e8f6923e759076eb16c844
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Menu/AccessMenuDto.cs
@@ -0,0 +1,15 @@
+namespace TaskTrackingSystem.Shared.Models.Menu
+{
+ public class AccessMenuDto
+ {
+ public string MenuCode { get; set; } = string.Empty;
+ public string ParentCode { get; set; } = string.Empty;
+ public string MenuName { get; set; } = string.Empty;
+ public string? MenuUrl { get; set; }
+ public int OrderNo { get; set; }
+ public string? Icon { get; set; }
+ public bool Visible { get; set; }
+ public System.Collections.Generic.List Permissions { get; set; } = new();
+ }
+}
+
diff --git a/TaskTrackingSystem.Shared/Models/Menu/AccessPermissionDto.cs b/TaskTrackingSystem.Shared/Models/Menu/AccessPermissionDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e4536bb32766a8ee5a07432884728a484cf06a8a
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Menu/AccessPermissionDto.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace TaskTrackingSystem.Shared.Models.Menu
+{
+ public class AccessPermissionDto
+ {
+ public string PermissionId { get; set; } = string.Empty;
+ public string PermissionCode { get; set; } = string.Empty;
+ public string ParentMenuCode { get; set; } = string.Empty;
+ public string ActionName { get; set; } = string.Empty;
+ public string ApiName { get; set; } = string.Empty;
+ public bool Visible { get; set; }
+ public int OrderNo { get; set; }
+ }
+}
+
diff --git a/TaskTrackingSystem.Shared/Models/Menu/MenuDto.cs b/TaskTrackingSystem.Shared/Models/Menu/MenuDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b5c7f9a9ab2e507a298fe8c83f483f623b12d305
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Menu/MenuDto.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+
+namespace TaskTrackingSystem.Shared.Models.Menu
+{
+ public class MenuDto
+ {
+ public string MenuCode { get; set; } = string.Empty;
+ public string ParentCode { get; set; } = string.Empty;
+ public string MenuName { get; set; } = string.Empty;
+ public string? MenuUrl { get; set; }
+ public int OrderNo { get; set; }
+ public string? Icon { get; set; }
+ public List SubMenus { get; set; } = new List();
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Notification/NotificationDto.cs b/TaskTrackingSystem.Shared/Models/Notification/NotificationDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..282c90d937ab102fd337047294369b4749b11be7
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Notification/NotificationDto.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace TaskTrackingSystem.Shared.Models.Notification;
+
+public class NotificationDto
+{
+ public long Id { get; set; }
+ public string Title { get; set; } = string.Empty;
+ public string Body { get; set; } = string.Empty;
+ public byte NotificationType { get; set; }
+ public string SourceType { get; set; } = string.Empty;
+ public long SourceId { get; set; }
+ public string TargetUrl { get; set; } = string.Empty;
+ public bool IsRead { get; set; }
+ public DateTime? CreatedAt { get; set; }
+ public DateTime? ReadAt { get; set; }
+ public string? SenderName { get; set; }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Notification/NotificationNavigation.cs b/TaskTrackingSystem.Shared/Models/Notification/NotificationNavigation.cs
new file mode 100644
index 0000000000000000000000000000000000000000..93cd446868cfc1704680a1607001018676dcfa27
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Notification/NotificationNavigation.cs
@@ -0,0 +1,22 @@
+namespace TaskTrackingSystem.Shared.Models.Notification;
+
+public static class NotificationNavigation
+{
+ public static string BuildTargetUrl(string sourceType, long sourceId, byte notificationType)
+ {
+ if (string.Equals(sourceType, "project", StringComparison.OrdinalIgnoreCase))
+ {
+ return $"/projects/{sourceId}/tasks";
+ }
+
+ if (string.Equals(sourceType, "task", StringComparison.OrdinalIgnoreCase))
+ {
+ var isCommentThread = notificationType is (byte)NotificationType.CommentAdded or (byte)NotificationType.Mention;
+ return isCommentThread
+ ? $"/tasks/{sourceId}/details?tab=comments"
+ : $"/tasks/{sourceId}/details";
+ }
+
+ return "/dashboard";
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Notification/NotificationType.cs b/TaskTrackingSystem.Shared/Models/Notification/NotificationType.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ecae3ec1161bf2d849f8c7ade2522cd05eaff3cc
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Notification/NotificationType.cs
@@ -0,0 +1,14 @@
+namespace TaskTrackingSystem.Shared.Models.Notification;
+
+public enum NotificationType : byte
+{
+ TaskAssigned = 1,
+ StatusChanged = 2,
+ DueDateReminder = 3,
+ OverdueAlert = 4,
+ CommentAdded = 5,
+ Mention = 6,
+ PriorityChanged = 7,
+ ProjectUpdated = 8,
+ System = 9
+}
diff --git a/TaskTrackingSystem.Shared/Models/Notification/RegisterDeviceTokenDto.cs b/TaskTrackingSystem.Shared/Models/Notification/RegisterDeviceTokenDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..30dc6677c5c385684cd079fd3d2839e82af628a1
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Notification/RegisterDeviceTokenDto.cs
@@ -0,0 +1,6 @@
+namespace TaskTrackingSystem.Shared.Models.Notification;
+
+public class RegisterDeviceTokenDto
+{
+ public string FcmToken { get; set; } = string.Empty;
+}
diff --git a/TaskTrackingSystem.Shared/Models/Project/AssignMembersDto.cs b/TaskTrackingSystem.Shared/Models/Project/AssignMembersDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0ec2b6dad6fff9e4cf3bd2963babec71ea2df4c9
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Project/AssignMembersDto.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+
+namespace TaskTrackingSystem.Shared.Models.Project
+{
+ public class AssignMembersDto
+ {
+ [Required]
+ public List UserIds { get; set; } = new List();
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Project/CreateProjectDto.cs b/TaskTrackingSystem.Shared/Models/Project/CreateProjectDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ed93c0b278ec3332538f892dadacf6945aca3129
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Project/CreateProjectDto.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+
+namespace TaskTrackingSystem.Shared.Models.Project
+{
+ public class CreateProjectDto : IValidatableObject
+ {
+ [Required, MaxLength(150)]
+ public string Name { get; set; } = string.Empty;
+
+ [MaxLength(500)]
+ public string? Description { get; set; }
+
+ [Required]
+ public DateTime StartDate { get; set; }
+
+ [Required]
+ public DateTime EndDate { get; set; }
+
+ [Required]
+ public long CreatedById { get; set; }
+
+ [Range(0, 100000)]
+ public int? BudgetedHours { get; set; }
+
+ public IEnumerable Validate(ValidationContext validationContext)
+ {
+ if (EndDate < StartDate)
+ {
+ yield return new ValidationResult(
+ "End date cannot be before start date.",
+ new[] { nameof(EndDate), nameof(StartDate) });
+ }
+ }
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Project/ProjectDto.cs b/TaskTrackingSystem.Shared/Models/Project/ProjectDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7f18c0df7f49180ef78ad199c29ec7681145178a
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Project/ProjectDto.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TaskTrackingSystem.Shared.Models.Project
+{
+ public class ProjectDto
+ {
+ public long Id { get; set; }
+ public string Name { get; set; } = string.Empty;
+ public string? Description { get; set; }
+ public DateTime StartDate { get; set; }
+ public DateTime EndDate { get; set; }
+ public long CreatedById { get; set; }
+ public DateTime CreatedAt { get; set; }
+ public int? BudgetedHours { get; set; }
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Project/UpdateProjectDto.cs b/TaskTrackingSystem.Shared/Models/Project/UpdateProjectDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d93fff14a0bbd5d0f18024def99da2fd374b1fdf
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Project/UpdateProjectDto.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+
+namespace TaskTrackingSystem.Shared.Models.Project
+{
+ public class UpdateProjectDto : IValidatableObject
+ {
+ [Required, MaxLength(150)]
+ public string Name { get; set; } = string.Empty;
+
+ [MaxLength(500)]
+ public string? Description { get; set; }
+
+ [Required]
+ public DateTime StartDate { get; set; }
+
+ [Required]
+ public DateTime EndDate { get; set; }
+
+ [Range(0, 100000)]
+ public int? BudgetedHours { get; set; }
+
+ public IEnumerable Validate(ValidationContext validationContext)
+ {
+ if (EndDate < StartDate)
+ {
+ yield return new ValidationResult(
+ "End date cannot be before start date.",
+ new[] { nameof(EndDate), nameof(StartDate) });
+ }
+ }
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Report/EmployeeProductivityReportDto.cs b/TaskTrackingSystem.Shared/Models/Report/EmployeeProductivityReportDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..597fe97f734dbc082f33bf7783aff078855c79f2
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Report/EmployeeProductivityReportDto.cs
@@ -0,0 +1,20 @@
+namespace TaskTrackingSystem.Shared.Models.Report;
+
+public class EmployeeProductivityReportDto
+{
+ public long UserId { get; set; }
+ public string FullName { get; set; } = string.Empty;
+ public string Username { get; set; } = string.Empty;
+ public int AssignedCount { get; set; }
+ public int CompletedCount { get; set; }
+ public int AssignedIssues { get; set; }
+ public int OpenIssues { get; set; }
+ public int OverdueIssues { get; set; }
+ public decimal ActualHours { get; set; }
+ public string WorkloadLevel { get; set; } = "Balanced";
+ public double AverageCompletionTimeDays { get; set; }
+ public string Trend { get; set; } = "Stable";
+ public double CompletionRate { get; set; }
+ public double Efficiency { get; set; }
+ public double OnTimeDeliveryRate { get; set; }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Report/IssueReportSummaryDto.cs b/TaskTrackingSystem.Shared/Models/Report/IssueReportSummaryDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f97ac81bd324a421b32178f9e406b553e529c315
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Report/IssueReportSummaryDto.cs
@@ -0,0 +1,13 @@
+namespace TaskTrackingSystem.Shared.Models.Report;
+
+public class IssueReportSummaryDto
+{
+ public int TotalIssues { get; set; }
+ public int OpenIssues { get; set; }
+ public int OverdueIssues { get; set; }
+ public int BlockedIssues { get; set; }
+ public decimal EstimatedHours { get; set; }
+ public decimal ActualHours { get; set; }
+ public decimal VarianceHours { get; set; }
+ public double UtilizationPercent { get; set; }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Report/OverdueCriticalTaskDto.cs b/TaskTrackingSystem.Shared/Models/Report/OverdueCriticalTaskDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..acd1aef25271448748aefa42aa37994b46ffdaa9
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Report/OverdueCriticalTaskDto.cs
@@ -0,0 +1,24 @@
+using System;
+
+namespace TaskTrackingSystem.Shared.Models.Report
+{
+ public class OverdueCriticalTaskDto
+ {
+ public long TaskId { get; set; }
+ public string Title { get; set; } = string.Empty;
+ public string? Description { get; set; }
+ public string ProjectName { get; set; } = string.Empty;
+ public string StatusName { get; set; } = string.Empty;
+ public string PriorityName { get; set; } = string.Empty;
+ public string? AssignedTo { get; set; }
+ public long? AssignedToUserId { get; set; }
+ public DateTime DueDate { get; set; }
+ public int DaysOverdue { get; set; }
+ public DateTime CreatedAt { get; set; }
+ public int OverdueIssues { get; set; }
+ public string DelayReason { get; set; } = string.Empty;
+ public string BlockedBy { get; set; } = string.Empty;
+ public int EscalationLevel { get; set; }
+ public string RecordType { get; set; } = "Task";
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Report/ProjectProgressReportDto.cs b/TaskTrackingSystem.Shared/Models/Report/ProjectProgressReportDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..4720b10009cc32a86f1179276b9b9b1c8ad209f0
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Report/ProjectProgressReportDto.cs
@@ -0,0 +1,21 @@
+namespace TaskTrackingSystem.Shared.Models.Report;
+
+public class ProjectProgressReportDto
+{
+ public long ProjectId { get; set; }
+ public string ProjectName { get; set; } = string.Empty;
+ public DateTime StartDate { get; set; }
+ public DateTime EndDate { get; set; }
+ public int TotalTasks { get; set; }
+ public int CompletedTasks { get; set; }
+ public int OpenIssues { get; set; }
+ public int OverdueIssues { get; set; }
+ public int BlockedIssues { get; set; }
+ public double Progress { get; set; }
+ public bool IsAhead { get; set; }
+ public bool IsAtRisk { get; set; }
+ public string ProjectHealth { get; set; } = "Healthy";
+ public DateTime LastActivity { get; set; }
+ public DateTime? NextMilestone { get; set; }
+ public decimal ActualHours { get; set; }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Report/TaskReportDto.cs b/TaskTrackingSystem.Shared/Models/Report/TaskReportDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..48814c2198280f7a9135a5a628b8ebd6c05d96f2
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Report/TaskReportDto.cs
@@ -0,0 +1,29 @@
+using System;
+using TaskTrackingSystem.Shared.Enums;
+using AppTaskStatus = TaskTrackingSystem.Shared.Enums.AppTaskStatus;
+
+namespace TaskTrackingSystem.Shared.Models.Report
+{
+ public class TaskReportDto
+ {
+ public long TaskId { get; set; }
+ public string Title { get; set; } = string.Empty;
+ public string? Description { get; set; }
+ public string ProjectName { get; set; } = string.Empty;
+ public long ProjectId { get; set; }
+ public AppTaskStatus StatusId { get; set; }
+ public string StatusName { get; set; } = string.Empty;
+ public TaskPriority PriorityId { get; set; }
+ public string PriorityName { get; set; } = string.Empty;
+ public string? AssignedToUser { get; set; }
+ public long? AssignedToUserId { get; set; }
+ public string? AssignedByUser { get; set; }
+ public DateTime DueDate { get; set; }
+ public DateTime CreatedAt { get; set; }
+ public DateTime LastActivity { get; set; }
+ public int OpenIssues { get; set; }
+ public int OverdueIssues { get; set; }
+ public string IssueSummary { get; set; } = string.Empty;
+ public string TaskHealth { get; set; } = "Healthy";
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Report/TaskStatusSummaryDto.cs b/TaskTrackingSystem.Shared/Models/Report/TaskStatusSummaryDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..77ca2cec85c23e81ac2715fdf68a6c6cee51b702
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Report/TaskStatusSummaryDto.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace TaskTrackingSystem.Shared.Models.Report
+{
+ public class TaskStatusSummaryDto
+ {
+ public long TaskId { get; set; }
+ public string Title { get; set; } = string.Empty;
+ public string ProjectName { get; set; } = string.Empty;
+ public string StatusName { get; set; } = string.Empty;
+ public string PriorityName { get; set; } = string.Empty;
+ public string? AssignedTo { get; set; }
+ public DateTime DueDate { get; set; }
+ public DateTime CreatedAt { get; set; }
+ public bool IsOverdue => DueDate < DateTime.UtcNow && StatusName != "Done";
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Report/TeamProductivityReportDto.cs b/TaskTrackingSystem.Shared/Models/Report/TeamProductivityReportDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c3793d0081de3a6c3c2fc367addd8e0db999ecba
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Report/TeamProductivityReportDto.cs
@@ -0,0 +1,15 @@
+namespace TaskTrackingSystem.Shared.Models.Report
+{
+ public class TeamProductivityReportDto
+ {
+ public long UserId { get; set; }
+ public string FullName { get; set; } = string.Empty;
+ public string Username { get; set; } = string.Empty;
+ public int TotalAssigned { get; set; }
+ public int Completed { get; set; }
+ public int InProgress { get; set; }
+ public int ToDo { get; set; }
+ public int Overdue { get; set; }
+ public double CompletionRate { get; set; }
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Report/UserProductivityDto.cs b/TaskTrackingSystem.Shared/Models/Report/UserProductivityDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e281345524d0fb8bc18d0f4be7562f539d4d855d
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Report/UserProductivityDto.cs
@@ -0,0 +1,13 @@
+namespace TaskTrackingSystem.Shared.Models.Report
+{
+ public class UserProductivityDto
+ {
+ public long UserId { get; set; }
+ public string Username { get; set; } = string.Empty;
+ public string FullName { get; set; } = string.Empty;
+ public int TotalAssignedTasks { get; set; }
+ public int CompletedTasksCount { get; set; }
+ public double EfficiencyRatio { get; set; } // Completed / Total Asssined (percentage or ratio)
+ public double OnTimeDeliveryRate { get; set; }
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Role/AssignAccessDto.cs b/TaskTrackingSystem.Shared/Models/Role/AssignAccessDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..44c19cbbc4fcfd5754aebabb9682bc294fe49295
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Role/AssignAccessDto.cs
@@ -0,0 +1,15 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+
+namespace TaskTrackingSystem.Shared.Models.Role
+{
+ public class AssignAccessDto
+ {
+ [Required]
+ ///
+ /// Menu codes and permission codes selected for the role.
+ ///
+ public List AccessCodes { get; set; } = new List();
+ }
+}
+
diff --git a/TaskTrackingSystem.Shared/Models/Role/CreateRoleDto.cs b/TaskTrackingSystem.Shared/Models/Role/CreateRoleDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..56a50e52c9cddf596afef421a53fce3c32d6b409
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Role/CreateRoleDto.cs
@@ -0,0 +1,20 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+
+namespace TaskTrackingSystem.Shared.Models.Role
+{
+ public class CreateRoleDto
+ {
+ [Required, MaxLength(50)]
+ public string Name { get; set; } = string.Empty;
+
+ [MaxLength(200)]
+ public string? Description { get; set; }
+
+ ///
+ /// Optional: Menu and permission codes to assign to this role on creation.
+ ///
+ public List AccessCodes { get; set; } = new();
+ }
+}
+
diff --git a/TaskTrackingSystem.Shared/Models/Role/RoleDto.cs b/TaskTrackingSystem.Shared/Models/Role/RoleDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d8bed3d9343b3fc0a82dc66cca604868f519acff
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Role/RoleDto.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace TaskTrackingSystem.Shared.Models.Role
+{
+ public class RoleDto
+ {
+ public long Id { get; set; }
+ public string Name { get; set; } = string.Empty;
+ public string? Description { get; set; }
+ public DateTime CreatedAt { get; set; }
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Role/UpdateRoleDto.cs b/TaskTrackingSystem.Shared/Models/Role/UpdateRoleDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b1a3c3bdde4fe95ee24ae86d669bf20a599f4b5b
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Role/UpdateRoleDto.cs
@@ -0,0 +1,13 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace TaskTrackingSystem.Shared.Models.Role
+{
+ public class UpdateRoleDto
+ {
+ [Required, MaxLength(50)]
+ public string Name { get; set; } = string.Empty;
+
+ [MaxLength(200)]
+ public string? Description { get; set; }
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Task/CreateTaskDto.cs b/TaskTrackingSystem.Shared/Models/Task/CreateTaskDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d5dd20ceb9ffdbcc6e42a5ff092a2f55165ec6aa
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Task/CreateTaskDto.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using TaskTrackingSystem.Shared.Enums;
+using AppTaskStatus = TaskTrackingSystem.Shared.Enums.AppTaskStatus;
+
+namespace TaskTrackingSystem.Shared.Models.Task
+{
+ public class CreateTaskDto : IValidatableObject
+ {
+ [Required, MaxLength(200)]
+ public string Title { get; set; } = string.Empty;
+
+ [MaxLength(1000)]
+ public string? Description { get; set; }
+
+ [Required]
+ [Range(1, long.MaxValue)]
+ public long ProjectId { get; set; }
+
+ [EnumDataType(typeof(AppTaskStatus))]
+ public AppTaskStatus StatusId { get; set; }
+
+ [EnumDataType(typeof(TaskPriority))]
+ public TaskPriority PriorityId { get; set; }
+
+ [Range(0, long.MaxValue)]
+ public long? AssignedTo { get; set; }
+
+ [Range(0, long.MaxValue)]
+ public long? AssignedBy { get; set; }
+
+ [Required]
+ public DateTime DueDate { get; set; }
+
+ public IEnumerable Validate(ValidationContext validationContext)
+ {
+ if (DueDate == default)
+ {
+ yield return new ValidationResult(
+ "Due date is required.",
+ new[] { nameof(DueDate) });
+ }
+ }
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Task/TaskDto.cs b/TaskTrackingSystem.Shared/Models/Task/TaskDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d6fd677b5398ffa7a8425b1f92ec7ef48df5345d
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Task/TaskDto.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using TaskTrackingSystem.Shared.Enums;
+using AppTaskStatus = TaskTrackingSystem.Shared.Enums.AppTaskStatus;
+
+namespace TaskTrackingSystem.Shared.Models.Task
+{
+ public class TaskDto
+ {
+ public long Id { get; set; }
+ public string Title { get; set; } = string.Empty;
+ public string? Description { get; set; }
+ public long ProjectId { get; set; }
+ public AppTaskStatus StatusId { get; set; }
+ public TaskPriority PriorityId { get; set; }
+ public long? AssignedTo { get; set; }
+ public long? AssignedBy { get; set; }
+ public DateTime DueDate { get; set; }
+ public DateTime CreatedAt { get; set; }
+ public DateTime? CompletedAt { get; set; }
+ public bool IsArchived { get; set; }
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Models/Task/UpdateTaskDto.cs b/TaskTrackingSystem.Shared/Models/Task/UpdateTaskDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1b09b89f57e5c4f209cf7fb0dc54d0d99acf3105
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/Task/UpdateTaskDto.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using TaskTrackingSystem.Shared.Enums;
+using AppTaskStatus = TaskTrackingSystem.Shared.Enums.AppTaskStatus;
+
+namespace TaskTrackingSystem.Shared.Models.Task
+{
+ public class UpdateTaskDto : IValidatableObject
+ {
+ [Required, MaxLength(200)]
+ public string Title { get; set; } = string.Empty;
+
+ [MaxLength(1000)]
+ public string? Description { get; set; }
+
+ [Required]
+ [EnumDataType(typeof(AppTaskStatus))]
+ public AppTaskStatus StatusId { get; set; }
+
+ [Required]
+ [EnumDataType(typeof(TaskPriority))]
+ public TaskPriority PriorityId { get; set; }
+
+ [Range(0, long.MaxValue)]
+ public long? AssignedTo { get; set; }
+
+ [Range(0, long.MaxValue)]
+ public long? AssignedBy { get; set; }
+
+ [Required]
+ public DateTime DueDate { get; set; }
+
+ public IEnumerable Validate(ValidationContext validationContext)
+ {
+ if (DueDate == default)
+ {
+ yield return new ValidationResult(
+ "Due date is required.",
+ new[] { nameof(DueDate) });
+ }
+ }
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Models/User/CreateUserDto.cs b/TaskTrackingSystem.Shared/Models/User/CreateUserDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..683e10c6acfdb2906c885f90522ef45978399779
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/User/CreateUserDto.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TaskTrackingSystem.Shared.Models.User
+{
+ public class CreateUserDto
+ {
+ [Required, MaxLength(50)]
+ [MinLength(3, ErrorMessage = ResultMessages.UsernameMinLength)]
+ [RegularExpression(@"^[a-zA-Z0-9._]+$", ErrorMessage = ResultMessages.UsernameInvalidCharacters)]
+ public string Username { get; set; } = string.Empty;
+
+ [Required, MaxLength(50)]
+ public string FirstName { get; set; } = string.Empty;
+
+ [Required, MaxLength(50)]
+ public string LastName { get; set; } = string.Empty;
+
+ [Required, EmailAddress, MaxLength(256)]
+ public string Email { get; set; } = string.Empty;
+
+ [Required]
+ [MinLength(8, ErrorMessage = ResultMessages.PasswordMinLengthRule)]
+ [RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^a-zA-Z0-9]).+$", ErrorMessage = ResultMessages.PasswordComplexityRule)]
+ public string Password { get; set; } = string.Empty;
+
+ [MaxLength(20)]
+ public string? Phone { get; set; }
+
+ [Required]
+ public long RoleId { get; set; }
+ }
+ }
diff --git a/TaskTrackingSystem.Shared/Models/User/UpdateUserDto.cs b/TaskTrackingSystem.Shared/Models/User/UpdateUserDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..812ddd01976efc0837b5e0a59a15d1edf976fe5b
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/User/UpdateUserDto.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TaskTrackingSystem.Shared.Models.User
+{
+ public class UpdateUserDto
+ {
+ [Required, MaxLength(50)]
+ [MinLength(3, ErrorMessage = ResultMessages.UsernameMinLength)]
+ [RegularExpression(@"^[a-zA-Z0-9._]+$", ErrorMessage = ResultMessages.UsernameInvalidCharacters)]
+ public string Username { get; set; } = string.Empty;
+
+ [Required, MaxLength(50)]
+ public string FirstName { get; set; } = string.Empty;
+
+ [Required, MaxLength(50)]
+ public string LastName { get; set; } = string.Empty;
+
+ [MaxLength(20)]
+ public string? Phone { get; set; }
+
+ [Required]
+ public long RoleId { get; set; }
+
+ public bool IsActive { get; set; }
+ }
+}
diff --git a/TaskTrackingSystem.Shared/Models/User/UserDto.cs b/TaskTrackingSystem.Shared/Models/User/UserDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..89073dbedd0b264da3a9f9a2530fae2d4691f9ea
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Models/User/UserDto.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TaskTrackingSystem.Shared.Models.User
+{
+ public class UserDto
+ {
+ public long Id { get; set; }
+ public string Username { get; set; } = string.Empty;
+ public string FirstName { get; set; } = string.Empty;
+ public string LastName { get; set; } = string.Empty;
+ public string Email { get; set; } = string.Empty;
+ public string? Phone { get; set; }
+ public long RoleId { get; set; }
+ public bool IsActive { get; set; }
+ public DateTime CreatedAt { get; set; }
+ }
+
+
+}
diff --git a/TaskTrackingSystem.Shared/PagedResult.cs b/TaskTrackingSystem.Shared/PagedResult.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f63a92a40d4d9f6725ac1c9134fe31d48156ac6e
--- /dev/null
+++ b/TaskTrackingSystem.Shared/PagedResult.cs
@@ -0,0 +1,10 @@
+namespace TaskTrackingSystem.Shared;
+
+public class PagedResult
+{
+ public List Items { get; set; } = new();
+ public int TotalCount { get; set; }
+ public int Page { get; set; } = 1;
+ public int PageSize { get; set; } = 20;
+ public int TotalPages { get; set; }
+}
diff --git a/TaskTrackingSystem.Shared/PaginationQuery.cs b/TaskTrackingSystem.Shared/PaginationQuery.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d95a94e854b409c6f494502fee68bc0c647e1ff7
--- /dev/null
+++ b/TaskTrackingSystem.Shared/PaginationQuery.cs
@@ -0,0 +1,8 @@
+namespace TaskTrackingSystem.Shared;
+
+public class PaginationQuery
+{
+ public int? Page { get; set; }
+
+ public int? Limit { get; set; }
+}
diff --git a/TaskTrackingSystem.Shared/Result.cs b/TaskTrackingSystem.Shared/Result.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7dfc64e4368a5823279e546bda81b8bf590490f5
--- /dev/null
+++ b/TaskTrackingSystem.Shared/Result.cs
@@ -0,0 +1,111 @@
+using System;
+using TaskTrackingSystem.Shared.Localization;
+
+namespace TaskTrackingSystem.Shared
+{
+ public class Result
+ {
+ public bool IsSuccess { get; set; }
+ public T? Value { get; set; }
+ public string? ErrorMessage { get; set; }
+ public int StatusCode { get; set; }
+
+ public static Result Success(T value, int statusCode = 200)
+ {
+ return new Result
+ {
+ IsSuccess = true,
+ Value = value,
+ StatusCode = statusCode
+ };
+ }
+
+ public static Result Failure(string errorMessage, int statusCode = 400)
+ {
+ return new Result
+ {
+ IsSuccess = false,
+ ErrorMessage = errorMessage,
+ StatusCode = statusCode
+ };
+ }
+ }
+
+ public class Result
+ {
+ public bool IsSuccess { get; set; }
+ public string? ErrorMessage { get; set; }
+ public int StatusCode { get; set; }
+
+ public static Result Success(int statusCode = 200)
+ {
+ return new Result
+ {
+ IsSuccess = true,
+ StatusCode = statusCode
+ };
+ }
+
+ public static Result Failure(string errorMessage, int statusCode = 400)
+ {
+ return new Result
+ {
+ IsSuccess = false,
+ ErrorMessage = errorMessage,
+ StatusCode = statusCode
+ };
+ }
+ }
+
+ public static class ResultMessages
+ {
+ // Auth
+ public const string InvalidCredentials = "Invalid username/email or password.";
+ public const string ParseAuthResponseFailed = "Failed to parse authentication response tokens.";
+
+ // Project
+ public const string ProjectNameRequired = "Project name is required.";
+ public const string FailedToUpdateProject = "Failed to update project.";
+ public const string FailedToCreateProject = "Failed to create project.";
+ public static string ProjectNotFound(long id) => AppLocalization.Text("result.projectNotFound", $"Project with ID {id} not found.").Replace("{id}", id.ToString());
+ public const string UserIdsCannotBeNull = "User IDs cannot be null.";
+ public static string InvalidUserIds(string ids) => $"The following user IDs are invalid or deleted: {ids}";
+ public static string UserNotProjectMember(long userId, long projectId) => $"User with ID {userId} is not a member of project with ID {projectId}.";
+
+ // Task
+ public const string TaskTitleRequired = "Task title is required.";
+ public const string SelectProjectRequired = "Please select a project.";
+ public const string FailedToUpdateTask = "Failed to update task.";
+ public const string FailedToCreateTask = "Failed to create task.";
+ public static string TaskNotFound(long id) => AppLocalization.Text("result.taskNotFound", $"Task with ID {id} not found.").Replace("{id}", id.ToString());
+ public static string TaskNotFoundOrDeleted(long id) => AppLocalization.Text("result.taskNotFoundOrDeleted", $"Task with ID {id} not found or already deleted.").Replace("{id}", id.ToString());
+
+ // Role/Permission
+ public const string RoleNameRequired = "Role name is required.";
+ public const string FailedToUpdateRole = "Failed to update role.";
+ public const string FailedToCreateRole = "Failed to create role.";
+ public static string RoleNotFound(long id) => AppLocalization.Text("result.roleNotFound", $"Role with ID {id} not found.").Replace("{id}", id.ToString());
+ public static string AccessCodesCannotBeNull => AppLocalization.Text("result.accessCodesCannotBeNull", "Access codes cannot be null.");
+ public static string InvalidAccessCodes(string ids) => AppLocalization.Text("result.invalidAccessCodes", $"The following access codes are invalid or deleted: {ids}").Replace("{ids}", ids);
+
+ // User
+ public const string FillAllFields = "Please fill in all required fields.";
+ public const string FailedToUpdateUser = "Failed to update user.";
+ public const string PasswordMinLength = "Password must be at least 6 characters.";
+ public const string FailedToCreateUser = "Failed to create user.";
+ public static string UserNotFound(long id) => AppLocalization.Text("result.userNotFound", $"User with ID {id} not found.").Replace("{id}", id.ToString());
+
+ // Register
+ public const string ParseRegistrationResponseFailed = "Failed to parse registration response.";
+ public const string RegistrationFailed = "Registration failed. Please try again.";
+
+ // Username Validation Rules
+ public const string UsernameMinLength = "Username must be at least 3 characters long.";
+ public const string UsernameNoSpaces = "Username cannot contain spaces.";
+ public const string UsernameInvalidCharacters = "Username cannot contain spaces, and can only contain letters, numbers, underscores (_), and periods (.).";
+
+ // Password Validation Rules
+ public const string PasswordMinLengthRule = "Password must be at least 8 characters long.";
+ public const string PasswordComplexityRule = "Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character.";
+ }
+}
diff --git a/TaskTrackingSystem.Shared/SimplePdfReportBuilder.cs b/TaskTrackingSystem.Shared/SimplePdfReportBuilder.cs
new file mode 100644
index 0000000000000000000000000000000000000000..54f80d6e0c6961577c2d85338f4129e853413e66
--- /dev/null
+++ b/TaskTrackingSystem.Shared/SimplePdfReportBuilder.cs
@@ -0,0 +1,205 @@
+using System.Text;
+
+namespace TaskTrackingSystem.Shared;
+
+public static class SimplePdfReportBuilder
+{
+ private const int PageWidth = 612;
+ private const int PageHeight = 792;
+ private const int LeftMargin = 48;
+ private const int TopMargin = 52;
+ private const int BottomMargin = 48;
+ private const int TitleFontSize = 16;
+ private const int BodyFontSize = 10;
+ private const int LineSpacing = 14;
+ private const int BodyLinesPerPage = 42;
+
+ public static byte[] BuildTableReport(
+ string title,
+ IEnumerable? summaryLines,
+ IReadOnlyList headers,
+ IEnumerable> rows)
+ {
+ var lines = new List();
+
+ if (summaryLines != null)
+ {
+ foreach (var line in summaryLines.Where(line => !string.IsNullOrWhiteSpace(line)))
+ {
+ lines.AddRange(WrapLine(line.Trim()));
+ }
+ }
+
+ if (headers.Count > 0)
+ {
+ lines.Add(string.Join(" | ", headers));
+ lines.Add(new string('-', Math.Min(96, Math.Max(24, headers.Sum(h => h.Length + 3)))));
+ }
+
+ foreach (var row in rows)
+ {
+ lines.AddRange(WrapLine(string.Join(" | ", row.Select(NormalizeCell))));
+ }
+
+ if (lines.Count == 0)
+ {
+ lines.Add("No data available.");
+ }
+
+ return BuildTextReport(title, lines);
+ }
+
+ public static byte[] BuildTextReport(string title, IEnumerable bodyLines)
+ {
+ var normalizedLines = bodyLines
+ .SelectMany(line => WrapLine(line))
+ .ToList();
+
+ var pages = normalizedLines
+ .Chunk(BodyLinesPerPage)
+ .Select(chunk => chunk.ToList())
+ .ToList();
+
+ if (pages.Count == 0)
+ {
+ pages.Add(new List { "No data available." });
+ }
+
+ var objects = new List();
+ objects.Add("1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
+
+ var pageObjectIds = new List();
+ var contentObjectIds = new List();
+ var nextObjectId = 4;
+
+ foreach (var _ in pages)
+ {
+ pageObjectIds.Add(nextObjectId++);
+ contentObjectIds.Add(nextObjectId++);
+ }
+
+ var pagesKids = string.Join(" ", pageObjectIds.Select(id => $"{id} 0 R"));
+ objects.Add($"2 0 obj\n<< /Type /Pages /Kids [{pagesKids}] /Count {pages.Count} >>\nendobj\n");
+ objects.Add("3 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\nendobj\n");
+
+ for (var pageIndex = 0; pageIndex < pages.Count; pageIndex++)
+ {
+ var pageObjectId = pageObjectIds[pageIndex];
+ var contentObjectId = contentObjectIds[pageIndex];
+ var pageLines = pages[pageIndex];
+ var content = BuildContentStream(title, pageLines, pageIndex + 1, pages.Count);
+ var contentBytes = Encoding.ASCII.GetBytes(content);
+
+ objects.Add(
+ $"{pageObjectId} 0 obj\n" +
+ $"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 {PageWidth} {PageHeight}] /Resources << /Font << /F1 3 0 R >> >> /Contents {contentObjectId} 0 R >>\n" +
+ "endobj\n");
+
+ objects.Add(
+ $"{contentObjectId} 0 obj\n" +
+ $"<< /Length {contentBytes.Length} >>\n" +
+ "stream\n" +
+ content +
+ "\nendstream\nendobj\n");
+ }
+
+ var buffer = new StringBuilder();
+ buffer.Append("%PDF-1.4\n");
+
+ var offsets = new List { 0 };
+ foreach (var obj in objects)
+ {
+ offsets.Add(Encoding.ASCII.GetByteCount(buffer.ToString()));
+ buffer.Append(obj);
+ }
+
+ var xrefStart = Encoding.ASCII.GetByteCount(buffer.ToString());
+ var xref = new StringBuilder();
+ xref.Append("xref\n");
+ xref.Append($"0 {objects.Count + 1}\n");
+ xref.Append("0000000000 65535 f \n");
+
+ foreach (var offset in offsets.Skip(1))
+ {
+ xref.Append($"{offset:D10} 00000 n \n");
+ }
+
+ xref.Append("trailer\n");
+ xref.Append($"<< /Size {objects.Count + 1} /Root 1 0 R >>\n");
+ xref.Append("startxref\n");
+ xref.Append($"{xrefStart}\n");
+ xref.Append("%%EOF");
+
+ buffer.Append(xref);
+ return Encoding.ASCII.GetBytes(buffer.ToString());
+ }
+
+ private static string BuildContentStream(string title, IReadOnlyList lines, int pageNumber, int totalPages)
+ {
+ var content = new StringBuilder();
+ content.AppendLine("BT");
+ content.AppendLine("/F1 16 Tf");
+ content.AppendLine($"{LeftMargin} {PageHeight - TopMargin} Td");
+ content.AppendLine($"({EscapeText(title)}) Tj");
+ content.AppendLine("/F1 10 Tf");
+ content.AppendLine($"0 -22 Td");
+ content.AppendLine($"({EscapeText($"Page {pageNumber} of {totalPages}")}) Tj");
+
+ foreach (var line in lines)
+ {
+ content.AppendLine($"0 -{LineSpacing} Td");
+ content.AppendLine($"({EscapeText(line)}) Tj");
+ }
+
+ content.AppendLine("ET");
+ return content.ToString();
+ }
+
+ private static IEnumerable WrapLine(string? line)
+ {
+ if (string.IsNullOrWhiteSpace(line))
+ {
+ yield return string.Empty;
+ yield break;
+ }
+
+ const int maxWidth = 92;
+ var remaining = line.Trim();
+
+ while (remaining.Length > maxWidth)
+ {
+ var breakIndex = remaining.LastIndexOf(' ', maxWidth);
+ if (breakIndex <= 0)
+ {
+ breakIndex = maxWidth;
+ }
+
+ yield return remaining[..breakIndex].Trim();
+ remaining = remaining[breakIndex..].TrimStart();
+ }
+
+ if (!string.IsNullOrWhiteSpace(remaining))
+ {
+ yield return remaining;
+ }
+ }
+
+ private static string NormalizeCell(string? value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ return "-";
+ }
+
+ return value.Replace("\r", " ").Replace("\n", " ").Trim();
+ }
+
+ private static string EscapeText(string value)
+ {
+ var ascii = new string(value.Select(ch => ch is >= ' ' and <= '~' ? ch : '?').ToArray());
+ return ascii
+ .Replace("\\", "\\\\")
+ .Replace("(", "\\(")
+ .Replace(")", "\\)");
+ }
+}
diff --git a/TaskTrackingSystem.Shared/SpreadsheetReportBuilder.cs b/TaskTrackingSystem.Shared/SpreadsheetReportBuilder.cs
new file mode 100644
index 0000000000000000000000000000000000000000..413c95fa74143e51a3528edd846ddddc2ecd5799
--- /dev/null
+++ b/TaskTrackingSystem.Shared/SpreadsheetReportBuilder.cs
@@ -0,0 +1,348 @@
+using System.Globalization;
+using System.IO.Compression;
+using System.Security;
+using System.Text;
+using System.Xml;
+
+namespace TaskTrackingSystem.Shared;
+
+public static class SpreadsheetReportBuilder
+{
+ private const string DateFormat = "dd-MM-yyyy";
+ private const string ContentTypeWorkbook = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml";
+ private const string ContentTypeWorksheet = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml";
+ private const string ContentTypeStyles = "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml";
+
+ public static byte[] BuildTableWorkbook(
+ string worksheetName,
+ string title,
+ IReadOnlyList headers,
+ IEnumerable> rows,
+ IEnumerable? summaryLines = null)
+ {
+ var rowData = rows.Select(row => row.ToArray()).ToList();
+ var summary = summaryLines?
+ .Where(line => !string.IsNullOrWhiteSpace(line))
+ .Select(line => line.Trim())
+ .ToList() ?? new List();
+
+ var totalRows = 0;
+ if (!string.IsNullOrWhiteSpace(title))
+ {
+ totalRows++;
+ }
+
+ totalRows += summary.Count;
+ totalRows++; // blank row before header
+ totalRows++; // header row
+ totalRows += rowData.Count;
+
+ var columnCount = Math.Max(1, headers.Count);
+ var lastColumnName = ToColumnName(columnCount);
+ var lastRowNumber = Math.Max(1, totalRows);
+ var dimension = $"A1:{lastColumnName}{lastRowNumber}";
+
+ using var stream = new MemoryStream();
+ using (var archive = new ZipArchive(stream, ZipArchiveMode.Create, leaveOpen: true))
+ {
+ AddEntry(archive, "[Content_Types].xml", BuildContentTypesXml());
+ AddEntry(archive, "_rels/.rels", BuildRootRelsXml());
+ AddEntry(archive, "xl/workbook.xml", BuildWorkbookXml(worksheetName));
+ AddEntry(archive, "xl/_rels/workbook.xml.rels", BuildWorkbookRelsXml());
+ AddEntry(archive, "xl/styles.xml", BuildStylesXml());
+ AddEntry(archive, "xl/worksheets/sheet1.xml", BuildWorksheetXml(title, headers, rowData, summary, dimension));
+ }
+
+ return stream.ToArray();
+ }
+
+ private static void AddEntry(ZipArchive archive, string path, string content)
+ {
+ var entry = archive.CreateEntry(path, CompressionLevel.Optimal);
+ using var writer = new StreamWriter(entry.Open(), new UTF8Encoding(false));
+ writer.Write(content);
+ }
+
+ private static string BuildContentTypesXml() =>
+ """
+
+
+
+
+
+
+
+
+ """;
+
+ private static string BuildRootRelsXml() =>
+ """
+
+
+
+
+ """;
+
+ private static string BuildWorkbookXml(string worksheetName)
+ {
+ var safeName = EscapeXml(SanitizeWorksheetName(worksheetName));
+ return $"""
+
+
+
+
+
+
+ """;
+ }
+
+ private static string BuildWorkbookRelsXml() =>
+ """
+
+
+
+
+
+ """;
+
+ private static string BuildStylesXml() =>
+ """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """;
+
+ private static string BuildWorksheetXml(
+ string title,
+ IReadOnlyList headers,
+ IReadOnlyList rows,
+ IReadOnlyList summaryLines,
+ string dimension)
+ {
+ var rowXml = new StringBuilder();
+ var rowIndex = 1;
+ var columnCount = Math.Max(1, headers.Count);
+
+ if (!string.IsNullOrWhiteSpace(title))
+ {
+ rowXml.Append(BuildSingleCellRow(rowIndex++, 0, title.Trim(), styleIndex: 1));
+ }
+
+ foreach (var line in summaryLines)
+ {
+ rowXml.Append(BuildSingleCellRow(rowIndex++, 0, line, styleIndex: 2));
+ }
+
+ rowIndex++; // blank row
+
+ rowXml.Append("");
+ for (var col = 1; col <= columnCount; col++)
+ {
+ var header = col <= headers.Count ? headers[col - 1] : string.Empty;
+ rowXml.Append(BuildInlineCell(rowIndex, col, header, styleIndex: 1));
+ }
+ rowXml.AppendLine("
");
+ rowIndex++;
+
+ foreach (var row in rows)
+ {
+ rowXml.Append("");
+
+ for (var col = 1; col <= columnCount; col++)
+ {
+ var value = col <= row.Length ? row[col - 1] : null;
+ rowXml.Append(BuildCell(rowIndex, col, value));
+ }
+
+ rowXml.AppendLine("
");
+ rowIndex++;
+ }
+
+ var autoFilterRow = 1 + (string.IsNullOrWhiteSpace(title) ? 0 : 1) + summaryLines.Count + 1;
+
+ var worksheetXml = new StringBuilder();
+ worksheetXml.AppendLine("");
+ worksheetXml.AppendLine("");
+ worksheetXml.AppendLine($" ");
+ worksheetXml.AppendLine(" ");
+ worksheetXml.AppendLine(" ");
+ worksheetXml.AppendLine($" ");
+ worksheetXml.AppendLine($" ");
+ worksheetXml.AppendLine(" ");
+ worksheetXml.AppendLine(" ");
+ worksheetXml.AppendLine(" ");
+ worksheetXml.AppendLine(" ");
+ worksheetXml.Append(BuildColumnDefinitions(columnCount));
+ worksheetXml.AppendLine(" ");
+ worksheetXml.AppendLine(" ");
+ worksheetXml.Append(rowXml);
+ worksheetXml.AppendLine(" ");
+ worksheetXml.AppendLine($" ");
+ worksheetXml.AppendLine(" ");
+ return worksheetXml.ToString();
+ }
+
+ private static string BuildSingleCellRow(int rowIndex, int colIndex, string value, int styleIndex)
+ {
+ return $"{BuildInlineCell(rowIndex, colIndex + 1, value, styleIndex)}
\n";
+ }
+
+ private static string BuildColumnDefinitions(int columnCount)
+ {
+ var builder = new StringBuilder();
+ for (var col = 1; col <= columnCount; col++)
+ {
+ builder.Append($" \n");
+ }
+ return builder.ToString();
+ }
+
+ private static string BuildCell(int rowIndex, int columnIndex, object? value)
+ {
+ if (value == null)
+ {
+ return string.Empty;
+ }
+
+ return value switch
+ {
+ DateTime dateTime => BuildNumericCell(rowIndex, columnIndex, dateTime.ToOADate().ToString(CultureInfo.InvariantCulture), styleIndex: 3),
+ DateTimeOffset dateTimeOffset => BuildNumericCell(rowIndex, columnIndex, dateTimeOffset.DateTime.ToOADate().ToString(CultureInfo.InvariantCulture), styleIndex: 3),
+ DateOnly dateOnly => BuildNumericCell(rowIndex, columnIndex, dateOnly.ToDateTime(TimeOnly.MinValue).ToOADate().ToString(CultureInfo.InvariantCulture), styleIndex: 3),
+ bool boolean => BuildBooleanCell(rowIndex, columnIndex, boolean),
+ sbyte or byte or short or ushort or int or uint or long or ulong or float or double or decimal
+ => BuildNumericCell(rowIndex, columnIndex, Convert.ToString(value, CultureInfo.InvariantCulture) ?? "0"),
+ _ => BuildInlineCell(rowIndex, columnIndex, Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty, styleIndex: 0)
+ };
+ }
+
+ private static string BuildInlineCell(int rowIndex, int columnIndex, string value, int styleIndex)
+ {
+ var cellRef = $"{ToColumnName(columnIndex)}{rowIndex}";
+ return $"""
+
+ {EscapeXml(value)}
+
+ """;
+ }
+
+ private static string BuildNumericCell(int rowIndex, int columnIndex, string value, int styleIndex = 0)
+ {
+ var cellRef = $"{ToColumnName(columnIndex)}{rowIndex}";
+ return $"""{value} """;
+ }
+
+ private static string BuildBooleanCell(int rowIndex, int columnIndex, bool value)
+ {
+ var cellRef = $"{ToColumnName(columnIndex)}{rowIndex}";
+ return $"""{(value ? "1" : "0")} """;
+ }
+
+ private static string ToColumnName(int columnNumber)
+ {
+ var dividend = columnNumber;
+ var columnName = string.Empty;
+
+ while (dividend > 0)
+ {
+ var modulo = (dividend - 1) % 26;
+ columnName = Convert.ToChar('A' + modulo) + columnName;
+ dividend = (dividend - modulo) / 26;
+ }
+
+ return columnName;
+ }
+
+ private static string SanitizeWorksheetName(string name)
+ {
+ var invalid = new[] { '[', ']', ':', '*', '?', '/', '\\' };
+ var cleaned = new string(name.Select(ch => invalid.Contains(ch) ? ' ' : ch).ToArray()).Trim();
+ if (string.IsNullOrWhiteSpace(cleaned))
+ {
+ cleaned = "Report";
+ }
+
+ return cleaned.Length <= 31 ? cleaned : cleaned[..31];
+ }
+
+ private static string EscapeXml(string value) =>
+ SecurityElement.Escape(value) ?? string.Empty;
+}
diff --git a/TaskTrackingSystem.Shared/TaskTrackingSystem.Shared.csproj b/TaskTrackingSystem.Shared/TaskTrackingSystem.Shared.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..a95641af451e786b9229c244df3dfec3b399afb7
--- /dev/null
+++ b/TaskTrackingSystem.Shared/TaskTrackingSystem.Shared.csproj
@@ -0,0 +1,16 @@
+
+
+
+ net8.0
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
+
diff --git a/TaskTrackingSystem.WebApi/Features/Audit/AuditLogController.cs b/TaskTrackingSystem.WebApi/Features/Audit/AuditLogController.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6f4549237ce6423e00bdac3310bbca2a52f1b4dc
--- /dev/null
+++ b/TaskTrackingSystem.WebApi/Features/Audit/AuditLogController.cs
@@ -0,0 +1,101 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.EntityFrameworkCore;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using TaskTrackingSystem.Database.AppDbContextModels;
+using TaskTrackingSystem.Shared;
+using TaskTrackingSystem.Shared.Models.AuditLog;
+using TaskTrackingSystem.WebApi.Infrastructure;
+
+namespace TaskTrackingSystem.WebApi.Features.Audit
+{
+ [Route("api/[controller]")]
+ [ApiController]
+ [Authorize]
+ public class AuditLogController : ControllerBase
+ {
+ private readonly AppDbContext _db;
+ private readonly PermissionAuthorizationService _permissionAuthorizationService;
+
+ public AuditLogController(AppDbContext db, PermissionAuthorizationService permissionAuthorizationService)
+ {
+ _db = db;
+ _permissionAuthorizationService = permissionAuthorizationService;
+ }
+
+ [HttpGet]
+ [ProducesResponseType(typeof(PagedResult), StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+ public async Task GetAuditLogs(
+ [FromQuery] string? search,
+ [FromQuery] PaginationQuery? paging = null)
+ {
+ if (!await _permissionAuthorizationService.CanAccessAsync(User, "api/AuditLog", "List"))
+ {
+ return Forbid();
+ }
+
+ var query = _db.AuditLogs
+ .Include(a => a.User)
+ .AsQueryable();
+
+ if (!string.IsNullOrWhiteSpace(search))
+ {
+ var searchLower = search.Trim().ToLower();
+ query = query.Where(a =>
+ a.Action.ToLower().Contains(searchLower) ||
+ a.Module.ToLower().Contains(searchLower) ||
+ a.Description.ToLower().Contains(searchLower) ||
+ (a.User != null && (a.User.Username.ToLower().Contains(searchLower) ||
+ (a.User.FirstName + " " + a.User.LastName).ToLower().Contains(searchLower))) ||
+ (a.IpAddress != null && a.IpAddress.ToLower().Contains(searchLower))
+ );
+ }
+
+ if (paging == null || (!paging.Page.HasValue && !paging.Limit.HasValue))
+ {
+ var fullLogs = await query
+ .OrderByDescending(a => a.CreatedAt)
+ .Select(a => new AuditLogDto
+ {
+ Id = a.Id,
+ UserId = a.UserId,
+ Username = a.User != null ? a.User.Username : "System",
+ UserFullName = a.User != null ? $"{a.User.FirstName} {a.User.LastName}" : "System",
+ Action = a.Action,
+ Module = a.Module,
+ Description = a.Description,
+ IpAddress = a.IpAddress,
+ CreatedAt = a.CreatedAt ?? System.DateTime.UtcNow
+ })
+ .ToListAsync();
+
+ return Ok(fullLogs);
+ }
+
+ var page = PaginationExtensions.NormalizePage(paging.Page);
+ var limit = PaginationExtensions.NormalizePageSize(paging.Limit ?? 0);
+
+ var pagedLogs = await query
+ .OrderByDescending(a => a.CreatedAt)
+ .Select(a => new AuditLogDto
+ {
+ Id = a.Id,
+ UserId = a.UserId,
+ Username = a.User != null ? a.User.Username : "System",
+ UserFullName = a.User != null ? $"{a.User.FirstName} {a.User.LastName}" : "System",
+ Action = a.Action,
+ Module = a.Module,
+ Description = a.Description,
+ IpAddress = a.IpAddress,
+ CreatedAt = a.CreatedAt ?? System.DateTime.UtcNow
+ })
+ .ToPagedResultAsync(page, limit);
+
+ return Ok(pagedLogs);
+ }
+ }
+}
diff --git a/TaskTrackingSystem.WebApi/Features/Auth/AuthController.cs b/TaskTrackingSystem.WebApi/Features/Auth/AuthController.cs
new file mode 100644
index 0000000000000000000000000000000000000000..280700e65bf7daaf014aa75f458c6a12cf3fa032
--- /dev/null
+++ b/TaskTrackingSystem.WebApi/Features/Auth/AuthController.cs
@@ -0,0 +1,68 @@
+using Microsoft.AspNetCore.Mvc;
+using System;
+using System.Threading.Tasks;
+using TaskTrackingSystem.Shared;
+using TaskTrackingSystem.Shared.Models.Auth;
+
+namespace TaskTrackingSystem.WebApi.Features.Auth
+{
+ [ApiController]
+ [Route("api/[controller]")]
+ public class AuthController : ControllerBase
+ {
+ private readonly AuthService _authService;
+
+ public AuthController(AuthService authService)
+ {
+ _authService = authService;
+ }
+
+ [HttpPost("login")]
+ public async Task Login([FromBody] LoginDto loginDto)
+ {
+ try
+ {
+ var response = await _authService.LoginAsync(loginDto);
+
+ if (response == null)
+ {
+ // Wrap the error inside your official Result architecture
+ return Unauthorized(Result.Failure(ResultMessages.InvalidCredentials, 401));
+ }
+
+ // Wrap the payload inside your official Result architecture
+ return Ok(Result.Success(response));
+ }
+ catch (InvalidOperationException ex)
+ {
+ return BadRequest(Result.Failure(ex.Message, 400));
+ }
+ }
+
+ [HttpPost("register")]
+ public async Task Register([FromBody] RegisterDto registerDto)
+ {
+ try
+ {
+ var response = await _authService.RegisterAsync(registerDto);
+ return Ok(Result.Success(response));
+ }
+ catch (InvalidOperationException ex)
+ {
+ return BadRequest(Result.Failure(ex.Message, 400));
+ }
+ }
+
+ [HttpPost("reset-password")]
+ public async Task ResetPassword([FromBody] ResetPasswordDto resetPasswordDto)
+ {
+ var result = await _authService.ResetPasswordAsync(resetPasswordDto);
+ if (!result.IsSuccess)
+ {
+ return StatusCode(result.StatusCode, result);
+ }
+
+ return Ok(result);
+ }
+ }
+}
diff --git a/TaskTrackingSystem.WebApi/Features/Auth/AuthService.cs b/TaskTrackingSystem.WebApi/Features/Auth/AuthService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..cb5a0fed60b64c7d1d14f70be3537ee617709632
--- /dev/null
+++ b/TaskTrackingSystem.WebApi/Features/Auth/AuthService.cs
@@ -0,0 +1,197 @@
+using System;
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.Extensions.Configuration;
+using Microsoft.IdentityModel.Tokens;
+using TaskTrackingSystem.Shared;
+using TaskTrackingSystem.Shared.Models.Auth;
+using TaskTrackingSystem.Database;
+using TaskTrackingSystem.Database.AppDbContextModels;
+
+namespace TaskTrackingSystem.WebApi.Features.Auth
+{
+ public class AuthService
+ {
+ private readonly AppDbContext _db;
+ private readonly IConfiguration _configuration;
+ private readonly IPasswordHasher _passwordHasher;
+
+ public AuthService(AppDbContext db, IConfiguration configuration, IPasswordHasher passwordHasher)
+ {
+ _db = db;
+ _configuration = configuration;
+ _passwordHasher = passwordHasher;
+ }
+
+ public async Task LoginAsync(LoginDto loginDto)
+ {
+
+ var user = await _db.Users
+ .Include(u => u.Role)
+ .FirstOrDefaultAsync(u => (u.Username == loginDto.UsernameOrEmail || u.Email == loginDto.UsernameOrEmail)
+ && !u.IsDeleted);
+
+ if (user == null) return null;
+
+
+ if (!user.IsActive)
+ {
+ throw new InvalidOperationException("This account has been deactivated.");
+ }
+
+
+ var passwordCheck = _passwordHasher.VerifyHashedPassword(user, user.PasswordHash, loginDto.Password);
+ if (passwordCheck == PasswordVerificationResult.Failed)
+ {
+ if (!IsLegacyPasswordMatch(user.PasswordHash, loginDto.Password))
+ {
+ return null;
+ }
+
+ user.PasswordHash = _passwordHasher.HashPassword(user, loginDto.Password);
+ await _db.SaveChangesAsync();
+ }
+
+ var roleId = user.RoleId;
+ if (roleId <= 0 && user.Role != null)
+ {
+ roleId = user.Role.Id;
+ }
+
+ var roleName = user.Role?.Name ?? string.Empty;
+ var token = GenerateJwtToken(user.Id.ToString(), user.Username, user.Email, roleId, roleName);
+
+ return new AuthResponseDto
+ {
+ Token = token,
+ Username = user.Username,
+ Email = user.Email,
+ RoleId = roleId,
+ RoleName = roleName
+ };
+ }
+
+ public async Task RegisterAsync(RegisterDto dto)
+ {
+ var usernameExists = await _db.Users.AnyAsync(u => u.Username == dto.Username && !u.IsDeleted);
+ if (usernameExists)
+ {
+ throw new InvalidOperationException("Username is already taken.");
+ }
+
+ var emailExists = await _db.Users.AnyAsync(u => u.Email == dto.Email && !u.IsDeleted);
+ if (emailExists)
+ {
+ throw new InvalidOperationException("Email is already registered.");
+ }
+
+ // Find default Role (Employee role, or fallback to lowest ID)
+ var defaultRole = await _db.Roles.FirstOrDefaultAsync(r => r.Name == "Employee" && r.IsDeleted != true)
+ ?? await _db.Roles.FirstOrDefaultAsync(r => r.IsDeleted != true);
+ if (defaultRole == null)
+ {
+ throw new InvalidOperationException("No system roles configured. Registration failed.");
+ }
+
+ var user = new TaskTrackingSystem.Database.AppDbContextModels.User
+ {
+ Username = dto.Username,
+ FirstName = dto.FirstName,
+ LastName = dto.LastName,
+ Email = dto.Email,
+ PasswordHash = string.Empty,
+ Phone = dto.Phone,
+ RoleId = defaultRole.Id,
+ IsActive = true,
+ CreatedAt = DateTime.UtcNow
+ };
+
+ user.PasswordHash = _passwordHasher.HashPassword(user, dto.Password);
+
+ _db.Users.Add(user);
+ await _db.SaveChangesAsync();
+
+ var token = GenerateJwtToken(user.Id.ToString(), user.Username, user.Email, defaultRole.Id, defaultRole.Name);
+
+ return new AuthResponseDto
+ {
+ Token = token,
+ Username = user.Username,
+ Email = user.Email,
+ RoleId = defaultRole.Id,
+ RoleName = defaultRole.Name
+ };
+ }
+
+ public async Task ResetPasswordAsync(ResetPasswordDto dto)
+ {
+ if (string.IsNullOrWhiteSpace(dto.UsernameOrEmail) ||
+ string.IsNullOrWhiteSpace(dto.RecoveryCode) ||
+ string.IsNullOrWhiteSpace(dto.NewPassword))
+ {
+ return Result.Failure(ResultMessages.FillAllFields, 400);
+ }
+
+ var expectedCode = _configuration["PasswordReset:RecoveryCode"]
+ ?? throw new InvalidOperationException("PasswordReset:RecoveryCode must be configured.");
+ if (!string.Equals(dto.RecoveryCode.Trim(), expectedCode, StringComparison.Ordinal))
+ {
+ return Result.Failure("Invalid recovery code.", 400);
+ }
+
+ var user = await _db.Users
+ .FirstOrDefaultAsync(u => (u.Username == dto.UsernameOrEmail || u.Email == dto.UsernameOrEmail) && !u.IsDeleted);
+
+ if (user == null)
+ {
+ return Result.Failure("Account not found.", 404);
+ }
+
+ user.PasswordHash = _passwordHasher.HashPassword(user, dto.NewPassword);
+ user.UpdatedAt = DateTime.UtcNow;
+ _db.Users.Update(user);
+ await _db.SaveChangesAsync();
+
+ return Result.Success(200);
+ }
+
+ private string GenerateJwtToken(string userId, string username, string email, long roleId, string roleName)
+ {
+ var jwtSettings = _configuration.GetSection("JwtSettings");
+ var keyStr = jwtSettings["Key"]
+ ?? throw new InvalidOperationException("JwtSettings:Key must be configured.");
+ var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(keyStr));
+ var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
+
+ var claims = new[]
+ {
+ new Claim(ClaimTypes.NameIdentifier, userId),
+ new Claim(ClaimTypes.Name, username),
+ new Claim(ClaimTypes.Email, email),
+ new Claim("role_id", roleId.ToString()),
+ new Claim(ClaimTypes.Role, roleName)
+ };
+
+ var token = new JwtSecurityToken(
+ issuer: jwtSettings["Issuer"]
+ ?? throw new InvalidOperationException("JwtSettings:Issuer must be configured."),
+ audience: jwtSettings["Audience"]
+ ?? throw new InvalidOperationException("JwtSettings:Audience must be configured."),
+ claims: claims,
+ expires: DateTime.UtcNow.AddDays(7),
+ signingCredentials: creds
+ );
+
+ return new JwtSecurityTokenHandler().WriteToken(token);
+ }
+
+ private static bool IsLegacyPasswordMatch(string passwordHash, string password)
+ {
+ return string.Equals(passwordHash, "HASHED_" + password, StringComparison.Ordinal);
+ }
+ }
+}
diff --git a/TaskTrackingSystem.WebApi/Features/Dashboard/DashboardController.cs b/TaskTrackingSystem.WebApi/Features/Dashboard/DashboardController.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6a4e4a1dad80acb7f2d077fd17567eba785ac599
--- /dev/null
+++ b/TaskTrackingSystem.WebApi/Features/Dashboard/DashboardController.cs
@@ -0,0 +1,44 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using TaskTrackingSystem.Shared;
+using TaskTrackingSystem.Shared.Models.Dashboard;
+using TaskTrackingSystem.WebApi.Infrastructure;
+
+namespace TaskTrackingSystem.WebApi.Features.Dashboard
+{
+ [Route("api/[controller]")]
+ [ApiController]
+ [Authorize]
+ public class DashboardController : ControllerBase
+ {
+ private readonly DashboardService _dashboardService;
+
+ public DashboardController(DashboardService dashboardService)
+ {
+ _dashboardService = dashboardService;
+ }
+
+ [HttpGet("summary")]
+ public async Task>> GetSummary()
+ {
+ var result = await _dashboardService.GetSummaryAsync(User.GetRoleId(), User.GetUserId());
+ return StatusCode(result.StatusCode, result);
+ }
+
+ [HttpGet("tasks-overview")]
+ public async Task>>> GetTasksOverview()
+ {
+ var result = await _dashboardService.GetTasksOverviewAsync(User.GetRoleId(), User.GetUserId());
+ return StatusCode(result.StatusCode, result);
+ }
+
+ [HttpGet("project-progress")]
+ public async Task>>> GetProjectProgress()
+ {
+ var result = await _dashboardService.GetProjectProgressAsync(User.GetRoleId(), User.GetUserId());
+ return StatusCode(result.StatusCode, result);
+ }
+ }
+}
diff --git a/TaskTrackingSystem.WebApi/Features/Dashboard/DashboardService.cs b/TaskTrackingSystem.WebApi/Features/Dashboard/DashboardService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7b3d08b065a23cb6f08e2747fde416615226c561
--- /dev/null
+++ b/TaskTrackingSystem.WebApi/Features/Dashboard/DashboardService.cs
@@ -0,0 +1,162 @@
+using Microsoft.EntityFrameworkCore;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using TaskTrackingSystem.Database.AppDbContextModels;
+using TaskTrackingSystem.Shared;
+using TaskTrackingSystem.Shared.Enums;
+using TaskTrackingSystem.Shared.Models.Dashboard;
+using TaskTrackingSystem.WebApi.Infrastructure;
+
+namespace TaskTrackingSystem.WebApi.Features.Dashboard
+{
+ public class DashboardService
+ {
+ private readonly AppDbContext _db;
+
+ public DashboardService(AppDbContext db)
+ {
+ _db = db;
+ }
+
+ public async Task> GetSummaryAsync(long roleId, long currentUserId)
+ {
+ var isAdmin = await DataScopeAuthorization.IsAdminScopeAsync(_db, roleId);
+ var isManager = await DataScopeAuthorization.IsManagerScopeAsync(_db, roleId);
+
+ var projects = BuildAccessibleProjectQuery(isAdmin, currentUserId);
+ var tasks = BuildAccessibleTaskQuery(isAdmin, isManager, currentUserId);
+
+ var totalUsers = isAdmin
+ ? await _db.Users.CountAsync(u => !u.IsDeleted)
+ : await _db.Users
+ .Where(u => !u.IsDeleted &&
+ projects.SelectMany(p => p.ProjectMembers.Select(pm => pm.UserId))
+ .Distinct()
+ .Contains(u.Id))
+ .CountAsync();
+
+ var activeProjectsCount = await projects.CountAsync();
+ var pendingTasksCount = await tasks.CountAsync(t => t.StatusId != AppTaskStatus.Done);
+
+ var summary = new DashboardSummaryDto
+ {
+ TotalUsers = totalUsers,
+ ActiveProjectsCount = activeProjectsCount,
+ PendingTasksCount = pendingTasksCount
+ };
+
+ return Result.Success(summary);
+ }
+
+ public async Task>> GetTasksOverviewAsync(long roleId, long currentUserId)
+ {
+ var isAdmin = await DataScopeAuthorization.IsAdminScopeAsync(_db, roleId);
+ var isManager = await DataScopeAuthorization.IsManagerScopeAsync(_db, roleId);
+
+ var groupedTasks = await BuildAccessibleTaskQuery(isAdmin, isManager, currentUserId)
+ .GroupBy(t => t.StatusId)
+ .Select(g => new
+ {
+ StatusId = g.Key,
+ Count = g.Count()
+ })
+ .ToListAsync();
+
+ var statusMap = new Dictionary
+ {
+ { AppTaskStatus.Todo, "To Do" },
+ { AppTaskStatus.InProgress, "In Progress" },
+ { AppTaskStatus.Done, "Done" }
+ };
+
+ var overview = groupedTasks.Select(gt => new TaskStatusOverviewDto
+ {
+ StatusId = gt.StatusId,
+ StatusName = statusMap.TryGetValue(gt.StatusId, out var name) ? name : $"Status {gt.StatusId}",
+ TaskCount = gt.Count
+ }).ToList();
+
+ foreach (var status in statusMap)
+ {
+ if (!overview.Any(o => o.StatusId == status.Key))
+ {
+ overview.Add(new TaskStatusOverviewDto
+ {
+ StatusId = status.Key,
+ StatusName = status.Value,
+ TaskCount = 0
+ });
+ }
+ }
+
+ return Result>.Success(overview.OrderBy(o => o.StatusId));
+ }
+
+ public async Task>> GetProjectProgressAsync(long roleId, long currentUserId)
+ {
+ var isAdmin = await DataScopeAuthorization.IsAdminScopeAsync(_db, roleId);
+ var activeProjects = await BuildAccessibleProjectQuery(isAdmin, currentUserId).ToListAsync();
+ var progressList = new List();
+
+ foreach (var project in activeProjects)
+ {
+ var tasks = await _db.Tasks
+ .Where(t => t.ProjectId == project.Id && t.IsDeleted != true && !t.IsArchived)
+ .ToListAsync();
+
+ int totalTasks = tasks.Count;
+ int completedTasks = tasks.Count(t => t.StatusId == AppTaskStatus.Done);
+ double percentage = totalTasks > 0 ? Math.Round(((double)completedTasks / totalTasks) * 100, 2) : 0;
+
+ progressList.Add(new ProjectProgressDto
+ {
+ ProjectId = project.Id,
+ ProjectName = project.Name,
+ TotalTasksCount = totalTasks,
+ CompletedTasksCount = completedTasks,
+ CompletionPercentage = percentage
+ });
+ }
+
+ return Result>.Success(progressList);
+ }
+
+ private IQueryable BuildAccessibleTaskQuery(bool isAdmin, bool isManager, long currentUserId)
+ {
+ var query = _db.Tasks.Where(t => t.IsDeleted != true && !t.IsArchived);
+
+ if (isAdmin)
+ {
+ return query;
+ }
+
+ if (isManager)
+ {
+ return query.Where(t =>
+ t.AssignedTo == currentUserId ||
+ t.CreatedBy == currentUserId ||
+ t.Project.ProjectMembers.Any(pm => pm.UserId == currentUserId));
+ }
+
+ return query.Where(t =>
+ t.AssignedTo == currentUserId ||
+ t.CreatedBy == currentUserId);
+ }
+
+ private IQueryable BuildAccessibleProjectQuery(bool isAdmin, long currentUserId)
+ {
+ var query = _db.Projects.Where(p => p.IsDeleted != true);
+
+ if (isAdmin)
+ {
+ return query;
+ }
+
+ return query.Where(p =>
+ p.CreatedById == currentUserId ||
+ p.ProjectMembers.Any(pm => pm.UserId == currentUserId));
+ }
+ }
+}
diff --git a/TaskTrackingSystem.WebApi/Features/Issue/IssueController.cs b/TaskTrackingSystem.WebApi/Features/Issue/IssueController.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7fcef120e3bb61e18be0e8d1e6a3eb7b77181894
--- /dev/null
+++ b/TaskTrackingSystem.WebApi/Features/Issue/IssueController.cs
@@ -0,0 +1,116 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using TaskTrackingSystem.Shared;
+using TaskTrackingSystem.Shared.Enums;
+using TaskTrackingSystem.Shared.Models.Issue;
+using TaskTrackingSystem.WebApi.Infrastructure;
+
+namespace TaskTrackingSystem.WebApi.Features.Issue
+{
+ [Route("api/[controller]")]
+ [ApiController]
+ [Authorize]
+ public class IssueController : ControllerBase
+ {
+ private readonly IssueService _issueService;
+ private readonly PermissionAuthorizationService _permissionAuthorizationService;
+
+ public IssueController(IssueService issueService, PermissionAuthorizationService permissionAuthorizationService)
+ {
+ _issueService = issueService;
+ _permissionAuthorizationService = permissionAuthorizationService;
+ }
+
+ [HttpGet]
+ [ProducesResponseType(typeof(PagedResult), StatusCodes.Status200OK)]
+ public async Task GetIssues(
+ [FromQuery] string? search,
+ [FromQuery] long? taskId,
+ [FromQuery] long? projectId,
+ [FromQuery] AppTaskStatus? statusId,
+ [FromQuery] TaskPriority? priorityId,
+ [FromQuery] bool assignedOnly = false,
+ [FromQuery] PaginationQuery? paging = null)
+ {
+ if (paging == null || (!paging.Page.HasValue && !paging.Limit.HasValue))
+ {
+ var issues = await _issueService.GetAllIssuesAsync(User.GetRoleId(), User.GetUserId());
+ return Ok(issues);
+ }
+
+ var page = PaginationExtensions.NormalizePage(paging.Page);
+ var limit = PaginationExtensions.NormalizePageSize(paging.Limit ?? 0);
+ var paged = await _issueService.GetPagedIssuesAsync(
+ User.GetRoleId(),
+ User.GetUserId(),
+ search,
+ taskId,
+ projectId,
+ statusId,
+ priorityId,
+ assignedOnly,
+ page,
+ limit);
+
+ return Ok(paged);
+ }
+
+ [HttpGet("{id}")]
+ public async Task> GetIssue(long id)
+ {
+ var issue = await _issueService.GetIssueByIdAsync(id, User.GetRoleId(), User.GetUserId());
+ if (issue == null)
+ {
+ return NotFound(new { message = $"Issue with ID {id} not found." });
+ }
+
+ return Ok(issue);
+ }
+
+ [HttpGet("task/{taskId}")]
+ public async Task>> GetIssuesByTask(long taskId)
+ {
+ var issues = await _issueService.GetIssuesByTaskIdAsync(taskId, User.GetRoleId(), User.GetUserId());
+ return Ok(issues);
+ }
+
+ [HttpPost]
+ public async Task>> CreateIssue([FromBody] CreateIssueDto dto)
+ {
+ if (!await _permissionAuthorizationService.CanAccessAsync(User, "api/Issue", "Create"))
+ {
+ return StatusCode(403, Result.Failure("You do not have permission to create issues.", 403));
+ }
+
+ var result = await _issueService.CreateIssueAsync(dto, User.GetRoleId(), User.GetUserId());
+ return StatusCode(result.StatusCode, result);
+ }
+
+ [HttpPut("{id}")]
+ public async Task> UpdateIssue(long id, [FromBody] UpdateIssueDto dto)
+ {
+ if (!await _permissionAuthorizationService.CanAccessAsync(User, "api/Issue", "Update"))
+ {
+ return StatusCode(403, Result.Failure("You do not have permission to update issues.", 403));
+ }
+
+ var result = await _issueService.UpdateIssueAsync(id, dto, User.GetRoleId(), User.GetUserId());
+ return StatusCode(result.StatusCode, result);
+ }
+
+ [HttpDelete("{id}")]
+ public async Task> DeleteIssue(long id)
+ {
+ if (!await _permissionAuthorizationService.CanAccessAsync(User, "api/Issue", "Delete"))
+ {
+ return StatusCode(403, Result.Failure("You do not have permission to delete issues.", 403));
+ }
+
+ var result = await _issueService.SoftDeleteIssueAsync(id, User.GetRoleId(), User.GetUserId());
+ return StatusCode(result.StatusCode, result);
+ }
+ }
+}
diff --git a/TaskTrackingSystem.WebApi/Features/Issue/IssueService.cs b/TaskTrackingSystem.WebApi/Features/Issue/IssueService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..716cb348890f6765300bfccd966dde0cec6b27ba
--- /dev/null
+++ b/TaskTrackingSystem.WebApi/Features/Issue/IssueService.cs
@@ -0,0 +1,338 @@
+using Microsoft.EntityFrameworkCore;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using TaskTrackingSystem.Database.AppDbContextModels;
+using TaskTrackingSystem.Shared;
+using TaskTrackingSystem.Shared.Enums;
+using TaskTrackingSystem.Shared.Models.Issue;
+using TaskTrackingSystem.WebApi.Infrastructure;
+using IssueEntity = TaskTrackingSystem.Database.AppDbContextModels.Issue;
+
+namespace TaskTrackingSystem.WebApi.Features.Issue
+{
+ public class IssueService
+ {
+ private readonly AppDbContext _db;
+
+ public IssueService(AppDbContext db)
+ {
+ _db = db;
+ }
+
+ private static DateTime ToUtcDate(DateTime value)
+ {
+ return value.Kind == DateTimeKind.Utc
+ ? value
+ : DateTime.SpecifyKind(value.Date, DateTimeKind.Utc);
+ }
+
+ public async Task> GetAllIssuesAsync(long roleId, long currentUserId)
+ {
+ var isAdmin = await DataScopeAuthorization.IsAdminScopeAsync(_db, roleId);
+ var isManager = await DataScopeAuthorization.IsManagerScopeAsync(_db, roleId);
+ return await BuildAccessibleIssueQuery(isAdmin, isManager, currentUserId)
+ .OrderByDescending(i => i.CreatedAt ?? DateTime.UtcNow)
+ .Select(ToDtoProjection())
+ .ToListAsync();
+ }
+
+ public async Task> GetPagedIssuesAsync(
+ long roleId,
+ long currentUserId,
+ string? search,
+ long? taskId,
+ long? projectId,
+ AppTaskStatus? statusId,
+ TaskPriority? priorityId,
+ bool assignedOnly,
+ int page,
+ int pageSize)
+ {
+ var isAdmin = await DataScopeAuthorization.IsAdminScopeAsync(_db, roleId);
+ var isManager = await DataScopeAuthorization.IsManagerScopeAsync(_db, roleId);
+ var query = BuildAccessibleIssueQuery(isAdmin, isManager, currentUserId);
+
+ if (assignedOnly)
+ {
+ query = query.Where(i => i.AssignedTo == currentUserId);
+ }
+
+ if (!string.IsNullOrWhiteSpace(search))
+ {
+ var searchTerm = search.Trim().ToLower();
+ query = query.Where(i =>
+ (i.Title != null && i.Title.ToLower().Contains(searchTerm)) ||
+ (i.Description != null && i.Description.ToLower().Contains(searchTerm)));
+ }
+
+ if (taskId.HasValue && taskId.Value > 0)
+ {
+ query = query.Where(i => i.TaskId == taskId.Value);
+ }
+
+ if (projectId.HasValue && projectId.Value > 0)
+ {
+ query = query.Where(i => i.Task.ProjectId == projectId.Value);
+ }
+
+ if (statusId.HasValue)
+ {
+ query = query.Where(i => i.StatusId == statusId.Value);
+ }
+
+ if (priorityId.HasValue)
+ {
+ query = query.Where(i => i.PriorityId == priorityId.Value);
+ }
+
+ return await query
+ .OrderByDescending(i => i.CreatedAt ?? DateTime.UtcNow)
+ .Select(ToDtoProjection())
+ .ToPagedResultAsync(page, pageSize);
+ }
+
+
+
+ public async Task GetIssueByIdAsync(long id, long roleId, long currentUserId)
+ {
+ var isAdmin = await DataScopeAuthorization.IsAdminScopeAsync(_db, roleId);
+ var isManager = await DataScopeAuthorization.IsManagerScopeAsync(_db, roleId);
+ var issue = await BuildAccessibleIssueQuery(isAdmin, isManager, currentUserId)
+ .FirstOrDefaultAsync(i => i.Id == id);
+
+ if (issue == null)
+ {
+ return null;
+ }
+
+ return await BuildAccessibleIssueQuery(isAdmin, isManager, currentUserId)
+ .Where(i => i.Id == id)
+ .Select(ToDtoProjection())
+ .FirstOrDefaultAsync();
+ }
+
+ public async Task> GetIssuesByTaskIdAsync(long taskId, long roleId, long currentUserId)
+ {
+ var isAdmin = await DataScopeAuthorization.IsAdminScopeAsync(_db, roleId);
+ if (!await CanAccessTaskAsync(taskId, isAdmin, currentUserId))
+ {
+ return Array.Empty();
+ }
+
+ var isManager = await DataScopeAuthorization.IsManagerScopeAsync(_db, roleId);
+ return await BuildAccessibleIssueQuery(isAdmin, isManager, currentUserId)
+ .Where(i => i.TaskId == taskId)
+ .OrderByDescending(i => i.CreatedAt ?? DateTime.UtcNow)
+ .Select(ToDtoProjection())
+ .ToListAsync();
+ }
+
+ public async Task> CreateIssueAsync(CreateIssueDto dto, long roleId, long currentUserId)
+ {
+ if (string.IsNullOrWhiteSpace(dto.Title))
+ {
+ return Result.Failure("Issue title is required.", 400);
+ }
+
+ var task = await _db.Tasks
+ .Include(t => t.Project)
+ .FirstOrDefaultAsync(t => t.Id == dto.TaskId && t.IsDeleted != true && !t.IsArchived && t.Project.IsDeleted != true);
+
+ if (task == null)
+ {
+ return Result.Failure($"Task with ID {dto.TaskId} not found.", 404);
+ }
+
+ var isAdmin = await DataScopeAuthorization.IsAdminScopeAsync(_db, roleId);
+ if (!await CanAccessTaskAsync(task.Id, isAdmin, currentUserId))
+ {
+ return Result.Failure("You do not have access to this task.", 403);
+ }
+
+ if (dto.AssignedTo.HasValue && !await IsTaskMemberAsync(task.ProjectId, dto.AssignedTo.Value))
+ {
+ return Result.Failure("Issue assignee must belong to the selected project.", 400);
+ }
+
+ var issue = new IssueEntity
+ {
+ TaskId = dto.TaskId,
+ Title = dto.Title.Trim(),
+ Description = dto.Description,
+ AssignedTo = dto.AssignedTo,
+ EstimatedHours = dto.EstimatedHours,
+ ActualHours = dto.ActualHours,
+ DelayReason = dto.DelayReason,
+ IsBlocked = dto.IsBlocked,
+ BlockedBy = dto.BlockedBy,
+ EscalationLevel = dto.EscalationLevel,
+ StartDate = ToUtcDate(dto.StartDate),
+ DueDate = ToUtcDate(dto.DueDate),
+ StatusId = dto.StatusId == 0 ? AppTaskStatus.Todo : dto.StatusId,
+ PriorityId = dto.PriorityId == 0 ? TaskPriority.Medium : dto.PriorityId,
+ CreatedAt = DateTime.UtcNow,
+ CreatedBy = currentUserId,
+ IsDeleted = false
+ };
+
+ _db.Issues.Add(issue);
+ await _db.SaveChangesAsync();
+
+ var result = await GetIssueByIdAsync(issue.Id, roleId, currentUserId);
+ return Result.Success(result!, 201);
+ }
+
+ public async Task UpdateIssueAsync(long id, UpdateIssueDto dto, long roleId, long currentUserId)
+ {
+ if (string.IsNullOrWhiteSpace(dto.Title))
+ {
+ return Result.Failure("Issue title is required.", 400);
+ }
+
+ var issue = await _db.Issues
+ .Include(i => i.Task)
+ .ThenInclude(t => t.Project)
+ .FirstOrDefaultAsync(i => i.Id == id && i.IsDeleted != true);
+
+ if (issue == null)
+ {
+ return Result.Failure($"Issue with ID {id} not found.", 404);
+ }
+
+ var isAdmin = await DataScopeAuthorization.IsAdminScopeAsync(_db, roleId);
+ if (!await CanAccessTaskAsync(issue.TaskId, isAdmin, currentUserId))
+ {
+ return Result.Failure("You do not have access to this task.", 403);
+ }
+
+ if (dto.AssignedTo.HasValue && !await IsTaskMemberAsync(issue.Task.ProjectId, dto.AssignedTo.Value))
+ {
+ return Result.Failure("Issue assignee must belong to the selected project.", 400);
+ }
+
+ issue.Title = dto.Title.Trim();
+ issue.Description = dto.Description;
+ issue.AssignedTo = dto.AssignedTo;
+ issue.EstimatedHours = dto.EstimatedHours;
+ issue.ActualHours = dto.ActualHours;
+ issue.DelayReason = dto.DelayReason;
+ issue.IsBlocked = dto.IsBlocked;
+ issue.BlockedBy = dto.BlockedBy;
+ issue.EscalationLevel = dto.EscalationLevel;
+ issue.StartDate = ToUtcDate(dto.StartDate);
+ issue.DueDate = ToUtcDate(dto.DueDate);
+ issue.StatusId = dto.StatusId;
+ issue.PriorityId = dto.PriorityId;
+ issue.UpdatedAt = DateTime.UtcNow;
+ issue.UpdatedBy = currentUserId;
+
+ _db.Issues.Update(issue);
+ await _db.SaveChangesAsync();
+ return Result.Success(200);
+ }
+
+ public async Task SoftDeleteIssueAsync(long id, long roleId, long currentUserId)
+ {
+ var issue = await _db.Issues
+ .Include(i => i.Task)
+ .ThenInclude(t => t.Project)
+ .FirstOrDefaultAsync(i => i.Id == id && i.IsDeleted != true);
+
+ if (issue == null)
+ {
+ return Result.Failure($"Issue with ID {id} not found.", 404);
+ }
+
+ var isAdmin = await DataScopeAuthorization.IsAdminScopeAsync(_db, roleId);
+ if (!await CanAccessTaskAsync(issue.TaskId, isAdmin, currentUserId))
+ {
+ return Result.Failure("You do not have access to this task.", 403);
+ }
+
+ issue.IsDeleted = true;
+ issue.UpdatedAt = DateTime.UtcNow;
+ issue.UpdatedBy = currentUserId;
+
+ _db.Issues.Update(issue);
+ await _db.SaveChangesAsync();
+ return Result.Success(200);
+ }
+
+ private IQueryable BuildAccessibleIssueQuery(bool isAdmin, bool isManager, long currentUserId)
+ {
+ var query = _db.Issues
+ .Where(i => i.IsDeleted != true && i.Task.IsDeleted != true && i.Task.Project.IsDeleted != true);
+
+ if (isAdmin)
+ {
+ return query;
+ }
+
+ if (isManager)
+ {
+ return query.Where(i =>
+ i.AssignedTo == currentUserId ||
+ i.CreatedBy == currentUserId ||
+ i.Task.Project.ProjectMembers.Any(pm => pm.UserId == currentUserId));
+ }
+
+ return query.Where(i =>
+ i.AssignedTo == currentUserId ||
+ i.CreatedBy == currentUserId);
+ }
+
+ private async Task CanAccessTaskAsync(long taskId, bool isAdmin, long currentUserId)
+ {
+ if (isAdmin)
+ {
+ return await _db.Tasks.AnyAsync(t => t.Id == taskId && t.IsDeleted != true && !t.IsArchived && t.Project.IsDeleted != true);
+ }
+
+ return await _db.Tasks.AnyAsync(t =>
+ t.Id == taskId &&
+ t.IsDeleted != true &&
+ !t.IsArchived &&
+ t.Project.IsDeleted != true &&
+ (t.AssignedTo == currentUserId ||
+ t.CreatedBy == currentUserId ||
+ t.Project.ProjectMembers.Any(pm => pm.UserId == currentUserId)));
+ }
+
+ private async Task IsTaskMemberAsync(long projectId, long userId)
+ {
+ return await _db.ProjectMembers.AnyAsync(pm => pm.ProjectId == projectId && pm.UserId == userId);
+ }
+
+ private static System.Linq.Expressions.Expression> ToDtoProjection()
+ {
+ return i => new IssueDto
+ {
+ Id = i.Id,
+ TaskId = i.TaskId,
+ TaskTitle = i.Task.Title,
+ ProjectId = i.Task.ProjectId,
+ ProjectName = i.Task.Project.Name,
+ Title = i.Title,
+ Description = i.Description,
+ AssignedTo = i.AssignedTo,
+ AssignedToName = i.AssignedToNavigation != null
+ ? i.AssignedToNavigation.FirstName + " " + i.AssignedToNavigation.LastName
+ : null,
+ EstimatedHours = i.EstimatedHours,
+ ActualHours = i.ActualHours,
+ DelayReason = i.DelayReason,
+ IsBlocked = i.IsBlocked,
+ BlockedBy = i.BlockedBy,
+ EscalationLevel = i.EscalationLevel,
+ StartDate = i.StartDate,
+ DueDate = i.DueDate,
+ StatusId = i.StatusId,
+ PriorityId = i.PriorityId,
+ CreatedAt = i.CreatedAt ?? DateTime.UtcNow,
+ UpdatedAt = i.UpdatedAt
+ };
+ }
+ }
+}
diff --git a/TaskTrackingSystem.WebApi/Features/Menu/MenuController.cs b/TaskTrackingSystem.WebApi/Features/Menu/MenuController.cs
new file mode 100644
index 0000000000000000000000000000000000000000..00df521a9ea8a0cb07a3ce561bbd13bbcf9df812
--- /dev/null
+++ b/TaskTrackingSystem.WebApi/Features/Menu/MenuController.cs
@@ -0,0 +1,86 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Authorization;
+using System.Collections.Generic;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using TaskTrackingSystem.Shared.Models.Menu;
+using TaskTrackingSystem.WebApi.Infrastructure;
+
+namespace TaskTrackingSystem.WebApi.Features.Menu
+{
+ [Route("api/[controller]")]
+ [ApiController]
+ [Authorize]
+ public class MenuController : ControllerBase
+ {
+ private readonly MenuService _menuService;
+ private readonly PermissionAuthorizationService _permissionAuthorizationService;
+
+ public MenuController(MenuService menuService, PermissionAuthorizationService permissionAuthorizationService)
+ {
+ _menuService = menuService;
+ _permissionAuthorizationService = permissionAuthorizationService;
+ }
+
+ [HttpGet]
+ [HttpGet("tree")]
+ public async Task>> GetMenuTree()
+ {
+ var roleId = User.GetRoleId();
+ if (roleId > 0)
+ {
+ var menuTree = await _menuService.GetMenuTreeByRoleIdAsync(roleId);
+ return Ok(menuTree);
+ }
+
+ var roleName = User.GetRoleName();
+ if (string.IsNullOrWhiteSpace(roleName))
+ {
+ return Forbid();
+ }
+
+ var menuTreeByName = await _menuService.GetMenuTreeByRoleNameAsync(roleName);
+ return Ok(menuTreeByName);
+ }
+
+ [HttpGet("all")]
+ [HttpGet("access-items")]
+ public async Task>> GetAllAccessItems()
+ {
+ var canCreate = await _permissionAuthorizationService.CanAccessAsync(User, "api/Role", "Create");
+ var canUpdate = await _permissionAuthorizationService.CanAccessAsync(User, "api/Role", "Update");
+ if (!canCreate && !canUpdate)
+ {
+ return Forbid();
+ }
+
+ var menus = await _menuService.GetAllAccessItemsAsync();
+ return Ok(menus);
+ }
+
+ [HttpGet("access-codes")]
+ public async Task>> GetCurrentAccessCodes()
+ {
+ var roleId = User.GetRoleId();
+ var roleName = User.GetRoleName() ?? string.Empty;
+ var codes = await _menuService.GetCurrentAccessCodesAsync(roleId, roleName);
+ return Ok(codes);
+ }
+
+ ///
+ /// Returns a lightweight version string for the current user's role permissions.
+ /// Clients poll this to detect when an admin has changed permissions without fetching the full menu list.
+ ///
+ [HttpGet("version")]
+ public async Task> GetMenuVersion()
+ {
+ var roleId = User.GetRoleId();
+ var roleName = User.GetRoleName() ?? string.Empty;
+ var version = await _menuService.GetMenuVersionAsync(roleId, roleName);
+ return Ok(version);
+ }
+ }
+}
+
+
+
diff --git a/TaskTrackingSystem.WebApi/Features/Menu/MenuService.cs b/TaskTrackingSystem.WebApi/Features/Menu/MenuService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..430d2281c0a2cbecea05cb89b3c78e7daba63d0d
--- /dev/null
+++ b/TaskTrackingSystem.WebApi/Features/Menu/MenuService.cs
@@ -0,0 +1,336 @@
+using Microsoft.EntityFrameworkCore;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using TaskTrackingSystem.Database.AppDbContextModels;
+using TaskTrackingSystem.Shared.Models.Menu;
+using MenuEntity = TaskTrackingSystem.Database.AppDbContextModels.Menu;
+
+namespace TaskTrackingSystem.WebApi.Features.Menu
+{
+ public class MenuService
+ {
+ private readonly AppDbContext _db;
+
+ public MenuService(AppDbContext db)
+ {
+ _db = db;
+ }
+
+ public async Task> GetMenuTreeByRoleIdAsync(long roleId)
+ {
+ var role = await _db.Roles.FirstOrDefaultAsync(r => r.Id == roleId && r.IsDeleted != true);
+ if (role == null)
+ {
+ return new List();
+ }
+
+ return await GetMenuTreeForRoleAsync(role.Id);
+ }
+
+ public async Task> GetMenuTreeByRoleNameAsync(string roleName)
+ {
+ if (string.IsNullOrWhiteSpace(roleName))
+ {
+ return new List();
+ }
+
+ var role = await _db.Roles.FirstOrDefaultAsync(r => r.Name == roleName && r.IsDeleted != true);
+ if (role == null)
+ {
+ return new List();
+ }
+
+ return await GetMenuTreeForRoleAsync(role.Id);
+ }
+
+ private async Task> GetMenuTreeForRoleAsync(long roleId)
+ {
+ var visibleMenus = await _db.Menus
+ .Where(m => !m.IsDeleted && m.Visible)
+ .OrderBy(m => m.OrderNo)
+ .ToListAsync();
+
+ var assignedMenuIds = await _db.RoleMenus
+ .Where(rm => rm.RoleId == roleId && !rm.IsDeleted)
+ .Select(rm => rm.MenuId)
+ .ToListAsync();
+
+ var assignedPermissionMenuIds = await _db.RolePermissions
+ .Where(rp => rp.RoleId == roleId && !rp.IsDeleted)
+ .Select(rp => rp.Permission.MenuId)
+ .ToListAsync();
+
+ var allowedMenuIds = assignedMenuIds
+ .Concat(assignedPermissionMenuIds)
+ .Distinct()
+ .ToHashSet();
+
+ if (allowedMenuIds.Count == 0)
+ {
+ return new List();
+ }
+
+ var visibleMenuLookup = visibleMenus.ToDictionary(m => m.MenuId);
+ var expandedMenuIds = ExpandWithAncestors(allowedMenuIds, visibleMenuLookup);
+
+ var filteredMenus = visibleMenus
+ .Where(m => expandedMenuIds.Contains(m.MenuId))
+ .OrderBy(m => m.OrderNo)
+ .ToList();
+
+ return BuildHierarchy(filteredMenus);
+ }
+
+ private static List BuildHierarchy(List menus)
+ {
+ if (menus.Count == 0)
+ {
+ return new List();
+ }
+
+ var menuLookup = menus.ToDictionary(m => m.MenuId);
+ var dtoLookup = menus.ToDictionary(
+ m => m.MenuId,
+ m => new MenuDto
+ {
+ MenuCode = m.MenuCode,
+ ParentCode = GetParentCode(m, menuLookup),
+ MenuName = m.MenuName,
+ MenuUrl = m.MenuUrl,
+ OrderNo = m.OrderNo,
+ Icon = m.Icon
+ });
+
+ foreach (var dto in dtoLookup.Values)
+ {
+ dto.SubMenus = new List();
+ }
+
+ var roots = new List();
+
+ foreach (var menu in menus.OrderBy(m => m.OrderNo).ThenBy(m => m.MenuName, StringComparer.OrdinalIgnoreCase))
+ {
+ var dto = dtoLookup[menu.MenuId];
+ var parentId = menu.ParentMenuId;
+
+ if (!parentId.HasValue || !dtoLookup.TryGetValue(parentId.Value, out var parentDto))
+ {
+ roots.Add(dto);
+ continue;
+ }
+
+ parentDto.SubMenus.Add(dto);
+ }
+
+ SortMenuTree(roots);
+ return roots;
+ }
+
+ private static string GetParentCode(MenuEntity menu, IReadOnlyDictionary menuLookup)
+ {
+ if (!menu.ParentMenuId.HasValue)
+ {
+ return string.Empty;
+ }
+
+ return menuLookup.TryGetValue(menu.ParentMenuId.Value, out var parent)
+ ? parent.MenuCode
+ : string.Empty;
+ }
+
+ private static HashSet ExpandWithAncestors(IEnumerable menuIds, IReadOnlyDictionary menuLookup)
+ {
+ var expanded = new HashSet();
+
+ foreach (var menuId in menuIds)
+ {
+ var currentId = menuId;
+
+ while (currentId > 0 && expanded.Add(currentId))
+ {
+ if (!menuLookup.TryGetValue(currentId, out var currentMenu) || !currentMenu.ParentMenuId.HasValue)
+ {
+ break;
+ }
+
+ currentId = currentMenu.ParentMenuId.Value;
+ }
+ }
+
+ return expanded;
+ }
+
+ private static void SortMenuTree(IList menus)
+ {
+ var ordered = menus
+ .OrderBy(m => m.OrderNo)
+ .ThenBy(m => m.MenuName, StringComparer.OrdinalIgnoreCase)
+ .ToList();
+
+ menus.Clear();
+
+ foreach (var menu in ordered)
+ {
+ if (menu.SubMenus.Count > 0)
+ {
+ SortMenuTree(menu.SubMenus);
+ }
+
+ menus.Add(menu);
+ }
+ }
+
+ public async Task GetMenuVersionAsync(long roleId, string roleName)
+ {
+ if (roleId <= 0 && !string.IsNullOrWhiteSpace(roleName))
+ {
+ var role = await _db.Roles.FirstOrDefaultAsync(r => r.Name == roleName && r.IsDeleted != true);
+ if (role != null)
+ {
+ roleId = role.Id;
+ }
+ }
+
+ if (roleId <= 0)
+ {
+ return "0";
+ }
+
+ var latestRoleMenuChange = await _db.RoleMenus
+ .Where(rm => rm.RoleId == roleId && !rm.IsDeleted)
+ .Select(rm => (DateTime?)(rm.UpdatedAt ?? rm.CreatedAt))
+ .MaxAsync();
+
+ var latestPermissionChange = await _db.RolePermissions
+ .Where(rp => rp.RoleId == roleId && !rp.IsDeleted)
+ .Select(rp => (DateTime?)(rp.UpdatedAt ?? rp.CreatedAt))
+ .MaxAsync();
+
+ var latestMenuMetadataChange = await _db.Menus
+ .Select(m => (DateTime?)(m.UpdatedAt ?? m.CreatedAt))
+ .MaxAsync();
+
+ var latest = latestRoleMenuChange ?? latestPermissionChange;
+ if (latestPermissionChange.HasValue && (!latest.HasValue || latestPermissionChange.Value > latest.Value))
+ {
+ latest = latestPermissionChange;
+ }
+ if (latestMenuMetadataChange.HasValue && (!latest.HasValue || latestMenuMetadataChange.Value > latest.Value))
+ {
+ latest = latestMenuMetadataChange;
+ }
+
+ return latest.HasValue ? latest.Value.Ticks.ToString() : "0";
+ }
+
+ public async Task> GetAllAccessItemsAsync()
+ {
+ var menus = await _db.Menus
+ .Where(m => !m.IsDeleted && m.MenuCode != "ROLE_LAYOUTS" && m.MenuName != "Role Layouts")
+ .OrderBy(m => m.OrderNo)
+ .ToListAsync();
+
+ var permissions = await _db.Permissions
+ .Where(p => !p.IsDeleted)
+ .OrderBy(p => p.OrderNo)
+ .ToListAsync();
+
+ var menuLookup = menus.ToDictionary(m => m.MenuId);
+ var menuDtoLookup = menus
+ .Select(m => new
+ {
+ m.MenuId,
+ Dto = new AccessMenuDto
+ {
+ MenuCode = m.MenuCode,
+ ParentCode = GetParentCode(m, menuLookup),
+ MenuName = m.MenuName,
+ MenuUrl = m.MenuUrl,
+ OrderNo = m.OrderNo,
+ Icon = m.Icon,
+ Visible = m.Visible
+ }
+ })
+ .ToList();
+
+ var menuDtos = menuDtoLookup.Select(x => x.Dto).ToList();
+ var dtoLookup = menuDtoLookup.ToDictionary(x => x.MenuId, x => x.Dto);
+
+ foreach (var permissionGroup in permissions.GroupBy(p => p.MenuId))
+ {
+ if (!dtoLookup.TryGetValue(permissionGroup.Key, out var dto))
+ {
+ continue;
+ }
+
+ foreach (var permission in permissionGroup.OrderBy(p => p.OrderNo).ThenBy(p => p.ActionName, StringComparer.OrdinalIgnoreCase))
+ {
+ dto.Permissions.Add(new AccessPermissionDto
+ {
+ PermissionId = permission.PermissionId.ToString(),
+ PermissionCode = permission.PermissionCode,
+ ParentMenuCode = menuLookup.TryGetValue(permission.MenuId, out var parentMenu) ? parentMenu.MenuCode : string.Empty,
+ ActionName = permission.ActionName,
+ ApiName = permission.ApiName,
+ Visible = permission.Visible,
+ OrderNo = permission.OrderNo
+ });
+ }
+ }
+
+ return menuDtos;
+ }
+
+ public async Task> GetCurrentAccessCodesAsync(long roleId, string roleName)
+ {
+ if (roleId <= 0 && !string.IsNullOrWhiteSpace(roleName))
+ {
+ var role = await _db.Roles.FirstOrDefaultAsync(r => r.Name == roleName && r.IsDeleted != true);
+ if (role != null)
+ {
+ roleId = role.Id;
+ }
+ }
+
+ if (roleId <= 0)
+ {
+ return new List();
+ }
+
+ var validMenuCodes = await _db.Menus
+ .Where(m => !m.IsDeleted)
+ .Select(m => m.MenuCode)
+ .ToListAsync();
+
+ var validPermissionCodes = await _db.Permissions
+ .Where(p => !p.IsDeleted)
+ .Select(p => p.PermissionCode)
+ .ToListAsync();
+
+ var validCodes = validMenuCodes
+ .Concat(validPermissionCodes)
+ .ToHashSet(StringComparer.OrdinalIgnoreCase);
+
+ var menuCodes = await _db.RoleMenus
+ .Where(rm => rm.RoleId == roleId && !rm.IsDeleted)
+ .Select(rm => rm.Menu.MenuCode)
+ .ToListAsync();
+
+ var permissionCodes = await _db.RolePermissions
+ .Where(rp => rp.RoleId == roleId && !rp.IsDeleted)
+ .Select(rp => rp.Permission.PermissionCode)
+ .ToListAsync();
+
+ return menuCodes
+ .Concat(permissionCodes)
+ .Where(code => validCodes.Contains(code))
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .ToList();
+ }
+ }
+}
+
+
+
diff --git a/TaskTrackingSystem.WebApi/Features/Notification/FirebaseNotificationService.cs b/TaskTrackingSystem.WebApi/Features/Notification/FirebaseNotificationService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9946c7442239726f88312698871c371f0f9673d4
--- /dev/null
+++ b/TaskTrackingSystem.WebApi/Features/Notification/FirebaseNotificationService.cs
@@ -0,0 +1,474 @@
+using System.Security.Claims;
+using System.Security.Cryptography;
+using System.Text.RegularExpressions;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
+using TaskTrackingSystem.Database.AppDbContextModels;
+using TaskTrackingSystem.Shared.Models.Notification;
+using TaskTrackingSystem.Shared.Enums;
+using DbNotification = TaskTrackingSystem.Database.AppDbContextModels.Notification;
+
+namespace TaskTrackingSystem.WebApi.Features.Notification;
+
+public class FirebaseNotificationService
+{
+ private static readonly Regex MentionRegex = new(@"@(?[A-Za-z0-9._]+)", RegexOptions.Compiled | RegexOptions.CultureInvariant);
+ private static readonly SemaphoreSlim AccessTokenLock = new(1, 1);
+ private static string? CachedAccessToken;
+ private static DateTimeOffset CachedAccessTokenExpiresAt;
+
+ private readonly AppDbContext _db;
+ private readonly IHttpClientFactory _httpClientFactory;
+ private readonly NotificationRealtimeService _realtimeService;
+ private readonly string _serviceAccountPath;
+ private readonly bool _pushEnabled;
+
+ public FirebaseNotificationService(
+ AppDbContext db,
+ IHttpClientFactory httpClientFactory,
+ NotificationRealtimeService realtimeService,
+ IHostEnvironment environment,
+ IConfiguration configuration)
+ {
+ _db = db;
+ _httpClientFactory = httpClientFactory;
+ _realtimeService = realtimeService;
+
+ var configuredPath = configuration["Firebase:ServiceAccountPath"] ?? "Firebase/ttsfirebasekey.json";
+ _serviceAccountPath = Path.IsPathRooted(configuredPath)
+ ? configuredPath
+ : Path.Combine(environment.ContentRootPath, configuredPath);
+ _pushEnabled = configuration.GetValue("Firebase:PushEnabled", false);
+ }
+
+ public global::System.Threading.Tasks.Task NotifyTaskAssignedAsync(Database.AppDbContextModels.Task task, long senderId)
+ {
+ if (!task.AssignedTo.HasValue)
+ {
+ return global::System.Threading.Tasks.Task.CompletedTask;
+ }
+
+ var title = "Task assigned";
+ var body = $"You have been assigned a new task: {task.Title}";
+
+ return SendAsync(
+ recipientIds: new[] { task.AssignedTo.Value },
+ senderId: senderId,
+ notificationType: NotificationType.TaskAssigned,
+ sourceType: "task",
+ sourceId: task.Id,
+ title: title,
+ body: body);
+ }
+
+ public async global::System.Threading.Tasks.Task NotifyTaskStatusChangedAsync(Database.AppDbContextModels.Task task, long senderId, AppTaskStatus oldStatusId, AppTaskStatus newStatusId)
+ {
+ var recipientIds = BuildTaskAudience(task, senderId);
+ if (recipientIds.Count == 0)
+ {
+ return;
+ }
+
+ var title = "Task status changed";
+ var body = $"Task '{task.Title}' changed from {GetStatusLabel(oldStatusId)} to {GetStatusLabel(newStatusId)}";
+
+ await SendAsync(
+ recipientIds,
+ senderId,
+ NotificationType.StatusChanged,
+ "task",
+ task.Id,
+ title,
+ body);
+ }
+
+ public async global::System.Threading.Tasks.Task NotifyProjectAssignedAsync(Database.AppDbContextModels.Project project, IEnumerable recipientIds, long senderId)
+ {
+ var uniqueRecipientIds = recipientIds
+ .Where(id => id > 0 && id != senderId)
+ .Distinct()
+ .ToList();
+
+ if (uniqueRecipientIds.Count == 0)
+ {
+ return;
+ }
+
+ var title = "Project assigned";
+ var body = $"You have been assigned to project '{project.Name}'";
+
+ await SendAsync(
+ uniqueRecipientIds,
+ senderId,
+ NotificationType.ProjectUpdated,
+ "project",
+ project.Id,
+ title,
+ body);
+ }
+
+ public async global::System.Threading.Tasks.Task NotifyCommentAddedAsync(Database.AppDbContextModels.Task task, long senderId, string actorName, string message)
+ {
+ var recipientIds = BuildTaskAudience(task, senderId);
+ var mentionedIds = await FindMentionedUserIdsAsync(task.ProjectId, senderId, message);
+ if (mentionedIds.Count > 0)
+ {
+ recipientIds = recipientIds
+ .Except(mentionedIds)
+ .ToList();
+ }
+
+ if (recipientIds.Count == 0)
+ {
+ return;
+ }
+
+ var title = "New comment";
+ var body = $"{actorName} commented on task '{task.Title}'";
+
+ await SendAsync(
+ recipientIds,
+ senderId,
+ NotificationType.CommentAdded,
+ "task",
+ task.Id,
+ title,
+ body);
+ }
+
+ public async global::System.Threading.Tasks.Task NotifyMentionedAsync(Database.AppDbContextModels.Task task, long senderId, string actorName, string message)
+ {
+ var recipientIds = await FindMentionedUserIdsAsync(task.ProjectId, senderId, message);
+ if (recipientIds.Count == 0)
+ {
+ return;
+ }
+
+ var title = "You were mentioned";
+ var body = $"{actorName} mentioned you in task '{task.Title}'";
+
+ await SendAsync(
+ recipientIds,
+ senderId,
+ NotificationType.Mention,
+ "task",
+ task.Id,
+ title,
+ body);
+ }
+
+ private List BuildTaskAudience(Database.AppDbContextModels.Task task, long senderId)
+ {
+ var audience = new HashSet();
+
+ if (task.AssignedTo.HasValue && task.AssignedTo.Value != senderId)
+ {
+ audience.Add(task.AssignedTo.Value);
+ }
+
+ if (task.AssignedBy.HasValue && task.AssignedBy.Value != senderId)
+ {
+ audience.Add(task.AssignedBy.Value);
+ }
+
+ if (task.CreatedBy.HasValue && task.CreatedBy.Value != senderId)
+ {
+ audience.Add(task.CreatedBy.Value);
+ }
+
+ return audience.ToList();
+ }
+
+ private async global::System.Threading.Tasks.Task> FindMentionedUserIdsAsync(long projectId, long senderId, string message)
+ {
+ var usernames = MentionRegex.Matches(message)
+ .Select(match => match.Groups["username"].Value)
+ .Where(username => !string.IsNullOrWhiteSpace(username))
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .ToList();
+
+ if (usernames.Count == 0)
+ {
+ return new List();
+ }
+
+ var projectMemberIds = await _db.ProjectMembers
+ .Where(pm => pm.ProjectId == projectId)
+ .Select(pm => pm.UserId)
+ .ToListAsync();
+
+ if (projectMemberIds.Count == 0)
+ {
+ return new List();
+ }
+
+ return await _db.Users
+ .Where(u => u.Id != senderId && projectMemberIds.Contains(u.Id) && usernames.Contains(u.Username))
+ .Select(u => u.Id)
+ .Distinct()
+ .ToListAsync();
+ }
+
+ private async global::System.Threading.Tasks.Task SendAsync(
+ IEnumerable recipientIds,
+ long senderId,
+ NotificationType notificationType,
+ string sourceType,
+ long sourceId,
+ string title,
+ string body)
+ {
+ var uniqueRecipientIds = recipientIds.Distinct().ToList();
+ if (uniqueRecipientIds.Count == 0)
+ {
+ return;
+ }
+
+ var notifications = uniqueRecipientIds.Select(recipientId => new DbNotification
+ {
+ RecipientId = recipientId,
+ SenderId = senderId > 0 ? senderId : null,
+ NotificationType = (byte)notificationType,
+ Title = title,
+ Body = body,
+ SourceType = sourceType,
+ SourceId = sourceId,
+ IsRead = false,
+ CreatedAt = DateTime.UtcNow
+ }).ToList();
+
+ _db.Notifications.AddRange(notifications);
+ await _db.SaveChangesAsync();
+
+ var senderName = senderId > 0
+ ? await _db.Users
+ .Where(u => u.Id == senderId)
+ .Select(u => $"{u.FirstName} {u.LastName}")
+ .FirstOrDefaultAsync()
+ : null;
+
+ var unreadCounts = await _db.Notifications
+ .Where(n => uniqueRecipientIds.Contains(n.RecipientId) && !n.IsRead)
+ .GroupBy(n => n.RecipientId)
+ .Select(g => new { RecipientId = g.Key, Count = g.Count() })
+ .ToListAsync();
+ var unreadCountLookup = unreadCounts.ToDictionary(x => x.RecipientId, x => x.Count);
+
+ foreach (var notification in notifications)
+ {
+ var dto = new NotificationDto
+ {
+ Id = notification.Id,
+ Title = notification.Title,
+ Body = notification.Body,
+ NotificationType = notification.NotificationType,
+ SourceType = notification.SourceType,
+ SourceId = notification.SourceId,
+ TargetUrl = NotificationNavigation.BuildTargetUrl(notification.SourceType, notification.SourceId, notification.NotificationType),
+ IsRead = notification.IsRead,
+ CreatedAt = notification.CreatedAt,
+ SenderName = senderName
+ };
+
+ var recipientUnreadCount = unreadCountLookup.TryGetValue(notification.RecipientId, out var unreadCount)
+ ? unreadCount
+ : 0;
+
+ await _realtimeService.SendCreatedAsync(notification.RecipientId, dto, recipientUnreadCount);
+ }
+
+ if (!_pushEnabled)
+ {
+ return;
+ }
+
+ var tokens = await _db.UserDevices
+ .Where(d => uniqueRecipientIds.Contains(d.UserId))
+ .Select(d => d.FcmToken)
+ .Distinct()
+ .ToListAsync();
+
+ foreach (var token in tokens)
+ {
+ try
+ {
+ await SendPushAsync(token, title, body, sourceType, sourceId, notificationType);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[Firebase] Push notification skipped: {ex.Message}");
+ }
+ }
+ }
+
+ private async global::System.Threading.Tasks.Task SendPushAsync(
+ string token,
+ string title,
+ string body,
+ string sourceType,
+ long sourceId,
+ NotificationType notificationType)
+ {
+ var serviceAccount = await LoadServiceAccountAsync();
+ var accessToken = await GetAccessTokenAsync(serviceAccount);
+
+ var client = _httpClientFactory.CreateClient();
+ var request = new HttpRequestMessage(HttpMethod.Post, $"https://fcm.googleapis.com/v1/projects/{serviceAccount.ProjectId}/messages:send");
+ request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
+
+ var payload = new
+ {
+ message = new
+ {
+ token,
+ notification = new
+ {
+ title,
+ body
+ },
+ data = new Dictionary
+ {
+ ["sourceType"] = sourceType,
+ ["sourceId"] = sourceId.ToString(),
+ ["notificationType"] = ((byte)notificationType).ToString(),
+ ["targetUrl"] = NotificationNavigation.BuildTargetUrl(sourceType, sourceId, (byte)notificationType)
+ }
+ }
+ };
+
+ request.Content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
+ using var response = await client.SendAsync(request);
+ if (!response.IsSuccessStatusCode)
+ {
+ var error = await response.Content.ReadAsStringAsync();
+ Console.WriteLine($"[Firebase] Failed to send push: {(int)response.StatusCode} {response.ReasonPhrase}. {error}");
+ }
+ }
+
+ private async global::System.Threading.Tasks.Task GetAccessTokenAsync(FirebaseServiceAccount serviceAccount)
+ {
+ if (!string.IsNullOrWhiteSpace(CachedAccessToken) && CachedAccessTokenExpiresAt > DateTimeOffset.UtcNow.AddMinutes(1))
+ {
+ return CachedAccessToken!;
+ }
+
+ await AccessTokenLock.WaitAsync();
+ try
+ {
+ if (!string.IsNullOrWhiteSpace(CachedAccessToken) && CachedAccessTokenExpiresAt > DateTimeOffset.UtcNow.AddMinutes(1))
+ {
+ return CachedAccessToken!;
+ }
+
+ var iat = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
+ var exp = iat + 3600;
+
+ var headerJson = JsonSerializer.Serialize(new { alg = "RS256", typ = "JWT" });
+ var claimJson = JsonSerializer.Serialize(new
+ {
+ iss = serviceAccount.ClientEmail,
+ scope = "https://www.googleapis.com/auth/firebase.messaging",
+ aud = "https://oauth2.googleapis.com/token",
+ iat,
+ exp
+ });
+
+ var unsignedJwt = $"{Base64UrlEncode(Encoding.UTF8.GetBytes(headerJson))}.{Base64UrlEncode(Encoding.UTF8.GetBytes(claimJson))}";
+ var signature = SignJwt(unsignedJwt, serviceAccount.PrivateKey);
+ var assertion = $"{unsignedJwt}.{Base64UrlEncode(signature)}";
+
+ var client = _httpClientFactory.CreateClient();
+ var form = new FormUrlEncodedContent(new[]
+ {
+ new KeyValuePair("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"),
+ new KeyValuePair("assertion", assertion)
+ });
+
+ using var response = await client.PostAsync("https://oauth2.googleapis.com/token", form);
+ response.EnsureSuccessStatusCode();
+
+ var json = await response.Content.ReadAsStringAsync();
+ using var doc = JsonDocument.Parse(json);
+ var token = doc.RootElement.GetProperty("access_token").GetString();
+ var expiresIn = doc.RootElement.GetProperty("expires_in").GetInt32();
+
+ if (string.IsNullOrWhiteSpace(token))
+ {
+ throw new InvalidOperationException("Firebase access token response did not contain an access_token.");
+ }
+
+ CachedAccessToken = token;
+ CachedAccessTokenExpiresAt = DateTimeOffset.UtcNow.AddSeconds(expiresIn);
+
+ return token;
+ }
+ finally
+ {
+ AccessTokenLock.Release();
+ }
+ }
+
+ private async global::System.Threading.Tasks.Task LoadServiceAccountAsync()
+ {
+ var json = await File.ReadAllTextAsync(_serviceAccountPath);
+ var serviceAccount = JsonSerializer.Deserialize(json);
+ if (serviceAccount == null ||
+ string.IsNullOrWhiteSpace(serviceAccount.ProjectId) ||
+ string.IsNullOrWhiteSpace(serviceAccount.ClientEmail) ||
+ string.IsNullOrWhiteSpace(serviceAccount.PrivateKey))
+ {
+ throw new InvalidOperationException("Firebase service account file is missing required fields.");
+ }
+
+ return serviceAccount;
+ }
+
+ private static byte[] SignJwt(string unsignedJwt, string privateKey)
+ {
+ var pem = privateKey
+ .Replace("\\n", "\n", StringComparison.Ordinal)
+ .Replace("-----BEGIN PRIVATE KEY-----", string.Empty, StringComparison.Ordinal)
+ .Replace("-----END PRIVATE KEY-----", string.Empty, StringComparison.Ordinal)
+ .Replace("\r", string.Empty, StringComparison.Ordinal)
+ .Replace("\n", string.Empty, StringComparison.Ordinal)
+ .Trim();
+
+ var keyBytes = Convert.FromBase64String(pem);
+ using var rsa = RSA.Create();
+ rsa.ImportPkcs8PrivateKey(keyBytes, out _);
+
+ return rsa.SignData(Encoding.ASCII.GetBytes(unsignedJwt), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
+ }
+
+ private static string Base64UrlEncode(byte[] input)
+ {
+ return Convert.ToBase64String(input)
+ .TrimEnd('=')
+ .Replace('+', '-')
+ .Replace('/', '_');
+ }
+
+ private static string GetStatusLabel(AppTaskStatus statusId) => statusId switch
+ {
+ AppTaskStatus.Todo => "To Do",
+ AppTaskStatus.InProgress => "In Progress",
+ AppTaskStatus.Done => "Done",
+ _ => $"Status {statusId}"
+ };
+
+ private sealed class FirebaseServiceAccount
+ {
+ [JsonPropertyName("project_id")]
+ public string ProjectId { get; set; } = string.Empty;
+
+ [JsonPropertyName("client_email")]
+ public string ClientEmail { get; set; } = string.Empty;
+
+ [JsonPropertyName("private_key")]
+ public string PrivateKey { get; set; } = string.Empty;
+ }
+}
diff --git a/TaskTrackingSystem.WebApi/Features/Notification/NotificationCleanupHostedService.cs b/TaskTrackingSystem.WebApi/Features/Notification/NotificationCleanupHostedService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ecfe5c6fa673726796bdb67d9bbeb21484a2a146
--- /dev/null
+++ b/TaskTrackingSystem.WebApi/Features/Notification/NotificationCleanupHostedService.cs
@@ -0,0 +1,58 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using TaskTrackingSystem.Database.AppDbContextModels;
+
+namespace TaskTrackingSystem.WebApi.Features.Notification;
+
+public class NotificationCleanupHostedService : BackgroundService
+{
+ private static readonly TimeSpan RunInterval = TimeSpan.FromHours(24);
+ private static readonly TimeSpan RetentionWindow = TimeSpan.FromDays(30);
+
+ private readonly IServiceScopeFactory _scopeFactory;
+ private readonly ILogger _logger;
+
+ public NotificationCleanupHostedService(IServiceScopeFactory scopeFactory, ILogger logger)
+ {
+ _scopeFactory = scopeFactory;
+ _logger = logger;
+ }
+
+ protected override async System.Threading.Tasks.Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ using var timer = new PeriodicTimer(RunInterval);
+
+ await CleanupOnceAsync(stoppingToken);
+
+ while (await timer.WaitForNextTickAsync(stoppingToken))
+ {
+ await CleanupOnceAsync(stoppingToken);
+ }
+ }
+
+ private async System.Threading.Tasks.Task CleanupOnceAsync(CancellationToken cancellationToken)
+ {
+ try
+ {
+ using var scope = _scopeFactory.CreateScope();
+ var db = scope.ServiceProvider.GetRequiredService();
+ var cutoffUtc = DateTime.UtcNow.Subtract(RetentionWindow);
+
+ var deletedCount = await db.Notifications
+ .Where(n => n.IsRead && (!n.CreatedAt.HasValue || n.CreatedAt < cutoffUtc))
+ .ExecuteDeleteAsync(cancellationToken);
+
+ if (deletedCount > 0)
+ {
+ _logger.LogInformation("Notification cleanup removed {DeletedCount} read notifications older than {CutoffUtc}.", deletedCount, cutoffUtc);
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Notification cleanup failed.");
+ }
+ }
+}
diff --git a/TaskTrackingSystem.WebApi/Features/Notification/NotificationController.cs b/TaskTrackingSystem.WebApi/Features/Notification/NotificationController.cs
new file mode 100644
index 0000000000000000000000000000000000000000..74861119f5faefc2173d7644529d5bcd8971ecde
--- /dev/null
+++ b/TaskTrackingSystem.WebApi/Features/Notification/NotificationController.cs
@@ -0,0 +1,71 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using TaskTrackingSystem.Shared;
+using TaskTrackingSystem.WebApi.Infrastructure;
+
+namespace TaskTrackingSystem.WebApi.Features.Notification;
+
+[ApiController]
+[Authorize]
+[Route("api/[controller]")]
+public class NotificationController : ControllerBase
+{
+ private readonly NotificationService _notificationService;
+
+ public NotificationController(NotificationService notificationService)
+ {
+ _notificationService = notificationService;
+ }
+
+ [HttpGet("mine")]
+ public async Task>> GetMine([FromQuery] int take = 10)
+ {
+ var userId = User.GetUserId();
+ if (userId <= 0)
+ {
+ return Unauthorized();
+ }
+
+ var items = await _notificationService.GetRecentAsync(userId, take);
+ return Ok(items);
+ }
+
+ [HttpGet("unread-count")]
+ public async Task> GetUnreadCount()
+ {
+ var userId = User.GetUserId();
+ if (userId <= 0)
+ {
+ return Unauthorized();
+ }
+
+ var count = await _notificationService.GetUnreadCountAsync(userId);
+ return Ok(count);
+ }
+
+ [HttpPost("{id}/read")]
+ public async Task> MarkRead(long id)
+ {
+ var userId = User.GetUserId();
+ if (userId <= 0)
+ {
+ return Unauthorized(Result.Failure("User is not authenticated.", 401));
+ }
+
+ var result = await _notificationService.MarkReadAsync(userId, id);
+ return StatusCode(result.StatusCode, result);
+ }
+
+ [HttpDelete("read")]
+ public async Task>> DeleteRead([FromQuery] DateTime? before = null)
+ {
+ var userId = User.GetUserId();
+ if (userId <= 0)
+ {
+ return Unauthorized(Result.Failure("User is not authenticated.", 401));
+ }
+
+ var deletedCount = await _notificationService.DeleteReadNotificationsAsync(userId, before);
+ return Ok(Result.Success(deletedCount));
+ }
+}
diff --git a/TaskTrackingSystem.WebApi/Features/Notification/NotificationHub.cs b/TaskTrackingSystem.WebApi/Features/Notification/NotificationHub.cs
new file mode 100644
index 0000000000000000000000000000000000000000..082cf93c52527222c2e6a1b8b69bb302d823b52e
--- /dev/null
+++ b/TaskTrackingSystem.WebApi/Features/Notification/NotificationHub.cs
@@ -0,0 +1,33 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.SignalR;
+using TaskTrackingSystem.WebApi.Infrastructure;
+
+namespace TaskTrackingSystem.WebApi.Features.Notification;
+
+[Authorize]
+public class NotificationHub : Hub
+{
+ public static string GetUserGroupName(long userId) => $"notification-user-{userId}";
+
+ public override async global::System.Threading.Tasks.Task OnConnectedAsync()
+ {
+ var userId = Context.User?.GetUserId() ?? 0;
+ if (userId > 0)
+ {
+ await Groups.AddToGroupAsync(Context.ConnectionId, GetUserGroupName(userId));
+ }
+
+ await base.OnConnectedAsync();
+ }
+
+ public override async global::System.Threading.Tasks.Task OnDisconnectedAsync(Exception? exception)
+ {
+ var userId = Context.User?.GetUserId() ?? 0;
+ if (userId > 0)
+ {
+ await Groups.RemoveFromGroupAsync(Context.ConnectionId, GetUserGroupName(userId));
+ }
+
+ await base.OnDisconnectedAsync(exception);
+ }
+}
diff --git a/TaskTrackingSystem.WebApi/Features/Notification/NotificationRealtimeService.cs b/TaskTrackingSystem.WebApi/Features/Notification/NotificationRealtimeService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a9590df1c49d18c75e1495ac27c767991f461c31
--- /dev/null
+++ b/TaskTrackingSystem.WebApi/Features/Notification/NotificationRealtimeService.cs
@@ -0,0 +1,35 @@
+using Microsoft.AspNetCore.SignalR;
+using TaskTrackingSystem.Shared.Models.Notification;
+
+namespace TaskTrackingSystem.WebApi.Features.Notification;
+
+public class NotificationRealtimeService
+{
+ private readonly IHubContext _hubContext;
+
+ public NotificationRealtimeService(IHubContext hubContext)
+ {
+ _hubContext = hubContext;
+ }
+
+ public async global::System.Threading.Tasks.Task SendCreatedAsync(long recipientId, NotificationDto notification, int unreadCount)
+ {
+ var group = _hubContext.Clients.Group(NotificationHub.GetUserGroupName(recipientId));
+ await group.SendAsync("NotificationCreated", notification);
+ await group.SendAsync("UnreadCountChanged", unreadCount);
+ }
+
+ public global::System.Threading.Tasks.Task SendUnreadCountAsync(long recipientId, int unreadCount)
+ {
+ return _hubContext.Clients
+ .Group(NotificationHub.GetUserGroupName(recipientId))
+ .SendAsync("UnreadCountChanged", unreadCount);
+ }
+
+ public global::System.Threading.Tasks.Task SendReadAsync(long recipientId, long notificationId, int unreadCount)
+ {
+ return _hubContext.Clients
+ .Group(NotificationHub.GetUserGroupName(recipientId))
+ .SendAsync("NotificationRead", notificationId, unreadCount);
+ }
+}
diff --git a/TaskTrackingSystem.WebApi/Features/Notification/NotificationService.cs b/TaskTrackingSystem.WebApi/Features/Notification/NotificationService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..59d51b1ee6011f4b1f42a2d4fffb1b4c4a6347a9
--- /dev/null
+++ b/TaskTrackingSystem.WebApi/Features/Notification/NotificationService.cs
@@ -0,0 +1,92 @@
+using Microsoft.EntityFrameworkCore;
+using TaskTrackingSystem.Database.AppDbContextModels;
+using TaskTrackingSystem.Shared;
+using TaskTrackingSystem.Shared.Models.Notification;
+using DbNotification = TaskTrackingSystem.Database.AppDbContextModels.Notification;
+
+namespace TaskTrackingSystem.WebApi.Features.Notification;
+
+public class NotificationService
+{
+ private readonly AppDbContext _db;
+ private readonly NotificationRealtimeService _realtimeService;
+
+ public NotificationService(AppDbContext db, NotificationRealtimeService realtimeService)
+ {
+ _db = db;
+ _realtimeService = realtimeService;
+ }
+
+ public async Task> GetRecentAsync(long userId, int take = 10)
+ {
+ var items = await (
+ from n in _db.Notifications
+ where n.RecipientId == userId
+ join sender in _db.Users on n.SenderId equals sender.Id into senderGroup
+ from sender in senderGroup.DefaultIfEmpty()
+ orderby n.CreatedAt descending
+ select new NotificationDto
+ {
+ Id = n.Id,
+ Title = n.Title,
+ Body = n.Body,
+ NotificationType = n.NotificationType,
+ SourceType = n.SourceType,
+ SourceId = n.SourceId,
+ IsRead = n.IsRead,
+ CreatedAt = n.CreatedAt,
+ ReadAt = n.ReadAt,
+ SenderName = sender == null ? null : $"{sender.FirstName} {sender.LastName}"
+ })
+ .Take(take)
+ .ToListAsync();
+
+ foreach (var item in items)
+ {
+ item.TargetUrl = NotificationNavigation.BuildTargetUrl(item.SourceType, item.SourceId, item.NotificationType);
+ }
+
+ return items;
+ }
+
+ public async Task GetUnreadCountAsync(long userId)
+ {
+ return await _db.Notifications.CountAsync(n => n.RecipientId == userId && !n.IsRead);
+ }
+
+ public async Task MarkReadAsync(long userId, long notificationId)
+ {
+ var notification = await _db.Notifications
+ .FirstOrDefaultAsync(n => n.Id == notificationId && n.RecipientId == userId);
+
+ if (notification == null)
+ {
+ return Result.Failure("Notification not found.", 404);
+ }
+
+ if (!notification.IsRead)
+ {
+ notification.IsRead = true;
+ notification.ReadAt = DateTime.UtcNow;
+ await _db.SaveChangesAsync();
+
+ var unreadCount = await GetUnreadCountAsync(userId);
+ await _realtimeService.SendReadAsync(userId, notificationId, unreadCount);
+ }
+
+ return Result.Success();
+ }
+
+ public async Task DeleteReadNotificationsAsync(long userId, DateTime? olderThanUtc = null)
+ {
+ var query = _db.Notifications.Where(n => n.RecipientId == userId && n.IsRead);
+
+ if (olderThanUtc.HasValue)
+ {
+ var cutoff = olderThanUtc.Value;
+ query = query.Where(n => !n.CreatedAt.HasValue || n.CreatedAt < cutoff);
+ }
+
+ return await query.ExecuteDeleteAsync();
+ }
+}
diff --git a/TaskTrackingSystem.WebApi/Features/Project/ProjectController.cs b/TaskTrackingSystem.WebApi/Features/Project/ProjectController.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b096abaabb1f91e5b2f281103bf392c20295e97d
--- /dev/null
+++ b/TaskTrackingSystem.WebApi/Features/Project/ProjectController.cs
@@ -0,0 +1,140 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using TaskTrackingSystem.Shared;
+using TaskTrackingSystem.Shared.Models.User;
+using TaskTrackingSystem.Shared.Models.Task;
+using TaskTrackingSystem.Shared.Models.Project;
+using TaskTrackingSystem.WebApi.Infrastructure;
+
+namespace TaskTrackingSystem.WebApi.Features.Project
+{
+ [Route("api/[controller]")]
+ [ApiController]
+ [Authorize]
+ public class ProjectController : ControllerBase
+ {
+ private readonly ProjectService _projectService;
+ private readonly PermissionAuthorizationService _permissionAuthorizationService;
+
+ public ProjectController(ProjectService projectService, PermissionAuthorizationService permissionAuthorizationService)
+ {
+ _projectService = projectService;
+ _permissionAuthorizationService = permissionAuthorizationService;
+ }
+
+ [HttpGet]
+ [ProducesResponseType(typeof(PagedResult), StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+ public async Task GetProjects(
+ [FromQuery] string? search,
+ [FromQuery] PaginationQuery? paging = null)
+ {
+ if (paging == null || (!paging.Page.HasValue && !paging.Limit.HasValue))
+ {
+ var projects = await _projectService.GetAllProjectsAsync(User.GetRoleId(), User.GetUserId());
+ return Ok(projects);
+ }
+
+ var page = PaginationExtensions.NormalizePage(paging.Page);
+ var limit = PaginationExtensions.NormalizePageSize(paging.Limit ?? 0);
+ var paged = await _projectService.GetPagedProjectsAsync(User.GetRoleId(), User.GetUserId(), search, page, limit);
+ return Ok(paged);
+ }
+
+ [HttpGet("{id}")]
+ public async Task> GetProject(long id)
+ {
+ var project = await _projectService.GetProjectByIdAsync(id, User.GetRoleId(), User.GetUserId());
+ if (project == null)
+ {
+ return NotFound(new { message = $"Project with ID {id} not found." });
+ }
+ return Ok(project);
+ }
+
+ [HttpPost]
+ public async Task>> CreateProject([FromBody] CreateProjectDto createProjectDto)
+ {
+ if (!await _permissionAuthorizationService.CanAccessAsync(User, "api/Project", "Create"))
+ {
+ return StatusCode(403, Result.Failure("You do not have permission to create projects.", 403));
+ }
+
+ long? currentUserId = User.GetUserId();
+ var result = await _projectService.CreateProjectAsync(createProjectDto, currentUserId);
+ return StatusCode(result.StatusCode, result);
+ }
+
+ [HttpPut("{id}")]
+ public async Task> UpdateProject(long id, [FromBody] UpdateProjectDto updateProjectDto)
+ {
+ if (!await _permissionAuthorizationService.CanAccessAsync(User, "api/Project", "Update"))
+ {
+ return StatusCode(403, Result.Failure("You do not have permission to update projects.", 403));
+ }
+
+ var currentUserId = User.GetUserId();
+ var result = await _projectService.UpdateProjectAsync(id, updateProjectDto, User.GetRoleId(), currentUserId);
+ return StatusCode(result.StatusCode, result);
+ }
+
+ [HttpDelete("{id}")]
+ public async Task> DeleteProject(long id)
+ {
+ if (!await _permissionAuthorizationService.CanAccessAsync(User, "api/Project", "Delete"))
+ {
+ return StatusCode(403, Result.Failure("You do not have permission to delete projects.", 403));
+ }
+
+ var result = await _projectService.SoftDeleteProjectAsync(id, User.GetRoleId(), User.GetUserId());
+ return StatusCode(result.StatusCode, result);
+ }
+
+ [HttpGet("{id}/members")]
+ public async Task>>> GetProjectMembers(long id)
+ {
+ var result = await _projectService.GetProjectMembersAsync(id, User.GetRoleId(), User.GetUserId());
+ return StatusCode(result.StatusCode, result);
+ }
+
+ [HttpPost("{id}/members")]
+ public async Task AssignProjectMembers(long id, [FromBody] AssignMembersDto dto)
+ {
+ if (!await _permissionAuthorizationService.CanAccessAsync(User, "api/Project", "Update"))
+ {
+ return StatusCode(403, Result.Failure("You do not have permission to assign project members.", 403));
+ }
+
+ var result = await _projectService.AssignMembersToProjectAsync(id, dto, User.GetRoleId(), User.GetUserId());
+ if (!result.IsSuccess)
+ {
+ return StatusCode(result.StatusCode, new { message = result.ErrorMessage });
+ }
+ return Ok();
+ }
+
+ [HttpDelete("{id}/members/{userId}")]
+ public async Task RemoveProjectMember(long id, long userId)
+ {
+ if (!await _permissionAuthorizationService.CanAccessAsync(User, "api/Project", "Update"))
+ {
+ return StatusCode(403, Result.Failure("You do not have permission to update project members.", 403));
+ }
+
+ var result = await _projectService.RemoveMemberFromProjectAsync(id, userId, User.GetRoleId(), User.GetUserId());
+ if (!result.IsSuccess)
+ {
+ return StatusCode(result.StatusCode, new { message = result.ErrorMessage });
+ }
+ return NoContent();
+ }
+
+ [HttpGet("{id}/tasks")]
+ public async Task>>> GetProjectTasks(long id)
+ {
+ var result = await _projectService.GetProjectTasksAsync(id, User.GetRoleId(), User.GetUserId());
+ return StatusCode(result.StatusCode, result);
+ }
+ }
+}
diff --git a/TaskTrackingSystem.WebApi/Features/Project/ProjectService.cs b/TaskTrackingSystem.WebApi/Features/Project/ProjectService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..2c45b20ef110bcecfda0d199f6a1275c63eeb197
--- /dev/null
+++ b/TaskTrackingSystem.WebApi/Features/Project/ProjectService.cs
@@ -0,0 +1,411 @@
+using Microsoft.EntityFrameworkCore;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using TaskTrackingSystem.Database.AppDbContextModels;
+using TaskTrackingSystem.Shared;
+using TaskTrackingSystem.Shared.Enums;
+using TaskTrackingSystem.Shared.Models.Project;
+using TaskTrackingSystem.Shared.Models.Task;
+using TaskTrackingSystem.Shared.Models.User;
+using TaskTrackingSystem.WebApi.Infrastructure;
+
+namespace TaskTrackingSystem.WebApi.Features.Project
+{
+ public class ProjectService
+ {
+ private readonly AppDbContext _db;
+ private readonly TaskTrackingSystem.WebApi.Features.Notification.FirebaseNotificationService _notificationService;
+ private readonly Infrastructure.AuditLogService _auditLog;
+
+ public ProjectService(
+ AppDbContext db,
+ TaskTrackingSystem.WebApi.Features.Notification.FirebaseNotificationService notificationService,
+ Infrastructure.AuditLogService auditLog)
+ {
+ _db = db;
+ _notificationService = notificationService;
+ _auditLog = auditLog;
+ }
+
+ private static DateTime? GetCompletedAt(TaskTrackingSystem.Database.AppDbContextModels.Task task)
+ {
+ return task.StatusId == AppTaskStatus.Done
+ ? task.UpdatedAt ?? task.CreatedAt
+ : null;
+ }
+
+ private static DateTime ToUtcDate(DateTime value)
+ {
+ return value.Kind == DateTimeKind.Utc
+ ? value
+ : DateTime.SpecifyKind(value.Date, DateTimeKind.Utc);
+ }
+
+ public async Task> GetAllProjectsAsync(long roleId, long currentUserId)
+ {
+ var isAdmin = await DataScopeAuthorization.IsAdminScopeAsync(_db, roleId);
+ return await BuildAccessibleProjectQuery(isAdmin, currentUserId)
+ .OrderBy(p => p.EndDate)
+ .ThenByDescending(p => p.CreatedAt ?? DateTime.UtcNow)
+ .Select(p => new ProjectDto
+ {
+ Id = p.Id,
+ Name = p.Name,
+ Description = p.Description,
+ StartDate = p.StartDate,
+ EndDate = p.EndDate,
+ CreatedById = p.CreatedById,
+ CreatedAt = p.CreatedAt ?? DateTime.UtcNow,
+ BudgetedHours = p.BudgetedHours
+ }).ToListAsync();
+ }
+
+ public async Task> GetPagedProjectsAsync(long roleId, long currentUserId, string? search, int page, int pageSize)
+ {
+ var isAdmin = await DataScopeAuthorization.IsAdminScopeAsync(_db, roleId);
+ var query = BuildAccessibleProjectQuery(isAdmin, currentUserId);
+
+ if (!string.IsNullOrWhiteSpace(search))
+ {
+ var searchTerm = search.Trim().ToLower();
+ query = query.Where(p =>
+ (p.Name != null && p.Name.ToLower().Contains(searchTerm)) ||
+ (p.Description != null && p.Description.ToLower().Contains(searchTerm)));
+ }
+
+ return await query
+ .OrderBy(p => p.EndDate)
+ .ThenByDescending(p => p.CreatedAt ?? DateTime.UtcNow)
+ .Select(p => new ProjectDto
+ {
+ Id = p.Id,
+ Name = p.Name,
+ Description = p.Description,
+ StartDate = p.StartDate,
+ EndDate = p.EndDate,
+ CreatedById = p.CreatedById,
+ CreatedAt = p.CreatedAt ?? DateTime.UtcNow,
+ BudgetedHours = p.BudgetedHours
+ })
+ .ToPagedResultAsync(page, pageSize);
+ }
+
+ public async Task GetProjectByIdAsync(long id, long roleId, long currentUserId)
+ {
+ var isAdmin = await DataScopeAuthorization.IsAdminScopeAsync(_db, roleId);
+ var project = await BuildAccessibleProjectQuery(isAdmin, currentUserId)
+ .FirstOrDefaultAsync(p => p.Id == id);
+
+ if (project == null)
+ {
+ return null;
+ }
+
+ return new ProjectDto
+ {
+ Id = project.Id,
+ Name = project.Name,
+ Description = project.Description,
+ StartDate = project.StartDate,
+ EndDate = project.EndDate,
+ CreatedById = project.CreatedById,
+ CreatedAt = project.CreatedAt ?? DateTime.UtcNow,
+ BudgetedHours = project.BudgetedHours
+ };
+ }
+
+ public async Task> CreateProjectAsync(CreateProjectDto dto, long? currentUserId = null)
+ {
+ if (string.IsNullOrWhiteSpace(dto.Name))
+ {
+ return Result.Failure(ResultMessages.ProjectNameRequired, 400);
+ }
+
+ var project = new TaskTrackingSystem.Database.AppDbContextModels.Project
+ {
+ Name = dto.Name,
+ Description = dto.Description,
+ StartDate = ToUtcDate(dto.StartDate),
+ EndDate = ToUtcDate(dto.EndDate),
+ CreatedById = dto.CreatedById,
+ IsDeleted = false,
+ CreatedAt = DateTime.UtcNow,
+ CreatedBy = currentUserId,
+ BudgetedHours = dto.BudgetedHours
+ };
+
+ _db.Projects.Add(project);
+ await _db.SaveChangesAsync();
+ await _auditLog.LogAsync("Create", "Project", $"Created project '{project.Name}'");
+
+ var resultDto = new ProjectDto
+ {
+ Id = project.Id,
+ Name = project.Name,
+ Description = project.Description,
+ StartDate = project.StartDate,
+ EndDate = project.EndDate,
+ CreatedById = project.CreatedById,
+ CreatedAt = project.CreatedAt ?? DateTime.UtcNow,
+ BudgetedHours = project.BudgetedHours
+ };
+
+ return Result.Success(resultDto, 201);
+ }
+
+ public async Task UpdateProjectAsync(long id, UpdateProjectDto dto, long roleId, long currentUserId)
+ {
+ if (string.IsNullOrWhiteSpace(dto.Name))
+ {
+ return Result.Failure(ResultMessages.ProjectNameRequired, 400);
+ }
+
+ var project = await _db.Projects.FirstOrDefaultAsync(p => p.Id == id && p.IsDeleted != true);
+ if (project == null)
+ {
+ return Result.Failure(ResultMessages.ProjectNotFound(id), 404);
+ }
+
+ var isAdmin = await DataScopeAuthorization.IsAdminScopeAsync(_db, roleId);
+ if (!await IsProjectAccessibleAsync(id, isAdmin, currentUserId))
+ {
+ return Result.Failure("You do not have access to this project.", 403);
+ }
+
+ project.Name = dto.Name;
+ project.Description = dto.Description;
+ project.StartDate = ToUtcDate(dto.StartDate);
+ project.EndDate = ToUtcDate(dto.EndDate);
+ project.BudgetedHours = dto.BudgetedHours;
+ project.UpdatedAt = DateTime.UtcNow;
+ project.UpdatedBy = currentUserId;
+
+ _db.Projects.Update(project);
+ await _db.SaveChangesAsync();
+ await _auditLog.LogAsync("Update", "Project", $"Updated project '{project.Name}'");
+ return Result.Success(200);
+ }
+
+ public async Task SoftDeleteProjectAsync(long id, long roleId, long currentUserId)
+ {
+ var project = await _db.Projects.FirstOrDefaultAsync(p => p.Id == id && p.IsDeleted != true);
+ if (project == null)
+ {
+ return Result.Failure(ResultMessages.ProjectNotFound(id), 404);
+ }
+
+ var isAdmin = await DataScopeAuthorization.IsAdminScopeAsync(_db, roleId);
+ if (!await IsProjectAccessibleAsync(id, isAdmin, currentUserId))
+ {
+ return Result.Failure("You do not have access to this project.", 403);
+ }
+
+ project.IsDeleted = true;
+ _db.Projects.Update(project);
+
+ var tasksToUpdate = await _db.Tasks
+ .Where(t => t.ProjectId == id && t.IsDeleted != true)
+ .ToListAsync();
+ foreach (var task in tasksToUpdate)
+ {
+ task.IsDeleted = true;
+ task.UpdatedAt = DateTime.UtcNow;
+ task.UpdatedBy = currentUserId;
+ }
+
+ if (tasksToUpdate.Any())
+ {
+ _db.Tasks.UpdateRange(tasksToUpdate);
+ }
+
+ await _db.SaveChangesAsync();
+ await _auditLog.LogAsync("Delete", "Project", $"Deleted project '{project.Name}'");
+ return Result.Success(200);
+ }
+
+ public async Task>> GetProjectMembersAsync(long projectId, long roleId, long currentUserId)
+ {
+ var isAdmin = await DataScopeAuthorization.IsAdminScopeAsync(_db, roleId);
+ if (!await IsProjectAccessibleAsync(projectId, isAdmin, currentUserId))
+ {
+ return Result>.Failure("You do not have access to this project.", 403);
+ }
+
+ var members = await _db.ProjectMembers
+ .Where(pm => pm.ProjectId == projectId)
+ .Join(_db.Users.Where(u => !u.IsDeleted),
+ pm => pm.UserId,
+ u => u.Id,
+ (pm, u) => new UserDto
+ {
+ Id = u.Id,
+ Username = u.Username,
+ FirstName = u.FirstName,
+ LastName = u.LastName,
+ Email = u.Email,
+ Phone = u.Phone,
+ RoleId = u.RoleId,
+ IsActive = u.IsActive,
+ CreatedAt = u.CreatedAt ?? DateTime.UtcNow
+ })
+ .ToListAsync();
+
+ return Result>.Success(members);
+ }
+
+ public async Task AssignMembersToProjectAsync(long projectId, AssignMembersDto dto, long roleId, long currentUserId)
+ {
+ var projectExists = await _db.Projects.AnyAsync(p => p.Id == projectId && p.IsDeleted != true);
+ if (!projectExists)
+ {
+ return Result.Failure(ResultMessages.ProjectNotFound(projectId), 404);
+ }
+
+ var isAdmin = await DataScopeAuthorization.IsAdminScopeAsync(_db, roleId);
+ if (!await IsProjectAccessibleAsync(projectId, isAdmin, currentUserId))
+ {
+ return Result.Failure("You do not have access to this project.", 403);
+ }
+
+ if (dto.UserIds == null)
+ {
+ return Result.Failure(ResultMessages.UserIdsCannotBeNull, 400);
+ }
+
+ if (dto.UserIds.Any())
+ {
+ var validUserIds = await _db.Users
+ .Where(u => dto.UserIds.Contains(u.Id) && !u.IsDeleted)
+ .Select(u => u.Id)
+ .ToListAsync();
+
+ var invalidUserIds = dto.UserIds.Except(validUserIds).ToList();
+ if (invalidUserIds.Any())
+ {
+ return Result.Failure(ResultMessages.InvalidUserIds(string.Join(", ", invalidUserIds)), 400);
+ }
+ }
+
+ var existingMembers = await _db.ProjectMembers
+ .Where(pm => pm.ProjectId == projectId)
+ .ToListAsync();
+ var previousMemberIds = existingMembers.Select(pm => pm.UserId).ToHashSet();
+
+ if (existingMembers.Any())
+ {
+ _db.ProjectMembers.RemoveRange(existingMembers);
+ }
+
+ foreach (var userId in dto.UserIds)
+ {
+ _db.ProjectMembers.Add(new ProjectMember
+ {
+ ProjectId = projectId,
+ UserId = userId,
+ CreatedAt = DateTime.UtcNow
+ });
+ }
+
+ await _db.SaveChangesAsync();
+
+ var addedMemberIds = dto.UserIds.Except(previousMemberIds).ToList();
+ if (addedMemberIds.Count > 0)
+ {
+ var project = await _db.Projects.FirstOrDefaultAsync(p => p.Id == projectId && p.IsDeleted != true);
+ if (project != null)
+ {
+ await _notificationService.NotifyProjectAssignedAsync(project, addedMemberIds, currentUserId);
+ }
+ }
+
+ return Result.Success(200);
+ }
+
+ public async Task RemoveMemberFromProjectAsync(long projectId, long userId, long roleId, long currentUserId)
+ {
+ var projectExists = await _db.Projects.AnyAsync(p => p.Id == projectId && p.IsDeleted != true);
+ if (!projectExists)
+ {
+ return Result.Failure(ResultMessages.ProjectNotFound(projectId), 404);
+ }
+
+ var isAdmin = await DataScopeAuthorization.IsAdminScopeAsync(_db, roleId);
+ if (!await IsProjectAccessibleAsync(projectId, isAdmin, currentUserId))
+ {
+ return Result.Failure("You do not have access to this project.", 403);
+ }
+
+ var member = await _db.ProjectMembers
+ .FirstOrDefaultAsync(pm => pm.ProjectId == projectId && pm.UserId == userId);
+
+ if (member == null)
+ {
+ return Result.Failure(ResultMessages.UserNotProjectMember(userId, projectId), 404);
+ }
+
+ _db.ProjectMembers.Remove(member);
+ await _db.SaveChangesAsync();
+
+ return Result.Success(204);
+ }
+
+ public async Task>> GetProjectTasksAsync(long projectId, long roleId, long currentUserId)
+ {
+ var isAdmin = await DataScopeAuthorization.IsAdminScopeAsync(_db, roleId);
+ if (!await IsProjectAccessibleAsync(projectId, isAdmin, currentUserId))
+ {
+ return Result>.Failure("You do not have access to this project.", 403);
+ }
+
+ var tasks = await _db.Tasks
+ .Where(t => t.ProjectId == projectId && t.IsDeleted != true && !t.IsArchived)
+ .Select(t => new TaskDto
+ {
+ Id = t.Id,
+ Title = t.Title,
+ Description = t.Description,
+ ProjectId = t.ProjectId,
+ StatusId = t.StatusId,
+ PriorityId = t.PriorityId,
+ AssignedTo = t.AssignedTo,
+ AssignedBy = t.AssignedBy,
+ DueDate = t.DueDate,
+ CreatedAt = t.CreatedAt ?? DateTime.UtcNow,
+ IsArchived = t.IsArchived,
+ CompletedAt = GetCompletedAt(t)
+ })
+ .ToListAsync();
+
+ return Result>.Success(tasks);
+ }
+
+ private IQueryable BuildAccessibleProjectQuery(bool isAdmin, long currentUserId)
+ {
+ var query = _db.Projects.Where(p => p.IsDeleted != true);
+
+ if (isAdmin)
+ {
+ return query;
+ }
+
+ return query.Where(p =>
+ p.CreatedById == currentUserId ||
+ p.ProjectMembers.Any(pm => pm.UserId == currentUserId));
+ }
+
+ private async Task IsProjectAccessibleAsync(long projectId, bool isAdmin, long currentUserId)
+ {
+ if (isAdmin)
+ {
+ return await _db.Projects.AnyAsync(p => p.Id == projectId && p.IsDeleted != true);
+ }
+
+ return await _db.Projects.AnyAsync(p =>
+ p.Id == projectId &&
+ p.IsDeleted != true &&
+ (p.CreatedById == currentUserId || p.ProjectMembers.Any(pm => pm.UserId == currentUserId)));
+ }
+ }
+}
diff --git a/TaskTrackingSystem.WebApi/Features/Report/ReportController.cs b/TaskTrackingSystem.WebApi/Features/Report/ReportController.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f7e9d51dcf6afacd01776fbfb768a04315760a0f
--- /dev/null
+++ b/TaskTrackingSystem.WebApi/Features/Report/ReportController.cs
@@ -0,0 +1,232 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using TaskTrackingSystem.Shared;
+using TaskTrackingSystem.Shared.Enums;
+using TaskTrackingSystem.Shared.Models.Report;
+using TaskTrackingSystem.Shared.Models.Issue;
+using TaskTrackingSystem.WebApi.Infrastructure;
+
+namespace TaskTrackingSystem.WebApi.Features.Report
+{
+ [Route("api/[controller]")]
+ [ApiController]
+ [Authorize]
+ public class ReportController : ControllerBase
+ {
+ private readonly ReportService _reportService;
+
+ public ReportController(ReportService reportService)
+ {
+ _reportService = reportService;
+ }
+
+ [HttpGet("issues")]
+ [ProducesResponseType(typeof(List), StatusCodes.Status200OK)]
+ public async Task>>> GetIssuesReport()
+ {
+ var result = await _reportService.GetIssuesReportAsync(User.GetRoleId(), User.GetUserId());
+ return StatusCode(result.StatusCode, result);
+ }
+
+ [HttpGet("tasks")]
+ [ProducesResponseType(typeof(PagedResult), StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+ public async Task>> GetTasksReport(
+ [FromQuery] DateTime? startDate,
+ [FromQuery] DateTime? endDate,
+ [FromQuery] string? status,
+ [FromQuery] int? projectId,
+ [FromQuery] bool? assignedToMe,
+ [FromQuery] bool? assignedToMyTeam,
+ [FromQuery] PaginationQuery? paging = null)
+ {
+ if (paging == null || (!paging.Page.HasValue && !paging.Limit.HasValue))
+ {
+ var full = await _reportService.GetTasksReportAsync(
+ startDate, endDate, status, projectId, User.GetRoleId(), User.GetUserId(), assignedToMe, assignedToMyTeam);
+ return StatusCode(full.StatusCode, full);
+ }
+
+ var page = PaginationExtensions.NormalizePage(paging.Page);
+ var limit = PaginationExtensions.NormalizePageSize(paging.Limit ?? 0);
+ var result = await _reportService.GetPagedTasksReportAsync(
+ startDate, endDate, status, projectId, User.GetRoleId(), User.GetUserId(), page, limit, assignedToMe, assignedToMyTeam);
+ return Ok(result);
+ }
+
+ [HttpGet("user-productivity")]
+ [ProducesResponseType(typeof(PagedResult), StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+ public async Task>> GetUserProductivityReport(
+ [FromQuery] PaginationQuery? paging = null)
+ {
+ var users = await _reportService.GetUserProductivityReportAsync(User.GetRoleId(), User.GetUserId());
+ if (!users.IsSuccess || users.Value == null)
+ {
+ return StatusCode(users.StatusCode, new PagedResult());
+ }
+
+ if (paging == null || (!paging.Page.HasValue && !paging.Limit.HasValue))
+ {
+ return StatusCode(users.StatusCode, users);
+ }
+
+ var page = PaginationExtensions.NormalizePage(paging?.Page);
+ var limit = PaginationExtensions.NormalizePageSize(paging?.Limit ?? 0);
+ var query = users.Value.AsQueryable();
+ var paged = await query.ToPagedResultAsync(page, limit);
+ return Ok(paged);
+ }
+
+ [HttpGet("task-status-summary")]
+ public async Task>>> GetTaskStatusSummary(
+ [FromQuery] string? search,
+ [FromQuery] AppTaskStatus? statusId,
+ [FromQuery] long? projectId)
+ {
+ var result = await _reportService.GetTaskStatusSummaryAsync(search, statusId, projectId, User.GetRoleId(), User.GetUserId());
+ return StatusCode(result.StatusCode, result);
+ }
+
+ [HttpGet("task-status-summary/excel")]
+ public async Task DownloadTaskStatusSummaryExcel(
+ [FromQuery] string? search,
+ [FromQuery] AppTaskStatus? statusId,
+ [FromQuery] long? projectId)
+ {
+ var result = await _reportService.GetTaskStatusSummaryAsync(search, statusId, projectId, User.GetRoleId(), User.GetUserId());
+ if (!result.IsSuccess || result.Value == null)
+ return BadRequest(new { message = result.ErrorMessage });
+ var bytes = _reportService.ExportTaskStatusSummaryToExcel(result.Value);
+ return File(bytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ $"TaskStatusSummary_{DateTime.Today:yyyyMMdd}.xlsx");
+ }
+
+ [HttpGet("team-productivity")]
+ [ProducesResponseType(typeof(PagedResult), StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+ public async Task>> GetTeamProductivity(
+ [FromQuery] string? search,
+ [FromQuery] PaginationQuery? paging = null)
+ {
+ if (paging == null || (!paging.Page.HasValue && !paging.Limit.HasValue))
+ {
+ var full = await _reportService.GetTeamProductivityAsync(search, User.GetRoleId(), User.GetUserId());
+ return StatusCode(full.StatusCode, full);
+ }
+
+ var page = PaginationExtensions.NormalizePage(paging?.Page);
+ var limit = PaginationExtensions.NormalizePageSize(paging?.Limit ?? 0);
+ var result = await _reportService.GetPagedTeamProductivityAsync(search, User.GetRoleId(), User.GetUserId(), page, limit);
+ return Ok(result);
+ }
+
+ [HttpGet("team-productivity/excel")]
+ public async Task DownloadTeamProductivityExcel([FromQuery] string? search)
+ {
+ var result = await _reportService.GetTeamProductivityAsync(search, User.GetRoleId(), User.GetUserId());
+ if (!result.IsSuccess || result.Value == null)
+ return BadRequest(new { message = result.ErrorMessage });
+ var bytes = _reportService.ExportTeamProductivityToExcel(result.Value);
+ return File(bytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ $"TeamProductivity_{DateTime.Today:yyyyMMdd}.xlsx");
+ }
+
+ [HttpGet("overdue-critical")]
+ [ProducesResponseType(typeof(PagedResult), StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+ public async Task