Browse Source

SVI Разработка кода

SVI 8 months ago
parent
commit
46a1549ce3

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+bin
+bin_dev

+ 16 - 0
.vscode/launch.json

@@ -0,0 +1,16 @@
+{
+    // Используйте IntelliSense, чтобы узнать о возможных атрибутах.
+    // Наведите указатель мыши, чтобы просмотреть описания существующих атрибутов.
+    // Для получения дополнительной информации посетите: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "name": "Serv.Run",
+            "type": "go",
+            "request": "launch",
+            "mode": "auto",
+            "program": "${workspaceFolder}/cmd/adt_service/main.go",
+            "cwd": "${workspaceFolder}/bin_dev",
+        }
+    ]
+}

+ 10 - 0
Makefile

@@ -0,0 +1,10 @@
+build:
+	clear
+	go fmt ./...
+	go build -o ./bin/adt_service ./cmd/adt_service/main.go
+dev.run:
+	clear
+	go fmt ./...
+	go build -race -o ./bin_dev/adt_service_dev ./cmd/adt_service/main.go
+	cd ./bin_dev && \
+	./adt_service_dev

+ 27 - 3
README.md

@@ -4,6 +4,30 @@
 
 ## Оглавление
 
-- [техническое задание](./docs/tz.md);
-- [описание архитектуры](./docs/arch.md);
-- [описание исправлений](./docs/change.md).
+* [техническое задание](./docs/tz.md);
+* [описание архитектуры](./docs/arch.md);
+* [описание исправлений](./docs/change.md).
+
+## Цели сборки
+
+```bash
+make         # сборка для работы
+make build   # -//-
+make dev.run # сборка и запуск для отладки с детектором гонок
+```
+
+## Проверка вызова
+
+```bash
+
+curl --location --request POST 'localhost:8090/orders' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+    "hotel_id": "reddison",
+    "room_id": "lux",
+    "email": "guest@mail.ru",
+    "from": "2024-01-02T00:00:00Z",
+    "to": "2024-01-04T00:00:00Z"
+}'
+
+```

+ 1 - 0
cmd/adt_service/main.go

@@ -9,6 +9,7 @@ import (
 )
 
 func main() {
+	log.Println("main(): adt_service")
 	serv, err := fabrics.MakeService()
 	if err != nil {
 		log.Printf("main(): in create IService, err=\n\t%v\n", err)

+ 13 - 0
docs/change.md

@@ -7,3 +7,16 @@
 * выделение крупных частей в отдельные пакеты;
 * вынесение грязных фабрик в отдельные пакеты.
 * в файле `main.go` исправление структуры `if`, добавление кодов выхода;
+* вытаскивание логики веб-сервера в отдельную сущность;
+* вытаскивание объекта заказа в отдельную сущность;
+* вытаскивание объекта отеля в отдельную сущность;
+* вытаскивание объекта номера в отдельную сущность;
+* вытаскивание набора номера отеля в отдельную сущность;
+* введение пользовательских алиасов типов;
+* вытаскивание лоигки заказа номера в отдельную сущность;
+* вытакивание определения дней заказа в сущность заказа;
+* исправлнеие ошибки вычисления дней заказа (возможно бесконечное число);
+* добавление отсутствия валидации данных во входящем запросе с заказом;
+* изменение имени списка дней из заказа;
+* выявление неправильного типа признака бронирования номера;
+* изменение ID отеля и ID номера на строку (многие сеуверны; `4` и `13` может не быть);

+ 1 - 1
docs/tz.md

@@ -50,7 +50,7 @@ go run main.go
 ```
 
 ```sh
-curl --location --request POST 'localhost:8080/orders' \
+curl --location --request POST 'localhost:8090/orders' \
 --header 'Content-Type: application/json' \
 --data-raw '{
     "hotel_id": "reddison",

+ 20 - 1
intertnal/fabrics/fabrics.go

@@ -2,12 +2,31 @@
 package fabrics
 
 import (
+	"fmt"
+	"log"
+
+	"adt/intertnal/serv_http"
+	"adt/intertnal/serv_process"
 	"adt/intertnal/service"
+	"adt/intertnal/store_ram"
 	"adt/pkg/types"
 )
 
 // MakeService -- возвращает новый объект сервиса
 func MakeService() (types.IService, error) {
-	sf := service.NewService()
+	log.Println("MakeService()")
+	store := store_ram.NewStoreRam()
+	servProcess, err := serv_process.NewServProcess(store)
+	if err != nil {
+		return nil, fmt.Errorf("MakeService(): in create ServProcess, err=\n\t%w", err)
+	}
+	servHttp, err := serv_http.NewServHttp(servProcess)
+	if err != nil {
+		return nil, fmt.Errorf("MakeService(): in create ServHttp, err=\n\t%w", err)
+	}
+	sf, err := service.NewService(store, servHttp)
+	if err != nil {
+		return nil, fmt.Errorf("MakeService(): in service.NewService, err=\n\t%w", err)
+	}
 	return sf, nil
 }

+ 71 - 0
intertnal/serv_http/serv_http.go

@@ -1,7 +1,78 @@
 // package serv_http -- веб-сервер сервиса (графика и АПИ)
 package serv_http
 
+import (
+	"fmt"
+	"io"
+	"log"
+	"net/http"
+
+	"adt/pkg/entities/order"
+	"adt/pkg/types"
+)
+
 // ServHttp -- веб-сервер сервиса (графика и АПИ)
 type ServHttp struct {
+	mux  *http.ServeMux
+	proc types.IServProcess
+}
+
+// NewServHttp -- возвращает новый объект веб-сервера
+func NewServHttp(servProcess types.IServProcess) (*ServHttp, error) {
+	log.Println("NewServHttp()")
+	if servProcess == nil {
+		return nil, fmt.Errorf("NewServHttp(): IServProcess==nil")
+	}
+	sf := &ServHttp{
+		mux:  http.NewServeMux(),
+		proc: servProcess,
+	}
+	sf.mux.HandleFunc("/orders", sf.createOrder)
+	return sf, nil
 }
 
+// Run -- запускает веб-сервер в работу
+func (sf *ServHttp) Run() error {
+	log.Println("ServHttp.Run(): on http://localhost:8090/")
+	err := http.ListenAndServe(":8090", sf.mux)
+	if err != nil {
+		return fmt.Errorf("ServHttp.Run(): in ListenAndServe, err=\n\t%w", err)
+	}
+	return nil
+}
+
+// Создаёт новый заказ
+func (sf *ServHttp) createOrder(w http.ResponseWriter, r *http.Request) {
+	binData, err := io.ReadAll(r.Body)
+	if err != nil {
+		msg := fmt.Sprintf("ERROR ServHttp.createOrder(): in ioutil.ReadAll, err=\n\t%v\n", err)
+		http.Error(w, msg, http.StatusBadRequest)
+		log.Println(msg)
+		return
+	}
+	var newOrder types.IHotelRoomOrder
+	newOrder, err = order.Unmarshall(binData)
+	if err != nil {
+		msg := fmt.Sprintf("ERROR ServHttp.createOrder(): in order.Unmarshal, err=\n\t%v\n", err)
+		http.Error(w, msg, http.StatusBadRequest)
+		log.Println(msg)
+		return
+	}
+	err = sf.proc.MakeHotelRoomOrder(newOrder)
+	if err != nil {
+		msg := fmt.Sprintf("ERROR ServHttp.createOrder(): in proc.MakeHotelRoomOrder, err=\n\t%v\n", err)
+		http.Error(w, msg, http.StatusBadRequest)
+		log.Println(msg)
+		return
+	}
+	w.Header().Set("Content-Type", "application/json")
+	w.WriteHeader(http.StatusCreated)
+	_, err = w.Write(newOrder.Marshall())
+	if err != nil {
+		msg := fmt.Sprintf("ERROR ServHttp.createOrder(): in w.Write response, err=\n\t%v\n", err)
+		http.Error(w, msg, http.StatusBadRequest)
+		log.Println(msg)
+		return
+	}
+	log.Printf("ServHttp.createOrder(): order=%q, created ok\n", newOrder.Id())
+}

+ 74 - 0
intertnal/serv_process/serv_process.go

@@ -0,0 +1,74 @@
+// package serv_process -- процессинг сервиса
+package serv_process
+
+import (
+	"fmt"
+	"log"
+	"strings"
+
+	"adt/pkg/alias"
+	"adt/pkg/entities/hotel_room_busy"
+	"adt/pkg/types"
+)
+
+// ServProcess -- процессинг сервиса
+type ServProcess struct {
+	store types.IStore // Хранилище сервиса
+}
+
+// NewServProcess -- возвращает новый объект процессинга сервиса
+func NewServProcess(store types.IStore) (*ServProcess, error) {
+	log.Println("NewServProcess()")
+	if store == nil {
+		log.Fatalln("NewServProcess(): IStore==nil")
+	}
+	sf := &ServProcess{
+		store: store,
+	}
+	return sf, nil
+}
+
+// MakeOrder -- создаёт новый заказ бронирования
+func (sf *ServProcess) MakeHotelRoomOrder(order types.IHotelRoomOrder) error {
+	if order == nil {
+		return fmt.Errorf("ServProcess.MakeHotelRoomOrder(): IHotelRoomOrder==nil")
+	}
+	log.Printf("ServProcess.MakeHotelRoomOrder(): order=%v\n", order.Id())
+	lstOrderDay := order.ListDay()                           // Дни для бронирования
+	lstRoomBusy := make(map[alias.Date]types.IHotelRoomBusy) // Занятые дни
+	for _, day := range lstOrderDay {
+		busyId := alias.BusyId(fmt.Sprintf("%v.%v.%v", order.Hotel(), order.Room(), day))
+		roomBusy, err := sf.store.GetHotelRoomBusy(busyId)
+		if err != nil {
+			if !strings.Contains(err.Error(), "not found") {
+				return fmt.Errorf("ServProcess.MakeHotelRoomOrder(): in store.GetHotelRoomBusy, err=\n\t%w", err)
+			}
+			roomBusy, err = hotel_room_busy.NewHotelRoomBusy(order.Hotel(), order.Room(), day, false)
+			if err != nil {
+				return fmt.Errorf("ServProcess.MakeHotelRoomOrder(): in create IHotelRoomBusy, err=\n\t%w", err)
+			}
+		}
+		lstRoomBusy[day] = roomBusy
+	}
+
+	for _, roomBusy := range lstRoomBusy {
+		if roomBusy.IsBusy() {
+			delete(lstRoomBusy, roomBusy.Date())
+			continue
+		}
+		roomBusy.SetBusy()
+		if err := sf.store.SetHotelRoomBusy(roomBusy); err != nil {
+			return fmt.Errorf("ServProcess.MakeHotelRoomOrder(): order=%q, in store.SetHotelRoomBusy, err=\n\t%w", order.Id(), err)
+		}
+	}
+
+	if len(lstRoomBusy) == 0 {
+		return fmt.Errorf("ServProcess.MakeHotelRoomOrder(): order=%q, hotel room is busy for selected dates: %v..%v", order.Id(), order.From(), order.To())
+	}
+
+	if err := sf.store.SetOrder(order); err != nil {
+		return fmt.Errorf("ServProcess.MakeHotelRoomOrder(): order=%q, in save.SetOrder, err=\n\t%w", order.Id(), err)
+	}
+	log.Printf("ServProcess.MakeHotelRoomOrder(): order=\n\t%#+v\n", order)
+	return nil
+}

+ 30 - 6
intertnal/service/service.go

@@ -1,19 +1,43 @@
 // package service -- главный тип сервиса
 package service
 
+import (
+	"fmt"
+	"log"
+
+	"adt/pkg/types"
+)
 
 // Service -- главный тип приложения
 type Service struct {
-	// ...
+	store    types.IStore    // Хранилище сервиса
+	servHttp types.IServHttp // Веб-сервер сервиса
 }
 
 // NewService -- возвращает новый объект сервиса
-func NewService() *Service {
-	return &Service{}
+func NewService(store types.IStore,
+	servHttp types.IServHttp) (*Service, error) {
+	log.Println("NewService()")
+	{ // Предусловия
+		if store == nil {
+			return nil, fmt.Errorf("NewService(): IStore==nil")
+		}
+		if servHttp == nil {
+			return nil, fmt.Errorf("NewService(): IServHttp==nil")
+		}
+	}
+	sf := &Service{
+		store:    store,
+		servHttp: servHttp,
+	}
+	return sf, nil
 }
 
 // Run -- запускает сервис в работу
-func (s *Service) Run() error {
-	// ...
+func (sf *Service) Run() error {
+	log.Println("Service.Run()")
+	if err := sf.servHttp.Run(); err != nil {
+		return fmt.Errorf("Service.Run(): in IServHttp.Run, err=\n\t%w", err)
+	}
 	return nil
-}
+}

+ 118 - 0
intertnal/store_ram/store_ram.go

@@ -0,0 +1,118 @@
+// package store_ram -- хранилище данных сервиса
+package store_ram
+
+import (
+	"fmt"
+	"log"
+	"sync"
+
+	"adt/pkg/alias"
+	"adt/pkg/entities/hotel_room_busy"
+	"adt/pkg/types"
+)
+
+// StoreRam -- хранилище данных сервиса
+type StoreRam struct {
+	tableOrder    map[alias.OrderId]types.IHotelRoomOrder // таблца заказов бронирования
+	blockOrder    sync.RWMutex                            // блокировка для работы с таблицей заказов бронирования
+	tableRoomBusy map[alias.BusyId]types.IHotelRoomBusy   // таблца состояний бронирования номеров
+	blockRoomBusy sync.RWMutex                            // блокировка для работы с таблицей состояний бронирования номеров
+}
+
+// NewStoreRam -- возвращает новый хранилище данных сервиса
+func NewStoreRam() *StoreRam {
+	log.Println("NewStoreRam()")
+	return &StoreRam{
+		tableOrder: map[alias.OrderId]types.IHotelRoomOrder{},
+		tableRoomBusy: map[alias.BusyId]types.IHotelRoomBusy{
+			"reddison.lux.2024-01-01": &hotel_room_busy.HotelRoomBusy{
+				Id_:      "reddison.lux.2024-01-01",
+				HotelId_: "reddison",
+				RoomId_:  "lux",
+				Date_:    "2024-01-01",
+				IsBusy_:  true,
+			},
+			"reddison.lux.2024-01-02": &hotel_room_busy.HotelRoomBusy{
+				Id_:      "reddison.lux.2024-01-02",
+				HotelId_: "reddison",
+				RoomId_:  "lux",
+				Date_:    "2024-01-02",
+				IsBusy_:  true,
+			},
+			"reddison.lux.2024-01-03": &hotel_room_busy.HotelRoomBusy{
+				Id_:      "reddison.lux.2024-01-03",
+				HotelId_: "reddison",
+				RoomId_:  "lux",
+				Date_:    "2024-01-03",
+				IsBusy_:  true,
+			},
+			"reddison.lux.2024-01-04": &hotel_room_busy.HotelRoomBusy{
+				Id_:      "reddison.lux.2024-01-04",
+				HotelId_: "reddison",
+				RoomId_:  "lux",
+				Date_:    "2024-01-04",
+				IsBusy_:  true,
+			},
+			"reddison.lux.2024-01-05": &hotel_room_busy.HotelRoomBusy{
+				Id_:      "reddison.lux.2024-01-05",
+				HotelId_: "reddison",
+				RoomId_:  "lux",
+				Date_:    "2024-01-05",
+				IsBusy_:  false,
+			},
+		},
+	}
+}
+
+// SetHotelRoomBusy -- устанавливает состояние бронирования номера
+func (sf *StoreRam) SetHotelRoomBusy(busy types.IHotelRoomBusy) error {
+	sf.blockRoomBusy.Lock()
+	defer sf.blockRoomBusy.Unlock()
+	if busy == nil {
+		return fmt.Errorf("StoreRam.SetHotelRoomBusy(): IHotelRoomBusy==nil")
+	}
+	// _, isOk := sf.tableRoomBusy[busy.Id()]
+	// if isOk {
+	// 	return fmt.Errorf("StoreRam.SetHotelRoomBusy(): IHotelRoomBusy with id=%q already exists", busy.Id())
+	// }
+	sf.tableRoomBusy[busy.Id()] = busy
+	return nil
+}
+
+// GetHotelRoomBusy -- возвращает состояние бронирования номера
+func (sf *StoreRam) GetHotelRoomBusy(id alias.BusyId) (types.IHotelRoomBusy, error) {
+	sf.blockRoomBusy.RLock()
+	defer sf.blockRoomBusy.RUnlock()
+	busy, isOk := sf.tableRoomBusy[id]
+	if !isOk {
+		return nil, fmt.Errorf("StoreRam.GetHotelRoomBusy(): IHotelRoomBusy with id=%q not found", id)
+	}
+	return busy, nil
+}
+
+// SetOrder -- устанавливает ордер заказа бронирования в хранилище
+func (sf *StoreRam) SetOrder(order types.IHotelRoomOrder) error {
+	sf.blockOrder.Lock()
+	defer sf.blockOrder.Unlock()
+	if order == nil {
+		return fmt.Errorf("StoreRam.SetOrder(): IHotelRoomOrder==nil")
+	}
+
+	_, isOk := sf.tableOrder[order.Id()]
+	if isOk {
+		return fmt.Errorf("StoreRam.SetOrder(): IHotelRoomOrder with id %q already exists", order.Id())
+	}
+	sf.tableOrder[order.Id()] = order
+	return nil
+}
+
+// GetOrder -- возвращает ордер по ID
+func (sf *StoreRam) GetOrder(id alias.OrderId) (types.IHotelRoomOrder, error) {
+	sf.blockOrder.RLock()
+	defer sf.blockOrder.RUnlock()
+	order, isOk := sf.tableOrder[id]
+	if !isOk {
+		return nil, fmt.Errorf("StoreRam.GetOrder(): IHotelRoomOrder with id=%q not found", id)
+	}
+	return order, nil
+}

+ 1 - 0
old/current.go

@@ -0,0 +1 @@
+package old

+ 5 - 1
old/main.go → old/original.go.old

@@ -7,7 +7,7 @@
 // - провести рефакторинг кода с выделением слоев и абстракций
 // - применить best-practices там где это имеет смысл
 // - исправить имеющиеся в реализации логические и технические ошибки и неточности
-package main
+package old
 
 import (
 	"encoding/json"
@@ -44,6 +44,10 @@ var Availability = []RoomAvailability{
 	{"reddison", "lux", date(2024, 1, 5), 0},
 }
 
+func init(){
+	main()
+}
+
 func main() {
 	mux := http.NewServeMux()
 	mux.HandleFunc("/orders", createOrder)

+ 26 - 0
pkg/alias/alias.go

@@ -0,0 +1,26 @@
+// package alias -- пакет с псевдонимами
+package alias
+
+// HotelId -- ID отеля
+type HotelId string
+
+// HotelName -- название отеля
+type HotelName string
+
+// RoomId -- ID номера
+type RoomId string
+
+// Email -- email
+type Email string
+
+// DateTime -- строковое представление времени в формате "2006-01-02 15:04:05.000 -0700 MST"
+type DateTime string
+
+// Date -- строковое представление даты в формате "2006-01-02"
+type Date string
+
+// OrderId -- ID заказа бронирования
+type OrderId string
+
+// BusyId -- ID бронирования номера в отеле в формате "hotel_id.room_id.date"
+type BusyId string

+ 2 - 0
pkg/cons/cons.go

@@ -0,0 +1,2 @@
+// package cons -- константы
+package cons

+ 80 - 0
pkg/entities/hotel_room_busy/hotel_room_busy.go

@@ -0,0 +1,80 @@
+// package hotel_room_busy -- сущность состояния номера в отеле
+package hotel_room_busy
+
+import (
+	"fmt"
+	"sync"
+
+	"adt/pkg/alias"
+)
+
+// HotelRoomBusy -- сущность состояния номера в отеле
+type HotelRoomBusy struct {
+	Id_      alias.BusyId  `json:"id"`       // ID бронирования номера в отеле в формате "hotel_id.room_id.date"
+	HotelId_ alias.HotelId `json:"hotel_id"` // ID отеля
+	RoomId_  alias.RoomId  `json:"room_id"`  // ID номера в отеле
+	Date_    alias.Date    `json:"date"`     // Дата бронирования
+	IsBusy_  bool          `json:"is_busy"`  // Состояние бронирования
+	block    sync.RWMutex  // Блокировка на признак занятости
+}
+
+// NewHotelRoomBusy -- возвращает новую сущность состояния номера в отеле
+func NewHotelRoomBusy(
+	hotelId alias.HotelId,
+	roomId alias.RoomId,
+	date alias.Date,
+	isBusy bool) (*HotelRoomBusy, error) {
+	{ // Предусловия
+		if hotelId == "" {
+			return nil, fmt.Errorf("hotel_room_busy.NewHotelRoomBusy(): hotelId is empty")
+		}
+		if roomId == "" {
+			return nil, fmt.Errorf("hotel_room_busy.NewHotelRoomBusy(): roomId is empty")
+		}
+		if date == "" {
+			return nil, fmt.Errorf("hotel_room_busy.NewHotelRoomBusy(): date is empty")
+		}
+	}
+	sf := &HotelRoomBusy{
+		Id_:      alias.BusyId(fmt.Sprintf("%s.%s.%s", hotelId, roomId, date)),
+		HotelId_: hotelId,
+		RoomId_:  roomId,
+		Date_:    date,
+		IsBusy_:  isBusy,
+	}
+	return sf, nil
+}
+
+// Id -- возвращает ID бронирования
+func (sf *HotelRoomBusy) Id() alias.BusyId {
+	return sf.Id_
+}
+
+// SetBusy -- устанавливает состояние бронирования
+func (sf *HotelRoomBusy) SetBusy() {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	sf.IsBusy_ = true
+}
+
+// RoomId -- возвращает ID номера
+func (sf *HotelRoomBusy) RoomId() alias.RoomId {
+	return sf.RoomId_
+}
+
+// IsBusy -- возвращает состояние бронирования
+func (sf *HotelRoomBusy) IsBusy() bool {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.IsBusy_
+}
+
+// HotelId -- возвращает ID отеля
+func (sf *HotelRoomBusy) HotelId() alias.HotelId {
+	return sf.HotelId_
+}
+
+// Date -- возвращает дату бронирования
+func (sf *HotelRoomBusy) Date() alias.Date {
+	return sf.Date_
+}

+ 130 - 0
pkg/entities/order/order.go

@@ -0,0 +1,130 @@
+// package order -- сущность заказа бронирвоания
+package order
+
+import (
+	"encoding/json"
+	"fmt"
+	"strings"
+	"time"
+
+	"adt/pkg/alias"
+)
+
+// Order -- сущность заказа бронирования
+type Order struct {
+	Id_        alias.OrderId `json:"id"`       // Номер заказа
+	HotelId_   alias.HotelId `json:"hotel_id"` // ID отеля
+	RoomID_    alias.RoomId  `json:"room_id"`  // ID номера в указанном отеле
+	UserEmail_ alias.Email   `json:"email"`    // Email пользователя
+	From_      time.Time     `json:"from"`     // Дата заезда
+	To_        time.Time     `json:"to"`       // Дата выезда включительно
+	LstDay_    []alias.Date  `json:"lst_day"`  // Список дней бронирования
+}
+
+// Unmarshall -- десерилаизует структуру Order из json
+func Unmarshall(binData []byte) (*Order, error) {
+	sf := &Order{}
+	err := json.Unmarshal(binData, sf)
+	if err != nil {
+		return nil, fmt.Errorf("order.Unmarshall(): in json.Unmarshal, err=\n\t%w", err)
+	}
+	if sf.HotelId_ == "" {
+		return nil, fmt.Errorf("order.Unmarshall(): hotel_id is empty")
+	}
+	if sf.RoomID_ == "" {
+		return nil, fmt.Errorf("order.Unmarshall(): room_id is empty")
+	}
+	if sf.UserEmail_ == "" {
+		return nil, fmt.Errorf("order.Unmarshall(): email is empty")
+	}
+	if sf.From_ == (time.Time{}) {
+		return nil, fmt.Errorf("order.Unmarshall(): from is empty")
+	}
+	{ // Проверка email
+		if !strings.Contains(string(sf.UserEmail_), "@") {
+			return nil, fmt.Errorf("order.Unmarshall(): email(%q) not have @", sf.UserEmail_)
+		}
+		if !strings.Contains(string(sf.UserEmail_), ".") {
+			return nil, fmt.Errorf("order.Unmarshall(): email(%q) not have .", sf.UserEmail_)
+		}
+	}
+
+	if sf.To_ == (time.Time{}) {
+		return nil, fmt.Errorf("order.Unmarshall(): to is empty")
+	}
+	if err := sf.daysBetween(); err != nil {
+		return nil, fmt.Errorf("order.Unmarshall(): in calc daysBetween, err=\n\t%w", err)
+	}
+	sf.Id_ = alias.OrderId(fmt.Sprintf("%v.%v.%v.%v", sf.HotelId_, sf.RoomID_, sf.From(), sf.To()))
+	return sf, nil
+}
+
+// Marshall -- сериализует структуру Order
+func (sf *Order) Marshall() []byte {
+	binData, _ := json.Marshal(sf)
+	return binData
+}
+
+// Room -- возвращает ID номера в отеле
+func (sf *Order) Room() alias.RoomId {
+	return sf.RoomID_
+}
+
+// Hotel -- возвращает ID отеля
+func (sf *Order) Hotel() alias.HotelId {
+	return sf.HotelId_
+}
+
+// Id -- возвращает номер заказа
+func (sf *Order) Id() alias.OrderId {
+	return sf.Id_
+}
+
+func (sf *Order) daysBetween() error {
+	if sf.From_.After(sf.To_) {
+		return fmt.Errorf("Order.daysBetween(): from > to")
+	}
+	sf.LstDay_ = make([]alias.Date, 0)
+	cleanTo := cleanDay(sf.To_)
+	cleanNext := cleanDay(sf.From_)
+	for { // Перебор дней на добавление
+		cleanFrom := cleanDay(cleanNext)
+		if cleanFrom.After(cleanTo) {
+			break
+		}
+		dateOrder := alias.Date(cleanNext.UTC().Format("2006-01-02"))
+		sf.LstDay_ = append(sf.LstDay_, dateOrder)
+		if len(sf.LstDay_) > 60 {
+			return fmt.Errorf("Order.daysBetween(): lst_day > 60")
+		}
+		cleanNext = cleanNext.AddDate(0, 0, 1)
+	}
+	if len(sf.LstDay_) == 0 {
+		return fmt.Errorf("Order.daysBetween(): lst_day is empty")
+	}
+	return nil
+}
+
+// Округялет дату до дня, FIXME: дата заезда и выезда в 12:00
+func cleanDay(timestamp time.Time) time.Time {
+	return time.Date(timestamp.Year(),
+		timestamp.Month(),
+		timestamp.Day(),
+		0, 0, 0, 0,
+		time.UTC)
+}
+
+// listDay -- возвращает список дней бронирования
+func (sf *Order) ListDay() []alias.Date {
+	return sf.LstDay_
+}
+
+// To -- возвращает дату окончания бронирования (включительно)
+func (sf *Order) To() alias.Date {
+	return alias.Date(sf.To_.UTC().Format("2006-01-02"))
+}
+
+// From -- возвращает дату начала бронирования
+func (sf *Order) From() alias.Date {
+	return alias.Date(sf.From_.UTC().Format("2006-01-02"))
+}

+ 41 - 0
pkg/entities/order/order_test.go

@@ -0,0 +1,41 @@
+package order
+
+import (
+	"testing"
+	"time"
+
+	"adt/pkg/alias"
+)
+
+type tester struct {
+	t *testing.T
+}
+
+func TestOrder(t *testing.T) {
+	sf := &tester{
+		t: t,
+	}
+	sf.unmarshall()
+}
+
+// Десериализует заказ бронирования
+func (sf *tester) unmarshall() {
+	sf.t.Log("unmarshall")
+	sf.unmarshallBad1()
+}
+
+// даты заезда и выезда кривые
+func (sf *tester) unmarshallBad1() {
+	sf.t.Log("unmarshallBad1")
+	from, _ := time.Parse("2006-01-02", "2024-01-02")
+	to, _ := time.Parse("2006-01-02", "2024-01-04")
+	order := &Order{
+		HotelId_:   alias.HotelId("1"),
+		RoomID_:    alias.RoomId("2"),
+		UserEmail_: alias.Email("guest@mail.ru"),
+		From_:      from,
+		To_:        to,
+	}
+	_, err := Unmarshall(order.Marshall())
+	sf.t.Log(err)
+}

+ 9 - 0
pkg/types/ihotel_room.go

@@ -0,0 +1,9 @@
+package types
+
+import "adt/pkg/alias"
+
+// IHotelRoom -- интерфейс для работы с номером отеля
+type IHotelRoom interface {
+	// Id -- возвращает ID номера отеля
+	Id() alias.RoomId
+}

+ 19 - 0
pkg/types/ihotel_room_busy.go

@@ -0,0 +1,19 @@
+package types
+
+import "adt/pkg/alias"
+
+// IHotelRoomBusy -- интерфейс бронирования номера
+type IHotelRoomBusy interface {
+	// Id -- ID бронирования
+	Id() alias.BusyId
+	// HotelId -- ID отеля
+	HotelId() alias.HotelId
+	// RoomId -- ID номера
+	RoomId() alias.RoomId
+	// Date -- дата бронирования номера
+	Date() alias.Date
+	// IsBusy -- возвращает признак состояния бронирования
+	IsBusy() bool
+	// SetBusy -- устанавливает признак состояния бронирования
+	SetBusy()
+}

+ 23 - 0
pkg/types/ihotel_room_order.go

@@ -0,0 +1,23 @@
+package types
+
+import (
+	"adt/pkg/alias"
+)
+
+// IHotelRoomOrder -- интерфейс к заказу номера в отеле
+type IHotelRoomOrder interface {
+	// Id -- номер заказа
+	Id() alias.OrderId
+	// From -- с какой даты заезд
+	From() alias.Date
+	// To -- по какую дату выезд
+	To() alias.Date
+	// ListDay -- список дней для бронирования
+	ListDay() []alias.Date
+	// Hotel -- ID отеля
+	Hotel() alias.HotelId
+	// Room -- ID номера в отеле
+	Room() alias.RoomId
+	// Marshall -- сериализует себя
+	Marshall() []byte
+}

+ 7 - 0
pkg/types/iserv_http.go

@@ -0,0 +1,7 @@
+package types
+
+// IServHttp -- интерфейс встроенного веб-сервера
+type IServHttp interface {
+	// Run -- запускает веб-сервер в работу
+	Run() error
+}

+ 6 - 0
pkg/types/iserv_process.go

@@ -0,0 +1,6 @@
+package types
+
+// IServProcess -- интерфейс сервиса процессинга
+type IServProcess interface {
+	MakeHotelRoomOrder(order IHotelRoomOrder) error
+}

+ 15 - 0
pkg/types/istore.go

@@ -0,0 +1,15 @@
+package types
+
+import "adt/pkg/alias"
+
+// IStore -- интерфейс хранилища
+type IStore interface {
+	// GetOrder -- возвращает ордер по ID
+	GetOrder(id alias.OrderId) (IHotelRoomOrder, error)
+	// SetOrder -- устанавливает ордер по ID
+	SetOrder(IHotelRoomOrder) error
+	// GetHotelRoomBusy -- возвращает состояние бронирования номера
+	GetHotelRoomBusy(id alias.BusyId) (IHotelRoomBusy, error)
+	// SetHotelRoomBusy -- устанавливает состояние бронирования номера
+	SetHotelRoomBusy(IHotelRoomBusy) error
+}