|
@@ -37,15 +37,15 @@
|
|
|
При редактировании продукции в поля для ввода загружены данные из БД | 0.3
|
|
При редактировании продукции в поля для ввода загружены данные из БД | 0.3
|
|
|
Выбор типа продукта реализован в виде выпадающего списка со значениями из БД | 0.3
|
|
Выбор типа продукта реализован в виде выпадающего списка со значениями из БД | 0.3
|
|
|
Для ввода описания продукции предусмотрено многострочное поле для ввода | 0.2
|
|
Для ввода описания продукции предусмотрено многострочное поле для ввода | 0.2
|
|
|
-Реализован список используемых материалов для текущего продукта | 0.3
|
|
|
|
|
-В списке присутствует название материала и используемое количество | 0.2
|
|
|
|
|
-При редактировании продукции список материалов заполнен значениями из БД | 0.2
|
|
|
|
|
-В список можно добавлять новые позиции | 0.3
|
|
|
|
|
-Из списка можно удалять существующие позиции | 0.2
|
|
|
|
|
-При добавлении материалы выбираются из выпадающего списка со значениями из БД | 0.3
|
|
|
|
|
-В списке материалов реализована возможность поиска по наименованию | 0.2
|
|
|
|
|
-Список используемых материалов сохраняется в БД при добавлении | 0.5
|
|
|
|
|
-Список используемых материалов сохраняется в БД при редактировании | 0.5
|
|
|
|
|
|
|
+*Реализован список используемых материалов для текущего продукта* | 0.3
|
|
|
|
|
+*В списке присутствует название материала и используемое количество* | 0.2
|
|
|
|
|
+*При редактировании продукции список материалов заполнен значениями из БД* | 0.2
|
|
|
|
|
+*В список можно добавлять новые позиции* | 0.3
|
|
|
|
|
+*Из списка можно удалять существующие позиции* | 0.2
|
|
|
|
|
+*При добавлении материалы выбираются из выпадающего списка со значениями из БД* | 0.3
|
|
|
|
|
+*В списке материалов реализована возможность поиска по наименованию* | 0.2
|
|
|
|
|
+*Список используемых материалов сохраняется в БД при добавлении* | 0.5
|
|
|
|
|
+*Список используемых материалов сохраняется в БД при редактировании* | 0.5
|
|
|
Стоимость продукции не может быть отрицательной | 0.1
|
|
Стоимость продукции не может быть отрицательной | 0.1
|
|
|
Стоимость продукции записывается только с точностью до сотых | 0.2
|
|
Стоимость продукции записывается только с точностью до сотых | 0.2
|
|
|
Реализована проверка артикула на уникальность | 0.3
|
|
Реализована проверка артикула на уникальность | 0.3
|
|
@@ -55,12 +55,339 @@
|
|
|
Данные при добавлении сохраняются в БД | 0.5
|
|
Данные при добавлении сохраняются в БД | 0.5
|
|
|
Данные при редактировании изменяются в БД | 0.5
|
|
Данные при редактировании изменяются в БД | 0.5
|
|
|
Открывается только одно окно редактирования | 0.1
|
|
Открывается только одно окно редактирования | 0.1
|
|
|
-Реализовано удаление выбранного продукта, у которого не заполнен список используемых материалов | 0.2
|
|
|
|
|
-Реализовано удаление продукта вместе с информацией об используемых материалах | 0.5
|
|
|
|
|
-Запрещено удаление продукта, по которому были выполнены продажи агентом | 0.3
|
|
|
|
|
-После удаления реализован автоматический переход обратно в список | 0.1
|
|
|
|
|
|
|
+*Реализовано удаление выбранного продукта, у которого не заполнен список используемых материалов* | 0.2
|
|
|
|
|
+*Реализовано удаление продукта вместе с информацией об используемых материалах* | 0.5
|
|
|
|
|
+*Запрещено удаление продукта, по которому были выполнены продажи агентом* | 0.3
|
|
|
|
|
+*После удаления реализован автоматический переход обратно в список* | 0.1
|
|
|
После закрытия окна данные в таблице обновляются | 0.3
|
|
После закрытия окна данные в таблице обновляются | 0.3
|
|
|
**Итого** | **7.9**
|
|
**Итого** | **7.9**
|
|
|
|
|
|
|
|
# Создание окна редактирования продукции
|
|
# Создание окна редактирования продукции
|
|
|
|
|
|
|
|
|
|
+Для добавления и редактирования мы будем использовать одно и то же окно. Название окна будем вычислять по наличию ID у продукции (у новой записи это поле = 0)
|
|
|
|
|
+
|
|
|
|
|
+1. Создайте новое окно: **EditWindow**
|
|
|
|
|
+
|
|
|
|
|
+2. В классе окна **EditWindow** добавьте свойство *CurrentProduct*, в котором будет храниться добавляемый/редактируемый экземпляр продукции:
|
|
|
|
|
+
|
|
|
|
|
+ ```cs
|
|
|
|
|
+ public Product CurrentProduct { get; set; }
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+ И геттер для названия окна:
|
|
|
|
|
+
|
|
|
|
|
+ ```cs
|
|
|
|
|
+ public string WindowName {
|
|
|
|
|
+ get {
|
|
|
|
|
+ return CurrentService.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
|
|
|
|
|
+ Margin="5">
|
|
|
|
|
+
|
|
|
|
|
+ <Label Content="Артикул"/>
|
|
|
|
|
+ <TextBox Text="{Binding CurrentProduct.ArticleNumber}"/>
|
|
|
|
|
+
|
|
|
|
|
+ <Label Content="Наименование продукта"/>
|
|
|
|
|
+ <TextBox Text="{Binding CurrentService.Title}"/>
|
|
|
|
|
+
|
|
|
|
|
+ ...
|
|
|
|
|
+
|
|
|
|
|
+ </StackPanel>
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+ Обычные поля наклепайте по шаблону сами, а я подробнее остановлюсь на полях: тип продукта, изображение и описание:
|
|
|
|
|
+
|
|
|
|
|
+ * Выбор типа продукта из списка
|
|
|
|
|
+
|
|
|
|
|
+ В классе окна объявляем свойство *ProductTypes* - список типов продукции
|
|
|
|
|
+
|
|
|
|
|
+ ```cs
|
|
|
|
|
+ public IEnumerable<ProductType> ProductTypes { get; set; }
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+ И в конструкторе получаем его из поставщика данных
|
|
|
|
|
+
|
|
|
|
|
+ ```cs
|
|
|
|
|
+ ProductTypes = Globals.DataProvider.GetProductTypes();
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+ В выпадающий список мы должны передать собственно список выбираемых объектов (*ItemsSource*) и текущий объект (*SelectedItem*) из этого списка. Но у нас в модели **Product** нет объекта **ProductType**, есть отдельные поля *ProductTypeID* и *ProductTypeTitle*. Изменим модель, вместо этих полей сделаем поле *CurrentProductType*, значение которого будем получать из списка типов по ID
|
|
|
|
|
+
|
|
|
|
|
+ В поставщик данных добавим переменную, в которой будет храниться список типов продукции:
|
|
|
|
|
+
|
|
|
|
|
+ ```cs
|
|
|
|
|
+ private List<ProductType> ProductTypes = null;
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+ Исправим метод **GetProductTypes**, чтобы он считывал список только в первый раз (поиск объектов происходит по хешу и нам важно, чтобы этот список был всегда один и тот же)
|
|
|
|
|
+
|
|
|
|
|
+ ```cs
|
|
|
|
|
+ public IEnumerable<ProductType> GetProductTypes()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (ProductTypes == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ ProductTypes = new List<ProductType>();
|
|
|
|
|
+ ...
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+ В модели **Product** убираем свойства *ProductTypeTitle*, *ProductTypeID* и добавляем *CurrentProductType* (после этого пересоберите проект и исправьте возникшие ошибки)
|
|
|
|
|
+
|
|
|
|
|
+ ```cs
|
|
|
|
|
+ // public string ProductTypeTitle { get; set; }
|
|
|
|
|
+ // public int ProductTypeID { get; set; }
|
|
|
|
|
+ public ProductType CurrentProductType { get; set; }
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+ И в классе **MySQLDataProvider** переделайте получение типа продукта:
|
|
|
|
|
+
|
|
|
|
|
+ ```cs
|
|
|
|
|
+ // NewProduct.ProductTypeID = Reader.GetInt32("ProductTypeID");
|
|
|
|
|
+ // NewProduct.ProductTypeTitle = Reader["ProductTypeTitle"].ToString();
|
|
|
|
|
+ NewProduct.CurrentProductType = GetProductType(Reader.GetInt32("ProductTypeID"));
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+ Реализация метода *GetProductType*:
|
|
|
|
|
+
|
|
|
|
|
+ ```cs
|
|
|
|
|
+ private ProductType GetProductType(int Id)
|
|
|
|
|
+ {
|
|
|
|
|
+ // тут заполнится список типов продукции, если он ещё пустой
|
|
|
|
|
+ GetProductTypes();
|
|
|
|
|
+ return ProductTypes.Find(pt => pt.ID == Id);
|
|
|
|
|
+ }
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+ Теперь в верстке окна редактирования продукции мы можем использовать выпадающий список
|
|
|
|
|
+
|
|
|
|
|
+ ```xml
|
|
|
|
|
+ <ComboBox
|
|
|
|
|
+ ItemsSource="{Binding ProductTypes}"
|
|
|
|
|
+ SelectedItem="{Binding CurrentProduct.CurrentProductType}"/>
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+ * смена изображения продукции
|
|
|
|
|
+
|
|
|
|
|
+ Вывод изображения производится как и в главном окне
|
|
|
|
|
+
|
|
|
|
|
+ ```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)
|
|
|
|
|
+ {
|
|
|
|
|
+ // вся работа с БД должна быть завернута в исключения
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ // метод SaveProduct реализуем ниже
|
|
|
|
|
+ Globals.DataProvider.SaveProduct(CurrentProduct);
|
|
|
|
|
+ DialogResult = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception ex)
|
|
|
|
|
+ {
|
|
|
|
|
+ MessageBox.Show(ex.Message);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+ В интерфейсе **IDataProvider** объявляем метод *SaveProduct*:
|
|
|
|
|
+
|
|
|
|
|
+ ```cs
|
|
|
|
|
+ void SaveProduct(Product ChangedProduct);
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+ И реализуем его в классе поставщика данных
|
|
|
|
|
+
|
|
|
|
|
+ ```cs
|
|
|
|
|
+ public void SaveProduct(Product ChangedProduct)
|
|
|
|
|
+ {
|
|
|
|
|
+ Connection.Open();
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ if (ChangedProduct.ID == 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ // новый продукт - добавляем запись
|
|
|
|
|
+ string Query = @"INSERT INTO Product
|
|
|
|
|
+ (Title,
|
|
|
|
|
+ ProductTypeID,
|
|
|
|
|
+ ArticleNumber,
|
|
|
|
|
+ Description,
|
|
|
|
|
+ Image,
|
|
|
|
|
+ ProductionPersonCount,
|
|
|
|
|
+ ProductionWorkshopNumber,
|
|
|
|
|
+ MinCostForAgent)
|
|
|
|
|
+ VALUES
|
|
|
|
|
+ (@Title,
|
|
|
|
|
+ @ProductTypeID,
|
|
|
|
|
+ @ArticleNumber,
|
|
|
|
|
+ @Description,
|
|
|
|
|
+ @Image,
|
|
|
|
|
+ @ProductionPersonCount,
|
|
|
|
|
+ @ProductionWorkshopNumber,
|
|
|
|
|
+ @MinCostForAgent)";
|
|
|
|
|
+
|
|
|
|
|
+ MySqlCommand Command = new MySqlCommand(Query, Connection);
|
|
|
|
|
+ Command.Parameters.AddWithValue("@Title", ChangedProduct.Title);
|
|
|
|
|
+ Command.Parameters.AddWithValue("@ProductTypeID", ChangedProduct.CurrentProductType.ID);
|
|
|
|
|
+ Command.Parameters.AddWithValue("@ArticleNumber", ChangedProduct.ArticleNumber);
|
|
|
|
|
+ Command.Parameters.AddWithValue("@Description", ChangedProduct.Description);
|
|
|
|
|
+ Command.Parameters.AddWithValue("@Image", ChangedProduct.Image);
|
|
|
|
|
+ Command.Parameters.AddWithValue("@ProductionPersonCount", ChangedProduct.ProductionPersonCount);
|
|
|
|
|
+ Command.Parameters.AddWithValue("@ProductionWorkshopNumber", ChangedProduct.ProductionWorkshopNumber);
|
|
|
|
|
+ Command.Parameters.AddWithValue("@MinCostForAgent", ChangedProduct.MinCostForAgent);
|
|
|
|
|
+ Command.ExecuteNonQuery();
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ // существующий продукт - изменяем запись
|
|
|
|
|
+
|
|
|
|
|
+ string Query = @"UPDATE Product
|
|
|
|
|
+ SET
|
|
|
|
|
+ Title = @Title,
|
|
|
|
|
+ ProductTypeID = @ProductTypeID,
|
|
|
|
|
+ ArticleNumber = @ArticleNumber,
|
|
|
|
|
+ Description = @Description,
|
|
|
|
|
+ Image = @Image,
|
|
|
|
|
+ ProductionPersonCount = @ProductionPersonCount,
|
|
|
|
|
+ ProductionWorkshopNumber = @ProductionWorkshopNumber,
|
|
|
|
|
+ MinCostForAgent = @MinCostForAgent
|
|
|
|
|
+ WHERE ID = @ID";
|
|
|
|
|
+
|
|
|
|
|
+ MySqlCommand Command = new MySqlCommand(Query, Connection);
|
|
|
|
|
+ Command.Parameters.AddWithValue("@Title", ChangedProduct.Title);
|
|
|
|
|
+ Command.Parameters.AddWithValue("@ProductTypeID", ChangedProduct.CurrentProductType.ID);
|
|
|
|
|
+ Command.Parameters.AddWithValue("@ArticleNumber", ChangedProduct.ArticleNumber);
|
|
|
|
|
+ Command.Parameters.AddWithValue("@Description", ChangedProduct.Description);
|
|
|
|
|
+ Command.Parameters.AddWithValue("@Image", ChangedProduct.Image);
|
|
|
|
|
+ Command.Parameters.AddWithValue("@ProductionPersonCount", ChangedProduct.ProductionPersonCount);
|
|
|
|
|
+ Command.Parameters.AddWithValue("@ProductionWorkshopNumber", ChangedProduct.ProductionWorkshopNumber);
|
|
|
|
|
+ Command.Parameters.AddWithValue("@MinCostForAgent", ChangedProduct.MinCostForAgent);
|
|
|
|
|
+ Command.Parameters.AddWithValue("@ID", ChangedProduct.ID);
|
|
|
|
|
+ Command.ExecuteNonQuery();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ finally
|
|
|
|
|
+ {
|
|
|
|
|
+ Connection.Close();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+8. Открытие окна редактирования для существующей и новой продукции
|
|
|
|
|
+
|
|
|
|
|
+ * для редактирования существующей продукции в списке продукции реализуем обработчик двойного клика
|
|
|
|
|
+
|
|
|
|
|
+ ```cs
|
|
|
|
|
+ private void ProductListView_MouseDoubleClick(object sender, MouseButtonEventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ // в создаваемое окно передаем выбранный продукт
|
|
|
|
|
+ var NewEditWindow = new EditWindow(ProductListView.SelectedItem as Product);
|
|
|
|
|
+ if ((bool)NewEditWindow.ShowDialog())
|
|
|
|
|
+ {
|
|
|
|
|
+ // при успешном сохранении продукта перечитываем список продукции
|
|
|
|
|
+ ProductList = Globals.DataProvider.GetProducts();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+ * для создания нового продукта в разметке главного окна создайте кнопку "Добавить продукцию" (либо в верхней панели, либо в левой) и в её обработчике создайте новый экземпляр продукта
|
|
|
|
|
+
|
|
|
|
|
+ ```cs
|
|
|
|
|
+ var NewEditWindow = new EditWindow(new Product());
|
|
|
|
|
+ ...
|
|
|
|
|
+ ```
|
|
|
|
|
+9. Проверки перед сохранением продукта
|
|
|
|
|
+
|
|
|
|
|
+ Все проверки вставляем в метод *SaveProduct*, до сохранения продукта
|
|
|
|
|
+
|
|
|
|
|
+ * Стоимость продукции не может быть отрицательной
|
|
|
|
|
+
|
|
|
|
|
+ ```cs
|
|
|
|
|
+ if (ChangedProduct.MinCostForAgent < 0)
|
|
|
|
|
+ throw new Exception("Цена продукта не может быть отрицательной");
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+ * Стоимость продукции записывается только с точностью до сотых
|
|
|
|
|
+
|
|
|
|
|
+ * Реализована проверка артикула на уникальность
|
|
|
|
|
+
|
|
|
|
|
+ Тут по идее надо делать запрос к базе, но у нас есть метод получения списка продукции и мы можем искать в нём используя LINQ-запросы
|
|
|
|
|
+
|