Bläddra i källkod

add edit product

Евгений Колесников 3 år sedan
förälder
incheckning
1952e5c645
4 ändrade filer med 320 tillägg och 4 borttagningar
  1. 2 2
      articles/cs_coloring2.md
  2. 316 0
      articles/cs_edit_product2.md
  3. 1 1
      articles/cs_pagination2.md
  4. 1 1
      readme.md

+ 2 - 2
articles/cs_coloring2.md

@@ -3,7 +3,7 @@
 </a></td><td style="width: 20%;">
 <a href="../readme.md">Содержание
 </a></td><td style="width: 40%;">
-<a href="../articles/cs_edit_product.md">Создание, изменение продукции
+<a href="../articles/cs_edit_product2.md">Создание, изменение продукции
 </a></td><tr></table>
 
 # Подсветка элементов по условию. Дополнительные выборки.
@@ -325,5 +325,5 @@ if ((bool)NewWindow.ShowDialog())
 </a></td><td style="width: 20%;">
 <a href="../readme.md">Содержание
 </a></td><td style="width: 40%;">
-<a href="../articles/cs_edit_product.md">Создание, изменение продукции
+<a href="../articles/cs_edit_product2.md">Создание, изменение продукции
 </a></td><tr></table>

+ 316 - 0
articles/cs_edit_product2.md

@@ -0,0 +1,316 @@
+<table style="width: 100%;"><tr><td style="width: 40%;">
+<a href="../articles/cs_coloring2.md">Подсветка элементов по условию. Дополнительные выборки.Массовая смена цены продукции.
+</a></td><td style="width: 20%;">
+<a href="../readme.md">Содержание
+</a></td><td style="width: 40%;">
+<a href="../articles/kotlin.md">Основы языка Kotlin
+</a></td><tr></table>
+
+# Добавление/редактирование продукции
+
+>Необходимо добавить возможность редактирования данных существующей продукции, а также добавление новой продукции в новом окне - форме для добавления/редактирования продукции.
+>
+>Переходы на данное окно должны быть реализованы из главной формы списка: для редактирования - при нажатии на конкретный элемент, для добавления - при нажатии кнопки “Добавить продукцию”.
+>
+>На форме должны быть предусмотрены следующие поля: артикул, наименование, тип продукта (выпадающий список), изображение, количество человек для производства, номер производственного цеха, минимальная стоимость для агента и подробное описание (с возможностью многострочного ввода).
+>
+>Также необходимо реализовать вывод списка материалов, используемых при производстве продукции, с указанием количества. В список можно добавлять новые позиции и удалять существующие. При добавлении материалы должны выбираться из выпадающего списка с возможностью поиска по наименованию.
+>
+>При открытии формы для редактирования все поля выбранного объекта должны быть подгружены в соответствующие поля из базы данных, а таблица заполнена актуальными значениями.
+>
+>Стоимость продукции может включать сотые части, а также не может быть отрицательной. Система должна проверять существование продукта с введенным артикулом и не давать использовать один
+артикул для нескольких продуктов.
+>
+>Пользователь может добавить/заменить изображение у продукции.
+>
+>Для того чтобы администратор случайно не изменял несколько продуктов, предусмотрите невозможность открытия более одного окна редактирования.
+>
+>В окне редактирования продукта должна присутствовать кнопка “Удалить”, которая удаляет продукт из базы данных. При этом должны соблюдаться следующие условия. Если у продукта есть информация о материалах, используемых при его производстве, или история изменения цен, то эта информация должна быть удалена вместе с продуктом. Но если у продукта есть информация о его продажах агентами, то удаление продукта из базы данных должно быть запрещено. После удаления продукта система должна сразу вернуть пользователя обратно к списку продукции.
+>
+>После редактирования/добавления/удаления продукции данные в окне списка продукции должны быть обновлены.
+
+Критерий | Баллы
+---------|:---:
+Реализован переход на окно добавления | 0.1
+Реализован переход на окно редактирования выбранного объекта | 0.2
+Присутствуют все поля для заполнения | 0.5
+При редактировании продукции в поля для ввода загружены данные из БД | 0.3
+Выбор типа продукта реализован в виде выпадающего списка со значениями из БД | 0.3
+Для ввода описания продукции предусмотрено многострочное поле для ввода | 0.2
+***Реализован список используемых материалов для текущего продукта*** | 0.3
+***В списке присутствует название материала и используемое количество*** | 0.2
+***При редактировании продукции список материалов заполнен значениями из БД*** | 0.2
+***В список можно добавлять новые позиции*** | 0.3
+***Из списка можно удалять существующие позиции*** | 0.2
+***При добавлении материалы выбираются из выпадающего списка со значениями из БД*** | 0.3
+***В списке материалов реализована возможность поиска по наименованию*** | 0.2
+***Список используемых материалов сохраняется в БД при добавлении*** | 0.5
+***Список используемых материалов сохраняется в БД при редактировании*** | 0.5
+Стоимость продукции не может быть отрицательной | 0.1
+Стоимость продукции записывается только с точностью до сотых | 0.2
+Реализована проверка артикула на уникальность | 0.3
+Есть возможность выбрать изображение | 0.2
+Изображение продукции подгружается из БД при редактировании | 0.2
+Есть возможность заменить изображение | 0.1
+Данные при добавлении сохраняются в БД | 0.5
+Данные при редактировании изменяются в БД | 0.5
+Открывается только одно окно редактирования | 0.1
+*Реализовано удаление выбранного продукта, у которого не заполнен список используемых материалов* | 0.2
+*Реализовано удаление продукта вместе с информацией об используемых материалах* | 0.5
+*Запрещено удаление продукта, по которому были выполнены продажи агентом* | 0.3
+*После удаления реализован автоматический переход обратно в список* | 0.1
+После закрытия окна данные в таблице обновляются | 0.3
+**Итого** | **7.9**
+
+# Создание окна редактирования продукции
+
+Для добавления и редактирования мы будем использовать одно и то же окно. Название окна будем вычислять по наличию ID у продукции (у новой записи это поле = 0)
+
+1. Создайте новое окно: **EditProductWindow** (в папке **Windows** - не забываем про логическую структуру проекта)
+
+2. В классе окна **EditProductWindow** добавьте свойство *CurrentProduct*, в котором будет храниться добавляемый/редактируемый экземпляр продукции:
+
+    ```cs
+    public Product CurrentProduct { get; set; }
+    ```
+
+    И геттер для названия окна:
+
+    ```cs
+    public string WindowName {
+        get {
+            return CurrentProduct.Id == 0 ? "Новый продукт" : "Редактирование продукта";
+        }
+    }
+    ```
+3. В конструктор окна добавьте параметр типа **Product** и присвойте его ранее объявленному свойству:
+
+    ```cs
+    public EditWindow(Product EditProduct)
+    {
+        InitializeComponent();
+        DataContext = this;
+        CurrentProduct = EditProduct;
+    }
+    ```
+
+4. В разметке окна вместо фиксированного названия вставьте привязку к свойству *WindowName*
+
+    ```xml
+    <Window
+        ...
+        Title="{Binding WindowName}">
+    ```
+
+5. В окне редактирования продукции создайте сетку из трёх колонок: в первой у нас будет изображение, во второй редактируемые поля продукта, а в третей список материалов
+
+    ```xml
+    <Grid.ColumnDefinitions>
+        <ColumnDefinition Width="auto"/>
+        <ColumnDefinition Width="*"/>
+        <ColumnDefinition Width="auto"/>
+    </Grid.ColumnDefinitions>
+    ```
+
+6. Во вторую колонку добавьте **StackPanel** с границами (чтобы визуальные компоненты не прилипали к границам окна) и в этом списке разместите редактируемые элементы
+
+    >На форме должны быть предусмотрены следующие поля: артикул, наименование, тип продукта (выпадающий список), изображение, количество человек для производства, номер производственного цеха, минимальная стоимость для агента и подробное описание
+
+    ```xml
+    <StackPanel
+        Grid.Column="1" 
+        Margin="5">
+
+        <Label Content="Артикул"/>
+        <TextBox Text="{Binding CurrentProduct.ArticleNumber}"/>
+
+        <Label Content="Наименование продукта"/>
+        <TextBox Text="{Binding CurrentProduct.Title}"/>
+
+        ...
+
+    </StackPanel>
+    ```
+
+    Обычные поля наклепайте по шаблону сами, а я подробнее остановлюсь на полях: *тип продукта*, *изображение* и *описание*:
+
+    * Выбор типа продукта из списка
+
+        В классе окна объявляем свойство *ProductTypes* - список типов продукции
+
+        ```cs
+        public IEnumerable<ProductType> ProductTypeList { get; set; }
+        ```
+
+        И в конструкторе получаем его из контекста БД:
+
+        ```cs
+        using (var context = new ksmirnovContext())
+        {
+            ProductTypeList = context.ProductTypes.ToList();
+        }        
+        ```
+
+        В вёрстке окна редактирования продукции мы можем использовать выпадающий список, атрибут *SelectedItem* которого позволяет отобразить текущее значение редактируемого элемента
+
+        ```xml
+        <ComboBox 
+            ItemsSource="{Binding ProductTypeList}"
+            SelectedItem="{Binding CurrentProduct.ProductType}"/>
+        ```
+
+        >Из документации: *Класс ComboBox выполняет поиск указанного объекта с помощью **IndexOf** метода. Этот метод использует **Equals** метод для определения равенства*.
+
+        А метод **Equals** сравнивает объекты по уникальному идентификатору, т.е. свойство **ProductType** у экземпляра продукции НЕ РАВНО экземпляру элемента списка *ProductTypeList*
+
+        Чтобы исправить эту неприятность нужно переопределить метод **Equals** у класса **ProductType**:
+
+        ```cs
+        public override bool Equals(object obj)
+        {
+            return (obj != null) && 
+                (obj is ProductType) && 
+                (this.Id == (obj as ProductType).Id);
+        
+        }
+        ```
+
+        Здесь мы проверяем определён ли вообще объект (у нового продукта его ещё нет), нужного ли он типа и совпадет ли его **Id** с **Id** текущего типа продукции 
+
+    * смена изображения продукции
+
+        Вывод изображения производится как и в главном окне
+
+        ```xml
+        <Image
+            Width="200" 
+            Height="200"
+            Source="{Binding CurrentProduct.ImagePreview,TargetNullValue={StaticResource defaultImage}}" />
+        ```
+
+        А для смены изображения используем стандартный диалог Windows, повесив его на кнопку *Сменить картинку* (кнопку добавьте сами в **StackPanel**)
+
+        Обработчик кнопки:
+
+        ```cs
+        private void ChangeImage_Click(object sender, RoutedEventArgs e)
+        {
+            OpenFileDialog GetImageDialog = new OpenFileDialog();
+            // задаем фильтр для выбираемых файлов
+            // до символа "|" идет произвольный текст, а после него шаблоны файлов разделенные точкой с запятой
+            GetImageDialog.Filter = "Файлы изображений: (*.png, *.jpg)|*.png;*.jpg";
+            // чтобы не искать по всему диску задаем начальный каталог
+            GetImageDialog.InitialDirectory = Environment.CurrentDirectory;
+            if (GetImageDialog.ShowDialog() == true)
+            {
+                // перед присвоением пути к картинке обрезаем начало строки, т.к. диалог возвращает полный путь
+                CurrentProduct.Image = GetImageDialog.FileName.Substring(Environment.CurrentDirectory.Length);
+                // обратите внимание, это другое окно и другой Invalidate, который реализуйте сами
+                Invalidate();
+            }
+        }
+        ```
+
+    * Многострочное описание
+
+        Тут просто - разрешаем переносы и задаем высоту элемента
+
+        ```xml
+        <Label Content="Описание продукта"/>
+        <TextBox 
+            AcceptsReturn="True"
+            Height="2cm"
+            Text="{Binding CurrentProduct.Description}"/>
+        ```
+
+7. Сохранение введенных данных
+
+    В разметку добавьте кнопку **Сохранить** и напишите обработчик
+
+    ```cs
+    private void Button_Click(object sender, RoutedEventArgs e)
+    {
+        using (var context = new ksmirnovContext())
+        {
+            // вся работа с БД должна быть завернута в исключения
+            try
+            {
+                // сюда добавлять проверки
+
+                /*  ПОКА НЕ РАБОТАЕТ !!!
+                if (CurrentProduct.Id != 0)
+                {
+                    context.Products.Update(CurrentProduct);
+                }
+                else
+                {
+                    CurrentProduct.ProductTypeId = CurrentProduct.ProductType.Id;
+                    context.Products.Add(CurrentProduct);
+                }
+                */
+
+                if (context.SaveChanges() > 0)
+                {
+                    DialogResult = true;
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show(ex.InnerException.Message);
+            }
+        }
+    }
+    ```
+
+8. Открытие окна редактирования для существующей и новой продукции
+
+    * для редактирования существующей продукции в списке продукции реализуем обработчик двойного клика
+
+        ```cs
+        private void ProductListView_MouseDoubleClick(object sender, MouseButtonEventArgs e)
+        {
+            // в создаваемое окно передаем выбранный продукт
+            var NewEditWindow = new EditProductWindow(ProductListView.SelectedItem as Product);
+            if ((bool)NewEditWindow.ShowDialog())
+            {
+                // при успешном сохранении продукта перечитываем список продукции
+                using (var context = new ksmirnovContext()) {
+                    ProductList = context.Products
+                        .Include(product => product.ProductType)
+                        .Include(product => product.ProductMaterials)
+                        .ToList();
+                }
+            }
+        }
+        ```
+
+    * для создания нового продукта в разметке главного окна создайте кнопку "Добавить продукцию" (в левой панели) и в её обработчике создайте новый экземпляр продукта
+
+        ```cs
+        var NewEditWindow = new EditWindow(new Product());
+        ...
+        ```
+9. Проверки перед сохранением продукта
+
+    Все проверки вставляем в обработчик кнопки "Сохранить" окна редактирования, т.к. это относится к бизнес-логике, до вызова метода сохранения продукта
+
+    * Стоимость продукции не может быть отрицательной
+
+        ```cs
+        if (ChangedProduct.MinCostForAgent < 0)
+            throw new Exception("Цена продукта не может быть отрицательной");
+        ```
+
+    * Стоимость продукции записывается только с точностью до сотых
+
+    * Реализована проверка артикула на уникальность
+
+        Тут по идее надо делать запрос к базе, но у нас есть метод получения списка продукции и мы можем искать в нём используя LINQ-запросы
+
+<table style="width: 100%;"><tr><td style="width: 40%;">
+<a href="../articles/cs_coloring2.md">Подсветка элементов по условию. Дополнительные выборки.Массовая смена цены продукции.
+</a></td><td style="width: 20%;">
+<a href="../readme.md">Содержание
+</a></td><td style="width: 40%;">
+<a href="../articles/kotlin.md">Основы языка Kotlin
+</a></td><tr></table>

+ 1 - 1
articles/cs_pagination2.md

@@ -454,7 +454,7 @@ public IEnumerable<Product> ProductList {
     }
     ```
 
-1. В классе окна объявить переменную **PageList** и в геттере списка продукции заполнять её, а не генерировать динамически содержимое для пагинатора
+1. В **классе окна** объявить массив **PageList** и в **геттере списка продукции** заполнять его, а не генерировать динамически содержимое для пагинатора
 
     ```cs
     public List<PageItem> PageList { get; set; } = new List<PageItem>();

+ 1 - 1
readme.md

@@ -238,7 +238,7 @@ https://office-menu.ru/uroki-sql Уроки SQL
 
 1. [Подсветка элементов по условию. Дополнительные выборки. Массовая смена цены продукции.](./articles/cs_coloring2.md)
 
-1. [Создание, изменение продукции](./articles/cs_edit_product.md)
+1. [Создание, изменение продукции](./articles/cs_edit_product2.md)
 
 ## Тема 5.1.5. Разработка своего API.