diff --git a/app/uk/gov/hmrc/formpproxy/cis/models/requests/ApplyPrepopulationRequest.scala b/app/uk/gov/hmrc/formpproxy/cis/models/requests/ApplyPrepopulationRequest.scala index 46664eef..85bdc079 100644 --- a/app/uk/gov/hmrc/formpproxy/cis/models/requests/ApplyPrepopulationRequest.scala +++ b/app/uk/gov/hmrc/formpproxy/cis/models/requests/ApplyPrepopulationRequest.scala @@ -17,7 +17,6 @@ package uk.gov.hmrc.formpproxy.cis.models.requests import play.api.libs.json.{Json, OFormat} -import uk.gov.hmrc.formpproxy.cis.models.SubcontractorType final case class ApplyPrepopulationRequest( schemeId: Int, @@ -32,7 +31,7 @@ final case class ApplyPrepopulationRequest( prePopCount: Int, prePopSuccessful: String, version: Int, - subcontractorTypes: Seq[SubcontractorType] + subcontractors: Seq[PrepopulationSubcontractor] ) object ApplyPrepopulationRequest { diff --git a/app/uk/gov/hmrc/formpproxy/cis/models/requests/PrepopulationSubcontractor.scala b/app/uk/gov/hmrc/formpproxy/cis/models/requests/PrepopulationSubcontractor.scala new file mode 100644 index 00000000..6addf0e6 --- /dev/null +++ b/app/uk/gov/hmrc/formpproxy/cis/models/requests/PrepopulationSubcontractor.scala @@ -0,0 +1,37 @@ +/* + * Copyright 2026 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.hmrc.formpproxy.cis.models.requests + +import play.api.libs.json.{Json, OFormat} +import uk.gov.hmrc.formpproxy.cis.models.SubcontractorType + +final case class PrepopulationSubcontractor( + subcontractorType: SubcontractorType, + utr: String, + verificationNumber: Option[String], + firstName: Option[String], + secondName: Option[String], + surname: Option[String], + tradingName: Option[String], + partnershipTradingName: Option[String], + verified: Option[String], + autoVerified: Option[String] +) + +object PrepopulationSubcontractor { + implicit val format: OFormat[PrepopulationSubcontractor] = Json.format[PrepopulationSubcontractor] +} diff --git a/app/uk/gov/hmrc/formpproxy/cis/repositories/CisFormpRepository.scala b/app/uk/gov/hmrc/formpproxy/cis/repositories/CisFormpRepository.scala index 2c070920..ed54a9d0 100644 --- a/app/uk/gov/hmrc/formpproxy/cis/repositories/CisFormpRepository.scala +++ b/app/uk/gov/hmrc/formpproxy/cis/repositories/CisFormpRepository.scala @@ -510,7 +510,7 @@ class CisFormpRepository @Inject() (@NamedDatabase("cis") db: Database)(implicit override def applyPrepopulation(req: ApplyPrepopulationRequest): Future[Int] = Future { logger.info( - s"[CIS] applyPrepopulation(schemeId=${req.schemeId}, instanceId=${req.instanceId}, version=${req.version}, subs=${req.subcontractorTypes.size})" + s"[CIS] applyPrepopulation(schemeId=${req.schemeId}, instanceId=${req.instanceId}, version=${req.version}, subs=${req.subcontractors.size})" ) db.withTransaction { conn => @@ -533,16 +533,9 @@ class CisFormpRepository @Inject() (@NamedDatabase("cis") db: Database)(implicit cs.execute() } - // 2) Create_Subcontractor for each subcontractorType - req.subcontractorTypes.foreach { subcontractorType => - withCall(conn, CallCreateSubcontractor) { cs => - cs.setInt(1, req.schemeId) - cs.setInt(2, req.version) - cs.setString(3, subcontractorType.toString) - cs.registerOutParameter(4, OracleTypes.INTEGER) - - cs.execute() - } + // 2) Create_Subcontractor_Prepop for each subcontractor (rolled back with scheme on failure) + req.subcontractors.foreach { subcontractor => + callCreateSubcontractorPrepop(conn, req.schemeId, subcontractor) } // 3) Update_Version_Number – increment version atomically in same transaction @@ -1044,6 +1037,49 @@ class CisFormpRepository @Inject() (@NamedDatabase("cis") db: Database)(implicit } finally cs.close() } + private def callCreateSubcontractorPrepop( + conn: Connection, + schemeId: Int, + subcontractor: PrepopulationSubcontractor + ): Int = { + val cs = conn.prepareCall(CallCreateSubcontractorPrepop) + try { + cs.setInt(1, schemeId) + cs.setString(2, subcontractor.utr) + cs.setInt(3, 0) // page_visited + cs.setOptionalString(4, None) // partner_utr + cs.setOptionalString(5, None) // crn + cs.setOptionalString(6, subcontractor.firstName) + cs.setOptionalString(7, None) // nino + cs.setOptionalString(8, subcontractor.secondName) + cs.setOptionalString(9, subcontractor.surname) + cs.setOptionalString(10, subcontractor.partnershipTradingName) + cs.setOptionalString(11, subcontractor.tradingName) + cs.setString(12, subcontractor.subcontractorType.toString) + cs.setOptionalString(13, None) // address_line_1 + cs.setOptionalString(14, None) // address_line_2 + cs.setOptionalString(15, None) // address_line_3 + cs.setOptionalString(16, None) // address_line_4 + cs.setOptionalString(17, None) // country + cs.setOptionalString(18, None) // postcode + cs.setOptionalString(19, None) // email_address + cs.setOptionalString(20, None) // phone_number + cs.setOptionalString(21, None) // mobile_phone_number + cs.setOptionalString(22, None) // works_reference_number + cs.setOptionalString(23, None) // matched + cs.setOptionalString(24, subcontractor.autoVerified) + cs.setOptionalString(25, subcontractor.verified) + cs.setOptionalString(26, subcontractor.verificationNumber) + cs.setOptionalString(27, None) // tax_treatment + cs.setOptionalString(28, None) // updated_tax_treatment + cs.setOptionalTimestamp(29, None) // verification_date + cs.registerOutParameter(30, OracleTypes.INTEGER) + + cs.execute() + cs.getInt(30) + } finally cs.close() + } + private def callUpdateSubcontractor( conn: Connection, schemeId: Long, diff --git a/app/uk/gov/hmrc/formpproxy/cis/repositories/CisStoredProcedures.scala b/app/uk/gov/hmrc/formpproxy/cis/repositories/CisStoredProcedures.scala index b372f855..3f257043 100644 --- a/app/uk/gov/hmrc/formpproxy/cis/repositories/CisStoredProcedures.scala +++ b/app/uk/gov/hmrc/formpproxy/cis/repositories/CisStoredProcedures.scala @@ -27,6 +27,8 @@ object CisStoredProcedures { val CallCreateScheme = "{ call SCHEME_PROCS.Create_Scheme(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) }" val CallUpdateScheme = "{ call SCHEME_PROCS.Update_Scheme(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) }" val CallCreateSubcontractor = "{ call SUBCONTRACTOR_PROCS.CREATE_SUBCONTRACTOR(?, ?, ?, ?) }" + val CallCreateSubcontractorPrepop = + "{ call SUBCONTRACTOR_PROCS.Create_Subcontractor_Prepop(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) }" val CallGetAllMonthlyReturns = "{ call MONTHLY_RETURN_PROCS_2016.Get_All_Monthly_Returns(?, ?, ?) }" val CallGetUnsubmittedMonthlyReturns = "{ call MONTHLY_RETURN_PROCS_2016.Get_Monthly_Returns(?, ?, ?) }" val CallGetSubmittedMonthlyReturns = "{ call MONTHLY_RETURN_PROCS_2016.GET_SUBMITTED_RETURNS(?, ?, ?, ?) }" diff --git a/test/uk/gov/hmrc/formpproxy/cis/controllers/ContractorSchemeControllerSpec.scala b/test/uk/gov/hmrc/formpproxy/cis/controllers/ContractorSchemeControllerSpec.scala index ef767787..53ed7927 100644 --- a/test/uk/gov/hmrc/formpproxy/cis/controllers/ContractorSchemeControllerSpec.scala +++ b/test/uk/gov/hmrc/formpproxy/cis/controllers/ContractorSchemeControllerSpec.scala @@ -27,7 +27,7 @@ import play.api.mvc.{AnyContent, ControllerComponents, PlayBodyParsers, Result} import play.api.test.FakeRequest import play.api.test.Helpers.* import uk.gov.hmrc.formpproxy.actions.FakeAuthAction -import uk.gov.hmrc.formpproxy.cis.models.requests.ApplyPrepopulationRequest +import uk.gov.hmrc.formpproxy.cis.models.requests.{ApplyPrepopulationRequest, PrepopulationSubcontractor} import uk.gov.hmrc.formpproxy.cis.models.{Company, ContractorScheme, CreateContractorSchemeParams, SoleTrader, UpdateContractorSchemeParams} import uk.gov.hmrc.formpproxy.cis.services.ContractorSchemeService import uk.gov.hmrc.http.UpstreamErrorResponse @@ -573,7 +573,32 @@ class ContractorSchemeControllerSpec extends AnyFreeSpec with Matchers with Scal prePopCount = 5, prePopSuccessful = "Y", version = 1, - subcontractorTypes = Seq(SoleTrader, Company) + subcontractors = Seq( + PrepopulationSubcontractor( + subcontractorType = SoleTrader, + utr = "1111111111", + verificationNumber = Some("V1"), + firstName = Some("A"), + secondName = None, + surname = Some("B"), + tradingName = None, + partnershipTradingName = None, + verified = Some("Y"), + autoVerified = Some("Y") + ), + PrepopulationSubcontractor( + subcontractorType = Company, + utr = "2222222222", + verificationNumber = Some("V2"), + firstName = None, + secondName = None, + surname = None, + tradingName = Some("Acme"), + partnershipTradingName = None, + verified = Some("Y"), + autoVerified = Some("Y") + ) + ) ) } diff --git a/test/uk/gov/hmrc/formpproxy/cis/models/requests/ApplyPrepopulationRequestSpec.scala b/test/uk/gov/hmrc/formpproxy/cis/models/requests/ApplyPrepopulationRequestSpec.scala index b792d2b7..fd7fe00c 100644 --- a/test/uk/gov/hmrc/formpproxy/cis/models/requests/ApplyPrepopulationRequestSpec.scala +++ b/test/uk/gov/hmrc/formpproxy/cis/models/requests/ApplyPrepopulationRequestSpec.scala @@ -23,6 +23,32 @@ import uk.gov.hmrc.formpproxy.cis.models.{Company, SoleTrader} class ApplyPrepopulationRequestSpec extends AnyFreeSpec with Matchers { + private val soleTrader = PrepopulationSubcontractor( + subcontractorType = SoleTrader, + utr = "1111111111", + verificationNumber = Some("V1"), + firstName = Some("A"), + secondName = Some("B"), + surname = Some("C"), + tradingName = None, + partnershipTradingName = None, + verified = Some("Y"), + autoVerified = Some("Y") + ) + + private val company = PrepopulationSubcontractor( + subcontractorType = Company, + utr = "2222222222", + verificationNumber = Some("V2"), + firstName = None, + secondName = None, + surname = None, + tradingName = Some("Acme Ltd"), + partnershipTradingName = None, + verified = Some("Y"), + autoVerified = Some("Y") + ) + "ApplyPrepopulationRequest.format" - { "writes then reads back (round-trip) with all fields" in { @@ -39,7 +65,7 @@ class ApplyPrepopulationRequestSpec extends AnyFreeSpec with Matchers { prePopCount = 5, prePopSuccessful = "Y", version = 1, - subcontractorTypes = Seq(SoleTrader, Company) + subcontractors = Seq(soleTrader, company) ) val json = Json.toJson(model) @@ -57,7 +83,12 @@ class ApplyPrepopulationRequestSpec extends AnyFreeSpec with Matchers { "prePopCount" -> 5, "prePopSuccessful" -> "Y", "version" -> 1, - "subcontractorTypes" -> Json.arr("soletrader", "company") + "subcontractors" -> Json.arr( + Json.obj( + "subcontractorType" -> "soletrader", + "utr" -> "1111111111" + ) + ) ) val expected = ApplyPrepopulationRequest( @@ -73,7 +104,20 @@ class ApplyPrepopulationRequestSpec extends AnyFreeSpec with Matchers { prePopCount = 5, prePopSuccessful = "Y", version = 1, - subcontractorTypes = Seq(SoleTrader, Company) + subcontractors = Seq( + PrepopulationSubcontractor( + subcontractorType = SoleTrader, + utr = "1111111111", + verificationNumber = None, + firstName = None, + secondName = None, + surname = None, + tradingName = None, + partnershipTradingName = None, + verified = None, + autoVerified = None + ) + ) ) json.validate[ApplyPrepopulationRequest] mustBe JsSuccess(expected) diff --git a/test/uk/gov/hmrc/formpproxy/cis/repositories/CisFormpRepositorySpec.scala b/test/uk/gov/hmrc/formpproxy/cis/repositories/CisFormpRepositorySpec.scala index 6d60aae5..29c81368 100644 --- a/test/uk/gov/hmrc/formpproxy/cis/repositories/CisFormpRepositorySpec.scala +++ b/test/uk/gov/hmrc/formpproxy/cis/repositories/CisFormpRepositorySpec.scala @@ -970,16 +970,49 @@ final class CisFormpRepositorySpec extends SpecBase { when(conn.prepareCall(eqTo("{ call SCHEME_PROCS.Update_Scheme(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) }"))) .thenReturn(csUpdate) - when(conn.prepareCall(eqTo("{ call SUBCONTRACTOR_PROCS.CREATE_SUBCONTRACTOR(?, ?, ?, ?) }"))) - .thenReturn(csSub) + when( + conn.prepareCall( + eqTo( + "{ call SUBCONTRACTOR_PROCS.Create_Subcontractor_Prepop(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) }" + ) + ) + ).thenReturn(csSub) when(conn.prepareCall(eqTo("{ call SCHEME_PROCS.Update_Version_Number(?, ?) }"))) .thenReturn(csUpdateVer) when(csUpdateVer.getInt(2)).thenReturn(2) + when(csSub.getInt(30)).thenReturn(101, 102) val repo = new CisFormpRepository(db) + val subcontractors = Seq( + PrepopulationSubcontractor( + subcontractorType = SoleTrader, + utr = "1111111111", + verificationNumber = Some("V1"), + firstName = Some("Ann"), + secondName = None, + surname = Some("Smith"), + tradingName = None, + partnershipTradingName = None, + verified = Some("Y"), + autoVerified = Some("Y") + ), + PrepopulationSubcontractor( + subcontractorType = Company, + utr = "2222222222", + verificationNumber = Some("V2"), + firstName = None, + secondName = None, + surname = None, + tradingName = Some("Acme Ltd"), + partnershipTradingName = None, + verified = Some("Y"), + autoVerified = Some("Y") + ) + ) + val req = ApplyPrepopulationRequest( schemeId = 789, instanceId = "abc-123", @@ -993,19 +1026,27 @@ final class CisFormpRepositorySpec extends SpecBase { prePopCount = 5, prePopSuccessful = "Y", version = 1, - subcontractorTypes = Seq(SoleTrader, Company) + subcontractors = subcontractors ) val out = repo.applyPrepopulation(req).futureValue out mustBe 2 verify(conn).prepareCall(eqTo("{ call SCHEME_PROCS.Update_Scheme(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) }")) - verify(conn, times(req.subcontractorTypes.size)) - .prepareCall(eqTo("{ call SUBCONTRACTOR_PROCS.CREATE_SUBCONTRACTOR(?, ?, ?, ?) }")) + verify(conn, times(req.subcontractors.size)) + .prepareCall( + eqTo( + "{ call SUBCONTRACTOR_PROCS.Create_Subcontractor_Prepop(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) }" + ) + ) verify(conn).prepareCall(eqTo("{ call SCHEME_PROCS.Update_Version_Number(?, ?) }")) verify(csUpdate).execute() - verify(csSub, times(req.subcontractorTypes.size)).execute() + verify(csSub, times(req.subcontractors.size)).execute() + verify(csSub).setString(2, "1111111111") + verify(csSub).setString(2, "2222222222") + verify(csSub).setString(12, "soletrader") + verify(csSub).setString(12, "company") verify(csUpdateVer).execute() } } diff --git a/test/uk/gov/hmrc/formpproxy/cis/services/ContractorSchemeServiceSpec.scala b/test/uk/gov/hmrc/formpproxy/cis/services/ContractorSchemeServiceSpec.scala index e4e1beed..4ece5004 100644 --- a/test/uk/gov/hmrc/formpproxy/cis/services/ContractorSchemeServiceSpec.scala +++ b/test/uk/gov/hmrc/formpproxy/cis/services/ContractorSchemeServiceSpec.scala @@ -19,7 +19,7 @@ package uk.gov.hmrc.formpproxy.cis.services import org.mockito.ArgumentMatchers.eq as eqTo import org.mockito.Mockito.* import uk.gov.hmrc.formpproxy.base.SpecBase -import uk.gov.hmrc.formpproxy.cis.models.requests.ApplyPrepopulationRequest +import uk.gov.hmrc.formpproxy.cis.models.requests.{ApplyPrepopulationRequest, PrepopulationSubcontractor} import uk.gov.hmrc.formpproxy.cis.models.{Company, ContractorScheme, CreateContractorSchemeParams, SoleTrader, UpdateContractorSchemeParams} import uk.gov.hmrc.formpproxy.cis.repositories.CisMonthlyReturnSource @@ -71,7 +71,32 @@ final class ContractorSchemeServiceSpec extends SpecBase { prePopCount = 5, prePopSuccessful = "Y", version = 1, - subcontractorTypes = Seq(SoleTrader, Company) + subcontractors = Seq( + PrepopulationSubcontractor( + subcontractorType = SoleTrader, + utr = "1111111111", + verificationNumber = Some("V1"), + firstName = Some("A"), + secondName = None, + surname = Some("B"), + tradingName = None, + partnershipTradingName = None, + verified = Some("Y"), + autoVerified = Some("Y") + ), + PrepopulationSubcontractor( + subcontractorType = Company, + utr = "2222222222", + verificationNumber = Some("V2"), + firstName = None, + secondName = None, + surname = None, + tradingName = Some("Acme"), + partnershipTradingName = None, + verified = Some("Y"), + autoVerified = Some("Y") + ) + ) ) "ContractorSchemeService getScheme" - {