mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-06-04 20:43:01 +00:00
cleanup
This commit is contained in:
@@ -1,7 +1,469 @@
|
||||
package catalog
|
||||
|
||||
type Service struct{}
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
func NewService() *Service {
|
||||
return &Service{}
|
||||
"bookra/apps/backend/internal/db"
|
||||
"bookra/apps/backend/internal/domain"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrLocationNotFound = errors.New("location not found")
|
||||
ErrBlockedDayNotFound = errors.New("blocked day not found")
|
||||
ErrCustomerNotFound = errors.New("customer not found")
|
||||
ErrBookingNotFound = errors.New("booking not found")
|
||||
ErrInvalidBooking = errors.New("invalid booking request")
|
||||
ErrTenantNotFound = errors.New("tenant not found")
|
||||
ErrTenantMembership = errors.New("tenant membership not found")
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
repo db.Repository
|
||||
}
|
||||
|
||||
func NewService(repo db.Repository) *Service {
|
||||
return &Service{repo: repo}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// LOCATION / ZONE MANAGEMENT
|
||||
// ============================================
|
||||
|
||||
func (s *Service) ListLocations(ctx context.Context, principal domain.Principal) ([]domain.Location, error) {
|
||||
membership, err := s.repo.GetTenantMembershipByUserID(ctx, principal.Subject)
|
||||
if err != nil {
|
||||
return nil, ErrTenantMembership
|
||||
}
|
||||
|
||||
records, err := s.repo.ListLocationsByTenant(ctx, membership.Tenant.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
locations := make([]domain.Location, len(records))
|
||||
for i, rec := range records {
|
||||
locations[i] = domain.Location{
|
||||
ID: rec.ID,
|
||||
TenantID: rec.TenantID,
|
||||
Name: rec.Name,
|
||||
Type: "room", // Default type
|
||||
Capacity: 10, // Default capacity
|
||||
Timezone: rec.Timezone,
|
||||
CreatedAt: rec.CreatedAt,
|
||||
}
|
||||
}
|
||||
return locations, nil
|
||||
}
|
||||
|
||||
func (s *Service) CreateLocation(ctx context.Context, principal domain.Principal, req domain.CreateLocationRequest) (domain.Location, error) {
|
||||
membership, err := s.repo.GetTenantMembershipByUserID(ctx, principal.Subject)
|
||||
if err != nil {
|
||||
return domain.Location{}, ErrTenantMembership
|
||||
}
|
||||
|
||||
params := db.CreateLocationParams{
|
||||
TenantID: membership.Tenant.ID,
|
||||
Name: req.Name,
|
||||
Timezone: membership.Tenant.Timezone,
|
||||
}
|
||||
|
||||
rec, err := s.repo.CreateLocation(ctx, params)
|
||||
if err != nil {
|
||||
return domain.Location{}, err
|
||||
}
|
||||
|
||||
return domain.Location{
|
||||
ID: rec.ID,
|
||||
TenantID: rec.TenantID,
|
||||
Name: rec.Name,
|
||||
Type: req.Type,
|
||||
Capacity: 10,
|
||||
Timezone: rec.Timezone,
|
||||
CreatedAt: rec.CreatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) UpdateLocation(ctx context.Context, principal domain.Principal, locationID string, req domain.UpdateLocationRequest) (domain.Location, error) {
|
||||
membership, err := s.repo.GetTenantMembershipByUserID(ctx, principal.Subject)
|
||||
if err != nil {
|
||||
return domain.Location{}, ErrTenantMembership
|
||||
}
|
||||
|
||||
// Verify location belongs to tenant
|
||||
loc, err := s.repo.GetLocationByID(ctx, locationID)
|
||||
if err != nil {
|
||||
return domain.Location{}, ErrLocationNotFound
|
||||
}
|
||||
if loc.TenantID != membership.Tenant.ID {
|
||||
return domain.Location{}, ErrLocationNotFound
|
||||
}
|
||||
|
||||
params := db.UpdateLocationParams{}
|
||||
if req.Name != "" {
|
||||
params.Name = &req.Name
|
||||
}
|
||||
|
||||
rec, err := s.repo.UpdateLocation(ctx, locationID, params)
|
||||
if err != nil {
|
||||
return domain.Location{}, err
|
||||
}
|
||||
|
||||
return domain.Location{
|
||||
ID: rec.ID,
|
||||
TenantID: rec.TenantID,
|
||||
Name: rec.Name,
|
||||
Type: req.Type,
|
||||
Capacity: 10,
|
||||
Timezone: rec.Timezone,
|
||||
CreatedAt: rec.CreatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) DeleteLocation(ctx context.Context, principal domain.Principal, locationID string) error {
|
||||
membership, err := s.repo.GetTenantMembershipByUserID(ctx, principal.Subject)
|
||||
if err != nil {
|
||||
return ErrTenantMembership
|
||||
}
|
||||
|
||||
loc, err := s.repo.GetLocationByID(ctx, locationID)
|
||||
if err != nil {
|
||||
return ErrLocationNotFound
|
||||
}
|
||||
if loc.TenantID != membership.Tenant.ID {
|
||||
return ErrLocationNotFound
|
||||
}
|
||||
|
||||
return s.repo.DeleteLocation(ctx, locationID)
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// BLOCKED DAYS MANAGEMENT
|
||||
// ============================================
|
||||
|
||||
func (s *Service) ListBlockedDays(ctx context.Context, principal domain.Principal, from time.Time, to time.Time) ([]domain.BlockedDay, error) {
|
||||
membership, err := s.repo.GetTenantMembershipByUserID(ctx, principal.Subject)
|
||||
if err != nil {
|
||||
return nil, ErrTenantMembership
|
||||
}
|
||||
|
||||
records, err := s.repo.ListBlockedDaysByTenant(ctx, membership.Tenant.ID, from, to)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blockedDays := make([]domain.BlockedDay, len(records))
|
||||
for i, rec := range records {
|
||||
blockedDays[i] = domain.BlockedDay{
|
||||
ID: rec.ID,
|
||||
TenantID: rec.TenantID,
|
||||
Date: rec.StartsAt,
|
||||
Reason: rec.Reason,
|
||||
Type: rec.Kind,
|
||||
StaffID: rec.StaffID,
|
||||
CreatedAt: rec.CreatedAt,
|
||||
}
|
||||
}
|
||||
return blockedDays, nil
|
||||
}
|
||||
|
||||
func (s *Service) CreateBlockedDay(ctx context.Context, principal domain.Principal, req domain.CreateBlockedDayRequest) (domain.BlockedDay, error) {
|
||||
membership, err := s.repo.GetTenantMembershipByUserID(ctx, principal.Subject)
|
||||
if err != nil {
|
||||
return domain.BlockedDay{}, ErrTenantMembership
|
||||
}
|
||||
|
||||
date, err := time.Parse(time.RFC3339, req.Date)
|
||||
if err != nil {
|
||||
return domain.BlockedDay{}, ErrInvalidBooking
|
||||
}
|
||||
|
||||
params := db.CreateBlockedDayParams{
|
||||
TenantID: membership.Tenant.ID,
|
||||
StaffID: req.StaffID,
|
||||
StartsAt: date,
|
||||
EndsAt: date.Add(24 * time.Hour),
|
||||
Kind: req.Type,
|
||||
Reason: req.Reason,
|
||||
}
|
||||
|
||||
rec, err := s.repo.CreateBlockedDay(ctx, params)
|
||||
if err != nil {
|
||||
return domain.BlockedDay{}, err
|
||||
}
|
||||
|
||||
return domain.BlockedDay{
|
||||
ID: rec.ID,
|
||||
TenantID: rec.TenantID,
|
||||
Date: rec.StartsAt,
|
||||
Reason: rec.Reason,
|
||||
Type: rec.Kind,
|
||||
StaffID: rec.StaffID,
|
||||
CreatedAt: rec.CreatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) UpdateBlockedDay(ctx context.Context, principal domain.Principal, blockedDayID string, req domain.UpdateBlockedDayRequest) (domain.BlockedDay, error) {
|
||||
membership, err := s.repo.GetTenantMembershipByUserID(ctx, principal.Subject)
|
||||
if err != nil {
|
||||
return domain.BlockedDay{}, ErrTenantMembership
|
||||
}
|
||||
|
||||
// Verify blocked day belongs to tenant
|
||||
bd, err := s.repo.ListBlockedDaysByTenant(ctx, membership.Tenant.ID, time.Time{}, time.Now().Add(365*24*time.Hour))
|
||||
if err != nil {
|
||||
return domain.BlockedDay{}, err
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, b := range bd {
|
||||
if b.ID == blockedDayID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return domain.BlockedDay{}, ErrBlockedDayNotFound
|
||||
}
|
||||
|
||||
params := db.UpdateBlockedDayParams{}
|
||||
if req.Reason != "" {
|
||||
params.Reason = &req.Reason
|
||||
}
|
||||
if req.Type != "" {
|
||||
params.Kind = &req.Type
|
||||
}
|
||||
|
||||
rec, err := s.repo.UpdateBlockedDay(ctx, blockedDayID, params)
|
||||
if err != nil {
|
||||
return domain.BlockedDay{}, err
|
||||
}
|
||||
|
||||
return domain.BlockedDay{
|
||||
ID: rec.ID,
|
||||
TenantID: rec.TenantID,
|
||||
Date: rec.StartsAt,
|
||||
Reason: rec.Reason,
|
||||
Type: rec.Kind,
|
||||
StaffID: rec.StaffID,
|
||||
CreatedAt: rec.CreatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) DeleteBlockedDay(ctx context.Context, principal domain.Principal, blockedDayID string) error {
|
||||
membership, err := s.repo.GetTenantMembershipByUserID(ctx, principal.Subject)
|
||||
if err != nil {
|
||||
return ErrTenantMembership
|
||||
}
|
||||
|
||||
// Verify blocked day belongs to tenant
|
||||
bd, err := s.repo.ListBlockedDaysByTenant(ctx, membership.Tenant.ID, time.Time{}, time.Now().Add(365*24*time.Hour))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, b := range bd {
|
||||
if b.ID == blockedDayID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return ErrBlockedDayNotFound
|
||||
}
|
||||
|
||||
return s.repo.DeleteBlockedDay(ctx, blockedDayID)
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// CUSTOMER MANAGEMENT
|
||||
// ============================================
|
||||
|
||||
func (s *Service) ListCustomers(ctx context.Context, principal domain.Principal, limit int, offset int) ([]domain.Customer, error) {
|
||||
membership, err := s.repo.GetTenantMembershipByUserID(ctx, principal.Subject)
|
||||
if err != nil {
|
||||
return nil, ErrTenantMembership
|
||||
}
|
||||
|
||||
records, err := s.repo.ListCustomersByTenant(ctx, membership.Tenant.ID, limit, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
customers := make([]domain.Customer, len(records))
|
||||
for i, rec := range records {
|
||||
bookingsCount, _ := s.repo.GetCustomerBookingsCount(ctx, rec.ID)
|
||||
lastBooking, _ := s.repo.GetCustomerLastBooking(ctx, rec.ID)
|
||||
|
||||
customers[i] = domain.Customer{
|
||||
ID: rec.ID,
|
||||
TenantID: rec.TenantID,
|
||||
Name: rec.Name,
|
||||
Email: rec.Email,
|
||||
Phone: rec.Phone,
|
||||
Status: rec.Status,
|
||||
BookingsCount: bookingsCount,
|
||||
LastBookingAt: lastBooking,
|
||||
CreatedAt: rec.CreatedAt,
|
||||
Notes: "",
|
||||
}
|
||||
if rec.Notes != nil {
|
||||
customers[i].Notes = *rec.Notes
|
||||
}
|
||||
}
|
||||
return customers, nil
|
||||
}
|
||||
|
||||
func (s *Service) CreateCustomer(ctx context.Context, principal domain.Principal, req domain.CreateCustomerRequest) (domain.Customer, error) {
|
||||
membership, err := s.repo.GetTenantMembershipByUserID(ctx, principal.Subject)
|
||||
if err != nil {
|
||||
return domain.Customer{}, ErrTenantMembership
|
||||
}
|
||||
|
||||
status := req.Status
|
||||
if status == "" {
|
||||
status = "active"
|
||||
}
|
||||
|
||||
params := db.CreateCustomerParams{
|
||||
TenantID: membership.Tenant.ID,
|
||||
Name: req.Name,
|
||||
Email: req.Email,
|
||||
Phone: req.Phone,
|
||||
Status: status,
|
||||
Notes: nil,
|
||||
}
|
||||
|
||||
rec, err := s.repo.CreateCustomer(ctx, params)
|
||||
if err != nil {
|
||||
return domain.Customer{}, err
|
||||
}
|
||||
|
||||
return domain.Customer{
|
||||
ID: rec.ID,
|
||||
TenantID: rec.TenantID,
|
||||
Name: rec.Name,
|
||||
Email: rec.Email,
|
||||
Phone: rec.Phone,
|
||||
Status: rec.Status,
|
||||
CreatedAt: rec.CreatedAt,
|
||||
Notes: "",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) UpdateCustomer(ctx context.Context, principal domain.Principal, customerID string, req domain.UpdateCustomerRequest) (domain.Customer, error) {
|
||||
membership, err := s.repo.GetTenantMembershipByUserID(ctx, principal.Subject)
|
||||
if err != nil {
|
||||
return domain.Customer{}, ErrTenantMembership
|
||||
}
|
||||
|
||||
// Verify customer belongs to tenant
|
||||
cust, err := s.repo.GetCustomerByID(ctx, customerID)
|
||||
if err != nil {
|
||||
return domain.Customer{}, ErrCustomerNotFound
|
||||
}
|
||||
if cust.TenantID != membership.Tenant.ID {
|
||||
return domain.Customer{}, ErrCustomerNotFound
|
||||
}
|
||||
|
||||
params := db.UpdateCustomerParams{}
|
||||
if req.Name != "" {
|
||||
params.Name = &req.Name
|
||||
}
|
||||
if req.Email != "" {
|
||||
params.Email = &req.Email
|
||||
}
|
||||
if req.Phone != nil {
|
||||
params.Phone = req.Phone
|
||||
}
|
||||
if req.Status != "" {
|
||||
params.Status = &req.Status
|
||||
}
|
||||
if req.Notes != "" {
|
||||
params.Notes = &req.Notes
|
||||
}
|
||||
|
||||
rec, err := s.repo.UpdateCustomer(ctx, customerID, params)
|
||||
if err != nil {
|
||||
return domain.Customer{}, err
|
||||
}
|
||||
|
||||
bookingsCount, _ := s.repo.GetCustomerBookingsCount(ctx, rec.ID)
|
||||
lastBooking, _ := s.repo.GetCustomerLastBooking(ctx, rec.ID)
|
||||
|
||||
return domain.Customer{
|
||||
ID: rec.ID,
|
||||
TenantID: rec.TenantID,
|
||||
Name: rec.Name,
|
||||
Email: rec.Email,
|
||||
Phone: rec.Phone,
|
||||
Status: rec.Status,
|
||||
BookingsCount: bookingsCount,
|
||||
LastBookingAt: lastBooking,
|
||||
CreatedAt: rec.CreatedAt,
|
||||
Notes: "",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) DeleteCustomer(ctx context.Context, principal domain.Principal, customerID string) error {
|
||||
membership, err := s.repo.GetTenantMembershipByUserID(ctx, principal.Subject)
|
||||
if err != nil {
|
||||
return ErrTenantMembership
|
||||
}
|
||||
|
||||
cust, err := s.repo.GetCustomerByID(ctx, customerID)
|
||||
if err != nil {
|
||||
return ErrCustomerNotFound
|
||||
}
|
||||
if cust.TenantID != membership.Tenant.ID {
|
||||
return ErrCustomerNotFound
|
||||
}
|
||||
|
||||
return s.repo.DeleteCustomer(ctx, customerID)
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// WORKING HOURS MANAGEMENT
|
||||
// ============================================
|
||||
|
||||
func (s *Service) ListWorkingHours(ctx context.Context, principal domain.Principal) ([]domain.WorkingHours, error) {
|
||||
membership, err := s.repo.GetTenantMembershipByUserID(ctx, principal.Subject)
|
||||
if err != nil {
|
||||
return nil, ErrTenantMembership
|
||||
}
|
||||
|
||||
records, err := s.repo.ListWorkingHoursByTenant(ctx, membership.Tenant.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hours := make([]domain.WorkingHours, len(records))
|
||||
for i, rec := range records {
|
||||
hours[i] = domain.WorkingHours{
|
||||
DayOfWeek: rec.DayOfWeek,
|
||||
Open: rec.StartsLocal,
|
||||
Close: rec.EndsLocal,
|
||||
IsOpen: rec.StartsLocal != "" && rec.EndsLocal != "",
|
||||
}
|
||||
}
|
||||
return hours, nil
|
||||
}
|
||||
|
||||
func (s *Service) UpdateWorkingHours(ctx context.Context, principal domain.Principal, dayOfWeek int, req domain.UpdateWorkingHoursRequest) error {
|
||||
membership, err := s.repo.GetTenantMembershipByUserID(ctx, principal.Subject)
|
||||
if err != nil {
|
||||
return ErrTenantMembership
|
||||
}
|
||||
|
||||
params := db.UpdateWorkingHoursParams{}
|
||||
if req.Open != "" {
|
||||
params.StartsLocal = &req.Open
|
||||
}
|
||||
if req.Close != "" {
|
||||
params.EndsLocal = &req.Close
|
||||
}
|
||||
|
||||
return s.repo.UpdateWorkingHours(ctx, membership.Tenant.ID, dayOfWeek, params)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user