DEMO shop: simplified project with comments
This commit is contained in:
commit
7f990d6f7d
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
# билд
|
||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
# студия
|
||||||
|
.vs/
|
||||||
|
# юзерские настройки
|
||||||
|
*.user
|
||||||
|
*.suo
|
||||||
3
DEMO.slnx
Normal file
3
DEMO.slnx
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<Solution>
|
||||||
|
<Project Path="DEMO/DEMO.csproj" />
|
||||||
|
</Solution>
|
||||||
76
DEMO/Controllers/AuthController.cs
Normal file
76
DEMO/Controllers/AuthController.cs
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
// вход и регистрация
|
||||||
|
using DEMO.Data; // база
|
||||||
|
using DEMO.Models; // пользователи
|
||||||
|
using Microsoft.AspNetCore.Mvc; // контроллер
|
||||||
|
using Microsoft.EntityFrameworkCore; // поиск в базе
|
||||||
|
|
||||||
|
namespace DEMO.Controllers;
|
||||||
|
|
||||||
|
[ApiController] // это api
|
||||||
|
[Route("api/[controller]")] // адрес будет api/auth
|
||||||
|
public class AuthController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly AppDbContext _context; // база
|
||||||
|
|
||||||
|
// запоминаю базу
|
||||||
|
public AuthController(AppDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
// войти, адрес POST api/auth/login
|
||||||
|
[HttpPost("login")]
|
||||||
|
public async Task<IActionResult> Login(User user) // приходят логин и пароль
|
||||||
|
{
|
||||||
|
// ищу такого человека в базе
|
||||||
|
var existingUser = await _context.Users
|
||||||
|
.Include(u => u.Role) // сразу беру его роль
|
||||||
|
.FirstOrDefaultAsync(u =>
|
||||||
|
u.Login == user.Login &&
|
||||||
|
u.Password == user.Password);
|
||||||
|
|
||||||
|
// не нашел значит неправильный пароль
|
||||||
|
if (existingUser == null)
|
||||||
|
return Unauthorized("Неверный логин или пароль.");
|
||||||
|
|
||||||
|
// нашел, отдаю его обратно
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
existingUser.Id,
|
||||||
|
existingUser.Login,
|
||||||
|
Role = existingUser.Role?.Name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// регистрация, адрес POST api/auth/register
|
||||||
|
[HttpPost("register")]
|
||||||
|
public async Task<IActionResult> Register(User user) // приходят логин и пароль
|
||||||
|
{
|
||||||
|
// проверяю нет ли уже такого логина
|
||||||
|
var exists = await _context.Users
|
||||||
|
.AnyAsync(u => u.Login == user.Login);
|
||||||
|
|
||||||
|
if (exists)
|
||||||
|
return BadRequest("Такой логин уже занят.");
|
||||||
|
|
||||||
|
// ищу роль клиент чтоб дать ее новичку
|
||||||
|
var clientRole = await _context.Roles
|
||||||
|
.FirstOrDefaultAsync(r => r.Name == "Client");
|
||||||
|
|
||||||
|
if (clientRole == null)
|
||||||
|
return BadRequest("Роль Client не найдена.");
|
||||||
|
|
||||||
|
user.RoleId = clientRole.Id; // даю роль клиент
|
||||||
|
|
||||||
|
_context.Users.Add(user); // добавляю в базу
|
||||||
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
|
|
||||||
|
// отдаю нового человека
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
user.Id,
|
||||||
|
user.Login,
|
||||||
|
Role = clientRole.Name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
67
DEMO/Controllers/CategoriesController.cs
Normal file
67
DEMO/Controllers/CategoriesController.cs
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
// все про категории
|
||||||
|
using DEMO.Data; // база
|
||||||
|
using DEMO.Models; // категории
|
||||||
|
using Microsoft.AspNetCore.Mvc; // контроллер
|
||||||
|
using Microsoft.EntityFrameworkCore; // база
|
||||||
|
|
||||||
|
namespace DEMO.Controllers;
|
||||||
|
|
||||||
|
[ApiController] // это api
|
||||||
|
[Route("api/[controller]")] // адрес будет api/categories
|
||||||
|
public class CategoriesController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly AppDbContext _context; // база
|
||||||
|
|
||||||
|
// запоминаю базу
|
||||||
|
public CategoriesController(AppDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
// отдать все категории
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> GetCategories()
|
||||||
|
{
|
||||||
|
var categories = await _context.Categories.ToListAsync(); // беру все
|
||||||
|
return Ok(categories); // отдаю
|
||||||
|
}
|
||||||
|
|
||||||
|
// добавить категорию
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> CreateCategory(Category category) // приходит название
|
||||||
|
{
|
||||||
|
_context.Categories.Add(category); // добавляю
|
||||||
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
|
return Ok(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
// изменить категорию
|
||||||
|
[HttpPut("{id}")]
|
||||||
|
public async Task<IActionResult> UpdateCategory(int id, Category category)
|
||||||
|
{
|
||||||
|
var existingCategory = await _context.Categories.FindAsync(id); // ищу старую
|
||||||
|
|
||||||
|
if (existingCategory == null)
|
||||||
|
return NotFound(); // нет такой
|
||||||
|
|
||||||
|
existingCategory.Name = category.Name; // меняю название
|
||||||
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
|
|
||||||
|
return Ok(existingCategory);
|
||||||
|
}
|
||||||
|
|
||||||
|
// удалить категорию
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
public async Task<IActionResult> DeleteCategory(int id)
|
||||||
|
{
|
||||||
|
var category = await _context.Categories.FindAsync(id); // ищу
|
||||||
|
|
||||||
|
if (category == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
_context.Categories.Remove(category); // удаляю
|
||||||
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
|
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
77
DEMO/Controllers/OrderItemsController.cs
Normal file
77
DEMO/Controllers/OrderItemsController.cs
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
// товары внутри заказов
|
||||||
|
using DEMO.Data; // база
|
||||||
|
using DEMO.Models; // позиции заказа
|
||||||
|
using Microsoft.AspNetCore.Mvc; // контроллер
|
||||||
|
using Microsoft.EntityFrameworkCore; // база
|
||||||
|
|
||||||
|
namespace DEMO.Controllers;
|
||||||
|
|
||||||
|
[ApiController] // это api
|
||||||
|
[Route("api/[controller]")] // адрес будет api/orderitems
|
||||||
|
public class OrderItemsController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly AppDbContext _context; // база
|
||||||
|
|
||||||
|
// запоминаю базу
|
||||||
|
public OrderItemsController(AppDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
// отдать все позиции
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> GetOrderItems()
|
||||||
|
{
|
||||||
|
// беру позиции и сразу заказ и товар
|
||||||
|
var orderItems = await _context.OrderItems
|
||||||
|
.Include(i => i.Order)
|
||||||
|
.Include(i => i.Product)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return Ok(orderItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
// добавить товар в заказ
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> CreateOrderItem(OrderItem orderItem) // приходит заказ товар колво цена
|
||||||
|
{
|
||||||
|
_context.OrderItems.Add(orderItem); // добавляю
|
||||||
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
|
return Ok(orderItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
// изменить позицию
|
||||||
|
[HttpPut("{id}")]
|
||||||
|
public async Task<IActionResult> UpdateOrderItem(int id, OrderItem orderItem)
|
||||||
|
{
|
||||||
|
var existingItem = await _context.OrderItems.FindAsync(id); // ищу старую
|
||||||
|
|
||||||
|
if (existingItem == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
// меняю поля
|
||||||
|
existingItem.OrderId = orderItem.OrderId;
|
||||||
|
existingItem.ProductId = orderItem.ProductId;
|
||||||
|
existingItem.Quantity = orderItem.Quantity;
|
||||||
|
existingItem.Price = orderItem.Price;
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
|
|
||||||
|
return Ok(existingItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
// удалить позицию
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
public async Task<IActionResult> DeleteOrderItem(int id)
|
||||||
|
{
|
||||||
|
var orderItem = await _context.OrderItems.FindAsync(id); // ищу
|
||||||
|
|
||||||
|
if (orderItem == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
_context.OrderItems.Remove(orderItem); // удаляю
|
||||||
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
|
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
75
DEMO/Controllers/OrdersController.cs
Normal file
75
DEMO/Controllers/OrdersController.cs
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
// все про заказы
|
||||||
|
using DEMO.Data; // база
|
||||||
|
using DEMO.Models; // заказы
|
||||||
|
using Microsoft.AspNetCore.Mvc; // контроллер
|
||||||
|
using Microsoft.EntityFrameworkCore; // база
|
||||||
|
|
||||||
|
namespace DEMO.Controllers;
|
||||||
|
|
||||||
|
[ApiController] // это api
|
||||||
|
[Route("api/[controller]")] // адрес будет api/orders
|
||||||
|
public class OrdersController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly AppDbContext _context; // база
|
||||||
|
|
||||||
|
// запоминаю базу
|
||||||
|
public OrdersController(AppDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
// отдать все заказы
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> GetOrders()
|
||||||
|
{
|
||||||
|
// беру заказы и кто их сделал
|
||||||
|
var orders = await _context.Orders
|
||||||
|
.Include(o => o.User)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return Ok(orders);
|
||||||
|
}
|
||||||
|
|
||||||
|
// добавить заказ
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> CreateOrder(Order order) // приходит человек дата статус
|
||||||
|
{
|
||||||
|
_context.Orders.Add(order); // добавляю
|
||||||
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
|
return Ok(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
// изменить заказ
|
||||||
|
[HttpPut("{id}")]
|
||||||
|
public async Task<IActionResult> UpdateOrder(int id, Order order)
|
||||||
|
{
|
||||||
|
var existingOrder = await _context.Orders.FindAsync(id); // ищу старый
|
||||||
|
|
||||||
|
if (existingOrder == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
// меняю поля
|
||||||
|
existingOrder.UserId = order.UserId;
|
||||||
|
existingOrder.Date = order.Date;
|
||||||
|
existingOrder.Status = order.Status;
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
|
|
||||||
|
return Ok(existingOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
// удалить заказ
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
public async Task<IActionResult> DeleteOrder(int id)
|
||||||
|
{
|
||||||
|
var order = await _context.Orders.FindAsync(id); // ищу
|
||||||
|
|
||||||
|
if (order == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
_context.Orders.Remove(order); // удаляю
|
||||||
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
|
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
85
DEMO/Controllers/ProductsController.cs
Normal file
85
DEMO/Controllers/ProductsController.cs
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
// все про товары
|
||||||
|
using DEMO.Data; // база
|
||||||
|
using DEMO.Models; // товары
|
||||||
|
using Microsoft.AspNetCore.Mvc; // контроллер
|
||||||
|
using Microsoft.EntityFrameworkCore; // поиск в базе
|
||||||
|
|
||||||
|
namespace DEMO.Controllers;
|
||||||
|
|
||||||
|
[ApiController] // это api
|
||||||
|
[Route("api/[controller]")] // адрес будет api/products
|
||||||
|
public class ProductsController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly AppDbContext _context; // база
|
||||||
|
|
||||||
|
// запоминаю базу
|
||||||
|
public ProductsController(AppDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
// отдать все товары, GET api/products
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> GetProducts()
|
||||||
|
{
|
||||||
|
// беру товары и сразу их категории
|
||||||
|
var products = await _context.Products
|
||||||
|
.Include(p => p.Category)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return Ok(products); // отдаю список
|
||||||
|
}
|
||||||
|
|
||||||
|
// добавить товар, POST api/products
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> CreateProduct(Product product) // приходит товар
|
||||||
|
{
|
||||||
|
// проверяю есть ли такая категория
|
||||||
|
var categoryExists = await _context.Categories
|
||||||
|
.AnyAsync(c => c.Id == product.CategoryId);
|
||||||
|
|
||||||
|
if (!categoryExists)
|
||||||
|
return BadRequest("Такой категории нет.");
|
||||||
|
|
||||||
|
_context.Products.Add(product); // добавляю
|
||||||
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
|
|
||||||
|
return Ok(product); // отдаю обратно
|
||||||
|
}
|
||||||
|
|
||||||
|
// изменить товар, PUT api/products/5
|
||||||
|
[HttpPut("{id}")]
|
||||||
|
public async Task<IActionResult> UpdateProduct(int id, Product product)
|
||||||
|
{
|
||||||
|
// ищу старый товар
|
||||||
|
var existingProduct = await _context.Products.FindAsync(id);
|
||||||
|
|
||||||
|
if (existingProduct == null)
|
||||||
|
return NotFound(); // нет такого
|
||||||
|
|
||||||
|
// меняю поля
|
||||||
|
existingProduct.Name = product.Name;
|
||||||
|
existingProduct.Price = product.Price;
|
||||||
|
existingProduct.CategoryId = product.CategoryId;
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
|
|
||||||
|
return Ok(existingProduct);
|
||||||
|
}
|
||||||
|
|
||||||
|
// удалить товар, DELETE api/products/5
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
public async Task<IActionResult> DeleteProduct(int id)
|
||||||
|
{
|
||||||
|
// ищу товар
|
||||||
|
var product = await _context.Products.FindAsync(id);
|
||||||
|
|
||||||
|
if (product == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
_context.Products.Remove(product); // удаляю
|
||||||
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
|
|
||||||
|
return NoContent(); // все хорошо, отдавать нечего
|
||||||
|
}
|
||||||
|
}
|
||||||
67
DEMO/Controllers/RolesController.cs
Normal file
67
DEMO/Controllers/RolesController.cs
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
// все про роли (админ клиент и тд)
|
||||||
|
using DEMO.Data; // база
|
||||||
|
using DEMO.Models; // роли
|
||||||
|
using Microsoft.AspNetCore.Mvc; // контроллер
|
||||||
|
using Microsoft.EntityFrameworkCore; // база
|
||||||
|
|
||||||
|
namespace DEMO.Controllers;
|
||||||
|
|
||||||
|
[ApiController] // это api
|
||||||
|
[Route("api/[controller]")] // адрес будет api/roles
|
||||||
|
public class RolesController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly AppDbContext _context; // база
|
||||||
|
|
||||||
|
// запоминаю базу
|
||||||
|
public RolesController(AppDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
// отдать все роли
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> GetRoles()
|
||||||
|
{
|
||||||
|
var roles = await _context.Roles.ToListAsync(); // беру все
|
||||||
|
return Ok(roles);
|
||||||
|
}
|
||||||
|
|
||||||
|
// добавить роль
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> CreateRole(Role role) // приходит название
|
||||||
|
{
|
||||||
|
_context.Roles.Add(role); // добавляю
|
||||||
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
|
return Ok(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
// изменить роль
|
||||||
|
[HttpPut("{id}")]
|
||||||
|
public async Task<IActionResult> UpdateRole(int id, Role role)
|
||||||
|
{
|
||||||
|
var existingRole = await _context.Roles.FindAsync(id); // ищу старую
|
||||||
|
|
||||||
|
if (existingRole == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
existingRole.Name = role.Name; // меняю название
|
||||||
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
|
|
||||||
|
return Ok(existingRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
// удалить роль
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
public async Task<IActionResult> DeleteRole(int id)
|
||||||
|
{
|
||||||
|
var role = await _context.Roles.FindAsync(id); // ищу
|
||||||
|
|
||||||
|
if (role == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
_context.Roles.Remove(role); // удаляю
|
||||||
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
|
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
76
DEMO/Controllers/StocksController.cs
Normal file
76
DEMO/Controllers/StocksController.cs
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
// все про остатки на складах
|
||||||
|
using DEMO.Data; // база
|
||||||
|
using DEMO.Models; // остатки
|
||||||
|
using Microsoft.AspNetCore.Mvc; // контроллер
|
||||||
|
using Microsoft.EntityFrameworkCore; // база
|
||||||
|
|
||||||
|
namespace DEMO.Controllers;
|
||||||
|
|
||||||
|
[ApiController] // это api
|
||||||
|
[Route("api/[controller]")] // адрес будет api/stocks
|
||||||
|
public class StocksController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly AppDbContext _context; // база
|
||||||
|
|
||||||
|
// запоминаю базу
|
||||||
|
public StocksController(AppDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
// отдать все остатки
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> GetStocks()
|
||||||
|
{
|
||||||
|
// беру остатки и сразу товары и склады чтоб показать названия
|
||||||
|
var stocks = await _context.Stocks
|
||||||
|
.Include(s => s.Product)
|
||||||
|
.Include(s => s.Warehouse)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return Ok(stocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
// добавить остаток
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> CreateStock(Stock stock) // приходит товар склад и количество
|
||||||
|
{
|
||||||
|
_context.Stocks.Add(stock); // добавляю
|
||||||
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
|
return Ok(stock);
|
||||||
|
}
|
||||||
|
|
||||||
|
// изменить остаток
|
||||||
|
[HttpPut("{id}")]
|
||||||
|
public async Task<IActionResult> UpdateStock(int id, Stock stock)
|
||||||
|
{
|
||||||
|
var existingStock = await _context.Stocks.FindAsync(id); // ищу старый
|
||||||
|
|
||||||
|
if (existingStock == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
// меняю поля
|
||||||
|
existingStock.ProductId = stock.ProductId;
|
||||||
|
existingStock.WarehouseId = stock.WarehouseId;
|
||||||
|
existingStock.Quantity = stock.Quantity;
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
|
|
||||||
|
return Ok(existingStock);
|
||||||
|
}
|
||||||
|
|
||||||
|
// удалить остаток
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
public async Task<IActionResult> DeleteStock(int id)
|
||||||
|
{
|
||||||
|
var stock = await _context.Stocks.FindAsync(id); // ищу
|
||||||
|
|
||||||
|
if (stock == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
_context.Stocks.Remove(stock); // удаляю
|
||||||
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
|
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
75
DEMO/Controllers/UsersController.cs
Normal file
75
DEMO/Controllers/UsersController.cs
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
// все про пользователей
|
||||||
|
using DEMO.Data; // база
|
||||||
|
using DEMO.Models; // пользователи
|
||||||
|
using Microsoft.AspNetCore.Mvc; // контроллер
|
||||||
|
using Microsoft.EntityFrameworkCore; // база
|
||||||
|
|
||||||
|
namespace DEMO.Controllers;
|
||||||
|
|
||||||
|
[ApiController] // это api
|
||||||
|
[Route("api/[controller]")] // адрес будет api/users
|
||||||
|
public class UsersController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly AppDbContext _context; // база
|
||||||
|
|
||||||
|
// запоминаю базу
|
||||||
|
public UsersController(AppDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
// отдать всех людей
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> GetUsers()
|
||||||
|
{
|
||||||
|
// беру людей и их роли
|
||||||
|
var users = await _context.Users
|
||||||
|
.Include(u => u.Role)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return Ok(users);
|
||||||
|
}
|
||||||
|
|
||||||
|
// добавить человека
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> CreateUser(User user) // приходит логин пароль роль
|
||||||
|
{
|
||||||
|
_context.Users.Add(user); // добавляю
|
||||||
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
|
return Ok(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
// изменить человека
|
||||||
|
[HttpPut("{id}")]
|
||||||
|
public async Task<IActionResult> UpdateUser(int id, User user)
|
||||||
|
{
|
||||||
|
var existingUser = await _context.Users.FindAsync(id); // ищу старого
|
||||||
|
|
||||||
|
if (existingUser == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
// меняю поля
|
||||||
|
existingUser.Login = user.Login;
|
||||||
|
existingUser.Password = user.Password;
|
||||||
|
existingUser.RoleId = user.RoleId;
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
|
|
||||||
|
return Ok(existingUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
// удалить человека
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
public async Task<IActionResult> DeleteUser(int id)
|
||||||
|
{
|
||||||
|
var user = await _context.Users.FindAsync(id); // ищу
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
_context.Users.Remove(user); // удаляю
|
||||||
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
|
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
70
DEMO/Controllers/WarehousesController.cs
Normal file
70
DEMO/Controllers/WarehousesController.cs
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
// все про склады
|
||||||
|
using DEMO.Data; // база
|
||||||
|
using DEMO.Models; // склады
|
||||||
|
using Microsoft.AspNetCore.Mvc; // контроллер
|
||||||
|
using Microsoft.EntityFrameworkCore; // база
|
||||||
|
|
||||||
|
namespace DEMO.Controllers;
|
||||||
|
|
||||||
|
[ApiController] // это api
|
||||||
|
[Route("api/[controller]")] // адрес будет api/warehouses
|
||||||
|
public class WarehousesController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly AppDbContext _context; // база
|
||||||
|
|
||||||
|
// запоминаю базу
|
||||||
|
public WarehousesController(AppDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
// отдать все склады
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> GetWarehouses()
|
||||||
|
{
|
||||||
|
var warehouses = await _context.Warehouses.ToListAsync(); // беру все
|
||||||
|
return Ok(warehouses);
|
||||||
|
}
|
||||||
|
|
||||||
|
// добавить склад
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> CreateWarehouse(Warehouse warehouse) // приходит название и адрес
|
||||||
|
{
|
||||||
|
_context.Warehouses.Add(warehouse); // добавляю
|
||||||
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
|
return Ok(warehouse);
|
||||||
|
}
|
||||||
|
|
||||||
|
// изменить склад
|
||||||
|
[HttpPut("{id}")]
|
||||||
|
public async Task<IActionResult> UpdateWarehouse(int id, Warehouse warehouse)
|
||||||
|
{
|
||||||
|
var existingWarehouse = await _context.Warehouses.FindAsync(id); // ищу старый
|
||||||
|
|
||||||
|
if (existingWarehouse == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
// меняю поля
|
||||||
|
existingWarehouse.Name = warehouse.Name;
|
||||||
|
existingWarehouse.Address = warehouse.Address;
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
|
|
||||||
|
return Ok(existingWarehouse);
|
||||||
|
}
|
||||||
|
|
||||||
|
// удалить склад
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
public async Task<IActionResult> DeleteWarehouse(int id)
|
||||||
|
{
|
||||||
|
var warehouse = await _context.Warehouses.FindAsync(id); // ищу
|
||||||
|
|
||||||
|
if (warehouse == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
_context.Warehouses.Remove(warehouse); // удаляю
|
||||||
|
await _context.SaveChangesAsync(); // сохраняю
|
||||||
|
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
18
DEMO/DEMO.csproj
Normal file
18
DEMO/DEMO.csproj
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.5" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.12">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.12" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
6
DEMO/DEMO.http
Normal file
6
DEMO/DEMO.http
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
@DEMO_HostAddress = http://localhost:5214
|
||||||
|
|
||||||
|
GET {{DEMO_HostAddress}}/weatherforecast/
|
||||||
|
Accept: application/json
|
||||||
|
|
||||||
|
###
|
||||||
23
DEMO/Data/AppDbContext.cs
Normal file
23
DEMO/Data/AppDbContext.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
// это моя база данных, тут все таблицы
|
||||||
|
using DEMO.Models; // мои классы
|
||||||
|
using Microsoft.EntityFrameworkCore; // сама база
|
||||||
|
|
||||||
|
namespace DEMO.Data;
|
||||||
|
|
||||||
|
public class AppDbContext : DbContext // главный класс базы
|
||||||
|
{
|
||||||
|
// конструктор, просто передает настройки дальше
|
||||||
|
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// каждая строчка это одна таблица в базе
|
||||||
|
public DbSet<Category> Categories { get; set; } // категории
|
||||||
|
public DbSet<Product> Products { get; set; } // товары
|
||||||
|
public DbSet<Warehouse> Warehouses { get; set; } // склады
|
||||||
|
public DbSet<Stock> Stocks { get; set; } // остатки
|
||||||
|
public DbSet<Role> Roles { get; set; } // роли
|
||||||
|
public DbSet<User> Users { get; set; } // пользователи
|
||||||
|
public DbSet<Order> Orders { get; set; } // заказы
|
||||||
|
public DbSet<OrderItem> OrderItems { get; set; } // товары в заказах
|
||||||
|
}
|
||||||
20
DEMO/Data/AppDbContextFactory.cs
Normal file
20
DEMO/Data/AppDbContextFactory.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
// это нужно чтоб миграции знали как подключиться к базе
|
||||||
|
using Microsoft.EntityFrameworkCore; // работа с базой
|
||||||
|
using Microsoft.EntityFrameworkCore.Design; // для миграций
|
||||||
|
|
||||||
|
namespace DEMO.Data;
|
||||||
|
|
||||||
|
public class AppDbContextFactory : IDesignTimeDbContextFactory<AppDbContext> // фабрика для создания базы
|
||||||
|
{
|
||||||
|
// создаю базу с моей строкой подключения
|
||||||
|
public AppDbContext CreateDbContext(string[] args)
|
||||||
|
{
|
||||||
|
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>(); // настройки
|
||||||
|
|
||||||
|
// говорю где лежит база (у меня на компе)
|
||||||
|
optionsBuilder.UseSqlServer(
|
||||||
|
"Server=(localdb)\\MSSQLLocalDB;Database=DemoDB;Trusted_Connection=True;TrustServerCertificate=True;");
|
||||||
|
|
||||||
|
return new AppDbContext(optionsBuilder.Options); // отдаю готовую базу
|
||||||
|
}
|
||||||
|
}
|
||||||
46
DEMO/Migrations/20260908211451_InitialCreate.Designer.cs
generated
Normal file
46
DEMO/Migrations/20260908211451_InitialCreate.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
// <auto-generated />
|
||||||
|
using DEMO.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DEMO.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDbContext))]
|
||||||
|
[Migration("20260908211451_InitialCreate")]
|
||||||
|
partial class InitialCreate
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.12")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||||
|
|
||||||
|
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Category", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Categories");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
DEMO/Migrations/20260908211451_InitialCreate.cs
Normal file
34
DEMO/Migrations/20260908211451_InitialCreate.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DEMO.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class InitialCreate : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Categories",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("SqlServer:Identity", "1, 1"),
|
||||||
|
Name = table.Column<string>(type: "nvarchar(max)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Categories", x => x.Id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Categories");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
82
DEMO/Migrations/20260908211931_AddProducts.Designer.cs
generated
Normal file
82
DEMO/Migrations/20260908211931_AddProducts.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
// <auto-generated />
|
||||||
|
using DEMO.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DEMO.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDbContext))]
|
||||||
|
[Migration("20260908211931_AddProducts")]
|
||||||
|
partial class AddProducts
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.12")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||||
|
|
||||||
|
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Category", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Categories");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Product", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("CategoryId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<decimal>("Price")
|
||||||
|
.HasColumnType("decimal(18,2)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CategoryId");
|
||||||
|
|
||||||
|
b.ToTable("Products");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Product", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DEMO.Models.Category", "Category")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CategoryId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Category");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
47
DEMO/Migrations/20260908211931_AddProducts.cs
Normal file
47
DEMO/Migrations/20260908211931_AddProducts.cs
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DEMO.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddProducts : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Products",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("SqlServer:Identity", "1, 1"),
|
||||||
|
Name = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||||
|
Price = table.Column<decimal>(type: "decimal(18,2)", nullable: false),
|
||||||
|
CategoryId = table.Column<int>(type: "int", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Products", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Products_Categories_CategoryId",
|
||||||
|
column: x => x.CategoryId,
|
||||||
|
principalTable: "Categories",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Products_CategoryId",
|
||||||
|
table: "Products",
|
||||||
|
column: "CategoryId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Products");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
103
DEMO/Migrations/20260908215804_AddWarehouses.Designer.cs
generated
Normal file
103
DEMO/Migrations/20260908215804_AddWarehouses.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
// <auto-generated />
|
||||||
|
using DEMO.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DEMO.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDbContext))]
|
||||||
|
[Migration("20260908215804_AddWarehouses")]
|
||||||
|
partial class AddWarehouses
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.12")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||||
|
|
||||||
|
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Category", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Categories");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Product", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("CategoryId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<decimal>("Price")
|
||||||
|
.HasColumnType("decimal(18,2)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CategoryId");
|
||||||
|
|
||||||
|
b.ToTable("Products");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Warehouse", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Address")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Warehouses");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Product", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DEMO.Models.Category", "Category")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CategoryId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Category");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
DEMO/Migrations/20260908215804_AddWarehouses.cs
Normal file
35
DEMO/Migrations/20260908215804_AddWarehouses.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DEMO.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddWarehouses : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Warehouses",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("SqlServer:Identity", "1, 1"),
|
||||||
|
Name = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||||
|
Address = table.Column<string>(type: "nvarchar(max)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Warehouses", x => x.Id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Warehouses");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
148
DEMO/Migrations/20260908220130_AddStock.Designer.cs
generated
Normal file
148
DEMO/Migrations/20260908220130_AddStock.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
// <auto-generated />
|
||||||
|
using DEMO.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DEMO.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDbContext))]
|
||||||
|
[Migration("20260908220130_AddStock")]
|
||||||
|
partial class AddStock
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.12")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||||
|
|
||||||
|
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Category", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Categories");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Product", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("CategoryId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<decimal>("Price")
|
||||||
|
.HasColumnType("decimal(18,2)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CategoryId");
|
||||||
|
|
||||||
|
b.ToTable("Products");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Stock", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("ProductId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("Quantity")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("WarehouseId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ProductId");
|
||||||
|
|
||||||
|
b.HasIndex("WarehouseId");
|
||||||
|
|
||||||
|
b.ToTable("Stocks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Warehouse", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Address")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Warehouses");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Product", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DEMO.Models.Category", "Category")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CategoryId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Category");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Stock", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DEMO.Models.Product", "Product")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ProductId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("DEMO.Models.Warehouse", "Warehouse")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("WarehouseId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Product");
|
||||||
|
|
||||||
|
b.Navigation("Warehouse");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
58
DEMO/Migrations/20260908220130_AddStock.cs
Normal file
58
DEMO/Migrations/20260908220130_AddStock.cs
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DEMO.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddStock : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Stocks",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("SqlServer:Identity", "1, 1"),
|
||||||
|
ProductId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
WarehouseId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
Quantity = table.Column<int>(type: "int", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Stocks", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Stocks_Products_ProductId",
|
||||||
|
column: x => x.ProductId,
|
||||||
|
principalTable: "Products",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Stocks_Warehouses_WarehouseId",
|
||||||
|
column: x => x.WarehouseId,
|
||||||
|
principalTable: "Warehouses",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Stocks_ProductId",
|
||||||
|
table: "Stocks",
|
||||||
|
column: "ProductId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Stocks_WarehouseId",
|
||||||
|
table: "Stocks",
|
||||||
|
column: "WarehouseId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Stocks");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
207
DEMO/Migrations/20260908220723_AddUsersAndRoles.Designer.cs
generated
Normal file
207
DEMO/Migrations/20260908220723_AddUsersAndRoles.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,207 @@
|
||||||
|
// <auto-generated />
|
||||||
|
using DEMO.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DEMO.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDbContext))]
|
||||||
|
[Migration("20260908220723_AddUsersAndRoles")]
|
||||||
|
partial class AddUsersAndRoles
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.12")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||||
|
|
||||||
|
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Category", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Categories");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Product", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("CategoryId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<decimal>("Price")
|
||||||
|
.HasColumnType("decimal(18,2)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CategoryId");
|
||||||
|
|
||||||
|
b.ToTable("Products");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Role", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Roles");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Stock", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("ProductId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("Quantity")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("WarehouseId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ProductId");
|
||||||
|
|
||||||
|
b.HasIndex("WarehouseId");
|
||||||
|
|
||||||
|
b.ToTable("Stocks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Login")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<int>("RoleId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("RoleId");
|
||||||
|
|
||||||
|
b.ToTable("Users");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Warehouse", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Address")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Warehouses");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Product", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DEMO.Models.Category", "Category")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CategoryId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Category");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Stock", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DEMO.Models.Product", "Product")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ProductId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("DEMO.Models.Warehouse", "Warehouse")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("WarehouseId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Product");
|
||||||
|
|
||||||
|
b.Navigation("Warehouse");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.User", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DEMO.Models.Role", "Role")
|
||||||
|
.WithMany("Users")
|
||||||
|
.HasForeignKey("RoleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Role");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Role", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Users");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
63
DEMO/Migrations/20260908220723_AddUsersAndRoles.cs
Normal file
63
DEMO/Migrations/20260908220723_AddUsersAndRoles.cs
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DEMO.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddUsersAndRoles : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Roles",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("SqlServer:Identity", "1, 1"),
|
||||||
|
Name = table.Column<string>(type: "nvarchar(max)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Roles", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Users",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("SqlServer:Identity", "1, 1"),
|
||||||
|
Login = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||||
|
Password = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||||
|
RoleId = table.Column<int>(type: "int", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Users", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Users_Roles_RoleId",
|
||||||
|
column: x => x.RoleId,
|
||||||
|
principalTable: "Roles",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Users_RoleId",
|
||||||
|
table: "Users",
|
||||||
|
column: "RoleId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Users");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Roles");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
239
DEMO/Migrations/20260908221344_AddOrders.Designer.cs
generated
Normal file
239
DEMO/Migrations/20260908221344_AddOrders.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,239 @@
|
||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using DEMO.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DEMO.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDbContext))]
|
||||||
|
[Migration("20260908221344_AddOrders")]
|
||||||
|
partial class AddOrders
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.12")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||||
|
|
||||||
|
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Category", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Categories");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Order", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("Date")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<string>("Status")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("Orders");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Product", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("CategoryId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<decimal>("Price")
|
||||||
|
.HasColumnType("decimal(18,2)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CategoryId");
|
||||||
|
|
||||||
|
b.ToTable("Products");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Role", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Roles");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Stock", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("ProductId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("Quantity")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("WarehouseId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ProductId");
|
||||||
|
|
||||||
|
b.HasIndex("WarehouseId");
|
||||||
|
|
||||||
|
b.ToTable("Stocks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Login")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<int>("RoleId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("RoleId");
|
||||||
|
|
||||||
|
b.ToTable("Users");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Warehouse", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Address")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Warehouses");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Order", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DEMO.Models.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Product", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DEMO.Models.Category", "Category")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CategoryId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Category");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Stock", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DEMO.Models.Product", "Product")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ProductId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("DEMO.Models.Warehouse", "Warehouse")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("WarehouseId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Product");
|
||||||
|
|
||||||
|
b.Navigation("Warehouse");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.User", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DEMO.Models.Role", "Role")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RoleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Role");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
48
DEMO/Migrations/20260908221344_AddOrders.cs
Normal file
48
DEMO/Migrations/20260908221344_AddOrders.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DEMO.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddOrders : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Orders",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("SqlServer:Identity", "1, 1"),
|
||||||
|
UserId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
Date = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||||
|
Status = table.Column<string>(type: "nvarchar(max)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Orders", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Orders_Users_UserId",
|
||||||
|
column: x => x.UserId,
|
||||||
|
principalTable: "Users",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Orders_UserId",
|
||||||
|
table: "Orders",
|
||||||
|
column: "UserId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Orders");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
287
DEMO/Migrations/20260908221738_AddOrderItems.Designer.cs
generated
Normal file
287
DEMO/Migrations/20260908221738_AddOrderItems.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,287 @@
|
||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using DEMO.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DEMO.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDbContext))]
|
||||||
|
[Migration("20260908221738_AddOrderItems")]
|
||||||
|
partial class AddOrderItems
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.12")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||||
|
|
||||||
|
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Category", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Categories");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Order", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("Date")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<string>("Status")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("Orders");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.OrderItem", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("OrderId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<decimal>("Price")
|
||||||
|
.HasColumnType("decimal(18,2)");
|
||||||
|
|
||||||
|
b.Property<int>("ProductId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("Quantity")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("OrderId");
|
||||||
|
|
||||||
|
b.HasIndex("ProductId");
|
||||||
|
|
||||||
|
b.ToTable("OrderItems");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Product", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("CategoryId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<decimal>("Price")
|
||||||
|
.HasColumnType("decimal(18,2)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CategoryId");
|
||||||
|
|
||||||
|
b.ToTable("Products");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Role", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Roles");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Stock", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("ProductId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("Quantity")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("WarehouseId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ProductId");
|
||||||
|
|
||||||
|
b.HasIndex("WarehouseId");
|
||||||
|
|
||||||
|
b.ToTable("Stocks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Login")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<int>("RoleId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("RoleId");
|
||||||
|
|
||||||
|
b.ToTable("Users");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Warehouse", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Address")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Warehouses");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Order", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DEMO.Models.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.OrderItem", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DEMO.Models.Order", "Order")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("OrderId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("DEMO.Models.Product", "Product")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ProductId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Order");
|
||||||
|
|
||||||
|
b.Navigation("Product");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Product", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DEMO.Models.Category", "Category")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CategoryId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Category");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Stock", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DEMO.Models.Product", "Product")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ProductId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("DEMO.Models.Warehouse", "Warehouse")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("WarehouseId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Product");
|
||||||
|
|
||||||
|
b.Navigation("Warehouse");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.User", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DEMO.Models.Role", "Role")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RoleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Role");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
59
DEMO/Migrations/20260908221738_AddOrderItems.cs
Normal file
59
DEMO/Migrations/20260908221738_AddOrderItems.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DEMO.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddOrderItems : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "OrderItems",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("SqlServer:Identity", "1, 1"),
|
||||||
|
OrderId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
ProductId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
Quantity = table.Column<int>(type: "int", nullable: false),
|
||||||
|
Price = table.Column<decimal>(type: "decimal(18,2)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_OrderItems", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_OrderItems_Orders_OrderId",
|
||||||
|
column: x => x.OrderId,
|
||||||
|
principalTable: "Orders",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_OrderItems_Products_ProductId",
|
||||||
|
column: x => x.ProductId,
|
||||||
|
principalTable: "Products",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_OrderItems_OrderId",
|
||||||
|
table: "OrderItems",
|
||||||
|
column: "OrderId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_OrderItems_ProductId",
|
||||||
|
table: "OrderItems",
|
||||||
|
column: "ProductId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "OrderItems");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
284
DEMO/Migrations/AppDbContextModelSnapshot.cs
Normal file
284
DEMO/Migrations/AppDbContextModelSnapshot.cs
Normal file
|
|
@ -0,0 +1,284 @@
|
||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using DEMO.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DEMO.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDbContext))]
|
||||||
|
partial class AppDbContextModelSnapshot : ModelSnapshot
|
||||||
|
{
|
||||||
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.12")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||||
|
|
||||||
|
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Category", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Categories");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Order", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("Date")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<string>("Status")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("Orders");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.OrderItem", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("OrderId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<decimal>("Price")
|
||||||
|
.HasColumnType("decimal(18,2)");
|
||||||
|
|
||||||
|
b.Property<int>("ProductId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("Quantity")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("OrderId");
|
||||||
|
|
||||||
|
b.HasIndex("ProductId");
|
||||||
|
|
||||||
|
b.ToTable("OrderItems");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Product", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("CategoryId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<decimal>("Price")
|
||||||
|
.HasColumnType("decimal(18,2)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CategoryId");
|
||||||
|
|
||||||
|
b.ToTable("Products");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Role", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Roles");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Stock", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("ProductId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("Quantity")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("WarehouseId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ProductId");
|
||||||
|
|
||||||
|
b.HasIndex("WarehouseId");
|
||||||
|
|
||||||
|
b.ToTable("Stocks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Login")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<int>("RoleId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("RoleId");
|
||||||
|
|
||||||
|
b.ToTable("Users");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Warehouse", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Address")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Warehouses");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Order", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DEMO.Models.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.OrderItem", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DEMO.Models.Order", "Order")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("OrderId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("DEMO.Models.Product", "Product")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ProductId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Order");
|
||||||
|
|
||||||
|
b.Navigation("Product");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Product", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DEMO.Models.Category", "Category")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CategoryId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Category");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.Stock", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DEMO.Models.Product", "Product")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ProductId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("DEMO.Models.Warehouse", "Warehouse")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("WarehouseId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Product");
|
||||||
|
|
||||||
|
b.Navigation("Warehouse");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DEMO.Models.User", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DEMO.Models.Role", "Role")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RoleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Role");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
DEMO/Models/Category.cs
Normal file
9
DEMO/Models/Category.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
// категория товаров, например еда или одежда
|
||||||
|
namespace DEMO.Models;
|
||||||
|
|
||||||
|
public class Category
|
||||||
|
{
|
||||||
|
public int Id { get; set; } // номер
|
||||||
|
|
||||||
|
public string Name { get; set; } = string.Empty; // название
|
||||||
|
}
|
||||||
15
DEMO/Models/Order.cs
Normal file
15
DEMO/Models/Order.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
// заказ от покупателя
|
||||||
|
namespace DEMO.Models;
|
||||||
|
|
||||||
|
public class Order
|
||||||
|
{
|
||||||
|
public int Id { get; set; } // номер заказа
|
||||||
|
|
||||||
|
public int UserId { get; set; } // кто заказал (номер человека)
|
||||||
|
|
||||||
|
public DateTime Date { get; set; } // когда заказал
|
||||||
|
|
||||||
|
public string Status { get; set; } = string.Empty; // статус: новый, готов и тд
|
||||||
|
|
||||||
|
public User? User { get; set; } // сам человек (подтянется)
|
||||||
|
}
|
||||||
19
DEMO/Models/OrderItem.cs
Normal file
19
DEMO/Models/OrderItem.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
// один товар внутри заказа
|
||||||
|
namespace DEMO.Models;
|
||||||
|
|
||||||
|
public class OrderItem
|
||||||
|
{
|
||||||
|
public int Id { get; set; } // номер
|
||||||
|
|
||||||
|
public int OrderId { get; set; } // к какому заказу относится
|
||||||
|
|
||||||
|
public int ProductId { get; set; } // какой товар
|
||||||
|
|
||||||
|
public int Quantity { get; set; } // сколько штук взяли
|
||||||
|
|
||||||
|
public decimal Price { get; set; } // цена на момент покупки
|
||||||
|
|
||||||
|
public Order? Order { get; set; } // сам заказ (подтянется)
|
||||||
|
|
||||||
|
public Product? Product { get; set; } // сам товар (подтянется)
|
||||||
|
}
|
||||||
20
DEMO/Models/Product.cs
Normal file
20
DEMO/Models/Product.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
// товар в магазине
|
||||||
|
using System.ComponentModel.DataAnnotations; // проверки
|
||||||
|
|
||||||
|
namespace DEMO.Models;
|
||||||
|
|
||||||
|
public class Product
|
||||||
|
{
|
||||||
|
public int Id { get; set; } // номер товара
|
||||||
|
|
||||||
|
[Required] // название обязательно
|
||||||
|
[MinLength(2)] // минимум 2 буквы
|
||||||
|
public string Name { get; set; } = string.Empty; // название
|
||||||
|
|
||||||
|
[Range(0.01, double.MaxValue)] // цена больше нуля
|
||||||
|
public decimal Price { get; set; } // цена
|
||||||
|
|
||||||
|
public int CategoryId { get; set; } // к какой категории относится
|
||||||
|
|
||||||
|
public Category? Category { get; set; } // сама категория (подтянется сама)
|
||||||
|
}
|
||||||
9
DEMO/Models/Role.cs
Normal file
9
DEMO/Models/Role.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
// роль человека, например админ или клиент
|
||||||
|
namespace DEMO.Models;
|
||||||
|
|
||||||
|
public class Role
|
||||||
|
{
|
||||||
|
public int Id { get; set; } // номер
|
||||||
|
|
||||||
|
public string Name { get; set; } = string.Empty; // название роли
|
||||||
|
}
|
||||||
17
DEMO/Models/Stock.cs
Normal file
17
DEMO/Models/Stock.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
// сколько какого товара лежит на складе
|
||||||
|
namespace DEMO.Models;
|
||||||
|
|
||||||
|
public class Stock
|
||||||
|
{
|
||||||
|
public int Id { get; set; } // номер записи
|
||||||
|
|
||||||
|
public int ProductId { get; set; } // какой товар
|
||||||
|
|
||||||
|
public int WarehouseId { get; set; } // какой склад
|
||||||
|
|
||||||
|
public int Quantity { get; set; } // сколько штук
|
||||||
|
|
||||||
|
public Product? Product { get; set; } // сам товар (подтянется)
|
||||||
|
|
||||||
|
public Warehouse? Warehouse { get; set; } // сам склад (подтянется)
|
||||||
|
}
|
||||||
15
DEMO/Models/User.cs
Normal file
15
DEMO/Models/User.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
// человек в системе
|
||||||
|
namespace DEMO.Models;
|
||||||
|
|
||||||
|
public class User
|
||||||
|
{
|
||||||
|
public int Id { get; set; } // номер
|
||||||
|
|
||||||
|
public string Login { get; set; } = string.Empty; // логин
|
||||||
|
|
||||||
|
public string Password { get; set; } = string.Empty; // пароль
|
||||||
|
|
||||||
|
public int RoleId { get; set; } // какая роль (номер)
|
||||||
|
|
||||||
|
public Role? Role { get; set; } // сама роль (подтянется)
|
||||||
|
}
|
||||||
11
DEMO/Models/Warehouse.cs
Normal file
11
DEMO/Models/Warehouse.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
// склад где лежат товары
|
||||||
|
namespace DEMO.Models;
|
||||||
|
|
||||||
|
public class Warehouse
|
||||||
|
{
|
||||||
|
public int Id { get; set; } // номер
|
||||||
|
|
||||||
|
public string Name { get; set; } = string.Empty; // название склада
|
||||||
|
|
||||||
|
public string Address { get; set; } = string.Empty; // где находится
|
||||||
|
}
|
||||||
34
DEMO/Program.cs
Normal file
34
DEMO/Program.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
// это главный файл, тут все начинается
|
||||||
|
using DEMO.Data; // база данных
|
||||||
|
using Microsoft.EntityFrameworkCore; // чтоб работать с базой
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
// говорю что у меня есть контроллеры (api)
|
||||||
|
builder.Services.AddControllers();
|
||||||
|
|
||||||
|
// подключаю базу данных
|
||||||
|
builder.Services.AddDbContext<AppDbContext>(options =>
|
||||||
|
options.UseSqlServer(
|
||||||
|
builder.Configuration.GetConnectionString("DefaultConnection"))); // беру строку из настроек
|
||||||
|
|
||||||
|
builder.Services.AddOpenApi(); // для проверки api
|
||||||
|
|
||||||
|
var app = builder.Build(); // строю программу
|
||||||
|
|
||||||
|
// в режиме разработки показываю api
|
||||||
|
if (app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.MapOpenApi();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseHttpsRedirection(); // всегда https
|
||||||
|
|
||||||
|
app.UseDefaultFiles(); // чтоб открывался index.html сам
|
||||||
|
app.UseStaticFiles(); // чтоб отдавать файлы из wwwroot
|
||||||
|
|
||||||
|
app.UseAuthorization(); // проверка прав (пока простая)
|
||||||
|
|
||||||
|
app.MapControllers(); // включаю все контроллеры
|
||||||
|
|
||||||
|
app.Run(); // запускаю, поехали
|
||||||
23
DEMO/Properties/launchSettings.json
Normal file
23
DEMO/Properties/launchSettings.json
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": false,
|
||||||
|
"applicationUrl": "http://localhost:5214",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"https": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": false,
|
||||||
|
"applicationUrl": "https://localhost:7184;http://localhost:5214",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
DEMO/appsettings.Development.json
Normal file
8
DEMO/appsettings.Development.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
DEMO/appsettings.json
Normal file
12
DEMO/appsettings.json
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"DefaultConnection": "Server=(localdb)\\MSSQLLocalDB;Database=DemoDB;Trusted_Connection=True;TrustServerCertificate=True;"
|
||||||
|
},
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
||||||
160
DEMO/wwwroot/index.html
Normal file
160
DEMO/wwwroot/index.html
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>DEMO Shop</title>
|
||||||
|
<!-- подключаю мои стили -->
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- экран входа, виден сначала -->
|
||||||
|
<div id="authScreen" class="auth-screen">
|
||||||
|
<div class="auth-card">
|
||||||
|
|
||||||
|
<!-- логотип -->
|
||||||
|
<div class="auth-logo">
|
||||||
|
DEMO
|
||||||
|
<small>МАГАЗИН</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- форма входа -->
|
||||||
|
<div id="loginFormContainer">
|
||||||
|
<div class="auth-heading">
|
||||||
|
<h1>Вход</h1>
|
||||||
|
<p>Войдите чтобы работать</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form id="loginForm" class="auth-form">
|
||||||
|
<label>
|
||||||
|
Логин
|
||||||
|
<input id="login" type="text" placeholder="Ваш логин" required>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Пароль
|
||||||
|
<input id="password" type="password" placeholder="Ваш пароль" required>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<!-- сюда пишу ошибку входа -->
|
||||||
|
<div id="loginError" class="auth-error"></div>
|
||||||
|
|
||||||
|
<button type="submit" class="auth-button">Войти</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="auth-switch">
|
||||||
|
<span>Нет аккаунта?</span>
|
||||||
|
<button id="showRegister" type="button">Зарегистрироваться</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- форма регистрации, сначала спрятана -->
|
||||||
|
<div id="registerFormContainer" class="hidden">
|
||||||
|
<div class="auth-heading">
|
||||||
|
<h1>Регистрация</h1>
|
||||||
|
<p>Создайте новый аккаунт</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form id="registerForm" class="auth-form">
|
||||||
|
<label>
|
||||||
|
Логин
|
||||||
|
<input id="registerLogin" type="text" placeholder="Придумайте логин" required>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Пароль
|
||||||
|
<input id="registerPassword" type="password" placeholder="Придумайте пароль" required>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Повторите пароль
|
||||||
|
<input id="registerPasswordRepeat" type="password" placeholder="Еще раз пароль" required>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<!-- сюда пишу ошибку регистрации -->
|
||||||
|
<div id="registerError" class="auth-error"></div>
|
||||||
|
|
||||||
|
<button type="submit" class="auth-button">Зарегистрироваться</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="auth-switch">
|
||||||
|
<span>Уже есть аккаунт?</span>
|
||||||
|
<button id="showLogin" type="button">Войти</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- главное окно, сначала спрятано -->
|
||||||
|
<div id="app" class="app hidden">
|
||||||
|
|
||||||
|
<!-- меню слева -->
|
||||||
|
<aside class="sidebar">
|
||||||
|
<div class="logo">
|
||||||
|
DEMO
|
||||||
|
<span>МАГАЗИН</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- кто сейчас вошел -->
|
||||||
|
<div class="user-panel">
|
||||||
|
<div class="user-avatar" id="userAvatar">U</div>
|
||||||
|
<div class="user-info">
|
||||||
|
<strong id="currentUser">Пользователь</strong>
|
||||||
|
<span id="currentRole">Client</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- кнопки страниц -->
|
||||||
|
<nav>
|
||||||
|
<button class="nav-button active" data-page="products">Товары</button>
|
||||||
|
<button class="nav-button" data-page="categories">Категории</button>
|
||||||
|
<button class="nav-button" data-page="warehouses">Склады</button>
|
||||||
|
<button class="nav-button" data-page="stock">Остатки</button>
|
||||||
|
<button class="nav-button" data-page="users">Пользователи</button>
|
||||||
|
<button class="nav-button" data-page="orders">Заказы</button>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- выйти из аккаунта -->
|
||||||
|
<button id="logoutButton" class="logout-button">Выйти</button>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- правая часть -->
|
||||||
|
<main class="content">
|
||||||
|
<header class="header">
|
||||||
|
<div>
|
||||||
|
<h1 id="pageTitle">Товары</h1>
|
||||||
|
<p id="pageDescription">Управление товарами магазина</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- кнопка добавить новое -->
|
||||||
|
<button id="addButton" class="primary-button">+ Добавить</button>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- сюда рисую таблицу или карточки -->
|
||||||
|
<section id="pageContent"></section>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- всплывающее окно формы -->
|
||||||
|
<div id="modal" class="modal hidden">
|
||||||
|
<div class="modal-overlay"></div>
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2 id="modalTitle">Добавить</h2>
|
||||||
|
<button id="closeModal" class="close-button">X</button>
|
||||||
|
</div>
|
||||||
|
<!-- сюда рисую поля формы -->
|
||||||
|
<form id="modalForm"></form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- маленькое сообщение снизу -->
|
||||||
|
<div id="toast" class="toast"></div>
|
||||||
|
|
||||||
|
<!-- подключаю мой код -->
|
||||||
|
<script src="script.js"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
877
DEMO/wwwroot/script.js
Normal file
877
DEMO/wwwroot/script.js
Normal file
|
|
@ -0,0 +1,877 @@
|
||||||
|
// нахожу экран входа и главное окно
|
||||||
|
const authScreen = document.getElementById("authScreen");
|
||||||
|
const app = document.getElementById("app");
|
||||||
|
|
||||||
|
// нахожу формы входа и регистрации
|
||||||
|
const loginFormContainer = document.getElementById("loginFormContainer");
|
||||||
|
const registerFormContainer = document.getElementById("registerFormContainer");
|
||||||
|
const loginForm = document.getElementById("loginForm");
|
||||||
|
const registerForm = document.getElementById("registerForm");
|
||||||
|
|
||||||
|
// сюда пишу ошибки
|
||||||
|
const loginError = document.getElementById("loginError");
|
||||||
|
const registerError = document.getElementById("registerError");
|
||||||
|
|
||||||
|
// кнопки переключения вход / регистрация
|
||||||
|
const showRegisterButton = document.getElementById("showRegister");
|
||||||
|
const showLoginButton = document.getElementById("showLogin");
|
||||||
|
|
||||||
|
// кнопка выйти
|
||||||
|
const logoutButton = document.getElementById("logoutButton");
|
||||||
|
|
||||||
|
// где показываю кто вошел
|
||||||
|
const currentUser = document.getElementById("currentUser");
|
||||||
|
const currentRole = document.getElementById("currentRole");
|
||||||
|
const userAvatar = document.getElementById("userAvatar");
|
||||||
|
|
||||||
|
// заголовок страницы и место для таблицы
|
||||||
|
const pageTitle = document.getElementById("pageTitle");
|
||||||
|
const pageDescription = document.getElementById("pageDescription");
|
||||||
|
const pageContent = document.getElementById("pageContent");
|
||||||
|
const addButton = document.getElementById("addButton");
|
||||||
|
|
||||||
|
// всплывающее окно
|
||||||
|
const modal = document.getElementById("modal");
|
||||||
|
const modalTitle = document.getElementById("modalTitle");
|
||||||
|
const modalForm = document.getElementById("modalForm");
|
||||||
|
const closeModal = document.getElementById("closeModal");
|
||||||
|
|
||||||
|
// маленькое сообщение снизу
|
||||||
|
const toast = document.getElementById("toast");
|
||||||
|
|
||||||
|
// список всех страниц и откуда брать данные
|
||||||
|
const pages = {
|
||||||
|
products: { title: "Товары", description: "Все товары магазина", endpoint: "/api/products" },
|
||||||
|
categories: { title: "Категории", description: "Категории товаров", endpoint: "/api/categories" },
|
||||||
|
warehouses: { title: "Склады", description: "Все склады", endpoint: "/api/warehouses" },
|
||||||
|
stock: { title: "Остатки", description: "Что лежит на складах", endpoint: "/api/stocks" },
|
||||||
|
users: { title: "Пользователи", description: "Все люди в системе", endpoint: "/api/users" },
|
||||||
|
orders: { title: "Заказы", description: "Все заказы", endpoint: "/api/orders" }
|
||||||
|
};
|
||||||
|
|
||||||
|
// какая страница открыта сейчас
|
||||||
|
let currentPage = "products";
|
||||||
|
// кого изменяю (если null значит добавляю нового)
|
||||||
|
let editingId = null;
|
||||||
|
// кто вошел в систему
|
||||||
|
let currentAuthUser = null;
|
||||||
|
|
||||||
|
// показываю сообщение снизу на 2 секунды
|
||||||
|
function showToast(message) {
|
||||||
|
toast.textContent = message; // пишу текст
|
||||||
|
toast.classList.add("show"); // показываю
|
||||||
|
setTimeout(() => {
|
||||||
|
toast.classList.remove("show"); // прячу обратно
|
||||||
|
}, 2500);
|
||||||
|
}
|
||||||
|
|
||||||
|
// защита от плохих символов чтоб не ломали страницу
|
||||||
|
function escapeHtml(value) {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return String(value)
|
||||||
|
.replaceAll("&", "&")
|
||||||
|
.replaceAll("<", "<")
|
||||||
|
.replaceAll(">", ">")
|
||||||
|
.replaceAll('"', """)
|
||||||
|
.replaceAll("'", "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// делаю цену красивой: 1000 -> 1 000,00 ₽
|
||||||
|
function formatMoney(value) {
|
||||||
|
return Number(value).toLocaleString("ru-RU", {
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2
|
||||||
|
}) + " ₽";
|
||||||
|
}
|
||||||
|
|
||||||
|
// делаю дату понятной
|
||||||
|
function formatDate(value) {
|
||||||
|
if (!value) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return new Date(value).toLocaleString("ru-RU");
|
||||||
|
}
|
||||||
|
|
||||||
|
// одна функция для всех запросов на сервер
|
||||||
|
async function api(url, options = {}) {
|
||||||
|
// отправляю запрос
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
// если ошибка то показываю что случилось
|
||||||
|
if (!response.ok) {
|
||||||
|
let message = "Ошибка.";
|
||||||
|
const text = await response.text();
|
||||||
|
if (text) {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(text);
|
||||||
|
message = data.title || data.message || text;
|
||||||
|
} catch {
|
||||||
|
message = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
// если пусто то возвращаю null
|
||||||
|
if (response.status === 204) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const text = await response.text();
|
||||||
|
if (!text) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return JSON.parse(text); // превращаю ответ в объект
|
||||||
|
}
|
||||||
|
|
||||||
|
// показываю форму входа
|
||||||
|
function showLoginForm() {
|
||||||
|
loginFormContainer.classList.remove("hidden");
|
||||||
|
registerFormContainer.classList.add("hidden");
|
||||||
|
loginError.textContent = "";
|
||||||
|
registerError.textContent = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// показываю форму регистрации
|
||||||
|
function showRegisterForm() {
|
||||||
|
loginFormContainer.classList.add("hidden");
|
||||||
|
registerFormContainer.classList.remove("hidden");
|
||||||
|
loginError.textContent = "";
|
||||||
|
registerError.textContent = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// захожу в программу после входа
|
||||||
|
function enterApplication(user) {
|
||||||
|
currentAuthUser = user; // запоминаю кто вошел
|
||||||
|
sessionStorage.setItem("demoUser", JSON.stringify(user)); // сохраняю чтоб не выкидывало
|
||||||
|
currentUser.textContent = user.login; // пишу логин
|
||||||
|
currentRole.textContent = user.role || "Client"; // пишу роль
|
||||||
|
userAvatar.textContent = user.login ? user.login.charAt(0).toUpperCase() : "U"; // первая буква в кружок
|
||||||
|
authScreen.classList.add("hidden"); // прячу вход
|
||||||
|
app.classList.remove("hidden"); // показываю программу
|
||||||
|
showPage("products"); // открываю товары
|
||||||
|
}
|
||||||
|
|
||||||
|
// выхожу из аккаунта
|
||||||
|
function logout() {
|
||||||
|
sessionStorage.removeItem("demoUser"); // забываю пользователя
|
||||||
|
currentAuthUser = null;
|
||||||
|
app.classList.add("hidden"); // прячу программу
|
||||||
|
authScreen.classList.remove("hidden"); // показываю вход
|
||||||
|
loginForm.reset(); // чищу поля
|
||||||
|
registerForm.reset();
|
||||||
|
showLoginForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
// когда жму войти
|
||||||
|
loginForm.addEventListener("submit", async (event) => {
|
||||||
|
event.preventDefault(); // чтоб страница не перезагружалась
|
||||||
|
loginError.textContent = "";
|
||||||
|
const login = document.getElementById("login").value.trim();
|
||||||
|
const password = document.getElementById("password").value;
|
||||||
|
if (!login || !password) {
|
||||||
|
loginError.textContent = "Напишите логин и пароль.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const user = await api("/api/auth/login", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({ login, password })
|
||||||
|
});
|
||||||
|
enterApplication(user); // захожу
|
||||||
|
loginForm.reset();
|
||||||
|
} catch (error) {
|
||||||
|
loginError.textContent = error.message || "Не получилось войти.";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// когда жму зарегистрироваться
|
||||||
|
registerForm.addEventListener("submit", async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
registerError.textContent = "";
|
||||||
|
const login = document.getElementById("registerLogin").value.trim();
|
||||||
|
const password = document.getElementById("registerPassword").value;
|
||||||
|
const passwordRepeat = document.getElementById("registerPasswordRepeat").value;
|
||||||
|
// простые проверки
|
||||||
|
if (!login || !password || !passwordRepeat) {
|
||||||
|
registerError.textContent = "Заполните все поля.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (login.length < 2) {
|
||||||
|
registerError.textContent = "Логин слишком короткий.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (password.length < 4) {
|
||||||
|
registerError.textContent = "Пароль слишком короткий.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (password !== passwordRepeat) {
|
||||||
|
registerError.textContent = "Пароли не совпадают.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await api("/api/auth/register", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({ login, password, roleId: 2 })
|
||||||
|
});
|
||||||
|
registerForm.reset();
|
||||||
|
showLoginForm(); // иду на вход
|
||||||
|
document.getElementById("login").value = login;
|
||||||
|
loginError.textContent = "Готово. Теперь войдите.";
|
||||||
|
} catch (error) {
|
||||||
|
registerError.textContent = error.message || "Не получилось.";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// кнопки переключения
|
||||||
|
showRegisterButton.addEventListener("click", showRegisterForm);
|
||||||
|
showLoginButton.addEventListener("click", showLoginForm);
|
||||||
|
logoutButton.addEventListener("click", logout);
|
||||||
|
|
||||||
|
// кнопки меню слева
|
||||||
|
document.querySelectorAll(".nav-button").forEach((button) => {
|
||||||
|
button.addEventListener("click", () => {
|
||||||
|
// убираю синий цвет у всех
|
||||||
|
document.querySelectorAll(".nav-button").forEach((b) => b.classList.remove("active"));
|
||||||
|
button.classList.add("active"); // крашу нажатую
|
||||||
|
showPage(button.dataset.page); // открываю страницу
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// открываю страницу (товары, заказы и тд)
|
||||||
|
async function showPage(page) {
|
||||||
|
currentPage = page; // запоминаю где я
|
||||||
|
const config = pages[page];
|
||||||
|
if (!config) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pageTitle.textContent = config.title; // пишу название
|
||||||
|
pageDescription.textContent = config.description;
|
||||||
|
pageContent.innerHTML = "<div class='empty-page'><strong>Загрузка...</strong></div>"; // пока грузится
|
||||||
|
try {
|
||||||
|
const data = await api(config.endpoint); // беру данные с сервера
|
||||||
|
renderPage(page, data); // рисую
|
||||||
|
} catch (error) {
|
||||||
|
pageContent.innerHTML = "<div class='error-page'>Не получилось: " + escapeHtml(error.message) + "</div>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// выбираю что рисовать
|
||||||
|
function renderPage(page, data) {
|
||||||
|
// если пусто то так и пишу
|
||||||
|
if (!Array.isArray(data) || data.length === 0) {
|
||||||
|
pageContent.innerHTML = "<div class='empty-page'><strong>Пока пусто</strong><span>Нажмите Добавить</span></div>";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (page === "products") {
|
||||||
|
renderProducts(data);
|
||||||
|
}
|
||||||
|
if (page === "categories") {
|
||||||
|
renderCategories(data);
|
||||||
|
}
|
||||||
|
if (page === "warehouses") {
|
||||||
|
renderWarehouses(data);
|
||||||
|
}
|
||||||
|
if (page === "stock") {
|
||||||
|
renderStock(data);
|
||||||
|
}
|
||||||
|
if (page === "users") {
|
||||||
|
renderUsers(data);
|
||||||
|
}
|
||||||
|
if (page === "orders") {
|
||||||
|
renderOrders(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// рисую товары карточками
|
||||||
|
function renderProducts(products) {
|
||||||
|
let html = "<div class=\"cards-grid\">"; // начало сетки
|
||||||
|
for (const p of products) {
|
||||||
|
html += "<div class=\"card\">";
|
||||||
|
html += "<div class=\"card-id\">#" + p.id + "</div>";
|
||||||
|
html += "<h3>" + escapeHtml(p.name) + "</h3>";
|
||||||
|
html += "<div class=\"card-price\">" + formatMoney(p.price) + "</div>";
|
||||||
|
html += "<div class=\"card-info\">Категория: <strong>" + escapeHtml(p.category ? p.category.name : "—") + "</strong></div>";
|
||||||
|
html += "<div class=\"card-actions\">";
|
||||||
|
html += "<button class=\"action-button\" onclick=\"editProduct(" + p.id + ")\">Изменить</button>";
|
||||||
|
html += "<button class=\"action-button delete\" onclick=\"deleteItem('products'," + p.id + ")\">Удалить</button>";
|
||||||
|
html += "</div></div>";
|
||||||
|
}
|
||||||
|
html += "</div>";
|
||||||
|
pageContent.innerHTML = html; // показываю все
|
||||||
|
}
|
||||||
|
|
||||||
|
// рисую категории таблицей
|
||||||
|
function renderCategories(categories) {
|
||||||
|
let html = "<div class=\"table-wrapper\"><table><thead><tr><th>ID</th><th>Название</th><th>Действия</th></tr></thead><tbody>";
|
||||||
|
for (const c of categories) {
|
||||||
|
html += "<tr><td>#" + c.id + "</td><td>" + escapeHtml(c.name) + "</td><td><div class=\"table-actions\">";
|
||||||
|
html += "<button class=\"action-button\" onclick=\"editCategory(" + c.id + ")\">Изменить</button>";
|
||||||
|
html += "<button class=\"action-button delete\" onclick=\"deleteItem('categories'," + c.id + ")\">Удалить</button>";
|
||||||
|
html += "</div></td></tr>";
|
||||||
|
}
|
||||||
|
html += "</tbody></table></div>";
|
||||||
|
pageContent.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
// рисую склады
|
||||||
|
function renderWarehouses(warehouses) {
|
||||||
|
let html = "<div class=\"table-wrapper\"><table><thead><tr><th>ID</th><th>Название</th><th>Адрес</th><th>Действия</th></tr></thead><tbody>";
|
||||||
|
for (const w of warehouses) {
|
||||||
|
html += "<tr><td>#" + w.id + "</td><td>" + escapeHtml(w.name) + "</td><td>" + escapeHtml(w.address) + "</td><td><div class=\"table-actions\">";
|
||||||
|
html += "<button class=\"action-button\" onclick=\"editWarehouse(" + w.id + ")\">Изменить</button>";
|
||||||
|
html += "<button class=\"action-button delete\" onclick=\"deleteItem('warehouses'," + w.id + ")\">Удалить</button>";
|
||||||
|
html += "</div></td></tr>";
|
||||||
|
}
|
||||||
|
html += "</tbody></table></div>";
|
||||||
|
pageContent.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
// рисую остатки
|
||||||
|
function renderStock(stocks) {
|
||||||
|
let html = "<div class=\"table-wrapper\"><table><thead><tr><th>ID</th><th>Товар</th><th>Склад</th><th>Кол-во</th><th>Действия</th></tr></thead><tbody>";
|
||||||
|
for (const s of stocks) {
|
||||||
|
const pname = s.product ? s.product.name : "—";
|
||||||
|
const wname = s.warehouse ? s.warehouse.name : "—";
|
||||||
|
html += "<tr><td>#" + s.id + "</td><td>" + escapeHtml(pname) + "</td><td>" + escapeHtml(wname) + "</td><td>" + s.quantity + "</td><td><div class=\"table-actions\">";
|
||||||
|
html += "<button class=\"action-button\" onclick=\"editStock(" + s.id + ")\">Изменить</button>";
|
||||||
|
html += "<button class=\"action-button delete\" onclick=\"deleteItem('stock'," + s.id + ")\">Удалить</button>";
|
||||||
|
html += "</div></td></tr>";
|
||||||
|
}
|
||||||
|
html += "</tbody></table></div>";
|
||||||
|
pageContent.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
// рисую пользователей
|
||||||
|
function renderUsers(users) {
|
||||||
|
let html = "<div class=\"table-wrapper\"><table><thead><tr><th>ID</th><th>Логин</th><th>Роль</th><th>Действия</th></tr></thead><tbody>";
|
||||||
|
for (const u of users) {
|
||||||
|
const rname = u.role ? u.role.name : "—";
|
||||||
|
html += "<tr><td>#" + u.id + "</td><td>" + escapeHtml(u.login) + "</td><td><span class=\"status\">" + escapeHtml(rname) + "</span></td><td><div class=\"table-actions\">";
|
||||||
|
html += "<button class=\"action-button\" onclick=\"editUser(" + u.id + ")\">Изменить</button>";
|
||||||
|
html += "<button class=\"action-button delete\" onclick=\"deleteItem('users'," + u.id + ")\">Удалить</button>";
|
||||||
|
html += "</div></td></tr>";
|
||||||
|
}
|
||||||
|
html += "</tbody></table></div>";
|
||||||
|
pageContent.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
// рисую заказы
|
||||||
|
function renderOrders(orders) {
|
||||||
|
let html = "<div class=\"table-wrapper\"><table><thead><tr><th>ID</th><th>Кто заказал</th><th>Дата</th><th>Статус</th><th>Действия</th></tr></thead><tbody>";
|
||||||
|
for (const o of orders) {
|
||||||
|
const uname = o.user ? o.user.login : "—";
|
||||||
|
html += "<tr><td>#" + o.id + "</td><td>" + escapeHtml(uname) + "</td><td>" + formatDate(o.date) + "</td><td><span class=\"status\">" + escapeHtml(o.status) + "</span></td><td><div class=\"table-actions\">";
|
||||||
|
html += "<button class=\"action-button\" onclick=\"showOrderItems(" + o.id + ")\">Товары</button>";
|
||||||
|
html += "<button class=\"action-button\" onclick=\"editOrder(" + o.id + ")\">Изменить</button>";
|
||||||
|
html += "<button class=\"action-button delete\" onclick=\"deleteItem('orders'," + o.id + ")\">Удалить</button>";
|
||||||
|
html += "</div></td></tr>";
|
||||||
|
}
|
||||||
|
html += "</tbody></table></div>";
|
||||||
|
pageContent.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
// кнопка Добавить сверху
|
||||||
|
addButton.addEventListener("click", () => {
|
||||||
|
editingId = null; // значит добавляю а не изменяю
|
||||||
|
if (currentPage === "products") {
|
||||||
|
openProductForm();
|
||||||
|
}
|
||||||
|
if (currentPage === "categories") {
|
||||||
|
openCategoryForm();
|
||||||
|
}
|
||||||
|
if (currentPage === "warehouses") {
|
||||||
|
openWarehouseForm();
|
||||||
|
}
|
||||||
|
if (currentPage === "stock") {
|
||||||
|
openStockForm();
|
||||||
|
}
|
||||||
|
if (currentPage === "users") {
|
||||||
|
openUserForm();
|
||||||
|
}
|
||||||
|
if (currentPage === "orders") {
|
||||||
|
openOrderForm();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// открываю всплывающее окно
|
||||||
|
function openModal(title, content) {
|
||||||
|
modalTitle.textContent = title; // пишу заголовок
|
||||||
|
modalForm.innerHTML = content; // рисую поля
|
||||||
|
modal.classList.remove("hidden"); // показываю
|
||||||
|
}
|
||||||
|
|
||||||
|
// закрываю окно
|
||||||
|
function closeModalWindow() {
|
||||||
|
modal.classList.add("hidden"); // прячу
|
||||||
|
modalForm.innerHTML = ""; // чищу
|
||||||
|
editingId = null; // забываю кого изменял
|
||||||
|
}
|
||||||
|
|
||||||
|
// закрытие по крестику и по темному фону
|
||||||
|
closeModal.addEventListener("click", closeModalWindow);
|
||||||
|
document.querySelector(".modal-overlay").addEventListener("click", closeModalWindow);
|
||||||
|
|
||||||
|
// форма товара
|
||||||
|
async function openProductForm(product = null) {
|
||||||
|
const categories = await api("/api/categories"); // беру категории для списка
|
||||||
|
let options = "";
|
||||||
|
for (const c of categories) {
|
||||||
|
const sel = product && product.categoryId === c.id ? "selected" : "";
|
||||||
|
options += "<option value=\"" + c.id + "\" " + sel + ">" + escapeHtml(c.name) + "</option>";
|
||||||
|
}
|
||||||
|
const title = product ? "Изменить товар" : "Добавить товар";
|
||||||
|
const name = product ? escapeHtml(product.name) : "";
|
||||||
|
const price = product ? product.price : "";
|
||||||
|
openModal(title,
|
||||||
|
"<div class=\"form\">" +
|
||||||
|
"<label>Название<input id=\"productName\" type=\"text\" value=\"" + name + "\"></label>" +
|
||||||
|
"<label>Цена<input id=\"productPrice\" type=\"number\" min=\"0.01\" step=\"0.01\" value=\"" + price + "\"></label>" +
|
||||||
|
"<label>Категория<select id=\"productCategory\">" + options + "</select></label>" +
|
||||||
|
"<div class=\"form-actions\"><button type=\"button\" class=\"secondary-button\" onclick=\"closeModalWindow()\">Отмена</button>" +
|
||||||
|
"<button type=\"button\" class=\"primary-button\" onclick=\"saveProduct()\">Сохранить</button></div></div>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// сохраняю товар
|
||||||
|
async function saveProduct() {
|
||||||
|
const name = document.getElementById("productName").value.trim();
|
||||||
|
const price = Number(document.getElementById("productPrice").value);
|
||||||
|
const categoryId = Number(document.getElementById("productCategory").value);
|
||||||
|
if (!name || name.length < 2) {
|
||||||
|
showToast("Название слишком короткое.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!price || price <= 0) {
|
||||||
|
showToast("Цена должна быть больше 0.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const data = { name, price, categoryId };
|
||||||
|
if (editingId) {
|
||||||
|
await api("/api/products/" + editingId, { method: "PUT", body: JSON.stringify(data) }); // изменяю
|
||||||
|
showToast("Товар изменен.");
|
||||||
|
} else {
|
||||||
|
await api("/api/products", { method: "POST", body: JSON.stringify(data) }); // добавляю
|
||||||
|
showToast("Товар добавлен.");
|
||||||
|
}
|
||||||
|
closeModalWindow();
|
||||||
|
showPage("products"); // обновляю страницу
|
||||||
|
} catch (error) {
|
||||||
|
showToast(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// жму изменить на товаре
|
||||||
|
async function editProduct(id) {
|
||||||
|
const products = await api("/api/products"); // беру все
|
||||||
|
const product = products.find((x) => x.id === id); // ищу нужный
|
||||||
|
if (!product) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editingId = id; // запоминаю кого меняю
|
||||||
|
await openProductForm(product);
|
||||||
|
}
|
||||||
|
|
||||||
|
// форма категории
|
||||||
|
function openCategoryForm(category = null) {
|
||||||
|
const title = category ? "Изменить категорию" : "Добавить категорию";
|
||||||
|
const name = category ? escapeHtml(category.name) : "";
|
||||||
|
openModal(title,
|
||||||
|
"<div class=\"form\">" +
|
||||||
|
"<label>Название<input id=\"categoryName\" type=\"text\" value=\"" + name + "\"></label>" +
|
||||||
|
"<div class=\"form-actions\"><button type=\"button\" class=\"secondary-button\" onclick=\"closeModalWindow()\">Отмена</button>" +
|
||||||
|
"<button type=\"button\" class=\"primary-button\" onclick=\"saveCategory()\">Сохранить</button></div></div>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// сохраняю категорию
|
||||||
|
async function saveCategory() {
|
||||||
|
const name = document.getElementById("categoryName").value.trim();
|
||||||
|
if (!name) {
|
||||||
|
showToast("Напишите название.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (editingId) {
|
||||||
|
await api("/api/categories/" + editingId, { method: "PUT", body: JSON.stringify({ name }) });
|
||||||
|
showToast("Категория изменена.");
|
||||||
|
} else {
|
||||||
|
await api("/api/categories", { method: "POST", body: JSON.stringify({ name }) });
|
||||||
|
showToast("Категория добавлена.");
|
||||||
|
}
|
||||||
|
closeModalWindow();
|
||||||
|
showPage("categories");
|
||||||
|
} catch (error) {
|
||||||
|
showToast(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// жму изменить на категории
|
||||||
|
async function editCategory(id) {
|
||||||
|
const categories = await api("/api/categories");
|
||||||
|
const category = categories.find((x) => x.id === id);
|
||||||
|
if (!category) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editingId = id;
|
||||||
|
openCategoryForm(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
// форма склада
|
||||||
|
function openWarehouseForm(warehouse = null) {
|
||||||
|
const title = warehouse ? "Изменить склад" : "Добавить склад";
|
||||||
|
const name = warehouse ? escapeHtml(warehouse.name) : "";
|
||||||
|
const address = warehouse ? escapeHtml(warehouse.address) : "";
|
||||||
|
openModal(title,
|
||||||
|
"<div class=\"form\">" +
|
||||||
|
"<label>Название<input id=\"warehouseName\" type=\"text\" value=\"" + name + "\"></label>" +
|
||||||
|
"<label>Адрес<input id=\"warehouseAddress\" type=\"text\" value=\"" + address + "\"></label>" +
|
||||||
|
"<div class=\"form-actions\"><button type=\"button\" class=\"secondary-button\" onclick=\"closeModalWindow()\">Отмена</button>" +
|
||||||
|
"<button type=\"button\" class=\"primary-button\" onclick=\"saveWarehouse()\">Сохранить</button></div></div>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// сохраняю склад
|
||||||
|
async function saveWarehouse() {
|
||||||
|
const name = document.getElementById("warehouseName").value.trim();
|
||||||
|
const address = document.getElementById("warehouseAddress").value.trim();
|
||||||
|
if (!name || !address) {
|
||||||
|
showToast("Заполните все поля.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const data = { name, address };
|
||||||
|
if (editingId) {
|
||||||
|
await api("/api/warehouses/" + editingId, { method: "PUT", body: JSON.stringify(data) });
|
||||||
|
showToast("Склад изменен.");
|
||||||
|
} else {
|
||||||
|
await api("/api/warehouses", { method: "POST", body: JSON.stringify(data) });
|
||||||
|
showToast("Склад добавлен.");
|
||||||
|
}
|
||||||
|
closeModalWindow();
|
||||||
|
showPage("warehouses");
|
||||||
|
} catch (error) {
|
||||||
|
showToast(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// жму изменить на складе
|
||||||
|
async function editWarehouse(id) {
|
||||||
|
const warehouses = await api("/api/warehouses");
|
||||||
|
const warehouse = warehouses.find((x) => x.id === id);
|
||||||
|
if (!warehouse) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editingId = id;
|
||||||
|
openWarehouseForm(warehouse);
|
||||||
|
}
|
||||||
|
|
||||||
|
// форма остатка
|
||||||
|
async function openStockForm(stock = null) {
|
||||||
|
const products = await api("/api/products"); // беру товары
|
||||||
|
const warehouses = await api("/api/warehouses"); // беру склады
|
||||||
|
let pOptions = "";
|
||||||
|
for (const p of products) {
|
||||||
|
const sel = stock && stock.productId === p.id ? "selected" : "";
|
||||||
|
pOptions += "<option value=\"" + p.id + "\" " + sel + ">" + escapeHtml(p.name) + "</option>";
|
||||||
|
}
|
||||||
|
let wOptions = "";
|
||||||
|
for (const w of warehouses) {
|
||||||
|
const sel = stock && stock.warehouseId === w.id ? "selected" : "";
|
||||||
|
wOptions += "<option value=\"" + w.id + "\" " + sel + ">" + escapeHtml(w.name) + "</option>";
|
||||||
|
}
|
||||||
|
const qty = stock ? stock.quantity : 0;
|
||||||
|
const title = stock ? "Изменить остаток" : "Добавить остаток";
|
||||||
|
openModal(title,
|
||||||
|
"<div class=\"form\">" +
|
||||||
|
"<label>Товар<select id=\"stockProduct\">" + pOptions + "</select></label>" +
|
||||||
|
"<label>Склад<select id=\"stockWarehouse\">" + wOptions + "</select></label>" +
|
||||||
|
"<label>Количество<input id=\"stockQuantity\" type=\"number\" min=\"0\" value=\"" + qty + "\"></label>" +
|
||||||
|
"<div class=\"form-actions\"><button type=\"button\" class=\"secondary-button\" onclick=\"closeModalWindow()\">Отмена</button>" +
|
||||||
|
"<button type=\"button\" class=\"primary-button\" onclick=\"saveStock()\">Сохранить</button></div></div>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// сохраняю остаток
|
||||||
|
async function saveStock() {
|
||||||
|
const productId = Number(document.getElementById("stockProduct").value);
|
||||||
|
const warehouseId = Number(document.getElementById("stockWarehouse").value);
|
||||||
|
const quantity = Number(document.getElementById("stockQuantity").value);
|
||||||
|
if (quantity < 0) {
|
||||||
|
showToast("Количество не может быть минусом.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const data = { productId, warehouseId, quantity };
|
||||||
|
if (editingId) {
|
||||||
|
await api("/api/stocks/" + editingId, { method: "PUT", body: JSON.stringify(data) });
|
||||||
|
showToast("Остаток изменен.");
|
||||||
|
} else {
|
||||||
|
await api("/api/stocks", { method: "POST", body: JSON.stringify(data) });
|
||||||
|
showToast("Остаток добавлен.");
|
||||||
|
}
|
||||||
|
closeModalWindow();
|
||||||
|
showPage("stock");
|
||||||
|
} catch (error) {
|
||||||
|
showToast(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// жму изменить на остатке
|
||||||
|
async function editStock(id) {
|
||||||
|
const stocks = await api("/api/stocks");
|
||||||
|
const stock = stocks.find((x) => x.id === id);
|
||||||
|
if (!stock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editingId = id;
|
||||||
|
await openStockForm(stock);
|
||||||
|
}
|
||||||
|
|
||||||
|
// форма пользователя
|
||||||
|
async function openUserForm(user = null) {
|
||||||
|
const roles = await api("/api/roles"); // беру роли
|
||||||
|
let options = "";
|
||||||
|
for (const r of roles) {
|
||||||
|
const sel = user && user.roleId === r.id ? "selected" : "";
|
||||||
|
options += "<option value=\"" + r.id + "\" " + sel + ">" + escapeHtml(r.name) + "</option>";
|
||||||
|
}
|
||||||
|
const title = user ? "Изменить пользователя" : "Добавить пользователя";
|
||||||
|
const login = user ? escapeHtml(user.login) : "";
|
||||||
|
const password = user ? escapeHtml(user.password) : "";
|
||||||
|
openModal(title,
|
||||||
|
"<div class=\"form\">" +
|
||||||
|
"<label>Логин<input id=\"userLogin\" type=\"text\" value=\"" + login + "\"></label>" +
|
||||||
|
"<label>Пароль<input id=\"userPassword\" type=\"text\" value=\"" + password + "\"></label>" +
|
||||||
|
"<label>Роль<select id=\"userRole\">" + options + "</select></label>" +
|
||||||
|
"<div class=\"form-actions\"><button type=\"button\" class=\"secondary-button\" onclick=\"closeModalWindow()\">Отмена</button>" +
|
||||||
|
"<button type=\"button\" class=\"primary-button\" onclick=\"saveUser()\">Сохранить</button></div></div>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// сохраняю пользователя
|
||||||
|
async function saveUser() {
|
||||||
|
const login = document.getElementById("userLogin").value.trim();
|
||||||
|
const password = document.getElementById("userPassword").value;
|
||||||
|
const roleId = Number(document.getElementById("userRole").value);
|
||||||
|
if (!login || !password) {
|
||||||
|
showToast("Заполните все поля.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const data = { login, password, roleId };
|
||||||
|
if (editingId) {
|
||||||
|
await api("/api/users/" + editingId, { method: "PUT", body: JSON.stringify(data) });
|
||||||
|
showToast("Пользователь изменен.");
|
||||||
|
} else {
|
||||||
|
await api("/api/users", { method: "POST", body: JSON.stringify(data) });
|
||||||
|
showToast("Пользователь добавлен.");
|
||||||
|
}
|
||||||
|
closeModalWindow();
|
||||||
|
showPage("users");
|
||||||
|
} catch (error) {
|
||||||
|
showToast(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// жму изменить на пользователе
|
||||||
|
async function editUser(id) {
|
||||||
|
const users = await api("/api/users");
|
||||||
|
const user = users.find((x) => x.id === id);
|
||||||
|
if (!user) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editingId = id;
|
||||||
|
openUserForm(user); // тут без await специально, форма и так откроется
|
||||||
|
}
|
||||||
|
|
||||||
|
// форма заказа
|
||||||
|
async function openOrderForm(order = null) {
|
||||||
|
const users = await api("/api/users"); // беру людей для списка
|
||||||
|
let options = "";
|
||||||
|
for (const u of users) {
|
||||||
|
const sel = order && order.userId === u.id ? "selected" : "";
|
||||||
|
options += "<option value=\"" + u.id + "\" " + sel + ">" + escapeHtml(u.login) + "</option>";
|
||||||
|
}
|
||||||
|
// дата для поля (просто сегодня или дата заказа)
|
||||||
|
const dateValue = order ? new Date(order.date).toISOString().slice(0, 16) : new Date().toISOString().slice(0, 16);
|
||||||
|
const title = order ? "Изменить заказ" : "Добавить заказ";
|
||||||
|
// какой статус выбран
|
||||||
|
const st = order ? order.status : "New";
|
||||||
|
openModal(title,
|
||||||
|
"<div class=\"form\">" +
|
||||||
|
"<label>Пользователь<select id=\"orderUser\">" + options + "</select></label>" +
|
||||||
|
"<label>Дата<input id=\"orderDate\" type=\"datetime-local\" value=\"" + dateValue + "\"></label>" +
|
||||||
|
"<label>Статус<select id=\"orderStatus\">" +
|
||||||
|
"<option value=\"New\"" + (st === "New" ? " selected" : "") + ">New</option>" +
|
||||||
|
"<option value=\"Processing\"" + (st === "Processing" ? " selected" : "") + ">Processing</option>" +
|
||||||
|
"<option value=\"Completed\"" + (st === "Completed" ? " selected" : "") + ">Completed</option>" +
|
||||||
|
"<option value=\"Cancelled\"" + (st === "Cancelled" ? " selected" : "") + ">Cancelled</option>" +
|
||||||
|
"</select></label>" +
|
||||||
|
"<div class=\"form-actions\"><button type=\"button\" class=\"secondary-button\" onclick=\"closeModalWindow()\">Отмена</button>" +
|
||||||
|
"<button type=\"button\" class=\"primary-button\" onclick=\"saveOrder()\">Сохранить</button></div></div>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// сохраняю заказ
|
||||||
|
async function saveOrder() {
|
||||||
|
const userId = Number(document.getElementById("orderUser").value);
|
||||||
|
const date = document.getElementById("orderDate").value;
|
||||||
|
const status = document.getElementById("orderStatus").value;
|
||||||
|
if (!date) {
|
||||||
|
showToast("Выберите дату.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const data = { userId, date: new Date(date).toISOString(), status };
|
||||||
|
if (editingId) {
|
||||||
|
await api("/api/orders/" + editingId, { method: "PUT", body: JSON.stringify(data) });
|
||||||
|
showToast("Заказ изменен.");
|
||||||
|
} else {
|
||||||
|
await api("/api/orders", { method: "POST", body: JSON.stringify(data) });
|
||||||
|
showToast("Заказ добавлен.");
|
||||||
|
}
|
||||||
|
closeModalWindow();
|
||||||
|
showPage("orders");
|
||||||
|
} catch (error) {
|
||||||
|
showToast(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// жму изменить на заказе
|
||||||
|
async function editOrder(id) {
|
||||||
|
const orders = await api("/api/orders");
|
||||||
|
const order = orders.find((x) => x.id === id);
|
||||||
|
if (!order) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editingId = id;
|
||||||
|
await openOrderForm(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
// показываю товары внутри заказа
|
||||||
|
async function showOrderItems(orderId) {
|
||||||
|
try {
|
||||||
|
const items = await api("/api/orderitems"); // беру все позиции
|
||||||
|
const products = await api("/api/products"); // беру товары чтоб узнать названия
|
||||||
|
const orderItems = items.filter((i) => i.orderId === orderId); // оставляю только этот заказ
|
||||||
|
let html = "<div>";
|
||||||
|
if (orderItems.length === 0) {
|
||||||
|
html += "<div class=\"empty-page\"><strong>Тут пока пусто</strong></div>"; // если ничего нет
|
||||||
|
} else {
|
||||||
|
for (const item of orderItems) {
|
||||||
|
const prod = products.find((x) => x.id === item.productId); // ищу название товара
|
||||||
|
const pname = prod ? prod.name : "Товар";
|
||||||
|
html += "<div class=\"order-item\"><div class=\"order-item-info\"><strong>" + escapeHtml(pname) + "</strong><span>Кол-во: " + item.quantity + "</span></div>";
|
||||||
|
html += "<strong>" + formatMoney(item.price) + "</strong></div>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
html += "</div><div class=\"form-actions\"><button type=\"button\" class=\"primary-button\" onclick=\"openOrderItemForm(" + orderId + ")\">+ Добавить товар</button></div>";
|
||||||
|
openModal("Товары заказа #" + orderId, html);
|
||||||
|
} catch (error) {
|
||||||
|
showToast(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// форма добавить товар в заказ
|
||||||
|
async function openOrderItemForm(orderId) {
|
||||||
|
const products = await api("/api/products");
|
||||||
|
let options = "";
|
||||||
|
for (const p of products) {
|
||||||
|
options += "<option value=\"" + p.id + "\">" + escapeHtml(p.name) + "</option>";
|
||||||
|
}
|
||||||
|
openModal("Товар в заказ #" + orderId,
|
||||||
|
"<div class=\"form\">" +
|
||||||
|
"<label>Товар<select id=\"orderItemProduct\">" + options + "</select></label>" +
|
||||||
|
"<label>Количество<input id=\"orderItemQuantity\" type=\"number\" min=\"1\" value=\"1\"></label>" +
|
||||||
|
"<label>Цена<input id=\"orderItemPrice\" type=\"number\" min=\"0.01\" step=\"0.01\" value=\"0\"></label>" +
|
||||||
|
"<div class=\"form-actions\"><button type=\"button\" class=\"secondary-button\" onclick=\"showOrderItems(" + orderId + ")\">Назад</button>" +
|
||||||
|
"<button type=\"button\" class=\"primary-button\" onclick=\"saveOrderItem(" + orderId + ")\">Добавить</button></div></div>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// сохраняю товар в заказ
|
||||||
|
async function saveOrderItem(orderId) {
|
||||||
|
const productId = Number(document.getElementById("orderItemProduct").value);
|
||||||
|
const quantity = Number(document.getElementById("orderItemQuantity").value);
|
||||||
|
const price = Number(document.getElementById("orderItemPrice").value);
|
||||||
|
if (quantity <= 0) {
|
||||||
|
showToast("Количество должно быть больше 0.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (price <= 0) {
|
||||||
|
showToast("Цена должна быть больше 0.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await api("/api/orderitems", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({ orderId, productId, quantity, price })
|
||||||
|
});
|
||||||
|
showToast("Товар добавлен.");
|
||||||
|
showOrderItems(orderId); // обновляю список
|
||||||
|
} catch (error) {
|
||||||
|
showToast(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// удаляю что угодно (товар, заказ и тд)
|
||||||
|
async function deleteItem(type, id) {
|
||||||
|
// красивые названия для вопроса
|
||||||
|
const names = {
|
||||||
|
products: "товар",
|
||||||
|
categories: "категорию",
|
||||||
|
warehouses: "склад",
|
||||||
|
stock: "остаток",
|
||||||
|
users: "пользователя",
|
||||||
|
orders: "заказ"
|
||||||
|
};
|
||||||
|
// откуда удалять
|
||||||
|
const endpoints = {
|
||||||
|
products: "/api/products",
|
||||||
|
categories: "/api/categories",
|
||||||
|
warehouses: "/api/warehouses",
|
||||||
|
stock: "/api/stocks",
|
||||||
|
users: "/api/users",
|
||||||
|
orders: "/api/orders"
|
||||||
|
};
|
||||||
|
const name = names[type] || "запись";
|
||||||
|
if (!confirm("Удалить " + name + "?")) { // спрашиваю точно ли надо
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await api(endpoints[type] + "/" + id, { method: "DELETE" }); // удаляю
|
||||||
|
showToast("Удалено.");
|
||||||
|
showPage(currentPage); // обновляю страницу
|
||||||
|
} catch (error) {
|
||||||
|
showToast(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// при запуске смотрю входил ли уже
|
||||||
|
const savedUser = sessionStorage.getItem("demoUser");
|
||||||
|
if (savedUser) {
|
||||||
|
try {
|
||||||
|
const user = JSON.parse(savedUser);
|
||||||
|
if (user && user.login) {
|
||||||
|
enterApplication(user); // да, уже входил, сразу захожу
|
||||||
|
} else {
|
||||||
|
app.classList.add("hidden");
|
||||||
|
authScreen.classList.remove("hidden");
|
||||||
|
showLoginForm();
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
sessionStorage.removeItem("demoUser");
|
||||||
|
app.classList.add("hidden");
|
||||||
|
authScreen.classList.remove("hidden");
|
||||||
|
showLoginForm();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// нет, показываю вход
|
||||||
|
app.classList.add("hidden");
|
||||||
|
authScreen.classList.remove("hidden");
|
||||||
|
showLoginForm();
|
||||||
|
}
|
||||||
496
DEMO/wwwroot/style.css
Normal file
496
DEMO/wwwroot/style.css
Normal file
|
|
@ -0,0 +1,496 @@
|
||||||
|
/* это самые простые стили, чтоб все было чисто и понятно */
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #f2f2f2; /* серый фон */
|
||||||
|
color: #222; /* темный текст */
|
||||||
|
font-family: Arial, sans-serif; /* простой шрифт */
|
||||||
|
}
|
||||||
|
|
||||||
|
button, input, select {
|
||||||
|
font: inherit; /* шрифт как у всех */
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
cursor: pointer; /* рука при наведении */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* прячу то что не надо показывать */
|
||||||
|
.hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* экран входа */
|
||||||
|
.auth-screen {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* белая карточка входа */
|
||||||
|
.auth-card {
|
||||||
|
width: 380px;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 30px;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* большой текст DEMO */
|
||||||
|
.auth-logo {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-logo small {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #888;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* заголовок входа */
|
||||||
|
.auth-heading {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-heading p {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* форма входа */
|
||||||
|
.auth-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px; /* отступы между полями */
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-form label {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-form input {
|
||||||
|
height: 40px;
|
||||||
|
padding: 0 10px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* синяя кнопка */
|
||||||
|
.auth-button {
|
||||||
|
height: 42px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #3366ff;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ссылка регистрация */
|
||||||
|
.auth-switch {
|
||||||
|
margin-top: 15px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-switch button {
|
||||||
|
border: 0;
|
||||||
|
background: none;
|
||||||
|
color: #3366ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* красный текст ошибки */
|
||||||
|
.auth-error {
|
||||||
|
min-height: 18px;
|
||||||
|
color: red;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* главное окно */
|
||||||
|
.app {
|
||||||
|
display: flex;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* меню слева */
|
||||||
|
.sidebar {
|
||||||
|
width: 220px;
|
||||||
|
padding: 20px 12px;
|
||||||
|
background: white;
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* логотип */
|
||||||
|
.logo {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo span {
|
||||||
|
display: block;
|
||||||
|
font-size: 11px;
|
||||||
|
color: #888;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* кто вошел */
|
||||||
|
.user-panel {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
margin: 20px 0;
|
||||||
|
padding: 10px;
|
||||||
|
background: #f2f2f2;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* кружок с буквой */
|
||||||
|
.user-avatar {
|
||||||
|
width: 35px;
|
||||||
|
height: 35px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background: #3366ff;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info strong {
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info span {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* кнопки меню */
|
||||||
|
.nav-button {
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
padding: 10px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: none;
|
||||||
|
color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* активная кнопка меню */
|
||||||
|
.nav-button.active {
|
||||||
|
background: #e6ecff;
|
||||||
|
color: #2244cc;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* кнопка выйти */
|
||||||
|
.logout-button {
|
||||||
|
margin-top: auto; /* прижимаю вниз */
|
||||||
|
padding: 10px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: none;
|
||||||
|
color: #666;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* правая часть */
|
||||||
|
.content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* верх с названием */
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header p {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* синяя кнопка добавить */
|
||||||
|
.primary-button {
|
||||||
|
padding: 10px 16px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #3366ff;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* сетка товаров */
|
||||||
|
.cards-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* одна карточка товара */
|
||||||
|
.card {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card h3 {
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-id {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-price {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-info {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* маленькие кнопки */
|
||||||
|
.action-button {
|
||||||
|
padding: 6px 10px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: white;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* кнопка удалить красная */
|
||||||
|
.action-button.delete {
|
||||||
|
color: red;
|
||||||
|
border-color: #ffaaaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* таблица */
|
||||||
|
.table-wrapper {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow-x: auto; /* чтоб на телефоне листалась */
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse; /* убираю двойные линии */
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: left;
|
||||||
|
padding: 10px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 10px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* плашка статуса */
|
||||||
|
.status {
|
||||||
|
background: #e6ecff;
|
||||||
|
padding: 3px 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* когда нет данных */
|
||||||
|
.empty-page {
|
||||||
|
background: white;
|
||||||
|
border: 1px dashed #ccc;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 40px;
|
||||||
|
text-align: center;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* когда ошибка */
|
||||||
|
.error-page {
|
||||||
|
background: #ffe6e6;
|
||||||
|
border: 1px solid #ffaaaa;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* темное окно формы */
|
||||||
|
.modal {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* затемнение сзади */
|
||||||
|
.modal-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* белая форма */
|
||||||
|
.modal-content {
|
||||||
|
position: relative;
|
||||||
|
width: 450px;
|
||||||
|
max-width: 100%;
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* верх формы */
|
||||||
|
.modal-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* кнопка X */
|
||||||
|
.close-button {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* поля формы */
|
||||||
|
.form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form label {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form input, .form select {
|
||||||
|
height: 38px;
|
||||||
|
padding: 0 10px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* кнопки сохранить отмена */
|
||||||
|
.form-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary-button {
|
||||||
|
padding: 8px 14px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* товары внутри заказа */
|
||||||
|
.order-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-item-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 3px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* сообщение снизу */
|
||||||
|
.toast {
|
||||||
|
position: fixed;
|
||||||
|
right: 20px;
|
||||||
|
bottom: 20px;
|
||||||
|
background: #333;
|
||||||
|
color: white;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
opacity: 0; /* прячу */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* показываю сообщение */
|
||||||
|
.toast.show {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* для телефона */
|
||||||
|
@media (max-width: 800px) {
|
||||||
|
.app {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user