Compare commits

..

No commits in common. "main" and "Storekeeper" have entirely different histories.

20 changed files with 117 additions and 146 deletions

View File

@ -129,6 +129,7 @@ namespace HardwareShopBusinessLogic.OfficePackage
} }
} }
rowIndex++;
} }
SaveExcel(info); SaveExcel(info);

View File

@ -264,7 +264,7 @@ namespace HardwareShopStorekeeperApp.Controllers
} }
[HttpPost] [HttpPost]
public void CreateComponent(string name, string cost) public void CreateComponent(string name, string cost, DateTime date)
{ {
if (APIClient.User == null) if (APIClient.User == null)
{ {
@ -282,7 +282,8 @@ namespace HardwareShopStorekeeperApp.Controllers
{ {
UserId = APIClient.User.Id, UserId = APIClient.User.Id,
ComponentName = name, ComponentName = name,
Cost = Convert.ToDouble(cost.Replace('.', ',')) Cost = Convert.ToDouble(cost.Replace('.', ',')),
DateCreate = date
}); });
Response.Redirect("Components"); Response.Redirect("Components");
} }
@ -442,15 +443,5 @@ namespace HardwareShopStorekeeperApp.Controllers
reportModel.UserEmail = APIClient.User.Email; reportModel.UserEmail = APIClient.User.Email;
APIClient.PostRequest("api/report/componentsreportsendonmail", reportModel); APIClient.PostRequest("api/report/componentsreportsendonmail", reportModel);
} }
[HttpGet]
public List<CommentViewModel> GetCommentsOnBuild(int buildId)
{
if (APIClient.User == null)
{
throw new Exception("Вы как сюда попали? Сюда вход только авторизованным");
}
return APIClient.GetRequest<List<CommentViewModel>>($"api/comment/GetCommentsOnBuild?buildId={buildId}")!;
}
} }
} }

View File

@ -16,6 +16,10 @@
<label class="form-label">Стоимость</label> <label class="form-label">Стоимость</label>
<input type="number" step="0.01" class="form-control" name="cost" min="0.01" required> <input type="number" step="0.01" class="form-control" name="cost" min="0.01" required>
</div> </div>
<div class="col-sm-3">
<label class="form-label">Дата приобретения</label>
<input type="datetime-local" class="form-control" name="date" required>
</div>
<div class="col-sm-2 d-flex justify-content-evenly align-items-baseline"> <div class="col-sm-2 d-flex justify-content-evenly align-items-baseline">
<button type="submit" class="btn btn-primary mt-3 px-4">Сохранить</button> <button type="submit" class="btn btn-primary mt-3 px-4">Сохранить</button>
</div> </div>

View File

@ -41,9 +41,6 @@
<div class="modal-body"> <div class="modal-body">
<label class="form-label">Сборка</label> <label class="form-label">Сборка</label>
<select id="build" name="build" class="form-control" asp-items="@(new SelectList(@ViewBag.Builds, "Id", "BuildName"))" required></select> <select id="build" name="build" class="form-control" asp-items="@(new SelectList(@ViewBag.Builds, "Id", "BuildName"))" required></select>
<table class="table table-hover">
<thead><tr><th scope="col">Комментарии</th></tr></thead>
<tbody id="comments"></tbody>
<label class="form-label">Количество</label> <label class="form-label">Количество</label>
<input type="number" class="form-control" name="count" id="count" min="1" value="1" required> <input type="number" class="form-control" name="count" id="count" min="1" value="1" required>
</div> </div>
@ -65,21 +62,6 @@
const saveBtn = document.getElementById("linkbuilds"); const saveBtn = document.getElementById("linkbuilds");
const countElem = document.getElementById("count"); const countElem = document.getElementById("count");
const resultTable = document.getElementById("result"); const resultTable = document.getElementById("result");
const selectBuilds = document.getElementById("build");
const comments = document.getElementById("comments");
selectBuilds.addEventListener('change', function() { getCommentsOnBuild() });
function getCommentsOnBuild() {
$.ajax({
method: "GET",
url: `/Storekeeper/GetCommentsOnBuild`,
data: { buildId: selectBuilds.value },
success: function (result) {
reloadCommentsTable(result)
}
});
}
submitBuildBtn.addEventListener("click", () => { submitBuildBtn.addEventListener("click", () => {
console.log('try to add build') console.log('try to add build')
@ -139,19 +121,13 @@
}) })
} }
function reloadCommentsTable(result) {
comments.innerHTML = ''
result.forEach((elem) => {
comments.innerHTML += `<tr><td>${elem.text}</td></tr>`
})
}
function deleteBuild(id) { function deleteBuild(id) {
list = list.filter(value => value.build.buildName != resultTable.rows[id].cells[0].innerText) list = list.filter(value => value.build.buildName != resultTable.rows[id].cells[0].innerText)
reloadTable() reloadTable()
} }
function getComponentBuilds() { function getComponentBuilds() {
console.log(componentid)
if (componentid) { if (componentid) {
$.ajax({ $.ajax({
method: "GET", method: "GET",
@ -166,6 +142,7 @@
url: "/Storekeeper/GetComponentBuilds", url: "/Storekeeper/GetComponentBuilds",
data: { componentid: componentid }, data: { componentid: componentid },
success: function (result) { success: function (result) {
console.log(result)
if (result) { if (result) {
result.forEach(elem => { result.forEach(elem => {
list.push({ build: elem.item1, count: elem.item2 }) list.push({ build: elem.item1, count: elem.item2 })
@ -177,7 +154,6 @@
}; };
} }
getComponentBuilds(); getComponentBuilds();
getCommentsOnBuild();
</script> </script>
} }

View File

@ -0,0 +1,19 @@
using HardwareShopDataModels.Models;
namespace HardwareShopContracts.BindingModels
{
public class MessageInfoBindingModel : IMessageInfoModel
{
public string MessageId { get; set; } = string.Empty;
public int? UserId { get; set; }
public string SenderName { get; set; } = string.Empty;
public string Subject { get; set; } = string.Empty;
public string Body { get; set; } = string.Empty;
public DateTime DateDelivery { get; set; }
}
}

View File

@ -0,0 +1,17 @@
namespace HardwareShopDataModels.Models
{
public interface IMessageInfoModel
{
string MessageId { get; }
int? UserId { get; }
string SenderName { get; }
DateTime DateDelivery { get; }
string Subject { get; }
string Body { get; }
}
}

View File

@ -3,7 +3,6 @@ using HardwareShopDatabaseImplement.Models.ManyToMany;
using HardwareShopDatabaseImplement.Models.Storekeeper; using HardwareShopDatabaseImplement.Models.Storekeeper;
using HardwareShopDatabaseImplement.Models.Worker; using HardwareShopDatabaseImplement.Models.Worker;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
namespace HardwareShopDatabaseImplement namespace HardwareShopDatabaseImplement
{ {
@ -11,8 +10,8 @@ namespace HardwareShopDatabaseImplement
{ {
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{ {
optionsBuilder.UseNpgsql("Host=localhost;Port=5432;Database=Computer_Hardware_Store5;Username=postgres;Password=1234"); optionsBuilder.UseNpgsql("Host=localhost;Port=5433;Database=Computer_Hardware_Store1;Username=user;Password=12345");
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
} }
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)

View File

@ -4,7 +4,6 @@ using HardwareShopContracts.StoragesContracts;
using HardwareShopContracts.ViewModels; using HardwareShopContracts.ViewModels;
using HardwareShopDatabaseImplement.Models.Storekeeper; using HardwareShopDatabaseImplement.Models.Storekeeper;
using HardwareShopDatabaseImplement.Models.Worker; using HardwareShopDatabaseImplement.Models.Worker;
using HardwareShopDataModels.Enums;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace HardwareShopDatabaseImplement.Implements.Worker namespace HardwareShopDatabaseImplement.Implements.Worker
@ -90,8 +89,8 @@ namespace HardwareShopDatabaseImplement.Implements.Worker
var build = context.Builds var build = context.Builds
.Include(x => x.Purchases) .Include(x => x.Purchases)
.ThenInclude(x => x.Purchase) .ThenInclude(x => x.Purchase)
.FirstOrDefault(x => x.Id == model.Id && x.UserId == model.UserId); .FirstOrDefault(x => x.Id == model.Id);
if (build == null) if (build == null || build.UserId != build.UserId)
{ {
return null; return null;
} }
@ -117,17 +116,7 @@ namespace HardwareShopDatabaseImplement.Implements.Worker
.FirstOrDefault(rec => rec.Id == model.Id); .FirstOrDefault(rec => rec.Id == model.Id);
if (element != null) if (element != null)
{ {
var buildPurchases = context.PurchasesBuilds.Where(x => x.BuildId == element.Id).ToList(); context.Builds.Remove(element);
buildPurchases.ForEach(x =>
{
var purchase = context.Purchases.Include(x => x.Builds).FirstOrDefault(rec => rec.Id == x.PurchaseId && rec.PurchaseStatus != PurchaseStatus.Выдан);
if (purchase != null)
{
purchase.Sum -= element.Price * x.Count;
purchase.Sum = Math.Round(purchase.Sum, 2);
}
});
context.Builds.Remove(element);
context.SaveChanges(); context.SaveChanges();
return element.GetViewModel; return element.GetViewModel;
} }

View File

@ -22,7 +22,7 @@ namespace HardwareShopDatabaseImplement.Implements.Worker
public List<PurchaseViewModel> GetFilteredList(PurchaseSearchModel model) public List<PurchaseViewModel> GetFilteredList(PurchaseSearchModel model)
{ {
using var context = new HardwareShopDatabase(); using var context = new HardwareShopDatabase();
if (model.UserId.HasValue && model.DateFrom.HasValue && model.DateTo.HasValue) if (model.UserId.HasValue && !model.PurchaseStatus.HasValue && model.DateFrom.HasValue && model.DateTo.HasValue)
{ {
return context.Purchases return context.Purchases
.Include(x => x.Builds) .Include(x => x.Builds)
@ -36,28 +36,21 @@ namespace HardwareShopDatabaseImplement.Implements.Worker
.Select(x => x.GetViewModel) .Select(x => x.GetViewModel)
.ToList(); .ToList();
} }
if (model.UserId.HasValue && model.PurchaseStatus.HasValue)
{
return context.Purchases
.Include(x => x.Goods)
.ThenInclude(x => x.Good)
.Where(x => x.PurchaseStatus == model.PurchaseStatus && x.UserId == model.UserId)
.Select(x => x.GetViewModel)
.ToList();
}
if (model.UserId.HasValue) if (model.UserId.HasValue)
{
return context.Purchases return context.Purchases
.Include(x => x.Goods) .Include(x => x.Goods)
.ThenInclude(x => x.Good) .ThenInclude(x => x.Good)
.Where(x => x.UserId == model.UserId) .Where(x => x.UserId == model.UserId)
.Select(x => x.GetViewModel) .Select(x => x.GetViewModel)
.ToList(); .ToList();
}
return context.Purchases return context.Purchases
.Include(x => x.Goods) .Include(x => x.Goods)
.ThenInclude(x => x.Good) .ThenInclude(x => x.Good)
.Where(x => x.PurchaseStatus == model.PurchaseStatus) .Where(x => x.PurchaseStatus == model.PurchaseStatus)
.Select(x => x.GetViewModel) .Select(x => x.GetViewModel)
.ToList(); .ToList();
} }
public List<PurchaseViewModel> GetReportFilteredList(PurchaseSearchModel model) public List<PurchaseViewModel> GetReportFilteredList(PurchaseSearchModel model)
@ -124,8 +117,8 @@ namespace HardwareShopDatabaseImplement.Implements.Worker
var purchase = context.Purchases var purchase = context.Purchases
.Include(x => x.Goods) .Include(x => x.Goods)
.ThenInclude(x => x.Good) .ThenInclude(x => x.Good)
.FirstOrDefault(x => x.Id == model.Id && x.UserId == model.UserId); .FirstOrDefault(x => x.Id == model.Id);
if (purchase == null) if (purchase == null || purchase.UserId != model.UserId)
{ {
return null; return null;
} }

View File

@ -202,7 +202,6 @@ namespace HardwareShopRestApi.Controllers
PurchaseStatus = purchase.PurchaseStatus, PurchaseStatus = purchase.PurchaseStatus,
DatePurchase = purchase.DatePurchase, DatePurchase = purchase.DatePurchase,
PurchaseGoods = purchase.PurchaseGoods, PurchaseGoods = purchase.PurchaseGoods,
UserId = purchase.UserId
}); });
return true; return true;
} }

View File

@ -37,24 +37,7 @@ namespace HardwareShopRestApi.Controllers
} }
} }
[HttpGet] [HttpGet]
public List<CommentViewModel>? GetCommentsOnBuild(int buildId)
{
try
{
return _commentLogic.ReadList(new CommentSearchModel
{
BuildId = buildId
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Ошибка получения списка комментариев пользоватля");
throw;
}
}
[HttpGet]
public CommentViewModel? GetComment(int commentId) public CommentViewModel? GetComment(int commentId)
{ {
try try

View File

@ -49,12 +49,12 @@ namespace HardwareShopRestApi.Controllers
} }
} }
[HttpPost] [HttpGet]
public List<PurchaseViewModel>? GetPurchases(PurchaseSearchModel model) public List<PurchaseViewModel>? GetPurchases(int userId)
{ {
try try
{ {
return _purchaseLogic.ReadList(model); return _purchaseLogic.ReadList(new() { UserId = userId });
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -63,21 +63,7 @@ namespace HardwareShopRestApi.Controllers
} }
} }
[HttpGet] [HttpGet]
public List<PurchaseViewModel>? GetPurchasesNotDelivery(int userId)
{
try
{
return _purchaseLogic.ReadList(new() { UserId = userId, PurchaseStatus = PurchaseStatus.Выполняется });
}
catch (Exception ex)
{
_logger.LogError(ex, "Ошибка получения списка товаров");
throw;
}
}
[HttpGet]
public PurchaseViewModel? GetPurchase(int purchaseId) public PurchaseViewModel? GetPurchase(int purchaseId)
{ {
try try

View File

@ -327,7 +327,7 @@ namespace HardwareShopWorkerApp.Controllers
{ {
return Redirect("~/Home/Enter"); return Redirect("~/Home/Enter");
} }
return View(APIClient.PostRequestWithResult<PurchaseSearchModel, List<PurchaseViewModel>>($"api/purchase/getpurchases", new() { UserId = APIClient.User.Id })); return View(APIClient.GetRequest<List<PurchaseViewModel>>($"api/purchase/getpurchases?userId={APIClient.User.Id}"));
} }
[HttpPost] [HttpPost]
@ -358,8 +358,7 @@ namespace HardwareShopWorkerApp.Controllers
{ {
return Redirect("~/Home/Enter"); return Redirect("~/Home/Enter");
} }
ViewBag.Purchases = APIClient.GetRequest<List<PurchaseViewModel>>($"api/purchase/getpurchases?userId={APIClient.User.Id}");
ViewBag.Purchases = APIClient.PostRequestWithResult<PurchaseSearchModel, List<PurchaseViewModel>> ($"api/purchase/getpurchases", new() {UserId = APIClient.User.Id });
return View(); return View();
} }
@ -439,8 +438,7 @@ namespace HardwareShopWorkerApp.Controllers
APIClient.PostRequest("api/purchase/UpdateStatusPurchase", new PurchaseBindingModel APIClient.PostRequest("api/purchase/UpdateStatusPurchase", new PurchaseBindingModel
{ {
Id = id, Id = id,
PurchaseStatus = (PurchaseStatus)status, PurchaseStatus = (PurchaseStatus)status
UserId = APIClient.User.Id
}); });
} }
@ -523,7 +521,7 @@ namespace HardwareShopWorkerApp.Controllers
{ {
throw new Exception($"Идентификтаор сборки не может быть ниже или равен 0"); throw new Exception($"Идентификтаор сборки не может быть ниже или равен 0");
} }
ViewBag.Purchases = APIClient.PostRequestWithResult<PurchaseSearchModel, List<PurchaseViewModel>>($"api/purchase/getpurchases", new() { UserId = APIClient.User.Id, PurchaseStatus = PurchaseStatus.Выполняется }); ViewBag.Purchase = APIClient.GetRequest<List<PurchaseViewModel>>($"api/purchase/getpurchases?userId={APIClient.User.Id}");
return View(APIClient.GetRequest<List<Tuple<PurchaseViewModel, int>>>($"api/build/GetBuildPurchase?buildId={buildId}")); return View(APIClient.GetRequest<List<Tuple<PurchaseViewModel, int>>>($"api/build/GetBuildPurchase?buildId={buildId}"));
} }

View File

@ -30,7 +30,7 @@
Текст Текст
</th> </th>
<th> <th>
Сборка Название сборки к которой относится комментарий
</th> </th>
<th> <th>
Действия Действия
@ -101,6 +101,10 @@
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div>
<label class="form-label">Сборка</label>
<select id="buildId" name="buildId" class="form-control" asp-items="@(new SelectList(@ViewBag.Builds,"Id", "BuildName"))"></select>
</div>
<div> <div>
<label class="form-label">Текст</label> <label class="form-label">Текст</label>
<input type="text" class="form-control" required="required" id="text" name="text" /> <input type="text" class="form-control" required="required" id="text" name="text" />

View File

@ -21,9 +21,8 @@
<thead> <thead>
<tr> <tr>
<th scope="col">Товар</th> <th scope="col">Товар</th>
<th scope="col">Цена</th>
<th scope="col">Количество</th> <th scope="col">Количество</th>
<th scope="col">Сумма</th> <th scope="col">Цена</th>
<th scope="col">Действие</th> <th scope="col">Действие</th>
</tr> </tr>
</thead> </thead>
@ -71,7 +70,6 @@
console.log('try to add good') console.log('try to add good')
var count = $('#count').val(); var count = $('#count').val();
var good = $('#good').val(); var good = $('#good').val();
if (count > 0 && good > 0){
$.ajax({ $.ajax({
method: "GET", method: "GET",
url: `/Home/GetGood`, url: `/Home/GetGood`,
@ -91,7 +89,6 @@
countElem.value = '1' countElem.value = '1'
} }
}); });
}
}) })
saveBtn.addEventListener("click", () => { saveBtn.addEventListener("click", () => {
@ -129,8 +126,7 @@
let price = 0; let price = 0;
let count = 0; let count = 0;
list.forEach((elem) => { list.forEach((elem) => {
console.log(elem); resultTable.innerHTML += `<tr><td>${elem.good.goodName}</td><td>${elem.count}</td><td>${Math.round(elem.good.price * elem.count * 100) / 100}</td><td> \
resultTable.innerHTML += `<tr><td>${elem.good.goodName}</td><td>${elem.good.price}</td><td>${elem.count}</td><td>${Math.round(elem.good.price * elem.count * 100) / 100}</td><td> \
<div> \ <div> \
<button onclick="deleteGood(${count})" type="button" class="btn btn-danger"> \ <button onclick="deleteGood(${count})" type="button" class="btn btn-danger"> \
<i class="fa fa-trash" aria-hidden="true"></i> \ <i class="fa fa-trash" aria-hidden="true"></i> \

View File

@ -2,7 +2,7 @@
@model List<Tuple<PurchaseViewModel, int>> @model List<Tuple<PurchaseViewModel, int>>
@{ @{
ViewData["Title"] = "Link purchase"; ViewData["Title"] = "Привязка сборок";
Layout = "~/Views/Shared/_LayoutWorker.cshtml"; Layout = "~/Views/Shared/_LayoutWorker.cshtml";
} }
@ -79,7 +79,7 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<label class="form-label">Покупка</label> <label class="form-label">Покупка</label>
<select id="purchaseId" name="purchaseId" class="form-control" asp-items="@(new SelectList(@ViewBag.Purchases, "Id","Id"))"></select> <select id="purchaseId" name="purchaseId" class="form-control" asp-items="@(new SelectList(@ViewBag.Purchase, "Id","Id"))"></select>
<div class="form-group"> <div class="form-group">
<label>Количество</label> <label>Количество</label>
<input type="number" class="form-control" required="required" name="count" /> <input type="number" class="form-control" required="required" name="count" />

View File

@ -82,16 +82,22 @@
@section Scripts @section Scripts
{ {
<script> <script>
// get selected row
// display selected row data in text input
var table = document.getElementById("table"); var table = document.getElementById("table");
var remove = document.getElementById("delete"); var remove = document.getElementById("delete");
var done = document.getElementById("done"); var done = document.getElementById("done");
var purchase = 0; var purchase = 0;
for (var i = 1; i < table.rows.length; i++) { for (var i = 1; i < table.rows.length; i++) {
table.rows[i].onclick = function () { table.rows[i].onclick = function () {
// remove the background from the previous selected row
if (typeof index !== "undefined") { if (typeof index !== "undefined") {
table.rows[index].classList.toggle("selected"); table.rows[index].classList.toggle("selected");
} }
// get the selected row index
index = this.rowIndex; index = this.rowIndex;
// add class selected to the row
this.classList.toggle("selected"); this.classList.toggle("selected");
purchase = parseInt(this.cells[0].innerText); purchase = parseInt(this.cells[0].innerText);
remove.addEventListener("click", () => { remove.addEventListener("click", () => {

View File

@ -72,27 +72,25 @@
console.log('try to add good') console.log('try to add good')
var count = $('#count').val(); var count = $('#count').val();
var good = $('#good').val(); var good = $('#good').val();
if (count > 0 && good > 0) { $.ajax({
$.ajax({ method: "GET",
method: "GET", url: `/Home/GetGood`,
url: `/Home/GetGood`, data: { Id: good },
data: { Id: good }, success: function (result) {
success: function (result) { let flag = false
let flag = false if (list.length > 0) {
if (list.length > 0) { list.forEach((elem) => {
list.forEach((elem) => { if (elem.good.id === parseInt(result.id)) {
if (elem.good.id === parseInt(result.id)) { console.log('good already added')
console.log('good already added') flag = true
flag = true }
} })
})
}
if (!flag) list.push({ good: result, count: count })
reloadTable()
countElem.value = '1'
} }
}); if (!flag) list.push({ good: result, count: count })
} reloadTable()
countElem.value = '1'
}
});
}) })
saveBtn.addEventListener("click", () => { saveBtn.addEventListener("click", () => {

View File

@ -48,6 +48,8 @@
const errorP = document.getElementById("error-p"); const errorP = document.getElementById("error-p");
const errorDivShell = document.getElementById("error-div-shell"); const errorDivShell = document.getElementById("error-div-shell");
// [Event listeners]
generateButton.addEventListener("click", () => { generateButton.addEventListener("click", () => {
const dateFrom = new Date(dateFromInput.value); const dateFrom = new Date(dateFromInput.value);
const dateTo = new Date(dateToInput.value); const dateTo = new Date(dateToInput.value);
@ -83,6 +85,10 @@
}); });
}); });
// ![Event listeners]
// [HTML gen]
const renderTable = function (reportData) { const renderTable = function (reportData) {
tbody.innerHTML = ""; tbody.innerHTML = "";
reportData.forEach((record) => { reportData.forEach((record) => {
@ -128,6 +134,10 @@
return td; return td;
} }
// ![HTML gen]
// [Other]
const getDate = function (iso) { const getDate = function (iso) {
const year = iso.substring(0, 4); const year = iso.substring(0, 4);
const month = iso.substring(5, 7); const month = iso.substring(5, 7);
@ -136,5 +146,7 @@
return date; return date;
} }
// ![Other]
</script> </script>
} }

View File

@ -1,6 +1,6 @@
@using HardwareShopContracts.ViewModels @using HardwareShopContracts.ViewModels
@{ @{
ViewData["Title"] = "List components"; ViewData["Title"] = "Получение списка";
Layout = "~/Views/Shared/_LayoutWorker.cshtml"; Layout = "~/Views/Shared/_LayoutWorker.cshtml";
} }