Skip to content

Commit 48d39d6

Browse files
committed
Add Elements and local effective composition integration
1 parent cbdc409 commit 48d39d6

38 files changed

Lines changed: 705 additions & 109 deletions

CMakeLists.txt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,14 @@ qt_add_executable(BrickSuite
7878
src/import/RebrickableThemeCatalogImporter.cpp
7979
src/import/RebrickableInventoryCompositionImporter.h
8080
src/import/RebrickableInventoryCompositionImporter.cpp
81+
src/import/RebrickableElementImporter.h
82+
src/import/RebrickableElementImporter.cpp
8183
src/repositories/SetInventoryRevisionRepository.h
8284
src/repositories/SetInventoryRevisionRepository.cpp
85+
src/repositories/PartElementIdentifierRepository.h
86+
src/repositories/PartElementIdentifierRepository.cpp
87+
src/repositories/EffectiveSetCompositionRepository.h
88+
src/repositories/EffectiveSetCompositionRepository.cpp
8389
src/ui/import/GlobalRebrickableImportDialog.h
8490
src/ui/import/GlobalRebrickableImportDialog.cpp
8591
resources/resources.qrc
@@ -380,6 +386,8 @@ qt_add_executable(BrickSuite
380386
src/services/minifigs/MinifigCompositionReplacementService.cpp
381387
src/services/minifigs/RebrickableMinifigPartsService.h
382388
src/services/minifigs/RebrickableMinifigPartsService.cpp
389+
src/services/minifigs/RebrickableMinifigThemeDerivationService.h
390+
src/services/minifigs/RebrickableMinifigThemeDerivationService.cpp
383391
src/models/SetCatalogPart.h
384392
src/repositories/SetCatalogPartRepository.h
385393
src/repositories/SetCatalogPartRepository.cpp
@@ -803,6 +811,7 @@ if(BUILD_TESTING)
803811
src/repositories/ManufacturerRepository.h src/repositories/ManufacturerRepository.cpp
804812
src/repositories/SetCatalogRepository.h src/repositories/SetCatalogRepository.cpp
805813
src/repositories/SetCatalogPartRepository.h src/repositories/SetCatalogPartRepository.cpp
814+
src/repositories/EffectiveSetCompositionRepository.h src/repositories/EffectiveSetCompositionRepository.cpp
806815
src/services/builds/SetBuildCreationService.h src/services/builds/SetBuildCreationService.cpp
807816
src/services/sets/SetCompositionReplacementService.h src/services/sets/SetCompositionReplacementService.cpp
808817
)
@@ -897,6 +906,9 @@ if(BUILD_TESTING)
897906
src/import/RebrickableSetCatalogImporter.h src/import/RebrickableSetCatalogImporter.cpp
898907
src/import/RebrickableMinifigCatalogImporter.h src/import/RebrickableMinifigCatalogImporter.cpp
899908
src/import/RebrickableInventoryCompositionImporter.h src/import/RebrickableInventoryCompositionImporter.cpp
909+
src/import/RebrickableElementImporter.h src/import/RebrickableElementImporter.cpp
910+
src/services/minifigs/RebrickableMinifigThemeDerivationService.h
911+
src/services/minifigs/RebrickableMinifigThemeDerivationService.cpp
900912
src/database/DatabaseManager.h src/database/DatabaseManager.cpp
901913
src/models/PartRelationship.h
902914
)
@@ -920,6 +932,22 @@ if(BUILD_TESTING)
920932
Qt6::Core Qt6::Sql ZLIB::ZLIB)
921933
add_test(NAME InventoryCompositionPersistence
922934
COMMAND InventoryCompositionPersistenceTest)
935+
936+
qt_add_executable(ElementsEffectiveCompositionTest EXCLUDE_FROM_ALL
937+
tests/ElementsEffectiveCompositionTest.cpp
938+
src/database/DatabaseSchema.h src/database/DatabaseSchema.cpp
939+
src/database/DatabaseManager.h src/database/DatabaseManager.cpp
940+
src/import/RebrickableCsvInputResolver.h src/import/RebrickableCsvInputResolver.cpp
941+
src/import/global/RebrickableImportCancellation.h
942+
src/import/RebrickableElementImporter.h src/import/RebrickableElementImporter.cpp
943+
src/repositories/PartElementIdentifierRepository.h src/repositories/PartElementIdentifierRepository.cpp
944+
src/repositories/EffectiveSetCompositionRepository.h src/repositories/EffectiveSetCompositionRepository.cpp
945+
src/services/minifigs/RebrickableMinifigThemeDerivationService.h
946+
src/services/minifigs/RebrickableMinifigThemeDerivationService.cpp
947+
)
948+
target_link_libraries(ElementsEffectiveCompositionTest PRIVATE
949+
Qt6::Core Qt6::Sql ZLIB::ZLIB)
950+
add_test(NAME ElementsEffectiveComposition COMMAND ElementsEffectiveCompositionTest)
923951
endif()
924952

925953
if(WIN32)

resources/help/getting_started.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ <h2>1. Workspace</h2><p>Create or select the Workspace representing the workshop
44
<h2>2. Storage</h2><p>Create the physical hierarchy you use. Active leaf locations may allow Inventory, Collection, or Both; parent locations organize the hierarchy but are not destinations.</p><img src="images/getting_started_storage.png" style="max-width:100%" alt="Storage">
55
<h2>3. Optional providers</h2><p>Configure Rebrickable or Brickset under <b>Settings → APIs</b> when you want API-backed composition, images, enrichment, or instructions. Download-based catalog imports and local workflows do not require a continuous connection.</p>
66
<h2>4. Reference catalogs</h2><p>Populate Parts, Sets, and Minifigs from supported Rebrickable downloads. Catalogs describe reference identities; they do not assert ownership.</p>
7-
<h3>Folder-based Rebrickable import</h3><p>Choose <b>Tools → Import Rebrickable Data Files...</b> and select a folder containing official Rebrickable <code>.csv</code> or <code>.csv.zip</code> bulk downloads. BrickSuite validates a visible plan before enabling Import. Themes, Colors, Part Categories, Parts, Part Relationships, Sets, Minifigs, inventory revisions, Inventory Parts, Inventory Minifigs, and contained Sets are supported. Elements remains deferred.</p><p>Rebrickable's inventory files contain both Set and Minifig inventory identities. BrickSuite recognizes both, persists revision history for Sets, and intentionally excludes Minifig-owned Part rows from Set composition.</p><p>Revision-backed composition is catalog data, not owned inventory. Historical Set revisions are retained, and BrickSuite selects the highest complete Rebrickable revision as preferred after all composition files finish successfully. Spare rows are preserved as provider metadata and do not imply physical ownership.</p><p>Each supported dataset uses its own transaction. A failed or cancelled current dataset is rolled back while earlier committed datasets remain available; dependent datasets are blocked, while independent datasets may continue. Required identities are checked before authoritative snapshot synchronization. BrickSuite does not download these files automatically.</p>
7+
<h3>Folder-based Rebrickable import</h3><p>Choose <b>Tools → Import Rebrickable Data Files...</b> and select a folder containing official Rebrickable <code>.csv</code> or <code>.csv.zip</code> bulk downloads. BrickSuite validates a visible plan before enabling Import. All twelve datasets are supported: Themes, Colors, Part Categories, Parts, Elements, Part Relationships, Sets, Minifigs, Inventories, Inventory Parts, Inventory Minifigs, and Inventory Sets. Elements map an exact Rebrickable Element ID to its exact Part and Color; they do not replace canonical Part numbers.</p><p>Rebrickable's inventory files contain both Set and Minifig inventory identities. BrickSuite recognizes both, persists revision history for Sets, and intentionally excludes Minifig-owned Part rows from Set composition.</p><p>Revision-backed composition is catalog data, not owned inventory. Historical Set revisions are retained, and BrickSuite selects the highest complete Rebrickable revision as preferred after all composition files finish successfully. A valid preferred revision supplies Set composition; existing legacy catalog composition remains the fallback when no preferred revision exists. Catalog spare rows remain available as provider metadata but are not copied into required Build pieces and do not imply physical ownership. A Build keeps the requirement snapshot created at that time, so later catalog imports do not rewrite it.</p><p>Each supported dataset uses its own transaction. A failed or cancelled current dataset is rolled back while earlier committed datasets remain available; dependent datasets are blocked, while independent datasets may continue. Required identities are checked before authoritative snapshot synchronization. BrickSuite does not download these files automatically.</p>
88
<h2>5. My Inventory</h2><p>Add or import loose physical pieces with exact Part, Color, Manufacturer, condition, ownership, quantity, and Storage.</p><img src="images/getting_started_inventory.png" style="max-width:100%" alt="My Inventory">
99
<h2>6. Builds</h2><p>Create Set, Minifig, or MOC Builds. Requirements can be allocated, deliberately substituted, pulled, reconciled, completed, cancelled, or disassembled.</p>
1010
<h2>7. My Collection</h2><p>Record each physical Set, Minifig, or MOC independently from loose inventory and Build history.</p>

resources/help/minifigs_catalog.html

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,11 @@ <h2>Theme Classification</h2>
4141
<h2>Minifig Details and Images</h2>
4242

4343
<p>Use <b>Actions... → Details</b> to view the selected Minifig's catalog metadata, provider
44-
source, and imported composition. Images are downloaded asynchronously from the imported
45-
Rebrickable image URL and cached in BrickSuite's dedicated Minifig image cache.</p>
44+
source, and local composition. A Global Rebrickable import can populate this composition from the
45+
Minifig-owned rows in <code>inventory_parts.csv</code>. Existing local composition is displayed
46+
automatically when Details opens; opening the dialog does not request parts from an API. Images
47+
are downloaded asynchronously from the imported Rebrickable image URL and cached in BrickSuite's
48+
dedicated Minifig image cache.</p>
4649

4750
<h2>Get or Import a Minifig Parts List</h2>
4851

@@ -55,7 +58,8 @@ <h2>Get or Import a Minifig Parts List</h2>
5558

5659
<p><b>Import Parts List...</b> remains available as the offline/manual fallback. Select the
5760
Rebrickable Parts CSV or its downloaded ZIP. The selected BrickSuite Minifig is always the
58-
destination.</p>
61+
destination. Both manual buttons remain explicit refresh/replacement options even when bulk-imported
62+
local composition is already displayed.</p>
5963

6064
<p>If the filename contains a recognizable identifier such as <code>fig-005210</code>, BrickSuite
6165
checks it against the selected Minifig before saving anything. A renamed file without a

resources/help/sets_catalog.html

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,19 @@ <h2>Set Details</h2>
4949

5050
<h3>Catalog Parts List</h3>
5151

52-
<p>The <b>Catalog Parts List</b> stores reusable exact Part, Color, quantity, and spare information
53-
for the selected Set. Choose <b>Get Parts from Rebrickable...</b> to retrieve the complete list
54-
through the Rebrickable API. This API workflow includes the constituent pieces of Minifigs in the
55-
Set so the catalog composition represents the physical pieces needed to assemble it.</p>
52+
<p>The <b>Catalog Parts List</b> automatically displays locally available exact Part, Color,
53+
quantity, and spare information when Set Details opens. BrickSuite uses the preferred local
54+
Rebrickable inventory revision when one is available and otherwise uses the existing Catalog
55+
Parts List fallback. The source line identifies which composition is displayed.</p>
56+
57+
<p>Choose <b>Get Parts from Rebrickable...</b> to explicitly refresh the complete list through
58+
the Rebrickable API. This API workflow includes the constituent pieces of Minifigs in the Set so
59+
the catalog composition represents the physical pieces needed to assemble it.</p>
5660

5761
<p>If API access is unavailable, choose <b>Import Parts List...</b> and select a Rebrickable parts
58-
list in CSV or ZIP form. ZIP files may contain the CSV in a nested folder. Both acquisition methods
59-
replace the same catalog composition and require confirmation when a parts list already exists.</p>
62+
list in CSV or ZIP form. ZIP files may contain the CSV in a nested folder. Both manual acquisition
63+
methods remain available as explicit refresh or replacement workflows and require confirmation
64+
when a parts list already exists. Opening Set Details itself never starts an API request.</p>
6065

6166
<div class="note"><b>Exact catalog identities:</b> Every imported row must resolve to an existing
6267
BrickSuite Part and Rebrickable Color. If the input is malformed or an identity cannot be resolved,

src/database/DatabaseSchema.cpp

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,18 @@ bool DatabaseSchema::initialize(QSqlDatabase& database)
576576
version = 33;
577577
}
578578

579+
if (version == 33) {
580+
if (!migrateVersion33ToVersion34(database)) {
581+
database.rollback();
582+
return false;
583+
}
584+
if (!setSchemaVersion(database, 34)) {
585+
database.rollback();
586+
return false;
587+
}
588+
version = 34;
589+
}
590+
579591
if (version != CurrentSchemaVersion) {
580592
qCritical() << "Unsupported BrickSuite database schema version:" << version;
581593

@@ -3791,3 +3803,50 @@ bool DatabaseSchema::migrateVersion32ToVersion33(QSqlDatabase& database)
37913803
}
37923804
return true;
37933805
}
3806+
3807+
bool DatabaseSchema::createPartElementIdentifierTable(QSqlDatabase& database)
3808+
{
3809+
QSqlQuery query(database);
3810+
const QStringList statements = {
3811+
R"(CREATE TABLE IF NOT EXISTS part_element_identifier (
3812+
id INTEGER PRIMARY KEY AUTOINCREMENT,
3813+
provider TEXT NOT NULL COLLATE NOCASE,
3814+
element_id TEXT NOT NULL COLLATE NOCASE,
3815+
part_id INTEGER NOT NULL,
3816+
color_id INTEGER NOT NULL,
3817+
design_id TEXT NOT NULL,
3818+
is_active INTEGER NOT NULL DEFAULT 1 CHECK(is_active IN (0,1)),
3819+
created_utc TEXT NOT NULL,
3820+
modified_utc TEXT NOT NULL,
3821+
FOREIGN KEY(part_id) REFERENCES part(id),
3822+
FOREIGN KEY(color_id) REFERENCES color(id),
3823+
UNIQUE(provider, element_id)
3824+
))",
3825+
"CREATE INDEX IF NOT EXISTS idx_part_element_part_color "
3826+
"ON part_element_identifier(part_id,color_id,is_active)",
3827+
"CREATE INDEX IF NOT EXISTS idx_part_element_design "
3828+
"ON part_element_identifier(provider,design_id,is_active)",
3829+
"CREATE INDEX IF NOT EXISTS idx_part_element_provider_active "
3830+
"ON part_element_identifier(provider,is_active)"
3831+
};
3832+
for (const QString& statement : statements) {
3833+
if (!query.exec(statement)) {
3834+
qCritical() << "Unable to create Element identifier schema:"
3835+
<< query.lastError().text();
3836+
return false;
3837+
}
3838+
}
3839+
return true;
3840+
}
3841+
3842+
bool DatabaseSchema::migrateVersion33ToVersion34(QSqlDatabase& database)
3843+
{
3844+
if (!createPartElementIdentifierTable(database))
3845+
return false;
3846+
QSqlQuery check(database);
3847+
if (!check.exec("PRAGMA foreign_key_check") || check.next()) {
3848+
qCritical() << "Foreign-key validation failed after Version 34 migration.";
3849+
return false;
3850+
}
3851+
return true;
3852+
}

src/database/DatabaseSchema.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
class DatabaseSchema
2626
{
2727
public:
28-
static constexpr int CurrentSchemaVersion = 33;
28+
static constexpr int CurrentSchemaVersion = 34;
2929

3030
static bool initialize(QSqlDatabase& database);
3131
static bool seedManufacturers(QSqlDatabase& database);
@@ -107,6 +107,7 @@ class DatabaseSchema
107107
static bool migrateVersion30ToVersion31(QSqlDatabase& database);
108108
static bool migrateVersion31ToVersion32(QSqlDatabase& database);
109109
static bool migrateVersion32ToVersion33(QSqlDatabase& database);
110+
static bool migrateVersion33ToVersion34(QSqlDatabase& database);
110111

111112
static bool createExternalColorMappingTable(QSqlDatabase& database);
112113
static bool createExternalPartMappingTable(QSqlDatabase& database);
@@ -123,4 +124,5 @@ class DatabaseSchema
123124
static bool createMinifigCatalogPartTable(QSqlDatabase& database);
124125
static bool createSetCatalogPartTable(QSqlDatabase& database);
125126
static bool createSetInventoryRevisionTables(QSqlDatabase& database);
127+
static bool createPartElementIdentifierTable(QSqlDatabase& database);
126128
};

0 commit comments

Comments
 (0)