Skip to main content
Duit orchestrates the complete lifecycle of service engagements, from user registration through job completion and mutual ratings.

User registration

New users create accounts by providing their basic information and selecting their role on the platform.

Account creation

Users register with the following information:
  • Name: First name (2-100 characters) and last name (up to 150 characters)
  • Email: Used as username for authentication
  • Password: Securely encrypted with BCrypt
  • DNI: Optional Spanish national ID (format: 8 digits + letter)
  • Phone: Optional contact number
  • Address: Optional default service location
@NotBlank(message = "El correo electrónico es obligatorio")
@Email(message = "El correo electrónico debe tener un formato válido")
@Column(name = "username", length = 100, nullable = false, unique = true)
private String username;

Role assignment

During registration, users are assigned a role that determines their capabilities:

USER (Client)

Can create service requests and hire professionals

PROFESSIONAL

Can create a professional profile and apply to service requests
Users can later upgrade from USER to PROFESSIONAL by creating a professional profile.

Security measures

  • Passwords are hashed using BCrypt before storage
  • Spring Security manages authentication and session handling
  • User accounts can be deactivated but not deleted (for data integrity)
@Column(name = "active", nullable = false)
private Boolean active = true;

Professional profile creation

Users who want to offer services create a professional profile linked to their account.

Setting up a professional profile

1

Provide credentials

Enter NIF (tax identification number) required for legal compliance
2

Write description

Create a professional description (50-2000 characters) highlighting skills and experience
3

Set pricing

Define hourly rate between €5.00 and €500.00
4

Select categories

Choose service categories that match your expertise

Profile validation

The platform enforces strict validation to ensure quality:
@NotBlank(message = "La descripción profesional es obligatoria")
@Size(min = 50, max = 2000)
@Column(name = "description", columnDefinition = "TEXT", nullable = false)
private String description;
A complete and detailed professional profile increases visibility and trust with potential clients.

Category association

Professionals can select multiple categories, stored as a many-to-many relationship:
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
    name = "professional_category",
    joinColumns = @JoinColumn(name = "id_professional"),
    inverseJoinColumns = @JoinColumn(name = "id_category")
)
private Set<Category> categories = new HashSet<>();
This enables clients to find professionals by searching specific service categories.

Service request workflow

Clients follow a structured process to post service requests and receive applications.

Step 1: Creating the request

Clients access the request form at /requests/request and provide:
1

Basic information

Title, detailed description, and service category
2

Location

Choose default address or specify custom service location
3

Timeline

Optionally set a deadline for service completion
4

Publication

Save as draft or publish immediately

Request validation

The system validates all inputs before saving:
@NotBlank(message = "La descripción es obligatoria")
@Size(min = 20, max = 2000)
@Column(name = "description", columnDefinition = "TEXT", nullable = false)
private String description;

Step 2: Publishing

Requests can be saved in two states:
  • DRAFT: Visible only to the creator, can be edited freely
  • PUBLISHED: Visible to all professionals, accepting applications
public enum Status {
    DRAFT, PUBLISHED, IN_PROGRESS, COMPLETED, CANCELLED, EXPIRED
}
Only PUBLISHED requests appear in professional searches and receive applications.

Step 3: Receiving applications

Once published, professionals can discover the request and submit applications. The client receives notifications and can view all applications at /jobs/applications/{requestId}.

Application process

Professionals apply to service requests they’re qualified and interested in completing.

Finding requests

Professionals browse published service requests, filtered by:
  • Service category
  • Location
  • Status (only PUBLISHED requests accept applications)
  • Deadline

Submitting an application

When applying, professionals provide:

Proposed price

Quote for completing the service (required)

Cover message

Optional message explaining qualifications (up to 1000 characters)
@DecimalMin(value = "0.00", message = "El precio propuesto debe ser positivo")
@Column(name = "proposed_price", precision = 8, scale = 2)
private BigDecimal proposedPrice;
Applications are created with PENDING status and a timestamp:
@PrePersist
protected void onCreate() {
    if (appliedAt == null) {
        appliedAt = LocalDateTime.now();
    }
}

Application review

Clients review all applications on a single page, comparing:
  • Professional profiles and ratings
  • Proposed prices
  • Cover messages
  • Application timestamps

Accepting an application

When a client accepts an application:
1

Application accepted

Selected application status changes to ACCEPTED
2

Others rejected

All other pending applications automatically change to REJECTED
3

Job created

A new ServiceJob is created with the agreed price
4

Request in progress

Service request status changes to IN_PROGRESS
// From PostulacionesController.java:72
@PostMapping("/aceptar/{postulacionId}")
public String aceptarPostulacionEspecifica(@PathVariable Long postulacionId,
        Authentication auth,
        RedirectAttributes redirectAttributes) {
    // ...
    jobService.acceptApplication(postulacionId, usuarioLogueado);
    // ...
}

Job lifecycle

Once an application is accepted, the ServiceJob becomes the central entity for work tracking.

Job initialization

A new job is created with:
  • Agreed price: From the accepted application’s proposed price
  • Status: CREATED
  • Start date: Automatically set to current timestamp
  • Linked entities: References to both the request and accepted application
@PrePersist
protected void onCreate() {
    if (startDate == null) {
        startDate = LocalDateTime.now();
    }
}

Job progression

The professional manages job status through their dashboard:
1

Start work

Change status from CREATED to IN_PROGRESS
2

Pause if needed

Temporarily pause work (status: PAUSED)
3

Resume work

Continue after pause (back to IN_PROGRESS)
4

Complete job

Mark as COMPLETED when work is finished

Job management endpoints

// From PostulacionesController.java
@PostMapping("/iniciar/{jobId}")  // Start job
@PostMapping("/pausar/{jobId}")   // Pause job
@PostMapping("/reanudar/{jobId}") // Resume job
@PostMapping("/finalizar/{jobId}") // Complete job
@PostMapping("/cancelar/{jobId}") // Cancel job

Job cancellation

Both parties can cancel a job if needed:
  • Professional cancels via their applications dashboard
  • Client cancels via their requests dashboard
Cancelled jobs cannot be reactivated. A new application must be accepted to create a replacement job.

Job completion

When a professional marks a job as completed, the system prepares for the rating phase.

Completion process

1

Professional completes

Professional clicks Finish on the job
2

Status updated

Job status changes to COMPLETED, end_date is set
3

Redirect to ratings

Professional is redirected to the ratings page
4

Client notified

Client sees the completed job in their dashboard
@PostMapping("/finalizar/{jobId}")
public String finalizarTrabajoCompletado(@PathVariable Long jobId,
        Authentication auth,
        RedirectAttributes redirectAttributes) {
    // ...
    jobService.completeJob(jobId, usuarioLogueado);
    redirectAttributes.addFlashAttribute("success", "Trabajo finalizado. Procede a valorar.");
    return "redirect:/shared/ratings?jobId=" + jobId;
}

ServiceJob completed state

public boolean isCompleted() {
    return status == Status.COMPLETED;
}
Only completed jobs can receive ratings, preventing premature or fraudulent reviews.

Rating and reputation

After job completion, both parties rate their experience, building reputation on the platform.

Creating ratings

Each completed job generates two rating opportunities:

Client rates professional

Type: CLIENT_TO_PROFESSIONAL

Professional rates client

Type: PROFESSIONAL_TO_CLIENT

Rating structure

Each rating includes:
@NotNull
@Min(value = 1, message = "La puntuación mínima es 1")
@Max(value = 5, message = "La puntuación máxima es 5")
@Column(name = "score", nullable = false)
private Integer score;

@Size(max = 500, message = "El comentario no puede exceder los 500 caracteres")
@Column(name = "comment", length = 500)
private String comment;

Rating lifecycle

1

Rating created

User submits rating with score and optional comment (status: PENDING)
2

Admin review

Optionally reviewed by moderators for inappropriate content
3

Published

Rating becomes visible on the recipient’s profile (status: PUBLISHED)
4

Hidden if needed

Moderators can hide ratings that violate guidelines (status: HIDDEN)

Rating visibility

Published ratings appear on professional profiles, helping clients make informed hiring decisions:
public enum Status {
    PENDING, PUBLISHED, HIDDEN
}

Sender and receiver identification

The system tracks who sent and received each rating:
public AppUser getSender() {
    if (job == null) return null;
    return type == Type.CLIENT_TO_PROFESSIONAL 
        ? job.getRequest().getClient()
        : job.getApplication().getProfessional().getUser();
}

public AppUser getReceiver() {
    if (job == null) return null;
    return type == Type.CLIENT_TO_PROFESSIONAL 
        ? job.getApplication().getProfessional().getUser()
        : job.getRequest().getClient();
}

Positive rating threshold

Ratings with 4 or 5 stars are marked as positive:
public boolean isPositive() {
    return score >= 4;
}
Encouraging both parties to rate honestly builds trust throughout the platform and helps maintain service quality.

Complete workflow summary

Here’s how all the pieces connect in a typical service engagement:
1

Client posts request

Client creates and publishes a service request with details and location
2

Professionals apply

Multiple professionals submit applications with proposed prices
3

Client selects professional

Client reviews applications and accepts one, creating a ServiceJob
4

Work begins

Professional starts the job and updates status as work progresses
5

Job completed

Professional marks job as completed when work is finished
6

Mutual ratings

Both parties rate their experience, building platform reputation
This workflow ensures transparency, accountability, and trust throughout every service engagement on Duit.

Build docs developers (and LLMs) love