| package controllers |
|
|
| import ( |
| crand "crypto/rand" |
| "encoding/hex" |
| "fmt" |
| "log" |
| "math/rand" |
| "net/http" |
| "strconv" |
| "time" |
|
|
| "service-warungpos-go/config" |
| "service-warungpos-go/middleware" |
| "service-warungpos-go/models" |
| "service-warungpos-go/services" |
|
|
| "github.com/gin-gonic/gin" |
| "gorm.io/gorm" |
| ) |
|
|
| |
| func SeedDefaultAdmin() { |
| var count int64 |
| config.DB.Model(&models.User{}).Where("email = ?", "admin@xogmgo.com").Count(&count) |
| if count == 0 { |
| hashedPassword, _ := middleware.HashPassword("admin123") |
| admin := models.User{ |
| Name: "Admin XOGM Go", |
| Email: "admin@xogmgo.com", |
| Password: hashedPassword, |
| Role: "super_admin", |
| CompanyID: nil, |
| BranchID: nil, |
| IsActive: true, |
| } |
| if err := config.DB.Create(&admin).Error; err != nil { |
| log.Printf("[SEED] Gagal membuat default admin: %v", err) |
| } else { |
| log.Println("[SEED] Sukses membuat default admin!") |
| } |
| } else { |
| log.Println("[SEED] Admin default sudah terbuat.") |
| } |
| } |
|
|
| |
| func SeedDefaultSetting() { |
| var count int64 |
| config.DB.Model(&models.Setting{}).Count(&count) |
| if count == 0 { |
| setting := models.Setting{ |
| ID: 1, |
| TripayMerchantCode: config.GlobalConfig.TripayMerchantCode, |
| TripayAPIKey: config.GlobalConfig.TripayAPIKey, |
| TripayPrivateKey: config.GlobalConfig.TripayPrivateKey, |
| TripayProxyURL: config.GlobalConfig.TripayProxyURL, |
| TripayPaymentMethod: "QRISC", |
| } |
| if err := config.DB.Create(&setting).Error; err != nil { |
| log.Printf("[SEED] Gagal membuat default setting: %v", err) |
| } else { |
| log.Println("[SEED] Sukses membuat default setting dari file .env!") |
| } |
| } else { |
| log.Println("[SEED] Setting default sudah terbuat di DB.") |
| } |
| } |
|
|
| |
| type LoginRequest struct { |
| Email string `json:"email" binding:"required"` |
| Password string `json:"password" binding:"required"` |
| } |
|
|
| type UserResponse struct { |
| ID uint `json:"id"` |
| Name string `json:"name"` |
| Email string `json:"email"` |
| Phone string `json:"phone"` |
| Role string `json:"role"` |
| CompanyID *uint `json:"company_id"` |
| BranchID *uint `json:"branch_id"` |
| IsActive bool `json:"is_active"` |
| CompanyName string `json:"company_name,omitempty"` |
| BranchName string `json:"branch_name,omitempty"` |
| CompanySettings map[string]interface{} `json:"company_settings,omitempty"` |
| CreatedAt time.Time `json:"created_at"` |
| } |
|
|
| type TokenResponse struct { |
| AccessToken string `json:"access_token"` |
| TokenType string `json:"token_type"` |
| User UserResponse `json:"user"` |
| } |
|
|
| type RegisterRequest struct { |
| Name string `json:"name" binding:"required"` |
| Email string `json:"email" binding:"required,email"` |
| Password string `json:"password" binding:"required"` |
| Role string `json:"role"` |
| CompanyID *uint `json:"company_id"` |
| BranchID *uint `json:"branch_id"` |
| } |
|
|
| type TenantRegistrationRequest struct { |
| CompanyName string `json:"company_name" binding:"required"` |
| CompanyAddress string `json:"company_address"` |
| NPWP string `json:"npwp"` |
| OwnerName string `json:"owner_name" binding:"required"` |
| OwnerEmail string `json:"owner_email" binding:"required,email"` |
| OwnerPhone string `json:"owner_phone" binding:"required"` |
| OwnerPassword string `json:"owner_password" binding:"required"` |
| BankAccountNumber string `json:"bank_account_number"` |
| BankName string `json:"bank_name"` |
| } |
|
|
| type VerifyOTPRequest struct { |
| Phone string `json:"phone" binding:"required"` |
| OTPCode string `json:"otp_code" binding:"required"` |
| } |
|
|
| type ResendOTPRequest struct { |
| Phone string `json:"phone" binding:"required"` |
| } |
|
|
| type ForgotPasswordRequest struct { |
| Phone string `json:"phone" binding:"required"` |
| } |
|
|
| type ResetPasswordRequest struct { |
| Phone string `json:"phone" binding:"required"` |
| OTPCode string `json:"otp_code" binding:"required"` |
| NewPassword string `json:"new_password" binding:"required"` |
| } |
|
|
| type UpdateUserRequest struct { |
| Name string `json:"name"` |
| Email string `json:"email"` |
| Password string `json:"password"` |
| Role string `json:"role"` |
| IsActive *bool `json:"is_active"` |
| } |
|
|
| type UpdateKasirRequest struct { |
| Name string `json:"name"` |
| Email string `json:"email"` |
| Password string `json:"password"` |
| BranchID *uint `json:"branch_id"` |
| IsActive *bool `json:"is_active"` |
| } |
|
|
| func toUserResponse(user *models.User) UserResponse { |
| resp := UserResponse{ |
| ID: user.ID, |
| Name: user.Name, |
| Email: user.Email, |
| Phone: user.Phone, |
| Role: user.Role, |
| CompanyID: user.CompanyID, |
| BranchID: user.BranchID, |
| IsActive: user.IsActive, |
| CreatedAt: user.CreatedAt, |
| } |
| if user.Company != nil { |
| resp.CompanyName = user.Company.Name |
| resp.CompanySettings = map[string]interface{}{ |
| "enable_member": user.Company.EnableMember, |
| "enable_voucher": user.Company.EnableVoucher, |
| "enable_multi_branch": user.Company.EnableMultiBranch, |
| "enable_multi_kasir": user.Company.EnableMultiKasir, |
| } |
| } |
| if user.Branch != nil { |
| resp.BranchName = user.Branch.Name |
| } |
| return resp |
| } |
|
|
| |
| func Login(c *gin.Context) { |
| var req LoginRequest |
| if err := c.ShouldBindJSON(&req); err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": "Data tidak valid"}) |
| return |
| } |
|
|
| var user models.User |
| if err := config.DB.Select("id, name, email, password, phone, role, company_id, branch_id, is_active, created_at"). |
| Preload("Company", func(db *gorm.DB) *gorm.DB { |
| return db.Select("id, name, approved_at, is_active, enable_member, enable_voucher, enable_multi_branch, enable_multi_kasir") |
| }). |
| Preload("Branch", func(db *gorm.DB) *gorm.DB { |
| return db.Select("id, name") |
| }). |
| Where("email = ?", req.Email). |
| First(&user).Error; err != nil { |
| c.JSON(http.StatusUnauthorized, gin.H{"detail": "Email atau password salah"}) |
| return |
| } |
|
|
| if !middleware.VerifyPassword(req.Password, user.Password) { |
| c.JSON(http.StatusUnauthorized, gin.H{"detail": "Email atau password salah"}) |
| return |
| } |
|
|
| |
| if !user.IsActive { |
| if user.Role == "owner" && user.Company != nil { |
| if user.Company.ApprovedAt != nil { |
| |
| c.JSON(http.StatusUnauthorized, gin.H{"detail": "ACCOUNT_DEACTIVATED"}) |
| return |
| } else { |
| |
| c.JSON(http.StatusUnauthorized, gin.H{"detail": "PENDING_APPROVAL"}) |
| return |
| } |
| } |
| c.JSON(http.StatusForbidden, gin.H{"detail": "Akun tidak aktif, hubungi admin"}) |
| return |
| } |
|
|
| |
| var sessionTokenStr string |
| if user.Role == "kasir" && user.CompanyID != nil { |
| |
| var company models.Company |
| if err := config.DB.Select("id, enforce_single_session").First(&company, *user.CompanyID).Error; err == nil { |
| if company.EnforceSingleSession { |
| |
| tokenBytes := make([]byte, 32) |
| crand.Read(tokenBytes) |
| sessionTokenStr = hex.EncodeToString(tokenBytes) |
|
|
| |
| config.DB.Where("user_id = ?", user.ID).Delete(&models.UserSession{}) |
|
|
| |
| newSession := models.UserSession{ |
| UserID: user.ID, |
| SessionToken: sessionTokenStr, |
| UserAgent: c.GetHeader("User-Agent"), |
| IPAddress: c.ClientIP(), |
| ExpiresAt: time.Now().Add(24 * time.Hour), |
| } |
| config.DB.Create(&newSession) |
| } |
| } |
| } |
|
|
| token, err := middleware.CreateAccessToken(user.ID, user.Role, user.CompanyID, sessionTokenStr) |
| if err != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal men-generate token"}) |
| return |
| } |
|
|
| c.JSON(http.StatusOK, TokenResponse{ |
| AccessToken: token, |
| TokenType: "bearer", |
| User: toUserResponse(&user), |
| }) |
| } |
|
|
| func Logout(c *gin.Context) { |
| userVal, exists := c.Get("user") |
| if !exists { |
| c.JSON(http.StatusOK, gin.H{"message": "Logged out"}) |
| return |
| } |
| user := userVal.(*models.User) |
|
|
| |
| config.DB.Where("user_id = ?", user.ID).Delete(&models.UserSession{}) |
|
|
| c.JSON(http.StatusOK, gin.H{"message": "Berhasil logout"}) |
| } |
|
|
| func Register(c *gin.Context) { |
| var req RegisterRequest |
| if err := c.ShouldBindJSON(&req); err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()}) |
| return |
| } |
|
|
| var existingCount int64 |
| config.DB.Model(&models.User{}).Where("email = ?", req.Email).Count(&existingCount) |
| if existingCount > 0 { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": "Email sudah terdaftar"}) |
| return |
| } |
|
|
| hashedPassword, _ := middleware.HashPassword(req.Password) |
| role := "kasir" |
| if req.Role != "" { |
| role = req.Role |
| } |
|
|
| newUser := models.User{ |
| Name: req.Name, |
| Email: req.Email, |
| Password: hashedPassword, |
| Role: role, |
| CompanyID: req.CompanyID, |
| BranchID: req.BranchID, |
| IsActive: true, |
| } |
|
|
| if err := config.DB.Create(&newUser).Error; err != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal menyimpan user"}) |
| return |
| } |
|
|
| c.JSON(http.StatusCreated, toUserResponse(&newUser)) |
| } |
|
|
| func RegisterTenant(c *gin.Context) { |
| var req TenantRegistrationRequest |
| if err := c.ShouldBindJSON(&req); err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()}) |
| return |
| } |
|
|
| var existingCount int64 |
| config.DB.Model(&models.User{}).Where("email = ?", req.OwnerEmail).Count(&existingCount) |
| if existingCount > 0 { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": "Email sudah digunakan!"}) |
| return |
| } |
|
|
| tx := config.DB.Begin() |
| defer func() { |
| if r := recover(); r != nil { |
| tx.Rollback() |
| } |
| }() |
| |
| comp := models.Company{ |
| Name: req.CompanyName, |
| Address: req.CompanyAddress, |
| NPWP: req.NPWP, |
| BankAccountNumber: req.BankAccountNumber, |
| BankName: req.BankName, |
| Email: req.OwnerEmail, |
| Phone: req.OwnerPhone, |
| IsActive: false, |
| } |
| if err := tx.Create(&comp).Error; err != nil { |
| tx.Rollback() |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal membuat perusahaan"}) |
| return |
| } |
|
|
| |
| hashedPassword, _ := middleware.HashPassword(req.OwnerPassword) |
| owner := models.User{ |
| Name: req.OwnerName, |
| Email: req.OwnerEmail, |
| Password: hashedPassword, |
| Phone: req.OwnerPhone, |
| Role: "owner", |
| CompanyID: &comp.ID, |
| IsActive: false, |
| } |
| if err := tx.Create(&owner).Error; err != nil { |
| tx.Rollback() |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal membuat akun owner"}) |
| return |
| } |
|
|
| |
| code := fmt.Sprintf("%06d", rand.Intn(900000)+100000) |
| otpEntry := models.OTP{ |
| PhoneNumber: req.OwnerPhone, |
| OTPCode: code, |
| IsVerified: false, |
| ExpiresAt: time.Now().Add(5 * time.Minute), |
| } |
| if err := tx.Create(&otpEntry).Error; err != nil { |
| tx.Rollback() |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal membuat OTP"}) |
| return |
| } |
|
|
| if err := tx.Commit().Error; err != nil { |
| tx.Rollback() |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal melakukan registrasi"}) |
| return |
| } |
|
|
| |
| go services.SendOtpWa(req.OwnerPhone, code) |
| |
| go services.SendNewRegistrationAdminWa("085789599065", req.CompanyName, req.OwnerName, req.OwnerEmail, req.OwnerPhone) |
|
|
| c.JSON(http.StatusCreated, gin.H{ |
| "status": "success", |
| "message": "OTP sent to phone", |
| }) |
| } |
|
|
| func VerifyOTP(c *gin.Context) { |
| var req VerifyOTPRequest |
| if err := c.ShouldBindJSON(&req); err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()}) |
| return |
| } |
|
|
| var record models.OTP |
| if err := config.DB.Where("phone_number = ? AND otp_code = ? AND is_verified = ?", req.Phone, req.OTPCode, false).Order("id desc").First(&record).Error; err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": "Kode OTP salah atau tidak ditemukan"}) |
| return |
| } |
|
|
| if record.ExpiresAt.Before(time.Now()) { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": "Kode OTP sudah kadaluarsa"}) |
| return |
| } |
|
|
| record.IsVerified = true |
| config.DB.Save(&record) |
|
|
| c.JSON(http.StatusOK, gin.H{ |
| "status": "success", |
| "message": "Nomor Hape diverifikasi! Tunggu Approve Super Admin.", |
| }) |
| } |
|
|
| func ResendOTP(c *gin.Context) { |
| var req ResendOTPRequest |
| if err := c.ShouldBindJSON(&req); err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()}) |
| return |
| } |
|
|
| var lastOTP models.OTP |
| err := config.DB.Where("phone_number = ? AND is_verified = ?", req.Phone, false).Order("id desc").First(&lastOTP).Error |
| if err == nil { |
| |
| createdAt := lastOTP.ExpiresAt.Add(-5 * time.Minute) |
| timeSince := time.Since(createdAt) |
| if timeSince < 3*time.Minute { |
| remaining := int(180 - timeSince.Seconds()) |
| c.JSON(http.StatusTooManyRequests, gin.H{ |
| "detail": fmt.Sprintf("Tunggu %d detik lagi sebelum kirim ulang OTP", remaining), |
| }) |
| return |
| } |
| } |
|
|
| code := fmt.Sprintf("%06d", rand.Intn(900000)+100000) |
| otpEntry := models.OTP{ |
| PhoneNumber: req.Phone, |
| OTPCode: code, |
| IsVerified: false, |
| ExpiresAt: time.Now().Add(5 * time.Minute), |
| } |
| if err := config.DB.Create(&otpEntry).Error; err != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal mengirim ulang OTP"}) |
| return |
| } |
|
|
| go services.SendOtpWa(req.Phone, code) |
|
|
| c.JSON(http.StatusOK, gin.H{ |
| "status": "success", |
| "message": "OTP baru telah dikirim ke WhatsApp Anda", |
| }) |
| } |
|
|
| func ForgotPassword(c *gin.Context) { |
| var req ForgotPasswordRequest |
| if err := c.ShouldBindJSON(&req); err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()}) |
| return |
| } |
|
|
| |
| var user models.User |
| if err := config.DB.Where("phone = ?", req.Phone).First(&user).Error; err != nil { |
| c.JSON(http.StatusNotFound, gin.H{"detail": "Nomor WhatsApp tidak terdaftar"}) |
| return |
| } |
|
|
| |
| code := fmt.Sprintf("%06d", rand.Intn(900000)+100000) |
|
|
| |
| otpEntry := models.OTP{ |
| PhoneNumber: req.Phone, |
| OTPCode: code, |
| IsVerified: false, |
| ExpiresAt: time.Now().Add(10 * time.Minute), |
| } |
| if err := config.DB.Create(&otpEntry).Error; err != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal membuat OTP reset password"}) |
| return |
| } |
|
|
| |
| go services.SendResetPasswordOtpWa(req.Phone, code) |
|
|
| c.JSON(http.StatusOK, gin.H{ |
| "status": "success", |
| "message": "Kode OTP reset password telah dikirim ke WhatsApp Anda", |
| }) |
| } |
|
|
| func ResetPassword(c *gin.Context) { |
| var req ResetPasswordRequest |
| if err := c.ShouldBindJSON(&req); err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()}) |
| return |
| } |
|
|
| |
| var record models.OTP |
| if err := config.DB.Where("phone_number = ? AND otp_code = ? AND is_verified = ?", req.Phone, req.OTPCode, false).Order("id desc").First(&record).Error; err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": "Kode OTP salah atau tidak ditemukan"}) |
| return |
| } |
|
|
| if record.ExpiresAt.Before(time.Now()) { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": "Kode OTP sudah kadaluarsa"}) |
| return |
| } |
|
|
| |
| var user models.User |
| if err := config.DB.Where("phone = ?", req.Phone).First(&user).Error; err != nil { |
| c.JSON(http.StatusNotFound, gin.H{"detail": "User tidak ditemukan"}) |
| return |
| } |
|
|
| |
| hashed, err := middleware.HashPassword(req.NewPassword) |
| if err != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal enkripsi password"}) |
| return |
| } |
|
|
| |
| tx := config.DB.Begin() |
| |
| |
| if err := tx.Model(&user).Update("password", hashed).Error; err != nil { |
| tx.Rollback() |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal mereset password"}) |
| return |
| } |
|
|
| |
| record.IsVerified = true |
| if err := tx.Save(&record).Error; err != nil { |
| tx.Rollback() |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal memperbarui status OTP"}) |
| return |
| } |
|
|
| tx.Commit() |
|
|
| c.JSON(http.StatusOK, gin.H{ |
| "status": "success", |
| "message": "Password Anda berhasil diperbarui. Silakan login kembali dengan password baru Anda.", |
| }) |
| } |
|
|
| func GetMe(c *gin.Context) { |
| userVal, _ := c.Get("user") |
| user := userVal.(*models.User) |
|
|
| resp := toUserResponse(user) |
|
|
| |
| if user.CompanyID != nil { |
| var company models.Company |
| if err := config.DB.First(&company, *user.CompanyID).Error; err == nil { |
| resp.CompanyName = company.Name |
| resp.CompanySettings = map[string]interface{}{ |
| "enable_member": company.EnableMember, |
| "enable_voucher": company.EnableVoucher, |
| "enable_multi_branch": company.EnableMultiBranch, |
| "enable_multi_kasir": company.EnableMultiKasir, |
| } |
| } |
| } |
|
|
| if user.BranchID != nil { |
| var branch models.Branch |
| if err := config.DB.Select("id, name").First(&branch, *user.BranchID).Error; err == nil { |
| resp.BranchName = branch.Name |
| } |
| } |
|
|
| c.JSON(http.StatusOK, resp) |
| } |
|
|
| func ListUsers(c *gin.Context) { |
| limitStr := c.Query("limit") |
| skipStr := c.Query("skip") |
| searchQuery := c.Query("search") |
|
|
| query := config.DB.Preload("Company").Preload("Branch") |
|
|
| if searchQuery != "" { |
| query = query.Where("name ILIKE ? OR email ILIKE ? OR phone ILIKE ?", |
| "%"+searchQuery+"%", |
| "%"+searchQuery+"%", |
| "%"+searchQuery+"%") |
| } |
|
|
| if limitStr != "" { |
| if limit, err := strconv.Atoi(limitStr); err == nil { |
| query = query.Limit(limit) |
| } |
| } |
| if skipStr != "" { |
| if skip, err := strconv.Atoi(skipStr); err == nil { |
| query = query.Offset(skip) |
| } |
| } |
|
|
| var users []models.User |
| if err := query.Find(&users).Error; err != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal mengambil data user"}) |
| return |
| } |
|
|
| var resp []UserResponse |
| for _, u := range users { |
| resp = append(resp, toUserResponse(&u)) |
| } |
| c.JSON(http.StatusOK, resp) |
| } |
|
|
| func UpdateUser(c *gin.Context) { |
| userIDStr := c.Param("user_id") |
| userID, err := strconv.Atoi(userIDStr) |
| if err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": "ID user tidak valid"}) |
| return |
| } |
|
|
| var req UpdateUserRequest |
| if err := c.ShouldBindJSON(&req); err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()}) |
| return |
| } |
|
|
| var user models.User |
| if err := config.DB.First(&user, userID).Error; err != nil { |
| c.JSON(http.StatusNotFound, gin.H{"detail": "User tidak ditemukan"}) |
| return |
| } |
|
|
| if req.Name != "" { |
| user.Name = req.Name |
| } |
| if req.Email != "" { |
| var count int64 |
| config.DB.Model(&models.User{}).Where("email = ? AND id != ?", req.Email, userID).Count(&count) |
| if count > 0 { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": "Email sudah digunakan user lain"}) |
| return |
| } |
| user.Email = req.Email |
| } |
| if req.Password != "" { |
| hashed, _ := middleware.HashPassword(req.Password) |
| user.Password = hashed |
| } |
| if req.Role != "" { |
| user.Role = req.Role |
| } |
| if req.IsActive != nil { |
| user.IsActive = *req.IsActive |
| } |
|
|
| if err := config.DB.Save(&user).Error; err != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal meng-update user"}) |
| return |
| } |
|
|
| c.JSON(http.StatusOK, toUserResponse(&user)) |
| } |
|
|
| |
| func ListKasir(c *gin.Context) { |
| userVal, _ := c.Get("user") |
| owner := userVal.(*models.User) |
|
|
| var kasir []models.User |
| if err := config.DB.Select("id, name, email, phone, role, company_id, branch_id, is_active, created_at").Preload("Branch").Where("company_id = ? AND role = ?", *owner.CompanyID, "kasir").Find(&kasir).Error; err != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal mengambil data kasir"}) |
| return |
| } |
|
|
| resp := []UserResponse{} |
| for _, k := range kasir { |
| resp = append(resp, toUserResponse(&k)) |
| } |
| c.JSON(http.StatusOK, resp) |
| } |
|
|
| func CreateKasir(c *gin.Context) { |
| userVal, _ := c.Get("user") |
| owner := userVal.(*models.User) |
|
|
| var req RegisterRequest |
| if err := c.ShouldBindJSON(&req); err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()}) |
| return |
| } |
|
|
| if req.Role != "" && req.Role != "kasir" { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": "Owner hanya bisa membuat role 'kasir'"}) |
| return |
| } |
|
|
| var existingCount int64 |
| config.DB.Model(&models.User{}).Where("email = ?", req.Email).Count(&existingCount) |
| if existingCount > 0 { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": "Email sudah terdaftar"}) |
| return |
| } |
|
|
| var company models.Company |
| if err := config.DB.First(&company, *owner.CompanyID).Error; err != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal mengambil data perusahaan"}) |
| return |
| } |
|
|
| if !company.EnableMultiKasir && company.MaxCashiers <= 0 { |
| var kasirCount int64 |
| config.DB.Model(&models.User{}).Where("company_id = ? AND role = ?", *owner.CompanyID, "kasir").Count(&kasirCount) |
| if kasirCount >= 1 { |
| c.JSON(http.StatusForbidden, gin.H{"detail": "Paket perusahaan Anda tidak mendukung pembuatan lebih dari 1 kasir"}) |
| return |
| } |
| } else if company.MaxCashiers > 0 { |
| var kasirCount int64 |
| config.DB.Model(&models.User{}).Where("company_id = ? AND role = ?", *owner.CompanyID, "kasir").Count(&kasirCount) |
| if kasirCount >= int64(company.MaxCashiers) { |
| c.JSON(http.StatusForbidden, gin.H{"detail": fmt.Sprintf("Batas maksimal kasir (%d) telah tercapai untuk perusahaan Anda", company.MaxCashiers)}) |
| return |
| } |
| } |
|
|
| hashedPassword, _ := middleware.HashPassword(req.Password) |
| newUser := models.User{ |
| Name: req.Name, |
| Email: req.Email, |
| Password: hashedPassword, |
| Role: "kasir", |
| CompanyID: owner.CompanyID, |
| BranchID: req.BranchID, |
| IsActive: true, |
| } |
|
|
| if err := config.DB.Create(&newUser).Error; err != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal menyimpan kasir"}) |
| return |
| } |
|
|
| c.JSON(http.StatusCreated, toUserResponse(&newUser)) |
| } |
|
|
| func UpdateKasir(c *gin.Context) { |
| userVal, _ := c.Get("user") |
| owner := userVal.(*models.User) |
|
|
| kasirIDStr := c.Param("user_id") |
| kasirID, err := strconv.Atoi(kasirIDStr) |
| if err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": "ID kasir tidak valid"}) |
| return |
| } |
|
|
| var req UpdateKasirRequest |
| if err := c.ShouldBindJSON(&req); err != nil { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()}) |
| return |
| } |
|
|
| var kasir models.User |
| if err := config.DB.Where("id = ? AND company_id = ? AND role = ?", kasirID, *owner.CompanyID, "kasir").First(&kasir).Error; err != nil { |
| c.JSON(http.StatusNotFound, gin.H{"detail": "Kasir tidak ditemukan di perusahaan ini"}) |
| return |
| } |
|
|
| if req.Name != "" { |
| kasir.Name = req.Name |
| } |
| if req.Email != "" { |
| var count int64 |
| config.DB.Model(&models.User{}).Where("email = ? AND id != ?", req.Email, kasirID).Count(&count) |
| if count > 0 { |
| c.JSON(http.StatusBadRequest, gin.H{"detail": "Email sudah digunakan user lain"}) |
| return |
| } |
| kasir.Email = req.Email |
| } |
| if req.Password != "" { |
| hashed, _ := middleware.HashPassword(req.Password) |
| kasir.Password = hashed |
| } |
| if req.BranchID != nil { |
| kasir.BranchID = req.BranchID |
| } |
| if req.IsActive != nil { |
| kasir.IsActive = *req.IsActive |
| } |
|
|
| if err := config.DB.Save(&kasir).Error; err != nil { |
| c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal meng-update kasir"}) |
| return |
| } |
|
|
| c.JSON(http.StatusOK, toUserResponse(&kasir)) |
| } |
|
|