| @@ -45,5 +45,5 @@ This project can also be run using gradle. | |||||
| ### Running the application | ### Running the application | ||||
| After creating the table in MySQL, run | After creating the table in MySQL, run | ||||
| ```shell | ```shell | ||||
| SPRING_PROFILES_ACTIVE=db-local,ldap-local ./gradlew bootRun | |||||
| ./gradlew bootRun --args='--spring.profiles.active=db-local' | |||||
| ``` | ``` | ||||
| @@ -0,0 +1,20 @@ | |||||
| package com.ffii.tsms.modules.data.service | |||||
| import com.ffii.tsms.modules.data.entity.Customer | |||||
| import com.ffii.tsms.modules.data.entity.CustomerRepository | |||||
| import org.springframework.stereotype.Service | |||||
| @Service | |||||
| class CustomerService(private val customerRepository: CustomerRepository) { | |||||
| fun saveCustomer(name: String, code: String, email: String, phone: String, contactName: String): Customer { | |||||
| return customerRepository.save( | |||||
| Customer().apply { | |||||
| this.name = name | |||||
| this.email = email | |||||
| this.phone = phone | |||||
| this.code = code | |||||
| this.contactName = contactName | |||||
| } | |||||
| ) | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,13 @@ | |||||
| package com.ffii.tsms.modules.data.service | |||||
| import com.ffii.tsms.modules.data.entity.Staff | |||||
| import com.ffii.tsms.modules.data.entity.StaffRepository | |||||
| import org.springframework.stereotype.Service | |||||
| @Service | |||||
| class StaffsService(private val staffRepository: StaffRepository) { | |||||
| fun getTeamLeads(): List<Staff> { | |||||
| // TODO: Replace with actual logic and make a projection instead of returning all fields | |||||
| return staffRepository.findAll() | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,16 @@ | |||||
| package com.ffii.tsms.modules.data.web | |||||
| import com.ffii.tsms.modules.data.entity.Staff | |||||
| import com.ffii.tsms.modules.data.service.StaffsService | |||||
| import org.springframework.web.bind.annotation.GetMapping | |||||
| import org.springframework.web.bind.annotation.RequestMapping | |||||
| import org.springframework.web.bind.annotation.RestController | |||||
| @RestController | |||||
| @RequestMapping("/staffs") | |||||
| class StaffsController(private val staffsService: StaffsService) { | |||||
| @GetMapping("/teamLeads") | |||||
| fun teamLeads(): List<Staff> { | |||||
| return staffsService.getTeamLeads() | |||||
| } | |||||
| } | |||||
| @@ -3,12 +3,13 @@ package com.ffii.tsms.modules.project.entity | |||||
| import com.ffii.core.entity.IdEntity | import com.ffii.core.entity.IdEntity | ||||
| import jakarta.persistence.Column | import jakarta.persistence.Column | ||||
| import jakarta.persistence.Entity | import jakarta.persistence.Entity | ||||
| import jakarta.persistence.OneToMany | |||||
| import jakarta.persistence.Table | import jakarta.persistence.Table | ||||
| import jakarta.validation.constraints.NotNull | import jakarta.validation.constraints.NotNull | ||||
| @Entity | @Entity | ||||
| @Table(name = "milestone") | @Table(name = "milestone") | ||||
| open class PaymentMilestone : IdEntity<Long>() { | |||||
| open class Milestone : IdEntity<Long>() { | |||||
| @NotNull | @NotNull | ||||
| @Column(name = "name") | @Column(name = "name") | ||||
| open var name: String? = null | open var name: String? = null | ||||
| @@ -16,4 +17,7 @@ open class PaymentMilestone : IdEntity<Long>() { | |||||
| @NotNull | @NotNull | ||||
| @Column(name = "description") | @Column(name = "description") | ||||
| open var description: String? = null | open var description: String? = null | ||||
| @OneToMany(mappedBy = "milestone", orphanRemoval = true) | |||||
| open var milestonePayments: MutableList<MilestonePayment> = mutableListOf() | |||||
| } | } | ||||
| @@ -0,0 +1,26 @@ | |||||
| package com.ffii.tsms.modules.project.entity | |||||
| import com.ffii.core.entity.IdEntity | |||||
| import jakarta.persistence.* | |||||
| import jakarta.validation.constraints.NotNull | |||||
| import java.time.LocalDate | |||||
| @Entity | |||||
| @Table(name = "milestone_payment") | |||||
| open class MilestonePayment : IdEntity<Long>() { | |||||
| @NotNull | |||||
| @Column(name = "date") | |||||
| open var date: LocalDate? = null | |||||
| @NotNull | |||||
| @Column(name = "amount") | |||||
| open var amount: Double? = null | |||||
| @NotNull | |||||
| @Column(name = "description") | |||||
| open var description: String? = null | |||||
| @ManyToOne | |||||
| @JoinColumn(name = "milestone_id") | |||||
| open var milestone: Milestone? = null | |||||
| } | |||||
| @@ -2,5 +2,5 @@ package com.ffii.tsms.modules.project.entity; | |||||
| import com.ffii.core.support.AbstractRepository | import com.ffii.core.support.AbstractRepository | ||||
| interface PaymentMilestoneRepository : AbstractRepository<PaymentMilestone, Long> { | |||||
| interface MilestoneRepository : AbstractRepository<Milestone, Long> { | |||||
| } | } | ||||
| @@ -18,6 +18,14 @@ open class Project : BaseEntity<Long>() { | |||||
| @Column(name = "description") | @Column(name = "description") | ||||
| open var description: String? = null | open var description: String? = null | ||||
| @NotNull | |||||
| @Column(name = "code", length = 30) | |||||
| open var code: String? = null | |||||
| @ManyToOne | |||||
| @JoinColumn(name = "projectCategoryId") | |||||
| open var projectCategory: ProjectCategory? = null | |||||
| @Column(name = "planStart") | @Column(name = "planStart") | ||||
| open var planStart: LocalDate? = null | open var planStart: LocalDate? = null | ||||
| @@ -0,0 +1,15 @@ | |||||
| package com.ffii.tsms.modules.project.entity | |||||
| import com.ffii.core.entity.IdEntity | |||||
| import jakarta.persistence.Column | |||||
| import jakarta.persistence.Entity | |||||
| import jakarta.persistence.Table | |||||
| import jakarta.validation.constraints.NotNull | |||||
| @Entity | |||||
| @Table(name = "project_category") | |||||
| open class ProjectCategory : IdEntity<Long>() { | |||||
| @NotNull | |||||
| @Column(name = "name") | |||||
| open var name: String? = null | |||||
| } | |||||
| @@ -0,0 +1,6 @@ | |||||
| package com.ffii.tsms.modules.project.entity; | |||||
| import com.ffii.core.support.AbstractRepository | |||||
| interface ProjectCategoryRepository : AbstractRepository<ProjectCategory, Long> { | |||||
| } | |||||
| @@ -13,7 +13,7 @@ open class ProjectTask : IdEntity<Long>() { | |||||
| @ManyToOne | @ManyToOne | ||||
| @JoinColumn(name = "milestoneId") | @JoinColumn(name = "milestoneId") | ||||
| open var paymentMilestone: PaymentMilestone? = null | |||||
| open var milestone: Milestone? = null | |||||
| @NotNull | @NotNull | ||||
| @ManyToOne | @ManyToOne | ||||
| @@ -0,0 +1,43 @@ | |||||
| package com.ffii.tsms.modules.project.service | |||||
| import com.ffii.tsms.modules.data.entity.StaffRepository | |||||
| import com.ffii.tsms.modules.data.service.CustomerService | |||||
| import com.ffii.tsms.modules.project.entity.Project | |||||
| import com.ffii.tsms.modules.project.entity.ProjectCategory | |||||
| import com.ffii.tsms.modules.project.entity.ProjectCategoryRepository | |||||
| import com.ffii.tsms.modules.project.entity.ProjectRepository | |||||
| import com.ffii.tsms.modules.project.web.models.NewProjectRequest | |||||
| import org.springframework.stereotype.Service | |||||
| @Service | |||||
| class ProjectsService( | |||||
| private val projectRepository: ProjectRepository, | |||||
| private val customerService: CustomerService, private val projectCategoryRepository: ProjectCategoryRepository, | |||||
| private val staffRepository: StaffRepository | |||||
| ) { | |||||
| fun allProjects(): List<Project> { | |||||
| return projectRepository.findAll() | |||||
| } | |||||
| fun allProjectCategories(): List<ProjectCategory> { | |||||
| return projectCategoryRepository.findAll() | |||||
| } | |||||
| fun saveProject(request: NewProjectRequest): Project { | |||||
| val projectCategory = projectCategoryRepository.findById(request.projectCategoryId).orElseThrow() | |||||
| val teamLead = staffRepository.findById(request.projectLeadId).orElseThrow() | |||||
| val customer = customerService.saveCustomer(request.clientName, request.clientCode, request.clientEmail, request.clientPhone, request.clientContactName) | |||||
| // TODO: Add tasks, milestones, allocated | |||||
| val project = Project().apply { | |||||
| name = request.projectName | |||||
| description = request.projectDescription | |||||
| code = request.projectCode | |||||
| this.projectCategory = projectCategory | |||||
| this.teamLead = teamLead | |||||
| this.customer = customer | |||||
| } | |||||
| return projectRepository.save(project) | |||||
| } | |||||
| } | |||||
| @@ -1,14 +1,27 @@ | |||||
| package com.ffii.tsms.modules.project.web | package com.ffii.tsms.modules.project.web | ||||
| import org.springframework.web.bind.annotation.GetMapping | |||||
| import org.springframework.web.bind.annotation.RequestMapping | |||||
| import org.springframework.web.bind.annotation.RestController | |||||
| import com.ffii.tsms.modules.project.entity.Project | |||||
| import com.ffii.tsms.modules.project.entity.ProjectCategory | |||||
| import com.ffii.tsms.modules.project.service.ProjectsService | |||||
| import com.ffii.tsms.modules.project.web.models.NewProjectRequest | |||||
| import jakarta.validation.Valid | |||||
| import org.springframework.web.bind.annotation.* | |||||
| @RestController | @RestController | ||||
| @RequestMapping("/projects") | @RequestMapping("/projects") | ||||
| class ProjectsController { | |||||
| class ProjectsController(private val projectsService: ProjectsService) { | |||||
| @GetMapping | @GetMapping | ||||
| fun allProjects() { | |||||
| fun allProjects(): List<Project> { | |||||
| return projectsService.allProjects() | |||||
| } | |||||
| @GetMapping("/categories") | |||||
| fun projectCategories(): List<ProjectCategory> { | |||||
| return projectsService.allProjectCategories() | |||||
| } | |||||
| @PostMapping("/new") | |||||
| fun saveProject(@Valid @RequestBody newProject: NewProjectRequest): Project { | |||||
| return projectsService.saveProject(newProject) | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,44 @@ | |||||
| package com.ffii.tsms.modules.project.web.models | |||||
| import jakarta.validation.constraints.NotBlank | |||||
| data class NewProjectRequest( | |||||
| @field:NotBlank(message = "project code cannot be empty") | |||||
| val projectCode: String, | |||||
| @field:NotBlank(message = "project name cannot be empty") | |||||
| val projectName: String, | |||||
| val projectCategoryId: Long, | |||||
| val projectDescription: String, | |||||
| val projectLeadId: Long, | |||||
| val clientCode: String, | |||||
| val clientName: String, | |||||
| val clientContactName: String, | |||||
| val clientPhone: String, | |||||
| val clientEmail: String, | |||||
| val clientSubsidiary: String, | |||||
| val tasks: Map<Long, TaskAllocation>, | |||||
| val allocatedStaffIds: List<Long>, | |||||
| val milestones: Map<Long, Milestone> | |||||
| ) | |||||
| data class TaskAllocation( | |||||
| val manhourAllocation: Map<Long, Double> | |||||
| ) | |||||
| data class Milestone( | |||||
| val startDate: String?, | |||||
| val endDate: String?, | |||||
| val payments: List<PaymentInputs> | |||||
| ) | |||||
| data class PaymentInputs( | |||||
| val id: Long, | |||||
| val description: String, | |||||
| val date: String, | |||||
| val amount: Double | |||||
| ) | |||||
| @@ -0,0 +1,41 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset wayne:salary_data | |||||
| INSERT INTO salary (salaryPoint, lowerLimit, upperLimit, increment) VALUES | |||||
| (36, 170001, 180000, 10000), | |||||
| (35, 160001, 170000, 10000), | |||||
| (34, 150001, 160000, 10000), | |||||
| (33, 140001, 150000, 10000), | |||||
| (32, 130001, 140000, 10000), | |||||
| (31, 120001, 130000, 10000), | |||||
| (30, 110001, 120000, 10000), | |||||
| (29, 100001, 110000, 10000), | |||||
| (28, 90001, 100000, 10000), | |||||
| (27, 80001, 90000, 10000), | |||||
| (26, 70001, 80000, 10000), | |||||
| (25, 68001, 70000, 2000), | |||||
| (24, 66001, 68000, 2000), | |||||
| (23, 64001, 66000, 2000), | |||||
| (22, 62001, 64000, 2000), | |||||
| (21, 60001, 62000, 2000), | |||||
| (20, 58001, 60000, 2000), | |||||
| (19, 56001, 58000, 2000), | |||||
| (18, 54001, 56000, 2000), | |||||
| (17, 52001, 54000, 2000), | |||||
| (16, 50001, 52000, 2000), | |||||
| (15, 47001, 50000, 3000), | |||||
| (14, 44001, 47000, 3000), | |||||
| (13, 41001, 44000, 3000), | |||||
| (12, 38001, 41000, 3000), | |||||
| (11, 35001, 38000, 3000), | |||||
| (10, 33001, 35000, 2000), | |||||
| (9, 31001, 33000, 2000), | |||||
| (8, 29001, 31000, 2000), | |||||
| (7, 27001, 29000, 2000), | |||||
| (6, 25001, 27000, 2000), | |||||
| (5, 23001, 25000, 2000), | |||||
| (4, 21001, 23000, 2000), | |||||
| (3, 19001, 21000, 2000), | |||||
| (2, 17001, 19000, 2000), | |||||
| (1, 15001, 17000, 2000); | |||||
| @@ -0,0 +1,35 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset wayne:mock_team_leads | |||||
| INSERT INTO `user` (name, username, password) VALUES | |||||
| ('Wayne Lee','wlee','$2a$10$65S7/AhKn8MldlYmvFN5JOfr1yaULwFNDIhTskLTuUCKgbbs8sFAi'), | |||||
| ('Ming Chan','mchan','$2a$10$65S7/AhKn8MldlYmvFN5JOfr1yaULwFNDIhTskLTuUCKgbbs8sFAi'); | |||||
| INSERT INTO company (companyCode, name) VALUES | |||||
| ('ABC', 'Company ABC'); | |||||
| INSERT INTO team (name, code) VALUES | |||||
| ('Team Wayne Lee', 'WL'), | |||||
| ('Team Ming Chan', 'MC'); | |||||
| INSERT INTO salary_effective (date, salaryId) VALUES | |||||
| (current_date, 1); | |||||
| INSERT INTO staff (userId, name, staffId, companyId, teamId, salaryEffId) VALUES | |||||
| ( | |||||
| (SELECT id from `user` where username = 'wlee'), | |||||
| 'Wayne Lee', | |||||
| '001', | |||||
| (SELECT id from company where companyCode = 'ABC'), | |||||
| (SELECT id from team where code = 'WL'), | |||||
| (SELECT id from salary_effective where salaryId = 1) | |||||
| ), | |||||
| ( | |||||
| (SELECT id from `user` where username = 'mchan'), | |||||
| 'Ming Chan', | |||||
| '002', | |||||
| (SELECT id from company where companyCode = 'ABC'), | |||||
| (SELECT id from team where code = 'MC'), | |||||
| (SELECT id from salary_effective where salaryId = 1) | |||||
| ); | |||||
| @@ -0,0 +1,12 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset wayne:project_category | |||||
| CREATE TABLE project_category ( | |||||
| id INT NOT NULL AUTO_INCREMENT, | |||||
| name VARCHAR(255) NOT NULL, | |||||
| CONSTRAINT pk_project_category PRIMARY KEY (id) | |||||
| ); | |||||
| INSERT INTO project_category (name) VALUES | |||||
| ('Project to be bidded'), | |||||
| ('Confirmed Project'); | |||||
| @@ -0,0 +1,7 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset wayne:update_project | |||||
| ALTER TABLE project ADD code VARCHAR(30) NOT NULL; | |||||
| ALTER TABLE project ADD projectCategoryId INT NULL; | |||||
| ALTER TABLE project ADD CONSTRAINT FK_PROJECT_ON_PROJECTCATEGORYID FOREIGN KEY (projectCategoryId) REFERENCES project_category (id); | |||||