diff --git a/backend/internal/dto/requests.go b/backend/internal/dto/requests.go index 35c786d..ae5b863 100755 --- a/backend/internal/dto/requests.go +++ b/backend/internal/dto/requests.go @@ -2,43 +2,45 @@ package dto // CreateJobRequest represents the request to create a new job type CreateJobRequest struct { - CompanyID string `json:"companyId" validate:"required"` - Title string `json:"title" validate:"required,min=5,max=255"` - Description string `json:"description" validate:"required,min=20"` - SalaryMin *float64 `json:"salaryMin,omitempty"` - SalaryMax *float64 `json:"salaryMax,omitempty"` - SalaryType *string `json:"salaryType,omitempty" validate:"omitempty,oneof=hourly daily weekly monthly yearly"` - Currency *string `json:"currency,omitempty" validate:"omitempty,oneof=BRL USD EUR GBP JPY"` - EmploymentType *string `json:"employmentType,omitempty" validate:"omitempty,oneof=full-time part-time dispatch contract temporary training voluntary permanent"` - WorkingHours *string `json:"workingHours,omitempty"` - Location *string `json:"location,omitempty"` - RegionID *int `json:"regionId,omitempty"` - CityID *int `json:"cityId,omitempty"` - Requirements map[string]interface{} `json:"requirements,omitempty"` - Benefits map[string]interface{} `json:"benefits,omitempty"` - VisaSupport bool `json:"visaSupport"` - LanguageLevel *string `json:"languageLevel,omitempty"` - Status string `json:"status" validate:"oneof=draft open closed review published paused expired archived reported"` + CompanyID string `json:"companyId" validate:"required"` + Title string `json:"title" validate:"required,min=5,max=255"` + Description string `json:"description" validate:"required,min=20"` + SalaryMin *float64 `json:"salaryMin,omitempty"` + SalaryMax *float64 `json:"salaryMax,omitempty"` + SalaryType *string `json:"salaryType,omitempty" validate:"omitempty,oneof=hourly daily weekly monthly yearly"` + Currency *string `json:"currency,omitempty" validate:"omitempty,oneof=BRL USD EUR GBP JPY"` + SalaryNegotiable bool `json:"salaryNegotiable"` + EmploymentType *string `json:"employmentType,omitempty" validate:"omitempty,oneof=full-time part-time dispatch contract temporary training voluntary permanent"` + WorkingHours *string `json:"workingHours,omitempty"` + Location *string `json:"location,omitempty"` + RegionID *int `json:"regionId,omitempty"` + CityID *int `json:"cityId,omitempty"` + Requirements map[string]interface{} `json:"requirements,omitempty"` + Benefits map[string]interface{} `json:"benefits,omitempty"` + VisaSupport bool `json:"visaSupport"` + LanguageLevel *string `json:"languageLevel,omitempty"` + Status string `json:"status" validate:"oneof=draft open closed review published paused expired archived reported"` } // UpdateJobRequest represents the request to update a job type UpdateJobRequest struct { - Title *string `json:"title,omitempty" validate:"omitempty,min=5,max=255"` - Description *string `json:"description,omitempty" validate:"omitempty,min=20"` - SalaryMin *float64 `json:"salaryMin,omitempty"` - SalaryMax *float64 `json:"salaryMax,omitempty"` - SalaryType *string `json:"salaryType,omitempty" validate:"omitempty,oneof=hourly daily weekly monthly yearly"` - Currency *string `json:"currency,omitempty" validate:"omitempty,oneof=BRL USD EUR GBP JPY"` - EmploymentType *string `json:"employmentType,omitempty" validate:"omitempty,oneof=full-time part-time dispatch contract temporary training voluntary permanent"` - WorkingHours *string `json:"workingHours,omitempty"` - Location *string `json:"location,omitempty"` - RegionID *int `json:"regionId,omitempty"` - CityID *int `json:"cityId,omitempty"` - Requirements map[string]interface{} `json:"requirements,omitempty"` - Benefits map[string]interface{} `json:"benefits,omitempty"` - VisaSupport *bool `json:"visaSupport,omitempty"` - LanguageLevel *string `json:"languageLevel,omitempty"` - Status *string `json:"status,omitempty" validate:"omitempty,oneof=draft open closed review published paused expired archived reported"` + Title *string `json:"title,omitempty" validate:"omitempty,min=5,max=255"` + Description *string `json:"description,omitempty" validate:"omitempty,min=20"` + SalaryMin *float64 `json:"salaryMin,omitempty"` + SalaryMax *float64 `json:"salaryMax,omitempty"` + SalaryType *string `json:"salaryType,omitempty" validate:"omitempty,oneof=hourly daily weekly monthly yearly"` + Currency *string `json:"currency,omitempty" validate:"omitempty,oneof=BRL USD EUR GBP JPY"` + SalaryNegotiable *bool `json:"salaryNegotiable,omitempty"` + EmploymentType *string `json:"employmentType,omitempty" validate:"omitempty,oneof=full-time part-time dispatch contract temporary training voluntary permanent"` + WorkingHours *string `json:"workingHours,omitempty"` + Location *string `json:"location,omitempty"` + RegionID *int `json:"regionId,omitempty"` + CityID *int `json:"cityId,omitempty"` + Requirements map[string]interface{} `json:"requirements,omitempty"` + Benefits map[string]interface{} `json:"benefits,omitempty"` + VisaSupport *bool `json:"visaSupport,omitempty"` + LanguageLevel *string `json:"languageLevel,omitempty"` + Status *string `json:"status,omitempty" validate:"omitempty,oneof=draft open closed review published paused expired archived reported"` } // CreateApplicationRequest represents a job application (guest or logged user) diff --git a/backend/internal/services/job_service.go b/backend/internal/services/job_service.go index 4c8998a..2756d81 100644 --- a/backend/internal/services/job_service.go +++ b/backend/internal/services/job_service.go @@ -26,31 +26,32 @@ func (s *JobService) CreateJob(req dto.CreateJobRequest, createdBy string) (*mod INSERT INTO jobs ( company_id, created_by, title, description, salary_min, salary_max, salary_type, employment_type, working_hours, location, region_id, city_id, - requirements, benefits, visa_support, language_level, status, created_at, updated_at - ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19) + requirements, benefits, visa_support, language_level, status, created_at, updated_at, salary_negotiable + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20) RETURNING id, created_at, updated_at ` job := &models.Job{ - CompanyID: req.CompanyID, - CreatedBy: createdBy, - Title: req.Title, - Description: req.Description, - SalaryMin: req.SalaryMin, - SalaryMax: req.SalaryMax, - SalaryType: req.SalaryType, - EmploymentType: req.EmploymentType, - WorkingHours: req.WorkingHours, - Location: req.Location, - RegionID: req.RegionID, - CityID: req.CityID, - Requirements: req.Requirements, - Benefits: req.Benefits, - VisaSupport: req.VisaSupport, - LanguageLevel: req.LanguageLevel, - Status: req.Status, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), + CompanyID: req.CompanyID, + CreatedBy: createdBy, + Title: req.Title, + Description: req.Description, + SalaryMin: req.SalaryMin, + SalaryMax: req.SalaryMax, + SalaryType: req.SalaryType, + SalaryNegotiable: req.SalaryNegotiable, + EmploymentType: req.EmploymentType, + WorkingHours: req.WorkingHours, + Location: req.Location, + RegionID: req.RegionID, + CityID: req.CityID, + Requirements: req.Requirements, + Benefits: req.Benefits, + VisaSupport: req.VisaSupport, + LanguageLevel: req.LanguageLevel, + Status: req.Status, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), } fmt.Println("[JOB_SERVICE DEBUG] Executing INSERT query...") @@ -60,7 +61,7 @@ func (s *JobService) CreateJob(req dto.CreateJobRequest, createdBy string) (*mod query, job.CompanyID, job.CreatedBy, job.Title, job.Description, job.SalaryMin, job.SalaryMax, job.SalaryType, job.EmploymentType, job.WorkingHours, job.Location, job.RegionID, job.CityID, - job.Requirements, job.Benefits, job.VisaSupport, job.LanguageLevel, job.Status, job.CreatedAt, job.UpdatedAt, + job.Requirements, job.Benefits, job.VisaSupport, job.LanguageLevel, job.Status, job.CreatedAt, job.UpdatedAt, job.SalaryNegotiable, ).Scan(&job.ID, &job.CreatedAt, &job.UpdatedAt) if err != nil { @@ -76,7 +77,7 @@ func (s *JobService) GetJobs(filter dto.JobFilterQuery) ([]models.JobWithCompany baseQuery := ` SELECT j.id, j.company_id, j.title, j.description, j.salary_min, j.salary_max, j.salary_type, - j.employment_type, j.work_mode, j.location, j.status, j.is_featured, j.created_at, j.updated_at, + j.employment_type, j.work_mode, j.working_hours, j.location, j.status, j.salary_negotiable, j.is_featured, j.created_at, j.updated_at, COALESCE(c.name, '') as company_name, c.logo_url as company_logo_url, r.name as region_name, ci.name as city_name FROM jobs j @@ -217,7 +218,7 @@ func (s *JobService) GetJobs(filter dto.JobFilterQuery) ([]models.JobWithCompany var j models.JobWithCompany if err := rows.Scan( &j.ID, &j.CompanyID, &j.Title, &j.Description, &j.SalaryMin, &j.SalaryMax, &j.SalaryType, - &j.EmploymentType, &j.WorkMode, &j.Location, &j.Status, &j.IsFeatured, &j.CreatedAt, &j.UpdatedAt, + &j.EmploymentType, &j.WorkMode, &j.WorkingHours, &j.Location, &j.Status, &j.SalaryNegotiable, &j.IsFeatured, &j.CreatedAt, &j.UpdatedAt, &j.CompanyName, &j.CompanyLogoURL, &j.RegionName, &j.CityName, ); err != nil { return nil, 0, err @@ -238,13 +239,14 @@ func (s *JobService) GetJobByID(id string) (*models.Job, error) { var j models.Job query := ` SELECT id, company_id, title, description, salary_min, salary_max, salary_type, - employment_type, working_hours, location, region_id, city_id, + employment_type, working_hours, location, region_id, city_id, salary_negotiable, requirements, benefits, visa_support, language_level, status, created_at, updated_at FROM jobs WHERE id = $1 ` err := s.DB.QueryRow(query, id).Scan( &j.ID, &j.CompanyID, &j.Title, &j.Description, &j.SalaryMin, &j.SalaryMax, &j.SalaryType, - &j.EmploymentType, &j.WorkingHours, &j.Location, &j.RegionID, &j.CityID, + &j.ID, &j.CompanyID, &j.Title, &j.Description, &j.SalaryMin, &j.SalaryMax, &j.SalaryType, + &j.EmploymentType, &j.WorkingHours, &j.Location, &j.RegionID, &j.CityID, &j.SalaryNegotiable, &j.Requirements, &j.Benefits, &j.VisaSupport, &j.LanguageLevel, &j.Status, &j.CreatedAt, &j.UpdatedAt, ) if err != nil { @@ -274,6 +276,11 @@ func (s *JobService) UpdateJob(id string, req dto.UpdateJobRequest) (*models.Job args = append(args, *req.Status) argId++ } + if req.SalaryNegotiable != nil { + setClauses = append(setClauses, fmt.Sprintf("salary_negotiable = $%d", argId)) + args = append(args, *req.SalaryNegotiable) + argId++ + } if len(setClauses) == 0 { return s.GetJobByID(id) diff --git a/frontend/src/app/post-job/page.tsx b/frontend/src/app/post-job/page.tsx index d11b95d..cc3b2d2 100644 --- a/frontend/src/app/post-job/page.tsx +++ b/frontend/src/app/post-job/page.tsx @@ -65,6 +65,7 @@ export default function PostJobPage() { salaryFixed: "", // For fixed salary mode employmentType: "", workMode: "remote", + workingHours: "", salaryNegotiable: false, // Candidate proposes salary }); @@ -145,6 +146,7 @@ export default function PostJobPage() { salaryMax: job.salaryNegotiable ? null : (salaryMode === 'fixed' ? (job.salaryFixed ? parseInt(job.salaryFixed) : null) : (job.salaryMax ? parseInt(job.salaryMax) : null)), salaryNegotiable: job.salaryNegotiable, employmentType: job.employmentType || null, + workingHours: job.workingHours || null, workMode: job.workMode, status: "pending", // Pending review }), @@ -418,7 +420,7 @@ export default function PostJobPage() { > )} -