0.1.2-prod

This commit is contained in:
2025-08-21 10:42:35 +03:00
parent 6fab3c0a0d
commit 173d4c670c
5 changed files with 333 additions and 106 deletions

1
go.mod
View File

@@ -5,6 +5,7 @@ go 1.23.4
require (
github.com/go-ole/go-ole v1.3.0
go.bug.st/serial v1.6.4
gopkg.in/natefinch/lumberjack.v2 v2.2.1
)
require (

2
go.sum
View File

@@ -13,5 +13,7 @@ go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

231
main.go
View File

@@ -3,6 +3,7 @@ package main
import (
"encoding/json"
"fmt"
"io"
"log"
"os"
"path/filepath"
@@ -10,15 +11,21 @@ import (
"time"
"shtrih-kkt/pkg/shtrih"
"gopkg.in/natefinch/lumberjack.v2"
)
const (
configFileName = "connect.json"
outputDir = "date"
comSearchTimeout = 220 * time.Millisecond
tcpSearchTimeout = 300 * time.Millisecond
configFileName = "connect.json"
serviceConfigName = "service.json"
outputDir = "date"
logsDir = "logs"
comSearchTimeout = 200 * time.Millisecond // Уменьшенный таймаут
tcpSearchTimeout = 150 * time.Millisecond
)
// --- СТРУКТУРЫ ДЛЯ ПАРСИНГА КОНФИГУРАЦИОННЫХ ФАЙЛОВ ---
// ConfigFile соответствует структуре файла connect.json
type ConfigFile struct {
Timeout int `json:"timeout_to_ip_port"`
@@ -35,6 +42,17 @@ type ConnectionSettings struct {
IPPort string `json:"ip_port"`
}
// ServiceFile используется для чтения настроек логирования из service.json
type ServiceFile struct {
Service ServiceConfig `json:"service"`
}
// ServiceConfig содержит параметры логирования
type ServiceConfig struct {
LogLevel string `json:"log_level"`
LogDays int `json:"log_days"`
}
// PolledDevice связывает конфигурацию, использованную для подключения,
// с фискальной информацией, полученной от устройства.
type PolledDevice struct {
@@ -42,13 +60,15 @@ type PolledDevice struct {
Info *shtrih.FiscalInfo
}
// --- ОСНОВНАЯ ЛОГИКА ПРИЛОЖЕНИЯ ---
func main() {
log.Println("Запуск приложения для сбора данных с ККТ Штрих-М...")
configData, err := os.ReadFile(configFileName)
if err != nil {
if os.IsNotExist(err) {
log.Printf("Файл конфигурации '%s' не найден. Запускаю режим автопоиска устройств...", configFileName)
log.Printf("Файл конфигурации '%s' не найден. Запускаю режим автопоиска...", configFileName)
runDiscoveryMode()
} else {
log.Fatalf("Ошибка чтения файла конфигурации '%s': %v", configFileName, err)
@@ -61,11 +81,56 @@ func main() {
log.Println("Работа приложения завершена.")
}
// setupLogger настраивает запись логов в файл для стационарного режима.
func setupLogger() {
data, err := os.ReadFile(serviceConfigName)
if err != nil {
log.Printf("Предупреждение: файл настроек '%s' не найден. Логирование продолжится в консоль.", serviceConfigName)
return
}
var serviceFile ServiceFile
if err := json.Unmarshal(data, &serviceFile); err != nil {
log.Printf("Предупреждение: не удалось прочитать настройки из '%s' (%v). Логирование продолжится в консоль.", serviceConfigName, err)
return
}
// Устанавливаем значения по умолчанию, если в файле их нет
logDays := serviceFile.Service.LogDays
if logDays <= 0 {
logDays = 7
}
// Создаем папку для логов, если ее нет
if err := os.MkdirAll(logsDir, 0755); err != nil {
log.Printf("Ошибка создания директории для логов '%s': %v. Логирование продолжится в консоль.", logsDir, err)
return
}
logFilePath := filepath.Join(logsDir, "shtrih-scanner.log")
// Настраиваем ротацию логов
lumberjackLogger := &lumberjack.Logger{
Filename: logFilePath,
MaxSize: 5, // мегабайты
MaxBackups: 10,
MaxAge: logDays, // дни
Compress: true, // сжимать старые файлы
}
// Устанавливаем вывод логов и в файл, и в консоль
log.SetOutput(io.MultiWriter(os.Stdout, lumberjackLogger))
log.Printf("Логирование настроено. Уровень: %s, ротация: %d дней. Файл: %s", serviceFile.Service.LogLevel, logDays, logFilePath)
}
func runConfigMode(data []byte) {
// Первым делом настраиваем логирование для стационарного режима!
setupLogger()
var configFile ConfigFile
if err := json.Unmarshal(data, &configFile); err != nil {
log.Printf("Ошибка парсинга JSON из '%s': %v. Переключаюсь на режим автопоиска.", configFileName, err)
runDiscoveryMode()
runDiscoveryMode() // В случае ошибки автопоиск будет логировать только в консоль
return
}
@@ -99,16 +164,13 @@ func runDiscoveryMode() {
log.Printf("Найдено %d устройств. Начинаю сбор информации...", len(configs))
polledDevices := processDevices(configs)
// Если были успешно опрошены какие-либо устройства, сохраняем их конфигурацию
if len(polledDevices) > 0 {
saveConfiguration(polledDevices)
}
}
// processDevices - основная функция, реализующая "умную" логику обновления и создания файлов.
// Теперь она возвращает срез успешно опрошенных устройств.
func processDevices(configs []shtrih.Config) []PolledDevice {
// Шаг 1: Сначала собираем информацию со всех найденных устройств.
var polledDevices []PolledDevice // Было: var freshKKTData []*shtrih.FiscalInfo
var polledDevices []PolledDevice
for _, config := range configs {
log.Printf("--- Опрашиваю устройство: %+v ---", config)
driver := shtrih.New(config)
@@ -119,7 +181,7 @@ func processDevices(configs []shtrih.Config) []PolledDevice {
}
info, err := driver.GetFiscalInfo()
driver.Disconnect() // Отключаемся сразу после получения данных
driver.Disconnect()
if err != nil {
log.Printf("Ошибка при получении фискальной информации: %v", err)
@@ -129,30 +191,26 @@ func processDevices(configs []shtrih.Config) []PolledDevice {
log.Println("Получена пустая информация или отсутствует серийный номер, данные проигнорированы.")
continue
}
// Сохраняем и конфигурацию, и результат
polledDevices = append(polledDevices, PolledDevice{Config: config, Info: info})
}
if len(polledDevices) == 0 {
log.Println("--- Не удалось собрать данные ни с одного устройства. Завершение. ---")
return nil // Возвращаем nil, если ничего не найдено
return nil
}
log.Printf("--- Всего собрано данных с %d устройств. Начинаю обработку файлов. ---", len(polledDevices))
// Шаг 2: Ищем "донора" данных о рабочей станции в папке /date.
sourceWSDataMap := findSourceWorkstationData()
// Шаг 3: Обрабатываем каждого "свежего" ККТ в соответствии с новой логикой.
var successCount int
for _, pd := range polledDevices { // Итерируемся по новой структуре
kktInfo := pd.Info // Получаем доступ к данным ККТ
for _, pd := range polledDevices {
kktInfo := pd.Info
fileName := fmt.Sprintf("%s.json", kktInfo.SerialNumber)
filePath := filepath.Join(outputDir, fileName)
// ... (остальная часть цикла остается без изменений) ...
if _, err := os.Stat(filePath); err == nil {
log.Printf("Файл для ККТ %s уже существует. Обновляю временную метку...", kktInfo.SerialNumber)
log.Printf("Файл для ККТ %s уже существует. Обновляю временные метки...", kktInfo.SerialNumber)
if err := updateTimestampInFile(filePath); err != nil {
log.Printf("Не удалось обновить файл %s: %v", filePath, err)
} else {
@@ -169,7 +227,7 @@ func processDevices(configs []shtrih.Config) []PolledDevice {
hostname, _ := os.Hostname()
wsDataToUse = map[string]interface{}{"hostname": hostname}
}
wsDataToUse["current_time"] = time.Now().Format("2006-01-02 15:04:05")
if err := saveNewMergedInfo(kktInfo, wsDataToUse, filePath); err != nil {
log.Printf("Не удалось создать файл для ККТ %s: %v", kktInfo.SerialNumber, err)
} else {
@@ -179,10 +237,11 @@ func processDevices(configs []shtrih.Config) []PolledDevice {
}
log.Printf("--- Обработка файлов завершена. Успешно создано/обновлено: %d файлов. ---", successCount)
return polledDevices // Возвращаем результат
return polledDevices
}
// findSourceWorkstationData ищет в папке /date любой .json файл и извлекает из него
// все данные как `map[string]interface{}`.
// --- ФУНКЦИИ ДЛЯ РАБОТЫ С ФАЙЛАМИ ---
func findSourceWorkstationData() map[string]interface{} {
files, err := os.ReadDir(outputDir)
if err != nil {
@@ -204,13 +263,11 @@ func findSourceWorkstationData() map[string]interface{} {
continue
}
// Проверяем, что это не файл от нашего ККТ (у него не должно быть поля modelName)
// и что у него есть hostname. Это делает выбор донора более надежным.
_, hasModelName := content["modelName"]
_, hasHostname := content["hostname"]
if !hasModelName && hasHostname {
log.Printf("Найден файл-донор с данными о рабочей станции: %s", filePath)
return content // Возвращаем все содержимое файла как карту.
return content
}
}
}
@@ -219,7 +276,6 @@ func findSourceWorkstationData() map[string]interface{} {
return nil
}
// updateTimestampInFile читает JSON-файл, обновляет в нем поле current_time и перезаписывает его.
func updateTimestampInFile(filePath string) error {
data, err := os.ReadFile(filePath)
if err != nil {
@@ -231,7 +287,10 @@ func updateTimestampInFile(filePath string) error {
return fmt.Errorf("ошибка парсинга JSON: %w", err)
}
content["current_time"] = time.Now().Format("2006-01-02 15:04:05")
// Обновляем оба поля времени
currentTime := time.Now().Format("2006-01-02 15:04:05")
content["current_time"] = currentTime
content["v_time"] = currentTime
updatedData, err := json.MarshalIndent(content, "", " ")
if err != nil {
@@ -241,22 +300,21 @@ func updateTimestampInFile(filePath string) error {
return os.WriteFile(filePath, updatedData, 0644)
}
// saveNewMergedInfo объединяет данные ККТ и данные рабочей станции (в виде map) и сохраняет в новый JSON-файл.
func saveNewMergedInfo(kktInfo *shtrih.FiscalInfo, wsData map[string]interface{}, filePath string) error {
var kktMap map[string]interface{}
kktJSON, _ := json.Marshal(kktInfo)
json.Unmarshal(kktJSON, &kktMap)
// Сливаем карты. Ключи из wsData перезапишут любые совпадения в kktMap.
// Добавляем актуальные временные метки в данные рабочей станции
currentTime := time.Now().Format("2006-01-02 15:04:05")
wsData["current_time"] = currentTime
wsData["v_time"] = currentTime
for key, value := range wsData {
kktMap[key] = value
}
// Удаляем поля, специфичные для ККТ, из данных донора, если они случайно туда попали.
// Это предотвратит запись, например, "serialNumber" от АТОЛ в файл Штриха.
delete(kktMap, "serialNumber")
// Возвращаем серийный номер нашего ККТ, который мы сохранили в структуре kktInfo.
delete(kktMap, "serialNumber")
kktMap["serialNumber"] = kktInfo.SerialNumber
if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil {
@@ -276,20 +334,50 @@ func saveNewMergedInfo(kktInfo *shtrih.FiscalInfo, wsData map[string]interface{}
return nil
}
// convertSettingsToConfigs преобразует настройки из файла в формат, понятный библиотеке.
func saveConfiguration(polledDevices []PolledDevice) {
log.Printf("Сохранение %d найденных конфигураций в файл '%s'...", len(polledDevices), configFileName)
var configFile ConfigFile
data, err := os.ReadFile(configFileName)
if err == nil {
if err := json.Unmarshal(data, &configFile); err != nil {
log.Printf("Предупреждение: файл '%s' поврежден (%v). Он будет перезаписан.", configFileName, err)
configFile = ConfigFile{}
}
}
var newShtrihSettings []ConnectionSettings
for _, pd := range polledDevices {
newShtrihSettings = append(newShtrihSettings, convertConfigToSettings(pd.Config))
}
configFile.Shtrih = newShtrihSettings
updatedData, err := json.MarshalIndent(configFile, "", " ")
if err != nil {
log.Printf("Ошибка: не удалось преобразовать конфигурацию в JSON: %v", err)
return
}
if err := os.WriteFile(configFileName, updatedData, 0644); err != nil {
log.Printf("Ошибка: не удалось записать конфигурацию в файл '%s': %v", configFileName, err)
return
}
log.Printf("Конфигурация успешно сохранена в '%s'.", configFileName)
}
// --- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ---
func convertSettingsToConfigs(settings []ConnectionSettings) []shtrih.Config {
var configs []shtrih.Config
baudRateMap := map[string]int32{
"115200": 6, "57600": 5, "38400": 4, "19200": 3, "9600": 2,
"115200": 6, "57600": 5, "38400": 4, "19200": 3, "9600": 2, "4800": 1,
}
for _, s := range settings {
config := shtrih.Config{
ConnectionType: s.TypeConnect,
Password: 30,
}
config := shtrih.Config{ConnectionType: s.TypeConnect, Password: 30}
switch s.TypeConnect {
case 0: // COM-порт
case 0:
comNum, err := strconv.Atoi(s.ComPort[3:])
if err != nil {
log.Printf("Некорректное имя COM-порта '%s' в конфигурации, пропуск.", s.ComPort)
@@ -303,7 +391,7 @@ func convertSettingsToConfigs(settings []ConnectionSettings) []shtrih.Config {
config.ComName = s.ComPort
config.ComNumber = int32(comNum)
config.BaudRate = baudRate
case 6: // TCP/IP
case 6:
port, err := strconv.Atoi(s.IPPort)
if err != nil {
log.Printf("Некорректный TCP-порт '%s' для IP '%s', пропуск.", s.IPPort, s.IP)
@@ -320,65 +408,20 @@ func convertSettingsToConfigs(settings []ConnectionSettings) []shtrih.Config {
return configs
}
// saveConfiguration обновляет файл connect.json, записывая в него
// конфигурации успешно найденных и опрошенных устройств.
func saveConfiguration(polledDevices []PolledDevice) {
log.Printf("Сохранение %d найденных конфигураций в файл '%s'...", len(polledDevices), configFileName)
// Шаг 1: Читаем существующий файл или создаем новую структуру.
var configFile ConfigFile
data, err := os.ReadFile(configFileName)
if err == nil {
// Файл есть, парсим его, чтобы не потерять другие секции (например, "atol")
if err := json.Unmarshal(data, &configFile); err != nil {
log.Printf("Предупреждение: файл '%s' поврежден (%v). Он будет перезаписан.", configFileName, err)
configFile = ConfigFile{} // Создаем пустую структуру в случае ошибки
}
}
// Шаг 2: Формируем новый срез настроек для "shtrih".
var newShtrihSettings []ConnectionSettings
for _, pd := range polledDevices {
newShtrihSettings = append(newShtrihSettings, convertConfigToSettings(pd.Config))
}
// Шаг 3: Полностью заменяем секцию "shtrih" новыми данными.
configFile.Shtrih = newShtrihSettings
// Шаг 4: Записываем обновленную структуру обратно в файл.
updatedData, err := json.MarshalIndent(configFile, "", " ")
if err != nil {
log.Printf("Ошибка: не удалось преобразовать конфигурацию в JSON: %v", err)
return
}
if err := os.WriteFile(configFileName, updatedData, 0644); err != nil {
log.Printf("Ошибка: не удалось записать конфигурацию в файл '%s': %v", configFileName, err)
return
}
log.Printf("Конфигурация успешно сохранена в '%s'.", configFileName)
}
// convertConfigToSettings преобразует внутренний формат shtrih.Config
// в формат ConnectionSettings для записи в connect.json.
func convertConfigToSettings(config shtrih.Config) ConnectionSettings {
// Карта для обратного преобразования индекса скорости в строку
baudRateReverseMap := map[int32]string{
6: "115200", 5: "57600", 4: "38400", 3: "19200", 2: "9600",
6: "115200", 5: "57600", 4: "38400", 3: "19200", 2: "9600", 1: "4800",
}
settings := ConnectionSettings{
TypeConnect: config.ConnectionType,
}
settings := ConnectionSettings{TypeConnect: config.ConnectionType}
switch config.ConnectionType {
case 0: // COM-порт
case 0:
settings.ComPort = config.ComName
settings.ComBaudrate = baudRateReverseMap[config.BaudRate]
case 6: // TCP/IP
case 6:
settings.IP = config.IPAddress
settings.IPPort = strconv.Itoa(int(config.TCPPort))
}
return settings
}
}

129
pkg/shtrih/driver_test.go Normal file
View File

@@ -0,0 +1,129 @@
// Тесты для пакета shtrih
package shtrih
import (
"fmt"
"reflect"
"testing"
)
// getSampleFiscalInfo создает пример структуры FiscalInfo для использования в тестах.
func getSampleFiscalInfo() *FiscalInfo {
return &FiscalInfo{
ModelName: "ШТРИХ-М-01Ф",
SerialNumber: "0012345678901234",
RNM: "0009876543210987",
OrganizationName: "ООО Ромашка",
Inn: "7701234567",
FnSerial: "9960440300112233",
FfdVersion: "120",
}
}
// TestMockDriver_SuccessfulPath проверяет стандартный успешный сценарий:
// подключение -> получение данных -> отключение.
func TestMockDriver_SuccessfulPath(t *testing.T) {
// Arrange: Готовим данные и создаем мок-драйвер.
sampleData := getSampleFiscalInfo()
driver := NewMockDriver(sampleData, nil, nil)
// Act 1: Подключаемся.
err := driver.Connect()
if err != nil {
t.Fatalf("Connect() вернул неожиданную ошибку: %v", err)
}
// Act 2: Получаем фискальную информацию.
info, err := driver.GetFiscalInfo()
if err != nil {
t.Fatalf("GetFiscalInfo() вернул неожиданную ошибку: %v", err)
}
// Act 3: Отключаемся.
err = driver.Disconnect()
if err != nil {
t.Fatalf("Disconnect() вернул неожиданную ошибку: %v", err)
}
// Assert: Проверяем, что полученные данные соответствуют ожидаемым.
if !reflect.DeepEqual(info, sampleData) {
t.Errorf("Полученные данные не совпадают с мок-данными.\nПолучено: %+v\nОжидалось: %+v", info, sampleData)
}
// Assert: Проверяем, что все методы были вызваны.
// Для этого нам нужно преобразовать интерфейс обратно в конкретный тип мок-драйвера.
mock, ok := driver.(*mockDriver)
if !ok {
t.Fatal("Не удалось преобразовать драйвер в *mockDriver для проверки вызовов.")
}
if !mock.ConnectCalled {
t.Error("Ожидалось, что Connect() будет вызван, но этого не произошло.")
}
if !mock.GetFiscalInfoCalled {
t.Error("Ожидалось, что GetFiscalInfo() будет вызван, но этого не произошло.")
}
if !mock.DisconnectCalled {
t.Error("Ожидалось, что Disconnect() будет вызван, но этого не произошло.")
}
}
// TestMockDriver_ConnectError проверяет, что драйвер корректно обрабатывает
// ошибку, возвращаемую при подключении.
func TestMockDriver_ConnectError(t *testing.T) {
// Arrange: Создаем симулируемую ошибку.
simulatedError := fmt.Errorf("порт COM5 занят")
driver := NewMockDriver(nil, simulatedError, nil)
// Act: Пытаемся подключиться.
err := driver.Connect()
// Assert: Проверяем, что была возвращена именно наша ошибка.
if err == nil {
t.Fatal("Connect() не вернул ошибку, хотя ожидалось.")
}
if err != simulatedError {
t.Errorf("Connect() вернул неверную ошибку. Получено: %v, Ожидалось: %v", err, simulatedError)
}
}
// TestMockDriver_GetInfoWhileDisconnected проверяет, что попытка получить
// данные без предварительного подключения вернет ошибку.
func TestMockDriver_GetInfoWhileDisconnected(t *testing.T) {
// Arrange: Создаем стандартный мок-драйвер.
driver := NewMockDriver(getSampleFiscalInfo(), nil, nil)
// Act: Сразу пытаемся получить данные.
_, err := driver.GetFiscalInfo()
// Assert: Проверяем, что получили ошибку.
if err == nil {
t.Fatal("GetFiscalInfo() не вернул ошибку при вызове без подключения.")
}
}
// TestMockDriver_GetInfoError проверяет, что драйвер корректно обрабатывает
// ошибку, возвращаемую при получении данных.
func TestMockDriver_GetInfoError(t *testing.T) {
// Arrange: Создаем симулируемую ошибку.
simulatedError := fmt.Errorf("ошибка чтения ФН")
driver := NewMockDriver(nil, nil, simulatedError)
// Act
err := driver.Connect()
if err != nil {
t.Fatalf("Connect() неожиданно вернул ошибку: %v", err)
}
info, err := driver.GetFiscalInfo()
// Assert
if err == nil {
t.Fatal("GetFiscalInfo() не вернул ошибку, хотя ожидалось.")
}
if err != simulatedError {
t.Errorf("GetFiscalInfo() вернул неверную ошибку. Получено: %v, Ожидалось: %v", err, simulatedError)
}
if info != nil {
t.Error("GetFiscalInfo() вернул данные вместе с ошибкой, хотя должен был вернуть nil.")
}
}

View File

@@ -1,33 +1,85 @@
// Package shtrih (продолжение)
package shtrih
// mockDriver — это имитация драйвера для тестирования.
import (
"fmt"
"log"
)
// mockDriver представляет собой имитацию реального драйвера для целей тестирования.
// Он реализует интерфейс Driver.
type mockDriver struct {
FiscalInfoToReturn *FiscalInfo
ErrorToReturn error
// MockData - это структура с фискальными данными, которую вернет GetFiscalInfo.
MockData *FiscalInfo
// ConnectErr - ошибка, которую вернет метод Connect, если она задана.
ConnectErr error
// GetFiscalInfoErr - ошибка, которую вернет метод GetFiscalInfo, если она задана.
GetFiscalInfoErr error
// Внутренние флаги для проверки вызовов в тестах.
connected bool
ConnectCalled bool
DisconnectCalled bool
GetFiscalInfoCalled bool
}
// NewMock создает новый мок-драйвер.
func NewMock(info *FiscalInfo, err error) Driver {
// NewMockDriver является конструктором для создания нового мок-драйвера.
// Позволяет заранее определить, какие данные и ошибки будут возвращаться.
func NewMockDriver(data *FiscalInfo, connectErr, getInfoErr error) Driver {
return &mockDriver{
FiscalInfoToReturn: info,
ErrorToReturn: err,
MockData: data,
ConnectErr: connectErr,
GetFiscalInfoErr: getInfoErr,
}
}
// Connect имитирует подключение к ККТ.
func (m *mockDriver) Connect() error {
if m.ErrorToReturn != nil {
return m.ErrorToReturn
m.ConnectCalled = true
log.Println("Mock Driver: Connect() вызван.")
// Если была задана ошибка подключения, возвращаем ее.
if m.ConnectErr != nil {
return m.ConnectErr
}
// Если уже "подключены", ничего не делаем.
if m.connected {
return nil
}
m.connected = true
return nil
}
// Disconnect имитирует отключение от ККТ.
func (m *mockDriver) Disconnect() error {
m.DisconnectCalled = true
log.Println("Mock Driver: Disconnect() вызван.")
// Если не были "подключены", ничего не делаем.
if !m.connected {
return nil
}
m.connected = false
return nil
}
// GetFiscalInfo имитирует получение фискальных данных.
func (m *mockDriver) GetFiscalInfo() (*FiscalInfo, error) {
if m.ErrorToReturn != nil {
return nil, m.ErrorToReturn
m.GetFiscalInfoCalled = true
log.Println("Mock Driver: GetFiscalInfo() вызван.")
// Проверяем, было ли установлено "соединение".
if !m.connected {
return nil, fmt.Errorf("мок-драйвер: не подключен")
}
return m.FiscalInfoToReturn, nil
// Если была задана ошибка получения данных, возвращаем ее.
if m.GetFiscalInfoErr != nil {
return nil, m.GetFiscalInfoErr
}
// Если не были предоставлены мок-данные, возвращаем ошибку.
if m.MockData == nil {
return nil, fmt.Errorf("мок-драйвер: данные для имитации не предоставлены")
}
return m.MockData, nil
}