@@ -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); | |||||