Compare commits
5 Commits
2b6e022f8b
...
13a1eb204c
| Author | SHA1 | Date |
|---|---|---|
|
|
13a1eb204c | |
|
|
caa2a86519 | |
|
|
7e7a6e0982 | |
|
|
244181bebd | |
|
|
155702b653 |
|
|
@ -1,4 +1,6 @@
|
||||||
using FruitBank.Common.Entities;
|
using DevExpress.Pdf.Native;
|
||||||
|
using FruitBank.Common;
|
||||||
|
using FruitBank.Common.Entities;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
@ -11,6 +13,7 @@ using Nop.Web.Areas.Admin.Controllers;
|
||||||
using Nop.Web.Framework;
|
using Nop.Web.Framework;
|
||||||
using Nop.Web.Framework.Mvc.Filters;
|
using Nop.Web.Framework.Mvc.Filters;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using UglyToad.PdfPig.DocumentLayoutAnalysis.TextExtractor;
|
||||||
|
|
||||||
namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
||||||
{
|
{
|
||||||
|
|
@ -135,34 +138,144 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[RequestSizeLimit(10485760)] // 10MB
|
[RequestSizeLimit(10485760)] // 10MB
|
||||||
[RequestFormLimits(MultipartBodyLengthLimit = 10485760)]
|
[RequestFormLimits(MultipartBodyLengthLimit = 10485760)]
|
||||||
public async Task<IActionResult> UploadFile(UploadModel model)
|
public async Task<IActionResult> UploadFile(List<IFormFile> files, int shippingDocumentId, int? partnerId)
|
||||||
{
|
|
||||||
var files = model.Files;
|
|
||||||
var shippingDocumentId = model.ShippingDocumentId;
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
|
//checks
|
||||||
|
// - files exist
|
||||||
if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL))
|
if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL))
|
||||||
return Json(new { success = false, errorMessage = "Access denied" });
|
return Json(new { success = false, errorMessage = "Access denied" });
|
||||||
|
|
||||||
|
// - permissions
|
||||||
if (files == null || files.Count == 0)
|
if (files == null || files.Count == 0)
|
||||||
return Json(new { success = false, errorMessage = "No file selected" });
|
return Json(new { success = false, errorMessage = "No file selected" });
|
||||||
|
|
||||||
|
// - do we have partnerId - if so, we already have at least one doecument uploaded earlier
|
||||||
|
if (partnerId.HasValue)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Associated with Partner ID: {partnerId.Value}");
|
||||||
|
|
||||||
|
//let's get the partner
|
||||||
|
var partner = await _dbContext.Partners.GetByIdAsync(partnerId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
var filesList = new List<Files>();
|
||||||
|
var shippingDocumentToFileList = new List<ShippingDocumentToFiles>();
|
||||||
|
|
||||||
|
|
||||||
|
//iteratation 1: iterate documents to determine their type by AI
|
||||||
|
|
||||||
|
|
||||||
foreach (var file in files)
|
foreach (var file in files)
|
||||||
{
|
{
|
||||||
// Validate file type (PDF only)
|
|
||||||
|
|
||||||
|
var fileName = file.FileName;
|
||||||
|
var fileSize = file.Length;
|
||||||
|
var dbFile = new Files();
|
||||||
|
string pdfText = "";
|
||||||
|
|
||||||
|
Console.WriteLine($"Received file: {fileName} for Document ID: {shippingDocumentId}");
|
||||||
|
|
||||||
if (!file.ContentType.Equals("application/pdf", StringComparison.OrdinalIgnoreCase))
|
if (!file.ContentType.Equals("application/pdf", StringComparison.OrdinalIgnoreCase))
|
||||||
return Json(new { success = false, errorMessage = "Only PDF files are allowed" });
|
return Json(new { success = false, errorMessage = "Only PDF files are allowed" });
|
||||||
|
|
||||||
// Validate file size (max 10MB)
|
// Validate file size (max 20MB)
|
||||||
if (file.Length > 10 * 1024 * 1024)
|
if (file.Length > 20 * 1024 * 1024)
|
||||||
return Json(new { success = false, errorMessage = "File size must be less than 10MB" });
|
return Json(new { success = false, errorMessage = "File size must be less than 10MB" });
|
||||||
|
|
||||||
|
// - get text extracted from pdf
|
||||||
|
// Validate file type (PDF only)
|
||||||
|
if (file.Length > 0 && file.ContentType == "application/pdf")
|
||||||
|
{
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Open the PDF from the IFormFile's stream directly in memory
|
||||||
|
using (var stream = file.OpenReadStream())
|
||||||
|
using (var pdf = UglyToad.PdfPig.PdfDocument.Open(stream))
|
||||||
|
{
|
||||||
|
// Now you can analyze the PDF content
|
||||||
|
|
||||||
|
foreach (var page in pdf.GetPages())
|
||||||
|
{
|
||||||
|
// Extract text from each page
|
||||||
|
pdfText += ContentOrderTextExtractor.GetText(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For demonstration, let's just log the extracted text
|
||||||
|
Console.WriteLine($"Extracted text from {file.FileName}: {pdfText}");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Handle potential exceptions during PDF processing
|
||||||
|
Console.Error.WriteLine($"Error processing PDF file {file.FileName}: {ex.Message}");
|
||||||
|
return StatusCode(500, $"Error processing PDF file: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string analysisPrompt = "Extract the document identification number from this document, determine the type of the " +
|
||||||
|
"document from the available list, and return them as JSON: documentNumber, documentType. " +
|
||||||
|
$"Available filetypes: {nameof(DocumentType.Invoice)}, {nameof(DocumentType.ShippingDocument)} , {nameof(DocumentType.OrderConfirmation)}, {nameof(DocumentType.Unknown)}" +
|
||||||
|
"If you can't find information of any of these, return null value for that field.";
|
||||||
|
|
||||||
|
//here I can start preparing the file entity
|
||||||
|
var metaAnalyzis = await _aiCalculationService.GetOpenAIPDFAnalysisFromText(pdfText.ToString(), analysisPrompt);
|
||||||
|
|
||||||
|
var extractedMetaData = ParseMetaDataAIResponse(metaAnalyzis);
|
||||||
|
|
||||||
|
if (extractedMetaData.DocumentNumber != null)
|
||||||
|
|
||||||
|
dbFile.RawText = pdfText;
|
||||||
|
dbFile.FileExtension = "pdf";
|
||||||
|
dbFile.FileName = extractedMetaData.DocumentNumber;
|
||||||
|
|
||||||
|
// - IF WE DON'T HAVE PARTNERID ALREADY: read partner information
|
||||||
|
// (check if all 3 refers to the same partner)
|
||||||
|
// save partner information to partners table { Id, Name, TaxId, CertificationNumber, PostalCode, Country, State, County, City, Street }
|
||||||
|
if (partnerId != null)
|
||||||
|
{
|
||||||
|
string partnerAnalysisPrompt = "Extract the sender information from this document, and return them as JSON: name, taxId, certificationNumber, postalCode, country, state, county, city, street. " +
|
||||||
|
"If you can't find information of any of these, return null value for that field.";
|
||||||
|
|
||||||
|
//here I can start preparing the file entity
|
||||||
|
var partnerAnalyzis = await _aiCalculationService.GetOpenAIPDFAnalysisFromText(pdfText.ToString(), partnerAnalysisPrompt);
|
||||||
|
var extractedPartnerData = ParsePartnerDataAIResponse(partnerAnalyzis);
|
||||||
|
|
||||||
|
if (extractedPartnerData.Name != null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("AI Analysis Partner Result:");
|
||||||
|
Console.WriteLine(extractedPartnerData.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extractedPartnerData.TaxId != null)
|
||||||
|
{
|
||||||
|
Console.WriteLine(extractedPartnerData.TaxId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extractedPartnerData.Country != null) {
|
||||||
|
|
||||||
|
Console.WriteLine(extractedPartnerData.Country);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extractedPartnerData.State != null)
|
||||||
|
{
|
||||||
|
Console.WriteLine(extractedPartnerData.State);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// - save the documents to file system - wwwroot/uploads/orders/order-{orderId}/fileId-documentId.pdf
|
||||||
|
// where documentId is the number or id IN the document
|
||||||
|
try
|
||||||
|
{
|
||||||
// Create upload directory if it doesn't exist
|
// Create upload directory if it doesn't exist
|
||||||
var uploadsPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "uploads", "shippingDocuments");
|
var uploadsPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "uploads", "orders", "order" + shippingDocumentId.ToString(), "documents");
|
||||||
Directory.CreateDirectory(uploadsPath);
|
Directory.CreateDirectory(uploadsPath);
|
||||||
|
|
||||||
// Generate unique filename
|
// Generate unique filename
|
||||||
var fileName = $"{Guid.NewGuid()}_{file.FileName}";
|
fileName = $"{Guid.NewGuid()}_{file.FileName}";
|
||||||
var filePath = Path.Combine(uploadsPath, fileName);
|
var filePath = Path.Combine(uploadsPath, fileName);
|
||||||
|
|
||||||
// Save file
|
// Save file
|
||||||
|
|
@ -170,23 +283,30 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
||||||
{
|
{
|
||||||
await file.CopyToAsync(stream);
|
await file.CopyToAsync(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract text with PdfPig
|
|
||||||
var pdfText = new StringBuilder();
|
|
||||||
using (var pdf = UglyToad.PdfPig.PdfDocument.Open(filePath))
|
|
||||||
{
|
|
||||||
foreach (var page in pdf.GetPages())
|
|
||||||
{
|
|
||||||
pdfText.AppendLine(page.Text);
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error saving file: {ex}");
|
||||||
|
//return Json(new { success = false, errorMessage = ex.Message });
|
||||||
|
return BadRequest("No files were uploaded.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log extracted text for debugging
|
|
||||||
Console.WriteLine("Extracted PDF text:");
|
// - create a list of documents to read information and to save the document's information to DB
|
||||||
Console.WriteLine(pdfText.ToString());
|
// - INSIDE ITERATION:
|
||||||
|
// - IF SHIPPINGDOCUMENT: read shipping information
|
||||||
|
// save document information into DB {Id, PartnerId, ShippingId, DocumentIdNumber, ShippingDate, Country, TotalPallets, IsAllMeasured}
|
||||||
|
// - IF Orderdocument: try reading shipping information and financial information
|
||||||
|
// save document information into DB {Id, PartnerId, ShippingId, DocumentIdNumber, ShippingDate, Country, TotalPallets, IsAllMeasured}
|
||||||
|
// - IF Invoice: try reading financial information
|
||||||
|
// save document information into DB {financial db table not created yet}
|
||||||
|
// - save the documents to Files Table - { name, extension, type, rawtext }
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
// Analyze PDF with AI to extract structured data
|
// Analyze PDF with AI to extract structured data
|
||||||
var aiAnalysis = await _aiCalculationService.GetOpenAIPDFAnalyzisFromText(
|
var aiAnalysis = await _aiCalculationService.GetOpenAIPDFAnalysisFromText(
|
||||||
pdfText.ToString(),
|
pdfText.ToString(),
|
||||||
"Extract the following information from this shipping document and return as JSON: documentDate, recipientName, senderName, invoiceNumber, totalAmount, itemCount, notes. If a field is not found, return null for that field."
|
"Extract the following information from this shipping document and return as JSON: documentDate, recipientName, senderName, invoiceNumber, totalAmount, itemCount, notes. If a field is not found, return null for that field."
|
||||||
);
|
);
|
||||||
|
|
@ -224,7 +344,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
||||||
RawAIAnalysis = aiAnalysis // Store the raw AI response for debugging
|
RawAIAnalysis = aiAnalysis // Store the raw AI response for debugging
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// var savedDocument = await _documentService.InsertDocumentAsync(document);
|
// var savedDocument = await _documentService.InsertDocumentAsync(document);
|
||||||
|
|
||||||
|
|
@ -237,7 +357,6 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
||||||
// document = documentModel
|
// document = documentModel
|
||||||
//});
|
//});
|
||||||
|
|
||||||
return Ok($"Files for Shipping Document ID {shippingDocumentId} were uploaded successfully!");
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -245,8 +364,49 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
||||||
//return Json(new { success = false, errorMessage = ex.Message });
|
//return Json(new { success = false, errorMessage = ex.Message });
|
||||||
return BadRequest("No files were uploaded.");
|
return BadRequest("No files were uploaded.");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//iteration 2: iterate documents again
|
||||||
|
// - determine the items listed in the documents by AI
|
||||||
|
// try to fill all shippingitems information from the iteration
|
||||||
|
|
||||||
|
|
||||||
|
return Ok($"Files for Shipping Document ID {shippingDocumentId} were uploaded successfully!");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//[HttpPost]
|
||||||
|
//public async Task<IActionResult> UploadFile(List<IFormFile> files, int shippingDocumentId)
|
||||||
|
//{
|
||||||
|
// if (files == null || files.Count == 0)
|
||||||
|
// {
|
||||||
|
// return BadRequest("No files were uploaded.");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// foreach (var file in files)
|
||||||
|
// {
|
||||||
|
// // Here, you would implement your logic to save the file.
|
||||||
|
// // For example, you can save the file to a specific folder on the server
|
||||||
|
// // or a cloud storage service like Azure Blob Storage.
|
||||||
|
// // You would use the 'shippingDocumentId' to associate the file with the correct entity in your database.
|
||||||
|
|
||||||
|
// // Example: Get file details
|
||||||
|
// var fileName = file.FileName;
|
||||||
|
// var fileSize = file.Length;
|
||||||
|
|
||||||
|
// // Log or process the file and its associated ID.
|
||||||
|
// // For demonstration, let's just return a success message.
|
||||||
|
// Console.WriteLine($"Received file: {fileName} for Document ID: {shippingDocumentId}");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Return a success response. The DevExtreme FileUploader expects a 200 OK status.
|
||||||
|
// return Ok(new { message = "Files uploaded successfully." });
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
private ExtractedDocumentData ParseShippingDocumentAIResponse(string aiResponse)
|
private ExtractedDocumentData ParseShippingDocumentAIResponse(string aiResponse)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -268,6 +428,79 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ExtractedDocumentMetaData ParseMetaDataAIResponse(string aiResponse)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Try to parse as JSON first
|
||||||
|
var data = System.Text.Json.JsonSerializer.Deserialize<ExtractedDocumentMetaData>(
|
||||||
|
aiResponse,
|
||||||
|
new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true }
|
||||||
|
);
|
||||||
|
return data ?? new ExtractedDocumentMetaData();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// If JSON parsing fails, return empty data with the raw response in notes
|
||||||
|
return new ExtractedDocumentMetaData
|
||||||
|
{
|
||||||
|
DocumentNumber = $"Unknown",
|
||||||
|
DocumentType = "Unknown"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExtractedPartnerData ParsePartnerDataAIResponse(string aiResponse)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Try to parse as JSON first
|
||||||
|
var data = System.Text.Json.JsonSerializer.Deserialize<ExtractedPartnerData>(
|
||||||
|
aiResponse,
|
||||||
|
new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true }
|
||||||
|
);
|
||||||
|
return data ?? new ExtractedPartnerData();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// If JSON parsing fails, return empty data with the raw response in notes
|
||||||
|
return new ExtractedPartnerData
|
||||||
|
{
|
||||||
|
Name = "Unknown",
|
||||||
|
CertificationNumber = "Unknown",
|
||||||
|
TaxId = "Unknown",
|
||||||
|
PostalCode = "Unknown",
|
||||||
|
Country = "Unknown",
|
||||||
|
State = "Unknown",
|
||||||
|
County = "Unknown",
|
||||||
|
City = "Unknown",
|
||||||
|
Street = "Unknown",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ExtractedDocumentMetaData
|
||||||
|
{
|
||||||
|
public string DocumentNumber { get; set; }
|
||||||
|
public string DocumentType { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ExtractedPartnerData
|
||||||
|
{
|
||||||
|
//Name, TaxId, CertificationNumber, PostalCode, Country, State, County, City, Street,
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string TaxId { get; set; }
|
||||||
|
public string CertificationNumber { get; set; }
|
||||||
|
public string PostalCode { get; set; }
|
||||||
|
public string Country { get; set; }
|
||||||
|
public string State { get; set; }
|
||||||
|
public string County { get; set; }
|
||||||
|
public string City { get; set; }
|
||||||
|
public string Street { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Helper class for extracted data
|
// Helper class for extracted data
|
||||||
private class ExtractedDocumentData
|
private class ExtractedDocumentData
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -329,7 +329,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
||||||
Console.WriteLine(pdfText.ToString());
|
Console.WriteLine(pdfText.ToString());
|
||||||
|
|
||||||
// Analyze PDF with AI to extract structured data
|
// Analyze PDF with AI to extract structured data
|
||||||
var aiAnalysis = await _aiCalculationService.GetOpenAIPDFAnalyzisFromText(
|
var aiAnalysis = await _aiCalculationService.GetOpenAIPDFAnalysisFromText(
|
||||||
pdfText.ToString(),
|
pdfText.ToString(),
|
||||||
"Extract the following information from this shipping document and return as JSON: documentDate, recipientName, senderName, invoiceNumber, totalAmount, itemCount, notes. If a field is not found, return null for that field."
|
"Extract the following information from this shipping document and return as JSON: documentDate, recipientName, senderName, invoiceNumber, totalAmount, itemCount, notes. If a field is not found, return null for that field."
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
using Nop.Web.Areas.Admin.Models.Catalog;
|
||||||
|
using Nop.Web.Framework.Models;
|
||||||
|
using Nop.Web.Framework.Mvc.ModelBinding;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models.Catalog
|
||||||
|
{
|
||||||
|
public partial record ProductModel : BaseNopEntityModel
|
||||||
|
{
|
||||||
|
// ... existing properties ...
|
||||||
|
|
||||||
|
[NopResourceDisplayName("Admin.Catalog.Products.Fields.IsMeasurable")]
|
||||||
|
public bool IsMeasurable { get; set; }
|
||||||
|
|
||||||
|
// ... rest of your model ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -340,7 +340,7 @@
|
||||||
Width = "80"
|
Width = "80"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
gridModel.ColumnCollection.Add(new ColumnProperty(nameof(OrderModelExtended.NeedsMeasurement))
|
gridModel.ColumnCollection.Add(new ColumnProperty(nameof(OrderModelExtended.IsMeasurable))
|
||||||
{
|
{
|
||||||
Title = "Needs Measurement",
|
Title = "Needs Measurement",
|
||||||
Width = "100",
|
Width = "100",
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,9 @@
|
||||||
)
|
)
|
||||||
|
|
||||||
<input type="hidden" name="ShippingDocumentId" value="<%- data.Id %>" />
|
<input type="hidden" name="ShippingDocumentId" value="<%- data.Id %>" />
|
||||||
|
<% if (data.PartnerId) { %>
|
||||||
|
<input type="hidden" name="PartnerId" value="<%- data.PartnerId %>" />
|
||||||
|
<% } %>
|
||||||
|
|
||||||
@(Html.DevExtreme().Button()
|
@(Html.DevExtreme().Button()
|
||||||
.Text("Upload Files")
|
.Text("Upload Files")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
// File: Plugins/YourCompany.ProductAttributes/Components/ProductAttributesViewComponent.cs
|
||||||
|
|
||||||
|
using FruitBank.Common.Interfaces;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Nop.Core;
|
||||||
|
using Nop.Core.Domain.Catalog;
|
||||||
|
using Nop.Plugin.Misc.FruitBankPlugin.Models;
|
||||||
|
using Nop.Services.Common;
|
||||||
|
using Nop.Web.Areas.Admin.Models.Catalog;
|
||||||
|
using Nop.Web.Framework.Components;
|
||||||
|
|
||||||
|
namespace Nop.Plugin.Misc.FruitBankPlugin.Components
|
||||||
|
{
|
||||||
|
[ViewComponent(Name = "ProductAttributes")]
|
||||||
|
public class ProductAttributesViewComponent : NopViewComponent
|
||||||
|
{
|
||||||
|
private const string NET_WEIGHT_KEY = nameof(IMeasuringAttributeValues.NetWeight);
|
||||||
|
private const string GROSS_WEIGHT_KEY = nameof(IMeasuringAttributeValues.GrossWeight);
|
||||||
|
private const string IS_MEASURABLE_KEY = nameof(IMeasuringAttributeValues.IsMeasurable);
|
||||||
|
|
||||||
|
private readonly IGenericAttributeService _genericAttributeService;
|
||||||
|
private readonly IWorkContext _workContext;
|
||||||
|
private readonly IStoreContext _storeContext;
|
||||||
|
|
||||||
|
public ProductAttributesViewComponent(
|
||||||
|
IGenericAttributeService genericAttributeService,
|
||||||
|
IWorkContext workContext,
|
||||||
|
IStoreContext storeContext)
|
||||||
|
{
|
||||||
|
_genericAttributeService = genericAttributeService;
|
||||||
|
_workContext = workContext;
|
||||||
|
_storeContext = storeContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IViewComponentResult> InvokeAsync(string widgetZone, object additionalData)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
if (additionalData is not ProductModel product)
|
||||||
|
return Content("");
|
||||||
|
|
||||||
|
var model = new ProductAttributesModel
|
||||||
|
{
|
||||||
|
ProductId = product.Id
|
||||||
|
};
|
||||||
|
|
||||||
|
//get store scope
|
||||||
|
var storeScope = await _storeContext.GetCurrentStoreAsync();
|
||||||
|
|
||||||
|
|
||||||
|
if (product.Id > 0)
|
||||||
|
{
|
||||||
|
// Load existing values
|
||||||
|
var dbProduct = new Core.Domain.Catalog.Product { Id = product.Id };
|
||||||
|
|
||||||
|
//var dbMesaurable = await _genericAttributeService.GetAttributeAsync<bool>(dbProduct, IS_MEASURABLE_KEY, storeScope.Id);
|
||||||
|
//var dbNetWeight = await _genericAttributeService.GetAttributeAsync<decimal?>(dbProduct, NET_WEIGHT_KEY, storeScope.Id);
|
||||||
|
//var dbIncomingQuantity = await _genericAttributeService.GetAttributeAsync<decimal?>(dbProduct, "IncomingQuantity", storeScope.Id);
|
||||||
|
|
||||||
|
model.IsMeasurable = await _genericAttributeService
|
||||||
|
.GetAttributeAsync<bool>(dbProduct, IS_MEASURABLE_KEY, storeScope.Id);
|
||||||
|
|
||||||
|
model.NetWeight = await _genericAttributeService
|
||||||
|
.GetAttributeAsync<decimal?>(dbProduct, NET_WEIGHT_KEY, storeScope.Id);
|
||||||
|
|
||||||
|
model.IncomingQuantity = await _genericAttributeService
|
||||||
|
.GetAttributeAsync<int?>(dbProduct, "IncomingQuantity", storeScope.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return View("~/Plugins/Misc.FruitBankPlugin/Views/ProductAttributes.cshtml", model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -31,4 +31,8 @@ public class PartnerDbTable : MgDbTableBase<Partner>
|
||||||
|
|
||||||
public Task<Partner> GetByIdAsync(int id, bool loadRelations)
|
public Task<Partner> GetByIdAsync(int id, bool loadRelations)
|
||||||
=> GetAll(loadRelations).FirstOrDefaultAsync(p => p.Id == id);
|
=> GetAll(loadRelations).FirstOrDefaultAsync(p => p.Id == id);
|
||||||
|
|
||||||
|
|
||||||
|
public Task<Partner> GetByIdNameAsync(string name, bool loadRelations)
|
||||||
|
=> GetAll(loadRelations).FirstOrDefaultAsync(p => p.Name == name);
|
||||||
}
|
}
|
||||||
|
|
@ -18,7 +18,7 @@ using Nop.Services.Events;
|
||||||
|
|
||||||
namespace Nop.Plugin.Misc.FruitBankPlugin.Domains.EventConsumers;
|
namespace Nop.Plugin.Misc.FruitBankPlugin.Domains.EventConsumers;
|
||||||
|
|
||||||
public class FruitBankEventConsumer(IHttpContextAccessor httpContextAccessor, FruitBankDbContext ctx, FruitBankAttributeService fruitBankAttributeService, IStoreContext storeContext, IEnumerable<IAcLogWriterBase> logWriters) :
|
public class FruitBankEventConsumer(IHttpContextAccessor httpContextAccessor, FruitBankDbContext ctx, FruitBankAttributeService fruitBankAttributeService, IStoreContext storeContext, IEnumerable<IAcLogWriterBase> logWriters, IGenericAttributeService genericAttributeService) :
|
||||||
MgEventConsumer(httpContextAccessor, logWriters),
|
MgEventConsumer(httpContextAccessor, logWriters),
|
||||||
IConsumer<EntityDeletedEvent<Shipping>>,
|
IConsumer<EntityDeletedEvent<Shipping>>,
|
||||||
IConsumer<EntityInsertedEvent<ShippingItem>>,
|
IConsumer<EntityInsertedEvent<ShippingItem>>,
|
||||||
|
|
@ -34,6 +34,9 @@ public class FruitBankEventConsumer(IHttpContextAccessor httpContextAccessor, Fr
|
||||||
public override async Task HandleEventAsync(EntityUpdatedEvent<Product> eventMessage)
|
public override async Task HandleEventAsync(EntityUpdatedEvent<Product> eventMessage)
|
||||||
{
|
{
|
||||||
var product = eventMessage.Entity;
|
var product = eventMessage.Entity;
|
||||||
|
|
||||||
|
await SaveCustomAttributesAsync(eventMessage.Entity);
|
||||||
|
|
||||||
var isMeasurableProduct = await fruitBankAttributeService.IsMeasurableEntityAsync<Product>(product.Id);
|
var isMeasurableProduct = await fruitBankAttributeService.IsMeasurableEntityAsync<Product>(product.Id);
|
||||||
|
|
||||||
var shippingItems = await ctx.ShippingItems.Table
|
var shippingItems = await ctx.ShippingItems.Table
|
||||||
|
|
@ -47,6 +50,53 @@ public class FruitBankEventConsumer(IHttpContextAccessor httpContextAccessor, Fr
|
||||||
await base.HandleEventAsync(eventMessage);
|
await base.HandleEventAsync(eventMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task HandleEventAsync(EntityInsertedEvent<Product> eventMessage)
|
||||||
|
{
|
||||||
|
await SaveCustomAttributesAsync(eventMessage.Entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveCustomAttributesAsync(Product product)
|
||||||
|
{
|
||||||
|
if (product == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var form = httpContextAccessor.HttpContext?.Request?.Form;
|
||||||
|
if (form == null || !form.Any())
|
||||||
|
return;
|
||||||
|
|
||||||
|
var isMeasurable = form["IsMeasurable"].ToString().Contains("true");
|
||||||
|
|
||||||
|
// Save IsMeasurable
|
||||||
|
if (form.ContainsKey("IsMeasurable"))
|
||||||
|
{
|
||||||
|
await genericAttributeService.SaveAttributeAsync(product, "IsMeasurable", isMeasurable);
|
||||||
|
//Akkor ez kell? - Á.
|
||||||
|
//await fruitBankAttributeService.InsertOrUpdateMeasuringAttributeValuesAsync<Product>(product.Id, 0, 0, isMeasurable, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save NetWeight
|
||||||
|
if (form.ContainsKey("NetWeight"))
|
||||||
|
{
|
||||||
|
var netWeightStr = form["NetWeight"].ToString();
|
||||||
|
if (!string.IsNullOrWhiteSpace(netWeightStr) && decimal.TryParse(netWeightStr, out var netWeight))
|
||||||
|
{
|
||||||
|
await genericAttributeService.SaveAttributeAsync(product, "NetWeight", netWeight);
|
||||||
|
//Akkor ez kell? - Á.
|
||||||
|
//await fruitBankAttributeService.InsertOrUpdateMeasuringAttributeValuesAsync<Product>(product.Id, 0, 0, , false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save IncomingQuantity
|
||||||
|
if (form.ContainsKey("IncomingQuantity"))
|
||||||
|
{
|
||||||
|
var incomingQtyStr = form["IncomingQuantity"].ToString();
|
||||||
|
if (!string.IsNullOrWhiteSpace(incomingQtyStr) && int.TryParse(incomingQtyStr, out var incomingQuantity))
|
||||||
|
{
|
||||||
|
await genericAttributeService.SaveAttributeAsync(product, "IncomingQuantity", incomingQuantity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task HandleEventAsync(EntityInsertedEvent<ShippingItemPallet> eventMessage)
|
public async Task HandleEventAsync(EntityInsertedEvent<ShippingItemPallet> eventMessage)
|
||||||
{
|
{
|
||||||
Logger.Info($"HandleEventAsync EntityInsertedEvent<ShippingItemPallet>; id: {eventMessage.Entity.Id}");
|
Logger.Info($"HandleEventAsync EntityInsertedEvent<ShippingItemPallet>; id: {eventMessage.Entity.Id}");
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Factories
|
||||||
{
|
{
|
||||||
var extendedOrder = new OrderModelExtended();
|
var extendedOrder = new OrderModelExtended();
|
||||||
CopyModelHelper.CopyPublicProperties(order, extendedOrder);
|
CopyModelHelper.CopyPublicProperties(order, extendedOrder);
|
||||||
extendedOrder.NeedsMeasurement = await ShouldMarkAsNeedsMeasurementAsync(order);
|
extendedOrder.IsMeasurable = await ShouldMarkAsNeedsMeasurementAsync(order);
|
||||||
Console.WriteLine(extendedOrder.Id);
|
Console.WriteLine(extendedOrder.Id);
|
||||||
extendedRows.Add(extendedOrder);
|
extendedRows.Add(extendedOrder);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin
|
||||||
|
|
||||||
public Task<IList<string>> GetWidgetZonesAsync()
|
public Task<IList<string>> GetWidgetZonesAsync()
|
||||||
{
|
{
|
||||||
return Task.FromResult<IList<string>>(new List<string> { PublicWidgetZones.ProductBoxAddinfoBefore, PublicWidgetZones.ProductDetailsBottom });
|
return Task.FromResult<IList<string>>(new List<string> { PublicWidgetZones.ProductBoxAddinfoBefore, PublicWidgetZones.ProductDetailsBottom, AdminWidgetZones.ProductDetailsBlock });
|
||||||
}
|
}
|
||||||
|
|
||||||
//public string GetWidgetViewComponentName(string widgetZone)
|
//public string GetWidgetViewComponentName(string widgetZone)
|
||||||
|
|
@ -128,7 +128,20 @@ namespace Nop.Plugin.Misc.FruitBankPlugin
|
||||||
|
|
||||||
var zones = GetWidgetZonesAsync().Result;
|
var zones = GetWidgetZonesAsync().Result;
|
||||||
|
|
||||||
|
if (zones.Any(widgetZone.Equals))
|
||||||
|
{
|
||||||
|
if (widgetZone == PublicWidgetZones.ProductBoxAddinfoBefore || widgetZone == PublicWidgetZones.ProductDetailsBottom)
|
||||||
|
{
|
||||||
return zones.Any(widgetZone.Equals) ? typeof(ProductAIWidgetViewComponent) : null;
|
return zones.Any(widgetZone.Equals) ? typeof(ProductAIWidgetViewComponent) : null;
|
||||||
}
|
}
|
||||||
|
else if (widgetZone == AdminWidgetZones.ProductDetailsBlock)
|
||||||
|
{
|
||||||
|
return zones.Any(widgetZone.Equals) ? typeof(ProductAttributesViewComponent) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Models
|
||||||
{
|
{
|
||||||
public partial record OrderModelExtended : OrderModel
|
public partial record OrderModelExtended : OrderModel
|
||||||
{
|
{
|
||||||
public bool NeedsMeasurement { get; set; }
|
public bool IsMeasurable { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
using Nop.Web.Framework.Models;
|
||||||
|
using Nop.Web.Framework.Mvc.ModelBinding;
|
||||||
|
|
||||||
|
namespace Nop.Plugin.Misc.FruitBankPlugin.Models
|
||||||
|
{
|
||||||
|
public record ProductAttributesModel : BaseNopModel
|
||||||
|
{
|
||||||
|
public int ProductId { get; set; }
|
||||||
|
|
||||||
|
[NopResourceDisplayName("Plugins.YourCompany.ProductAttributes.Fields.IsMeasurable")]
|
||||||
|
public bool IsMeasurable { get; set; }
|
||||||
|
|
||||||
|
[NopResourceDisplayName("Plugins.YourCompany.ProductAttributes.Fields.NetWeight")]
|
||||||
|
public decimal? NetWeight { get; set; }
|
||||||
|
|
||||||
|
[NopResourceDisplayName("Plugins.YourCompany.ProductAttributes.Fields.IncomingQuantity")]
|
||||||
|
public int? IncomingQuantity { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -605,6 +605,12 @@
|
||||||
<None Update="Views\ProductAIListWidget.cshtml">
|
<None Update="Views\ProductAIListWidget.cshtml">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
<None Update="Views\ProductAttributes.cshtml">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Views\ProductList.cshtml">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
<None Update="Views\ProductAIWidget.cshtml">
|
<None Update="Views\ProductAIWidget.cshtml">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
|
|
||||||
|
|
@ -37,9 +37,19 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetOpenAIPDFAnalyzisFromText(string pdfText, string userQuestion)
|
//public async Task<string> GetOpenAIPDFAnalyzisFromText(string pdfText, string userQuestion)
|
||||||
|
//{
|
||||||
|
// string systemMessage = $"You are a helpful assistant of a webshop, you work in the administration area, with the ADMIN user. The ADMIN user is asking you questions about a PDF document, that you have access to. The content of the PDF document is the following: {pdfText}";
|
||||||
|
// var response = await _openAIApiService.GetSimpleResponseAsync(systemMessage, userQuestion);
|
||||||
|
|
||||||
|
// var fixedResponse = TextHelper.FixJsonWithoutAI(response);
|
||||||
|
|
||||||
|
// return fixedResponse;
|
||||||
|
//}
|
||||||
|
|
||||||
|
public async Task<string> GetOpenAIPDFAnalysisFromText(string pdfText, string userQuestion)
|
||||||
{
|
{
|
||||||
string systemMessage = $"You are a helpful assistant of a webshop, you work in the administration area, with the ADMIN user. The ADMIN user is asking you questions about a PDF document, that you have access to. The content of the PDF document is the following: {pdfText}";
|
string systemMessage = $"You are a pdf analyzis assistant, the user is asking you questions about a PDF document, that you have access to. The content of the PDF document is the following: {pdfText}";
|
||||||
var response = await _openAIApiService.GetSimpleResponseAsync(systemMessage, userQuestion);
|
var response = await _openAIApiService.GetSimpleResponseAsync(systemMessage, userQuestion);
|
||||||
|
|
||||||
var fixedResponse = TextHelper.FixJsonWithoutAI(response);
|
var fixedResponse = TextHelper.FixJsonWithoutAI(response);
|
||||||
|
|
@ -47,7 +57,15 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
||||||
return fixedResponse;
|
return fixedResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//public async Task<string> GetOpenAIPartnerInfoFromText(string pdfText, string userQuestion)
|
||||||
|
//{
|
||||||
|
// string systemMessage = $"You are a pdf analyzis assistant, the user is asking you questions about a PDF document, that you have access to. The content of the PDF document is the following: {pdfText}";
|
||||||
|
// var response = await _openAIApiService.GetSimpleResponseAsync(systemMessage, userQuestion);
|
||||||
|
|
||||||
|
// var fixedResponse = TextHelper.FixJsonWithoutAI(response);
|
||||||
|
|
||||||
|
// return fixedResponse;
|
||||||
|
//}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using FruitBank.Common.Interfaces;
|
||||||
using Nop.Core;
|
using Nop.Core;
|
||||||
using Nop.Core.Domain.Catalog;
|
using Nop.Core.Domain.Catalog;
|
||||||
using Nop.Core.Domain.Orders;
|
using Nop.Core.Domain.Orders;
|
||||||
|
using Nop.Plugin.Misc.FruitBankPlugin.Models;
|
||||||
using Nop.Services.Catalog;
|
using Nop.Services.Catalog;
|
||||||
using Nop.Services.Common;
|
using Nop.Services.Common;
|
||||||
using Nop.Services.Events;
|
using Nop.Services.Events;
|
||||||
|
|
@ -113,7 +114,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
||||||
var store = await _storeContext.GetCurrentStoreAsync();
|
var store = await _storeContext.GetCurrentStoreAsync();
|
||||||
// itt adjuk hozzá a GenericAttribute flag-et az orderhez
|
// itt adjuk hozzá a GenericAttribute flag-et az orderhez
|
||||||
await _genericAttributeService.SaveAttributeAsync(order,
|
await _genericAttributeService.SaveAttributeAsync(order,
|
||||||
"PendingMeasurement", true, store.Id);
|
nameof(OrderModelExtended.IsMeasurable), true, store.Id);
|
||||||
// status pending
|
// status pending
|
||||||
// paymentstatus pending
|
// paymentstatus pending
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Nop.Core.Domain.Orders;
|
using Nop.Core.Domain.Orders;
|
||||||
|
using Nop.Plugin.Misc.FruitBankPlugin.Models;
|
||||||
using Nop.Services.Common;
|
using Nop.Services.Common;
|
||||||
|
|
||||||
namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
||||||
|
|
@ -23,7 +24,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return await _genericAttributeService.GetAttributeAsync<bool>(
|
return await _genericAttributeService.GetAttributeAsync<bool>(
|
||||||
order, "PendingMeasurement", order.StoreId);
|
order, nameof(OrderModelExtended.IsMeasurable), order.StoreId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
@* File: Plugins/Nop.Plugin.YourCompany.ProductAttributes/Views/ProductCustomAttributes.cshtml *@
|
||||||
|
|
||||||
|
@model Nop.Plugin.Misc.FruitBankPlugin.Models.ProductAttributesModel
|
||||||
|
|
||||||
|
<div class="card card-default">
|
||||||
|
<div class="card-header">
|
||||||
|
<i class="fas fa-tags"></i>
|
||||||
|
Custom Product Attributes
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<nop-label asp-for="IsMeasurable" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<nop-editor asp-for="IsMeasurable" />
|
||||||
|
<span asp-validation-for="IsMeasurable"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<nop-label asp-for="NetWeight" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<nop-editor asp-for="NetWeight" />
|
||||||
|
<span asp-validation-for="NetWeight"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<nop-label asp-for="IncomingQuantity" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<nop-editor asp-for="IncomingQuantity" />
|
||||||
|
<span asp-validation-for="IncomingQuantity"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
@* File: Plugins/YourCompany.ProductAttributes/Views/ProductList.cshtml *@
|
||||||
|
@* This view component will inject into the product list page *@
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function () {
|
||||||
|
// Wait for the DataTable to be initialized
|
||||||
|
console.log('custom product list JS triggered');
|
||||||
|
var checkDataTable = setInterval(function() {
|
||||||
|
if (typeof $('#products-grid').DataTable !== 'undefined') {
|
||||||
|
clearInterval(checkDataTable);
|
||||||
|
customizeProductGrid();
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
|
||||||
|
function customizeProductGrid() {
|
||||||
|
var table = $('#products-grid').DataTable();
|
||||||
|
|
||||||
|
// Add custom columns after initialization
|
||||||
|
if (table.column('ismeasurable:name').length === 0) {
|
||||||
|
table.column.add([
|
||||||
|
{
|
||||||
|
data: 'CustomProperties.IsMeasurable',
|
||||||
|
title: 'Measurable',
|
||||||
|
width: '80px',
|
||||||
|
render: function (data, type, row) {
|
||||||
|
return data ? '<i class="fas fa-check true-icon"></i>' : '<i class="fas fa-times false-icon"></i>';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: 'CustomProperties.NetWeight',
|
||||||
|
title: 'Net Weight (kg)',
|
||||||
|
width: '100px'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: 'CustomProperties.IncomingQuantity',
|
||||||
|
title: 'Incoming Qty',
|
||||||
|
width: '100px'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.true-icon {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.false-icon {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
Loading…
Reference in New Issue