34#include <unordered_map>
35#include <unordered_set>
43template <
typename RepoPtr,
typename LoadFn>
44auto loadCollection(
const RepoPtr& repo, LoadFn&& load) {
45 using Collection = std::decay_t<
decltype(load(*repo))>;
46 if (!repo)
return Collection{};
51template <
typename RepoPtr,
typename Collection,
typename UpsertFn>
52void upsertCollection(
const RepoPtr& repo,
const Collection& items, UpsertFn&& upsert) {
54 for (
const auto& item : items) {
61template <
typename RepoPtr,
typename Collection,
typename ClearFn,
typename UpsertFn>
62void replaceCollection(
const RepoPtr& repo,
const Collection& items, ClearFn&& clear, UpsertFn&& upsert) {
66 for (
const auto& item : items) {
72void dedupStrings(std::vector<std::string>& values) {
73 std::unordered_set<std::string> seen;
74 std::vector<std::string> out;
75 out.reserve(values.size());
76 for (
const auto& value : values) {
77 if (value.empty())
continue;
78 if (!seen.insert(value).second)
continue;
81 std::sort(out.begin(), out.end());
82 values = std::move(out);
85void dedupAliases(std::vector<core::domain::Alias>& values) {
86 std::unordered_set<std::string> seen;
87 std::vector<core::domain::Alias> out;
88 out.reserve(values.size());
89 for (
const auto& value : values) {
90 if (value.value().empty())
continue;
91 if (!seen.insert(value.value()).second)
continue;
94 values = std::move(out);
98 for (
auto& alias : aliases) {
99 if (alias.value().empty()) alias.setValue(alias.source());
100 if (alias.source().empty()) alias.setSource(alias.value());
104void projectActorPropertyLinksFromContracts(core::application::workspace::WorkspaceSessionState& document) {
108 if (!actor)
continue;
109 std::vector<std::string> contractIds;
110 for (
const auto& contract : contracts) {
111 if (contract && contract->containsActorId(actor->id())) {
112 contractIds.push_back(contract->id());
115 dedupStrings(contractIds);
116 actor->setContractIds(std::move(contractIds));
120 if (!property)
continue;
121 std::vector<std::string> contractIds;
122 for (
const auto& contract : contracts) {
123 if (contract && contract->containsPropertyId(property->id())) {
124 contractIds.push_back(contract->id());
127 dedupStrings(contractIds);
128 property->setContractIds(std::move(contractIds));
133std::shared_ptr<T> cloneEntity(
const std::shared_ptr<T>& entity) {
137template <
typename Dst,
typename Src>
138void cloneCollection(Dst& dst,
const Src& src) {
139 dst.reserve(src.size());
140 for (
const auto& item : src) {
141 dst.push_back(cloneEntity(item));
145void rehydrate(core::application::workspace::WorkspaceSessionState& document) {
146 auto& state = document.
catalog;
147 for (
auto& actor : state.actors()) {
148 if (!actor)
continue;
149 auto aliases = actor->aliases();
150 dedupAliases(aliases);
151 actor->setAliases(std::move(aliases));
153 for (
auto& property : state.properties()) {
154 if (!property)
continue;
155 auto aliases =
property->aliases();
156 dedupAliases(aliases);
157 property->setAliases(std::move(aliases));
159 for (
auto& contract : state.contracts()) {
160 if (!contract || contract->id().empty())
continue;
161 auto aliases = contract->aliases();
162 dedupAliases(aliases);
163 contract->setAliases(std::move(aliases));
164 auto actorIds = contract->actorIds();
165 dedupStrings(actorIds);
166 contract->setActorIds(std::move(actorIds));
167 auto propertyIds = contract->propertyIds();
168 dedupStrings(propertyIds);
169 contract->setPropertyIds(std::move(propertyIds));
171 for (
auto& statement : state.statements()) {
172 if (!statement)
continue;
173 auto transactionIds = statement->transactionIds();
174 dedupStrings(transactionIds);
175 statement->setTransactionIds(std::move(transactionIds));
177 for (
auto&
annual : state.annuals()) {
179 auto analysisIds =
annual->analysisIds();
180 dedupStrings(analysisIds);
181 annual->setAnalysisIds(std::move(analysisIds));
183 projectActorPropertyLinksFromContracts(document);
186void validate(
const core::application::workspace::WorkspaceSessionState& document,
bool strict) {
187 const auto& state = document.
catalog;
188 const auto fail = [strict](
const std::string& msg) {
189 if (strict)
throw std::runtime_error(msg);
192 for (
const auto& contract : state.contracts()) {
193 if (!contract)
continue;
196 if (!contract->hasRelations()) fail(
"Contract has no relations");
199 std::unordered_map<std::string, const core::domain::Statement*> statementById;
200 for (
const auto& statement : state.statements()) {
201 if (statement && !statement->id().empty()) {
202 statementById.emplace(statement->id(), statement.get());
206 std::unordered_set<std::string> transactionIds;
207 for (
const auto& transaction : state.transactions()) {
209 if (transaction && !transaction->id().empty()) transactionIds.insert(transaction->id());
212 std::unordered_set<std::string> actorIds;
213 std::unordered_set<std::string> contractIds;
214 std::unordered_set<std::string> analysisIds;
215 for (
const auto& actor : state.actors()) {
216 if (actor && !actor->id().empty()) actorIds.insert(actor->id());
218 for (
const auto& contract : state.contracts()) {
219 if (contract && !contract->id().empty()) contractIds.insert(contract->id());
221 for (
const auto&
analysis : state.analyses()) {
225 if (!
analysis->hasType() &&
analysis->hasExportFormat()) fail(
"Analysis.exportFormat without type");
226 if (
analysis->hasType() && !
analysis->hasExportFormat()) fail(
"Analysis.type without exportFormat");
227 if (
analysis->isConfigured() && !
analysis->isResultReady()) fail(
"Analysis configuration is incomplete");
231 for (
const auto& transaction : state.transactions()) {
232 if (!transaction)
continue;
233 if (transaction->hasActor() && !actorIds.count(transaction->actorId())) fail(
"Transaction.actorId unresolved");
234 if (transaction->hasContract() && !contractIds.count(transaction->contractId())) fail(
"Transaction.contractId unresolved");
235 if (transaction->hasStatement()) {
236 const auto it = statementById.find(transaction->statementId());
237 if (it == statementById.end()) {
238 fail(
"Transaction.statementId unresolved");
239 }
else if (!it->second->containsTransaction(transaction->id())) {
240 fail(
"Transaction missing from owning statement");
245 for (
const auto&
annual : state.annuals()) {
248 fail(
"Annual.name is invalid");
251 fail(
"Annual.year is invalid");
253 for (
const auto& analysisId :
annual->analysisIds()) {
254 if (!analysisIds.count(analysisId)) {
255 fail(
"Annual references missing analysis");
260 for (
const auto& actor : state.actors()) {
262 fail(
"Actor.name is invalid");
265 for (
const auto& contractId : actor->contractIds()) {
266 if (!contractIds.count(contractId)) {
267 fail(
"Actor references missing contract");
272 for (
const auto& property : state.properties()) {
274 fail(
"Property.name is invalid");
277 for (
const auto& contractId : property->contractIds()) {
278 if (!contractIds.count(contractId)) {
279 fail(
"Property references missing contract");
284 for (
const auto& statement : state.statements()) {
286 fail(
"Statement.name is invalid");
289 for (
const auto& statement : state.statements()) {
290 if (!statement)
continue;
291 for (
const auto& transactionId : statement->transactionIds()) {
292 if (!transactionIds.count(transactionId)) {
293 fail(
"Statement references missing transaction");
299core::application::workspace::WorkspaceSessionState prepareForSave(
const core::application::workspace::WorkspaceSessionState& document) {
300 core::application::workspace::WorkspaceSessionState out;
326 auto aliases = actor->aliases();
328 dedupAliases(aliases);
329 actor->setAliases(std::move(aliases));
334 auto aliases =
property->aliases();
336 dedupAliases(aliases);
337 property->setAliases(std::move(aliases));
341 if (!contract)
continue;
342 auto aliases = contract->aliases();
344 dedupAliases(aliases);
345 contract->setAliases(std::move(aliases));
346 auto actorIds = contract->actorIds();
347 dedupStrings(actorIds);
348 contract->setActorIds(std::move(actorIds));
349 auto propertyIds = contract->propertyIds();
350 dedupStrings(propertyIds);
351 contract->setPropertyIds(std::move(propertyIds));
354 if (!statement)
continue;
355 auto transactionIds = statement->transactionIds();
356 dedupStrings(transactionIds);
357 statement->setTransactionIds(std::move(transactionIds));
361 auto analysisIds =
annual->analysisIds();
362 dedupStrings(analysisIds);
363 annual->setAnalysisIds(std::move(analysisIds));
374 :
repos(std::move(repositories)) {
382 : impl_(std::make_unique<
Impl>(std::move(repos))) {
394 document.
catalog.
setActors(loadCollection(impl_->repos.actors, [](
const auto& repo) { return repo.getActors(); }));
395 document.
catalog.
setProperties(loadCollection(impl_->repos.properties, [](
const auto& repo) { return repo.getProperties(); }));
396 document.
catalog.
setContracts(loadCollection(impl_->repos.contracts, [](
const auto& repo) { return repo.getContracts(); }));
397 document.
catalog.
setStatements(loadCollection(impl_->repos.statements, [](
const auto& repo) { return repo.getStatements(); }));
398 document.
catalog.
setTransactions(loadCollection(impl_->repos.transactions, [](
const auto& repo) { return repo.getTransactions(); }));
399 document.
catalog.
setAnalyses(loadCollection(impl_->repos.analyses, [](
const auto& repo) { return repo.getAnalyses(); }));
400 document.
catalog.
setAnnuals(loadCollection(impl_->repos.annuals, [](
const auto& repo) { return repo.getAnnuals(); }));
401 document.
workflow.
importLogs = loadCollection(impl_->repos.importLogs, [](
const auto& repo) { return repo.getImportLogs(); });
402 document.
workflow.
exportLogs = loadCollection(impl_->repos.exportLogs, [](
const auto& repo) { return repo.getExportLogs(); });
404 document.
workflow.
statementDrafts = loadCollection(impl_->repos.statementDrafts, [](
const auto& repo) { return repo.getStatementDrafts(); });
406 if (impl_->repos.transactionDrafts) {
407 document.workflow.transactionDrafts = impl_->repos.transactionDrafts->getTransactionDrafts();
411 validate(document, impl_->strictValidation);
418 validate(projected, impl_->strictValidation);
420 upsertCollection(impl_->repos.actors, projected.
catalog.
actors(), [](
auto& repo,
const auto& actor) { repo.upsertActor(actor); });
421 upsertCollection(impl_->repos.properties, projected.
catalog.
properties(), [](
auto& repo,
const auto& property) { repo.upsertProperty(property); });
422 upsertCollection(impl_->repos.contracts, projected.
catalog.
contracts(), [](
auto& repo,
const auto& contract) { repo.upsertContract(contract); });
423 upsertCollection(impl_->repos.statements, projected.
catalog.
statements(), [](
auto& repo,
const auto& statement) { repo.upsertStatement(statement); });
424 upsertCollection(impl_->repos.transactions, projected.
catalog.
transactions(), [](
auto& repo,
const auto& transaction) { repo.upsertTransaction(transaction); });
425 upsertCollection(impl_->repos.analyses, projected.
catalog.
analyses(), [](
auto& repo,
const auto&
analysis) { repo.upsertAnalysis(analysis); });
426 upsertCollection(impl_->repos.annuals, projected.
catalog.
annuals(), [](
auto& repo,
const auto&
annual) { repo.upsertAnnual(annual); });
427 replaceCollection(impl_->repos.importLogs,
429 [](
auto& repo) { repo.clearImportLogs(); },
430 [](
auto& repo,
const auto& item) { repo.upsertImportLog(item); });
431 replaceCollection(impl_->repos.exportLogs,
433 [](
auto& repo) { repo.clearExportLogs(); },
434 [](
auto& repo,
const auto& item) { repo.upsertExportLog(item); });
435 replaceCollection(impl_->repos.statementDrafts,
437 [](
auto& repo) { repo.clearStatementDrafts(); },
438 [](
auto& repo,
const auto& item) { repo.upsertStatementDraft(item); });
439 replaceCollection(impl_->repos.transactionDrafts,
441 [](
auto& repo) { repo.clearTransactionDrafts(); },
442 [](
auto& repo,
const auto& item) { repo.upsertTransactionDraft(item); });
446 impl_->strictValidation = enabled;
Domain model for persisted analysis definitions.
Domain model for annual aggregates.
Domain model for contracts.
Value object for contract type labels.
Small value object for normalized entity display names.
Repository port for core::domain::Actor persistence operations.
Repository port for core::domain::Analysis persistence operations.
Repository port for core::domain::Annual persistence operations.
Repository port for core::domain::Contract persistence operations.
Repository port for core::domain::Property persistence operations.
Repository port for core::domain::Statement persistence operations.
Repository port for transaction draft persistence operations.
Repository port for core::domain::Transaction persistence operations.
Domain model for properties.
Aggregates repository ports required by workspace state persistence.
Domain model for statements.
Domain model for transactions.
Internal mutable workspace session state for application services.
Loader/saver for internal workspace session state using repository abstractions.
Impl(Repositories repositories)
Loads and saves aggregate workspace state via repository abstractions.
void setStrictValidation(bool enabled) noexcept
Enables or disables strict validation during load and save.
void save(const core::application::workspace::WorkspaceSessionState &document)
Projects, validates and persists workspace session state.
core::storage::RepositoryBundle Repositories
core::application::workspace::WorkspaceSessionState load()
Loads, rehydrates and validates workspace session state.
WorkspaceStateManager(Repositories repos)
Creates a manager over the supplied repository bundle.
static bool isValid(const std::string &value)
Checks whether a raw contract type is valid.
static bool isValid(const std::string &value)
Checks whether a raw name is valid.
EntityList< core::domain::Contract > ContractList
EntityList< core::domain::Transaction > TransactionList
void setAnnuals(AnnualList value)
Replaces all annuals in the catalog.
EntityList< core::domain::Annual > AnnualList
const PropertyList & properties() const noexcept
Returns all properties in the catalog.
void setAnalyses(AnalysisList value)
Replaces all analyses in the catalog.
EntityList< core::domain::Analysis > AnalysisList
void setProperties(PropertyList value)
Replaces all properties in the catalog.
void setStatements(StatementList value)
Replaces all statements in the catalog.
EntityList< core::domain::Property > PropertyList
const AnnualList & annuals() const noexcept
Returns all annuals in the catalog.
EntityList< core::domain::Statement > StatementList
const StatementList & statements() const noexcept
Returns all statements in the catalog.
const TransactionList & transactions() const noexcept
Returns all transactions in the catalog.
void setActors(ActorList value)
Replaces all actors in the catalog.
const ActorList & actors() const noexcept
Returns all actors in the catalog.
void setContracts(ContractList value)
Replaces all contracts in the catalog.
const ContractList & contracts() const noexcept
Returns all contracts in the catalog.
const AnalysisList & analyses() const noexcept
Returns all analyses in the catalog.
void setTransactions(TransactionList value)
Replaces all transactions in the catalog.
EntityList< core::domain::Actor > ActorList
Collects common core includes and aliases for precompiled-header builds.
void normalizeAliases(std::vector< Alias > &aliases)
Normalizes and deduplicates a list of aliases.
core::domain::catalog::WorkspaceCatalog catalog
WorkspaceWorkflowState workflow
TransactionDraftList transactionDrafts
StatementDraftList statementDrafts