Refactor Customer API Endpoints and Update Documentation
- Changed customer-related API endpoints to improve clarity and consistency, including renaming and restructuring routes. - Removed unused query parameters and handlers, streamlining the customer management functionality. - Updated customer entity and forms to reflect new example values for enhanced API documentation clarity. - Enhanced response structures to return customer entities directly, simplifying the response handling in API endpoints. - Updated API documentation to reflect changes in endpoint structure and response formats, ensuring consistency for API consumers.
This commit is contained in:
+48
-1040
File diff suppressed because it is too large
Load Diff
+48
-1040
File diff suppressed because it is too large
Load Diff
+33
-669
@@ -435,48 +435,6 @@ definitions:
|
||||
is_verified:
|
||||
type: boolean
|
||||
type: object
|
||||
customer.Address:
|
||||
properties:
|
||||
address_type:
|
||||
description: billing, shipping, etc.
|
||||
type: string
|
||||
city:
|
||||
type: string
|
||||
country:
|
||||
type: string
|
||||
is_default:
|
||||
type: boolean
|
||||
postal_code:
|
||||
type: string
|
||||
state:
|
||||
type: string
|
||||
street:
|
||||
type: string
|
||||
type: object
|
||||
customer.AddressForm:
|
||||
properties:
|
||||
address_type:
|
||||
type: string
|
||||
city:
|
||||
type: string
|
||||
country:
|
||||
type: string
|
||||
is_default:
|
||||
type: boolean
|
||||
postal_code:
|
||||
type: string
|
||||
state:
|
||||
type: string
|
||||
street:
|
||||
type: string
|
||||
type: object
|
||||
customer.AssignCompaniesForm:
|
||||
properties:
|
||||
company_ids:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
customer.AuthResponse:
|
||||
properties:
|
||||
access_token:
|
||||
@@ -495,100 +453,29 @@ definitions:
|
||||
name:
|
||||
type: string
|
||||
type: object
|
||||
customer.ContactPerson:
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
first_name:
|
||||
type: string
|
||||
full_name:
|
||||
type: string
|
||||
is_primary:
|
||||
type: boolean
|
||||
last_name:
|
||||
type: string
|
||||
mobile:
|
||||
type: string
|
||||
phone:
|
||||
type: string
|
||||
position:
|
||||
type: string
|
||||
type: object
|
||||
customer.ContactPersonForm:
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
first_name:
|
||||
type: string
|
||||
full_name:
|
||||
type: string
|
||||
is_primary:
|
||||
type: boolean
|
||||
last_name:
|
||||
type: string
|
||||
mobile:
|
||||
type: string
|
||||
phone:
|
||||
type: string
|
||||
position:
|
||||
type: string
|
||||
type: object
|
||||
customer.CreateCustomerForm:
|
||||
properties:
|
||||
address:
|
||||
allOf:
|
||||
- $ref: '#/definitions/customer.AddressForm'
|
||||
description: Address information
|
||||
annual_revenue:
|
||||
type: number
|
||||
business_type:
|
||||
description: Business information
|
||||
type: string
|
||||
company_ids:
|
||||
description: Company assignments
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
contact_person:
|
||||
allOf:
|
||||
- $ref: '#/definitions/customer.ContactPersonForm'
|
||||
description: Contact person (for company customers)
|
||||
currency:
|
||||
type: string
|
||||
email:
|
||||
example: user@opplens.com
|
||||
type: string
|
||||
employee_count:
|
||||
type: integer
|
||||
first_name:
|
||||
description: Individual customer fields
|
||||
type: string
|
||||
founded_year:
|
||||
type: integer
|
||||
full_name:
|
||||
type: string
|
||||
industry:
|
||||
type: string
|
||||
language:
|
||||
description: Preferences and settings
|
||||
type: string
|
||||
last_name:
|
||||
type: string
|
||||
mobile:
|
||||
example: User
|
||||
type: string
|
||||
password:
|
||||
example: User!1234
|
||||
type: string
|
||||
phone:
|
||||
type: string
|
||||
registration_number:
|
||||
description: Company customer fields
|
||||
type: string
|
||||
tax_id:
|
||||
type: string
|
||||
timezone:
|
||||
example: "+1234567890"
|
||||
type: string
|
||||
type:
|
||||
example: individual
|
||||
type: string
|
||||
username:
|
||||
example: user
|
||||
type: string
|
||||
type: object
|
||||
customer.CustomerListResponse:
|
||||
@@ -597,89 +484,49 @@ definitions:
|
||||
items:
|
||||
$ref: '#/definitions/customer.CustomerResponse'
|
||||
type: array
|
||||
limit:
|
||||
type: integer
|
||||
offset:
|
||||
type: integer
|
||||
total:
|
||||
type: integer
|
||||
total_pages:
|
||||
type: integer
|
||||
meta:
|
||||
$ref: '#/definitions/response.Meta'
|
||||
type: object
|
||||
customer.CustomerResponse:
|
||||
properties:
|
||||
address:
|
||||
$ref: '#/definitions/customer.Address'
|
||||
annual_revenue:
|
||||
type: number
|
||||
business_type:
|
||||
type: string
|
||||
companies:
|
||||
description: Company relationships
|
||||
items:
|
||||
$ref: '#/definitions/customer.CompanySummary'
|
||||
type: array
|
||||
compliance_notes:
|
||||
type: string
|
||||
contact_person:
|
||||
$ref: '#/definitions/customer.ContactPerson'
|
||||
created_at:
|
||||
companyIDs:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
createdAt:
|
||||
type: integer
|
||||
created_by:
|
||||
type: string
|
||||
currency:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
employee_count:
|
||||
type: integer
|
||||
first_name:
|
||||
type: string
|
||||
founded_year:
|
||||
type: integer
|
||||
full_name:
|
||||
fullName:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
industry:
|
||||
type: string
|
||||
is_compliant:
|
||||
type: boolean
|
||||
is_verified:
|
||||
type: boolean
|
||||
language:
|
||||
type: string
|
||||
last_name:
|
||||
type: string
|
||||
mobile:
|
||||
lastLoginAt:
|
||||
type: integer
|
||||
password:
|
||||
type: string
|
||||
phone:
|
||||
type: string
|
||||
registration_number:
|
||||
description: Company customer fields
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
tax_id:
|
||||
type: string
|
||||
timezone:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
updated_at:
|
||||
updatedAt:
|
||||
type: integer
|
||||
updated_by:
|
||||
type: string
|
||||
username:
|
||||
type: string
|
||||
type: object
|
||||
customer.LoginForm:
|
||||
properties:
|
||||
password:
|
||||
example: Nima.1998
|
||||
example: App!1234
|
||||
type: string
|
||||
username:
|
||||
example: nakhostin
|
||||
example: app
|
||||
type: string
|
||||
type: object
|
||||
customer.RefreshTokenForm:
|
||||
@@ -687,90 +534,40 @@ definitions:
|
||||
refresh_token:
|
||||
type: string
|
||||
type: object
|
||||
customer.RemoveCompaniesForm:
|
||||
properties:
|
||||
company_ids:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
customer.SuspendCustomerForm:
|
||||
properties:
|
||||
reason:
|
||||
type: string
|
||||
type: object
|
||||
customer.UpdateCustomerForm:
|
||||
properties:
|
||||
address:
|
||||
allOf:
|
||||
- $ref: '#/definitions/customer.AddressForm'
|
||||
description: Address information
|
||||
annual_revenue:
|
||||
type: number
|
||||
business_type:
|
||||
description: Business information
|
||||
type: string
|
||||
company_ids:
|
||||
description: Company assignments
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
company_name:
|
||||
description: Company customer fields
|
||||
type: string
|
||||
contact_person:
|
||||
allOf:
|
||||
- $ref: '#/definitions/customer.ContactPersonForm'
|
||||
description: Contact person (for company customers)
|
||||
currency:
|
||||
type: string
|
||||
email:
|
||||
example: user@opplens.com
|
||||
type: string
|
||||
employee_count:
|
||||
type: integer
|
||||
first_name:
|
||||
description: Individual customer fields
|
||||
type: string
|
||||
founded_year:
|
||||
type: integer
|
||||
full_name:
|
||||
example: User
|
||||
type: string
|
||||
industry:
|
||||
type: string
|
||||
language:
|
||||
description: Preferences and settings
|
||||
type: string
|
||||
last_name:
|
||||
type: string
|
||||
mobile:
|
||||
password:
|
||||
example: User!1234
|
||||
type: string
|
||||
phone:
|
||||
type: string
|
||||
registration_number:
|
||||
type: string
|
||||
tax_id:
|
||||
type: string
|
||||
timezone:
|
||||
example: "+1234567890"
|
||||
type: string
|
||||
type:
|
||||
example: individual
|
||||
type: string
|
||||
username:
|
||||
example: user
|
||||
type: string
|
||||
type: object
|
||||
customer.UpdateCustomerStatusForm:
|
||||
customer.UpdateStatusForm:
|
||||
properties:
|
||||
reason:
|
||||
example: Customer is active
|
||||
type: string
|
||||
status:
|
||||
example: active
|
||||
type: string
|
||||
type: object
|
||||
customer.UpdateCustomerVerificationForm:
|
||||
properties:
|
||||
compliance_notes:
|
||||
type: string
|
||||
is_compliant:
|
||||
type: boolean
|
||||
is_verified:
|
||||
type: boolean
|
||||
type: object
|
||||
feedback.CompanyFeedbackStatsResponse:
|
||||
properties:
|
||||
company_id:
|
||||
@@ -2477,26 +2274,6 @@ paths:
|
||||
in: query
|
||||
name: company_id
|
||||
type: string
|
||||
- description: Filter by industry
|
||||
in: query
|
||||
name: industry
|
||||
type: string
|
||||
- description: Filter by verification status
|
||||
in: query
|
||||
name: is_verified
|
||||
type: boolean
|
||||
- description: Filter by compliance status
|
||||
in: query
|
||||
name: is_compliant
|
||||
type: boolean
|
||||
- description: Filter by preferred language
|
||||
in: query
|
||||
name: language
|
||||
type: string
|
||||
- description: Filter by preferred currency
|
||||
in: query
|
||||
name: currency
|
||||
type: string
|
||||
- default: 20
|
||||
description: Number of customers per page (1-100)
|
||||
in: query
|
||||
@@ -2733,131 +2510,6 @@ paths:
|
||||
summary: Update customer information
|
||||
tags:
|
||||
- Admin-Customers
|
||||
/admin/v1/customers/{id}/activate:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Activate a suspended customer account
|
||||
parameters:
|
||||
- description: Customer ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Customer activated successfully
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"400":
|
||||
description: Bad request - Invalid customer ID
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"404":
|
||||
description: Not found - Customer not found
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Activate customer
|
||||
tags:
|
||||
- Admin-Customers
|
||||
/admin/v1/customers/{id}/companies/assign:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Assign one or more companies to a specific customer.
|
||||
parameters:
|
||||
- description: Customer ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
- description: List of company IDs to assign
|
||||
in: body
|
||||
name: companies
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/customer.AssignCompaniesForm'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Companies assigned successfully
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"400":
|
||||
description: Bad request - Invalid input data
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"404":
|
||||
description: Not found - Customer not found
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"422":
|
||||
description: Validation error - Invalid request data
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Assign companies to a customer
|
||||
tags:
|
||||
- Admin-Customers
|
||||
/admin/v1/customers/{id}/companies/remove:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Remove one or more companies from a specific customer.
|
||||
parameters:
|
||||
- description: Customer ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
- description: List of company IDs to remove
|
||||
in: body
|
||||
name: companies
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/customer.RemoveCompaniesForm'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Companies removed successfully
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"400":
|
||||
description: Bad request - Invalid input data
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"404":
|
||||
description: Not found - Customer not found
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"422":
|
||||
description: Validation error - Invalid request data
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Remove companies from a customer
|
||||
tags:
|
||||
- Admin-Customers
|
||||
/admin/v1/customers/{id}/status:
|
||||
patch:
|
||||
consumes:
|
||||
@@ -2874,7 +2526,7 @@ paths:
|
||||
name: status
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/customer.UpdateCustomerStatusForm'
|
||||
$ref: '#/definitions/customer.UpdateStatusForm'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
@@ -2903,294 +2555,6 @@ paths:
|
||||
summary: Update customer status
|
||||
tags:
|
||||
- Admin-Customers
|
||||
/admin/v1/customers/{id}/suspend:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Suspend a customer account with optional reason
|
||||
parameters:
|
||||
- description: Customer ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
- description: Suspension information
|
||||
in: body
|
||||
name: suspension
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/customer.SuspendCustomerForm'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Customer suspended successfully
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"400":
|
||||
description: Bad request - Invalid input data
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"404":
|
||||
description: Not found - Customer not found
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"422":
|
||||
description: Validation error - Invalid request data
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Suspend customer
|
||||
tags:
|
||||
- Admin-Customers
|
||||
/admin/v1/customers/{id}/verification:
|
||||
patch:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Update customer verification and compliance status
|
||||
parameters:
|
||||
- description: Customer ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
- description: Verification status update
|
||||
in: body
|
||||
name: verification
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/customer.UpdateCustomerVerificationForm'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Customer verification updated successfully
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"400":
|
||||
description: Bad request - Invalid input data
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"404":
|
||||
description: Not found - Customer not found
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"422":
|
||||
description: Validation error - Invalid request data
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Update customer verification status
|
||||
tags:
|
||||
- Admin-Customers
|
||||
/admin/v1/customers/{id}/verify:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Mark a customer as verified
|
||||
parameters:
|
||||
- description: Customer ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Customer verified successfully
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"400":
|
||||
description: Bad request - Invalid customer ID
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"404":
|
||||
description: Not found - Customer not found
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Verify customer
|
||||
tags:
|
||||
- Admin-Customers
|
||||
/admin/v1/customers/company/{companyId}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Retrieve all customers associated with a specific company
|
||||
parameters:
|
||||
- description: Company ID
|
||||
in: path
|
||||
name: companyId
|
||||
required: true
|
||||
type: string
|
||||
- description: 'Number of customers to return (default: 10, max: 100)'
|
||||
in: query
|
||||
name: limit
|
||||
type: integer
|
||||
- description: 'Number of customers to skip (default: 0)'
|
||||
in: query
|
||||
name: offset
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Customers retrieved successfully
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/response.APIResponse'
|
||||
- properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/definitions/customer.CustomerResponse'
|
||||
type: array
|
||||
type: object
|
||||
"400":
|
||||
description: Bad request - Invalid company ID
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Get customers by company ID
|
||||
tags:
|
||||
- Admin-Customers
|
||||
/admin/v1/customers/status/{status}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Retrieve customers filtered by their account status (active, inactive,
|
||||
suspended, or pending). Useful for administrative monitoring and management.
|
||||
parameters:
|
||||
- description: Customer status
|
||||
enum:
|
||||
- active
|
||||
- inactive
|
||||
- suspended
|
||||
- pending
|
||||
in: path
|
||||
name: status
|
||||
required: true
|
||||
type: string
|
||||
- default: 20
|
||||
description: Number of customers per page (1-100)
|
||||
in: query
|
||||
maximum: 100
|
||||
minimum: 1
|
||||
name: limit
|
||||
type: integer
|
||||
- default: 0
|
||||
description: Number of customers to skip for pagination
|
||||
in: query
|
||||
minimum: 0
|
||||
name: offset
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Customers retrieved successfully
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/response.APIResponse'
|
||||
- properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/definitions/customer.CustomerResponse'
|
||||
type: array
|
||||
meta:
|
||||
$ref: '#/definitions/response.Meta'
|
||||
type: object
|
||||
"400":
|
||||
description: Bad request - Invalid customer status
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Get customers by status
|
||||
tags:
|
||||
- Admin-Customers
|
||||
/admin/v1/customers/type/{type}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Retrieve customers filtered by their type (individual, company,
|
||||
or government). Useful for administrative reporting and management.
|
||||
parameters:
|
||||
- description: Customer type
|
||||
enum:
|
||||
- individual
|
||||
- company
|
||||
- government
|
||||
in: path
|
||||
name: type
|
||||
required: true
|
||||
type: string
|
||||
- default: 20
|
||||
description: Number of customers per page (1-100)
|
||||
in: query
|
||||
maximum: 100
|
||||
minimum: 1
|
||||
name: limit
|
||||
type: integer
|
||||
- default: 0
|
||||
description: Number of customers to skip for pagination
|
||||
in: query
|
||||
minimum: 0
|
||||
name: offset
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Customers retrieved successfully
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/response.APIResponse'
|
||||
- properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/definitions/customer.CustomerResponse'
|
||||
type: array
|
||||
meta:
|
||||
$ref: '#/definitions/response.Meta'
|
||||
type: object
|
||||
"400":
|
||||
description: Bad request - Invalid customer type
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Get customers by type
|
||||
tags:
|
||||
- Admin-Customers
|
||||
/admin/v1/feedback:
|
||||
get:
|
||||
consumes:
|
||||
|
||||
@@ -71,20 +71,11 @@ func RegisterAdminRoutes(e *echo.Echo, userHandler *user.Handler, companyHandler
|
||||
{
|
||||
customersGP.Use(userHandler.AuthMiddleware())
|
||||
customersGP.POST("", customerHandler.CreateCustomer)
|
||||
customersGP.GET("", customerHandler.ListCustomers)
|
||||
customersGP.GET("", customerHandler.Search)
|
||||
customersGP.GET("/:id", customerHandler.GetCustomerByID)
|
||||
customersGP.PUT("/:id", customerHandler.UpdateCustomer)
|
||||
customersGP.DELETE("/:id", customerHandler.DeleteCustomer)
|
||||
customersGP.PATCH("/:id/status", customerHandler.UpdateCustomerStatus)
|
||||
customersGP.PATCH("/:id/verification", customerHandler.UpdateCustomerVerification)
|
||||
customersGP.POST("/:id/verify", customerHandler.VerifyCustomer)
|
||||
customersGP.POST("/:id/suspend", customerHandler.SuspendCustomer)
|
||||
customersGP.POST("/:id/activate", customerHandler.ActivateCustomer)
|
||||
customersGP.GET("/company/:companyId", customerHandler.GetCustomersByCompanyID)
|
||||
customersGP.GET("/type/:type", customerHandler.GetCustomersByType)
|
||||
customersGP.GET("/status/:status", customerHandler.GetCustomersByStatus)
|
||||
customersGP.POST("/:id/companies/assign", customerHandler.AssignCompaniesToCustomer)
|
||||
customersGP.POST("/:id/companies/remove", customerHandler.RemoveCompaniesFromCustomer)
|
||||
customersGP.PUT("/:id/status", customerHandler.UpdateStatus)
|
||||
}
|
||||
|
||||
// Admin Tenders Routes
|
||||
|
||||
+42
-146
@@ -13,7 +13,6 @@ const (
|
||||
CustomerStatusActive CustomerStatus = "active"
|
||||
CustomerStatusInactive CustomerStatus = "inactive"
|
||||
CustomerStatusSuspended CustomerStatus = "suspended"
|
||||
CustomerStatusPending CustomerStatus = "pending"
|
||||
)
|
||||
|
||||
// CustomerType represents the type of customer
|
||||
@@ -29,52 +28,17 @@ const (
|
||||
// A customer can have an associated company for login purposes
|
||||
type Customer struct {
|
||||
mongo.Model `bson:",inline"`
|
||||
Type CustomerType `bson:"type" json:"type"`
|
||||
Status CustomerStatus `bson:"status" json:"status"`
|
||||
|
||||
// Individual customer fields
|
||||
FirstName *string `bson:"first_name,omitempty" json:"first_name,omitempty"`
|
||||
LastName *string `bson:"last_name,omitempty" json:"last_name,omitempty"`
|
||||
FullName *string `bson:"full_name,omitempty" json:"full_name,omitempty"`
|
||||
Username string `bson:"username" json:"username"` // Username for authentication
|
||||
Email string `bson:"email" json:"email"`
|
||||
Password string `bson:"password" json:"-"` // Hashed password for authentication
|
||||
Phone *string `bson:"phone,omitempty" json:"phone,omitempty"`
|
||||
Mobile *string `bson:"mobile,omitempty" json:"mobile,omitempty"`
|
||||
|
||||
// Company relationships - each customer can be assigned to multiple companies
|
||||
CompanyIDs []string `bson:"company_ids,omitempty" json:"company_ids,omitempty"`
|
||||
|
||||
// Company customer fields
|
||||
RegistrationNumber *string `bson:"registration_number,omitempty" json:"registration_number,omitempty"`
|
||||
TaxID *string `bson:"tax_id,omitempty" json:"tax_id,omitempty"`
|
||||
Industry *string `bson:"industry,omitempty" json:"industry,omitempty"`
|
||||
|
||||
// Address information
|
||||
Address *Address `bson:"address,omitempty" json:"address,omitempty"`
|
||||
|
||||
// Business information
|
||||
BusinessType *string `bson:"business_type,omitempty" json:"business_type,omitempty"`
|
||||
EmployeeCount *int `bson:"employee_count,omitempty" json:"employee_count,omitempty"`
|
||||
AnnualRevenue *float64 `bson:"annual_revenue,omitempty" json:"annual_revenue,omitempty"`
|
||||
FoundedYear *int `bson:"founded_year,omitempty" json:"founded_year,omitempty"`
|
||||
|
||||
// Contact person (for company customers)
|
||||
ContactPerson *ContactPerson `bson:"contact_person,omitempty" json:"contact_person,omitempty"`
|
||||
|
||||
// Verification and compliance
|
||||
IsVerified bool `bson:"is_verified" json:"is_verified"`
|
||||
IsCompliant bool `bson:"is_compliant" json:"is_compliant"`
|
||||
ComplianceNotes *string `bson:"compliance_notes,omitempty" json:"compliance_notes,omitempty"`
|
||||
|
||||
// Settings
|
||||
Language string `bson:"language" json:"language"` // Default: "en"
|
||||
Currency string `bson:"currency" json:"currency"` // Default: "USD"
|
||||
Timezone string `bson:"timezone" json:"timezone"` // Default: "UTC"
|
||||
|
||||
// Audit fields
|
||||
CreatedBy *string `bson:"created_by,omitempty" json:"created_by,omitempty"`
|
||||
UpdatedBy *string `bson:"updated_by,omitempty" json:"updated_by,omitempty"`
|
||||
FullName *string `bson:"full_name"`
|
||||
Username string `bson:"username"`
|
||||
Email string `bson:"email"`
|
||||
Password string `bson:"password"`
|
||||
Status CustomerStatus `bson:"status"`
|
||||
Type CustomerType `bson:"type"`
|
||||
Phone *string `bson:"phone"`
|
||||
CompanyIDs []string `bson:"company_ids"`
|
||||
LastLoginAt *int64 `bson:"last_login_at"`
|
||||
UpdatedAt int64 `bson:"updated_at"`
|
||||
CreatedAt int64 `bson:"created_at"`
|
||||
}
|
||||
|
||||
// SetID sets the customer ID (implements IDSetter interface)
|
||||
@@ -87,49 +51,26 @@ func (c *Customer) GetID() string {
|
||||
return c.ID.Hex()
|
||||
}
|
||||
|
||||
// SetCreatedAt sets the created timestamp (implements Timestampable interface)
|
||||
// SetCreatedAt sets the created timestamp (implements Timestamp interface)
|
||||
func (c *Customer) SetCreatedAt(timestamp int64) {
|
||||
c.CreatedAt = timestamp
|
||||
}
|
||||
|
||||
// SetUpdatedAt sets the updated timestamp (implements Timestampable interface)
|
||||
// SetUpdatedAt sets the updated timestamp (implements Timestamp interface)
|
||||
func (c *Customer) SetUpdatedAt(timestamp int64) {
|
||||
c.UpdatedAt = timestamp
|
||||
}
|
||||
|
||||
// GetCreatedAt returns the created timestamp (implements Timestampable interface)
|
||||
// GetCreatedAt returns the created timestamp (implements Timestamp interface)
|
||||
func (c *Customer) GetCreatedAt() int64 {
|
||||
return c.CreatedAt
|
||||
}
|
||||
|
||||
// GetUpdatedAt returns the updated timestamp (implements Timestampable interface)
|
||||
// GetUpdatedAt returns the updated timestamp (implements Timestamp interface)
|
||||
func (c *Customer) GetUpdatedAt() int64 {
|
||||
return c.UpdatedAt
|
||||
}
|
||||
|
||||
// Address represents a customer's address
|
||||
type Address struct {
|
||||
Street string `bson:"street" json:"street"`
|
||||
City string `bson:"city" json:"city"`
|
||||
State string `bson:"state" json:"state"`
|
||||
PostalCode string `bson:"postal_code" json:"postal_code"`
|
||||
Country string `bson:"country" json:"country"`
|
||||
AddressType string `bson:"address_type" json:"address_type"` // billing, shipping, etc.
|
||||
IsDefault bool `bson:"is_default" json:"is_default"`
|
||||
}
|
||||
|
||||
// ContactPerson represents a contact person for company customers
|
||||
type ContactPerson struct {
|
||||
FirstName string `bson:"first_name" json:"first_name"`
|
||||
LastName string `bson:"last_name" json:"last_name"`
|
||||
FullName string `bson:"full_name" json:"full_name"`
|
||||
Position string `bson:"position" json:"position"`
|
||||
Email string `bson:"email" json:"email"`
|
||||
Phone string `bson:"phone" json:"phone"`
|
||||
Mobile *string `bson:"mobile,omitempty" json:"mobile,omitempty"`
|
||||
IsPrimary bool `bson:"is_primary" json:"is_primary"`
|
||||
}
|
||||
|
||||
// CompanySummary represents company summary data for customer responses
|
||||
type CompanySummary struct {
|
||||
ID string `json:"id"`
|
||||
@@ -138,81 +79,36 @@ type CompanySummary struct {
|
||||
|
||||
// CustomerResponse represents the customer data sent in API responses
|
||||
type CustomerResponse struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
FirstName *string `json:"first_name,omitempty"`
|
||||
LastName *string `json:"last_name,omitempty"`
|
||||
FullName *string `json:"full_name,omitempty"`
|
||||
Email string `json:"email"`
|
||||
Phone *string `json:"phone,omitempty"`
|
||||
Mobile *string `json:"mobile,omitempty"`
|
||||
|
||||
// Company relationships
|
||||
Companies []*CompanySummary `json:"companies,omitempty"`
|
||||
|
||||
// Company customer fields
|
||||
RegistrationNumber *string `json:"registration_number,omitempty"`
|
||||
TaxID *string `json:"tax_id"`
|
||||
Industry *string `json:"industry"`
|
||||
Address *Address `json:"address"`
|
||||
BusinessType *string `json:"business_type"`
|
||||
EmployeeCount *int `json:"employee_count"`
|
||||
AnnualRevenue *float64 `json:"annual_revenue"`
|
||||
FoundedYear *int `json:"founded_year"`
|
||||
ContactPerson *ContactPerson `json:"contact_person"`
|
||||
IsVerified bool `json:"is_verified"`
|
||||
IsCompliant bool `json:"is_compliant"`
|
||||
ComplianceNotes *string `json:"compliance_notes"`
|
||||
Language string `json:"language"`
|
||||
Username string `json:"username"`
|
||||
Currency string `json:"currency"`
|
||||
Timezone string `json:"timezone"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
CreatedBy *string `json:"created_by"`
|
||||
UpdatedBy *string `json:"updated_by"`
|
||||
ID string `bson:"id"`
|
||||
FullName *string `bson:"full_name"`
|
||||
Username string `bson:"username"`
|
||||
Email string `bson:"email"`
|
||||
Password string `bson:"password"`
|
||||
Status string `bson:"status"`
|
||||
Type string `bson:"type"`
|
||||
Phone *string `bson:"phone"`
|
||||
CompanyIDs []string `bson:"company_ids"`
|
||||
UpdatedAt int64 `bson:"updated_at"`
|
||||
CreatedAt int64 `bson:"created_at"`
|
||||
LastLoginAt *int64 `bson:"last_login_at"`
|
||||
Companies []*CompanySummary `json:"companies"`
|
||||
}
|
||||
|
||||
// ToResponse converts Customer to CustomerResponse
|
||||
func (c *Customer) ToResponse() *CustomerResponse {
|
||||
func (c *Customer) ToResponse(companies []*CompanySummary) *CustomerResponse {
|
||||
return &CustomerResponse{
|
||||
ID: c.ID.Hex(),
|
||||
Type: string(c.Type),
|
||||
Status: string(c.Status),
|
||||
FirstName: c.FirstName,
|
||||
LastName: c.LastName,
|
||||
FullName: c.FullName,
|
||||
Username: c.Username,
|
||||
Email: c.Email,
|
||||
Phone: c.Phone,
|
||||
Mobile: c.Mobile,
|
||||
Companies: nil, // Will be populated by service layer
|
||||
RegistrationNumber: c.RegistrationNumber,
|
||||
TaxID: c.TaxID,
|
||||
Industry: c.Industry,
|
||||
Address: c.Address,
|
||||
BusinessType: c.BusinessType,
|
||||
EmployeeCount: c.EmployeeCount,
|
||||
AnnualRevenue: c.AnnualRevenue,
|
||||
FoundedYear: c.FoundedYear,
|
||||
ContactPerson: c.ContactPerson,
|
||||
IsVerified: c.IsVerified,
|
||||
IsCompliant: c.IsCompliant,
|
||||
ComplianceNotes: c.ComplianceNotes,
|
||||
Language: c.Language,
|
||||
Currency: c.Currency,
|
||||
Timezone: c.Timezone,
|
||||
CreatedAt: c.CreatedAt,
|
||||
UpdatedAt: c.UpdatedAt,
|
||||
CreatedBy: c.CreatedBy,
|
||||
UpdatedBy: c.UpdatedBy,
|
||||
ID: c.ID.Hex(),
|
||||
FullName: c.FullName,
|
||||
Username: c.Username,
|
||||
Email: c.Email,
|
||||
Password: c.Password,
|
||||
Type: string(c.Type),
|
||||
Status: string(c.Status),
|
||||
Phone: c.Phone,
|
||||
CompanyIDs: c.CompanyIDs,
|
||||
UpdatedAt: c.UpdatedAt,
|
||||
CreatedAt: c.CreatedAt,
|
||||
LastLoginAt: c.LastLoginAt,
|
||||
Companies: companies,
|
||||
}
|
||||
}
|
||||
|
||||
// ToResponseWithCompanies converts Customer to CustomerResponse with company information
|
||||
func (c *Customer) ToResponseWithCompanies(companies []*CompanySummary) *CustomerResponse {
|
||||
response := c.ToResponse()
|
||||
response.Companies = companies
|
||||
return response
|
||||
}
|
||||
|
||||
+31
-173
@@ -1,184 +1,52 @@
|
||||
package customer
|
||||
|
||||
import "tm/pkg/response"
|
||||
|
||||
// CreateCustomerForm represents the form for creating a new customer
|
||||
type CreateCustomerForm struct {
|
||||
Type string `json:"type" valid:"required,in(individual|company|government)"`
|
||||
|
||||
// Company assignments
|
||||
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"`
|
||||
CompanyIDs []string `json:"company_ids,omitempty" valid:"optional"`
|
||||
|
||||
// Individual customer fields
|
||||
FirstName *string `json:"first_name,omitempty" valid:"optional,length(2|50)"`
|
||||
LastName *string `json:"last_name,omitempty" valid:"optional,length(2|50)"`
|
||||
FullName *string `json:"full_name,omitempty" valid:"optional,length(2|100)"`
|
||||
Username string `json:"username" valid:"required,alphanum,length(3|30)"`
|
||||
Email string `json:"email" valid:"required,email"`
|
||||
Password string `json:"password" valid:"required,length(8|128)"`
|
||||
Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)"`
|
||||
Mobile *string `json:"mobile,omitempty" valid:"optional,length(10|20)"`
|
||||
|
||||
// Company customer fields
|
||||
RegistrationNumber *string `json:"registration_number,omitempty" valid:"optional,length(5|50)"`
|
||||
TaxID *string `json:"tax_id,omitempty" valid:"optional,length(5|50)"`
|
||||
Industry *string `json:"industry,omitempty" valid:"optional,length(2|100)"`
|
||||
|
||||
// Address information
|
||||
Address *AddressForm `json:"address,omitempty"`
|
||||
|
||||
// Business information
|
||||
BusinessType *string `json:"business_type,omitempty" valid:"optional,length(2|100)"`
|
||||
EmployeeCount *int `json:"employee_count,omitempty" valid:"optional,range(1|100000)"`
|
||||
AnnualRevenue *float64 `json:"annual_revenue,omitempty" valid:"optional,range(0|999999999999)"`
|
||||
FoundedYear *int `json:"founded_year,omitempty" valid:"optional,range(1800|2100)"`
|
||||
|
||||
// Contact person (for company customers)
|
||||
ContactPerson *ContactPersonForm `json:"contact_person,omitempty"`
|
||||
|
||||
// Preferences and settings
|
||||
Language *string `json:"language,omitempty" valid:"optional,in(en|ar|fr|es|de|zh|ja|ko)"`
|
||||
Currency *string `json:"currency,omitempty" valid:"optional,in(USD|EUR|GBP|JPY|CAD|AUD|CHF|CNY|INR|BRL)"`
|
||||
Timezone *string `json:"timezone,omitempty" valid:"optional,length(3|50)"`
|
||||
Username string `json:"username" valid:"required,alphanum,length(3|30)" example:"user"`
|
||||
Email string `json:"email" valid:"required,email" example:"user@opplens.com"`
|
||||
Password string `json:"password" valid:"required,length(8|128)" example:"User!1234"`
|
||||
Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)" example:"+1234567890"`
|
||||
}
|
||||
|
||||
// UpdateCustomerForm represents the form for updating a customer
|
||||
type UpdateCustomerForm struct {
|
||||
Type *string `json:"type,omitempty" valid:"optional,in(individual|company|government)"`
|
||||
|
||||
// Company assignments
|
||||
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"`
|
||||
CompanyIDs []string `json:"company_ids,omitempty" valid:"optional"`
|
||||
Username string `json:"username" valid:"required,alphanum,length(3|30)" example:"user"`
|
||||
Email string `json:"email" valid:"required,email" example:"user@opplens.com"`
|
||||
Password string `json:"password" valid:"required,length(8|128)" example:"User!1234"`
|
||||
Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)" example:"+1234567890"`
|
||||
}
|
||||
|
||||
// Individual customer fields
|
||||
FirstName *string `json:"first_name,omitempty" valid:"optional,length(2|50)"`
|
||||
LastName *string `json:"last_name,omitempty" valid:"optional,length(2|50)"`
|
||||
FullName *string `json:"full_name,omitempty" valid:"optional,length(2|100)"`
|
||||
Username *string `json:"username,omitempty" valid:"optional,alphanum,length(3|30)"`
|
||||
Email *string `json:"email,omitempty" valid:"optional,email"`
|
||||
Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)"`
|
||||
Mobile *string `json:"mobile,omitempty" valid:"optional,length(10|20)"`
|
||||
|
||||
// Company customer fields
|
||||
CompanyName *string `json:"company_name,omitempty" valid:"optional,length(2|200)"`
|
||||
RegistrationNumber *string `json:"registration_number,omitempty" valid:"optional,length(5|50)"`
|
||||
TaxID *string `json:"tax_id,omitempty" valid:"optional,length(5|50)"`
|
||||
Industry *string `json:"industry,omitempty" valid:"optional,length(2|100)"`
|
||||
|
||||
// Address information
|
||||
Address *AddressForm `json:"address,omitempty"`
|
||||
|
||||
// Business information
|
||||
BusinessType *string `json:"business_type,omitempty" valid:"optional,length(2|100)"`
|
||||
EmployeeCount *int `json:"employee_count,omitempty" valid:"optional,range(1|100000)"`
|
||||
AnnualRevenue *float64 `json:"annual_revenue,omitempty" valid:"optional,range(0|999999999999)"`
|
||||
FoundedYear *int `json:"founded_year,omitempty" valid:"optional,range(1800|2100)"`
|
||||
|
||||
// Contact person (for company customers)
|
||||
ContactPerson *ContactPersonForm `json:"contact_person,omitempty"`
|
||||
|
||||
// Preferences and settings
|
||||
Language *string `json:"language,omitempty" valid:"optional,in(en|ar|fr|es|de|zh|ja|ko)"`
|
||||
Currency *string `json:"currency,omitempty" valid:"optional,in(USD|EUR|GBP|JPY|CAD|AUD|CHF|CNY|INR|BRL)"`
|
||||
Timezone *string `json:"timezone,omitempty" valid:"optional,length(3|50)"`
|
||||
// CustomerStatusForm represents the form for suspending a customer
|
||||
type UpdateStatusForm struct {
|
||||
Status string `json:"status" valid:"required,in(active|inactive|suspended)" example:"active"`
|
||||
Reason string `json:"reason" valid:"required,length(10|500)" example:"Customer is active"`
|
||||
}
|
||||
|
||||
// ListCustomersForm represents the form for listing customers with filters
|
||||
type ListCustomersForm struct {
|
||||
Search *string `query:"search" valid:"optional"`
|
||||
Type *string `query:"type" valid:"optional,in(individual|company|government)"`
|
||||
Status *string `query:"status" valid:"optional,in(active|inactive|suspended|pending)"`
|
||||
CompanyID *string `query:"company_id" valid:"optional,uuid"`
|
||||
Industry *string `query:"industry" valid:"optional"`
|
||||
IsVerified *bool `query:"is_verified" valid:"optional"`
|
||||
IsCompliant *bool `query:"is_compliant" valid:"optional"`
|
||||
Language *string `query:"language" valid:"optional"`
|
||||
Currency *string `query:"currency" valid:"optional"`
|
||||
Limit *int `query:"limit" valid:"optional,range(1|100)"`
|
||||
Offset *int `query:"offset" valid:"optional,min(0)"`
|
||||
SortBy *string `query:"sort_by" valid:"optional,in(email|company_name|created_at|updated_at|status)"`
|
||||
SortOrder *string `query:"sort_order" valid:"optional,in(asc|desc)"`
|
||||
}
|
||||
|
||||
// UpdateCustomerStatusForm represents the form for updating customer status
|
||||
type UpdateCustomerStatusForm struct {
|
||||
Status string `json:"status" valid:"required,in(active|inactive|suspended|pending)"`
|
||||
}
|
||||
|
||||
// UpdateCustomerVerificationForm represents the form for updating customer verification status
|
||||
type UpdateCustomerVerificationForm struct {
|
||||
IsVerified bool `json:"is_verified"`
|
||||
IsCompliant bool `json:"is_compliant"`
|
||||
ComplianceNotes *string `json:"compliance_notes,omitempty" valid:"optional,length(0|1000)"`
|
||||
}
|
||||
|
||||
// SuspendCustomerForm represents the form for suspending a customer
|
||||
type SuspendCustomerForm struct {
|
||||
Reason string `json:"reason" valid:"required,length(10|500)"`
|
||||
}
|
||||
|
||||
// AddressForm represents the form for customer address
|
||||
type AddressForm struct {
|
||||
Street string `json:"street" valid:"required,length(5|200)"`
|
||||
City string `json:"city" valid:"required,length(2|100)"`
|
||||
State string `json:"state" valid:"required,length(2|100)"`
|
||||
PostalCode string `json:"postal_code" valid:"required,length(3|20)"`
|
||||
Country string `json:"country" valid:"required,length(2|100)"`
|
||||
AddressType string `json:"address_type" valid:"required,in(billing|shipping|both)"`
|
||||
IsDefault bool `json:"is_default"`
|
||||
}
|
||||
|
||||
// ContactPersonForm represents the form for contact person
|
||||
type ContactPersonForm struct {
|
||||
FirstName string `json:"first_name" valid:"required,length(2|50)"`
|
||||
LastName string `json:"last_name" valid:"required,length(2|50)"`
|
||||
FullName string `json:"full_name" valid:"required,length(2|100)"`
|
||||
Position string `json:"position" valid:"required,length(2|100)"`
|
||||
Email string `json:"email" valid:"required,email"`
|
||||
Phone string `json:"phone" valid:"required,length(10|20)"`
|
||||
Mobile *string `json:"mobile,omitempty" valid:"optional,length(10|20)"`
|
||||
IsPrimary bool `json:"is_primary"`
|
||||
type SearchCustomersForm struct {
|
||||
Search *string `query:"search" valid:"optional"`
|
||||
Type *string `query:"type" valid:"optional,in(individual|company|government)"`
|
||||
Status *string `query:"status" valid:"optional,in(active|inactive|suspended)"`
|
||||
CompanyID *string `query:"company_id" valid:"optional"`
|
||||
}
|
||||
|
||||
// CustomerListResponse represents the response for listing customers
|
||||
type CustomerListResponse struct {
|
||||
Customers []*CustomerResponse `json:"customers"`
|
||||
Total int64 `json:"total"`
|
||||
Limit int `json:"limit"`
|
||||
Offset int `json:"offset"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
Customers []*CustomerResponse `json:"customers"`
|
||||
Meta *response.Meta `json:"meta"`
|
||||
}
|
||||
|
||||
// Mobile-specific forms (simplified for mobile app)
|
||||
type CreateCustomerMobileForm struct {
|
||||
Type string `json:"type" valid:"required,in(individual|company)"`
|
||||
FirstName *string `json:"first_name,omitempty" valid:"optional,length(2|50)"`
|
||||
LastName *string `json:"last_name,omitempty" valid:"optional,length(2|50)"`
|
||||
FullName *string `json:"full_name,omitempty" valid:"optional,length(2|100)"`
|
||||
Username string `json:"username" valid:"required,alphanum,length(3|30)"`
|
||||
Email string `json:"email" valid:"required,email"`
|
||||
Password string `json:"password" valid:"required,length(8|128)"`
|
||||
Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)"`
|
||||
Mobile *string `json:"mobile,omitempty" valid:"optional,length(10|20)"`
|
||||
|
||||
// Company assignments
|
||||
CompanyIDs []string `json:"company_ids,omitempty" valid:"optional"`
|
||||
Industry *string `json:"industry,omitempty" valid:"optional,length(2|100)"`
|
||||
}
|
||||
|
||||
type UpdateCustomerMobileForm struct {
|
||||
FirstName *string `json:"first_name,omitempty" valid:"optional,length(2|50)"`
|
||||
LastName *string `json:"last_name,omitempty" valid:"optional,length(2|50)"`
|
||||
FullName *string `json:"full_name,omitempty" valid:"optional,length(2|100)"`
|
||||
Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)"`
|
||||
Mobile *string `json:"mobile,omitempty" valid:"optional,length(10|20)"`
|
||||
|
||||
// Company assignments
|
||||
CompanyIDs []string `json:"company_ids,omitempty" valid:"optional"`
|
||||
Industry *string `json:"industry,omitempty" valid:"optional,length(2|100)"`
|
||||
}
|
||||
|
||||
// Customer Authentication DTOs
|
||||
type LoginForm struct {
|
||||
Username string `json:"username" valid:"required" example:"nakhostin"`
|
||||
Password string `json:"password" valid:"required" example:"Nima.1998"`
|
||||
Username string `json:"username" valid:"required" example:"app"`
|
||||
Password string `json:"password" valid:"required" example:"App!1234"`
|
||||
}
|
||||
|
||||
type RefreshTokenForm struct {
|
||||
@@ -191,20 +59,11 @@ type ChangePasswordForm struct {
|
||||
}
|
||||
|
||||
type UpdateProfileForm struct {
|
||||
FirstName *string `json:"first_name,omitempty" valid:"optional,length(2|50)"`
|
||||
LastName *string `json:"last_name,omitempty" valid:"optional,length(2|50)"`
|
||||
FullName *string `json:"full_name,omitempty" valid:"optional,length(2|100)"`
|
||||
Username *string `json:"username,omitempty" valid:"optional,alphanum,length(3|30)"`
|
||||
Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)"`
|
||||
Mobile *string `json:"mobile,omitempty" valid:"optional,length(10|20)"`
|
||||
CompanyName *string `json:"company_name,omitempty" valid:"optional,length(2|200)"`
|
||||
Industry *string `json:"industry,omitempty" valid:"optional,length(2|100)"`
|
||||
Language *string `json:"language,omitempty" valid:"optional,in(en|ar|fr|es|de|zh|ja|ko)"`
|
||||
Currency *string `json:"currency,omitempty" valid:"optional,in(USD|EUR|GBP|JPY|CAD|AUD|CHF|CNY|INR|BRL)"`
|
||||
Timezone *string `json:"timezone,omitempty" valid:"optional,length(3|50)"`
|
||||
FullName *string `json:"full_name,omitempty" valid:"optional,length(2|100)"`
|
||||
Username *string `json:"username,omitempty" valid:"optional,alphanum,length(3|30)"`
|
||||
Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)"`
|
||||
}
|
||||
|
||||
// Customer Authentication Response DTOs
|
||||
type AuthResponse struct {
|
||||
Customer *CustomerResponse `json:"customer"`
|
||||
AccessToken string `json:"access_token"`
|
||||
@@ -212,7 +71,6 @@ type AuthResponse struct {
|
||||
ExpiresAt int64 `json:"expires_at"`
|
||||
}
|
||||
|
||||
// Company assignment forms
|
||||
type AssignCompaniesForm struct {
|
||||
CompanyIDs []string `json:"company_ids" valid:"required"`
|
||||
}
|
||||
|
||||
+23
-445
@@ -1,7 +1,6 @@
|
||||
package customer
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"tm/internal/user"
|
||||
"tm/pkg/authorization"
|
||||
"tm/pkg/logger"
|
||||
@@ -48,13 +47,7 @@ func (h *Handler) CreateCustomer(c echo.Context) error {
|
||||
return response.ValidationError(c, "Invalid request data", err.Error())
|
||||
}
|
||||
|
||||
// Get user ID from JWT token context
|
||||
createdBy, err := user.GetUserIDFromContext(c)
|
||||
if err != nil {
|
||||
return response.Unauthorized(c, "User not authenticated")
|
||||
}
|
||||
|
||||
customer, err := h.service.CreateCustomer(c.Request().Context(), form, &createdBy)
|
||||
customer, err := h.service.Register(c.Request().Context(), form)
|
||||
if err != nil {
|
||||
if err.Error() == "customer with this email already exists" ||
|
||||
err.Error() == "company with this name already exists" ||
|
||||
@@ -65,7 +58,7 @@ func (h *Handler) CreateCustomer(c echo.Context) error {
|
||||
return response.InternalServerError(c, "Failed to create customer")
|
||||
}
|
||||
|
||||
return response.Created(c, customer.ToResponse(), "Customer created successfully")
|
||||
return response.Created(c, customer, "Customer created successfully")
|
||||
}
|
||||
|
||||
// GetCustomerByID retrieves a customer by ID (Web Panel)
|
||||
@@ -82,9 +75,9 @@ func (h *Handler) CreateCustomer(c echo.Context) error {
|
||||
// @Security BearerAuth
|
||||
// @Router /admin/v1/customers/{id} [get]
|
||||
func (h *Handler) GetCustomerByID(c echo.Context) error {
|
||||
idStr := c.Param("id")
|
||||
id := c.Param("id")
|
||||
|
||||
customer, err := h.service.GetCustomerByIDWithCompanies(c.Request().Context(), idStr)
|
||||
customer, err := h.service.GetByID(c.Request().Context(), id)
|
||||
if err != nil {
|
||||
if err.Error() == "customer not found" {
|
||||
return response.NotFound(c, "Customer not found")
|
||||
@@ -112,19 +105,15 @@ func (h *Handler) GetCustomerByID(c echo.Context) error {
|
||||
// @Security BearerAuth
|
||||
// @Router /admin/v1/customers/{id} [put]
|
||||
func (h *Handler) UpdateCustomer(c echo.Context) error {
|
||||
idStr := c.Param("id")
|
||||
id := c.Param("id")
|
||||
form, err := response.Parse[UpdateCustomerForm](c)
|
||||
if err != nil {
|
||||
return response.ValidationError(c, "Invalid request data", err.Error())
|
||||
}
|
||||
|
||||
// Get user ID from JWT token context
|
||||
updatedBy, err := user.GetUserIDFromContext(c)
|
||||
if err != nil {
|
||||
return response.Unauthorized(c, "User not authenticated")
|
||||
}
|
||||
|
||||
customer, err := h.service.UpdateCustomer(c.Request().Context(), idStr, form, &updatedBy)
|
||||
customer, err := h.service.Update(c.Request().Context(), id, form)
|
||||
if err != nil {
|
||||
if err.Error() == "customer not found" {
|
||||
return response.NotFound(c, "Customer not found")
|
||||
@@ -138,7 +127,7 @@ func (h *Handler) UpdateCustomer(c echo.Context) error {
|
||||
return response.InternalServerError(c, "Failed to update customer")
|
||||
}
|
||||
|
||||
return response.Success(c, customer.ToResponse(), "Customer updated successfully")
|
||||
return response.Success(c, customer, "Customer updated successfully")
|
||||
}
|
||||
|
||||
// DeleteCustomer deletes a customer (Web Panel)
|
||||
@@ -155,15 +144,9 @@ func (h *Handler) UpdateCustomer(c echo.Context) error {
|
||||
// @Security BearerAuth
|
||||
// @Router /admin/v1/customers/{id} [delete]
|
||||
func (h *Handler) DeleteCustomer(c echo.Context) error {
|
||||
idStr := c.Param("id")
|
||||
id := c.Param("id")
|
||||
|
||||
// Get user ID from JWT token context
|
||||
deletedBy, err := user.GetUserIDFromContext(c)
|
||||
if err != nil {
|
||||
return response.Unauthorized(c, "User not authenticated")
|
||||
}
|
||||
|
||||
err = h.service.DeleteCustomer(c.Request().Context(), idStr, &deletedBy)
|
||||
err := h.service.Delete(c.Request().Context(), id)
|
||||
if err != nil {
|
||||
if err.Error() == "customer not found" {
|
||||
return response.NotFound(c, "Customer not found")
|
||||
@@ -186,11 +169,6 @@ func (h *Handler) DeleteCustomer(c echo.Context) error {
|
||||
// @Param type query string false "Filter by customer type" Enums(individual, company, government)
|
||||
// @Param status query string false "Filter by customer status" Enums(active, inactive, suspended, pending)
|
||||
// @Param company_id query string false "Filter by company ID" format(uuid)
|
||||
// @Param industry query string false "Filter by industry"
|
||||
// @Param is_verified query boolean false "Filter by verification status"
|
||||
// @Param is_compliant query boolean false "Filter by compliance status"
|
||||
// @Param language query string false "Filter by preferred language"
|
||||
// @Param currency query string false "Filter by preferred currency"
|
||||
// @Param limit query integer false "Number of customers per page (1-100)" minimum(1) maximum(100) default(20)
|
||||
// @Param offset query integer false "Number of customers to skip for pagination" minimum(0) default(0)
|
||||
// @Param sort_by query string false "Field to sort by" Enums(email, company_name, created_at, updated_at, status) default(created_at)
|
||||
@@ -201,13 +179,15 @@ func (h *Handler) DeleteCustomer(c echo.Context) error {
|
||||
// @Failure 500 {object} response.APIResponse "Internal server error"
|
||||
// @Security BearerAuth
|
||||
// @Router /admin/v1/customers [get]
|
||||
func (h *Handler) ListCustomers(c echo.Context) error {
|
||||
form, err := response.Parse[ListCustomersForm](c)
|
||||
func (h *Handler) Search(c echo.Context) error {
|
||||
form, err := response.Parse[SearchCustomersForm](c)
|
||||
if err != nil {
|
||||
return response.ValidationError(c, "Invalid request data", err.Error())
|
||||
}
|
||||
|
||||
result, err := h.service.ListCustomers(c.Request().Context(), form)
|
||||
pagination := response.NewPagination(c)
|
||||
|
||||
result, err := h.service.Search(c.Request().Context(), form, pagination)
|
||||
if err != nil {
|
||||
return response.InternalServerError(c, "Failed to list customers")
|
||||
}
|
||||
@@ -222,7 +202,7 @@ func (h *Handler) ListCustomers(c echo.Context) error {
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Customer ID"
|
||||
// @Param status body UpdateCustomerStatusForm true "New customer status"
|
||||
// @Param status body UpdateStatusForm true "New customer status"
|
||||
// @Success 200 {object} response.APIResponse "Customer status updated successfully"
|
||||
// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
|
||||
// @Failure 404 {object} response.APIResponse "Not found - Customer not found"
|
||||
@@ -230,24 +210,16 @@ func (h *Handler) ListCustomers(c echo.Context) error {
|
||||
// @Failure 500 {object} response.APIResponse "Internal server error"
|
||||
// @Security BearerAuth
|
||||
// @Router /admin/v1/customers/{id}/status [patch]
|
||||
func (h *Handler) UpdateCustomerStatus(c echo.Context) error {
|
||||
idStr := c.Param("id")
|
||||
form, err := response.Parse[UpdateCustomerStatusForm](c)
|
||||
func (h *Handler) UpdateStatus(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
|
||||
form, err := response.Parse[UpdateStatusForm](c)
|
||||
if err != nil {
|
||||
return response.ValidationError(c, "Invalid request data", err.Error())
|
||||
}
|
||||
|
||||
// Get user ID from JWT token context
|
||||
updatedBy, err := user.GetUserIDFromContext(c)
|
||||
err = h.service.UpdateStatus(c.Request().Context(), id, form)
|
||||
if err != nil {
|
||||
return response.Unauthorized(c, "User not authenticated")
|
||||
}
|
||||
|
||||
err = h.service.UpdateCustomerStatus(c.Request().Context(), idStr, form, &updatedBy)
|
||||
if err != nil {
|
||||
if err.Error() == "customer not found" {
|
||||
return response.NotFound(c, "Customer not found")
|
||||
}
|
||||
return response.InternalServerError(c, "Failed to update customer status")
|
||||
}
|
||||
|
||||
@@ -256,403 +228,9 @@ func (h *Handler) UpdateCustomerStatus(c echo.Context) error {
|
||||
}, "Customer status updated successfully")
|
||||
}
|
||||
|
||||
// UpdateCustomerVerification updates customer verification status (Web Panel)
|
||||
// @Summary Update customer verification status
|
||||
// @Description Update customer verification and compliance status
|
||||
// @Tags Admin-Customers
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Customer ID"
|
||||
// @Param verification body UpdateCustomerVerificationForm true "Verification status update"
|
||||
// @Success 200 {object} response.APIResponse "Customer verification updated successfully"
|
||||
// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
|
||||
// @Failure 404 {object} response.APIResponse "Not found - Customer not found"
|
||||
// @Failure 422 {object} response.APIResponse "Validation error - Invalid request data"
|
||||
// @Failure 500 {object} response.APIResponse "Internal server error"
|
||||
// @Security BearerAuth
|
||||
// @Router /admin/v1/customers/{id}/verification [patch]
|
||||
func (h *Handler) UpdateCustomerVerification(c echo.Context) error {
|
||||
idStr := c.Param("id")
|
||||
form, err := response.Parse[UpdateCustomerVerificationForm](c)
|
||||
if err != nil {
|
||||
return response.ValidationError(c, "Invalid request data", err.Error())
|
||||
}
|
||||
|
||||
// Get user ID from JWT token context
|
||||
updatedBy, err := user.GetUserIDFromContext(c)
|
||||
if err != nil {
|
||||
return response.Unauthorized(c, "User not authenticated")
|
||||
}
|
||||
|
||||
err = h.service.UpdateCustomerVerification(c.Request().Context(), idStr, form, &updatedBy)
|
||||
if err != nil {
|
||||
if err.Error() == "customer not found" {
|
||||
return response.NotFound(c, "Customer not found")
|
||||
}
|
||||
return response.InternalServerError(c, "Failed to update customer verification")
|
||||
}
|
||||
|
||||
return response.Success(c, map[string]interface{}{
|
||||
"message": "Customer verification updated successfully",
|
||||
}, "Customer verification updated successfully")
|
||||
}
|
||||
|
||||
// VerifyCustomer verifies a customer (Web Panel)
|
||||
// @Summary Verify customer
|
||||
// @Description Mark a customer as verified
|
||||
// @Tags Admin-Customers
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Customer ID"
|
||||
// @Success 200 {object} response.APIResponse "Customer verified successfully"
|
||||
// @Failure 400 {object} response.APIResponse "Bad request - Invalid customer ID"
|
||||
// @Failure 404 {object} response.APIResponse "Not found - Customer not found"
|
||||
// @Failure 500 {object} response.APIResponse "Internal server error"
|
||||
// @Security BearerAuth
|
||||
// @Router /admin/v1/customers/{id}/verify [post]
|
||||
func (h *Handler) VerifyCustomer(c echo.Context) error {
|
||||
idStr := c.Param("id")
|
||||
|
||||
// Get user ID from JWT token context
|
||||
verifiedBy, err := user.GetUserIDFromContext(c)
|
||||
if err != nil {
|
||||
return response.Unauthorized(c, "User not authenticated")
|
||||
}
|
||||
|
||||
err = h.service.VerifyCustomer(c.Request().Context(), idStr, &verifiedBy)
|
||||
if err != nil {
|
||||
if err.Error() == "customer not found" {
|
||||
return response.NotFound(c, "Customer not found")
|
||||
}
|
||||
return response.InternalServerError(c, "Failed to verify customer")
|
||||
}
|
||||
|
||||
return response.Success(c, map[string]interface{}{
|
||||
"message": "Customer verified successfully",
|
||||
}, "Customer verified successfully")
|
||||
}
|
||||
|
||||
// SuspendCustomer suspends a customer (Web Panel)
|
||||
// @Summary Suspend customer
|
||||
// @Description Suspend a customer account with optional reason
|
||||
// @Tags Admin-Customers
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Customer ID"
|
||||
// @Param suspension body SuspendCustomerForm true "Suspension information"
|
||||
// @Success 200 {object} response.APIResponse "Customer suspended successfully"
|
||||
// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
|
||||
// @Failure 404 {object} response.APIResponse "Not found - Customer not found"
|
||||
// @Failure 422 {object} response.APIResponse "Validation error - Invalid request data"
|
||||
// @Failure 500 {object} response.APIResponse "Internal server error"
|
||||
// @Security BearerAuth
|
||||
// @Router /admin/v1/customers/{id}/suspend [post]
|
||||
func (h *Handler) SuspendCustomer(c echo.Context) error {
|
||||
idStr := c.Param("id")
|
||||
form, err := response.Parse[SuspendCustomerForm](c)
|
||||
if err != nil {
|
||||
return response.ValidationError(c, "Invalid request data", err.Error())
|
||||
}
|
||||
|
||||
// Get user ID from JWT token context
|
||||
suspendedBy, err := user.GetUserIDFromContext(c)
|
||||
if err != nil {
|
||||
return response.Unauthorized(c, "User not authenticated")
|
||||
}
|
||||
|
||||
err = h.service.SuspendCustomer(c.Request().Context(), idStr, form.Reason, &suspendedBy)
|
||||
if err != nil {
|
||||
if err.Error() == "customer not found" {
|
||||
return response.NotFound(c, "Customer not found")
|
||||
}
|
||||
return response.InternalServerError(c, "Failed to suspend customer")
|
||||
}
|
||||
|
||||
return response.Success(c, map[string]interface{}{
|
||||
"message": "Customer suspended successfully",
|
||||
}, "Customer suspended successfully")
|
||||
}
|
||||
|
||||
// ActivateCustomer activates a customer (Web Panel)
|
||||
// @Summary Activate customer
|
||||
// @Description Activate a suspended customer account
|
||||
// @Tags Admin-Customers
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Customer ID"
|
||||
// @Success 200 {object} response.APIResponse "Customer activated successfully"
|
||||
// @Failure 400 {object} response.APIResponse "Bad request - Invalid customer ID"
|
||||
// @Failure 404 {object} response.APIResponse "Not found - Customer not found"
|
||||
// @Failure 500 {object} response.APIResponse "Internal server error"
|
||||
// @Security BearerAuth
|
||||
// @Router /admin/v1/customers/{id}/activate [post]
|
||||
func (h *Handler) ActivateCustomer(c echo.Context) error {
|
||||
idStr := c.Param("id")
|
||||
|
||||
// Get user ID from JWT token context
|
||||
activatedBy, err := user.GetUserIDFromContext(c)
|
||||
if err != nil {
|
||||
return response.Unauthorized(c, "User not authenticated")
|
||||
}
|
||||
|
||||
err = h.service.ActivateCustomer(c.Request().Context(), idStr, &activatedBy)
|
||||
if err != nil {
|
||||
if err.Error() == "customer not found" {
|
||||
return response.NotFound(c, "Customer not found")
|
||||
}
|
||||
return response.InternalServerError(c, "Failed to activate customer")
|
||||
}
|
||||
|
||||
return response.Success(c, map[string]interface{}{
|
||||
"message": "Customer activated successfully",
|
||||
}, "Customer activated successfully")
|
||||
}
|
||||
|
||||
// GetCustomersByCompanyID retrieves customers by company ID (Web Panel)
|
||||
// @Summary Get customers by company ID
|
||||
// @Description Retrieve all customers associated with a specific company
|
||||
// @Tags Admin-Customers
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param companyId path string true "Company ID"
|
||||
// @Param limit query int false "Number of customers to return (default: 10, max: 100)"
|
||||
// @Param offset query int false "Number of customers to skip (default: 0)"
|
||||
// @Success 200 {object} response.APIResponse{data=[]CustomerResponse} "Customers retrieved successfully"
|
||||
// @Failure 400 {object} response.APIResponse "Bad request - Invalid company ID"
|
||||
// @Failure 500 {object} response.APIResponse "Internal server error"
|
||||
// @Security BearerAuth
|
||||
// @Router /admin/v1/customers/company/{companyId} [get]
|
||||
func (h *Handler) GetCustomersByCompanyID(c echo.Context) error {
|
||||
companyIDStr := c.Param("companyId")
|
||||
|
||||
// Parse pagination parameters
|
||||
limit := 10 // Default limit
|
||||
offset := 0 // Default offset
|
||||
|
||||
if limitStr := c.QueryParam("limit"); limitStr != "" {
|
||||
if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 && parsedLimit <= 100 {
|
||||
limit = parsedLimit
|
||||
}
|
||||
}
|
||||
|
||||
if offsetStr := c.QueryParam("offset"); offsetStr != "" {
|
||||
if parsedOffset, err := strconv.Atoi(offsetStr); err == nil && parsedOffset >= 0 {
|
||||
offset = parsedOffset
|
||||
}
|
||||
}
|
||||
|
||||
customers, total, err := h.service.GetCustomersByCompanyID(c.Request().Context(), companyIDStr, limit, offset)
|
||||
if err != nil {
|
||||
return response.InternalServerError(c, "Failed to get customers by company")
|
||||
}
|
||||
|
||||
// Convert to responses
|
||||
var customerResponses []*CustomerResponse
|
||||
for _, customer := range customers {
|
||||
customerResponses = append(customerResponses, customer.ToResponse())
|
||||
}
|
||||
|
||||
return response.Success(c, map[string]interface{}{
|
||||
"customers": customerResponses,
|
||||
"total": total,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
}, "Customers retrieved successfully")
|
||||
}
|
||||
|
||||
// GetCustomersByType retrieves customers by type
|
||||
// @Summary Get customers by type
|
||||
// @Description Retrieve customers filtered by their type (individual, company, or government). Useful for administrative reporting and management.
|
||||
// @Tags Admin-Customers
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param type path string true "Customer type" Enums(individual, company, government)
|
||||
// @Param limit query integer false "Number of customers per page (1-100)" minimum(1) maximum(100) default(20)
|
||||
// @Param offset query integer false "Number of customers to skip for pagination" minimum(0) default(0)
|
||||
// @Success 200 {object} response.APIResponse{data=[]CustomerResponse,meta=response.Meta} "Customers retrieved successfully"
|
||||
// @Failure 400 {object} response.APIResponse "Bad request - Invalid customer type"
|
||||
// @Failure 500 {object} response.APIResponse "Internal server error"
|
||||
// @Security BearerAuth
|
||||
// @Router /admin/v1/customers/type/{type} [get]
|
||||
func (h *Handler) GetCustomersByType(c echo.Context) error {
|
||||
customerTypeStr := c.Param("type")
|
||||
customerType := CustomerType(customerTypeStr)
|
||||
|
||||
// Validate customer type
|
||||
if customerType != CustomerTypeIndividual && customerType != CustomerTypeCompany && customerType != CustomerTypeGovernment {
|
||||
return response.BadRequest(c, "Invalid customer type", "Must be individual, company, or government")
|
||||
}
|
||||
|
||||
limit := 20
|
||||
if limitStr := c.QueryParam("limit"); limitStr != "" {
|
||||
if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 && parsedLimit <= 100 {
|
||||
limit = parsedLimit
|
||||
}
|
||||
}
|
||||
|
||||
offset := 0
|
||||
if offsetStr := c.QueryParam("offset"); offsetStr != "" {
|
||||
if parsedOffset, err := strconv.Atoi(offsetStr); err == nil && parsedOffset >= 0 {
|
||||
offset = parsedOffset
|
||||
}
|
||||
}
|
||||
|
||||
customers, total, err := h.service.GetCustomersByType(c.Request().Context(), customerType, limit, offset)
|
||||
if err != nil {
|
||||
return response.InternalServerError(c, "Failed to retrieve customers by type")
|
||||
}
|
||||
|
||||
var customerResponses []*CustomerResponse
|
||||
for _, customer := range customers {
|
||||
customerResponses = append(customerResponses, customer.ToResponse())
|
||||
}
|
||||
|
||||
meta := &response.Meta{
|
||||
Total: int(total),
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
}
|
||||
|
||||
return response.SuccessWithMeta(c, customerResponses, meta, "Customers retrieved successfully")
|
||||
}
|
||||
|
||||
// GetCustomersByStatus retrieves customers by status
|
||||
// @Summary Get customers by status
|
||||
// @Description Retrieve customers filtered by their account status (active, inactive, suspended, or pending). Useful for administrative monitoring and management.
|
||||
// @Tags Admin-Customers
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param status path string true "Customer status" Enums(active, inactive, suspended, pending)
|
||||
// @Param limit query integer false "Number of customers per page (1-100)" minimum(1) maximum(100) default(20)
|
||||
// @Param offset query integer false "Number of customers to skip for pagination" minimum(0) default(0)
|
||||
// @Success 200 {object} response.APIResponse{data=[]CustomerResponse,meta=response.Meta} "Customers retrieved successfully"
|
||||
// @Failure 400 {object} response.APIResponse "Bad request - Invalid customer status"
|
||||
// @Failure 500 {object} response.APIResponse "Internal server error"
|
||||
// @Security BearerAuth
|
||||
// @Router /admin/v1/customers/status/{status} [get]
|
||||
func (h *Handler) GetCustomersByStatus(c echo.Context) error {
|
||||
statusStr := c.Param("status")
|
||||
status := CustomerStatus(statusStr)
|
||||
|
||||
// Validate customer status
|
||||
if status != CustomerStatusActive && status != CustomerStatusInactive && status != CustomerStatusSuspended && status != CustomerStatusPending {
|
||||
return response.BadRequest(c, "Invalid customer status", "Must be active, inactive, suspended, or pending")
|
||||
}
|
||||
|
||||
limit := 20
|
||||
if limitStr := c.QueryParam("limit"); limitStr != "" {
|
||||
if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 && parsedLimit <= 100 {
|
||||
limit = parsedLimit
|
||||
}
|
||||
}
|
||||
|
||||
offset := 0
|
||||
if offsetStr := c.QueryParam("offset"); offsetStr != "" {
|
||||
if parsedOffset, err := strconv.Atoi(offsetStr); err == nil && parsedOffset >= 0 {
|
||||
offset = parsedOffset
|
||||
}
|
||||
}
|
||||
|
||||
customers, total, err := h.service.GetCustomersByStatus(c.Request().Context(), status, limit, offset)
|
||||
if err != nil {
|
||||
return response.InternalServerError(c, "Failed to retrieve customers by status")
|
||||
}
|
||||
|
||||
var customerResponses []*CustomerResponse
|
||||
for _, customer := range customers {
|
||||
customerResponses = append(customerResponses, customer.ToResponse())
|
||||
}
|
||||
|
||||
meta := &response.Meta{
|
||||
Total: int(total),
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
}
|
||||
|
||||
return response.SuccessWithMeta(c, customerResponses, meta, "Customers retrieved successfully")
|
||||
}
|
||||
|
||||
// AssignCompaniesToCustomer assigns companies to a customer (Web Panel)
|
||||
// @Summary Assign companies to a customer
|
||||
// @Description Assign one or more companies to a specific customer.
|
||||
// @Tags Admin-Customers
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Customer ID"
|
||||
// @Param companies body AssignCompaniesForm true "List of company IDs to assign"
|
||||
// @Success 200 {object} response.APIResponse "Companies assigned successfully"
|
||||
// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
|
||||
// @Failure 404 {object} response.APIResponse "Not found - Customer not found"
|
||||
// @Failure 422 {object} response.APIResponse "Validation error - Invalid request data"
|
||||
// @Failure 500 {object} response.APIResponse "Internal server error"
|
||||
// @Security BearerAuth
|
||||
// @Router /admin/v1/customers/{id}/companies/assign [post]
|
||||
func (h *Handler) AssignCompaniesToCustomer(c echo.Context) error {
|
||||
idStr := c.Param("id")
|
||||
form, err := response.Parse[AssignCompaniesForm](c)
|
||||
if err != nil {
|
||||
return response.ValidationError(c, "Invalid request data", err.Error())
|
||||
}
|
||||
|
||||
// Get user ID from JWT token context
|
||||
assignedBy, err := user.GetUserIDFromContext(c)
|
||||
if err != nil {
|
||||
return response.Unauthorized(c, "User not authenticated")
|
||||
}
|
||||
|
||||
err = h.service.AssignCompaniesToCustomer(c.Request().Context(), idStr, form.CompanyIDs, &assignedBy)
|
||||
if err != nil {
|
||||
if err.Error() == "customer not found" {
|
||||
return response.NotFound(c, "Customer not found")
|
||||
}
|
||||
return response.InternalServerError(c, "Failed to assign companies to customer")
|
||||
}
|
||||
|
||||
return response.Success(c, map[string]interface{}{
|
||||
"message": "Companies assigned successfully",
|
||||
}, "Companies assigned successfully")
|
||||
}
|
||||
|
||||
// RemoveCompaniesFromCustomer removes companies from a customer (Web Panel)
|
||||
// @Summary Remove companies from a customer
|
||||
// @Description Remove one or more companies from a specific customer.
|
||||
// @Tags Admin-Customers
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Customer ID"
|
||||
// @Param companies body RemoveCompaniesForm true "List of company IDs to remove"
|
||||
// @Success 200 {object} response.APIResponse "Companies removed successfully"
|
||||
// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
|
||||
// @Failure 404 {object} response.APIResponse "Not found - Customer not found"
|
||||
// @Failure 422 {object} response.APIResponse "Validation error - Invalid request data"
|
||||
// @Failure 500 {object} response.APIResponse "Internal server error"
|
||||
// @Security BearerAuth
|
||||
// @Router /admin/v1/customers/{id}/companies/remove [post]
|
||||
func (h *Handler) RemoveCompaniesFromCustomer(c echo.Context) error {
|
||||
idStr := c.Param("id")
|
||||
form, err := response.Parse[RemoveCompaniesForm](c)
|
||||
if err != nil {
|
||||
return response.ValidationError(c, "Invalid request data", err.Error())
|
||||
}
|
||||
|
||||
// Get user ID from JWT token context
|
||||
removedBy, err := user.GetUserIDFromContext(c)
|
||||
if err != nil {
|
||||
return response.Unauthorized(c, "User not authenticated")
|
||||
}
|
||||
|
||||
err = h.service.RemoveCompaniesFromCustomer(c.Request().Context(), idStr, form.CompanyIDs, &removedBy)
|
||||
if err != nil {
|
||||
if err.Error() == "customer not found" {
|
||||
return response.NotFound(c, "Customer not found")
|
||||
}
|
||||
return response.InternalServerError(c, "Failed to remove companies from customer")
|
||||
}
|
||||
|
||||
return response.Success(c, map[string]interface{}{
|
||||
"message": "Companies removed successfully",
|
||||
}, "Companies removed successfully")
|
||||
}
|
||||
// **************************************************
|
||||
// * PROFILE HANDLERS *
|
||||
// **************************************************
|
||||
|
||||
// Login handles customer authentication
|
||||
// @Summary Customer login
|
||||
|
||||
+93
-285
@@ -5,61 +5,46 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
"tm/pkg/logger"
|
||||
mongopkg "tm/pkg/mongo"
|
||||
orm "tm/pkg/mongo"
|
||||
"tm/pkg/response"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
// Repository defines the interface for customer data operations
|
||||
type Repository interface {
|
||||
Create(ctx context.Context, customer *Customer) error
|
||||
Login(ctx context.Context, id string) error
|
||||
Register(ctx context.Context, customer *Customer) error
|
||||
Update(ctx context.Context, customer *Customer) error
|
||||
UpdateStatus(ctx context.Context, id string, status CustomerStatus) error
|
||||
GetByID(ctx context.Context, id string) (*Customer, error)
|
||||
GetByEmail(ctx context.Context, email string) (*Customer, error)
|
||||
GetByUsername(ctx context.Context, username string) (*Customer, error)
|
||||
GetByCompanyName(ctx context.Context, companyName string) (*Customer, error)
|
||||
GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Customer, error)
|
||||
GetByTaxID(ctx context.Context, taxID string) (*Customer, error)
|
||||
GetByCompanyID(ctx context.Context, companyID string, limit, offset int) ([]*Customer, error)
|
||||
Update(ctx context.Context, customer *Customer) error
|
||||
GetByCompanyID(ctx context.Context, companyID string, pagination *response.Pagination) ([]*Customer, int64, error)
|
||||
Search(ctx context.Context, form *SearchCustomersForm, pagination *response.Pagination) ([]Customer, int64, error)
|
||||
Delete(ctx context.Context, id string) error
|
||||
List(ctx context.Context, limit, offset int) ([]*Customer, error)
|
||||
Search(ctx context.Context, search string, customerType *string, status *string, companyID *string, industry *string, isVerified *bool, isCompliant *bool, language *string, currency *string, limit, offset int, sortBy, sortOrder string) ([]*Customer, error)
|
||||
CountByCompanyID(ctx context.Context, companyID string) (int64, error)
|
||||
CountByType(ctx context.Context, customerType CustomerType) (int64, error)
|
||||
CountByStatus(ctx context.Context, status CustomerStatus) (int64, error)
|
||||
UpdateStatus(ctx context.Context, id string, status CustomerStatus) error
|
||||
UpdateVerification(ctx context.Context, id string, isVerified, isCompliant bool, complianceNotes *string) error
|
||||
}
|
||||
|
||||
// customerRepository implements the Repository interface using the MongoDB ORM
|
||||
type customerRepository struct {
|
||||
ormRepo mongopkg.Repository[Customer]
|
||||
ormRepo orm.Repository[Customer]
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
func collection() string {
|
||||
return "customers"
|
||||
}
|
||||
|
||||
// NewCustomerRepository creates a new customer repository
|
||||
func NewCustomerRepository(mongoManager *mongopkg.ConnectionManager, logger logger.Logger) Repository {
|
||||
func NewCustomerRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository {
|
||||
// Create indexes using the ORM's index management
|
||||
indexes := []mongopkg.Index{
|
||||
*mongopkg.CreateUniqueIndex("email_idx", bson.D{{Key: "email", Value: 1}}),
|
||||
*mongopkg.CreateUniqueIndex("username_idx", bson.D{{Key: "username", Value: 1}}),
|
||||
*mongopkg.NewIndex("company_name_idx", bson.D{{Key: "company_name", Value: 1}}),
|
||||
*mongopkg.NewIndex("registration_number_idx", bson.D{{Key: "registration_number", Value: 1}}),
|
||||
*mongopkg.NewIndex("tax_id_idx", bson.D{{Key: "tax_id", Value: 1}}),
|
||||
*mongopkg.NewIndex("company_ids_idx", bson.D{{Key: "company_ids", Value: 1}}), // Index for CompanyIDs array
|
||||
*mongopkg.NewIndex("type_idx", bson.D{{Key: "type", Value: 1}}),
|
||||
*mongopkg.NewIndex("status_idx", bson.D{{Key: "status", Value: 1}}),
|
||||
*mongopkg.NewIndex("industry_idx", bson.D{{Key: "industry", Value: 1}}),
|
||||
*mongopkg.NewIndex("is_verified_idx", bson.D{{Key: "is_verified", Value: 1}}),
|
||||
*mongopkg.NewIndex("is_compliant_idx", bson.D{{Key: "is_compliant", Value: 1}}),
|
||||
*mongopkg.NewIndex("language_idx", bson.D{{Key: "language", Value: 1}}),
|
||||
*mongopkg.NewIndex("currency_idx", bson.D{{Key: "currency", Value: 1}}),
|
||||
*mongopkg.NewIndex("created_at_idx", bson.D{{Key: "created_at", Value: -1}}),
|
||||
*mongopkg.CreateTextIndex("search_idx", "first_name", "last_name", "full_name", "company_name", "email", "username"),
|
||||
indexes := []orm.Index{
|
||||
*orm.CreateUniqueIndex("username_idx", bson.D{{Key: "username", Value: 1}}),
|
||||
*orm.CreateTextIndex("search_idx", "full_name", "email", "username", "phone"),
|
||||
}
|
||||
|
||||
// Create indexes
|
||||
err := mongoManager.CreateIndexes("customers", indexes)
|
||||
err := mongoManager.CreateIndexes(collection(), indexes)
|
||||
if err != nil {
|
||||
logger.Warn("Failed to create customer indexes", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
@@ -67,7 +52,7 @@ func NewCustomerRepository(mongoManager *mongopkg.ConnectionManager, logger logg
|
||||
}
|
||||
|
||||
// Create ORM repository
|
||||
ormRepo := mongopkg.NewRepository[Customer](mongoManager.GetCollection("customers"), logger)
|
||||
ormRepo := orm.NewRepository[Customer](mongoManager.GetCollection(collection()), logger)
|
||||
|
||||
return &customerRepository{
|
||||
ormRepo: ormRepo,
|
||||
@@ -75,17 +60,16 @@ func NewCustomerRepository(mongoManager *mongopkg.ConnectionManager, logger logg
|
||||
}
|
||||
}
|
||||
|
||||
// Create creates a new customer
|
||||
func (r *customerRepository) Create(ctx context.Context, customer *Customer) error {
|
||||
// Register creates a new customer
|
||||
func (r *customerRepository) Register(ctx context.Context, customer *Customer) error {
|
||||
// Set created/updated timestamps using Unix timestamps
|
||||
now := time.Now().Unix()
|
||||
customer.SetCreatedAt(now)
|
||||
customer.SetUpdatedAt(now)
|
||||
|
||||
// Use ORM to create customer
|
||||
err := r.ormRepo.Create(ctx, customer)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to create customer", map[string]interface{}{
|
||||
r.logger.Error("Failed to register customer", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"email": customer.Email,
|
||||
"username": customer.Username,
|
||||
@@ -93,7 +77,7 @@ func (r *customerRepository) Create(ctx context.Context, customer *Customer) err
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("Customer created successfully", map[string]interface{}{
|
||||
r.logger.Info("Customer registered successfully", map[string]interface{}{
|
||||
"customer_id": customer.ID,
|
||||
"email": customer.Email,
|
||||
"type": customer.Type,
|
||||
@@ -102,11 +86,40 @@ func (r *customerRepository) Create(ctx context.Context, customer *Customer) err
|
||||
return nil
|
||||
}
|
||||
|
||||
// Login updates the last login timestamp
|
||||
func (r *customerRepository) Login(ctx context.Context, id string) error {
|
||||
// Get customer first
|
||||
customer, err := r.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update last login timestamp
|
||||
customer.LastLoginAt = &[]int64{time.Now().Unix()}[0]
|
||||
customer.SetUpdatedAt(time.Now().Unix())
|
||||
|
||||
// Update in database
|
||||
err = r.ormRepo.Update(ctx, customer)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to update last login", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": id,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("Last login updated successfully", map[string]interface{}{
|
||||
"customer_id": id,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetByID retrieves a customer by ID
|
||||
func (r *customerRepository) GetByID(ctx context.Context, id string) (*Customer, error) {
|
||||
customer, err := r.ormRepo.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
if errors.Is(err, mongopkg.ErrDocumentNotFound) {
|
||||
if errors.Is(err, orm.ErrDocumentNotFound) {
|
||||
return nil, errors.New("customer not found")
|
||||
}
|
||||
r.logger.Error("Failed to get customer by ID", map[string]interface{}{
|
||||
@@ -124,7 +137,7 @@ func (r *customerRepository) GetByEmail(ctx context.Context, email string) (*Cus
|
||||
filter := bson.M{"email": email}
|
||||
customer, err := r.ormRepo.FindOne(ctx, filter)
|
||||
if err != nil {
|
||||
if errors.Is(err, mongopkg.ErrDocumentNotFound) {
|
||||
if errors.Is(err, orm.ErrDocumentNotFound) {
|
||||
return nil, errors.New("customer not found")
|
||||
}
|
||||
r.logger.Error("Failed to get customer by email", map[string]interface{}{
|
||||
@@ -142,7 +155,7 @@ func (r *customerRepository) GetByUsername(ctx context.Context, username string)
|
||||
filter := bson.M{"username": username}
|
||||
customer, err := r.ormRepo.FindOne(ctx, filter)
|
||||
if err != nil {
|
||||
if errors.Is(err, mongopkg.ErrDocumentNotFound) {
|
||||
if errors.Is(err, orm.ErrDocumentNotFound) {
|
||||
return nil, errors.New("customer not found")
|
||||
}
|
||||
r.logger.Error("Failed to get customer by username", map[string]interface{}{
|
||||
@@ -155,67 +168,13 @@ func (r *customerRepository) GetByUsername(ctx context.Context, username string)
|
||||
return customer, nil
|
||||
}
|
||||
|
||||
// GetByCompanyName retrieves a customer by company name
|
||||
func (r *customerRepository) GetByCompanyName(ctx context.Context, companyName string) (*Customer, error) {
|
||||
filter := bson.M{"company_name": companyName}
|
||||
customer, err := r.ormRepo.FindOne(ctx, filter)
|
||||
if err != nil {
|
||||
if errors.Is(err, mongopkg.ErrDocumentNotFound) {
|
||||
return nil, errors.New("customer not found")
|
||||
}
|
||||
r.logger.Error("Failed to get customer by company name", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"company_name": companyName,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return customer, nil
|
||||
}
|
||||
|
||||
// GetByRegistrationNumber retrieves a customer by registration number
|
||||
func (r *customerRepository) GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Customer, error) {
|
||||
filter := bson.M{"registration_number": registrationNumber}
|
||||
customer, err := r.ormRepo.FindOne(ctx, filter)
|
||||
if err != nil {
|
||||
if errors.Is(err, mongopkg.ErrDocumentNotFound) {
|
||||
return nil, errors.New("customer not found")
|
||||
}
|
||||
r.logger.Error("Failed to get customer by registration number", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"registration_number": registrationNumber,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return customer, nil
|
||||
}
|
||||
|
||||
// GetByTaxID retrieves a customer by tax ID
|
||||
func (r *customerRepository) GetByTaxID(ctx context.Context, taxID string) (*Customer, error) {
|
||||
filter := bson.M{"tax_id": taxID}
|
||||
customer, err := r.ormRepo.FindOne(ctx, filter)
|
||||
if err != nil {
|
||||
if errors.Is(err, mongopkg.ErrDocumentNotFound) {
|
||||
return nil, errors.New("customer not found")
|
||||
}
|
||||
r.logger.Error("Failed to get customer by tax ID", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"tax_id": taxID,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return customer, nil
|
||||
}
|
||||
|
||||
// GetByCompanyID retrieves customers by company ID with pagination
|
||||
func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID string, limit, offset int) ([]*Customer, error) {
|
||||
func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID string, pagination *response.Pagination) ([]*Customer, int64, error) {
|
||||
// Build pagination
|
||||
pagination := mongopkg.NewPaginationBuilder().
|
||||
Limit(limit).
|
||||
Skip(offset).
|
||||
SortDesc("created_at").
|
||||
paginationBuilder := orm.NewPaginationBuilder().
|
||||
Limit(pagination.Limit).
|
||||
Skip(pagination.Offset).
|
||||
SortBy(pagination.SortBy, pagination.SortOrder).
|
||||
Build()
|
||||
|
||||
// Filter by company ID and active status
|
||||
@@ -225,15 +184,15 @@ func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID strin
|
||||
}
|
||||
|
||||
// Use ORM to find customers
|
||||
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
|
||||
result, err := r.ormRepo.FindAll(ctx, filter, paginationBuilder)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to get customers by company ID", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"company_id": companyID,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
"limit": pagination.Limit,
|
||||
"offset": pagination.Offset,
|
||||
})
|
||||
return nil, err
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Convert []Customer to []*Customer
|
||||
@@ -242,7 +201,7 @@ func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID strin
|
||||
customers[i] = &result.Items[i]
|
||||
}
|
||||
|
||||
return customers, nil
|
||||
return customers, result.TotalCount, nil
|
||||
}
|
||||
|
||||
// Update updates a customer
|
||||
@@ -268,20 +227,24 @@ func (r *customerRepository) Update(ctx context.Context, customer *Customer) err
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes a customer (soft delete by setting status to inactive)
|
||||
// Delete deletes a customer (hard delete)
|
||||
func (r *customerRepository) Delete(ctx context.Context, id string) error {
|
||||
// Get customer first
|
||||
customer, err := r.GetByID(ctx, id)
|
||||
_, err := r.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if errors.Is(err, orm.ErrDocumentNotFound) {
|
||||
return errors.New("customer not found")
|
||||
}
|
||||
r.logger.Error("Failed to delete customer", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": id,
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Update status to inactive
|
||||
customer.Status = CustomerStatusInactive
|
||||
customer.SetUpdatedAt(time.Now().Unix())
|
||||
|
||||
// Update in database
|
||||
err = r.ormRepo.Update(ctx, customer)
|
||||
err = r.ormRepo.Delete(ctx, id)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to delete customer", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
@@ -297,166 +260,44 @@ func (r *customerRepository) Delete(ctx context.Context, id string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// List retrieves customers with pagination
|
||||
func (r *customerRepository) List(ctx context.Context, limit, offset int) ([]*Customer, error) {
|
||||
// Build pagination
|
||||
pagination := mongopkg.NewPaginationBuilder().
|
||||
Limit(limit).
|
||||
Skip(offset).
|
||||
SortDesc("created_at").
|
||||
Build()
|
||||
|
||||
// Only active customers by default
|
||||
filter := bson.M{"status": bson.M{"$ne": CustomerStatusInactive}}
|
||||
|
||||
// Use ORM to find customers
|
||||
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to list customers", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert []Customer to []*Customer
|
||||
customers := make([]*Customer, len(result.Items))
|
||||
for i := range result.Items {
|
||||
customers[i] = &result.Items[i]
|
||||
}
|
||||
|
||||
return customers, nil
|
||||
}
|
||||
|
||||
// Search retrieves customers with search and filters
|
||||
func (r *customerRepository) Search(ctx context.Context, search string, customerType *string, status *string, companyID *string, industry *string, isVerified *bool, isCompliant *bool, language *string, currency *string, limit, offset int, sortBy, sortOrder string) ([]*Customer, error) {
|
||||
func (r *customerRepository) Search(ctx context.Context, form *SearchCustomersForm, pagination *response.Pagination) ([]Customer, int64, error) {
|
||||
// Build filter
|
||||
filter := bson.M{}
|
||||
|
||||
if search != "" {
|
||||
filter["$text"] = bson.M{"$search": search}
|
||||
if form.Search != nil {
|
||||
filter["$text"] = bson.M{"$search": *form.Search}
|
||||
}
|
||||
|
||||
if customerType != nil {
|
||||
filter["type"] = *customerType
|
||||
if form.Type != nil {
|
||||
filter["type"] = *form.Type
|
||||
}
|
||||
|
||||
if status != nil {
|
||||
filter["status"] = *status
|
||||
if form.Status != nil {
|
||||
filter["status"] = *form.Status
|
||||
}
|
||||
|
||||
if companyID != nil {
|
||||
filter["company_id"] = *companyID
|
||||
}
|
||||
|
||||
if industry != nil {
|
||||
filter["industry"] = *industry
|
||||
}
|
||||
|
||||
if isVerified != nil {
|
||||
filter["is_verified"] = *isVerified
|
||||
}
|
||||
|
||||
if isCompliant != nil {
|
||||
filter["is_compliant"] = *isCompliant
|
||||
}
|
||||
|
||||
if language != nil {
|
||||
filter["language"] = *language
|
||||
}
|
||||
|
||||
if currency != nil {
|
||||
filter["currency"] = *currency
|
||||
if form.CompanyID != nil {
|
||||
filter["company_id"] = *form.CompanyID
|
||||
}
|
||||
|
||||
// Build pagination
|
||||
pagination := mongopkg.NewPaginationBuilder().
|
||||
Limit(limit).
|
||||
Skip(offset)
|
||||
|
||||
// Set sort
|
||||
if sortBy != "" {
|
||||
if sortOrder == "desc" {
|
||||
pagination.SortDesc(sortBy)
|
||||
} else {
|
||||
pagination.SortAsc(sortBy)
|
||||
}
|
||||
} else {
|
||||
pagination.SortDesc("created_at") // Default sort
|
||||
}
|
||||
paginationBuilder := orm.NewPaginationBuilder().
|
||||
Limit(pagination.Limit).
|
||||
Skip(pagination.Offset).
|
||||
SortBy(pagination.SortBy, pagination.SortOrder)
|
||||
|
||||
// Use ORM to find customers
|
||||
result, err := r.ormRepo.FindAll(ctx, filter, pagination.Build())
|
||||
result, err := r.ormRepo.FindAll(ctx, filter, paginationBuilder.Build())
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to search customers", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"search": search,
|
||||
"search": form.Search,
|
||||
})
|
||||
return nil, err
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Convert []Customer to []*Customer
|
||||
customers := make([]*Customer, len(result.Items))
|
||||
for i := range result.Items {
|
||||
customers[i] = &result.Items[i]
|
||||
}
|
||||
|
||||
return customers, nil
|
||||
}
|
||||
|
||||
// CountByCompanyID counts customers by company ID
|
||||
func (r *customerRepository) CountByCompanyID(ctx context.Context, companyID string) (int64, error) {
|
||||
filter := bson.M{
|
||||
"company_id": companyID,
|
||||
"status": bson.M{"$ne": CustomerStatusInactive},
|
||||
}
|
||||
|
||||
count, err := r.ormRepo.Count(ctx, filter)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to count customers by company ID", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"company_id": companyID,
|
||||
})
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// CountByType counts customers by type
|
||||
func (r *customerRepository) CountByType(ctx context.Context, customerType CustomerType) (int64, error) {
|
||||
filter := bson.M{
|
||||
"type": customerType,
|
||||
"status": bson.M{"$ne": CustomerStatusInactive},
|
||||
}
|
||||
|
||||
count, err := r.ormRepo.Count(ctx, filter)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to count customers by type", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_type": customerType,
|
||||
})
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// CountByStatus counts customers by status
|
||||
func (r *customerRepository) CountByStatus(ctx context.Context, status CustomerStatus) (int64, error) {
|
||||
filter := bson.M{"status": status}
|
||||
|
||||
count, err := r.ormRepo.Count(ctx, filter)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to count customers by status", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"status": status,
|
||||
})
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
return result.Items, result.TotalCount, nil
|
||||
}
|
||||
|
||||
// UpdateStatus updates customer status
|
||||
@@ -489,36 +330,3 @@ func (r *customerRepository) UpdateStatus(ctx context.Context, id string, status
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateVerification updates customer verification status
|
||||
func (r *customerRepository) UpdateVerification(ctx context.Context, id string, isVerified, isCompliant bool, complianceNotes *string) error {
|
||||
// Get customer first
|
||||
customer, err := r.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update verification fields
|
||||
customer.IsVerified = isVerified
|
||||
customer.IsCompliant = isCompliant
|
||||
customer.ComplianceNotes = complianceNotes
|
||||
customer.SetUpdatedAt(time.Now().Unix())
|
||||
|
||||
// Update in database
|
||||
err = r.ormRepo.Update(ctx, customer)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to update customer verification", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": id,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("Customer verification updated successfully", map[string]interface{}{
|
||||
"customer_id": id,
|
||||
"is_verified": isVerified,
|
||||
"is_compliant": isCompliant,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
+112
-628
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"tm/internal/company"
|
||||
"tm/pkg/logger"
|
||||
"tm/pkg/response"
|
||||
|
||||
"tm/pkg/authorization"
|
||||
|
||||
@@ -17,41 +18,18 @@ import (
|
||||
// Service defines business logic for customer operations
|
||||
type Service interface {
|
||||
// Core customer operations
|
||||
CreateCustomer(ctx context.Context, form *CreateCustomerForm, createdBy *string) (*Customer, error)
|
||||
GetCustomerByID(ctx context.Context, id string) (*Customer, error)
|
||||
GetCustomerByIDWithCompanies(ctx context.Context, id string) (*CustomerResponse, error)
|
||||
UpdateCustomer(ctx context.Context, id string, form *UpdateCustomerForm, updatedBy *string) (*Customer, error)
|
||||
DeleteCustomer(ctx context.Context, id string, deletedBy *string) error
|
||||
|
||||
// Customer listing and search
|
||||
ListCustomers(ctx context.Context, form *ListCustomersForm) (*CustomerListResponse, error)
|
||||
GetCustomersByCompanyID(ctx context.Context, companyID string, limit, offset int) ([]*Customer, int64, error)
|
||||
GetCustomersByType(ctx context.Context, customerType CustomerType, limit, offset int) ([]*Customer, int64, error)
|
||||
GetCustomersByStatus(ctx context.Context, status CustomerStatus, limit, offset int) ([]*Customer, int64, error)
|
||||
|
||||
// Customer management
|
||||
UpdateCustomerStatus(ctx context.Context, id string, form *UpdateCustomerStatusForm, updatedBy *string) error
|
||||
UpdateCustomerVerification(ctx context.Context, id string, form *UpdateCustomerVerificationForm, updatedBy *string) error
|
||||
|
||||
// Company assignments
|
||||
AssignCompaniesToCustomer(ctx context.Context, customerID string, companyIDs []string, updatedBy *string) error
|
||||
RemoveCompaniesFromCustomer(ctx context.Context, customerID string, companyIDs []string, updatedBy *string) error
|
||||
|
||||
// Mobile-specific operations (simplified)
|
||||
CreateCustomerMobile(ctx context.Context, form *CreateCustomerMobileForm, createdBy *string) (*Customer, error)
|
||||
UpdateCustomerMobile(ctx context.Context, id string, form *UpdateCustomerMobileForm, updatedBy *string) (*Customer, error)
|
||||
|
||||
// Business operations
|
||||
VerifyCustomer(ctx context.Context, id string, verifiedBy *string) error
|
||||
SuspendCustomer(ctx context.Context, id string, reason string, suspendedBy *string) error
|
||||
ActivateCustomer(ctx context.Context, id string, activatedBy *string) error
|
||||
Register(ctx context.Context, form *CreateCustomerForm) (*CustomerResponse, error)
|
||||
GetByID(ctx context.Context, id string) (*CustomerResponse, error)
|
||||
Search(ctx context.Context, form *SearchCustomersForm, pagination *response.Pagination) (*CustomerListResponse, error)
|
||||
Update(ctx context.Context, id string, form *UpdateCustomerForm) (*CustomerResponse, error)
|
||||
Delete(ctx context.Context, id string) error
|
||||
UpdateStatus(ctx context.Context, id string, form *UpdateStatusForm) error
|
||||
|
||||
// Authentication operations
|
||||
Login(ctx context.Context, form *LoginForm) (*AuthResponse, error)
|
||||
RefreshToken(ctx context.Context, refreshToken string) (*AuthResponse, error)
|
||||
GetProfile(ctx context.Context, customerID string) (*Customer, error)
|
||||
GetProfileWithCompanies(ctx context.Context, customerID string) (*CustomerResponse, error)
|
||||
Logout(ctx context.Context, customerID string, accessToken string) error
|
||||
GetProfileWithCompanies(ctx context.Context, id string) (*CustomerResponse, error)
|
||||
Logout(ctx context.Context, id string, accessToken string) error
|
||||
}
|
||||
|
||||
// customerService implements the Service interface
|
||||
@@ -72,34 +50,17 @@ func NewCustomerService(repository Repository, logger logger.Logger, authService
|
||||
}
|
||||
}
|
||||
|
||||
// CreateCustomer creates a new customer
|
||||
func (s *customerService) CreateCustomer(ctx context.Context, form *CreateCustomerForm, createdBy *string) (*Customer, error) {
|
||||
// Register creates a new customer
|
||||
func (s *customerService) Register(ctx context.Context, form *CreateCustomerForm) (*CustomerResponse, error) {
|
||||
// Check if email already exists
|
||||
existingCustomer, _ := s.repository.GetByEmail(ctx, form.Email)
|
||||
if existingCustomer != nil {
|
||||
return nil, errors.New("customer with this email already exists")
|
||||
}
|
||||
|
||||
// Check if registration number already exists (for company customers)
|
||||
if form.Type == string(CustomerTypeCompany) && form.RegistrationNumber != nil {
|
||||
existingCustomer, _ = s.repository.GetByRegistrationNumber(ctx, *form.RegistrationNumber)
|
||||
if existingCustomer != nil {
|
||||
return nil, errors.New("company with this registration number already exists")
|
||||
}
|
||||
}
|
||||
|
||||
// Check if tax ID already exists (for company customers)
|
||||
if form.Type == string(CustomerTypeCompany) && form.TaxID != nil {
|
||||
existingCustomer, _ = s.repository.GetByTaxID(ctx, *form.TaxID)
|
||||
if existingCustomer != nil {
|
||||
return nil, errors.New("company with this tax ID already exists")
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare company assignments
|
||||
// Verify that all company IDs exist
|
||||
var companyIDs []string
|
||||
if len(form.CompanyIDs) > 0 {
|
||||
// Verify that all company IDs exist
|
||||
for _, companyID := range form.CompanyIDs {
|
||||
_, err := s.companyService.GetCompanyByID(ctx, companyID)
|
||||
if err != nil {
|
||||
@@ -124,36 +85,18 @@ func (s *customerService) CreateCustomer(ctx context.Context, form *CreateCustom
|
||||
|
||||
// Create customer
|
||||
customer := &Customer{
|
||||
Type: CustomerType(form.Type),
|
||||
Status: CustomerStatusPending,
|
||||
CompanyIDs: companyIDs,
|
||||
FirstName: form.FirstName,
|
||||
LastName: form.LastName,
|
||||
FullName: form.FullName,
|
||||
Username: form.Username,
|
||||
Email: form.Email,
|
||||
Password: string(hashedPassword),
|
||||
Phone: form.Phone,
|
||||
Mobile: form.Mobile,
|
||||
RegistrationNumber: form.RegistrationNumber,
|
||||
TaxID: form.TaxID,
|
||||
Industry: form.Industry,
|
||||
Address: s.convertAddressForm(form.Address),
|
||||
BusinessType: form.BusinessType,
|
||||
EmployeeCount: form.EmployeeCount,
|
||||
AnnualRevenue: form.AnnualRevenue,
|
||||
FoundedYear: form.FoundedYear,
|
||||
ContactPerson: s.convertContactPersonForm(form.ContactPerson),
|
||||
IsVerified: false,
|
||||
IsCompliant: false,
|
||||
Language: s.getDefaultLanguage(form.Language),
|
||||
Currency: s.getDefaultCurrency(form.Currency),
|
||||
Timezone: s.getDefaultTimezone(form.Timezone),
|
||||
CreatedBy: createdBy,
|
||||
Type: CustomerType(form.Type),
|
||||
Status: CustomerStatusActive,
|
||||
CompanyIDs: companyIDs,
|
||||
FullName: form.FullName,
|
||||
Username: form.Username,
|
||||
Email: form.Email,
|
||||
Password: string(hashedPassword),
|
||||
Phone: form.Phone,
|
||||
}
|
||||
|
||||
// Save to database
|
||||
err = s.repository.Create(ctx, customer)
|
||||
err = s.repository.Register(ctx, customer)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to create customer", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
@@ -168,14 +111,13 @@ func (s *customerService) CreateCustomer(ctx context.Context, form *CreateCustom
|
||||
"email": customer.Email,
|
||||
"type": customer.Type,
|
||||
"company_ids": companyIDs,
|
||||
"created_by": *createdBy,
|
||||
})
|
||||
|
||||
return customer, nil
|
||||
return customer.ToResponse(nil), nil
|
||||
}
|
||||
|
||||
// GetCustomerByID retrieves a customer by ID
|
||||
func (s *customerService) GetCustomerByID(ctx context.Context, id string) (*Customer, error) {
|
||||
// GetByID retrieves a customer by ID
|
||||
func (s *customerService) GetByID(ctx context.Context, id string) (*CustomerResponse, error) {
|
||||
customer, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get customer by ID", map[string]interface{}{
|
||||
@@ -185,48 +127,56 @@ func (s *customerService) GetCustomerByID(ctx context.Context, id string) (*Cust
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var companies []*CompanySummary
|
||||
if len(customer.CompanyIDs) > 0 {
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
s.logger.Info("Customer retrieved successfully", map[string]interface{}{
|
||||
"customer_id": customer.ID,
|
||||
"email": customer.Email,
|
||||
})
|
||||
|
||||
return customer, nil
|
||||
return customer.ToResponse(companies), nil
|
||||
}
|
||||
|
||||
// GetCustomerByIDWithCompanies retrieves a customer by ID and loads associated companies
|
||||
func (s *customerService) GetCustomerByIDWithCompanies(ctx context.Context, id string) (*CustomerResponse, error) {
|
||||
customer, err := s.repository.GetByID(ctx, id)
|
||||
// Search searches for customers
|
||||
func (s *customerService) Search(ctx context.Context, form *SearchCustomersForm, pagination *response.Pagination) (*CustomerListResponse, error) {
|
||||
customers, total, err := s.repository.Search(ctx, form, pagination)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get customer by ID with companies", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": id,
|
||||
s.logger.Error("Failed to search customers", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return nil, err
|
||||
return nil, errors.New("failed to search customers")
|
||||
}
|
||||
|
||||
// Load companies for the customer
|
||||
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,
|
||||
})
|
||||
// Continue without companies if loading fails
|
||||
companies = []*CompanySummary{}
|
||||
var customerResponses []*CustomerResponse
|
||||
for _, customer := range customers {
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
customerResponses = append(customerResponses, customer.ToResponse(companies))
|
||||
}
|
||||
|
||||
customerResponse := customer.ToResponseWithCompanies(companies)
|
||||
|
||||
s.logger.Info("Customer retrieved with companies successfully", map[string]interface{}{
|
||||
"customer_id": customer.ID,
|
||||
"email": customer.Email,
|
||||
})
|
||||
|
||||
return customerResponse, nil
|
||||
return &CustomerListResponse{
|
||||
Customers: customerResponses,
|
||||
Meta: pagination.Response(total),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateCustomer updates a customer
|
||||
func (s *customerService) UpdateCustomer(ctx context.Context, id string, form *UpdateCustomerForm, updatedBy *string) (*Customer, error) {
|
||||
// Update updates a customer
|
||||
func (s *customerService) Update(ctx context.Context, id string, form *UpdateCustomerForm) (*CustomerResponse, error) {
|
||||
// Get existing customer
|
||||
customer, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
@@ -238,44 +188,21 @@ func (s *customerService) UpdateCustomer(ctx context.Context, id string, form *U
|
||||
}
|
||||
|
||||
// Check if email already exists (if changing email)
|
||||
if form.Email != nil && *form.Email != customer.Email {
|
||||
existingCustomer, _ := s.repository.GetByEmail(ctx, *form.Email)
|
||||
if form.Email != "" && form.Email != customer.Email {
|
||||
existingCustomer, _ := s.repository.GetByEmail(ctx, form.Email)
|
||||
if existingCustomer != nil {
|
||||
return nil, errors.New("customer with this email already exists")
|
||||
}
|
||||
customer.Email = *form.Email
|
||||
customer.Email = form.Email
|
||||
}
|
||||
|
||||
// Check if username already exists (if changing username)
|
||||
if form.Username != nil && *form.Username != customer.Username {
|
||||
existingCustomer, _ := s.repository.GetByUsername(ctx, *form.Username)
|
||||
if form.Username != "" && form.Username != customer.Username {
|
||||
existingCustomer, _ := s.repository.GetByUsername(ctx, form.Username)
|
||||
if existingCustomer != nil {
|
||||
return nil, errors.New("customer with this username already exists")
|
||||
}
|
||||
customer.Username = *form.Username
|
||||
}
|
||||
|
||||
// Check if registration number already exists (if changing)
|
||||
if form.RegistrationNumber != nil && *form.RegistrationNumber != *customer.RegistrationNumber {
|
||||
existingCustomer, _ := s.repository.GetByRegistrationNumber(ctx, *form.RegistrationNumber)
|
||||
if existingCustomer != nil {
|
||||
return nil, errors.New("company with this registration number already exists")
|
||||
}
|
||||
customer.RegistrationNumber = form.RegistrationNumber
|
||||
}
|
||||
|
||||
// Check if tax ID already exists (if changing)
|
||||
if form.TaxID != nil && *form.TaxID != *customer.TaxID {
|
||||
existingCustomer, _ := s.repository.GetByTaxID(ctx, *form.TaxID)
|
||||
if existingCustomer != nil {
|
||||
return nil, errors.New("company with this tax ID already exists")
|
||||
}
|
||||
customer.TaxID = form.TaxID
|
||||
}
|
||||
|
||||
// Update fields if provided
|
||||
if form.Type != nil {
|
||||
customer.Type = CustomerType(*form.Type)
|
||||
customer.Username = form.Username
|
||||
}
|
||||
|
||||
// Handle company assignments
|
||||
@@ -296,14 +223,6 @@ func (s *customerService) UpdateCustomer(ctx context.Context, id string, form *U
|
||||
customer.CompanyIDs = validCompanyIDs
|
||||
}
|
||||
|
||||
if form.FirstName != nil {
|
||||
customer.FirstName = form.FirstName
|
||||
}
|
||||
|
||||
if form.LastName != nil {
|
||||
customer.LastName = form.LastName
|
||||
}
|
||||
|
||||
if form.FullName != nil {
|
||||
customer.FullName = form.FullName
|
||||
}
|
||||
@@ -312,54 +231,6 @@ func (s *customerService) UpdateCustomer(ctx context.Context, id string, form *U
|
||||
customer.Phone = form.Phone
|
||||
}
|
||||
|
||||
if form.Mobile != nil {
|
||||
customer.Mobile = form.Mobile
|
||||
}
|
||||
|
||||
if form.Industry != nil {
|
||||
customer.Industry = form.Industry
|
||||
}
|
||||
|
||||
if form.Address != nil {
|
||||
customer.Address = s.convertAddressForm(form.Address)
|
||||
}
|
||||
|
||||
if form.BusinessType != nil {
|
||||
customer.BusinessType = form.BusinessType
|
||||
}
|
||||
|
||||
if form.EmployeeCount != nil {
|
||||
customer.EmployeeCount = form.EmployeeCount
|
||||
}
|
||||
|
||||
if form.AnnualRevenue != nil {
|
||||
customer.AnnualRevenue = form.AnnualRevenue
|
||||
}
|
||||
|
||||
if form.FoundedYear != nil {
|
||||
customer.FoundedYear = form.FoundedYear
|
||||
}
|
||||
|
||||
if form.ContactPerson != nil {
|
||||
customer.ContactPerson = s.convertContactPersonForm(form.ContactPerson)
|
||||
}
|
||||
|
||||
if form.Language != nil {
|
||||
customer.Language = *form.Language
|
||||
}
|
||||
|
||||
if form.Currency != nil {
|
||||
customer.Currency = *form.Currency
|
||||
}
|
||||
|
||||
if form.Timezone != nil {
|
||||
customer.Timezone = *form.Timezone
|
||||
}
|
||||
|
||||
// Set updated by
|
||||
customer.UpdatedBy = updatedBy
|
||||
customer.UpdatedAt = time.Now().Unix()
|
||||
|
||||
// Update in database
|
||||
err = s.repository.Update(ctx, customer)
|
||||
if err != nil {
|
||||
@@ -373,23 +244,23 @@ func (s *customerService) UpdateCustomer(ctx context.Context, id string, form *U
|
||||
s.logger.Info("Customer updated successfully", map[string]interface{}{
|
||||
"customer_id": customer.ID,
|
||||
"email": customer.Email,
|
||||
"updated_by": *updatedBy,
|
||||
})
|
||||
|
||||
return customer, nil
|
||||
return customer.ToResponse(nil), nil
|
||||
}
|
||||
|
||||
// DeleteCustomer deletes a customer (soft delete)
|
||||
func (s *customerService) DeleteCustomer(ctx context.Context, id string, deletedBy *string) error {
|
||||
// Delete deletes a customer (soft delete)
|
||||
func (s *customerService) Delete(ctx context.Context, id string) error {
|
||||
// Get customer to check if exists
|
||||
customer, err := s.repository.GetByID(ctx, id)
|
||||
_, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get customer for delete", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": id,
|
||||
})
|
||||
return errors.New("customer not found")
|
||||
}
|
||||
|
||||
// Set updated by before deletion
|
||||
customer.UpdatedBy = deletedBy
|
||||
|
||||
// Delete customer (soft delete)
|
||||
err = s.repository.Delete(ctx, id)
|
||||
if err != nil {
|
||||
@@ -402,154 +273,13 @@ func (s *customerService) DeleteCustomer(ctx context.Context, id string, deleted
|
||||
|
||||
s.logger.Info("Customer deleted successfully", map[string]interface{}{
|
||||
"customer_id": id,
|
||||
"deleted_by": deletedBy,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListCustomers lists customers with search and filters
|
||||
func (s *customerService) ListCustomers(ctx context.Context, form *ListCustomersForm) (*CustomerListResponse, error) {
|
||||
// Set defaults
|
||||
limit := 20
|
||||
if form.Limit != nil {
|
||||
limit = *form.Limit
|
||||
}
|
||||
|
||||
offset := 0
|
||||
if form.Offset != nil {
|
||||
offset = *form.Offset
|
||||
}
|
||||
|
||||
search := ""
|
||||
if form.Search != nil {
|
||||
search = *form.Search
|
||||
}
|
||||
|
||||
sortBy := "created_at"
|
||||
if form.SortBy != nil {
|
||||
sortBy = *form.SortBy
|
||||
}
|
||||
|
||||
sortOrder := "desc"
|
||||
if form.SortOrder != nil {
|
||||
sortOrder = *form.SortOrder
|
||||
}
|
||||
|
||||
// Parse company ID if provided
|
||||
var companyID *string
|
||||
if form.CompanyID != nil {
|
||||
companyID = form.CompanyID
|
||||
}
|
||||
|
||||
// Get customers
|
||||
customers, err := s.repository.Search(ctx, search, form.Type, form.Status, companyID, form.Industry, form.IsVerified, form.IsCompliant, form.Language, form.Currency, limit, offset, sortBy, sortOrder)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to list customers", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return nil, errors.New("failed to list customers")
|
||||
}
|
||||
|
||||
// Convert to responses
|
||||
var customerResponses []*CustomerResponse
|
||||
for _, customer := range customers {
|
||||
customerResponses = append(customerResponses, customer.ToResponse())
|
||||
}
|
||||
|
||||
// TODO: Implement proper count for pagination metadata
|
||||
total := int64(len(customers)) // This should be a separate count query
|
||||
totalPages := (total + int64(limit) - 1) / int64(limit)
|
||||
|
||||
return &CustomerListResponse{
|
||||
Customers: customerResponses,
|
||||
Total: total,
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
TotalPages: int(totalPages),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetCustomersByCompanyID retrieves customers by company ID with pagination
|
||||
func (s *customerService) GetCustomersByCompanyID(ctx context.Context, companyID string, limit, offset int) ([]*Customer, int64, error) {
|
||||
customers, err := s.repository.GetByCompanyID(ctx, companyID, limit, offset)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get customers by company ID", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"company_id": companyID,
|
||||
})
|
||||
return nil, 0, errors.New("failed to get customers by company")
|
||||
}
|
||||
|
||||
// Get total count
|
||||
total, err := s.repository.CountByCompanyID(ctx, companyID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to count customers by company ID", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"company_id": companyID,
|
||||
})
|
||||
return customers, 0, nil // Return customers even if count fails
|
||||
}
|
||||
|
||||
return customers, total, nil
|
||||
}
|
||||
|
||||
// GetCustomersByType retrieves customers by type with pagination
|
||||
func (s *customerService) GetCustomersByType(ctx context.Context, customerType CustomerType, limit, offset int) ([]*Customer, int64, error) {
|
||||
// This would need to be implemented in the repository
|
||||
// For now, we'll use the search method
|
||||
customerTypeStr := string(customerType)
|
||||
customers, err := s.repository.Search(ctx, "", &customerTypeStr, nil, nil, nil, nil, nil, nil, nil, limit, offset, "created_at", "desc")
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get customers by type", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_type": customerType,
|
||||
})
|
||||
return nil, 0, errors.New("failed to get customers by type")
|
||||
}
|
||||
|
||||
// Get total count
|
||||
total, err := s.repository.CountByType(ctx, customerType)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to count customers by type", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_type": customerType,
|
||||
})
|
||||
return customers, 0, nil
|
||||
}
|
||||
|
||||
return customers, total, nil
|
||||
}
|
||||
|
||||
// GetCustomersByStatus retrieves customers by status with pagination
|
||||
func (s *customerService) GetCustomersByStatus(ctx context.Context, status CustomerStatus, limit, offset int) ([]*Customer, int64, error) {
|
||||
// This would need to be implemented in the repository
|
||||
// For now, we'll use the search method
|
||||
statusStr := string(status)
|
||||
customers, err := s.repository.Search(ctx, "", nil, &statusStr, nil, nil, nil, nil, nil, nil, limit, offset, "created_at", "desc")
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get customers by status", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"status": status,
|
||||
})
|
||||
return nil, 0, errors.New("failed to get customers by status")
|
||||
}
|
||||
|
||||
// Get total count
|
||||
total, err := s.repository.CountByStatus(ctx, status)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to count customers by status", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"status": status,
|
||||
})
|
||||
return customers, 0, nil
|
||||
}
|
||||
|
||||
return customers, total, nil
|
||||
}
|
||||
|
||||
// UpdateCustomerStatus updates customer status
|
||||
func (s *customerService) UpdateCustomerStatus(ctx context.Context, id string, form *UpdateCustomerStatusForm, updatedBy *string) error {
|
||||
// UpdateStatus updates customer status
|
||||
func (s *customerService) UpdateStatus(ctx context.Context, id string, form *UpdateStatusForm) error {
|
||||
// Get customer to check if exists
|
||||
_, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
@@ -571,42 +301,13 @@ func (s *customerService) UpdateCustomerStatus(ctx context.Context, id string, f
|
||||
s.logger.Info("Customer status updated successfully", map[string]interface{}{
|
||||
"customer_id": id,
|
||||
"status": form.Status,
|
||||
"updated_by": updatedBy,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateCustomerVerification updates customer verification status
|
||||
func (s *customerService) UpdateCustomerVerification(ctx context.Context, id string, form *UpdateCustomerVerificationForm, updatedBy *string) error {
|
||||
// Get customer to check if exists
|
||||
_, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return errors.New("customer not found")
|
||||
}
|
||||
|
||||
// Update verification
|
||||
err = s.repository.UpdateVerification(ctx, id, form.IsVerified, form.IsCompliant, form.ComplianceNotes)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to update customer verification", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": id,
|
||||
})
|
||||
return errors.New("failed to update customer verification")
|
||||
}
|
||||
|
||||
s.logger.Info("Customer verification updated successfully", map[string]interface{}{
|
||||
"customer_id": id,
|
||||
"is_verified": form.IsVerified,
|
||||
"is_compliant": form.IsCompliant,
|
||||
"updated_by": updatedBy,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AssignCompaniesToCustomer assigns companies to a customer
|
||||
func (s *customerService) AssignCompaniesToCustomer(ctx context.Context, customerID string, companyIDs []string, updatedBy *string) error {
|
||||
// AssignCompanies assigns companies to a customer
|
||||
func (s *customerService) AssignCompanies(ctx context.Context, customerID string, companyIDs []string) error {
|
||||
// Get customer to check if exists
|
||||
customer, err := s.repository.GetByID(ctx, customerID)
|
||||
if err != nil {
|
||||
@@ -630,7 +331,6 @@ func (s *customerService) AssignCompaniesToCustomer(ctx context.Context, custome
|
||||
|
||||
// Update customer's company IDs
|
||||
customer.CompanyIDs = append(customer.CompanyIDs, validCompanyIDs...)
|
||||
customer.UpdatedBy = updatedBy
|
||||
customer.UpdatedAt = time.Now().Unix()
|
||||
|
||||
err = s.repository.Update(ctx, customer)
|
||||
@@ -645,14 +345,13 @@ func (s *customerService) AssignCompaniesToCustomer(ctx context.Context, custome
|
||||
s.logger.Info("Companies assigned to customer successfully", map[string]interface{}{
|
||||
"customer_id": customerID,
|
||||
"company_ids": validCompanyIDs,
|
||||
"updated_by": updatedBy,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveCompaniesFromCustomer removes companies from a customer
|
||||
func (s *customerService) RemoveCompaniesFromCustomer(ctx context.Context, customerID string, companyIDs []string, updatedBy *string) error {
|
||||
func (s *customerService) RemoveCompanies(ctx context.Context, customerID string, companyIDs []string) error {
|
||||
// Get customer to check if exists
|
||||
customer, err := s.repository.GetByID(ctx, customerID)
|
||||
if err != nil {
|
||||
@@ -676,7 +375,6 @@ func (s *customerService) RemoveCompaniesFromCustomer(ctx context.Context, custo
|
||||
|
||||
// Update customer's company IDs
|
||||
customer.CompanyIDs = updatedCompanyIDs
|
||||
customer.UpdatedBy = updatedBy
|
||||
customer.UpdatedAt = time.Now().Unix()
|
||||
|
||||
err = s.repository.Update(ctx, customer)
|
||||
@@ -691,194 +389,13 @@ func (s *customerService) RemoveCompaniesFromCustomer(ctx context.Context, custo
|
||||
s.logger.Info("Companies removed from customer successfully", map[string]interface{}{
|
||||
"customer_id": customerID,
|
||||
"company_ids": companyIDs,
|
||||
"updated_by": updatedBy,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateCustomerMobile creates a new customer via mobile app (simplified)
|
||||
func (s *customerService) CreateCustomerMobile(ctx context.Context, form *CreateCustomerMobileForm, createdBy *string) (*Customer, error) {
|
||||
// Check if email already exists
|
||||
existingCustomer, _ := s.repository.GetByEmail(ctx, form.Email)
|
||||
if existingCustomer != nil {
|
||||
return nil, errors.New("customer with this email already exists")
|
||||
}
|
||||
|
||||
// Prepare company assignments
|
||||
var companyIDs []string
|
||||
if len(form.CompanyIDs) > 0 {
|
||||
// Verify that all company IDs exist
|
||||
for _, companyID := range form.CompanyIDs {
|
||||
_, err := s.companyService.GetCompanyByID(ctx, companyID)
|
||||
if err != nil {
|
||||
s.logger.Error("Invalid company ID provided in mobile form", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"company_id": companyID,
|
||||
})
|
||||
return nil, errors.New("invalid company ID: " + companyID)
|
||||
}
|
||||
}
|
||||
companyIDs = form.CompanyIDs
|
||||
}
|
||||
|
||||
// Create customer with mobile form
|
||||
customer := &Customer{
|
||||
Type: CustomerType(form.Type),
|
||||
Status: CustomerStatusActive,
|
||||
CompanyIDs: companyIDs,
|
||||
FirstName: form.FirstName,
|
||||
LastName: form.LastName,
|
||||
FullName: form.FullName,
|
||||
Username: form.Username,
|
||||
Email: form.Email,
|
||||
Password: "", // Will be set after hashing
|
||||
Phone: form.Phone,
|
||||
Mobile: form.Mobile,
|
||||
Industry: form.Industry,
|
||||
IsVerified: false,
|
||||
IsCompliant: false,
|
||||
Language: "en",
|
||||
Currency: "USD",
|
||||
Timezone: "UTC",
|
||||
CreatedBy: createdBy,
|
||||
}
|
||||
|
||||
// Hash password
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(form.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to hash password for mobile customer", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return nil, errors.New("failed to process password")
|
||||
}
|
||||
customer.Password = string(hashedPassword)
|
||||
|
||||
// Save to database
|
||||
err = s.repository.Create(ctx, customer)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to create customer via mobile", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"email": form.Email,
|
||||
"type": form.Type,
|
||||
"created_by": createdBy,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.logger.Info("Customer created via mobile successfully", map[string]interface{}{
|
||||
"customer_id": customer.ID,
|
||||
"email": customer.Email,
|
||||
"type": customer.Type,
|
||||
"company_ids": companyIDs,
|
||||
"created_by": *createdBy,
|
||||
})
|
||||
|
||||
return customer, nil
|
||||
}
|
||||
|
||||
// UpdateCustomerMobile updates a customer via mobile app (simplified)
|
||||
func (s *customerService) UpdateCustomerMobile(ctx context.Context, id string, form *UpdateCustomerMobileForm, updatedBy *string) (*Customer, error) {
|
||||
// Get existing customer
|
||||
customer, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, errors.New("customer not found")
|
||||
}
|
||||
|
||||
// Update fields if provided
|
||||
if form.FirstName != nil {
|
||||
customer.FirstName = form.FirstName
|
||||
}
|
||||
|
||||
if form.LastName != nil {
|
||||
customer.LastName = form.LastName
|
||||
}
|
||||
|
||||
if form.FullName != nil {
|
||||
customer.FullName = form.FullName
|
||||
}
|
||||
|
||||
if form.Phone != nil {
|
||||
customer.Phone = form.Phone
|
||||
}
|
||||
|
||||
if form.Mobile != nil {
|
||||
customer.Mobile = form.Mobile
|
||||
}
|
||||
|
||||
// Handle company assignments
|
||||
if len(form.CompanyIDs) > 0 {
|
||||
// Verify that all company IDs exist
|
||||
var validCompanyIDs []string
|
||||
for _, companyID := range form.CompanyIDs {
|
||||
_, err := s.companyService.GetCompanyByID(ctx, companyID)
|
||||
if err != nil {
|
||||
s.logger.Error("Invalid company ID provided in mobile update", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"company_id": companyID,
|
||||
})
|
||||
return nil, errors.New("invalid company ID: " + companyID)
|
||||
}
|
||||
validCompanyIDs = append(validCompanyIDs, companyID)
|
||||
}
|
||||
customer.CompanyIDs = validCompanyIDs
|
||||
}
|
||||
|
||||
if form.Industry != nil {
|
||||
customer.Industry = form.Industry
|
||||
}
|
||||
|
||||
// Set updated by
|
||||
customer.UpdatedBy = updatedBy
|
||||
|
||||
// Update in database
|
||||
err = s.repository.Update(ctx, customer)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to update customer via mobile", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": id,
|
||||
})
|
||||
return nil, errors.New("failed to update customer")
|
||||
}
|
||||
|
||||
s.logger.Info("Customer updated via mobile successfully", map[string]interface{}{
|
||||
"customer_id": customer.ID,
|
||||
"email": customer.Email,
|
||||
"company_ids": customer.CompanyIDs,
|
||||
"updated_by": *updatedBy,
|
||||
})
|
||||
|
||||
return customer, nil
|
||||
}
|
||||
|
||||
// VerifyCustomer verifies a customer
|
||||
func (s *customerService) VerifyCustomer(ctx context.Context, id string, verifiedBy *string) error {
|
||||
// Get customer to check if exists
|
||||
customer, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return errors.New("customer not found")
|
||||
}
|
||||
|
||||
// Update verification
|
||||
err = s.repository.UpdateVerification(ctx, id, true, customer.IsCompliant, customer.ComplianceNotes)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to verify customer", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": id,
|
||||
})
|
||||
return errors.New("failed to verify customer")
|
||||
}
|
||||
|
||||
s.logger.Info("Customer verified successfully", map[string]interface{}{
|
||||
"customer_id": id,
|
||||
"verified_by": verifiedBy,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SuspendCustomer suspends a customer
|
||||
func (s *customerService) SuspendCustomer(ctx context.Context, id string, reason string, suspendedBy *string) error {
|
||||
// Suspend suspends a customer
|
||||
func (s *customerService) Suspend(ctx context.Context, id string, reason string) error {
|
||||
// Get customer to check if exists
|
||||
_, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
@@ -896,16 +413,15 @@ func (s *customerService) SuspendCustomer(ctx context.Context, id string, reason
|
||||
}
|
||||
|
||||
s.logger.Info("Customer suspended successfully", map[string]interface{}{
|
||||
"customer_id": id,
|
||||
"reason": reason,
|
||||
"suspended_by": suspendedBy,
|
||||
"customer_id": id,
|
||||
"reason": reason,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ActivateCustomer activates a customer
|
||||
func (s *customerService) ActivateCustomer(ctx context.Context, id string, activatedBy *string) error {
|
||||
// Activate activates a customer
|
||||
func (s *customerService) Activate(ctx context.Context, id string) error {
|
||||
// Get customer to check if exists
|
||||
_, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
@@ -923,8 +439,7 @@ func (s *customerService) ActivateCustomer(ctx context.Context, id string, activ
|
||||
}
|
||||
|
||||
s.logger.Info("Customer activated successfully", map[string]interface{}{
|
||||
"customer_id": id,
|
||||
"activated_by": activatedBy,
|
||||
"customer_id": id,
|
||||
})
|
||||
|
||||
return nil
|
||||
@@ -985,6 +500,22 @@ func (s *customerService) Login(ctx context.Context, form *LoginForm) (*AuthResp
|
||||
return nil, errors.New("failed to generate authentication tokens")
|
||||
}
|
||||
|
||||
err = s.repository.Login(ctx, customer.ID.Hex())
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to update customer last login", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": customer.ID,
|
||||
})
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
s.logger.Info("Customer login successful", map[string]interface{}{
|
||||
"username": form.Username,
|
||||
"customer_id": customer.ID,
|
||||
@@ -992,7 +523,7 @@ func (s *customerService) Login(ctx context.Context, form *LoginForm) (*AuthResp
|
||||
})
|
||||
|
||||
return &AuthResponse{
|
||||
Customer: customer.ToResponse(),
|
||||
Customer: customer.ToResponse(companies),
|
||||
AccessToken: tokenPair.AccessToken,
|
||||
RefreshToken: tokenPair.RefreshToken,
|
||||
ExpiresAt: time.Now().Add(15 * time.Minute).Unix(), // 15 minutes from now
|
||||
@@ -1053,13 +584,21 @@ func (s *customerService) RefreshToken(ctx context.Context, refreshToken string)
|
||||
return nil, errors.New("customer account is inactive")
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
s.logger.Info("Access token refreshed successfully", map[string]interface{}{
|
||||
"customer_id": customer.ID,
|
||||
"customer_type": string(customer.Type),
|
||||
})
|
||||
|
||||
return &AuthResponse{
|
||||
Customer: customer.ToResponse(),
|
||||
Customer: customer.ToResponse(companies),
|
||||
AccessToken: newAccessToken,
|
||||
RefreshToken: refreshToken, // Return the same refresh token
|
||||
ExpiresAt: time.Now().Add(15 * time.Minute).Unix(), // 15 minutes from now
|
||||
@@ -1115,7 +654,7 @@ func (s *customerService) GetProfileWithCompanies(ctx context.Context, customerI
|
||||
companies = []*CompanySummary{}
|
||||
}
|
||||
|
||||
customerResponse := customer.ToResponseWithCompanies(companies)
|
||||
customerResponse := customer.ToResponse(companies)
|
||||
|
||||
s.logger.Info("Customer profile retrieved with companies successfully", map[string]interface{}{
|
||||
"customer_id": customerID,
|
||||
@@ -1149,61 +688,6 @@ func (s *customerService) Logout(ctx context.Context, customerID string, accessT
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper methods for converting forms to domain objects
|
||||
func (s *customerService) convertAddressForm(form *AddressForm) *Address {
|
||||
if form == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Address{
|
||||
Street: form.Street,
|
||||
City: form.City,
|
||||
State: form.State,
|
||||
PostalCode: form.PostalCode,
|
||||
Country: form.Country,
|
||||
AddressType: form.AddressType,
|
||||
IsDefault: form.IsDefault,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *customerService) convertContactPersonForm(form *ContactPersonForm) *ContactPerson {
|
||||
if form == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &ContactPerson{
|
||||
FirstName: form.FirstName,
|
||||
LastName: form.LastName,
|
||||
FullName: form.FullName,
|
||||
Position: form.Position,
|
||||
Email: form.Email,
|
||||
Phone: form.Phone,
|
||||
Mobile: form.Mobile,
|
||||
IsPrimary: form.IsPrimary,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *customerService) getDefaultLanguage(language *string) string {
|
||||
if language != nil {
|
||||
return *language
|
||||
}
|
||||
return "en"
|
||||
}
|
||||
|
||||
func (s *customerService) getDefaultCurrency(currency *string) string {
|
||||
if currency != nil {
|
||||
return *currency
|
||||
}
|
||||
return "USD"
|
||||
}
|
||||
|
||||
func (s *customerService) getDefaultTimezone(timezone *string) string {
|
||||
if timezone != nil {
|
||||
return *timezone
|
||||
}
|
||||
return "UTC"
|
||||
}
|
||||
|
||||
// loadCompaniesForCustomer loads company summaries for a customer
|
||||
func (s *customerService) loadCompaniesForCustomer(ctx context.Context, customer *Customer) ([]*CompanySummary, error) {
|
||||
if len(customer.CompanyIDs) == 0 {
|
||||
|
||||
Reference in New Issue
Block a user