Adding validaiton for existing suppliers to prevent from duplication
This commit is contained in:
parent
d1c9c4b52b
commit
b02af975ac
@ -1,10 +1,12 @@
|
|||||||
using CPRNIMS.Domain.Services.ICanvass;
|
using CPRNIMS.Domain.Services;
|
||||||
|
using CPRNIMS.Domain.Services.ICanvass;
|
||||||
using CPRNIMS.Infrastructure.Dto.Canvass;
|
using CPRNIMS.Infrastructure.Dto.Canvass;
|
||||||
using CPRNIMS.Infrastructure.Dto.Canvass.Request;
|
using CPRNIMS.Infrastructure.Dto.Canvass.Request;
|
||||||
using CPRNIMS.Infrastructure.Dto.Canvass.Response;
|
using CPRNIMS.Infrastructure.Dto.Canvass.Response;
|
||||||
using CPRNIMS.Infrastructure.Dto.Common;
|
using CPRNIMS.Infrastructure.Dto.Common;
|
||||||
using CPRNIMS.Infrastructure.Entities.Canvass;
|
using CPRNIMS.Infrastructure.Entities.Canvass;
|
||||||
using CPRNIMS.Infrastructure.Entities.Purchasing;
|
using CPRNIMS.Infrastructure.Entities.Purchasing;
|
||||||
|
using CPRNIMS.Infrastructure.ViewModel.Canvass;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -16,9 +18,9 @@ namespace CPRNIMS.Domain.Contracts.Canvass
|
|||||||
public interface ICanvass : ISupplier
|
public interface ICanvass : ISupplier
|
||||||
{
|
{
|
||||||
#region Post Put
|
#region Post Put
|
||||||
Task<RFQ> PostPerSupplierToken(CanvassDto CanvassDto);
|
Task<Result<StartCanvassResponse>> StartCanvass(CanvassVM canvass,CancellationToken ct);
|
||||||
|
Task<RFQ> PostPerSupplierToken(ForCanvassDto CanvassDto);
|
||||||
Task<ForCanvassFollowUp> PutSupplierCanvass(long canvassSupplierId);
|
Task<ForCanvassFollowUp> PutSupplierCanvass(long canvassSupplierId);
|
||||||
Task<PRCanvassDetail> PostCanvass(CanvassDto CanvassDto);
|
|
||||||
Task<SupplierResponse> PostPutSupplier(CanvassDto CanvassDto);
|
Task<SupplierResponse> PostPutSupplier(CanvassDto CanvassDto);
|
||||||
Task<SupplierResponse> PostTaggingSupplier(CanvassDto CanvassDto);
|
Task<SupplierResponse> PostTaggingSupplier(CanvassDto CanvassDto);
|
||||||
Task<SupplierResponse> PostApprovedSupp(CanvassDto CanvassDto);
|
Task<SupplierResponse> PostApprovedSupp(CanvassDto CanvassDto);
|
||||||
@ -34,7 +36,7 @@ namespace CPRNIMS.Domain.Contracts.Canvass
|
|||||||
Task<List<WOResponse>> GetCanvassWOResponse(CanvassDto CanvassDto);
|
Task<List<WOResponse>> GetCanvassWOResponse(CanvassDto CanvassDto);
|
||||||
Task<List<WOResponseById>> GetWOResponseBySuppId(CanvassDto CanvassDto);
|
Task<List<WOResponseById>> GetWOResponseBySuppId(CanvassDto CanvassDto);
|
||||||
Task<List<SupplierResponseDto>> GetSupplierById(CanvassDto CanvassDto);
|
Task<List<SupplierResponseDto>> GetSupplierById(CanvassDto CanvassDto);
|
||||||
Task<List<RFQReference>> GetRFQ(CanvassDto CanvassDto);
|
Task<List<RFQReference>> GetRFQ(ForCanvassDto CanvassDto);
|
||||||
Task<List<BiddingItem>> GetSupplierBid(CanvassDto CanvassDto);
|
Task<List<BiddingItem>> GetSupplierBid(CanvassDto CanvassDto);
|
||||||
Task<List<RFQPerSupplier>> GetSupplierBidByItem(CanvassDto CanvassDto);
|
Task<List<RFQPerSupplier>> GetSupplierBidByItem(CanvassDto CanvassDto);
|
||||||
Task<List<SupplierBidById>> GetSupplierBidById(CanvassDto CanvassDto);
|
Task<List<SupplierBidById>> GetSupplierBidById(CanvassDto CanvassDto);
|
||||||
|
|||||||
@ -12,10 +12,12 @@ namespace CPRNIMS.Domain.Services.ICanvass
|
|||||||
{
|
{
|
||||||
public interface ISupplier
|
public interface ISupplier
|
||||||
{
|
{
|
||||||
Task<List<ItemWithoutSupplier>> GetItemWithoutSupplier();
|
Task<List<Suppliers>> GetSuppliers(CancellationToken ct);
|
||||||
Task<Result<SupplierResponse>> PostSupplierAsync(SupplierRequest request, CancellationToken ct);
|
Task<List<ForAISearchingTagging>> GetForAISearchingTagging(CancellationToken ct);
|
||||||
Task SendRFQ(SupplierEmailRequest supplierEmailRequest);
|
Task<List<ItemWithoutSupplier>> GetItemWithoutSupplier(CancellationToken ct);
|
||||||
Task<bool> SearchingUpdate(long pRDetailsId);
|
Task<List<SupplierForCanvass>> GetSupplierForCanvass(int supplierId, string userName,CancellationToken ct);
|
||||||
Task<List<ForAISearchingTagging>> GetForAISearchingTagging();
|
Task<Result<SupplierResponse>> PostPutSupplierAsync(SupplierRequest request, CancellationToken ct);
|
||||||
|
Task<bool> SendRFQ(SupplierEmailRequest supplierEmailRequest);
|
||||||
|
Task DeleteAsync(long pRDetailsId, CancellationToken ct);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using CPRNIMS.Infrastructure.Dto.PO;
|
using CPRNIMS.Infrastructure.Dto.Canvass.Response;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.PO;
|
||||||
using CPRNIMS.Infrastructure.Entities.Canvass;
|
using CPRNIMS.Infrastructure.Entities.Canvass;
|
||||||
using CPRNIMS.Infrastructure.Entities.Common;
|
using CPRNIMS.Infrastructure.Entities.Common;
|
||||||
using CPRNIMS.Infrastructure.Entities.LocalDb.NonInvent;
|
using CPRNIMS.Infrastructure.Entities.LocalDb.NonInvent;
|
||||||
@ -37,7 +38,7 @@ namespace CPRNIMS.Domain.Contracts.PO
|
|||||||
Task<List<SystemSettings>> GetLatestPO2(PODto pODto);
|
Task<List<SystemSettings>> GetLatestPO2(PODto pODto);
|
||||||
Task<List<DocRequired>> GetDocRequired(PODto pODto);
|
Task<List<DocRequired>> GetDocRequired(PODto pODto);
|
||||||
Task<List<OtherCharges>> GetOtherCharges(PODto itemDto);
|
Task<List<OtherCharges>> GetOtherCharges(PODto itemDto);
|
||||||
Task<List<Suppliers>> GetSuppliers(PODto itemDto);
|
Task<List<SupplierResponseDto>> GetSuppliers(PODto itemDto);
|
||||||
Task<List<CreatedPO>> GetCreatedPO(PODto pODto);
|
Task<List<CreatedPO>> GetCreatedPO(PODto pODto);
|
||||||
Task<List<POItemDetail>> GetPOItemDetail(PODto pODto);
|
Task<List<POItemDetail>> GetPOItemDetail(PODto pODto);
|
||||||
Task<List<CreatedPO>> GetMyCreatedPO(PODto pODto);
|
Task<List<CreatedPO>> GetMyCreatedPO(PODto pODto);
|
||||||
|
|||||||
@ -14,10 +14,11 @@ namespace CPRNIMS.Domain.Profile.Canvass
|
|||||||
public SupplierRequestProfile()
|
public SupplierRequestProfile()
|
||||||
{
|
{
|
||||||
CreateMap<SupplierRequest, Suppliers>();
|
CreateMap<SupplierRequest, Suppliers>();
|
||||||
|
|
||||||
CreateMap<Suppliers, SupplierResponse>();
|
CreateMap<Suppliers, SupplierResponse>();
|
||||||
|
|
||||||
CreateMap<SupplierResponse, SupplierRequest>().ReverseMap();
|
CreateMap<SupplierResponse, SupplierRequest>().ReverseMap();
|
||||||
|
CreateMap<StartCanvassRequest, ForAISearchingTagging>();
|
||||||
|
CreateMap<ForAISearchingTagging, StartCanvassResponse>().ReverseMap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
|
using CPRNIMS.Domain.Services.ICanvass;
|
||||||
using CPRNIMS.Infrastructure.Database;
|
using CPRNIMS.Infrastructure.Database;
|
||||||
using CPRNIMS.Infrastructure.Dto.Canvass;
|
using CPRNIMS.Infrastructure.Dto.Canvass;
|
||||||
using CPRNIMS.Infrastructure.Dto.Canvass.Request;
|
using CPRNIMS.Infrastructure.Dto.Canvass.Request;
|
||||||
@ -7,10 +8,10 @@ using CPRNIMS.Infrastructure.Dto.Common;
|
|||||||
using CPRNIMS.Infrastructure.Entities.Canvass;
|
using CPRNIMS.Infrastructure.Entities.Canvass;
|
||||||
using CPRNIMS.Infrastructure.Entities.Purchasing;
|
using CPRNIMS.Infrastructure.Entities.Purchasing;
|
||||||
using CPRNIMS.Infrastructure.Helper;
|
using CPRNIMS.Infrastructure.Helper;
|
||||||
|
using CPRNIMS.Infrastructure.ViewModel.Canvass;
|
||||||
using CPRNIMS.Infrastructure.ViewModel.Common;
|
using CPRNIMS.Infrastructure.ViewModel.Common;
|
||||||
using Microsoft.Data.SqlClient;
|
using Microsoft.Data.SqlClient;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -30,6 +31,14 @@ namespace CPRNIMS.Domain.Services.Canvass
|
|||||||
_mapper = mapper;
|
_mapper = mapper;
|
||||||
}
|
}
|
||||||
#region Get
|
#region Get
|
||||||
|
public async Task<List<Suppliers>> GetSuppliers(CancellationToken ct)
|
||||||
|
{
|
||||||
|
|
||||||
|
return await _dbContext.Suppliers
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(s => s.IsActive)
|
||||||
|
.ToListAsync(ct);
|
||||||
|
}
|
||||||
public async Task<List<ItemListWOEmail>> GetItemSupplierWOEmail(CanvassDto CanvassDto)
|
public async Task<List<ItemListWOEmail>> GetItemSupplierWOEmail(CanvassDto CanvassDto)
|
||||||
{
|
{
|
||||||
var allItems = await _dbContext.ItemListWOEmails
|
var allItems = await _dbContext.ItemListWOEmails
|
||||||
@ -97,6 +106,7 @@ namespace CPRNIMS.Domain.Services.Canvass
|
|||||||
ItemNo = Convert.ToInt64(reader["ItemNo"]),
|
ItemNo = Convert.ToInt64(reader["ItemNo"]),
|
||||||
ItemName = reader["ItemName"]?.ToString(),
|
ItemName = reader["ItemName"]?.ToString(),
|
||||||
ItemDescription = reader["ItemDescription"]?.ToString(),
|
ItemDescription = reader["ItemDescription"]?.ToString(),
|
||||||
|
Qty = Convert.ToDecimal(reader["Qty"]),
|
||||||
Department = reader["Department"]?.ToString(),
|
Department = reader["Department"]?.ToString(),
|
||||||
CreatedBy = reader["CreatedBy"]?.ToString(),
|
CreatedBy = reader["CreatedBy"]?.ToString(),
|
||||||
CreatedDate =Convert.ToDateTime(reader["CreatedDate"]),
|
CreatedDate =Convert.ToDateTime(reader["CreatedDate"]),
|
||||||
@ -253,7 +263,7 @@ namespace CPRNIMS.Domain.Services.Canvass
|
|||||||
|
|
||||||
return items ?? new List<SupplierResponseDto>();
|
return items ?? new List<SupplierResponseDto>();
|
||||||
}
|
}
|
||||||
public async Task<List<RFQReference>> GetRFQ(CanvassDto CanvassDto)
|
public async Task<List<RFQReference>> GetRFQ(ForCanvassDto CanvassDto)
|
||||||
{
|
{
|
||||||
var allItems = await _dbContext.RFQReferences
|
var allItems = await _dbContext.RFQReferences
|
||||||
.FromSqlRaw($"EXEC GetRFQPerSupplier @SupplierId,@UserId",
|
.FromSqlRaw($"EXEC GetRFQPerSupplier @SupplierId,@UserId",
|
||||||
@ -310,23 +320,34 @@ namespace CPRNIMS.Domain.Services.Canvass
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public async Task<List<ForAISearchingTagging>> GetForAISearchingTagging()
|
public async Task<List<ForAISearchingTagging>> GetForAISearchingTagging(CancellationToken ct)
|
||||||
{
|
{
|
||||||
var allItems = await _dbContext.ForAISearchingTaggings
|
var allItems = await _dbContext.ForAISearchingTaggings
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Take(10)
|
.Take(1)
|
||||||
.ToListAsync();
|
.ToListAsync(ct);
|
||||||
|
|
||||||
return allItems ?? new List<ForAISearchingTagging>();
|
return allItems ?? new List<ForAISearchingTagging>();
|
||||||
}
|
}
|
||||||
public async Task<List<ItemWithoutSupplier>> GetItemWithoutSupplier()
|
public async Task<List<ItemWithoutSupplier>> GetItemWithoutSupplier(CancellationToken ct)
|
||||||
{
|
{
|
||||||
var allItems = await _dbContext.ItemWithoutSuppliers
|
var allItems = await _dbContext.ItemWithoutSuppliers
|
||||||
.FromSqlRaw("EXEC GetItemWithoutSupplier")
|
.FromSqlRaw("EXEC GetItemWithoutSupplier")
|
||||||
.ToListAsync();
|
.ToListAsync(ct);
|
||||||
|
|
||||||
return allItems ?? new List<ItemWithoutSupplier>();
|
return allItems ?? new List<ItemWithoutSupplier>();
|
||||||
}
|
}
|
||||||
|
public async Task<List<SupplierForCanvass>> GetSupplierForCanvass(int supplierId,string userName,
|
||||||
|
CancellationToken ct)
|
||||||
|
{
|
||||||
|
var allItems = await _dbContext.SupplierForCanvass
|
||||||
|
.FromSqlRaw("EXEC GetSupplierForCanvass @UserName,@SupplierId",
|
||||||
|
new SqlParameter("@UserName", userName),
|
||||||
|
new SqlParameter("@SupplierId", supplierId)
|
||||||
|
).AsNoTracking().ToListAsync(ct);
|
||||||
|
|
||||||
|
return allItems ?? new List<SupplierForCanvass>();
|
||||||
|
}
|
||||||
public async Task<List<WOResponseById>> GetWOResponseBySuppId(CanvassDto CanvassDto)
|
public async Task<List<WOResponseById>> GetWOResponseBySuppId(CanvassDto CanvassDto)
|
||||||
{
|
{
|
||||||
var allItems = await _dbContext.WOResponseByIds
|
var allItems = await _dbContext.WOResponseByIds
|
||||||
@ -335,7 +356,7 @@ namespace CPRNIMS.Domain.Services.Canvass
|
|||||||
|
|
||||||
return allItems ?? new List<WOResponseById>();
|
return allItems ?? new List<WOResponseById>();
|
||||||
}
|
}
|
||||||
public async Task<RFQ> PostPerSupplierToken(CanvassDto CanvassDto)
|
public async Task<RFQ> PostPerSupplierToken(ForCanvassDto CanvassDto)
|
||||||
{
|
{
|
||||||
await _dbContext.Database
|
await _dbContext.Database
|
||||||
.ExecuteSqlRawAsync("EXEC PostPerSupplierToken @UserId,@SupplierId,@PRNo,@ItemNo,@CanvassNo,@PRDetailsId",
|
.ExecuteSqlRawAsync("EXEC PostPerSupplierToken @UserId,@SupplierId,@PRNo,@ItemNo,@CanvassNo,@PRDetailsId",
|
||||||
@ -438,7 +459,7 @@ namespace CPRNIMS.Domain.Services.Canvass
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
#region Post Put
|
#region Post Put
|
||||||
public async Task<Result<SupplierResponse>> PostSupplierAsync(SupplierRequest request, CancellationToken ct)
|
public async Task<Result<SupplierResponse>> PostPutSupplierAsync(SupplierRequest request, CancellationToken ct)
|
||||||
{
|
{
|
||||||
var strategy = _dbContext.Database.CreateExecutionStrategy();
|
var strategy = _dbContext.Database.CreateExecutionStrategy();
|
||||||
|
|
||||||
@ -449,7 +470,7 @@ namespace CPRNIMS.Domain.Services.Canvass
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var supplier = await _dbContext.Suppliers
|
var supplier = await _dbContext.Suppliers
|
||||||
.FirstOrDefaultAsync(s => s.SupplierName == request.SupplierName, ct);
|
.FirstOrDefaultAsync(s => s.SupplierId == request.SupplierId, ct);
|
||||||
|
|
||||||
if (supplier == null)
|
if (supplier == null)
|
||||||
{
|
{
|
||||||
@ -469,8 +490,6 @@ namespace CPRNIMS.Domain.Services.Canvass
|
|||||||
catch (DbUpdateException ex)
|
catch (DbUpdateException ex)
|
||||||
{
|
{
|
||||||
await transaction.RollbackAsync(ct);
|
await transaction.RollbackAsync(ct);
|
||||||
|
|
||||||
// handle unique constraint violation here if needed
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -512,16 +531,6 @@ namespace CPRNIMS.Domain.Services.Canvass
|
|||||||
new SqlParameter("@CanvassId", CanvassDto.CanvassId));
|
new SqlParameter("@CanvassId", CanvassDto.CanvassId));
|
||||||
return new CanvassDetail();
|
return new CanvassDetail();
|
||||||
}
|
}
|
||||||
public async Task<PRCanvassDetail> PostCanvass(CanvassDto CanvassDto)
|
|
||||||
{
|
|
||||||
await _dbContext.Database
|
|
||||||
.ExecuteSqlRawAsync("EXEC PostCanvass @UserId, @SupplierId, @Status,@Remarks",
|
|
||||||
new SqlParameter("@SupplierId", CanvassDto.SupplierId != null ? CanvassDto.SupplierId : 0L),
|
|
||||||
new SqlParameter("@UserId", CanvassDto.UserId),
|
|
||||||
new SqlParameter("@Status", CanvassDto.Status),
|
|
||||||
new SqlParameter("@Remarks", CanvassDto.Remarks ?? "N/A"));
|
|
||||||
return new PRCanvassDetail();
|
|
||||||
}
|
|
||||||
public async Task<SupplierResponse> PostTaggingSupplier(CanvassDto CanvassDto)
|
public async Task<SupplierResponse> PostTaggingSupplier(CanvassDto CanvassDto)
|
||||||
{
|
{
|
||||||
var (messCode, message) = CreateOutputParams();
|
var (messCode, message) = CreateOutputParams();
|
||||||
@ -674,8 +683,7 @@ namespace CPRNIMS.Domain.Services.Canvass
|
|||||||
new SqlParameter("@CanvassSupplierId", canvassDto.CanvassSupplierId));
|
new SqlParameter("@CanvassSupplierId", canvassDto.CanvassSupplierId));
|
||||||
return new CanvassSupplier();
|
return new CanvassSupplier();
|
||||||
}
|
}
|
||||||
|
public async Task<bool> SendRFQ(SupplierEmailRequest supplierEmailRequest)
|
||||||
public async Task SendRFQ(SupplierEmailRequest supplierEmailRequest)
|
|
||||||
{
|
{
|
||||||
var baseTemplate = EMailTemplate("Content\\SMTPEmailContent", "SendToSupplier.cshtml");
|
var baseTemplate = EMailTemplate("Content\\SMTPEmailContent", "SendToSupplier.cshtml");
|
||||||
|
|
||||||
@ -702,7 +710,9 @@ namespace CPRNIMS.Domain.Services.Canvass
|
|||||||
AttachPath=supplierEmailRequest.AttachPath
|
AttachPath=supplierEmailRequest.AttachPath
|
||||||
};
|
};
|
||||||
|
|
||||||
await _smptHelper.SendEmailAsync(messageDetails);
|
if (await _smptHelper.SendEmailAsync(messageDetails))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
public string EMailTemplate(string relativePath, string emailTemplate)
|
public string EMailTemplate(string relativePath, string emailTemplate)
|
||||||
{
|
{
|
||||||
@ -720,14 +730,61 @@ namespace CPRNIMS.Domain.Services.Canvass
|
|||||||
return "Template file not found";
|
return "Template file not found";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public async Task DeleteAsync(long pRDetailsId, CancellationToken ct)
|
||||||
public async Task<bool> SearchingUpdate(long pRDetailsId)
|
|
||||||
{
|
{
|
||||||
var rowsAffected = await _dbContext.PRDetails
|
await _dbContext.ForAISearchingTaggings
|
||||||
.Where(p => p.PRDetailsId == pRDetailsId)
|
.Where(p => p.PRDetailsId == pRDetailsId)
|
||||||
.ExecuteUpdateAsync(s => s.SetProperty(p => p.IsSearched, true));
|
.ExecuteDeleteAsync(ct);
|
||||||
|
}
|
||||||
|
|
||||||
return rowsAffected > 0;
|
public async Task<Result<StartCanvassResponse>> StartCanvass(CanvassVM request, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var detailIds = request.ForSupplierSearchList?.PRDetailsId;
|
||||||
|
if (detailIds == null || !detailIds.Any())
|
||||||
|
return Result<StartCanvassResponse>.Failure("No items provided.");
|
||||||
|
|
||||||
|
// 1. Get IDs that already exist in one call to avoid the loop-check
|
||||||
|
var existingIds = await _dbContext.ForAISearchingTaggings
|
||||||
|
.Where(f => detailIds.Contains(f.PRDetailsId))
|
||||||
|
.Select(f => f.PRDetailsId)
|
||||||
|
.ToListAsync(ct);
|
||||||
|
|
||||||
|
var newTags = new List<ForAISearchingTagging>();
|
||||||
|
var idsToUpdate = new List<long>();
|
||||||
|
|
||||||
|
for (int i = 0; i < detailIds.Count; i++)
|
||||||
|
{
|
||||||
|
var currentId = detailIds[i];
|
||||||
|
if (existingIds.Contains(currentId)) continue;
|
||||||
|
|
||||||
|
newTags.Add(new ForAISearchingTagging
|
||||||
|
{
|
||||||
|
PRDetailsId = currentId,
|
||||||
|
PRNo = request.ForSupplierSearchList.PRNo[i],
|
||||||
|
ItemNo = request.ForSupplierSearchList.ItemNo[i],
|
||||||
|
ItemName = request.ForSupplierSearchList.ItemName[i],
|
||||||
|
ItemDescription = request.ForSupplierSearchList.ItemDescription[i],
|
||||||
|
IsInternational = request.IsInternational,
|
||||||
|
FullName = request.FullName,
|
||||||
|
UserId = request.UserId
|
||||||
|
});
|
||||||
|
|
||||||
|
idsToUpdate.Add(currentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newTags.Any())
|
||||||
|
{
|
||||||
|
// 2. Bulk Add
|
||||||
|
await _dbContext.ForAISearchingTaggings.AddRangeAsync(newTags, ct);
|
||||||
|
await _dbContext.SaveChangesAsync(ct);
|
||||||
|
|
||||||
|
// 3. Bulk Update the flags AFTER saving the tags
|
||||||
|
await _dbContext.PRDetails
|
||||||
|
.Where(p => idsToUpdate.Contains(p.PRDetailsId))
|
||||||
|
.ExecuteUpdateAsync(s => s.SetProperty(p => p.IsSearched, true), ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result<StartCanvassResponse>.Success(new StartCanvassResponse { });
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,9 +3,11 @@ using CPRNIMS.Infrastructure.Dto.Canvass.Result;
|
|||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@ -19,7 +21,125 @@ namespace CPRNIMS.Domain.Services.Canvass
|
|||||||
// Common contact page suffixes to try
|
// Common contact page suffixes to try
|
||||||
private static readonly string[] ContactPaths =
|
private static readonly string[] ContactPaths =
|
||||||
{ "/contact", "/contact-us", "/pages/contact-us", "/about/contact", "/about" };
|
{ "/contact", "/contact-us", "/pages/contact-us", "/about/contact", "/about" };
|
||||||
|
/// <summary>
|
||||||
|
/// Uses Groq to fuzzy-match a new supplier against existing ones.
|
||||||
|
/// Handles rebranding, spacing in phone numbers, name variations, etc.
|
||||||
|
/// Returns the matched existing SupplierId, or null if no match.
|
||||||
|
/// </summary>
|
||||||
|
public async Task<int?> FindMatchingExistingSupplierAsync(
|
||||||
|
SupplierResponse incoming,
|
||||||
|
List<SupplierResponse> existingSuppliers)
|
||||||
|
{
|
||||||
|
if (!existingSuppliers.Any()) return null;
|
||||||
|
|
||||||
|
// ── Layer 1: Exact C# match (fast, free, no API call) ──────────────
|
||||||
|
var incomingEmail = (incoming.EmailAddress ?? "").Trim().ToLower();
|
||||||
|
var incomingPhone = NormalizePhone(incoming.ContactNo ?? "");
|
||||||
|
var incomingDomain = ExtractDomain(incoming.Website ?? "");
|
||||||
|
|
||||||
|
foreach (var s in existingSuppliers)
|
||||||
|
{
|
||||||
|
var existEmail = (s.EmailAddress ?? "").Trim().ToLower();
|
||||||
|
var existPhone = NormalizePhone(s.ContactNo ?? "");
|
||||||
|
var existDomain = ExtractDomain(s.Website ?? "");
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(incomingEmail) && incomingEmail == existEmail)
|
||||||
|
return s.SupplierId;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(incomingPhone) && incomingPhone == existPhone)
|
||||||
|
return s.SupplierId;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(incomingDomain) && incomingDomain == existDomain)
|
||||||
|
return s.SupplierId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Layer 2: Fuzzy C# pre-filter — narrow to top candidates ────────
|
||||||
|
var incomingName = (incoming.SupplierName ?? "").ToLower();
|
||||||
|
|
||||||
|
var candidates = existingSuppliers
|
||||||
|
.Where(s =>
|
||||||
|
{
|
||||||
|
var name = (s.SupplierName ?? "").ToLower();
|
||||||
|
|
||||||
|
// Keep if first word matches (e.g. "Linde" in "Linde PH" vs "Linde Philippines")
|
||||||
|
var incomingFirstWord = incomingName.Split(' ').FirstOrDefault() ?? "";
|
||||||
|
var existFirstWord = name.Split(' ').FirstOrDefault() ?? "";
|
||||||
|
|
||||||
|
return !string.IsNullOrEmpty(incomingFirstWord)
|
||||||
|
&& incomingFirstWord.Length > 2 // ignore short words like "co", "ph"
|
||||||
|
&& existFirstWord.StartsWith(incomingFirstWord, StringComparison.OrdinalIgnoreCase);
|
||||||
|
})
|
||||||
|
.Take(5) // max 5 candidates to Groq — well within token limit
|
||||||
|
.Select(s => new
|
||||||
|
{
|
||||||
|
s.SupplierId,
|
||||||
|
s.SupplierName,
|
||||||
|
s.EmailAddress,
|
||||||
|
s.ContactNo,
|
||||||
|
s.Website
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// No fuzzy candidates found — it's a new supplier
|
||||||
|
if (!candidates.Any()) return null;
|
||||||
|
|
||||||
|
// ── Layer 3: Groq fuzzy match — only on small candidate list ────────
|
||||||
|
var incomingJson = JsonSerializer.Serialize(new
|
||||||
|
{
|
||||||
|
incoming.SupplierName,
|
||||||
|
incoming.EmailAddress,
|
||||||
|
incoming.ContactNo,
|
||||||
|
incoming.Website
|
||||||
|
});
|
||||||
|
|
||||||
|
var candidatesJson = JsonSerializer.Serialize(candidates);
|
||||||
|
|
||||||
|
var prompt =
|
||||||
|
"TASK: Determine if the INCOMING supplier already exists in the CANDIDATES list.\n\n" +
|
||||||
|
"MATCHING RULES (any one is enough):\n" +
|
||||||
|
"1. Same email address (case-insensitive).\n" +
|
||||||
|
"2. Same phone number after stripping spaces, dashes, country codes.\n" +
|
||||||
|
"3. Same company despite rebranding, abbreviation, or spacing differences.\n" +
|
||||||
|
"4. Same website domain (ignore www, http/https).\n\n" +
|
||||||
|
"If matched: respond ONLY { \"matched\": true, \"supplierId\": <number> }\n" +
|
||||||
|
"If not matched: respond ONLY { \"matched\": false, \"supplierId\": null }\n" +
|
||||||
|
"No explanation. No markdown. JSON only.\n\n" +
|
||||||
|
$"INCOMING:\n{incomingJson}\n\n" +
|
||||||
|
$"CANDIDATES:\n{candidatesJson}";
|
||||||
|
|
||||||
|
var payload = new
|
||||||
|
{
|
||||||
|
model = _config["Groq:Model"] ?? "llama-3.1-8b-instant",
|
||||||
|
stream = false,
|
||||||
|
max_tokens = 50,
|
||||||
|
temperature = 0,
|
||||||
|
messages = new[]
|
||||||
|
{
|
||||||
|
new { role = "system", content = "You are a supplier deduplication engine. Return ONLY valid JSON. No markdown. No explanation." },
|
||||||
|
new { role = "user", content = prompt }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post, _config["Groq:ApiUrl"]);
|
||||||
|
request.Headers.Add("Authorization", $"Bearer {_config["Groq:ApiKey"]}");
|
||||||
|
request.Content = new StringContent(
|
||||||
|
JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
|
var response = await _httpClient.SendAsync(request);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var groqResp = JsonSerializer.Deserialize<GroqResponse>(body,
|
||||||
|
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
||||||
|
|
||||||
|
var rawText = groqResp?.Choices?[0]?.Message?.Content ?? string.Empty;
|
||||||
|
rawText = Regex.Replace(rawText, @"```[a-z]*|```", "").Trim();
|
||||||
|
|
||||||
|
var match = JsonSerializer.Deserialize<GroqMatchResult>(rawText,
|
||||||
|
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
||||||
|
|
||||||
|
return match?.Matched == true ? match.SupplierId : null;
|
||||||
|
}
|
||||||
public SupplierSearchService(HttpClient httpClient, IConfiguration config)
|
public SupplierSearchService(HttpClient httpClient, IConfiguration config)
|
||||||
{
|
{
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
@ -29,9 +149,9 @@ namespace CPRNIMS.Domain.Services.Canvass
|
|||||||
public async Task<List<SupplierResponse>> SearchAndFilterSuppliersAsync(
|
public async Task<List<SupplierResponse>> SearchAndFilterSuppliersAsync(
|
||||||
string itemName, string itemDescription, bool isInternational)
|
string itemName, string itemDescription, bool isInternational)
|
||||||
{
|
{
|
||||||
string locality = "Philippines";
|
var locality = isInternational
|
||||||
if (isInternational) { locality = "all over the asia including Philippines"; }
|
? "all over Asia including Philippines"
|
||||||
else { locality = "Philippines"; }
|
: "Philippines";
|
||||||
|
|
||||||
// Step 1: Tavily — get supplier URLs
|
// Step 1: Tavily — get supplier URLs
|
||||||
var (searchContent, supplierUrls) = await SearchTavilyAsync(itemName, itemDescription, locality);
|
var (searchContent, supplierUrls) = await SearchTavilyAsync(itemName, itemDescription, locality);
|
||||||
@ -41,7 +161,7 @@ namespace CPRNIMS.Domain.Services.Canvass
|
|||||||
|
|
||||||
// Step 3: Combine search + contact content, send to Groq
|
// Step 3: Combine search + contact content, send to Groq
|
||||||
var combined = searchContent + " CONTACT_PAGES_DATA: " + contactContent;
|
var combined = searchContent + " CONTACT_PAGES_DATA: " + contactContent;
|
||||||
var suppliers = await FilterWithGroqAsync(itemName, itemDescription, combined);
|
var suppliers = await FilterWithGroqAsync(itemName, itemDescription, combined,isInternational);
|
||||||
|
|
||||||
return suppliers;
|
return suppliers;
|
||||||
}
|
}
|
||||||
@ -49,6 +169,8 @@ namespace CPRNIMS.Domain.Services.Canvass
|
|||||||
// ── Tavily ──
|
// ── Tavily ──
|
||||||
private async Task<(string content, List<string> urls)> SearchTavilyAsync(
|
private async Task<(string content, List<string> urls)> SearchTavilyAsync(
|
||||||
string itemName, string itemDescription,string locality)
|
string itemName, string itemDescription,string locality)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var query = $"{itemName} {itemDescription} suppliers {locality} budget price contact email phone";
|
var query = $"{itemName} {itemDescription} suppliers {locality} budget price contact email phone";
|
||||||
|
|
||||||
@ -100,6 +222,13 @@ namespace CPRNIMS.Domain.Services.Canvass
|
|||||||
|
|
||||||
return (fullText, urls);
|
return (fullText, urls);
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ex.ToString();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// ── Fetch Contact Pages ───
|
// ── Fetch Contact Pages ───
|
||||||
private async Task<string> FetchContactPagesAsync(List<string> baseUrls)
|
private async Task<string> FetchContactPagesAsync(List<string> baseUrls)
|
||||||
@ -152,16 +281,42 @@ namespace CPRNIMS.Domain.Services.Canvass
|
|||||||
|
|
||||||
// ── Groq ─────────────────────────────────────────────────────────────────
|
// ── Groq ─────────────────────────────────────────────────────────────────
|
||||||
private async Task<List<SupplierResponse>> FilterWithGroqAsync(
|
private async Task<List<SupplierResponse>> FilterWithGroqAsync(
|
||||||
string itemName, string itemDescription, string searchContent)
|
string itemName, string itemDescription, string searchContent, bool isInternational)
|
||||||
{
|
{
|
||||||
var prompt = $"Extract top 10 unique suppliers for: {itemName} {itemDescription}. " +
|
try
|
||||||
"Prioritize Philippines suppliers first. " +
|
{
|
||||||
"IMPORTANT: Look carefully in CONTACT_PAGES_DATA section for real phone numbers and emails. " +
|
var localityRule = isInternational
|
||||||
"Extract exact email addresses and phone numbers found. " +
|
? "1. Include suppliers from Philippines first, then other Asian countries (e.g. China, Japan, South Korea, Taiwan, India, Singapore).\n"
|
||||||
"For domains without contact data found, infer email as sales@domain or info@domain. " +
|
: "1. STRICT: Include ONLY suppliers based in the Philippines. Exclude ANY supplier from other countries — even if they ship to Philippines. If a supplier's country is not Philippines, skip it entirely.\n";
|
||||||
"Prefer budget-friendly suppliers. No duplicates. " +
|
|
||||||
"Return ONLY a raw JSON array: company_name, country, phone_number, contact_email, website, estimated_price_usd, item_specifications. " +
|
var prompt =
|
||||||
$"Null for missing. JSON array only. Data: {searchContent}";
|
$"TASK: Extract up to 10 unique suppliers that sell: [{itemName}] — {itemDescription}.\n\n" +
|
||||||
|
"RULES:\n" +
|
||||||
|
localityRule +
|
||||||
|
"2. Prefer budget-friendly suppliers with known pricing.\n" +
|
||||||
|
"3. DEDUPLICATION (strict): Each entry must have a unique company_name, contact_email, AND phone_number.\n" +
|
||||||
|
" - If two entries share the same email OR phone number, keep only the first.\n" +
|
||||||
|
" - If two inferred emails resolve to the same address, keep only one.\n" +
|
||||||
|
"4. CONTACT EXTRACTION:\n" +
|
||||||
|
" - Look in the CONTACT_PAGES_DATA section for real emails and phone numbers.\n" +
|
||||||
|
" - Use exact values found. Do not fabricate contact details.\n" +
|
||||||
|
" - If no email is found for a domain, infer: sales@domain.com or info@domain.com.\n" +
|
||||||
|
" - If no phone is found, use null — do not guess.\n" +
|
||||||
|
"5. estimated_price_usd MUST be a number (e.g. 12.50) or null. NEVER a string.\n" +
|
||||||
|
"6. Exclude any supplier with no company_name or no contact_email.\n\n" +
|
||||||
|
|
||||||
|
"OUTPUT FORMAT:\n" +
|
||||||
|
"Return ONLY a valid raw JSON array. No markdown. No explanation. No extra text.\n" +
|
||||||
|
"Each object must have exactly these fields:\n" +
|
||||||
|
" company_name (string)\n" +
|
||||||
|
" country (string)\n" +
|
||||||
|
" phone_number (string | null)\n" +
|
||||||
|
" contact_email (string | null)\n" +
|
||||||
|
" website (string | null)\n" +
|
||||||
|
" estimated_price_usd (number | null)\n" +
|
||||||
|
" item_specifications (string | null)\n\n" +
|
||||||
|
|
||||||
|
$"DATA:\n{searchContent}";
|
||||||
|
|
||||||
var payload = new
|
var payload = new
|
||||||
{
|
{
|
||||||
@ -193,20 +348,52 @@ namespace CPRNIMS.Domain.Services.Canvass
|
|||||||
var match = Regex.Match(rawText, @"\[[\s\S]*\]");
|
var match = Regex.Match(rawText, @"\[[\s\S]*\]");
|
||||||
if (!match.Success) return new List<SupplierResponse>();
|
if (!match.Success) return new List<SupplierResponse>();
|
||||||
|
|
||||||
var groqList = JsonSerializer.Deserialize<List<GroqSupplierResult>>(match.Value,
|
// Add the converter to the shared options
|
||||||
new JsonSerializerOptions { PropertyNameCaseInsensitive = true })
|
var jsonOptions = new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNameCaseInsensitive = true,
|
||||||
|
Converters = { new FlexibleDecimalConverter() }
|
||||||
|
};
|
||||||
|
|
||||||
|
var groqList = JsonSerializer.Deserialize<List<GroqSupplierResult>>(match.Value, jsonOptions)
|
||||||
?? new List<GroqSupplierResult>();
|
?? new List<GroqSupplierResult>();
|
||||||
|
|
||||||
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
var seenNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var seenEmails = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var seenPhones = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
var suppliers = new List<SupplierResponse>();
|
var suppliers = new List<SupplierResponse>();
|
||||||
|
var allowedCountries = isInternational
|
||||||
|
? new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
"Philippines", "China", "Japan", "South Korea", "Taiwan",
|
||||||
|
"India", "Singapore", "Malaysia", "Thailand", "Vietnam",
|
||||||
|
"Indonesia", "Hong Kong"
|
||||||
|
}
|
||||||
|
: new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
"Philippines"
|
||||||
|
};
|
||||||
|
|
||||||
foreach (var s in groqList)
|
foreach (var s in groqList)
|
||||||
{
|
{
|
||||||
var key = (s.CompanyName ?? "").Trim().ToLower();
|
var key = (s.CompanyName ?? "").Trim().ToLower();
|
||||||
if (string.IsNullOrEmpty(key) || seen.Contains(key)) continue;
|
var email = (s.ContactEmail ?? "").Trim().ToLower();
|
||||||
seen.Add(key);
|
var phone = NormalizePhone(s.PhoneNumber ?? "");
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(s.ContactEmail)) continue;
|
// Skip if no company name
|
||||||
|
if (string.IsNullOrEmpty(key)) continue;
|
||||||
|
|
||||||
|
// Skip if no email
|
||||||
|
if (string.IsNullOrEmpty(email)) continue;
|
||||||
|
|
||||||
|
// ✅ Skip if company name, email, OR phone already seen
|
||||||
|
if (seenNames.Contains(key)) continue;
|
||||||
|
if (seenEmails.Contains(email)) continue;
|
||||||
|
if (!string.IsNullOrEmpty(phone) && seenPhones.Contains(phone)) continue;
|
||||||
|
|
||||||
|
seenNames.Add(key);
|
||||||
|
seenEmails.Add(email);
|
||||||
|
if (!string.IsNullOrEmpty(phone)) seenPhones.Add(phone);
|
||||||
|
|
||||||
suppliers.Add(new SupplierResponse
|
suppliers.Add(new SupplierResponse
|
||||||
{
|
{
|
||||||
@ -231,5 +418,36 @@ namespace CPRNIMS.Domain.Services.Canvass
|
|||||||
|
|
||||||
return suppliers;
|
return suppliers;
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ex.ToString();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
private static string NormalizePhone(string phone)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(phone)) return string.Empty;
|
||||||
|
|
||||||
|
// Strip everything except digits
|
||||||
|
var digits = Regex.Replace(phone, @"\D", "");
|
||||||
|
|
||||||
|
// Remove leading country code "1" for US/CA numbers (11 digits starting with 1)
|
||||||
|
if (digits.Length == 11 && digits.StartsWith("1"))
|
||||||
|
digits = digits[1..];
|
||||||
|
|
||||||
|
return digits;
|
||||||
|
}
|
||||||
|
private static string ExtractDomain(string url)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(url)) return string.Empty;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!url.StartsWith("http")) url = "https://" + url;
|
||||||
|
var host = new Uri(url).Host;
|
||||||
|
return host.StartsWith("www.") ? host[4..] : host;
|
||||||
|
}
|
||||||
|
catch { return string.Empty; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using CPRNIMS.Domain.Contracts.PO;
|
using CPRNIMS.Domain.Contracts.PO;
|
||||||
using CPRNIMS.Infrastructure.Database;
|
using CPRNIMS.Infrastructure.Database;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Canvass.Response;
|
||||||
using CPRNIMS.Infrastructure.Dto.PO;
|
using CPRNIMS.Infrastructure.Dto.PO;
|
||||||
using CPRNIMS.Infrastructure.Dto.PR;
|
using CPRNIMS.Infrastructure.Dto.PR;
|
||||||
using CPRNIMS.Infrastructure.Entities.Canvass;
|
using CPRNIMS.Infrastructure.Entities.Canvass;
|
||||||
@ -73,15 +74,15 @@ namespace CPRNIMS.Domain.Services.PO
|
|||||||
var charges = await _dbContext.OtherCharges.ToListAsync();
|
var charges = await _dbContext.OtherCharges.ToListAsync();
|
||||||
return charges;
|
return charges;
|
||||||
}
|
}
|
||||||
public async Task<List<Suppliers>> GetSuppliers(PODto itemDto)
|
public async Task<List<SupplierResponseDto>> GetSuppliers(PODto itemDto)
|
||||||
{
|
{
|
||||||
var allItems = await _dbContext.Suppliers
|
var allItems = await _dbContext.SupplierResponses
|
||||||
.FromSqlRaw($"EXEC GetSuppliers @UserId,@SupplierName",
|
.FromSqlRaw($"EXEC GetSuppliers @UserId,@SupplierName",
|
||||||
new SqlParameter("@UserId", itemDto.UserId),
|
new SqlParameter("@UserId", itemDto.UserId),
|
||||||
new SqlParameter("@SupplierName", itemDto.SupplierName))
|
new SqlParameter("@SupplierName", itemDto.SupplierName))
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
return allItems ?? new List<Suppliers>();
|
return allItems ?? new List<SupplierResponseDto>();
|
||||||
}
|
}
|
||||||
public async Task<List<PRWOCanvass>> GetPRWOCanvass(PODto itemDto)
|
public async Task<List<PRWOCanvass>> GetPRWOCanvass(PODto itemDto)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -39,6 +39,7 @@ namespace CPRNIMS.Domain.UIContracts.Canvass
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Post Put
|
#region Post Put
|
||||||
|
Task<CanvassVM> PostSupplierForCanvass(User user, CanvassVM viewModel);
|
||||||
Task<CanvassVM> PostCanvass(User user, CanvassVM viewModel);
|
Task<CanvassVM> PostCanvass(User user, CanvassVM viewModel);
|
||||||
Task<CanvassVM> PostPutSupplier(User user, CanvassVM viewModel);
|
Task<CanvassVM> PostPutSupplier(User user, CanvassVM viewModel);
|
||||||
Task<CanvassVM> PostTaggingSupplier(User user, CanvassVM viewModel);
|
Task<CanvassVM> PostTaggingSupplier(User user, CanvassVM viewModel);
|
||||||
@ -49,6 +50,7 @@ namespace CPRNIMS.Domain.UIContracts.Canvass
|
|||||||
Task<CanvassVM> PostPutMySupplier(User user, CanvassVM viewModel);
|
Task<CanvassVM> PostPutMySupplier(User user, CanvassVM viewModel);
|
||||||
Task<CanvassVM> PostPutItemTagging(User user, CanvassVM viewModel);
|
Task<CanvassVM> PostPutItemTagging(User user, CanvassVM viewModel);
|
||||||
Task<CanvassVM> UnlockFormLink(User user, CanvassVM viewModel);
|
Task<CanvassVM> UnlockFormLink(User user, CanvassVM viewModel);
|
||||||
|
Task<CanvassVM> StartCanvass(User user, CanvassVM viewModel);
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -343,6 +343,18 @@ namespace CPRNIMS.Domain.UIServices.Canvass
|
|||||||
_configuration["LLI:NonInvent:CanvassMgmt:UnlockFormLink"]);
|
_configuration["LLI:NonInvent:CanvassMgmt:UnlockFormLink"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<CanvassVM> StartCanvass(User user, CanvassVM viewModel)
|
||||||
|
{
|
||||||
|
return await SendPostApiRequest(user, viewModel,
|
||||||
|
_configuration["LLI:NonInvent:CanvassMgmt:StartCanvass"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CanvassVM> PostSupplierForCanvass(User user, CanvassVM viewModel)
|
||||||
|
{
|
||||||
|
return await SendPostApiRequest(user, viewModel,
|
||||||
|
_configuration["LLI:NonInvent:CanvassMgmt:PostSupplierForCanvass"]);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -66,6 +66,7 @@ namespace CPRNIMS.Infrastructure.Database
|
|||||||
public virtual DbSet<ForRR> ForRRs { get; set; }
|
public virtual DbSet<ForRR> ForRRs { get; set; }
|
||||||
public virtual DbSet<RR> RRs { 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<SupplierResponseDto> SupplierResponses { get; set; }
|
public DbSet<SupplierResponseDto> SupplierResponses { get; set; }
|
||||||
public DbSet<SupplierItems> SupplierItems { get; set; }
|
public DbSet<SupplierItems> SupplierItems { get; set; }
|
||||||
public DbSet<ItemsForTagging> ItemsForTaggings { get; set; }
|
public DbSet<ItemsForTagging> ItemsForTaggings { get; set; }
|
||||||
|
|||||||
@ -19,5 +19,6 @@ namespace CPRNIMS.Infrastructure.Entities.Canvass
|
|||||||
public DateTime DateNeeded { get; set; }
|
public DateTime DateNeeded { get; set; }
|
||||||
public DateTime CreatedDate { get; set; }
|
public DateTime CreatedDate { get; set; }
|
||||||
public string? CreatedBy { get; set; }
|
public string? CreatedBy { get; set; }
|
||||||
|
public decimal Qty { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Infrastructure.Entities.Canvass
|
||||||
|
{
|
||||||
|
public class SupplierForCanvass
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public long PRDetailsId { get; set; }
|
||||||
|
public int SupplierId { get; set; }
|
||||||
|
public string? EmailAddress { get; set; }
|
||||||
|
public long PRNo { get; set; }
|
||||||
|
public long ItemNo { get; set; }
|
||||||
|
public string? ItemName { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -14,16 +14,16 @@ namespace CPRNIMS.Infrastructure.Entities.Canvass
|
|||||||
[Key]
|
[Key]
|
||||||
public int SupplierId { get; set; }
|
public int SupplierId { get; set; }
|
||||||
public string SupplierName { get; set; } = string.Empty;
|
public string SupplierName { get; set; } = string.Empty;
|
||||||
public string EmailAddress { get; set; } = string.Empty;
|
public string? EmailAddress { get; set; }
|
||||||
public bool IsActive { get; set; } = true;
|
public bool IsActive { get; set; }
|
||||||
public string ContactNo { get; set; } = string.Empty;
|
public string? ContactNo { get; set; }
|
||||||
public string ContactPerson { get; set; } = string.Empty;
|
public string? ContactPerson { get; set; }
|
||||||
public string LeadTime { get; set; } = string.Empty;
|
public string? LeadTime { get; set; }
|
||||||
public bool IsVatable { get; set; }=false;
|
public bool? IsVatable { get; set; }
|
||||||
public byte PaymentTermsId { get; set; } = 1;
|
public byte? PaymentTermsId { get; set; }
|
||||||
public byte CurrencyId { get; set; } = 1;
|
public byte? CurrencyId { get; set; }
|
||||||
public string TinNo { get; set; } = string.Empty;
|
public string? TinNo { get; set; }
|
||||||
public string Address { get; set; } = string.Empty;
|
public string? Address { get; set; }
|
||||||
public string Website { get; set; } = string.Empty;
|
public string? Website { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -85,6 +85,7 @@ namespace CPRNIMS.Infrastructure.ViewModel.Canvass
|
|||||||
public SupplierList? SupplierList { get; set; }
|
public SupplierList? SupplierList { get; set; }
|
||||||
public CanvassList? CanvassList { get; set; }
|
public CanvassList? CanvassList { get; set; }
|
||||||
public ItemList? ItemList { get; set; }
|
public ItemList? ItemList { get; set; }
|
||||||
|
public ForSupplierSearchList? ForSupplierSearchList { get; set; }
|
||||||
public string? ItemName { get; set; }
|
public string? ItemName { get; set; }
|
||||||
public string? URL { get; set; }
|
public string? URL { get; set; }
|
||||||
public string? Token { get; set; }
|
public string? Token { get; set; }
|
||||||
@ -113,5 +114,6 @@ namespace CPRNIMS.Infrastructure.ViewModel.Canvass
|
|||||||
public bool IsApproved { get; set; }
|
public bool IsApproved { get; set; }
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
public long AlternativeId { get; set; }
|
public long AlternativeId { get; set; }
|
||||||
|
public bool IsInternational { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CPRNIMS.Infrastructure.ViewModel.Canvass
|
||||||
|
{
|
||||||
|
public class ForSupplierSearchList
|
||||||
|
{
|
||||||
|
public List<long>? PRDetailsId { get; set; }
|
||||||
|
public List<long>? PRNo { get; set; }
|
||||||
|
public List<long>? ItemNo { get; set; }
|
||||||
|
public List<string>? ItemName { get; set; }
|
||||||
|
public List<string>? ItemDescription { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,13 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace CPRNIMS.Infrastructure.ViewModel.Canvass
|
|
||||||
{
|
|
||||||
public class XCheckItemVM
|
|
||||||
{
|
|
||||||
public List<long>? ItemNo { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -5,9 +5,11 @@ using CPRNIMS.Domain.Services.Canvass;
|
|||||||
using CPRNIMS.Infrastructure.Dto.Canvass;
|
using CPRNIMS.Infrastructure.Dto.Canvass;
|
||||||
using CPRNIMS.Infrastructure.Dto.Canvass.Request;
|
using CPRNIMS.Infrastructure.Dto.Canvass.Request;
|
||||||
using CPRNIMS.Infrastructure.Dto.Canvass.Response;
|
using CPRNIMS.Infrastructure.Dto.Canvass.Response;
|
||||||
|
using CPRNIMS.Infrastructure.Entities.Canvass;
|
||||||
using CPRNIMS.Infrastructure.Helper;
|
using CPRNIMS.Infrastructure.Helper;
|
||||||
using CPRNIMS.Infrastructure.ViewModel.Canvass;
|
using CPRNIMS.Infrastructure.ViewModel.Canvass;
|
||||||
using CPRNIMS.Infrastructure.ViewModel.Common;
|
using CPRNIMS.Infrastructure.ViewModel.Common;
|
||||||
|
using CPRNIMS.WebApi.Security;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
@ -214,31 +216,92 @@ namespace CPRNIMS.WebApi.Controllers.Canvass
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Post Put
|
#region Post Put
|
||||||
|
[HttpPost("PostSupplierForCanvass")]
|
||||||
|
public async Task<IActionResult> PostSupplierForCanvass(CanvassVM canvass, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var currentUser = User.ToUserClaims();
|
||||||
|
if (currentUser == null)
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
var results = await _canvass.GetSupplierForCanvass(canvass.SupplierId, currentUser.UserName, ct);
|
||||||
|
|
||||||
|
var baseTemplate = EMailTemplate("Content\\SMTPEmailContent", "SendToSupplier.cshtml");
|
||||||
|
|
||||||
|
var CanvassDto = new ForCanvassDto();
|
||||||
|
int canvassNo = await _canvass.GetCanvassNo();
|
||||||
|
|
||||||
|
foreach (var item in results)
|
||||||
|
{
|
||||||
|
CanvassDto = new ForCanvassDto
|
||||||
|
{
|
||||||
|
PRDetailsId = item.PRDetailsId,
|
||||||
|
PRNo = item.PRNo,
|
||||||
|
ItemNo = item.ItemNo,
|
||||||
|
SupplierId = item.SupplierId,
|
||||||
|
UserId = currentUser.UserId,
|
||||||
|
FullName = currentUser.FullName,
|
||||||
|
CanvassNo = canvassNo + 1,
|
||||||
|
};
|
||||||
|
await _canvass.PostPerSupplierToken(CanvassDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
var rfq = await _canvass.GetRFQ(CanvassDto);
|
||||||
|
|
||||||
|
var message = new StringBuilder(baseTemplate);
|
||||||
|
message.Replace("@ViewBag.FormLink", Convert.ToString(_configuration["WebEndPoint:SupplierForm"] + rfq[0].Token));
|
||||||
|
message.Replace("@ViewBag.Supplier", rfq[0].SupplierName);
|
||||||
|
message.Replace("@ViewBag.Signature", currentUser.FullName);
|
||||||
|
|
||||||
|
var messageDetails = new EmailMessageDetailsVM();
|
||||||
|
messageDetails.AttachPath = GetRelativePath(@"Content\Documents\Pdf\Offer_Submission_Procedure.pdf");
|
||||||
|
|
||||||
|
messageDetails.Recipient = rfq[0].EmailAddress;
|
||||||
|
messageDetails.Message = message.ToString();
|
||||||
|
messageDetails.Subject = "CLMS - Request For Quotation #PRNo: " + rfq[0].AggrePRNo;
|
||||||
|
messageDetails.CC = Convert.ToString(_configuration["Canvass:CC"]);
|
||||||
|
messageDetails.SenderEmail = _config["SMTP:SenderEmail"];
|
||||||
|
messageDetails.DisplayName = "lloydlabinc.com";
|
||||||
|
messageDetails.NewPassword = _config["SMTP:Password"];
|
||||||
|
messageDetails.OutGoingPort = 587;
|
||||||
|
messageDetails.Server = _config["SMTP:Server"];
|
||||||
|
messageDetails.UserName = _config["SMTP:UserName"];
|
||||||
|
messageDetails.IsSuccess = false;
|
||||||
|
messageDetails.IsCanvass = true;
|
||||||
|
|
||||||
|
await _smtpHelper.SendEmailAsync(messageDetails);
|
||||||
|
|
||||||
|
return Ok(new {success=true, data = rfq, messCode=1});
|
||||||
|
}
|
||||||
|
[HttpPost("StartCanvass")]
|
||||||
|
public async Task<IActionResult> StartCanvass([FromBody] CanvassVM canvass,CancellationToken ct)
|
||||||
|
{
|
||||||
|
var result = await _canvass.StartCanvass(canvass,ct);
|
||||||
|
if (!result.IsSuccess)
|
||||||
|
{
|
||||||
|
return BadRequest(result.Error);
|
||||||
|
}
|
||||||
|
return Ok(new { result, messCode = 1 });
|
||||||
|
}
|
||||||
[HttpPost("PostAllCanvass")]
|
[HttpPost("PostAllCanvass")]
|
||||||
public async Task<IActionResult> PostAllCanvass()
|
public async Task<IActionResult> PostAllCanvass()
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var baseTemplate = EMailTemplate("Content\\SMTPEmailContent", "SendToSupplier.cshtml");
|
var baseTemplate = EMailTemplate("Content\\SMTPEmailContent", "SendToSupplier.cshtml");
|
||||||
|
|
||||||
// Get all canvass items
|
|
||||||
var allCanvass = await _canvass.GetAllForCanvass();
|
var allCanvass = await _canvass.GetAllForCanvass();
|
||||||
|
|
||||||
// ✅ Group all items by supplier
|
|
||||||
var groupedBySupplier = allCanvass
|
var groupedBySupplier = allCanvass
|
||||||
.GroupBy(x => x.SupplierId)
|
.GroupBy(x => x.SupplierId)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// Process each supplier group
|
|
||||||
foreach (var supplierGroup in groupedBySupplier)
|
foreach (var supplierGroup in groupedBySupplier)
|
||||||
{
|
{
|
||||||
var firstItem = supplierGroup.First();
|
var firstItem = supplierGroup.First();
|
||||||
int canvassNo = await _canvass.GetCanvassNo();
|
int canvassNo = await _canvass.GetCanvassNo();
|
||||||
|
|
||||||
// Insert all items for this supplier
|
|
||||||
foreach (var rfqq in supplierGroup)
|
foreach (var rfqq in supplierGroup)
|
||||||
{
|
{
|
||||||
var canvass = new CanvassDto
|
var canvass = new ForCanvassDto
|
||||||
{
|
{
|
||||||
PRDetailsId = rfqq.PRDetailsId,
|
PRDetailsId = rfqq.PRDetailsId,
|
||||||
PRNo = rfqq.PRNo,
|
PRNo = rfqq.PRNo,
|
||||||
@ -252,8 +315,7 @@ namespace CPRNIMS.WebApi.Controllers.Canvass
|
|||||||
await _canvass.PostPerSupplierToken(canvass);
|
await _canvass.PostPerSupplierToken(canvass);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ After inserting all items for this supplier, retrieve RFQ info
|
var canvassInfo = new ForCanvassDto
|
||||||
var canvassInfo = new CanvassDto
|
|
||||||
{
|
{
|
||||||
SupplierId = firstItem.SupplierId,
|
SupplierId = firstItem.SupplierId,
|
||||||
UserId = firstItem.UserId,
|
UserId = firstItem.UserId,
|
||||||
@ -265,13 +327,11 @@ namespace CPRNIMS.WebApi.Controllers.Canvass
|
|||||||
|
|
||||||
if (rfq?.Any() == true)
|
if (rfq?.Any() == true)
|
||||||
{
|
{
|
||||||
// ✅ Prepare email body
|
|
||||||
var message = new StringBuilder(baseTemplate);
|
var message = new StringBuilder(baseTemplate);
|
||||||
message.Replace("@ViewBag.FormLink", Convert.ToString(_configuration["WebEndPoint:SupplierForm"] + rfq[0].Token));
|
message.Replace("@ViewBag.FormLink", Convert.ToString(_configuration["WebEndPoint:SupplierForm"] + rfq[0].Token));
|
||||||
message.Replace("@ViewBag.Supplier", rfq[0].SupplierName);
|
message.Replace("@ViewBag.Supplier", rfq[0].SupplierName);
|
||||||
message.Replace("@ViewBag.Signature", firstItem.FullName);
|
message.Replace("@ViewBag.Signature", firstItem.FullName);
|
||||||
|
|
||||||
// ✅ Prepare email details
|
|
||||||
var messageDetails = new EmailMessageDetailsVM
|
var messageDetails = new EmailMessageDetailsVM
|
||||||
{
|
{
|
||||||
AttachPath = GetRelativePath(@"Content\Documents\Pdf\Offer_Submission_Procedure.pdf"),
|
AttachPath = GetRelativePath(@"Content\Documents\Pdf\Offer_Submission_Procedure.pdf"),
|
||||||
@ -290,21 +350,11 @@ namespace CPRNIMS.WebApi.Controllers.Canvass
|
|||||||
};
|
};
|
||||||
|
|
||||||
await _smtpHelper.SendEmailAsync(messageDetails);
|
await _smtpHelper.SendEmailAsync(messageDetails);
|
||||||
|
|
||||||
// ✅ Post canvass record after successful send
|
|
||||||
await _canvass.PostCanvass(canvassInfo);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(new { message = "All canvass emails sent successfully." });
|
return Ok(new { message = "All canvass emails sent successfully." });
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var errorMessage = ex.InnerException?.ToString() ?? ex.Message.ToString();
|
|
||||||
await PostErrorMessage(errorMessage, "WebApi");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("PostCanvassFollowUp")]
|
[HttpPost("PostCanvassFollowUp")]
|
||||||
public async Task<IActionResult> PostCanvassFollowUp(CanvassDto itemDto)
|
public async Task<IActionResult> PostCanvassFollowUp(CanvassDto itemDto)
|
||||||
@ -412,13 +462,14 @@ namespace CPRNIMS.WebApi.Controllers.Canvass
|
|||||||
{
|
{
|
||||||
var baseTemplate = EMailTemplate("Content\\SMTPEmailContent", "SendToSupplier.cshtml");
|
var baseTemplate = EMailTemplate("Content\\SMTPEmailContent", "SendToSupplier.cshtml");
|
||||||
|
|
||||||
var CanvassDto = new CanvassDto();
|
var CanvassDto = new ForCanvassDto();
|
||||||
int canvassNo = await _canvass.GetCanvassNo();
|
int canvassNo = await _canvass.GetCanvassNo();
|
||||||
|
|
||||||
foreach (var itemCartId in canvassVM.CanvassList.PRDetailsId)
|
foreach (var itemCartId in canvassVM.CanvassList.PRDetailsId)
|
||||||
{
|
{
|
||||||
var index = canvassVM.CanvassList.PRDetailsId.IndexOf(itemCartId);
|
var index = canvassVM.CanvassList.PRDetailsId.IndexOf(itemCartId);
|
||||||
|
|
||||||
CanvassDto = new CanvassDto
|
CanvassDto = new ForCanvassDto
|
||||||
{
|
{
|
||||||
PRDetailsId = canvassVM.CanvassList.PRDetailsId[index],
|
PRDetailsId = canvassVM.CanvassList.PRDetailsId[index],
|
||||||
PRNo = canvassVM.CanvassList.PRNo[index],
|
PRNo = canvassVM.CanvassList.PRNo[index],
|
||||||
@ -430,6 +481,7 @@ namespace CPRNIMS.WebApi.Controllers.Canvass
|
|||||||
};
|
};
|
||||||
await _canvass.PostPerSupplierToken(CanvassDto);
|
await _canvass.PostPerSupplierToken(CanvassDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
var rfq = await _canvass.GetRFQ(CanvassDto);
|
var rfq = await _canvass.GetRFQ(CanvassDto);
|
||||||
|
|
||||||
var message = new StringBuilder(baseTemplate);
|
var message = new StringBuilder(baseTemplate);
|
||||||
@ -454,48 +506,83 @@ namespace CPRNIMS.WebApi.Controllers.Canvass
|
|||||||
messageDetails.IsCanvass = true;
|
messageDetails.IsCanvass = true;
|
||||||
await _smtpHelper.SendEmailAsync(messageDetails);
|
await _smtpHelper.SendEmailAsync(messageDetails);
|
||||||
|
|
||||||
var pR = await _canvass.PostCanvass(CanvassDto);
|
return Ok(rfq);
|
||||||
|
|
||||||
return Ok(pR);
|
|
||||||
}
|
}
|
||||||
[HttpPost("PostSearchSupplierAndSend")]
|
[HttpPost("PostSearchSupplierAndSend")]
|
||||||
public async Task<IActionResult> PostSearchSupplierAndSend(CancellationToken ct)
|
public async Task<IActionResult> PostSearchSupplierAndSend(CancellationToken ct)
|
||||||
{
|
{
|
||||||
// #1 Get top 10 items without suppliers — must process all
|
var items = await _canvass.GetForAISearchingTagging(ct);
|
||||||
var response = await _canvass.GetForAISearchingTagging();
|
if (items == null || !items.Any())
|
||||||
if (response == null || !response.Any()) return BadRequest("No items found.");
|
return BadRequest("No items found.");
|
||||||
|
|
||||||
|
var rawSuppliers = await _canvass.GetSuppliers(ct) ?? new List<Suppliers>();
|
||||||
|
var existingSuppliers = _mapper.Map<List<SupplierResponse>>(rawSuppliers);
|
||||||
|
|
||||||
var supplierResults = new List<object>();
|
var supplierResults = new List<object>();
|
||||||
|
|
||||||
foreach (var item in response)
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var results = await ProcessItemAsync(item, existingSuppliers, ct);
|
||||||
|
supplierResults.AddRange(results);
|
||||||
|
}
|
||||||
|
catch (Exception) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(new { totalProcessed = supplierResults.Count, suppliers = supplierResults });
|
||||||
|
}
|
||||||
|
private async Task<List<object>> ProcessItemAsync(
|
||||||
|
ForAISearchingTagging item,
|
||||||
|
List<SupplierResponse> existingSuppliers,
|
||||||
|
CancellationToken ct)
|
||||||
{
|
{
|
||||||
// #2 Search Tavily + Filter with Groq
|
|
||||||
var suppliers = await _supplierSearchService
|
var suppliers = await _supplierSearchService
|
||||||
.SearchAndFilterSuppliersAsync(item.ItemName, item.ItemDescription, item.IsInternational);
|
.SearchAndFilterSuppliersAsync(item.ItemName, item.ItemDescription, item.IsInternational);
|
||||||
|
|
||||||
if (!suppliers.Any())
|
if (!suppliers.Any())
|
||||||
{
|
{
|
||||||
await _canvass.SearchingUpdate(item.PRDetailsId);
|
await _canvass.DeleteAsync(item.PRDetailsId, ct);
|
||||||
continue;
|
return new List<object>();
|
||||||
}
|
}
|
||||||
|
|
||||||
// #3 & #4 Loop each found supplier
|
var results = new List<object>();
|
||||||
|
int canvassNo = await _canvass.GetCanvassNo();
|
||||||
|
bool anySuccess = false;
|
||||||
|
bool isProd = Convert.ToBoolean(_configuration["SMTP:IsLive"]);
|
||||||
|
|
||||||
foreach (var supplier in suppliers)
|
foreach (var supplier in suppliers)
|
||||||
{
|
{
|
||||||
int canvassNo = await _canvass.GetCanvassNo();
|
try
|
||||||
|
{
|
||||||
|
int supplierId;
|
||||||
|
|
||||||
|
var matchedId = await _supplierSearchService
|
||||||
|
.FindMatchingExistingSupplierAsync(supplier, existingSuppliers);
|
||||||
|
|
||||||
|
if (matchedId.HasValue)
|
||||||
|
{
|
||||||
|
supplierId = matchedId.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
var supplierRequest = _mapper.Map<SupplierRequest>(supplier);
|
var supplierRequest = _mapper.Map<SupplierRequest>(supplier);
|
||||||
supplierRequest.ItemNo = item.ItemNo;
|
supplierRequest.ItemNo = item.ItemNo;
|
||||||
|
|
||||||
var result = await _canvass.PostSupplierAsync(supplierRequest, ct);
|
var result = await _canvass.PostPutSupplierAsync(supplierRequest, ct);
|
||||||
if (result?.Value == null) continue;
|
if (result?.Value == null) continue;
|
||||||
|
|
||||||
var canvassDto = new CanvassDto
|
supplierId = result.Value.SupplierId;
|
||||||
|
|
||||||
|
existingSuppliers.Add(result.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
var canvassDto = new ForCanvassDto
|
||||||
{
|
{
|
||||||
PRDetailsId = item.PRDetailsId,
|
PRDetailsId = item.PRDetailsId,
|
||||||
PRNo = item.PRNo,
|
PRNo = item.PRNo,
|
||||||
ItemNo = item.ItemNo,
|
ItemNo = item.ItemNo,
|
||||||
SupplierId = result.Value.SupplierId,
|
SupplierId = supplierId,
|
||||||
UserId = item.UserId,
|
UserId = item.UserId,
|
||||||
FullName = item.FullName,
|
FullName = item.FullName,
|
||||||
CanvassNo = ++canvassNo,
|
CanvassNo = ++canvassNo,
|
||||||
@ -506,46 +593,46 @@ namespace CPRNIMS.WebApi.Controllers.Canvass
|
|||||||
var rfq = await _canvass.GetRFQ(canvassDto);
|
var rfq = await _canvass.GetRFQ(canvassDto);
|
||||||
if (rfq == null || !rfq.Any()) continue;
|
if (rfq == null || !rfq.Any()) continue;
|
||||||
|
|
||||||
var supplierEmailRequest = new SupplierEmailRequest
|
var emailRequest = BuildEmailRequest(item, supplier, rfq[0].Token, isProd);
|
||||||
|
await _canvass.SendRFQ(emailRequest);
|
||||||
|
|
||||||
|
anySuccess = true;
|
||||||
|
results.Add(new
|
||||||
{
|
{
|
||||||
AttachPath = GetRelativePath(@"Content\\Documents\\Pdf\\Offer_Submission_Procedure.pdf"),
|
item = item.ItemName,
|
||||||
Recipient = "rmsoriano@lloydlab.com",//rfq[0].EmailAddress, // this will be implemented later
|
supplier = supplier.SupplierName,
|
||||||
Subject = $"CLMS - Request For Quotation #PRNo: {rfq[0].AggrePRNo}",
|
email = supplier.EmailAddress,
|
||||||
CC = Convert.ToString(_configuration["Canvass:CC"] ?? ""),
|
canvassNo = canvassDto.CanvassNo,
|
||||||
|
isExisting = matchedId.HasValue
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception) { continue; }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anySuccess)
|
||||||
|
await _canvass.DeleteAsync(item.PRDetailsId, ct);
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
private SupplierEmailRequest BuildEmailRequest(
|
||||||
|
ForAISearchingTagging item, SupplierResponse supplier, string token,bool isProd) => new()
|
||||||
|
{
|
||||||
|
AttachPath = GetRelativePath(@"Content\Documents\Pdf\Offer_Submission_Procedure.pdf"),
|
||||||
|
Recipient = isProd ? supplier.EmailAddress : _configuration["SMTP:TestRecipient"] ?? "rmsoriano@lloydlab.com",
|
||||||
|
Subject = $"LLI - Request For Quotation #PRNo: {item.PRNo}",
|
||||||
|
CC = _configuration["Canvass:CC"] ?? "rmsoriano@lloydlab.com",
|
||||||
SenderEmail = _config["SMTP:SenderEmail"],
|
SenderEmail = _config["SMTP:SenderEmail"],
|
||||||
DisplayName = "lloydlabinc.com",
|
DisplayName = "lloydlabinc.com",
|
||||||
Password = _config["SMTP:Password"],
|
Password = _config["SMTP:Password"],
|
||||||
OutGoingPort = 587,
|
OutGoingPort = 587,
|
||||||
Server = _config["SMTP:Server"],
|
Server = _config["SMTP:Server"],
|
||||||
UserName = _config["SMTP:UserName"],
|
UserName = _config["SMTP:UserName"],
|
||||||
FormLink = Convert.ToString(_configuration["WebEndPoint:SupplierForm"] ?? ""),
|
FormLink = _configuration["WebEndPoint:SupplierForm"] ?? "",
|
||||||
Token = rfq[0].Token,
|
Token = token,
|
||||||
SupplierName = result.Value.SupplierName,
|
SupplierName = supplier.SupplierName,
|
||||||
Purchaser = item.FullName,
|
Purchaser = item.FullName,
|
||||||
IsSuccess = false,
|
|
||||||
IsCanvass = true,
|
IsCanvass = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
await _canvass.SearchingUpdate(item.PRDetailsId);
|
|
||||||
await _canvass.SendRFQ(supplierEmailRequest);
|
|
||||||
|
|
||||||
supplierResults.Add(new
|
|
||||||
{
|
|
||||||
item = item.ItemName,
|
|
||||||
supplier = supplier.SupplierName,
|
|
||||||
email = supplier.EmailAddress,
|
|
||||||
canvassNo = canvassDto.CanvassNo
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(new
|
|
||||||
{
|
|
||||||
totalProcessed = supplierResults.Count,
|
|
||||||
suppliers = supplierResults
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("PostSuggestedSupp")]
|
[HttpPost("PostSuggestedSupp")]
|
||||||
public async Task<IActionResult> PostSuggestedSupp(CanvassDto CanvassDto)
|
public async Task<IActionResult> PostSuggestedSupp(CanvassDto CanvassDto)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -14,8 +14,8 @@ namespace CPRNIMS.WebApi.Security
|
|||||||
{
|
{
|
||||||
UserId = user.FindFirstValue(ClaimTypes.NameIdentifier) ?? "",
|
UserId = user.FindFirstValue(ClaimTypes.NameIdentifier) ?? "",
|
||||||
UserName = user.FindFirstValue(ClaimTypes.Name) ?? "",
|
UserName = user.FindFirstValue(ClaimTypes.Name) ?? "",
|
||||||
FullName = user.FindFirstValue("fullName") ?? "",
|
FullName = user.FindFirstValue("FullName") ?? "",
|
||||||
Company = user.FindFirstValue("company") ?? "",
|
Company = user.FindFirstValue("Company") ?? "",
|
||||||
Roles = user.FindAll(ClaimTypes.Role)
|
Roles = user.FindAll(ClaimTypes.Role)
|
||||||
.Select(r => r.Value)
|
.Select(r => r.Value)
|
||||||
.ToList()
|
.ToList()
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
using CPRNIMS.Domain.UIContracts.Account;
|
using CPRNIMS.Domain.UIContracts.Account;
|
||||||
using CPRNIMS.Domain.UIContracts.Canvass;
|
using CPRNIMS.Domain.UIContracts.Canvass;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Canvass.Request;
|
||||||
|
using CPRNIMS.Infrastructure.Dto.Common;
|
||||||
using CPRNIMS.Infrastructure.Helper;
|
using CPRNIMS.Infrastructure.Helper;
|
||||||
using CPRNIMS.Infrastructure.ViewModel.Canvass;
|
using CPRNIMS.Infrastructure.ViewModel.Canvass;
|
||||||
using CPRNIMS.WebApps.Controllers.Base;
|
using CPRNIMS.WebApps.Controllers.Base;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using CPRNIMS.Infrastructure.Dto.Common;
|
|
||||||
|
|
||||||
namespace CPRNIMS.WebApps.Controllers.Canvass
|
namespace CPRNIMS.WebApps.Controllers.Canvass
|
||||||
{
|
{
|
||||||
@ -21,6 +22,16 @@ namespace CPRNIMS.WebApps.Controllers.Canvass
|
|||||||
_canvass = canvass;
|
_canvass = canvass;
|
||||||
}
|
}
|
||||||
#region POST PUT
|
#region POST PUT
|
||||||
|
public async Task<IActionResult> PostSupplierForCanvass(CanvassVM viewModel)
|
||||||
|
{
|
||||||
|
var response = await _canvass.PostSupplierForCanvass(GetUser(), viewModel);
|
||||||
|
|
||||||
|
if (response.messCode != 0)
|
||||||
|
{
|
||||||
|
return Json(new { success = true });
|
||||||
|
}
|
||||||
|
return Json(new { success = false, response = response.errMessage });
|
||||||
|
}
|
||||||
public async Task<IActionResult> PostCanvass(CanvassVM viewModel, List<CanvassList> CanvassList)
|
public async Task<IActionResult> PostCanvass(CanvassVM viewModel, List<CanvassList> CanvassList)
|
||||||
{
|
{
|
||||||
if (CanvassList.Count > 0)
|
if (CanvassList.Count > 0)
|
||||||
@ -57,8 +68,6 @@ namespace CPRNIMS.WebApps.Controllers.Canvass
|
|||||||
List<SupplierList> SupplierList)
|
List<SupplierList> SupplierList)
|
||||||
{
|
{
|
||||||
var postPutItem = new CanvassVM();
|
var postPutItem = new CanvassVM();
|
||||||
try
|
|
||||||
{
|
|
||||||
if (SupplierList.Count > 0)
|
if (SupplierList.Count > 0)
|
||||||
{
|
{
|
||||||
viewModel.SupplierList = new SupplierList
|
viewModel.SupplierList = new SupplierList
|
||||||
@ -77,26 +86,17 @@ namespace CPRNIMS.WebApps.Controllers.Canvass
|
|||||||
}
|
}
|
||||||
return Json(new { success = false });
|
return Json(new { success = false });
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var message = ex.InnerException?.ToString() ?? ex.Message.ToString();
|
|
||||||
|
|
||||||
return Json(new { success = false, response = postPutItem.errMessage });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public async Task<IActionResult> PostPutItemTagging(CanvassVM viewModel,
|
public async Task<IActionResult> PostPutItemTagging(CanvassVM viewModel,
|
||||||
List<ItemList> ItemList)
|
List<ItemList> ItemList)
|
||||||
{
|
{
|
||||||
var postPutItem = new CanvassVM();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (ItemList.Count > 0)
|
if (ItemList.Count > 0)
|
||||||
{
|
{
|
||||||
viewModel.ItemList = new ItemList
|
viewModel.ItemList = new ItemList
|
||||||
{
|
{
|
||||||
ItemNo = ItemList.SelectMany(ic => ic.ItemNo).ToList(),
|
ItemNo = ItemList.SelectMany(ic => ic.ItemNo).ToList(),
|
||||||
};
|
};
|
||||||
postPutItem = await _canvass.PostPutItemTagging(GetUser(), viewModel);
|
var postPutItem = await _canvass.PostPutItemTagging(GetUser(), viewModel);
|
||||||
if (postPutItem.messCode != 0)
|
if (postPutItem.messCode != 0)
|
||||||
{
|
{
|
||||||
return Json(new { success = true });
|
return Json(new { success = true });
|
||||||
@ -108,13 +108,6 @@ namespace CPRNIMS.WebApps.Controllers.Canvass
|
|||||||
}
|
}
|
||||||
return Json(new { success = false });
|
return Json(new { success = false });
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var message = ex.InnerException?.ToString() ?? ex.Message.ToString();
|
|
||||||
|
|
||||||
return Json(new { success = false, response = postPutItem.errMessage });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public async Task<IActionResult> PostApprovedSupp(CanvassVM viewModel)
|
public async Task<IActionResult> PostApprovedSupp(CanvassVM viewModel)
|
||||||
{
|
{
|
||||||
var postPutItem = await _canvass.PostApprovedSupp(GetUser(), viewModel);
|
var postPutItem = await _canvass.PostApprovedSupp(GetUser(), viewModel);
|
||||||
@ -181,6 +174,31 @@ namespace CPRNIMS.WebApps.Controllers.Canvass
|
|||||||
|
|
||||||
return Json(new { success = false, Response = postPutItem.errMessage });
|
return Json(new { success = false, Response = postPutItem.errMessage });
|
||||||
}
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> StartCanvass([FromBody] StartCanvassRequest request)
|
||||||
|
{
|
||||||
|
if (request?.Items == null || request.Items.Count == 0)
|
||||||
|
return Json(new { success = false, response = "Array Empty" });
|
||||||
|
|
||||||
|
var viewModel = new CanvassVM
|
||||||
|
{
|
||||||
|
ForSupplierSearchList = new ForSupplierSearchList
|
||||||
|
{
|
||||||
|
PRDetailsId = request.Items.Select(i => i.PRDetailsId).ToList(),
|
||||||
|
PRNo = request.Items.Select(i => i.PRNo).ToList(),
|
||||||
|
ItemNo = request.Items.Select(i => i.ItemNo).ToList(),
|
||||||
|
ItemDescription = request.Items.Select(i => i.ItemDescription).ToList(),
|
||||||
|
ItemName = request.Items.Select(i => i.ItemName).ToList(),
|
||||||
|
},
|
||||||
|
IsInternational = request.IsInternational
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await _canvass.StartCanvass(GetUser(), viewModel);
|
||||||
|
if (result.messCode != 0)
|
||||||
|
return Json(new { success = true });
|
||||||
|
|
||||||
|
return Json(new { success = false });
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
#region Get
|
#region Get
|
||||||
public async Task<IActionResult> GetItemSupplierWOEmail(long PRNo)
|
public async Task<IActionResult> GetItemSupplierWOEmail(long PRNo)
|
||||||
|
|||||||
@ -124,7 +124,6 @@
|
|||||||
// </button>
|
// </button>
|
||||||
grid.innerHTML = data.map(item =>
|
grid.innerHTML = data.map(item =>
|
||||||
H.buildCardHtml(item, i => `
|
H.buildCardHtml(item, i => `
|
||||||
|
|
||||||
<button class="cv-btn cv-btn-outline btn-email" data-id="${i.supplierId}">
|
<button class="cv-btn cv-btn-outline btn-email" data-id="${i.supplierId}">
|
||||||
<i class="fas fa-paper-plane"></i> Send Email
|
<i class="fas fa-paper-plane"></i> Send Email
|
||||||
</button>`)
|
</button>`)
|
||||||
@ -133,16 +132,40 @@
|
|||||||
grid.querySelectorAll(".btn-canvass").forEach(b =>
|
grid.querySelectorAll(".btn-canvass").forEach(b =>
|
||||||
b.addEventListener("click", () => openCanvass(b.dataset.id)));
|
b.addEventListener("click", () => openCanvass(b.dataset.id)));
|
||||||
grid.querySelectorAll(".btn-email").forEach(b =>
|
grid.querySelectorAll(".btn-email").forEach(b =>
|
||||||
b.addEventListener("click", () => sendEmail(b.dataset.id)));
|
b.addEventListener("click", () => postCanvass(b.dataset.id)));
|
||||||
}
|
}
|
||||||
|
|
||||||
function openCanvass(id) {
|
function postCanvass(supplierId) {
|
||||||
document.getElementById("supplierId").value = id;
|
loader = $('#overlay, #loader').css('z-index', 1070);
|
||||||
// e.g. $('#canvassModal').modal('show');
|
const SupplierId = supplierId;
|
||||||
console.log("Open canvass:", id);
|
console.log(supplierId);
|
||||||
|
showConfirmation({
|
||||||
|
title: 'RFQ Submission',
|
||||||
|
message: 'Are you sure you want to submit this request for quotation? This action cannot be undone.',
|
||||||
|
type: 'warning',
|
||||||
|
confirmText: 'Yes',
|
||||||
|
cancelText: 'No'
|
||||||
|
}).then((confirmed) => {
|
||||||
|
if (confirmed) {
|
||||||
|
$.ajax($.extend({
|
||||||
|
url: '/CanvassMgmt/PostSupplierForCanvass',
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
SupplierId: SupplierId
|
||||||
|
},
|
||||||
|
success: function (response) {
|
||||||
|
if (response.success) {
|
||||||
|
fetchData();
|
||||||
|
showToast('success', 'RFQ Successfully Sent!', 'success!', 4000);
|
||||||
|
} else {
|
||||||
|
fetchData();
|
||||||
|
showToast('error', response.response, 'Submission of canvass failed!', 4000);
|
||||||
}
|
}
|
||||||
function sendEmail(id) {
|
},
|
||||||
console.log("Send email:", id);
|
error: errorHandler
|
||||||
|
}, beforeComplete(loader)));
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchData();
|
fetchData();
|
||||||
|
|||||||
@ -79,7 +79,6 @@
|
|||||||
<span class="cv-pg-info" id="ft-pageInfo"></span>
|
<span class="cv-pg-info" id="ft-pageInfo"></span>
|
||||||
<div class="cv-pg-btns" id="ft-pageButtons"></div>
|
<div class="cv-pg-btns" id="ft-pageButtons"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* ── department dropdown (unchanged) ─────────────── */
|
/* ── department dropdown (unchanged) ─────────────── */
|
||||||
.cv-department-wrap {
|
.cv-department-wrap {
|
||||||
@ -278,6 +277,15 @@
|
|||||||
gap: 5px;
|
gap: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ft-qty {
|
||||||
|
font-size: .77rem;
|
||||||
|
color: rgba(255,255,255,.65);
|
||||||
|
margin-top: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.ft-department {
|
.ft-department {
|
||||||
font-size: .77rem;
|
font-size: .77rem;
|
||||||
color: rgba(255,255,255,.65);
|
color: rgba(255,255,255,.65);
|
||||||
@ -537,6 +545,94 @@
|
|||||||
animation: ftDots 1s steps(3, end) infinite;
|
animation: ftDots 1s steps(3, end) infinite;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<!-- Modal viewAllSuppliers -->
|
||||||
|
<div class="modal fade" id="viewItemSuppliers"
|
||||||
|
tabindex="-1" aria-labelledby="suppliersModalLabel" aria-hidden="true"
|
||||||
|
data-bs-backdrop="static">
|
||||||
|
<div class="modal-dialog vis-dialog">
|
||||||
|
<div class="modal-content vis-content">
|
||||||
|
|
||||||
|
@* {{-- ── MODAL HEADER ── --}} *@
|
||||||
|
<div class="vis-header">
|
||||||
|
<div class="vis-header-inner">
|
||||||
|
<div class="vis-header-icon">
|
||||||
|
<i class="fas fa-store"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="vis-title" id="suppliersModalLabel">Tag Suppliers</h5>
|
||||||
|
<p class="vis-subtitle">Select suppliers to assign to this item</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="vis-close" data-bs-dismiss="modal" aria-label="Close">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body vis-body">
|
||||||
|
|
||||||
|
@* {{-- ── ITEM INFO CARD ── --}} *@
|
||||||
|
<div class="vis-item-card">
|
||||||
|
<div class="vis-item-grid">
|
||||||
|
<div class="vis-item-field">
|
||||||
|
<span class="vis-field-lbl"><i class="fas fa-hashtag"></i> Item No</span>
|
||||||
|
<span class="vis-field-val" id="item-No">—</span>
|
||||||
|
</div>
|
||||||
|
<div class="vis-item-field">
|
||||||
|
<span class="vis-field-lbl"><i class="fas fa-box"></i> Item Name</span>
|
||||||
|
<span class="vis-field-val" id="item-Name">—</span>
|
||||||
|
</div>
|
||||||
|
<div class="vis-item-field vis-item-field--accent">
|
||||||
|
<span class="vis-field-lbl"><i class="fas fa-check-circle"></i> Selected Suppliers</span>
|
||||||
|
<span class="vis-field-val vis-sel-count" id="totalSelected">0</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* {{-- ── TOOLBAR ── --}}
|
||||||
|
<div class="vis-toolbar">
|
||||||
|
<button id="btnAddNewSupplier" type="button"
|
||||||
|
class="vis-btn vis-btn-success"
|
||||||
|
onclick="showModalNewUpdateSupplier(0);">
|
||||||
|
<i class="fas fa-plus"></i> Add New Supplier
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
*@
|
||||||
|
|
||||||
|
@* {{-- ── TABLE ── --}} *@
|
||||||
|
<div class="vis-table-wrap">
|
||||||
|
<table id="SupplierDataTable" class="row-border vis-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="vis-th-check">
|
||||||
|
<input id="selectAllHeaderCheckbox" type="checkbox"
|
||||||
|
class="selectAllCheckbox vis-checkbox"
|
||||||
|
title="Select all" />
|
||||||
|
</th>
|
||||||
|
<th>Supplier Name</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* {{-- ── MODAL FOOTER ── --}} *@
|
||||||
|
<div class="vis-footer">
|
||||||
|
<button type="button" class="vis-btn vis-btn-ghost"
|
||||||
|
onclick="refreshCanvasTable();" data-bs-dismiss="modal">
|
||||||
|
<i class="fas fa-arrow-left"></i> Back
|
||||||
|
</button>
|
||||||
|
<button id="btnSubmitSupplier" type="button"
|
||||||
|
class="vis-btn vis-btn-primary"
|
||||||
|
onclick="postTaggingSupplier();">
|
||||||
|
<i class="fas fa-paper-plane"></i> Submit Tagging
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
@ -605,9 +701,11 @@
|
|||||||
const id = card.dataset.id;
|
const id = card.dataset.id;
|
||||||
if (!s.selected.has(id)) {
|
if (!s.selected.has(id)) {
|
||||||
s.selected.set(id, {
|
s.selected.set(id, {
|
||||||
prDetailsId: id,
|
prDetailsId: parseInt(card.dataset.id, 10),
|
||||||
prNo: card.dataset.prno,
|
prNo: parseInt(card.dataset.prno, 10),
|
||||||
itemName: card.dataset.itemname
|
itemNo: parseInt(card.dataset.itemno, 10),
|
||||||
|
itemName: card.dataset.itemname,
|
||||||
|
itemDescription: card.dataset.itemdescription
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -615,8 +713,8 @@
|
|||||||
updateSelectionBar();
|
updateSelectionBar();
|
||||||
});
|
});
|
||||||
|
|
||||||
btnLocal.addEventListener("click", () => fireCanvass("Local"));
|
btnLocal.addEventListener("click", () => fireCanvass(false));
|
||||||
btnIntl.addEventListener("click", () => fireCanvass("International"));
|
btnIntl.addEventListener("click", () => fireCanvass(true));
|
||||||
|
|
||||||
// ── Sync checkbox visual state to s.selected ────────
|
// ── Sync checkbox visual state to s.selected ────────
|
||||||
function syncCheckboxes() {
|
function syncCheckboxes() {
|
||||||
@ -640,25 +738,62 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Fire to backend ──────────────────────────────────
|
// ── Fire to backend ──────────────────────────────────
|
||||||
async function fireCanvass(locationType) {
|
async function fireCanvass(isInternational) {
|
||||||
const items = Array.from(s.selected.values());
|
const items = Array.from(s.selected.values());
|
||||||
|
const btn = isInternational ? btnIntl : btnLocal;
|
||||||
|
|
||||||
if (!items.length) return;
|
if (!items.length) return;
|
||||||
|
|
||||||
const btn = locationType === "Local" ? btnLocal : btnIntl;
|
// ── Identity config per mode ─────────────────────────
|
||||||
|
const mode = isInternational
|
||||||
|
? {
|
||||||
|
type: 'international',
|
||||||
|
icon: '🌐',
|
||||||
|
label: 'International',
|
||||||
|
badgeClass: 'confirm-badge--intl',
|
||||||
|
type: 'warning',
|
||||||
|
title: '🌐 International AI Canvass',
|
||||||
|
message: 'The AI will search <strong>both local and international suppliers</strong> for the selected items. This action cannot be undone.',
|
||||||
|
confirmText: 'Yes, Go International',
|
||||||
|
cancelText: 'No',
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'info',
|
||||||
|
icon: '📍',
|
||||||
|
label: 'Local',
|
||||||
|
badgeClass: 'confirm-badge--local',
|
||||||
|
title: '📍 Local AI Canvass',
|
||||||
|
message: 'The AI will search <strong>local suppliers only</strong> for the selected items. This action cannot be undone.',
|
||||||
|
confirmText: 'Yes, Go Local',
|
||||||
|
cancelText: 'No',
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmed = await showConfirmation({
|
||||||
|
title: mode.title,
|
||||||
|
message: mode.message,
|
||||||
|
type: mode.type,
|
||||||
|
confirmText: mode.confirmText,
|
||||||
|
cancelText: mode.cancelText,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
btn.classList.add("loading");
|
btn.classList.add("loading");
|
||||||
const origLabel = btn.querySelector("span").textContent;
|
const origLabel = btn.querySelector("span").textContent;
|
||||||
btn.querySelector("span").textContent = "Sending";
|
btn.querySelector("span").textContent = "Sending…";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/CanvassMgmt/StartCanvass", {
|
const res = await fetch("/CanvassMgmt/StartCanvass", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
locationType: locationType,
|
isInternational: isInternational,
|
||||||
items: items.map(i => ({
|
items: items.map(i => ({
|
||||||
prDetailsId: i.prDetailsId,
|
prDetailsId: parseInt(i.prDetailsId, 10),
|
||||||
prNo: i.prNo,
|
prNo: parseInt(i.prNo, 10),
|
||||||
itemName: i.itemName
|
itemNo: parseInt(i.itemNo, 10),
|
||||||
|
itemName: i.itemName,
|
||||||
|
itemDescription: i.itemDescription
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@ -666,22 +801,19 @@
|
|||||||
if (!res.ok) throw new Error("HTTP " + res.status);
|
if (!res.ok) throw new Error("HTTP " + res.status);
|
||||||
await res.json();
|
await res.json();
|
||||||
|
|
||||||
// Clear selection after successful fire
|
|
||||||
s.selected.clear();
|
s.selected.clear();
|
||||||
syncCheckboxes();
|
syncCheckboxes();
|
||||||
updateSelectionBar();
|
updateSelectionBar();
|
||||||
|
|
||||||
showToast(
|
|
||||||
(locationType === "Local"
|
|
||||||
? '<i class="fas fa-map-marker-alt"></i> Local canvass started for '
|
|
||||||
: '<i class="fas fa-globe"></i> International canvass started for ')
|
|
||||||
+ items.length + ' item' + (items.length !== 1 ? 's' : '') + '.',
|
|
||||||
"success"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Refresh grid so processed items reflect updated state
|
|
||||||
fetchData();
|
fetchData();
|
||||||
|
|
||||||
|
showToast(
|
||||||
|
isInternational
|
||||||
|
? '<i class="fas fa-globe"></i> <strong>International</strong> AI canvass started for ' + items.length + ' item' + (items.length !== 1 ? 's' : '') + '.'
|
||||||
|
: '<i class="fas fa-map-marker-alt"></i> <strong>Local</strong> AI canvass started for ' + items.length + ' item' + (items.length !== 1 ? 's' : '') + '.',
|
||||||
|
isInternational ? "warning" : "success"
|
||||||
|
);
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("fireCanvass error:", err);
|
console.error("fireCanvass error:", err);
|
||||||
showToast('<i class="fas fa-exclamation-triangle"></i> Failed to start canvass. Please try again.', "error");
|
showToast('<i class="fas fa-exclamation-triangle"></i> Failed to start canvass. Please try again.', "error");
|
||||||
@ -779,7 +911,9 @@
|
|||||||
return '<div class="cv-card' + (isChecked ? ' ft-selected' : '') + '"'
|
return '<div class="cv-card' + (isChecked ? ' ft-selected' : '') + '"'
|
||||||
+ ' data-id="' + attr(String(item.prDetailsId)) + '"'
|
+ ' data-id="' + attr(String(item.prDetailsId)) + '"'
|
||||||
+ ' data-prno="' + attr(String(item.prNo || "")) + '"'
|
+ ' data-prno="' + attr(String(item.prNo || "")) + '"'
|
||||||
+ ' data-itemname="' + attr(item.itemName || "") + '">'
|
+ ' data-itemno="' + attr(String(item.itemNo || "")) + '"'
|
||||||
|
+ ' data-itemname="' + attr(item.itemName || "") + '"'
|
||||||
|
+ ' data-itemdescription="' + attr(item.itemDescription || "") + '">'
|
||||||
|
|
||||||
// ── Card header ──────────────────────────
|
// ── Card header ──────────────────────────
|
||||||
+ '<div class="ft-card-hd">'
|
+ '<div class="ft-card-hd">'
|
||||||
@ -790,6 +924,7 @@
|
|||||||
+ '<div class="ft-pr-badge"><i class="fas fa-file-alt"></i> PR #' + esc(String(item.prNo || "-")) + '</div>'
|
+ '<div class="ft-pr-badge"><i class="fas fa-file-alt"></i> PR #' + esc(String(item.prNo || "-")) + '</div>'
|
||||||
+ '<div class="ft-item-name">' + esc(item.itemName || "-") + '</div>'
|
+ '<div class="ft-item-name">' + esc(item.itemName || "-") + '</div>'
|
||||||
+ '<div class="ft-item-no"><i class="fas fa-hashtag"></i> Item No: ' + esc(String(item.itemNo || "-")) + '</div>'
|
+ '<div class="ft-item-no"><i class="fas fa-hashtag"></i> Item No: ' + esc(String(item.itemNo || "-")) + '</div>'
|
||||||
|
+ '<div class="ft-qty"><i class="fas fa-hashtag"></i> Qty: ' + esc(String(item.qty || "-")) + '</div>'
|
||||||
+ '<div class="ft-department"><i class="fas fa-building-user"></i> Department: ' + esc(String(item.department || "-")) + '</div>'
|
+ '<div class="ft-department"><i class="fas fa-building-user"></i> Department: ' + esc(String(item.department || "-")) + '</div>'
|
||||||
+ '</div>'
|
+ '</div>'
|
||||||
|
|
||||||
@ -813,6 +948,7 @@
|
|||||||
+ '<button class="cv-btn cv-btn-primary btn-tag"'
|
+ '<button class="cv-btn cv-btn-primary btn-tag"'
|
||||||
+ ' data-id="' + attr(String(item.prDetailsId)) + '"'
|
+ ' data-id="' + attr(String(item.prDetailsId)) + '"'
|
||||||
+ ' data-prno="' + attr(String(item.prNo || "")) + '"'
|
+ ' data-prno="' + attr(String(item.prNo || "")) + '"'
|
||||||
|
+ ' data-itemno="' + attr(String(item.itemNo || "")) + '"'
|
||||||
+ ' data-itemname="' + attr(item.itemName || "") + '">'
|
+ ' data-itemname="' + attr(item.itemName || "") + '">'
|
||||||
+ '<i class="fas fa-user-tag"></i> Tag Supplier'
|
+ '<i class="fas fa-user-tag"></i> Tag Supplier'
|
||||||
+ '</button>'
|
+ '</button>'
|
||||||
@ -831,9 +967,11 @@
|
|||||||
const id = card.dataset.id;
|
const id = card.dataset.id;
|
||||||
if (cb.checked) {
|
if (cb.checked) {
|
||||||
s.selected.set(id, {
|
s.selected.set(id, {
|
||||||
prDetailsId: id,
|
prDetailsId: parseInt(card.dataset.id, 10),
|
||||||
prNo: card.dataset.prno,
|
prNo: parseInt(card.dataset.prno, 10),
|
||||||
itemName: card.dataset.itemname
|
itemNo: parseInt(card.dataset.itemno, 10),
|
||||||
|
itemName: card.dataset.itemname,
|
||||||
|
itemDescription: card.dataset.itemdescription
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
s.selected.delete(id);
|
s.selected.delete(id);
|
||||||
@ -846,7 +984,7 @@
|
|||||||
// ── Wire action button clicks ────────────────────
|
// ── Wire action button clicks ────────────────────
|
||||||
grid.querySelectorAll(".btn-tag").forEach(function (b) {
|
grid.querySelectorAll(".btn-tag").forEach(function (b) {
|
||||||
b.addEventListener("click", function () {
|
b.addEventListener("click", function () {
|
||||||
tagSupplier(b.dataset.id, b.dataset.prno, b.dataset.itemname);
|
viewTagSupplier(b.dataset.itemno, b.dataset.itemname);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -861,10 +999,73 @@
|
|||||||
} catch (e) { return String(raw); }
|
} catch (e) { return String(raw); }
|
||||||
}
|
}
|
||||||
|
|
||||||
function tagSupplier(prDetailsId, prNo, itemName) {
|
|
||||||
console.log("Tag supplier | PRDetailsId:", prDetailsId, "| PR#:", prNo, "| Item:", itemName);
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchData();
|
fetchData();
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
function viewTagSupplier(itemNo, itemName) {
|
||||||
|
loader = $('#overlay, #loader').css('z-index', 1080);
|
||||||
|
$('#viewItemSuppliers').modal('show');
|
||||||
|
$('#viewItemSuppliers').css('z-index', 1065);
|
||||||
|
|
||||||
|
tableName = '#SupplierDataTable';
|
||||||
|
totalSelectedLabel = $('#totalSelected');
|
||||||
|
|
||||||
|
clearTableSelection(tableName, selectedProductsMap, () => {
|
||||||
|
totalSelectedLabel.text(0);
|
||||||
|
}, 'selected-row', '.select-all-CanvassItem-checkbox');
|
||||||
|
|
||||||
|
tableElement = $(tableName);
|
||||||
|
tableDestroy(tableElement);
|
||||||
|
|
||||||
|
ItemNo = parseInt(itemNo, 10);
|
||||||
|
|
||||||
|
document.getElementById('item-Name').innerText = itemName;
|
||||||
|
document.getElementById('item-No').innerText = ItemNo;
|
||||||
|
|
||||||
|
supplierDataTable = tableElement.DataTable({
|
||||||
|
ajax: $.extend({
|
||||||
|
url: endpoint.GetSupplierItemWOEmail,
|
||||||
|
type: 'POST',
|
||||||
|
data: { ItemNo },
|
||||||
|
}, beforeComplete(loader)),
|
||||||
|
language: {
|
||||||
|
emptyTable: "No record available"
|
||||||
|
},
|
||||||
|
initComplete: function () {
|
||||||
|
initializeTableSelection({
|
||||||
|
tableName: tableName,
|
||||||
|
dataTable: supplierDataTable,
|
||||||
|
selectedItemsMap: selectedProductsMap,
|
||||||
|
idKey: 'supplierId',
|
||||||
|
idKey2: 'itemNo',
|
||||||
|
checkboxClass: '.select-CanvassItem-checkbox',
|
||||||
|
selectAllClass: '.select-all-CanvassItem-checkbox',
|
||||||
|
selectedRowClass: 'selected-row',
|
||||||
|
updateCountCallback: function () {
|
||||||
|
totalSelectedLabel.text(getSelectedCount(selectedProductsMap));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
columns: [{
|
||||||
|
data: 'supplierId',
|
||||||
|
title: '<input type="checkbox" class="select-all-CanvassItem-checkbox" />',
|
||||||
|
render: function () {
|
||||||
|
return '<input type="checkbox" class="select-CanvassItem-checkbox" />';
|
||||||
|
},
|
||||||
|
orderable: false,
|
||||||
|
searchable: false
|
||||||
|
},
|
||||||
|
{data: 'supplierName'}],
|
||||||
|
rowCallback: function (row, data) {
|
||||||
|
var statusCell = $('td:eq(9)', row);
|
||||||
|
var myStatus = statusCell.text().trim();
|
||||||
|
if (myStatus === 'Yes' || myStatus === 'true' || myStatus === true) {
|
||||||
|
statusCell.text('Yes').addClass('status-active');
|
||||||
|
} else {
|
||||||
|
statusCell.text('No').addClass('status-partial');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: errorHandler
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -15,7 +15,7 @@
|
|||||||
<script src="~/jsfunctions/common/IndexCard.js"></script>
|
<script src="~/jsfunctions/common/IndexCard.js"></script>
|
||||||
<script src="~/jsfunctions/common/ColumnCommonV2.js"></script>
|
<script src="~/jsfunctions/common/ColumnCommonV2.js"></script>
|
||||||
|
|
||||||
<script src="~/jsfunctions/canvass/CanvassViewV6.js"></script>
|
<script src="~/jsfunctions/canvass/CanvassViewV7.js"></script>
|
||||||
<script src="~/jsfunctions/common/termsV2.js"></script>
|
<script src="~/jsfunctions/common/termsV2.js"></script>
|
||||||
<script src="~/jsfunctions/canvass/PostPutV9.js"></script>
|
<script src="~/jsfunctions/canvass/PostPutV9.js"></script>
|
||||||
<script src="~/jsfunctions/common/PostPutV2.js"></script>
|
<script src="~/jsfunctions/common/PostPutV2.js"></script>
|
||||||
|
|||||||
@ -753,4 +753,338 @@
|
|||||||
font-size: .875rem;
|
font-size: .875rem;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
}
|
}
|
||||||
|
/* ── SUPPLIER MODAL (vis = viewItemSuppliers) ────────── */
|
||||||
|
.vis-dialog {
|
||||||
|
max-width: 92vw;
|
||||||
|
width: 750px;
|
||||||
|
margin: 40px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-content {
|
||||||
|
border: none;
|
||||||
|
border-radius: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 20px 60px rgba(0,0,0,.18), 0 4px 16px rgba(0,0,0,.10);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Header ─────────────────────────────────────────── */
|
||||||
|
.vis-header {
|
||||||
|
background: linear-gradient(135deg, #0d5c63 0%, #0e7c86 55%, #18a8b5 100%);
|
||||||
|
padding: 20px 24px 18px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-header::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none'%3E%3Cg fill='%23ffffff' fill-opacity='0.04'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-header-inner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-header-icon {
|
||||||
|
width: 42px;
|
||||||
|
height: 42px;
|
||||||
|
background: rgba(255,255,255,.15);
|
||||||
|
border-radius: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #fff;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-title {
|
||||||
|
font-family: 'Space Grotesk', sans-serif;
|
||||||
|
font-size: 1.15rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #fff;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-subtitle {
|
||||||
|
font-size: .78rem;
|
||||||
|
color: rgba(255,255,255,.72);
|
||||||
|
margin: 3px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-close {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
background: rgba(255,255,255,.12);
|
||||||
|
border: 1px solid rgba(255,255,255,.2);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: rgba(255,255,255,.85);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background .18s;
|
||||||
|
font-size: .85rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-close:hover {
|
||||||
|
background: rgba(255,255,255,.22);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Body ────────────────────────────────────────────── */
|
||||||
|
.vis-body {
|
||||||
|
padding: 20px 24px 8px;
|
||||||
|
background: #f0f6f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Item info card ──────────────────────────────────── */
|
||||||
|
.vis-item-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid #d6eaec;
|
||||||
|
padding: 14px 18px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
box-shadow: 0 2px 8px rgba(13,92,99,.07);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-item-grid {
|
||||||
|
display: flex;
|
||||||
|
gap: 0;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-item-field {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 160px;
|
||||||
|
padding: 4px 16px 4px 0;
|
||||||
|
border-right: 1px solid #d6eaec;
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-item-field:last-child {
|
||||||
|
border-right: none;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-field-lbl {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
font-size: .67rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: .06em;
|
||||||
|
color: #6b8890;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-field-lbl i {
|
||||||
|
font-size: .65rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-field-val {
|
||||||
|
font-family: 'Space Grotesk', sans-serif;
|
||||||
|
font-size: .95rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #0d5c63;
|
||||||
|
line-height: 1.3;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-sel-count {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
color: #c0392b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Toolbar ─────────────────────────────────────────── */
|
||||||
|
.vis-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Buttons ─────────────────────────────────────────── */
|
||||||
|
.vis-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 7px;
|
||||||
|
padding: 8px 18px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: none;
|
||||||
|
font-family: 'DM Sans', sans-serif;
|
||||||
|
font-size: .84rem;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all .18s;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-btn-primary {
|
||||||
|
background: #0e7c86;
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 2px 8px rgba(14,124,134,.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-btn-primary:hover {
|
||||||
|
background: #0d5c63;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 14px rgba(14,124,134,.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-btn-success {
|
||||||
|
background: #28a745;
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 2px 8px rgba(40,167,69,.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-btn-success:hover {
|
||||||
|
background: #218838;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-btn-ghost {
|
||||||
|
background: #fff;
|
||||||
|
color: #0d5c63;
|
||||||
|
border: 1.5px solid #d6eaec;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-btn-ghost:hover {
|
||||||
|
background: #e6f7f8;
|
||||||
|
border-color: #0e7c86;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Table wrapper ───────────────────────────────────── */
|
||||||
|
.vis-table-wrap {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid #d6eaec;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2px 8px rgba(13,92,99,.07);
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-table {
|
||||||
|
width: 100% !important;
|
||||||
|
font-family: 'DM Sans', sans-serif;
|
||||||
|
font-size: .84rem;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-table thead tr {
|
||||||
|
background: linear-gradient(135deg, #1a3a4a, #1e5468);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-table thead th {
|
||||||
|
color: rgba(255,255,255,.88);
|
||||||
|
font-size: .7rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: .05em;
|
||||||
|
padding: 11px 13px;
|
||||||
|
border-bottom: none !important;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-th-check {
|
||||||
|
width: 40px;
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-table tbody tr {
|
||||||
|
border-bottom: 1px solid #edf2f3;
|
||||||
|
transition: background .12s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-table tbody tr:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-table tbody tr:hover {
|
||||||
|
background: #f0f6f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-table tbody tr.selected-row {
|
||||||
|
background: #e6f7f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vis-table tbody td {
|
||||||
|
padding: 10px 13px;
|
||||||
|
color: #1a2e35;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Checkbox styling ────────────────────────────────── */
|
||||||
|
.vis-checkbox,
|
||||||
|
.vis-table .select-CanvassItem-checkbox,
|
||||||
|
.vis-table .select-all-CanvassItem-checkbox,
|
||||||
|
#selectAllHeaderCheckbox {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
accent-color: #0e7c86;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Footer ──────────────────────────────────────────── */
|
||||||
|
.vis-footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 14px 24px 18px;
|
||||||
|
background: #f0f6f7;
|
||||||
|
border-top: 1px solid #d6eaec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── DataTables overrides to match vis style ─────────── */
|
||||||
|
#viewItemSuppliers .dataTables_wrapper .dataTables_filter input {
|
||||||
|
border: 1.5px solid #d6eaec;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
font-family: 'DM Sans', sans-serif;
|
||||||
|
font-size: .84rem;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color .18s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#viewItemSuppliers .dataTables_wrapper .dataTables_filter input:focus {
|
||||||
|
border-color: #0e7c86;
|
||||||
|
}
|
||||||
|
|
||||||
|
#viewItemSuppliers .dataTables_wrapper .dataTables_info,
|
||||||
|
#viewItemSuppliers .dataTables_wrapper .dataTables_length label {
|
||||||
|
font-family: 'DM Sans', sans-serif;
|
||||||
|
font-size: .82rem;
|
||||||
|
color: #6b8890;
|
||||||
|
}
|
||||||
|
|
||||||
|
#viewItemSuppliers .dataTables_wrapper .dataTables_paginate .paginate_button {
|
||||||
|
border-radius: 6px !important;
|
||||||
|
font-family: 'DM Sans', sans-serif;
|
||||||
|
font-size: .82rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#viewItemSuppliers .dataTables_wrapper .dataTables_paginate .paginate_button.current {
|
||||||
|
background: #0e7c86 !important;
|
||||||
|
border-color: #0e7c86 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#viewItemSuppliers .dataTables_wrapper {
|
||||||
|
padding: 14px 16px 10px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -291,6 +291,8 @@ function viewSuppDetail(data) {
|
|||||||
$('#viewItemSuppliers').modal('show');
|
$('#viewItemSuppliers').modal('show');
|
||||||
$('#viewItemSuppliers').css('z-index', 1065);
|
$('#viewItemSuppliers').css('z-index', 1065);
|
||||||
|
|
||||||
|
console.log('dito diba???');
|
||||||
|
|
||||||
tableName = '#SupplierDataTable';
|
tableName = '#SupplierDataTable';
|
||||||
totalSelectedLabel = $('#totalSelected');
|
totalSelectedLabel = $('#totalSelected');
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user