init push

This commit is contained in:
2026-07-16 22:16:45 +07:00
commit 8b068bdb10
1021 changed files with 332816 additions and 0 deletions

View File

@@ -0,0 +1,230 @@
package airrescuechecklist
import "strings"
const (
AirRescuerChecklistScopeCAT = "CAT"
AirRescuerChecklistScopeTA = "TA"
AirRescuerChecklistScopeMO = "MO"
AirRescuerChecklistScopeDI = "DI"
AirRescuerChecklistScopeMI = "MI"
AirRescuerChecklistScopeDO = "DO"
AirRescuerChecklistScopeFR = "FR"
AirRescuerChecklistScopeSA = "SA"
AirRescuerChecklistScopeSO = "SO"
AirRescuerChecklistScopeM10 = "M10"
AirRescuerChecklistScopeM15 = "M15"
AirRescuerChecklistScopeM20 = "M20"
AirRescuerChecklistScopeM25 = "M25"
AirRescuerChecklistScopeDS = "DS"
)
const (
AirRescuerChecklistScopeOverview = AirRescuerChecklistScopeCAT
AirRescuerChecklistScopeDaily = AirRescuerChecklistScopeTA
AirRescuerChecklistScopeMonday = AirRescuerChecklistScopeMO
AirRescuerChecklistScopeTuesday = AirRescuerChecklistScopeDI
AirRescuerChecklistScopeWednesday = AirRescuerChecklistScopeMI
AirRescuerChecklistScopeThursday = AirRescuerChecklistScopeDO
AirRescuerChecklistScopeFriday = AirRescuerChecklistScopeFR
AirRescuerChecklistScopeSaturday = AirRescuerChecklistScopeSA
AirRescuerChecklistScopeSunday = AirRescuerChecklistScopeSO
AirRescuerChecklistScopeDeadline = AirRescuerChecklistScopeDS
)
var airRescuerChecklistPersistedScopeOrder = []string{
AirRescuerChecklistScopeTA,
AirRescuerChecklistScopeMO,
AirRescuerChecklistScopeDI,
AirRescuerChecklistScopeMI,
AirRescuerChecklistScopeDO,
AirRescuerChecklistScopeFR,
AirRescuerChecklistScopeSA,
AirRescuerChecklistScopeSO,
AirRescuerChecklistScopeM10,
AirRescuerChecklistScopeM15,
AirRescuerChecklistScopeM20,
AirRescuerChecklistScopeM25,
AirRescuerChecklistScopeDS,
}
var airRescuerChecklistPersistedScopeSet = map[string]struct{}{
AirRescuerChecklistScopeTA: {},
AirRescuerChecklistScopeMO: {},
AirRescuerChecklistScopeDI: {},
AirRescuerChecklistScopeMI: {},
AirRescuerChecklistScopeDO: {},
AirRescuerChecklistScopeFR: {},
AirRescuerChecklistScopeSA: {},
AirRescuerChecklistScopeSO: {},
AirRescuerChecklistScopeM10: {},
AirRescuerChecklistScopeM15: {},
AirRescuerChecklistScopeM20: {},
AirRescuerChecklistScopeM25: {},
AirRescuerChecklistScopeDS: {},
}
var airRescuerChecklistScopeDisplayNamesEN = map[string]string{
AirRescuerChecklistScopeCAT: "Category",
AirRescuerChecklistScopeTA: "Daily",
AirRescuerChecklistScopeMO: "Monday",
AirRescuerChecklistScopeDI: "Tuesday",
AirRescuerChecklistScopeMI: "Wednesday",
AirRescuerChecklistScopeDO: "Thursday",
AirRescuerChecklistScopeFR: "Friday",
AirRescuerChecklistScopeSA: "Saturday",
AirRescuerChecklistScopeSO: "Sunday",
AirRescuerChecklistScopeM10: "M10",
AirRescuerChecklistScopeM15: "M15",
AirRescuerChecklistScopeM20: "M20",
AirRescuerChecklistScopeM25: "M25",
AirRescuerChecklistScopeDS: "Deadline",
}
var airRescuerChecklistScopeDisplayNamesDE = map[string]string{
AirRescuerChecklistScopeCAT: "Category",
AirRescuerChecklistScopeTA: "Taeglich",
AirRescuerChecklistScopeMO: "Montag",
AirRescuerChecklistScopeDI: "Dienstag",
AirRescuerChecklistScopeMI: "Mittwoch",
AirRescuerChecklistScopeDO: "Donnerstag",
AirRescuerChecklistScopeFR: "Freitag",
AirRescuerChecklistScopeSA: "Samstag",
AirRescuerChecklistScopeSO: "Sonntag",
AirRescuerChecklistScopeM10: "M10",
AirRescuerChecklistScopeM15: "M15",
AirRescuerChecklistScopeM20: "M20",
AirRescuerChecklistScopeM25: "M25",
AirRescuerChecklistScopeDS: "Deadline",
}
var airRescuerChecklistScopeCodesEN = map[string]string{
AirRescuerChecklistScopeCAT: "CAT",
AirRescuerChecklistScopeTA: "DA",
AirRescuerChecklistScopeMO: "MO",
AirRescuerChecklistScopeDI: "TU",
AirRescuerChecklistScopeMI: "WE",
AirRescuerChecklistScopeDO: "TH",
AirRescuerChecklistScopeFR: "FR",
AirRescuerChecklistScopeSA: "SA",
AirRescuerChecklistScopeSO: "SU",
AirRescuerChecklistScopeM10: "M10",
AirRescuerChecklistScopeM15: "M15",
AirRescuerChecklistScopeM20: "M20",
AirRescuerChecklistScopeM25: "M25",
AirRescuerChecklistScopeDS: "DL",
}
var airRescuerChecklistScopeAliases = map[string]string{
"CAT": AirRescuerChecklistScopeCAT,
"OVERVIEW": AirRescuerChecklistScopeCAT,
"TA": AirRescuerChecklistScopeTA,
"DA": AirRescuerChecklistScopeTA,
"DAY": AirRescuerChecklistScopeTA,
"DAILY": AirRescuerChecklistScopeTA,
"MO": AirRescuerChecklistScopeMO,
"MON": AirRescuerChecklistScopeMO,
"MONDAY": AirRescuerChecklistScopeMO,
"DI": AirRescuerChecklistScopeDI,
"TU": AirRescuerChecklistScopeDI,
"TUE": AirRescuerChecklistScopeDI,
"TUESDAY": AirRescuerChecklistScopeDI,
"MI": AirRescuerChecklistScopeMI,
"WE": AirRescuerChecklistScopeMI,
"WED": AirRescuerChecklistScopeMI,
"WEDNESDAY": AirRescuerChecklistScopeMI,
"DO": AirRescuerChecklistScopeDO,
"TH": AirRescuerChecklistScopeDO,
"THU": AirRescuerChecklistScopeDO,
"THURSDAY": AirRescuerChecklistScopeDO,
"FR": AirRescuerChecklistScopeFR,
"FRI": AirRescuerChecklistScopeFR,
"FRIDAY": AirRescuerChecklistScopeFR,
"SA": AirRescuerChecklistScopeSA,
"SAT": AirRescuerChecklistScopeSA,
"SATURDAY": AirRescuerChecklistScopeSA,
"SO": AirRescuerChecklistScopeSO,
"SU": AirRescuerChecklistScopeSO,
"SUN": AirRescuerChecklistScopeSO,
"SUNDAY": AirRescuerChecklistScopeSO,
"M10": AirRescuerChecklistScopeM10,
"M15": AirRescuerChecklistScopeM15,
"M20": AirRescuerChecklistScopeM20,
"M25": AirRescuerChecklistScopeM25,
"DS": AirRescuerChecklistScopeDS,
"DL": AirRescuerChecklistScopeDS,
"DEADLINE": AirRescuerChecklistScopeDS,
}
func CanonicalizeAirRescuerChecklistScope(scopeCode string) string {
return normalizeAirRescuerChecklistScope(scopeCode)
}
func IsAirRescuerChecklistPersistedScope(scopeCode string) bool {
_, ok := airRescuerChecklistPersistedScopeSet[normalizeAirRescuerChecklistScope(scopeCode)]
return ok
}
func IsAirRescuerChecklistQueryScope(scopeCode string) bool {
normalized := normalizeAirRescuerChecklistScope(scopeCode)
if normalized == AirRescuerChecklistScopeCAT {
return true
}
return IsAirRescuerChecklistPersistedScope(normalized)
}
func AirRescuerChecklistScopeDisplayName(scopeCode string) string {
normalized := normalizeAirRescuerChecklistScope(scopeCode)
if v, ok := airRescuerChecklistScopeDisplayNamesEN[normalized]; ok {
return v
}
return normalized
}
func AirRescuerChecklistScopeDisplayNameDE(scopeCode string) string {
normalized := normalizeAirRescuerChecklistScope(scopeCode)
if v, ok := airRescuerChecklistScopeDisplayNamesDE[normalized]; ok {
return v
}
return normalized
}
func AirRescuerChecklistScopeCodeEN(scopeCode string) string {
normalized := normalizeAirRescuerChecklistScope(scopeCode)
if v, ok := airRescuerChecklistScopeCodesEN[normalized]; ok {
return v
}
return normalized
}
func AirRescuerChecklistScopeCodeDE(scopeCode string) string {
return normalizeAirRescuerChecklistScope(scopeCode)
}
func AirRescuerChecklistPersistedScopeOrder() []string {
out := make([]string, 0, len(airRescuerChecklistPersistedScopeOrder))
out = append(out, airRescuerChecklistPersistedScopeOrder...)
return out
}
func IsAirRescuerChecklistScope(scopeCode string) bool {
return IsAirRescuerChecklistPersistedScope(scopeCode)
}
func normalizeAirRescuerChecklistScope(scopeCode string) string {
normalized := strings.ToUpper(strings.TrimSpace(scopeCode))
if canonical, ok := airRescuerChecklistScopeAliases[normalized]; ok {
return canonical
}
return normalized
}

View File

@@ -0,0 +1,159 @@
package airrescuechecklist
import "testing"
func TestAirRescuerChecklistScopeHelpers(t *testing.T) {
persistedScopes := []string{
AirRescuerChecklistScopeTA,
AirRescuerChecklistScopeMO,
AirRescuerChecklistScopeDI,
AirRescuerChecklistScopeMI,
AirRescuerChecklistScopeDO,
AirRescuerChecklistScopeFR,
AirRescuerChecklistScopeSA,
AirRescuerChecklistScopeSO,
AirRescuerChecklistScopeM10,
AirRescuerChecklistScopeM15,
AirRescuerChecklistScopeM20,
AirRescuerChecklistScopeM25,
AirRescuerChecklistScopeDS,
}
for _, scope := range persistedScopes {
if !IsAirRescuerChecklistPersistedScope(scope) {
t.Fatalf("expected persisted scope valid: %s", scope)
}
if !IsAirRescuerChecklistPersistedScope(" " + scope + " ") {
t.Fatalf("expected persisted scope valid with trim: %s", scope)
}
if !IsAirRescuerChecklistPersistedScope(lower(scope)) {
t.Fatalf("expected persisted scope valid with lowercase: %s", scope)
}
if !IsAirRescuerChecklistQueryScope(scope) {
t.Fatalf("expected query scope valid: %s", scope)
}
if !IsAirRescuerChecklistScope(scope) {
t.Fatalf("expected compatibility scope valid: %s", scope)
}
}
if IsAirRescuerChecklistPersistedScope(AirRescuerChecklistScopeCAT) {
t.Fatalf("expected CAT not persisted")
}
if !IsAirRescuerChecklistQueryScope(AirRescuerChecklistScopeCAT) {
t.Fatalf("expected CAT valid for query")
}
if IsAirRescuerChecklistQueryScope("UNKNOWN") {
t.Fatalf("expected unknown query scope invalid")
}
if IsAirRescuerChecklistScope("UNKNOWN") {
t.Fatalf("expected unknown compatibility scope invalid")
}
englishAliases := map[string]string{
"daily": AirRescuerChecklistScopeTA,
"tuesday": AirRescuerChecklistScopeDI,
"wednesday": AirRescuerChecklistScopeMI,
"thursday": AirRescuerChecklistScopeDO,
"sunday": AirRescuerChecklistScopeSO,
"deadline": AirRescuerChecklistScopeDS,
"overview": AirRescuerChecklistScopeCAT,
"tu": AirRescuerChecklistScopeDI,
"we": AirRescuerChecklistScopeMI,
"th": AirRescuerChecklistScopeDO,
"su": AirRescuerChecklistScopeSO,
}
for alias, expected := range englishAliases {
if got := CanonicalizeAirRescuerChecklistScope(alias); got != expected {
t.Fatalf("expected alias %s canonicalized to %s, got %s", alias, expected, got)
}
}
if !IsAirRescuerChecklistPersistedScope("tu") {
t.Fatalf("expected TU alias valid as persisted scope")
}
if !IsAirRescuerChecklistQueryScope("overview") {
t.Fatalf("expected OVERVIEW alias valid as query scope")
}
}
func TestAirRescuerChecklistScopeDisplayName(t *testing.T) {
if got := AirRescuerChecklistScopeDisplayName(AirRescuerChecklistScopeCAT); got != "Category" {
t.Fatalf("unexpected CAT display name: %s", got)
}
if got := AirRescuerChecklistScopeDisplayName(" mo "); got != "Monday" {
t.Fatalf("unexpected MO display name: %s", got)
}
if got := AirRescuerChecklistScopeDisplayName("x"); got != "X" {
t.Fatalf("unexpected fallback display name: %s", got)
}
if got := AirRescuerChecklistScopeDisplayName("tu"); got != "Tuesday" {
t.Fatalf("unexpected TU display name: %s", got)
}
}
func TestAirRescuerChecklistScopeDisplayNameDE(t *testing.T) {
if got := AirRescuerChecklistScopeDisplayNameDE(AirRescuerChecklistScopeCAT); got != "Category" {
t.Fatalf("unexpected CAT DE display name: %s", got)
}
if got := AirRescuerChecklistScopeDisplayNameDE(" mo "); got != "Montag" {
t.Fatalf("unexpected MO DE display name: %s", got)
}
if got := AirRescuerChecklistScopeDisplayNameDE("x"); got != "X" {
t.Fatalf("unexpected DE fallback display name: %s", got)
}
if got := AirRescuerChecklistScopeDisplayNameDE("thursday"); got != "Donnerstag" {
t.Fatalf("unexpected THURSDAY DE display name: %s", got)
}
if got := AirRescuerChecklistScopeDisplayNameDE(AirRescuerChecklistScopeDS); got != "Deadline" {
t.Fatalf("unexpected DS DE display name: %s", got)
}
}
func TestAirRescuerChecklistScopeCodes(t *testing.T) {
if got := AirRescuerChecklistScopeCodeEN(AirRescuerChecklistScopeTA); got != "DA" {
t.Fatalf("unexpected TA EN code: %s", got)
}
if got := AirRescuerChecklistScopeCodeEN("thursday"); got != "TH" {
t.Fatalf("unexpected THURSDAY EN code: %s", got)
}
if got := AirRescuerChecklistScopeCodeEN("x"); got != "X" {
t.Fatalf("unexpected EN fallback code: %s", got)
}
if got := AirRescuerChecklistScopeCodeDE(AirRescuerChecklistScopeDI); got != AirRescuerChecklistScopeDI {
t.Fatalf("unexpected DI DE code: %s", got)
}
if got := AirRescuerChecklistScopeCodeDE("tu"); got != AirRescuerChecklistScopeDI {
t.Fatalf("unexpected TU DE code: %s", got)
}
}
func TestAirRescuerChecklistPersistedScopeOrderReturnsCopy(t *testing.T) {
first := AirRescuerChecklistPersistedScopeOrder()
if len(first) != 13 {
t.Fatalf("unexpected persisted scope order size: %d", len(first))
}
if first[0] != AirRescuerChecklistScopeTA || first[len(first)-1] != AirRescuerChecklistScopeDS {
t.Fatalf("unexpected persisted scope order: %+v", first)
}
first[0] = "BROKEN"
second := AirRescuerChecklistPersistedScopeOrder()
if second[0] != AirRescuerChecklistScopeTA {
t.Fatalf("expected second call to return independent copy, got: %s", second[0])
}
}
func lower(s string) string {
out := make([]byte, 0, len(s))
for i := 0; i < len(s); i++ {
ch := s[i]
if ch >= 'A' && ch <= 'Z' {
ch = ch + ('a' - 'A')
}
out = append(out, ch)
}
return string(out)
}

View File

@@ -0,0 +1,117 @@
package airrescuechecklist
import (
"time"
"gorm.io/gorm"
"wucher/internal/shared/pkg/uuidv7"
)
type AirRescuerChecklist struct {
ID []byte `gorm:"column:id;type:binary(16);primaryKey"`
HEMSBaseID []byte `gorm:"column:hems_base_id;type:binary(16);not null"`
ScopeCode string `gorm:"column:scope_code;type:varchar(20);not null"`
Title string `gorm:"column:title;type:varchar(150);not null"`
Position int `gorm:"column:position;not null;default:1"`
CreatedAt time.Time `gorm:"column:created_at;autoCreateTime"`
CreatedBy []byte `gorm:"column:created_by;type:binary(16)"`
UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime"`
UpdatedBy []byte `gorm:"column:updated_by;type:binary(16)"`
DeletedAt *time.Time `gorm:"column:deleted_at"`
DeletedBy []byte `gorm:"column:deleted_by;type:binary(16)"`
}
func (AirRescuerChecklist) TableName() string {
return "air_rescuer_checklists"
}
func (h *AirRescuerChecklist) BeforeCreate(tx *gorm.DB) error {
if len(h.ID) == 0 {
h.ID = uuidv7.MustBytes()
}
return nil
}
type AirRescuerChecklistItem struct {
ID []byte `gorm:"column:id;type:binary(16);primaryKey"`
ChecklistID []byte `gorm:"column:checklist_id;type:binary(16);not null"`
Name string `gorm:"column:name;type:varchar(255);not null"`
Position int `gorm:"column:position;not null;default:1"`
CreatedAt time.Time `gorm:"column:created_at;autoCreateTime"`
CreatedBy []byte `gorm:"column:created_by;type:binary(16)"`
UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime"`
UpdatedBy []byte `gorm:"column:updated_by;type:binary(16)"`
DeletedAt *time.Time `gorm:"column:deleted_at"`
DeletedBy []byte `gorm:"column:deleted_by;type:binary(16)"`
}
func (AirRescuerChecklistItem) TableName() string {
return "air_rescuer_checklist_items"
}
func (h *AirRescuerChecklistItem) BeforeCreate(tx *gorm.DB) error {
if len(h.ID) == 0 {
h.ID = uuidv7.MustBytes()
}
return nil
}
type HEMSBaseView struct {
ID []byte
BaseName string
BaseAbbreviation string
BaseCategory string
Position int
}
type ChecklistHeaderView struct {
ID []byte
HEMSBaseID []byte
BaseName string
BaseAbbreviation string
BaseCategory string
ScopeCode string
Title string
Position int
CreatedAt time.Time
CreatedBy []byte
UpdatedAt time.Time
}
type ChecklistItemView struct {
ID []byte
ChecklistID []byte
Name string
Position int
CreatedAt time.Time
CreatedBy []byte
UpdatedAt time.Time
}
type ChecklistHeaderFilter struct {
HEMSBaseID []byte
BaseName string
BaseAbbreviation string
ScopeCode string
CategoryType string
}
type HEMSBaseFilter struct {
HEMSBaseID []byte
BaseName string
BaseAbbreviation string
CategoryType string
}
type ChecklistCreateInput struct {
Checklist AirRescuerChecklist
Items []AirRescuerChecklistItem
}
type ChecklistItemReorderInput struct {
ID []byte
Position int
}

View File

@@ -0,0 +1,52 @@
package airrescuechecklist
import (
"bytes"
"testing"
)
func TestAirRescuerChecklistModelTableNameAndBeforeCreate(t *testing.T) {
var header AirRescuerChecklist
if got := header.TableName(); got != "air_rescuer_checklists" {
t.Fatalf("unexpected checklist table name: %s", got)
}
if err := header.BeforeCreate(nil); err != nil {
t.Fatalf("BeforeCreate checklist: %v", err)
}
if len(header.ID) == 0 {
t.Fatalf("expected checklist id generated")
}
existingID := []byte("1234567890123456")
header2 := AirRescuerChecklist{ID: append([]byte(nil), existingID...)}
if err := header2.BeforeCreate(nil); err != nil {
t.Fatalf("BeforeCreate checklist with existing id: %v", err)
}
if !bytes.Equal(header2.ID, existingID) {
t.Fatalf("expected existing checklist id preserved")
}
}
func TestAirRescuerChecklistItemModelTableNameAndBeforeCreate(t *testing.T) {
var item AirRescuerChecklistItem
if got := item.TableName(); got != "air_rescuer_checklist_items" {
t.Fatalf("unexpected checklist item table name: %s", got)
}
if err := item.BeforeCreate(nil); err != nil {
t.Fatalf("BeforeCreate checklist item: %v", err)
}
if len(item.ID) == 0 {
t.Fatalf("expected checklist item id generated")
}
existingID := []byte("abcdefghijklmnop")
item2 := AirRescuerChecklistItem{ID: append([]byte(nil), existingID...)}
if err := item2.BeforeCreate(nil); err != nil {
t.Fatalf("BeforeCreate checklist item with existing id: %v", err)
}
if !bytes.Equal(item2.ID, existingID) {
t.Fatalf("expected existing checklist item id preserved")
}
}

View File

@@ -0,0 +1,28 @@
package airrescuechecklist
import "context"
type TxRepository interface {
CreateChecklist(ctx context.Context, row *AirRescuerChecklist) error
NextChecklistPosition(ctx context.Context, hemsBaseID []byte, scopeCode string) (int, error)
LockChecklistByID(ctx context.Context, id []byte) (*AirRescuerChecklist, error)
UpdateChecklist(ctx context.Context, row *AirRescuerChecklist) error
SoftDeleteChecklist(ctx context.Context, id []byte, deletedBy []byte) error
SoftDeleteItemsByChecklistID(ctx context.Context, checklistID []byte, deletedBy []byte) error
CreateChecklistItem(ctx context.Context, row *AirRescuerChecklistItem) error
CreateChecklistItems(ctx context.Context, rows []AirRescuerChecklistItem) error
NextChecklistItemPosition(ctx context.Context, checklistID []byte) (int, error)
LockChecklistItemByID(ctx context.Context, id []byte) (*AirRescuerChecklistItem, error)
UpdateChecklistItem(ctx context.Context, row *AirRescuerChecklistItem) error
SoftDeleteChecklistItem(ctx context.Context, id []byte, deletedBy []byte) error
}
type Repository interface {
InTx(ctx context.Context, fn func(txRepo TxRepository) error) error
GetChecklistByID(ctx context.Context, id []byte) (*ChecklistHeaderView, error)
GetChecklistItemByID(ctx context.Context, id []byte) (*ChecklistItemView, error)
ListChecklistHeaders(ctx context.Context, filter ChecklistHeaderFilter) ([]ChecklistHeaderView, error)
ListChecklistItemsByChecklistIDs(ctx context.Context, checklistIDs [][]byte) ([]ChecklistItemView, error)
ListHEMSBases(ctx context.Context, filter HEMSBaseFilter) ([]HEMSBaseView, error)
}

View File

@@ -0,0 +1,21 @@
package airrescuechecklist
import "context"
type Service interface {
CreateBulk(ctx context.Context, rows []ChecklistCreateInput) error
UpdateChecklist(ctx context.Context, row *AirRescuerChecklist) error
UpdateChecklistWithNewItems(ctx context.Context, row *AirRescuerChecklist, newItems []AirRescuerChecklistItem) error
DeleteChecklist(ctx context.Context, id []byte, deletedBy []byte) error
CreateChecklistItem(ctx context.Context, row *AirRescuerChecklistItem) error
CreateChecklistItems(ctx context.Context, checklistID []byte, rows []AirRescuerChecklistItem) error
ReorderChecklistItems(ctx context.Context, checklistID []byte, rows []ChecklistItemReorderInput, updatedBy []byte) error
UpdateChecklistItem(ctx context.Context, row *AirRescuerChecklistItem) error
DeleteChecklistItem(ctx context.Context, id []byte, deletedBy []byte) error
GetChecklistByID(ctx context.Context, id []byte) (*ChecklistHeaderView, error)
GetChecklistItemByID(ctx context.Context, id []byte) (*ChecklistItemView, error)
ListChecklistHeaders(ctx context.Context, filter ChecklistHeaderFilter) ([]ChecklistHeaderView, error)
ListChecklistItemsByChecklistIDs(ctx context.Context, checklistIDs [][]byte) ([]ChecklistItemView, error)
ListHEMSBases(ctx context.Context, filter HEMSBaseFilter) ([]HEMSBaseView, error)
}