RIS creation, approval and Cancel working well
This commit is contained in:
parent
6710f04bd7
commit
44862d01b5
@ -1,4 +1,6 @@
|
|||||||
using CPRNIMS.Infrastructure.Dto.Inventory;
|
using CPRNIMS.Infrastructure.Dto.Inventory;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory.Request;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory.Response;
|
||||||
using CPRNIMS.Infrastructure.Entities.Inventory;
|
using CPRNIMS.Infrastructure.Entities.Inventory;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -11,11 +13,15 @@ namespace CPRNIMS.Domain.Contracts.Inventory
|
|||||||
public interface IInventory
|
public interface IInventory
|
||||||
{
|
{
|
||||||
Task<List<Lot>> GetLotNo(InventoryDto itemDto);
|
Task<List<Lot>> GetLotNo(InventoryDto itemDto);
|
||||||
Task<List<Lot>> GetLotNoById(InventoryDto itemDto);//
|
Task<List<Lot>> GetLotNoById(InventoryDto itemDto);
|
||||||
Task<List<LotQtyByItem>> GetLotQtyByItem(InventoryDto itemDto);
|
Task<List<LotQtyByItem>> GetLotQtyByItem(InventoryDto itemDto);
|
||||||
Task<List<Infrastructure.Entities.Inventory.Inventory>> GetInventoryByUserId(InventoryDto itemDto);
|
Task<List<Infrastructure.Entities.Inventory.Inventory>> GetInventoryByUserId(InventoryDto itemDto);
|
||||||
Task<List<RequestItemDetail>> GetRequestedItemByUserId(InventoryDto itemDto);
|
Task<List<RequestItemDetail>> GetRequestedItemByUserId(InventoryDto itemDto);
|
||||||
Task<List<ItemDetail>> GetInventoryById(InventoryDto itemDto);
|
Task<List<ItemDetail>> GetInventoryById(InventoryDto itemDto);
|
||||||
|
Task<PagedResult<InventoryResponse>> GetInventory(InventoryRequest request, CancellationToken ct);
|
||||||
|
Task<List<InventoryByIdResponse>> GetInventoryById(InventoryRequest itemDto, CancellationToken ct);
|
||||||
|
Task<TransactContextDto?> GetTransactContextAsync(int inventoryId, CancellationToken ct);
|
||||||
|
Task<IEnumerable<DisciplineDto>> GetDisciplinesAsync(CancellationToken ct);
|
||||||
Task<Infrastructure.Entities.Inventory.Inventory> PostPutReqApproval(InventoryDto itemDto);
|
Task<Infrastructure.Entities.Inventory.Inventory> PostPutReqApproval(InventoryDto itemDto);
|
||||||
Task<RequestItem> PostPutReqItems(InventoryDto itemDto);
|
Task<RequestItem> PostPutReqItems(InventoryDto itemDto);
|
||||||
Task<Lot> PostPutLotNo(InventoryDto itemDto);
|
Task<Lot> PostPutLotNo(InventoryDto itemDto);
|
||||||
|
|||||||
19
CPRNIMS.Domain/Contracts/Inventory/IMRS.cs
Normal file
19
CPRNIMS.Domain/Contracts/Inventory/IMRS.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory.Request;
|
||||||
|
using CPRNIMS.Infrastructure.Entities.Inventory;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Domain.Contracts.Inventory
|
||||||
|
{
|
||||||
|
public interface IMRS
|
||||||
|
{
|
||||||
|
Task<MRSPagedResult> GetPagedAsync(MRSFilterDto filter);
|
||||||
|
Task<MRS?> GetByIdAsync(long mrsId);
|
||||||
|
Task<MRS> CreateAsync(CreateMRSRequest dto, string createdBy);
|
||||||
|
Task ApproveAsync(long mrsId, string approvedBy);
|
||||||
|
}
|
||||||
|
}
|
||||||
22
CPRNIMS.Domain/Contracts/Inventory/IRIS.cs
Normal file
22
CPRNIMS.Domain/Contracts/Inventory/IRIS.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory.Request;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory.Response;
|
||||||
|
using CPRNIMS.Infrastructure.Entities.Inventory;
|
||||||
|
using CPRNIMS.Infrastructure.Entities.Purchasing;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Domain.Contracts.Inventory
|
||||||
|
{
|
||||||
|
public interface IRIS
|
||||||
|
{
|
||||||
|
Task<RISPagedResult> GetPagedAsync(RISFilterDto filter, CancellationToken ct);
|
||||||
|
Task<RISResponse?> GetByIdAsync(long risId, CancellationToken ct);
|
||||||
|
Task<Infrastructure.Entities.Inventory.RIS> CreateAsync(CreateRISRequest dto, string createdBy, CancellationToken ct);
|
||||||
|
Task ApproveAsync(ApproveRISRequest request, string approvedBy, CancellationToken ct);
|
||||||
|
Task CancelAsync(CancelRISRequest request, CancellationToken ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,8 +1,8 @@
|
|||||||
using CPRNIMS.Domain.Contracts.Inventory;
|
using CPRNIMS.Domain.Contracts.Inventory;
|
||||||
using CPRNIMS.Infrastructure.Database;
|
using CPRNIMS.Infrastructure.Database;
|
||||||
using CPRNIMS.Infrastructure.Dto.Inventory;
|
using CPRNIMS.Infrastructure.Dto.Inventory;
|
||||||
using CPRNIMS.Infrastructure.Dto.Items;
|
using CPRNIMS.Infrastructure.Dto.Inventory.Request;
|
||||||
using CPRNIMS.Infrastructure.Entities.Finance;
|
using CPRNIMS.Infrastructure.Dto.Inventory.Response;
|
||||||
using CPRNIMS.Infrastructure.Entities.Inventory;
|
using CPRNIMS.Infrastructure.Entities.Inventory;
|
||||||
using Microsoft.Data.SqlClient;
|
using Microsoft.Data.SqlClient;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@ -21,65 +21,203 @@ namespace CPRNIMS.Domain.Services.Inventory
|
|||||||
{
|
{
|
||||||
_dbContext = dbContext;
|
_dbContext = dbContext;
|
||||||
}
|
}
|
||||||
public async Task<List<Infrastructure.Entities.Inventory.ItemDetail>> GetInventoryById(InventoryDto itemDto)
|
public async Task<TransactContextDto?> GetTransactContextAsync(int inventoryId, CancellationToken ct)
|
||||||
{
|
{
|
||||||
try
|
var inv = await _dbContext.Inventories
|
||||||
{
|
.Where(i => i.InventoryId == inventoryId && i.IsActive)
|
||||||
var allItems = await _dbContext.ItemDetails
|
.Select(i => new
|
||||||
.FromSqlRaw($"EXEC GetInventoryById @UserId = '{itemDto.UserId}',@InventoryId = '{itemDto.InventoryId}'")
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
return allItems ?? new List<Infrastructure.Entities.Inventory.ItemDetail>();
|
|
||||||
}
|
|
||||||
catch (SqlException ex)
|
|
||||||
{
|
|
||||||
ex.ToString();
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<List<Infrastructure.Entities.Inventory.Inventory>>
|
|
||||||
GetInventoryByUserId(InventoryDto itemDto)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if(itemDto.IsSorting == false)
|
|
||||||
{
|
{
|
||||||
itemDto.DateFrom=DateTime.Now;
|
i.InventoryId,
|
||||||
itemDto.DateTo = DateTime.Now;
|
i.QtyOnHand,
|
||||||
}
|
i.QtyIn,
|
||||||
var allItems = await _dbContext.Inventories
|
i.QtyOut,
|
||||||
.FromSqlRaw($"EXEC GetInventoryByUserId @UserId,@DateFrom,@DateTo,@IsSorting",
|
|
||||||
new SqlParameter("@UserId", itemDto.UserId),
|
|
||||||
new SqlParameter("@DateFrom", itemDto.DateFrom),
|
|
||||||
new SqlParameter("@DateTo", itemDto.DateTo),
|
|
||||||
new SqlParameter("@IsSorting", itemDto.IsSorting))
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
return allItems ?? new List<Infrastructure.Entities.Inventory.Inventory>();
|
LotNo = i.Lot != null ? i.Lot.LotName : null,
|
||||||
}
|
|
||||||
catch (SqlException ex)
|
Department = i.User != null && i.User.Department != null
|
||||||
|
? i.User.Department.Department
|
||||||
|
: null,
|
||||||
|
i.ItemNo,
|
||||||
|
FirstDetail = i.InventTrans
|
||||||
|
.Where(t => t.IsActive)
|
||||||
|
.SelectMany(t => t.InventTransDetails)
|
||||||
|
.Where(d => d.PRDetails != null
|
||||||
|
&& d.PRDetails.PRs != null
|
||||||
|
&& d.IsActive)
|
||||||
|
.Select(d => new
|
||||||
|
{
|
||||||
|
d.PRDetails!.ItemName,
|
||||||
|
PRNo = d.PRDetails.PRs!.PRNo
|
||||||
|
})
|
||||||
|
.FirstOrDefault()
|
||||||
|
})
|
||||||
|
.FirstOrDefaultAsync(ct);
|
||||||
|
|
||||||
|
if (inv == null) return null;
|
||||||
|
|
||||||
|
// Computed property QtyAvailableToReturn can't be used in EF Where,
|
||||||
|
// so filter after projection using a raw expression in the query.
|
||||||
|
var openRIS = await _dbContext.RIS
|
||||||
|
.Where(r => r.InventoryId == inventoryId
|
||||||
|
&& r.Status == 1)
|
||||||
|
.Select(r => new RISReferenceDto
|
||||||
|
{
|
||||||
|
RISId = r.RISId,
|
||||||
|
RISNo = r.RISNo,
|
||||||
|
QtyIssued = r.QtyIssued,
|
||||||
|
TotalReturned = r.MaterialReturns
|
||||||
|
.Where(m => m.Status != 2)
|
||||||
|
.Sum(m => (int?)m.QtyReturned) ?? 0,
|
||||||
|
DisciplineName = r.Discipline.DisciplineName,
|
||||||
|
CreatedDate = r.CreatedDate
|
||||||
|
})
|
||||||
|
|
||||||
|
// Can't use the computed property here — EF won't translate it
|
||||||
|
// so we repeat the expression inline
|
||||||
|
.Where(r => r.QtyIssued - r.TotalReturned > 0)
|
||||||
|
.OrderByDescending(r => r.CreatedDate)
|
||||||
|
.ToListAsync(ct);
|
||||||
|
|
||||||
|
var disciplines = await GetDisciplinesAsync(ct);
|
||||||
|
|
||||||
|
return new TransactContextDto
|
||||||
{
|
{
|
||||||
ex.ToString();
|
InventoryId = inv.InventoryId,
|
||||||
throw;
|
ItemName = inv.FirstDetail?.ItemName ?? "—",
|
||||||
|
PRNo = inv.FirstDetail.PRNo,
|
||||||
|
ItemNo = inv.ItemNo,
|
||||||
|
LotNo = inv.LotNo,
|
||||||
|
Department = inv.Department,
|
||||||
|
QtyOnHand = inv.QtyOnHand,
|
||||||
|
QtyIn = inv.QtyIn,
|
||||||
|
QtyOut = inv.QtyOut,
|
||||||
|
Disciplines = disciplines,
|
||||||
|
OpenRISList = openRIS
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public async Task<IEnumerable<DisciplineDto>> GetDisciplinesAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
return await _dbContext.Disciplines
|
||||||
|
.OrderBy(d => d.DisciplineName)
|
||||||
|
.Select(d => new DisciplineDto
|
||||||
|
{
|
||||||
|
DisciplineId = d.DisciplineId,
|
||||||
|
DisciplineName = d.DisciplineName
|
||||||
|
})
|
||||||
|
.ToListAsync(ct);
|
||||||
|
}
|
||||||
|
public async Task<List<ItemDetail>> GetInventoryById(InventoryDto itemDto)
|
||||||
|
{
|
||||||
|
var allItems = await _dbContext.ItemDetails
|
||||||
|
.FromSqlRaw($"EXEC GetInventoryById @UserId = '{itemDto.UserId}',@InventoryId = '{itemDto.InventoryId}'")
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return allItems ?? new List<Infrastructure.Entities.Inventory.ItemDetail>();
|
||||||
|
}
|
||||||
|
public async Task<PagedResult<InventoryResponse>> GetInventory(InventoryRequest request, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var parameters = new[]
|
||||||
|
{
|
||||||
|
new SqlParameter("@UserId", request.UserId),
|
||||||
|
new SqlParameter("@SearchPRNo", request.SearchPRNo ?? ""),
|
||||||
|
new SqlParameter("@SearchItemNo", request.SearchItemNo ?? ""),
|
||||||
|
new SqlParameter("@SearchItemName", request.SearchItemName ?? ""),
|
||||||
|
new SqlParameter("@SearchDept", request.SearchDept ?? ""),
|
||||||
|
new SqlParameter("@SearchProjectCode",request.SearchProjectCode ?? ""),
|
||||||
|
new SqlParameter("@PageNumber", request.PageNumber),
|
||||||
|
new SqlParameter("@PageSize", request.PageSize)
|
||||||
|
};
|
||||||
|
|
||||||
|
var departmentList = new List<string>();
|
||||||
|
int totalCount = 0;
|
||||||
|
var items = new List<InventoryResponse>();
|
||||||
|
|
||||||
|
await using var conn = _dbContext.Database.GetDbConnection();
|
||||||
|
await conn.OpenAsync(ct);
|
||||||
|
|
||||||
|
using var cmd = conn.CreateCommand();
|
||||||
|
cmd.CommandText = @"EXEC GetInventory @UserId, @SearchPRNo, @SearchItemNo, @SearchItemName, @SearchDept,@SearchProjectCode, @PageNumber, @PageSize";
|
||||||
|
foreach (var p in parameters) cmd.Parameters.Add(p);
|
||||||
|
cmd.CommandTimeout = 60;
|
||||||
|
|
||||||
|
using var reader = await cmd.ExecuteReaderAsync(ct);
|
||||||
|
|
||||||
|
while (await reader.ReadAsync(ct))
|
||||||
|
departmentList.Add(reader.GetString(0));
|
||||||
|
|
||||||
|
await reader.NextResultAsync(ct);
|
||||||
|
if (await reader.ReadAsync(ct))
|
||||||
|
totalCount = reader.GetInt32(0);
|
||||||
|
|
||||||
|
await reader.NextResultAsync(ct);
|
||||||
|
while (await reader.ReadAsync(ct))
|
||||||
|
{
|
||||||
|
items.Add(new InventoryResponse
|
||||||
|
{
|
||||||
|
InventoryId = reader.GetInt32(reader.GetOrdinal("InventoryId")),
|
||||||
|
QtyIn = reader["QtyIn"] as decimal? ?? 0,
|
||||||
|
QtyOut = reader["QtyOut"] as decimal? ?? 0,
|
||||||
|
QtyOnHand = reader["QtyOnHand"] as decimal? ?? 0,
|
||||||
|
LotNo = reader["LotNo"]?.ToString(),
|
||||||
|
PRNo = Convert.ToInt64(reader["PRNo"]),
|
||||||
|
UserId = reader["UserId"]?.ToString(),
|
||||||
|
ItemName = reader["ItemName"]?.ToString(),
|
||||||
|
ItemNo = Convert.ToInt64(reader["ItemNo"]),
|
||||||
|
ItemDescription = reader["ItemDescription"]?.ToString(),
|
||||||
|
ItemCategoryName = reader["ItemCategoryName"]?.ToString(),
|
||||||
|
Department = reader["Department"]?.ToString(),
|
||||||
|
ProjectCode = reader["ProjectCode"]?.ToString(),
|
||||||
|
CreatedDate = reader["CreatedDate"] == DBNull.Value
|
||||||
|
? DateTime.MinValue
|
||||||
|
: Convert.ToDateTime(reader["CreatedDate"])
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await conn.CloseAsync();
|
||||||
|
|
||||||
|
return new PagedResult<InventoryResponse>
|
||||||
|
{
|
||||||
|
Data = items,
|
||||||
|
TotalCount = totalCount,
|
||||||
|
PageNumber = request.PageNumber,
|
||||||
|
PageSize = request.PageSize,
|
||||||
|
DepartmentList = departmentList
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public async Task<List<InventoryByIdResponse>> GetInventoryById(InventoryRequest itemDto,CancellationToken ct)
|
||||||
|
{
|
||||||
|
var allItems = await _dbContext.InventoryByIdResponses
|
||||||
|
.FromSqlRaw($"EXEC GetInventoryById @InventoryId",
|
||||||
|
new SqlParameter("@InventoryId", itemDto.InventoryId))
|
||||||
|
.ToListAsync(ct);
|
||||||
|
|
||||||
|
return allItems ?? new List<InventoryByIdResponse>();
|
||||||
|
}
|
||||||
|
public async Task<List<Infrastructure.Entities.Inventory.Inventory>>GetInventoryByUserId(InventoryDto itemDto)
|
||||||
|
{
|
||||||
|
if (itemDto.IsSorting == false)
|
||||||
|
{
|
||||||
|
itemDto.DateFrom = DateTime.Now;
|
||||||
|
itemDto.DateTo = DateTime.Now;
|
||||||
|
}
|
||||||
|
var allItems = await _dbContext.Inventories
|
||||||
|
.FromSqlRaw($"EXEC GetInventoryByUserId @UserId,@DateFrom,@DateTo,@IsSorting",
|
||||||
|
new SqlParameter("@UserId", itemDto.UserId),
|
||||||
|
new SqlParameter("@DateFrom", itemDto.DateFrom),
|
||||||
|
new SqlParameter("@DateTo", itemDto.DateTo),
|
||||||
|
new SqlParameter("@IsSorting", itemDto.IsSorting))
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return allItems ?? new List<Infrastructure.Entities.Inventory.Inventory>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<Lot>> GetLotNoById(InventoryDto itemDto)
|
public async Task<List<Lot>> GetLotNoById(InventoryDto itemDto)
|
||||||
{
|
{
|
||||||
try
|
var allItems = await _dbContext.Lots
|
||||||
{
|
.FromSqlRaw($"EXEC GetLotById @UserId = '{itemDto.UserId}'")
|
||||||
var allItems = await _dbContext.Lots
|
.ToListAsync();
|
||||||
.FromSqlRaw($"EXEC GetLotById @UserId = '{itemDto.UserId}'")
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
return allItems ?? new List<Lot>();
|
return allItems ?? new List<Lot>();
|
||||||
}
|
|
||||||
catch (SqlException ex)
|
|
||||||
{
|
|
||||||
ex.ToString();
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<Lot>> GetLotNo(InventoryDto itemDto)
|
public async Task<List<Lot>> GetLotNo(InventoryDto itemDto)
|
||||||
@ -94,125 +232,77 @@ namespace CPRNIMS.Domain.Services.Inventory
|
|||||||
|
|
||||||
public async Task<Lot> PostPutLotNo(InventoryDto itemDto)
|
public async Task<Lot> PostPutLotNo(InventoryDto itemDto)
|
||||||
{
|
{
|
||||||
try
|
await _dbContext.Database
|
||||||
{
|
.ExecuteSqlRawAsync("EXEC PostPutLotNo @UserId, @LotId, @LotTypeId,@LotName",
|
||||||
await _dbContext.Database
|
new SqlParameter("@LotId", itemDto.LotId != null ? itemDto.LotId : 0L),
|
||||||
.ExecuteSqlRawAsync("EXEC PostPutLotNo @UserId, @LotId, @LotTypeId,@LotName",
|
new SqlParameter("@UserId", itemDto.UserId),
|
||||||
new SqlParameter("@LotId", itemDto.LotId != null ? itemDto.LotId : 0L),
|
new SqlParameter("@LotTypeId", itemDto.LotTypeId),
|
||||||
new SqlParameter("@UserId", itemDto.UserId),
|
new SqlParameter("@LotName", itemDto.LotName));
|
||||||
new SqlParameter("@LotTypeId", itemDto.LotTypeId),
|
return new Lot();
|
||||||
new SqlParameter("@LotName", itemDto.LotName));
|
|
||||||
return new Lot();
|
|
||||||
}
|
|
||||||
catch (SqlException ex)
|
|
||||||
{
|
|
||||||
ex.ToString();
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Infrastructure.Entities.Inventory.Inventory> PostPutReqApproval(InventoryDto itemDto)
|
public async Task<Infrastructure.Entities.Inventory.Inventory> PostPutReqApproval(InventoryDto itemDto)
|
||||||
{
|
{
|
||||||
try
|
await _dbContext.Database
|
||||||
{
|
.ExecuteSqlRawAsync("EXEC PostPutReqApproval @UserId, @ItemNo, @Status, @Remarks",
|
||||||
await _dbContext.Database
|
new SqlParameter("@ItemNo", itemDto.InventoryId != null ? itemDto.InventoryId : 0L),
|
||||||
.ExecuteSqlRawAsync("EXEC PostPutReqApproval @UserId, @ItemNo, @Status, @Remarks",
|
new SqlParameter("@UserId", itemDto.UserId),
|
||||||
new SqlParameter("@ItemNo", itemDto.InventoryId != null ? itemDto.InventoryId : 0L),
|
new SqlParameter("@Status", itemDto.Status),
|
||||||
new SqlParameter("@UserId", itemDto.UserId),
|
new SqlParameter("@Remarks", itemDto.Remarks ?? "N/A"));
|
||||||
new SqlParameter("@Status", itemDto.Status),
|
return new Infrastructure.Entities.Inventory.Inventory();
|
||||||
new SqlParameter("@Remarks", itemDto.Remarks ?? "N/A"));
|
|
||||||
return new Infrastructure.Entities.Inventory.Inventory();
|
|
||||||
}
|
|
||||||
catch (SqlException ex)
|
|
||||||
{
|
|
||||||
ex.ToString();
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ItemDetail> PostPutLotBin(InventoryDto itemDto)
|
public async Task<ItemDetail> PostPutLotBin(InventoryDto itemDto)
|
||||||
{
|
{
|
||||||
try
|
await _dbContext.Database
|
||||||
{
|
.ExecuteSqlRawAsync("EXEC PostPutLotBin @UserId, @LotId, @InventoryId",
|
||||||
await _dbContext.Database
|
new SqlParameter("@InventoryId", itemDto.InventoryId),
|
||||||
.ExecuteSqlRawAsync("EXEC PostPutLotBin @UserId, @LotId, @InventoryId",
|
new SqlParameter("@UserId", itemDto.UserId),
|
||||||
new SqlParameter("@InventoryId", itemDto.InventoryId),
|
new SqlParameter("@LotId", itemDto.LotId));
|
||||||
new SqlParameter("@UserId", itemDto.UserId),
|
return new ItemDetail();
|
||||||
new SqlParameter("@LotId", itemDto.LotId));
|
|
||||||
return new ItemDetail();
|
|
||||||
}
|
|
||||||
catch (SqlException ex)
|
|
||||||
{
|
|
||||||
ex.ToString();
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RequestItem> PostPutReqItems(InventoryDto itemDto)
|
public async Task<RequestItem> PostPutReqItems(InventoryDto itemDto)
|
||||||
{
|
{
|
||||||
try
|
if (itemDto.QtyReceived == null || itemDto.QtyReceived == 0)
|
||||||
{
|
{
|
||||||
if(itemDto.QtyReceived == null || itemDto.QtyReceived == 0)
|
itemDto.QtyReceived = 0;
|
||||||
{
|
|
||||||
itemDto.QtyReceived = 0;
|
|
||||||
}
|
|
||||||
if(itemDto.LotId == null || itemDto.LotId == 0)
|
|
||||||
{
|
|
||||||
itemDto.LotId = 0;
|
|
||||||
}
|
|
||||||
await _dbContext.Database
|
|
||||||
.ExecuteSqlRawAsync("EXEC PostPutReqItems @UserId, @RequestItemId, @ItemNo, @QtyRequest,@Status,@IsApproved,@QtyReceived,@LotId",
|
|
||||||
new SqlParameter("@UserId", itemDto.UserId),
|
|
||||||
new SqlParameter("@ItemNo", itemDto.ItemNo),
|
|
||||||
new SqlParameter("@RequestItemId", itemDto.RequestItemId != null ? itemDto.RequestItemId : 0L),
|
|
||||||
new SqlParameter("@QtyRequest", itemDto.QtyRequest),
|
|
||||||
new SqlParameter("@Status", itemDto.Status),
|
|
||||||
new SqlParameter("@IsApproved", itemDto.IsApproved),
|
|
||||||
new SqlParameter("@QtyReceived", itemDto.QtyReceived),
|
|
||||||
new SqlParameter("@LotId", itemDto.LotId));
|
|
||||||
return new RequestItem();
|
|
||||||
}
|
}
|
||||||
catch (SqlException ex)
|
if (itemDto.LotId == null || itemDto.LotId == 0)
|
||||||
{
|
{
|
||||||
ex.ToString();
|
itemDto.LotId = 0;
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
|
await _dbContext.Database
|
||||||
|
.ExecuteSqlRawAsync("EXEC PostPutReqItems @UserId, @RequestItemId, @ItemNo, @QtyRequest,@Status,@IsApproved,@QtyReceived,@LotId",
|
||||||
|
new SqlParameter("@UserId", itemDto.UserId),
|
||||||
|
new SqlParameter("@ItemNo", itemDto.ItemNo),
|
||||||
|
new SqlParameter("@RequestItemId", itemDto.RequestItemId != null ? itemDto.RequestItemId : 0L),
|
||||||
|
new SqlParameter("@QtyRequest", itemDto.QtyRequest),
|
||||||
|
new SqlParameter("@Status", itemDto.Status),
|
||||||
|
new SqlParameter("@IsApproved", itemDto.IsApproved),
|
||||||
|
new SqlParameter("@QtyReceived", itemDto.QtyReceived),
|
||||||
|
new SqlParameter("@LotId", itemDto.LotId));
|
||||||
|
return new RequestItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<RequestItemDetail>> GetRequestedItemByUserId(InventoryDto itemDto)
|
public async Task<List<RequestItemDetail>> GetRequestedItemByUserId(InventoryDto itemDto)
|
||||||
{
|
{
|
||||||
try
|
var allItems = await _dbContext.RequestItemDetails
|
||||||
{
|
|
||||||
var allItems = await _dbContext.RequestItemDetails
|
|
||||||
.FromSqlRaw($"EXEC GetRequestedItemByUserId @UserId = '{itemDto.UserId}', " +
|
.FromSqlRaw($"EXEC GetRequestedItemByUserId @UserId = '{itemDto.UserId}', " +
|
||||||
$"@RequestItemId = '{itemDto.RequestItemId}',@WithoutStocks = '{itemDto.WithoutStocks}'")
|
$"@RequestItemId = '{itemDto.RequestItemId}',@WithoutStocks = '{itemDto.WithoutStocks}'")
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
return allItems ?? new List<RequestItemDetail>();
|
return allItems ?? new List<RequestItemDetail>();
|
||||||
}
|
|
||||||
catch (SqlException ex)
|
|
||||||
{
|
|
||||||
ex.ToString();
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<LotQtyByItem>> GetLotQtyByItem(InventoryDto itemDto)
|
public async Task<List<LotQtyByItem>> GetLotQtyByItem(InventoryDto itemDto)
|
||||||
{
|
{
|
||||||
try
|
var allItems = await _dbContext.LotQtyByItems
|
||||||
{
|
.FromSqlRaw($"EXEC GetLotQtyByItem @UserId = '{itemDto.UserId}', " +
|
||||||
var allItems = await _dbContext.LotQtyByItems
|
$"@ItemNo = '{itemDto.ItemNo}',@LotId = '{itemDto.LotId}'")
|
||||||
.FromSqlRaw($"EXEC GetLotQtyByItem @UserId = '{itemDto.UserId}', " +
|
.ToListAsync();
|
||||||
$"@ItemNo = '{itemDto.ItemNo}',@LotId = '{itemDto.LotId}'")
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
return allItems ?? new List<LotQtyByItem>();
|
return allItems ?? new List<LotQtyByItem>();
|
||||||
}
|
|
||||||
catch (SqlException ex)
|
|
||||||
{
|
|
||||||
ex.ToString();
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
155
CPRNIMS.Domain/Services/Inventory/MRS.cs
Normal file
155
CPRNIMS.Domain/Services/Inventory/MRS.cs
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
using CPRNIMS.Domain.Contracts.Inventory;
|
||||||
|
using CPRNIMS.Infrastructure.Database;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory.Request;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory.Response;
|
||||||
|
using CPRNIMS.Infrastructure.Entities.Inventory;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Domain.Services.Inventory
|
||||||
|
{
|
||||||
|
public class MRS : IMRS
|
||||||
|
{
|
||||||
|
private readonly NonInventoryDbContext _db;
|
||||||
|
public MRS(NonInventoryDbContext db) => _db = db;
|
||||||
|
|
||||||
|
public async Task ApproveAsync(long mrsId, string approvedBy)
|
||||||
|
{
|
||||||
|
var rms = await _db.MRS.FindAsync(mrsId)
|
||||||
|
?? throw new InvalidOperationException("MRS not found.");
|
||||||
|
|
||||||
|
if (rms.Status != 0)
|
||||||
|
throw new InvalidOperationException("Only Draft MRS records can be approved.");
|
||||||
|
|
||||||
|
rms.Status = 1; // Approved
|
||||||
|
rms.ApprovedBy = approvedBy;
|
||||||
|
rms.ApprovedDate = DateTime.Now;
|
||||||
|
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Infrastructure.Entities.Inventory.MRS> CreateAsync(CreateMRSRequest dto, string createdBy)
|
||||||
|
{
|
||||||
|
var ris = await _db.RIS
|
||||||
|
.Include(r => r.Inventory)
|
||||||
|
.FirstOrDefaultAsync(r => r.RISId == dto.RISId)
|
||||||
|
?? throw new InvalidOperationException("Referenced RIS not found.");
|
||||||
|
|
||||||
|
if (dto.QtyReturned > ris.QtyIssued)
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Cannot return more than issued. Issued: {ris.QtyIssued}.");
|
||||||
|
|
||||||
|
var mrsNo = await GenerateMRSNoAsync();
|
||||||
|
|
||||||
|
var mrs = new Infrastructure.Entities.Inventory.MRS
|
||||||
|
{
|
||||||
|
MRSNo = mrsNo,
|
||||||
|
RISId = dto.RISId,
|
||||||
|
InventoryId = ris.InventoryId,
|
||||||
|
ReturnedBy = dto.ReturnedBy,
|
||||||
|
QtyReturned = dto.QtyReturned,
|
||||||
|
Condition = dto.Condition,
|
||||||
|
Remarks = dto.Remarks,
|
||||||
|
Status = 0,
|
||||||
|
CreatedBy = createdBy,
|
||||||
|
CreatedDate = DateTime.Now
|
||||||
|
};
|
||||||
|
_db.MRS.Add(mrs);
|
||||||
|
|
||||||
|
var inventory = ris.Inventory;
|
||||||
|
inventory.QtyOut = Math.Max(0m, inventory.QtyOut - dto.QtyReturned);
|
||||||
|
|
||||||
|
inventory.QtyOnHand = inventory.QtyIn - inventory.QtyOut;
|
||||||
|
|
||||||
|
var trans = await _db.InventTrans
|
||||||
|
.FirstOrDefaultAsync(t => t.InventoryId == ris.InventoryId && t.IsActive == true)!;
|
||||||
|
|
||||||
|
_db.InventTransDetails.Add(new InventTransDetail
|
||||||
|
{
|
||||||
|
InventTransId = trans.InventTransId,
|
||||||
|
TransTypeId = 6,
|
||||||
|
QtyIn = dto.QtyReturned,
|
||||||
|
CreatedDate = DateTime.Now,
|
||||||
|
Remarks = $"MRS: {mrsNo} — return from MRS: {ris.RISNo}",
|
||||||
|
IsActive = true
|
||||||
|
});
|
||||||
|
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
return mrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Infrastructure.Entities.Inventory.MRS?> GetByIdAsync(long mrsId)
|
||||||
|
|
||||||
|
=> await _db.MRS
|
||||||
|
.Include(r => r.Inventory)
|
||||||
|
.Include(r => r.RIS)
|
||||||
|
.FirstOrDefaultAsync(r => r.RISId == mrsId);
|
||||||
|
|
||||||
|
public async Task<MRSPagedResult> GetPagedAsync(MRSFilterDto filter)
|
||||||
|
{
|
||||||
|
var q = _db.MRS
|
||||||
|
.Include(m => m.RIS)
|
||||||
|
.Include(m => m.Inventory)
|
||||||
|
.AsQueryable();
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(filter.SearchMRSNo))
|
||||||
|
q = q.Where(m => m.MRSNo.Contains(filter.SearchMRSNo));
|
||||||
|
|
||||||
|
if (filter.RISId.HasValue)
|
||||||
|
q = q.Where(m => m.RISId == filter.RISId.Value);
|
||||||
|
|
||||||
|
if (filter.Status.HasValue)
|
||||||
|
q = q.Where(m => m.Status == filter.Status.Value);
|
||||||
|
|
||||||
|
if (filter.DateFrom.HasValue)
|
||||||
|
q = q.Where(m => m.CreatedDate >= filter.DateFrom.Value);
|
||||||
|
|
||||||
|
if (filter.DateTo.HasValue)
|
||||||
|
q = q.Where(m => m.CreatedDate <= filter.DateTo.Value.AddDays(1));
|
||||||
|
|
||||||
|
var total = await q.CountAsync();
|
||||||
|
|
||||||
|
var data = await q
|
||||||
|
.OrderByDescending(m => m.CreatedDate)
|
||||||
|
.Skip((filter.Page - 1) * filter.PageSize)
|
||||||
|
.Take(filter.PageSize)
|
||||||
|
.Select(m => new MRSResponse
|
||||||
|
{
|
||||||
|
MRSId = m.MRSId,
|
||||||
|
MRSNo = m.MRSNo,
|
||||||
|
RISId = m.RISId,
|
||||||
|
RISNo = m.RIS.RISNo,
|
||||||
|
InventoryId = m.InventoryId,
|
||||||
|
ItemName = m.Inventory.InventTrans
|
||||||
|
.SelectMany(t => t.InventTransDetails)
|
||||||
|
.Select(d => d.PRDetails != null ? d.PRDetails.ItemName : "—")
|
||||||
|
.FirstOrDefault() ?? "—",
|
||||||
|
ReturnedBy = m.ReturnedBy,
|
||||||
|
QtyReturned = m.QtyReturned,
|
||||||
|
Condition = m.Condition,
|
||||||
|
Remarks = m.Remarks,
|
||||||
|
Status = m.Status,
|
||||||
|
CreatedBy = m.CreatedBy,
|
||||||
|
CreatedDate = m.CreatedDate,
|
||||||
|
ApprovedBy = m.ApprovedBy,
|
||||||
|
ApprovedDate = m.ApprovedDate
|
||||||
|
})
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return new MRSPagedResult { Data = data, RecordsTotal = total };
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> GenerateMRSNoAsync()
|
||||||
|
{
|
||||||
|
var year = DateTime.Now.Year;
|
||||||
|
var month = DateTime.Now.Month.ToString("D2");
|
||||||
|
var count = await _db.MRS.CountAsync(m => m.CreatedDate.Year == year) + 1;
|
||||||
|
return $"MRS-{year}{month}-{count:D4}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
307
CPRNIMS.Domain/Services/Inventory/RIS.cs
Normal file
307
CPRNIMS.Domain/Services/Inventory/RIS.cs
Normal file
@ -0,0 +1,307 @@
|
|||||||
|
using CPRNIMS.Domain.Contracts.Inventory;
|
||||||
|
using CPRNIMS.Domain.UIServices.Inventory;
|
||||||
|
using CPRNIMS.Infrastructure.Database;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory.Request;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory.Response;
|
||||||
|
using CPRNIMS.Infrastructure.Entities.Inventory;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Domain.Services.Inventory
|
||||||
|
{
|
||||||
|
public class RIS : IRIS
|
||||||
|
{
|
||||||
|
private readonly NonInventoryDbContext _db;
|
||||||
|
|
||||||
|
public RIS(NonInventoryDbContext db) => _db = db;
|
||||||
|
|
||||||
|
public async Task<Infrastructure.Entities.Inventory.RIS> CreateAsync(CreateRISRequest dto, string createdBy, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var strategy = _db.Database.CreateExecutionStrategy();
|
||||||
|
|
||||||
|
return await strategy.ExecuteAsync(async () =>
|
||||||
|
{
|
||||||
|
await using var tx = await _db.Database.BeginTransactionAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var inventory = await _db.Inventories
|
||||||
|
.FirstOrDefaultAsync(i => i.InventoryId == dto.InventoryId, ct)
|
||||||
|
?? throw new InvalidOperationException("Inventory record not found.");
|
||||||
|
|
||||||
|
if (inventory.QtyOnHand < dto.QtyIssued)
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Insufficient stock. On hand: {inventory.QtyOnHand}, requested: {dto.QtyIssued}.");
|
||||||
|
|
||||||
|
var risNo = await GenerateRISNoAsync(ct);
|
||||||
|
|
||||||
|
var ris = new Infrastructure.Entities.Inventory.RIS
|
||||||
|
{
|
||||||
|
RISNo = risNo,
|
||||||
|
InventoryId = dto.InventoryId,
|
||||||
|
PRDetailId = dto.PRDetailId,
|
||||||
|
IssuedTo = dto.IssuedTo,
|
||||||
|
DisciplineId = dto.DisciplineId,
|
||||||
|
QtyIssued = dto.QtyIssued,
|
||||||
|
Remarks = dto.Remarks,
|
||||||
|
Status = 0,
|
||||||
|
CreatedBy = createdBy,
|
||||||
|
CreatedDate = DateTime.Now
|
||||||
|
};
|
||||||
|
_db.RIS.Add(ris);
|
||||||
|
await _db.SaveChangesAsync(ct);
|
||||||
|
|
||||||
|
var trans = await _db.InventTrans
|
||||||
|
.Where(t => t.InventoryId == dto.InventoryId && t.IsActive == true)
|
||||||
|
.FirstOrDefaultAsync(ct)
|
||||||
|
?? throw new InvalidOperationException(
|
||||||
|
"No active InventTrans found for this inventory record.");
|
||||||
|
|
||||||
|
_db.InventTransDetails.Add(new InventTransDetail
|
||||||
|
{
|
||||||
|
InventTransId = trans.InventTransId,
|
||||||
|
TransTypeId = 5,
|
||||||
|
PRDetailId = dto.PRDetailId,
|
||||||
|
QtyOut = dto.QtyIssued,
|
||||||
|
CreatedDate = DateTime.Now,
|
||||||
|
Remarks = $"RIS: {risNo}",
|
||||||
|
IsActive = true
|
||||||
|
});
|
||||||
|
|
||||||
|
inventory.QtyOut = Math.Max(0m, inventory.QtyOut) + dto.QtyIssued;
|
||||||
|
inventory.QtyOnHand = Math.Max(0m, inventory.QtyIn) - (decimal)inventory.QtyOut;
|
||||||
|
|
||||||
|
await _db.SaveChangesAsync(ct);
|
||||||
|
await tx.CommitAsync(ct);
|
||||||
|
|
||||||
|
return ris;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await tx.RollbackAsync(ct);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ApproveAsync(ApproveRISRequest request, string approvedBy, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var ris = await _db.RIS.FindAsync(request.RISId, ct)
|
||||||
|
?? throw new InvalidOperationException("RIS not found.");
|
||||||
|
|
||||||
|
if (ris.Status != 0)
|
||||||
|
throw new InvalidOperationException("Only Draft RIS records can be approved.");
|
||||||
|
|
||||||
|
ris.Status = 1; // Approved
|
||||||
|
ris.ApprovedBy = approvedBy;
|
||||||
|
ris.ApprovedDate = DateTime.Now;
|
||||||
|
|
||||||
|
await _db.SaveChangesAsync(ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CancelAsync(CancelRISRequest request, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var strategy = _db.Database.CreateExecutionStrategy();
|
||||||
|
|
||||||
|
await strategy.ExecuteAsync(async () =>
|
||||||
|
{
|
||||||
|
await using var tx = await _db.Database.BeginTransactionAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var ris = await _db.RIS
|
||||||
|
.Include(r => r.Inventory)
|
||||||
|
.FirstOrDefaultAsync(r => r.RISId == request.RISId, ct)
|
||||||
|
?? throw new InvalidOperationException("RIS not found.");
|
||||||
|
|
||||||
|
if (ris.Status == 2)
|
||||||
|
throw new InvalidOperationException("RIS is already cancelled.");
|
||||||
|
|
||||||
|
ris.Inventory.QtyOut = Math.Max(0m, ris.Inventory.QtyOut - ris.QtyIssued);
|
||||||
|
ris.Inventory.QtyOnHand = ris.Inventory.QtyIn - ris.Inventory.QtyOut;
|
||||||
|
ris.Reason = request.Reason;
|
||||||
|
ris.Status = 2; // Cancelled
|
||||||
|
|
||||||
|
var trans = await _db.InventTrans
|
||||||
|
.Where(t => t.InventoryId == ris.InventoryId && t.IsActive == true)
|
||||||
|
.FirstOrDefaultAsync(ct)
|
||||||
|
?? throw new InvalidOperationException(
|
||||||
|
"No active InventTrans found for this inventory record.");
|
||||||
|
|
||||||
|
_db.InventTransDetails.Add(new InventTransDetail
|
||||||
|
{
|
||||||
|
InventTransId = trans.InventTransId,
|
||||||
|
TransTypeId = 5,
|
||||||
|
PRDetailId = ris.PRDetailId,
|
||||||
|
QtyIn = ris.QtyIssued,//Return the issued Qty
|
||||||
|
CreatedDate = DateTime.Now,
|
||||||
|
Remarks = request.Reason,
|
||||||
|
IsActive = true
|
||||||
|
});
|
||||||
|
|
||||||
|
//var inventory = await _db.Inventories
|
||||||
|
// .FirstOrDefaultAsync(i => i.InventoryId == ris.InventoryId, ct)
|
||||||
|
// ?? throw new InvalidOperationException("Inventory record not found.");
|
||||||
|
|
||||||
|
//if (inventory.QtyOnHand < ris.QtyIssued)
|
||||||
|
// throw new InvalidOperationException(
|
||||||
|
// $"Insufficient stock. On hand: {inventory.QtyOnHand}, requested: {ris.QtyIssued}.");
|
||||||
|
|
||||||
|
////restore the QtyOnHand using ris.QtyIssued
|
||||||
|
//inventory.QtyOnHand = Math.Max(0m, inventory.QtyOnHand) + ris.QtyIssued;
|
||||||
|
////reduce the QtyOut using ris.QtyIssued as we cancel the return isuance slip
|
||||||
|
//inventory.QtyOut = Math.Max(0m, inventory.QtyOut) - ris.QtyIssued;
|
||||||
|
|
||||||
|
await _db.SaveChangesAsync(ct);
|
||||||
|
await tx.CommitAsync(ct);
|
||||||
|
|
||||||
|
return ris;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await tx.RollbackAsync(ct);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> GenerateRISNoAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
var year = DateTime.Now.Year;
|
||||||
|
var month = DateTime.Now.Month.ToString("D2");
|
||||||
|
var count = await _db.RIS
|
||||||
|
.CountAsync(r => r.CreatedDate.Year == year,ct) + 1;
|
||||||
|
return $"RIS-{year}{month}-{count:D4}"; // e.g. RIS-202606-0001
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RISPagedResult> GetPagedAsync(RISFilterDto filter, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var q = _db.RIS
|
||||||
|
.Include(r => r.Discipline)
|
||||||
|
.Include(r => r.Inventory)
|
||||||
|
.Include(r => r.MaterialReturns)
|
||||||
|
.AsQueryable();
|
||||||
|
|
||||||
|
// Status filter (default to Draft=0 if null)
|
||||||
|
if (filter.Status.HasValue)
|
||||||
|
q = q.Where(r => r.Status == filter.Status.Value);
|
||||||
|
else
|
||||||
|
q = q.Where(r => r.Status == 0);
|
||||||
|
|
||||||
|
// RIS No
|
||||||
|
if (!string.IsNullOrWhiteSpace(filter.SearchRISNo))
|
||||||
|
q = q.Where(r => r.RISNo.Contains(filter.SearchRISNo));
|
||||||
|
|
||||||
|
// Item Name
|
||||||
|
if (!string.IsNullOrWhiteSpace(filter.SearchItemName))
|
||||||
|
q = q.Where(r => r.Inventory.InventTrans
|
||||||
|
.SelectMany(t => t.InventTransDetails)
|
||||||
|
.Any(d => d.PRDetails != null &&
|
||||||
|
d.PRDetails.ItemName.Contains(filter.SearchItemName)));
|
||||||
|
|
||||||
|
// Issued To
|
||||||
|
if (!string.IsNullOrWhiteSpace(filter.SearchIssuedTo))
|
||||||
|
q = q.Where(r => r.IssuedTo.Contains(filter.SearchIssuedTo));
|
||||||
|
|
||||||
|
// Discipline
|
||||||
|
if (!string.IsNullOrWhiteSpace(filter.Discipline))
|
||||||
|
q = q.Where(r => r.Discipline.DisciplineName == filter.Discipline);
|
||||||
|
|
||||||
|
// Date range (if you ever add date filters)
|
||||||
|
if (filter.DateFrom.HasValue)
|
||||||
|
q = q.Where(r => r.CreatedDate >= filter.DateFrom.Value);
|
||||||
|
if (filter.DateTo.HasValue)
|
||||||
|
q = q.Where(r => r.CreatedDate <= filter.DateTo.Value.AddDays(1));
|
||||||
|
|
||||||
|
var total = await q.CountAsync(ct);
|
||||||
|
|
||||||
|
var data = await q
|
||||||
|
.OrderByDescending(r => r.CreatedDate)
|
||||||
|
.Skip((filter.PageNumber - 1) * filter.PageSize)
|
||||||
|
.Take(filter.PageSize)
|
||||||
|
.Select(r => new RISResponse
|
||||||
|
{
|
||||||
|
RISId = r.RISId,
|
||||||
|
RISNo = r.RISNo,
|
||||||
|
InventoryId = r.InventoryId,
|
||||||
|
ItemName = r.Inventory.InventTrans
|
||||||
|
.SelectMany(t => t.InventTransDetails)
|
||||||
|
.Select(d => d.PRDetails != null ? d.PRDetails.ItemName : "—")
|
||||||
|
.FirstOrDefault() ?? "—",
|
||||||
|
ItemNo = r.Inventory.ItemNo,
|
||||||
|
LotNo = r.Inventory.Lot != null ? r.Inventory.Lot.LotName : null,
|
||||||
|
IssuedTo = r.IssuedTo,
|
||||||
|
DisciplineName = r.Discipline.DisciplineName,
|
||||||
|
DisciplineId = r.DisciplineId,
|
||||||
|
QtyIssued = r.QtyIssued,
|
||||||
|
Remarks = r.Remarks,
|
||||||
|
Status = r.Status,
|
||||||
|
CreatedBy = r.CreatedBy,
|
||||||
|
CreatedDate = r.CreatedDate,
|
||||||
|
ApprovedBy = r.ApprovedBy,
|
||||||
|
ApprovedDate = r.ApprovedDate,
|
||||||
|
MRSCount = r.MaterialReturns.Count(m => m.Status != 2),
|
||||||
|
TotalReturned = r.MaterialReturns
|
||||||
|
.Where(m => m.Status != 2)
|
||||||
|
.Sum(m => m.QtyReturned)
|
||||||
|
})
|
||||||
|
.ToListAsync(ct);
|
||||||
|
|
||||||
|
// Full department list (never filtered)
|
||||||
|
var departments = await _db.Departments
|
||||||
|
.Select(d => d.Department)
|
||||||
|
.OrderBy(d => d)
|
||||||
|
.ToListAsync(ct);
|
||||||
|
|
||||||
|
var disciplines = await GetDisciplinesAsync(ct);
|
||||||
|
|
||||||
|
return new RISPagedResult
|
||||||
|
{
|
||||||
|
Data = data,
|
||||||
|
RecordsTotal = total,
|
||||||
|
DepartmentList = departments,
|
||||||
|
DisciplineList = disciplines
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public async Task<IEnumerable<DisciplineDto>> GetDisciplinesAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
return await _db.Disciplines
|
||||||
|
.OrderBy(d => d.DisciplineName)
|
||||||
|
.Select(d => new DisciplineDto
|
||||||
|
{
|
||||||
|
DisciplineId = d.DisciplineId,
|
||||||
|
DisciplineName = d.DisciplineName
|
||||||
|
})
|
||||||
|
.ToListAsync(ct);
|
||||||
|
}
|
||||||
|
public async Task<RISResponse?> GetByIdAsync(long risId, CancellationToken ct)
|
||||||
|
{
|
||||||
|
return await _db.RIS
|
||||||
|
.Where(r => r.RISId == risId)
|
||||||
|
.Select(r => new RISResponse
|
||||||
|
{
|
||||||
|
RISId = r.RISId,
|
||||||
|
RISNo = r.RISNo,
|
||||||
|
InventoryId = r.InventoryId,
|
||||||
|
IssuedTo = r.IssuedTo,
|
||||||
|
DisciplineName = r.Discipline.DisciplineName,
|
||||||
|
DisciplineId = r.DisciplineId,
|
||||||
|
QtyIssued = r.QtyIssued,
|
||||||
|
Remarks = r.Remarks,
|
||||||
|
Status = r.Status,
|
||||||
|
CreatedBy = r.CreatedBy,
|
||||||
|
CreatedDate = r.CreatedDate,
|
||||||
|
ApprovedBy = r.ApprovedBy,
|
||||||
|
ApprovedDate = r.ApprovedDate,
|
||||||
|
MRSCount = r.MaterialReturns.Count(m => m.Status != 2),
|
||||||
|
TotalReturned = r.MaterialReturns
|
||||||
|
.Where(m => m.Status != 2)
|
||||||
|
.Sum(m => m.QtyReturned)
|
||||||
|
})
|
||||||
|
.FirstOrDefaultAsync(ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,6 @@
|
|||||||
using CPRNIMS.Infrastructure.Dto.Inventory;
|
using CPRNIMS.Infrastructure.Dto.Inventory;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory.Request;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory.Response;
|
||||||
using CPRNIMS.Infrastructure.Models.Account;
|
using CPRNIMS.Infrastructure.Models.Account;
|
||||||
using CPRNIMS.Infrastructure.ViewModel.Inventory;
|
using CPRNIMS.Infrastructure.ViewModel.Inventory;
|
||||||
using System;
|
using System;
|
||||||
@ -11,12 +13,15 @@ namespace CPRNIMS.Domain.UIContracts.Inventory
|
|||||||
{
|
{
|
||||||
public interface IInventory
|
public interface IInventory
|
||||||
{
|
{
|
||||||
|
Task<TransactContextDto?> GetTransactContextAsync(int inventoryId);
|
||||||
Task<List<InventoryVM>> GetInventoryByUserId(User user, InventoryVM viewModel);
|
Task<List<InventoryVM>> GetInventoryByUserId(User user, InventoryVM viewModel);
|
||||||
Task<List<InventoryVM>> GetRequestedItemByUserId(User user, InventoryVM viewModel);
|
Task<List<InventoryVM>> GetRequestedItemByUserId(User user, InventoryVM viewModel);
|
||||||
Task<List<InventoryVM>> GetInventoryById(User user, InventoryVM viewModel);
|
Task<List<InventoryVM>> GetInventoryById(User user, InventoryVM viewModel);
|
||||||
Task<List<InventoryVM>> GetLotNo(User user, InventoryVM viewModel);
|
Task<List<InventoryVM>> GetLotNo(User user, InventoryVM viewModel);
|
||||||
Task<List<InventoryVM>> GetLotQtyByItem(User user, InventoryVM viewModel);
|
Task<List<InventoryVM>> GetLotQtyByItem(User user, InventoryVM viewModel);
|
||||||
Task<List<InventoryVM>> GetLotNoById(User user, InventoryVM viewModel);
|
Task<List<InventoryVM>> GetLotNoById(User user, InventoryVM viewModel);
|
||||||
|
Task<PagedResult<InventoryResponse>> GetInventory(User user, InventoryRequest request);
|
||||||
|
|
||||||
Task<InventoryVM> PostPutLotNo(User user, InventoryVM viewModel);
|
Task<InventoryVM> PostPutLotNo(User user, InventoryVM viewModel);
|
||||||
Task<InventoryVM> PostPutLotBin(User user, InventoryVM viewModel);
|
Task<InventoryVM> PostPutLotBin(User user, InventoryVM viewModel);
|
||||||
Task<InventoryVM> PostPutReqApproval(User user, InventoryVM viewModel);
|
Task<InventoryVM> PostPutReqApproval(User user, InventoryVM viewModel);
|
||||||
|
|||||||
20
CPRNIMS.Domain/UIContracts/Inventory/IRIS.cs
Normal file
20
CPRNIMS.Domain/UIContracts/Inventory/IRIS.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory.Request;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory.Response;
|
||||||
|
using CPRNIMS.Infrastructure.ViewModel.Inventory;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Domain.UIContracts.Inventory
|
||||||
|
{
|
||||||
|
public interface IRIS
|
||||||
|
{
|
||||||
|
Task<bool> ApproveRIS(ApproveRISRequest request, CancellationToken ct);
|
||||||
|
Task<bool> CancelRIS(CancelRISRequest request, CancellationToken ct);
|
||||||
|
Task<RISResponse> CreateRIS(CreateRISRequest request, CancellationToken ct);
|
||||||
|
Task<RISPagedResponse> GetRISById(int risId, CancellationToken ct);
|
||||||
|
Task<RISPagedResponse> GetRISPaged(RISPagedRequest request, CancellationToken ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,9 +1,11 @@
|
|||||||
using CPRNIMS.Domain.UIContracts.Common;
|
using CPRNIMS.Domain.UIContracts.Common;
|
||||||
using CPRNIMS.Domain.UIContracts.Inventory;
|
using CPRNIMS.Domain.UIContracts.Inventory;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory.Request;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory.Response;
|
||||||
using CPRNIMS.Infrastructure.Helper;
|
using CPRNIMS.Infrastructure.Helper;
|
||||||
using CPRNIMS.Infrastructure.Models.Account;
|
using CPRNIMS.Infrastructure.Models.Account;
|
||||||
using CPRNIMS.Infrastructure.Models.Common;
|
using CPRNIMS.Infrastructure.Models.Common;
|
||||||
using CPRNIMS.Infrastructure.ViewModel.Finance;
|
|
||||||
using CPRNIMS.Infrastructure.ViewModel.Inventory;
|
using CPRNIMS.Infrastructure.ViewModel.Inventory;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using System;
|
using System;
|
||||||
@ -123,6 +125,73 @@ namespace CPRNIMS.Domain.UIServices.Inventory
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public async Task<TransactContextDto?> GetTransactContextAsync(int inventoryId)
|
||||||
|
{
|
||||||
|
var token = await _tokenHelper.GetValidTokenAsync();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(token))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var apiEndpoint =
|
||||||
|
_configuration["LLI:NonInvent:InventoryMgmt:GetTransactContext"] ?? "api/InventoryMgmt/GetTransactContext";
|
||||||
|
|
||||||
|
using var httpClient = _apiConfigurationService.CreateHttpClientWithDefaultHeaders(token);
|
||||||
|
|
||||||
|
var response = await httpClient.GetAsync( $"{apiEndpoint}?inventoryId={inventoryId}");
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var jsonResponse = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
return JsonSerializer.Deserialize<TransactContextDto>(jsonResponse,
|
||||||
|
new JsonSerializerOptions{PropertyNameCaseInsensitive = true});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Infrastructure.Dto.Inventory.PagedResult<InventoryResponse>> SendGetPageListApiRequest
|
||||||
|
(User user, InventoryRequest request, string apiEndpoint)
|
||||||
|
{
|
||||||
|
var token = await _tokenHelper.GetValidTokenAsync();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(token))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
request.UserId = user.UserId;
|
||||||
|
var jsonContent = JsonSerializer.Serialize(request);
|
||||||
|
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
|
using (var httpClient = _apiConfigurationService.CreateHttpClientWithDefaultHeaders(token))
|
||||||
|
{
|
||||||
|
HttpResponseMessage response;
|
||||||
|
|
||||||
|
response = await httpClient.PostAsync(apiEndpoint, content);
|
||||||
|
var jsonResponse = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
|
||||||
|
var result = JsonSerializer.Deserialize<PagedResult<InventoryResponse>>(jsonResponse, options);
|
||||||
|
return result ?? new PagedResult<InventoryResponse>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new PagedResult<InventoryResponse>
|
||||||
|
{
|
||||||
|
IsSuccess = false,
|
||||||
|
ErrorMessage = $"API returned {response.StatusCode}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
#region Get
|
#region Get
|
||||||
public async Task<List<InventoryVM>> GetInventoryById(User user, InventoryVM viewModel)
|
public async Task<List<InventoryVM>> GetInventoryById(User user, InventoryVM viewModel)
|
||||||
@ -141,7 +210,11 @@ namespace CPRNIMS.Domain.UIServices.Inventory
|
|||||||
return await SendGetApiRequest(user, viewModel,
|
return await SendGetApiRequest(user, viewModel,
|
||||||
_configuration["LLI:NonInvent:InventoryMgmt:GetLotNoById"]);
|
_configuration["LLI:NonInvent:InventoryMgmt:GetLotNoById"]);
|
||||||
}
|
}
|
||||||
|
public async Task<Infrastructure.Dto.Inventory.PagedResult<InventoryResponse>> GetInventory(User user, InventoryRequest reqquest)
|
||||||
|
{
|
||||||
|
return await SendGetPageListApiRequest(user, reqquest,
|
||||||
|
_configuration["LLI:NonInvent:InventoryMgmt:GetInventory"]);
|
||||||
|
}
|
||||||
public async Task<List<InventoryVM>> GetInventoryByUserId(User user, InventoryVM viewModel)
|
public async Task<List<InventoryVM>> GetInventoryByUserId(User user, InventoryVM viewModel)
|
||||||
{
|
{
|
||||||
return await SendGetApiRequest(user, viewModel,
|
return await SendGetApiRequest(user, viewModel,
|
||||||
@ -153,7 +226,11 @@ namespace CPRNIMS.Domain.UIServices.Inventory
|
|||||||
return await SendGetApiRequest(user, viewModel,
|
return await SendGetApiRequest(user, viewModel,
|
||||||
_configuration["LLI:NonInvent:InventoryMgmt:GetRequestedItemByUserId"]);
|
_configuration["LLI:NonInvent:InventoryMgmt:GetRequestedItemByUserId"]);
|
||||||
}
|
}
|
||||||
|
public async Task<List<InventoryVM>> GetLotQtyByItem(User user, InventoryVM viewModel)
|
||||||
|
{
|
||||||
|
return await SendGetApiRequest(user, viewModel,
|
||||||
|
_configuration["LLI:NonInvent:InventoryMgmt:GetLotQtyByItem"]);
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
#region Post Put
|
#region Post Put
|
||||||
public async Task<InventoryVM> PostPutReqApproval(User user, InventoryVM viewModel)
|
public async Task<InventoryVM> PostPutReqApproval(User user, InventoryVM viewModel)
|
||||||
@ -177,12 +254,6 @@ namespace CPRNIMS.Domain.UIServices.Inventory
|
|||||||
return await SendPostApiRequest(user, viewModel,
|
return await SendPostApiRequest(user, viewModel,
|
||||||
_configuration["LLI:NonInvent:InventoryMgmt:PostPutReqItems"]);
|
_configuration["LLI:NonInvent:InventoryMgmt:PostPutReqItems"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<InventoryVM>> GetLotQtyByItem(User user, InventoryVM viewModel)
|
|
||||||
{
|
|
||||||
return await SendGetApiRequest(user, viewModel,
|
|
||||||
_configuration["LLI:NonInvent:InventoryMgmt:GetLotQtyByItem"]);
|
|
||||||
}
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
170
CPRNIMS.Domain/UIServices/Inventory/RIS.cs
Normal file
170
CPRNIMS.Domain/UIServices/Inventory/RIS.cs
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
using CPRNIMS.Domain.UIContracts.Common;
|
||||||
|
using CPRNIMS.Domain.UIContracts.Inventory;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Common;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory.Request;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory.Response;
|
||||||
|
using CPRNIMS.Infrastructure.Helper;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Domain.UIServices.Inventory
|
||||||
|
{
|
||||||
|
public class RIS : IRIS
|
||||||
|
{
|
||||||
|
private readonly IConfiguration _configuration;
|
||||||
|
private readonly TokenHelper _tokenHelper;
|
||||||
|
private readonly IApiConfigurationService _apiConfigurationService;
|
||||||
|
public RIS(IConfiguration configuration, TokenHelper tokenHelper,
|
||||||
|
IApiConfigurationService apiConfigurationService)
|
||||||
|
{
|
||||||
|
_configuration = configuration;
|
||||||
|
_tokenHelper = tokenHelper;
|
||||||
|
_apiConfigurationService = apiConfigurationService;
|
||||||
|
}
|
||||||
|
#region Get
|
||||||
|
public async Task<RISPagedResponse> GetRISPaged(RISPagedRequest request, CancellationToken ct)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var token = await _tokenHelper.GetValidTokenAsync();
|
||||||
|
if (string.IsNullOrEmpty(token))
|
||||||
|
throw new InvalidOperationException("Token has been expired.");
|
||||||
|
|
||||||
|
var baseEndpoint = _configuration["LLI:NonInvent:InventoryMgmt:GetRIS"]
|
||||||
|
?? throw new InvalidOperationException("GetRIS endpoint is not configured.");
|
||||||
|
|
||||||
|
// ── Build query string — GET request, no body ──
|
||||||
|
var qs = new StringBuilder(baseEndpoint).Append('?');
|
||||||
|
qs.Append($"pageNumber={request.PageNumber}");
|
||||||
|
qs.Append($"&pageSize={request.PageSize}");
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(request.SearchRISNo))
|
||||||
|
qs.Append($"&searchRISNo={Uri.EscapeDataString(request.SearchRISNo)}");
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(request.SearchItemName))
|
||||||
|
qs.Append($"&searchItemName={Uri.EscapeDataString(request.SearchItemName)}");
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(request.SearchIssuedTo))
|
||||||
|
qs.Append($"&searchIssuedTo={Uri.EscapeDataString(request.SearchIssuedTo)}");
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(request.Discipline))
|
||||||
|
qs.Append($"&discipline={Uri.EscapeDataString(request.Discipline)}");
|
||||||
|
|
||||||
|
if (request.Status.HasValue)
|
||||||
|
qs.Append($"&status={request.Status.Value}");
|
||||||
|
|
||||||
|
using var httpClient = _apiConfigurationService.CreateHttpClientWithDefaultHeaders(token);
|
||||||
|
|
||||||
|
// ── GET, no content body ──
|
||||||
|
var response = await httpClient.GetAsync(qs.ToString(), ct);
|
||||||
|
var json = await response.Content.ReadAsStringAsync(ct);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[GetRISPaged] Error {(int)response.StatusCode}: {json}");
|
||||||
|
return new RISPagedResponse { Data = [], RecordsTotal = 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = JsonSerializer.Deserialize<RISPagedResponse>(json, _jsonOptions);
|
||||||
|
return result ?? new RISPagedResponse();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ex.ToString();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
#region Post Put
|
||||||
|
public async Task<bool> ApproveRIS(ApproveRISRequest request, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var token = await _tokenHelper.GetValidTokenAsync();
|
||||||
|
if (string.IsNullOrEmpty(token))
|
||||||
|
throw new InvalidOperationException("Token has been expired.");
|
||||||
|
|
||||||
|
var endpoint = _configuration["LLI:NonInvent:InventoryMgmt:ApproveRIS"]
|
||||||
|
?? throw new InvalidOperationException("ApproveRIS endpoint is not configured.");
|
||||||
|
|
||||||
|
using var httpClient = _apiConfigurationService.CreateHttpClientWithDefaultHeaders(token);
|
||||||
|
using var content = new StringContent(JsonSerializer.Serialize(request),Encoding.UTF8,"application/json");
|
||||||
|
|
||||||
|
var response = await httpClient.PutAsync(endpoint, content, ct);
|
||||||
|
var json = await response.Content.ReadAsStringAsync(ct);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> CancelRIS(CancelRISRequest request, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var token = await _tokenHelper.GetValidTokenAsync();
|
||||||
|
if (string.IsNullOrEmpty(token))
|
||||||
|
throw new InvalidOperationException("Token has been expired.");
|
||||||
|
|
||||||
|
var endpoint = _configuration["LLI:NonInvent:InventoryMgmt:CancelRIS"]
|
||||||
|
?? throw new InvalidOperationException("CancelRIS endpoint is not configured.");
|
||||||
|
|
||||||
|
using var httpClient = _apiConfigurationService.CreateHttpClientWithDefaultHeaders(token);
|
||||||
|
using var content = new StringContent(
|
||||||
|
JsonSerializer.Serialize(request),Encoding.UTF8,"application/json");
|
||||||
|
|
||||||
|
var response = await httpClient.PutAsync(endpoint, content, ct);
|
||||||
|
var json = await response.Content.ReadAsStringAsync(ct);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public async Task<RISResponse> CreateRIS(CreateRISRequest request, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var token = await _tokenHelper.GetValidTokenAsync();
|
||||||
|
if (string.IsNullOrEmpty(token))
|
||||||
|
throw new InvalidOperationException("Token has been expired.");
|
||||||
|
|
||||||
|
|
||||||
|
var endpoint = _configuration["LLI:NonInvent:InventoryMgmt:CreateRIS"] ??
|
||||||
|
throw new InvalidOperationException("CreateRIS endpoint is not configured.");
|
||||||
|
|
||||||
|
using var httpClient = _apiConfigurationService.CreateHttpClientWithDefaultHeaders(token);
|
||||||
|
using var content = new StringContent(
|
||||||
|
JsonSerializer.Serialize(request),
|
||||||
|
Encoding.UTF8,
|
||||||
|
"application/json");
|
||||||
|
|
||||||
|
var response = await httpClient.PostAsync(endpoint, content,ct);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
return new RISResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
var json = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
var result = JsonSerializer.Deserialize<ApiResponse<RISResponse>>(json, _jsonOptions);
|
||||||
|
return result?.data ?? new RISResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<RISPagedResponse> GetRISById(int risId, CancellationToken ct)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly JsonSerializerOptions _jsonOptions = new()
|
||||||
|
{
|
||||||
|
PropertyNameCaseInsensitive = true
|
||||||
|
};
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
using CPRNIMS.Infrastructure.Dto.Canvass.Response;
|
using CPRNIMS.Infrastructure.Dto.Canvass.Response;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory.Response;
|
||||||
using CPRNIMS.Infrastructure.Entities.Account;
|
using CPRNIMS.Infrastructure.Entities.Account;
|
||||||
using CPRNIMS.Infrastructure.Entities.Canvass;
|
using CPRNIMS.Infrastructure.Entities.Canvass;
|
||||||
using CPRNIMS.Infrastructure.Entities.Common;
|
using CPRNIMS.Infrastructure.Entities.Common;
|
||||||
@ -22,9 +23,7 @@ namespace CPRNIMS.Infrastructure.Database
|
|||||||
public class NonInventoryDbContext : IdentityDbContext<ApplicationUser>
|
public class NonInventoryDbContext : IdentityDbContext<ApplicationUser>
|
||||||
{
|
{
|
||||||
public NonInventoryDbContext(DbContextOptions<NonInventoryDbContext> options) : base(options) { }
|
public NonInventoryDbContext(DbContextOptions<NonInventoryDbContext> options) : base(options) { }
|
||||||
public virtual DbSet<ItemCode> ItemCodes { get; set; }
|
#region Common
|
||||||
public virtual DbSet<ItemList> ItemList { get; set; }
|
|
||||||
public virtual DbSet<Item> Items { get; set; }
|
|
||||||
public DbSet<Departments> Departments { get; set; }
|
public DbSet<Departments> Departments { get; set; }
|
||||||
public DbSet<IdentityRole> IdentityRoles { get; set; }
|
public DbSet<IdentityRole> IdentityRoles { get; set; }
|
||||||
public DbSet<AuthorizeRoles> AuthorizeRoles { get; set; }
|
public DbSet<AuthorizeRoles> AuthorizeRoles { get; set; }
|
||||||
@ -32,39 +31,51 @@ namespace CPRNIMS.Infrastructure.Database
|
|||||||
public DbSet<IdentityUserRole<string>> IdentityUserRoles { get; set; }
|
public DbSet<IdentityUserRole<string>> IdentityUserRoles { get; set; }
|
||||||
public DbSet<ForgotPassword> ForgotPasswords { get; set; }
|
public DbSet<ForgotPassword> ForgotPasswords { get; set; }
|
||||||
public virtual DbSet<Otps> Otps { get; set; }
|
public virtual DbSet<Otps> Otps { get; set; }
|
||||||
|
public virtual DbSet<ErrorMessage> ErrorMessages { get; set; }
|
||||||
|
public virtual DbSet<ControllerAccess> ControllerAccess { get; set; }
|
||||||
|
public virtual DbSet<SMTPCredential> SMTPCredentials { get; set; }
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Item
|
||||||
|
public virtual DbSet<ItemCode> ItemCodes { get; set; }
|
||||||
|
public virtual DbSet<ItemList> ItemList { get; set; }
|
||||||
|
public virtual DbSet<Item> Items { get; set; }
|
||||||
public DbSet<Attachment> Attachments { get; set; }
|
public DbSet<Attachment> Attachments { get; set; }
|
||||||
public virtual DbSet<AttachmentExtension> AttachmentExtensions { get; set; }
|
public virtual DbSet<AttachmentExtension> AttachmentExtensions { get; set; }
|
||||||
public virtual DbSet<AttachmentFileType> AttachmentFileTypes { get; set; }
|
public virtual DbSet<AttachmentFileType> AttachmentFileTypes { get; set; }
|
||||||
public virtual DbSet<ItemAttachement> ItemAttachements { get; set; }
|
public virtual DbSet<ItemAttachement> ItemAttachements { get; set; }
|
||||||
public virtual DbSet<ErrorMessage> ErrorMessages { get; set; }
|
|
||||||
public virtual DbSet<ControllerAccess> ControllerAccess { get; set; }
|
|
||||||
public virtual DbSet<ItemCategory> ItemCategories { get; set; }
|
public virtual DbSet<ItemCategory> ItemCategories { get; set; }
|
||||||
public virtual DbSet<UnitOfMessure> UnitOfMessures { get; set; }
|
public virtual DbSet<UnitOfMessure> UnitOfMessures { get; set; }
|
||||||
public virtual DbSet<ItemColor> ItemColors { get; set; }
|
public virtual DbSet<ItemColor> ItemColors { get; set; }
|
||||||
public virtual DbSet<ItemLocalization> ItemLocalizations { get; set; }
|
public virtual DbSet<ItemLocalization> ItemLocalizations { get; set; }
|
||||||
public virtual DbSet<ItemCart> ItemCarts { get; set; }
|
public virtual DbSet<ItemCart> ItemCarts { get; set; }
|
||||||
|
public virtual DbSet<ItemApproval> ItemApprovals { get; set; }
|
||||||
|
public virtual DbSet<Entities.Inventory.ItemDetail> ItemDetails { get; set; }
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region PR
|
||||||
public virtual DbSet<PR> PRs { get; set; }
|
public virtual DbSet<PR> PRs { get; set; }
|
||||||
public virtual DbSet<Approved> Approved { get; set; }
|
public virtual DbSet<Approved> Approved { get; set; }
|
||||||
public virtual DbSet<ApprovedPR> ApprovedPrs { get; set; }
|
public virtual DbSet<ApprovedPR> ApprovedPrs { get; set; }
|
||||||
public virtual DbSet<DeletedPR> DeletedPRs { get; set; }
|
public virtual DbSet<DeletedPR> DeletedPRs { get; set; }
|
||||||
public virtual DbSet<SMTPCredential> SMTPCredentials { get; set; }
|
|
||||||
public virtual DbSet<PRDetails> PRDetails { get; set; }
|
public virtual DbSet<PRDetails> PRDetails { get; set; }
|
||||||
public virtual DbSet<PRItemList> PRItemLists { get; set; }
|
public virtual DbSet<PRItemList> PRItemLists { get; set; }
|
||||||
public DbSet<PRAttachments> PRAttachments { get; set; }
|
public DbSet<PRAttachments> PRAttachments { get; set; }
|
||||||
public DbSet<ProjectCodes> ProjectCodes { get; set; }
|
public DbSet<ProjectCodes> ProjectCodes { get; set; }
|
||||||
|
public virtual DbSet<Entities.Purchasing.PRList> PRLists { get; set; }
|
||||||
|
public virtual DbSet<Entities.Canvass.PRList> PRItemList { get; set; }
|
||||||
|
public virtual DbSet<PRWOCanvass> PRWOCanvasses { get; set; }
|
||||||
|
public virtual DbSet<Entities.PO.ItemDetail> PRItemDetails { get; set; }
|
||||||
|
public virtual DbSet<DetailedPRTracking> DetailedPRTrackings { get; set; }
|
||||||
|
public virtual DbSet<PRTracking> PRTrackings { get; set; }
|
||||||
|
public virtual DbSet<Dashboard> Dashboards { get; set; }
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Canvassing
|
||||||
public virtual DbSet<NotificationById> NotificationByIds { get; set; }
|
public virtual DbSet<NotificationById> NotificationByIds { get; set; }
|
||||||
public virtual DbSet<AlternativeOffer> AlternativeOffers { get; set; }
|
public virtual DbSet<AlternativeOffer> AlternativeOffers { get; set; }
|
||||||
public virtual DbSet<AlternativeOfferDetails> AlternativeOfferDetails { get; set; }
|
public virtual DbSet<AlternativeOfferDetails> AlternativeOfferDetails { get; set; }
|
||||||
public virtual DbSet<MyPRWOCanvass> MyPRWOCanvass { get; set; }
|
public virtual DbSet<MyPRWOCanvass> MyPRWOCanvass { get; set; }
|
||||||
public virtual DbSet<Entities.Purchasing.PRList> PRLists { get; set; }
|
|
||||||
public virtual DbSet<Entities.Canvass.PRList> PRItemList { get; set; }
|
|
||||||
public virtual DbSet<ItemApproval> ItemApprovals { get; set; }
|
|
||||||
public virtual DbSet<ForReceiving> ForReceivings { get; set; }
|
|
||||||
public virtual DbSet<Infrastructure.Entities.Receiving.RRReport> RRReports { get; set; }
|
|
||||||
public virtual DbSet<ReceivingDetail> ReceivingDetails { get; set; }
|
|
||||||
public virtual DbSet<RRDetail> RRDetails { get; set; }
|
|
||||||
public virtual DbSet<ForRR> ForRRs { get; set; }
|
|
||||||
public virtual DbSet<RR> RRs { get; set; }
|
|
||||||
public virtual DbSet<Canvass> Canvasses { get; set; }
|
public virtual DbSet<Canvass> Canvasses { get; set; }
|
||||||
public DbSet<SupplierForCanvass> SupplierForCanvass { get; set; }
|
public DbSet<SupplierForCanvass> SupplierForCanvass { get; set; }
|
||||||
public DbSet<SupplierResponseDto> SupplierResponses { get; set; }
|
public DbSet<SupplierResponseDto> SupplierResponses { get; set; }
|
||||||
@ -87,13 +98,17 @@ namespace CPRNIMS.Infrastructure.Database
|
|||||||
public DbSet<ForAISearchingTagging> ForAISearchingTaggings { get; set; }
|
public DbSet<ForAISearchingTagging> ForAISearchingTaggings { get; set; }
|
||||||
public virtual DbSet<CanvassGroupByPRNo> CanvassGroupByPRNos { get; set; }
|
public virtual DbSet<CanvassGroupByPRNo> CanvassGroupByPRNos { get; set; }
|
||||||
public virtual DbSet<ForCanvass> ForCanvasses { get; set; }
|
public virtual DbSet<ForCanvass> ForCanvasses { get; set; }
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region PO
|
||||||
public virtual DbSet<ForPO> ForPOs { get; set; }
|
public virtual DbSet<ForPO> ForPOs { get; set; }
|
||||||
public virtual DbSet<CreatedPO> CreatedPOs { get; set; }
|
public virtual DbSet<CreatedPO> CreatedPOs { get; set; }
|
||||||
public virtual DbSet<CustomPO> CustomPOs { get; set; }
|
public virtual DbSet<CustomPO> CustomPOs { get; set; }
|
||||||
public virtual DbSet<IndexCard> IndexCards { get; set; }
|
public virtual DbSet<IndexCard> IndexCards { get; set; }
|
||||||
public virtual DbSet<ForPOApproval> ForPOApprovals { get; set; }
|
public virtual DbSet<ForPOApproval> ForPOApprovals { get; set; }
|
||||||
public virtual DbSet<ApprovedPO> ApprovedPOs { get; set; }
|
public virtual DbSet<ApprovedPO> ApprovedPOs { get; set; }
|
||||||
public virtual DbSet<PurchaseOrder> PurchaseOrders { get; set; }
|
public virtual DbSet<PurchaseOrder> PurchaseOrders { get; set; }
|
||||||
|
public virtual DbSet<ForPayment> ForPayments { get; set; }
|
||||||
public virtual DbSet<PO> POs { get; set; }
|
public virtual DbSet<PO> POs { get; set; }
|
||||||
public DbSet<PODetails> PODetails { get; set; }
|
public DbSet<PODetails> PODetails { get; set; }
|
||||||
public virtual DbSet<PRPOSummaryCount> PRPOSummaryCounts { get; set; }
|
public virtual DbSet<PRPOSummaryCount> PRPOSummaryCounts { get; set; }
|
||||||
@ -103,29 +118,42 @@ namespace CPRNIMS.Infrastructure.Database
|
|||||||
public virtual DbSet<PortOfDischarges> PortOfDischarges { get; set; }
|
public virtual DbSet<PortOfDischarges> PortOfDischarges { get; set; }
|
||||||
public virtual DbSet<DocRequired> DocRequireds { get; set; }
|
public virtual DbSet<DocRequired> DocRequireds { get; set; }
|
||||||
public virtual DbSet<BiddingApproval> BiddingApprovals { get; set; }
|
public virtual DbSet<BiddingApproval> BiddingApprovals { get; set; }
|
||||||
public virtual DbSet<Entities.Receiving.RRDetail> RRDetailss { get; set; }
|
|
||||||
public virtual DbSet<RRSeries> RRSeries { get; set; }
|
|
||||||
public virtual DbSet<PRWOCanvass> PRWOCanvasses { get; set; }
|
|
||||||
public virtual DbSet<ForPayment> ForPayments { get; set; }
|
|
||||||
public virtual DbSet<Inventory> Inventories { get; set; }
|
|
||||||
public virtual DbSet<Entities.Inventory.ItemDetail> ItemDetails { get; set; }
|
|
||||||
public virtual DbSet<Entities.PO.ItemDetail> PRItemDetails { get; set; }
|
|
||||||
public virtual DbSet<Lot> Lots { get; set; }
|
|
||||||
public virtual DbSet<LotQtyByItem> LotQtyByItems { get; set; }
|
|
||||||
public virtual DbSet<RequestItem> RequestItems { get; set; }
|
|
||||||
public virtual DbSet<RequestItemDetail> RequestItemDetails { get; set; }
|
|
||||||
public virtual DbSet<NotifUserKey> NotifUserKeys { get; set; }
|
public virtual DbSet<NotifUserKey> NotifUserKeys { get; set; }
|
||||||
public virtual DbSet<ItemListForPO> ItemListForPOs { get; set; }
|
public virtual DbSet<ItemListForPO> ItemListForPOs { get; set; }
|
||||||
public virtual DbSet<CreatedPOPerSupId> CreatedPOPerSupIds { get; set; }
|
public virtual DbSet<CreatedPOPerSupId> CreatedPOPerSupIds { get; set; }
|
||||||
public virtual DbSet<PRTracking> PRTrackings { get; set; }
|
public DbSet<IncomingShipment> IncomingShipments { get; set; }
|
||||||
public virtual DbSet<Dashboard> Dashboards { get; set; }
|
public virtual DbSet<IncomingShipmentDto> IncomingShipmentDtos { get; set; }
|
||||||
public virtual DbSet<PaymentTerm> PaymentTerms { get; set; }
|
public virtual DbSet<PaymentTerm> PaymentTerms { get; set; }
|
||||||
public virtual DbSet<Incoterm> Incoterms { get; set; }
|
public virtual DbSet<Incoterm> Incoterms { get; set; }
|
||||||
public virtual DbSet<CentralPONo> CentralPONos { get; set; }
|
public virtual DbSet<CentralPONo> CentralPONos { get; set; }
|
||||||
public virtual DbSet<DetailedPRTracking> DetailedPRTrackings { get; set; }
|
#endregion
|
||||||
public DbSet<IncomingShipment> IncomingShipments { get; set; }
|
|
||||||
public virtual DbSet<IncomingShipmentDto> IncomingShipmentDtos { get; set; }
|
#region Inventory
|
||||||
|
public virtual DbSet<Inventory> Inventories { get; set; }
|
||||||
|
public virtual DbSet<Lot> Lots { get; set; }
|
||||||
|
public virtual DbSet<LotType> LotTypes { get; set; }
|
||||||
|
public virtual DbSet<LotQtyByItem> LotQtyByItems { get; set; }
|
||||||
|
public virtual DbSet<RequestItem> RequestItems { get; set; }
|
||||||
|
public virtual DbSet<RequestItemDetail> RequestItemDetails { get; set; }
|
||||||
|
public virtual DbSet<RIS> RIS { get; set; }
|
||||||
|
public virtual DbSet<MRS> MRS { get; set; }
|
||||||
|
public virtual DbSet<Discipline> Disciplines { get; set; }
|
||||||
|
public virtual DbSet<InventTrans> InventTrans { get; set; }
|
||||||
|
public virtual DbSet<InventTransDetail> InventTransDetails { get; set; }
|
||||||
|
public DbSet<InventoryByIdResponse> InventoryByIdResponses { get; set; }
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region RR
|
||||||
|
public virtual DbSet<ForReceiving> ForReceivings { get; set; }
|
||||||
|
public virtual DbSet<Infrastructure.Entities.Receiving.RRReport> RRReports { get; set; }
|
||||||
|
public virtual DbSet<ReceivingDetail> ReceivingDetails { get; set; }
|
||||||
|
public virtual DbSet<RRDetail> RRDetails { get; set; }
|
||||||
|
public virtual DbSet<ForRR> ForRRs { get; set; }
|
||||||
|
public virtual DbSet<RR> RRs { get; set; }
|
||||||
|
public virtual DbSet<Entities.Receiving.RRDetail> RRDetailss { get; set; }
|
||||||
|
public virtual DbSet<RRSeries> RRSeries { get; set; }
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Automation Part
|
#region Automation Part
|
||||||
public virtual DbSet<AllForCanvass> AllForCanvasses { get; set; }
|
public virtual DbSet<AllForCanvass> AllForCanvasses { get; set; }
|
||||||
@ -236,6 +264,52 @@ namespace CPRNIMS.Infrastructure.Database
|
|||||||
{
|
{
|
||||||
b.ToTable("UserRoles");
|
b.ToTable("UserRoles");
|
||||||
});
|
});
|
||||||
|
modelBuilder.Entity<Inventory>()
|
||||||
|
.HasOne(i => i.Lot)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(i => i.LotId)
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
modelBuilder.Entity<InventTrans>(e =>
|
||||||
|
{
|
||||||
|
e.HasMany(r => r.InventTransDetails)
|
||||||
|
.WithOne()
|
||||||
|
.HasForeignKey(t => t.InventTransId)
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<InventTransDetail>(e =>
|
||||||
|
{
|
||||||
|
e.HasOne(i => i.PRDetails)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(t => t.PRDetailId)
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
});
|
||||||
|
modelBuilder.Entity<PRDetails>(e =>
|
||||||
|
{
|
||||||
|
e.HasOne(i => i.PRs)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(t => t.PRId)
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<RIS>(e => {
|
||||||
|
e.HasOne(r => r.Inventory)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(r => r.InventoryId)
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
e.HasOne(r => r.Discipline)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(r => r.DisciplineId);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<MRS>(e => {
|
||||||
|
e.HasOne(m => m.RIS)
|
||||||
|
.WithMany(r => r.MaterialReturns)
|
||||||
|
.HasForeignKey(m => m.RISId)
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
15
CPRNIMS.Infrastructure/Dto/Common/ApiResponse.cs
Normal file
15
CPRNIMS.Infrastructure/Dto/Common/ApiResponse.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Infrastructure.Dto.Common
|
||||||
|
{
|
||||||
|
public class ApiResponse<T>
|
||||||
|
{
|
||||||
|
public bool success { get; set; }
|
||||||
|
public string? message { get; set; }
|
||||||
|
public T? data { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
14
CPRNIMS.Infrastructure/Dto/Inventory/DisciplineDto.cs
Normal file
14
CPRNIMS.Infrastructure/Dto/Inventory/DisciplineDto.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Infrastructure.Dto.Inventory
|
||||||
|
{
|
||||||
|
public class DisciplineDto
|
||||||
|
{
|
||||||
|
public byte DisciplineId { get; set; }
|
||||||
|
public string DisciplineName { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
20
CPRNIMS.Infrastructure/Dto/Inventory/MRSFilterDto.cs
Normal file
20
CPRNIMS.Infrastructure/Dto/Inventory/MRSFilterDto.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Infrastructure.Dto.Inventory
|
||||||
|
{
|
||||||
|
public class MRSFilterDto
|
||||||
|
{
|
||||||
|
public int Page { get; set; } = 1;
|
||||||
|
public int PageSize { get; set; } = 12;
|
||||||
|
public string? SearchMRSNo { get; set; }
|
||||||
|
public long? RISId { get; set; }
|
||||||
|
public short? Status { get; set; }
|
||||||
|
public DateTime? DateFrom { get; set; }
|
||||||
|
public DateTime? DateTo { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
17
CPRNIMS.Infrastructure/Dto/Inventory/MRSPagedResult.cs
Normal file
17
CPRNIMS.Infrastructure/Dto/Inventory/MRSPagedResult.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory.Response;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Infrastructure.Dto.Inventory
|
||||||
|
{
|
||||||
|
public class MRSPagedResult
|
||||||
|
{
|
||||||
|
public IEnumerable<MRSResponse> Data { get; set; } = [];
|
||||||
|
public int RecordsTotal { get; set; }
|
||||||
|
public List<string> DepartmentList { get; set; } = new List<string>();
|
||||||
|
public List<string> DisciplineList { get; set; } = new List<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
20
CPRNIMS.Infrastructure/Dto/Inventory/PagedResult.cs
Normal file
20
CPRNIMS.Infrastructure/Dto/Inventory/PagedResult.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Infrastructure.Dto.Inventory
|
||||||
|
{
|
||||||
|
public class PagedResult<T>
|
||||||
|
{
|
||||||
|
public List<T> Data { get; set; } = new List<T>();
|
||||||
|
public int TotalCount { get; set; }
|
||||||
|
public int PageNumber { get; set; }
|
||||||
|
public int PageSize { get; set; }
|
||||||
|
public List<string> DepartmentList { get; set; } = new List<string>();
|
||||||
|
public List<string> CategoryList { get; set; } = new List<string>();
|
||||||
|
public bool IsSuccess { get; set; } = true;
|
||||||
|
public string? ErrorMessage { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
21
CPRNIMS.Infrastructure/Dto/Inventory/RISFilterDto.cs
Normal file
21
CPRNIMS.Infrastructure/Dto/Inventory/RISFilterDto.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Infrastructure.Dto.Inventory
|
||||||
|
{
|
||||||
|
public class RISFilterDto
|
||||||
|
{
|
||||||
|
public string? SearchRISNo { get; set; }
|
||||||
|
public string? SearchItemName { get; set; }
|
||||||
|
public string? SearchIssuedTo { get; set; }
|
||||||
|
public string? Discipline { get; set; }
|
||||||
|
public short? Status { get; set; }
|
||||||
|
public int PageNumber { get; set; } = 1;
|
||||||
|
public int PageSize { get; set; } = 12;
|
||||||
|
public DateTime? DateFrom { get; set; }
|
||||||
|
public DateTime? DateTo { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
17
CPRNIMS.Infrastructure/Dto/Inventory/RISPagedResult.cs
Normal file
17
CPRNIMS.Infrastructure/Dto/Inventory/RISPagedResult.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory.Response;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Infrastructure.Dto.Inventory
|
||||||
|
{
|
||||||
|
public class RISPagedResult
|
||||||
|
{
|
||||||
|
public IEnumerable<RISResponse> Data { get; set; } = [];
|
||||||
|
public int RecordsTotal { get; set; }
|
||||||
|
public IEnumerable<string> DepartmentList { get; set; } = [];
|
||||||
|
public IEnumerable<DisciplineDto> DisciplineList { get; set; } = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
19
CPRNIMS.Infrastructure/Dto/Inventory/RISReferenceDto.cs
Normal file
19
CPRNIMS.Infrastructure/Dto/Inventory/RISReferenceDto.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Infrastructure.Dto.Inventory
|
||||||
|
{
|
||||||
|
public class RISReferenceDto
|
||||||
|
{
|
||||||
|
public long RISId { get; set; }
|
||||||
|
public string RISNo { get; set; } = string.Empty;
|
||||||
|
public decimal QtyIssued { get; set; }
|
||||||
|
public decimal TotalReturned { get; set; }
|
||||||
|
public decimal QtyAvailableToReturn => QtyIssued - TotalReturned;
|
||||||
|
public string DisciplineName { get; set; } = string.Empty;
|
||||||
|
public DateTime CreatedDate { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Infrastructure.Dto.Inventory.Request
|
||||||
|
{
|
||||||
|
public class ApproveRISRequest
|
||||||
|
{
|
||||||
|
public long RISId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Infrastructure.Dto.Inventory.Request
|
||||||
|
{
|
||||||
|
public class CancelRISRequest
|
||||||
|
{
|
||||||
|
public long RISId { get; set; }
|
||||||
|
public string Reason { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Infrastructure.Dto.Inventory.Request
|
||||||
|
{
|
||||||
|
public class CreateMRSRequest
|
||||||
|
{
|
||||||
|
public long RISId { get; set; }
|
||||||
|
public decimal QtyReturned { get; set; }
|
||||||
|
public string ReturnedBy { get; set; } = string.Empty;
|
||||||
|
public string? Condition { get; set; }
|
||||||
|
public string? Remarks { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Infrastructure.Dto.Inventory.Request
|
||||||
|
{
|
||||||
|
public class CreateRISRequest
|
||||||
|
{
|
||||||
|
public int InventoryId { get; set; }
|
||||||
|
public long PRDetailId { get; set; }
|
||||||
|
public string IssuedTo { get; set; } = string.Empty;
|
||||||
|
public byte DisciplineId { get; set; }
|
||||||
|
public decimal QtyIssued { get; set; }
|
||||||
|
public string? Remarks { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Infrastructure.Dto.Inventory.Request
|
||||||
|
{
|
||||||
|
public class InventoryRequest
|
||||||
|
{
|
||||||
|
public string? SearchPRNo { get; set; }
|
||||||
|
public string? SearchItemName { get; set; }
|
||||||
|
public string? SearchDept { get; set; }
|
||||||
|
public int PageNumber { get; set; } = 1;
|
||||||
|
public int PageSize { get; set; } = 10;
|
||||||
|
public byte MessCode { get; set; }
|
||||||
|
public string? SearchProjectCode { get; set; }
|
||||||
|
public string? SearchItemNo { get; set; }
|
||||||
|
public string? UserId { get; set; }
|
||||||
|
public int InventoryId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Infrastructure.Dto.Inventory.Request
|
||||||
|
{
|
||||||
|
public class RISPagedRequest
|
||||||
|
{
|
||||||
|
public string? SearchRISNo { get; set; }
|
||||||
|
public string? SearchItemName { get; set; }
|
||||||
|
public string? SearchIssuedTo { get; set; }
|
||||||
|
public string? Discipline { get; set; }
|
||||||
|
public short? Status { get; set; }
|
||||||
|
public int PageNumber { get; set; } = 1;
|
||||||
|
public int PageSize { get; set; } = 12;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Infrastructure.Dto.Inventory.Response
|
||||||
|
{
|
||||||
|
public class InventoryByIdResponse
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public int InventoryId { get; set; }
|
||||||
|
public decimal QtyIn { get; set; }
|
||||||
|
public decimal QtyOut { get; set; }
|
||||||
|
public decimal QtyOnHand { get; set; }
|
||||||
|
public string? LotNo { get; set; }
|
||||||
|
public string? ProjectCode { get; set; }
|
||||||
|
public string? UserId { get; set; }
|
||||||
|
public string? ItemName { get; set; }
|
||||||
|
public string? ItemDescription { get; set; }
|
||||||
|
public string? ItemCategoryName { get; set; }
|
||||||
|
public string? Department { get; set; }
|
||||||
|
public DateTime CreatedDate { get; set; }
|
||||||
|
public long ItemNo { get; set; }
|
||||||
|
public long PRNo { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Infrastructure.Dto.Inventory.Response
|
||||||
|
{
|
||||||
|
public class InventoryResponse
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public int InventoryId { get; set; }
|
||||||
|
public decimal QtyIn { get; set; }
|
||||||
|
public decimal QtyOut { get; set; }
|
||||||
|
public decimal QtyOnHand { get; set; }
|
||||||
|
public string? LotNo { get; set; }
|
||||||
|
public string? ProjectCode { get; set; }
|
||||||
|
public string? UserId { get; set; }
|
||||||
|
public string? ItemName { get; set; }
|
||||||
|
public string? ItemDescription { get; set; }
|
||||||
|
public string? ItemCategoryName { get; set; }
|
||||||
|
public string? Department { get; set; }
|
||||||
|
public DateTime CreatedDate { get; set; }
|
||||||
|
public long ItemNo { get; set; }
|
||||||
|
public long PRNo { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
28
CPRNIMS.Infrastructure/Dto/Inventory/Response/MRSResponse.cs
Normal file
28
CPRNIMS.Infrastructure/Dto/Inventory/Response/MRSResponse.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Infrastructure.Dto.Inventory.Response
|
||||||
|
{
|
||||||
|
public class MRSResponse
|
||||||
|
{
|
||||||
|
public long MRSId { get; set; }
|
||||||
|
public string MRSNo { get; set; } = string.Empty;
|
||||||
|
public long RISId { get; set; }
|
||||||
|
public string RISNo { get; set; } = string.Empty;
|
||||||
|
public int InventoryId { get; set; }
|
||||||
|
public string ItemName { get; set; } = string.Empty;
|
||||||
|
public string ReturnedBy { get; set; } = string.Empty;
|
||||||
|
public decimal QtyReturned { get; set; }
|
||||||
|
public string? Condition { get; set; }
|
||||||
|
public string? Remarks { get; set; }
|
||||||
|
public short Status { get; set; }
|
||||||
|
public string StatusLabel => Status switch { 0 => "Draft", 1 => "Approved", 2 => "Cancelled", _ => "Unknown" };
|
||||||
|
public string CreatedBy { get; set; } = string.Empty;
|
||||||
|
public DateTime CreatedDate { get; set; }
|
||||||
|
public string? ApprovedBy { get; set; }
|
||||||
|
public DateTime? ApprovedDate { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Infrastructure.Dto.Inventory.Response
|
||||||
|
{
|
||||||
|
public class RISPagedResponse
|
||||||
|
{
|
||||||
|
public List<RISListItem> Data { get; set; } = [];
|
||||||
|
|
||||||
|
public int RecordsTotal { get; set; }
|
||||||
|
|
||||||
|
public List<string> DepartmentList { get; set; } = [];
|
||||||
|
|
||||||
|
public List<DisciplineItem> DisciplineList { get; set; } = [];
|
||||||
|
}
|
||||||
|
public class RISListItem
|
||||||
|
{
|
||||||
|
public long RISId { get; set; }
|
||||||
|
|
||||||
|
public string RISNo { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public int InventoryId { get; set; }
|
||||||
|
public string? ItemName { get; set; }
|
||||||
|
|
||||||
|
public long ItemNo { get; set; }
|
||||||
|
|
||||||
|
public string? IssuedTo { get; set; }
|
||||||
|
|
||||||
|
public string? DisciplineName { get; set; }
|
||||||
|
|
||||||
|
public decimal QtyIssued { get; set; }
|
||||||
|
|
||||||
|
public decimal TotalReturned { get; set; }
|
||||||
|
public string? Remarks { get; set; }
|
||||||
|
|
||||||
|
public short Status { get; set; }
|
||||||
|
|
||||||
|
public string? StatusLabel { get; set; }
|
||||||
|
|
||||||
|
public string? CreatedBy { get; set; }
|
||||||
|
|
||||||
|
public DateTime CreatedDate { get; set; }
|
||||||
|
|
||||||
|
public string? ApprovedBy { get; set; }
|
||||||
|
public DateTime? ApprovedDate { get; set; }
|
||||||
|
public decimal MRSCount { get; set; }
|
||||||
|
}
|
||||||
|
public class DisciplineItem
|
||||||
|
{
|
||||||
|
public byte DisciplineId { get; set; }
|
||||||
|
public string DisciplineName { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
32
CPRNIMS.Infrastructure/Dto/Inventory/Response/RISResponse.cs
Normal file
32
CPRNIMS.Infrastructure/Dto/Inventory/Response/RISResponse.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Infrastructure.Dto.Inventory.Response
|
||||||
|
{
|
||||||
|
public class RISResponse
|
||||||
|
{
|
||||||
|
public long RISId { get; set; }
|
||||||
|
public string RISNo { get; set; } = string.Empty;
|
||||||
|
public int InventoryId { get; set; }
|
||||||
|
public string ItemName { get; set; } = string.Empty;
|
||||||
|
public long ItemNo { get; set; }
|
||||||
|
public string? LotNo { get; set; }
|
||||||
|
public string IssuedTo { get; set; } = string.Empty;
|
||||||
|
public string DisciplineName { get; set; } = string.Empty;
|
||||||
|
public string? Message { get; set; }
|
||||||
|
public byte DisciplineId { get; set; }
|
||||||
|
public decimal QtyIssued { get; set; }
|
||||||
|
public string? Remarks { get; set; }
|
||||||
|
public short Status { get; set; }
|
||||||
|
public string StatusLabel => Status switch { 0 => "Draft", 1 => "Approved", 2 => "Cancelled", _ => "Unknown" };
|
||||||
|
public string CreatedBy { get; set; } = string.Empty;
|
||||||
|
public DateTime CreatedDate { get; set; }
|
||||||
|
public string? ApprovedBy { get; set; }
|
||||||
|
public DateTime? ApprovedDate { get; set; }
|
||||||
|
public decimal MRSCount { get; set; }
|
||||||
|
public decimal TotalReturned { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
23
CPRNIMS.Infrastructure/Dto/Inventory/TransactContextDto.cs
Normal file
23
CPRNIMS.Infrastructure/Dto/Inventory/TransactContextDto.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Infrastructure.Dto.Inventory
|
||||||
|
{
|
||||||
|
public class TransactContextDto
|
||||||
|
{
|
||||||
|
public int InventoryId { get; set; }
|
||||||
|
public string ItemName { get; set; } = string.Empty;
|
||||||
|
public long ItemNo { get; set; }
|
||||||
|
public long PRNo { get; set; }
|
||||||
|
public string? LotNo { get; set; }
|
||||||
|
public string? Department { get; set; }
|
||||||
|
public decimal QtyOnHand { get; set; }
|
||||||
|
public decimal QtyIn { get; set; }
|
||||||
|
public decimal QtyOut { get; set; }
|
||||||
|
public IEnumerable<DisciplineDto> Disciplines { get; set; } = [];
|
||||||
|
public IEnumerable<RISReferenceDto> OpenRISList { get; set; } = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
18
CPRNIMS.Infrastructure/Entities/Inventory/Discipline.cs
Normal file
18
CPRNIMS.Infrastructure/Entities/Inventory/Discipline.cs
Normal file
@ -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 CPRNIMS.Infrastructure.Entities.Inventory
|
||||||
|
{
|
||||||
|
public class Discipline
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public byte DisciplineId { get; set; }
|
||||||
|
|
||||||
|
[Required, MaxLength(100)]
|
||||||
|
public string DisciplineName { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
22
CPRNIMS.Infrastructure/Entities/Inventory/InventTrans.cs
Normal file
22
CPRNIMS.Infrastructure/Entities/Inventory/InventTrans.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Infrastructure.Entities.Inventory
|
||||||
|
{
|
||||||
|
[Table("InventTrans")]
|
||||||
|
public class InventTrans
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public int InventTransId { get; set; }
|
||||||
|
public int InventoryId { get; set; }
|
||||||
|
public long RRNo { get; set; }
|
||||||
|
public string CreatedBy { get; set; }=string.Empty;
|
||||||
|
public bool IsActive { get; set; }
|
||||||
|
public ICollection<InventTransDetail> InventTransDetails { get; set; } = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
using CPRNIMS.Infrastructure.Entities.Purchasing;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Infrastructure.Entities.Inventory
|
||||||
|
{
|
||||||
|
[Table("InventTransDetail")]
|
||||||
|
public class InventTransDetail
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public long InventTransDetailId { get; set; }
|
||||||
|
public int InventTransId { get; set; }
|
||||||
|
public byte TransTypeId { get; set; }
|
||||||
|
public long RRDetailId { get; set; }
|
||||||
|
public decimal QtyIn { get; set; }
|
||||||
|
public decimal QtyOut { get; set; }
|
||||||
|
public DateTime CreatedDate { get; set; }
|
||||||
|
public string? Remarks { get; set; }
|
||||||
|
public bool IsActive { get; set; }
|
||||||
|
public long PRDetailId { get; set; }
|
||||||
|
public PRDetails? PRDetails { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,12 +1,15 @@
|
|||||||
using System;
|
using CPRNIMS.Infrastructure.Entities.Account;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace CPRNIMS.Infrastructure.Entities.Inventory
|
namespace CPRNIMS.Infrastructure.Entities.Inventory
|
||||||
{
|
{
|
||||||
|
[Table("Inventory")]
|
||||||
public class Inventory
|
public class Inventory
|
||||||
{
|
{
|
||||||
[Key]
|
[Key]
|
||||||
@ -14,15 +17,12 @@ namespace CPRNIMS.Infrastructure.Entities.Inventory
|
|||||||
public decimal QtyIn { get; set; }
|
public decimal QtyIn { get; set; }
|
||||||
public decimal QtyOut { get; set; }
|
public decimal QtyOut { get; set; }
|
||||||
public decimal QtyOnHand { get; set; }
|
public decimal QtyOnHand { get; set; }
|
||||||
public decimal RemainingQty { get; set; }
|
|
||||||
public string? LotNo { get; set; }
|
|
||||||
public string? UserId { get; set; }
|
public string? UserId { get; set; }
|
||||||
public string? ItemName { get; set; }
|
|
||||||
public string? ItemDescription { get; set; }
|
|
||||||
public string? ItemCategoryName { get; set; }
|
|
||||||
public string? Department { get; set; }
|
|
||||||
public DateTime CreatedDate { get; set; }
|
|
||||||
public bool IsActive { get; set; }
|
public bool IsActive { get; set; }
|
||||||
public long ItemNo { get; set; }
|
public long ItemNo { get; set; }
|
||||||
|
public int LotId { get; set; }
|
||||||
|
public Lot? Lot { get; set; }
|
||||||
|
public ICollection<InventTrans> InventTrans { get; set; } = [];
|
||||||
|
public ApplicationUser? User { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,8 +17,8 @@ namespace CPRNIMS.Infrastructure.Entities.Inventory
|
|||||||
public int LotId { get; set; }
|
public int LotId { get; set; }
|
||||||
public int InventoryId { get; set; }
|
public int InventoryId { get; set; }
|
||||||
public string? LotName { get; set; }
|
public string? LotName { get; set; }
|
||||||
public string? LotTypeName { get; set; }
|
|
||||||
public byte LotTypeId { get; set; }
|
public byte LotTypeId { get; set; }
|
||||||
public string? UserId { get; set; }
|
public string? UserId { get; set; }
|
||||||
|
public LotType? LotType { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
CPRNIMS.Infrastructure/Entities/Inventory/LotType.cs
Normal file
18
CPRNIMS.Infrastructure/Entities/Inventory/LotType.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Infrastructure.Entities.Inventory
|
||||||
|
{
|
||||||
|
[Table("LotType")]
|
||||||
|
public class LotType
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public int LotTypeId { get; set; }
|
||||||
|
public string? LotTypeName { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
46
CPRNIMS.Infrastructure/Entities/Inventory/MRS.cs
Normal file
46
CPRNIMS.Infrastructure/Entities/Inventory/MRS.cs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Infrastructure.Entities.Inventory
|
||||||
|
{
|
||||||
|
[Table("MRS")]
|
||||||
|
public class MRS
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public long MRSId { get; set; }
|
||||||
|
|
||||||
|
[Required, MaxLength(50)]
|
||||||
|
public string MRSNo { get; set; } = string.Empty;
|
||||||
|
public long RISId { get; set; }
|
||||||
|
public int InventoryId { get; set; }
|
||||||
|
[Required, MaxLength(450)]
|
||||||
|
public string ReturnedBy { get; set; } = string.Empty;
|
||||||
|
public decimal QtyReturned { get; set; }
|
||||||
|
|
||||||
|
[MaxLength(100)]
|
||||||
|
public string? Condition { get; set; }
|
||||||
|
|
||||||
|
[MaxLength(500)]
|
||||||
|
public string? Remarks { get; set; }
|
||||||
|
|
||||||
|
public short Status { get; set; } = 0;
|
||||||
|
|
||||||
|
[Required, MaxLength(450)]
|
||||||
|
public string CreatedBy { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public DateTime CreatedDate { get; set; } = DateTime.Now;
|
||||||
|
public string? ApprovedBy { get; set; }
|
||||||
|
public DateTime? ApprovedDate { get; set; }
|
||||||
|
public string? CanceledBy { get; set; }
|
||||||
|
public DateTime? CanceledDate { get; set; }
|
||||||
|
public string? Reason { get; set; }
|
||||||
|
public RIS RIS { get; set; } = null!;
|
||||||
|
public Inventory Inventory { get; set; } = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
48
CPRNIMS.Infrastructure/Entities/Inventory/RIS.cs
Normal file
48
CPRNIMS.Infrastructure/Entities/Inventory/RIS.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
using CPRNIMS.Infrastructure.Entities.Purchasing;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Infrastructure.Entities.Inventory
|
||||||
|
{
|
||||||
|
[Table("RIS")]
|
||||||
|
public class RIS
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public long RISId { get; set; }
|
||||||
|
|
||||||
|
[Required, MaxLength(50)]
|
||||||
|
public string RISNo { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public int InventoryId { get; set; }
|
||||||
|
public long PRDetailId { get; set; }
|
||||||
|
|
||||||
|
[Required, MaxLength(450)]
|
||||||
|
public string IssuedTo { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public byte DisciplineId { get; set; }
|
||||||
|
public decimal QtyIssued { get; set; }
|
||||||
|
|
||||||
|
[MaxLength(500)]
|
||||||
|
public string? Remarks { get; set; }
|
||||||
|
|
||||||
|
public short Status { get; set; } = 0;
|
||||||
|
|
||||||
|
[Required, MaxLength(450)]
|
||||||
|
public string CreatedBy { get; set; } = string.Empty;
|
||||||
|
public DateTime CreatedDate { get; set; } = DateTime.Now;
|
||||||
|
public string? ApprovedBy { get; set; }
|
||||||
|
public DateTime? ApprovedDate { get; set; }
|
||||||
|
public string? CanceledBy { get; set; }
|
||||||
|
public DateTime? CanceledDate { get; set; }
|
||||||
|
public string? Reason { get; set; }
|
||||||
|
public Inventory Inventory { get; set; } = null!;
|
||||||
|
public PRDetails PRDetail { get; set; } = null!;
|
||||||
|
public Discipline Discipline { get; set; } = null!;
|
||||||
|
public ICollection<MRS> MaterialReturns { get; set; } = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -15,6 +15,7 @@ namespace CPRNIMS.Infrastructure.Entities.Purchasing
|
|||||||
{
|
{
|
||||||
[Key]
|
[Key]
|
||||||
public long PRDetailsId { get; set; }
|
public long PRDetailsId { get; set; }
|
||||||
|
public long PRId { get; set; }
|
||||||
public string ItemName { get; set; }=string.Empty;
|
public string ItemName { get; set; }=string.Empty;
|
||||||
public string ItemDescription { get; set; } = string.Empty;
|
public string ItemDescription { get; set; } = string.Empty;
|
||||||
public short Status { get; set; }
|
public short Status { get; set; }
|
||||||
@ -22,5 +23,6 @@ namespace CPRNIMS.Infrastructure.Entities.Purchasing
|
|||||||
public long ItemNo { get; set; }
|
public long ItemNo { get; set; }
|
||||||
public decimal Qty { get; set; }
|
public decimal Qty { get; set; }
|
||||||
public bool IsSearched { get; set; }
|
public bool IsSearched { get; set; }
|
||||||
|
public PR? PRs { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,15 +10,11 @@ namespace CPRNIMS.Infrastructure.Models
|
|||||||
{
|
{
|
||||||
public class RegisterModel : ApplicationUser
|
public class RegisterModel : ApplicationUser
|
||||||
{
|
{
|
||||||
[EmailAddress]
|
public string? Role { get; set; }
|
||||||
[Required(ErrorMessage = "Email is required")]
|
|
||||||
public string Email { get; set; }
|
|
||||||
public string Role { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
[Required(ErrorMessage = "Password is required")]
|
[Required(ErrorMessage = "Password is required")]
|
||||||
public string? Password { get; set; }
|
public string? Password { get; set; }
|
||||||
//public string? UserId { get; set; }
|
|
||||||
[Required(ErrorMessage = "ClaimType is required")]
|
[Required(ErrorMessage = "ClaimType is required")]
|
||||||
public string? ClaimType { get; set; }
|
public string? ClaimType { get; set; }
|
||||||
[Required(ErrorMessage = "ClaimValue is required")]
|
[Required(ErrorMessage = "ClaimValue is required")]
|
||||||
|
|||||||
@ -13,7 +13,5 @@ namespace CPRNIMS.Infrastructure.ViewModel.Account
|
|||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public string? Message { get; set; }
|
public string? Message { get; set; }
|
||||||
public string? Status { get; set; }
|
public string? Status { get; set; }
|
||||||
public string? Token { get; internal set; }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -70,5 +70,12 @@ namespace CPRNIMS.Infrastructure.ViewModel.Inventory
|
|||||||
public DateTime DateTo { get; set; }
|
public DateTime DateTo { get; set; }
|
||||||
public bool IsSorting { get; set; }
|
public bool IsSorting { get; set; }
|
||||||
public string? URL { get; set; }
|
public string? URL { get; set; }
|
||||||
|
public string SearchPRNo { get; set; } = "";
|
||||||
|
public string SearchItemName { get; set; } = "";
|
||||||
|
public string SearchDept { get; set; } = "";
|
||||||
|
public int PageNumber { get; set; } = 1;
|
||||||
|
public int PageSize { get; set; } = 10;
|
||||||
|
public byte MessCode { get; set; }
|
||||||
|
public string? SearchProjectCode { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -147,6 +147,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Folder Include="Sql\Phase 5\" />
|
||||||
<Folder Include="wwwroot\Content\Uploads\PRAttachment\" />
|
<Folder Include="wwwroot\Content\Uploads\PRAttachment\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@ -152,6 +152,8 @@ namespace CPRNIMS.WebApi.Common
|
|||||||
services.AddScoped<IItem, Domain.Services.Items.Item>();
|
services.AddScoped<IItem, Domain.Services.Items.Item>();
|
||||||
services.AddScoped<IPRequest, Domain.Services.PR.PRequest>();
|
services.AddScoped<IPRequest, Domain.Services.PR.PRequest>();
|
||||||
services.AddScoped<ICanvass, Canvass>();
|
services.AddScoped<ICanvass, Canvass>();
|
||||||
|
services.AddScoped<IRIS, RIS>();
|
||||||
|
services.AddScoped<IMRS, MRS>();
|
||||||
|
|
||||||
#region Automation using LLM
|
#region Automation using LLM
|
||||||
services.AddHttpClient<SupplierSearchService>();
|
services.AddHttpClient<SupplierSearchService>();
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
using CPRNIMS.Domain.Contracts.Inventory;
|
using CPRNIMS.Domain.Contracts.Inventory;
|
||||||
using CPRNIMS.Domain.Services;
|
using CPRNIMS.Domain.Services;
|
||||||
using CPRNIMS.Infrastructure.Dto.Inventory;
|
using CPRNIMS.Infrastructure.Dto.Inventory;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory.Request;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.PR;
|
||||||
using CPRNIMS.WebApi.Controllers.Base;
|
using CPRNIMS.WebApi.Controllers.Base;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
@ -10,16 +12,23 @@ namespace CPRNIMS.WebApi.Controllers.Inventory
|
|||||||
public class InventoryMgmtController : BaseController
|
public class InventoryMgmtController : BaseController
|
||||||
{
|
{
|
||||||
private readonly IInventory _inventory;
|
private readonly IInventory _inventory;
|
||||||
|
|
||||||
public InventoryMgmtController(ErrorMessageService errorMessageService,
|
public InventoryMgmtController(ErrorMessageService errorMessageService,
|
||||||
IWebHostEnvironment webHostEnvironment, IConfiguration configuration,
|
IWebHostEnvironment webHostEnvironment, IConfiguration configuration,
|
||||||
IInventory inventory) :
|
IInventory inventory) :
|
||||||
base(errorMessageService, webHostEnvironment,configuration)
|
base(errorMessageService, webHostEnvironment,configuration)
|
||||||
|
=> _inventory = inventory;
|
||||||
|
|
||||||
|
#region Get
|
||||||
|
[HttpGet("GetTransactContext")]
|
||||||
|
public async Task<IActionResult> GetTransactContext([FromQuery] int inventoryId, CancellationToken ct)
|
||||||
{
|
{
|
||||||
_inventory = inventory;
|
var ctx = await _inventory.GetTransactContextAsync(inventoryId, ct);
|
||||||
|
if (ctx == null)
|
||||||
|
return NotFound(new { success = false, message = "Inventory record not found." });
|
||||||
|
|
||||||
|
return Ok(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Get
|
|
||||||
[HttpPost("GetInventoryByUserId")]
|
[HttpPost("GetInventoryByUserId")]
|
||||||
public async Task<IActionResult> GetInventoryByUserId(InventoryDto itemCodeDto)
|
public async Task<IActionResult> GetInventoryByUserId(InventoryDto itemCodeDto)
|
||||||
{
|
{
|
||||||
@ -28,83 +37,45 @@ namespace CPRNIMS.WebApi.Controllers.Inventory
|
|||||||
[HttpPost("GetRequestedItemByUserId")]
|
[HttpPost("GetRequestedItemByUserId")]
|
||||||
public async Task<IActionResult> GetRequestedItemByUserId(InventoryDto itemCodeDto)
|
public async Task<IActionResult> GetRequestedItemByUserId(InventoryDto itemCodeDto)
|
||||||
{
|
{
|
||||||
try
|
var allPR = await _inventory.GetRequestedItemByUserId(itemCodeDto);
|
||||||
{
|
|
||||||
var allPR = await _inventory.GetRequestedItemByUserId(itemCodeDto);
|
|
||||||
|
|
||||||
return Ok(allPR);
|
return Ok(allPR);
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var message = ex.InnerException?.ToString() ?? ex.Message.ToString();
|
|
||||||
await PostErrorMessage(message + "GetRequestedItemByUserId", "WebApi");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
//GetRequestedItemByUserId
|
|
||||||
[HttpPost("GetInventoryById")]
|
[HttpPost("GetInventory")]
|
||||||
public async Task<IActionResult> GetInventoryById(InventoryDto itemCodeDto)
|
public async Task<IActionResult> GetInventory(InventoryRequest request, CancellationToken ct)
|
||||||
{
|
{
|
||||||
try
|
var result = await _inventory.GetInventory(request, ct);
|
||||||
{
|
return Ok(result);
|
||||||
var allPR = await _inventory.GetInventoryById(itemCodeDto);
|
|
||||||
|
|
||||||
return Ok(allPR);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var message = ex.InnerException?.ToString() ?? ex.Message.ToString();
|
|
||||||
await PostErrorMessage(message + "GetInventoryById", "WebApi");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("GetInventoryById")]
|
||||||
|
public async Task<IActionResult> GetInventoryById(InventoryRequest request, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var result = await _inventory.GetInventoryById(request, ct);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPost("GetLotNo")]
|
[HttpPost("GetLotNo")]
|
||||||
public async Task<IActionResult> GetLotNo(InventoryDto itemCodeDto)
|
public async Task<IActionResult> GetLotNo(InventoryDto itemCodeDto)
|
||||||
{
|
{
|
||||||
try
|
var allPR = await _inventory.GetLotNo(itemCodeDto);
|
||||||
{
|
|
||||||
var allPR = await _inventory.GetLotNo(itemCodeDto);
|
|
||||||
|
|
||||||
return Ok(allPR);
|
return Ok(allPR);
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var message = ex.InnerException?.ToString() ?? ex.Message.ToString();
|
|
||||||
await PostErrorMessage(message + "GetInventoryById", "WebApi");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
[HttpPost("GetLotNoById")]
|
[HttpPost("GetLotNoById")]
|
||||||
public async Task<IActionResult> GetLotNoById(InventoryDto itemCodeDto)
|
public async Task<IActionResult> GetLotNoById(InventoryDto itemCodeDto)
|
||||||
{
|
{
|
||||||
try
|
var allPR = await _inventory.GetLotNoById(itemCodeDto);
|
||||||
{
|
|
||||||
var allPR = await _inventory.GetLotNoById(itemCodeDto);
|
|
||||||
|
|
||||||
return Ok(allPR);
|
return Ok(allPR);
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var message = ex.InnerException?.ToString() ?? ex.Message.ToString();
|
|
||||||
await PostErrorMessage(message + "GetInventoryById", "WebApi");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
[HttpPost("GetLotQtyByItem")]
|
[HttpPost("GetLotQtyByItem")]
|
||||||
public async Task<IActionResult> GetLotQtyByItem(InventoryDto itemCodeDto)
|
public async Task<IActionResult> GetLotQtyByItem(InventoryDto itemCodeDto)
|
||||||
{
|
{
|
||||||
try
|
var allPR = await _inventory.GetLotQtyByItem(itemCodeDto);
|
||||||
{
|
|
||||||
var allPR = await _inventory.GetLotQtyByItem(itemCodeDto);
|
|
||||||
|
|
||||||
return Ok(allPR);
|
return Ok(allPR);
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var message = ex.InnerException?.ToString() ?? ex.Message.ToString();
|
|
||||||
await PostErrorMessage(message + "GetLotQtyByItem", "WebApi");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@ -112,66 +83,28 @@ namespace CPRNIMS.WebApi.Controllers.Inventory
|
|||||||
[HttpPost("PostPutReqItems")]
|
[HttpPost("PostPutReqItems")]
|
||||||
public async Task<IActionResult> PostPutReqItems(InventoryDto InventoryDto)
|
public async Task<IActionResult> PostPutReqItems(InventoryDto InventoryDto)
|
||||||
{
|
{
|
||||||
try
|
var pR = await _inventory.PostPutReqItems(InventoryDto);
|
||||||
{
|
|
||||||
var pR = await _inventory.PostPutReqItems(InventoryDto);
|
|
||||||
|
|
||||||
return Ok(pR);
|
return Ok(pR);
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var message = ex.InnerException?.ToString() ?? ex.Message.ToString();
|
|
||||||
await PostErrorMessage(message + "PostPutReqItems", "WebApi");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
[HttpPost("PostPutReqApproval")]
|
[HttpPost("PostPutReqApproval")]
|
||||||
public async Task<IActionResult> PostPutReqApproval(InventoryDto InventoryDto)
|
public async Task<IActionResult> PostPutReqApproval(InventoryDto InventoryDto)
|
||||||
{
|
{
|
||||||
try
|
var pR = await _inventory.PostPutReqApproval(InventoryDto);
|
||||||
{
|
|
||||||
var pR = await _inventory.PostPutReqApproval(InventoryDto);
|
|
||||||
|
|
||||||
return Ok(pR);
|
return Ok(pR);
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var message = ex.InnerException?.ToString() ?? ex.Message.ToString();
|
|
||||||
await PostErrorMessage(message + "PostPutReqApproval", "WebApi");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
[HttpPost("PostPutLotNo")]
|
[HttpPost("PostPutLotNo")]
|
||||||
public async Task<IActionResult> PostPutLotNo(InventoryDto InventoryDto)
|
public async Task<IActionResult> PostPutLotNo(InventoryDto InventoryDto)
|
||||||
{
|
{
|
||||||
try
|
var pR = await _inventory.PostPutLotNo(InventoryDto);
|
||||||
{
|
return Ok(pR);
|
||||||
var pR = await _inventory.PostPutLotNo(InventoryDto);
|
|
||||||
|
|
||||||
return Ok(pR);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var message = ex.InnerException?.ToString() ?? ex.Message.ToString();
|
|
||||||
await PostErrorMessage(message + "PostPutReqApproval", "WebApi");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
[HttpPost("PostPutLotBin")]
|
[HttpPost("PostPutLotBin")]
|
||||||
public async Task<IActionResult> PostPutLotBin(InventoryDto InventoryDto)
|
public async Task<IActionResult> PostPutLotBin(InventoryDto InventoryDto)
|
||||||
{
|
{
|
||||||
try
|
var pR = await _inventory.PostPutLotBin(InventoryDto);
|
||||||
{
|
return Ok(pR);
|
||||||
var pR = await _inventory.PostPutLotBin(InventoryDto);
|
|
||||||
|
|
||||||
return Ok(pR);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var message = ex.InnerException?.ToString() ?? ex.Message.ToString();
|
|
||||||
await PostErrorMessage(message + "PostPutLotBin", "WebApi");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
12
CPRNIMS.WebApi/Controllers/Inventory/MRSMgmtController.cs
Normal file
12
CPRNIMS.WebApi/Controllers/Inventory/MRSMgmtController.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace CPRNIMS.WebApi.Controllers.Inventory
|
||||||
|
{
|
||||||
|
public class MRSMgmtController : Controller
|
||||||
|
{
|
||||||
|
public IActionResult Index()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
76
CPRNIMS.WebApi/Controllers/Inventory/RISMgmtController.cs
Normal file
76
CPRNIMS.WebApi/Controllers/Inventory/RISMgmtController.cs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
using Azure.Core;
|
||||||
|
using CPRNIMS.Domain.Contracts.Inventory;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory.Request;
|
||||||
|
using CPRNIMS.WebApi.Security;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using System.Security.Claims;
|
||||||
|
|
||||||
|
namespace CPRNIMS.WebApi.Controllers.Inventory
|
||||||
|
{
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[ApiController]
|
||||||
|
public class RISMgmtController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IRIS _risRepo;
|
||||||
|
public RISMgmtController(IRIS risRepo) {
|
||||||
|
_risRepo = risRepo;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("GetRIS")]
|
||||||
|
public async Task<IActionResult> GetRIS([FromQuery] RISFilterDto filter, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var result = await _risRepo.GetPagedAsync(filter,ct);
|
||||||
|
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
public async Task<IActionResult> GetRISById(long id, CancellationToken ct)
|
||||||
|
{
|
||||||
|
return Ok(await _risRepo.GetByIdAsync(id, ct));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> CreateRIS([FromBody] CreateRISRequest request,CancellationToken ct)
|
||||||
|
{
|
||||||
|
var currentUser = User.ToUserClaims();
|
||||||
|
if (currentUser == null)
|
||||||
|
return BadRequest();
|
||||||
|
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
return BadRequest(new
|
||||||
|
{
|
||||||
|
success = false,
|
||||||
|
message = "Validation failed.",
|
||||||
|
errors = ModelState.Values
|
||||||
|
.SelectMany(v => v.Errors)
|
||||||
|
.Select(e => e.ErrorMessage)
|
||||||
|
});
|
||||||
|
|
||||||
|
var ris = await _risRepo.CreateAsync(request, currentUser.UserName,ct);
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
success = true,
|
||||||
|
message = $"RIS {ris.RISNo} created successfully.",
|
||||||
|
data= ris
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("ApproveRIS")]
|
||||||
|
public async Task<IActionResult> ApproveRIS([FromBody] ApproveRISRequest request, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var currentUser = User.ToUserClaims();
|
||||||
|
if(currentUser == null)
|
||||||
|
return BadRequest();
|
||||||
|
|
||||||
|
await _risRepo.ApproveAsync(request, currentUser.UserName, ct);
|
||||||
|
return Ok(new { success = true, message = "RIS approved." });
|
||||||
|
}
|
||||||
|
[HttpPut("CancelRIS")]
|
||||||
|
public async Task<IActionResult> CancelRIS([FromBody] CancelRISRequest request, CancellationToken ct)
|
||||||
|
{
|
||||||
|
await _risRepo.CancelAsync(request, ct);
|
||||||
|
return Ok(new { success = true, message = "RIS cancelled and inventory restored." });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
117
CPRNIMS.WebApi/Sql/Phase 5/StoredProc.sql
Normal file
117
CPRNIMS.WebApi/Sql/Phase 5/StoredProc.sql
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
USE [CPRNIMS]
|
||||||
|
GO
|
||||||
|
/****** Object: StoredProcedure [dbo].[GetInventory] Script Date: 6/11/2026 9:29:29 AM ******/
|
||||||
|
SET ANSI_NULLS ON
|
||||||
|
GO
|
||||||
|
SET QUOTED_IDENTIFIER ON
|
||||||
|
GO
|
||||||
|
ALTER PROCEDURE [dbo].[GetInventory]
|
||||||
|
(
|
||||||
|
@UserId VARCHAR(450)='89da2977-c70f-4df9-94d4-9a610aa999ea',
|
||||||
|
@SearchPRNo VARCHAR(50) = '',
|
||||||
|
@SearchItemNo VARCHAR(50) = '',
|
||||||
|
@SearchItemName VARCHAR(100) = '',
|
||||||
|
@SearchDept VARCHAR(200) = '',
|
||||||
|
@SearchProjectCode VARCHAR(200) = '',
|
||||||
|
@PageNumber INT = 1,
|
||||||
|
@PageSize INT = 10
|
||||||
|
)
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
DECLARE @Offset INT = (@PageNumber - 1) * @PageSize;
|
||||||
|
|
||||||
|
-- ── 1. Full department list — unaffected by any filter ──────────────
|
||||||
|
SELECT DISTINCT D.Department
|
||||||
|
FROM dbo.Departments D
|
||||||
|
INNER JOIN dbo.Users U
|
||||||
|
ON U.DepartmentId = D.DepartmentId
|
||||||
|
INNER JOIN dbo.Inventory IV
|
||||||
|
ON IV.UserId = U.Id
|
||||||
|
ORDER BY D.Department;
|
||||||
|
|
||||||
|
-- ── 2. Filtered temp table ───────────────────────────────────────────
|
||||||
|
IF OBJECT_ID('tempdb..#Inventory') IS NOT NULL
|
||||||
|
DROP TABLE #Inventory;
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
IV.InventoryId
|
||||||
|
, IV.ItemNo
|
||||||
|
, IV.QtyOnHand
|
||||||
|
, IV.QtyIn
|
||||||
|
, IV.QtyOut
|
||||||
|
, L.LotName AS LotNo
|
||||||
|
, IV.UserId
|
||||||
|
, PR.PRNo
|
||||||
|
, PRD.ItemName
|
||||||
|
, D.Department
|
||||||
|
, PRD.ItemDescription
|
||||||
|
, ICAT.ItemCategoryName
|
||||||
|
, RRD.RemainingQty
|
||||||
|
, ITD.CreatedDate
|
||||||
|
,COALESCE(PCD.ProjectCode,'N/A') ProjectCode
|
||||||
|
INTO #Inventory
|
||||||
|
FROM dbo.Inventory IV
|
||||||
|
INNER JOIN dbo.InventTrans IT
|
||||||
|
ON IV.InventoryId = IT.InventoryId
|
||||||
|
INNER JOIN dbo.InventTransDetail ITD
|
||||||
|
ON IT.InventTransId = ITD.InventTransId
|
||||||
|
INNER JOIN dbo.PRDetails PRD
|
||||||
|
ON ITD.PRDetailId = PRD.PRDetailsId
|
||||||
|
AND PRD.IsActive = 1
|
||||||
|
INNER JOIN dbo.RRDetails RRD
|
||||||
|
ON PRD.PRDetailsId = RRD.PRDetailId
|
||||||
|
AND RRD.IsActive = 1
|
||||||
|
INNER JOIN dbo.ItemCategories ICAT
|
||||||
|
ON PRD.ItemCategoryId = ICAT.ItemCategoryId
|
||||||
|
INNER JOIN dbo.Lot L
|
||||||
|
ON IV.LotId = L.LotId
|
||||||
|
INNER JOIN dbo.LotType LT
|
||||||
|
ON L.LotTypeId = LT.LotTypeId
|
||||||
|
INNER JOIN dbo.PR PR
|
||||||
|
ON PR.UserId = IV.UserId
|
||||||
|
AND PR.IsActive = 1
|
||||||
|
INNER JOIN dbo.ProjectCodes PC
|
||||||
|
ON PR.ProjectCodeId = PC.ProjectCodeId
|
||||||
|
INNER JOIN dbo.Users U
|
||||||
|
ON IV.UserId = U.Id
|
||||||
|
INNER JOIN dbo.Departments D
|
||||||
|
ON U.DepartmentId = D.DepartmentId
|
||||||
|
INNER JOIN dbo.ProjectCodes PCD
|
||||||
|
ON PR.ProjectCodeId = PCD.ProjectCodeId
|
||||||
|
WHERE ITD.TransTypeId=2 AND IV.IsActive=1 AND ITD.IsActive=1 AND
|
||||||
|
(@SearchPRNo = '' OR PR.PRNo = @SearchPRNo)
|
||||||
|
AND (@SearchItemNo = '' OR PRD.ItemNo = @SearchItemNo)
|
||||||
|
AND (@SearchItemName = '' OR PRD.ItemName LIKE '%' + @SearchItemName + '%')
|
||||||
|
AND (@SearchDept = '' OR D.Department LIKE '%' + @SearchDept + '%')
|
||||||
|
AND (@SearchProjectCode = '' OR PC.ProjectCode LIKE '%' + @SearchProjectCode + '%')
|
||||||
|
GROUP BY
|
||||||
|
IV.InventoryId
|
||||||
|
, IV.ItemNo
|
||||||
|
, IV.QtyOnHand
|
||||||
|
, IV.QtyIn
|
||||||
|
, IV.QtyOut
|
||||||
|
, L.LotName
|
||||||
|
, IV.UserId
|
||||||
|
, PR.PRNo
|
||||||
|
, PRD.ItemName
|
||||||
|
, D.Department
|
||||||
|
, PRD.ItemDescription
|
||||||
|
, ICAT.ItemCategoryName
|
||||||
|
, RRD.RemainingQty
|
||||||
|
, ITD.CreatedDate
|
||||||
|
,PCD.ProjectCode;
|
||||||
|
|
||||||
|
-- ── 3. Total count of filtered results ──────────────────────────────
|
||||||
|
SELECT COUNT(*) AS TotalCount
|
||||||
|
FROM #Inventory;
|
||||||
|
|
||||||
|
-- ── 4. Paged filtered results ────────────────────────────────────────
|
||||||
|
SELECT *
|
||||||
|
FROM #Inventory
|
||||||
|
ORDER BY CreatedDate DESC
|
||||||
|
OFFSET @Offset ROWS
|
||||||
|
FETCH NEXT @PageSize ROWS ONLY;
|
||||||
|
|
||||||
|
DROP TABLE #Inventory;
|
||||||
|
END
|
||||||
61
CPRNIMS.WebApi/Sql/Phase 5/Table.sql
Normal file
61
CPRNIMS.WebApi/Sql/Phase 5/Table.sql
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
-- ── Disciplines lookup (Trade / Matrix / Structural / Architectural) ──
|
||||||
|
CREATE TABLE [dbo].[Disciplines] (
|
||||||
|
[DisciplineId] TINYINT IDENTITY(1,1) PRIMARY KEY NOT NULL,
|
||||||
|
[DisciplineName] VARCHAR(100) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO [dbo].[Disciplines] ([DisciplineName]) VALUES
|
||||||
|
('Trade'),
|
||||||
|
('Matrix'),
|
||||||
|
('Structural'),
|
||||||
|
('Architectural');
|
||||||
|
|
||||||
|
-- ── RIS: Return Issuance Slip ─────────────────────────────────────
|
||||||
|
CREATE TABLE [dbo].[RIS] (
|
||||||
|
[RISId] BIGINT IDENTITY(1,1) PRIMARY KEY NOT NULL,
|
||||||
|
[RISNo] VARCHAR(50) NOT NULL, -- generated slip number
|
||||||
|
[InventoryId] INT NOT NULL,
|
||||||
|
[PRDetailId] BIGINT NOT NULL,
|
||||||
|
[IssuedTo] VARCHAR(450) NOT NULL, -- UserId receiving items
|
||||||
|
[DisciplineId] TINYINT NOT NULL, -- Trade/Matrix/Structural/Architectural
|
||||||
|
[QtyIssued] DECIMAL(14,2) NOT NULL,
|
||||||
|
[Remarks] VARCHAR(500) NULL,
|
||||||
|
[Status] SMALLINT NOT NULL DEFAULT 0, -- 0=Draft,1=Approved,2=Cancelled
|
||||||
|
[CreatedBy] VARCHAR(450) NOT NULL,
|
||||||
|
[CreatedDate] DATETIME NOT NULL DEFAULT GETDATE(),
|
||||||
|
[ApprovedBy] VARCHAR(450) NULL,
|
||||||
|
[ApprovedDate] DATETIME NULL,
|
||||||
|
CanceledBy VARCHAR(50) NULL,
|
||||||
|
CanceledDate DATETIME NULL,
|
||||||
|
Reason VARCHAR(150) NULL,
|
||||||
|
FOREIGN KEY ([InventoryId]) REFERENCES [Inventory]([InventoryId]),
|
||||||
|
FOREIGN KEY ([PRDetailId]) REFERENCES [PRDetails]([PRDetailsId]),
|
||||||
|
FOREIGN KEY ([DisciplineId]) REFERENCES [Disciplines]([DisciplineId])
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ── MRS: Material Return Slip ─────────────────────────────────────
|
||||||
|
CREATE TABLE [dbo].[MRS] (
|
||||||
|
[MRSId] BIGINT IDENTITY(1,1) PRIMARY KEY NOT NULL,
|
||||||
|
[MRSNo] VARCHAR(50) NOT NULL,
|
||||||
|
[RISId] BIGINT NOT NULL, -- must reference an original issuance
|
||||||
|
[InventoryId] INT NOT NULL,
|
||||||
|
[ReturnedBy] VARCHAR(450) NOT NULL,
|
||||||
|
[QtyReturned] DECIMAL(14,2) NOT NULL,
|
||||||
|
[Condition] VARCHAR(100) NULL, -- Good / Damaged / Partial
|
||||||
|
[Remarks] VARCHAR(500) NULL,
|
||||||
|
[Status] SMALLINT NOT NULL DEFAULT 0,
|
||||||
|
[CreatedBy] VARCHAR(450) NOT NULL,
|
||||||
|
[CreatedDate] DATETIME NOT NULL DEFAULT GETDATE(),
|
||||||
|
[ApprovedBy] VARCHAR(450) NULL,
|
||||||
|
[ApprovedDate] DATETIME NULL,
|
||||||
|
CanceledBy VARCHAR(50) NULL,
|
||||||
|
CanceledDate DATETIME NULL,
|
||||||
|
Reason VARCHAR(150) NULL,
|
||||||
|
FOREIGN KEY ([RISId]) REFERENCES [RIS]([RISId]),
|
||||||
|
FOREIGN KEY ([InventoryId]) REFERENCES [Inventory]([InventoryId])
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO [dbo].[TransTypes]
|
||||||
|
(TransTypeName)
|
||||||
|
VALUES
|
||||||
|
('RIS'),('MRS')
|
||||||
@ -87,6 +87,7 @@ namespace CPRNIMS.WebApps.Common
|
|||||||
builder.Services.AddTransient<IAccount, Account>();
|
builder.Services.AddTransient<IAccount, Account>();
|
||||||
builder.Services.AddTransient<ICaptchaService, CaptchaService>();
|
builder.Services.AddTransient<ICaptchaService, CaptchaService>();
|
||||||
builder.Services.AddScoped<ErrorLogHelper>();
|
builder.Services.AddScoped<ErrorLogHelper>();
|
||||||
|
builder.Services.AddScoped<IRIS, RIS>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddSessionAndAuthentication(WebApplicationBuilder builder)
|
private static void AddSessionAndAuthentication(WebApplicationBuilder builder)
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
using CPRNIMS.Domain.UIContracts.Account;
|
using Azure.Core;
|
||||||
|
using CPRNIMS.Domain.UIContracts.Account;
|
||||||
using CPRNIMS.Domain.UIContracts.Inventory;
|
using CPRNIMS.Domain.UIContracts.Inventory;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory.Request;
|
||||||
using CPRNIMS.Infrastructure.Helper;
|
using CPRNIMS.Infrastructure.Helper;
|
||||||
using CPRNIMS.Infrastructure.ViewModel.Account;
|
|
||||||
using CPRNIMS.Infrastructure.ViewModel.Finance;
|
|
||||||
using CPRNIMS.Infrastructure.ViewModel.Inventory;
|
using CPRNIMS.Infrastructure.ViewModel.Inventory;
|
||||||
using CPRNIMS.WebApps.Controllers.Base;
|
using CPRNIMS.WebApps.Controllers.Base;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
|
||||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
|
||||||
|
|
||||||
namespace CPRNIMS.WebApps.Controllers.Inventory
|
namespace CPRNIMS.WebApps.Controllers.Inventory
|
||||||
{
|
{
|
||||||
@ -23,61 +22,30 @@ namespace CPRNIMS.WebApps.Controllers.Inventory
|
|||||||
_inventory = inventory;
|
_inventory = inventory;
|
||||||
}
|
}
|
||||||
#region Get
|
#region Get
|
||||||
|
public async Task<IActionResult> GetTransactContext(int inventoryId)
|
||||||
|
{
|
||||||
|
var response = await _inventory.GetTransactContextAsync(inventoryId);
|
||||||
|
return GetResponse(response);
|
||||||
|
}
|
||||||
public async Task<IActionResult> GetLotQtyByItem(InventoryVM viewModels)
|
public async Task<IActionResult> GetLotQtyByItem(InventoryVM viewModels)
|
||||||
{
|
{
|
||||||
try
|
response = await _inventory.GetLotQtyByItem(GetUser(), viewModels);
|
||||||
{
|
return GetResponse(response);
|
||||||
response = await _inventory.GetLotQtyByItem(GetUser(), viewModels);
|
|
||||||
return GetResponse(response);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var message = ex.InnerException?.ToString() ?? ex.Message.ToString();
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
public async Task<IActionResult> GetLotNo(InventoryVM viewModels)
|
public async Task<IActionResult> GetLotNo(InventoryVM viewModels)
|
||||||
{
|
{
|
||||||
try
|
response = await _inventory.GetLotNo(GetUser(), viewModels);
|
||||||
{
|
return GetResponse(response);
|
||||||
response = await _inventory.GetLotNo(GetUser(), viewModels);
|
|
||||||
return GetResponse(response);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var message = ex.InnerException?.ToString() ?? ex.Message.ToString();
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
public async Task<IActionResult> GetLotNoById(InventoryVM viewModels)
|
public async Task<IActionResult> GetLotNoById(InventoryVM viewModels)
|
||||||
{
|
{
|
||||||
try
|
response = await _inventory.GetLotNoById(GetUser(), viewModels);
|
||||||
{
|
return GetResponse(response);
|
||||||
response = await _inventory.GetLotNoById(GetUser(), viewModels);
|
|
||||||
return GetResponse(response);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var message = ex.InnerException?.ToString() ?? ex.Message.ToString();
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
public async Task<IActionResult> GetInventoryById(InventoryVM viewModels)
|
public async Task<IActionResult> GetInventoryById(InventoryVM viewModels)
|
||||||
{
|
{
|
||||||
try
|
response = await _inventory.GetInventoryById(GetUser(), viewModels);
|
||||||
{
|
return GetResponse(response);
|
||||||
response = await _inventory.GetInventoryById(GetUser(), viewModels);
|
|
||||||
return GetResponse(response);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var message = ex.InnerException?.ToString() ?? ex.Message.ToString();
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
public async Task<IActionResult> GetInventoryByUserId(InventoryVM viewModels)
|
public async Task<IActionResult> GetInventoryByUserId(InventoryVM viewModels)
|
||||||
{
|
{
|
||||||
@ -86,99 +54,88 @@ namespace CPRNIMS.WebApps.Controllers.Inventory
|
|||||||
}
|
}
|
||||||
public async Task<IActionResult> GetRequestedItemByUserId(InventoryVM viewModels)
|
public async Task<IActionResult> GetRequestedItemByUserId(InventoryVM viewModels)
|
||||||
{
|
{
|
||||||
try
|
response = await _inventory.GetRequestedItemByUserId(GetUser(), viewModels);
|
||||||
|
return GetResponse(response);
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> GetInventory(string searchPRNo = "", string searchItemNo = "",string searchItemName = "",
|
||||||
|
string searchDept = "",string searchProjectCode="", int pageNumber = 1, int pageSize = 10)
|
||||||
|
{
|
||||||
|
var request = new InventoryRequest
|
||||||
{
|
{
|
||||||
response = await _inventory.GetRequestedItemByUserId(GetUser(), viewModels);
|
SearchPRNo = searchPRNo,
|
||||||
return GetResponse(response);
|
SearchItemNo = searchItemNo,
|
||||||
}
|
SearchItemName = searchItemName,
|
||||||
catch (Exception ex)
|
SearchDept = searchDept,
|
||||||
|
SearchProjectCode = searchProjectCode,
|
||||||
|
PageNumber = pageNumber,
|
||||||
|
PageSize = pageSize
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await _inventory.GetInventory(GetUser(), request);
|
||||||
|
int draw = int.TryParse(Request.Query["draw"], out int d) ? d : 1;
|
||||||
|
|
||||||
|
return Json(new
|
||||||
{
|
{
|
||||||
var message = ex.InnerException?.ToString() ?? ex.Message.ToString();
|
draw = draw,
|
||||||
|
recordsTotal = result.TotalCount,
|
||||||
throw;
|
recordsFiltered = result.TotalCount,
|
||||||
}
|
data = result.Data,
|
||||||
|
departmentList=result.DepartmentList,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
#region POST PUT
|
#region POST PUT
|
||||||
public async Task<IActionResult> PostPutLotNo(InventoryVM viewModel)
|
public async Task<IActionResult> PostPutLotNo(InventoryVM viewModel)
|
||||||
{
|
{
|
||||||
try
|
var postPutItem = await _inventory.PostPutLotNo(GetUser(), viewModel);
|
||||||
{
|
|
||||||
var postPutItem = await _inventory.PostPutLotNo(GetUser(), viewModel);
|
|
||||||
|
|
||||||
if (postPutItem.StatusResponse != "Error")
|
if (postPutItem.StatusResponse != "Error")
|
||||||
{
|
|
||||||
return Json(new { success = true });
|
|
||||||
}
|
|
||||||
|
|
||||||
return Json(new { success = false, Response = postPutItem.Message });
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
{
|
||||||
var message = ex.InnerException?.ToString() ?? ex.Message.ToString();
|
return Json(new { success = true });
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Json(new { success = false, Response = postPutItem.Message });
|
||||||
}
|
}
|
||||||
public async Task<IActionResult> PostPutLotBin(InventoryVM viewModel)
|
public async Task<IActionResult> PostPutLotBin(InventoryVM viewModel)
|
||||||
{
|
{
|
||||||
try
|
var postPutItem = await _inventory.PostPutLotBin(GetUser(), viewModel);
|
||||||
{
|
|
||||||
var postPutItem = await _inventory.PostPutLotBin(GetUser(), viewModel);
|
|
||||||
|
|
||||||
if (postPutItem.StatusResponse != "Error")
|
if (postPutItem.StatusResponse != "Error")
|
||||||
{
|
|
||||||
return Json(new { success = true });
|
|
||||||
}
|
|
||||||
|
|
||||||
return Json(new { success = false, Response = postPutItem.Message });
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
{
|
||||||
var message = ex.InnerException?.ToString() ?? ex.Message.ToString();
|
return Json(new { success = true });
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Json(new { success = false, Response = postPutItem.Message });
|
||||||
}
|
}
|
||||||
public async Task<IActionResult> PostPutReqApproval(InventoryVM viewModel)
|
public async Task<IActionResult> PostPutReqApproval(InventoryVM viewModel)
|
||||||
{
|
{
|
||||||
try
|
var postPutItem = await _inventory.PostPutReqApproval(GetUser(), viewModel);
|
||||||
{
|
|
||||||
var postPutItem = await _inventory.PostPutReqApproval(GetUser(), viewModel);
|
|
||||||
|
|
||||||
if (postPutItem.StatusResponse != "Error")
|
if (postPutItem.StatusResponse != "Error")
|
||||||
{
|
|
||||||
return Json(new { success = true });
|
|
||||||
}
|
|
||||||
|
|
||||||
return Json(new { success = false, Response = postPutItem.Message });
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
{
|
||||||
var message = ex.InnerException?.ToString() ?? ex.Message.ToString();
|
return Json(new { success = true });
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Json(new { success = false, Response = postPutItem.Message });
|
||||||
}
|
}
|
||||||
public async Task<IActionResult> PostPutReqItems(InventoryVM viewModel)
|
public async Task<IActionResult> PostPutReqItems(InventoryVM viewModel)
|
||||||
{
|
{
|
||||||
try
|
var postPutItem = await _inventory.PostPutReqItems(GetUser(), viewModel);
|
||||||
{
|
|
||||||
var postPutItem = await _inventory.PostPutReqItems(GetUser(), viewModel);
|
|
||||||
|
|
||||||
if (postPutItem.StatusResponse != "Error")
|
if (postPutItem.StatusResponse != "Error")
|
||||||
{
|
|
||||||
return Json(new { success = true });
|
|
||||||
}
|
|
||||||
|
|
||||||
return Json(new { success = false, Response = postPutItem.Message });
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
{
|
||||||
var message = ex.InnerException?.ToString() ?? ex.Message.ToString();
|
return Json(new { success = true });
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Json(new { success = false, Response = postPutItem.Message });
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
#region Views
|
#region Views
|
||||||
|
public IActionResult GetInventoryTabPage(int id)
|
||||||
|
{
|
||||||
|
return ViewComponent("InventoryTabPage", new { inventoryTabPageId = id });
|
||||||
|
}
|
||||||
public async Task<IActionResult> Inventory()
|
public async Task<IActionResult> Inventory()
|
||||||
{
|
{
|
||||||
return await IsAuthenTicated();
|
return await IsAuthenTicated();
|
||||||
|
|||||||
12
CPRNIMS.WebApps/Controllers/Inventory/MRSController.cs
Normal file
12
CPRNIMS.WebApps/Controllers/Inventory/MRSController.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace CPRNIMS.WebApps.Controllers.Inventory
|
||||||
|
{
|
||||||
|
public class MRSController : Controller
|
||||||
|
{
|
||||||
|
public IActionResult Index()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
101
CPRNIMS.WebApps/Controllers/Inventory/RISMgmtController.cs
Normal file
101
CPRNIMS.WebApps/Controllers/Inventory/RISMgmtController.cs
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
using CPRNIMS.Domain.UIContracts.Account;
|
||||||
|
using CPRNIMS.Domain.UIContracts.Inventory;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Inventory.Request;
|
||||||
|
using CPRNIMS.Infrastructure.Helper;
|
||||||
|
using CPRNIMS.WebApps.Controllers.Base;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace CPRNIMS.WebApps.Controllers.Inventory
|
||||||
|
{
|
||||||
|
public class RISMgmtController : BaseMethod
|
||||||
|
{
|
||||||
|
private readonly IRIS _ris;
|
||||||
|
public RISMgmtController(ErrorLogHelper errorMessageService,
|
||||||
|
IWebHostEnvironment webHostEnvironment, TokenHelper tokenHelper
|
||||||
|
, IRIS ris, IAccount account)
|
||||||
|
: base(errorMessageService, webHostEnvironment, tokenHelper, account)
|
||||||
|
{
|
||||||
|
_ris = ris;
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> CreateRIS([FromBody] CreateRISRequest request,CancellationToken ct)
|
||||||
|
{
|
||||||
|
var result = await _ris.CreateRIS(request,ct);
|
||||||
|
|
||||||
|
return Json(new { success = true, message= $"RIS {result.RISNo} created successfully.", data = result });
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> ApproveRIS([FromBody] ApproveRISRequest request,CancellationToken ct)
|
||||||
|
{
|
||||||
|
bool isSuccess = await _ris.ApproveRIS(request, ct);
|
||||||
|
|
||||||
|
if (!isSuccess)
|
||||||
|
return BadRequest(new { success = false, message = "RIS cancelled failed" });
|
||||||
|
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
success = true,
|
||||||
|
message = $"RIS approved successfully."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> CancelRIS([FromBody] CancelRISRequest request,CancellationToken ct)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(request.Reason))
|
||||||
|
return BadRequest(new
|
||||||
|
{
|
||||||
|
success = false,
|
||||||
|
message = "A reason for cancellation is required."
|
||||||
|
});
|
||||||
|
bool isSuccess = await _ris.CancelRIS(request,ct);
|
||||||
|
|
||||||
|
if (!isSuccess)
|
||||||
|
return BadRequest(new { success = false, message = "RIS cancelled failed" });
|
||||||
|
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
success = true,
|
||||||
|
message = "RIS cancelled and inventory restored."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> GetRIS(
|
||||||
|
[FromQuery] string? searchRISNo,string? searchItemName,string? searchIssuedTo,string? discipline,string? status,
|
||||||
|
int pageNumber = 1,int pageSize = 12,CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
short? statusCode = status switch
|
||||||
|
{
|
||||||
|
"0" => 0,
|
||||||
|
"1" => 1,
|
||||||
|
"2" => 2,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await _ris.GetRISPaged(new RISPagedRequest
|
||||||
|
{
|
||||||
|
SearchRISNo = searchRISNo,
|
||||||
|
SearchItemName = searchItemName,
|
||||||
|
SearchIssuedTo = searchIssuedTo,
|
||||||
|
Discipline = discipline,
|
||||||
|
Status = statusCode,
|
||||||
|
PageNumber = pageNumber,
|
||||||
|
PageSize = pageSize
|
||||||
|
}, ct);
|
||||||
|
|
||||||
|
return Json(new
|
||||||
|
{
|
||||||
|
data = result.Data,
|
||||||
|
recordsTotal = result.RecordsTotal,
|
||||||
|
departmentList = result.DepartmentList,
|
||||||
|
disciplineList = result.DisciplineList
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public async Task<IActionResult> GetRISById(int risId, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var response = await _ris.GetRISById(risId,ct);
|
||||||
|
return GetResponse(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace CPRNIMS.WebApps.ViewComponents.Inventory
|
||||||
|
{
|
||||||
|
public class InventoryTabPageViewComponent : ViewComponent
|
||||||
|
{
|
||||||
|
public IViewComponentResult Invoke(int InventoryTabPageId)
|
||||||
|
{
|
||||||
|
string viewName = InventoryTabPageId switch
|
||||||
|
{
|
||||||
|
1 => "~/Views/Components/Inventory/TabPage/Inventory.cshtml",
|
||||||
|
2 => "~/Views/Components/Inventory/TabPage/RISForApproval.cshtml",
|
||||||
|
3 => "~/Views/Components/Inventory/TabPage/MRS.cshtml",
|
||||||
|
_ => "~/Views/Components/Inventory/TabPage/InventoryTransaction.cshtml"
|
||||||
|
};
|
||||||
|
return View(viewName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -70,5 +70,5 @@
|
|||||||
@await Html.PartialAsync("PagesView/Canvass/_Suppliers")
|
@await Html.PartialAsync("PagesView/Canvass/_Suppliers")
|
||||||
@await Html.PartialAsync("PagesView/PR/_PRWOCanvass")
|
@await Html.PartialAsync("PagesView/PR/_PRWOCanvass")
|
||||||
@await Html.PartialAsync("PagesView/Canvass/_CanvassSCript")
|
@await Html.PartialAsync("PagesView/Canvass/_CanvassSCript")
|
||||||
<script src="~/jsfunctions/common/ParamConfigV2.js"></script>
|
<script src="~/jsfunctions/common/ParamConfigV3.js"></script>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -0,0 +1,299 @@
|
|||||||
|
<div class="inv-filters">
|
||||||
|
<div class="inv-search-box">
|
||||||
|
<i class="fas fa-file-alt"></i>
|
||||||
|
<input type="text" id="inv-srchPRNo" placeholder="PR Number..." />
|
||||||
|
</div>
|
||||||
|
<div class="inv-search-box">
|
||||||
|
<i class="fas fa-hashtag"></i>
|
||||||
|
<input type="text" id="inv-srchItemNo" placeholder="Item Number..." />
|
||||||
|
</div>
|
||||||
|
<div class="inv-search-box">
|
||||||
|
<i class="fas fa-box"></i>
|
||||||
|
<input type="text" id="inv-srchItemName" placeholder="Item Name..." />
|
||||||
|
</div>
|
||||||
|
<div class="inv-search-box">
|
||||||
|
<i class="fas fa-qrcode"></i>
|
||||||
|
<input type="text" id="inv-srchProjectCode" placeholder="Project Code..." />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inv-department-wrap" id="inv-departmentWrap">
|
||||||
|
<div class="inv-dep-trigger">
|
||||||
|
<span class="inv-dep-left">
|
||||||
|
<i class="fas fa-building"></i>
|
||||||
|
<span class="inv-dep-lbl">All Department</span>
|
||||||
|
</span>
|
||||||
|
<i class="fas fa-chevron-down inv-dep-caret"></i>
|
||||||
|
</div>
|
||||||
|
<div class="inv-dep-dropdown">
|
||||||
|
<div class="inv-dep-searchbox">
|
||||||
|
<i class="fas fa-search"></i>
|
||||||
|
<input type="text" placeholder="Search department name..." autocomplete="off" />
|
||||||
|
</div>
|
||||||
|
<div class="inv-dep-list">
|
||||||
|
<div class="inv-dep-opt active" data-value="">
|
||||||
|
<i class="fas fa-th-large"></i> All Department
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inv-filter-right">
|
||||||
|
<span class="inv-pgsz-lbl">Show</span>
|
||||||
|
<select class="inv-pgsz-sel" id="inv-pageSize">
|
||||||
|
<option value="6">6 per page</option>
|
||||||
|
<option value="12" selected>12 per page</option>
|
||||||
|
<option value="24">24 per page</option>
|
||||||
|
<option value="48">48 per page</option>
|
||||||
|
<option value="96">96 per page</option>
|
||||||
|
</select>
|
||||||
|
<span class="inv-result-count" id="inv-resultCount">0 results</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* {{-- CARD GRID --}} *@
|
||||||
|
<div class="inv-grid" id="inv-grid">
|
||||||
|
<div class="inv-state" style="grid-column:1/-1">
|
||||||
|
<div class="inv-spinner"></div><p>Loading…</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* {{-- PAGINATION --}} *@
|
||||||
|
<div class="inv-pagination">
|
||||||
|
<span class="inv-pg-info" id="inv-pageInfo"></span>
|
||||||
|
<div class="inv-pg-btns" id="inv-pageButtons"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
const H = window.InventoryHelpers;
|
||||||
|
|
||||||
|
// ── Single state object ──
|
||||||
|
const s = {
|
||||||
|
page: 1, pageSize: 12, totalCount: 0,
|
||||||
|
Department: "", searchPR: "", searchItem: "", searchName: "",
|
||||||
|
searchProjectCode: "", timer: null
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── Elements ──
|
||||||
|
const grid = document.getElementById("inv-grid");
|
||||||
|
const countEl = document.getElementById("inv-resultCount");
|
||||||
|
const pageInfo = document.getElementById("inv-pageInfo");
|
||||||
|
const pageBtns = document.getElementById("inv-pageButtons");
|
||||||
|
const inItemNo = document.getElementById("inv-srchItemNo");
|
||||||
|
const inName = document.getElementById("inv-srchItemName");
|
||||||
|
const inPR = document.getElementById("inv-srchPRNo");
|
||||||
|
const inSize = document.getElementById("inv-pageSize");
|
||||||
|
const depWrap = document.getElementById("inv-departmentWrap");
|
||||||
|
|
||||||
|
// ── Guard ──
|
||||||
|
if (!grid || !depWrap) {
|
||||||
|
console.error("Tab 1 init failed. Missing:", { grid, depWrap });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Single dropdown, using s.Department ──
|
||||||
|
const depDropdown = H.initDepartmentDropdown(depWrap, val => {
|
||||||
|
s.Department = val;
|
||||||
|
s.page = 1;
|
||||||
|
fetchData();
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Search inputs ──
|
||||||
|
[inItemNo, inName, inPR].forEach(el => {
|
||||||
|
if (!el) return;
|
||||||
|
el.addEventListener("input", () => {
|
||||||
|
clearTimeout(s.timer);
|
||||||
|
s.timer = setTimeout(() => {
|
||||||
|
s.searchItem = inItemNo?.value.trim() ?? "";
|
||||||
|
s.searchName = inName?.value.trim() ?? "";
|
||||||
|
s.searchPR = inPR?.value.trim() ?? "";
|
||||||
|
s.page = 1;
|
||||||
|
fetchData();
|
||||||
|
}, 350);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (inSize) {
|
||||||
|
inSize.addEventListener("change", () => {
|
||||||
|
s.pageSize = parseInt(inSize.value, 10);
|
||||||
|
s.page = 1;
|
||||||
|
fetchData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchData() {
|
||||||
|
grid.innerHTML = `<div class="inv-state" style="grid-column:1/-1">
|
||||||
|
<div class="inv-spinner"></div><p>Loading…</p></div>`;
|
||||||
|
|
||||||
|
const p = new URLSearchParams({
|
||||||
|
searchPRNo: s.searchPR,
|
||||||
|
searchItemNo: s.searchItem,
|
||||||
|
searchItemName: s.searchName,
|
||||||
|
searchDept: s.Department,
|
||||||
|
searchProjectCode: s.searchProjectCode ?? "",
|
||||||
|
pageNumber: s.page,
|
||||||
|
pageSize: s.pageSize,
|
||||||
|
draw: Date.now()
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/InventoryMgmt/GetInventory?${p}`);
|
||||||
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
s.totalCount = json.recordsTotal ?? 0;
|
||||||
|
|
||||||
|
// Populate department dropdown from API response
|
||||||
|
if (json.departmentList) depDropdown.setItems(json.departmentList);
|
||||||
|
|
||||||
|
renderCards(json.data ?? []);
|
||||||
|
|
||||||
|
H.renderPagination(pageBtns, pageInfo, s, pg => {
|
||||||
|
s.page = pg;
|
||||||
|
fetchData();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (countEl) countEl.textContent =
|
||||||
|
`${s.totalCount.toLocaleString()} result${s.totalCount !== 1 ? "s" : ""}`;
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error("GetInventory error:", err);
|
||||||
|
grid.innerHTML = `<div class="inv-state" style="grid-column:1/-1">
|
||||||
|
<i class="fas fa-exclamation-triangle" style="color:#ff5c5c"></i>
|
||||||
|
<p>Failed to load data.</p></div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCards(data) {
|
||||||
|
if (!data.length) {
|
||||||
|
grid.innerHTML = `
|
||||||
|
<div class="inv-state" style="grid-column:1/-1">
|
||||||
|
<i class="fas fa-inbox"></i>
|
||||||
|
<p>No records found.</p>
|
||||||
|
</div>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.innerHTML = data.map(item => buildInventoryCardHtml(item)).join("");
|
||||||
|
|
||||||
|
// Wire Transact buttons
|
||||||
|
grid.querySelectorAll(".btn-transact").forEach(btn => {
|
||||||
|
|
||||||
|
btn.addEventListener("click", () => {
|
||||||
|
const inventoryId = parseInt(btn.dataset.inventoryid, 10);
|
||||||
|
TransactModal.open(inventoryId, () => fetchData());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function buildInventoryCardHtml(item) {
|
||||||
|
const H = window.InventoryHelpers;
|
||||||
|
const qtyIn = item.qtyIn ?? 0;
|
||||||
|
const qtyOut = item.qtyOut ?? 0;
|
||||||
|
const qtyOnHand = item.qtyOnHand ?? 0;
|
||||||
|
|
||||||
|
// Stock level % for progress bar (relative to qtyIn; floor at 0)
|
||||||
|
const pct = qtyIn > 0 ? Math.max(0, Math.min(100, Math.round((qtyOnHand / qtyIn) * 100))) : 0;
|
||||||
|
const isLow = pct < 20;
|
||||||
|
|
||||||
|
// Status badge
|
||||||
|
const statusHtml = qtyOnHand <= 0
|
||||||
|
? `<span class="inv-stock-badge inv-stock-empty">Out of stock</span>`
|
||||||
|
: isLow
|
||||||
|
? `<span class="inv-stock-badge inv-stock-low">Low stock</span>`
|
||||||
|
: `<span class="inv-stock-badge inv-stock-ok">In stock</span>`;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="inv-card">
|
||||||
|
|
||||||
|
<!-- HEAD -->
|
||||||
|
<div class="inv-card-hd">
|
||||||
|
<div style="display:flex;align-items:flex-start;gap:10px;flex:1;min-width:0">
|
||||||
|
<div class="inv-card-icon-wrap ${isLow ? "inv-card-icon-low" : ""}">
|
||||||
|
<i class="fas fa-box${qtyOnHand <= 0 ? "-open" : ""}"></i>
|
||||||
|
</div>
|
||||||
|
<div style="flex:1;min-width:0">
|
||||||
|
<div class="inv-card-code">PR NO #${(item.prNo ?? "—")}</div>
|
||||||
|
<div class="inv-card-code">ITEM NO #${H.escHtml(String(item.itemNo ?? "—"))}</div>
|
||||||
|
<div class="inv-card-name">${H.escHtml(item.itemName ?? "—")}</div>
|
||||||
|
<div class="inv-card-meta-row">
|
||||||
|
<span><i class="fas fa-building"></i> ${H.escHtml(item.department ?? "—")}</span>
|
||||||
|
<span><i class="fas fa-tag"></i> ${H.escHtml(item.itemCategoryName ?? "—")}</span>
|
||||||
|
</div>
|
||||||
|
<div class="inv-card-meta-row">
|
||||||
|
<span><i class="fas fa-clock"></i> ${_formatDate(item.createdDate)}</span>
|
||||||
|
${item.lotNo ? `<span><i class="fas fa-barcode"></i> ${H.escHtml(item.lotNo)}</span>` : ""}
|
||||||
|
</div>
|
||||||
|
<div class="inv-card-sub">
|
||||||
|
<i class="fas fa-qrcode"></i>
|
||||||
|
${(item.projectCode ?? "—")}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
${statusHtml}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- STATS GRID -->
|
||||||
|
<div class="inv-stats-grid">
|
||||||
|
<div class="inv-stat-cell inv-stat-in">
|
||||||
|
<span class="inv-stat-lbl"><i class="fas fa-arrow-circle-down"></i> QTY IN</span>
|
||||||
|
<span class="inv-stat-val">${_fmtNum(qtyIn)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="inv-stat-cell inv-stat-out">
|
||||||
|
<span class="inv-stat-lbl"><i class="fas fa-arrow-circle-up"></i> QTY OUT</span>
|
||||||
|
<span class="inv-stat-val">${_fmtNum(qtyOut)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="inv-stat-cell inv-stat-hand">
|
||||||
|
<span class="inv-stat-lbl"><i class="fas fa-layer-group"></i> ON HAND</span>
|
||||||
|
<span class="inv-stat-val">${_fmtNum(qtyOnHand)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- PROGRESS BAR -->
|
||||||
|
<div class="inv-progress-wrap">
|
||||||
|
<div class="inv-progress-labels">
|
||||||
|
<span>Stock level</span>
|
||||||
|
<span>${pct}%</span>
|
||||||
|
</div>
|
||||||
|
<div class="inv-progress-track">
|
||||||
|
<div class="inv-progress-fill ${isLow ? "inv-progress-low" : ""}"
|
||||||
|
style="width:${pct}%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ITEM ROW -->
|
||||||
|
${item.itemDescription ? `
|
||||||
|
<div class="inv-item-desc-row">
|
||||||
|
<span class="inv-item-desc-lbl"><i class="fas fa-info-circle"></i> ITEM</span>
|
||||||
|
<span class="inv-item-desc-name">${H.escHtml(item.itemName ?? "—")}</span>
|
||||||
|
<span class="inv-item-desc-qty">
|
||||||
|
<i class="fas fa-cubes"></i> ${_fmtNum(qtyIn)}
|
||||||
|
</span>
|
||||||
|
</div>` : ""}
|
||||||
|
|
||||||
|
<!-- FOOTER -->
|
||||||
|
<div class="inv-card-ft">
|
||||||
|
<button class="inv-btn inv-btn-primary btn-transact"
|
||||||
|
data-inventoryid="${item.inventoryId}">
|
||||||
|
<i class="fas fa-pen"></i> Transact
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function _fmtNum(n) {
|
||||||
|
const v = parseFloat(n);
|
||||||
|
return isNaN(v) ? "—" : v.toLocaleString(undefined, { maximumFractionDigits: 2 });
|
||||||
|
}
|
||||||
|
|
||||||
|
function _formatDate(raw) {
|
||||||
|
if (!raw) return "—";
|
||||||
|
const d = new Date(raw);
|
||||||
|
return isNaN(d) ? raw : d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
|
||||||
|
}
|
||||||
|
fetchData();
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
@ -0,0 +1,936 @@
|
|||||||
|
@* ── Tab: Return Issuance Slip — For Approval ───────────────────────────────
|
||||||
|
Returned by GetInventoryTabPage?id=2 (or whichever tab id you assign)
|
||||||
|
Depends on: _InventoryStyles, _InventoryHelpers, window.InventoryHelpers
|
||||||
|
────────────────────────────────────────────────────────────────────────────── *@
|
||||||
|
|
||||||
|
@* ── FILTER BAR ── *@
|
||||||
|
<div class="inv-filters">
|
||||||
|
<div class="inv-search-box">
|
||||||
|
<i class="fas fa-hashtag"></i>
|
||||||
|
<input type="text" id="ris-srchRISNo" placeholder="RIS Number..." />
|
||||||
|
</div>
|
||||||
|
<div class="inv-search-box">
|
||||||
|
<i class="fas fa-box"></i>
|
||||||
|
<input type="text" id="ris-srchItemName" placeholder="Item Name..." />
|
||||||
|
</div>
|
||||||
|
<div class="inv-search-box">
|
||||||
|
<i class="fas fa-user"></i>
|
||||||
|
<input type="text" id="ris-srchIssuedTo" placeholder="Issued To..." />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* ── Discipline dropdown ── *@
|
||||||
|
<div class="inv-department-wrap" id="ris-disciplineWrap">
|
||||||
|
<div class="inv-dep-trigger">
|
||||||
|
<span class="inv-dep-left">
|
||||||
|
<i class="fas fa-tools"></i>
|
||||||
|
<span class="inv-dep-lbl">All Disciplines</span>
|
||||||
|
</span>
|
||||||
|
<i class="fas fa-chevron-down inv-dep-caret"></i>
|
||||||
|
</div>
|
||||||
|
<div class="inv-dep-dropdown">
|
||||||
|
<div class="inv-dep-searchbox">
|
||||||
|
<i class="fas fa-search"></i>
|
||||||
|
<input type="text" placeholder="Search discipline..." autocomplete="off" />
|
||||||
|
</div>
|
||||||
|
<div class="inv-dep-list">
|
||||||
|
<div class="inv-dep-opt active" data-value="">
|
||||||
|
<i class="fas fa-th-large"></i> All Disciplines
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* ── Status filter ── *@
|
||||||
|
<div class="inv-department-wrap" id="ris-statusWrap" style="max-width:160px">
|
||||||
|
<div class="inv-dep-trigger">
|
||||||
|
<span class="inv-dep-left">
|
||||||
|
<i class="fas fa-circle-dot"></i>
|
||||||
|
<span class="inv-dep-lbl">All Status</span>
|
||||||
|
</span>
|
||||||
|
<i class="fas fa-chevron-down inv-dep-caret"></i>
|
||||||
|
</div>
|
||||||
|
<div class="inv-dep-dropdown">
|
||||||
|
<div class="inv-dep-searchbox">
|
||||||
|
<i class="fas fa-search"></i>
|
||||||
|
<input type="text" placeholder="Search status..." autocomplete="off" />
|
||||||
|
</div>
|
||||||
|
<div class="inv-dep-list">
|
||||||
|
<div class="inv-dep-opt active" data-value="">
|
||||||
|
<i class="fas fa-th-large"></i> All Status
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inv-filter-right">
|
||||||
|
<span class="inv-pgsz-lbl">Show</span>
|
||||||
|
<select class="inv-pgsz-sel" id="ris-pageSize">
|
||||||
|
<option value="6">6 per page</option>
|
||||||
|
<option value="12" selected>12 per page</option>
|
||||||
|
<option value="24">24 per page</option>
|
||||||
|
<option value="48">48 per page</option>
|
||||||
|
</select>
|
||||||
|
<span class="inv-result-count" id="ris-resultCount">0 results</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* ── CARD GRID ── *@
|
||||||
|
<div class="ris-grid" id="ris-grid">
|
||||||
|
<div class="inv-state">
|
||||||
|
<div class="inv-spinner"></div>
|
||||||
|
<p>Loading…</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* ── PAGINATION ── *@
|
||||||
|
<div class="inv-pagination">
|
||||||
|
<span class="inv-pg-info" id="ris-pageInfo"></span>
|
||||||
|
<div class="inv-pg-btns" id="ris-pageButtons"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* ── APPROVE CONFIRMATION MODAL ── *@
|
||||||
|
<div id="ris-approve-modal-overlay" style="display:none;position:fixed;inset:0;
|
||||||
|
z-index:1080;background:rgba(0,0,0,.45);
|
||||||
|
display:none;align-items:center;justify-content:center;padding:16px">
|
||||||
|
<div style="background:var(--card-bg,#fff);border-radius:14px;
|
||||||
|
border:1px solid var(--border,#d6eaec);width:100%;max-width:440px;
|
||||||
|
overflow:hidden;box-shadow:0 20px 60px rgba(0,0,0,.2)">
|
||||||
|
<div style="background:linear-gradient(135deg,#0d5c63,#0e7c86);
|
||||||
|
padding:16px 18px;display:flex;align-items:center;gap:12px">
|
||||||
|
<div style="width:38px;height:38px;border-radius:10px;
|
||||||
|
background:rgba(255,255,255,.15);
|
||||||
|
display:flex;align-items:center;justify-content:center">
|
||||||
|
<i class="fas fa-check-circle" style="color:#fff;font-size:16px"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style="font-size:14px;font-weight:600;color:#fff">Approve RIS</div>
|
||||||
|
<div id="ris-approve-subtitle"
|
||||||
|
style="font-size:11px;color:rgba(255,255,255,.65);margin-top:2px">
|
||||||
|
Loading…
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="padding:20px 18px">
|
||||||
|
<div id="ris-approve-detail"
|
||||||
|
style="background:var(--teal-pale,#e6f7f8);border:1px solid var(--border,#d6eaec);
|
||||||
|
border-radius:10px;padding:14px 16px;margin-bottom:16px;
|
||||||
|
display:grid;grid-template-columns:1fr 1fr;gap:10px">
|
||||||
|
</div>
|
||||||
|
<p style="font-size:13px;color:var(--text-dark,#1a2e35);line-height:1.6">
|
||||||
|
Approving this slip will confirm the issuance and update the inventory.
|
||||||
|
<strong>This action cannot be undone.</strong>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style="padding:12px 18px;border-top:1px solid var(--border,#d6eaec);
|
||||||
|
background:var(--bg-page,#f0f6f7);
|
||||||
|
display:flex;align-items:center;justify-content:flex-end;gap:8px">
|
||||||
|
<button id="ris-approve-cancel-btn" class="tm-btn-cancel">Cancel</button>
|
||||||
|
<button id="ris-approve-confirm-btn"
|
||||||
|
style="padding:8px 20px;border-radius:8px;border:none;
|
||||||
|
background:#0e7c86;color:#fff;font-family:'DM Sans',sans-serif;
|
||||||
|
font-size:.85rem;font-weight:600;cursor:pointer;
|
||||||
|
display:flex;align-items:center;gap:7px">
|
||||||
|
<i class="fas fa-check"></i> Approve RIS
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* ── CANCEL CONFIRMATION MODAL ── *@
|
||||||
|
<div id="ris-cancel-modal-overlay" style="display:none;position:fixed;inset:0;
|
||||||
|
z-index:1080;background:rgba(0,0,0,.45);
|
||||||
|
display:none;align-items:center;justify-content:center;padding:16px">
|
||||||
|
<div style="background:var(--card-bg,#fff);border-radius:14px;
|
||||||
|
border:1px solid var(--border,#d6eaec);width:100%;max-width:440px;
|
||||||
|
overflow:hidden;box-shadow:0 20px 60px rgba(0,0,0,.2)">
|
||||||
|
<div style="background:linear-gradient(135deg,#7f1d1d,#b91c1c);
|
||||||
|
padding:16px 18px;display:flex;align-items:center;gap:12px">
|
||||||
|
<div style="width:38px;height:38px;border-radius:10px;
|
||||||
|
background:rgba(255,255,255,.15);
|
||||||
|
display:flex;align-items:center;justify-content:center">
|
||||||
|
<i class="fas fa-ban" style="color:#fff;font-size:16px"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style="font-size:14px;font-weight:600;color:#fff">Cancel RIS</div>
|
||||||
|
<div id="ris-cancel-subtitle"
|
||||||
|
style="font-size:11px;color:rgba(255,255,255,.65);margin-top:2px">
|
||||||
|
Loading…
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="padding:20px 18px">
|
||||||
|
<div id="ris-cancel-detail"
|
||||||
|
style="background:#fef2f2;border:1px solid #fecaca;
|
||||||
|
border-radius:10px;padding:14px 16px;margin-bottom:16px;
|
||||||
|
display:grid;grid-template-columns:1fr 1fr;gap:10px">
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;gap:8px;padding:10px 12px;background:#fef2f2;
|
||||||
|
border:1px solid #fecaca;border-radius:8px;margin-bottom:12px">
|
||||||
|
<i class="fas fa-exclamation-triangle"
|
||||||
|
style="color:#dc2626;flex-shrink:0;margin-top:2px"></i>
|
||||||
|
<p style="font-size:12px;color:#7f1d1d;line-height:1.6;margin:0">
|
||||||
|
Cancelling this slip will <strong>reverse the inventory deduction</strong>
|
||||||
|
and restore the qty back to on-hand. This cannot be undone.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="tm-form-group">
|
||||||
|
<label class="tm-label">
|
||||||
|
<i class="fas fa-sticky-note"></i> Reason for cancellation
|
||||||
|
<span class="tm-req">*</span>
|
||||||
|
</label>
|
||||||
|
<input id="ris-cancel-reason" class="tm-input"
|
||||||
|
type="text" placeholder="Enter reason…" maxlength="500">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="padding:12px 18px;border-top:1px solid var(--border,#d6eaec);
|
||||||
|
background:var(--bg-page,#f0f6f7);
|
||||||
|
display:flex;align-items:center;justify-content:flex-end;gap:8px">
|
||||||
|
<button id="ris-cancel-dismiss-btn" class="tm-btn-cancel">Dismiss</button>
|
||||||
|
<button id="ris-cancel-confirm-btn"
|
||||||
|
style="padding:8px 20px;border-radius:8px;border:none;
|
||||||
|
background:#dc2626;color:#fff;font-family:'DM Sans',sans-serif;
|
||||||
|
font-size:.85rem;font-weight:600;cursor:pointer;
|
||||||
|
display:flex;align-items:center;gap:7px">
|
||||||
|
<i class="fas fa-ban"></i> Cancel RIS
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* ── RIS card grid ── */
|
||||||
|
.ris-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
min-height: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@media (max-width: 768px) { .ris-grid { grid-template-columns: 1fr; } }
|
||||||
|
@@media (min-width: 769px) and (max-width: 1100px) {
|
||||||
|
.ris-grid { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── RIS card ── */
|
||||||
|
.ris-card {
|
||||||
|
background: var(--card-bg, #fff);
|
||||||
|
border-radius: var(--radius-lg, 14px);
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
|
border: 1px solid var(--border, #d6eaec);
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
transition: box-shadow .25s, transform .25s;
|
||||||
|
}
|
||||||
|
.ris-card:hover {
|
||||||
|
box-shadow: var(--shadow-hover);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Card header ── */
|
||||||
|
.ris-card-hd {
|
||||||
|
background: linear-gradient(135deg, var(--teal-dark, #0d5c63), var(--teal-mid, #0e7c86));
|
||||||
|
padding: 14px 16px 12px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.ris-card-no {
|
||||||
|
font-size: .7rem;
|
||||||
|
color: rgba(255,255,255,.6);
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: .07em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
}
|
||||||
|
.ris-card-title {
|
||||||
|
font-family: 'Space Grotesk', sans-serif;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #fff;
|
||||||
|
line-height: 1.25;
|
||||||
|
word-break: break-word;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
.ris-card-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: rgba(255,255,255,.65);
|
||||||
|
}
|
||||||
|
.ris-card-meta span {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Status badge positioned top-right ── */
|
||||||
|
.ris-status-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
right: 14px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 50px;
|
||||||
|
letter-spacing: .05em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.ris-status-draft { background: rgba(255,255,255,.18); color: #fff; }
|
||||||
|
.ris-status-approved { background: #dcfce7; color: #166534; }
|
||||||
|
.ris-status-cancelled { background: #fee2e2; color: #991b1b; }
|
||||||
|
|
||||||
|
/* ── Stats row ── */
|
||||||
|
.ris-stats-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 1px;
|
||||||
|
background: var(--border, #d6eaec);
|
||||||
|
border-bottom: 1px solid var(--border, #d6eaec);
|
||||||
|
}
|
||||||
|
.ris-stat {
|
||||||
|
background: var(--card-bg, #fff);
|
||||||
|
padding: 10px 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
.ris-stat-lbl {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: .05em;
|
||||||
|
color: var(--text-muted, #6b8890);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.ris-stat-lbl i { font-size: 11px; }
|
||||||
|
.ris-stat-val {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-dark, #1a2e35);
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
.ris-stat-val.teal { color: var(--teal-mid, #0e7c86); }
|
||||||
|
.ris-stat-val.red { color: #dc2626; }
|
||||||
|
.ris-stat-val.amber { color: #92400e; }
|
||||||
|
|
||||||
|
/* ── Body ── */
|
||||||
|
.ris-card-body {
|
||||||
|
padding: 12px 16px;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.ris-field-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.ris-field-lbl {
|
||||||
|
min-width: 80px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: .05em;
|
||||||
|
color: var(--text-muted, #6b8890);
|
||||||
|
padding-top: 1px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.ris-field-lbl i { font-size: 11px; }
|
||||||
|
.ris-field-val {
|
||||||
|
color: var(--text-dark, #1a2e35);
|
||||||
|
line-height: 1.5;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Discipline chip ── */
|
||||||
|
.ris-discipline-chip {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 50px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
background: var(--teal-pale, #e6f7f8);
|
||||||
|
color: var(--teal-dark, #0d5c63);
|
||||||
|
border: 1px solid var(--border, #d6eaec);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Footer ── */
|
||||||
|
.ris-card-ft {
|
||||||
|
padding: 10px 16px 14px;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
border-top: 1px solid var(--border, #d6eaec);
|
||||||
|
}
|
||||||
|
.ris-btn {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: var(--radius-sm, 8px);
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: 'DM Sans', sans-serif;
|
||||||
|
font-size: .82rem;
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
transition: all .2s;
|
||||||
|
}
|
||||||
|
.ris-btn-approve {
|
||||||
|
background: var(--teal-mid, #0e7c86);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.ris-btn-approve:hover { background: var(--teal-dark, #0d5c63); }
|
||||||
|
.ris-btn-approve:disabled { opacity: .4; cursor: default; }
|
||||||
|
|
||||||
|
.ris-btn-cancel {
|
||||||
|
background: transparent;
|
||||||
|
color: #dc2626;
|
||||||
|
border: 1.5px solid #fca5a5;
|
||||||
|
}
|
||||||
|
.ris-btn-cancel:hover { background: #fef2f2; border-color: #dc2626; }
|
||||||
|
.ris-btn-cancel:disabled { opacity: .4; cursor: default; }
|
||||||
|
|
||||||
|
/* ── Approve modal detail field ── */
|
||||||
|
.am-field { display: flex; flex-direction: column; gap: 3px; }
|
||||||
|
.am-lbl {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: .05em;
|
||||||
|
color: var(--text-muted, #6b8890);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.am-lbl i { font-size: 11px; }
|
||||||
|
.am-val {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--teal-dark, #0d5c63);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
const H = window.InventoryHelpers;
|
||||||
|
|
||||||
|
// ── State ──────────────────────────────────────────────────────────────
|
||||||
|
const s = {
|
||||||
|
page: 1, pageSize: 12, totalCount: 0,
|
||||||
|
searchRISNo: "", searchItemName: "", searchIssuedTo: "",
|
||||||
|
discipline: "", status: "",
|
||||||
|
timer: null
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── Elements ───────────────────────────────────────────────────────────
|
||||||
|
const grid = document.getElementById("ris-grid");
|
||||||
|
const countEl = document.getElementById("ris-resultCount");
|
||||||
|
const pageInfo = document.getElementById("ris-pageInfo");
|
||||||
|
const pageBtns = document.getElementById("ris-pageButtons");
|
||||||
|
const inRISNo = document.getElementById("ris-srchRISNo");
|
||||||
|
const inItem = document.getElementById("ris-srchItemName");
|
||||||
|
const inIssued = document.getElementById("ris-srchIssuedTo");
|
||||||
|
const inSize = document.getElementById("ris-pageSize");
|
||||||
|
|
||||||
|
const discWrap = document.getElementById("ris-disciplineWrap");
|
||||||
|
const statusWrap = document.getElementById("ris-statusWrap");
|
||||||
|
|
||||||
|
// ── Guard ──────────────────────────────────────────────────────────────
|
||||||
|
if (!grid || !discWrap) {
|
||||||
|
console.error("RIS tab init failed — missing elements.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Discipline dropdown ────────────────────────────────────────────────
|
||||||
|
const discDropdown = H.initSearchDropdown(discWrap, val => {
|
||||||
|
s.discipline = val;
|
||||||
|
s.page = 1;
|
||||||
|
fetchData();
|
||||||
|
}, {
|
||||||
|
cssPrefix: "inv-dep",
|
||||||
|
allLabel: "All Disciplines",
|
||||||
|
allIcon: "fas fa-th-large",
|
||||||
|
itemIcon: "fas fa-tools"
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Status dropdown (static options) ──────────────────────────────────
|
||||||
|
const statusDropdown = H.initSearchDropdown(statusWrap, val => {
|
||||||
|
s.status = val;
|
||||||
|
s.page = 1;
|
||||||
|
fetchData();
|
||||||
|
}, {
|
||||||
|
cssPrefix: "inv-dep",
|
||||||
|
allLabel: "All Status",
|
||||||
|
allIcon: "fas fa-th-large",
|
||||||
|
itemIcon: "fas fa-circle-dot"
|
||||||
|
});
|
||||||
|
// Seed static status options immediately
|
||||||
|
statusDropdown.setItems(["Draft", "Approved", "Cancelled"]);
|
||||||
|
|
||||||
|
// ── Search inputs ──────────────────────────────────────────────────────
|
||||||
|
[inRISNo, inItem, inIssued].forEach(el => {
|
||||||
|
if (!el) return;
|
||||||
|
el.addEventListener("input", () => {
|
||||||
|
clearTimeout(s.timer);
|
||||||
|
s.timer = setTimeout(() => {
|
||||||
|
s.searchRISNo = inRISNo?.value.trim() ?? "";
|
||||||
|
s.searchItemName = inItem?.value.trim() ?? "";
|
||||||
|
s.searchIssuedTo = inIssued?.value.trim() ?? "";
|
||||||
|
s.page = 1;
|
||||||
|
fetchData();
|
||||||
|
}, 350);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (inSize) {
|
||||||
|
inSize.addEventListener("change", () => {
|
||||||
|
s.pageSize = parseInt(inSize.value, 10);
|
||||||
|
s.page = 1;
|
||||||
|
fetchData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Status value → short code for API ─────────────────────────────────
|
||||||
|
function statusToCode(label) {
|
||||||
|
return { "Draft": "0", "Approved": "1", "Cancelled": "2" }[label] ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════════════════
|
||||||
|
// FETCH
|
||||||
|
// ══════════════════════════════════════════════════════════════════════
|
||||||
|
async function fetchData() {
|
||||||
|
grid.innerHTML = `<div class="inv-state" style="grid-column:1/-1">
|
||||||
|
<div class="inv-spinner"></div><p>Loading…</p></div>`;
|
||||||
|
|
||||||
|
const p = new URLSearchParams({
|
||||||
|
searchRISNo: s.searchRISNo,
|
||||||
|
searchItemName: s.searchItemName,
|
||||||
|
searchIssuedTo: s.searchIssuedTo,
|
||||||
|
discipline: s.discipline,
|
||||||
|
status: statusToCode(s.status),
|
||||||
|
pageNumber: s.page,
|
||||||
|
pageSize: s.pageSize,
|
||||||
|
draw: Date.now()
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/RISMgmt/GetRIS?${p}`);
|
||||||
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
const data = json.data ?? json;
|
||||||
|
s.totalCount = json.recordsTotal ?? 0;
|
||||||
|
|
||||||
|
if (json.disciplineList?.length)
|
||||||
|
discDropdown.setItems(json.disciplineList.map(d => d.disciplineName));
|
||||||
|
renderCards(json.data ?? []);
|
||||||
|
|
||||||
|
H.renderPagination(
|
||||||
|
document.getElementById("ris-pageButtons"),
|
||||||
|
document.getElementById("ris-pageInfo"),
|
||||||
|
s,
|
||||||
|
pg => { s.page = pg; fetchData(); }
|
||||||
|
);
|
||||||
|
|
||||||
|
const c = document.getElementById("ris-resultCount");
|
||||||
|
if (c) c.textContent =
|
||||||
|
`${s.totalCount.toLocaleString()} result${s.totalCount !== 1 ? "s" : ""}`;
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error("GetRIS error:", err);
|
||||||
|
grid.innerHTML = `<div class="inv-state" style="grid-column:1/-1">
|
||||||
|
<i class="fas fa-exclamation-triangle" style="color:#ff5c5c"></i>
|
||||||
|
<p>Failed to load data.</p></div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════════════════
|
||||||
|
// RENDER CARDS
|
||||||
|
// ══════════════════════════════════════════════════════════════════════
|
||||||
|
function renderCards(data) {
|
||||||
|
const g = document.getElementById("ris-grid");
|
||||||
|
if (!data.length) {
|
||||||
|
g.innerHTML = `<div class="inv-state" style="grid-column:1/-1">
|
||||||
|
<i class="fas fa-inbox"></i>
|
||||||
|
<p>No RIS records found.</p></div>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g.innerHTML = data.map(item => buildRISCardHtml(item)).join("");
|
||||||
|
|
||||||
|
// ── Wire Approve buttons ──
|
||||||
|
g.querySelectorAll(".btn-ris-approve").forEach(btn =>
|
||||||
|
btn.addEventListener("click", () => {
|
||||||
|
const risId = parseInt(btn.dataset.risid, 10);
|
||||||
|
const risNo = btn.dataset.risno;
|
||||||
|
openApproveModal(risId, risNo, btn.dataset);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// ── Wire Cancel buttons ──
|
||||||
|
g.querySelectorAll(".btn-ris-cancel").forEach(btn =>
|
||||||
|
btn.addEventListener("click", () => {
|
||||||
|
const risId = parseInt(btn.dataset.risid, 10);
|
||||||
|
const risNo = btn.dataset.risno;
|
||||||
|
openCancelModal(risId, risNo, btn.dataset);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════════════════
|
||||||
|
// CARD HTML BUILDER
|
||||||
|
// ══════════════════════════════════════════════════════════════════════
|
||||||
|
function buildRISCardHtml(item) {
|
||||||
|
const risId = item.risId ?? item.RISId ?? 0;
|
||||||
|
const risNo = item.risNo ?? item.RISNo ?? "—";
|
||||||
|
const itemName = item.itemName ?? item.ItemName ?? "—";
|
||||||
|
const itemNo = item.itemNo ?? item.ItemNo ?? "—";
|
||||||
|
const issuedTo = item.issuedTo ?? item.IssuedTo ?? "—";
|
||||||
|
const discipline = item.disciplineName ?? item.DisciplineName ?? "—";
|
||||||
|
const qtyIssued = item.qtyIssued ?? item.QtyIssued ?? 0;
|
||||||
|
const totalReturned= item.totalReturned ?? item.TotalReturned ?? 0;
|
||||||
|
const netIssued = qtyIssued - totalReturned;
|
||||||
|
const status = item.status ?? item.Status ?? 0;
|
||||||
|
const statusLabel = item.statusLabel ?? item.StatusLabel ?? _statusLabel(status);
|
||||||
|
const remarks = item.remarks ?? item.Remarks ?? null;
|
||||||
|
const createdDate = item.createdDate ?? item.CreatedDate ?? null;
|
||||||
|
const approvedBy = item.approvedBy ?? item.ApprovedBy ?? null;
|
||||||
|
const approvedDate = item.approvedDate ?? item.ApprovedDate ?? null;
|
||||||
|
const mrsCount = item.mrsCount ?? item.MRSCount ?? 0;
|
||||||
|
|
||||||
|
const isDraft = status === 0;
|
||||||
|
const isApproved = status === 1;
|
||||||
|
const isCancelled = status === 2;
|
||||||
|
|
||||||
|
const statusCls = isDraft ? "ris-status-draft"
|
||||||
|
: isApproved ? "ris-status-approved"
|
||||||
|
: "ris-status-cancelled";
|
||||||
|
|
||||||
|
const statusIcon = isDraft ? "fas fa-clock"
|
||||||
|
: isApproved ? "fas fa-check-circle"
|
||||||
|
: "fas fa-ban";
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="ris-card">
|
||||||
|
|
||||||
|
<!-- HEAD -->
|
||||||
|
<div class="ris-card-hd">
|
||||||
|
<span class="${statusCls} ris-status-badge">
|
||||||
|
<i class="${statusIcon}"></i> ${H.escHtml(statusLabel)}
|
||||||
|
</span>
|
||||||
|
<div class="ris-card-no">RIS NO</div>
|
||||||
|
<div class="ris-card-title">${H.escHtml(risNo)}</div>
|
||||||
|
<div class="ris-card-meta">
|
||||||
|
<span><i class="fas fa-box"></i> ${H.escHtml(itemName)}</span>
|
||||||
|
<span><i class="fas fa-hashtag"></i> #${H.escHtml(String(itemNo))}</span>
|
||||||
|
</div>
|
||||||
|
<div class="ris-card-meta" style="margin-top:4px">
|
||||||
|
<span><i class="fas fa-user"></i> ${H.escHtml(issuedTo)}</span>
|
||||||
|
<span><i class="fas fa-clock"></i> ${_formatDate(createdDate)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- STATS -->
|
||||||
|
<div class="ris-stats-row">
|
||||||
|
<div class="ris-stat">
|
||||||
|
<span class="ris-stat-lbl">
|
||||||
|
<i class="fas fa-cubes"></i> Qty Issued
|
||||||
|
</span>
|
||||||
|
<span class="ris-stat-val teal">${qtyIssued}</span>
|
||||||
|
</div>
|
||||||
|
<div class="ris-stat">
|
||||||
|
<span class="ris-stat-lbl">
|
||||||
|
<i class="fas fa-undo"></i> Returned
|
||||||
|
</span>
|
||||||
|
<span class="ris-stat-val amber">${totalReturned}</span>
|
||||||
|
</div>
|
||||||
|
<div class="ris-stat">
|
||||||
|
<span class="ris-stat-lbl">
|
||||||
|
<i class="fas fa-layer-group"></i> Net Issued
|
||||||
|
</span>
|
||||||
|
<span class="ris-stat-val ${netIssued > 0 ? "red" : ""}">${netIssued}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- BODY -->
|
||||||
|
<div class="ris-card-body">
|
||||||
|
<div class="ris-field-row">
|
||||||
|
<span class="ris-field-lbl">
|
||||||
|
<i class="fas fa-tools"></i> Discipline
|
||||||
|
</span>
|
||||||
|
<span class="ris-field-val">
|
||||||
|
<span class="ris-discipline-chip">
|
||||||
|
<i class="fas fa-tools"></i> ${H.escHtml(discipline)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${mrsCount > 0 ? `
|
||||||
|
<div class="ris-field-row">
|
||||||
|
<span class="ris-field-lbl">
|
||||||
|
<i class="fas fa-file-import"></i> MRS Count
|
||||||
|
</span>
|
||||||
|
<span class="ris-field-val">
|
||||||
|
<span style="background:#E6F1FB;color:#185FA5;padding:2px 8px;
|
||||||
|
border-radius:50px;font-size:11px;font-weight:600">
|
||||||
|
${mrsCount} return slip${mrsCount !== 1 ? "s" : ""}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>` : ""}
|
||||||
|
|
||||||
|
${remarks ? `
|
||||||
|
<div class="ris-field-row">
|
||||||
|
<span class="ris-field-lbl">
|
||||||
|
<i class="fas fa-sticky-note"></i> Remarks
|
||||||
|
</span>
|
||||||
|
<span class="ris-field-val" style="color:var(--text-muted,#6b8890)">
|
||||||
|
${H.escHtml(remarks)}
|
||||||
|
</span>
|
||||||
|
</div>` : ""}
|
||||||
|
|
||||||
|
${isApproved && approvedBy ? `
|
||||||
|
<div class="ris-field-row">
|
||||||
|
<span class="ris-field-lbl">
|
||||||
|
<i class="fas fa-user-check"></i> Approved by
|
||||||
|
</span>
|
||||||
|
<span class="ris-field-val">
|
||||||
|
${H.escHtml(approvedBy)}
|
||||||
|
<span style="color:var(--text-muted,#6b8890);font-size:11px;margin-left:4px">
|
||||||
|
${_formatDate(approvedDate)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>` : ""}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- FOOTER -->
|
||||||
|
<div class="ris-card-ft">
|
||||||
|
${isDraft ? `
|
||||||
|
<button class="ris-btn ris-btn-approve btn-ris-approve"
|
||||||
|
data-risid="${risId}"
|
||||||
|
data-risno="${H.escAttr(risNo)}"
|
||||||
|
data-itemname="${H.escAttr(itemName)}"
|
||||||
|
data-issuedto="${H.escAttr(issuedTo)}"
|
||||||
|
data-discipline="${H.escAttr(discipline)}"
|
||||||
|
data-qtyissued="${qtyIssued}">
|
||||||
|
<i class="fas fa-check-circle"></i> Approve
|
||||||
|
</button>
|
||||||
|
<button class="ris-btn ris-btn-cancel btn-ris-cancel"
|
||||||
|
data-risid="${risId}"
|
||||||
|
data-risno="${H.escAttr(risNo)}"
|
||||||
|
data-itemname="${H.escAttr(itemName)}"
|
||||||
|
data-issuedto="${H.escAttr(issuedTo)}"
|
||||||
|
data-discipline="${H.escAttr(discipline)}"
|
||||||
|
data-qtyissued="${qtyIssued}">
|
||||||
|
<i class="fas fa-ban"></i> Cancel
|
||||||
|
</button>` : `
|
||||||
|
<button class="ris-btn ris-btn-cancel btn-ris-cancel"
|
||||||
|
${isCancelled ? "disabled" : ""}
|
||||||
|
data-risid="${risId}"
|
||||||
|
data-risno="${H.escAttr(risNo)}"
|
||||||
|
data-itemname="${H.escAttr(itemName)}"
|
||||||
|
data-issuedto="${H.escAttr(issuedTo)}"
|
||||||
|
data-discipline="${H.escAttr(discipline)}"
|
||||||
|
data-qtyissued="${qtyIssued}"
|
||||||
|
style="flex:1">
|
||||||
|
<i class="fas fa-ban"></i>
|
||||||
|
${isCancelled ? "Cancelled" : "Cancel"}
|
||||||
|
</button>`}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════════════════
|
||||||
|
// APPROVE MODAL
|
||||||
|
// ══════════════════════════════════════════════════════════════════════
|
||||||
|
function openApproveModal(risId, risNo, dataset) {
|
||||||
|
const overlay = document.getElementById("ris-approve-modal-overlay");
|
||||||
|
document.getElementById("ris-approve-subtitle").textContent =
|
||||||
|
`${risNo} — ${dataset.itemname ?? ""}`;
|
||||||
|
|
||||||
|
document.getElementById("ris-approve-detail").innerHTML = `
|
||||||
|
<div class="am-field">
|
||||||
|
<span class="am-lbl"><i class="fas fa-hashtag"></i> RIS No</span>
|
||||||
|
<span class="am-val">${H.escHtml(risNo)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="am-field">
|
||||||
|
<span class="am-lbl"><i class="fas fa-cubes"></i> Qty Issued</span>
|
||||||
|
<span class="am-val">${dataset.qtyissued ?? 0} pcs</span>
|
||||||
|
</div>
|
||||||
|
<div class="am-field">
|
||||||
|
<span class="am-lbl"><i class="fas fa-box"></i> Item</span>
|
||||||
|
<span class="am-val">${H.escHtml(dataset.itemname ?? "—")}</span>
|
||||||
|
</div>
|
||||||
|
<div class="am-field">
|
||||||
|
<span class="am-lbl"><i class="fas fa-user"></i> Issued To</span>
|
||||||
|
<span class="am-val">${H.escHtml(dataset.issuedto ?? "—")}</span>
|
||||||
|
</div>
|
||||||
|
<div class="am-field">
|
||||||
|
<span class="am-lbl"><i class="fas fa-tools"></i> Discipline</span>
|
||||||
|
<span class="am-val">${H.escHtml(dataset.discipline ?? "—")}</span>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
overlay.style.display = "flex";
|
||||||
|
|
||||||
|
// ── Wire buttons fresh each open ──
|
||||||
|
const confirmBtn = document.getElementById("ris-approve-confirm-btn");
|
||||||
|
const cancelBtn = document.getElementById("ris-approve-cancel-btn");
|
||||||
|
|
||||||
|
const closeModal = () => { overlay.style.display = "none"; };
|
||||||
|
|
||||||
|
// Clone to remove stale listeners
|
||||||
|
const newConfirm = confirmBtn.cloneNode(true);
|
||||||
|
const newCancel = cancelBtn.cloneNode(true);
|
||||||
|
confirmBtn.replaceWith(newConfirm);
|
||||||
|
cancelBtn.replaceWith(newCancel);
|
||||||
|
|
||||||
|
newCancel.addEventListener("click", closeModal);
|
||||||
|
overlay.addEventListener("click", e => { if (e.target === overlay) closeModal(); });
|
||||||
|
|
||||||
|
newConfirm.addEventListener("click", async () => {
|
||||||
|
newConfirm.disabled = true;
|
||||||
|
newConfirm.innerHTML = `<i class="fas fa-spinner fa-spin"></i> Approving…`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/RISMgmt/ApproveRIS`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ risId })
|
||||||
|
});
|
||||||
|
const json = await res.json();
|
||||||
|
const d = json.data ?? json;
|
||||||
|
|
||||||
|
if (!res.ok || !d.success) {
|
||||||
|
showToast("error", d.message ?? "Could not approve RIS.", "Failed", 4000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showToast("success", d.message ?? `RIS ${risNo} approved.`, "Approved!", 3500);
|
||||||
|
closeModal();
|
||||||
|
fetchData();
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error("ApproveRIS error:", err);
|
||||||
|
showToast("error", "Request failed. Please try again.", "Error", 4000);
|
||||||
|
} finally {
|
||||||
|
newConfirm.disabled = false;
|
||||||
|
newConfirm.innerHTML = `<i class="fas fa-check"></i> Approve RIS`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════════════════
|
||||||
|
// CANCEL MODAL
|
||||||
|
// ══════════════════════════════════════════════════════════════════════
|
||||||
|
function openCancelModal(risId, risNo, dataset) {
|
||||||
|
const overlay = document.getElementById("ris-cancel-modal-overlay");
|
||||||
|
const reasonEl = document.getElementById("ris-cancel-reason");
|
||||||
|
reasonEl.value = "";
|
||||||
|
reasonEl.classList.remove("error");
|
||||||
|
|
||||||
|
document.getElementById("ris-cancel-subtitle").textContent =
|
||||||
|
`${risNo} — ${dataset.itemname ?? ""}`;
|
||||||
|
|
||||||
|
document.getElementById("ris-cancel-detail").innerHTML = `
|
||||||
|
<div class="am-field">
|
||||||
|
<span class="am-lbl"><i class="fas fa-hashtag"></i> RIS No</span>
|
||||||
|
<span class="am-val" style="color:#991b1b">${H.escHtml(risNo)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="am-field">
|
||||||
|
<span class="am-lbl"><i class="fas fa-cubes"></i> Qty Issued</span>
|
||||||
|
<span class="am-val" style="color:#991b1b">${dataset.qtyissued ?? 0} pcs</span>
|
||||||
|
</div>
|
||||||
|
<div class="am-field">
|
||||||
|
<span class="am-lbl"><i class="fas fa-box"></i> Item</span>
|
||||||
|
<span class="am-val">${H.escHtml(dataset.itemname ?? "—")}</span>
|
||||||
|
</div>
|
||||||
|
<div class="am-field">
|
||||||
|
<span class="am-lbl"><i class="fas fa-user"></i> Issued To</span>
|
||||||
|
<span class="am-val">${H.escHtml(dataset.issuedto ?? "—")}</span>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
overlay.style.display = "flex";
|
||||||
|
|
||||||
|
const confirmBtn = document.getElementById("ris-cancel-confirm-btn");
|
||||||
|
const dismissBtn = document.getElementById("ris-cancel-dismiss-btn");
|
||||||
|
|
||||||
|
const closeModal = () => { overlay.style.display = "none"; };
|
||||||
|
|
||||||
|
const newConfirm = confirmBtn.cloneNode(true);
|
||||||
|
const newDismiss = dismissBtn.cloneNode(true);
|
||||||
|
confirmBtn.replaceWith(newConfirm);
|
||||||
|
dismissBtn.replaceWith(newDismiss);
|
||||||
|
|
||||||
|
newDismiss.addEventListener("click", closeModal);
|
||||||
|
overlay.addEventListener("click", e => { if (e.target === overlay) closeModal(); });
|
||||||
|
|
||||||
|
newConfirm.addEventListener("click", async () => {
|
||||||
|
const reason = reasonEl.value.trim();
|
||||||
|
if (!reason) {
|
||||||
|
reasonEl.classList.add("error");
|
||||||
|
reasonEl.focus();
|
||||||
|
showToast("warning", "Please enter a reason for cancellation.", "Required", 3000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
newConfirm.disabled = true;
|
||||||
|
newConfirm.innerHTML = `<i class="fas fa-spinner fa-spin"></i> Cancelling…`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/RISMgmt/CancelRIS`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ risId, reason })
|
||||||
|
});
|
||||||
|
const json = await res.json();
|
||||||
|
const d = json.data ?? json;
|
||||||
|
|
||||||
|
if (!res.ok || !d.success) {
|
||||||
|
showToast("error", d.message ?? "Could not cancel RIS.", "Failed", 4000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showToast("success", d.message ?? `RIS ${risNo} cancelled.`, "Cancelled", 3500);
|
||||||
|
closeModal();
|
||||||
|
fetchData();
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error("CancelRIS error:", err);
|
||||||
|
showToast("error", "Request failed. Please try again.", "Error", 4000);
|
||||||
|
} finally {
|
||||||
|
newConfirm.disabled = false;
|
||||||
|
newConfirm.innerHTML = `<i class="fas fa-ban"></i> Cancel RIS`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════════════════
|
||||||
|
// HELPERS
|
||||||
|
// ══════════════════════════════════════════════════════════════════════
|
||||||
|
function _statusLabel(code) {
|
||||||
|
return { 0: "Draft", 1: "Approved", 2: "Cancelled" }[code] ?? "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
function _formatDate(raw) {
|
||||||
|
if (!raw) return "—";
|
||||||
|
const d = new Date(raw);
|
||||||
|
return isNaN(d) ? raw : d.toLocaleDateString("en-US", {
|
||||||
|
month: "short", day: "numeric", year: "numeric"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Initial load ───────────────────────────────────────────────────────
|
||||||
|
fetchData();
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
@ -1,176 +1,114 @@
|
|||||||
<body>
|
@await Html.PartialAsync("PagesView/Inventory/_InventoryStyles")
|
||||||
<div class="container-fluid">
|
@await Html.PartialAsync("PagesView/Inventory/_InventoryHelpers")
|
||||||
<div class="table-container shadow-lg p-3 mb-5 bg-white rounded">
|
|
||||||
<div class="header-container">
|
|
||||||
<h2 style="display: flex; flex-direction: column; align-items: center;">Inventory List</h2>
|
|
||||||
</div>
|
|
||||||
<div class="row" style="margin-top:10px">
|
|
||||||
<div class="col-md-3 col-sm-6 form-group">
|
|
||||||
<label for="dateFrom">From:</label>
|
|
||||||
<input type="date" id="dateFrom" class="form-control" />
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3 col-sm-6 form-group">
|
|
||||||
<label for="dateTo">To:</label>
|
|
||||||
<input type="date" id="dateTo" class="form-control" />
|
|
||||||
</div>
|
|
||||||
<div class="col-md-2 col-sm-6 form-group d-flex align-items-end">
|
|
||||||
<button type="button" id="btnSort" onclick="sortByDate();" class="btn btn-primary">Sort</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<table id="InventoryTable" class="row-border" cellspacing="0" width="100%">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Txn.Id.</th>
|
|
||||||
<th>ItemNo</th>
|
|
||||||
<th>ItemName</th>
|
|
||||||
<th>Specification</th>
|
|
||||||
<th>CategoryName</th>
|
|
||||||
<th>Department</th>
|
|
||||||
<th>In</th>
|
|
||||||
<th>Out</th>
|
|
||||||
<th>OnHand</th>
|
|
||||||
<th>Ousts.Qty</th>
|
|
||||||
<th>Location</th>
|
|
||||||
<th>Trans.Date</th>
|
|
||||||
<th>Action</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<!-- Modal viewItemDetails -->
|
|
||||||
<div class="modal fade custom-modal-backdrop" id="viewItemDetails" tabindex="-1" aria-labelledby="ModalLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-lg">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title" id="ModalLabel">Item Details</h5>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
@* <div style="margin-bottom:10px;margin-left:10px;font-size:large">
|
|
||||||
<label for="PONumber">PO#: </label>
|
|
||||||
<label id="PONumber"></label>
|
|
||||||
<input hidden id="PRNumber" />
|
|
||||||
</div> *@
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="itemName">Item Name</label>
|
|
||||||
<input readonly class="form-control" style="margin-bottom:5px;" id="itemName">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="itemDescription">Item Description</label>
|
|
||||||
<textarea readonly id="itemDescription" style="width: 100%; height: 100px;"></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="inventory-wrapper">
|
||||||
<label for="itemCategoryName">Category</label>
|
@* {{-- HEADER --}} *@
|
||||||
<input readonly id="itemCategoryName" class="form-control" style="margin-bottom:5px;" name="itemCategoryName" />
|
<div class="inv-header">
|
||||||
|
<div class="inv-header-inner">
|
||||||
</div>
|
<i class="fas fa-file-invoice inv-header-icon"></i>
|
||||||
|
<div>
|
||||||
<div class="form-group">
|
<h1>Inventory Management</h1>
|
||||||
<label for="uomName">UOM</label>
|
<p>Manage inventory transaction, status, and comparison</p>
|
||||||
<input readonly id="uomName" class="form-control" style="margin-bottom:5px;" name="uomName" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="itemColorName">Item Color</label>
|
|
||||||
<input readonly id="itemColorName" class="form-control" style="margin-bottom:5px;" name="itemColorName" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="itemNo">Item No</label>
|
|
||||||
<input readonly class="form-control" style="margin-bottom:5px;" id="itemNo">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="qtyIn">Qty In</label>
|
|
||||||
<input readonly id="qtyIn" class="form-control" style="margin-bottom:5px;" name="qtyIn" />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="qtyOut">Qty Out</label>
|
|
||||||
<input readonly id="qtyOut" class="form-control" style="margin-bottom:5px;" name="qtyOut" />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="qtyOnHand">Qty On Hand</label>
|
|
||||||
<input readonly id="qtyOnHand" class="form-control" style="margin-bottom:5px;" name="qtyOnHand" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="lotTypeName">Packing Type</label>
|
|
||||||
<input readonly id="lotTypeName" class="form-control" style="margin-bottom:5px;" name="lotTypeName" />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="lotNo">Location</label>
|
|
||||||
<select name="lotNo" id="lotNo" class="form-control">
|
|
||||||
</select>
|
|
||||||
<input type="hidden" id="lotId" name="lotId" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
<div>
|
|
||||||
<img id="itemPictureImage" alt="itemPictureImage" width="450" class="img-fluid" style="margin-bottom:5px; border-radius:15px; box-shadow:15px;">
|
|
||||||
<input type="file" id="itemPictureImageInput" accept="image/*" style="display: none; margin-bottom:5px; border-radius:15px; box-shadow:15px;">
|
|
||||||
<input type="hidden" id="prDetailsId" name="prDetailsId" />
|
|
||||||
<input type="hidden" id="itemAttachId" name="itemAttachId" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Back</button>
|
|
||||||
<button type="button" id="btnUpdateItem" onclick="postPutLotBin(0);" class="btn btn-warning">Update</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Modal addNewItem -->
|
|
||||||
<div class="modal fade custom-modal-backdrop" id="addNewItem"
|
|
||||||
tabindex="-1" aria-labelledby="ModalLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-xl">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header" style="display: flex; flex-direction: column; align-items: center;">
|
|
||||||
<h2 class="modal-title" id="ModalLabel">Item List</h2>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="table-container shadow-lg p-3 mb-5 bg-white rounded">
|
|
||||||
<table id="ItemDataTable" class="row-border" style="width: 100%;">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>ItemNo</th>
|
|
||||||
<th>ItemName</th>
|
|
||||||
<th>Specs</th>
|
|
||||||
<th>Qty</th>
|
|
||||||
<th>E-Address</th>
|
|
||||||
<th>Sup.Name</th>
|
|
||||||
<th>Manufacturer</th>
|
|
||||||
<th>Price</th>
|
|
||||||
<th>Action</th>
|
|
||||||
<th hidden></th>
|
|
||||||
<th hidden></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Back</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input hidden id="roleRights" value="@ViewBag.UserRoles" />
|
|
||||||
<div id="overlay" class="overlay" style="display: none;">
|
|
||||||
<div id="loader" class="loader"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="~/jsfunctions/Inventory/InventoryV2.js"></script>
|
</div>
|
||||||
<script src="~/jsfunctions/utilities/utilsV3.js"></script>
|
@* {{-- TAB NAV --}}
|
||||||
<script src="~/jsfunctions/inventory/inventvar.js"></script>
|
{{-- data-tab-id matches the ViewComponent switch:*@
|
||||||
</body>
|
<div class="inv-tabs" role="tablist">
|
||||||
|
<button class="inv-tab-btn active" data-tab-id="1" role="tab">
|
||||||
|
<i class="fas fa-user-tag"></i> Inventory
|
||||||
|
</button>
|
||||||
|
<button class="inv-tab-btn" data-tab-id="2" role="tab">
|
||||||
|
<i class="fas fa-clock"></i> For Approval
|
||||||
|
</button>
|
||||||
|
<button class="inv-tab-btn" data-tab-id="3" role="tab">
|
||||||
|
<i class="fas fa-id-badge"></i> Return Issuance Slip (Report)
|
||||||
|
</button>
|
||||||
|
<button class="inv-tab-btn" data-tab-id="4" role="tab">
|
||||||
|
<i class="fas fa-store"></i> Material Return Slip (Report)
|
||||||
|
</button>
|
||||||
|
<button class="inv-tab-btn" data-tab-id="5" role="tab">
|
||||||
|
<i class="fas fa-check-circle"></i> Transaction History
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="inv-tab-content">
|
||||||
|
<div class="inv-tab-loading">
|
||||||
|
<div class="inv-spinner"></div>
|
||||||
|
<span>Loading…</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
// ── Prevent re-initialization on cache restore ──
|
||||||
|
if (window.__invTab1Initialized) return;
|
||||||
|
window.__invTab1Initialized = true;
|
||||||
|
|
||||||
|
const tabContent = document.getElementById("inv-tab-content");
|
||||||
|
const tabBtns = document.querySelectorAll(".inv-tab-btn");
|
||||||
|
|
||||||
|
// Cache stores the raw HTML string per tab id
|
||||||
|
const cache = {};
|
||||||
|
let activeTabId = null;
|
||||||
|
|
||||||
|
async function loadTab(tabId) {
|
||||||
|
if (activeTabId === tabId) return;
|
||||||
|
activeTabId = tabId;
|
||||||
|
|
||||||
|
// Mark active button
|
||||||
|
tabBtns.forEach(b => b.classList.toggle("active", b.dataset.tabId === String(tabId)));
|
||||||
|
|
||||||
|
if (cache[tabId]) {
|
||||||
|
// ── FIX: inject cached HTML then re-run its <script> blocks ──────
|
||||||
|
injectHtml(tabContent, cache[tabId]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tabContent.innerHTML = `<div class="inv-tab-loading">
|
||||||
|
<div class="inv-spinner"></div><span>Loading…</span></div>`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/InventoryMgmt/GetInventoryTabPage?id=${tabId}`);
|
||||||
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||||
|
const html = await res.text();
|
||||||
|
cache[tabId] = html;
|
||||||
|
injectHtml(tabContent, html);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Tab load error:", err);
|
||||||
|
tabContent.innerHTML = `
|
||||||
|
<div class="inv-placeholder">
|
||||||
|
<i class="fas fa-exclamation-triangle" style="color:#ff5c5c"></i>
|
||||||
|
<h3>Failed to load</h3>
|
||||||
|
<p>Please try again or refresh the page.</p>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set innerHTML then re-execute every <script> block so IIFEs inside
|
||||||
|
* ViewComponent views fire correctly — both on first load AND cache restore.
|
||||||
|
*/
|
||||||
|
function injectHtml(container, html) {
|
||||||
|
container.innerHTML = html;
|
||||||
|
container.querySelectorAll("script").forEach(oldScript => {
|
||||||
|
const newScript = document.createElement("script");
|
||||||
|
Array.from(oldScript.attributes).forEach(attr =>
|
||||||
|
newScript.setAttribute(attr.name, attr.value));
|
||||||
|
newScript.textContent = oldScript.textContent;
|
||||||
|
oldScript.replaceWith(newScript);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wire tab clicks
|
||||||
|
tabBtns.forEach(btn =>
|
||||||
|
btn.addEventListener("click", () => loadTab(parseInt(btn.dataset.tabId, 10)))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Auto-load the first tab on page ready
|
||||||
|
loadTab(1);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
@await Html.PartialAsync("PagesView/Inventory/_InventoryTransactModal")
|
||||||
5
CPRNIMS.WebApps/Views/RISMgmt/Index.cshtml
Normal file
5
CPRNIMS.WebApps/Views/RISMgmt/Index.cshtml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
@*
|
||||||
|
For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
|
||||||
|
*@
|
||||||
|
@{
|
||||||
|
}
|
||||||
@ -18,7 +18,7 @@
|
|||||||
<script src="~/jsfunctions/canvass/CanvassView.js"></script>
|
<script src="~/jsfunctions/canvass/CanvassView.js"></script>
|
||||||
<script src="~/jsfunctions/common/termsV2.js"></script>
|
<script src="~/jsfunctions/common/termsV2.js"></script>
|
||||||
<script src="~/jsfunctions/canvass/PostPut.js"></script>
|
<script src="~/jsfunctions/canvass/PostPut.js"></script>
|
||||||
<script src="~/jsfunctions/common/PostPutV2.js"></script>
|
<script src="~/jsfunctions/common/PostPutV3.js"></script>
|
||||||
<script src="~/jsfunctions/canvass/buttonsV5.js"></script>
|
<script src="~/jsfunctions/canvass/buttonsV5.js"></script>
|
||||||
<script src="~/jsfunctions/canvass/rowCallBackV2.js"></script>
|
<script src="~/jsfunctions/canvass/rowCallBackV2.js"></script>
|
||||||
<script src="~/jsfunctions/utilities/StylesV3.js"></script>
|
<script src="~/jsfunctions/utilities/StylesV3.js"></script>
|
||||||
|
|||||||
@ -0,0 +1,314 @@
|
|||||||
|
<script>
|
||||||
|
window.InventoryHelpers = (function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/* ── String helpers ──────────────────────────────── */
|
||||||
|
function splitAggr(raw) {
|
||||||
|
if (!raw) return [];
|
||||||
|
return raw.replace(/<br\s*\/?>/gi, ",").split(",").map(s => s.trim()).filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
function escHtml(s) {
|
||||||
|
return String(s).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");
|
||||||
|
}
|
||||||
|
|
||||||
|
function escAttr(s) {
|
||||||
|
return String(s).replace(/"/g,""").replace(/'/g,"'");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Pagination helpers ──────────────────────────── */
|
||||||
|
function buildPageRange(cur, total) {
|
||||||
|
if (total <= 7) return Array.from({length: total}, (_, i) => i + 1);
|
||||||
|
if (cur <= 4) return [1, 2, 3, 4, 5, "…", total];
|
||||||
|
if (cur >= total - 3) return [1, "…", total-4, total-3, total-2, total-1, total];
|
||||||
|
return [1, "…", cur-1, cur, cur+1, "…", total];
|
||||||
|
}
|
||||||
|
|
||||||
|
function mkPageBtn(html, disabled, active) {
|
||||||
|
const b = document.createElement("button");
|
||||||
|
b.className = "inv-pg-btn" + (active ? " active" : "");
|
||||||
|
b.innerHTML = html; b.disabled = !!disabled;
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPagination(container, infoEl, state, onPageChange) {
|
||||||
|
const { page, pageSize, totalCount } = state;
|
||||||
|
const totalPages = Math.ceil(totalCount / pageSize) || 1;
|
||||||
|
const from = Math.min((page - 1) * pageSize + 1, totalCount);
|
||||||
|
const to = Math.min(page * pageSize, totalCount);
|
||||||
|
|
||||||
|
infoEl.textContent = totalCount
|
||||||
|
? `Showing ${from.toLocaleString()}–${to.toLocaleString()} of ${totalCount.toLocaleString()}`
|
||||||
|
: "No records";
|
||||||
|
|
||||||
|
container.innerHTML = "";
|
||||||
|
|
||||||
|
const prev = mkPageBtn('<i class="fas fa-chevron-left"></i>', page <= 1);
|
||||||
|
prev.addEventListener("click", () => { if (page > 1) onPageChange(page - 1); });
|
||||||
|
container.appendChild(prev);
|
||||||
|
|
||||||
|
buildPageRange(page, totalPages).forEach(p => {
|
||||||
|
if (p === "…") {
|
||||||
|
const d = document.createElement("span");
|
||||||
|
d.className = "inv-pg-btn"; d.style.cursor = "default"; d.textContent = "…";
|
||||||
|
container.appendChild(d); return;
|
||||||
|
}
|
||||||
|
const b = mkPageBtn(p, false, p === page);
|
||||||
|
b.addEventListener("click", () => onPageChange(p));
|
||||||
|
container.appendChild(b);
|
||||||
|
});
|
||||||
|
|
||||||
|
const next = mkPageBtn('<i class="fas fa-chevron-right"></i>', page >= totalPages);
|
||||||
|
next.addEventListener("click", () => { if (page < totalPages) onPageChange(page + 1); });
|
||||||
|
container.appendChild(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Generic searchable dropdown factory ─────────────────────────────
|
||||||
|
*
|
||||||
|
* Works for BOTH Departments and ClientNames (or any list).
|
||||||
|
* CSS class prefixes are passed in so each dropdown is fully independent.
|
||||||
|
*
|
||||||
|
* param wrap HTMLElement — the root wrapper element
|
||||||
|
* param onChange Function — called with the selected value string
|
||||||
|
* param opts Object — optional overrides:
|
||||||
|
* allLabel : string label for "All" option (default "All")
|
||||||
|
* icon : string FA icon class (default "fa-th-large / fa-store")
|
||||||
|
* cssPrefix : string CSS class prefix (default "inv-dep")
|
||||||
|
*
|
||||||
|
* CSS classes expected inside `wrap`:
|
||||||
|
* {prefix}-trigger, {prefix}-dropdown, {prefix}-lbl,
|
||||||
|
* {prefix}-searchbox > input, {prefix}-list
|
||||||
|
*
|
||||||
|
* Returns: { setItems(list), getCurrent() }
|
||||||
|
──────────────────────────────────────────────────────────────────── */
|
||||||
|
function initSearchDropdown(wrap, onChange, opts) {
|
||||||
|
opts = opts || {};
|
||||||
|
const prefix = opts.cssPrefix || "inv-dep";
|
||||||
|
const allLabel = opts.allLabel || "All";
|
||||||
|
const allIcon = opts.allIcon || "fas fa-th-large";
|
||||||
|
const itemIcon = opts.itemIcon || "fas fa-store";
|
||||||
|
|
||||||
|
const trigger = wrap.querySelector(`.${prefix}-trigger`);
|
||||||
|
const dropdown = wrap.querySelector(`.${prefix}-dropdown`);
|
||||||
|
const label = wrap.querySelector(`.${prefix}-lbl`);
|
||||||
|
const search = wrap.querySelector(`.${prefix}-searchbox input`);
|
||||||
|
const list = wrap.querySelector(`.${prefix}-list`);
|
||||||
|
|
||||||
|
if (!trigger || !dropdown || !label || !search || !list) {
|
||||||
|
//console.warn("initSearchDropdown: one or more required elements not found in", wrap);
|
||||||
|
return { setItems: () => {}, getCurrent: () => "" };
|
||||||
|
}
|
||||||
|
|
||||||
|
let allItems = [];
|
||||||
|
let current = "";
|
||||||
|
let isOpen = false; // ← tracks whether dropdown is visible
|
||||||
|
|
||||||
|
// ── AbortController lets us remove the document listener in one call ──
|
||||||
|
const ac = new AbortController();
|
||||||
|
|
||||||
|
trigger.addEventListener("click", () => {
|
||||||
|
isOpen = dropdown.classList.toggle("open");
|
||||||
|
trigger.classList.toggle("open", isOpen);
|
||||||
|
if (isOpen) {
|
||||||
|
search.value = "";
|
||||||
|
renderOpts(allItems);
|
||||||
|
search.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("click", e => {
|
||||||
|
if (!wrap.contains(e.target)) {
|
||||||
|
isOpen = false;
|
||||||
|
dropdown.classList.remove("open");
|
||||||
|
trigger.classList.remove("open");
|
||||||
|
}
|
||||||
|
}, { signal: ac.signal });
|
||||||
|
|
||||||
|
search.addEventListener("input", () => {
|
||||||
|
const q = search.value.trim().toLowerCase();
|
||||||
|
renderOpts(q ? allItems.filter(s => s.toLowerCase().includes(q)) : allItems);
|
||||||
|
});
|
||||||
|
|
||||||
|
list.addEventListener("click", e => {
|
||||||
|
const opt = e.target.closest("[data-value]");
|
||||||
|
if (!opt) return;
|
||||||
|
current = opt.dataset.value;
|
||||||
|
label.textContent = current || allLabel;
|
||||||
|
isOpen = false;
|
||||||
|
dropdown.classList.remove("open");
|
||||||
|
trigger.classList.remove("open");
|
||||||
|
onChange(current);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Watch for wrap being removed from DOM → abort the document listener ──
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
if (!document.contains(wrap)) {
|
||||||
|
ac.abort(); // removes the document click listener
|
||||||
|
observer.disconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
observer.observe(document.body, { childList: true, subtree: true });
|
||||||
|
|
||||||
|
function renderOpts(items) {
|
||||||
|
// ── Only exclude current when the dropdown is open (user is browsing) ──
|
||||||
|
// ── When called from setItems (background refresh), show everything ─
|
||||||
|
|
||||||
|
const switchable = (isOpen && current)
|
||||||
|
? items.filter(n => n !== current)
|
||||||
|
: items;
|
||||||
|
|
||||||
|
const clearRow = `<div class="${prefix}-opt${current === "" ? " active" : ""}" data-value="">
|
||||||
|
<i class="${allIcon}"></i> ${escHtml(allLabel)}
|
||||||
|
${isOpen && current !== ""
|
||||||
|
? `<span style="margin-left:auto;font-size:.72rem;color:var(--text-muted);opacity:.7">clear</span>`
|
||||||
|
: ""}
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
if (!switchable.length) {
|
||||||
|
list.innerHTML = clearRow +
|
||||||
|
`<div style="padding:12px;text-align:center;font-size:.85rem;color:#6b8890">
|
||||||
|
${isOpen && current ? "No other departments" : "No results found"}
|
||||||
|
</div>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.innerHTML = clearRow + switchable.map(n =>
|
||||||
|
`<div class="${prefix}-opt${!isOpen && n === current ? " active" : ""}"
|
||||||
|
data-value="${escAttr(n)}">
|
||||||
|
<i class="${itemIcon}"></i> ${escHtml(n)}
|
||||||
|
</div>`
|
||||||
|
).join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function setItems(newList) {
|
||||||
|
allItems = (newList || []).filter(Boolean);
|
||||||
|
renderOpts(allItems); // isOpen is false here → full list rendered, active class applied
|
||||||
|
}
|
||||||
|
|
||||||
|
return { setItems, getCurrent: () => current };
|
||||||
|
}
|
||||||
|
/* ── Convenience wrappers (keep back-compat names) ───────────────── */
|
||||||
|
function initDepartmentDropdown(wrap, onChange) {
|
||||||
|
return initSearchDropdown(wrap, onChange, {
|
||||||
|
cssPrefix: "inv-dep",
|
||||||
|
allLabel: "All Departments",
|
||||||
|
allIcon: "fas fa-th-large",
|
||||||
|
itemIcon: "fas fa-store"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ── Card HTML builder ───────────────────────────── */
|
||||||
|
function buildCardHtml(item, footerHtml) {
|
||||||
|
const DESC_THRESHOLD = 80; // chars — expand toggle only if longer than this
|
||||||
|
const desc = (item.ItemDescription ?? "").trim();
|
||||||
|
const hasLongDesc = desc.length > DESC_THRESHOLD;
|
||||||
|
|
||||||
|
const fmt = v => v != null ? Number(v).toLocaleString("en-US", {
|
||||||
|
minimumFractionDigits: 2, maximumFractionDigits: 2
|
||||||
|
}) : "—";
|
||||||
|
|
||||||
|
const createdDate = item.createdDate
|
||||||
|
? new Date(item.createdDate).toLocaleDateString("en-US",
|
||||||
|
{ year: "numeric", month: "short", day: "numeric" })
|
||||||
|
: "—";
|
||||||
|
|
||||||
|
const lotPart = item.LotNo
|
||||||
|
? ` · Lot: ${escHtml(item.LotNo)}`
|
||||||
|
: "";
|
||||||
|
|
||||||
|
const descBlock = !desc ? "" : hasLongDesc
|
||||||
|
? `<div class="inv-desc-wrap">
|
||||||
|
<div class="inv-desc-header" onclick="
|
||||||
|
this.querySelector('.inv-desc-caret').classList.toggle('open');
|
||||||
|
this.nextElementSibling.classList.toggle('open');">
|
||||||
|
<span class="inv-desc-lbl">
|
||||||
|
<i class="fas fa-align-left"></i> Description
|
||||||
|
</span>
|
||||||
|
<i class="fas fa-chevron-down inv-desc-caret"></i>
|
||||||
|
</div>
|
||||||
|
<div class="inv-desc-body">${escHtml(desc)}</div>
|
||||||
|
</div>`
|
||||||
|
: `<div class="inv-desc-wrap">
|
||||||
|
<div style="padding:8px 11px;">
|
||||||
|
<span class="inv-desc-lbl" style="margin-bottom:4px;">
|
||||||
|
<i class="fas fa-align-left"></i> Description
|
||||||
|
</span>
|
||||||
|
<div style="font-size:.82rem;color:var(--text-dark);line-height:1.5;margin-top:4px;">
|
||||||
|
${escHtml(desc)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="inv-card">
|
||||||
|
<div class="inv-card-hd">
|
||||||
|
<div class="inv-card-code">ITEMNO #${escHtml(String(item.itemNo))}${lotPart}</div>
|
||||||
|
<div class="inv-card-name">${escHtml(item.itemName ?? "—")}</div>
|
||||||
|
${(item.department || item.itemCategoryName) ? `
|
||||||
|
<div class="inv-card-sub">
|
||||||
|
${item.department ? `<i class="fas fa-building"></i> ${escHtml(item.department)}` : ""}
|
||||||
|
${item.department && item.itemCategoryName ? ` · ` : ""}
|
||||||
|
${item.itemCategoryName ? `<i class="fas fa-tag"></i> ${escHtml(item.itemCategoryName)}` : ""}
|
||||||
|
</div>
|
||||||
|
<div class="inv-card-sub">
|
||||||
|
<i class="fas fa-clock"></i>
|
||||||
|
${escHtml(createdDate ?? "—")}
|
||||||
|
</div>
|
||||||
|
<div class="inv-card-sub">
|
||||||
|
<i class="fas fa-qrcode"></i>
|
||||||
|
${escHtml(item.projectCode ?? "—")}
|
||||||
|
</div>
|
||||||
|
` : ""}
|
||||||
|
</div>
|
||||||
|
<div class="inv-card-body">
|
||||||
|
<div class="inv-agg-row">
|
||||||
|
<div class="inv-agg-badge">
|
||||||
|
<span class="inv-agg-lbl"><i class="fas fa-arrow-alt-circle-down"></i> Qty In</span>
|
||||||
|
<span class="inv-agg-val">${fmt(item.qtyIn)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="inv-agg-badge">
|
||||||
|
<span class="inv-agg-lbl"><i class="fas fa-arrow-alt-circle-up"></i> Qty Out</span>
|
||||||
|
<span class="inv-agg-val">${fmt(item.qtyOut)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="inv-agg-row">
|
||||||
|
<div class="inv-agg-badge">
|
||||||
|
<span class="inv-agg-lbl"><i class="fas fa-layer-group"></i> On Hand</span>
|
||||||
|
<span class="inv-agg-val">${fmt(item.qtyOnHand)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="inv-agg-badge">
|
||||||
|
<span class="inv-agg-lbl"><i class="fas fa-history"></i> Remaining</span>
|
||||||
|
<span class="inv-agg-val">${fmt(item.remainingQty)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
${descBlock}
|
||||||
|
<div>
|
||||||
|
<div class="inv-item-lbl"><i class="fas fa-box"></i> Item</div>
|
||||||
|
<div class="inv-item-row">
|
||||||
|
<span class="inv-item-name" title="${escAttr(item.itemName ?? "")}">
|
||||||
|
${escHtml(item.itemName ?? "—")}
|
||||||
|
</span>
|
||||||
|
<span class="inv-item-qty">
|
||||||
|
<i class="fas fa-cubes"></i> ${fmt(item.qtyIn)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="inv-card-ft">${footerHtml(item)}</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
splitAggr,
|
||||||
|
escHtml,
|
||||||
|
escAttr,
|
||||||
|
buildPageRange,
|
||||||
|
mkPageBtn,
|
||||||
|
renderPagination,
|
||||||
|
buildCardHtml,
|
||||||
|
initSearchDropdown,
|
||||||
|
initDepartmentDropdown,
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,738 @@
|
|||||||
|
<style>
|
||||||
|
.tm-type-opt {
|
||||||
|
border: 1.5px solid var(--border, #d6eaec);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all .15s;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-type-opt:hover {
|
||||||
|
border-color: var(--teal-mid, #0e7c86);
|
||||||
|
background: var(--teal-pale, #e6f7f8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-type-opt.selected {
|
||||||
|
border-color: var(--teal-mid, #0e7c86);
|
||||||
|
background: var(--teal-pale, #e6f7f8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-type-icon {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-icon-ris {
|
||||||
|
background: #e6f7f8;
|
||||||
|
color: #0e7c86;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-icon-mrs {
|
||||||
|
background: #E6F1FB;
|
||||||
|
color: #185FA5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-type-opt.selected .tm-icon-ris {
|
||||||
|
background: #0e7c86;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-type-opt.selected .tm-icon-mrs {
|
||||||
|
background: #185FA5;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-stock-badge {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 9px 12px;
|
||||||
|
background: var(--teal-pale, #e6f7f8);
|
||||||
|
border: 1px solid var(--border, #d6eaec);
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-dark, #1a2e35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-stock-badge i {
|
||||||
|
color: var(--teal-mid, #0e7c86);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-stock-badge strong {
|
||||||
|
margin-left: auto;
|
||||||
|
color: var(--teal-dark, #0d5c63);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-form-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-label {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-muted, #6b8890);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: .05em;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-label i {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-req {
|
||||||
|
color: #e53e3e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-input {
|
||||||
|
padding: 8px 10px;
|
||||||
|
border: 1.5px solid var(--border, #d6eaec);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fff;
|
||||||
|
color: var(--text-dark, #1a2e35);
|
||||||
|
font-family: 'DM Sans', sans-serif;
|
||||||
|
font-size: 13px;
|
||||||
|
width: 100%;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color .2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-input:focus {
|
||||||
|
border-color: var(--teal-mid, #0e7c86);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-input.error {
|
||||||
|
border-color: #e53e3e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-warn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: #c53030;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-hint {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-muted, #6b8890);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-info-box {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: #E6F1FB;
|
||||||
|
border: 1px solid #B5D4F4;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #0C447C;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-btn-cancel {
|
||||||
|
padding: 8px 18px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1.5px solid var(--border, #d6eaec);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-muted, #6b8890);
|
||||||
|
font-family: 'DM Sans', sans-serif;
|
||||||
|
font-size: .85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-btn-submit {
|
||||||
|
padding: 8px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: none;
|
||||||
|
background: var(--teal-mid, #0e7c86);
|
||||||
|
color: #fff;
|
||||||
|
font-family: 'DM Sans', sans-serif;
|
||||||
|
font-size: .85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 7px;
|
||||||
|
transition: background .18s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-btn-submit:hover:not(:disabled) {
|
||||||
|
background: var(--teal-dark, #0d5c63);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tm-btn-submit:disabled {
|
||||||
|
opacity: .5;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
window.TransactModal = (function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const H = window.InventoryHelpers;
|
||||||
|
|
||||||
|
// ── State ────────────────────────────────────────────────────────────────
|
||||||
|
let _ctx = null; // TransactContextDto from server
|
||||||
|
let _activeType = "ris"; // "ris" | "mrs"
|
||||||
|
let _onSuccess = null; // callback after successful submit
|
||||||
|
|
||||||
|
// ── DOM refs (resolved once modal is injected) ────────────────────────────
|
||||||
|
let modal, overlay, form, btnSubmit, submitLabel;
|
||||||
|
let optRIS, optMRS, formRIS, formMRS;
|
||||||
|
|
||||||
|
// ── RIS fields ────────────────────────────────────────────────────────────
|
||||||
|
let risItemBadge, risOnHand, risDiscipline, risIssuedTo, risQty,
|
||||||
|
risPRRef, risRemarks, risQtyWarn;
|
||||||
|
|
||||||
|
// ── MRS fields ────────────────────────────────────────────────────────────
|
||||||
|
let mrsRISSelect, mrsQty, mrsCondition, mrsRemarks,
|
||||||
|
mrsQtyWarn, mrsMaxHint;
|
||||||
|
|
||||||
|
// ════════════════════════════════════════════════════════════════════════
|
||||||
|
// PUBLIC API
|
||||||
|
// ════════════════════════════════════════════════════════════════════════
|
||||||
|
async function open(inventoryId, onSuccessCallback) {
|
||||||
|
_onSuccess = onSuccessCallback || null;
|
||||||
|
_injectModalHtml();
|
||||||
|
_bindRefs();
|
||||||
|
_showLoading(true);
|
||||||
|
_showOverlay(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`/InventoryMgmt/GetTransactContext?inventoryId=${inventoryId}`
|
||||||
|
);
|
||||||
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
_ctx = json.data ?? json;
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
showToast("error", "Could not load item context.", "Error", 4000);
|
||||||
|
_showOverlay(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_populateContext();
|
||||||
|
_selectType("ris");
|
||||||
|
_showLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ════════════════════════════════════════════════════════════════════════
|
||||||
|
// MODAL HTML INJECTION
|
||||||
|
// ════════════════════════════════════════════════════════════════════════
|
||||||
|
function _injectModalHtml() {
|
||||||
|
const existing = document.getElementById("transact-modal-overlay");
|
||||||
|
if (existing) existing.remove();
|
||||||
|
|
||||||
|
document.body.insertAdjacentHTML("beforeend", `
|
||||||
|
<div id="transact-modal-overlay" style="
|
||||||
|
position:fixed;inset:0;z-index:1080;
|
||||||
|
background:rgba(0,0,0,.45);
|
||||||
|
display:flex;align-items:center;justify-content:center;
|
||||||
|
padding:16px">
|
||||||
|
|
||||||
|
<div id="transact-modal" style="
|
||||||
|
background:var(--card-bg,#fff);
|
||||||
|
border-radius:14px;
|
||||||
|
border:1px solid var(--border,#d6eaec);
|
||||||
|
width:100%;max-width:520px;
|
||||||
|
overflow:hidden;
|
||||||
|
box-shadow:0 20px 60px rgba(0,0,0,.2)">
|
||||||
|
|
||||||
|
<!-- HEAD -->
|
||||||
|
<div style="background:linear-gradient(135deg,#0d5c63,#0e7c86);
|
||||||
|
padding:16px 18px;display:flex;align-items:center;
|
||||||
|
justify-content:space-between">
|
||||||
|
<div style="display:flex;align-items:center;gap:12px">
|
||||||
|
<div style="width:38px;height:38px;border-radius:10px;
|
||||||
|
background:rgba(255,255,255,.15);
|
||||||
|
display:flex;align-items:center;justify-content:center">
|
||||||
|
<i class="fas fa-transfer-alt" style="color:#fff;font-size:16px"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style="font-size:14px;font-weight:600;color:#fff">
|
||||||
|
New Transaction
|
||||||
|
</div>
|
||||||
|
<div id="tm-subtitle" style="font-size:11px;color:rgba(255,255,255,.65);margin-top:2px">
|
||||||
|
Loading…
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button id="tm-close" style="
|
||||||
|
width:30px;height:30px;border-radius:8px;
|
||||||
|
background:rgba(255,255,255,.12);
|
||||||
|
border:1px solid rgba(255,255,255,.2);
|
||||||
|
color:rgba(255,255,255,.85);cursor:pointer;
|
||||||
|
display:flex;align-items:center;justify-content:center">
|
||||||
|
<i class="fas fa-times" style="font-size:13px"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- LOADING STATE -->
|
||||||
|
<div id="tm-loading" style="
|
||||||
|
display:flex;align-items:center;justify-content:center;
|
||||||
|
gap:12px;padding:60px 20px;color:var(--text-muted,#6b8890)">
|
||||||
|
<div class="inv-spinner"></div>
|
||||||
|
<span style="font-size:.9rem">Loading…</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- BODY (hidden until loaded) -->
|
||||||
|
<div id="tm-body" style="display:none">
|
||||||
|
|
||||||
|
<!-- TYPE PICKER -->
|
||||||
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;
|
||||||
|
padding:14px 16px;border-bottom:1px solid var(--border,#d6eaec)">
|
||||||
|
<div id="tm-opt-ris" class="tm-type-opt" data-type="ris">
|
||||||
|
<div class="tm-type-icon tm-icon-ris">
|
||||||
|
<i class="fas fa-file-export"></i>
|
||||||
|
</div>
|
||||||
|
<div style="font-size:13px;font-weight:600;color:var(--text-dark,#1a2e35)">
|
||||||
|
Return Issuance Slip
|
||||||
|
</div>
|
||||||
|
<div style="font-size:11px;color:var(--text-muted,#6b8890);line-height:1.4">
|
||||||
|
Issue items out of inventory
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="tm-opt-mrs" class="tm-type-opt" data-type="mrs">
|
||||||
|
<div class="tm-type-icon tm-icon-mrs">
|
||||||
|
<i class="fas fa-file-import"></i>
|
||||||
|
</div>
|
||||||
|
<div style="font-size:13px;font-weight:600;color:var(--text-dark,#1a2e35)">
|
||||||
|
Material Return Slip
|
||||||
|
</div>
|
||||||
|
<div style="font-size:11px;color:var(--text-muted,#6b8890);line-height:1.4">
|
||||||
|
Return unused items to stock
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- RIS FORM -->
|
||||||
|
<div id="tm-form-ris" style="padding:14px 16px;display:flex;flex-direction:column;gap:12px">
|
||||||
|
<div id="tm-ris-stock-badge" class="tm-stock-badge"></div>
|
||||||
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
|
||||||
|
<div class="tm-form-group">
|
||||||
|
<label class="tm-label">
|
||||||
|
<i class="fas fa-tools"></i> Discipline <span class="tm-req">*</span>
|
||||||
|
</label>
|
||||||
|
<select id="tm-ris-discipline" class="tm-input">
|
||||||
|
<option value="">Select discipline…</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="tm-form-group">
|
||||||
|
<label class="tm-label">
|
||||||
|
<i class="fas fa-user"></i> Issued to <span class="tm-req">*</span>
|
||||||
|
</label>
|
||||||
|
<input id="tm-ris-issuedto" class="tm-input"
|
||||||
|
type="text" placeholder="Name or user ID…">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
|
||||||
|
<div class="tm-form-group">
|
||||||
|
<label class="tm-label">
|
||||||
|
<i class="fas fa-cubes"></i> Qty to issue <span class="tm-req">*</span>
|
||||||
|
</label>
|
||||||
|
<input id="tm-ris-qty" class="tm-input"
|
||||||
|
type="number" min="1" placeholder="0">
|
||||||
|
<span id="tm-ris-qty-warn" class="tm-warn" style="display:none">
|
||||||
|
<i class="fas fa-exclamation-triangle"></i>
|
||||||
|
<span id="tm-ris-qty-warn-text"></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="tm-form-group">
|
||||||
|
<label class="tm-label">
|
||||||
|
<i class="fas fa-file-alt"></i> PR reference
|
||||||
|
</label>
|
||||||
|
<input id="tm-ris-prref" class="tm-input"
|
||||||
|
type="text" placeholder="PR-2026-XXXX">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tm-form-group">
|
||||||
|
<label class="tm-label">
|
||||||
|
<i class="fas fa-sticky-note"></i> Remarks
|
||||||
|
</label>
|
||||||
|
<input id="tm-ris-remarks" class="tm-input"
|
||||||
|
type="text" placeholder="Optional notes…">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MRS FORM -->
|
||||||
|
<div id="tm-form-mrs" style="padding:14px 16px;display:none;flex-direction:column;gap:12px">
|
||||||
|
<div class="tm-info-box">
|
||||||
|
<i class="fas fa-info-circle" style="color:#185FA5;flex-shrink:0;margin-top:2px"></i>
|
||||||
|
<span>Select the original RIS for this return.
|
||||||
|
Only the qty issued on that slip can be returned.</span>
|
||||||
|
</div>
|
||||||
|
<div class="tm-form-group">
|
||||||
|
<label class="tm-label">
|
||||||
|
<i class="fas fa-receipt"></i> Original RIS reference <span class="tm-req">*</span>
|
||||||
|
</label>
|
||||||
|
<select id="tm-mrs-ris" class="tm-input">
|
||||||
|
<option value="">Select RIS…</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
|
||||||
|
<div class="tm-form-group">
|
||||||
|
<label class="tm-label">
|
||||||
|
<i class="fas fa-cubes"></i> Qty to return <span class="tm-req">*</span>
|
||||||
|
</label>
|
||||||
|
<input id="tm-mrs-qty" class="tm-input"
|
||||||
|
type="number" min="1" placeholder="0">
|
||||||
|
<span id="tm-mrs-max-hint" class="tm-hint" style="display:none"></span>
|
||||||
|
<span id="tm-mrs-qty-warn" class="tm-warn" style="display:none">
|
||||||
|
<i class="fas fa-exclamation-triangle"></i>
|
||||||
|
<span id="tm-mrs-qty-warn-text"></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="tm-form-group">
|
||||||
|
<label class="tm-label">
|
||||||
|
<i class="fas fa-tag"></i> Condition
|
||||||
|
</label>
|
||||||
|
<select id="tm-mrs-condition" class="tm-input">
|
||||||
|
<option value="Good">Good</option>
|
||||||
|
<option value="Damaged">Damaged</option>
|
||||||
|
<option value="Partial">Partial</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tm-form-group">
|
||||||
|
<label class="tm-label">
|
||||||
|
<i class="fas fa-sticky-note"></i> Remarks
|
||||||
|
</label>
|
||||||
|
<input id="tm-mrs-remarks" class="tm-input"
|
||||||
|
type="text" placeholder="Reason for return…">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div><!-- /tm-body -->
|
||||||
|
|
||||||
|
<!-- FOOTER -->
|
||||||
|
<div style="
|
||||||
|
padding:12px 16px;
|
||||||
|
border-top:1px solid var(--border,#d6eaec);
|
||||||
|
background:var(--bg-page,#f0f6f7);
|
||||||
|
display:flex;align-items:center;justify-content:flex-end;gap:8px">
|
||||||
|
<button id="tm-cancel" class="tm-btn-cancel">Cancel</button>
|
||||||
|
<button id="tm-submit" class="tm-btn-submit" disabled>
|
||||||
|
<i class="fas fa-check"></i>
|
||||||
|
<span id="tm-submit-label">Create RIS</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ════════════════════════════════════════════════════════════════════════
|
||||||
|
// BIND DOM REFS + EVENTS
|
||||||
|
// ════════════════════════════════════════════════════════════════════════
|
||||||
|
function _bindRefs() {
|
||||||
|
overlay = document.getElementById("transact-modal-overlay");
|
||||||
|
modal = document.getElementById("transact-modal");
|
||||||
|
btnSubmit = document.getElementById("tm-submit");
|
||||||
|
submitLabel = document.getElementById("tm-submit-label");
|
||||||
|
optRIS = document.getElementById("tm-opt-ris");
|
||||||
|
optMRS = document.getElementById("tm-opt-mrs");
|
||||||
|
formRIS = document.getElementById("tm-form-ris");
|
||||||
|
formMRS = document.getElementById("tm-form-mrs");
|
||||||
|
|
||||||
|
// RIS fields
|
||||||
|
risItemBadge = document.getElementById("tm-ris-stock-badge");
|
||||||
|
risDiscipline = document.getElementById("tm-ris-discipline");
|
||||||
|
risIssuedTo = document.getElementById("tm-ris-issuedto");
|
||||||
|
risQty = document.getElementById("tm-ris-qty");
|
||||||
|
risPRRef = document.getElementById("tm-ris-prref");
|
||||||
|
risRemarks = document.getElementById("tm-ris-remarks");
|
||||||
|
risQtyWarn = document.getElementById("tm-ris-qty-warn");
|
||||||
|
|
||||||
|
// MRS fields
|
||||||
|
mrsRISSelect = document.getElementById("tm-mrs-ris");
|
||||||
|
mrsQty = document.getElementById("tm-mrs-qty");
|
||||||
|
mrsCondition = document.getElementById("tm-mrs-condition");
|
||||||
|
mrsRemarks = document.getElementById("tm-mrs-remarks");
|
||||||
|
mrsQtyWarn = document.getElementById("tm-mrs-qty-warn");
|
||||||
|
mrsMaxHint = document.getElementById("tm-mrs-max-hint");
|
||||||
|
|
||||||
|
// Type picker
|
||||||
|
[optRIS, optMRS].forEach(el =>
|
||||||
|
el.addEventListener("click", () => _selectType(el.dataset.type))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Close / Cancel
|
||||||
|
document.getElementById("tm-close").addEventListener("click", _close);
|
||||||
|
document.getElementById("tm-cancel").addEventListener("click", _close);
|
||||||
|
overlay.addEventListener("click", e => { if (e.target === overlay) _close(); });
|
||||||
|
|
||||||
|
// Qty live validation
|
||||||
|
risQty.addEventListener("input", _validateRISQty);
|
||||||
|
mrsRISSelect.addEventListener("change", _onMRSRISChange);
|
||||||
|
mrsQty.addEventListener("input", _validateMRSQty);
|
||||||
|
|
||||||
|
// Submit
|
||||||
|
btnSubmit.addEventListener("click", _handleSubmit);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ════════════════════════════════════════════════════════════════════════
|
||||||
|
// POPULATE FROM CONTEXT
|
||||||
|
// ════════════════════════════════════════════════════════════════════════
|
||||||
|
function _populateContext() {
|
||||||
|
|
||||||
|
// Header subtitle
|
||||||
|
document.getElementById("tm-subtitle").textContent =
|
||||||
|
`${H.escHtml(_ctx.itemName)} · Item #${_ctx.itemNo}`;
|
||||||
|
|
||||||
|
document.getElementById("tm-ris-prref").value = _ctx.prNo ?? "";
|
||||||
|
|
||||||
|
// Stock badge
|
||||||
|
risItemBadge.innerHTML = `
|
||||||
|
<i class="fas fa-layer-group"></i>
|
||||||
|
<span>On hand</span>
|
||||||
|
<strong>${_ctx.qtyOnHand} pcs</strong>
|
||||||
|
<span style="margin-left:8px;color:var(--text-muted,#6b8890)">
|
||||||
|
In: ${_ctx.qtyIn} · Out: ${_ctx.qtyOut}
|
||||||
|
</span>`;
|
||||||
|
|
||||||
|
// Discipline dropdown
|
||||||
|
risDiscipline.innerHTML = `
|
||||||
|
<option value="">Select discipline…</option>` +
|
||||||
|
(_ctx.disciplines || []).map(d =>
|
||||||
|
`
|
||||||
|
<option value="${d.disciplineId}">${H.escHtml(d.disciplineName)}</option>`
|
||||||
|
).join("");
|
||||||
|
|
||||||
|
// MRS — open RIS list
|
||||||
|
const hasRIS = _ctx.openRISList && _ctx.openRISList.length > 0;
|
||||||
|
mrsRISSelect.innerHTML = `
|
||||||
|
<option value="">Select RIS…</option>` +
|
||||||
|
(hasRIS
|
||||||
|
? _ctx.openRISList.map(r =>
|
||||||
|
`
|
||||||
|
<option value="${r.risId}"
|
||||||
|
data-max="${r.qtyAvailableToReturn}"
|
||||||
|
data-discipline="${H.escAttr(r.disciplineName)}">
|
||||||
|
${H.escHtml(r.risNo)} — ${r.qtyAvailableToReturn} pcs avail — ${H.escHtml(r.disciplineName)}
|
||||||
|
</option>`)
|
||||||
|
.join("")
|
||||||
|
: "");
|
||||||
|
|
||||||
|
if (!hasRIS) {
|
||||||
|
optMRS.style.opacity = ".45";
|
||||||
|
optMRS.style.cursor = "not-allowed";
|
||||||
|
optMRS.title = "No approved RIS records with remaining qty for this item.";
|
||||||
|
optMRS.onclick = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ════════════════════════════════════════════════════════════════════════
|
||||||
|
// TYPE SWITCHING
|
||||||
|
// ════════════════════════════════════════════════════════════════════════
|
||||||
|
function _selectType(type) {
|
||||||
|
_activeType = type;
|
||||||
|
const isRIS = type === "ris";
|
||||||
|
|
||||||
|
optRIS.classList.toggle("selected", isRIS);
|
||||||
|
optMRS.classList.toggle("selected", !isRIS);
|
||||||
|
|
||||||
|
formRIS.style.display = isRIS ? "flex" : "none";
|
||||||
|
formMRS.style.display = !isRIS ? "flex" : "none";
|
||||||
|
|
||||||
|
submitLabel.textContent = isRIS ? "Create RIS" : "Create MRS";
|
||||||
|
btnSubmit.disabled = false;
|
||||||
|
|
||||||
|
_clearErrors();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ════════════════════════════════════════════════════════════════════════
|
||||||
|
// VALIDATION
|
||||||
|
// ════════════════════════════════════════════════════════════════════════
|
||||||
|
function _validateRISQty() {
|
||||||
|
const val = parseInt(risQty.value, 10);
|
||||||
|
const max = _ctx?.qtyOnHand ?? 0;
|
||||||
|
const over = !isNaN(val) && val > max;
|
||||||
|
const zero = !isNaN(val) && val < 1;
|
||||||
|
|
||||||
|
document.getElementById("tm-ris-qty-warn-text").textContent =
|
||||||
|
over ? `Cannot exceed ${max} on hand.`
|
||||||
|
: zero ? "Must be at least 1." : "";
|
||||||
|
|
||||||
|
risQtyWarn.style.display = (over || zero) ? "flex" : "none";
|
||||||
|
risQty.classList.toggle("error", over || zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _onMRSRISChange() {
|
||||||
|
const opt = mrsRISSelect.selectedOptions[0];
|
||||||
|
const max = opt ? parseInt(opt.dataset.max, 10) : 0;
|
||||||
|
|
||||||
|
if (opt && opt.value) {
|
||||||
|
mrsMaxHint.style.display = "block";
|
||||||
|
mrsMaxHint.textContent = `Max returnable: ${max} pcs`;
|
||||||
|
mrsQty.max = max;
|
||||||
|
} else {
|
||||||
|
mrsMaxHint.style.display = "none";
|
||||||
|
mrsQty.max = "";
|
||||||
|
}
|
||||||
|
_validateMRSQty();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _validateMRSQty() {
|
||||||
|
const val = parseInt(mrsQty.value, 10);
|
||||||
|
const opt = mrsRISSelect.selectedOptions[0];
|
||||||
|
const max = opt ? parseInt(opt.dataset.max, 10) : Infinity;
|
||||||
|
const over = !isNaN(val) && val > max;
|
||||||
|
const zero = !isNaN(val) && val < 1;
|
||||||
|
|
||||||
|
document.getElementById("tm-mrs-qty-warn-text").textContent =
|
||||||
|
over ? `Cannot exceed ${max} available.`
|
||||||
|
: zero ? "Must be at least 1." : "";
|
||||||
|
|
||||||
|
mrsQtyWarn.style.display = (over || zero) ? "flex" : "none";
|
||||||
|
mrsQty.classList.toggle("error", over || zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _clearErrors() {
|
||||||
|
[risQty, risIssuedTo, risDiscipline, mrsRISSelect, mrsQty].forEach(el => {
|
||||||
|
if (el) el.classList.remove("error");
|
||||||
|
});
|
||||||
|
[risQtyWarn, mrsQtyWarn].forEach(el => {
|
||||||
|
if (el) el.style.display = "none";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function _validateForm() {
|
||||||
|
let valid = true;
|
||||||
|
|
||||||
|
if (_activeType === "ris") {
|
||||||
|
if (!risDiscipline.value) { risDiscipline.classList.add("error"); valid = false; }
|
||||||
|
if (!risIssuedTo.value.trim()) { risIssuedTo.classList.add("error"); valid = false; }
|
||||||
|
const qty = parseInt(risQty.value, 10);
|
||||||
|
if (isNaN(qty) || qty < 1 || qty > _ctx.qtyOnHand) {
|
||||||
|
risQty.classList.add("error"); valid = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!mrsRISSelect.value) { mrsRISSelect.classList.add("error"); valid = false; }
|
||||||
|
const qty = parseInt(mrsQty.value, 10);
|
||||||
|
const opt = mrsRISSelect.selectedOptions[0];
|
||||||
|
const maxRet = opt ? parseInt(opt.dataset.max, 10) : 0;
|
||||||
|
if (isNaN(qty) || qty < 1 || qty > maxRet) {
|
||||||
|
mrsQty.classList.add("error"); valid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ════════════════════════════════════════════════════════════════════════
|
||||||
|
// SUBMIT
|
||||||
|
// ════════════════════════════════════════════════════════════════════════
|
||||||
|
async function _handleSubmit() {
|
||||||
|
_clearErrors();
|
||||||
|
if (!_validateForm()) {
|
||||||
|
showToast("warning", "Please fix the highlighted fields.", "Validation", 3000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmed = await showConfirmation({
|
||||||
|
title: _activeType === "ris" ? "Create Return Issuance Slip" : "Create Material Return Slip",
|
||||||
|
message: _activeType === "ris"
|
||||||
|
? `Issue <strong>${risQty.value} pcs</strong> from inventory?`
|
||||||
|
: `Return <strong>${mrsQty.value} pcs</strong> back to stock?`,
|
||||||
|
type: "warning",
|
||||||
|
confirmText: _activeType === "ris" ? "Create RIS" : "Create MRS",
|
||||||
|
cancelText: "Cancel"
|
||||||
|
});
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
|
btnSubmit.disabled = true;
|
||||||
|
btnSubmit.querySelector("i").className = "fas fa-spinner fa-spin";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const payload = _activeType === "ris"
|
||||||
|
? {
|
||||||
|
InventoryId: _ctx.inventoryId,
|
||||||
|
PRDetailId: parseInt(risPRRef.value, 10) || 0,
|
||||||
|
IssuedTo: risIssuedTo.value.trim(),
|
||||||
|
DisciplineId: parseInt(risDiscipline.value, 10),
|
||||||
|
QtyIssued: parseInt(risQty.value, 10),
|
||||||
|
Remarks: risRemarks.value.trim() || null
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
RISId: parseInt(mrsRISSelect.value, 10),
|
||||||
|
QtyReturned: parseInt(mrsQty.value, 10),
|
||||||
|
ReturnedBy: _ctx.department || "N/A",
|
||||||
|
Condition: mrsCondition.value,
|
||||||
|
Remarks: mrsRemarks.value.trim() || null
|
||||||
|
};
|
||||||
|
|
||||||
|
const endpoint = _activeType === "ris"
|
||||||
|
? "/RISMgmt/CreateRIS"
|
||||||
|
: "/MRSMgmt/CreateMRS";
|
||||||
|
|
||||||
|
const res = await fetch(endpoint, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
if (!res.ok || !json.success) {
|
||||||
|
showToast("error", json.message ?? "An error occurred.", "Failed", 4000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showToast("success", json.message, "Done!", 3500);
|
||||||
|
|
||||||
|
const onSuccess = _onSuccess;
|
||||||
|
_close();
|
||||||
|
|
||||||
|
if (typeof onSuccess === "function") onSuccess();
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
showToast("error", "Request failed. Please try again.", "Error", 4000);
|
||||||
|
} finally {
|
||||||
|
btnSubmit.disabled = false;
|
||||||
|
btnSubmit.querySelector("i").className = "fas fa-check";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ════════════════════════════════════════════════════════════════════════
|
||||||
|
// HELPERS
|
||||||
|
// ════════════════════════════════════════════════════════════════════════
|
||||||
|
function _showOverlay(show) {
|
||||||
|
const el = document.getElementById("transact-modal-overlay");
|
||||||
|
if (el) el.style.display = show ? "flex" : "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
function _showLoading(loading) {
|
||||||
|
document.getElementById("tm-loading").style.display = loading ? "flex" : "none";
|
||||||
|
document.getElementById("tm-body").style.display = loading ? "none" : "block";
|
||||||
|
if (btnSubmit) btnSubmit.disabled = loading;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _close() {
|
||||||
|
document.getElementById("transact-modal-overlay")?.remove();
|
||||||
|
|
||||||
|
_ctx = null;
|
||||||
|
_onSuccess = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { open };
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
@ -18,6 +18,6 @@
|
|||||||
<link href="~/lib/jquery-ui-1132custom/jquery-ui.css" rel="stylesheet" />
|
<link href="~/lib/jquery-ui-1132custom/jquery-ui.css" rel="stylesheet" />
|
||||||
<link href="~/lib/jquery-ui-1132custom/jquery-ui.min.css" rel="stylesheet" />
|
<link href="~/lib/jquery-ui-1132custom/jquery-ui.min.css" rel="stylesheet" />
|
||||||
<link href="~/css/StyleConflict.css" rel="stylesheet" />
|
<link href="~/css/StyleConflict.css" rel="stylesheet" />
|
||||||
<link href="~/css/toast-notifications.css" rel="stylesheet" />
|
<link href="~/css/toast-notificationsV2.css" rel="stylesheet" />
|
||||||
<link href="~/css/confirmation-modal.css" rel="stylesheet" />
|
<link href="~/css/confirmation-modalV2.css" rel="stylesheet" />
|
||||||
|
|
||||||
|
|||||||
@ -18,5 +18,5 @@
|
|||||||
<script src="~/datatables/pdfmake-0.2.7/pdfmake.min.js"></script>
|
<script src="~/datatables/pdfmake-0.2.7/pdfmake.min.js"></script>
|
||||||
<script src="~/datatables/pdfmake-0.2.7/vfs_fonts.js"></script>
|
<script src="~/datatables/pdfmake-0.2.7/vfs_fonts.js"></script>
|
||||||
<script src="~/js/toast-notifications.js"></script>
|
<script src="~/js/toast-notifications.js"></script>
|
||||||
<script src="~/js/confirmation-modal.js"></script>
|
<script src="~/js/confirmation-modalV2.js"></script>
|
||||||
|
|
||||||
|
|||||||
@ -113,6 +113,8 @@
|
|||||||
"PutSuppBidDetails": "api/CanvassMgmt/PutSuppBidDetails/",
|
"PutSuppBidDetails": "api/CanvassMgmt/PutSuppBidDetails/",
|
||||||
"PostPutMySupplier": "api/CanvassMgmt/PostPutMySupplier/",
|
"PostPutMySupplier": "api/CanvassMgmt/PostPutMySupplier/",
|
||||||
"PostPutItemTagging": "api/CanvassMgmt/PostPutItemTagging/",
|
"PostPutItemTagging": "api/CanvassMgmt/PostPutItemTagging/",
|
||||||
|
"PostSupplierForCanvass": "api/CanvassMgmt/PostSupplierForCanvass/",
|
||||||
|
"StartCanvass": "api/CanvassMgmt/StartCanvass/",
|
||||||
"UnlockFormLink": "api/CanvassMgmt/UnlockFormLink/"
|
"UnlockFormLink": "api/CanvassMgmt/UnlockFormLink/"
|
||||||
},
|
},
|
||||||
"POMgmt": {
|
"POMgmt": {
|
||||||
@ -188,10 +190,16 @@
|
|||||||
"GetLotNo": "api/InventoryMgmt/GetLotNo/",
|
"GetLotNo": "api/InventoryMgmt/GetLotNo/",
|
||||||
"GetLotQtyByItem": "api/InventoryMgmt/GetLotQtyByItem/",
|
"GetLotQtyByItem": "api/InventoryMgmt/GetLotQtyByItem/",
|
||||||
"GetLotNoById": "api/InventoryMgmt/GetLotNoById/",
|
"GetLotNoById": "api/InventoryMgmt/GetLotNoById/",
|
||||||
|
"GetInventory": "api/InventoryMgmt/GetInventory/",
|
||||||
|
"GetTransactContextAsync": "api/InventoryMgmt/GetTransactContextAsync/",
|
||||||
"PostPutReqApproval": "api/InventoryMgmt/PostPutReqApproval/",
|
"PostPutReqApproval": "api/InventoryMgmt/PostPutReqApproval/",
|
||||||
"PostPutLotNo": "api/InventoryMgmt/PostPutLotNo/",
|
"PostPutLotNo": "api/InventoryMgmt/PostPutLotNo/",
|
||||||
"PostPutReqItems": "api/InventoryMgmt/PostPutReqItems/",
|
"PostPutReqItems": "api/InventoryMgmt/PostPutReqItems/",
|
||||||
"PostPutLotBin": "api/InventoryMgmt/PostPutLotBin/"
|
"PostPutLotBin": "api/InventoryMgmt/PostPutLotBin/",
|
||||||
|
"ApproveRIS": "api/RISMgmt/ApproveRIS/",
|
||||||
|
"CancelRIS": "api/RISMgmt/CancelRIS/",
|
||||||
|
"CreateRIS": "api/RISMgmt/",
|
||||||
|
"GetRIS": "api/RISMgmt/GetRIS/"
|
||||||
},
|
},
|
||||||
"ImageUploadSettings": {
|
"ImageUploadSettings": {
|
||||||
"UploadPath": "C:\\WebApps\\wwwroot\\Content\\Images"
|
"UploadPath": "C:\\WebApps\\wwwroot\\Content\\Images"
|
||||||
|
|||||||
161
CPRNIMS.WebApps/wwwroot/JsFunctions/Inventory/InventoryTabs.js
Normal file
161
CPRNIMS.WebApps/wwwroot/JsFunctions/Inventory/InventoryTabs.js
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const tabConfig = {
|
||||||
|
'Inventory-inv-per-supplier': {
|
||||||
|
title: 'inv Per (Supplier)',
|
||||||
|
tableId: 1,
|
||||||
|
endpoint: '/InventoryMgmt/GetTabbedById'
|
||||||
|
},
|
||||||
|
'Inventory-inv-per-item': {
|
||||||
|
title: 'inv Per (Item)',
|
||||||
|
tableId: 2,
|
||||||
|
endpoint: '/InventoryMgmt/GetTabbedById'
|
||||||
|
},
|
||||||
|
'Inventory-suppliers': {
|
||||||
|
title: 'Supplier Management',
|
||||||
|
tableId: 3,
|
||||||
|
endpoint: '/InventoryMgmt/GetTabbedById'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let currentTab = 'Inventory-inv-per-supplier';
|
||||||
|
let isSwitching = false;
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
const tabs = document.querySelectorAll('.Inventory-tab-btn');
|
||||||
|
tabs.forEach(tab => {
|
||||||
|
tab.addEventListener('click', handleTabClick);
|
||||||
|
});
|
||||||
|
|
||||||
|
loadTabContent('Inventory-inv-per-supplier');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTabClick(e) {
|
||||||
|
if (isSwitching) return;
|
||||||
|
|
||||||
|
const btn = e.currentTarget;
|
||||||
|
const tabId = btn.getAttribute('data-tab');
|
||||||
|
|
||||||
|
if (tabId === currentTab) return;
|
||||||
|
|
||||||
|
switchTab(tabId, btn);
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchTab(tabId, btn) {
|
||||||
|
if (!tabConfig[tabId]) {
|
||||||
|
console.error('Invalid tab ID:', tabId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSwitching = true;
|
||||||
|
|
||||||
|
document.querySelectorAll('.Inventory-tab-btn').forEach(b => {
|
||||||
|
b.classList.remove('active');
|
||||||
|
});
|
||||||
|
btn.classList.add('active');
|
||||||
|
|
||||||
|
updateTitle(tabConfig[tabId].title);
|
||||||
|
|
||||||
|
loadTabContent(tabId, btn);
|
||||||
|
|
||||||
|
currentTab = tabId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTitle(title) {
|
||||||
|
const titleEl = document.getElementById('pageTitle');
|
||||||
|
if (!titleEl) return;
|
||||||
|
|
||||||
|
titleEl.classList.add('updating');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
titleEl.textContent = title;
|
||||||
|
|
||||||
|
void titleEl.offsetWidth;
|
||||||
|
|
||||||
|
titleEl.classList.remove('updating');
|
||||||
|
}, 250);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadTabContent(tabId, btn = null) {
|
||||||
|
const config = tabConfig[tabId];
|
||||||
|
if (!config) {
|
||||||
|
console.error('Tab config not found:', tabId);
|
||||||
|
isSwitching = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (btn) {
|
||||||
|
btn.classList.add('loading');
|
||||||
|
}
|
||||||
|
showContainerLoading(true);
|
||||||
|
|
||||||
|
InventoryTabbedComponent(config.tableId, config.endpoint, function (success) {
|
||||||
|
if (btn) {
|
||||||
|
btn.classList.remove('loading');
|
||||||
|
}
|
||||||
|
showContainerLoading(false);
|
||||||
|
isSwitching = false;
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
console.error('Failed to load tab content');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showContainerLoading(isLoading) {
|
||||||
|
const container = document.getElementById('TabbedContainer');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
container.style.opacity = '0.5';
|
||||||
|
container.style.pointerEvents = 'none';
|
||||||
|
} else {
|
||||||
|
container.style.opacity = '1';
|
||||||
|
container.style.pointerEvents = 'auto';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', init);
|
||||||
|
} else {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.PRTabs = {
|
||||||
|
switchTo: function (tabId) {
|
||||||
|
const btn = document.querySelector(`.Inventory-tab-btn[data-tab="${tabId}"]`);
|
||||||
|
if (btn && !isSwitching) {
|
||||||
|
switchTab(tabId, btn);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reload: function () {
|
||||||
|
loadTabContent(currentTab);
|
||||||
|
},
|
||||||
|
getCurrent: function () {
|
||||||
|
return currentTab;
|
||||||
|
},
|
||||||
|
addTab: function (tabId, config) {
|
||||||
|
tabConfig[tabId] = config;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
function InventoryTabbedComponent(id, endpoint, callback) {
|
||||||
|
$.ajax({
|
||||||
|
url: endpoint,
|
||||||
|
type: 'GET',
|
||||||
|
data: { TableId: id },
|
||||||
|
success: function (response) {
|
||||||
|
$('#TabbedContainer').html(response);
|
||||||
|
|
||||||
|
if (callback) callback(true);
|
||||||
|
},
|
||||||
|
error: function (xhr, status, error) {
|
||||||
|
console.error("Error loading component:", error);
|
||||||
|
$('#TabbedContainer').html('<div class="alert alert-danger">Failed to load content. Please try again.</div>');
|
||||||
|
if (callback) callback(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
.confirmation-modal {
|
.confirmation-modal {
|
||||||
z-index: 9999;
|
z-index: 99999 !important;
|
||||||
background-color: rgba(0, 0, 0, 0.6);
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2,7 +2,7 @@
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
top: 20px;
|
top: 20px;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
z-index: 9999;
|
z-index: 99999 !important;
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,38 +17,46 @@
|
|||||||
const config = {
|
const config = {
|
||||||
title: 'Confirm Action',
|
title: 'Confirm Action',
|
||||||
message: 'Are you sure you want to proceed?',
|
message: 'Are you sure you want to proceed?',
|
||||||
type: 'warning', // warning, danger, info, success
|
type: 'warning',
|
||||||
confirmText: 'Confirm',
|
confirmText: 'Confirm',
|
||||||
cancelText: 'Cancel',
|
cancelText: 'Cancel',
|
||||||
size: 'md', // sm, md, lg, xl
|
size: 'md',
|
||||||
...options
|
...options
|
||||||
};
|
};
|
||||||
|
|
||||||
const modalId = `confirmation-modal-${++this.modalCount}`;
|
const modalId = `confirmation-modal-${++this.modalCount}`;
|
||||||
const modal = this.createModal(modalId, config);
|
const modal = this.createModal(modalId, config);
|
||||||
|
|
||||||
this.container.appendChild(modal);
|
// ── Append directly to body, AFTER everything else ────────────
|
||||||
|
document.body.appendChild(modal);
|
||||||
|
|
||||||
const bsModal = new bootstrap.Modal(modal, {
|
const bsModal = new bootstrap.Modal(modal, {
|
||||||
backdrop: 'static',
|
backdrop: 'static',
|
||||||
keyboard: false
|
keyboard: false
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle confirm button
|
modal.addEventListener('shown.bs.modal', () => {
|
||||||
|
// Force modal above transact overlay
|
||||||
|
modal.style.zIndex = '99999';
|
||||||
|
|
||||||
|
// Grab the very last backdrop Bootstrap added
|
||||||
|
const backdrops = document.querySelectorAll('.modal-backdrop');
|
||||||
|
const lastBackdrop = backdrops[backdrops.length - 1];
|
||||||
|
if (lastBackdrop) lastBackdrop.style.zIndex = '99998';
|
||||||
|
});
|
||||||
|
|
||||||
const confirmBtn = modal.querySelector('.btn-confirm');
|
const confirmBtn = modal.querySelector('.btn-confirm');
|
||||||
confirmBtn.addEventListener('click', () => {
|
confirmBtn.addEventListener('click', () => {
|
||||||
bsModal.hide();
|
bsModal.hide();
|
||||||
resolve(true);
|
resolve(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle cancel button
|
|
||||||
const cancelBtn = modal.querySelector('.btn-cancel');
|
const cancelBtn = modal.querySelector('.btn-cancel');
|
||||||
cancelBtn.addEventListener('click', () => {
|
cancelBtn.addEventListener('click', () => {
|
||||||
bsModal.hide();
|
bsModal.hide();
|
||||||
resolve(false);
|
resolve(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle modal close (X button or backdrop)
|
|
||||||
modal.addEventListener('hidden.bs.modal', () => {
|
modal.addEventListener('hidden.bs.modal', () => {
|
||||||
modal.remove();
|
modal.remove();
|
||||||
if (!confirmBtn.clicked && !cancelBtn.clicked) {
|
if (!confirmBtn.clicked && !cancelBtn.clicked) {
|
||||||
@ -56,7 +64,6 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mark buttons as clicked to differentiate from modal close
|
|
||||||
confirmBtn.addEventListener('click', () => confirmBtn.clicked = true);
|
confirmBtn.addEventListener('click', () => confirmBtn.clicked = true);
|
||||||
cancelBtn.addEventListener('click', () => cancelBtn.clicked = true);
|
cancelBtn.addEventListener('click', () => cancelBtn.clicked = true);
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user