DEMO shop: simplified project with comments

This commit is contained in:
Erman 2026-09-09 03:57:03 +05:00
commit 7f990d6f7d
45 changed files with 4211 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# билд
bin/
obj/
# студия
.vs/
# юзерские настройки
*.user
*.suo

3
DEMO.slnx Normal file
View File

@ -0,0 +1,3 @@
<Solution>
<Project Path="DEMO/DEMO.csproj" />
</Solution>

View 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
});
}
}

View 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();
}
}

View 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();
}
}

View 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();
}
}

View 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(); // все хорошо, отдавать нечего
}
}

View 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();
}
}

View 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();
}
}

View 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();
}
}

View 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
View 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
View File

@ -0,0 +1,6 @@
@DEMO_HostAddress = http://localhost:5214
GET {{DEMO_HostAddress}}/weatherforecast/
Accept: application/json
###

23
DEMO/Data/AppDbContext.cs Normal file
View 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; } // товары в заказах
}

View 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); // отдаю готовую базу
}
}

View 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
}
}
}

View 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");
}
}
}

View 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
}
}
}

View 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");
}
}
}

View 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
}
}
}

View 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");
}
}
}

View 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
}
}
}

View 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");
}
}
}

View 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
}
}
}

View 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");
}
}
}

View 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
}
}
}

View 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");
}
}
}

View 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
}
}
}

View 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");
}
}
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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(); // запускаю, поехали

View 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"
}
}
}
}

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

12
DEMO/appsettings.json Normal file
View 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
View 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
View 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("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#039;");
}
// делаю цену красивой: 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
View 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;
}
}