diff --git a/App.config b/App.config
new file mode 100644
index 0000000..4a9f8d0
--- /dev/null
+++ b/App.config
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Form1.Designer.cs b/Form1.Designer.cs
new file mode 100644
index 0000000..bf0f3ca
--- /dev/null
+++ b/Form1.Designer.cs
@@ -0,0 +1,223 @@
+//------------------------------------------------------------------------------
+//
+// Этот код создан программой.
+// Исполняемая версия:4.0.30319.42000
+//
+// Изменения в этом файле могут привести к неправильной работе и будут потеряны в случае
+// повторной генерации кода.
+//
+//------------------------------------------------------------------------------
+
+namespace OzonInternalLabelPrinter // Убедитесь, что это ваше пространство имен
+{
+ partial class Form1
+ {
+ ///
+ /// Обязательная переменная конструктора.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Освободить все используемые ресурсы.
+ ///
+ /// истинно, если управляемый ресурс должен быть удален; иначе ложно.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ // Освобождаем ресурсы, созданные в коде Form1.cs
+ _httpClient?.Dispose();
+ _printDocForSetup?.Dispose(); // Добавлено освобождение PrintDocument
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Код, автоматически созданный конструктором форм Windows
+
+ ///
+ /// Требуемый метод для поддержки конструктора — не изменяйте
+ /// содержимое этого метода с помощью редактора кода.
+ ///
+ private void InitializeComponent()
+ {
+ this.label3 = new System.Windows.Forms.Label();
+ this.txtOfferId = new System.Windows.Forms.TextBox();
+ this.btnGetData = new System.Windows.Forms.Button();
+ this.lblProductName = new System.Windows.Forms.Label();
+ this.lblProductSku = new System.Windows.Forms.Label();
+ this.lblProductBarcode = new System.Windows.Forms.Label();
+ this.btnPrintLabel = new System.Windows.Forms.Button();
+ this.lblStatus = new System.Windows.Forms.Label();
+ this.cmbStores = new System.Windows.Forms.ComboBox();
+ this.btnManageStores = new System.Windows.Forms.Button();
+ this.labelStore = new System.Windows.Forms.Label();
+ this.btnPageSetup = new System.Windows.Forms.Button(); // Объявление кнопки Настройки
+ this.pageSetupDialog1 = new System.Windows.Forms.PageSetupDialog(); // Объявление диалога Настройки
+ this.SuspendLayout();
+ //
+ // label3
+ //
+ this.label3.AutoSize = true;
+ this.label3.Location = new System.Drawing.Point(12, 94); // Скорректировано
+ this.label3.Name = "label3";
+ this.label3.Size = new System.Drawing.Size(51, 13);
+ this.label3.TabIndex = 4;
+ this.label3.Text = "Артикул:";
+ //
+ // txtOfferId
+ //
+ this.txtOfferId.Enabled = false;
+ this.txtOfferId.Location = new System.Drawing.Point(88, 91); // Скорректировано
+ this.txtOfferId.Name = "txtOfferId";
+ this.txtOfferId.Size = new System.Drawing.Size(176, 20);
+ this.txtOfferId.TabIndex = 3; // TabIndex изменен
+ this.txtOfferId.TextChanged += new System.EventHandler(this.txtOfferId_TextChanged);
+ //
+ // btnGetData
+ //
+ this.btnGetData.Enabled = false;
+ this.btnGetData.Location = new System.Drawing.Point(270, 89); // Скорректировано
+ this.btnGetData.Name = "btnGetData";
+ this.btnGetData.Size = new System.Drawing.Size(118, 23);
+ this.btnGetData.TabIndex = 4; // TabIndex изменен
+ this.btnGetData.Text = "Получить данные";
+ this.btnGetData.UseVisualStyleBackColor = true;
+ this.btnGetData.Click += new System.EventHandler(this.btnGetData_Click);
+ //
+ // lblProductName
+ //
+ this.lblProductName.AutoSize = true;
+ this.lblProductName.Location = new System.Drawing.Point(12, 133); // Скорректировано
+ this.lblProductName.Name = "lblProductName";
+ this.lblProductName.Size = new System.Drawing.Size(60, 13);
+ this.lblProductName.TabIndex = 5;
+ this.lblProductName.Text = "Название:";
+ //
+ // lblProductSku
+ //
+ this.lblProductSku.AutoSize = true;
+ this.lblProductSku.Location = new System.Drawing.Point(12, 156); // Скорректировано
+ this.lblProductSku.Name = "lblProductSku";
+ this.lblProductSku.Size = new System.Drawing.Size(51, 13);
+ this.lblProductSku.TabIndex = 6;
+ this.lblProductSku.Text = "Артикул:";
+ //
+ // lblProductBarcode
+ //
+ this.lblProductBarcode.AutoSize = true;
+ this.lblProductBarcode.Location = new System.Drawing.Point(12, 179); // Скорректировано
+ this.lblProductBarcode.Name = "lblProductBarcode";
+ this.lblProductBarcode.Size = new System.Drawing.Size(59, 13);
+ this.lblProductBarcode.TabIndex = 7;
+ this.lblProductBarcode.Text = "Штрихкод:";
+ //
+ // btnPrintLabel
+ //
+ this.btnPrintLabel.Enabled = false;
+ this.btnPrintLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(204)));
+ this.btnPrintLabel.Location = new System.Drawing.Point(16, 214); // Скорректировано
+ this.btnPrintLabel.Name = "btnPrintLabel";
+ this.btnPrintLabel.Size = new System.Drawing.Size(373, 33);
+ this.btnPrintLabel.TabIndex = 8; // TabIndex изменен
+ this.btnPrintLabel.Text = "Печать этикетки";
+ this.btnPrintLabel.UseVisualStyleBackColor = true;
+ this.btnPrintLabel.Click += new System.EventHandler(this.btnPrintLabel_Click);
+ //
+ // lblStatus
+ //
+ this.lblStatus.AutoSize = true;
+ this.lblStatus.Location = new System.Drawing.Point(13, 260); // Скорректировано
+ this.lblStatus.Name = "lblStatus";
+ this.lblStatus.Size = new System.Drawing.Size(44, 13);
+ this.lblStatus.TabIndex = 9;
+ this.lblStatus.Text = "Статус:";
+ //
+ // cmbStores
+ //
+ this.cmbStores.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+ this.cmbStores.FormattingEnabled = true;
+ this.cmbStores.Location = new System.Drawing.Point(88, 24);
+ this.cmbStores.Name = "cmbStores";
+ this.cmbStores.Size = new System.Drawing.Size(176, 21);
+ this.cmbStores.TabIndex = 0;
+ this.cmbStores.SelectedIndexChanged += new System.EventHandler(this.cmbStores_SelectedIndexChanged);
+ //
+ // btnManageStores
+ //
+ this.btnManageStores.Location = new System.Drawing.Point(270, 23);
+ this.btnManageStores.Name = "btnManageStores";
+ this.btnManageStores.Size = new System.Drawing.Size(118, 23);
+ this.btnManageStores.TabIndex = 1;
+ this.btnManageStores.Text = "Упр. магазинами...";
+ this.btnManageStores.UseVisualStyleBackColor = true;
+ this.btnManageStores.Click += new System.EventHandler(this.btnManageStores_Click);
+ //
+ // labelStore
+ //
+ this.labelStore.AutoSize = true;
+ this.labelStore.Location = new System.Drawing.Point(12, 27);
+ this.labelStore.Name = "labelStore";
+ this.labelStore.Size = new System.Drawing.Size(54, 13);
+ this.labelStore.TabIndex = 15;
+ this.labelStore.Text = "Магазин:";
+ //
+ // btnPageSetup
+ // *** Новая кнопка Настройки принтера ***
+ this.btnPageSetup.Location = new System.Drawing.Point(270, 53);
+ this.btnPageSetup.Name = "btnPageSetup";
+ this.btnPageSetup.Size = new System.Drawing.Size(118, 23);
+ this.btnPageSetup.TabIndex = 2; // Изменен TabIndex
+ this.btnPageSetup.Text = "Настройка принтера";
+ this.btnPageSetup.UseVisualStyleBackColor = true;
+ this.btnPageSetup.Click += new System.EventHandler(this.btnPageSetup_Click);
+ //
+ // pageSetupDialog1
+ // *** Диалог настройки страницы ***
+ this.pageSetupDialog1 = new System.Windows.Forms.PageSetupDialog();
+ this.pageSetupDialog1.EnableMetric = true;
+ //
+ // Form1
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(404, 288); // Скорректирован размер
+ this.Controls.Add(this.btnPageSetup); // Добавлена кнопка
+ this.Controls.Add(this.labelStore);
+ this.Controls.Add(this.btnManageStores);
+ this.Controls.Add(this.cmbStores);
+ this.Controls.Add(this.lblStatus);
+ this.Controls.Add(this.btnPrintLabel);
+ this.Controls.Add(this.lblProductBarcode);
+ this.Controls.Add(this.lblProductSku);
+ this.Controls.Add(this.lblProductName);
+ this.Controls.Add(this.btnGetData);
+ this.Controls.Add(this.txtOfferId);
+ this.Controls.Add(this.label3);
+ this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
+ this.MaximizeBox = false;
+ this.Name = "Form1";
+ this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+ this.Text = "Генератор внутренних этикеток Ozon";
+ this.ResumeLayout(false);
+ this.PerformLayout();
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.Label label3;
+ private System.Windows.Forms.TextBox txtOfferId;
+ private System.Windows.Forms.Button btnGetData;
+ private System.Windows.Forms.Label lblProductName;
+ private System.Windows.Forms.Label lblProductSku;
+ private System.Windows.Forms.Label lblProductBarcode;
+ private System.Windows.Forms.Button btnPrintLabel;
+ private System.Windows.Forms.Label lblStatus;
+ private System.Windows.Forms.ComboBox cmbStores;
+ private System.Windows.Forms.Button btnManageStores;
+ private System.Windows.Forms.Label labelStore;
+ private System.Windows.Forms.Button btnPageSetup; // Добавлено
+ private System.Windows.Forms.PageSetupDialog pageSetupDialog1; // Добавлено
+ }
+}
\ No newline at end of file
diff --git a/Form1.cs b/Form1.cs
new file mode 100644
index 0000000..0194b77
--- /dev/null
+++ b/Form1.cs
@@ -0,0 +1,500 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Printing;
+using System.Linq;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Security.Cryptography; // Для шифрования/дешифрования
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using SKLADm.Properties; // Для доступа к Settings.Default
+using SKLADm;
+using ZXing;
+using ZXing.Common;
+using ZXing.Windows.Compatibility;
+
+namespace OzonInternalLabelPrinter // Убедитесь, что это ваше пространство имен
+{
+ public partial class Form1 : Form
+ {
+ private HttpClient _httpClient;
+ // Bitmap больше не нужен как поле класса
+ // private Bitmap _labelBitmap;
+ private string _currentProductName;
+ private string _currentSku;
+ private string _currentBarcode;
+ private List _availableStores;
+ // Поле для хранения экземпляра документа между настройкой и печатью
+ private PrintDocument _printDocForSetup; // Используем для PageSetup и Print
+
+ // Конструктор формы
+ public Form1()
+ {
+ InitializeComponent(); // Инициализирует компоненты из ДИЗАЙНЕРА
+ InitializeHttpClient();
+ // Инициализируем PrintDocument один раз
+ _printDocForSetup = new PrintDocument();
+ // Устанавливаем обработчик печати один раз
+ _printDocForSetup.PrintPage += new PrintPageEventHandler(pd_PrintPage);
+ // Загружаем магазины при запуске ПОСЛЕ инициализации _printDocForSetup
+ LoadStoresToComboBox();
+ }
+
+ // Инициализация HTTP клиента
+ private void InitializeHttpClient()
+ {
+ _httpClient = new HttpClient
+ {
+ BaseAddress = new Uri("https://api-seller.ozon.ru/")
+ };
+ _httpClient.DefaultRequestHeaders.Accept.Clear();
+ _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
+ }
+
+ // --- Логика работы с магазинами и ключами ---
+
+ private static readonly byte[] s_entropy = null; // Энтропия для шифрования
+
+ // Дешифрование Api-Key
+ private string DecryptApiKey(string encryptedKeyBase64)
+ {
+ if (string.IsNullOrEmpty(encryptedKeyBase64)) return string.Empty;
+ try
+ {
+ byte[] encryptedBytes = Convert.FromBase64String(encryptedKeyBase64);
+ byte[] decryptedBytes = ProtectedData.Unprotect(encryptedBytes, s_entropy, DataProtectionScope.CurrentUser);
+ return Encoding.UTF8.GetString(decryptedBytes);
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Ошибка дешифрования ключа: {ex.Message}");
+ MessageBox.Show($"Не удалось дешифровать Api-Key для магазина '{cmbStores.Text}'. Возможно, настройки повреждены или созданы под другим пользователем.\n\nОшибка: {ex.Message}",
+ "Ошибка ключа", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ return null;
+ }
+ }
+
+ // Загрузка магазинов из настроек в ComboBox
+ private void LoadStoresToComboBox()
+ {
+ string json = Settings.Default.SavedStoresJson;
+ _availableStores = new List();
+ if (!string.IsNullOrEmpty(json))
+ {
+ try
+ {
+ _availableStores = JsonConvert.DeserializeObject>(json) ?? new List();
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"Ошибка загрузки списка магазинов: {ex.Message}", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Warning);
+ _availableStores = new List();
+ }
+ }
+
+ cmbStores.DataSource = null;
+ cmbStores.DataSource = _availableStores;
+ cmbStores.DisplayMember = "StoreName";
+
+ bool storesExist = _availableStores.Any();
+ cmbStores.Enabled = storesExist;
+ txtOfferId.Enabled = storesExist;
+ btnPageSetup.Enabled = storesExist; // Включаем настройку, если есть магазины
+ // Кнопка GetData включится при вводе артикула
+
+ if (storesExist)
+ {
+ cmbStores.SelectedIndex = 0;
+ // Установим принтер по умолчанию для _printDocForSetup, если возможно
+ TrySetDefaultPrinterForDocument();
+ }
+ else
+ {
+ cmbStores.SelectedIndex = -1;
+ // Показываем сообщение, если нужно
+ // MessageBox.Show("Нет сохраненных магазинов...", "Внимание", MessageBoxButtons.OK, Information);
+ }
+ ResetProductData();
+ // Обновляем состояние кнопки GetData после загрузки
+ txtOfferId_TextChanged(txtOfferId, EventArgs.Empty);
+ }
+
+ // Попытка установить принтер по умолчанию для PrintDocument
+ private void TrySetDefaultPrinterForDocument()
+ {
+ string thermalPrinterName = "Xprinter XP-365B"; // Или другое имя по умолчанию
+ foreach (string printerName in PrinterSettings.InstalledPrinters)
+ {
+ if (printerName.Equals(thermalPrinterName, StringComparison.OrdinalIgnoreCase))
+ {
+ try
+ {
+ _printDocForSetup.PrinterSettings.PrinterName = printerName;
+ System.Diagnostics.Debug.WriteLine($"Принтер по умолчанию '{printerName}' установлен для PrintDocument.");
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Ошибка установки принтера по умолчанию '{printerName}': {ex.Message}");
+ }
+ return; // Выходим после нахождения
+ }
+ }
+ System.Diagnostics.Debug.WriteLine($"Принтер по умолчанию '{thermalPrinterName}' не найден.");
+ }
+
+ // --- Обработчики событий UI ---
+
+ private void btnManageStores_Click(object sender, EventArgs e)
+ {
+ using (FormManageStores manageForm = new FormManageStores())
+ {
+ manageForm.ShowDialog(this);
+ LoadStoresToComboBox(); // Перезагружаем список
+ }
+ }
+
+ private void cmbStores_SelectedIndexChanged(object sender, EventArgs e)
+ {
+ ResetProductData();
+ txtOfferId_TextChanged(txtOfferId, EventArgs.Empty); // Обновляем состояние кнопки GetData
+ // Можно попробовать обновить принтер по умолчанию для _printDocForSetup,
+ // если у разных магазинов могут быть разные принтеры (но это усложнит логику)
+ }
+
+ private void txtOfferId_TextChanged(object sender, EventArgs e)
+ {
+ btnGetData.Enabled = cmbStores.SelectedItem != null && !string.IsNullOrEmpty(txtOfferId.Text.Trim());
+ if (string.IsNullOrEmpty(txtOfferId.Text.Trim()))
+ {
+ ResetProductData();
+ }
+ }
+
+ // --- Кнопка "Настройка принтера" ---
+ private void btnPageSetup_Click(object sender, EventArgs e)
+ {
+ // Убедимся, что принтер в _printDocForSetup актуален (если не нашли Xprinter при запуске,
+ // или если у пользователя несколько принтеров)
+ if (!PrinterSettings.InstalledPrinters.Cast().Contains(_printDocForSetup.PrinterSettings.PrinterName))
+ {
+ // Если текущий принтер недоступен, сбрасываем на принтер по умолчанию Windows
+ try
+ {
+ _printDocForSetup.PrinterSettings = new PrinterSettings(); // Сброс на настройки по умолчанию
+ System.Diagnostics.Debug.WriteLine("Текущий принтер в PrintDocument был недоступен, сброшен на принтер по умолчанию Windows.");
+ }
+ catch { }
+ }
+
+ pageSetupDialog1.Document = _printDocForSetup; // Связываем диалог с нашим PrintDocument
+ pageSetupDialog1.MinMargins = new Margins(0, 0, 0, 0); // Минимальные поля
+ pageSetupDialog1.AllowMargins = true; // Разрешаем менять поля
+ pageSetupDialog1.AllowOrientation = false; // Запрещаем менять ориентацию
+ pageSetupDialog1.AllowPaper = true; // Разрешаем менять размер бумаги
+ pageSetupDialog1.AllowPrinter = true; // Разрешаем выбирать другой принтер
+
+ if (pageSetupDialog1.ShowDialog(this) == DialogResult.OK)
+ {
+ // Настройки применились к _printDocForSetup
+ lblStatus.Text = "Настройки принтера применены.";
+ System.Diagnostics.Debug.WriteLine($"PageSetupDialog OK: New PaperSize={_printDocForSetup.DefaultPageSettings.PaperSize.PaperName}, Printer={_printDocForSetup.PrinterSettings.PrinterName}");
+ }
+ else
+ {
+ lblStatus.Text = "Настройка принтера отменена.";
+ }
+ }
+
+ // --- Получение данных из API ---
+ private async void btnGetData_Click(object sender, EventArgs e)
+ {
+ OzonStore selectedStore = cmbStores.SelectedItem as OzonStore;
+ if (selectedStore == null) { MessageBox.Show("Выберите магазин.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; }
+
+ string clientId = selectedStore.ClientId;
+ string apiKey = DecryptApiKey(selectedStore.EncryptedApiKey);
+ if (apiKey == null) { lblStatus.Text = "Ошибка ключа API."; return; }
+
+ string rawOfferId = txtOfferId.Text.Trim();
+ if (string.IsNullOrEmpty(rawOfferId)) { MessageBox.Show("Введите Артикул.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; }
+
+ string offerId = Regex.Replace(rawOfferId, @"[^a-zA-Zа-яА-Я0-9_-]", "");
+ if (string.IsNullOrEmpty(offerId)) { MessageBox.Show("Артикул некорректен.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; }
+ if (offerId != rawOfferId) { txtOfferId.Text = offerId; }
+
+ ResetProductData();
+ lblStatus.Text = "Запрос данных...";
+ Application.DoEvents();
+
+ _httpClient.DefaultRequestHeaders.Remove("Client-Id");
+ _httpClient.DefaultRequestHeaders.Remove("Api-Key");
+ _httpClient.DefaultRequestHeaders.Add("Client-Id", clientId);
+ _httpClient.DefaultRequestHeaders.Add("Api-Key", apiKey);
+
+ string responseBody = string.Empty;
+ try
+ {
+ var requestData = new { offer_id = new[] { offerId } };
+ var jsonRequest = JsonConvert.SerializeObject(requestData);
+ var content = new StringContent(jsonRequest, Encoding.UTF8, "application/json");
+
+ HttpResponseMessage response = await _httpClient.PostAsync("/v3/product/info/list", content);
+ responseBody = await response.Content.ReadAsStringAsync();
+ System.Diagnostics.Debug.WriteLine($"API Response ({response.StatusCode}): {responseBody}");
+
+ if (response.IsSuccessStatusCode)
+ {
+ JObject jsonResponse = JObject.Parse(responseBody);
+ JToken itemsArray = jsonResponse["items"];
+
+ if (itemsArray != null && itemsArray.Type == JTokenType.Array && itemsArray.HasValues)
+ {
+ JToken firstItem = itemsArray[0];
+ if (firstItem != null)
+ {
+ _currentProductName = firstItem["name"]?.ToString() ?? "N/A";
+ _currentSku = firstItem["offer_id"]?.ToString() ?? offerId;
+ _currentBarcode = firstItem["barcode"]?.ToString();
+ if (string.IsNullOrEmpty(_currentBarcode))
+ {
+ JToken barcodesToken = firstItem["barcodes"];
+ if (barcodesToken != null && barcodesToken.Type == JTokenType.Array && barcodesToken.HasValues)
+ { _currentBarcode = barcodesToken.First?.ToString(); }
+ }
+ _currentBarcode = _currentBarcode ?? "N/A";
+
+ GenerateLabelPreview(); // Обновляем UI и включаем кнопку печати
+ }
+ else { HandleApiError("Нет данных о товаре в ответе.", responseBody); }
+ }
+ else { HandleApiError("Ответ API не содержит списка товаров.", responseBody); }
+ }
+ else { HandleApiError($"Ошибка API: {response.StatusCode}", responseBody); }
+ }
+ catch (JsonReaderException jsonEx) { MessageBox.Show($"Ошибка JSON: {jsonEx.Message}\n{responseBody}", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); ResetProductData(); }
+ catch (HttpRequestException httpEx) { MessageBox.Show($"Ошибка сети: {httpEx.Message}", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); ResetProductData(); }
+ catch (Exception ex) { MessageBox.Show($"Ошибка: {ex.Message}", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); ResetProductData(); }
+ finally { apiKey = null; }
+ }
+
+ // Обработка ошибок API
+ private void HandleApiError(string messagePrefix, string responseBody)
+ {
+ string errorMessage = $"{messagePrefix}";
+ try
+ {
+ JObject errorJson = JObject.Parse(responseBody);
+ string ozonError = errorJson["message"]?.ToString() ?? errorJson["error"]?["message"]?.ToString();
+ errorMessage = string.IsNullOrEmpty(ozonError) ? $"{messagePrefix}\n{responseBody}" : $"{messagePrefix}\n{ozonError}";
+ }
+ catch { errorMessage = $"{messagePrefix}\n{responseBody}"; }
+
+ MessageBox.Show(errorMessage, "Ошибка API Ozon", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ lblStatus.Text = "Ошибка API.";
+ ResetProductData();
+ }
+
+ // Сброс данных о товаре и состояния UI
+ private void ResetProductData()
+ {
+ _currentProductName = null;
+ _currentSku = null;
+ _currentBarcode = null;
+ lblProductName.Text = "Название:";
+ lblProductSku.Text = "Артикул:";
+ lblProductBarcode.Text = "Штрихкод:";
+ // Очищаем превью, если оно есть
+ if (this.Controls.ContainsKey("picLabelPreview"))
+ (this.Controls["picLabelPreview"] as PictureBox).Image = null;
+ btnPrintLabel.Enabled = false;
+ // lblStatus.Text = "Статус:";
+ }
+
+ // Генерация (только обновление UI)
+ private void GenerateLabelPreview()
+ {
+ bool dataAvailable = !string.IsNullOrEmpty(_currentSku) &&
+ !string.IsNullOrEmpty(_currentProductName) &&
+ !string.IsNullOrEmpty(_currentBarcode) &&
+ _currentBarcode != "N/A";
+ if (!dataAvailable) { ResetProductData(); return; }
+
+ lblProductName.Text = $"Название: {_currentProductName}";
+ lblProductSku.Text = $"Артикул: {_currentSku}";
+ lblProductBarcode.Text = $"Штрихкод: {_currentBarcode}";
+
+ // Очистка превью, если оно есть
+ if (this.Controls.ContainsKey("picLabelPreview"))
+ (this.Controls["picLabelPreview"] as PictureBox).Image = null;
+
+ btnPrintLabel.Enabled = true;
+ lblStatus.Text = "Данные получены, готово к печати.";
+ }
+
+
+ // --- Печать ---
+
+ // Кнопка "Печать этикетки"
+ private void btnPrintLabel_Click(object sender, EventArgs e)
+ {
+ if (string.IsNullOrEmpty(_currentSku) || string.IsNullOrEmpty(_currentProductName) || string.IsNullOrEmpty(_currentBarcode) || _currentBarcode == "N/A")
+ {
+ MessageBox.Show("Нет данных для печати.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Warning);
+ return;
+ }
+
+ PrintDialog printDialog = new PrintDialog();
+ printDialog.Document = _printDocForSetup; // Используем настроенный документ
+ printDialog.AllowSomePages = false;
+ printDialog.AllowSelection = false;
+ printDialog.UseEXDialog = true;
+
+ if (printDialog.ShowDialog(this) == DialogResult.OK)
+ {
+ // _printDocForSetup уже содержит принтер и настройки страницы, выбранные в PrintDialog или PageSetupDialog
+ // Переустанавливаем поля на случай, если PrintDialog их сбросил
+ _printDocForSetup.DefaultPageSettings.Margins = new Margins(0, 0, 0, 0);
+ _printDocForSetup.OriginAtMargins = false;
+
+ try
+ {
+ lblStatus.Text = $"Печать на {_printDocForSetup.PrinterSettings.PrinterName}...";
+ _printDocForSetup.Print(); // Печатаем с текущими настройками
+ lblStatus.Text = "Отправлено на печать.";
+ }
+ catch (InvalidPrinterException)
+ {
+ MessageBox.Show($"Ошибка: Принтер '{_printDocForSetup.PrinterSettings.PrinterName}' недоступен или не найден.\nПроверьте подключение или выберите другой принтер в настройках.", "Ошибка принтера", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ lblStatus.Text = "Ошибка принтера.";
+ }
+
+ catch (Exception ex)
+ {
+ MessageBox.Show($"Ошибка печати: {ex.Message}", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ lblStatus.Text = "Ошибка печати.";
+ }
+ }
+ else { lblStatus.Text = "Печать отменена."; }
+
+ printDialog.Dispose();
+ }
+
+ // Метод отрисовки страницы для печати (динамический)
+ private void pd_PrintPage(object sender, PrintPageEventArgs e)
+ {
+ // Проверка данных
+ if (string.IsNullOrEmpty(_currentSku) || string.IsNullOrEmpty(_currentProductName) || string.IsNullOrEmpty(_currentBarcode) || _currentBarcode == "N/A")
+ { System.Diagnostics.Debug.WriteLine("PrintPage ABORTED - Missing data!"); e.Cancel = true; return; }
+
+ Graphics g = e.Graphics;
+ // Получаем РЕАЛЬНЫЕ границы печатаемой области в единицах Graphics (обычно сотые дюйма)
+ // Не PageBounds, а PrintableArea! PageBounds - это физический размер листа.
+ RectangleF printArea = e.PageSettings.PrintableArea;
+
+ // Отступы от КРАЕВ ПЕЧАТАЕМОЙ ОБЛАСТИ (в единицах Graphics)
+ // Подбирайте эти значения, чтобы получить рамку внутри печатаемой зоны
+ float marginX = 5 * g.DpiX / 100f; // Пример: 5/100 дюйма -> в единицах Graphics
+ float marginY = 3 * g.DpiY / 100f; // Пример: 3/100 дюйма -> в единицах Graphics
+
+ // Область для рисования контента внутри отступов
+ float drawableX = printArea.Left + marginX;
+ float drawableY = printArea.Top + marginY;
+ float drawableWidth = printArea.Width - (marginX * 2);
+ float drawableHeight = printArea.Height - (marginY * 2);
+ float currentY = drawableY; // Текущая позиция по Y
+
+ // Проверка на валидность области
+ if (drawableWidth <= 0 || drawableHeight <= 0) { System.Diagnostics.Debug.WriteLine("PrintPage ABORTED - Invalid drawable area!"); e.Cancel = true; return; }
+
+ System.Diagnostics.Debug.WriteLine("--- Printing Page (Dynamic) ---");
+ System.Diagnostics.Debug.WriteLine($"Paper: {e.PageSettings.PaperSize.PaperName} ({e.PageSettings.PaperSize.Width}x{e.PageSettings.PaperSize.Height}) hund.inch");
+ System.Diagnostics.Debug.WriteLine($"PrintableArea ({g.PageUnit}): X={printArea.X:F2}, Y={printArea.Y:F2}, W={printArea.Width:F2}, H={printArea.Height:F2}");
+ System.Diagnostics.Debug.WriteLine($"Drawable Area ({g.PageUnit}): X={drawableX:F2}, Y={drawableY:F2}, W={drawableWidth:F2}, H={drawableHeight:F2}");
+
+ // Шрифты (размер в пунктах)
+ float baseFontSizePoints = 6f; // Базовый размер, можно менять
+ Font titleFont = new Font("Arial", baseFontSizePoints + 1, FontStyle.Bold);
+ Font regularFont = new Font("Arial", baseFontSizePoints);
+ Font barcodeTextFont = new Font("Arial", baseFontSizePoints - 1);
+
+ try
+ {
+ // --- Рисуем Название ---
+ string productName = _currentProductName ?? "N/A";
+ SizeF titleSize = g.MeasureString(productName, titleFont, (int)drawableWidth);
+ // Ограничиваем высоту, чтобы поместилось остальное
+ float maxTitleHeight = drawableHeight * 0.3f;
+ float actualTitleHeight = Math.Min(titleSize.Height, maxTitleHeight);
+ g.DrawString(productName, titleFont, Brushes.Black, new RectangleF(drawableX, currentY, drawableWidth, actualTitleHeight));
+ currentY += actualTitleHeight + (2 * g.DpiY / 72f); // Отступ 2 пункта
+
+ // --- Рисуем Артикул ---
+ string skuText = $"Артикул: {_currentSku ?? "N/A"}";
+ SizeF skuSize = g.MeasureString(skuText, regularFont, (int)drawableWidth);
+ if (currentY + skuSize.Height < printArea.Bottom - marginY - 15 * 100f / g.DpiY) // Оставляем ~15px под ШК + текст
+ {
+ g.DrawString(skuText, regularFont, Brushes.Black, drawableX, currentY);
+ currentY += skuSize.Height + (3 * g.DpiY / 72f); // Отступ 3 пункта
+ }
+
+ // --- Рисуем Штрихкод ---
+ float remainingHeight = (printArea.Bottom - marginY) - currentY; // Оставшаяся высота
+ if (remainingHeight > 15 * 100f / g.DpiY) // Если осталось хотя бы ~15px
+ {
+ try
+ {
+ // --- Расчет размеров ШК ---
+ float barcodeTextHeight = g.MeasureString(_currentBarcode, barcodeTextFont).Height;
+ float barcodePrintHeight = Math.Max(10 * 100f / g.DpiY, remainingHeight - barcodeTextHeight - (2 * g.DpiY / 72f)); // Вычитаем текст и отступ
+ float barcodePrintWidth = drawableWidth * 0.98f;
+
+ int barcodePixelHeight = (int)(barcodePrintHeight * g.DpiY / 100f);
+ int barcodePixelWidth = (int)(barcodePrintWidth * g.DpiX / 100f);
+ if (barcodePixelHeight < 10 || barcodePixelWidth < 40) throw new Exception("Слишком мало места для ШК.");
+
+ BarcodeFormat barcodeFormat = BarcodeFormat.CODE_128;
+ // ... (определение barcodeFormat) ...
+ if (_currentBarcode.Length == 13 && long.TryParse(_currentBarcode, out _)) barcodeFormat = BarcodeFormat.EAN_13;
+ else if (_currentBarcode.Length == 8 && long.TryParse(_currentBarcode, out _)) barcodeFormat = BarcodeFormat.EAN_8;
+
+ var barcodeWriter = new ZXing.Windows.Compatibility.BarcodeWriter { Format = barcodeFormat, Options = new EncodingOptions { Height = barcodePixelHeight, Width = barcodePixelWidth, PureBarcode = true, Margin = 1 } };
+
+ using (var barcodeBitmap = barcodeWriter.Write(_currentBarcode))
+ {
+ float barcodeX = drawableX + (drawableWidth - barcodePrintWidth) / 2.0f; // Центрируем
+ g.DrawImage(barcodeBitmap, barcodeX, currentY, barcodePrintWidth, barcodePrintHeight);
+ currentY += barcodePrintHeight + (1 * g.DpiY / 72f); // Отступ 1 пункт
+ }
+
+ // --- Рисуем текст ШК ---
+ SizeF barcodeTextSize = g.MeasureString(_currentBarcode, barcodeTextFont);
+ if (currentY + barcodeTextSize.Height <= printArea.Bottom - marginY)
+ {
+ float textX = drawableX + (drawableWidth - barcodeTextSize.Width) / 2.0f; // Центрируем
+ g.DrawString(_currentBarcode, barcodeTextFont, Brushes.Black, textX, currentY);
+ }
+ else { System.Diagnostics.Debug.WriteLine("SKIPPING Barcode text - no space"); }
+
+ }
+ catch (Exception exBarcode)
+ {
+ System.Diagnostics.Debug.WriteLine($"Error drawing barcode: {exBarcode.Message}");
+ g.DrawString("Ошибка ШК", regularFont, Brushes.Red, drawableX, currentY); // Рисуем ошибку вместо ШК
+ }
+ }
+ else { System.Diagnostics.Debug.WriteLine("SKIPPING Barcode - not enough vertical space"); }
+
+ }
+ catch (Exception exDraw) { System.Diagnostics.Debug.WriteLine($"Error drawing page: {exDraw}"); }
+ finally { titleFont.Dispose(); regularFont.Dispose(); barcodeTextFont.Dispose(); }
+
+ e.HasMorePages = false;
+ }
+
+ } // Конец класса Form1
+} // Конец namespace
\ No newline at end of file
diff --git a/Form1.resx b/Form1.resx
new file mode 100644
index 0000000..1af7de1
--- /dev/null
+++ b/Form1.resx
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/FormManageStores.Designer.cs b/FormManageStores.Designer.cs
new file mode 100644
index 0000000..09e93fd
--- /dev/null
+++ b/FormManageStores.Designer.cs
@@ -0,0 +1,201 @@
+namespace SKLADm
+{
+ partial class FormManageStores
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.lstStores = new System.Windows.Forms.ListBox();
+ this.label1 = new System.Windows.Forms.Label();
+ this.groupBox1 = new System.Windows.Forms.GroupBox();
+ this.btnAddStore = new System.Windows.Forms.Button();
+ this.txtEditApiKey = new System.Windows.Forms.TextBox();
+ this.label4 = new System.Windows.Forms.Label();
+ this.txtEditClientId = new System.Windows.Forms.TextBox();
+ this.label3 = new System.Windows.Forms.Label();
+ this.txtStoreName = new System.Windows.Forms.TextBox();
+ this.label2 = new System.Windows.Forms.Label();
+ this.btnDeleteStore = new System.Windows.Forms.Button();
+ this.btnClose = new System.Windows.Forms.Button();
+ this.groupBox1.SuspendLayout();
+ this.SuspendLayout();
+ //
+ // lstStores
+ //
+ this.lstStores.FormattingEnabled = true;
+ this.lstStores.Location = new System.Drawing.Point(12, 25);
+ this.lstStores.Name = "lstStores";
+ this.lstStores.Size = new System.Drawing.Size(188, 199); // Adjust height as needed
+ this.lstStores.TabIndex = 0;
+ // Add event handler if you want to display details on selection:
+ // this.lstStores.SelectedIndexChanged += new System.EventHandler(this.lstStores_SelectedIndexChanged);
+ //
+ // label1
+ //
+ this.label1.AutoSize = true;
+ this.label1.Location = new System.Drawing.Point(12, 9);
+ this.label1.Name = "label1";
+ this.label1.Size = new System.Drawing.Size(121, 13);
+ this.label1.TabIndex = 1;
+ this.label1.Text = "Сохраненные магазины:";
+ //
+ // groupBox1
+ //
+ this.groupBox1.Controls.Add(this.btnAddStore);
+ this.groupBox1.Controls.Add(this.txtEditApiKey);
+ this.groupBox1.Controls.Add(this.label4);
+ this.groupBox1.Controls.Add(this.txtEditClientId);
+ this.groupBox1.Controls.Add(this.label3);
+ this.groupBox1.Controls.Add(this.txtStoreName);
+ this.groupBox1.Controls.Add(this.label2);
+ this.groupBox1.Location = new System.Drawing.Point(219, 25);
+ this.groupBox1.Name = "groupBox1";
+ this.groupBox1.Size = new System.Drawing.Size(303, 155); // Adjust size as needed
+ this.groupBox1.TabIndex = 2;
+ this.groupBox1.TabStop = false;
+ this.groupBox1.Text = "Добавить новый магазин";
+ //
+ // btnAddStore
+ //
+ this.btnAddStore.Location = new System.Drawing.Point(196, 118);
+ this.btnAddStore.Name = "btnAddStore";
+ this.btnAddStore.Size = new System.Drawing.Size(97, 23);
+ this.btnAddStore.TabIndex = 6;
+ this.btnAddStore.Text = "Добавить";
+ this.btnAddStore.UseVisualStyleBackColor = true;
+ this.btnAddStore.Click += new System.EventHandler(this.btnAddStore_Click);
+ //
+ // txtEditApiKey
+ //
+ this.txtEditApiKey.Location = new System.Drawing.Point(74, 86);
+ this.txtEditApiKey.Name = "txtEditApiKey";
+ this.txtEditApiKey.PasswordChar = '*';
+ this.txtEditApiKey.Size = new System.Drawing.Size(219, 20);
+ this.txtEditApiKey.TabIndex = 5;
+ //
+ // label4
+ //
+ this.label4.AutoSize = true;
+ this.label4.Location = new System.Drawing.Point(16, 89);
+ this.label4.Name = "label4";
+ this.label4.Size = new System.Drawing.Size(48, 13);
+ this.label4.TabIndex = 4;
+ this.label4.Text = "Api-Key:";
+ //
+ // txtEditClientId
+ //
+ this.txtEditClientId.Location = new System.Drawing.Point(74, 57);
+ this.txtEditClientId.Name = "txtEditClientId";
+ this.txtEditClientId.Size = new System.Drawing.Size(219, 20);
+ this.txtEditClientId.TabIndex = 3;
+ //
+ // label3
+ //
+ this.label3.AutoSize = true;
+ this.label3.Location = new System.Drawing.Point(16, 60);
+ this.label3.Name = "label3";
+ this.label3.Size = new System.Drawing.Size(50, 13);
+ this.label3.TabIndex = 2;
+ this.label3.Text = "Client ID:";
+ //
+ // txtStoreName
+ //
+ this.txtStoreName.Location = new System.Drawing.Point(74, 28);
+ this.txtStoreName.Name = "txtStoreName";
+ this.txtStoreName.Size = new System.Drawing.Size(219, 20);
+ this.txtStoreName.TabIndex = 1;
+ //
+ // label2
+ //
+ this.label2.AutoSize = true;
+ this.label2.Location = new System.Drawing.Point(16, 31);
+ this.label2.Name = "label2";
+ this.label2.Size = new System.Drawing.Size(60, 13);
+ this.label2.TabIndex = 0;
+ this.label2.Text = "Название:";
+ //
+ // btnDeleteStore
+ //
+ this.btnDeleteStore.Location = new System.Drawing.Point(12, 230); // Position below listbox
+ this.btnDeleteStore.Name = "btnDeleteStore";
+ this.btnDeleteStore.Size = new System.Drawing.Size(188, 23);
+ this.btnDeleteStore.TabIndex = 3;
+ this.btnDeleteStore.Text = "Удалить выбранный";
+ this.btnDeleteStore.UseVisualStyleBackColor = true;
+ this.btnDeleteStore.Click += new System.EventHandler(this.btnDeleteStore_Click);
+ //
+ // btnClose
+ //
+ this.btnClose.DialogResult = System.Windows.Forms.DialogResult.Cancel; // Makes Esc close the form
+ this.btnClose.Location = new System.Drawing.Point(447, 230); // Position bottom right
+ this.btnClose.Name = "btnClose";
+ this.btnClose.Size = new System.Drawing.Size(75, 23);
+ this.btnClose.TabIndex = 4;
+ this.btnClose.Text = "Закрыть";
+ this.btnClose.UseVisualStyleBackColor = true;
+ this.btnClose.Click += new System.EventHandler(this.btnClose_Click);
+ //
+ // FormManageStores
+ //
+ this.AcceptButton = this.btnAddStore; // Enter in groupbox adds store
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.CancelButton = this.btnClose; // Esc closes form
+ this.ClientSize = new System.Drawing.Size(534, 267); // Adjust size as needed
+ this.Controls.Add(this.btnClose);
+ this.Controls.Add(this.btnDeleteStore);
+ this.Controls.Add(this.groupBox1);
+ this.Controls.Add(this.label1);
+ this.Controls.Add(this.lstStores);
+ this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; // Prevent resizing
+ this.MaximizeBox = false;
+ this.MinimizeBox = false;
+ this.Name = "FormManageStores";
+ this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; // Center over Form1
+ this.Text = "Управление магазинами Ozon";
+ this.groupBox1.ResumeLayout(false);
+ this.groupBox1.PerformLayout();
+ this.ResumeLayout(false);
+ this.PerformLayout();
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.ListBox lstStores;
+ private System.Windows.Forms.Label label1;
+ private System.Windows.Forms.GroupBox groupBox1;
+ private System.Windows.Forms.Button btnAddStore;
+ private System.Windows.Forms.TextBox txtEditApiKey;
+ private System.Windows.Forms.Label label4;
+ private System.Windows.Forms.TextBox txtEditClientId;
+ private System.Windows.Forms.Label label3;
+ private System.Windows.Forms.TextBox txtStoreName;
+ private System.Windows.Forms.Label label2;
+ private System.Windows.Forms.Button btnDeleteStore;
+ private System.Windows.Forms.Button btnClose;
+ }
+}
diff --git a/FormManageStores.cs b/FormManageStores.cs
new file mode 100644
index 0000000..41df99e
--- /dev/null
+++ b/FormManageStores.cs
@@ -0,0 +1,150 @@
+using Newtonsoft.Json;
+using OzonInternalLabelPrinter; // Добавляем пространство имен для OzonStore
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography; // Для ProtectedData
+using System.Text;
+using System.Windows.Forms;
+
+namespace SKLADm
+{
+ public partial class FormManageStores : Form
+ {
+ private List _stores;
+ // Энтропия для шифрования (можно оставить null или использовать свой секрет)
+ private static readonly byte[] s_entropy = null; // Или Encoding.UTF8.GetBytes("MyOptionalEntropy");
+
+ public FormManageStores()
+ {
+ InitializeComponent();
+ LoadStoresToListBox();
+ }
+
+ // --- Шифрование Api-Key ---
+ private string EncryptApiKey(string apiKey)
+ {
+ if (string.IsNullOrEmpty(apiKey)) return string.Empty;
+ try
+ {
+ byte[] apiKeyBytes = Encoding.UTF8.GetBytes(apiKey);
+ byte[] encryptedBytes = ProtectedData.Protect(apiKeyBytes, s_entropy, DataProtectionScope.CurrentUser);
+ return Convert.ToBase64String(encryptedBytes);
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"Ошибка шифрования ключа: {ex.Message}", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ return null; // Возвращаем null при ошибке
+ }
+ }
+
+ // --- Загрузка магазинов из настроек ---
+ private void LoadStoresToListBox()
+ {
+ string json = Properties.Settings.Default.SavedStoresJson;
+ _stores = new List();
+ if (!string.IsNullOrEmpty(json))
+ {
+ try
+ {
+ _stores = JsonConvert.DeserializeObject>(json) ?? new List();
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"Ошибка загрузки списка магазинов: {ex.Message}", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Warning);
+ _stores = new List(); // Создаем пустой список при ошибке
+ }
+ }
+ UpdateListBox();
+ }
+
+ // --- Сохранение магазинов в настройки ---
+ private void SaveStores()
+ {
+ try
+ {
+ string json = JsonConvert.SerializeObject(_stores, Formatting.Indented);
+ Properties.Settings.Default.SavedStoresJson = json;
+ Properties.Settings.Default.Save();
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"Ошибка сохранения списка магазинов: {ex.Message}", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+ }
+
+ // --- Обновление ListBox ---
+ private void UpdateListBox()
+ {
+ lstStores.DataSource = null; // Сначала отвязываем источник
+ lstStores.DataSource = _stores; // Привязываем обновленный список
+ lstStores.DisplayMember = "StoreName"; // Отображаем имя
+ }
+
+ // --- Кнопка "Добавить магазин" ---
+ private void btnAddStore_Click(object sender, EventArgs e)
+ {
+ string storeName = txtStoreName.Text.Trim();
+ string clientId = txtEditClientId.Text.Trim();
+ string apiKey = txtEditApiKey.Text.Trim(); // Берем ключ как есть
+
+ if (string.IsNullOrEmpty(storeName) || string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(apiKey))
+ {
+ MessageBox.Show("Пожалуйста, заполните все поля: Название, Client ID, Api-Key.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Warning);
+ return;
+ }
+
+ // Проверяем, нет ли уже магазина с таким именем
+ if (_stores.Any(s => s.StoreName.Equals(storeName, StringComparison.OrdinalIgnoreCase)))
+ {
+ MessageBox.Show("Магазин с таким названием уже существует.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Warning);
+ return;
+ }
+
+ // Шифруем ключ
+ string encryptedKey = EncryptApiKey(apiKey);
+ if (encryptedKey == null) // Если шифрование не удалось
+ {
+ return;
+ }
+
+ // Создаем и добавляем магазин
+ OzonStore newStore = new OzonStore(storeName, clientId, encryptedKey);
+ _stores.Add(newStore);
+
+ // Сохраняем и обновляем список
+ SaveStores();
+ UpdateListBox();
+
+ // Очищаем поля ввода
+ txtStoreName.Clear();
+ txtEditClientId.Clear();
+ txtEditApiKey.Clear();
+ }
+
+ // --- Кнопка "Удалить выбранный" (Опционально) ---
+ private void btnDeleteStore_Click(object sender, EventArgs e)
+ {
+ if (lstStores.SelectedItem is OzonStore selectedStore)
+ {
+ if (MessageBox.Show($"Вы уверены, что хотите удалить магазин '{selectedStore.StoreName}'?", "Подтверждение", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
+ {
+ _stores.Remove(selectedStore);
+ SaveStores();
+ UpdateListBox();
+ }
+ }
+ else
+ {
+ MessageBox.Show("Выберите магазин для удаления.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ }
+ }
+
+ // --- Кнопка "Закрыть" ---
+ private void btnClose_Click(object sender, EventArgs e)
+ {
+ this.DialogResult = DialogResult.OK; // Указываем, что форма закрыта штатно
+ this.Close();
+ }
+ }
+}
diff --git a/FormManageStores.resx b/FormManageStores.resx
new file mode 100644
index 0000000..1af7de1
--- /dev/null
+++ b/FormManageStores.resx
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/OzonStore.cs b/OzonStore.cs
new file mode 100644
index 0000000..54b28ee
--- /dev/null
+++ b/OzonStore.cs
@@ -0,0 +1,28 @@
+using System;
+
+namespace OzonInternalLabelPrinter
+{
+ [Serializable] // Важно для возможной сериализации в будущем, хотя с JSON необязательно
+ public class OzonStore
+ {
+ public string StoreName { get; set; } // Имя, которое видит пользователь
+ public string ClientId { get; set; }
+ public string EncryptedApiKey { get; set; } // Храним ЗАШИФРОВАННЫЙ ключ
+
+ // Конструктор для удобства
+ public OzonStore(string name, string clientId, string encryptedKey)
+ {
+ StoreName = name;
+ ClientId = clientId;
+ EncryptedApiKey = encryptedKey;
+ }
+
+ public OzonStore() { } // Пустой конструктор для сериализации
+
+ // Переопределяем ToString, чтобы в ComboBox отображалось имя
+ public override string ToString()
+ {
+ return StoreName ?? "Без имени";
+ }
+ }
+}
\ No newline at end of file
diff --git a/Program.cs b/Program.cs
new file mode 100644
index 0000000..93fa4e4
--- /dev/null
+++ b/Program.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using OzonInternalLabelPrinter; // Добавляем директиву using для пространства имен с Form1
+
+namespace SKLADm
+{
+ internal static class Program
+ {
+ ///
+ /// Главная точка входа для приложения.
+ ///
+ [STAThread]
+ static void Main()
+ {
+ Application.EnableVisualStyles();
+ Application.SetCompatibleTextRenderingDefault(false);
+ Application.Run(new Form1());
+ }
+ }
+}
diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..d9343ab
--- /dev/null
+++ b/Properties/AssemblyInfo.cs
@@ -0,0 +1,33 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// Общие сведения об этой сборке предоставляются следующим набором
+// набора атрибутов. Измените значения этих атрибутов для изменения сведений,
+// связанных со сборкой.
+[assembly: AssemblyTitle("SKLADm")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("SKLADm")]
+[assembly: AssemblyCopyright("Copyright © 2025")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Установка значения False для параметра ComVisible делает типы в этой сборке невидимыми
+// для компонентов COM. Если необходимо обратиться к типу в этой сборке через
+// COM, следует установить атрибут ComVisible в TRUE для этого типа.
+[assembly: ComVisible(false)]
+
+// Следующий GUID служит для идентификации библиотеки типов, если этот проект будет видимым для COM
+[assembly: Guid("17f6818e-f645-43c4-a25a-eab41e5e1be6")]
+
+// Сведения о версии сборки состоят из указанных ниже четырех значений:
+//
+// Основной номер версии
+// Дополнительный номер версии
+// Номер сборки
+// Редакция
+//
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..6630341
--- /dev/null
+++ b/Properties/Resources.Designer.cs
@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+//
+// Этот код создан программным средством.
+// Версия среды выполнения: 4.0.30319.42000
+//
+// Изменения в этом файле могут привести к неправильному поведению и будут утрачены, если
+// код создан повторно.
+//
+//------------------------------------------------------------------------------
+
+namespace SKLADm.Properties
+{
+
+
+ ///
+ /// Класс ресурсов со строгим типом для поиска локализованных строк и пр.
+ ///
+ // Этот класс был автоматически создан при помощи StronglyTypedResourceBuilder
+ // класс с помощью таких средств, как ResGen или Visual Studio.
+ // Для добавления или удаления члена измените файл .ResX, а затем перезапустите ResGen
+ // с параметром /str или заново постройте свой VS-проект.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources
+ {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources()
+ {
+ }
+
+ ///
+ /// Возврат кэшированного экземпляра ResourceManager, используемого этим классом.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager
+ {
+ get
+ {
+ if ((resourceMan == null))
+ {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SKLADm.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Переопределяет свойство CurrentUICulture текущего потока для всех
+ /// подстановки ресурсов с помощью этого класса ресурсов со строгим типом.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture
+ {
+ get
+ {
+ return resourceCulture;
+ }
+ set
+ {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/Properties/Resources.resx b/Properties/Resources.resx
new file mode 100644
index 0000000..af7dbeb
--- /dev/null
+++ b/Properties/Resources.resx
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/Properties/Settings.Designer.cs b/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..0c10770
--- /dev/null
+++ b/Properties/Settings.Designer.cs
@@ -0,0 +1,38 @@
+//------------------------------------------------------------------------------
+//
+// Этот код создан программой.
+// Исполняемая версия:4.0.30319.42000
+//
+// Изменения в этом файле могут привести к неправильной работе и будут потеряны в случае
+// повторной генерации кода.
+//
+//------------------------------------------------------------------------------
+
+namespace SKLADm.Properties {
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.13.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default {
+ get {
+ return defaultInstance;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("")]
+ public string SavedStoresJson {
+ get {
+ return ((string)(this["SavedStoresJson"]));
+ }
+ set {
+ this["SavedStoresJson"] = value;
+ }
+ }
+ }
+}
diff --git a/Properties/Settings.settings b/Properties/Settings.settings
new file mode 100644
index 0000000..da36574
--- /dev/null
+++ b/Properties/Settings.settings
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SKLADm.csproj b/SKLADm.csproj
new file mode 100644
index 0000000..502966b
--- /dev/null
+++ b/SKLADm.csproj
@@ -0,0 +1,122 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {17F6818E-F645-43C4-A25A-EAB41E5E1BE6}
+ WinExe
+ SKLADm
+ SKLADm
+ v4.6.2
+ 512
+ true
+ true
+
+
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll
+
+
+
+
+ packages\System.Drawing.Common.8.0.12\lib\net462\System.Drawing.Common.dll
+
+
+
+ packages\System.Security.Cryptography.ProtectedData.9.0.4\lib\net462\System.Security.Cryptography.ProtectedData.dll
+
+
+
+
+
+
+
+
+
+
+
+ packages\ZXing.Net.0.16.10\lib\net461\zxing.dll
+
+
+ packages\ZXing.Net.0.16.10\lib\net461\zxing.presentation.dll
+
+
+ packages\ZXing.Net.Bindings.Windows.Compatibility.0.16.13\lib\netstandard2.0\ZXing.Windows.Compatibility.dll
+
+
+
+
+ Form
+
+
+ Form1.cs
+
+
+ Form
+
+
+ FormManageStores.cs
+
+
+
+
+
+ Form1.cs
+
+
+ FormManageStores.cs
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+ Designer
+
+
+ True
+ Resources.resx
+
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+ True
+ Settings.settings
+ True
+
+
+
+
+
+
+
+
+
+ Данный проект ссылается на пакеты NuGet, отсутствующие на этом компьютере. Используйте восстановление пакетов NuGet, чтобы скачать их. Дополнительную информацию см. по адресу: http://go.microsoft.com/fwlink/?LinkID=322105. Отсутствует следующий файл: {0}.
+
+
+
+
\ No newline at end of file
diff --git a/SKLADm.sln b/SKLADm.sln
new file mode 100644
index 0000000..e5a9478
--- /dev/null
+++ b/SKLADm.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.13.35913.81 d17.13
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SKLADm", "SKLADm.csproj", "{17F6818E-F645-43C4-A25A-EAB41E5E1BE6}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {17F6818E-F645-43C4-A25A-EAB41E5E1BE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {17F6818E-F645-43C4-A25A-EAB41E5E1BE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {17F6818E-F645-43C4-A25A-EAB41E5E1BE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {17F6818E-F645-43C4-A25A-EAB41E5E1BE6}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {58FC063D-E3B8-421B-BCB0-0349A0C4DCDB}
+ EndGlobalSection
+EndGlobal
diff --git a/packages.config b/packages.config
new file mode 100644
index 0000000..c09cfa3
--- /dev/null
+++ b/packages.config
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file