import excel na novo z izpiso napak

This commit is contained in:
2026-02-28 12:25:39 +01:00
parent 3d4bb077cc
commit 1da4b1e751
5 changed files with 301 additions and 250 deletions

View File

@@ -7,65 +7,200 @@
Layout = "~/Pages/Layouts/_Layout.cshtml";
}
<form method="post">
<h4 class="d-flex justify-content-between align-items-center w-100 font-weight-bold py-1 mb-4">
<h4 class="d-flex justify-content-between align-items-center w-100 font-weight-bold py-1 mb-4">
<span>
<span class="text-muted font-weight-light">Projekt /</span> Uvoz pozicij dela projekta
</span>
</h4>
</h4>
<div class="row">
<div class="row">
<div class="col-6">
<div class="card">
<!-- Step 1: Upload -->
<div id="step-upload">
<h6 class="card-header">
Povezovanje @Model.FileName z pozicijo
Podatki pozicije
</h6>
<div class="card-body">
<input type="hidden" asp-for="IdProjectPart" />
<input type="hidden" asp-for="IdProject" />
<input type="hidden" asp-for="FileName" />
<input type="hidden" asp-for="SelectedItems" name="selectedItems" class="inp-selected-items" />
<div class="row">
<div class="col-12">
@{
foreach (var item in Model.ExcelItems)
{
<div class="form-group">
<label class="control-label">@item.Name</label>
<select data-index="@item.CellIndex" class="form-control select-item" asp-items="ViewBag.ProjectPartItems"></select>
<input type="file" id="fileInput" name="postedFiles" />
</div>
}
}
</div>
</div>
</div>
<div class="card-footer py-3 text-right">
<button type="submit" class="btn btn-primary" onclick="prepareData(); return true;">Potrdi</button>
<a asp-page="Edit" asp-route-id="IdProject" class="btn btn-default">Prekliči</a>
</div>
</div>
<button type="button" id="btnUpload" class="btn btn-primary">Naloži excel</button>
<a asp-page="Edit" asp-route-id="@Model.IdProject" class="btn btn-default">Prekliči</a>
</div>
</div>
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<!-- Step 2: Mapping -->
<div id="step-mapping" style="display: none;">
<h6 class="card-header">
Povezovanje <span id="mappingFileName"></span> z pozicijo
</h6>
<div class="card-body">
<div class="row">
<div class="col-12" id="mappingFields">
</div>
</div>
</div>
<div class="card-footer py-3 text-right">
<button type="button" id="btnImport" class="btn btn-primary">Potrdi</button>
<a asp-page="Edit" asp-route-id="@Model.IdProject" class="btn btn-default">Prekliči</a>
</div>
</div>
</div>
</div>
<div class="col-6">
<div id="validation-errors" style="display: none;">
<div class="card border-danger">
<h6 class="card-header bg-danger text-white">Napake pri uvozu</h6>
<div class="card-body p-0">
<table class="table table-sm table-bordered m-0">
<thead>
<tr>
<th>Vrstica</th>
<th>Polje</th>
<th>Vrednost</th>
<th>Napaka</th>
</tr>
</thead>
<tbody id="validation-errors-body">
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</form>
@Html.AntiForgeryToken()
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script type="text/javascript">
function prepareData(){
let data = "";
$('.select-item').each(function(index, element) {
let value = $(element).val();
if (value !== '') {
let dataIndex = $(element).attr("data-index");
data += `${dataIndex};${value}#`;
var uploadedFileName = '';
$('#btnUpload').click(function () {
var fileInput = document.getElementById('fileInput');
if (fileInput.files.length === 0) {
Swal.fire('Izberite datoteko.');
return;
}
var formData = new FormData();
formData.append('postedFiles', fileInput.files[0]);
formData.append('idProject', '@Model.IdProject');
formData.append('idProjectPart', '@Model.IdProjectPart');
$.blockUI();
$.ajax({
type: "POST",
beforeSend: function (xhr) {
xhr.setRequestHeader("XSRF-TOKEN",
$('input:hidden[name="__RequestVerificationToken"]').val());
},
url: "CreatePartItemImportExcel?handler=Upload",
data: formData,
processData: false,
contentType: false,
success: function (data) {
$.unblockUI();
if (data.successful) {
uploadedFileName = data.fileName;
$('#mappingFileName').text(data.fileName);
var container = $('#mappingFields');
container.empty();
$.each(data.excelItems, function (i, item) {
var group = $('<div class="form-group"></div>');
group.append('<label class="control-label">' + item.name + '</label>');
var select = $('<select class="form-control select-item" data-index="' + item.cellIndex + '"></select>');
select.append('<option value="">Ni izbrano</option>');
$.each(data.mappingOptions, function (j, opt) {
select.append('<option value="' + opt.name + '">' + opt.display + '</option>');
});
group.append(select);
container.append(group);
});
$('#step-upload').hide();
$('#step-mapping').show();
} else {
Swal.fire('Napaka', data.error, 'error');
}
},
error: function (xhr) {
$.unblockUI();
console.log(xhr);
alert(xhr.responseText);
}
});
$('.inp-selected-items').val(data);
});
$('#btnImport').click(function () {
var data = "";
$('.select-item').each(function (index, element) {
var value = $(element).val();
if (value !== '') {
var dataIndex = $(element).attr("data-index");
data += dataIndex + ";" + value + "#";
}
});
if (data === '') {
Swal.fire('Izberite vsaj eno polje.');
return;
}
$.blockUI();
$.ajax({
type: "POST",
beforeSend: function (xhr) {
xhr.setRequestHeader("XSRF-TOKEN",
$('input:hidden[name="__RequestVerificationToken"]').val());
},
url: "CreatePartItemImportExcel?handler=Import",
data: {
selectedItems: data,
fileName: uploadedFileName,
idProjectPart: '@Model.IdProjectPart',
idProject: '@Model.IdProject'
},
success: function (data) {
$.unblockUI();
if (data.successful) {
window.location.href = data.redirectUrl;
} else if (data.validationErrors && data.validationErrors.length > 0) {
var tbody = $('#validation-errors-body');
tbody.empty();
$.each(data.validationErrors, function (i, err) {
tbody.append('<tr>' +
'<td>' + err.row + '</td>' +
'<td>' + err.property + '</td>' +
'<td><code>' + (err.value || '') + '</code></td>' +
'<td class="text-danger">' + err.message + '</td>' +
'</tr>');
});
$('#validation-errors').show();
Swal.fire('Napaka pri uvozu', 'Podatki vsebujejo ' + data.validationErrors.length + ' napak. Preglejte tabelo napak.', 'error');
} else {
Swal.fire('Napaka', data.error, 'error');
}
},
error: function (xhr) {
$.unblockUI();
console.log(xhr);
alert(xhr.responseText);
}
});
});
</script>
}

View File

@@ -48,31 +48,48 @@ namespace EveryThing.Pages.Projects
public string SelectedItems { get; set; }
public IActionResult OnGet(int idProject, int idProjectPart, string fileName)
public IActionResult OnGet(int idProject, int idProjectPart)
{
var user = _userManager.GetUserAsync(User).Result;
IdProject = idProject;
IdProjectPart = idProjectPart;
FileName = fileName;
var tmpList = typeof(Models.Project.ProjectPartItem).GetProperties()
.Where(x => x.GetCustomAttributes(true).Length > 0 && x.GetCustomAttributes(true).Any(y => y.GetType() == typeof(System.ComponentModel.DataAnnotations.DisplayAttribute)))
.Select(x => new
{
Name = x.Name,
Display = ((System.ComponentModel.DataAnnotations.DisplayAttribute)x.GetCustomAttributes(true).First(y => y.GetType() == typeof(System.ComponentModel.DataAnnotations.DisplayAttribute))).Name
}).ToList();
tmpList.Insert(0, new { Name = "", Display = "Ni izbrano" });
ViewData["ProjectPartItems"] = new SelectList(tmpList, "Name", "Display");
ExcelItems = new List<ExcelItem>();
var path = Path.Combine(_hostingEnvironment.WebRootPath, "Uploads", "TempExcelImport", fileName);
var xlWorkbook = new XLWorkbook(path);
return Page();
}
//ONLY FIRST LIST
public async Task<IActionResult> OnPostUpload(int idProject, int idProjectPart, List<IFormFile> postedFiles)
{
if (postedFiles == null
|| postedFiles.Count != 1)
{
return new JsonResult(new { successful = false, error = "Izberite eno datoteko." });
}
var path = Path.Combine(_hostingEnvironment.WebRootPath, "Uploads", "TempExcelImport");
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
else
{
//Pocistimo mapo
foreach (var fileInfo in new DirectoryInfo(path).GetFiles("*.*"))
{
fileInfo.Delete();
}
}
var postedFile = postedFiles[0];
var fileName = postedFile.FileName;
await using (var stream = new FileStream(Path.Combine(path, fileName), FileMode.Create))
{
await postedFile.CopyToAsync(stream);
}
// Read excel headers
var excelItems = new List<ExcelItem>();
var filePath = Path.Combine(path, fileName);
var xlWorkbook = new XLWorkbook(filePath);
var worksheet = xlWorkbook.Worksheet(1);
int i = 1;
@@ -82,26 +99,36 @@ namespace EveryThing.Pages.Projects
int j = 1;
while (!row.Cell(j).IsEmpty())
{
var cellData = row.Cell(j).Value;
ExcelItems.Add(new ExcelItem
excelItems.Add(new ExcelItem
{
CellIndex = j,
Name = cellData.ToString(),
Name = row.Cell(j).Value.ToString(),
});
j++;
}
}
return Page();
// Build mapping options
var mappingOptions = typeof(Models.Project.ProjectPartItem).GetProperties()
.Where(x => x.GetCustomAttributes(true).Length > 0 && x.GetCustomAttributes(true).Any(y => y.GetType() == typeof(System.ComponentModel.DataAnnotations.DisplayAttribute)))
.Select(x => new
{
name = x.Name,
display = ((System.ComponentModel.DataAnnotations.DisplayAttribute)x.GetCustomAttributes(true).First(y => y.GetType() == typeof(System.ComponentModel.DataAnnotations.DisplayAttribute))).Name
}).ToList();
return new JsonResult(new { successful = true, fileName, excelItems, mappingOptions });
}
public async Task<IActionResult> OnPostAsync(string selectedItems)
public async Task<IActionResult> OnPostImport(string selectedItems, string fileName, int idProjectPart, int idProject)
{
if (selectedItems == "")
FileName = fileName;
IdProjectPart = idProjectPart;
IdProject = idProject;
if (string.IsNullOrEmpty(selectedItems))
{
return Page(); //TODO Error
return new JsonResult(new { successful = false, error = "Izberite vsaj eno polje." });
}
var user = _userManager.GetUserAsync(User).Result;
@@ -128,6 +155,73 @@ namespace EveryThing.Pages.Projects
//ONLY FIRST LIST
var worksheet = xlWorkbook.Worksheet(1);
// Build display name lookup for properties
var propertyDisplayNames = typeof(Models.Project.ProjectPartItem).GetProperties()
.Where(x => x.GetCustomAttributes(true).Any(y => y is System.ComponentModel.DataAnnotations.DisplayAttribute))
.ToDictionary(
x => x.Name,
x => ((System.ComponentModel.DataAnnotations.DisplayAttribute)x.GetCustomAttributes(true).First(y => y is System.ComponentModel.DataAnnotations.DisplayAttribute)).Name
);
// Validation pass - check all rows before importing
var errors = new List<object>();
int validationRow = 2; // Skip header
while (!worksheet.Row(validationRow).IsEmpty())
{
IXLRow row = worksheet.Row(validationRow);
// Check if all mapped cells in the row are empty
bool allEmpty = excelItems.All(ei =>
row.Cell(ei.CellIndex) == null || string.IsNullOrWhiteSpace(row.Cell(ei.CellIndex).Value.ToString()));
if (allEmpty)
{
errors.Add(new { row = validationRow, property = "-", value = "", message = "Vsa polja v vrstici so prazna." });
validationRow++;
continue;
}
foreach (var excelItem in excelItems)
{
if (row.Cell(excelItem.CellIndex) == null)
continue;
string value = row.Cell(excelItem.CellIndex).Value.ToString();
string displayName = propertyDisplayNames.ContainsKey(excelItem.Name) ? propertyDisplayNames[excelItem.Name] : excelItem.Name;
// Empty individual cells are allowed - skip validation
if (string.IsNullOrWhiteSpace(value))
continue;
if (excelItem.Name is "IdItemFk" or "IdMaterialFk" or "IdMaterialSupplierFk")
{
// These are text lookups, no type conversion needed
continue;
}
// Validate type conversion for other properties
var propertyInfo = typeof(Models.Project.ProjectPartItem).GetProperties().FirstOrDefault(x => x.Name == excelItem.Name);
if (propertyInfo != null)
{
try
{
var targetType = Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType;
Convert.ChangeType(value, targetType);
}
catch
{
errors.Add(new { row = validationRow, property = displayName, value, message = $"Ni mogoče pretvoriti v tip {propertyInfo.PropertyType.Name}." });
}
}
}
validationRow++;
}
if (errors.Count > 0)
{
return new JsonResult(new { successful = false, validationErrors = errors });
}
var insertedParts = _context.ProjectPartItems.Where(x => x.IdProjectPartFk == IdProjectPart);
var currentPositionNumber = insertedParts.Any()
@@ -158,6 +252,9 @@ namespace EveryThing.Pages.Projects
string value = row.Cell(excelItem.CellIndex).Value.ToString();
if (string.IsNullOrWhiteSpace(value))
continue;
if (excelItem.Name is "IdItemFk" or "IdMaterialFk")
{
var completableItem = _context.CodeTableItems.FirstOrDefault(x => x.Title == value && x.Active == true);
@@ -218,7 +315,7 @@ namespace EveryThing.Pages.Projects
System.IO.File.Delete(path);
return RedirectToPage("./Edit", new {id = IdProject});
return new JsonResult(new { successful = true, redirectUrl = Url.Page("./Edit", new { id = IdProject }) });
}
public class ExcelItem

View File

@@ -1,50 +0,0 @@
@page
@using EveryThing.Models.Project
@model EveryThing.Pages.Projects.CreatePartItemUploadExcelModel
@{
ViewData["Title"] = "Uvoz pozicij dela projekta";
Layout = "~/Pages/Layouts/_Layout.cshtml";
}
<form method="post" enctype="multipart/form-data">
<h4 class="d-flex justify-content-between align-items-center w-100 font-weight-bold py-1 mb-4">
<span>
<span class="text-muted font-weight-light">Projekt /</span> Uvoz pozicij dela projekta
</span>
</h4>
<div class="row">
<div class="col-6">
<div class="card">
<h6 class="card-header">
Podatki pozicije
</h6>
<div class="card-body">
<input type="hidden" asp-for="IdProjectPart" name="idProjectPart" />
<input type="hidden" asp-for="IdProject" name="idProject"/>
<div class="row">
<div class="col-12">
<div class="form-group">
<input type="file" name="postedFiles" multiple/>
</div>
</div>
</div>
</div>
<div class="card-footer py-3 text-right">
<input type="submit" class="btn btn-primary" value="Naloži excel" asp-page-handler="Upload" />
<a asp-page="Edit" asp-route-id="IdProject" class="btn btn-default">Prekliči</a>
</div>
</div>
</div>
</div>
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
</form>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

View File

@@ -1,131 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using EveryThing.Data;
using EveryThing.Models;
using EveryThing.Models.Project;
using Microsoft.AspNetCore.Http;
using System.IO;
using Microsoft.AspNetCore.Hosting;
namespace EveryThing.Pages.Projects
{
[Authorize(Roles = "Administrator,ProjecThingUser")]
public class CreatePartItemUploadExcelModel : PageModel
{
private readonly IWebHostEnvironment _hostingEnvironment;
private readonly ApplicationDbContext _context;
private readonly UserManager<IdentityApplicationUser> _userManager;
private readonly SignInManager<IdentityApplicationUser> _loginManager;
private readonly RoleManager<IdentityApplicationRole> _roleManager;
public CreatePartItemUploadExcelModel(ApplicationDbContext context, UserManager<IdentityApplicationUser> userManager, SignInManager<IdentityApplicationUser> loginManager, RoleManager<IdentityApplicationRole> roleManager, IWebHostEnvironment environment)
{
_context = context;
_userManager = userManager;
_loginManager = loginManager;
_roleManager = roleManager;
_hostingEnvironment = environment;
}
[BindProperty]
public IFormFile File { get; set; }
[BindProperty]
public int IdProject { get; set; }
[BindProperty]
public int IdProjectPart { get; set; }
public IActionResult OnGet(int idProject, int idProjectPart)
{
var user = _userManager.GetUserAsync(User).Result;
IdProject = idProject;
IdProjectPart = idProjectPart;
return Page();
}
//public async Task<IActionResult> OnPostAsync()
//{
// if (!ModelState.IsValid)
// {
// return Page();
// }
// string uploads = Path.Combine(_hostingEnvironment.WebRootPath, "uploads");
// if (File.Length > 0)
// {
// string filePath = Path.Combine(uploads, File.FileName);
// using (Stream fileStream = new FileStream(filePath, FileMode.Create))
// {
// await File.CopyToAsync(fileStream);
// }
// }
// return RedirectToPage("./Edit");
//}
//public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> files)
//{
// long size = files.Sum(f => f.Length);
// foreach (var formFile in files)
// {
// if (formFile.Length > 0)
// {
// var filePath = Path.GetTempFileName();
// System.Diagnostics.Debug.WriteLine(filePath);
// using (var stream = System.IO.File.Create(filePath))
// {
// await formFile.CopyToAsync(stream);
// }
// }
// }
// // Process uploaded files
// // Don't rely on or trust the FileName property without validation.
// return RedirectToPage("./Edit");
//}
public async Task<IActionResult> OnPostUpload(int idProject, int idProjectPart, List<IFormFile> postedFiles)
{
if (postedFiles == null
|| postedFiles.Count != 1)
{
return Page();//TODO return error
}
var path = Path.Combine(_hostingEnvironment.WebRootPath, "Uploads", "TempExcelImport");
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
else
{
//Pocistimo mapo
foreach (var fileInfo in new DirectoryInfo(path).GetFiles("*.*"))
{
fileInfo.Delete();
}
}
var postedFile = postedFiles[0];
var fileName = postedFile.FileName;//Guid.NewGuid().ToString().Replace("-", "_") + Path.GetExtension(postedFile.FileName);
await using (var stream = new FileStream(Path.Combine(path, fileName), FileMode.Create))
{
await postedFile.CopyToAsync(stream);
}
return RedirectToPage("./CreatePartItemImportExcel", new { idProject = idProject, idProjectPart = idProjectPart, fileName = fileName});
}
}
}

View File

@@ -727,7 +727,7 @@
</div>
<div class="col-6" style="text-align: right">
<a asp-page="CreateEditPartItem" asp-route-idProject="@Model.Project.IdProject" asp-route-idProjectPart="@part.IdProjectPart" asp-route-edit="@false" class="btn btn-sm btn-primary">Dodaj pozicijo</a>
<a asp-page="CreatePartItemUploadExcel" asp-route-idProject="@Model.Project.IdProject" asp-route-idProjectPart="@part.IdProjectPart" class="btn btn-sm btn-success">Uvozi kosovnico</a>
<a asp-page="CreatePartItemImportExcel" asp-route-idProject="@Model.Project.IdProject" asp-route-idProjectPart="@part.IdProjectPart" class="btn btn-sm btn-success">Uvozi kosovnico</a>
</div>
</div>
</div>