package service import ( "context" "apskel-pos-be/internal/appcontext" "apskel-pos-be/internal/constants" "apskel-pos-be/internal/contract" "apskel-pos-be/internal/processor" "apskel-pos-be/internal/transformer" "github.com/google/uuid" ) type CashAdvanceService interface { CreateCashAdvance(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.CreateCashAdvanceRequest) *contract.Response UpdateCashAdvance(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID, req *contract.UpdateCashAdvanceRequest) *contract.Response DeleteCashAdvance(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID) *contract.Response GetCashAdvanceByID(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID) *contract.Response ListCashAdvances(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.ListCashAdvancesRequest) *contract.Response UpdateCashAdvanceStatus(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID, status string) *contract.Response ListCashAdvanceTeams(ctx context.Context, apctx *appcontext.ContextInfo) *contract.Response } type CashAdvanceServiceImpl struct { cashAdvanceProcessor processor.CashAdvanceProcessor } func NewCashAdvanceService(cashAdvanceProcessor processor.CashAdvanceProcessor) *CashAdvanceServiceImpl { return &CashAdvanceServiceImpl{cashAdvanceProcessor: cashAdvanceProcessor} } func (s *CashAdvanceServiceImpl) CreateCashAdvance(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.CreateCashAdvanceRequest) *contract.Response { modelReq, err := transformer.CreateCashAdvanceRequestToModel(req) if err != nil { errorResp := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.CashAdvanceServiceEntity, "Invalid date format. Use YYYY-MM-DD format") return contract.BuildErrorResponse([]*contract.ResponseError{errorResp}) } cashAdvance, err := s.cashAdvanceProcessor.CreateCashAdvance(ctx, apctx.OrganizationID, outletFromContext(apctx), modelReq) if err != nil { errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.CashAdvanceServiceEntity, err.Error()) return contract.BuildErrorResponse([]*contract.ResponseError{errorResp}) } return contract.BuildSuccessResponse(transformer.CashAdvanceModelResponseToResponse(cashAdvance)) } func (s *CashAdvanceServiceImpl) UpdateCashAdvance(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID, req *contract.UpdateCashAdvanceRequest) *contract.Response { modelReq, err := transformer.UpdateCashAdvanceRequestToModel(req) if err != nil { errorResp := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.CashAdvanceServiceEntity, "Invalid date format. Use YYYY-MM-DD format") return contract.BuildErrorResponse([]*contract.ResponseError{errorResp}) } cashAdvance, err := s.cashAdvanceProcessor.UpdateCashAdvance(ctx, id, apctx.OrganizationID, modelReq) if err != nil { errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.CashAdvanceServiceEntity, err.Error()) return contract.BuildErrorResponse([]*contract.ResponseError{errorResp}) } return contract.BuildSuccessResponse(transformer.CashAdvanceModelResponseToResponse(cashAdvance)) } func (s *CashAdvanceServiceImpl) DeleteCashAdvance(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID) *contract.Response { if err := s.cashAdvanceProcessor.DeleteCashAdvance(ctx, id, apctx.OrganizationID); err != nil { errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.CashAdvanceServiceEntity, err.Error()) return contract.BuildErrorResponse([]*contract.ResponseError{errorResp}) } return contract.BuildSuccessResponse(map[string]interface{}{ "message": "Cash advance deleted successfully", }) } func (s *CashAdvanceServiceImpl) GetCashAdvanceByID(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID) *contract.Response { cashAdvance, err := s.cashAdvanceProcessor.GetCashAdvanceByID(ctx, id, apctx.OrganizationID) if err != nil { errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.CashAdvanceServiceEntity, err.Error()) return contract.BuildErrorResponse([]*contract.ResponseError{errorResp}) } return contract.BuildSuccessResponse(transformer.CashAdvanceModelResponseToResponse(cashAdvance)) } func (s *CashAdvanceServiceImpl) ListCashAdvances(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.ListCashAdvancesRequest) *contract.Response { modelReq := transformer.ListCashAdvancesRequestToModel(req) filters := make(map[string]interface{}) if modelReq.Search != "" { filters["search"] = modelReq.Search } if modelReq.Status != "" { filters["status"] = modelReq.Status } if modelReq.SettlementStatus != "" { filters["settlement_status"] = modelReq.SettlementStatus } if modelReq.TeamScope != "" { filters["team_scope"] = modelReq.TeamScope } if modelReq.TeamCategoryID != nil { filters["team_category_id"] = *modelReq.TeamCategoryID } // team spells out the same two filters in one value; the validator has already // ruled out sending it together with them. switch modelReq.Team { case "": case constants.PurchaseTeamScopeCentral: filters["team_scope"] = constants.PurchaseTeamScopeCentral default: if teamCategoryID, err := uuid.Parse(modelReq.Team); err == nil { filters["team_scope"] = constants.PurchaseTeamScopeCategory filters["team_category_id"] = teamCategoryID } } if modelReq.StartDate != nil { filters["start_date"] = *modelReq.StartDate } if modelReq.EndDate != nil { filters["end_date"] = *modelReq.EndDate } // Cash belongs to the drawer it came out of, so a user signed in to one outlet // only sees that outlet's cash advances. if outletID := outletFromContext(apctx); outletID != nil { filters["outlet_id"] = *outletID } cashAdvances, totalPages, err := s.cashAdvanceProcessor.ListCashAdvances(ctx, apctx.OrganizationID, filters, modelReq.Page, modelReq.Limit) if err != nil { errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.CashAdvanceServiceEntity, err.Error()) return contract.BuildErrorResponse([]*contract.ResponseError{errorResp}) } responses := make([]contract.CashAdvanceResponse, len(cashAdvances)) for i, cashAdvance := range cashAdvances { if response := transformer.CashAdvanceModelResponseToResponse(cashAdvance); response != nil { responses[i] = *response } } return contract.BuildSuccessResponse(contract.ListCashAdvancesResponse{ CashAdvances: responses, TotalCount: len(responses), Page: modelReq.Page, Limit: modelReq.Limit, TotalPages: totalPages, }) } func (s *CashAdvanceServiceImpl) UpdateCashAdvanceStatus(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID, status string) *contract.Response { cashAdvance, err := s.cashAdvanceProcessor.UpdateCashAdvanceStatus(ctx, id, apctx.OrganizationID, status) if err != nil { errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.CashAdvanceServiceEntity, err.Error()) return contract.BuildErrorResponse([]*contract.ResponseError{errorResp}) } return contract.BuildSuccessResponse(transformer.CashAdvanceModelResponseToResponse(cashAdvance)) } func (s *CashAdvanceServiceImpl) ListCashAdvanceTeams(ctx context.Context, apctx *appcontext.ContextInfo) *contract.Response { teams, err := s.cashAdvanceProcessor.ListCashAdvanceTeams(ctx, apctx.OrganizationID, outletFromContext(apctx)) if err != nil { errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.CashAdvanceServiceEntity, err.Error()) return contract.BuildErrorResponse([]*contract.ResponseError{errorResp}) } return contract.BuildSuccessResponse(transformer.ListPurchaseTeamsModelResponseToResponse(teams)) } // outletFromContext reads the caller's outlet as an optional value: an organization // level user has none, and uuid.Nil is how that arrives on the context. func outletFromContext(apctx *appcontext.ContextInfo) *uuid.UUID { if apctx.OutletID == uuid.Nil { return nil } outletID := apctx.OutletID return &outletID }