# AlfaToad.DataSetCRUDS

NuGet-пакет готовых **форм добавления и редактирования** для контролов [AlfaToad.CustomControls](https://www.nuget.org/packages/AlfaToad.CustomControls).

Достаточно указать `DataSet`, имя таблицы и делегат сохранения — CRUD для `ScrollableDetailPanel` и `ScrollableOrderPanel` работает автоматически.

## Возможности

| Компонент | Назначение |
|-----------|------------|
| `DetailRecordEditForm` | Модальная форма добавления/редактирования записи каталога |
| `OrderRecordEditForm` | Модальная форма добавления/редактирования записи заказа |
| `DetailPanelCrudBinder` | Подключает `ScrollableDetailPanel` к форме и сохранению |
| `OrderPanelCrudBinder` | Подключает `ScrollableOrderPanel` к форме и сохранению |
| `DataSetCrudHost` | Единая точка настройки обеих панелей |
| `DetailRecordFieldMap` | Сопоставление имён столбцов каталога |
| `OrderRecordFieldMap` | Сопоставление имён столбцов заказов |
| `DataSetCrudOptions` | Оформление, папка изображений, ресайз фото, ConnectionString, ClientUsersTable, валидация удаления, сохранение |
| `AppDialog` | Единые окна: ошибка / предупреждение / информация / подтверждение |
| `ViewDataTablePersistence` | **Insert/Update/Delete для view без TableAdapter.Update** |
| `ViewPersistenceProfile` | Профиль SQL: `NormalizedCatalogView`, `NormalizedOrdersView` |

### Формы редактирования

- Разметка и код в **одном файле** (без отдельного `Designer.cs`)
- Палитра `AppearancePalette` из CustomControls
- Валидация обязательных полей и диапазонов (цена, скидка, количество)
- Предпросмотр изображения из `ImagesFolder`
- ComboBox `DropDownList` с placeholder «— выберите —» для категории, производителя, поставщика, единицы измерения, статуса и клиента заказа
- NumericUpDown для цены, количества и скидки
- Валидация: цена ≥ 0 и не более 2 знаков после запятой; количество ≥ 0; скидка 0–100
- Кнопки «Обзор…» и «Очистить» — выбор и удаление изображения
- Автономер заказа: MAX + 1 при добавлении, readonly при редактировании

### Биндеры

- Автоматическая подписка на `RecordAddRequested`, `RecordEditRequested`, `RecordDeleted`
- Вызов `EndEditSession()` после закрытия формы
- Обновление списка через `RefreshRows()` после сохранения
- Обработка ошибок сохранения в БД через `PersistChanges`

## Установка

```bash
dotnet add package AlfaToad.DataSetCRUDS --version 1.0.5
```

```xml
<PackageReference Include="AlfaToad.DataSetCRUDS" Version="1.0.5" />
```

Зависимость: `AlfaToad.CustomControls` ≥ 4.2.4

## Сохранение view без TableAdapter.Update (глобальное решение)

Visual Studio **не генерирует** `Update()` для представлений (`DbObjectType="View"`).  
Пакет решает это на уровне NuGet: достаточно создать Typed DataSet с **одним view** и использовать только `Fill()`.

```csharp
using AlfaToad.DataSetCRUDS.Configuration;
using AlfaToad.DataSetCRUDS.Persistence;

// DataSet с view каталога — только Fill(), Update() в TableAdapter не нужен
// ViewPersistenceProfile.NormalizedCatalogView — INSERT/UPDATE/DELETE в Product + справочники
var persistCatalog = ViewDataTablePersistence.CreatePersistAction(
    dataSet,
    "vCatalog",
    connectionString,
    ViewPersistenceProfile.NormalizedCatalogView,
    settings.DetailFields);

DetailPanelCrudBinder.WireUp(panel, dataSet, "vCatalog", persistCatalog, settings.DetailFields);
```

Для заказов:

```csharp
// ViewPersistenceProfile.NormalizedOrdersView — INSERT/UPDATE/DELETE в dbo.[Order] + OrderItem;
// ClientUserId передаётся параметром @ClientUserId (поиск по FullName в dbo.[User])
var persistOrders = ViewDataTablePersistence.CreatePersistAction(
    dataSet,
    "vOrders",
    connectionString,
    ViewPersistenceProfile.NormalizedOrdersView,
    orderFields: settings.OrderFields);
```

`ViewDataTablePersistence.ApplyChanges` выполняет INSERT/UPDATE/DELETE в таблицы `Product` / `[Order]` / `OrderItem`,  
переводя текстовые поля view (Поставщик, Категория, Статус…) в ID справочников.

С **AlfaToad.DefaultScreens** — одна строка:

```csharp
var data = new ApplicationDataContext { /* DataSet, Fill-делегаты */ }
    .WithViewDataPersistence(connectionString, settings);
```

**Ручное редактирование `.xsd` не требуется.**

## Пример `Program.cs` (полный сценарий с AlfaToad.DefaultScreens)

См. также [AlfaToad.DefaultScreens README](https://www.nuget.org/packages/AlfaToad.DefaultScreens) — ниже тот же шаблон с комментариями к полям пакета:

```csharp
using System;
using System.Drawing;
using System.Windows.Forms;
using AlfaToad.CustomControls.Theme;
using AlfaToad.DataSetCRUDS.Configuration;
using AlfaToad.DefaultScreens.Configuration;
using AlfaToad.DefaultScreens.Navigation;
using YourApp;                                    // ваш Typed DataSet (MirIgryshkiDataSet)
using YourApp.YourDataSetTableAdapters;

namespace YourApp
{
    internal static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            // ═══════════════════════════════════════════════════════════════
            // ШАГ 1. DataSet и TableAdapter-ы
            // Для view достаточно Fill() — Insert/Update/Delete делает пакет
            // ═══════════════════════════════════════════════════════════════
            var dataSet = new MirIgryshkiDataSet(); // замените на ваш Typed DataSet
            var tovarTableAdapter = new vCatalogTableAdapter();
            var ordersTableAdapter = new vOrdersTableAdapter();
            var usersTableAdapter = new vw_UsersLoginTableAdapter();

            var connectionString = "Server=YOUR_SERVER;Database=YOUR_DATABASE;Trusted_Connection=True;";

            // ═══════════════════════════════════════════════════════════════
            // ШАГ 2. ApplicationScreenSettings
            // ═══════════════════════════════════════════════════════════════
            var settings = ApplicationScreenSettings.CreateDefault();
            settings.BackMenuText = "Назад";
            settings.ImagesFolder = @"C:\Path\To\Your\Resources\import";
            settings.ImageResizeWidth = 300;
            settings.ImageResizeHeight = 200;
            settings.ApplyResourcePaths();

            // CreateCatalogCrudOptions() задаёт AutoResizeImages, DeleteImageFileOnRemove,
            // DeleteOldImageOnReplace, ConnectionString, ImageResizeWidth/Height
            // CreateOrderCrudOptions(clientUsers, connectionString) — ClientUsersTable для ComboBox клиента

            settings.DetailFields = new DetailRecordFieldMap { /* столбцы каталога */ };
            settings.OrderFields = new OrderRecordFieldMap
            {
                ClientNameField = "ФИО авторизированного клиента" // ComboBox на форме заказа
            };

            // ═══════════════════════════════════════════════════════════════
            // ШАГ 6. ApplicationDataContext + WithViewDataPersistence
            // ViewPersistenceProfile.NormalizedCatalogView / NormalizedOrdersView
            // ═══════════════════════════════════════════════════════════════
            var data = new ApplicationDataContext
            {
                CatalogDataSet = dataSet,
                CatalogTableName = "vCatalog",
                OrdersDataSet = dataSet,
                OrdersTableName = "vOrders",
                UsersDataSet = dataSet,
                UsersTableName = "vw_UsersLogin",
                LoadUsers = () => usersTableAdapter.Fill(dataSet.vw_UsersLogin),
                LoadCatalog = () => tovarTableAdapter.Fill(dataSet.vCatalog),
                LoadOrders = () => ordersTableAdapter.Fill(dataSet.vOrders),
            }
            .WithViewDataPersistence(connectionString, settings);

            new ApplicationNavigator(settings, data).Start();
        }
    }
}
```

## Быстрый старт — каталог

```csharp
using AlfaToad.CustomControls.Controls;
using AlfaToad.DataSetCRUDS.Integration;

var panel = new ScrollableDetailPanel { Dock = DockStyle.Fill };
panel.AllowAdd = panel.AllowEdit = panel.AllowDelete = true;

DetailPanelCrudBinder.WireUp(
    panel,
    catalogDataSet,
    "CatalogItems",
    () => catalogTableAdapter.Update(catalogDataSet),
    options: new DataSetCrudOptions
    {
        ImagesFolder = @"C:\Path\To\Your\Resources\images",
        AutoResizeImages = true,
        DeleteImageFileOnRemove = true,
        DeleteOldImageOnReplace = true,
        ConnectionString = connectionString,
        ImageResizeWidth = 300,
        ImageResizeHeight = 200,
        PersistChanges = () => catalogTableAdapter.Update(catalogDataSet)
    });
```

## Быстрый старт — заказы

```csharp
using AlfaToad.DataSetCRUDS.Integration;

OrderPanelCrudBinder.WireUp(
    ordersPanel,
    ordersDataSet,
    "Orders",
    () => ordersTableAdapter.Update(ordersDataSet),
    orderFields,
    new DataSetCrudOptions
    {
        ClientUsersTable = clientUsersTable, // UserId + FullName, роль = Client
        DefaultClientName = "Иванов Иван Иванович",
        ConnectionString = connectionString
    });
```

## Обе панели через DataSetCrudHost

```csharp
new DataSetCrudHost()
    .WireDetailPanel(detailPanel, catalogDataSet, "CatalogItems", () => taCatalog.Update(catalogDataSet))
    .WireOrderPanel(orderPanel, ordersDataSet, "Orders", () => taOrders.Update(ordersDataSet));
```

## Настройка имён столбцов

Если ваши столбцы в БД имеют другие имена, задайте карту полей:

```csharp
var fields = new DetailRecordFieldMap
{
    PrimaryKeyField = "Article",
    NameField = "Наименование товара",
    CategoryField = "Категория товара",
    ImageField = "Фото",
    PriceField = "Цена",
    StockField = "Кол-во на складе",
    DiscountField = "Действующая скидка"
};

DetailPanelCrudBinder.WireUp(panel, dataSet, "vCatalog", persist, fields);
```

## Валидация перед сохранением

```csharp
var options = new DataSetCrudOptions
{
    ValidateBeforeSave = row =>
    {
        var key = row["RecordId"]?.ToString();
        return IsUsedInOrders(key) ? "Запись используется в связанных данных." : null;
    },
    PersistChanges = () => tableAdapter.Update(dataSet)
};
```

## Пошаговая настройка (без WireUp)

```csharp
var binder = new DetailPanelCrudBinder(panel)
    .Configure(dataSet, "CatalogItems", () => ta.Update(dataSet))
    .Attach();

// При закрытии формы:
binder.Detach();
```

## Платформы

- .NET Framework 4.8
- .NET 8 (Windows)
