diff --git a/core/src/main/java/com/arturjarosz/task/contractor/application/ContractorApplicationService.java b/core/src/main/java/com/arturjarosz/task/contractor/application/ContractorApplicationService.java index b16449af..f88ac4f3 100644 --- a/core/src/main/java/com/arturjarosz/task/contractor/application/ContractorApplicationService.java +++ b/core/src/main/java/com/arturjarosz/task/contractor/application/ContractorApplicationService.java @@ -1,5 +1,6 @@ package com.arturjarosz.task.contractor.application; +import com.arturjarosz.task.dto.ContractorContractorJobsDataDto; import com.arturjarosz.task.dto.ContractorDto; import java.util.List; @@ -34,4 +35,9 @@ public interface ContractorApplicationService { */ List getContractors(); + /** + * Returns Contractor Jobs data for Contractor with given contractorId. + */ + ContractorContractorJobsDataDto getContractorJobsData(Long contractorId); + } diff --git a/core/src/main/java/com/arturjarosz/task/contractor/application/impl/ContractorApplicationServiceImpl.java b/core/src/main/java/com/arturjarosz/task/contractor/application/impl/ContractorApplicationServiceImpl.java index 95ac3313..78908f33 100644 --- a/core/src/main/java/com/arturjarosz/task/contractor/application/impl/ContractorApplicationServiceImpl.java +++ b/core/src/main/java/com/arturjarosz/task/contractor/application/impl/ContractorApplicationServiceImpl.java @@ -2,10 +2,12 @@ import com.arturjarosz.task.contractor.application.ContractorApplicationService; import com.arturjarosz.task.contractor.application.ContractorValidator; +import com.arturjarosz.task.contractor.application.mapper.ContractorContractorJobMapper; import com.arturjarosz.task.contractor.application.mapper.ContractorMapper; import com.arturjarosz.task.contractor.infrastructure.ContractorRepository; import com.arturjarosz.task.contractor.model.ContractorCategory; import com.arturjarosz.task.contractor.query.ContractorQueryService; +import com.arturjarosz.task.dto.ContractorContractorJobsDataDto; import com.arturjarosz.task.dto.ContractorDto; import com.arturjarosz.task.sharedkernel.annotations.ApplicationService; import com.arturjarosz.task.sharedkernel.exceptions.ResourceNotFoundException; @@ -24,6 +26,7 @@ public class ContractorApplicationServiceImpl implements ContractorApplicationSe private final ContractorValidator contractorValidator; private final ContractorMapper contractorMapper; private final ContractorQueryService contractorQueryService; + private final ContractorContractorJobMapper contractorContractorJobMapper; @Transactional @@ -91,4 +94,14 @@ public List getContractors() { numberOfJobsByContractorId.get(contractor.getId()))) .toList(); } + + @Override + public ContractorContractorJobsDataDto getContractorJobsData(Long contractorId) { + LOG.debug("Loading Contractor Jobs data for Contractor with id {}", contractorId); + + this.contractorValidator.validateContractorExistence(contractorId); + var data = this.contractorQueryService.getContractorJobsData(contractorId); + + return this.contractorContractorJobMapper.mapToContractorContractorJobsDataDto(data); + } } diff --git a/core/src/main/java/com/arturjarosz/task/contractor/application/mapper/ContractorContractorJobMapper.java b/core/src/main/java/com/arturjarosz/task/contractor/application/mapper/ContractorContractorJobMapper.java new file mode 100644 index 00000000..145d2a1f --- /dev/null +++ b/core/src/main/java/com/arturjarosz/task/contractor/application/mapper/ContractorContractorJobMapper.java @@ -0,0 +1,72 @@ +package com.arturjarosz.task.contractor.application.mapper; + +import com.arturjarosz.task.configuration.UserProperties; +import com.arturjarosz.task.dto.ContractorContractorJobsDataDto; +import com.arturjarosz.task.dto.FinancialPartialDataDto; +import com.arturjarosz.task.finance.application.TaxCalculator; +import com.arturjarosz.task.finance.application.dto.FinancialValueDto; +import com.arturjarosz.task.finance.model.ContractorContractorJobDto; +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.springframework.beans.factory.annotation.Autowired; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Set; + +@Mapper(uses = {UserProperties.class}, injectionStrategy = InjectionStrategy.CONSTRUCTOR) +public abstract class ContractorContractorJobMapper { + + @Autowired + private UserProperties userProperties; + + private static FinancialPartialDataDto calculateAverage(FinancialValueDto financialSummary, int count) { + var averageSummary = new FinancialPartialDataDto(); + if (count > 0) { + averageSummary.setNetValue(financialSummary.getNetValue() + .divide(BigDecimal.valueOf(count), 2, RoundingMode.HALF_UP) + .doubleValue()); + averageSummary.setGrossValue(financialSummary.getGrossValue() + .divide(BigDecimal.valueOf(count), 2, RoundingMode.HALF_UP) + .doubleValue()); + averageSummary.setVatTax(financialSummary.getVatTax() + .divide(BigDecimal.valueOf(count), 2, RoundingMode.HALF_UP) + .doubleValue()); + averageSummary.setIncomeTax(financialSummary.getIncomeTax() + .divide(BigDecimal.valueOf(count), 2, RoundingMode.HALF_UP) + .doubleValue()); + } else { + averageSummary.setNetValue(0.0); + averageSummary.setGrossValue(0.0); + averageSummary.setIncomeTax(0.0); + averageSummary.setVatTax(0.0); + } + return averageSummary; + } + + public ContractorContractorJobsDataDto mapToContractorContractorJobsDataDto( + Set contractorContractorJobDtos) { + var contractorContractorJobsDataDto = new ContractorContractorJobsDataDto(); + + contractorContractorJobsDataDto.setContractorJobs( + contractorContractorJobDtos.stream().map(ContractorContractorJobDto::contractorJob).toList()); + + var financialSummary = new FinancialValueDto(); + for (ContractorContractorJobDto contractorJobsDataDto : contractorContractorJobDtos) { + var contractorJobFinancialDetails = contractorJobsDataDto.financialData(); + var recalculatedSupplyFinancialDetails = TaxCalculator.recalculateObjectTaxes(contractorJobFinancialDetails, + this.userProperties); + financialSummary.addValues(recalculatedSupplyFinancialDetails); + } + var financialSummaryDto = new FinancialPartialDataDto(contractorContractorJobDtos.size(), + financialSummary.getNetValue().doubleValue(), financialSummary.getGrossValue().doubleValue(), + financialSummary.getVatTax().doubleValue(), financialSummary.getIncomeTax().doubleValue()); + financialSummaryDto.setCount(contractorContractorJobDtos.size()); + contractorContractorJobsDataDto.setFinancialData(financialSummaryDto); + + var averageSummary = calculateAverage(financialSummary, financialSummaryDto.getCount()); + contractorContractorJobsDataDto.setAverageFinancialData(averageSummary); + + return contractorContractorJobsDataDto; + } +} diff --git a/core/src/main/java/com/arturjarosz/task/contractor/query/ContractorQueryService.java b/core/src/main/java/com/arturjarosz/task/contractor/query/ContractorQueryService.java index 078dc346..f08f22d7 100644 --- a/core/src/main/java/com/arturjarosz/task/contractor/query/ContractorQueryService.java +++ b/core/src/main/java/com/arturjarosz/task/contractor/query/ContractorQueryService.java @@ -1,6 +1,9 @@ package com.arturjarosz.task.contractor.query; +import com.arturjarosz.task.finance.model.ContractorContractorJobDto; + import java.util.Map; +import java.util.Set; public interface ContractorQueryService { @@ -10,4 +13,6 @@ public interface ContractorQueryService { boolean contractorWithIdExists(long contractorId); Map getNumberOfJobsPerContractor(); + + Set getContractorJobsData(long contractorId); } diff --git a/core/src/main/java/com/arturjarosz/task/contractor/query/impl/ContractorQueryServiceImpl.java b/core/src/main/java/com/arturjarosz/task/contractor/query/impl/ContractorQueryServiceImpl.java index ac709f78..1a60cc2f 100644 --- a/core/src/main/java/com/arturjarosz/task/contractor/query/impl/ContractorQueryServiceImpl.java +++ b/core/src/main/java/com/arturjarosz/task/contractor/query/impl/ContractorQueryServiceImpl.java @@ -2,11 +2,17 @@ import com.arturjarosz.task.contractor.model.QContractor; import com.arturjarosz.task.contractor.query.ContractorQueryService; +import com.arturjarosz.task.finance.application.mapper.ContractorJobMapper; +import com.arturjarosz.task.finance.application.mapper.FinancialDataMapper; +import com.arturjarosz.task.finance.model.ContractorContractorJobDto; import com.arturjarosz.task.finance.model.QContractorJob; +import com.arturjarosz.task.finance.model.QProjectFinancialData; import com.arturjarosz.task.sharedkernel.annotations.Finder; import com.arturjarosz.task.sharedkernel.infrastructure.AbstractQueryService; import java.util.Map; +import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; @Finder @@ -14,9 +20,15 @@ public class ContractorQueryServiceImpl extends AbstractQueryService getNumberOfJobsPerContractor() { .collect(Collectors.toMap(contractorIdToCount -> contractorIdToCount.get(CONTRACTOR.id), contractorIdToCount -> contractorIdToCount.get(CONTRACTOR_JOB.count()))); } + + @Override + public Set getContractorJobsData(long contractorId) { + return this.query() + .from(CONTRACTOR_JOB) + .where(CONTRACTOR_JOB.contractorId.eq(contractorId)) + .leftJoin(PROJECT_FINANCIAL_DATA) + .on(CONTRACTOR_JOB.projectFinancialDataId.eq(PROJECT_FINANCIAL_DATA.id)) + .select(CONTRACTOR_JOB, PROJECT_FINANCIAL_DATA.projectId) + .fetch() + .stream() + .map(contractorJobAndProjectId -> new ContractorContractorJobDto( + this.contractorJobMapper.mapToDto(contractorJobAndProjectId.get(CONTRACTOR_JOB), + contractorJobAndProjectId.get(PROJECT_FINANCIAL_DATA.projectId)), + this.financialDataMapper.map( + Objects.requireNonNull(contractorJobAndProjectId.get(CONTRACTOR_JOB)) + .getFinancialData()))) + .collect(Collectors.toSet()); + } } diff --git a/core/src/main/java/com/arturjarosz/task/contractor/rest/ContractorRestService.java b/core/src/main/java/com/arturjarosz/task/contractor/rest/ContractorRestService.java index 1728db77..16e3d1cc 100644 --- a/core/src/main/java/com/arturjarosz/task/contractor/rest/ContractorRestService.java +++ b/core/src/main/java/com/arturjarosz/task/contractor/rest/ContractorRestService.java @@ -1,6 +1,7 @@ package com.arturjarosz.task.contractor.rest; import com.arturjarosz.task.contractor.application.ContractorApplicationService; +import com.arturjarosz.task.dto.ContractorContractorJobsDataDto; import com.arturjarosz.task.dto.ContractorDto; import com.arturjarosz.task.rest.ContractorApi; import com.arturjarosz.task.sharedkernel.testhelpers.HttpHeadersBuilder; @@ -31,22 +32,27 @@ public ResponseEntity createContractor(ContractorDto contractorDt @Override public ResponseEntity updateContractor(ContractorDto contractorDto, Long contractorId) { var updatedContractor = this.contractorApplicationService.updateContractor(contractorId, contractorDto); - return new ResponseEntity<>(updatedContractor, HttpStatus.OK); + return ResponseEntity.ok(updatedContractor); } @Override public ResponseEntity deleteContractor(Long contractorId) { this.contractorApplicationService.deleteContractor(contractorId); - return new ResponseEntity<>(HttpStatus.OK); + return ResponseEntity.ok().build(); } @Override public ResponseEntity getContractor(Long contractorId) { - return new ResponseEntity<>(this.contractorApplicationService.getContractor(contractorId), HttpStatus.OK); + return ResponseEntity.ok(this.contractorApplicationService.getContractor(contractorId)); } @Override public ResponseEntity> getContractors() { - return new ResponseEntity<>(this.contractorApplicationService.getContractors(), HttpStatus.OK); + return ResponseEntity.ok(this.contractorApplicationService.getContractors()); + } + + @Override + public ResponseEntity getContractorJobsData(Long contractorId) { + return ResponseEntity.ok(this.contractorApplicationService.getContractorJobsData(contractorId)); } } diff --git a/core/src/main/java/com/arturjarosz/task/finance/application/mapper/ContractorJobMapper.java b/core/src/main/java/com/arturjarosz/task/finance/application/mapper/ContractorJobMapper.java index c90a50fe..8bdbfa8b 100644 --- a/core/src/main/java/com/arturjarosz/task/finance/application/mapper/ContractorJobMapper.java +++ b/core/src/main/java/com/arturjarosz/task/finance/application/mapper/ContractorJobMapper.java @@ -14,5 +14,4 @@ default ContractorJob mapFromDto(ContractorJobDto contractorJobDto) { } ContractorJobDto mapToDto(ContractorJob contractorJob, Long projectId); - } diff --git a/core/src/main/java/com/arturjarosz/task/finance/model/ContractorContractorJobDto.java b/core/src/main/java/com/arturjarosz/task/finance/model/ContractorContractorJobDto.java new file mode 100644 index 00000000..ec3410c6 --- /dev/null +++ b/core/src/main/java/com/arturjarosz/task/finance/model/ContractorContractorJobDto.java @@ -0,0 +1,7 @@ +package com.arturjarosz.task.finance.model; + +import com.arturjarosz.task.dto.ContractorJobDto; +import com.arturjarosz.task.finance.domain.dto.FinancialDataDto; + +public record ContractorContractorJobDto(ContractorJobDto contractorJob, FinancialDataDto financialData) { +} diff --git a/core/src/main/java/com/arturjarosz/task/finance/model/ContractorJob.java b/core/src/main/java/com/arturjarosz/task/finance/model/ContractorJob.java index becab5f5..2ff15fdd 100644 --- a/core/src/main/java/com/arturjarosz/task/finance/model/ContractorJob.java +++ b/core/src/main/java/com/arturjarosz/task/finance/model/ContractorJob.java @@ -28,6 +28,7 @@ public class ContractorJob extends AbstractHistoryAwareEntity implements Partial @Column(name = "NAME", nullable = false) String name; + @Getter @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) @JoinColumn(name = "FINANCIAL_DATA_ID", referencedColumnName = "ID", nullable = false) FinancialData financialData; diff --git a/core/src/main/java/com/arturjarosz/task/supplier/query/impl/SupplierQueryServiceImpl.java b/core/src/main/java/com/arturjarosz/task/supplier/query/impl/SupplierQueryServiceImpl.java index d1cbbcc2..f1c09edc 100644 --- a/core/src/main/java/com/arturjarosz/task/supplier/query/impl/SupplierQueryServiceImpl.java +++ b/core/src/main/java/com/arturjarosz/task/supplier/query/impl/SupplierQueryServiceImpl.java @@ -2,7 +2,6 @@ import com.arturjarosz.task.finance.application.mapper.FinancialDataMapper; import com.arturjarosz.task.finance.application.mapper.SupplyMapper; -import com.arturjarosz.task.finance.model.QFinancialData; import com.arturjarosz.task.finance.model.QProjectFinancialData; import com.arturjarosz.task.finance.model.QSupply; import com.arturjarosz.task.finance.model.SupplierSupplyDataDto; @@ -19,7 +18,6 @@ public class SupplierQueryServiceImpl extends AbstractQueryService implements SupplierQueryService { private static final QProjectFinancialData PROJECT_FINANCIAL_DATA = QProjectFinancialData.projectFinancialData; - private static final QFinancialData FINANCIAL_DATA = QFinancialData.financialData; private static final QSupplier SUPPLIER = QSupplier.supplier; private static final QSupply SUPPLY = QSupply.supply; private final SupplyMapper supplyMapper; diff --git a/core/src/main/resources/openapi/openapi-spec.yml b/core/src/main/resources/openapi/openapi-spec.yml index e29895aa..6ecb20be 100644 --- a/core/src/main/resources/openapi/openapi-spec.yml +++ b/core/src/main/resources/openapi/openapi-spec.yml @@ -572,6 +572,36 @@ paths: schema: $ref: "#/components/schemas/ValidatorError" + /contractors/{contractorId}/contractor-jobs: + get: + tags: + - contractor + summary: Get all contractor jobs data for given contractor + operationId: getContractorJobsData + parameters: + - name: contractorId + in: path + description: "A unique identifier of the contractor" + required: true + style: simple + explode: false + schema: + type: integer + format: int64 + responses: + 200: + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/ContractorContractorJobsData" + 400: + description: Validator error message + content: + application/json: + schema: + $ref: "#/components/schemas/ValidatorError" + /projects/{projectId}/contractor-jobs: post: tags: @@ -2839,6 +2869,19 @@ components: items: $ref: "#/components/schemas/Contractor" + ContractorContractorJobsData: + type: + object + properties: + contractorJobs: + type: array + items: + $ref: "#/components/schemas/ContractorJob" + financialData: + $ref: "#/components/schemas/FinancialPartialData" + averageFinancialData: + $ref: "#/components/schemas/FinancialPartialData" + ContractorCategory: type: string enum: @@ -2870,6 +2913,9 @@ components: type: boolean paid: type: boolean + projectId: + type: integer + format: int64 createdDateTime: type: string format: time diff --git a/core/src/test/groovy/com/arturjarosz/task/contractor/application/impl/ContractorApplicationServiceImplTest.groovy b/core/src/test/groovy/com/arturjarosz/task/contractor/application/impl/ContractorApplicationServiceImplTest.groovy index 1f7a08b3..1797f303 100644 --- a/core/src/test/groovy/com/arturjarosz/task/contractor/application/impl/ContractorApplicationServiceImplTest.groovy +++ b/core/src/test/groovy/com/arturjarosz/task/contractor/application/impl/ContractorApplicationServiceImplTest.groovy @@ -1,6 +1,9 @@ package com.arturjarosz.task.contractor.application.impl +import com.arturjarosz.task.configuration.UserProperties import com.arturjarosz.task.contractor.application.ContractorValidator +import com.arturjarosz.task.contractor.application.mapper.ContractorContractorJobMapper +import com.arturjarosz.task.contractor.application.mapper.ContractorContractorJobMapperImpl import com.arturjarosz.task.contractor.application.mapper.ContractorMapperImpl import com.arturjarosz.task.contractor.infrastructure.ContractorRepository import com.arturjarosz.task.contractor.model.Contractor @@ -8,6 +11,9 @@ import com.arturjarosz.task.contractor.model.ContractorCategory import com.arturjarosz.task.contractor.query.ContractorQueryService import com.arturjarosz.task.dto.ContractorCategoryDto import com.arturjarosz.task.dto.ContractorDto +import com.arturjarosz.task.dto.ContractorJobDto +import com.arturjarosz.task.finance.domain.dto.FinancialDataDto +import com.arturjarosz.task.finance.model.ContractorContractorJobDto import com.arturjarosz.task.sharedkernel.testhelpers.TestUtils import spock.lang.Specification @@ -27,9 +33,10 @@ class ContractorApplicationServiceImplTest extends Specification { def contractorValidator = Mock(ContractorValidator) def contractorMapper = new ContractorMapperImpl() def contractorQueryService = Mock(ContractorQueryService) + def contractorContractorJobMapper = new ContractorContractorJobMapperImpl() def subject = new ContractorApplicationServiceImpl(contractorRepository, contractorValidator, - contractorMapper, contractorQueryService) + contractorMapper, contractorQueryService, contractorContractorJobMapper) def "createContractor should call validateCreateContractorDto from contractorValidator"() { @@ -211,7 +218,66 @@ class ContractorApplicationServiceImplTest extends Specification { } } + def "getContractorJobsData should return contractor validate contractor existence"() { + given: + this.contractorQueryService.getContractorJobsData(CONTRACTOR_ID) >> Set.of() + + when: + def result = subject.getContractorJobsData(CONTRACTOR_ID) + + then: + 1 * this.contractorValidator.validateContractorExistence(CONTRACTOR_ID) + } + + def "getContractorJobsData should return contractor jobs data for given contractor"() { + given: + var contractorJob1 = new ContractorJobDto(id: 1L) + var financialDataDto1 = new FinancialDataDto() + financialDataDto1.setHasInvoice(true) + financialDataDto1.setPaid(true) + financialDataDto1.setPayable(true) + financialDataDto1.setValue(BigDecimal.valueOf(100.0D)) + var contractorJobData1 = new ContractorContractorJobDto(contractorJob1, financialDataDto1) + var contractorJob2 = new ContractorJobDto(id: 2L) + var financialDataDto2 = new FinancialDataDto() + financialDataDto2.setHasInvoice(false) + financialDataDto2.setPaid(true) + financialDataDto2.setPayable(true) + financialDataDto2.setValue(BigDecimal.valueOf(200.0D)) + var contractorJobData2 = new ContractorContractorJobDto(contractorJob2, financialDataDto2) + this.contractorQueryService.getContractorJobsData(CONTRACTOR_ID) >> Set.of(contractorJobData1, contractorJobData2) + mockUserProperties(this.contractorContractorJobMapper) + + when: + def result = subject.getContractorJobsData(CONTRACTOR_ID) + + then: + result.contractorJobs.size() == 2 + result.averageFinancialData != null + with(result.averageFinancialData) { + netValue == 150.0D + grossValue == 161.5D + vatTax == 11.5D + incomeTax == 9.0D + } + result.financialData != null + with(result.financialData) { + count == 2 + netValue == 300.0D + grossValue == 323.0D + vatTax == 23.0D + incomeTax == 18.0D + } + } + private void mockContractorRepositoryLoad(Long contractorId) { this.contractorRepository.findById(contractorId) >> Optional.of(new Contractor(NAME, ContractorCategory.valueOf(CATEGORY.name()), UPDATED_EMAIL, TELEPHONE, NOTE)) } + + private void mockUserProperties(ContractorContractorJobMapper mapper) { + var userProperties = new UserProperties() + userProperties.setIncomeTax(0.18D) + userProperties.setVatTax(0.23D) + TestUtils.setFieldForObject(mapper, "userProperties", userProperties) + } } diff --git a/core/src/test/groovy/com/arturjarosz/task/contractor/application/mapper/ContractorContractorJobMapperTest.groovy b/core/src/test/groovy/com/arturjarosz/task/contractor/application/mapper/ContractorContractorJobMapperTest.groovy new file mode 100644 index 00000000..a542cffa --- /dev/null +++ b/core/src/test/groovy/com/arturjarosz/task/contractor/application/mapper/ContractorContractorJobMapperTest.groovy @@ -0,0 +1,51 @@ +package com.arturjarosz.task.contractor.application.mapper + +import com.arturjarosz.task.configuration.UserProperties +import com.arturjarosz.task.dto.ContractorJobDto +import com.arturjarosz.task.finance.domain.dto.FinancialDataDto +import com.arturjarosz.task.finance.model.ContractorContractorJobDto +import com.arturjarosz.task.sharedkernel.testhelpers.TestUtils +import spock.lang.Specification + +class ContractorContractorJobMapperTest extends Specification { + + def subject = new ContractorContractorJobMapperImpl() + + def setup() { + def userProperties = new UserProperties(incomeTax: 0.1D, vatTax: 0.23D) + TestUtils.setFieldForObject(subject, "userProperties", userProperties) + } + + def "should return correctly mapped dto"() { + given: + var jobs = prepareContractorJobs(numberOfJobs) + + when: + def result = subject.mapToContractorContractorJobsDataDto(jobs) + + then: + result.contractorJobs.size() == numberOfJobs + result.financialData.count == numberOfJobs + result.averageFinancialData.netValue == averageNet + result.averageFinancialData.grossValue == averageGross + result.averageFinancialData.incomeTax == averageIncome + result.averageFinancialData.vatTax == averageVat + + where: + numberOfJobs || averageNet | averageGross | averageIncome | averageVat + 0 || 0.0D | 0.0D | 0.0D | 0.0D + 1 || 200.0D | 246.0D | 20.0D | 46.0D + 2 || 250.0D | 307.5D | 25.0D | 57.5D + } + + Set prepareContractorJobs(int number) { + var contractorJobDtos = new HashSet() + for (int i = 0; i < number; i++) { + var contractorJobDto = new ContractorJobDto() + var financialData = new FinancialDataDto(value: (200.0D + 100.0D * i), hasInvoice: true) + var supplyData = new ContractorContractorJobDto(contractorJobDto, financialData) + contractorJobDtos.add(supplyData) + } + return contractorJobDtos + } +}