Files
apskel-pos-backend/internal/service/cash_advance_service.go
T
2026-08-13 14:38:28 +07:00

181 lines
8.0 KiB
Go

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
}