Skip to content

Commit

Permalink
feat(seeding): move company and address to overwrite dir (#1067)
Browse files Browse the repository at this point in the history
* make company and address overwriteable to provide the option to seed the bpn without changing the code it's moved into a directory where it can be overwritten

Co-authored-by: Norbert Truchsess <[email protected]>
Reviewed-by: Norbert Truchsess <[email protected]>
Reviewed-By: Evelyn Gurschler <[email protected]>
Refs: eclipse-tractusx/portal#449
  • Loading branch information
Phil91 and ntruchsess authored Oct 17, 2024
1 parent 58e0659 commit f4a371b
Show file tree
Hide file tree
Showing 13 changed files with 99 additions and 40 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Entities;

namespace Org.Eclipse.TractusX.Portal.Backend.PortalBackend.Migrations.Extensions;

public static class SeedingExtensions
{
#region Company

public static bool UpdateCompanyNeeded(this (Company dataEntity, Company dbEntity) data) =>
(data.dbEntity.SelfDescriptionDocumentId == null &&
data.dataEntity.SelfDescriptionDocumentId != null) ||
data.dbEntity.BusinessPartnerNumber != data.dataEntity.BusinessPartnerNumber ||
data.dbEntity.Shortname != data.dataEntity.Shortname ||
data.dbEntity.Name != data.dataEntity.Name;

public static void UpdateCompany(this Company dbEntry, Company entry)
{
if (dbEntry.SelfDescriptionDocumentId == null &&
entry.SelfDescriptionDocumentId != null)
{
dbEntry.SelfDescriptionDocumentId = entry.SelfDescriptionDocumentId;
}

dbEntry.BusinessPartnerNumber = entry.BusinessPartnerNumber;
dbEntry.Shortname = entry.Shortname;
dbEntry.Name = entry.Name;
}

#endregion

#region Address

public static bool UpdateAddressNeeded(this (Address dataEntity, Address dbEntity) data) =>
data.dataEntity.City != data.dbEntity.City ||
data.dbEntity.Region != data.dataEntity.Region ||
data.dbEntity.Streetadditional != data.dataEntity.Streetadditional ||
data.dbEntity.Streetname != data.dataEntity.Streetname ||
data.dbEntity.Streetnumber != data.dataEntity.Streetnumber ||
data.dbEntity.Zipcode != data.dataEntity.Zipcode ||
data.dbEntity.CountryAlpha2Code != data.dataEntity.CountryAlpha2Code;

public static void UpdateAddress(this Address dbEntry, Address entry)
{
dbEntry.City = entry.City;
dbEntry.Region = entry.Region;
dbEntry.Streetadditional = entry.Streetadditional;
dbEntry.Streetname = entry.Streetname;
dbEntry.Streetnumber = entry.Streetnumber;
dbEntry.Zipcode = entry.Zipcode;
dbEntry.CountryAlpha2Code = entry.CountryAlpha2Code;
}

#endregion
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
<OutputType>Exe</OutputType>
<!-- Exclude the project from analysis -->
<SonarQubeExclude>true</SonarQubeExclude>
<NoWarn>CS1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.10">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Org.Eclipse.TractusX.Portal.Backend.Framework.Seeding;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.Migrations.Extensions;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Entities;

Expand Down Expand Up @@ -144,11 +145,13 @@ await SeedTable<TechnicalUser>("technical_users",

await SeedTable<Company>("companies",
x => x.Id,
x => x.dbEntity.SelfDescriptionDocumentId == null && x.dataEntity.SelfDescriptionDocumentId != x.dbEntity.SelfDescriptionDocumentId,
(dbEntry, entry) =>
{
dbEntry.SelfDescriptionDocumentId = entry.SelfDescriptionDocumentId;
}, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);
SeedingExtensions.UpdateCompanyNeeded,
(dbEntry, entry) => dbEntry.UpdateCompany(entry), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);

await SeedTable<Address>("addresses",
x => x.Id,
SeedingExtensions.UpdateAddressNeeded,
(dbEntry, entry) => dbEntry.UpdateAddress(entry), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);

await SeedTable<CompanyApplication>("company_applications",
x => x.Id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
"id": "b4db3945-19a7-4a50-97d6-e66e8dfd04fb",
"date_created": "2022-03-24 18:01:33.306000 +00:00",
"date_last_changed": "2022-03-24 18:01:33.306000 +00:00",
"city": "Munich",
"region": null,
"streetadditional": null,
"streetname": "Street",
"streetnumber": "1",
"zipcode": "00001",
"city": "tbd",
"region": "",
"streetadditional": "",
"streetname": "tbd",
"streetnumber": "",
"zipcode": "",
"country_alpha2code": "DE"
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"id": "2dc4249f-b5ca-4d42-bef1-7a7a950a4f87",
"date_created": "2022-03-24 18:01:33.306000 +00:00",
"business_partner_number": "BPNL00000003CRHK",
"name": "Catena-X",
"shortname": "Catena-X",
"name": "CX-Operator",
"shortname": "CX-Operator",
"company_status_id": 2,
"address_id": "b4db3945-19a7-4a50-97d6-e66e8dfd04fb",
"self_description_document_id": "00000000-0000-0000-0000-000000000009"
Expand Down
3 changes: 2 additions & 1 deletion src/portalbackend/PortalBackend.Migrations/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"DeleteIntervalInDays": 80,
"Seeding": {
"DataPaths": [
"Seeder/Data"
"Seeder/Data",
"Seeder/Data/overwrite"
],
"TestDataEnvironments": []
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -450,14 +450,14 @@ public async Task GetCompanyDetailsAsync_ReturnsExpected()
result.Should().NotBeNull();

result!.CompanyId.Should().Be(new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"));
result.Name.Should().Be("Catena-X");
result.ShortName.Should().Be("Catena-X");
result.Name.Should().Be("CX-Operator");
result.ShortName.Should().Be("CX-Operator");
result.BusinessPartnerNumber.Should().Be("BPNL00000003CRHK");
result.CountryAlpha2Code.Should().Be("DE");
result.City.Should().Be("Munich");
result.StreetName.Should().Be("Street");
result.StreetNumber.Should().Be("1");
result.ZipCode.Should().Be("00001");
result.City.Should().Be("tbd");
result.StreetName.Should().Be("tbd");
result.StreetNumber.Should().Be("");
result.ZipCode.Should().Be("");
result.CompanyRole.Should().Contain(CompanyRoleId.ACTIVE_PARTICIPANT);
}

Expand Down Expand Up @@ -706,7 +706,7 @@ public async Task GetOwnCompanAndCompanyUseryIdWithCompanyNameAndUserEmailAsync_

// Assert
result.Should().NotBeNull();
result!.OrganizationName.Should().Be("Catena-X");
result!.OrganizationName.Should().Be("CX-Operator");
result.CompanyUserEmail.Should().Be("[email protected]");
}

Expand Down Expand Up @@ -739,7 +739,7 @@ public async Task GetOperatorBpns_ReturnsExpectedResult()
// Assert
result.Should().ContainSingle()
.Which.Should().BeOfType<OperatorBpnData>()
.And.Match<OperatorBpnData>(x => x.Bpn == "BPNL00000003CRHK" && x.OperatorName == "Catena-X");
.And.Match<OperatorBpnData>(x => x.Bpn == "BPNL00000003CRHK" && x.OperatorName == "CX-Operator");
}

#endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ public async Task GetOwnCompanyIdentityProviderAliasUntrackedAsync_WithValid_Ret
[InlineData("38f56465-ce26-4f25-9745-1791620dc198", "3390c2d7-75c1-4169-aa27-6ce00e1f3cdd", true, "Idp-123", false, true, "Bayerische Motorenwerke AG", new[] { "3390c2d7-75c1-4169-aa27-6ce00e1f3cdd", "0dcd8209-85e2-4073-b130-ac094fb47106" })]
[InlineData("38f56465-ce26-4f25-9745-1791620dc199", "ac861325-bc54-4583-bcdc-9e9f2a38ff84", true, "Test-Alias", false, false, "CX-Test-Access", new[] { "2dc4249f-b5ca-4d42-bef1-7a7a950a4f88" })]
[InlineData("38f56465-ce26-4f25-9745-1791620dc199", "2dc4249f-b5ca-4d42-bef1-7a7a950a4f88", true, "Test-Alias", true, false, "CX-Test-Access", new[] { "2dc4249f-b5ca-4d42-bef1-7a7a950a4f88" })]
[InlineData("38f56465-ce26-4f25-9745-1791620dc200", "41fd2ab8-71cd-4546-9bef-a388d91b2542", true, "Test-Alias2", false, false, "Catena-X", new[] { "41fd2ab8-71cd-4546-9bef-a388d91b2542", "41fd2ab8-7123-4546-9bef-a388d91b2999", "3390c2d7-75c1-4169-aa27-6ce00e1f3cdd", "0dcd8209-85e2-4073-b130-ac094fb47106", "2dc4249f-b5ca-4d42-bef1-7a7a950a4f88" })]
[InlineData("38f56465-ce26-4f25-9745-1791620dc200", "41fd2ab8-71cd-4546-9bef-a388d91b2542", false, "Test-Alias2", false, false, "Catena-X", new[] { "41fd2ab8-71cd-4546-9bef-a388d91b2542", "41fd2ab8-7123-4546-9bef-a388d91b2999", "3390c2d7-75c1-4169-aa27-6ce00e1f3cdd", "0dcd8209-85e2-4073-b130-ac094fb47106", "2dc4249f-b5ca-4d42-bef1-7a7a950a4f88" })]
[InlineData("38f56465-ce26-4f25-9745-1791620dc200", "41fd2ab8-71cd-4546-9bef-a388d91b2542", true, "Test-Alias2", false, false, "CX-Operator", new[] { "41fd2ab8-71cd-4546-9bef-a388d91b2542", "41fd2ab8-7123-4546-9bef-a388d91b2999", "3390c2d7-75c1-4169-aa27-6ce00e1f3cdd", "0dcd8209-85e2-4073-b130-ac094fb47106", "2dc4249f-b5ca-4d42-bef1-7a7a950a4f88" })]
[InlineData("38f56465-ce26-4f25-9745-1791620dc200", "41fd2ab8-71cd-4546-9bef-a388d91b2542", false, "Test-Alias2", false, false, "CX-Operator", new[] { "41fd2ab8-71cd-4546-9bef-a388d91b2542", "41fd2ab8-7123-4546-9bef-a388d91b2999", "3390c2d7-75c1-4169-aa27-6ce00e1f3cdd", "0dcd8209-85e2-4073-b130-ac094fb47106", "2dc4249f-b5ca-4d42-bef1-7a7a950a4f88" })]
public async Task GetOwnCompanyIdentityProviderStatusUpdateData_WithValidOwner_ReturnsExpected(Guid identityProviderId, Guid companyId, bool queryAliase, string alias, bool isOwner, bool companyUsersLinked, string ownerCompanyName, IEnumerable<string>? companyIds)
{
var sut = await CreateSut();
Expand Down Expand Up @@ -185,7 +185,7 @@ public async Task GetOwnCompanyIdentityProviderUpdateData_WithValidOwner_Returns
[InlineData("38f56465-ce26-4f25-9745-1791620dc198", "3390c2d7-75c1-4169-aa27-6ce00e1f3cdd", "Idp-123", false, "Bayerische Motorenwerke AG", new[] { "3390c2d7-75c1-4169-aa27-6ce00e1f3cdd", "0dcd8209-85e2-4073-b130-ac094fb47106" })]
[InlineData("38f56465-ce26-4f25-9745-1791620dc199", "ac861325-bc54-4583-bcdc-9e9f2a38ff84", "Test-Alias", false, "CX-Test-Access", new[] { "2dc4249f-b5ca-4d42-bef1-7a7a950a4f88" })]
[InlineData("38f56465-ce26-4f25-9745-1791620dc199", "2dc4249f-b5ca-4d42-bef1-7a7a950a4f88", "Test-Alias", true, "CX-Test-Access", new[] { "2dc4249f-b5ca-4d42-bef1-7a7a950a4f88" })]
[InlineData("38f56465-ce26-4f25-9745-1791620dc200", "41fd2ab8-71cd-4546-9bef-a388d91b2542", "Test-Alias2", false, "Catena-X", new[] { "41fd2ab8-71cd-4546-9bef-a388d91b2542", "41fd2ab8-7123-4546-9bef-a388d91b2999", "3390c2d7-75c1-4169-aa27-6ce00e1f3cdd", "0dcd8209-85e2-4073-b130-ac094fb47106", "2dc4249f-b5ca-4d42-bef1-7a7a950a4f88" })]
[InlineData("38f56465-ce26-4f25-9745-1791620dc200", "41fd2ab8-71cd-4546-9bef-a388d91b2542", "Test-Alias2", false, "CX-Operator", new[] { "41fd2ab8-71cd-4546-9bef-a388d91b2542", "41fd2ab8-7123-4546-9bef-a388d91b2999", "3390c2d7-75c1-4169-aa27-6ce00e1f3cdd", "0dcd8209-85e2-4073-b130-ac094fb47106", "2dc4249f-b5ca-4d42-bef1-7a7a950a4f88" })]
public async Task GetOwnCompanyIdentityProviderUpdateDataForDelete_WithValidOwner_ReturnsExpected(Guid identityProviderId, Guid companyId, string alias, bool isOwner, string ownerCompanyName, IEnumerable<string>? companyIds)
{
var sut = await CreateSut();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -836,8 +836,8 @@ public async Task CreateUpdateDeleteOfferDescriptions_Deleted_ReturnsExpectedRes
#region GetOfferReleaseDataById

[Theory]
[InlineData("99C5FD12-8085-4DE2-ABFD-215E1EE4BAA9", "Test App", "Catena-X", false)]
[InlineData("99C5FD12-8085-4DE2-ABFD-215E1EE4BAA7", "Latest App", "Catena-X", true)]
[InlineData("99C5FD12-8085-4DE2-ABFD-215E1EE4BAA9", "Test App", "CX-Operator", false)]
[InlineData("99C5FD12-8085-4DE2-ABFD-215E1EE4BAA7", "Latest App", "CX-Operator", true)]
public async Task GetOfferReleaseDataByIdAsync_ReturnsExpected(Guid offerId, string name, string companyName, bool hasPrivacyPolicies)
{
// Arrange
Expand Down Expand Up @@ -1194,7 +1194,7 @@ public async Task GetAllInReviewStatusServiceAsync_ReturnsExpectedResult_WithDat
// Assert
result.Should().NotBeNull();
result!.Data.Should().HaveCount(4)
.And.StartWith(new InReviewServiceData(new Guid("ac1cf001-7fbc-1f2f-817f-bce0000c0001"), "Consulting Service - Data Readiness", OfferStatusId.ACTIVE, "Catena-X", "Lorem ipsum dolor sit amet, consectetur adipiscing elit."));
.And.StartWith(new InReviewServiceData(new Guid("ac1cf001-7fbc-1f2f-817f-bce0000c0001"), "Consulting Service - Data Readiness", OfferStatusId.ACTIVE, "CX-Operator", "Lorem ipsum dolor sit amet, consectetur adipiscing elit."));
}

#endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ public async Task GetOwnCompanyProvidedOfferSubscriptionStatusesUntrackedAsync_W
var (sut, _) = await CreateSut();

// Act
var results = await sut.GetOwnCompanyProvidedOfferSubscriptionStatusesUntrackedAsync(_userCompanyId, OfferTypeId.SERVICE, SubscriptionStatusSorting.CompanyNameAsc, new[] { OfferSubscriptionStatusId.ACTIVE, OfferSubscriptionStatusId.PENDING }, null, "catena")(0, 15);
var results = await sut.GetOwnCompanyProvidedOfferSubscriptionStatusesUntrackedAsync(_userCompanyId, OfferTypeId.SERVICE, SubscriptionStatusSorting.CompanyNameAsc, new[] { OfferSubscriptionStatusId.ACTIVE, OfferSubscriptionStatusId.PENDING }, null, "operator")(0, 15);

// Assert
results.Should().NotBeNull();
Expand Down Expand Up @@ -210,7 +210,7 @@ public async Task GetOfferDetailsAndCheckUser_WithValidUserAndSubscriptionId_Ret
result!.OfferId.Should().Be(new Guid("ac1cf001-7fbc-1f2f-817f-bce0572c0007"));
result.Status.Should().Be(OfferSubscriptionStatusId.ACTIVE);
result.CompanyId.Should().Be(new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"));
result.CompanyName.Should().Be("Catena-X");
result.CompanyName.Should().Be("CX-Operator");
result.IsProviderCompany.Should().BeTrue();
result.Bpn.Should().Be("BPNL00000003CRHK");
result.OfferName.Should().Be("Trace-X");
Expand All @@ -233,7 +233,7 @@ public async Task GetOfferDetailsAndCheckUser_WithSubscriptionForOfferWithoutApp
result!.OfferId.Should().Be(new Guid("a16e73b9-5277-4b69-9f8d-3b227495dfae"));
result.Status.Should().Be(OfferSubscriptionStatusId.ACTIVE);
result.CompanyId.Should().Be(new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"));
result.CompanyName.Should().Be("Catena-X");
result.CompanyName.Should().Be("CX-Operator");
result.IsProviderCompany.Should().BeTrue();
result.Bpn.Should().Be("BPNL00000003CRHK");
result.OfferName.Should().Be("Service Test 123");
Expand All @@ -259,7 +259,7 @@ public async Task GetSubscriptionDetailForProviderAsync_ReturnsExpected()
result.IsUserOfCompany.Should().BeTrue();
result.Details.Should().NotBeNull().And.Match<ProviderSubscriptionDetailData>(x =>
x.Name == "SDE with EDC" &&
x.Customer == "Catena-X" &&
x.Customer == "CX-Operator" &&
x.Contact.SequenceEqual(new[] { "[email protected]" }) &&
x.OfferSubscriptionStatus == OfferSubscriptionStatusId.ACTIVE
&& x.TechnicalUserData.All(x => x.Id == new Guid("d0c8ae19-d4f3-49cc-9cb4-6c766d4680f2") && x.Name == "sa-x-4"));
Expand Down Expand Up @@ -313,7 +313,7 @@ public async Task GetAppSubscriptionDetailForProviderAsync_ReturnsExpected()
result.IsUserOfCompany.Should().BeTrue();
result.Details.Should().NotBeNull().And.Match<AppProviderSubscriptionDetail>(x =>
x.Name == "SDE with EDC" &&
x.Customer == "Catena-X" &&
x.Customer == "CX-Operator" &&
x.Contact.SequenceEqual(new[] { "[email protected]" }) &&
x.OfferSubscriptionStatus == OfferSubscriptionStatusId.ACTIVE &&
x.TenantUrl == "https://ec-qas.d13fe27.kyma.ondemand.com" &&
Expand Down Expand Up @@ -869,13 +869,13 @@ public async Task GetOwnCompanySubscribedOfferSubscriptionStatusAsync_ReturnsExp
x => x.OfferId == new Guid("ac1cf001-7fbc-1f2f-817f-bce0572c0007") &&
x.OfferSubscriptionStatusId == OfferSubscriptionStatusId.ACTIVE &&
x.OfferName == "Trace-X" &&
x.Provider == "Catena-X" &&
x.Provider == "CX-Operator" &&
x.OfferSubscriptionId == new Guid("ed4de48d-fd4b-4384-a72f-ecae3c6cc5ba") &&
x.DocumentId == new Guid("e020787d-1e04-4c0b-9c06-bd1cd44724b1"),
x => x.OfferId == new Guid("ac1cf001-7fbc-1f2f-817f-bce0572c0007") &&
x.OfferSubscriptionStatusId == OfferSubscriptionStatusId.PENDING &&
x.OfferName == "Trace-X" &&
x.Provider == "Catena-X" &&
x.Provider == "CX-Operator" &&
x.OfferSubscriptionId == new Guid("e8886159-9258-44a5-88d8-f5735a197a09") &&
x.DocumentId == new Guid("e020787d-1e04-4c0b-9c06-bd1cd44724b1")
);
Expand Down Expand Up @@ -920,7 +920,7 @@ public async Task GetOwnCompanySubscribedOfferSubscriptionStatusAsync_WithStatus
x => x.OfferId == new Guid("ac1cf001-7fbc-1f2f-817f-bce0572c0007") &&
x.OfferSubscriptionStatusId == OfferSubscriptionStatusId.ACTIVE &&
x.OfferName == "Trace-X" &&
x.Provider == "Catena-X" &&
x.Provider == "CX-Operator" &&
x.OfferSubscriptionId == new Guid("ed4de48d-fd4b-4384-a72f-ecae3c6cc5ba") &&
x.DocumentId == new Guid("e020787d-1e04-4c0b-9c06-bd1cd44724b1")
);
Expand Down Expand Up @@ -1070,7 +1070,7 @@ public async Task GetOwnCompanyActiveSubscribedOfferSubscriptionStatusesUntracke
result.Should().HaveCount(1).And.Satisfy(
x => x.OfferId == new Guid("ac1cf001-7fbc-1f2f-817f-bce0572c0007") &&
x.OfferName == "Trace-X" &&
x.Provider == "Catena-X" &&
x.Provider == "CX-Operator" &&
x.DocumentId == new Guid("e020787d-1e04-4c0b-9c06-bd1cd44724b1") &&
x.OfferSubscriptionId == new Guid("ed4de48d-fd4b-4384-a72f-ecae3c6cc5ba"));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public async Task GetServiceDetailByIdUntrackedAsync_ReturnsServiceDetailData()
result.Should().NotBeNull();
result!.Title.Should().Be("Consulting Service - Data Readiness");
result.ContactEmail.Should().BeNull();
result.Provider.Should<string>().Be("Catena-X");
result.Provider.Should<string>().Be("CX-Operator");
}

#endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public async Task InitializeAsync()
var seederOptions = Options.Create(new SeederSettings
{
TestDataEnvironments = new[] { "unittest" },
DataPaths = new[] { "Seeder/Data" }
DataPaths = new[] { "Seeder/Data", "Seeder/Data/overwrite" }
});
var insertSeeder = new BatchInsertSeeder(context,
LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger<BatchInsertSeeder>(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ await _container.StartAsync()
var seederOptions = Options.Create(new SeederSettings
{
TestDataEnvironments = new[] { "test" },
DataPaths = new[] { "Seeder/Data" }
DataPaths = new[] { "Seeder/Data", "Seeder/Data/overwrite" }
});
var insertSeeder = new BatchInsertSeeder(context,
LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger<BatchInsertSeeder>(),
Expand Down

0 comments on commit f4a371b

Please sign in to comment.