Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions internal/api/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ func NewRouter(deps app.Dependencies) *mux.Router {

peerlySubrouter.Handle("/admin/reported_appreciation_report", middleware.JwtAuthMiddleware(reportedAppreciationReportHandler(deps.UserService, deps.ReportAppreciationService), constants.Admin)).Methods(http.MethodGet)

peerlySubrouter.Handle("/admin/dynamic_engagers_report", middleware.JwtAuthMiddleware(dynamicEngagersReportHandler(deps.UserService), constants.Admin)).Methods(http.MethodGet)


//appreciations

peerlySubrouter.Handle("/appreciations/{id:[0-9]+}", middleware.JwtAuthMiddleware(getAppreciationByIDHandler(deps.AppreciationService), constants.User)).Methods(http.MethodGet).Headers(versionHeader, v1)
Expand Down
49 changes: 47 additions & 2 deletions internal/api/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,10 +228,24 @@ func listUsersHandler(userSvc user.Service) http.HandlerFunc {

func getActiveUserListHandler(userSvc user.Service) http.HandlerFunc {
return func(rw http.ResponseWriter, req *http.Request) {

ctx := req.Context()

quarterStr := req.URL.Query().Get("quarter")
yearStr := req.URL.Query().Get("year")

quarter, err := strconv.Atoi(quarterStr)
if err != nil || quarter < 1 || quarter > 4 {
http.Error(rw, "Invalid quarter", http.StatusBadRequest)
return
}
year, err := strconv.Atoi(yearStr)
if err != nil || year < 2024 {
http.Error(rw, "Invalid year", http.StatusBadRequest)
return
}

log.Info(ctx, "getActiveUserListHandler: req: ", req)
resp, err := userSvc.GetActiveUserList(req.Context())
resp, err := userSvc.GetActiveUserList(ctx, quarter, year)
if err != nil {
dto.ErrorRepsonse(rw, err)
return
Expand All @@ -241,6 +255,7 @@ func getActiveUserListHandler(userSvc user.Service) http.HandlerFunc {
dto.SuccessRepsonse(rw, http.StatusOK, "Active Users list", resp)
}
}

func getUserByIdHandler(userSvc user.Service) http.HandlerFunc {
return func(rw http.ResponseWriter, req *http.Request) {

Expand Down Expand Up @@ -341,3 +356,33 @@ func reportedAppreciationReportHandler(userSvc user.Service, reportAppreciationS
// dto.SuccessRepsonse(rw, 200, "Excel downloaded successfully", nil)
}
}

func dynamicEngagersReportHandler(userSvc user.Service) http.HandlerFunc {
return func(rw http.ResponseWriter, req *http.Request) {
ctx := req.Context()

quarterStr := req.URL.Query().Get("quarter")
yearStr := req.URL.Query().Get("year")

quarter, err := strconv.Atoi(quarterStr)
if err != nil || quarter < 1 || quarter > 4 {
http.Error(rw, "Invalid quarter", http.StatusBadRequest)
return
}
year, err := strconv.Atoi(yearStr)
if err != nil || year < 2024 {
http.Error(rw, "Invalid year", http.StatusBadRequest)
return
}

tempFileName, err := userSvc.DynamicEngagersReport(ctx, quarter, year)
if err != nil {
logger.Errorf(ctx, "dynamicEngagersReportHandler: err: %v", err)
dto.ErrorRepsonse(rw, err)
return
}

http.ServeFile(rw, req, tempFileName)
}
}

22 changes: 22 additions & 0 deletions internal/app/users/mocks/Service.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

144 changes: 130 additions & 14 deletions internal/app/users/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,13 @@ type Service interface {
ListUsers(ctx context.Context, reqData dto.ListUsersReq) (resp dto.ListUsersResp, err error)
GetUserById(ctx context.Context) (user dto.GetUserByIdResp, err error)
UpdateRewardQuota(ctx context.Context) (err error)
GetActiveUserList(ctx context.Context) ([]dto.ActiveUser, error)
GetActiveUserList(ctx context.Context, quarter int, year int) ([]dto.ActiveUser, error)
GetTop10Users(ctx context.Context) (users []dto.Top10User, err error)
AdminLogin(ctx context.Context, loginReq dto.AdminLoginReq) (resp dto.LoginUserResp, err error)
NotificationByAdmin(ctx context.Context, notificationReq dto.AdminNotificationReq) (err error)
AllAppreciationReport(ctx context.Context, appreciations []dto.AppreciationResponse) (tempFileName string, err error)
ReportedAppreciationReport(ctx context.Context, appreciations []dto.ReportedAppreciation) (tempFileName string, err error)
DynamicEngagersReport(ctx context.Context, quarter int, year int) (tempFileName string, err error)
}

func NewService(userRepo repository.UserStorer) Service {
Expand Down Expand Up @@ -483,8 +484,9 @@ func (us *service) GetUserById(ctx context.Context) (user dto.GetUserByIdResp, e
return
}

func (us *service) GetActiveUserList(ctx context.Context) ([]dto.ActiveUser, error) {
activeUserDb, err := us.userRepo.GetActiveUserList(ctx, nil)
func (us *service) GetActiveUserList(ctx context.Context, quarter int, year int) ([]dto.ActiveUser, error) {
quarterStart, quarterEnd := getQuarterRangeUnixTime(quarter, year)
activeUserDb, err := us.userRepo.GetActiveUserList(ctx, nil, quarterStart, quarterEnd)
if err != nil {
logger.Errorf(ctx, "userService: GetActiveUserList: err: %v", err)
return []dto.ActiveUser{}, err
Expand All @@ -496,6 +498,34 @@ func (us *service) GetActiveUserList(ctx context.Context) ([]dto.ActiveUser, err
}
return res, nil
}

func getQuarterRangeUnixTime(quarter int, year int) (start int64, end int64) {
var startMonth, endMonth time.Month
startYear := year
endYear := year
switch quarter {
case 1:
startMonth = time.March
endMonth = time.June
case 2:
startMonth = time.June
endMonth = time.September
case 3:
startMonth = time.September
endMonth = time.December
case 4:
startMonth = time.December
endMonth = time.March
endYear = year + 1 // Q4 ends next year's March
default:
startMonth = time.January
endMonth = time.January
}
startTime := time.Date(startYear, startMonth, 1, 0, 0, 0, 0, time.UTC)
endTime := time.Date(endYear, endMonth, 1, 0, 0, 0, 0, time.UTC)
return startTime.UnixMilli(), endTime.UnixMilli()
}

func (us *service) UpdateRewardQuota(ctx context.Context) error {
err := us.userRepo.UpdateRewardQuota(ctx, nil)
return err
Expand All @@ -504,22 +534,34 @@ func GetQuarterStartUnixTime() int64 {
now := time.Now()
year := now.Year()
var startMonth time.Month
var startYear int
switch now.Month() {
case time.March, time.April, time.May:
// Q1 (Mar-May)
startMonth = time.March
startYear = year
case time.June, time.July, time.August:
// Q2 (Jun-Aug)
startMonth = time.June
startYear = year
case time.September, time.October, time.November:
// Q3 (Sep-Nov)
startMonth = time.September
startYear = year
case time.December:
// Q4 starts Dec of current year
startMonth = time.December
startYear = year
case time.January, time.February:
// Q4 continues into next year (Dec of previous year)
startMonth = time.December
year = year - 1
startYear = year - 1
default:
startMonth = time.January
startYear = year
}

quarterStart := time.Date(year, startMonth, 1, 0, 0, 0, 0, time.UTC)
return quarterStart.Unix() * 1000
quarterStart := time.Date(startYear, startMonth, 1, 0, 0, 0, 0, time.UTC)
return quarterStart.Unix() * 1000
}

func (us *service) GetTop10Users(ctx context.Context) (users []dto.Top10User, err error) {
Expand Down Expand Up @@ -615,7 +657,6 @@ func GetQuarterName(t time.Time) string {
}
}


func (us *service) AllAppreciationReport(ctx context.Context, appreciations []dto.AppreciationResponse) (tempFileName string, err error) {

// Create a new Excel file
Expand All @@ -630,7 +671,7 @@ func (us *service) AllAppreciationReport(ctx context.Context, appreciations []dt
}

// Set header
headers := []string{"Core value", "Core value description", "Appreciation description", "Sender Employee ID" ,"Sender first name", "Sender last name", "Sender designation", "Receiver Employee ID", "Receiver first name", "Receiver last name", "Receiver designation", "Total rewards", "Total reward points", "Appreciated Date", "Quarter"}
headers := []string{"Core value", "Core value description", "Appreciation description", "Sender Employee ID", "Sender first name", "Sender last name", "Sender designation", "Receiver Employee ID", "Receiver first name", "Receiver last name", "Receiver designation", "Total rewards", "Total reward points", "Appreciated Date", "Quarter"}
for colIndex, header := range headers {

cell := fmt.Sprintf("%c1", 'A'+colIndex)
Expand All @@ -641,10 +682,10 @@ func (us *service) AllAppreciationReport(ctx context.Context, appreciations []dt
for rowIndex, app := range appreciations {
row := rowIndex + 2 // Starting from row 2

createdTime := time.UnixMilli(app.CreatedAt)
createdTime := time.UnixMilli(app.CreatedAt)
appreciatedAt := time.UnixMilli(app.CreatedAt).Format("02/01/2006")
quarter := GetQuarterName(createdTime)

f.SetCellValue(sheetName, fmt.Sprintf("A%d", row), app.CoreValueName)
f.SetCellValue(sheetName, fmt.Sprintf("B%d", row), app.CoreValueDesc)
f.SetCellValue(sheetName, fmt.Sprintf("C%d", row), app.Description)
Expand Down Expand Up @@ -690,7 +731,7 @@ func (us *service) ReportedAppreciationReport(ctx context.Context, appreciations
}

// Set header
headers := []string{"Core value", "Core value description", "Appreciation description", "Sender Employee ID", "Sender first name", "Sender last name", "Sender designation", "Receiver Employee ID" ,"Receiver first name", "Receiver last name", "Receiver designation", "Appreciated Date", "Reporter Emp ID","Reporting Comment", "Reported by first name", "Reported by last name", "Reported Date", "Moderator comment", "Moderator first name", "Moderator last name", "Status","Quarter"}
headers := []string{"Core value", "Core value description", "Appreciation description", "Sender Employee ID", "Sender first name", "Sender last name", "Sender designation", "Receiver Employee ID", "Receiver first name", "Receiver last name", "Receiver designation", "Appreciated Date", "Reporter Emp ID", "Reporting Comment", "Reported by first name", "Reported by last name", "Reported Date", "Moderator comment", "Moderator first name", "Moderator last name", "Status", "Quarter"}
for colIndex, header := range headers {
cell := fmt.Sprintf("%c1", 'A'+colIndex)
f.SetCellValue(sheetName, cell, header)
Expand All @@ -703,7 +744,7 @@ func (us *service) ReportedAppreciationReport(ctx context.Context, appreciations
reportedAt := time.UnixMilli(app.ReportedAt).Format("02/01/2006")

createdTime := time.UnixMilli(app.CreatedAt)
quarter := GetQuarterName(createdTime)
quarter := GetQuarterName(createdTime)

row := rowIndex + 2 // Starting from row 2
f.SetCellValue(sheetName, fmt.Sprintf("A%d", row), app.CoreValueName)
Expand All @@ -717,7 +758,7 @@ func (us *service) ReportedAppreciationReport(ctx context.Context, appreciations
f.SetCellValue(sheetName, fmt.Sprintf("I%d", row), app.ReceiverFirstName)
f.SetCellValue(sheetName, fmt.Sprintf("J%d", row), app.ReceiverLastName)
f.SetCellValue(sheetName, fmt.Sprintf("K%d", row), app.ReceiverDesignation)
f.SetCellValue(sheetName, fmt.Sprintf("L%d", row), appreciatedAt)
f.SetCellValue(sheetName, fmt.Sprintf("L%d", row), appreciatedAt)
f.SetCellValue(sheetName, fmt.Sprintf("M%d", row), app.ReporterEmployeeID)
f.SetCellValue(sheetName, fmt.Sprintf("N%d", row), app.ReportingComment)
f.SetCellValue(sheetName, fmt.Sprintf("O%d", row), app.ReportedByFirstName)
Expand All @@ -742,3 +783,78 @@ func (us *service) ReportedAppreciationReport(ctx context.Context, appreciations

return
}

func getStandardQuarterRange(quarter int, year int) (start int64, end int64) {
var startMonth, endMonth time.Month
startYear := year
endYear := year
switch quarter {
case 1:
// Q1: March 01 - May 31
startMonth = time.March
endMonth = time.June
case 2:
// Q2: June 01 - August 31
startMonth = time.June
endMonth = time.September
case 3:
// Q3: September 01 - November 30
startMonth = time.September
endMonth = time.December
case 4:
// Q4: December 01 - February 28/29 (next year)
startMonth = time.December
endMonth = time.March
endYear = year + 1
default:
startMonth = time.January
endMonth = time.January
}
startTime := time.Date(startYear, startMonth, 1, 0, 0, 0, 0, time.UTC)
endTime := time.Date(endYear, endMonth, 1, 0, 0, 0, 0, time.UTC)
return startTime.UnixMilli(), endTime.UnixMilli()
}

func (us *service) DynamicEngagersReport(ctx context.Context, quarter int, year int) (tempFileName string, err error) {
start, end := getStandardQuarterRange(quarter, year)
engagers, err := us.userRepo.GetDynamicEngagersReport(ctx, nil, start, end)
if err != nil {
logger.Errorf(ctx, "userService: DynamicEngagersReport: GetDynamicEngagersReport err: %v", err)
return "", err
}

f := excelize.NewFile()
sheetName := "DynamicEngagers"
index, err := f.NewSheet(sheetName)
if err != nil {
logger.Errorf(ctx, "userService: DynamicEngagersReport: err in generating newsheet, err: %v", err)
return "", err
}

headers := []string{"User ID", "First Name", "Last Name", "Sent", "Received", "Reward Points", "Total Points"}
for colIndex, header := range headers {
cell := fmt.Sprintf("%c1", 'A'+colIndex)
f.SetCellValue(sheetName, cell, header)
}

for rowIndex, eng := range engagers {
row := rowIndex + 2 // Starting from row 2
f.SetCellValue(sheetName, fmt.Sprintf("A%d", row), eng.UserID)
f.SetCellValue(sheetName, fmt.Sprintf("B%d", row), eng.FirstName)
f.SetCellValue(sheetName, fmt.Sprintf("C%d", row), eng.LastName)
f.SetCellValue(sheetName, fmt.Sprintf("D%d", row), eng.Sent)
f.SetCellValue(sheetName, fmt.Sprintf("E%d", row), eng.Received)
f.SetCellValue(sheetName, fmt.Sprintf("F%d", row), eng.RewardPoints)
f.SetCellValue(sheetName, fmt.Sprintf("G%d", row), eng.TotalPoints)
}

f.SetActiveSheet(index)

tempFileName = fmt.Sprintf("dynamic_engagers_report_Q%d_%d-%d.xlsx", quarter, year, year+1)
if err = f.SaveAs(tempFileName); err != nil {
logger.Errorf(ctx, "userService: DynamicEngagersReport: Failed to save file: %v", err)
return "", err
}

return tempFileName, nil
}
13 changes: 10 additions & 3 deletions internal/app/users/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,20 @@ import (
"github.com/joshsoftware/peerly-backend/internal/pkg/apperrors"
"github.com/joshsoftware/peerly-backend/internal/pkg/constants"
"github.com/joshsoftware/peerly-backend/internal/pkg/dto"
log "github.com/joshsoftware/peerly-backend/internal/pkg/logger"
"github.com/joshsoftware/peerly-backend/internal/pkg/testConfig"
"github.com/joshsoftware/peerly-backend/internal/repository"
"github.com/joshsoftware/peerly-backend/internal/repository/mocks"
l "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

func init() {
log.Logger = l.New()
}


func TestLoginUser(t *testing.T) {
testConfig.Load()
userRepo := mocks.NewUserStorer(t)
Expand Down Expand Up @@ -482,7 +489,7 @@ func TestGetActiveUserList(t *testing.T) {
name: "success",
context: context.Background(),
setup: func(userMock *mocks.UserStorer) {
userMock.On("GetActiveUserList", mock.Anything, mock.Anything).Return([]repository.ActiveUser{
userMock.On("GetActiveUserList", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]repository.ActiveUser{
{
ID: 55,
FirstName: "Deepak",
Expand Down Expand Up @@ -521,7 +528,7 @@ func TestGetActiveUserList(t *testing.T) {
name: "failure",
context: context.Background(),
setup: func(userMock *mocks.UserStorer) {
userMock.On("GetActiveUserList", mock.Anything, mock.Anything).Return([]repository.ActiveUser{}, apperrors.InternalServer).Once()
userMock.On("GetActiveUserList", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]repository.ActiveUser{}, apperrors.InternalServer).Once()
},
expectedResp: []dto.ActiveUser{},
expectedError: apperrors.InternalServer,
Expand All @@ -533,7 +540,7 @@ func TestGetActiveUserList(t *testing.T) {
test.setup(userRepo)

// test service
resp, err := service.GetActiveUserList(test.context)
resp, err := service.GetActiveUserList(test.context, 1, 2026)

if err != nil {
assert.Equal(t, test.expectedError, err)
Expand Down
Loading