Merge pull request 'Refactor customer form and service for company ID validation.' (#28) from TM-559 into develop

Reviewed-on: https://repo.ravanertebat.com/TM/tm_back/pulls/28
Reviewed-by: Hadi Barzegar <barzagarhadi@gmail.com>
This commit is contained in:
m.nazemi
2026-06-01 16:57:48 +03:30
3 changed files with 88 additions and 45 deletions
+6 -5
View File
@@ -24,11 +24,12 @@ type UpdateCustomerForm struct {
FullName *string `json:"full_name,omitempty" valid:"optional,length(2|100)" example:"User"`
Type string `json:"type" valid:"required,in(individual|company|government)" example:"individual"`
Role string `json:"role" valid:"required,in(admin|analyst)" example:"analyst"`
Companies []string `json:"companies,omitempty" valid:"optional"`
Username string `json:"username" valid:"required,username,length(3|30)" example:"user"`
Email string `json:"email" valid:"required,email" example:"user@opplens.com"`
Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)" example:"+1234567890"`
Keywords []string `json:"keywords,omitempty" valid:"optional"`
// Companies is a pointer so omitted/null leaves assignments unchanged; an empty JSON array clears them.
Companies *[]string `json:"companies,omitempty" valid:"optional"`
Username string `json:"username" valid:"required,username,length(3|30)" example:"user"`
Email string `json:"email" valid:"required,email" example:"user@opplens.com"`
Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)" example:"+1234567890"`
Keywords []string `json:"keywords,omitempty" valid:"optional"`
}
// CustomerStatusForm represents the form for suspending a customer
+3
View File
@@ -133,6 +133,9 @@ func (h *Handler) UpdateCustomer(c echo.Context) error {
err.Error() == "company with this tax ID already exists" {
return response.Conflict(c, err.Error())
}
if strings.HasPrefix(err.Error(), "invalid company ID") {
return response.BadRequest(c, err.Error(), err.Error())
}
return response.InternalServerError(c, "Failed to update customer")
}
+79 -40
View File
@@ -100,23 +100,13 @@ func (s *customerService) Register(ctx context.Context, form *CreateCustomerForm
return nil, errors.New("invalid username format")
}
// Verify that all company IDs exist
var Companies []string
if len(form.Companies) > 0 {
for _, companyID := range form.Companies {
if _, err := bson.ObjectIDFromHex(companyID); err != nil {
return nil, errors.New("invalid company ID format: " + companyID)
}
_, err := s.companyService.GetByID(ctx, companyID)
if err != nil {
s.logger.Error("Invalid company ID provided", map[string]interface{}{
"error": err.Error(),
"company_id": companyID,
})
return nil, errors.New("invalid company ID: " + companyID)
}
validated, err := s.validateCompanyIDs(ctx, form.Companies)
if err != nil {
return nil, err
}
Companies = form.Companies
Companies = validated
}
// Hash password
@@ -362,36 +352,21 @@ func (s *customerService) Update(ctx context.Context, id string, form *UpdateCus
changeUsername = true
}
// Handle company assignments
companies := make([]*CompanySummary, 0)
companiesChanged := false
if len(form.Companies) > 0 {
// Check if companies changed
if len(form.Companies) != len(customer.Companies) {
companiesChanged = true
} else {
companyMap := make(map[string]bool)
for _, id := range customer.Companies {
companyMap[id] = true
}
for _, id := range form.Companies {
if !companyMap[id] {
companiesChanged = true
break
}
}
}
customer.Companies = form.Companies
companies, err = s.loadCompaniesForCustomer(ctx, customer)
if err != nil {
s.logger.Error("Failed to load companies for customer", map[string]interface{}{
"error": err.Error(),
"customer_id": customer.ID,
})
if form.Companies != nil {
companyIDs, validateErr := s.validateCompanyIDs(ctx, *form.Companies)
if validateErr != nil {
return nil, validateErr
}
companiesChanged = !companyIDsEqual(customer.Companies, companyIDs)
customer.Companies = companyIDs
}
infoChanged := false
if form.Type != "" && customer.Type != CustomerType(form.Type) {
customer.Type = CustomerType(form.Type)
infoChanged = true
}
if form.FullName != nil && (customer.FullName == nil || *form.FullName != *customer.FullName) {
customer.FullName = form.FullName
infoChanged = true
@@ -458,7 +433,16 @@ func (s *customerService) Update(ctx context.Context, id string, form *UpdateCus
"email": customer.Email,
})
return customer.ToResponse(companies), nil
responseCompanies, err := s.loadCompaniesForCustomer(ctx, customer)
if err != nil {
s.logger.Error("Failed to load companies for customer response", map[string]interface{}{
"error": err.Error(),
"customer_id": customer.ID,
})
responseCompanies = []*CompanySummary{}
}
return customer.ToResponse(responseCompanies), nil
}
// Delete deletes a customer (soft delete)
@@ -1269,6 +1253,61 @@ func (s *customerService) isValidPassword(password string) bool {
return hasUpper && hasLower && hasSpecial
}
func (s *customerService) validateCompanyIDs(ctx context.Context, companyIDs []string) ([]string, error) {
if len(companyIDs) == 0 {
return []string{}, nil
}
validated := make([]string, 0, len(companyIDs))
seen := make(map[string]struct{}, len(companyIDs))
for _, companyID := range companyIDs {
if companyID == "" {
continue
}
if _, exists := seen[companyID]; exists {
continue
}
if _, err := bson.ObjectIDFromHex(companyID); err != nil {
return nil, errors.New("invalid company ID format: " + companyID)
}
if _, err := s.companyService.GetByID(ctx, companyID); err != nil {
s.logger.Error("Invalid company ID provided", map[string]interface{}{
"error": err.Error(),
"company_id": companyID,
})
return nil, errors.New("invalid company ID: " + companyID)
}
seen[companyID] = struct{}{}
validated = append(validated, companyID)
}
return validated, nil
}
func companyIDsEqual(existing, updated []string) bool {
if len(existing) != len(updated) {
return false
}
counts := make(map[string]int, len(existing))
for _, id := range existing {
counts[id]++
}
for _, id := range updated {
counts[id]--
if counts[id] < 0 {
return false
}
}
for _, count := range counts {
if count != 0 {
return false
}
}
return true
}
// loadCompaniesForCustomer loads company summaries for a customer
func (s *customerService) loadCompaniesForCustomer(ctx context.Context, customer *Customer) ([]*CompanySummary, error) {
if len(customer.Companies) == 0 {