diff options
author | Ola Aunronning <olaa@yahooinc.com> | 2023-10-24 10:08:12 +0200 |
---|---|---|
committer | Ola Aunronning <olaa@yahooinc.com> | 2023-10-24 10:08:17 +0200 |
commit | 900209e698fc4921103c505ed357eb368059e1a4 (patch) | |
tree | 92f27eb279e183efa8a133cd263de94afb100ccf /controller-server | |
parent | 0b6956a16bea480d9acbc0a03c747f69d69aaba8 (diff) |
Add tax code/purchase order/invoice email
Diffstat (limited to 'controller-server')
4 files changed, 195 insertions, 20 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java index d5bb47c94b0..087e7d54e12 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java @@ -93,6 +93,9 @@ public class TenantSerializer { private static final String cloudAccountsField = "cloudAccounts"; private static final String accountField = "account"; private static final String templateVersionField = "templateVersion"; + private static final String taxCodeField = "taxCode"; + private static final String purchaseOrderField = "purchaseOrder"; + private static final String invoiceEmailField = "invoiceEmail"; private static final String awsIdField = "awsId"; private static final String roleField = "role"; @@ -282,12 +285,19 @@ public class TenantSerializer { } private TenantBilling tenantInfoBillingContactFromSlime(Inspector billingObject) { + var taxCode = billingObject.field(taxCodeField).asString(); + var purchaseOrder = billingObject.field(purchaseOrderField).asString(); + var invoiceEmail = billingObject.field(invoiceEmailField).asString(); + return TenantBilling.empty() .withContact(TenantContact.from( billingObject.field("name").asString(), new Email(billingObject.field("email").asString(), billingObject.field("emailVerified").asBool()), billingObject.field("phone").asString())) - .withAddress(tenantInfoAddressFromSlime(billingObject.field("address"))); + .withAddress(tenantInfoAddressFromSlime(billingObject.field("address"))) + .withTaxCode(taxCode) + .withPurchaseOrder(purchaseOrder) + .withInvoiceEmail(invoiceEmail); } private List<TenantSecretStore> secretStoresFromSlime(Inspector secretStoresObject) { @@ -349,6 +359,9 @@ public class TenantSerializer { billingCursor.setString("email", billingContact.contact().email().getEmailAddress()); billingCursor.setBool("emailVerified", billingContact.contact().email().isVerified()); billingCursor.setString("phone", billingContact.contact().phone()); + billingCursor.setString(taxCodeField, billingContact.getTaxCode()); + billingCursor.setString(purchaseOrderField, billingContact.getPurchaseOrder()); + billingCursor.setString(invoiceEmailField, billingContact.getInvoiceEmail()); toSlime(billingContact.address(), billingCursor); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java index 6a6c8a51d72..d1d65f85dd3 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java @@ -694,6 +694,9 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { contact.setString("email", billingContact.contact().email().getEmailAddress()); contact.setBool("emailVerified", billingContact.contact().email().isVerified()); contact.setString("phone", billingContact.contact().phone()); + root.setString("taxCode", billingContact.getTaxCode()); + root.setString("purchaseOrder", billingContact.getPurchaseOrder()); + root.setString("invoiceEmail", billingContact.getInvoiceEmail()); toSlime(billingContact.address(), root); // will create "address" on the parent } @@ -708,10 +711,16 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { var mergedContact = updateBillingContact(inspector.field("contact"), cloudTenant.name(), contact); var mergedAddress = updateTenantInfoAddress(inspector.field("address"), info.billingContact().address()); + var mergedTaxCode = getString(inspector.field("taxCode"), info.billingContact().getTaxCode()); + var mergedPurchaseOrder = getString(inspector.field("purchaseOrder"), info.billingContact().getPurchaseOrder()); + var mergedInvoiceEmail = getString(inspector.field("invoiceEmail"), info.billingContact().getInvoiceEmail()); var mergedBilling = info.billingContact() .withContact(mergedContact) - .withAddress(mergedAddress); + .withAddress(mergedAddress) + .withTaxCode(mergedTaxCode) + .withPurchaseOrder(mergedPurchaseOrder) + .withInvoiceEmail(mergedInvoiceEmail); var mergedInfo = info.withBilling(mergedBilling); @@ -764,6 +773,11 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { throw new IllegalArgumentException("'website' needs to be a valid address"); } } + if (! mergedInfo.billingContact().getInvoiceEmail().isBlank()) { + // TODO: Validate invoice email is set if collection method is INVOICE + if (! mergedInfo.billingContact().getInvoiceEmail().contains("@")) + throw new IllegalArgumentException("'Invoice email' needs to be an email address"); + } } private void toSlime(TenantAddress address, Cursor parentCursor) { @@ -785,6 +799,9 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { billingCursor.setString("email", billingContact.contact().email().getEmailAddress()); billingCursor.setBool("emailVerified", billingContact.contact().email().isVerified()); billingCursor.setString("phone", billingContact.contact().phone()); + billingCursor.setString("taxCode", billingContact.getTaxCode()); + billingCursor.setString("purchaseOrder", billingContact.getPurchaseOrder()); + billingCursor.setString("invoiceEmail", billingContact.getInvoiceEmail()); toSlime(billingContact.address(), billingCursor); } @@ -914,9 +931,15 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { private TenantBilling updateTenantInfoBillingContact(Inspector insp, TenantName tenantName, TenantBilling oldContact) { if (!insp.valid()) return oldContact; + var taxCode = getString(insp.field("taxCode"), oldContact.getTaxCode()); + var purchaseOrder = getString(insp.field("purchaseOrder"), oldContact.getPurchaseOrder()); + var invoiceEmail = getString(insp.field("purchaseOrder"), oldContact.getInvoiceEmail()); return TenantBilling.empty() .withContact(updateBillingContact(insp, tenantName, oldContact.contact())) - .withAddress(updateTenantInfoAddress(insp.field("address"), oldContact.address())); + .withAddress(updateTenantInfoAddress(insp.field("address"), oldContact.address())) + .withTaxCode(taxCode) + .withPurchaseOrder(purchaseOrder) + .withInvoiceEmail(invoiceEmail); } private TenantContacts updateTenantInfoContacts(Inspector insp, TenantName tenantName, TenantContacts oldContacts) { diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java index 9de856971c9..31794aad350 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java @@ -234,7 +234,11 @@ public class TenantSerializerTest { .withCity("Suddery") .withCountry("Sodor") .withAddress("Central Station") - .withRegion("Irish Sea"))); + .withRegion("Irish Sea")) + .withPurchaseOrder("PO42") + .withTaxCode("1234L") + .withInvoiceEmail("billing@mycomp.any") + ); Slime slime = new Slime(); Cursor parentCursor = slime.setObject(); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java index 2b01d87c903..1b6425a324a 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java @@ -98,19 +98,68 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest { @Test void tenant_info_billing() { + var expectedResponse = """ + { + "contact": { + "name":"", + "email":"", + "emailVerified":false, + "phone":"" + }, + "taxCode":"", + "purchaseOrder":"", + "invoiceEmail":"" + } + """; var request = request("/application/v4/tenant/scoober/info/billing", GET) .roles(Set.of(Role.reader(tenantName))); - tester.assertResponse(request, "{\"contact\":{\"name\":\"\",\"email\":\"\",\"emailVerified\":false,\"phone\":\"\"}}", 200); - - var fullAddress = "{\"addressLines\":\"addressLines\",\"postalCodeOrZip\":\"postalCodeOrZip\",\"city\":\"city\",\"stateRegionProvince\":\"stateRegionProvince\",\"country\":\"country\"}"; - var fullBillingContact = "{\"contact\":{\"name\":\"name\",\"email\":\"foo@example\",\"phone\":\"phone\"},\"address\":" + fullAddress + "}"; - + tester.assertJsonResponse(request, expectedResponse, 200); + + var fullBillingContact = """ + { + "contact": { + "name":"name", + "email":"foo@example", + "phone":"phone" + }, + "taxCode":"1234L", + "purchaseOrder":"PO9001", + "invoiceEmail":"billing@mycomp.any", + "address": { + "addressLines":"addressLines", + "postalCodeOrZip":"postalCodeOrZip", + "city":"city", + "stateRegionProvince":"stateRegionProvince", + "country":"country" + } + } + """; var updateRequest = request("/application/v4/tenant/scoober/info/billing", PUT) .data(fullBillingContact) .roles(Set.of(Role.administrator(tenantName))); tester.assertResponse(updateRequest, "{\"message\":\"Tenant info updated\"}", 200); - tester.assertResponse(request, "{\"contact\":{\"name\":\"name\",\"email\":\"foo@example\",\"emailVerified\":false,\"phone\":\"phone\"},\"address\":{\"addressLines\":\"addressLines\",\"postalCodeOrZip\":\"postalCodeOrZip\",\"city\":\"city\",\"stateRegionProvince\":\"stateRegionProvince\",\"country\":\"country\"}}", 200); + expectedResponse = """ + { + "contact": { + "name":"name", + "email":"foo@example", + "emailVerified": false, + "phone":"phone" + }, + "taxCode":"1234L", + "purchaseOrder":"PO9001", + "invoiceEmail":"billing@mycomp.any", + "address": { + "addressLines":"addressLines", + "postalCodeOrZip":"postalCodeOrZip", + "city":"city", + "stateRegionProvince":"stateRegionProvince", + "country":"country" + } + } + """; + tester.assertJsonResponse(request, expectedResponse, 200); } @Test @@ -120,12 +169,32 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest { tester.assertResponse(request, "{\"contacts\":[{\"audiences\":[\"tenant\",\"notifications\"],\"email\":\"developer@scoober\",\"emailVerified\":true}]}", 200); - var fullContacts = "{\"contacts\":[{\"audiences\":[\"tenant\"],\"email\":\"contact1@example.com\",\"emailVerified\":false},{\"audiences\":[\"notifications\"],\"email\":\"contact2@example.com\",\"emailVerified\":false},{\"audiences\":[\"tenant\",\"notifications\"],\"email\":\"contact3@example.com\",\"emailVerified\":false}]}"; + var fullContacts = """ + { + "contacts":[ + { + "audiences":["tenant"] + ,"email":"contact1@example.com", + "emailVerified":false + }, + { + "audiences":["notifications"], + "email":"contact2@example.com", + "emailVerified":false + }, + { + "audiences":["tenant","notifications"], + "email":"contact3@example.com", + "emailVerified":false + } + ] + } + """; var updateRequest = request("/application/v4/tenant/scoober/info/contacts", PUT) .data(fullContacts) .roles(Set.of(Role.administrator(tenantName))); tester.assertResponse(updateRequest, "{\"message\":\"Tenant info updated\"}", 200); - tester.assertResponse(request, fullContacts, 200); + tester.assertJsonResponse(request, fullContacts, 200); } @Test @@ -150,13 +219,79 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest { tester.assertResponse(postPartialContacts, "{\"message\":\"Tenant info updated\"}", 200); // Read back the updated info - tester.assertResponse(infoRequest, "{\"name\":\"\",\"email\":\"\",\"website\":\"\",\"contactName\":\"newName\",\"contactEmail\":\"foo@example.com\",\"contactEmailVerified\":false,\"billingContact\":{\"name\":\"billingName\",\"email\":\"\",\"emailVerified\":false,\"phone\":\"\"},\"contacts\":[{\"audiences\":[\"tenant\"],\"email\":\"contact1@example.com\",\"emailVerified\":false}]}", 200); - - String fullAddress = "{\"addressLines\":\"addressLines\",\"postalCodeOrZip\":\"postalCodeOrZip\",\"city\":\"city\",\"stateRegionProvince\":\"stateRegionProvince\",\"country\":\"country\"}"; - String fullBillingContact = "{\"name\":\"name\",\"email\":\"foo@example\",\"emailVerified\":false,\"phone\":\"phone\",\"address\":" + fullAddress + "}"; - String fullContacts = "[{\"audiences\":[\"tenant\"],\"email\":\"contact1@example.com\",\"emailVerified\":false},{\"audiences\":[\"notifications\"],\"email\":\"contact2@example.com\",\"emailVerified\":false},{\"audiences\":[\"tenant\",\"notifications\"],\"email\":\"contact3@example.com\",\"emailVerified\":false}]"; - String fullInfo = "{\"name\":\"name\",\"email\":\"foo@example\",\"website\":\"https://yahoo.com\",\"contactName\":\"contactName\",\"contactEmail\":\"contact@example.com\",\"contactEmailVerified\":false,\"address\":" + fullAddress + ",\"billingContact\":" + fullBillingContact + ",\"contacts\":" + fullContacts + "}"; - + var expectedResponse = """ + { + "name":"", + "email":"", + "website":"", + "contactName":"newName", + "contactEmail":"foo@example.com", + "contactEmailVerified":false, + "billingContact": { + "name":"billingName", + "email":"","emailVerified":false, + "phone":"", + "taxCode":"", + "purchaseOrder":"", + "invoiceEmail":"" + }, + "contacts": [ + {"audiences":["tenant"],"email":"contact1@example.com","emailVerified":false} + ] + } + """; + tester.assertJsonResponse(infoRequest, expectedResponse, 200); + + var fullInfo = """ + { + "name":"name", + "email":"foo@example", + "website":"https://yahoo.com", + "contactName":"contactName", + "contactEmail":"contact@example.com", + "contactEmailVerified":false, + "address": { + "addressLines":"addressLines", + "postalCodeOrZip":"postalCodeOrZip", + "city":"city", + "stateRegionProvince":"stateRegionProvince", + "country":"country" + }, + "billingContact": { + "name":"name", + "email":"foo@example", + "emailVerified":false, + "phone":"phone", + "taxCode":"", + "purchaseOrder":"", + "invoiceEmail":"", + "address": { + "addressLines":"addressLines", + "postalCodeOrZip":"postalCodeOrZip", + "city":"city", + "stateRegionProvince":"stateRegionProvince", + "country":"country" + } + }, + "contacts": [ + { + "audiences":["tenant"], + "email":"contact1@example.com", + "emailVerified":false + }, + { + "audiences":["notifications"], + "email":"contact2@example.com", + "emailVerified":false + }, + { + "audiences":["tenant","notifications"] + ,"email":"contact3@example.com", + "emailVerified":false + } + ] + } + """; // Now set all fields var postFull = request("/application/v4/tenant/scoober/info", PUT) @@ -165,7 +300,7 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest { tester.assertResponse(postFull, "{\"message\":\"Tenant info updated\"}", 200); // Now compare the updated info with the full info we sent - tester.assertResponse(infoRequest, fullInfo, 200); + tester.assertJsonResponse(infoRequest, fullInfo, 200); var invalidBody = "{\"mail\":\"contact1@example.com\", \"mailType\":\"blurb\"}"; var resendMailRequest = |