Task_7.5_UI_Reports #11
@@ -72,7 +72,7 @@ public class ReportContract(IClientStorageContract clientStorage, ICurrencyStora
|
||||
return _baseWordBuilder
|
||||
.AddHeader("Клиенты по кредитным программам")
|
||||
.AddParagraph($"Сформировано на дату {DateTime.Now}")
|
||||
.AddTable([3000, 3000, 3000, 3000], tableRows)
|
||||
.AddTable([100, 100, 100, 100], tableRows)
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -90,20 +90,20 @@ public class ReportContract(IClientStorageContract clientStorage, ICurrencyStora
|
||||
{
|
||||
for (int i = 0; i < program.ClientSurname.Count; i++)
|
||||
{
|
||||
tableRows.Add(new string[]
|
||||
{
|
||||
tableRows.Add(
|
||||
[
|
||||
program.CreditProgramName,
|
||||
program.ClientSurname[i],
|
||||
program.ClientName[i],
|
||||
program.ClientBalance[i].ToString("N2")
|
||||
});
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return _baseExcelBuilder
|
||||
.AddHeader("Клиенты по кредитным программам", 0, 4)
|
||||
.AddParagraph($"Сформировано на дату {DateTime.Now}", 0)
|
||||
.AddTable([3000, 3000, 3000, 3000], tableRows)
|
||||
.AddTable([25, 25, 25, 25], tableRows)
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -169,21 +169,21 @@ public class ReportContract(IClientStorageContract clientStorage, ICurrencyStora
|
||||
|
||||
foreach (var client in data)
|
||||
{
|
||||
tableRows.Add(new string[]
|
||||
{
|
||||
tableRows.Add(
|
||||
[
|
||||
client.ClientSurname,
|
||||
client.ClientName,
|
||||
client.ClientBalance.ToString("N2"),
|
||||
client.DepositRate.ToString("N2"),
|
||||
$"{client.DepositPeriod} мес.",
|
||||
$"{client.FromPeriod.ToShortDateString()} - {client.ToPeriod.ToShortDateString()}"
|
||||
});
|
||||
]);
|
||||
}
|
||||
|
||||
return _basePdfBuilder
|
||||
.AddHeader("Клиенты по вкладам")
|
||||
.AddParagraph($"за период с {dateStart.ToShortDateString()} по {dateFinish.ToShortDateString()}")
|
||||
.AddTable([80, 80, 80, 80, 80, 80], tableRows)
|
||||
.AddTable([25, 25, 25, 25, 25, 25], tableRows)
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -229,23 +229,54 @@ public class ReportContract(IClientStorageContract clientStorage, ICurrencyStora
|
||||
|
||||
foreach (var currency in data)
|
||||
{
|
||||
// Вывод информации по кредитным программам
|
||||
for (int i = 0; i < currency.CreditProgramName.Count; i++)
|
||||
{
|
||||
tableRows.Add(new string[]
|
||||
// Вычисляем индекс депозита, если есть соответствующие
|
||||
string depositRate = "—";
|
||||
string depositPeriod = "—";
|
||||
|
||||
// Проверяем, есть ли депозиты для этой валюты и не вышли ли мы за границы массива
|
||||
if (currency.DepositRate.Count > 0)
|
||||
{
|
||||
// Берем индекс по модулю, чтобы не выйти за границы массива
|
||||
int depositIndex = i % currency.DepositRate.Count;
|
||||
depositRate = currency.DepositRate[depositIndex].ToString("N2");
|
||||
depositPeriod = $"{currency.DepositPeriod[depositIndex]} мес.";
|
||||
}
|
||||
|
||||
// Добавляем строку в таблицу
|
||||
tableRows.Add(
|
||||
[
|
||||
currency.CurrencyName,
|
||||
currency.CreditProgramName[i],
|
||||
currency.CreditProgramMaxCost[i].ToString("N2"),
|
||||
currency.DepositRate[i].ToString("N2"),
|
||||
$"{currency.DepositPeriod[i]} мес."
|
||||
});
|
||||
depositRate,
|
||||
depositPeriod
|
||||
]);
|
||||
}
|
||||
|
||||
// Если есть депозиты, но нет кредитных программ, добавляем строки только с депозитами
|
||||
if (currency.CreditProgramName.Count == 0 && currency.DepositRate.Count > 0)
|
||||
{
|
||||
for (int j = 0; j < currency.DepositRate.Count; j++)
|
||||
{
|
||||
tableRows.Add(
|
||||
[
|
||||
currency.CurrencyName,
|
||||
"—",
|
||||
"—",
|
||||
currency.DepositRate[j].ToString("N2"),
|
||||
$"{currency.DepositPeriod[j]} мес."
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _basePdfBuilder
|
||||
.AddHeader("Вклады и кредитные программы по валютам")
|
||||
.AddParagraph($"за период с {dateStart.ToShortDateString()} по {dateFinish.ToShortDateString()}")
|
||||
.AddTable([80, 100, 80, 80, 80], tableRows)
|
||||
.AddTable([25, 30, 25, 25, 25], tableRows)
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -284,20 +315,20 @@ public class ReportContract(IClientStorageContract clientStorage, ICurrencyStora
|
||||
{
|
||||
for (int i = 0; i < program.DepositRate.Count; i++)
|
||||
{
|
||||
tableRows.Add(new string[]
|
||||
{
|
||||
tableRows.Add(
|
||||
[
|
||||
program.CreditProgramName,
|
||||
program.DepositRate[i].ToString("N2"),
|
||||
program.DepositCost[i].ToString("N2"),
|
||||
program.DepositPeriod[i].ToString()
|
||||
});
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return _baseWordBuilder
|
||||
.AddHeader("Вклады по кредитным программам")
|
||||
.AddParagraph($"Сформировано на дату {DateTime.Now}")
|
||||
.AddTable([3000, 3000, 3000, 3000], tableRows)
|
||||
.AddTable([2000, 2000, 2000, 2000], tableRows)
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -315,20 +346,20 @@ public class ReportContract(IClientStorageContract clientStorage, ICurrencyStora
|
||||
{
|
||||
for (int i = 0; i < program.DepositRate.Count; i++)
|
||||
{
|
||||
tableRows.Add(new string[]
|
||||
{
|
||||
tableRows.Add(
|
||||
[
|
||||
program.CreditProgramName,
|
||||
program.DepositRate[i].ToString("N2"),
|
||||
program.DepositCost[i].ToString("N2"),
|
||||
program.DepositPeriod[i].ToString()
|
||||
});
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return _baseExcelBuilder
|
||||
.AddHeader("Вклады по кредитным программам", 0, 4)
|
||||
.AddParagraph($"Сформировано на дату {DateTime.Now}", 0)
|
||||
.AddTable([3000, 3000, 3000, 3000], tableRows)
|
||||
.AddTable([25, 25, 25, 25], tableRows)
|
||||
.Build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ public class MigraDocPdfBuilder : BasePdfBuilder
|
||||
// Добавляем столбцы с заданной шириной
|
||||
foreach (var width in columnsWidths)
|
||||
{
|
||||
var widthInCm = width / 28.35;
|
||||
var widthInCm = width / 10.35;
|
||||
var column = table.AddColumn(Unit.FromCentimeter(widthInCm));
|
||||
column.Format.Alignment = ParagraphAlignment.Left;
|
||||
}
|
||||
|
||||
@@ -15,4 +15,6 @@ public interface IClerkAdapter
|
||||
ClerkOperationResponse RegisterClerk(ClerkBindingModel clerkModel);
|
||||
|
||||
ClerkOperationResponse ChangeClerkInfo(ClerkBindingModel clerkModel);
|
||||
|
||||
ClerkOperationResponse Login(LoginBindingModel clerkModel, out string token);
|
||||
}
|
||||
|
||||
@@ -15,4 +15,6 @@ public interface IStorekeeperAdapter
|
||||
StorekeeperOperationResponse RegisterStorekeeper(StorekeeperBindingModel storekeeperModel);
|
||||
|
||||
StorekeeperOperationResponse ChangeStorekeeperInfo(StorekeeperBindingModel storekeeperModel);
|
||||
|
||||
StorekeeperOperationResponse Login(LoginBindingModel storekeeperModel, out string token);
|
||||
}
|
||||
|
||||
@@ -21,4 +21,7 @@ public class ClerkOperationResponse : OperationResponse
|
||||
|
||||
public static ClerkOperationResponse InternalServerError(string message) =>
|
||||
InternalServerError<ClerkOperationResponse>(message);
|
||||
|
||||
public static ClerkOperationResponse Unauthorized(string message) =>
|
||||
Unauthorized<ClerkOperationResponse>(message);
|
||||
}
|
||||
|
||||
@@ -18,4 +18,7 @@ public class ReportOperationResponse : OperationResponse
|
||||
public static ReportOperationResponse BadRequest(string message) => BadRequest<ReportOperationResponse>(message);
|
||||
|
||||
public static ReportOperationResponse InternalServerError(string message) => InternalServerError<ReportOperationResponse>(message);
|
||||
|
||||
public Stream? GetStream() => Result as Stream;
|
||||
public string? GetFileName() => FileName;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ public class StorekeeperOperationResponse : OperationResponse
|
||||
public static StorekeeperOperationResponse OK(List<StorekeeperViewModel> data) =>
|
||||
OK<StorekeeperOperationResponse, List<StorekeeperViewModel>>(data);
|
||||
|
||||
public static StorekeeperOperationResponse OK(string token) =>
|
||||
OK<StorekeeperOperationResponse, string>(token);
|
||||
|
||||
public static StorekeeperOperationResponse OK(StorekeeperViewModel data) =>
|
||||
OK<StorekeeperOperationResponse, StorekeeperViewModel>(data);
|
||||
|
||||
@@ -21,4 +24,7 @@ public class StorekeeperOperationResponse : OperationResponse
|
||||
|
||||
public static StorekeeperOperationResponse InternalServerError(string message) =>
|
||||
InternalServerError<StorekeeperOperationResponse>(message);
|
||||
|
||||
public static StorekeeperOperationResponse Unauthorized(string message) =>
|
||||
Unauthorized<StorekeeperOperationResponse>(message);
|
||||
}
|
||||
|
||||
8
TheBank/BankContracts/BindingModels/LoginBindingModel.cs
Normal file
8
TheBank/BankContracts/BindingModels/LoginBindingModel.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace BankContracts.BindingModels;
|
||||
|
||||
public class LoginBindingModel
|
||||
{
|
||||
public required string Login { get; set; }
|
||||
|
||||
public required string Password { get; set; }
|
||||
}
|
||||
@@ -2,9 +2,11 @@
|
||||
|
||||
public class MailSendInfoBindingModel
|
||||
{
|
||||
public string MailAddress { get; set; } = string.Empty;
|
||||
public string ToEmail { get; set; } = string.Empty;
|
||||
|
||||
public string Subject { get; set; } = string.Empty;
|
||||
public string Text { get; set; } = string.Empty;
|
||||
public MemoryStream Attachment { get; set; } = new MemoryStream();
|
||||
public string FileName { get; set; } = string.Empty;
|
||||
|
||||
public string Body { get; set; } = string.Empty;
|
||||
|
||||
public string? AttachmentPath { get; set; }
|
||||
}
|
||||
|
||||
@@ -58,4 +58,8 @@ public class OperationResponse
|
||||
protected static TResult InternalServerError<TResult>(string? errorMessage = null)
|
||||
where TResult : OperationResponse, new() =>
|
||||
new() { StatusCode = HttpStatusCode.InternalServerError, Result = errorMessage };
|
||||
|
||||
protected static TResult Unauthorized<TResult>(string? errorMessage = null)
|
||||
where TResult : OperationResponse, new() =>
|
||||
new() { StatusCode = HttpStatusCode.Unauthorized, Result = errorMessage };
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoMapper" Version="14.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.4">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ internal class BankDbContext(IConfigurationDatabase configurationDatabase) : DbC
|
||||
modelBuilder.Entity<CreditProgram>()
|
||||
.HasIndex(x => x.Name)
|
||||
.IsUnique();
|
||||
|
||||
|
||||
modelBuilder.Entity<Currency>()
|
||||
.HasIndex(x => x.Abbreviation)
|
||||
.IsUnique();
|
||||
@@ -80,17 +80,17 @@ internal class BankDbContext(IConfigurationDatabase configurationDatabase) : DbC
|
||||
public DbSet<Clerk> Clerks { get; set; }
|
||||
|
||||
public DbSet<Client> Clients { get; set; }
|
||||
|
||||
|
||||
public DbSet<CreditProgram> CreditPrograms { get; set; }
|
||||
|
||||
|
||||
public DbSet<Currency> Currencies { get; set; }
|
||||
|
||||
|
||||
public DbSet<Deposit> Deposits { get; set; }
|
||||
|
||||
|
||||
public DbSet<Period> Periods { get; set; }
|
||||
|
||||
|
||||
public DbSet<Replenishment> Replenishments { get; set; }
|
||||
|
||||
|
||||
public DbSet<Storekeeper> Storekeepers { get; set; }
|
||||
|
||||
public DbSet<DepositCurrency> DepositCurrencies { get; set; }
|
||||
|
||||
17
TheBank/BankDatabase/DesignTimeDbContextFactory.cs
Normal file
17
TheBank/BankDatabase/DesignTimeDbContextFactory.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using BankContracts.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
|
||||
namespace BankDatabase;
|
||||
|
||||
//public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<BankDbContext>
|
||||
//{
|
||||
// //public BankDbContext CreateDbContext(string[] args)
|
||||
// //{
|
||||
// // return new BankDbContext(new ConfigurationDatabase());
|
||||
// //}
|
||||
//}
|
||||
|
||||
internal class ConfigurationDatabase : IConfigurationDatabase
|
||||
{
|
||||
public string ConnectionString => "Host=127.0.0.1;Port=5432;Database=BankTest;Username=postgres;Password=admin123;";
|
||||
}
|
||||
@@ -85,7 +85,7 @@ internal class ClerkStorageContract : IClerkStorageContract
|
||||
_dbContext.Clerks.Add(_mapper.Map<Clerk>(clerkDataModel));
|
||||
_dbContext.SaveChanges();
|
||||
}
|
||||
catch (InvalidOperationException ex) when (ex.TargetSite?.Name == "ThrowIdentityConflict")
|
||||
catch (DbUpdateException ex) when (ex.InnerException is PostgresException { ConstraintName: "PK_Clerks" })
|
||||
{
|
||||
_dbContext.ChangeTracker.Clear();
|
||||
throw new ElementExistsException($"Id {clerkDataModel.Id}");
|
||||
|
||||
@@ -109,7 +109,7 @@ internal class DepositStorageContract : IDepositStorageContract
|
||||
catch (InvalidOperationException ex) when (ex.TargetSite?.Name == "ThrowIdentityConflict")
|
||||
{
|
||||
_dbContext.ChangeTracker.Clear();
|
||||
throw new ElementExistsException($"Id {depositDataModel.Id }");
|
||||
throw new ElementExistsException($"Id {depositDataModel.Id}");
|
||||
}
|
||||
catch (DbUpdateException ex) when (ex.InnerException is PostgresException { ConstraintName: "IX_Deposits_InterestRate" })
|
||||
{
|
||||
@@ -127,40 +127,76 @@ internal class DepositStorageContract : IDepositStorageContract
|
||||
{
|
||||
try
|
||||
{
|
||||
var transaction = _dbContext.Database.BeginTransaction();
|
||||
using var transaction = _dbContext.Database.BeginTransaction();
|
||||
try
|
||||
{
|
||||
var element = GetDepositById(depositDataModel.Id) ?? throw new ElementNotFoundException(depositDataModel.Id);
|
||||
// Загружаем существующий вклад со связями
|
||||
var existingDeposit = _dbContext.Deposits
|
||||
.Include(d => d.DepositCurrencies)
|
||||
.FirstOrDefault(d => d.Id == depositDataModel.Id);
|
||||
|
||||
if (existingDeposit == null)
|
||||
{
|
||||
throw new ElementNotFoundException(depositDataModel.Id);
|
||||
}
|
||||
|
||||
// Обновляем основные поля вклада
|
||||
existingDeposit.InterestRate = depositDataModel.InterestRate;
|
||||
existingDeposit.Cost = depositDataModel.Cost;
|
||||
existingDeposit.Period = depositDataModel.Period;
|
||||
existingDeposit.ClerkId = depositDataModel.ClerkId;
|
||||
|
||||
// Обновляем связи с валютами, если они переданы
|
||||
if (depositDataModel.Currencies != null)
|
||||
{
|
||||
if (element.DepositCurrencies != null || element.DepositCurrencies?.Count >= 0)
|
||||
// Удаляем все существующие связи
|
||||
if (existingDeposit.DepositCurrencies != null)
|
||||
{
|
||||
_dbContext.DepositCurrencies.RemoveRange(element.DepositCurrencies);
|
||||
_dbContext.DepositCurrencies.RemoveRange(existingDeposit.DepositCurrencies);
|
||||
}
|
||||
|
||||
element.DepositCurrencies = _mapper.Map<List<DepositCurrency>>(depositDataModel.Currencies);
|
||||
// Сохраняем изменения для применения удаления
|
||||
_dbContext.SaveChanges();
|
||||
|
||||
// Создаем новые связи
|
||||
existingDeposit.DepositCurrencies = depositDataModel.Currencies.Select(c =>
|
||||
new DepositCurrency
|
||||
{
|
||||
DepositId = existingDeposit.Id,
|
||||
CurrencyId = c.CurrencyId
|
||||
}).ToList();
|
||||
}
|
||||
_mapper.Map(depositDataModel, element);
|
||||
|
||||
// Сохраняем все изменения
|
||||
_dbContext.SaveChanges();
|
||||
transaction.Commit();
|
||||
|
||||
// Выводим отладочную информацию
|
||||
System.Console.WriteLine($"Updated deposit {existingDeposit.Id} with {existingDeposit.DepositCurrencies?.Count ?? 0} currency relations");
|
||||
foreach (var relation in existingDeposit.DepositCurrencies ?? Enumerable.Empty<DepositCurrency>())
|
||||
{
|
||||
System.Console.WriteLine($"Currency relation: DepositId={relation.DepositId}, CurrencyId={relation.CurrencyId}");
|
||||
}
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
transaction.Rollback();
|
||||
throw;
|
||||
System.Console.WriteLine($"Error in transaction: {ex.Message}");
|
||||
if (ex is ElementNotFoundException)
|
||||
throw;
|
||||
throw new StorageException(ex.Message);
|
||||
}
|
||||
}
|
||||
catch (ElementNotFoundException)
|
||||
{
|
||||
_dbContext.ChangeTracker.Clear();
|
||||
throw;
|
||||
}
|
||||
catch (DbUpdateException ex) when (ex.InnerException is PostgresException { ConstraintName: "IX_Deposits_InterestRate" })
|
||||
{
|
||||
_dbContext.ChangeTracker.Clear();
|
||||
throw new ElementExistsException($"InterestRate {depositDataModel.InterestRate}");
|
||||
}
|
||||
catch (ElementNotFoundException)
|
||||
{
|
||||
_dbContext.ChangeTracker.Clear();
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_dbContext.ChangeTracker.Clear();
|
||||
|
||||
562
TheBank/BankDatabase/Migrations/20250518195627_InitialCreate.Designer.cs
generated
Normal file
562
TheBank/BankDatabase/Migrations/20250518195627_InitialCreate.Designer.cs
generated
Normal file
@@ -0,0 +1,562 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using BankDatabase;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BankDatabase.Migrations
|
||||
{
|
||||
[DbContext(typeof(BankDbContext))]
|
||||
[Migration("20250518195627_InitialCreate")]
|
||||
partial class InitialCreate
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.4")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Clerk", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Login")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("MiddleName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Password")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Surname")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Email")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("Login")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("PhoneNumber")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Clerks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Client", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<decimal>("Balance")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<string>("ClerkId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Surname")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClerkId");
|
||||
|
||||
b.ToTable("Clients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.ClientCreditProgram", b =>
|
||||
{
|
||||
b.Property<string>("ClientId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("CreditProgramId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("ClientId", "CreditProgramId");
|
||||
|
||||
b.HasIndex("CreditProgramId");
|
||||
|
||||
b.ToTable("CreditProgramClients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.CreditProgram", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<decimal>("Cost")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal>("MaxCost")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PeriodId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("StorekeeperId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Name")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("PeriodId");
|
||||
|
||||
b.HasIndex("StorekeeperId");
|
||||
|
||||
b.ToTable("CreditPrograms");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.CreditProgramCurrency", b =>
|
||||
{
|
||||
b.Property<string>("CreditProgramId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("CurrencyId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("CreditProgramId", "CurrencyId");
|
||||
|
||||
b.HasIndex("CurrencyId");
|
||||
|
||||
b.ToTable("CurrencyCreditPrograms");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Currency", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Abbreviation")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<decimal>("Cost")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("StorekeeperId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Abbreviation")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("StorekeeperId");
|
||||
|
||||
b.ToTable("Currencies");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Deposit", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ClerkId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<decimal>("Cost")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<float>("InterestRate")
|
||||
.HasColumnType("real");
|
||||
|
||||
b.Property<int>("Period")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClerkId");
|
||||
|
||||
b.ToTable("Deposits");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.DepositClient", b =>
|
||||
{
|
||||
b.Property<string>("DepositId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ClientId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("DepositId", "ClientId");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.ToTable("DepositClients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.DepositCurrency", b =>
|
||||
{
|
||||
b.Property<string>("DepositId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("CurrencyId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("DepositId", "CurrencyId");
|
||||
|
||||
b.HasIndex("CurrencyId");
|
||||
|
||||
b.ToTable("DepositCurrencies");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Period", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime>("EndTime")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTime>("StartTime")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("StorekeeperId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StorekeeperId");
|
||||
|
||||
b.ToTable("Periods");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Replenishment", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<decimal>("Amount")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<string>("ClerkId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime>("Date")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("DepositId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClerkId");
|
||||
|
||||
b.HasIndex("DepositId");
|
||||
|
||||
b.ToTable("Replenishments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Storekeeper", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Login")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("MiddleName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Password")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Surname")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Email")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("Login")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("PhoneNumber")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Storekeepers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Client", b =>
|
||||
{
|
||||
b.HasOne("BankDatabase.Models.Clerk", "Clerk")
|
||||
.WithMany("Clients")
|
||||
.HasForeignKey("ClerkId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Clerk");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.ClientCreditProgram", b =>
|
||||
{
|
||||
b.HasOne("BankDatabase.Models.Client", "Client")
|
||||
.WithMany("CreditProgramClients")
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("BankDatabase.Models.CreditProgram", "CreditProgram")
|
||||
.WithMany("CreditProgramClients")
|
||||
.HasForeignKey("CreditProgramId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
|
||||
b.Navigation("CreditProgram");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.CreditProgram", b =>
|
||||
{
|
||||
b.HasOne("BankDatabase.Models.Period", "Period")
|
||||
.WithMany("CreditPrograms")
|
||||
.HasForeignKey("PeriodId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("BankDatabase.Models.Storekeeper", "Storekeeper")
|
||||
.WithMany("CreditPrograms")
|
||||
.HasForeignKey("StorekeeperId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Period");
|
||||
|
||||
b.Navigation("Storekeeper");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.CreditProgramCurrency", b =>
|
||||
{
|
||||
b.HasOne("BankDatabase.Models.CreditProgram", "CreditProgram")
|
||||
.WithMany("CurrencyCreditPrograms")
|
||||
.HasForeignKey("CreditProgramId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("BankDatabase.Models.Currency", "Currency")
|
||||
.WithMany("CurrencyCreditPrograms")
|
||||
.HasForeignKey("CurrencyId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("CreditProgram");
|
||||
|
||||
b.Navigation("Currency");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Currency", b =>
|
||||
{
|
||||
b.HasOne("BankDatabase.Models.Storekeeper", "Storekeeper")
|
||||
.WithMany("Currencies")
|
||||
.HasForeignKey("StorekeeperId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Storekeeper");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Deposit", b =>
|
||||
{
|
||||
b.HasOne("BankDatabase.Models.Clerk", "Clerk")
|
||||
.WithMany("Deposits")
|
||||
.HasForeignKey("ClerkId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Clerk");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.DepositClient", b =>
|
||||
{
|
||||
b.HasOne("BankDatabase.Models.Client", "Client")
|
||||
.WithMany("DepositClients")
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("BankDatabase.Models.Deposit", "Deposit")
|
||||
.WithMany("DepositClients")
|
||||
.HasForeignKey("DepositId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
|
||||
b.Navigation("Deposit");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.DepositCurrency", b =>
|
||||
{
|
||||
b.HasOne("BankDatabase.Models.Currency", "Currency")
|
||||
.WithMany("DepositCurrencies")
|
||||
.HasForeignKey("CurrencyId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("BankDatabase.Models.Deposit", "Deposit")
|
||||
.WithMany("DepositCurrencies")
|
||||
.HasForeignKey("DepositId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Currency");
|
||||
|
||||
b.Navigation("Deposit");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Period", b =>
|
||||
{
|
||||
b.HasOne("BankDatabase.Models.Storekeeper", "Storekeeper")
|
||||
.WithMany("Periods")
|
||||
.HasForeignKey("StorekeeperId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Storekeeper");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Replenishment", b =>
|
||||
{
|
||||
b.HasOne("BankDatabase.Models.Clerk", "Clerk")
|
||||
.WithMany("Replenishments")
|
||||
.HasForeignKey("ClerkId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("BankDatabase.Models.Deposit", "Deposit")
|
||||
.WithMany("Replenishments")
|
||||
.HasForeignKey("DepositId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Clerk");
|
||||
|
||||
b.Navigation("Deposit");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Clerk", b =>
|
||||
{
|
||||
b.Navigation("Clients");
|
||||
|
||||
b.Navigation("Deposits");
|
||||
|
||||
b.Navigation("Replenishments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Client", b =>
|
||||
{
|
||||
b.Navigation("CreditProgramClients");
|
||||
|
||||
b.Navigation("DepositClients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.CreditProgram", b =>
|
||||
{
|
||||
b.Navigation("CreditProgramClients");
|
||||
|
||||
b.Navigation("CurrencyCreditPrograms");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Currency", b =>
|
||||
{
|
||||
b.Navigation("CurrencyCreditPrograms");
|
||||
|
||||
b.Navigation("DepositCurrencies");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Deposit", b =>
|
||||
{
|
||||
b.Navigation("DepositClients");
|
||||
|
||||
b.Navigation("DepositCurrencies");
|
||||
|
||||
b.Navigation("Replenishments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Period", b =>
|
||||
{
|
||||
b.Navigation("CreditPrograms");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Storekeeper", b =>
|
||||
{
|
||||
b.Navigation("CreditPrograms");
|
||||
|
||||
b.Navigation("Currencies");
|
||||
|
||||
b.Navigation("Periods");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
433
TheBank/BankDatabase/Migrations/20250518195627_InitialCreate.cs
Normal file
433
TheBank/BankDatabase/Migrations/20250518195627_InitialCreate.cs
Normal file
@@ -0,0 +1,433 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BankDatabase.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitialCreate : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Clerks",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
Name = table.Column<string>(type: "text", nullable: false),
|
||||
Surname = table.Column<string>(type: "text", nullable: false),
|
||||
MiddleName = table.Column<string>(type: "text", nullable: false),
|
||||
Login = table.Column<string>(type: "text", nullable: false),
|
||||
Password = table.Column<string>(type: "text", nullable: false),
|
||||
Email = table.Column<string>(type: "text", nullable: false),
|
||||
PhoneNumber = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Clerks", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Storekeepers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
Name = table.Column<string>(type: "text", nullable: false),
|
||||
Surname = table.Column<string>(type: "text", nullable: false),
|
||||
MiddleName = table.Column<string>(type: "text", nullable: false),
|
||||
Login = table.Column<string>(type: "text", nullable: false),
|
||||
Password = table.Column<string>(type: "text", nullable: false),
|
||||
Email = table.Column<string>(type: "text", nullable: false),
|
||||
PhoneNumber = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Storekeepers", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Clients",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
Name = table.Column<string>(type: "text", nullable: false),
|
||||
Surname = table.Column<string>(type: "text", nullable: false),
|
||||
Balance = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
ClerkId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Clients", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Clients_Clerks_ClerkId",
|
||||
column: x => x.ClerkId,
|
||||
principalTable: "Clerks",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Deposits",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
InterestRate = table.Column<float>(type: "real", nullable: false),
|
||||
Cost = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
Period = table.Column<int>(type: "integer", nullable: false),
|
||||
ClerkId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Deposits", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Deposits_Clerks_ClerkId",
|
||||
column: x => x.ClerkId,
|
||||
principalTable: "Clerks",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Currencies",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
Name = table.Column<string>(type: "text", nullable: false),
|
||||
Abbreviation = table.Column<string>(type: "text", nullable: false),
|
||||
Cost = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
StorekeeperId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Currencies", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Currencies_Storekeepers_StorekeeperId",
|
||||
column: x => x.StorekeeperId,
|
||||
principalTable: "Storekeepers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Periods",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
StartTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
EndTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
StorekeeperId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Periods", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Periods_Storekeepers_StorekeeperId",
|
||||
column: x => x.StorekeeperId,
|
||||
principalTable: "Storekeepers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DepositClients",
|
||||
columns: table => new
|
||||
{
|
||||
DepositId = table.Column<string>(type: "text", nullable: false),
|
||||
ClientId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_DepositClients", x => new { x.DepositId, x.ClientId });
|
||||
table.ForeignKey(
|
||||
name: "FK_DepositClients_Clients_ClientId",
|
||||
column: x => x.ClientId,
|
||||
principalTable: "Clients",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_DepositClients_Deposits_DepositId",
|
||||
column: x => x.DepositId,
|
||||
principalTable: "Deposits",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Replenishments",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
Amount = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
Date = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
DepositId = table.Column<string>(type: "text", nullable: false),
|
||||
ClerkId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Replenishments", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Replenishments_Clerks_ClerkId",
|
||||
column: x => x.ClerkId,
|
||||
principalTable: "Clerks",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_Replenishments_Deposits_DepositId",
|
||||
column: x => x.DepositId,
|
||||
principalTable: "Deposits",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DepositCurrencies",
|
||||
columns: table => new
|
||||
{
|
||||
DepositId = table.Column<string>(type: "text", nullable: false),
|
||||
CurrencyId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_DepositCurrencies", x => new { x.DepositId, x.CurrencyId });
|
||||
table.ForeignKey(
|
||||
name: "FK_DepositCurrencies_Currencies_CurrencyId",
|
||||
column: x => x.CurrencyId,
|
||||
principalTable: "Currencies",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_DepositCurrencies_Deposits_DepositId",
|
||||
column: x => x.DepositId,
|
||||
principalTable: "Deposits",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "CreditPrograms",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
Name = table.Column<string>(type: "text", nullable: false),
|
||||
Cost = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
MaxCost = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
StorekeeperId = table.Column<string>(type: "text", nullable: false),
|
||||
PeriodId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_CreditPrograms", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_CreditPrograms_Periods_PeriodId",
|
||||
column: x => x.PeriodId,
|
||||
principalTable: "Periods",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_CreditPrograms_Storekeepers_StorekeeperId",
|
||||
column: x => x.StorekeeperId,
|
||||
principalTable: "Storekeepers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "CreditProgramClients",
|
||||
columns: table => new
|
||||
{
|
||||
ClientId = table.Column<string>(type: "text", nullable: false),
|
||||
CreditProgramId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_CreditProgramClients", x => new { x.ClientId, x.CreditProgramId });
|
||||
table.ForeignKey(
|
||||
name: "FK_CreditProgramClients_Clients_ClientId",
|
||||
column: x => x.ClientId,
|
||||
principalTable: "Clients",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_CreditProgramClients_CreditPrograms_CreditProgramId",
|
||||
column: x => x.CreditProgramId,
|
||||
principalTable: "CreditPrograms",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "CurrencyCreditPrograms",
|
||||
columns: table => new
|
||||
{
|
||||
CreditProgramId = table.Column<string>(type: "text", nullable: false),
|
||||
CurrencyId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_CurrencyCreditPrograms", x => new { x.CreditProgramId, x.CurrencyId });
|
||||
table.ForeignKey(
|
||||
name: "FK_CurrencyCreditPrograms_CreditPrograms_CreditProgramId",
|
||||
column: x => x.CreditProgramId,
|
||||
principalTable: "CreditPrograms",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_CurrencyCreditPrograms_Currencies_CurrencyId",
|
||||
column: x => x.CurrencyId,
|
||||
principalTable: "Currencies",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Clerks_Email",
|
||||
table: "Clerks",
|
||||
column: "Email",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Clerks_Login",
|
||||
table: "Clerks",
|
||||
column: "Login",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Clerks_PhoneNumber",
|
||||
table: "Clerks",
|
||||
column: "PhoneNumber",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Clients_ClerkId",
|
||||
table: "Clients",
|
||||
column: "ClerkId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CreditProgramClients_CreditProgramId",
|
||||
table: "CreditProgramClients",
|
||||
column: "CreditProgramId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CreditPrograms_Name",
|
||||
table: "CreditPrograms",
|
||||
column: "Name",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CreditPrograms_PeriodId",
|
||||
table: "CreditPrograms",
|
||||
column: "PeriodId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CreditPrograms_StorekeeperId",
|
||||
table: "CreditPrograms",
|
||||
column: "StorekeeperId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Currencies_Abbreviation",
|
||||
table: "Currencies",
|
||||
column: "Abbreviation",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Currencies_StorekeeperId",
|
||||
table: "Currencies",
|
||||
column: "StorekeeperId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CurrencyCreditPrograms_CurrencyId",
|
||||
table: "CurrencyCreditPrograms",
|
||||
column: "CurrencyId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DepositClients_ClientId",
|
||||
table: "DepositClients",
|
||||
column: "ClientId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DepositCurrencies_CurrencyId",
|
||||
table: "DepositCurrencies",
|
||||
column: "CurrencyId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Deposits_ClerkId",
|
||||
table: "Deposits",
|
||||
column: "ClerkId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Periods_StorekeeperId",
|
||||
table: "Periods",
|
||||
column: "StorekeeperId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Replenishments_ClerkId",
|
||||
table: "Replenishments",
|
||||
column: "ClerkId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Replenishments_DepositId",
|
||||
table: "Replenishments",
|
||||
column: "DepositId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Storekeepers_Email",
|
||||
table: "Storekeepers",
|
||||
column: "Email",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Storekeepers_Login",
|
||||
table: "Storekeepers",
|
||||
column: "Login",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Storekeepers_PhoneNumber",
|
||||
table: "Storekeepers",
|
||||
column: "PhoneNumber",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "CreditProgramClients");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "CurrencyCreditPrograms");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "DepositClients");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "DepositCurrencies");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Replenishments");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "CreditPrograms");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Clients");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Currencies");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Deposits");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Periods");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Clerks");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Storekeepers");
|
||||
}
|
||||
}
|
||||
}
|
||||
559
TheBank/BankDatabase/Migrations/BankDbContextModelSnapshot.cs
Normal file
559
TheBank/BankDatabase/Migrations/BankDbContextModelSnapshot.cs
Normal file
@@ -0,0 +1,559 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using BankDatabase;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BankDatabase.Migrations
|
||||
{
|
||||
[DbContext(typeof(BankDbContext))]
|
||||
partial class BankDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.4")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Clerk", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Login")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("MiddleName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Password")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Surname")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Email")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("Login")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("PhoneNumber")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Clerks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Client", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<decimal>("Balance")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<string>("ClerkId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Surname")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClerkId");
|
||||
|
||||
b.ToTable("Clients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.ClientCreditProgram", b =>
|
||||
{
|
||||
b.Property<string>("ClientId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("CreditProgramId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("ClientId", "CreditProgramId");
|
||||
|
||||
b.HasIndex("CreditProgramId");
|
||||
|
||||
b.ToTable("CreditProgramClients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.CreditProgram", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<decimal>("Cost")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<decimal>("MaxCost")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PeriodId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("StorekeeperId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Name")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("PeriodId");
|
||||
|
||||
b.HasIndex("StorekeeperId");
|
||||
|
||||
b.ToTable("CreditPrograms");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.CreditProgramCurrency", b =>
|
||||
{
|
||||
b.Property<string>("CreditProgramId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("CurrencyId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("CreditProgramId", "CurrencyId");
|
||||
|
||||
b.HasIndex("CurrencyId");
|
||||
|
||||
b.ToTable("CurrencyCreditPrograms");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Currency", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Abbreviation")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<decimal>("Cost")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("StorekeeperId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Abbreviation")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("StorekeeperId");
|
||||
|
||||
b.ToTable("Currencies");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Deposit", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ClerkId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<decimal>("Cost")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<float>("InterestRate")
|
||||
.HasColumnType("real");
|
||||
|
||||
b.Property<int>("Period")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClerkId");
|
||||
|
||||
b.ToTable("Deposits");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.DepositClient", b =>
|
||||
{
|
||||
b.Property<string>("DepositId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ClientId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("DepositId", "ClientId");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.ToTable("DepositClients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.DepositCurrency", b =>
|
||||
{
|
||||
b.Property<string>("DepositId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("CurrencyId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("DepositId", "CurrencyId");
|
||||
|
||||
b.HasIndex("CurrencyId");
|
||||
|
||||
b.ToTable("DepositCurrencies");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Period", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime>("EndTime")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTime>("StartTime")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("StorekeeperId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StorekeeperId");
|
||||
|
||||
b.ToTable("Periods");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Replenishment", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<decimal>("Amount")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<string>("ClerkId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime>("Date")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("DepositId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClerkId");
|
||||
|
||||
b.HasIndex("DepositId");
|
||||
|
||||
b.ToTable("Replenishments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Storekeeper", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Login")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("MiddleName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Password")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Surname")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Email")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("Login")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("PhoneNumber")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Storekeepers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Client", b =>
|
||||
{
|
||||
b.HasOne("BankDatabase.Models.Clerk", "Clerk")
|
||||
.WithMany("Clients")
|
||||
.HasForeignKey("ClerkId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Clerk");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.ClientCreditProgram", b =>
|
||||
{
|
||||
b.HasOne("BankDatabase.Models.Client", "Client")
|
||||
.WithMany("CreditProgramClients")
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("BankDatabase.Models.CreditProgram", "CreditProgram")
|
||||
.WithMany("CreditProgramClients")
|
||||
.HasForeignKey("CreditProgramId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
|
||||
b.Navigation("CreditProgram");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.CreditProgram", b =>
|
||||
{
|
||||
b.HasOne("BankDatabase.Models.Period", "Period")
|
||||
.WithMany("CreditPrograms")
|
||||
.HasForeignKey("PeriodId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("BankDatabase.Models.Storekeeper", "Storekeeper")
|
||||
.WithMany("CreditPrograms")
|
||||
.HasForeignKey("StorekeeperId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Period");
|
||||
|
||||
b.Navigation("Storekeeper");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.CreditProgramCurrency", b =>
|
||||
{
|
||||
b.HasOne("BankDatabase.Models.CreditProgram", "CreditProgram")
|
||||
.WithMany("CurrencyCreditPrograms")
|
||||
.HasForeignKey("CreditProgramId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("BankDatabase.Models.Currency", "Currency")
|
||||
.WithMany("CurrencyCreditPrograms")
|
||||
.HasForeignKey("CurrencyId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("CreditProgram");
|
||||
|
||||
b.Navigation("Currency");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Currency", b =>
|
||||
{
|
||||
b.HasOne("BankDatabase.Models.Storekeeper", "Storekeeper")
|
||||
.WithMany("Currencies")
|
||||
.HasForeignKey("StorekeeperId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Storekeeper");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Deposit", b =>
|
||||
{
|
||||
b.HasOne("BankDatabase.Models.Clerk", "Clerk")
|
||||
.WithMany("Deposits")
|
||||
.HasForeignKey("ClerkId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Clerk");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.DepositClient", b =>
|
||||
{
|
||||
b.HasOne("BankDatabase.Models.Client", "Client")
|
||||
.WithMany("DepositClients")
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("BankDatabase.Models.Deposit", "Deposit")
|
||||
.WithMany("DepositClients")
|
||||
.HasForeignKey("DepositId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
|
||||
b.Navigation("Deposit");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.DepositCurrency", b =>
|
||||
{
|
||||
b.HasOne("BankDatabase.Models.Currency", "Currency")
|
||||
.WithMany("DepositCurrencies")
|
||||
.HasForeignKey("CurrencyId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("BankDatabase.Models.Deposit", "Deposit")
|
||||
.WithMany("DepositCurrencies")
|
||||
.HasForeignKey("DepositId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Currency");
|
||||
|
||||
b.Navigation("Deposit");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Period", b =>
|
||||
{
|
||||
b.HasOne("BankDatabase.Models.Storekeeper", "Storekeeper")
|
||||
.WithMany("Periods")
|
||||
.HasForeignKey("StorekeeperId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Storekeeper");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Replenishment", b =>
|
||||
{
|
||||
b.HasOne("BankDatabase.Models.Clerk", "Clerk")
|
||||
.WithMany("Replenishments")
|
||||
.HasForeignKey("ClerkId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("BankDatabase.Models.Deposit", "Deposit")
|
||||
.WithMany("Replenishments")
|
||||
.HasForeignKey("DepositId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Clerk");
|
||||
|
||||
b.Navigation("Deposit");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Clerk", b =>
|
||||
{
|
||||
b.Navigation("Clients");
|
||||
|
||||
b.Navigation("Deposits");
|
||||
|
||||
b.Navigation("Replenishments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Client", b =>
|
||||
{
|
||||
b.Navigation("CreditProgramClients");
|
||||
|
||||
b.Navigation("DepositClients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.CreditProgram", b =>
|
||||
{
|
||||
b.Navigation("CreditProgramClients");
|
||||
|
||||
b.Navigation("CurrencyCreditPrograms");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Currency", b =>
|
||||
{
|
||||
b.Navigation("CurrencyCreditPrograms");
|
||||
|
||||
b.Navigation("DepositCurrencies");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Deposit", b =>
|
||||
{
|
||||
b.Navigation("DepositClients");
|
||||
|
||||
b.Navigation("DepositCurrencies");
|
||||
|
||||
b.Navigation("Replenishments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Period", b =>
|
||||
{
|
||||
b.Navigation("CreditPrograms");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BankDatabase.Models.Storekeeper", b =>
|
||||
{
|
||||
b.Navigation("CreditPrograms");
|
||||
|
||||
b.Navigation("Currencies");
|
||||
|
||||
b.Navigation("Periods");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace BankDatabase.Models;
|
||||
|
||||
class Clerk
|
||||
public class Clerk
|
||||
{
|
||||
public required string Id { get; set; }
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace BankDatabase.Models;
|
||||
|
||||
class Client
|
||||
public class Client
|
||||
{
|
||||
public required string Id { get; set; }
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
namespace BankDatabase.Models;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
class ClientCreditProgram
|
||||
namespace BankDatabase.Models;
|
||||
|
||||
public class ClientCreditProgram
|
||||
{
|
||||
public required string ClientId { get; set; }
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace BankDatabase.Models;
|
||||
|
||||
class CreditProgram
|
||||
public class CreditProgram
|
||||
{
|
||||
public required string Id { get; set; }
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace BankDatabase.Models;
|
||||
|
||||
class CreditProgramCurrency
|
||||
public class CreditProgramCurrency
|
||||
{
|
||||
public required string CreditProgramId { get; set; }
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace BankDatabase.Models;
|
||||
|
||||
class Currency
|
||||
public class Currency
|
||||
{
|
||||
public required string Id { get; set; }
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace BankDatabase.Models;
|
||||
|
||||
class Deposit
|
||||
public class Deposit
|
||||
{
|
||||
public required string Id { get; set; }
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
namespace BankDatabase.Models;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
class DepositClient
|
||||
namespace BankDatabase.Models;
|
||||
|
||||
public class DepositClient
|
||||
{
|
||||
public required string DepositId { get; set; }
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
namespace BankDatabase.Models;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
class DepositCurrency
|
||||
namespace BankDatabase.Models;
|
||||
|
||||
public class DepositCurrency
|
||||
{
|
||||
public required string DepositId { get; set; }
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace BankDatabase.Models;
|
||||
|
||||
class Period
|
||||
public class Period
|
||||
{
|
||||
public required string Id { get; set; }
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace BankDatabase.Models;
|
||||
|
||||
class Replenishment
|
||||
public class Replenishment
|
||||
{
|
||||
public required string Id { get; set; }
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace BankDatabase.Models;
|
||||
|
||||
class Storekeeper
|
||||
public class Storekeeper
|
||||
{
|
||||
public required string Id { get; set; }
|
||||
|
||||
|
||||
@@ -5,5 +5,5 @@ namespace BankTests.Infrastructure;
|
||||
internal class ConfigurationDatabase : IConfigurationDatabase
|
||||
{
|
||||
public string ConnectionString =>
|
||||
"Host=127.0.0.1;Port=5432;Database=TitanicTest;Username=postgres;Password=postgres;Include Error Detail=true";
|
||||
"Host=127.0.0.1;Port=5432;Database=TitanicTest;Username=postgres;Password=admin123;Include Error Detail=true";
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ internal class BaseStorageContractTest
|
||||
[OneTimeSetUp]
|
||||
public void OneTimeSetUp()
|
||||
{
|
||||
BankDbContext = new BankDbContext(new ConfigurationDatabase());
|
||||
BankDbContext = new BankDbContext(new Infrastructure.ConfigurationDatabase());
|
||||
|
||||
BankDbContext.Database.EnsureDeleted();
|
||||
BankDbContext.Database.EnsureCreated();
|
||||
|
||||
@@ -71,6 +71,186 @@ internal class CreditProgramStorageContractTests : BaseStorageContractTest
|
||||
Assert.That(list, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Try_GetList_WithCurrencyRelations_Test()
|
||||
{
|
||||
// Создаем storekeeper и сохраняем его
|
||||
var uniqueId = Guid.NewGuid();
|
||||
var storekeeper = BankDbContext.InsertStorekeeperToDatabaseAndReturn(
|
||||
login: $"storekeeper_{uniqueId}",
|
||||
email: $"storekeeper_{uniqueId}@email.com",
|
||||
phone: $"+7-777-777-{uniqueId.ToString().Substring(0, 4)}"
|
||||
);
|
||||
BankDbContext.SaveChanges();
|
||||
|
||||
// Проверяем, что storekeeper действительно сохранен
|
||||
var savedStorekeeper = BankDbContext.Storekeepers.FirstOrDefault(s => s.Id == storekeeper.Id);
|
||||
Assert.That(savedStorekeeper, Is.Not.Null, "Storekeeper не был сохранен в базе данных");
|
||||
var storekeeperId = savedStorekeeper.Id;
|
||||
|
||||
// Создаем несколько валют
|
||||
var currency1Id = BankDbContext.InsertCurrencyToDatabaseAndReturn(storekeeperId: storekeeperId, abbreviation: "USD").Id;
|
||||
var currency2Id = BankDbContext.InsertCurrencyToDatabaseAndReturn(storekeeperId: storekeeperId, abbreviation: "EUR").Id;
|
||||
|
||||
// Создаем кредитную программу с двумя валютами
|
||||
var creditProgram = BankDbContext.InsertCreditProgramToDatabaseAndReturn(
|
||||
storekeeperId: storekeeperId,
|
||||
periodId: _periodId,
|
||||
creditProgramCurrency: [
|
||||
(currency1Id, Guid.NewGuid().ToString()),
|
||||
(currency2Id, Guid.NewGuid().ToString())
|
||||
]
|
||||
);
|
||||
|
||||
var list = _storageContract.GetList();
|
||||
Assert.That(list, Is.Not.Null);
|
||||
Assert.That(list, Has.Count.EqualTo(1));
|
||||
|
||||
var result = list.First();
|
||||
Assert.That(result.Currencies, Is.Not.Null);
|
||||
Assert.That(result.Currencies, Has.Count.EqualTo(2));
|
||||
Assert.That(result.Currencies.Select(c => c.CurrencyId), Does.Contain(currency1Id));
|
||||
Assert.That(result.Currencies.Select(c => c.CurrencyId), Does.Contain(currency2Id));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Try_AddElement_WithCurrencyRelations_Test()
|
||||
{
|
||||
// Создаем storekeeper и сохраняем его
|
||||
var uniqueId = Guid.NewGuid();
|
||||
var storekeeper = BankDbContext.InsertStorekeeperToDatabaseAndReturn(
|
||||
login: $"storekeeper_{uniqueId}",
|
||||
email: $"storekeeper_{uniqueId}@email.com",
|
||||
phone: $"+7-777-777-{uniqueId.ToString().Substring(0, 4)}"
|
||||
);
|
||||
BankDbContext.SaveChanges();
|
||||
|
||||
// Проверяем, что storekeeper действительно сохранен
|
||||
var savedStorekeeper = BankDbContext.Storekeepers.FirstOrDefault(s => s.Id == storekeeper.Id);
|
||||
Assert.That(savedStorekeeper, Is.Not.Null, "Storekeeper не был сохранен в базе данных");
|
||||
var storekeeperId = savedStorekeeper.Id;
|
||||
|
||||
// Создаем валюту
|
||||
var currencyId = BankDbContext.InsertCurrencyToDatabaseAndReturn(storekeeperId: storekeeperId).Id;
|
||||
|
||||
// Создаем модель с валютой
|
||||
var creditProgram = CreateModel(
|
||||
name: "unique name",
|
||||
periodId: _periodId,
|
||||
storekeeperId: storekeeperId,
|
||||
currency: [
|
||||
new CreditProgramCurrencyDataModel(Guid.NewGuid().ToString(), currencyId)
|
||||
]
|
||||
);
|
||||
|
||||
_storageContract.AddElement(creditProgram);
|
||||
|
||||
var result = BankDbContext.GetCreditProgramFromDatabase(creditProgram.Id);
|
||||
Assert.That(result, Is.Not.Null);
|
||||
Assert.That(result.CurrencyCreditPrograms, Is.Not.Null);
|
||||
Assert.That(result.CurrencyCreditPrograms, Has.Count.EqualTo(1));
|
||||
Assert.That(result.CurrencyCreditPrograms.First().CurrencyId, Is.EqualTo(currencyId));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Try_UpdElement_WithCurrencyRelations_Test()
|
||||
{
|
||||
// Создаем storekeeper и сохраняем его
|
||||
var uniqueId = Guid.NewGuid();
|
||||
var storekeeper = BankDbContext.InsertStorekeeperToDatabaseAndReturn(
|
||||
login: $"storekeeper_{uniqueId}",
|
||||
email: $"storekeeper_{uniqueId}@email.com",
|
||||
phone: $"+7-777-777-{uniqueId.ToString().Substring(0, 4)}"
|
||||
);
|
||||
BankDbContext.SaveChanges();
|
||||
|
||||
// Проверяем, что storekeeper действительно сохранен
|
||||
var savedStorekeeper = BankDbContext.Storekeepers.FirstOrDefault(s => s.Id == storekeeper.Id);
|
||||
Assert.That(savedStorekeeper, Is.Not.Null, "Storekeeper не был сохранен в базе данных");
|
||||
var storekeeperId = savedStorekeeper.Id;
|
||||
|
||||
// Создаем две валюты
|
||||
var currency1Id = BankDbContext.InsertCurrencyToDatabaseAndReturn(storekeeperId: storekeeperId).Id;
|
||||
var currency2Id = BankDbContext.InsertCurrencyToDatabaseAndReturn(storekeeperId: storekeeperId).Id;
|
||||
|
||||
// Создаем кредитную программу с одной валютой
|
||||
var creditProgram = BankDbContext.InsertCreditProgramToDatabaseAndReturn(
|
||||
storekeeperId: storekeeperId,
|
||||
periodId: _periodId,
|
||||
creditProgramCurrency: [(currency1Id, Guid.NewGuid().ToString())]
|
||||
);
|
||||
|
||||
// Обновляем программу, добавляя вторую валюту
|
||||
var updatedModel = CreateModel(
|
||||
id: creditProgram.Id,
|
||||
name: creditProgram.Name,
|
||||
periodId: _periodId,
|
||||
storekeeperId: storekeeperId,
|
||||
currency: [
|
||||
new CreditProgramCurrencyDataModel(creditProgram.Id, currency1Id),
|
||||
new CreditProgramCurrencyDataModel(creditProgram.Id, currency2Id)
|
||||
]
|
||||
);
|
||||
|
||||
_storageContract.UpdElement(updatedModel);
|
||||
|
||||
var result = BankDbContext.GetCreditProgramFromDatabase(creditProgram.Id);
|
||||
Assert.That(result, Is.Not.Null);
|
||||
Assert.That(result.CurrencyCreditPrograms, Is.Not.Null);
|
||||
Assert.That(result.CurrencyCreditPrograms, Has.Count.EqualTo(2));
|
||||
Assert.That(result.CurrencyCreditPrograms.Select(c => c.CurrencyId), Does.Contain(currency1Id));
|
||||
Assert.That(result.CurrencyCreditPrograms.Select(c => c.CurrencyId), Does.Contain(currency2Id));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Try_UpdElement_RemoveCurrencyRelations_Test()
|
||||
{
|
||||
// Создаем storekeeper и сохраняем его
|
||||
var uniqueId = Guid.NewGuid();
|
||||
var storekeeper = BankDbContext.InsertStorekeeperToDatabaseAndReturn(
|
||||
login: $"storekeeper_{uniqueId}",
|
||||
email: $"storekeeper_{uniqueId}@email.com",
|
||||
phone: $"+7-777-777-{uniqueId.ToString().Substring(0, 4)}"
|
||||
);
|
||||
BankDbContext.SaveChanges();
|
||||
|
||||
// Проверяем, что storekeeper действительно сохранен
|
||||
var savedStorekeeper = BankDbContext.Storekeepers.FirstOrDefault(s => s.Id == storekeeper.Id);
|
||||
Assert.That(savedStorekeeper, Is.Not.Null, "Storekeeper не был сохранен в базе данных");
|
||||
var storekeeperId = savedStorekeeper.Id;
|
||||
|
||||
// Создаем две валюты
|
||||
var currency1Id = BankDbContext.InsertCurrencyToDatabaseAndReturn(storekeeperId: storekeeperId).Id;
|
||||
var currency2Id = BankDbContext.InsertCurrencyToDatabaseAndReturn(storekeeperId: storekeeperId).Id;
|
||||
|
||||
// Создаем кредитную программу с двумя валютами
|
||||
var creditProgram = BankDbContext.InsertCreditProgramToDatabaseAndReturn(
|
||||
storekeeperId: storekeeperId,
|
||||
periodId: _periodId,
|
||||
creditProgramCurrency: [
|
||||
(currency1Id, Guid.NewGuid().ToString()),
|
||||
(currency2Id, Guid.NewGuid().ToString())
|
||||
]
|
||||
);
|
||||
|
||||
// Обновляем программу, оставляя только одну валюту
|
||||
var updatedModel = CreateModel(
|
||||
id: creditProgram.Id,
|
||||
name: creditProgram.Name,
|
||||
periodId: _periodId,
|
||||
storekeeperId: storekeeperId,
|
||||
currency: [new CreditProgramCurrencyDataModel(creditProgram.Id, currency1Id)]
|
||||
);
|
||||
|
||||
_storageContract.UpdElement(updatedModel);
|
||||
|
||||
var result = BankDbContext.GetCreditProgramFromDatabase(creditProgram.Id);
|
||||
Assert.That(result, Is.Not.Null);
|
||||
Assert.That(result.CurrencyCreditPrograms, Is.Not.Null);
|
||||
Assert.That(result.CurrencyCreditPrograms, Has.Count.EqualTo(1));
|
||||
Assert.That(result.CurrencyCreditPrograms.First().CurrencyId, Is.EqualTo(currency1Id));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Try_GetElementById_WhenHaveRecord_Test()
|
||||
{
|
||||
|
||||
@@ -18,7 +18,12 @@ internal class DepositStorageContractTests : BaseStorageContractTest
|
||||
public void SetUp()
|
||||
{
|
||||
_storageContract = new DepositStorageContract(BankDbContext);
|
||||
_clerkId = BankDbContext.InsertClerkToDatabaseAndReturn().Id;
|
||||
var uniqueId = Guid.NewGuid();
|
||||
_clerkId = BankDbContext.InsertClerkToDatabaseAndReturn(
|
||||
login: $"clerk_{uniqueId}",
|
||||
email: $"clerk_{uniqueId}@email.com",
|
||||
phone: $"+7-777-777-{uniqueId.ToString().Substring(0, 4)}"
|
||||
).Id;
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
@@ -93,6 +98,131 @@ internal class DepositStorageContractTests : BaseStorageContractTest
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Try_GetList_ByClerkId_Test()
|
||||
{
|
||||
var uniqueId1 = Guid.NewGuid();
|
||||
var uniqueId2 = Guid.NewGuid();
|
||||
var clerkId1 = BankDbContext.InsertClerkToDatabaseAndReturn(
|
||||
email: $"clerk1_{uniqueId1}@email.com",
|
||||
login: $"clerk1_{uniqueId1}",
|
||||
phone: $"+7-777-777-{uniqueId1.ToString().Substring(0, 4)}"
|
||||
).Id;
|
||||
var clerkId2 = BankDbContext.InsertClerkToDatabaseAndReturn(
|
||||
email: $"clerk2_{uniqueId2}@email.com",
|
||||
login: $"clerk2_{uniqueId2}",
|
||||
phone: $"+7-777-777-{uniqueId2.ToString().Substring(0, 4)}"
|
||||
).Id;
|
||||
|
||||
BankDbContext.InsertDepositToDatabaseAndReturn(clerkId: clerkId1);
|
||||
BankDbContext.InsertDepositToDatabaseAndReturn(clerkId: clerkId1);
|
||||
BankDbContext.InsertDepositToDatabaseAndReturn(clerkId: clerkId2);
|
||||
|
||||
var list = _storageContract.GetList(clerkId1);
|
||||
Assert.That(list, Is.Not.Null);
|
||||
Assert.That(list, Has.Count.EqualTo(2));
|
||||
Assert.That(list.All(x => x.ClerkId == clerkId1), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Try_GetElementByInterestRate_WhenHaveRecord_Test()
|
||||
{
|
||||
var deposit = BankDbContext.InsertDepositToDatabaseAndReturn(clerkId: _clerkId, interestRate: 15.5f);
|
||||
var result = _storageContract.GetElementByInterestRate(15.5f);
|
||||
Assert.That(result, Is.Not.Null);
|
||||
AssertElement(result, deposit);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Try_GetElementByInterestRate_WhenNoRecord_Test()
|
||||
{
|
||||
var result = _storageContract.GetElementByInterestRate(15.5f);
|
||||
Assert.That(result, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Try_AddElement_WhenHaveRecordWithSameInterestRate_Test()
|
||||
{
|
||||
// Создаем первый депозит с определенной процентной ставкой
|
||||
var deposit1 = CreateModel(clerkId: _clerkId, interestRate: 10.5f);
|
||||
_storageContract.AddElement(deposit1);
|
||||
|
||||
// Создаем второй депозит с такой же процентной ставкой
|
||||
var deposit2 = CreateModel(clerkId: _clerkId, interestRate: 10.5f);
|
||||
|
||||
// Проверяем, что можно добавить депозит с такой же процентной ставкой
|
||||
_storageContract.AddElement(deposit2);
|
||||
var result = BankDbContext.GetDepositFromDatabase(deposit2.Id);
|
||||
Assert.That(result, Is.Not.Null);
|
||||
AssertElement(result, deposit2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Try_UpdElement_WithCurrencies_Test()
|
||||
{
|
||||
// Создаем валюты
|
||||
var storekeeperId = BankDbContext.InsertStorekeeperToDatabaseAndReturn(
|
||||
login: $"storekeeper_{Guid.NewGuid()}",
|
||||
email: $"storekeeper_{Guid.NewGuid()}@email.com",
|
||||
phone: $"+7-777-777-{Guid.NewGuid().ToString().Substring(0, 4)}"
|
||||
).Id;
|
||||
|
||||
var currency1 = BankDbContext.InsertCurrencyToDatabaseAndReturn(
|
||||
abbreviation: "USD",
|
||||
storekeeperId: storekeeperId
|
||||
);
|
||||
var currency2 = BankDbContext.InsertCurrencyToDatabaseAndReturn(
|
||||
abbreviation: "EUR",
|
||||
storekeeperId: storekeeperId
|
||||
);
|
||||
|
||||
// Создаем депозит
|
||||
var deposit = BankDbContext.InsertDepositToDatabaseAndReturn(clerkId: _clerkId);
|
||||
|
||||
// Обновляем депозит с валютами
|
||||
var updatedDeposit = CreateModel(
|
||||
id: deposit.Id,
|
||||
clerkId: _clerkId,
|
||||
deposits: new List<DepositCurrencyDataModel>
|
||||
{
|
||||
new(deposit.Id, currency1.Id),
|
||||
new(deposit.Id, currency2.Id)
|
||||
}
|
||||
);
|
||||
|
||||
_storageContract.UpdElement(updatedDeposit);
|
||||
var result = BankDbContext.GetDepositFromDatabase(deposit.Id);
|
||||
Assert.That(result.DepositCurrencies, Has.Count.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Try_GetListAsync_ByDateRange_Test()
|
||||
{
|
||||
var startDate = DateTime.Now.AddDays(-1);
|
||||
var endDate = DateTime.Now.AddDays(1);
|
||||
|
||||
var deposit = BankDbContext.InsertDepositToDatabaseAndReturn(clerkId: _clerkId);
|
||||
|
||||
var list = await _storageContract.GetListAsync(startDate, endDate, CancellationToken.None);
|
||||
Assert.That(list, Is.Not.Null);
|
||||
Assert.That(list, Has.Count.EqualTo(1));
|
||||
AssertElement(list.First(), deposit);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Try_AddElement_WhenDatabaseError_Test()
|
||||
{
|
||||
var deposit = CreateModel(clerkId: _clerkId);
|
||||
// Симулируем ошибку базы данных, пытаясь добавить депозит с несуществующим ID клерка
|
||||
var nonExistentClerkId = Guid.NewGuid().ToString();
|
||||
var depositWithInvalidClerk = CreateModel(clerkId: nonExistentClerkId);
|
||||
|
||||
Assert.That(
|
||||
() => _storageContract.AddElement(depositWithInvalidClerk),
|
||||
Throws.TypeOf<StorageException>()
|
||||
);
|
||||
}
|
||||
|
||||
private static DepositDataModel CreateModel(
|
||||
string? id = null,
|
||||
float interestRate = 10,
|
||||
|
||||
@@ -20,7 +20,12 @@ internal class ReplenishmentStorageContractTests : BaseStorageContractTest
|
||||
public void SetUp()
|
||||
{
|
||||
_storageContract = new ReplenishmentStorageContract(BankDbContext);
|
||||
_clerkId = BankDbContext.InsertClerkToDatabaseAndReturn().Id;
|
||||
var uniqueId = Guid.NewGuid();
|
||||
_clerkId = BankDbContext.InsertClerkToDatabaseAndReturn(
|
||||
login: $"clerk_{uniqueId}",
|
||||
email: $"clerk_{uniqueId}@email.com",
|
||||
phone: $"+7-777-777-{uniqueId.ToString().Substring(0, 4)}"
|
||||
).Id;
|
||||
_depositId = BankDbContext.InsertDepositToDatabaseAndReturn(clerkId: _clerkId).Id;
|
||||
}
|
||||
|
||||
@@ -62,6 +67,120 @@ internal class ReplenishmentStorageContractTests : BaseStorageContractTest
|
||||
Assert.That(list, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Try_GetList_WithDateFilters_Test()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var pastDate = now.AddDays(-1);
|
||||
var futureDate = now.AddDays(1);
|
||||
|
||||
// Insert replenishments with different dates
|
||||
var pastReplenishment = BankDbContext.InsertReplenishmentToDatabaseAndReturn(
|
||||
clerkId: _clerkId,
|
||||
depositId: _depositId,
|
||||
date: pastDate
|
||||
);
|
||||
var currentReplenishment = BankDbContext.InsertReplenishmentToDatabaseAndReturn(
|
||||
clerkId: _clerkId,
|
||||
depositId: _depositId,
|
||||
date: now
|
||||
);
|
||||
var futureReplenishment = BankDbContext.InsertReplenishmentToDatabaseAndReturn(
|
||||
clerkId: _clerkId,
|
||||
depositId: _depositId,
|
||||
date: futureDate
|
||||
);
|
||||
|
||||
// Test date range filter
|
||||
var filteredList = _storageContract.GetList(fromDate: pastDate, toDate: now);
|
||||
Assert.That(filteredList, Has.Count.EqualTo(2));
|
||||
Assert.That(filteredList.Select(x => x.Id), Does.Contain(pastReplenishment.Id));
|
||||
Assert.That(filteredList.Select(x => x.Id), Does.Contain(currentReplenishment.Id));
|
||||
Assert.That(filteredList.Select(x => x.Id), Does.Not.Contain(futureReplenishment.Id));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Try_GetList_WithClerkIdFilter_Test()
|
||||
{
|
||||
var otherClerkId = BankDbContext.InsertClerkToDatabaseAndReturn(
|
||||
login: $"clerk_other",
|
||||
email: "clerk_other@email.com",
|
||||
phone: "+7-777-777-0000"
|
||||
).Id;
|
||||
|
||||
// Insert replenishments for different clerks
|
||||
var replenishment1 = BankDbContext.InsertReplenishmentToDatabaseAndReturn(
|
||||
clerkId: _clerkId,
|
||||
depositId: _depositId
|
||||
);
|
||||
var replenishment2 = BankDbContext.InsertReplenishmentToDatabaseAndReturn(
|
||||
clerkId: otherClerkId,
|
||||
depositId: _depositId
|
||||
);
|
||||
|
||||
var filteredList = _storageContract.GetList(clerkId: _clerkId);
|
||||
Assert.That(filteredList, Has.Count.EqualTo(1));
|
||||
Assert.That(filteredList.First().Id, Is.EqualTo(replenishment1.Id));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Try_GetList_WithDepositIdFilter_Test()
|
||||
{
|
||||
var otherDepositId = BankDbContext.InsertDepositToDatabaseAndReturn(clerkId: _clerkId).Id;
|
||||
|
||||
// Insert replenishments for different deposits
|
||||
var replenishment1 = BankDbContext.InsertReplenishmentToDatabaseAndReturn(
|
||||
clerkId: _clerkId,
|
||||
depositId: _depositId
|
||||
);
|
||||
var replenishment2 = BankDbContext.InsertReplenishmentToDatabaseAndReturn(
|
||||
clerkId: _clerkId,
|
||||
depositId: otherDepositId
|
||||
);
|
||||
|
||||
var filteredList = _storageContract.GetList(depositId: _depositId);
|
||||
Assert.That(filteredList, Has.Count.EqualTo(1));
|
||||
Assert.That(filteredList.First().Id, Is.EqualTo(replenishment1.Id));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Try_GetList_WithCombinedFilters_Test()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var otherClerkId = BankDbContext.InsertClerkToDatabaseAndReturn(
|
||||
login: $"clerk_other",
|
||||
email: "clerk_other@email.com",
|
||||
phone: "+7-777-777-0000"
|
||||
).Id;
|
||||
var otherDepositId = BankDbContext.InsertDepositToDatabaseAndReturn(clerkId: _clerkId).Id;
|
||||
|
||||
// Insert replenishments with different combinations
|
||||
var replenishment1 = BankDbContext.InsertReplenishmentToDatabaseAndReturn(
|
||||
clerkId: _clerkId,
|
||||
depositId: _depositId,
|
||||
date: now
|
||||
);
|
||||
var replenishment2 = BankDbContext.InsertReplenishmentToDatabaseAndReturn(
|
||||
clerkId: otherClerkId,
|
||||
depositId: _depositId,
|
||||
date: now
|
||||
);
|
||||
var replenishment3 = BankDbContext.InsertReplenishmentToDatabaseAndReturn(
|
||||
clerkId: _clerkId,
|
||||
depositId: otherDepositId,
|
||||
date: now
|
||||
);
|
||||
|
||||
var filteredList = _storageContract.GetList(
|
||||
fromDate: now,
|
||||
toDate: now,
|
||||
clerkId: _clerkId,
|
||||
depositId: _depositId
|
||||
);
|
||||
Assert.That(filteredList, Has.Count.EqualTo(1));
|
||||
Assert.That(filteredList.First().Id, Is.EqualTo(replenishment1.Id));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Try_GetElementById_WhenHaveRecord_Test()
|
||||
{
|
||||
|
||||
@@ -72,7 +72,7 @@ internal class ClerkControllerTests : BaseWebApiControllerTest
|
||||
// Arrange
|
||||
var model = CreateModel();
|
||||
// Act
|
||||
var response = await HttpClient.PostAsJsonAsync("/api/clerks", model);
|
||||
var response = await HttpClient.PostAsJsonAsync("/api/clerks/register", model);
|
||||
// Assert
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.NoContent));
|
||||
AssertElement(BankDbContext.GetClerkFromDatabase(model.Id!), model);
|
||||
@@ -85,7 +85,7 @@ internal class ClerkControllerTests : BaseWebApiControllerTest
|
||||
var model = CreateModel();
|
||||
BankDbContext.InsertClerkToDatabaseAndReturn(id: model.Id);
|
||||
// Act
|
||||
var response = await HttpClient.PostAsJsonAsync("/api/clerks", model);
|
||||
var response = await HttpClient.PostAsJsonAsync("/api/clerks/register", model);
|
||||
// Assert
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
|
||||
}
|
||||
@@ -94,7 +94,7 @@ internal class ClerkControllerTests : BaseWebApiControllerTest
|
||||
public async Task Post_WhenSendEmptyData_ShouldBadRequest_Test()
|
||||
{
|
||||
// Act
|
||||
var response = await HttpClient.PostAsync("/api/clerks", MakeContent(string.Empty));
|
||||
var response = await HttpClient.PostAsync("/api/clerks/register", MakeContent(string.Empty));
|
||||
// Assert
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ internal class ClientControllerTests : BaseWebApiControllerTest
|
||||
var client1 = BankDbContext.InsertClientToDatabaseAndReturn(name: "Иван", surname: "Иванов", clerkId: _clerk.Id);
|
||||
var client2 = BankDbContext.InsertClientToDatabaseAndReturn(name: "Петр", surname: "Петров", clerkId: _clerk.Id);
|
||||
// Act
|
||||
var response = await HttpClient.GetAsync("/api/clients/getrecords");
|
||||
var response = await HttpClient.GetAsync("/api/clients/getallrecords");
|
||||
// Assert
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
|
||||
var data = await GetModelFromResponseAsync<List<ClientViewModel>>(response);
|
||||
@@ -48,7 +48,7 @@ internal class ClientControllerTests : BaseWebApiControllerTest
|
||||
public async Task GetList_WhenNoRecords_ShouldSuccess_Test()
|
||||
{
|
||||
// Act
|
||||
var response = await HttpClient.GetAsync("/api/clients/getrecords");
|
||||
var response = await HttpClient.GetAsync("/api/clients/getallrecords");
|
||||
// Assert
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
|
||||
var data = await GetModelFromResponseAsync<List<ClientViewModel>>(response);
|
||||
@@ -81,7 +81,7 @@ internal class ClientControllerTests : BaseWebApiControllerTest
|
||||
public async Task Post_ShouldSuccess_Test()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateModel();
|
||||
var model = CreateModel(clerkId: _clerk.Id);
|
||||
// Act
|
||||
var response = await HttpClient.PostAsJsonAsync("/api/clients/register", model);
|
||||
// Assert
|
||||
@@ -114,7 +114,7 @@ internal class ClientControllerTests : BaseWebApiControllerTest
|
||||
public async Task Put_ShouldSuccess_Test()
|
||||
{
|
||||
// Arrange
|
||||
var model = CreateModel();
|
||||
var model = CreateModel(name: "slava", surname: "fomichev", balance: 1_000_000, clerkId: _clerk.Id);
|
||||
BankDbContext.InsertClientToDatabaseAndReturn(id: model.Id, clerkId: _clerk.Id);
|
||||
// Act
|
||||
var response = await HttpClient.PutAsJsonAsync("/api/clients/changeinfo", model);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using AutoMapper;
|
||||
using BankBusinessLogic.Implementations;
|
||||
using BankContracts.AdapterContracts;
|
||||
using BankContracts.AdapterContracts.OperationResponses;
|
||||
using BankContracts.BindingModels;
|
||||
@@ -6,6 +7,7 @@ using BankContracts.BusinessLogicContracts;
|
||||
using BankContracts.DataModels;
|
||||
using BankContracts.Exceptions;
|
||||
using BankContracts.ViewModels;
|
||||
using BankWebApi.Infrastructure;
|
||||
|
||||
namespace BankWebApi.Adapters;
|
||||
|
||||
@@ -13,11 +15,13 @@ public class ClerkAdapter : IClerkAdapter
|
||||
{
|
||||
private readonly IClerkBusinessLogicContract _clerkBusinessLogicContract;
|
||||
|
||||
private readonly IJwtProvider _jwtProvider;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly Mapper _mapper;
|
||||
|
||||
public ClerkAdapter(IClerkBusinessLogicContract clerkBusinessLogicContract, ILogger logger)
|
||||
public ClerkAdapter(IClerkBusinessLogicContract clerkBusinessLogicContract, ILogger logger, IJwtProvider jwtProvider)
|
||||
{
|
||||
_clerkBusinessLogicContract = clerkBusinessLogicContract;
|
||||
_logger = logger;
|
||||
@@ -27,6 +31,7 @@ public class ClerkAdapter : IClerkAdapter
|
||||
cfg.CreateMap<ClerkDataModel, ClerkViewModel>();
|
||||
});
|
||||
_mapper = new Mapper(config);
|
||||
_jwtProvider = jwtProvider;
|
||||
}
|
||||
|
||||
public ClerkOperationResponse GetList()
|
||||
@@ -170,4 +175,30 @@ public class ClerkAdapter : IClerkAdapter
|
||||
return ClerkOperationResponse.InternalServerError(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public ClerkOperationResponse Login(LoginBindingModel clerkModel, out string token)
|
||||
{
|
||||
token = string.Empty;
|
||||
try
|
||||
{
|
||||
var clerk = _clerkBusinessLogicContract.GetClerkByData(clerkModel.Login);
|
||||
|
||||
|
||||
var result = clerkModel.Password == clerk.Password;
|
||||
|
||||
if (!result)
|
||||
{
|
||||
return ClerkOperationResponse.Unauthorized("Password are incorrect");
|
||||
}
|
||||
|
||||
token = _jwtProvider.GenerateToken(clerk);
|
||||
|
||||
return ClerkOperationResponse.OK(_mapper.Map<ClerkViewModel>(clerk));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Exception in Login");
|
||||
return ClerkOperationResponse.InternalServerError($"Exception in Login {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,13 +24,25 @@ public class ClientAdapter : IClientAdapter
|
||||
_logger = logger;
|
||||
var config = new MapperConfiguration(cfg =>
|
||||
{
|
||||
// Mapping for Client
|
||||
cfg.CreateMap<ClientBindingModel, ClientDataModel>();
|
||||
cfg.CreateMap<DepositDataModel, DepositViewModel>();
|
||||
cfg.CreateMap<ClientDataModel, ClientViewModel>()
|
||||
.ForMember(dest => dest.DepositClients, opt => opt.MapFrom(src => src.DepositClients))
|
||||
.ForMember(dest => dest.CreditProgramClients, opt => opt.MapFrom(src => src.CreditProgramClients));
|
||||
|
||||
// Mapping for Deposit
|
||||
cfg.CreateMap<DepositDataModel, DepositViewModel>()
|
||||
.ForMember(dest => dest.DepositCurrencies, opt => opt.MapFrom(src => src.Currencies)); // Adjust if Currencies is meant to map to DepositClients
|
||||
|
||||
// Mapping for ClientCreditProgram
|
||||
cfg.CreateMap<ClientCreditProgramBindingModel, ClientCreditProgramDataModel>();
|
||||
cfg.CreateMap<ClientCreditProgramDataModel, ClientCreditProgramViewModel>();
|
||||
|
||||
// Mapping for DepositClient
|
||||
cfg.CreateMap<DepositClientBindingModel, DepositClientDataModel>();
|
||||
cfg.CreateMap<DepositClientDataModel, DepositClientViewModel>();
|
||||
});
|
||||
|
||||
_mapper = new Mapper(config);
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ public class CreditProgramAdapter : ICreditProgramAdapter
|
||||
{
|
||||
_logger.LogError(ex, "StorageException");
|
||||
return CreditProgramOperationResponse.InternalServerError(
|
||||
$"Error while working with data storage:{ex.InnerException!.Message}"
|
||||
$"Error while working with data storage:{ex.InnerException?.Message}"
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -86,7 +86,7 @@ public class CreditProgramAdapter : ICreditProgramAdapter
|
||||
{
|
||||
_logger.LogError(ex, "StorageException");
|
||||
return CreditProgramOperationResponse.InternalServerError(
|
||||
$"Error while working with data storage: {ex.InnerException!.Message}"
|
||||
$"Error while working with data storage: {ex.InnerException?.Message}"
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -122,7 +122,7 @@ public class CreditProgramAdapter : ICreditProgramAdapter
|
||||
{
|
||||
_logger.LogError(ex, "StorageException");
|
||||
return CreditProgramOperationResponse.BadRequest(
|
||||
$"Error while working with data storage: {ex.InnerException!.Message}"
|
||||
$"Error while working with data storage: {ex.InnerException?.Message}"
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -164,7 +164,7 @@ public class CreditProgramAdapter : ICreditProgramAdapter
|
||||
{
|
||||
_logger.LogError(ex, "StorageException");
|
||||
return CreditProgramOperationResponse.BadRequest(
|
||||
$"Error while working with data storage: {ex.InnerException!.Message}"
|
||||
$"Error while working with data storage: {ex.InnerException?.Message}"
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -195,7 +195,7 @@ public class CreditProgramAdapter : ICreditProgramAdapter
|
||||
{
|
||||
_logger.LogError(ex, "StorageException");
|
||||
return CreditProgramOperationResponse.InternalServerError(
|
||||
$"Error while working with data storage:{ex.InnerException!.Message}"
|
||||
$"Error while working with data storage:{ex.InnerException?.Message}"
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -226,7 +226,7 @@ public class CreditProgramAdapter : ICreditProgramAdapter
|
||||
{
|
||||
_logger.LogError(ex, "StorageException");
|
||||
return CreditProgramOperationResponse.InternalServerError(
|
||||
$"Error while working with data storage:{ex.InnerException!.Message}"
|
||||
$"Error while working with data storage:{ex.InnerException?.Message}"
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -23,10 +23,34 @@ public class DepositAdapter : IDepositAdapter
|
||||
_logger = logger;
|
||||
var config = new MapperConfiguration(cfg =>
|
||||
{
|
||||
cfg.CreateMap<DepositBindingModel, DepositDataModel>();
|
||||
cfg.CreateMap<DepositDataModel, DepositViewModel>();
|
||||
cfg.CreateMap<DepositCurrencyBindingModel, DepositCurrencyDataModel>();
|
||||
// DepositBindingModel -> DepositDataModel
|
||||
cfg.CreateMap<DepositBindingModel, DepositDataModel>()
|
||||
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id ?? string.Empty))
|
||||
.ForMember(dest => dest.InterestRate, opt => opt.MapFrom(src => src.InterestRate))
|
||||
.ForMember(dest => dest.Cost, opt => opt.MapFrom(src => src.Cost))
|
||||
.ForMember(dest => dest.Period, opt => opt.MapFrom(src => src.Period))
|
||||
.ForMember(dest => dest.ClerkId, opt => opt.MapFrom(src => src.ClerkId ?? string.Empty))
|
||||
.ForMember(dest => dest.Currencies, opt => opt.MapFrom(src => src.DepositCurrencies ?? new List<DepositCurrencyBindingModel>()));
|
||||
|
||||
// DepositDataModel -> DepositViewModel
|
||||
cfg.CreateMap<DepositDataModel, DepositViewModel>()
|
||||
.ForMember(dest => dest.DepositCurrencies, opt => opt.MapFrom(src => src.Currencies != null ? src.Currencies : new List<DepositCurrencyDataModel>()));
|
||||
|
||||
// DepositCurrencyBindingModel -> DepositCurrencyDataModel
|
||||
cfg.CreateMap<DepositCurrencyBindingModel, DepositCurrencyDataModel>()
|
||||
.ForMember(dest => dest.DepositId, opt => opt.MapFrom(src => src.DepositId ?? string.Empty))
|
||||
.ForMember(dest => dest.CurrencyId, opt => opt.MapFrom(src => src.CurrencyId ?? string.Empty));
|
||||
|
||||
// DepositCurrencyDataModel -> DepositCurrencyViewModel
|
||||
cfg.CreateMap<DepositCurrencyDataModel, DepositCurrencyViewModel>();
|
||||
|
||||
// DepositCurrencyViewModel -> DepositCurrencyBindingModel
|
||||
cfg.CreateMap<DepositCurrencyViewModel, DepositCurrencyBindingModel>();
|
||||
|
||||
// Явный маппинг DepositCurrencyDataModel -> DepositCurrencyBindingModel
|
||||
cfg.CreateMap<DepositCurrencyDataModel, DepositCurrencyBindingModel>()
|
||||
.ForMember(dest => dest.DepositId, opt => opt.MapFrom(src => src.DepositId))
|
||||
.ForMember(dest => dest.CurrencyId, opt => opt.MapFrom(src => src.CurrencyId));
|
||||
});
|
||||
_mapper = new Mapper(config);
|
||||
}
|
||||
@@ -117,7 +141,7 @@ public class DepositAdapter : IDepositAdapter
|
||||
{
|
||||
_logger.LogError(ex, "StorageException");
|
||||
return DepositOperationResponse.BadRequest(
|
||||
$"Error while working with data storage: {ex.InnerException!.Message}"
|
||||
$"Error while working with data storage: {ex.InnerException?.Message}"
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -21,9 +21,17 @@ public class PeriodAdapter : IPeriodAdapter
|
||||
{
|
||||
_periodBusinessLogicContract = periodBusinessLogicContract;
|
||||
_logger = logger;
|
||||
var config = new MapperConfiguration(cfg =>
|
||||
var config = new MapperConfiguration(cfg =>
|
||||
{
|
||||
cfg.CreateMap<PeriodBindingModel, PeriodDataModel>();
|
||||
cfg.CreateMap<PeriodBindingModel, PeriodDataModel>()
|
||||
.ConstructUsing(src => new PeriodDataModel(
|
||||
src.Id,
|
||||
src.StartTime,
|
||||
src.EndTime,
|
||||
src.StorekeeperId
|
||||
));
|
||||
|
||||
// Маппинг PeriodDataModel -> PeriodViewModel
|
||||
cfg.CreateMap<PeriodDataModel, PeriodViewModel>();
|
||||
});
|
||||
_mapper = new Mapper(config);
|
||||
|
||||
@@ -7,6 +7,8 @@ using BankContracts.BusinessLogicContracts;
|
||||
using BankContracts.DataModels;
|
||||
using BankContracts.Exceptions;
|
||||
using BankContracts.ViewModels;
|
||||
using BankWebApi.Infrastructure;
|
||||
using System.Buffers;
|
||||
|
||||
namespace BankWebApi.Adapters;
|
||||
|
||||
@@ -14,11 +16,13 @@ public class StorekeeperAdapter : IStorekeeperAdapter
|
||||
{
|
||||
private readonly IStorekeeperBusinessLogicContract _storekeeperBusinessLogicContract;
|
||||
|
||||
private readonly IJwtProvider _jwtProvider;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly Mapper _mapper;
|
||||
|
||||
public StorekeeperAdapter(IStorekeeperBusinessLogicContract storekeeperBusinessLogicContract, ILogger logger)
|
||||
public StorekeeperAdapter(IStorekeeperBusinessLogicContract storekeeperBusinessLogicContract, IJwtProvider jwtProvider, ILogger logger)
|
||||
{
|
||||
_storekeeperBusinessLogicContract = storekeeperBusinessLogicContract;
|
||||
_logger = logger;
|
||||
@@ -28,6 +32,7 @@ public class StorekeeperAdapter : IStorekeeperAdapter
|
||||
cfg.CreateMap<StorekeeperDataModel, StorekeeperViewModel>();
|
||||
});
|
||||
_mapper = new Mapper(config);
|
||||
_jwtProvider = jwtProvider;
|
||||
}
|
||||
|
||||
public StorekeeperOperationResponse GetList()
|
||||
@@ -119,7 +124,7 @@ public class StorekeeperAdapter : IStorekeeperAdapter
|
||||
{
|
||||
_logger.LogError(ex, "StorageException");
|
||||
return StorekeeperOperationResponse.BadRequest(
|
||||
$"Error while working with data storage: {ex.InnerException!.Message}"
|
||||
$"Error while working with data storage: {ex.InnerException?.Message}"
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -170,5 +175,31 @@ public class StorekeeperAdapter : IStorekeeperAdapter
|
||||
_logger.LogError(ex, "Exception");
|
||||
return StorekeeperOperationResponse.InternalServerError(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public StorekeeperOperationResponse Login(LoginBindingModel storekeeperAuth, out string token)
|
||||
{
|
||||
token = string.Empty;
|
||||
try
|
||||
{
|
||||
var storekeeper = _storekeeperBusinessLogicContract.GetStorekeeperByData(storekeeperAuth.Login);
|
||||
|
||||
|
||||
var result = storekeeperAuth.Password == storekeeper.Password;
|
||||
|
||||
if (!result)
|
||||
{
|
||||
return StorekeeperOperationResponse.Unauthorized("Password are incorrect");
|
||||
}
|
||||
|
||||
token = _jwtProvider.GenerateToken(storekeeper);
|
||||
|
||||
return StorekeeperOperationResponse.OK(_mapper.Map<StorekeeperViewModel>(storekeeper));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Exception in Login");
|
||||
return StorekeeperOperationResponse.InternalServerError($"Exception in Login {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace BankWebApi;
|
||||
/// </summary>
|
||||
public class AuthOptions
|
||||
{
|
||||
public const string CookieName = "bank";
|
||||
public const string ISSUER = "Bank_AuthServer"; // издатель токена
|
||||
public const string AUDIENCE = "Bank_AuthClient"; // потребитель токена
|
||||
const string KEY = "banksuperpupersecret_secretsecretsecretkey!"; // ключ для шифрации
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.4" />
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using BankContracts.BindingModels;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace BankWebApi.Controllers;
|
||||
|
||||
@@ -39,7 +40,8 @@ public class ClerksController(IClerkAdapter adapter) : ControllerBase
|
||||
/// </summary>
|
||||
/// <param name="model">модель от пользователя</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[HttpPost("register")]
|
||||
[AllowAnonymous]
|
||||
public IActionResult Register([FromBody] ClerkBindingModel model)
|
||||
{
|
||||
return _adapter.RegisterClerk(model).GetResponse(Request, Response);
|
||||
@@ -55,4 +57,58 @@ public class ClerksController(IClerkAdapter adapter) : ControllerBase
|
||||
{
|
||||
return _adapter.ChangeClerkInfo(model).GetResponse(Request, Response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// вход для клерка
|
||||
/// </summary>
|
||||
/// <param name="model">модель с логином и паролем</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("login")]
|
||||
[AllowAnonymous]
|
||||
public IActionResult Login([FromBody] LoginBindingModel model)
|
||||
{
|
||||
var res = _adapter.Login(model, out string token);
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
return res.GetResponse(Request, Response);
|
||||
}
|
||||
|
||||
Response.Cookies.Append(AuthOptions.CookieName, token, new CookieOptions
|
||||
{
|
||||
HttpOnly = true,
|
||||
SameSite = SameSiteMode.None,
|
||||
Secure = true,
|
||||
Expires = DateTime.UtcNow.AddDays(2)
|
||||
});
|
||||
|
||||
return res.GetResponse(Request, Response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение данных текущего клерка
|
||||
/// </summary>
|
||||
/// <returns>Данные кладовщика</returns>
|
||||
[HttpGet("me")]
|
||||
public IActionResult GetCurrentUser()
|
||||
{
|
||||
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var response = _adapter.GetElement(userId);
|
||||
return response.GetResponse(Request, Response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Выход клерка
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost("logout")]
|
||||
public IActionResult Logout()
|
||||
{
|
||||
Response.Cookies.Delete(AuthOptions.CookieName);
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using BankContracts.AdapterContracts;
|
||||
using BankContracts.BindingModels;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BankWebApi.Controllers;
|
||||
@@ -10,7 +9,7 @@ namespace BankWebApi.Controllers;
|
||||
[Route("api/[controller]/[action]")]
|
||||
[ApiController]
|
||||
[Produces("application/json")]
|
||||
public class PeriodController(IPeriodAdapter adapter) : ControllerBase
|
||||
public class PeriodsController(IPeriodAdapter adapter) : ControllerBase
|
||||
{
|
||||
private readonly IPeriodAdapter _adapter = adapter;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using BankBusinessLogic.Implementations;
|
||||
using BankContracts.AdapterContracts;
|
||||
using BankContracts.BindingModels;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
@@ -8,16 +9,10 @@ namespace BankWebApi.Controllers;
|
||||
[Authorize]
|
||||
[Route("api/[controller]/[action]")]
|
||||
[ApiController]
|
||||
public class ReportController : ControllerBase
|
||||
public class ReportController(IReportAdapter adapter) : ControllerBase
|
||||
{
|
||||
private readonly IReportAdapter _adapter;
|
||||
private readonly EmailService _emailService;
|
||||
|
||||
public ReportController(IReportAdapter adapter)
|
||||
{
|
||||
_adapter = adapter;
|
||||
_emailService = EmailService.CreateYandexService();
|
||||
}
|
||||
private readonly IReportAdapter _adapter = adapter;
|
||||
private readonly EmailService _emailService = EmailService.CreateYandexService();
|
||||
|
||||
[HttpGet]
|
||||
[Consumes("application/json")]
|
||||
@@ -84,8 +79,7 @@ public class ReportController : ControllerBase
|
||||
[Consumes("application/json")]
|
||||
public async Task<IActionResult> GetDepositAndCreditProgramByCurrency(DateTime fromDate, DateTime toDate, CancellationToken cancellationToken)
|
||||
{
|
||||
return (await _adapter.GetDataDepositAndCreditProgramByCurrencyAsync(fromDate, toDate,
|
||||
cancellationToken)).GetResponse(Request, Response);
|
||||
return (await _adapter.GetDataDepositAndCreditProgramByCurrencyAsync(fromDate, toDate, cancellationToken)).GetResponse(Request, Response);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
@@ -96,33 +90,28 @@ public class ReportController : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> SendReportByCreditProgram(string email, CancellationToken ct)
|
||||
public async Task<IActionResult> SendReportByCreditProgram([FromBody] MailSendInfoBindingModel model, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var report = await _adapter.CreateDocumentClientsByCreditProgramAsync(ct);
|
||||
var response = report.GetResponse(Request, Response);
|
||||
|
||||
if (response is FileStreamResult fileResult)
|
||||
var stream = report.GetStream();
|
||||
var fileName = report.GetFileName() ?? "report.docx";
|
||||
if (stream == null)
|
||||
return BadRequest("Не удалось сформировать отчет");
|
||||
var tempPath = Path.Combine(Path.GetTempPath(), fileName);
|
||||
using (var fileStream = new FileStream(tempPath, FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
var tempPath = Path.GetTempFileName();
|
||||
using (var fileStream = new FileStream(tempPath, FileMode.Create))
|
||||
{
|
||||
await fileResult.FileStream.CopyToAsync(fileStream);
|
||||
}
|
||||
|
||||
await _emailService.SendReportAsync(
|
||||
toEmail: email,
|
||||
subject: "Отчет по клиентам по кредитным программам",
|
||||
body: "<h1>Отчет по клиентам по кредитным программам</h1><p>В приложении находится отчет по клиентам по кредитным программам.</p>",
|
||||
attachmentPath: tempPath
|
||||
);
|
||||
|
||||
System.IO.File.Delete(tempPath);
|
||||
return Ok("Отчет успешно отправлен на почту");
|
||||
await stream.CopyToAsync(fileStream);
|
||||
}
|
||||
|
||||
return BadRequest("Не удалось получить отчет");
|
||||
await _emailService.SendReportAsync(
|
||||
toEmail: model.ToEmail,
|
||||
subject: model.Subject,
|
||||
body: model.Body,
|
||||
attachmentPath: tempPath
|
||||
);
|
||||
System.IO.File.Delete(tempPath);
|
||||
return Ok("Отчет успешно отправлен на почту");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -131,33 +120,28 @@ public class ReportController : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> SendReportByDeposit(string email, DateTime fromDate, DateTime toDate, CancellationToken ct)
|
||||
public async Task<IActionResult> SendReportByDeposit([FromBody] MailSendInfoBindingModel model, DateTime fromDate, DateTime toDate, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var report = await _adapter.CreateDocumentClientsByDepositAsync(fromDate, toDate, ct);
|
||||
var response = report.GetResponse(Request, Response);
|
||||
|
||||
if (response is FileStreamResult fileResult)
|
||||
var stream = report.GetStream();
|
||||
var fileName = report.GetFileName() ?? "report.pdf";
|
||||
if (stream == null)
|
||||
return BadRequest("Не удалось сформировать отчет");
|
||||
var tempPath = Path.Combine(Path.GetTempPath(), fileName);
|
||||
using (var fileStream = new FileStream(tempPath, FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
var tempPath = Path.GetTempFileName();
|
||||
using (var fileStream = new FileStream(tempPath, FileMode.Create))
|
||||
{
|
||||
await fileResult.FileStream.CopyToAsync(fileStream);
|
||||
}
|
||||
|
||||
await _emailService.SendReportAsync(
|
||||
toEmail: email,
|
||||
subject: "Отчет по клиентам по вкладам",
|
||||
body: $"<h1>Отчет по клиентам по вкладам</h1><p>Отчет за период с {fromDate:dd.MM.yyyy} по {toDate:dd.MM.yyyy}</p>",
|
||||
attachmentPath: tempPath
|
||||
);
|
||||
|
||||
System.IO.File.Delete(tempPath);
|
||||
return Ok("Отчет успешно отправлен на почту");
|
||||
await stream.CopyToAsync(fileStream);
|
||||
}
|
||||
|
||||
return BadRequest("Не удалось получить отчет");
|
||||
await _emailService.SendReportAsync(
|
||||
toEmail: model.ToEmail,
|
||||
subject: model.Subject,
|
||||
body: model.Body,
|
||||
attachmentPath: tempPath
|
||||
);
|
||||
System.IO.File.Delete(tempPath);
|
||||
return Ok("Отчет успешно отправлен на почту");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -166,33 +150,28 @@ public class ReportController : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> SendReportByCurrency(string email, DateTime fromDate, DateTime toDate, CancellationToken ct)
|
||||
public async Task<IActionResult> SendReportByCurrency([FromBody] MailSendInfoBindingModel model, DateTime fromDate, DateTime toDate, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var report = await _adapter.CreateDocumentDepositAndCreditProgramByCurrencyAsync(fromDate, toDate, ct);
|
||||
var response = report.GetResponse(Request, Response);
|
||||
|
||||
if (response is FileStreamResult fileResult)
|
||||
var stream = report.GetStream();
|
||||
var fileName = report.GetFileName() ?? "report.pdf";
|
||||
if (stream == null)
|
||||
return BadRequest("Не удалось сформировать отчет");
|
||||
var tempPath = Path.Combine(Path.GetTempPath(), fileName);
|
||||
using (var fileStream = new FileStream(tempPath, FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
var tempPath = Path.GetTempFileName();
|
||||
using (var fileStream = new FileStream(tempPath, FileMode.Create))
|
||||
{
|
||||
await fileResult.FileStream.CopyToAsync(fileStream);
|
||||
}
|
||||
|
||||
await _emailService.SendReportAsync(
|
||||
toEmail: email,
|
||||
subject: "Отчет по вкладам и кредитным программам по валютам",
|
||||
body: $"<h1>Отчет по вкладам и кредитным программам по валютам</h1><p>Отчет за период с {fromDate:dd.MM.yyyy} по {toDate:dd.MM.yyyy}</p>",
|
||||
attachmentPath: tempPath
|
||||
);
|
||||
|
||||
System.IO.File.Delete(tempPath);
|
||||
return Ok("Отчет успешно отправлен на почту");
|
||||
await stream.CopyToAsync(fileStream);
|
||||
}
|
||||
|
||||
return BadRequest("Не удалось получить отчет");
|
||||
await _emailService.SendReportAsync(
|
||||
toEmail: model.ToEmail,
|
||||
subject: model.Subject,
|
||||
body: model.Body,
|
||||
attachmentPath: tempPath
|
||||
);
|
||||
System.IO.File.Delete(tempPath);
|
||||
return Ok("Отчет успешно отправлен на почту");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -201,33 +180,28 @@ public class ReportController : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> SendExcelReportByCreditProgram(string email, CancellationToken ct)
|
||||
public async Task<IActionResult> SendExcelReportByCreditProgram([FromBody] MailSendInfoBindingModel model, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var report = await _adapter.CreateExcelDocumentClientsByCreditProgramAsync(ct);
|
||||
var response = report.GetResponse(Request, Response);
|
||||
|
||||
if (response is FileStreamResult fileResult)
|
||||
var stream = report.GetStream();
|
||||
var fileName = report.GetFileName() ?? "report.xlsx";
|
||||
if (stream == null)
|
||||
return BadRequest("Не удалось сформировать отчет");
|
||||
var tempPath = Path.Combine(Path.GetTempPath(), fileName);
|
||||
using (var fileStream = new FileStream(tempPath, FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
var tempPath = Path.GetTempFileName();
|
||||
using (var fileStream = new FileStream(tempPath, FileMode.Create))
|
||||
{
|
||||
await fileResult.FileStream.CopyToAsync(fileStream);
|
||||
}
|
||||
|
||||
await _emailService.SendReportAsync(
|
||||
toEmail: email,
|
||||
subject: "Excel отчет по клиентам по кредитным программам",
|
||||
body: "<h1>Excel отчет по клиентам по кредитным программам</h1><p>В приложении находится Excel отчет по клиентам по кредитным программам.</p>",
|
||||
attachmentPath: tempPath
|
||||
);
|
||||
|
||||
System.IO.File.Delete(tempPath);
|
||||
return Ok("Excel отчет успешно отправлен на почту");
|
||||
await stream.CopyToAsync(fileStream);
|
||||
}
|
||||
|
||||
return BadRequest("Не удалось получить отчет");
|
||||
await _emailService.SendReportAsync(
|
||||
toEmail: model.ToEmail,
|
||||
subject: model.Subject,
|
||||
body: model.Body,
|
||||
attachmentPath: tempPath
|
||||
);
|
||||
System.IO.File.Delete(tempPath);
|
||||
return Ok("Excel отчет успешно отправлен на почту");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -236,33 +210,28 @@ public class ReportController : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> SendReportDepositByCreditProgram(string email, CancellationToken ct)
|
||||
public async Task<IActionResult> SendReportDepositByCreditProgram([FromBody] MailSendInfoBindingModel model, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var report = await _adapter.CreateDocumentDepositByCreditProgramAsync(ct);
|
||||
var response = report.GetResponse(Request, Response);
|
||||
|
||||
if (response is FileStreamResult fileResult)
|
||||
var stream = report.GetStream();
|
||||
var fileName = report.GetFileName() ?? "report.docx";
|
||||
if (stream == null)
|
||||
return BadRequest("Не удалось сформировать отчет");
|
||||
var tempPath = Path.Combine(Path.GetTempPath(), fileName);
|
||||
using (var fileStream = new FileStream(tempPath, FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
var tempPath = Path.GetTempFileName();
|
||||
using (var fileStream = new FileStream(tempPath, FileMode.Create))
|
||||
{
|
||||
await fileResult.FileStream.CopyToAsync(fileStream);
|
||||
}
|
||||
|
||||
await _emailService.SendReportAsync(
|
||||
toEmail: email,
|
||||
subject: "Отчет по вкладам по кредитным программам",
|
||||
body: "<h1>Отчет по вкладам по кредитным программам</h1><p>В приложении находится отчет по вкладам по кредитным программам.</p>",
|
||||
attachmentPath: tempPath
|
||||
);
|
||||
|
||||
System.IO.File.Delete(tempPath);
|
||||
return Ok("Отчет успешно отправлен на почту");
|
||||
await stream.CopyToAsync(fileStream);
|
||||
}
|
||||
|
||||
return BadRequest("Не удалось получить отчет");
|
||||
await _emailService.SendReportAsync(
|
||||
toEmail: model.ToEmail,
|
||||
subject: model.Subject,
|
||||
body: model.Body,
|
||||
attachmentPath: tempPath
|
||||
);
|
||||
System.IO.File.Delete(tempPath);
|
||||
return Ok("Отчет успешно отправлен на почту");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -271,33 +240,28 @@ public class ReportController : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> SendExcelReportDepositByCreditProgram(string email, CancellationToken ct)
|
||||
public async Task<IActionResult> SendExcelReportDepositByCreditProgram([FromBody] MailSendInfoBindingModel model, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var report = await _adapter.CreateExcelDocumentDepositByCreditProgramAsync(ct);
|
||||
var response = report.GetResponse(Request, Response);
|
||||
|
||||
if (response is FileStreamResult fileResult)
|
||||
var stream = report.GetStream();
|
||||
var fileName = report.GetFileName() ?? "report.xlsx";
|
||||
if (stream == null)
|
||||
return BadRequest("Не удалось сформировать отчет");
|
||||
var tempPath = Path.Combine(Path.GetTempPath(), fileName);
|
||||
using (var fileStream = new FileStream(tempPath, FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
var tempPath = Path.GetTempFileName();
|
||||
using (var fileStream = new FileStream(tempPath, FileMode.Create))
|
||||
{
|
||||
await fileResult.FileStream.CopyToAsync(fileStream);
|
||||
}
|
||||
|
||||
await _emailService.SendReportAsync(
|
||||
toEmail: email,
|
||||
subject: "Excel отчет по вкладам по кредитным программам",
|
||||
body: "<h1>Excel отчет по вкладам по кредитным программам</h1><p>В приложении находится Excel отчет по вкладам по кредитным программам.</p>",
|
||||
attachmentPath: tempPath
|
||||
);
|
||||
|
||||
System.IO.File.Delete(tempPath);
|
||||
return Ok("Excel отчет успешно отправлен на почту");
|
||||
await stream.CopyToAsync(fileStream);
|
||||
}
|
||||
|
||||
return BadRequest("Не удалось получить отчет");
|
||||
await _emailService.SendReportAsync(
|
||||
toEmail: model.ToEmail,
|
||||
subject: model.Subject,
|
||||
body: model.Body,
|
||||
attachmentPath: tempPath
|
||||
);
|
||||
System.IO.File.Delete(tempPath);
|
||||
return Ok("Excel отчет успешно отправлен на почту");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using BankContracts.BindingModels;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace BankWebApi.Controllers;
|
||||
|
||||
@@ -39,7 +40,8 @@ public class StorekeepersController(IStorekeeperAdapter adapter) : ControllerBas
|
||||
/// </summary>
|
||||
/// <param name="model">модель от пользователя</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[HttpPost("register")]
|
||||
[AllowAnonymous]
|
||||
public IActionResult Register([FromBody] StorekeeperBindingModel model)
|
||||
{
|
||||
return _adapter.RegisterStorekeeper(model).GetResponse(Request, Response);
|
||||
@@ -55,4 +57,58 @@ public class StorekeepersController(IStorekeeperAdapter adapter) : ControllerBas
|
||||
{
|
||||
return _adapter.ChangeStorekeeperInfo(model).GetResponse(Request, Response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// вход для кладовщика
|
||||
/// </summary>
|
||||
/// <param name="model">модель с логином и паролем</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("login")]
|
||||
[AllowAnonymous]
|
||||
public IActionResult Login([FromBody] LoginBindingModel model)
|
||||
{
|
||||
var res = _adapter.Login(model, out string token);
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
return res.GetResponse(Request, Response);
|
||||
}
|
||||
|
||||
Response.Cookies.Append(AuthOptions.CookieName, token, new CookieOptions
|
||||
{
|
||||
HttpOnly = true,
|
||||
SameSite = SameSiteMode.None,
|
||||
Secure = true,
|
||||
Expires = DateTime.UtcNow.AddDays(2)
|
||||
});
|
||||
|
||||
return res.GetResponse(Request, Response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение данных текущего кладовщика
|
||||
/// </summary>
|
||||
/// <returns>Данные кладовщика</returns>
|
||||
[HttpGet("me")]
|
||||
public IActionResult GetCurrentUser()
|
||||
{
|
||||
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var response = _adapter.GetElement(userId);
|
||||
return response.GetResponse(Request, Response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Выход кладовщика
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost("logout")]
|
||||
public IActionResult Logout()
|
||||
{
|
||||
Response.Cookies.Delete(AuthOptions.CookieName);
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
||||
10
TheBank/BankWebApi/Infrastructure/IJwtProvider.cs
Normal file
10
TheBank/BankWebApi/Infrastructure/IJwtProvider.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using BankContracts.DataModels;
|
||||
|
||||
namespace BankWebApi.Infrastructure;
|
||||
|
||||
public interface IJwtProvider
|
||||
{
|
||||
string GenerateToken(StorekeeperDataModel dataModel);
|
||||
|
||||
string GenerateToken(ClerkDataModel dataModel);
|
||||
}
|
||||
31
TheBank/BankWebApi/Infrastructure/JwtProvider.cs
Normal file
31
TheBank/BankWebApi/Infrastructure/JwtProvider.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using BankContracts.DataModels;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace BankWebApi.Infrastructure;
|
||||
|
||||
public class JwtProvider : IJwtProvider
|
||||
{
|
||||
public string GenerateToken(StorekeeperDataModel dataModel)
|
||||
{
|
||||
return GenerateToken(dataModel.Id);
|
||||
}
|
||||
|
||||
public string GenerateToken(ClerkDataModel dataModel)
|
||||
{
|
||||
return GenerateToken(dataModel.Id);
|
||||
}
|
||||
|
||||
private static string GenerateToken(string id)
|
||||
{
|
||||
var token = new JwtSecurityToken(
|
||||
issuer: AuthOptions.ISSUER,
|
||||
audience: AuthOptions.AUDIENCE,
|
||||
claims: [new(ClaimTypes.NameIdentifier, id)],
|
||||
expires: DateTime.UtcNow.Add(TimeSpan.FromDays(2)),
|
||||
signingCredentials: new SigningCredentials(AuthOptions.GetSymmetricSecurityKey(), SecurityAlgorithms.HmacSha256));
|
||||
|
||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||
}
|
||||
}
|
||||
10
TheBank/BankWebApi/Infrastructure/PasswordHelper.cs
Normal file
10
TheBank/BankWebApi/Infrastructure/PasswordHelper.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace BankWebApi.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// да пох на это
|
||||
/// </summary>
|
||||
public class PasswordHelper
|
||||
{
|
||||
public static string HashPassword(string password) => BCrypt.Net.BCrypt.HashPassword(password);
|
||||
public static bool VerifyPassword(string password, string hash) => BCrypt.Net.BCrypt.Verify(password, hash);
|
||||
}
|
||||
@@ -29,12 +29,11 @@ builder.Services.AddSwaggerGen(c =>
|
||||
{
|
||||
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Bank API", Version = "v1" });
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> XML-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD>)
|
||||
// Включение XML-комментариев (если они есть)
|
||||
var xmlFile = $"{System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
||||
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
|
||||
c.IncludeXmlComments(xmlPath, includeControllerXmlComments: true);
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> JWT-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> Swagger UI
|
||||
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
||||
{
|
||||
Description = "JWT Authorization header using the Bearer scheme. Example: 'Bearer {token}'",
|
||||
@@ -79,13 +78,33 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
IssuerSigningKey = AuthOptions.GetSymmetricSecurityKey(),
|
||||
|
||||
ValidateIssuerSigningKey = true,
|
||||
|
||||
};
|
||||
options.Events = new JwtBearerEvents
|
||||
{
|
||||
OnMessageReceived = context =>
|
||||
{
|
||||
context.Token = context.Request.Cookies[AuthOptions.CookieName];
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("AllowFrontend", policy =>
|
||||
{
|
||||
policy.WithOrigins("http://localhost:26312", "http://localhost:3654")
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.AllowCredentials();
|
||||
});
|
||||
});
|
||||
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
||||
builder.Services.AddOpenApi();
|
||||
builder.Services.AddSingleton<IConfigurationDatabase, ConfigurationDatabase>();
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
builder.Services.AddSingleton<IConfigurationDatabase, BankWebApi.Infrastructure.ConfigurationDatabase>();
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
builder.Services.AddTransient<IClerkBusinessLogicContract, ClerkBusinessLogicContract>();
|
||||
builder.Services.AddTransient<IPeriodBusinessLogicContract, PeriodBusinessLogicContract>();
|
||||
builder.Services.AddTransient<IDepositBusinessLogicContract, DepositBusinessLogicContract>();
|
||||
@@ -94,7 +113,7 @@ builder.Services.AddTransient<ICreditProgramBusinessLogicContract, CreditProgram
|
||||
builder.Services.AddTransient<ICurrencyBusinessLogicContract, CurrencyBusinessLogicContract>();
|
||||
builder.Services.AddTransient<IStorekeeperBusinessLogicContract, StorekeeperBusinessLogicContract>();
|
||||
builder.Services.AddTransient<IReplenishmentBusinessLogicContract, ReplenishmentBusinessLogicContract>();
|
||||
// <20><>
|
||||
// <20><>
|
||||
builder.Services.AddTransient<BankDbContext>();
|
||||
builder.Services.AddTransient<IClerkStorageContract, ClerkStorageContract>();
|
||||
builder.Services.AddTransient<IPeriodStorageContract, PeriodStorageContract>();
|
||||
@@ -104,7 +123,7 @@ builder.Services.AddTransient<ICreditProgramStorageContract, CreditProgramStorag
|
||||
builder.Services.AddTransient<ICurrencyStorageContract, CurrencyStorageContract>();
|
||||
builder.Services.AddTransient<IStorekeeperStorageContract, StorekeeperStorageContract>();
|
||||
builder.Services.AddTransient<IReplenishmentStorageContract, ReplenishmentStorageContract>();
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
builder.Services.AddTransient<IClerkAdapter, ClerkAdapter>();
|
||||
builder.Services.AddTransient<IPeriodAdapter, PeriodAdapter>();
|
||||
builder.Services.AddTransient<IDepositAdapter, DepositAdapter>();
|
||||
@@ -118,6 +137,8 @@ builder.Services.AddTransient<IReportAdapter, ReportAdapter>();
|
||||
builder.Services.AddTransient<BaseWordBuilder, OpenXmlWordBuilder>();
|
||||
builder.Services.AddTransient<BaseExcelBuilder, OpenXmlExcelBuilder>();
|
||||
builder.Services.AddTransient<BasePdfBuilder, MigraDocPdfBuilder>();
|
||||
// shit
|
||||
builder.Services.AddTransient<IJwtProvider, JwtProvider>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
@@ -129,7 +150,7 @@ if (app.Environment.IsDevelopment())
|
||||
app.UseSwaggerUI(c =>
|
||||
{
|
||||
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Bank API V1");
|
||||
c.RoutePrefix = "swagger"; // Swagger UI <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> /swagger
|
||||
c.RoutePrefix = "swagger"; // Swagger UI <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> /swagger
|
||||
});
|
||||
}
|
||||
if (app.Environment.IsProduction())
|
||||
@@ -141,11 +162,19 @@ if (app.Environment.IsProduction())
|
||||
dbContext.Database.Migrate();
|
||||
}
|
||||
}
|
||||
|
||||
app.UseCors("AllowFrontend");
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseCookiePolicy(new CookiePolicyOptions
|
||||
{
|
||||
HttpOnly = Microsoft.AspNetCore.CookiePolicy.HttpOnlyPolicy.Always,
|
||||
Secure = app.Environment.IsProduction() ? CookieSecurePolicy.Always : CookieSecurePolicy.None
|
||||
});
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
// это для тестов
|
||||
app.Map("/login/{username}", (string username) =>
|
||||
{
|
||||
return new JwtSecurityTokenHandler().WriteToken(new JwtSecurityToken(
|
||||
|
||||
@@ -13,6 +13,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BankTests", "BankTests\Bank
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BankWebApi", "BankWebApi\BankWebApi.csproj", "{C8A3A3BB-A096-429F-A763-5465C5CB735F}"
|
||||
EndProject
|
||||
Project("{54A90642-561A-4BB1-A94E-469ADEE60C69}") = "bankui", "BankUI\bankui.esproj", "{12D5DDAD-F24A-41B0-9FBC-BEFBFB01067D}"
|
||||
EndProject
|
||||
Project("{54A90642-561A-4BB1-A94E-469ADEE60C69}") = "bankuiclerk", "BankUIClerk\bankuiclerk.esproj", "{FD37483B-1D73-44B8-9D8B-38FE8F039E48}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -39,6 +43,18 @@ Global
|
||||
{C8A3A3BB-A096-429F-A763-5465C5CB735F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C8A3A3BB-A096-429F-A763-5465C5CB735F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C8A3A3BB-A096-429F-A763-5465C5CB735F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{12D5DDAD-F24A-41B0-9FBC-BEFBFB01067D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{12D5DDAD-F24A-41B0-9FBC-BEFBFB01067D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{12D5DDAD-F24A-41B0-9FBC-BEFBFB01067D}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||
{12D5DDAD-F24A-41B0-9FBC-BEFBFB01067D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{12D5DDAD-F24A-41B0-9FBC-BEFBFB01067D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{12D5DDAD-F24A-41B0-9FBC-BEFBFB01067D}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||
{FD37483B-1D73-44B8-9D8B-38FE8F039E48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FD37483B-1D73-44B8-9D8B-38FE8F039E48}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FD37483B-1D73-44B8-9D8B-38FE8F039E48}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||
{FD37483B-1D73-44B8-9D8B-38FE8F039E48}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FD37483B-1D73-44B8-9D8B-38FE8F039E48}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FD37483B-1D73-44B8-9D8B-38FE8F039E48}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
2
TheBank/bankui/.env
Normal file
2
TheBank/bankui/.env
Normal file
@@ -0,0 +1,2 @@
|
||||
VITE_API_URL=https://localhost:7204
|
||||
# VITE_API_URL=http://localhost:5189
|
||||
24
TheBank/bankui/.gitignore
vendored
Normal file
24
TheBank/bankui/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
7
TheBank/bankui/.prettierrc
Normal file
7
TheBank/bankui/.prettierrc
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"semi": true,
|
||||
"jsxSingleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
12
TheBank/bankui/CHANGELOG.md
Normal file
12
TheBank/bankui/CHANGELOG.md
Normal file
@@ -0,0 +1,12 @@
|
||||
This file explains how Visual Studio created the project.
|
||||
|
||||
The following tools were used to generate this project:
|
||||
- create-vite
|
||||
|
||||
The following steps were used to generate this project:
|
||||
- Create react project with create-vite: `npm init --yes vite@latest bankui -- --template=react-ts`.
|
||||
- Updating vite.config.ts with port.
|
||||
- Create project file (`bankui.esproj`).
|
||||
- Create `launch.json` to enable debugging.
|
||||
- Add project to solution.
|
||||
- Write this file.
|
||||
54
TheBank/bankui/README.md
Normal file
54
TheBank/bankui/README.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# React + TypeScript + Vite
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
|
||||
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
||||
|
||||
```js
|
||||
export default tseslint.config({
|
||||
extends: [
|
||||
// Remove ...tseslint.configs.recommended and replace with this
|
||||
...tseslint.configs.recommendedTypeChecked,
|
||||
// Alternatively, use this for stricter rules
|
||||
...tseslint.configs.strictTypeChecked,
|
||||
// Optionally, add this for stylistic rules
|
||||
...tseslint.configs.stylisticTypeChecked,
|
||||
],
|
||||
languageOptions: {
|
||||
// other options...
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
||||
|
||||
```js
|
||||
// eslint.config.js
|
||||
import reactX from 'eslint-plugin-react-x'
|
||||
import reactDom from 'eslint-plugin-react-dom'
|
||||
|
||||
export default tseslint.config({
|
||||
plugins: {
|
||||
// Add the react-x and react-dom plugins
|
||||
'react-x': reactX,
|
||||
'react-dom': reactDom,
|
||||
},
|
||||
rules: {
|
||||
// other rules...
|
||||
// Enable its recommended typescript rules
|
||||
...reactX.configs['recommended-typescript'].rules,
|
||||
...reactDom.configs.recommended.rules,
|
||||
},
|
||||
})
|
||||
```
|
||||
11
TheBank/bankui/bankui.esproj
Normal file
11
TheBank/bankui/bankui.esproj
Normal file
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.VisualStudio.JavaScript.Sdk/1.0.1738743">
|
||||
<PropertyGroup>
|
||||
<StartupCommand>npm run dev</StartupCommand>
|
||||
<JavaScriptTestRoot>src\</JavaScriptTestRoot>
|
||||
<JavaScriptTestFramework>Jest</JavaScriptTestFramework>
|
||||
<!-- Allows the build (or compile) script located on package.json to run on Build -->
|
||||
<ShouldRunBuildScript>false</ShouldRunBuildScript>
|
||||
<!-- Folder where production build objects will be placed -->
|
||||
<BuildOutputFolder>$(MSBuildProjectDirectory)\dist</BuildOutputFolder>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
BIN
TheBank/bankui/bun.lockb
Normal file
BIN
TheBank/bankui/bun.lockb
Normal file
Binary file not shown.
21
TheBank/bankui/components.json
Normal file
21
TheBank/bankui/components.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "src/index.css",
|
||||
"baseColor": "slate",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
||||
28
TheBank/bankui/eslint.config.js
Normal file
28
TheBank/bankui/eslint.config.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ['dist'] },
|
||||
{
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
'react-refresh': reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...reactHooks.configs.recommended.rules,
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
)
|
||||
13
TheBank/bankui/index.html
Normal file
13
TheBank/bankui/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Шрек</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root" class="roboto"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
962
TheBank/bankui/package-lock.json
generated
962
TheBank/bankui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
61
TheBank/bankui/package.json
Normal file
61
TheBank/bankui/package.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"name": "bankui",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^5.0.1",
|
||||
"@radix-ui/react-avatar": "^1.1.9",
|
||||
"@radix-ui/react-checkbox": "^1.3.1",
|
||||
"@radix-ui/react-dialog": "^1.1.13",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.14",
|
||||
"@radix-ui/react-label": "^2.1.6",
|
||||
"@radix-ui/react-menubar": "^1.1.14",
|
||||
"@radix-ui/react-popover": "^1.1.13",
|
||||
"@radix-ui/react-select": "^2.2.4",
|
||||
"@radix-ui/react-separator": "^1.1.6",
|
||||
"@radix-ui/react-slot": "^1.2.2",
|
||||
"@radix-ui/react-tabs": "^1.1.11",
|
||||
"@radix-ui/react-tooltip": "^1.2.6",
|
||||
"@tailwindcss/vite": "^4.1.7",
|
||||
"@tanstack/react-query": "^5.76.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"lucide-react": "^0.511.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"pdfjs-dist": "^5.2.133",
|
||||
"react": "^19.1.0",
|
||||
"react-day-picker": "8.10.1",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-hook-form": "^7.56.4",
|
||||
"react-pdf": "^9.2.1",
|
||||
"react-router-dom": "^7.6.0",
|
||||
"sonner": "^2.0.3",
|
||||
"tailwind-merge": "^3.3.0",
|
||||
"tailwindcss": "^4.1.7",
|
||||
"tw-animate-css": "^1.3.0",
|
||||
"zod": "^3.24.4",
|
||||
"zustand": "^5.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.25.0",
|
||||
"@types/node": "^22.15.18",
|
||||
"@types/react": "^19.1.2",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
"eslint": "^9.25.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.19",
|
||||
"globals": "^16.0.0",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.30.1",
|
||||
"vite": "^6.3.5"
|
||||
}
|
||||
}
|
||||
BIN
TheBank/bankui/public/Shrek.png
Normal file
BIN
TheBank/bankui/public/Shrek.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
1
TheBank/bankui/public/icons/excel.svg
Normal file
1
TheBank/bankui/public/icons/excel.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#169154" d="M29,6H15.744C14.781,6,14,6.781,14,7.744v7.259h15V6z"/><path fill="#18482a" d="M14,33.054v7.202C14,41.219,14.781,42,15.743,42H29v-8.946H14z"/><path fill="#0c8045" d="M14 15.003H29V24.005000000000003H14z"/><path fill="#17472a" d="M14 24.005H29V33.055H14z"/><g><path fill="#29c27f" d="M42.256,6H29v9.003h15V7.744C44,6.781,43.219,6,42.256,6z"/><path fill="#27663f" d="M29,33.054V42h13.257C43.219,42,44,41.219,44,40.257v-7.202H29z"/><path fill="#19ac65" d="M29 15.003H44V24.005000000000003H29z"/><path fill="#129652" d="M29 24.005H44V33.055H29z"/></g><path fill="#0c7238" d="M22.319,34H5.681C4.753,34,4,33.247,4,32.319V15.681C4,14.753,4.753,14,5.681,14h16.638 C23.247,14,24,14.753,24,15.681v16.638C24,33.247,23.247,34,22.319,34z"/><path fill="#fff" d="M9.807 19L12.193 19 14.129 22.754 16.175 19 18.404 19 15.333 24 18.474 29 16.123 29 14.013 25.07 11.912 29 9.526 29 12.719 23.982z"/></svg>
|
||||
|
After Width: | Height: | Size: 998 B |
4
TheBank/bankui/public/icons/pdf.svg
Normal file
4
TheBank/bankui/public/icons/pdf.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="35px" height="35px" viewBox="-4 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M25.6686 26.0962C25.1812 26.2401 24.4656 26.2563 23.6984 26.145C22.875 26.0256 22.0351 25.7739 21.2096 25.403C22.6817 25.1888 23.8237 25.2548 24.8005 25.6009C25.0319 25.6829 25.412 25.9021 25.6686 26.0962ZM17.4552 24.7459C17.3953 24.7622 17.3363 24.7776 17.2776 24.7939C16.8815 24.9017 16.4961 25.0069 16.1247 25.1005L15.6239 25.2275C14.6165 25.4824 13.5865 25.7428 12.5692 26.0529C12.9558 25.1206 13.315 24.178 13.6667 23.2564C13.9271 22.5742 14.193 21.8773 14.468 21.1894C14.6075 21.4198 14.7531 21.6503 14.9046 21.8814C15.5948 22.9326 16.4624 23.9045 17.4552 24.7459ZM14.8927 14.2326C14.958 15.383 14.7098 16.4897 14.3457 17.5514C13.8972 16.2386 13.6882 14.7889 14.2489 13.6185C14.3927 13.3185 14.5105 13.1581 14.5869 13.0744C14.7049 13.2566 14.8601 13.6642 14.8927 14.2326ZM9.63347 28.8054C9.38148 29.2562 9.12426 29.6782 8.86063 30.0767C8.22442 31.0355 7.18393 32.0621 6.64941 32.0621C6.59681 32.0621 6.53316 32.0536 6.44015 31.9554C6.38028 31.8926 6.37069 31.8476 6.37359 31.7862C6.39161 31.4337 6.85867 30.8059 7.53527 30.2238C8.14939 29.6957 8.84352 29.2262 9.63347 28.8054ZM27.3706 26.1461C27.2889 24.9719 25.3123 24.2186 25.2928 24.2116C24.5287 23.9407 23.6986 23.8091 22.7552 23.8091C21.7453 23.8091 20.6565 23.9552 19.2582 24.2819C18.014 23.3999 16.9392 22.2957 16.1362 21.0733C15.7816 20.5332 15.4628 19.9941 15.1849 19.4675C15.8633 17.8454 16.4742 16.1013 16.3632 14.1479C16.2737 12.5816 15.5674 11.5295 14.6069 11.5295C13.948 11.5295 13.3807 12.0175 12.9194 12.9813C12.0965 14.6987 12.3128 16.8962 13.562 19.5184C13.1121 20.5751 12.6941 21.6706 12.2895 22.7311C11.7861 24.0498 11.2674 25.4103 10.6828 26.7045C9.04334 27.3532 7.69648 28.1399 6.57402 29.1057C5.8387 29.7373 4.95223 30.7028 4.90163 31.7107C4.87693 32.1854 5.03969 32.6207 5.37044 32.9695C5.72183 33.3398 6.16329 33.5348 6.6487 33.5354C8.25189 33.5354 9.79489 31.3327 10.0876 30.8909C10.6767 30.0029 11.2281 29.0124 11.7684 27.8699C13.1292 27.3781 14.5794 27.011 15.985 26.6562L16.4884 26.5283C16.8668 26.4321 17.2601 26.3257 17.6635 26.2153C18.0904 26.0999 18.5296 25.9802 18.976 25.8665C20.4193 26.7844 21.9714 27.3831 23.4851 27.6028C24.7601 27.7883 25.8924 27.6807 26.6589 27.2811C27.3486 26.9219 27.3866 26.3676 27.3706 26.1461ZM30.4755 36.2428C30.4755 38.3932 28.5802 38.5258 28.1978 38.5301H3.74486C1.60224 38.5301 1.47322 36.6218 1.46913 36.2428L1.46884 3.75642C1.46884 1.6039 3.36763 1.4734 3.74457 1.46908H20.263L20.2718 1.4778V7.92396C20.2718 9.21763 21.0539 11.6669 24.0158 11.6669H30.4203L30.4753 11.7218L30.4755 36.2428ZM28.9572 10.1976H24.0169C21.8749 10.1976 21.7453 8.29969 21.7424 7.92417V2.95307L28.9572 10.1976ZM31.9447 36.2428V11.1157L21.7424 0.871022V0.823357H21.6936L20.8742 0H3.74491C2.44954 0 0 0.785336 0 3.75711V36.2435C0 37.5427 0.782956 40 3.74491 40H28.2001C29.4952 39.9997 31.9447 39.2143 31.9447 36.2428Z" fill="#EB5757"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
1
TheBank/bankui/public/icons/word.svg
Normal file
1
TheBank/bankui/public/icons/word.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="50px" height="50px"><path d="M 28.8125 0.03125 L 0.8125 5.34375 C 0.339844 5.433594 0 5.863281 0 6.34375 L 0 43.65625 C 0 44.136719 0.339844 44.566406 0.8125 44.65625 L 28.8125 49.96875 C 28.875 49.980469 28.9375 50 29 50 C 29.230469 50 29.445313 49.929688 29.625 49.78125 C 29.855469 49.589844 30 49.296875 30 49 L 30 1 C 30 0.703125 29.855469 0.410156 29.625 0.21875 C 29.394531 0.0273438 29.105469 -0.0234375 28.8125 0.03125 Z M 32 6 L 32 13 L 44 13 L 44 15 L 32 15 L 32 20 L 44 20 L 44 22 L 32 22 L 32 27 L 44 27 L 44 29 L 32 29 L 32 35 L 44 35 L 44 37 L 32 37 L 32 44 L 47 44 C 48.101563 44 49 43.101563 49 42 L 49 8 C 49 6.898438 48.101563 6 47 6 Z M 4.625 15.65625 L 8.1875 15.65625 L 10.21875 28.09375 C 10.308594 28.621094 10.367188 29.355469 10.40625 30.25 L 10.46875 30.25 C 10.496094 29.582031 10.613281 28.855469 10.78125 28.0625 L 13.40625 15.65625 L 16.90625 15.65625 L 19.28125 28.21875 C 19.367188 28.679688 19.433594 29.339844 19.5 30.21875 L 19.53125 30.21875 C 19.558594 29.53125 19.632813 28.828125 19.75 28.125 L 21.75 15.65625 L 25.0625 15.65625 L 21.21875 34.34375 L 17.59375 34.34375 L 15.1875 22.375 C 15.058594 21.75 14.996094 21.023438 14.96875 20.25 L 14.9375 20.25 C 14.875 21.101563 14.769531 21.824219 14.65625 22.375 L 12.1875 34.34375 L 8.4375 34.34375 Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
21
TheBank/bankui/public/pdf.min.mjs
Normal file
21
TheBank/bankui/public/pdf.min.mjs
Normal file
File diff suppressed because one or more lines are too long
1
TheBank/bankui/public/vite.svg
Normal file
1
TheBank/bankui/public/vite.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
51
TheBank/bankui/src/App.tsx
Normal file
51
TheBank/bankui/src/App.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { useAuthCheck } from '@/hooks/useAuthCheck';
|
||||
import { useAuthStore } from '@/store/workerStore';
|
||||
import { Link, Navigate, Outlet, useLocation } from 'react-router-dom';
|
||||
import { Header } from '@/components/layout/Header';
|
||||
import { Footer } from '@/components/layout/Footer';
|
||||
import { Suspense } from 'react';
|
||||
import { Button } from './components/ui/button';
|
||||
|
||||
function App() {
|
||||
const user = useAuthStore((store) => store.user);
|
||||
const { isLoading } = useAuthCheck();
|
||||
const location = useLocation();
|
||||
|
||||
if (isLoading) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
const redirect = encodeURIComponent(location.pathname + location.search);
|
||||
return <Navigate to={`/auth?redirect=${redirect}`} replace />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{location.pathname === '/' && (
|
||||
<main className="flex justify-center items-center">
|
||||
<div className="flex-1 flex justify-center items-center">
|
||||
<img className="block" src="/Shrek.png" alt="кладовщик" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div>Удобный сервис для кладовщиков</div>
|
||||
<Link to="/storekeepers">
|
||||
<Button>За работу</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</main>
|
||||
)}
|
||||
{location.pathname !== '/' && (
|
||||
<>
|
||||
<Header />
|
||||
<Suspense fallback={<p>Loading...</p>}>
|
||||
<Outlet />
|
||||
</Suspense>
|
||||
</>
|
||||
)}
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
135
TheBank/bankui/src/api/api.ts
Normal file
135
TheBank/bankui/src/api/api.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import {
|
||||
getData,
|
||||
getSingleData,
|
||||
postData,
|
||||
postLoginData,
|
||||
putData,
|
||||
} from './client';
|
||||
import type {
|
||||
ClientBindingModel,
|
||||
ClerkBindingModel,
|
||||
CreditProgramBindingModel,
|
||||
CurrencyBindingModel,
|
||||
DepositBindingModel,
|
||||
PeriodBindingModel,
|
||||
ReplenishmentBindingModel,
|
||||
StorekeeperBindingModel,
|
||||
LoginBindingModel,
|
||||
} from '../types/types';
|
||||
|
||||
// Clients API
|
||||
export const clientsApi = {
|
||||
getAll: () => getData<ClientBindingModel>('api/Clients/GetAllRecords'),
|
||||
getById: (id: string) =>
|
||||
getData<ClientBindingModel>(`api/Clients/GetRecord/${id}`),
|
||||
getByClerk: (clerkId: string) =>
|
||||
getData<ClientBindingModel>(`api/Clients/GetRecordByClerk/${clerkId}`),
|
||||
create: (data: ClientBindingModel) => postData('api/Clients/Register', data),
|
||||
update: (data: ClientBindingModel) => putData('api/Clients/ChangeInfo', data),
|
||||
};
|
||||
|
||||
// Clerks API
|
||||
export const clerksApi = {
|
||||
getAll: () => getData<ClerkBindingModel>('api/Clerks'),
|
||||
getById: (id: string) => getData<ClerkBindingModel>(`api/Clerks/${id}`),
|
||||
create: (data: ClerkBindingModel) => postData('api/Clerks/Register', data),
|
||||
update: (data: ClerkBindingModel) => putData('api/Clerks/ChangeInfo', data),
|
||||
};
|
||||
|
||||
// Credit Programs API
|
||||
export const creditProgramsApi = {
|
||||
getAll: () =>
|
||||
getData<CreditProgramBindingModel>('api/CreditPrograms/GetAllRecords'),
|
||||
getById: (id: string) =>
|
||||
getData<CreditProgramBindingModel>(`api/CreditPrograms/GetRecord/${id}`),
|
||||
getByStorekeeper: (storekeeperId: string) =>
|
||||
getData<CreditProgramBindingModel>(
|
||||
`api/CreditPrograms/GetRecordByStorekeeper/${storekeeperId}`,
|
||||
),
|
||||
create: (data: CreditProgramBindingModel) =>
|
||||
postData('api/CreditPrograms/Register', data),
|
||||
update: (data: CreditProgramBindingModel) =>
|
||||
putData('api/CreditPrograms/ChangeInfo', data),
|
||||
};
|
||||
|
||||
// Currencies API
|
||||
export const currenciesApi = {
|
||||
getAll: () => getData<CurrencyBindingModel>('api/Currencies/GetAllRecords'),
|
||||
getById: (id: string) =>
|
||||
getData<CurrencyBindingModel>(`api/Currencies/GetRecord/${id}`),
|
||||
getByStorekeeper: (storekeeperId: string) =>
|
||||
getData<CurrencyBindingModel>(
|
||||
`api/Currencies/GetRecordByStorekeeper/${storekeeperId}`,
|
||||
),
|
||||
create: (data: CurrencyBindingModel) =>
|
||||
postData('api/Currencies/Register', data),
|
||||
update: (data: CurrencyBindingModel) =>
|
||||
putData('api/Currencies/ChangeInfo', data),
|
||||
};
|
||||
|
||||
// Deposits API
|
||||
export const depositsApi = {
|
||||
getAll: () => getData<DepositBindingModel>('api/Deposits/GetAllRecords'),
|
||||
getById: (id: string) =>
|
||||
getData<DepositBindingModel>(`api/Deposits/GetRecord/${id}`),
|
||||
getByClerk: (clerkId: string) =>
|
||||
getData<DepositBindingModel>(`api/Deposits/GetRecordByClerk/${clerkId}`),
|
||||
create: (data: DepositBindingModel) =>
|
||||
postData('api/Deposits/Register', data),
|
||||
update: (data: DepositBindingModel) =>
|
||||
putData('api/Deposits/ChangeInfo', data),
|
||||
};
|
||||
|
||||
// Periods API
|
||||
export const periodsApi = {
|
||||
getAll: () => getData<PeriodBindingModel>('api/Periods/GetAllRecords'),
|
||||
getById: (id: string) =>
|
||||
getData<PeriodBindingModel>(`api/Periods/GetRecord/${id}`),
|
||||
getByStorekeeper: (storekeeperId: string) =>
|
||||
getData<PeriodBindingModel>(
|
||||
`api/Period/GetRecordByStorekeeper/${storekeeperId}`,
|
||||
),
|
||||
create: (data: PeriodBindingModel) => postData('api/Periods/Register', data),
|
||||
update: (data: PeriodBindingModel) => putData('api/Periods/ChangeInfo', data),
|
||||
};
|
||||
|
||||
// Replenishments API
|
||||
export const replenishmentsApi = {
|
||||
getAll: () =>
|
||||
getData<ReplenishmentBindingModel>('api/Replenishments/GetAllRecords'),
|
||||
getById: (id: string) =>
|
||||
getData<ReplenishmentBindingModel>(`api/Replenishments/GetRecord/${id}`),
|
||||
getByDeposit: (depositId: string) =>
|
||||
getData<ReplenishmentBindingModel>(
|
||||
`api/Replenishments/GetRecordByDeposit/${depositId}`,
|
||||
),
|
||||
getByClerk: (clerkId: string) =>
|
||||
getData<ReplenishmentBindingModel>(
|
||||
`api/Replenishments/GetRecordByClerk/${clerkId}`,
|
||||
),
|
||||
create: (data: ReplenishmentBindingModel) =>
|
||||
postData('api/Replenishments/Register', data),
|
||||
update: (data: ReplenishmentBindingModel) =>
|
||||
putData('api/Replenishments/ChangeInfo', data),
|
||||
};
|
||||
|
||||
// Storekeepers API
|
||||
export const storekeepersApi = {
|
||||
getAll: () => getData<StorekeeperBindingModel>('api/storekeepers'),
|
||||
getById: (id: string) =>
|
||||
getData<StorekeeperBindingModel>(`api/Storekeepers/GetRecord/${id}`),
|
||||
create: (data: StorekeeperBindingModel) =>
|
||||
postData('api/Storekeepers/Register', data),
|
||||
update: (data: StorekeeperBindingModel) => putData('api/Storekeepers', data),
|
||||
// auth
|
||||
login: (data: LoginBindingModel) =>
|
||||
postLoginData('api/Storekeepers/login', data),
|
||||
logout: () => postData('api/storekeepers/logout', {}),
|
||||
getCurrentUser: () =>
|
||||
getSingleData<StorekeeperBindingModel>('api/storekeepers/me'),
|
||||
};
|
||||
|
||||
//Reports API
|
||||
export const reportsApi = {
|
||||
// loadClientsByCreditProgram: () => getReport('path'),
|
||||
};
|
||||
124
TheBank/bankui/src/api/client.ts
Normal file
124
TheBank/bankui/src/api/client.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import { ConfigManager } from '@/lib/config';
|
||||
import type { MailSendInfoBindingModel } from '@/types/types';
|
||||
const API_URL = ConfigManager.loadUrl();
|
||||
// Устанавливаем прямой URL к API серверу ASP.NET
|
||||
// const API_URL = 'https://localhost:7224'; // URL API сервера ASP.NET
|
||||
|
||||
export async function getData<T>(path: string): Promise<T[]> {
|
||||
const res = await fetch(`${API_URL}/${path}`, {
|
||||
credentials: 'include',
|
||||
});
|
||||
if (!res.ok) {
|
||||
throw new Error(`Не получается загрузить ${path}: ${res.statusText}`);
|
||||
}
|
||||
const data = (await res.json()) as T[];
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function postData<T>(path: string, data: T) {
|
||||
const res = await fetch(`${API_URL}/${path}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
// mode: 'no-cors',
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
if (!res.ok) {
|
||||
throw new Error(`Не получается загрузить ${path}: ${res.statusText}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSingleData<T>(path: string): Promise<T> {
|
||||
const res = await fetch(`${API_URL}/${path}`, {
|
||||
credentials: 'include',
|
||||
});
|
||||
if (!res.ok) {
|
||||
throw new Error(`Не получается загрузить ${path}: ${res.statusText}`);
|
||||
}
|
||||
const data = (await res.json()) as T;
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function postLoginData<T>(path: string, data: T): Promise<T> {
|
||||
const res = await fetch(`${API_URL}/${path}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
if (!res.ok) {
|
||||
throw new Error(`Не получается загрузить ${path}: ${await res.text()}`);
|
||||
}
|
||||
|
||||
const userData = (await res.json()) as T;
|
||||
return userData;
|
||||
}
|
||||
|
||||
export async function putData<T>(path: string, data: T) {
|
||||
const res = await fetch(`${API_URL}/${path}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
if (!res.ok) {
|
||||
throw new Error(`Не получается загрузить ${path}: ${res.statusText}`);
|
||||
}
|
||||
}
|
||||
|
||||
// report api
|
||||
export interface ReportParams {
|
||||
fromDate?: string; // Например, '2025-01-01'
|
||||
toDate?: string; // Например, '2025-05-21'
|
||||
}
|
||||
|
||||
export type ReportType =
|
||||
| 'depositByCreditProgram'
|
||||
| 'depositAndCreditProgramByCurrency';
|
||||
export type ReportFormat = 'word' | 'excel' | 'pdf';
|
||||
|
||||
export async function sendReportByEmail(
|
||||
reportType: ReportType,
|
||||
format: ReportFormat,
|
||||
mailInfo: MailSendInfoBindingModel,
|
||||
params?: ReportParams,
|
||||
): Promise<void> {
|
||||
const actionMap: Record<ReportType, Record<ReportFormat, string>> = {
|
||||
depositByCreditProgram: {
|
||||
word: 'SendReportDepositByCreditProgram',
|
||||
excel: 'SendExcelReportDepositByCreditProgram',
|
||||
pdf: 'SendReportDepositByCreditProgram',
|
||||
},
|
||||
depositAndCreditProgramByCurrency: {
|
||||
word: 'SendReportByCurrency',
|
||||
excel: 'SendReportByCurrency',
|
||||
pdf: 'SendReportByCurrency',
|
||||
},
|
||||
};
|
||||
|
||||
const action = actionMap[reportType][format];
|
||||
|
||||
// Формируем тело запроса
|
||||
const requestBody = { ...mailInfo, ...params };
|
||||
|
||||
const res = await fetch(`${API_URL}/api/Report/${action}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(requestBody),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(
|
||||
`Не удалось отправить отчет ${reportType} (${format}): ${res.statusText}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
1
TheBank/bankui/src/assets/react.svg
Normal file
1
TheBank/bankui/src/assets/react.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
281
TheBank/bankui/src/components/features/CreditProgramForm.tsx
Normal file
281
TheBank/bankui/src/components/features/CreditProgramForm.tsx
Normal file
@@ -0,0 +1,281 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import type { CreditProgramBindingModel } from '@/types/types';
|
||||
import { useAuthStore } from '@/store/workerStore';
|
||||
import { usePeriods } from '@/hooks/usePeriods';
|
||||
|
||||
type BaseFormValues = {
|
||||
id?: string;
|
||||
name: string;
|
||||
cost: number;
|
||||
maxCost: number;
|
||||
periodId: string;
|
||||
};
|
||||
|
||||
type EditFormValues = Partial<BaseFormValues>;
|
||||
|
||||
const baseSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
name: z.string().min(1, 'Название обязательно'),
|
||||
cost: z.coerce.number().min(0, 'Стоимость не может быть отрицательной'),
|
||||
maxCost: z.coerce
|
||||
.number()
|
||||
.min(0, 'Максимальная стоимость не может быть отрицательной'),
|
||||
periodId: z.string().min(1, 'Выберите период'),
|
||||
});
|
||||
|
||||
const addSchema = baseSchema;
|
||||
|
||||
const editSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
name: z.string().min(1, 'Название обязательно').optional(),
|
||||
cost: z.coerce
|
||||
.number()
|
||||
.min(0, 'Стоимость не может быть отрицательной')
|
||||
.optional(),
|
||||
maxCost: z.coerce
|
||||
.number()
|
||||
.min(0, 'Максимальная стоимость не может быть отрицательной')
|
||||
.optional(),
|
||||
periodId: z.string().min(1, 'Выберите период').optional(),
|
||||
});
|
||||
|
||||
interface BaseCreditProgramFormProps {
|
||||
onSubmit: (data: CreditProgramBindingModel) => void;
|
||||
schema: z.ZodType<BaseFormValues | EditFormValues>;
|
||||
defaultValues?: Partial<BaseFormValues>;
|
||||
}
|
||||
|
||||
const BaseCreditProgramForm = ({
|
||||
onSubmit,
|
||||
schema,
|
||||
defaultValues,
|
||||
}: BaseCreditProgramFormProps): React.JSX.Element => {
|
||||
const form = useForm<BaseFormValues | EditFormValues>({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: defaultValues
|
||||
? {
|
||||
id: defaultValues.id ?? '',
|
||||
name: defaultValues.name ?? '',
|
||||
cost: defaultValues.cost ?? 0,
|
||||
maxCost: defaultValues.maxCost ?? 0,
|
||||
periodId: defaultValues.periodId ?? '',
|
||||
}
|
||||
: {
|
||||
id: '',
|
||||
name: '',
|
||||
cost: 0,
|
||||
maxCost: 0,
|
||||
periodId: '',
|
||||
},
|
||||
});
|
||||
|
||||
const { periods } = usePeriods();
|
||||
|
||||
useEffect(() => {
|
||||
if (defaultValues) {
|
||||
form.reset({
|
||||
id: defaultValues.id ?? '',
|
||||
name: defaultValues.name ?? '',
|
||||
cost: defaultValues.cost ?? 0,
|
||||
maxCost: defaultValues.maxCost ?? 0,
|
||||
periodId: defaultValues.periodId ?? '',
|
||||
});
|
||||
}
|
||||
}, [defaultValues, form]);
|
||||
|
||||
const storekeeper = useAuthStore((store) => store.user);
|
||||
|
||||
const handleSubmit = (data: BaseFormValues | EditFormValues) => {
|
||||
if (!storekeeper?.id) {
|
||||
console.error('Storekeeper ID is not available.');
|
||||
return;
|
||||
}
|
||||
|
||||
let payload: CreditProgramBindingModel;
|
||||
|
||||
if (schema === addSchema) {
|
||||
const addData = data as BaseFormValues;
|
||||
payload = {
|
||||
id: addData.id || crypto.randomUUID(),
|
||||
storekeeperId: storekeeper.id,
|
||||
name: addData.name,
|
||||
cost: addData.cost,
|
||||
maxCost: addData.maxCost,
|
||||
periodId: addData.periodId,
|
||||
};
|
||||
} else {
|
||||
const editData = data as EditFormValues;
|
||||
const currentDefaultValues = defaultValues as Partial<BaseFormValues>;
|
||||
|
||||
const changedData: Partial<CreditProgramBindingModel> = {};
|
||||
|
||||
if (editData.id !== undefined && editData.id !== currentDefaultValues?.id)
|
||||
changedData.id = editData.id;
|
||||
if (
|
||||
editData.name !== undefined &&
|
||||
editData.name !== currentDefaultValues?.name
|
||||
)
|
||||
changedData.name = editData.name;
|
||||
if (
|
||||
editData.cost !== undefined &&
|
||||
editData.cost !== currentDefaultValues?.cost
|
||||
)
|
||||
changedData.cost = editData.cost;
|
||||
if (
|
||||
editData.maxCost !== undefined &&
|
||||
editData.maxCost !== currentDefaultValues?.maxCost
|
||||
)
|
||||
changedData.maxCost = editData.maxCost;
|
||||
if (
|
||||
editData.periodId !== undefined &&
|
||||
editData.periodId !== currentDefaultValues?.periodId
|
||||
)
|
||||
changedData.periodId = editData.periodId;
|
||||
|
||||
if (currentDefaultValues?.id) changedData.id = currentDefaultValues.id;
|
||||
changedData.storekeeperId = storekeeper.id;
|
||||
|
||||
payload = {
|
||||
...(defaultValues as CreditProgramBindingModel),
|
||||
...changedData,
|
||||
};
|
||||
}
|
||||
|
||||
onSubmit(payload);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(handleSubmit)}
|
||||
className="space-y-4 max-w-md mx-auto p-4"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="id"
|
||||
render={({ field }) => <input type="hidden" {...field} />}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Название</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Название" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="cost"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Стоимость</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="number" placeholder="Стоимость" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="maxCost"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Максимальная стоимость</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="Максимальная стоимость"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="periodId"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Период</FormLabel>
|
||||
<Select onValueChange={field.onChange} value={field.value || ''}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Выберите период" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{periods &&
|
||||
periods?.map((period) => (
|
||||
<SelectItem key={period.id} value={period.id}>
|
||||
{`${new Date(
|
||||
period.startTime,
|
||||
).toLocaleDateString()} - ${new Date(
|
||||
period.endTime,
|
||||
).toLocaleDateString()}`}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type="submit" className="w-full">
|
||||
Сохранить
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export const CreditProgramFormAdd = ({
|
||||
onSubmit,
|
||||
}: {
|
||||
onSubmit: (data: CreditProgramBindingModel) => void;
|
||||
}): React.JSX.Element => {
|
||||
return <BaseCreditProgramForm onSubmit={onSubmit} schema={addSchema} />;
|
||||
};
|
||||
|
||||
export const CreditProgramFormEdit = ({
|
||||
onSubmit,
|
||||
defaultValues,
|
||||
}: {
|
||||
onSubmit: (data: CreditProgramBindingModel) => void;
|
||||
defaultValues: Partial<BaseFormValues>;
|
||||
}): React.JSX.Element => {
|
||||
return (
|
||||
<BaseCreditProgramForm
|
||||
onSubmit={onSubmit}
|
||||
schema={editSchema}
|
||||
defaultValues={defaultValues}
|
||||
/>
|
||||
);
|
||||
};
|
||||
180
TheBank/bankui/src/components/features/CurrencyForm.tsx
Normal file
180
TheBank/bankui/src/components/features/CurrencyForm.tsx
Normal file
@@ -0,0 +1,180 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import type { CurrencyBindingModel } from '@/types/types';
|
||||
import { useAuthStore } from '@/store/workerStore';
|
||||
|
||||
type BaseFormValues = {
|
||||
id?: string;
|
||||
name: string;
|
||||
abbreviation: string;
|
||||
cost: number;
|
||||
};
|
||||
|
||||
type EditFormValues = {
|
||||
id?: string;
|
||||
name?: string;
|
||||
abbreviation?: string;
|
||||
cost?: number;
|
||||
};
|
||||
|
||||
const baseSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
name: z.string({ message: 'Введите название' }),
|
||||
abbreviation: z.string({ message: 'Введите аббревиатуру' }),
|
||||
cost: z.coerce.number({ message: 'Введите стоимость' }),
|
||||
});
|
||||
|
||||
const addSchema = baseSchema;
|
||||
|
||||
const editSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
name: z.string().min(1, 'Укажите название валюты').optional(),
|
||||
abbreviation: z.string().min(1, 'Укажите аббревиатуру').optional(),
|
||||
cost: z.coerce
|
||||
.number()
|
||||
.min(0, 'Стоимость не может быть отрицательной')
|
||||
.optional(),
|
||||
});
|
||||
|
||||
interface BaseCurrencyFormProps {
|
||||
onSubmit: (data: CurrencyBindingModel) => void;
|
||||
schema: z.ZodType<BaseFormValues | EditFormValues>;
|
||||
defaultValues?: Partial<BaseFormValues | EditFormValues>;
|
||||
}
|
||||
|
||||
const BaseCurrencyForm = ({
|
||||
onSubmit,
|
||||
schema,
|
||||
defaultValues,
|
||||
}: BaseCurrencyFormProps): React.JSX.Element => {
|
||||
const form = useForm<BaseFormValues | EditFormValues>({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: {
|
||||
id: defaultValues?.id || '',
|
||||
name: defaultValues?.name || '',
|
||||
abbreviation: defaultValues?.abbreviation || '',
|
||||
cost: defaultValues?.cost || 0,
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (defaultValues) {
|
||||
form.reset({
|
||||
id: defaultValues.id || '',
|
||||
name: defaultValues.name || '',
|
||||
abbreviation: defaultValues.abbreviation || '',
|
||||
cost: defaultValues.cost || 0,
|
||||
});
|
||||
}
|
||||
}, [defaultValues, form]);
|
||||
|
||||
const storekeeper = useAuthStore((store) => store.user);
|
||||
|
||||
const handleSubmit = (data: BaseFormValues | EditFormValues) => {
|
||||
const payload: CurrencyBindingModel = {
|
||||
id: data.id || crypto.randomUUID(),
|
||||
storekeeperId: storekeeper?.id,
|
||||
name: 'name' in data && data.name !== undefined ? data.name : '',
|
||||
abbreviation:
|
||||
'abbreviation' in data && data.abbreviation !== undefined
|
||||
? data.abbreviation
|
||||
: '',
|
||||
cost: 'cost' in data && data.cost !== undefined ? data.cost : 0,
|
||||
};
|
||||
|
||||
onSubmit(payload);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(handleSubmit)}
|
||||
className="space-y-4 max-w-md mx-auto p-4"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="id"
|
||||
render={({ field }) => <input type="hidden" {...field} />}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Название</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Например, Доллар США" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="abbreviation"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Аббревиатура</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Например, USD" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="cost"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Стоимость</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="number" placeholder="Например, 1.0" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit" className="w-full">
|
||||
Сохранить
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export const CurrencyFormAdd = ({
|
||||
onSubmit,
|
||||
}: {
|
||||
onSubmit: (data: CurrencyBindingModel) => void;
|
||||
}): React.JSX.Element => {
|
||||
return <BaseCurrencyForm onSubmit={onSubmit} schema={addSchema} />;
|
||||
};
|
||||
|
||||
export const CurrencyFormEdit = ({
|
||||
onSubmit,
|
||||
defaultValues,
|
||||
}: {
|
||||
onSubmit: (data: CurrencyBindingModel) => void;
|
||||
defaultValues: Partial<BaseFormValues>;
|
||||
}): React.JSX.Element => {
|
||||
return (
|
||||
<BaseCurrencyForm
|
||||
onSubmit={onSubmit}
|
||||
schema={editSchema}
|
||||
defaultValues={defaultValues}
|
||||
/>
|
||||
);
|
||||
};
|
||||
82
TheBank/bankui/src/components/features/LoginForm.tsx
Normal file
82
TheBank/bankui/src/components/features/LoginForm.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import type { LoginBindingModel } from '@/types/types';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '../ui/form';
|
||||
import { Input } from '../ui/input';
|
||||
import { Button } from '../ui/button';
|
||||
|
||||
interface LoginFormProps {
|
||||
onSubmit: (data: LoginBindingModel) => void;
|
||||
defaultValues?: Partial<LoginBindingModel>;
|
||||
}
|
||||
const loginFormSchema = z.object({
|
||||
login: z.string().min(3, 'Логин должен быть не короче 3 символов'),
|
||||
password: z.string().min(6, 'Пароль должен быть не короче 6 символов'),
|
||||
});
|
||||
|
||||
type FormValues = z.infer<typeof loginFormSchema>;
|
||||
|
||||
export const LoginForm = ({
|
||||
onSubmit,
|
||||
defaultValues,
|
||||
}: LoginFormProps): React.JSX.Element => {
|
||||
const form = useForm<FormValues>({
|
||||
resolver: zodResolver(loginFormSchema),
|
||||
defaultValues: {
|
||||
login: defaultValues?.login || '',
|
||||
password: defaultValues?.password || '',
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = (data: FormValues) => {
|
||||
const payload: LoginBindingModel = {
|
||||
...data,
|
||||
};
|
||||
onSubmit(payload);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="login"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Логин</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Логин" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Пароль</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="password" placeholder="Пароль" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit" className="w-full">
|
||||
Войти
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
105
TheBank/bankui/src/components/features/PdfViewer.tsx
Normal file
105
TheBank/bankui/src/components/features/PdfViewer.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import React from 'react';
|
||||
import { Document, Page } from 'react-pdf';
|
||||
import * as pdfjs from 'pdfjs-dist';
|
||||
import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
|
||||
import 'react-pdf/dist/esm/Page/TextLayer.css';
|
||||
import { Button } from '../ui/button';
|
||||
|
||||
// Используем встроенный worker
|
||||
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.js`;
|
||||
|
||||
interface PdfViewerProps {
|
||||
report: { blob: Blob; fileName: string; mimeType: string } | undefined | null;
|
||||
}
|
||||
|
||||
export const PdfViewer = ({ report }: PdfViewerProps) => {
|
||||
const [numPages, setNumPages] = React.useState<number | null>(null);
|
||||
const [pageNumber, setPageNumber] = React.useState(1);
|
||||
const [pdfUrl, setPdfUrl] = React.useState<string | undefined>(undefined);
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
|
||||
// Создаем URL для Blob при изменении report
|
||||
React.useEffect(() => {
|
||||
if (report?.blob) {
|
||||
const url = URL.createObjectURL(report.blob);
|
||||
setPdfUrl(url);
|
||||
setError(null);
|
||||
// Очищаем URL при размонтировании компонента или изменении report
|
||||
return () => URL.revokeObjectURL(url);
|
||||
} else {
|
||||
setPdfUrl(undefined);
|
||||
}
|
||||
}, [report]);
|
||||
|
||||
const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) => {
|
||||
setNumPages(numPages);
|
||||
setPageNumber(1);
|
||||
};
|
||||
|
||||
const handlePrevPage = () => {
|
||||
setPageNumber((prev) => Math.max(prev - 1, 1));
|
||||
};
|
||||
|
||||
const handleNextPage = () => {
|
||||
setPageNumber((prev) => Math.min(prev + 1, numPages || 1));
|
||||
};
|
||||
|
||||
if (!pdfUrl) {
|
||||
return (
|
||||
<div className="p-4">Загрузка или нет данных для отображения PDF.</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<Document
|
||||
file={pdfUrl}
|
||||
onLoadSuccess={onDocumentLoadSuccess}
|
||||
onLoadError={(error) => {
|
||||
console.error('Ошибка загрузки PDF:', error);
|
||||
setError(
|
||||
'Ошибка при загрузке PDF документа. Пожалуйста, попробуйте снова.',
|
||||
);
|
||||
}}
|
||||
loading={<div className="text-center py-4">Загрузка PDF...</div>}
|
||||
error={
|
||||
<div className="text-center text-red-500 py-4">
|
||||
Не удалось загрузить PDF
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Page
|
||||
pageNumber={pageNumber}
|
||||
renderTextLayer={false}
|
||||
renderAnnotationLayer={false}
|
||||
scale={1.2}
|
||||
loading={<div className="text-center py-2">Загрузка страницы...</div>}
|
||||
error={
|
||||
<div className="text-center text-red-500 py-2">
|
||||
Ошибка загрузки страницы
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Document>
|
||||
|
||||
{error ? (
|
||||
<div className="text-red-500 py-2">{error}</div>
|
||||
) : (
|
||||
<div className="flex justify-between items-center mt-4">
|
||||
<Button onClick={handlePrevPage} disabled={pageNumber <= 1}>
|
||||
Предыдущая
|
||||
</Button>
|
||||
<p>
|
||||
Страница {pageNumber} из {numPages || 1}
|
||||
</p>
|
||||
<Button
|
||||
onClick={handleNextPage}
|
||||
disabled={pageNumber >= (numPages || 1)}
|
||||
>
|
||||
Следующая
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
235
TheBank/bankui/src/components/features/PeriodForm.tsx
Normal file
235
TheBank/bankui/src/components/features/PeriodForm.tsx
Normal file
@@ -0,0 +1,235 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import type { PeriodBindingModel } from '@/types/types';
|
||||
import { Calendar } from '@/components/ui/calendar';
|
||||
import { format } from 'date-fns';
|
||||
import { CalendarIcon } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/components/ui/popover';
|
||||
import { useAuthStore } from '@/store/workerStore';
|
||||
|
||||
type BaseFormValues = {
|
||||
id?: string;
|
||||
startTime: Date;
|
||||
endTime: Date;
|
||||
};
|
||||
|
||||
type EditFormValues = {
|
||||
id?: string;
|
||||
startTime?: Date;
|
||||
endTime?: Date;
|
||||
};
|
||||
|
||||
const baseSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
startTime: z.date({
|
||||
required_error: 'Укажите время начала',
|
||||
invalid_type_error: 'Неверный формат даты',
|
||||
}),
|
||||
endTime: z.date({
|
||||
required_error: 'Укажите время окончания',
|
||||
invalid_type_error: 'Неверный формат даты',
|
||||
}),
|
||||
});
|
||||
|
||||
const addSchema = baseSchema;
|
||||
|
||||
const editSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
startTime: z
|
||||
.date({
|
||||
required_error: 'Укажите время начала',
|
||||
invalid_type_error: 'Неверный формат даты',
|
||||
})
|
||||
.optional(),
|
||||
endTime: z
|
||||
.date({
|
||||
required_error: 'Укажите время окончания',
|
||||
invalid_type_error: 'Неверный формат даты',
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
interface BasePeriodFormProps {
|
||||
onSubmit: (data: PeriodBindingModel) => void;
|
||||
schema: z.ZodType<BaseFormValues | EditFormValues>;
|
||||
defaultValues?: Partial<BaseFormValues | EditFormValues>;
|
||||
}
|
||||
|
||||
const BasePeriodForm = ({
|
||||
onSubmit,
|
||||
schema,
|
||||
defaultValues,
|
||||
}: BasePeriodFormProps): React.JSX.Element => {
|
||||
const form = useForm<BaseFormValues | EditFormValues>({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: {
|
||||
id: defaultValues?.id || '',
|
||||
startTime: defaultValues?.startTime || new Date(),
|
||||
endTime: defaultValues?.endTime || new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (defaultValues) {
|
||||
form.reset({
|
||||
id: defaultValues.id || '',
|
||||
startTime: defaultValues.startTime || new Date(),
|
||||
endTime: defaultValues.endTime || new Date(),
|
||||
});
|
||||
}
|
||||
}, [defaultValues, form]);
|
||||
|
||||
const storekeeper = useAuthStore((store) => store.user);
|
||||
|
||||
const handleSubmit = (data: BaseFormValues | EditFormValues) => {
|
||||
const payload: PeriodBindingModel = {
|
||||
id: data.id || crypto.randomUUID(),
|
||||
storekeeperId: storekeeper?.id,
|
||||
startTime:
|
||||
'startTime' in data && data.startTime !== undefined
|
||||
? data.startTime
|
||||
: new Date(),
|
||||
endTime:
|
||||
'endTime' in data && data.endTime !== undefined
|
||||
? data.endTime
|
||||
: new Date(),
|
||||
};
|
||||
|
||||
onSubmit(payload);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(handleSubmit)}
|
||||
className="space-y-4 max-w-md mx-auto p-4"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="id"
|
||||
render={({ field }) => <input type="hidden" {...field} />}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="startTime"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col">
|
||||
<FormLabel>Время начала</FormLabel>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
variant={'outline'}
|
||||
className={cn(
|
||||
'w-full pl-3 text-left font-normal',
|
||||
!field.value && 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
{field.value ? (
|
||||
format(field.value, 'PPP')
|
||||
) : (
|
||||
<span>Выберите дату</span>
|
||||
)}
|
||||
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={field.value}
|
||||
onSelect={field.onChange}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="endTime"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col">
|
||||
<FormLabel>Время окончания</FormLabel>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
variant={'outline'}
|
||||
className={cn(
|
||||
'w-full pl-3 text-left font-normal',
|
||||
!field.value && 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
{field.value ? (
|
||||
format(field.value, 'PPP')
|
||||
) : (
|
||||
<span>Выберите дату</span>
|
||||
)}
|
||||
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={field.value}
|
||||
onSelect={field.onChange}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type="submit" className="w-full">
|
||||
Сохранить
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export const PeriodFormAdd = ({
|
||||
onSubmit,
|
||||
}: {
|
||||
onSubmit: (data: PeriodBindingModel) => void;
|
||||
}): React.JSX.Element => {
|
||||
return <BasePeriodForm onSubmit={onSubmit} schema={addSchema} />;
|
||||
};
|
||||
|
||||
export const PeriodFormEdit = ({
|
||||
onSubmit,
|
||||
defaultValues,
|
||||
}: {
|
||||
onSubmit: (data: PeriodBindingModel) => void;
|
||||
defaultValues: Partial<BaseFormValues>;
|
||||
}): React.JSX.Element => {
|
||||
return (
|
||||
<BasePeriodForm
|
||||
onSubmit={onSubmit}
|
||||
schema={editSchema}
|
||||
defaultValues={defaultValues}
|
||||
/>
|
||||
);
|
||||
};
|
||||
180
TheBank/bankui/src/components/features/ProfileForm.tsx
Normal file
180
TheBank/bankui/src/components/features/ProfileForm.tsx
Normal file
@@ -0,0 +1,180 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import type { StorekeeperBindingModel } from '@/types/types';
|
||||
|
||||
// Схема для редактирования профиля (все поля опциональны)
|
||||
const profileEditSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
name: z.string().optional(),
|
||||
surname: z.string().optional(),
|
||||
middleName: z.string().optional(),
|
||||
login: z.string().optional(),
|
||||
email: z.string().email('Неверный формат email').optional(),
|
||||
phoneNumber: z.string().optional(),
|
||||
// Пароль, вероятно, должен редактироваться отдельно, но добавим опционально
|
||||
password: z.string().optional(),
|
||||
});
|
||||
|
||||
type ProfileFormValues = z.infer<typeof profileEditSchema>;
|
||||
|
||||
interface ProfileFormProps {
|
||||
onSubmit: (data: Partial<StorekeeperBindingModel>) => void;
|
||||
defaultValues: ProfileFormValues;
|
||||
}
|
||||
|
||||
export const ProfileForm = ({
|
||||
onSubmit,
|
||||
defaultValues,
|
||||
}: ProfileFormProps): React.JSX.Element => {
|
||||
const form = useForm<ProfileFormValues>({
|
||||
resolver: zodResolver(profileEditSchema),
|
||||
defaultValues: defaultValues,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (defaultValues) {
|
||||
form.reset(defaultValues);
|
||||
}
|
||||
}, [defaultValues, form]);
|
||||
|
||||
const handleSubmit = (data: ProfileFormValues) => {
|
||||
const changedData: ProfileFormValues = {};
|
||||
(Object.keys(data) as (keyof ProfileFormValues)[]).forEach((key) => {
|
||||
changedData[key] = data[key];
|
||||
if (data[key] !== defaultValues[key]) {
|
||||
changedData[key] = data[key];
|
||||
}
|
||||
});
|
||||
|
||||
if (defaultValues.id) {
|
||||
changedData.id = defaultValues.id;
|
||||
}
|
||||
|
||||
onSubmit(changedData);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(handleSubmit)}
|
||||
className="space-y-4 max-w-md mx-auto p-4"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="id"
|
||||
render={({ field }) => <input type="hidden" {...field} />}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Имя</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Имя" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="surname"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Фамилия</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Фамилия" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="middleName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Отчество</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Отчество" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="login"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Логин</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Логин" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Email" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="phoneNumber"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Номер телефона</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Номер телефона" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{/* Поле пароля можно добавить здесь, если требуется его редактирование */}
|
||||
{/*
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Новый пароль (оставьте пустым, если не меняете)</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="password" placeholder="Пароль" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
*/}
|
||||
|
||||
<Button type="submit" className="w-full">
|
||||
Сохранить изменения
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
166
TheBank/bankui/src/components/features/RegisterForm.tsx
Normal file
166
TheBank/bankui/src/components/features/RegisterForm.tsx
Normal file
@@ -0,0 +1,166 @@
|
||||
import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import type { StorekeeperBindingModel } from '@/types/types';
|
||||
|
||||
interface RegisterFormProps {
|
||||
onSubmit: (data: StorekeeperBindingModel) => void;
|
||||
defaultValues?: Partial<StorekeeperBindingModel>;
|
||||
}
|
||||
|
||||
const registerFormSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
name: z.string().min(1, 'Имя обязательно'),
|
||||
surname: z.string().min(1, 'Фамилия обязательна'),
|
||||
middleName: z.string().min(1, 'Отчество обязательно'),
|
||||
login: z.string().min(3, 'Логин должен быть не короче 3 символов'),
|
||||
password: z.string().min(6, 'Пароль должен быть не короче 6 символов'),
|
||||
email: z.string().email('Введите корректный email'),
|
||||
phoneNumber: z.string().min(10, 'Введите корректный номер телефона'),
|
||||
});
|
||||
|
||||
type FormValues = z.infer<typeof registerFormSchema>;
|
||||
|
||||
export const RegisterForm = ({
|
||||
onSubmit,
|
||||
defaultValues,
|
||||
}: RegisterFormProps): React.JSX.Element => {
|
||||
const form = useForm<FormValues>({
|
||||
resolver: zodResolver(registerFormSchema),
|
||||
defaultValues: {
|
||||
id: defaultValues?.id || crypto.randomUUID(),
|
||||
name: defaultValues?.name || '',
|
||||
surname: defaultValues?.surname || '',
|
||||
middleName: defaultValues?.middleName || '',
|
||||
login: defaultValues?.login || '',
|
||||
password: defaultValues?.password || '',
|
||||
email: defaultValues?.email || '',
|
||||
phoneNumber: defaultValues?.phoneNumber || '',
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = (data: FormValues) => {
|
||||
const payload: StorekeeperBindingModel = {
|
||||
...data,
|
||||
id: data.id || crypto.randomUUID(),
|
||||
};
|
||||
onSubmit(payload);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="id"
|
||||
render={({ field }) => <input type="hidden" {...field} />}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Имя</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Имя" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="surname"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Фамилия</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Фамилия" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="middleName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Отчество</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Отчество" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="login"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Логин</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Логин" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Пароль</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="password" placeholder="Пароль" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="email" placeholder="Email" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="phoneNumber"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Номер телефона</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Номер телефона" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit" className="w-full">
|
||||
Зарегистрировать
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
518
TheBank/bankui/src/components/features/ReportViewer.tsx
Normal file
518
TheBank/bankui/src/components/features/ReportViewer.tsx
Normal file
@@ -0,0 +1,518 @@
|
||||
import React from 'react';
|
||||
import type { SelectedReport } from '../pages/Reports';
|
||||
import { Button } from '../ui/button';
|
||||
import { PdfViewer } from './PdfViewer';
|
||||
import { DialogForm } from '../layout/DialogForm';
|
||||
import { Input } from '../ui/input';
|
||||
import { Label } from '../ui/label';
|
||||
import { toast } from 'sonner';
|
||||
import { Calendar } from '../ui/calendar';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { format } from 'date-fns';
|
||||
import { CalendarIcon } from 'lucide-react';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '../ui/form';
|
||||
import { ConfigManager } from '@/lib/config';
|
||||
|
||||
type ReportViewerProps = {
|
||||
selectedReport: SelectedReport;
|
||||
};
|
||||
|
||||
type ReportData = { blob: Blob; fileName: string; mimeType: string };
|
||||
|
||||
const emailFormSchema = z.object({
|
||||
toEmail: z.string().email({ message: 'Введите корректный email' }),
|
||||
subject: z.string().min(1, { message: 'Тема обязательна' }),
|
||||
body: z.string().min(1, { message: 'Текст сообщения обязателен' }),
|
||||
});
|
||||
|
||||
const API_URL = ConfigManager.loadUrl();
|
||||
|
||||
export const ReportViewer = ({
|
||||
selectedReport,
|
||||
}: ReportViewerProps): React.JSX.Element => {
|
||||
const [isSendDialogOpen, setIsSendDialogOpen] = React.useState(false);
|
||||
const [fromDate, setFromDate] = React.useState<Date | undefined>(undefined);
|
||||
const [toDate, setToDate] = React.useState<Date | undefined>(undefined);
|
||||
const [isLoading, setIsLoading] = React.useState(false);
|
||||
const [error, setError] = React.useState<Error | null>(null);
|
||||
const [report, setReport] = React.useState<ReportData | null>(null);
|
||||
|
||||
const form = useForm<z.infer<typeof emailFormSchema>>({
|
||||
resolver: zodResolver(emailFormSchema),
|
||||
defaultValues: {
|
||||
toEmail: '',
|
||||
subject: getDefaultSubject(selectedReport),
|
||||
body: getDefaultBody(selectedReport, fromDate, toDate),
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
form.setValue('subject', getDefaultSubject(selectedReport));
|
||||
form.setValue('body', getDefaultBody(selectedReport, fromDate, toDate));
|
||||
|
||||
setReport(null);
|
||||
}, [selectedReport, fromDate, toDate, form]);
|
||||
|
||||
const getReportTitle = (report: SelectedReport) => {
|
||||
switch (report) {
|
||||
case 'word':
|
||||
return 'Отчет Word по вкладам по кредитным программам';
|
||||
case 'excel':
|
||||
return 'Отчет Excel по вкладам по кредитным программам';
|
||||
case 'pdf':
|
||||
return 'Отчет PDF по вкладам и кредитным программам по валютам';
|
||||
default:
|
||||
return 'Выберите тип отчета';
|
||||
}
|
||||
};
|
||||
|
||||
function getDefaultSubject(report: SelectedReport | undefined): string {
|
||||
switch (report) {
|
||||
case 'word':
|
||||
return 'Отчет по вкладам по кредитным программам';
|
||||
case 'excel':
|
||||
return 'Excel отчет по вкладам по кредитным программам';
|
||||
case 'pdf':
|
||||
return 'Отчет по вкладам и кредитным программам по валютам';
|
||||
default:
|
||||
return 'Отчет';
|
||||
}
|
||||
}
|
||||
|
||||
function getDefaultBody(
|
||||
report: SelectedReport | undefined,
|
||||
fromDate?: Date,
|
||||
toDate?: Date,
|
||||
): string {
|
||||
switch (report) {
|
||||
case 'word':
|
||||
return 'В приложении находится отчет по вкладам по кредитным программам.';
|
||||
case 'excel':
|
||||
return 'В приложении находится Excel отчет по вкладам по кредитным программам.';
|
||||
case 'pdf':
|
||||
return `В приложении находится отчет по вкладам и кредитным программам по валютам${
|
||||
fromDate && toDate
|
||||
? ` за период с ${format(fromDate, 'dd.MM.yyyy')} по ${format(
|
||||
toDate,
|
||||
'dd.MM.yyyy',
|
||||
)}`
|
||||
: ''
|
||||
}.`;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
const getReportUrl = (
|
||||
selectedReport: SelectedReport,
|
||||
fromDate?: Date,
|
||||
toDate?: Date,
|
||||
): string => {
|
||||
switch (selectedReport) {
|
||||
case 'word':
|
||||
return `${API_URL}/api/Report/LoadDepositByCreditProgram`;
|
||||
case 'excel':
|
||||
return `${API_URL}/api/Report/LoadExcelDepositByCreditProgram`;
|
||||
case 'pdf': {
|
||||
if (!fromDate || !toDate) {
|
||||
throw new Error('Необходимо выбрать даты для PDF отчета');
|
||||
}
|
||||
const fromDateStr = format(fromDate, 'yyyy-MM-dd');
|
||||
const toDateStr = format(toDate, 'yyyy-MM-dd');
|
||||
return `${API_URL}/api/Report/LoadDepositAndCreditProgramByCurrency?fromDate=${fromDateStr}&toDate=${toDateStr}`;
|
||||
}
|
||||
default:
|
||||
throw new Error('Выберите тип отчета');
|
||||
}
|
||||
};
|
||||
|
||||
const getSendEmailUrl = (selectedReport: SelectedReport): string => {
|
||||
switch (selectedReport) {
|
||||
case 'word':
|
||||
return `${API_URL}/api/Report/SendReportDepositByCreditProgram`;
|
||||
case 'excel':
|
||||
return `${API_URL}/api/Report/SendExcelReportDepositByCreditProgram`;
|
||||
case 'pdf': {
|
||||
if (!fromDate || !toDate) {
|
||||
throw new Error('Необходимо выбрать даты для PDF отчета');
|
||||
}
|
||||
const fromDateStr = format(fromDate, 'yyyy-MM-dd');
|
||||
const toDateStr = format(toDate, 'yyyy-MM-dd');
|
||||
return `${API_URL}/api/Report/SendReportByCurrency?fromDate=${fromDateStr}&toDate=${toDateStr}`;
|
||||
}
|
||||
default:
|
||||
throw new Error('Выберите тип отчета');
|
||||
}
|
||||
};
|
||||
|
||||
const fetchReport = async (): Promise<ReportData> => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
const url = getReportUrl(selectedReport, fromDate, toDate);
|
||||
console.log(`Загружаем отчет с URL: ${url}`);
|
||||
|
||||
const acceptHeader =
|
||||
selectedReport === 'pdf'
|
||||
? 'application/pdf'
|
||||
: selectedReport === 'word'
|
||||
? 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||
: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: acceptHeader,
|
||||
},
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Ошибка загрузки отчета: ${response.status} ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
console.log(
|
||||
`Отчет загружен. Тип: ${blob.type}, размер: ${blob.size} байт`,
|
||||
);
|
||||
|
||||
const contentDisposition = response.headers.get('Content-Disposition');
|
||||
const defaultExtension =
|
||||
selectedReport === 'pdf'
|
||||
? '.pdf'
|
||||
: selectedReport === 'word'
|
||||
? '.docx'
|
||||
: '.xlsx';
|
||||
let fileName = `report${defaultExtension}`;
|
||||
|
||||
if (contentDisposition && contentDisposition.includes('filename=')) {
|
||||
fileName = contentDisposition
|
||||
.split('filename=')[1]
|
||||
.replace(/"/g, '')
|
||||
.trim();
|
||||
}
|
||||
|
||||
const mimeType = response.headers.get('Content-Type') || '';
|
||||
|
||||
const reportData = { blob, fileName, mimeType };
|
||||
setReport(reportData);
|
||||
return reportData;
|
||||
} catch (error) {
|
||||
console.error('Ошибка при загрузке отчета:', error);
|
||||
const err =
|
||||
error instanceof Error ? error : new Error('Неизвестная ошибка');
|
||||
setError(err);
|
||||
throw err;
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleGenerate = async () => {
|
||||
try {
|
||||
await fetchReport();
|
||||
toast.success('Отчет успешно загружен');
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
`Ошибка загрузки отчета: ${
|
||||
error instanceof Error ? error.message : 'Неизвестная ошибка'
|
||||
}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownload = async () => {
|
||||
try {
|
||||
let reportData = report;
|
||||
// Для PDF всегда делаем новый запрос с актуальными датами
|
||||
if (selectedReport === 'pdf') {
|
||||
if (!fromDate || !toDate) {
|
||||
toast.error('Пожалуйста, выберите даты для PDF отчета');
|
||||
return;
|
||||
}
|
||||
toast.loading('Загрузка отчета...');
|
||||
reportData = await fetchReport();
|
||||
} else if (!reportData) {
|
||||
toast.loading('Загрузка отчета...');
|
||||
reportData = await fetchReport();
|
||||
}
|
||||
|
||||
// Скачиваем отчет
|
||||
const url = URL.createObjectURL(reportData.blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = reportData.fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
toast.success('Отчет успешно скачан');
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
`Ошибка при скачивании отчета: ${
|
||||
error instanceof Error ? error.message : 'Неизвестная ошибка'
|
||||
}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSendFormSubmit = async (
|
||||
values: z.infer<typeof emailFormSchema>,
|
||||
) => {
|
||||
try {
|
||||
// Если выбран PDF отчет, проверяем наличие дат
|
||||
if (selectedReport === 'pdf' && (!fromDate || !toDate)) {
|
||||
toast.error('Пожалуйста, выберите даты для PDF отчета');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
// Формируем данные для отправки
|
||||
const url = getSendEmailUrl(selectedReport);
|
||||
|
||||
// Параметры для запроса
|
||||
const data: Record<string, string> = {
|
||||
toEmail: values.toEmail,
|
||||
subject: values.subject,
|
||||
body: values.body,
|
||||
};
|
||||
|
||||
// Отправляем запрос
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(
|
||||
`Ошибка при отправке отчета: ${response.status} ${response.statusText}\n${errorText}`,
|
||||
);
|
||||
}
|
||||
|
||||
toast.success('Отчет успешно отправлен на почту');
|
||||
setIsSendDialogOpen(false);
|
||||
form.reset();
|
||||
} catch (error) {
|
||||
console.error('Ошибка при отправке отчета:', error);
|
||||
toast.error(
|
||||
`Ошибка при отправке отчета: ${
|
||||
error instanceof Error ? error.message : 'Неизвестная ошибка'
|
||||
}`,
|
||||
);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Проверка, можно ли сгенерировать/скачать/отправить PDF отчет
|
||||
const isPdfActionDisabled =
|
||||
selectedReport === 'pdf' && (!fromDate || !toDate || isLoading);
|
||||
|
||||
// Отображение ошибки, если она есть
|
||||
const renderError = () => {
|
||||
if (!error) return null;
|
||||
|
||||
return (
|
||||
<div className="p-4 border border-red-300 bg-red-50 rounded-md mt-2">
|
||||
<h3 className="text-red-700 font-semibold mb-1">Детали ошибки:</h3>
|
||||
<p className="text-red-600 whitespace-pre-wrap break-words">
|
||||
{error.message}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="text-lg font-semibold mb-4">
|
||||
{getReportTitle(selectedReport)}
|
||||
</div>
|
||||
|
||||
{/* Кнопки действий */}
|
||||
<div className="flex gap-4 mb-4">
|
||||
{/* Кнопка "Сгенерировать" только для PDF с выбранными датами */}
|
||||
{selectedReport === 'pdf' && (
|
||||
<Button onClick={handleGenerate} disabled={isPdfActionDisabled}>
|
||||
{isLoading ? 'Загрузка...' : 'Сгенерировать'}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* Кнопки "Скачать" и "Отправить" только когда выбран тип отчета */}
|
||||
{selectedReport && (
|
||||
<>
|
||||
<Button
|
||||
onClick={handleDownload}
|
||||
disabled={isPdfActionDisabled || isLoading}
|
||||
>
|
||||
{isLoading ? 'Загрузка...' : 'Скачать'}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={() => setIsSendDialogOpen(true)}
|
||||
disabled={isPdfActionDisabled || isLoading}
|
||||
>
|
||||
Отправить
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Календари для выбора периода для PDF отчета */}
|
||||
{selectedReport === 'pdf' && (
|
||||
<div className="flex gap-4 mb-4">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="fromDate">От даты</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant={'outline'}
|
||||
className={cn(
|
||||
'w-[240px] justify-start text-left font-normal',
|
||||
!fromDate && 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
<CalendarIcon className="mr-2 h-4 w-4" />
|
||||
{fromDate ? (
|
||||
format(fromDate, 'PPP')
|
||||
) : (
|
||||
<span>Выберите дату</span>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={fromDate}
|
||||
onSelect={setFromDate}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="toDate">До даты</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant={'outline'}
|
||||
className={cn(
|
||||
'w-[240px] justify-start text-left font-normal',
|
||||
!toDate && 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
<CalendarIcon className="mr-2 h-4 w-4" />
|
||||
{toDate ? format(toDate, 'PPP') : <span>Выберите дату</span>}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={toDate}
|
||||
onSelect={setToDate}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Форма отправки отчета на почту */}
|
||||
<DialogForm
|
||||
title="Отправка отчета"
|
||||
description="Введите данные для отправки отчета"
|
||||
isOpen={isSendDialogOpen}
|
||||
onClose={() => setIsSendDialogOpen(false)}
|
||||
onSubmit={form.handleSubmit(handleSendFormSubmit)}
|
||||
>
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(handleSendFormSubmit)}
|
||||
className="space-y-4"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="toEmail"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email получателя</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="example@mail.ru" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="subject"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Тема письма</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="body"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Текст сообщения</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit">Отправить</Button>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogForm>
|
||||
|
||||
<div className="mt-4">
|
||||
{isLoading && <div className="p-4">Загрузка документа...</div>}
|
||||
|
||||
{renderError()}
|
||||
|
||||
{!selectedReport && !isLoading && !error && (
|
||||
<div className="p-4">Выберите тип отчета из боковой панели</div>
|
||||
)}
|
||||
|
||||
{selectedReport && !report && !isLoading && !error && (
|
||||
<div className="p-4">
|
||||
{selectedReport === 'pdf'
|
||||
? 'Выберите даты и нажмите "Сгенерировать"'
|
||||
: 'Нажмите "Скачать" для загрузки отчета'}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedReport === 'pdf' && report && <PdfViewer report={report} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
86
TheBank/bankui/src/components/layout/DataTable.tsx
Normal file
86
TheBank/bankui/src/components/layout/DataTable.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '../ui/table';
|
||||
import { Checkbox } from '../ui/checkbox';
|
||||
|
||||
type DataTableProps<T> = {
|
||||
data: T[];
|
||||
columns: ColumnDef<T>[];
|
||||
selectedRow?: string;
|
||||
onRowSelected: (id: string | undefined) => void;
|
||||
};
|
||||
|
||||
export type ColumnDef<T> = {
|
||||
accessorKey: keyof T | string;
|
||||
header: string;
|
||||
renderCell?: (item: T) => React.ReactNode;
|
||||
};
|
||||
|
||||
export const DataTable = <T extends {}>({
|
||||
data,
|
||||
columns,
|
||||
selectedRow,
|
||||
onRowSelected,
|
||||
}: DataTableProps<T>): React.JSX.Element => {
|
||||
const handleRowSelect = (id: string) => {
|
||||
onRowSelected(selectedRow === id ? undefined : id);
|
||||
};
|
||||
return (
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[50px]"></TableHead>
|
||||
{columns.map((column) => (
|
||||
<TableHead key={column.accessorKey as string}>
|
||||
{column.header}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={columns.length + 1}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
Нет данных
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
data.map((item, index) => (
|
||||
<TableRow
|
||||
key={(item as any).id || index}
|
||||
data-state={
|
||||
selectedRow === (item as any).id ? 'selected' : undefined
|
||||
}
|
||||
>
|
||||
<TableCell>
|
||||
<Checkbox
|
||||
checked={selectedRow === (item as any).id}
|
||||
onCheckedChange={() => handleRowSelect((item as any).id)}
|
||||
aria-label="Select row"
|
||||
/>
|
||||
</TableCell>
|
||||
{columns.map((column) => (
|
||||
<TableCell key={column.accessorKey as string}>
|
||||
{column.renderCell
|
||||
? column.renderCell(item)
|
||||
: (item as any)[column.accessorKey] ?? 'N/A'}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
44
TheBank/bankui/src/components/layout/DialogForm.tsx
Normal file
44
TheBank/bankui/src/components/layout/DialogForm.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '../ui/dialog';
|
||||
|
||||
type DialogFormProps<T> = {
|
||||
children: React.ReactElement<{ onSubmit: (data: T) => void }>;
|
||||
title: string;
|
||||
description: string;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSubmit: (data: T) => void;
|
||||
};
|
||||
|
||||
export const DialogForm = <T,>({
|
||||
title,
|
||||
description,
|
||||
children,
|
||||
isOpen,
|
||||
onClose,
|
||||
onSubmit,
|
||||
}: DialogFormProps<T>): React.JSX.Element => {
|
||||
console.log(onSubmit);
|
||||
const wrappedSubmit = (data: T) => {
|
||||
onSubmit(data);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogDescription>{description}</DialogDescription>
|
||||
</DialogHeader>
|
||||
{React.cloneElement(children, { onSubmit: wrappedSubmit })}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
9
TheBank/bankui/src/components/layout/Footer.tsx
Normal file
9
TheBank/bankui/src/components/layout/Footer.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
export const Footer = (): React.JSX.Element => {
|
||||
return (
|
||||
<footer className="w-full flex border-t border-black shadow-lg p-2">
|
||||
<div className="text-black">Банк "Вы банкрот" 2025</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
183
TheBank/bankui/src/components/layout/Header.tsx
Normal file
183
TheBank/bankui/src/components/layout/Header.tsx
Normal file
@@ -0,0 +1,183 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
Menubar,
|
||||
MenubarContent,
|
||||
MenubarItem,
|
||||
MenubarMenu,
|
||||
MenubarSeparator,
|
||||
MenubarTrigger,
|
||||
} from '../ui/menubar';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '../ui/dropdown-menu';
|
||||
import { Avatar, AvatarFallback } from '../ui/avatar';
|
||||
import { Button } from '../ui/button';
|
||||
import { useAuthStore } from '@/store/workerStore';
|
||||
|
||||
type NavOptionValue = {
|
||||
name: string;
|
||||
link: string;
|
||||
id: number;
|
||||
};
|
||||
|
||||
type NavOption = {
|
||||
name: string;
|
||||
options: NavOptionValue[];
|
||||
};
|
||||
|
||||
const navOptions = [
|
||||
{
|
||||
name: 'Валюты',
|
||||
options: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Просмотреть',
|
||||
link: '/currencies',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Кредитные программы',
|
||||
options: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Просмотреть',
|
||||
link: '/credit-programs',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Сроки',
|
||||
options: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Просмотреть',
|
||||
link: '/periods',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Кладовщики',
|
||||
options: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Просмотреть',
|
||||
link: '/storekeepers',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Вклады',
|
||||
options: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Управление валютами вкладов',
|
||||
link: '/deposit-currencies',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Отчеты',
|
||||
options: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Выгрузить отчеты',
|
||||
link: '/reports',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const Header = (): React.JSX.Element => {
|
||||
const user = useAuthStore((store) => store.user);
|
||||
const logout = useAuthStore((store) => store.logout);
|
||||
const { logout: serverLogout } = useAuthStore();
|
||||
const loggedOut = () => {
|
||||
serverLogout();
|
||||
logout();
|
||||
};
|
||||
const fullName = `${user?.name ?? ''} ${user?.surname ?? ''}`;
|
||||
return (
|
||||
<header className="flex w-full p-2 justify-between">
|
||||
<nav className="text-black">
|
||||
<Menubar className="flex gap-10">
|
||||
{navOptions.map((item) => (
|
||||
<MenuOption item={item} key={item.name} />
|
||||
))}
|
||||
</Menubar>
|
||||
</nav>
|
||||
<div>
|
||||
<ProfileIcon name={fullName || 'Евгений Эгов'} logout={loggedOut} />
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
const MenuOption = ({ item }: { item: NavOption }) => {
|
||||
return (
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger className="">{item.name}</MenubarTrigger>
|
||||
<MenubarContent className="">
|
||||
{item.options.map((x, i) => (
|
||||
<React.Fragment key={x.id}>
|
||||
{i == 1 && item.options.length > 1 && <MenubarSeparator />}
|
||||
<MenubarItem className="">
|
||||
<Link className="" to={x.link}>
|
||||
{x.name}
|
||||
</Link>
|
||||
</MenubarItem>
|
||||
</React.Fragment>
|
||||
))}
|
||||
<MenubarSeparator />
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
);
|
||||
};
|
||||
|
||||
type ProfileIconProps = {
|
||||
name: string;
|
||||
logout: () => void;
|
||||
};
|
||||
|
||||
export const ProfileIcon = ({
|
||||
name,
|
||||
logout,
|
||||
}: ProfileIconProps): React.JSX.Element => {
|
||||
return (
|
||||
<div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Avatar className="h-9 w-9">
|
||||
<AvatarFallback>{name[0]}</AvatarFallback>
|
||||
</Avatar>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56">
|
||||
<DropdownMenuItem className="font-bold text-lg">
|
||||
{name}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem asChild>
|
||||
<Link to="/profile" className="block w-full text-left">
|
||||
Профиль
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem asChild>
|
||||
<Button
|
||||
onClick={logout}
|
||||
variant="outline"
|
||||
className="block w-full text-left"
|
||||
>
|
||||
Выйти
|
||||
</Button>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
58
TheBank/bankui/src/components/layout/ReportSidebar.tsx
Normal file
58
TheBank/bankui/src/components/layout/ReportSidebar.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarGroupContent,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarProvider,
|
||||
SidebarTrigger,
|
||||
} from '@/components/ui/sidebar';
|
||||
|
||||
type SidebarProps = {
|
||||
onWordClick: () => void;
|
||||
onPdfClick: () => void;
|
||||
onExcelClick: () => void;
|
||||
};
|
||||
export const ReportSidebar = ({
|
||||
onWordClick,
|
||||
onExcelClick,
|
||||
onPdfClick,
|
||||
}: SidebarProps): React.JSX.Element => {
|
||||
return (
|
||||
<SidebarProvider className="w-[400px]">
|
||||
<Sidebar variant="floating" collapsible="none">
|
||||
<SidebarContent />
|
||||
<SidebarGroupContent className="">
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild onClick={onWordClick}>
|
||||
<span>
|
||||
<img src="/icons/word.svg" alt="word-icon" />
|
||||
отчет word КЛАДОВЩИКА
|
||||
</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild onClick={onExcelClick}>
|
||||
<span>
|
||||
<img src="/icons/excel.svg" alt="excel-icon" />
|
||||
отчет excel КЛАДОВЩИКА
|
||||
</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild onClick={onPdfClick}>
|
||||
<span className="p-5">
|
||||
<img src="/icons/pdf.svg" alt="pdf-icon" />
|
||||
отчет pdf КЛАДОВЩИКА
|
||||
</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</Sidebar>
|
||||
</SidebarProvider>
|
||||
);
|
||||
};
|
||||
64
TheBank/bankui/src/components/layout/Sidebar.tsx
Normal file
64
TheBank/bankui/src/components/layout/Sidebar.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarGroupContent,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarProvider,
|
||||
SidebarTrigger,
|
||||
} from '@/components/ui/sidebar';
|
||||
|
||||
import { Plus, Pencil } from 'lucide-react';
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
type SidebarProps = {
|
||||
onAddClick: () => void;
|
||||
onEditClick: () => void;
|
||||
};
|
||||
|
||||
const availableTasks = [
|
||||
{
|
||||
name: 'Добавить',
|
||||
link: '',
|
||||
},
|
||||
{
|
||||
name: 'Редактировать',
|
||||
link: '',
|
||||
},
|
||||
];
|
||||
|
||||
export const AppSidebar = ({
|
||||
onAddClick,
|
||||
onEditClick,
|
||||
}: SidebarProps): React.JSX.Element => {
|
||||
return (
|
||||
<SidebarProvider>
|
||||
<Sidebar variant="floating" collapsible="icon">
|
||||
<SidebarTrigger />
|
||||
<SidebarContent />
|
||||
<SidebarGroupContent className="">
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild onClick={onAddClick}>
|
||||
<Link to={availableTasks[0].link}>
|
||||
<Plus />
|
||||
<span>{availableTasks[0].name}</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild onClick={onEditClick}>
|
||||
<Link to={availableTasks[1].link}>
|
||||
<Pencil />
|
||||
<span>{availableTasks[1].name}</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</Sidebar>
|
||||
</SidebarProvider>
|
||||
);
|
||||
};
|
||||
76
TheBank/bankui/src/components/pages/AuthStorekeeper.tsx
Normal file
76
TheBank/bankui/src/components/pages/AuthStorekeeper.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import { useStorekeepers } from '@/hooks/useStorekeepers';
|
||||
import React from 'react';
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from '../ui/tabs';
|
||||
import { RegisterForm } from '../features/RegisterForm';
|
||||
import { LoginForm } from '../features/LoginForm';
|
||||
import { toast } from 'sonner';
|
||||
import type { LoginBindingModel, StorekeeperBindingModel } from '@/types/types';
|
||||
|
||||
type Forms = 'login' | 'register';
|
||||
|
||||
export const AuthStorekeeper = (): React.JSX.Element => {
|
||||
const {
|
||||
createStorekeeper,
|
||||
loginStorekeeper,
|
||||
isLoginError,
|
||||
loginError,
|
||||
isCreateError,
|
||||
} = useStorekeepers();
|
||||
|
||||
const [currentForm, setCurrentForm] = React.useState<Forms>('login');
|
||||
|
||||
const handleRegister = (data: StorekeeperBindingModel) => {
|
||||
createStorekeeper(data, {
|
||||
onSuccess: () => {
|
||||
toast('Регистрация успешна! Войдите в систему.');
|
||||
},
|
||||
onError: (error) => {
|
||||
toast(`Ошибка регистрации: ${error.message}`);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleLogin = (data: LoginBindingModel) => {
|
||||
loginStorekeeper(data);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isLoginError) {
|
||||
toast(`Ошибка входа: ${loginError?.message}`);
|
||||
}
|
||||
if (isCreateError) {
|
||||
toast('Ошибка при регистрации');
|
||||
}
|
||||
}, [isLoginError, loginError, isCreateError]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<main className="flex flex-col justify-center items-center">
|
||||
<div>
|
||||
<Tabs defaultValue="login" className="w-[400px]">
|
||||
<TabsList>
|
||||
<TabsTrigger
|
||||
onClick={() => setCurrentForm('login')}
|
||||
value="login"
|
||||
>
|
||||
Вход
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
onClick={() => setCurrentForm('register')}
|
||||
value="register"
|
||||
>
|
||||
Регистрация
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value={currentForm}>
|
||||
<LoginForm onSubmit={handleLogin} />
|
||||
</TabsContent>
|
||||
<TabsContent value="register">
|
||||
<RegisterForm onSubmit={handleRegister} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
};
|
||||
183
TheBank/bankui/src/components/pages/CreditPrograms.tsx
Normal file
183
TheBank/bankui/src/components/pages/CreditPrograms.tsx
Normal file
@@ -0,0 +1,183 @@
|
||||
import React from 'react';
|
||||
import { AppSidebar } from '../layout/Sidebar';
|
||||
import { useCreditPrograms } from '@/hooks/useCreditPrograms';
|
||||
import { DialogForm } from '../layout/DialogForm';
|
||||
import { DataTable } from '../layout/DataTable';
|
||||
import {
|
||||
CreditProgramFormAdd,
|
||||
CreditProgramFormEdit,
|
||||
} from '../features/CreditProgramForm';
|
||||
import type { CreditProgramBindingModel } from '@/types/types';
|
||||
import type { ColumnDef } from '../layout/DataTable';
|
||||
import { toast } from 'sonner';
|
||||
import { usePeriods } from '@/hooks/usePeriods';
|
||||
import { useStorekeepers } from '@/hooks/useStorekeepers';
|
||||
|
||||
interface CreditProgramTableData extends CreditProgramBindingModel {
|
||||
formattedPeriod: string;
|
||||
storekeeperFullName: string;
|
||||
}
|
||||
|
||||
const columns: ColumnDef<CreditProgramTableData>[] = [
|
||||
{
|
||||
accessorKey: 'id',
|
||||
header: 'ID',
|
||||
},
|
||||
{
|
||||
accessorKey: 'name',
|
||||
header: 'Название',
|
||||
},
|
||||
{
|
||||
accessorKey: 'cost',
|
||||
header: 'Стоимость',
|
||||
},
|
||||
{
|
||||
accessorKey: 'maxCost',
|
||||
header: 'Макс. стоимость',
|
||||
},
|
||||
{
|
||||
accessorKey: 'storekeeperFullName',
|
||||
header: 'Кладовщик',
|
||||
},
|
||||
{
|
||||
accessorKey: 'formattedPeriod',
|
||||
header: 'Период',
|
||||
},
|
||||
];
|
||||
|
||||
export const CreditPrograms = (): React.JSX.Element => {
|
||||
const {
|
||||
isLoading,
|
||||
isError,
|
||||
error,
|
||||
creditPrograms,
|
||||
createCreditProgram,
|
||||
updateCreditProgram,
|
||||
} = useCreditPrograms();
|
||||
const { periods } = usePeriods();
|
||||
const { storekeepers } = useStorekeepers();
|
||||
|
||||
const finalData = React.useMemo(() => {
|
||||
if (!creditPrograms || !periods || !storekeepers) return [];
|
||||
|
||||
return creditPrograms.map((program) => {
|
||||
const period = periods?.find((p) => p.id === program.periodId);
|
||||
const storekeeper = storekeepers?.find(
|
||||
(s) => s.id === program.storekeeperId,
|
||||
);
|
||||
|
||||
const formattedPeriod = period
|
||||
? `${new Date(period.startTime).toLocaleDateString()} - ${new Date(
|
||||
period.endTime,
|
||||
).toLocaleDateString()}`
|
||||
: 'Неизвестный период';
|
||||
|
||||
const storekeeperFullName = storekeeper
|
||||
? [storekeeper.surname, storekeeper.name, storekeeper.middleName]
|
||||
.filter(Boolean)
|
||||
.join(' ') || 'Неизвестный кладовщик'
|
||||
: 'Неизвестный кладовщик';
|
||||
|
||||
return {
|
||||
...program,
|
||||
formattedPeriod,
|
||||
storekeeperFullName,
|
||||
};
|
||||
});
|
||||
}, [creditPrograms, periods, storekeepers]);
|
||||
|
||||
const [isAddDialogOpen, setIsAddDialogOpen] = React.useState<boolean>(false);
|
||||
const [isEditDialogOpen, setIsEditDialogOpen] =
|
||||
React.useState<boolean>(false);
|
||||
const [selectedItem, setSelectedItem] = React.useState<
|
||||
CreditProgramBindingModel | undefined
|
||||
>();
|
||||
|
||||
const handleAdd = (data: CreditProgramBindingModel) => {
|
||||
console.log('add', data);
|
||||
createCreditProgram(data);
|
||||
setIsAddDialogOpen(false);
|
||||
};
|
||||
|
||||
const handleEdit = (data: CreditProgramBindingModel) => {
|
||||
if (selectedItem) {
|
||||
updateCreditProgram({
|
||||
...selectedItem,
|
||||
...data,
|
||||
});
|
||||
console.log('edit', data);
|
||||
setIsEditDialogOpen(false);
|
||||
setSelectedItem(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectItem = (id: string | undefined) => {
|
||||
const item = creditPrograms?.find((cp) => cp.id === id);
|
||||
setSelectedItem(item);
|
||||
};
|
||||
|
||||
const openEditForm = () => {
|
||||
if (!selectedItem) {
|
||||
toast.error('Выберите элемент для редактирования');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsEditDialogOpen(true);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <main className="container mx-auto py-10">Загрузка...</main>;
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<main className="container mx-auto py-10">
|
||||
Ошибка загрузки: {error?.message}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="flex-1 flex relative">
|
||||
<AppSidebar
|
||||
onAddClick={() => {
|
||||
setIsAddDialogOpen(true);
|
||||
}}
|
||||
onEditClick={() => {
|
||||
openEditForm();
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="flex-1 p-4">
|
||||
<DialogForm<CreditProgramBindingModel>
|
||||
title="Форма кредитной программы"
|
||||
description="Добавить новую кредитную программу"
|
||||
isOpen={isAddDialogOpen}
|
||||
onClose={() => setIsAddDialogOpen(false)}
|
||||
onSubmit={handleAdd}
|
||||
>
|
||||
<CreditProgramFormAdd />
|
||||
</DialogForm>
|
||||
{selectedItem && (
|
||||
<DialogForm<CreditProgramBindingModel>
|
||||
title="Форма кредитной программы"
|
||||
description="Изменить кредитную программу"
|
||||
isOpen={isEditDialogOpen}
|
||||
onClose={() => setIsEditDialogOpen(false)}
|
||||
onSubmit={handleEdit}
|
||||
>
|
||||
<CreditProgramFormEdit defaultValues={selectedItem} />
|
||||
</DialogForm>
|
||||
)}
|
||||
<div className="">
|
||||
<DataTable
|
||||
data={finalData}
|
||||
columns={columns}
|
||||
onRowSelected={(id) => handleSelectItem(id)}
|
||||
selectedRow={selectedItem?.id}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
163
TheBank/bankui/src/components/pages/Currencies.tsx
Normal file
163
TheBank/bankui/src/components/pages/Currencies.tsx
Normal file
@@ -0,0 +1,163 @@
|
||||
import React from 'react';
|
||||
import { AppSidebar } from '../layout/Sidebar';
|
||||
import { DialogForm } from '../layout/DialogForm';
|
||||
import { DataTable } from '../layout/DataTable';
|
||||
import { useCurrencies } from '@/hooks/useCurrencies';
|
||||
import { useStorekeepers } from '@/hooks/useStorekeepers';
|
||||
import type { CurrencyBindingModel } from '@/types/types';
|
||||
import type { ColumnDef } from '../layout/DataTable';
|
||||
import { CurrencyFormAdd, CurrencyFormEdit } from '../features/CurrencyForm';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
interface CurrencyTableData extends CurrencyBindingModel {
|
||||
storekeeperName: string;
|
||||
}
|
||||
|
||||
const columns: ColumnDef<CurrencyTableData>[] = [
|
||||
{
|
||||
accessorKey: 'id',
|
||||
header: 'ID',
|
||||
},
|
||||
{
|
||||
accessorKey: 'name',
|
||||
header: 'Название',
|
||||
},
|
||||
{
|
||||
accessorKey: 'abbreviation',
|
||||
header: 'Аббревиатура',
|
||||
},
|
||||
{
|
||||
accessorKey: 'cost',
|
||||
header: 'Стоимость',
|
||||
},
|
||||
{
|
||||
accessorKey: 'storekeeperName',
|
||||
header: 'Кладовщик',
|
||||
},
|
||||
];
|
||||
|
||||
export const Currencies = (): React.JSX.Element => {
|
||||
const {
|
||||
isLoading,
|
||||
isError,
|
||||
error,
|
||||
currencies,
|
||||
createCurrency,
|
||||
updateCurrency,
|
||||
} = useCurrencies();
|
||||
const { storekeepers } = useStorekeepers();
|
||||
|
||||
const finalData = React.useMemo(() => {
|
||||
if (!currencies || !storekeepers) return [];
|
||||
|
||||
return currencies.map((currency) => {
|
||||
const storekeeper = storekeepers.find(
|
||||
(s) => s.id === currency.storekeeperId,
|
||||
);
|
||||
const storekeeperName = storekeeper
|
||||
? [storekeeper.surname, storekeeper.name, storekeeper.middleName]
|
||||
.filter(Boolean)
|
||||
.join(' ') || 'Неизвестный кладовщик'
|
||||
: 'Неизвестный кладовщик';
|
||||
|
||||
return {
|
||||
...currency,
|
||||
storekeeperName,
|
||||
};
|
||||
});
|
||||
}, [currencies, storekeepers]);
|
||||
|
||||
const [isAddDialogOpen, setIsAddDialogOpen] = React.useState<boolean>(false);
|
||||
const [isEditDialogOpen, setIsEditDialogOpen] =
|
||||
React.useState<boolean>(false);
|
||||
const [selectedItem, setSelectedItem] = React.useState<
|
||||
CurrencyBindingModel | undefined
|
||||
>();
|
||||
|
||||
const handleAdd = (data: CurrencyBindingModel) => {
|
||||
console.log('add', data);
|
||||
createCurrency(data);
|
||||
setIsAddDialogOpen(false);
|
||||
};
|
||||
|
||||
const handleEdit = (data: CurrencyBindingModel) => {
|
||||
if (selectedItem) {
|
||||
updateCurrency({
|
||||
...selectedItem,
|
||||
...data,
|
||||
});
|
||||
console.log('edit', data);
|
||||
setIsEditDialogOpen(false);
|
||||
setSelectedItem(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectItem = (id: string | undefined) => {
|
||||
const item = currencies?.find((c) => c.id === id);
|
||||
setSelectedItem(item);
|
||||
};
|
||||
|
||||
const openEditForm = () => {
|
||||
if (!selectedItem) {
|
||||
toast.error('Выберите элемент для редактирования');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsEditDialogOpen(true);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <main className="container mx-auto py-10">Загрузка...</main>;
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<main className="container mx-auto py-10">
|
||||
Ошибка загрузки: {error?.message}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="flex-1 flex relative">
|
||||
<AppSidebar
|
||||
onAddClick={() => {
|
||||
setIsAddDialogOpen(true);
|
||||
}}
|
||||
onEditClick={() => {
|
||||
openEditForm();
|
||||
}}
|
||||
/>
|
||||
<div className="flex-1 p-4">
|
||||
<DialogForm<CurrencyBindingModel>
|
||||
title="Форма валюты"
|
||||
description="Добавьте новую валюту"
|
||||
isOpen={isAddDialogOpen}
|
||||
onClose={() => setIsAddDialogOpen(false)}
|
||||
onSubmit={handleAdd}
|
||||
>
|
||||
<CurrencyFormAdd onSubmit={handleAdd} />
|
||||
</DialogForm>
|
||||
{selectedItem && (
|
||||
<DialogForm<CurrencyBindingModel>
|
||||
title="Форма валюты"
|
||||
description="Измените валюту"
|
||||
isOpen={isEditDialogOpen}
|
||||
onClose={() => setIsEditDialogOpen(false)}
|
||||
onSubmit={handleEdit}
|
||||
>
|
||||
<CurrencyFormEdit defaultValues={selectedItem} />
|
||||
</DialogForm>
|
||||
)}
|
||||
<div>
|
||||
<DataTable
|
||||
data={finalData}
|
||||
columns={columns}
|
||||
onRowSelected={(id) => handleSelectItem(id)}
|
||||
selectedRow={selectedItem?.id}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
367
TheBank/bankui/src/components/pages/DepositCurrencyManager.tsx
Normal file
367
TheBank/bankui/src/components/pages/DepositCurrencyManager.tsx
Normal file
@@ -0,0 +1,367 @@
|
||||
import React from 'react';
|
||||
import { useDeposits } from '@/hooks/useDeposits';
|
||||
import { useCurrencies } from '@/hooks/useCurrencies';
|
||||
import { useClerks } from '@/hooks/useClerks';
|
||||
import { DataTable, type ColumnDef } from '../layout/DataTable';
|
||||
import { AppSidebar } from '../layout/Sidebar';
|
||||
import { DialogForm } from '../layout/DialogForm';
|
||||
import { toast } from 'sonner';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type {
|
||||
DepositBindingModel,
|
||||
DepositCurrencyBindingModel,
|
||||
} from '@/types/types';
|
||||
|
||||
type DepositRowData = DepositBindingModel & {
|
||||
clerkName: string;
|
||||
currenciesDisplay: string;
|
||||
};
|
||||
|
||||
const columns: ColumnDef<DepositRowData>[] = [
|
||||
{
|
||||
accessorKey: 'id',
|
||||
header: 'ID',
|
||||
},
|
||||
{
|
||||
accessorKey: 'interestRate',
|
||||
header: 'Процентная ставка',
|
||||
},
|
||||
{
|
||||
accessorKey: 'cost',
|
||||
header: 'Стоимость',
|
||||
},
|
||||
{
|
||||
accessorKey: 'period',
|
||||
header: 'Срок вклада',
|
||||
},
|
||||
{
|
||||
accessorKey: 'clerkName',
|
||||
header: 'Клерк',
|
||||
},
|
||||
{
|
||||
accessorKey: 'currenciesDisplay',
|
||||
header: 'Валюты',
|
||||
},
|
||||
];
|
||||
|
||||
type FormValues = {
|
||||
currencyIds: string[];
|
||||
};
|
||||
|
||||
const schema = z.object({
|
||||
currencyIds: z.array(z.string()),
|
||||
});
|
||||
|
||||
const DepositCurrencyForm = ({
|
||||
onSubmit,
|
||||
defaultValues,
|
||||
}: {
|
||||
onSubmit: (data: { currencyIds: string[] }) => void;
|
||||
defaultValues: Partial<DepositBindingModel>;
|
||||
}): React.JSX.Element => {
|
||||
const { currencies } = useCurrencies();
|
||||
|
||||
const initialCurrencyIds = React.useMemo(
|
||||
() =>
|
||||
defaultValues?.depositCurrencies
|
||||
?.map((dc) => dc.currencyId)
|
||||
.filter((id): id is string => !!id) || [],
|
||||
[defaultValues?.depositCurrencies],
|
||||
);
|
||||
|
||||
const form = useForm<FormValues>({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: {
|
||||
currencyIds: initialCurrencyIds,
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
if (defaultValues) {
|
||||
form.reset({
|
||||
currencyIds: initialCurrencyIds,
|
||||
});
|
||||
}
|
||||
}, [defaultValues, form, initialCurrencyIds]);
|
||||
|
||||
const handleSubmit = (data: FormValues) => {
|
||||
onSubmit(data);
|
||||
};
|
||||
|
||||
const selectedCurrencyIds = form.watch('currencyIds') || [];
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(handleSubmit)}
|
||||
className="space-y-4 max-w-md mx-auto p-4"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="currencyIds"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Валюты</FormLabel>
|
||||
<Select
|
||||
onValueChange={(value) => {
|
||||
const currentValues = field.value || [];
|
||||
if (!currentValues.includes(value)) {
|
||||
field.onChange([...currentValues, value]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Выберите валюты" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{currencies?.map((currency) => (
|
||||
<SelectItem
|
||||
key={currency.id}
|
||||
value={currency.id || ''}
|
||||
className={cn(
|
||||
selectedCurrencyIds.includes(currency.id || '') &&
|
||||
'bg-muted',
|
||||
)}
|
||||
>
|
||||
{`${currency.name} (${currency.abbreviation})`}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{selectedCurrencyIds.map((currencyId) => {
|
||||
const currency = currencies?.find((c) => c.id === currencyId);
|
||||
return (
|
||||
<div
|
||||
key={currencyId}
|
||||
className="bg-muted px-2 py-1 rounded-md flex items-center gap-1"
|
||||
>
|
||||
<span>
|
||||
{currency
|
||||
? `${currency.name} (${currency.abbreviation})`
|
||||
: currencyId}
|
||||
</span>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-4 w-4 rounded-full"
|
||||
onClick={() => {
|
||||
const newValues = selectedCurrencyIds.filter(
|
||||
(id) => id !== currencyId,
|
||||
);
|
||||
form.setValue('currencyIds', newValues);
|
||||
}}
|
||||
>
|
||||
×
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit" className="w-full">
|
||||
Сохранить
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export const DepositCurrencyManager = (): React.JSX.Element => {
|
||||
const {
|
||||
deposits,
|
||||
isLoading: isDepositsLoading,
|
||||
error: depositsError,
|
||||
updateDeposit,
|
||||
} = useDeposits();
|
||||
|
||||
const { currencies, isLoading: isCurrenciesLoading } = useCurrencies();
|
||||
const { clerks, isLoading: isClerksLoading } = useClerks();
|
||||
|
||||
const [isEditDialogOpen, setIsEditDialogOpen] =
|
||||
React.useState<boolean>(false);
|
||||
const [selectedItem, setSelectedItem] = React.useState<
|
||||
DepositBindingModel | undefined
|
||||
>();
|
||||
|
||||
const finalData = React.useMemo(() => {
|
||||
if (!deposits || !currencies || !clerks) return [];
|
||||
|
||||
return deposits.map((deposit) => {
|
||||
// Находим клерка по ID
|
||||
const clerk = clerks.find((c) => c.id === deposit.clerkId);
|
||||
|
||||
// Формирование списка валют
|
||||
const currenciesDisplay =
|
||||
deposit.depositCurrencies
|
||||
?.map((dc) => {
|
||||
const currency = currencies?.find((c) => c.id === dc.currencyId);
|
||||
return currency
|
||||
? `${currency.name} (${currency.abbreviation})`
|
||||
: dc.currencyId;
|
||||
})
|
||||
.join(', ') || 'Нет валют';
|
||||
|
||||
return {
|
||||
...deposit,
|
||||
clerkName: clerk
|
||||
? `${clerk.name} ${clerk.surname}`
|
||||
: 'Неизвестный клерк',
|
||||
currenciesDisplay,
|
||||
};
|
||||
});
|
||||
}, [deposits, currencies, clerks]);
|
||||
|
||||
const handleEdit = (data: { currencyIds: string[] }) => {
|
||||
if (selectedItem) {
|
||||
// Проверка на дублирование валют
|
||||
const uniqueCurrencyIds = new Set(data.currencyIds);
|
||||
if (uniqueCurrencyIds.size !== data.currencyIds.length) {
|
||||
toast.error(
|
||||
'Обнаружены дублирующиеся валюты. Пожалуйста, убедитесь что каждая валюта выбрана только один раз.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Формируем массив связей, сохраняя существующие ID где это возможно
|
||||
const depositCurrencies: DepositCurrencyBindingModel[] =
|
||||
data.currencyIds.map((currencyId) => {
|
||||
// Ищем существующую связь с этой валютой
|
||||
const existingRelation = selectedItem.depositCurrencies?.find(
|
||||
(dc) => dc.currencyId === currencyId,
|
||||
);
|
||||
|
||||
// Если связь уже существует, возвращаем её с оригинальным ID
|
||||
if (existingRelation) {
|
||||
return { ...existingRelation };
|
||||
}
|
||||
|
||||
// Если это новая связь, создаем объект без ID
|
||||
return {
|
||||
currencyId,
|
||||
depositId: selectedItem.id,
|
||||
};
|
||||
});
|
||||
|
||||
console.log('Обновляем депозит с данными:', {
|
||||
...selectedItem,
|
||||
depositCurrencies,
|
||||
});
|
||||
|
||||
// Обновляем вклад, сохраняя все оригинальные поля и связи
|
||||
updateDeposit({
|
||||
...selectedItem, // Сохраняем все существующие поля
|
||||
depositCurrencies, // Обновляем только связи с валютами
|
||||
});
|
||||
|
||||
setIsEditDialogOpen(false);
|
||||
setSelectedItem(undefined);
|
||||
toast.success('Связи валют с вкладом успешно обновлены');
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectItem = (id: string | undefined) => {
|
||||
const item = deposits?.find((p) => p.id === id);
|
||||
if (item) {
|
||||
setSelectedItem({
|
||||
...item,
|
||||
});
|
||||
} else {
|
||||
setSelectedItem(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
const openEditForm = () => {
|
||||
if (!selectedItem) {
|
||||
toast('Выберите вклад для добавления валют');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsEditDialogOpen(true);
|
||||
};
|
||||
|
||||
if (isDepositsLoading || isCurrenciesLoading || isClerksLoading) {
|
||||
return <main className="container mx-auto py-10">Загрузка...</main>;
|
||||
}
|
||||
|
||||
if (depositsError) {
|
||||
return (
|
||||
<main className="container mx-auto py-10">
|
||||
Ошибка загрузки данных: {depositsError.message}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="flex-1 flex relative">
|
||||
<AppSidebar
|
||||
onAddClick={() => {
|
||||
toast(
|
||||
'Кладовщик не может создавать вклады, только связывать их с валютами',
|
||||
);
|
||||
}}
|
||||
onEditClick={() => {
|
||||
openEditForm();
|
||||
}}
|
||||
/>
|
||||
<div className="flex-1 p-4">
|
||||
{selectedItem && (
|
||||
<DialogForm
|
||||
title="Управление валютами вклада"
|
||||
description="Выберите валюты для связи с вкладом"
|
||||
isOpen={isEditDialogOpen}
|
||||
onClose={() => setIsEditDialogOpen(false)}
|
||||
onSubmit={handleEdit}
|
||||
>
|
||||
<DepositCurrencyForm
|
||||
onSubmit={handleEdit}
|
||||
defaultValues={selectedItem}
|
||||
/>
|
||||
</DialogForm>
|
||||
)}
|
||||
<div className="mb-4">
|
||||
<h2 className="text-2xl font-bold">
|
||||
Управление связями вкладов и валют
|
||||
</h2>
|
||||
<p className="text-muted-foreground">
|
||||
Кладовщик может связывать существующие вклады с валютами, но не
|
||||
может создавать новые вклады.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<DataTable
|
||||
data={finalData}
|
||||
columns={columns}
|
||||
onRowSelected={(id) => handleSelectItem(id)}
|
||||
selectedRow={selectedItem?.id}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
163
TheBank/bankui/src/components/pages/Periods.tsx
Normal file
163
TheBank/bankui/src/components/pages/Periods.tsx
Normal file
@@ -0,0 +1,163 @@
|
||||
import React from 'react';
|
||||
import { AppSidebar } from '../layout/Sidebar';
|
||||
import { DialogForm } from '../layout/DialogForm';
|
||||
import { DataTable } from '../layout/DataTable';
|
||||
import { usePeriods } from '@/hooks/usePeriods';
|
||||
import { useStorekeepers } from '@/hooks/useStorekeepers';
|
||||
import type { PeriodBindingModel } from '@/types/types';
|
||||
import type { ColumnDef } from '../layout/DataTable';
|
||||
import { PeriodFormAdd, PeriodFormEdit } from '../features/PeriodForm';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
interface PeriodTableData extends PeriodBindingModel {
|
||||
storekeeperName: string;
|
||||
}
|
||||
|
||||
const columns: ColumnDef<PeriodTableData>[] = [
|
||||
{
|
||||
accessorKey: 'id',
|
||||
header: 'ID',
|
||||
},
|
||||
{
|
||||
accessorKey: 'startTime',
|
||||
header: 'Время начала',
|
||||
renderCell: (item) => new Date(item.startTime).toLocaleDateString(),
|
||||
},
|
||||
{
|
||||
accessorKey: 'endTime',
|
||||
header: 'Время окончания',
|
||||
renderCell: (item) => new Date(item.endTime).toLocaleDateString(),
|
||||
},
|
||||
{
|
||||
accessorKey: 'storekeeperName',
|
||||
header: 'Кладовщик',
|
||||
},
|
||||
];
|
||||
|
||||
export const Periods = (): React.JSX.Element => {
|
||||
const { isLoading, isError, error, periods, createPeriod, updatePeriod } =
|
||||
usePeriods();
|
||||
const { storekeepers } = useStorekeepers();
|
||||
|
||||
const finalData = React.useMemo(() => {
|
||||
if (!periods || !storekeepers) return [];
|
||||
|
||||
return periods.map((period) => {
|
||||
const storekeeper = storekeepers.find(
|
||||
(s) => s.id === period.storekeeperId,
|
||||
);
|
||||
const storekeeperName = storekeeper
|
||||
? [storekeeper.surname, storekeeper.name, storekeeper.middleName]
|
||||
.filter(Boolean)
|
||||
.join(' ') || 'Неизвестный кладовщик'
|
||||
: 'Неизвестный кладовщик';
|
||||
|
||||
return {
|
||||
...period,
|
||||
storekeeperName,
|
||||
};
|
||||
});
|
||||
}, [periods, storekeepers]);
|
||||
|
||||
const [isAddDialogOpen, setIsAddDialogOpen] = React.useState<boolean>(false);
|
||||
const [isEditDialogOpen, setIsEditDialogOpen] =
|
||||
React.useState<boolean>(false);
|
||||
const [selectedItem, setSelectedItem] = React.useState<
|
||||
PeriodBindingModel | undefined
|
||||
>();
|
||||
|
||||
const handleAdd = (data: PeriodBindingModel) => {
|
||||
createPeriod(data);
|
||||
setIsAddDialogOpen(false);
|
||||
};
|
||||
|
||||
const handleEdit = (data: PeriodBindingModel) => {
|
||||
if (selectedItem) {
|
||||
updatePeriod({
|
||||
...selectedItem,
|
||||
...data,
|
||||
});
|
||||
setIsEditDialogOpen(false);
|
||||
setSelectedItem(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectItem = (id: string | undefined) => {
|
||||
const item = periods?.find((p) => p.id === id);
|
||||
if (item) {
|
||||
setSelectedItem({
|
||||
...item,
|
||||
startTime: new Date(item.startTime),
|
||||
endTime: new Date(item.endTime),
|
||||
});
|
||||
} else {
|
||||
setSelectedItem(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
const openEditForm = () => {
|
||||
if (!selectedItem) {
|
||||
toast.error('Выберите элемент для редактирования');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsEditDialogOpen(true);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <main className="container mx-auto py-10">Загрузка...</main>;
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<main className="container mx-auto py-10">
|
||||
Ошибка загрузки: {error?.message}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="flex-1 flex relative">
|
||||
<AppSidebar
|
||||
onAddClick={() => {
|
||||
setIsAddDialogOpen(true);
|
||||
}}
|
||||
onEditClick={() => {
|
||||
openEditForm();
|
||||
}}
|
||||
/>
|
||||
<div className="flex-1 p-4">
|
||||
{!selectedItem && (
|
||||
<DialogForm<PeriodBindingModel>
|
||||
title="Форма сроков"
|
||||
description="Добавить сроки"
|
||||
isOpen={isAddDialogOpen}
|
||||
onClose={() => setIsAddDialogOpen(false)}
|
||||
onSubmit={handleAdd}
|
||||
>
|
||||
<PeriodFormAdd />
|
||||
</DialogForm>
|
||||
)}
|
||||
{selectedItem && (
|
||||
<DialogForm<PeriodBindingModel>
|
||||
title="Форма сроков"
|
||||
description="Изменить сроки"
|
||||
isOpen={isEditDialogOpen}
|
||||
onClose={() => setIsEditDialogOpen(false)}
|
||||
onSubmit={handleEdit}
|
||||
>
|
||||
<PeriodFormEdit defaultValues={selectedItem} />
|
||||
</DialogForm>
|
||||
)}
|
||||
<div>
|
||||
<DataTable
|
||||
data={finalData}
|
||||
columns={columns}
|
||||
onRowSelected={(id) => handleSelectItem(id)}
|
||||
selectedRow={selectedItem?.id}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
38
TheBank/bankui/src/components/pages/Profile.tsx
Normal file
38
TheBank/bankui/src/components/pages/Profile.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import { useAuthStore } from '@/store/workerStore';
|
||||
import { ProfileForm } from '../features/ProfileForm';
|
||||
import type { StorekeeperBindingModel } from '@/types/types';
|
||||
import { useStorekeepers } from '@/hooks/useStorekeepers';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export const Profile = (): React.JSX.Element => {
|
||||
const { user, updateUser } = useAuthStore();
|
||||
const { updateStorekeeper, isUpdateError, updateError } = useStorekeepers();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isUpdateError) {
|
||||
toast(updateError?.message);
|
||||
}
|
||||
}, [isUpdateError, updateError]);
|
||||
|
||||
if (!user) {
|
||||
return (
|
||||
<main className="container mx-auto py-10">
|
||||
Загрузка данных пользователя...
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
const handleUpdate = (data: Partial<StorekeeperBindingModel>) => {
|
||||
console.log(data);
|
||||
updateUser(data);
|
||||
updateStorekeeper(data);
|
||||
};
|
||||
|
||||
return (
|
||||
<main className="container mx-auto py-10">
|
||||
<h1 className="text-2xl font-bold mb-6">Профиль пользователя</h1>
|
||||
<ProfileForm defaultValues={user} onSubmit={handleUpdate} />
|
||||
</main>
|
||||
);
|
||||
};
|
||||
20
TheBank/bankui/src/components/pages/Reports.tsx
Normal file
20
TheBank/bankui/src/components/pages/Reports.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import { ReportSidebar } from '../layout/ReportSidebar';
|
||||
import { ReportViewer } from '../features/ReportViewer';
|
||||
|
||||
export type SelectedReport = 'word' | 'pdf' | 'excel' | undefined;
|
||||
|
||||
export const Reports = (): React.JSX.Element => {
|
||||
const [selectedReport, setSelectedReport] = React.useState<SelectedReport>();
|
||||
|
||||
return (
|
||||
<main className="flex">
|
||||
<ReportSidebar
|
||||
onWordClick={() => setSelectedReport('word')}
|
||||
onPdfClick={() => setSelectedReport('pdf')}
|
||||
onExcelClick={() => setSelectedReport('excel')}
|
||||
/>
|
||||
<ReportViewer selectedReport={selectedReport} />
|
||||
</main>
|
||||
);
|
||||
};
|
||||
63
TheBank/bankui/src/components/pages/Storekeepers.tsx
Normal file
63
TheBank/bankui/src/components/pages/Storekeepers.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
import { DataTable } from '../layout/DataTable';
|
||||
import type { ColumnDef } from '../layout/DataTable';
|
||||
import { useStorekeepers } from '@/hooks/useStorekeepers';
|
||||
import type { StorekeeperBindingModel } from '@/types/types';
|
||||
|
||||
const columns: ColumnDef<StorekeeperBindingModel>[] = [
|
||||
{
|
||||
accessorKey: 'id',
|
||||
header: 'ID',
|
||||
},
|
||||
{
|
||||
accessorKey: 'name',
|
||||
header: 'Имя',
|
||||
},
|
||||
{
|
||||
accessorKey: 'surname',
|
||||
header: 'Фамилия',
|
||||
},
|
||||
{
|
||||
accessorKey: 'middleName',
|
||||
header: 'Отчество',
|
||||
},
|
||||
{
|
||||
accessorKey: 'login',
|
||||
header: 'Логин',
|
||||
},
|
||||
{
|
||||
accessorKey: 'email',
|
||||
header: 'Email',
|
||||
},
|
||||
{
|
||||
accessorKey: 'phoneNumber',
|
||||
header: 'Телефон',
|
||||
},
|
||||
];
|
||||
|
||||
export const Storekeepers = (): React.JSX.Element => {
|
||||
const { storekeepers, isLoading, error } = useStorekeepers();
|
||||
|
||||
if (isLoading) {
|
||||
return <main className="container mx-auto py-10">Загрузка...</main>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<main className="container mx-auto py-10">
|
||||
Ошибка загрузки данных: {error.message}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="container mx-auto py-10">
|
||||
<h1 className="text-2xl font-bold mb-6">Кладовщики</h1>
|
||||
<DataTable
|
||||
data={storekeepers || []}
|
||||
columns={columns}
|
||||
onRowSelected={console.log}
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
51
TheBank/bankui/src/components/ui/avatar.tsx
Normal file
51
TheBank/bankui/src/components/ui/avatar.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import * as React from "react"
|
||||
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Avatar({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
|
||||
return (
|
||||
<AvatarPrimitive.Root
|
||||
data-slot="avatar"
|
||||
className={cn(
|
||||
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function AvatarImage({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
|
||||
return (
|
||||
<AvatarPrimitive.Image
|
||||
data-slot="avatar-image"
|
||||
className={cn("aspect-square size-full", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function AvatarFallback({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
|
||||
return (
|
||||
<AvatarPrimitive.Fallback
|
||||
data-slot="avatar-fallback"
|
||||
className={cn(
|
||||
"bg-muted flex size-full items-center justify-center rounded-full",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Avatar, AvatarImage, AvatarFallback }
|
||||
59
TheBank/bankui/src/components/ui/button.tsx
Normal file
59
TheBank/bankui/src/components/ui/button.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||
outline:
|
||||
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
||||
ghost:
|
||||
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
||||
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
||||
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
||||
icon: "size-9",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
function Button({
|
||||
className,
|
||||
variant,
|
||||
size,
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<"button"> &
|
||||
VariantProps<typeof buttonVariants> & {
|
||||
asChild?: boolean
|
||||
}) {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
|
||||
return (
|
||||
<Comp
|
||||
data-slot="button"
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Button, buttonVariants }
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user