Company options values, portfolio options values migration done
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
package com.cygnus.client;
|
||||
|
||||
import com.cygnus.client.model.CloudIdentitySession;
|
||||
import com.cygnus.client.model.CloudDataItem;
|
||||
import com.cygnus.client.model.CloudDataRequest;
|
||||
import com.cygnus.client.model.CloudQueryResponse;
|
||||
import com.cygnus.client.model.LoginPayload;
|
||||
import com.cygnus.client.security.LoginEnvelopeEncryptor;
|
||||
import com.cygnus.client.security.MachineTokenProvider;
|
||||
import java.time.Clock;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -73,4 +77,18 @@ public class CloudIdentityClient {
|
||||
.bodyToMono(CloudQueryResponse.class))
|
||||
.timeout(properties.requestTimeout());
|
||||
}
|
||||
|
||||
public Mono<List<CloudDataItem>> fetchData(String scope, Map<String, Object> data) {
|
||||
return tokenProvider.accessToken()
|
||||
.flatMap(token -> webClient.post()
|
||||
.uri(properties.baseUri().resolve("/api/v1/platform/data"))
|
||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(new CloudDataRequest(scope, Map.copyOf(data)))
|
||||
.retrieve()
|
||||
.bodyToFlux(CloudDataItem.class)
|
||||
.collectList())
|
||||
.timeout(properties.requestTimeout());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.cygnus.client.model;
|
||||
|
||||
public record CloudDataItem(Object value, String label, String group) {
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.cygnus.client.model;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public record CloudDataRequest(String scope, Map<String, Object> data) {
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.cygnus.cloud.platform.api;
|
||||
|
||||
import com.cygnus.cloud.platform.service.ScopedDataException;
|
||||
import com.cygnus.cloud.platform.service.ScopedDataService;
|
||||
import jakarta.validation.Valid;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/platform/data")
|
||||
public class ScopedDataController {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ScopedDataController.class);
|
||||
private final ScopedDataService service;
|
||||
|
||||
public ScopedDataController(ScopedDataService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public Flux<ScopedDataItem> data(
|
||||
@AuthenticationPrincipal Jwt machineJwt,
|
||||
@Valid @RequestBody ScopedDataRequest request) {
|
||||
if (machineJwt == null) throw new ScopedDataException("Machine authentication required");
|
||||
try {
|
||||
return service.fetch(UUID.fromString(machineJwt.getClaimAsString("tenant_id")), request);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
throw new ScopedDataException("Machine tenant is invalid");
|
||||
}
|
||||
}
|
||||
|
||||
@ExceptionHandler(ScopedDataException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
Map<String, String> invalidRequest(ScopedDataException exception) {
|
||||
LOGGER.warn("Scoped platform-data request rejected: {}", exception.getMessage());
|
||||
return Map.of("code", "PLATFORM_DATA_INVALID", "message", exception.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.cygnus.cloud.platform.api;
|
||||
|
||||
public record ScopedDataItem(
|
||||
Object value,
|
||||
String label,
|
||||
String group) {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.cygnus.cloud.platform.api;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.util.Map;
|
||||
|
||||
public record ScopedDataRequest(
|
||||
@NotBlank String scope,
|
||||
@NotNull Map<String, Object> data) {
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.cygnus.cloud.platform.repository;
|
||||
|
||||
import com.cygnus.cloud.database.ReactiveDatabaseClient;
|
||||
import com.cygnus.cloud.platform.api.ScopedDataItem;
|
||||
import io.vertx.sqlclient.Row;
|
||||
import io.vertx.sqlclient.Tuple;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
@Repository
|
||||
public class ScopedDataRepository {
|
||||
private static final String COMPANY_OPTIONS = """
|
||||
SELECT ov.opt_value AS value,
|
||||
ov.val_description AS label,
|
||||
lower(o.description) || '.' AS group_name
|
||||
FROM platform.company_options_mapping m
|
||||
JOIN platform.options o ON o.option_id = m.option_id
|
||||
JOIN platform.options_values ov ON ov.opt_value_id = m.opt_value_id
|
||||
JOIN identity.company c ON c.company_id = m.company_id
|
||||
WHERE c.tenant_id = $1
|
||||
AND m.company_id = $2
|
||||
AND o.isactive = 1
|
||||
AND ov.isactive = 1
|
||||
AND o.description = ANY($3::text[])
|
||||
ORDER BY o.description, ov.val_description
|
||||
""";
|
||||
private static final String PORTFOLIO_OPTIONS = """
|
||||
SELECT ov.opt_value AS value,
|
||||
ov.val_description AS label,
|
||||
lower(o.description) || '.' AS group_name
|
||||
FROM platform.options_mapping m
|
||||
JOIN platform.options o ON o.option_id = m.option_id
|
||||
JOIN platform.options_values ov ON ov.opt_value_id = m.opt_value_id
|
||||
JOIN identity.company c ON c.company_id = m.company_id
|
||||
JOIN platform.company_branch_portfolios cbp
|
||||
ON cbp.company_id = m.company_id
|
||||
AND cbp.portfolio_reference = m.portfolio_reference
|
||||
WHERE c.tenant_id = $1
|
||||
AND m.company_id = $2
|
||||
AND cbp.company_branch_id = $3
|
||||
AND m.portfolio_reference = $4
|
||||
AND o.isactive = 1
|
||||
AND ov.isactive = 1
|
||||
AND o.description = ANY($5::text[])
|
||||
ORDER BY o.description, ov.val_description
|
||||
""";
|
||||
|
||||
private final ReactiveDatabaseClient database;
|
||||
|
||||
public ScopedDataRepository(ReactiveDatabaseClient database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
public Flux<ScopedDataItem> companyOptions(
|
||||
UUID tenantId, short companyId, List<String> descriptions) {
|
||||
return rows(COMPANY_OPTIONS, Tuple.of(tenantId, companyId, descriptions.toArray(String[]::new)));
|
||||
}
|
||||
|
||||
public Flux<ScopedDataItem> portfolioOptions(
|
||||
UUID tenantId, short companyId, short branchId, UUID portfolioReference,
|
||||
List<String> descriptions) {
|
||||
return rows(PORTFOLIO_OPTIONS,
|
||||
Tuple.of(tenantId, companyId, branchId, portfolioReference,
|
||||
descriptions.toArray(String[]::new)));
|
||||
}
|
||||
|
||||
private Flux<ScopedDataItem> rows(String sql, Tuple parameters) {
|
||||
return database.preparedQuery(sql, parameters)
|
||||
.flatMapMany(result -> Flux.fromIterable(result))
|
||||
.map(this::item);
|
||||
}
|
||||
|
||||
private ScopedDataItem item(Row row) {
|
||||
return new ScopedDataItem(
|
||||
row.getValue("value"), row.getString("label"), row.getString("group_name"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.cygnus.cloud.platform.service;
|
||||
|
||||
public class ScopedDataException extends RuntimeException {
|
||||
public ScopedDataException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.cygnus.cloud.platform.service;
|
||||
|
||||
import com.cygnus.cloud.platform.api.ScopedDataItem;
|
||||
import com.cygnus.cloud.platform.api.ScopedDataRequest;
|
||||
import com.cygnus.cloud.platform.repository.ScopedDataRepository;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
@Service
|
||||
public class ScopedDataService {
|
||||
public static final String COMPANY_OPTIONS = "company-options";
|
||||
public static final String PORTFOLIO_OPTIONS = "portfolio-options";
|
||||
|
||||
private final ScopedDataRepository repository;
|
||||
|
||||
public ScopedDataService(ScopedDataRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public Flux<ScopedDataItem> fetch(UUID tenantId, ScopedDataRequest request) {
|
||||
Map<String, Object> data = request.data();
|
||||
short companyId = shortValue(data, "companyId");
|
||||
List<String> descriptions = descriptions(data);
|
||||
return switch (request.scope().trim().toLowerCase(Locale.ROOT)) {
|
||||
case COMPANY_OPTIONS -> repository.companyOptions(tenantId, companyId, descriptions);
|
||||
case PORTFOLIO_OPTIONS -> repository.portfolioOptions(
|
||||
tenantId, companyId, shortValue(data, "branchId"),
|
||||
uuidValue(data, "portfolioReference"), descriptions);
|
||||
default -> Flux.error(new ScopedDataException("Unsupported data scope"));
|
||||
};
|
||||
}
|
||||
|
||||
private UUID uuidValue(Map<String, Object> data, String name) {
|
||||
try {
|
||||
return UUID.fromString(String.valueOf(data.get(name)));
|
||||
} catch (RuntimeException exception) {
|
||||
throw new ScopedDataException(name + " must be a valid UUID");
|
||||
}
|
||||
}
|
||||
|
||||
private short shortValue(Map<String, Object> data, String name) {
|
||||
Object value = data.get(name);
|
||||
try {
|
||||
short result = value instanceof Number number
|
||||
? number.shortValue() : Short.parseShort(String.valueOf(value));
|
||||
if (result <= 0) throw new NumberFormatException();
|
||||
return result;
|
||||
} catch (RuntimeException exception) {
|
||||
throw new ScopedDataException(name + " must be a positive number");
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> descriptions(Map<String, Object> data) {
|
||||
Object value = data.get("descriptions");
|
||||
if (!(value instanceof List<?> values)) {
|
||||
throw new ScopedDataException("descriptions must be a non-empty list");
|
||||
}
|
||||
List<String> result = values.stream()
|
||||
.map(String::valueOf)
|
||||
.map(String::trim)
|
||||
.filter(text -> !text.isEmpty())
|
||||
.map(text -> text.toUpperCase(Locale.ROOT))
|
||||
.distinct()
|
||||
.toList();
|
||||
if (result.isEmpty() || result.size() > 20) {
|
||||
throw new ScopedDataException("descriptions must contain between 1 and 20 values");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,8 @@ public class CloudSecurityConfiguration {
|
||||
.hasAuthority("SCOPE_identity.login")
|
||||
.pathMatchers("/api/v1/queries/**")
|
||||
.hasAuthority("SCOPE_identity.login")
|
||||
.pathMatchers("/api/v1/platform/data")
|
||||
.hasAuthority("SCOPE_identity.login")
|
||||
.pathMatchers("/api/v1/admin/**")
|
||||
.hasAuthority("SCOPE_cygnus.admin")
|
||||
.anyExchange().authenticated())
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package matrix.services.commons;
|
||||
|
||||
import com.cygnus.db.CygnusDbExecutor;
|
||||
import com.cygnus.client.CloudIdentityClient;
|
||||
import com.cygnus.client.model.CloudDataItem;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import lib.models.Option;
|
||||
import lib.models.UserSession;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -23,16 +26,19 @@ public class CommonService {
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
|
||||
private final CygnusDbExecutor dbExecutor;
|
||||
private final CloudIdentityClient cloudIdentityClient;
|
||||
|
||||
@Autowired
|
||||
public CommonService(CygnusDbExecutor dbExecutor) {
|
||||
public CommonService(CygnusDbExecutor dbExecutor, CloudIdentityClient cloudIdentityClient) {
|
||||
this.dbExecutor = Objects.requireNonNull(dbExecutor, "dbExecutor");
|
||||
this.cloudIdentityClient = Objects.requireNonNull(cloudIdentityClient, "cloudIdentityClient");
|
||||
}
|
||||
|
||||
/* Used by the session-only unit tests. Production construction is handled
|
||||
by Spring through the CygnusDbExecutor constructor above. */
|
||||
public CommonService() {
|
||||
this.dbExecutor = null;
|
||||
this.cloudIdentityClient = null;
|
||||
}
|
||||
|
||||
public Session getSession(HttpSession httpSession) {
|
||||
@@ -138,4 +144,22 @@ public class CommonService {
|
||||
Object[] parameters = queryParams == null ? new Object[0] : queryParams;
|
||||
return dbExecutor.query(queryId, parameters, Option.class);
|
||||
}
|
||||
|
||||
public List<Option> getCloudOptions(String scope, Map<String, Object> data) {
|
||||
if (cloudIdentityClient == null) {
|
||||
throw new IllegalStateException("CloudIdentityClient is not configured");
|
||||
}
|
||||
return cloudIdentityClient.fetchData(scope, data)
|
||||
.map(items -> items.stream().map(this::option).toList())
|
||||
.blockOptional()
|
||||
.orElseGet(List::of);
|
||||
}
|
||||
|
||||
private Option option(CloudDataItem item) {
|
||||
Option option = new Option();
|
||||
option.setValue(item.value());
|
||||
option.setLabel(item.label());
|
||||
option.setGroup(item.group());
|
||||
return option;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package matrix.services.edp;
|
||||
|
||||
import com.cygnus.db.CygnusDbExecutor;
|
||||
import com.cygnus.db.RowView;
|
||||
import com.cygnus.db.SqlArrayParameter;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -10,6 +9,7 @@ import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Date;
|
||||
@@ -60,8 +60,8 @@ public class PunchingService {
|
||||
private static final String ADD_CASE_TEMPLATE = "ADDCASE";
|
||||
private static final Pattern DYNAMIC_FIELD = Pattern.compile(
|
||||
"(?:field(?:[1-9]|1[0-2]|150(?:_2)?)|dtfield1)", Pattern.CASE_INSENSITIVE);
|
||||
private static final SqlArrayParameter GENERAL_OPTION_TYPES = SqlArrayParameter.text("CITY", "APPTYPE");
|
||||
private static final SqlArrayParameter PORTFOLIO_OPTION_TYPES = SqlArrayParameter.text("CATEGORY", "PRODUCT");
|
||||
private static final List<String> GENERAL_OPTION_TYPES = List.of("CITY", "APPTYPE");
|
||||
private static final List<String> PORTFOLIO_OPTION_TYPES = List.of("CATEGORY", "PRODUCT");
|
||||
|
||||
private final CommonService commonService;
|
||||
private final CygnusDbExecutor dbExecutor;
|
||||
@@ -467,17 +467,43 @@ public class PunchingService {
|
||||
Short branchId, Short companyId, Short portfolioId) {
|
||||
|
||||
Map<String, List<Option>> options = new LinkedHashMap<>();
|
||||
addOptions(options, PORTFOLIO, commonService.getOptions(
|
||||
3, new Object[] { branchId, ACTIVE }));
|
||||
List<Option> portfolios = commonService.getOptions(
|
||||
3, new Object[] { branchId, ACTIVE });
|
||||
addOptions(options, PORTFOLIO, portfolios);
|
||||
addOptions(options, BRANCH, commonService.getOptions(
|
||||
4, new Object[] { portfolioId, ACTIVE }));
|
||||
addGroupedOptions(options, commonService.getOptions(
|
||||
5, new Object[] { companyId, GENERAL_OPTION_TYPES }));
|
||||
addGroupedOptions(options, commonService.getOptions(
|
||||
70, new Object[] { portfolioId, PORTFOLIO_OPTION_TYPES }));
|
||||
addGroupedOptions(options, commonService.getCloudOptions(
|
||||
"company-options", Map.of(
|
||||
"companyId", companyId,
|
||||
"descriptions", GENERAL_OPTION_TYPES)));
|
||||
addGroupedOptions(options, commonService.getCloudOptions(
|
||||
"portfolio-options", Map.of(
|
||||
"companyId", companyId,
|
||||
"branchId", branchId,
|
||||
"portfolioReference", portfolioReference(portfolios, portfolioId),
|
||||
"descriptions", PORTFOLIO_OPTION_TYPES)));
|
||||
return freezeOptions(options);
|
||||
}
|
||||
|
||||
private UUID portfolioReference(List<Option> portfolios, Short portfolioId) {
|
||||
String reference = portfolios.stream()
|
||||
.filter(option -> option != null
|
||||
&& String.valueOf(option.getValue()).equals(String.valueOf(portfolioId)))
|
||||
.map(Option::getDescription)
|
||||
.filter(candidate -> candidate != null && !candidate.isBlank())
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new ApplicationException(
|
||||
ApplicationMessage.APPLICATION_ACCESS_DENIED,
|
||||
"Portfolio reference is unavailable"));
|
||||
try {
|
||||
return UUID.fromString(reference);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
throw new ApplicationException(
|
||||
ApplicationMessage.APPLICATION_ACCESS_DENIED,
|
||||
"Portfolio reference is invalid");
|
||||
}
|
||||
}
|
||||
|
||||
public CasePunching prepareApplicationEdit(
|
||||
Short portfolioId,
|
||||
String documentCaseId,
|
||||
|
||||
@@ -6,7 +6,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import com.cygnus.db.CygnusDbExecutor;
|
||||
import com.cygnus.db.RowView;
|
||||
import com.cygnus.db.SqlArrayParameter;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.ArrayList;
|
||||
@@ -131,19 +130,12 @@ class PunchingServiceTest {
|
||||
|
||||
Map<String, List<Option>> options = service.loadOptions((short) 5, (short) 1, (short) 38);
|
||||
|
||||
assertEquals(List.of(3, 4, 5, 70),
|
||||
assertEquals(List.of(3, 4),
|
||||
commonService.calls.stream().map(Call::queryId).toList());
|
||||
assertArrayEquals(new Object[] { (short) 5, (short) 1 },
|
||||
commonService.calls.get(0).parameters());
|
||||
assertArrayEquals(new Object[] { (short) 38, (short) 1 },
|
||||
commonService.calls.get(1).parameters());
|
||||
assertEquals((short) 1, commonService.calls.get(2).parameters()[0]);
|
||||
assertArrayEquals(new Object[] { "CITY", "APPTYPE" },
|
||||
((SqlArrayParameter) commonService.calls.get(2).parameters()[1]).values());
|
||||
assertEquals((short) 38, commonService.calls.get(3).parameters()[0]);
|
||||
assertArrayEquals(new Object[] { "CATEGORY", "PRODUCT" },
|
||||
((SqlArrayParameter) commonService.calls.get(3).parameters()[1]).values());
|
||||
|
||||
assertEquals("Portfolio", options.get("portfolio.").getFirst().getLabel());
|
||||
assertEquals("Branch", options.get("branch.").getFirst().getLabel());
|
||||
assertEquals("City", options.get("city.").getFirst().getLabel());
|
||||
@@ -196,12 +188,20 @@ class PunchingServiceTest {
|
||||
public List<Option> getOptions(Integer queryId, Object[] queryParams) {
|
||||
calls.add(new Call(queryId, queryParams.clone()));
|
||||
return switch (queryId) {
|
||||
case 3 -> List.of(option(38, "Portfolio", "portfolio."));
|
||||
case 3 -> List.of(option(38, "Portfolio", "portfolio.",
|
||||
"88e0cb4a-3b4d-401c-b11b-fe4117842fef"));
|
||||
case 4 -> List.of(option(7, "Branch", "branch."));
|
||||
case 5 -> List.of(
|
||||
default -> List.of();
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Option> getCloudOptions(String scope, Map<String, Object> data) {
|
||||
return switch (scope) {
|
||||
case "company-options" -> List.of(
|
||||
option("DEL", "City", "city."),
|
||||
option("APPLICANT", "Applicant", "apptype."));
|
||||
case 70 -> List.of(
|
||||
case "portfolio-options" -> List.of(
|
||||
option("DIRECTOR", "Category", "category."),
|
||||
option("AUTO", "Product", "product."));
|
||||
default -> List.of();
|
||||
@@ -210,10 +210,15 @@ class PunchingServiceTest {
|
||||
}
|
||||
|
||||
private static Option option(Object value, String label, String group) {
|
||||
return option(value, label, group, null);
|
||||
}
|
||||
|
||||
private static Option option(Object value, String label, String group, String description) {
|
||||
Option option = new Option();
|
||||
option.setValue(value);
|
||||
option.setLabel(label);
|
||||
option.setGroup(group);
|
||||
option.setDescription(description);
|
||||
return option;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
BEGIN;
|
||||
|
||||
CREATE SCHEMA IF NOT EXISTS platform;
|
||||
|
||||
ALTER TABLE public.portfolio
|
||||
ADD COLUMN IF NOT EXISTS portfolio_reference uuid;
|
||||
|
||||
UPDATE public.portfolio
|
||||
SET portfolio_reference = gen_random_uuid()
|
||||
WHERE portfolio_reference IS NULL;
|
||||
|
||||
ALTER TABLE public.portfolio
|
||||
ALTER COLUMN portfolio_reference SET DEFAULT gen_random_uuid(),
|
||||
ALTER COLUMN portfolio_reference SET NOT NULL;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_portfolio_reference
|
||||
ON public.portfolio (portfolio_reference);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_company_branch_company_branch
|
||||
ON public.company_branch (company_id, branch_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS platform.company_branch_portfolios (
|
||||
company_id smallint NOT NULL,
|
||||
company_branch_id smallint NOT NULL,
|
||||
portfolio_reference uuid NOT NULL,
|
||||
createdon timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT pk_company_branch_portfolios
|
||||
PRIMARY KEY (company_id, company_branch_id, portfolio_reference),
|
||||
CONSTRAINT fk_company_branch_portfolios_branch
|
||||
FOREIGN KEY (company_id, company_branch_id)
|
||||
REFERENCES public.company_branch (company_id, branch_id),
|
||||
CONSTRAINT fk_company_branch_portfolios_portfolio
|
||||
FOREIGN KEY (portfolio_reference)
|
||||
REFERENCES public.portfolio (portfolio_reference)
|
||||
);
|
||||
|
||||
INSERT INTO platform.company_branch_portfolios (
|
||||
company_id,
|
||||
company_branch_id,
|
||||
portfolio_reference,
|
||||
createdon
|
||||
)
|
||||
SELECT
|
||||
p.company_id,
|
||||
p.branch_id,
|
||||
p.portfolio_reference,
|
||||
COALESCE(p.createdon, CURRENT_TIMESTAMP)
|
||||
FROM public.portfolio p
|
||||
JOIN public.company_branch cb
|
||||
ON cb.company_id = p.company_id
|
||||
AND cb.branch_id = p.branch_id
|
||||
ON CONFLICT (company_id, company_branch_id, portfolio_reference) DO NOTHING;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,145 @@
|
||||
BEGIN;
|
||||
|
||||
CREATE SCHEMA IF NOT EXISTS platform;
|
||||
|
||||
CREATE TABLE platform.options
|
||||
(LIKE public.options INCLUDING ALL);
|
||||
|
||||
CREATE TABLE platform.options_values
|
||||
(LIKE public.options_values INCLUDING ALL);
|
||||
|
||||
CREATE TABLE platform.options_mapping
|
||||
(LIKE public.options_mapping INCLUDING ALL);
|
||||
|
||||
CREATE TABLE platform.company_options_mapping
|
||||
(LIKE public.comp_options_mapping INCLUDING ALL);
|
||||
|
||||
CREATE TABLE platform.options_history
|
||||
(LIKE public.options_history INCLUDING ALL);
|
||||
|
||||
ALTER TABLE platform.options_mapping
|
||||
ADD COLUMN company_id smallint,
|
||||
ADD COLUMN portfolio_reference uuid;
|
||||
|
||||
INSERT INTO platform.options
|
||||
SELECT * FROM public.options;
|
||||
|
||||
INSERT INTO platform.options_values
|
||||
SELECT * FROM public.options_values;
|
||||
|
||||
INSERT INTO platform.options_mapping (
|
||||
portfolio_id, option_id, createdon, createdby, lasteditedon,
|
||||
lasteditedby, opt_value_id, opt_map_id, company_id, portfolio_reference
|
||||
)
|
||||
SELECT
|
||||
om.portfolio_id, om.option_id, om.createdon, om.createdby, om.lasteditedon,
|
||||
om.lasteditedby, om.opt_value_id, om.opt_map_id, 1,
|
||||
COALESCE(p.portfolio_reference, archived.portfolio_reference)
|
||||
FROM public.options_mapping om
|
||||
LEFT JOIN public.portfolio p ON p.portfolio_id = om.portfolio_id
|
||||
LEFT JOIN (
|
||||
SELECT portfolio_id, gen_random_uuid() portfolio_reference
|
||||
FROM public.options_mapping
|
||||
GROUP BY portfolio_id
|
||||
) archived ON archived.portfolio_id = om.portfolio_id;
|
||||
|
||||
INSERT INTO platform.company_options_mapping (
|
||||
company_id, option_id, createdon, createdby, lasteditedon,
|
||||
lasteditedby, opt_value_id, opt_map_id
|
||||
)
|
||||
SELECT
|
||||
1, option_id, createdon, createdby, lasteditedon,
|
||||
lasteditedby, opt_value_id, opt_map_id
|
||||
FROM public.comp_options_mapping;
|
||||
|
||||
INSERT INTO platform.options_history
|
||||
SELECT * FROM public.options_history;
|
||||
|
||||
ALTER TABLE platform.options_mapping
|
||||
ALTER COLUMN company_id SET NOT NULL,
|
||||
ALTER COLUMN portfolio_reference SET NOT NULL;
|
||||
|
||||
DO $duplicates$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM platform.options_mapping
|
||||
GROUP BY option_id, opt_value_id, portfolio_reference, company_id
|
||||
HAVING count(*) > 1
|
||||
) THEN
|
||||
RAISE EXCEPTION
|
||||
'Duplicate platform.options_mapping rows found for (option_id, opt_value_id, portfolio_reference, company_id)';
|
||||
END IF;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM platform.company_options_mapping
|
||||
GROUP BY option_id, opt_value_id, company_id
|
||||
HAVING count(*) > 1
|
||||
) THEN
|
||||
RAISE EXCEPTION
|
||||
'Duplicate platform.company_options_mapping rows found for (option_id, opt_value_id, company_id)';
|
||||
END IF;
|
||||
END
|
||||
$duplicates$;
|
||||
|
||||
ALTER TABLE platform.options_mapping
|
||||
ADD CONSTRAINT uq_platform_options_mapping_scope
|
||||
UNIQUE (option_id, opt_value_id, portfolio_reference, company_id);
|
||||
|
||||
ALTER TABLE platform.company_options_mapping
|
||||
ADD CONSTRAINT uq_platform_company_options_mapping_scope
|
||||
UNIQUE (option_id, opt_value_id, company_id);
|
||||
|
||||
-- CREATE TABLE ... LIKE does not copy foreign keys. Recreate the source FK
|
||||
-- against the copied platform table rather than retaining a public dependency.
|
||||
ALTER TABLE platform.options_mapping
|
||||
DROP CONSTRAINT IF EXISTS options_mapping_opt_value_id;
|
||||
|
||||
ALTER TABLE platform.options_mapping
|
||||
ADD CONSTRAINT options_mapping_opt_value_id
|
||||
FOREIGN KEY (opt_value_id)
|
||||
REFERENCES platform.options_values (opt_value_id)
|
||||
ON UPDATE CASCADE ON DELETE CASCADE NOT VALID;
|
||||
|
||||
-- Replace copied defaults that still point to public sequences.
|
||||
CREATE SEQUENCE platform.options_mapping_opt_map_id_seq;
|
||||
ALTER SEQUENCE platform.options_mapping_opt_map_id_seq
|
||||
OWNED BY platform.options_mapping.opt_map_id;
|
||||
ALTER TABLE platform.options_mapping
|
||||
ALTER COLUMN opt_map_id
|
||||
SET DEFAULT nextval('platform.options_mapping_opt_map_id_seq'::regclass);
|
||||
SELECT setval(
|
||||
'platform.options_mapping_opt_map_id_seq',
|
||||
COALESCE((SELECT max(opt_map_id) FROM platform.options_mapping), 1),
|
||||
EXISTS (SELECT 1 FROM platform.options_mapping)
|
||||
);
|
||||
|
||||
ALTER TABLE platform.options_mapping
|
||||
DROP COLUMN portfolio_id;
|
||||
|
||||
CREATE SEQUENCE platform.company_options_mapping_opt_map_id_seq;
|
||||
ALTER SEQUENCE platform.company_options_mapping_opt_map_id_seq
|
||||
OWNED BY platform.company_options_mapping.opt_map_id;
|
||||
ALTER TABLE platform.company_options_mapping
|
||||
ALTER COLUMN opt_map_id
|
||||
SET DEFAULT nextval('platform.company_options_mapping_opt_map_id_seq'::regclass);
|
||||
SELECT setval(
|
||||
'platform.company_options_mapping_opt_map_id_seq',
|
||||
COALESCE((SELECT max(opt_map_id) FROM platform.company_options_mapping), 1),
|
||||
EXISTS (SELECT 1 FROM platform.company_options_mapping)
|
||||
);
|
||||
|
||||
CREATE SEQUENCE platform.options_history_uid_seq;
|
||||
ALTER SEQUENCE platform.options_history_uid_seq
|
||||
OWNED BY platform.options_history.uid;
|
||||
ALTER TABLE platform.options_history
|
||||
ALTER COLUMN uid
|
||||
SET DEFAULT nextval('platform.options_history_uid_seq'::regclass);
|
||||
SELECT setval(
|
||||
'platform.options_history_uid_seq',
|
||||
COALESCE((SELECT max(uid) FROM platform.options_history), 1),
|
||||
EXISTS (SELECT 1 FROM platform.options_history)
|
||||
);
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,76 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE platform.options_mapping
|
||||
ADD COLUMN IF NOT EXISTS portfolio_reference uuid;
|
||||
|
||||
CREATE TEMPORARY TABLE portfolio_reference_migration (
|
||||
company_id smallint NOT NULL,
|
||||
portfolio_id smallint NOT NULL,
|
||||
portfolio_reference uuid NOT NULL,
|
||||
PRIMARY KEY (company_id, portfolio_id)
|
||||
) ON COMMIT DROP;
|
||||
|
||||
DO $migration$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema='platform' AND table_name='options_mapping'
|
||||
AND column_name='portfolio_id'
|
||||
) THEN
|
||||
EXECUTE $sql$
|
||||
INSERT INTO portfolio_reference_migration
|
||||
(company_id, portfolio_id, portfolio_reference)
|
||||
SELECT om.company_id, om.portfolio_id,
|
||||
COALESCE(
|
||||
(array_agg(p.portfolio_reference)
|
||||
FILTER (WHERE p.portfolio_reference IS NOT NULL))[1],
|
||||
gen_random_uuid())
|
||||
FROM platform.options_mapping om
|
||||
LEFT JOIN public.portfolio p ON p.portfolio_id=om.portfolio_id
|
||||
GROUP BY om.company_id, om.portfolio_id
|
||||
$sql$;
|
||||
EXECUTE $sql$
|
||||
UPDATE platform.options_mapping om
|
||||
SET portfolio_reference=prm.portfolio_reference
|
||||
FROM portfolio_reference_migration prm
|
||||
WHERE prm.company_id=om.company_id
|
||||
AND prm.portfolio_id=om.portfolio_id
|
||||
AND om.portfolio_reference IS NULL
|
||||
$sql$;
|
||||
END IF;
|
||||
END
|
||||
$migration$;
|
||||
|
||||
DO $validation$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM platform.options_mapping WHERE portfolio_reference IS NULL) THEN
|
||||
RAISE EXCEPTION 'Some option mappings have no portfolio reference';
|
||||
END IF;
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM platform.options_mapping
|
||||
GROUP BY option_id, opt_value_id, portfolio_reference, company_id
|
||||
HAVING count(*) > 1
|
||||
) THEN
|
||||
RAISE EXCEPTION 'Duplicate option mappings found for portfolio references';
|
||||
END IF;
|
||||
END
|
||||
$validation$;
|
||||
|
||||
ALTER TABLE platform.options_mapping
|
||||
ALTER COLUMN portfolio_reference SET NOT NULL,
|
||||
DROP CONSTRAINT IF EXISTS uq_platform_options_mapping_scope;
|
||||
|
||||
ALTER TABLE platform.options_mapping
|
||||
ADD CONSTRAINT uq_platform_options_mapping_scope
|
||||
UNIQUE (option_id, opt_value_id, portfolio_reference, company_id);
|
||||
|
||||
ALTER TABLE platform.options_mapping
|
||||
DROP COLUMN IF EXISTS portfolio_id;
|
||||
|
||||
DROP INDEX IF EXISTS platform.uq_company_branch_portfolios_local_scope;
|
||||
|
||||
ALTER TABLE platform.company_branch_portfolios
|
||||
DROP COLUMN IF EXISTS portfolio_id;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,8 @@
|
||||
BEGIN;
|
||||
|
||||
UPDATE platform.application_query
|
||||
SET query_text = 'select!C0L!SELECT portfolio_id AS "value", portname AS "label", portfolio_reference::text AS "description", ''portfolio.'' AS "group" FROM portfolio WHERE branch_id=? AND isactive=? ORDER BY portname',
|
||||
ismigrated = true
|
||||
WHERE query_id = 3;
|
||||
|
||||
COMMIT;
|
||||
Reference in New Issue
Block a user