diff options
| author | Lester Caine <lester@lsces.co.uk> | 2026-06-07 16:51:15 +0100 |
|---|---|---|
| committer | Lester Caine <lester@lsces.co.uk> | 2026-06-07 16:51:15 +0100 |
| commit | 9f5cb5855df61554e38a6da5d036bd78d421a2a9 (patch) | |
| tree | 403cdd171269e8037cb8113f0735665ec916215c /import | |
| parent | d75d099650de58ede08583e3dac27986544f799f (diff) | |
| download | stock-9f5cb5855df61554e38a6da5d036bd78d421a2a9.tar.gz stock-9f5cb5855df61554e38a6da5d036bd78d421a2a9.tar.bz2 stock-9f5cb5855df61554e38a6da5d036bd78d421a2a9.zip | |
Stock package: assembly/component model, package-level xref, imports, print BOM
- LibertyXrefInfo/LibertyXrefGroup: package-level guid support ('stock')
so stgrp, supplier, kitlocker groups are shared across SA and SC
- schema_inc.php: stgrp group with KLG01-28, supplier consolidated to 'stock'
level, kitlocker moved to 'stock', duplicate #PN/#PR removed
- StockAssembly::getList(): non_root_only and show_empty fixes for standalone
assemblies (no gallery hierarchy)
- StockBase::loadXrefInfo(): instantiates with 'stock' package guid
- list_assemblies.php: gallery_id param, show_empty default for flat list
- edit_assembly.php: STOCKCOMPONENT_CONTENT_TYPE_GUID → string literal
- ImportSimpleComponent: SCREF lookup fixed (xkey not xkey_ext), #SUP row
now carries PN/price/URL directly; #PN #PR #URL separate rows removed
- ImportKitlockerAssemblies, load_kitlocker_assemblies, load_component_list:
new importers for KitlockerAssemblies.csv (A/C split) and Component List.csv
- print_bom.php + print_bom.tpl: read-only printable BOM (p_stock_view only)
with auto window.print() on load
- component_order.tpl: print icon (floaticon), position number for print,
hidden-print on form controls
- list_stock.tpl: print icon
- view_assembly.tpl: print icon → print_bom.php; permission gates tightened
- ipackage duplicate attributes removed from templates
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'import')
| -rw-r--r-- | import/ImportKitlockerAssemblies.php | 120 | ||||
| -rw-r--r-- | import/ImportSimpleComponent.php | 41 | ||||
| -rw-r--r-- | import/load_component_list.php | 68 | ||||
| -rw-r--r-- | import/load_kitlocker_assemblies.php | 73 |
4 files changed, 265 insertions, 37 deletions
diff --git a/import/ImportKitlockerAssemblies.php b/import/ImportKitlockerAssemblies.php new file mode 100644 index 0000000..aa96a1e --- /dev/null +++ b/import/ImportKitlockerAssemblies.php @@ -0,0 +1,120 @@ +<?php +/** + * Kitlocker assemblies/components CSV importer. + * + * CSV column layout (0-based, header row skipped by loader): + * 0 Title MERG product code / designation (used as lc.title) + * 1 KLID Kitlocker numeric ID code (stored as KLID xref on assemblies) + * 2 Description Long description (stored as content body) + * 3 KLSGL Kitlocker single-unit stock count (assemblies only → KLSGL xref) + * 4 KL3M 3-month sales count (assemblies only → KL3M xref) + * 5 Group Group number 1–28 → KLG01–KLG28 stgrp xref (both types) + * 6 Type 'A' = StockAssembly, 'C' = StockComponent + * + * @package stock + */ + +use Bitweaver\Stock\StockAssembly; +use Bitweaver\Stock\StockComponent; + +function stockExpungeKitlockerItemByTitle( string $title, string $type ): bool { + global $gBitDb; + + $guid = ( $type === 'A' ) ? 'stockassembly' : 'stockcomponent'; + $contentId = $gBitDb->getOne( + "SELECT lc.`content_id` FROM `".BIT_DB_PREFIX."liberty_content` lc + WHERE lc.`content_type_guid` = ? AND lc.`title` = ?", + [ $guid, $title ] + ); + if( !$contentId ) { + return false; + } + + $obj = ( $type === 'A' ) ? new StockAssembly( (int)$contentId ) : new StockComponent( (int)$contentId ); + $obj->expunge(); + return true; +} + +function stockImportKitlockerItem( array $data, int $rowNum ): array { + global $gBitDb; + + $result = [ 'loaded' => 0, 'skipped' => 0, 'errors' => [] ]; + + $title = trim( $data[0] ?? '' ); + if( empty( $title ) ) { + $result['skipped']++; + return $result; + } + + $type = strtoupper( trim( $data[6] ?? '' ) ); + if( !in_array( $type, [ 'A', 'C' ] ) ) { + $result['skipped']++; + $result['errors'][] = "Row $rowNum: '$title' — unknown type '$type', skipped."; + return $result; + } + + $guid = ( $type === 'A' ) ? 'stockassembly' : 'stockcomponent'; + + $exists = $gBitDb->getOne( + "SELECT lc.`content_id` FROM `".BIT_DB_PREFIX."liberty_content` lc + WHERE lc.`content_type_guid` = ? AND lc.`title` = ?", + [ $guid, $title ] + ); + if( $exists ) { + $result['skipped']++; + $result['errors'][] = "Row $rowNum: '$title' already exists, skipped."; + return $result; + } + + $klid = trim( $data[1] ?? '' ); + $desc = trim( $data[2] ?? '' ); + $klsgl = trim( $data[3] ?? '' ); + $kl3m = trim( $data[4] ?? '' ); + $group = (int)( $data[5] ?? 0 ); + + $obj = ( $type === 'A' ) ? new StockAssembly() : new StockComponent(); + $pHash = [ + 'title' => $title, + 'edit' => $desc, + 'format_guid' => 'bithtml', + ]; + if( !$obj->store( $pHash ) ) { + $result['skipped']++; + $result['errors'][] = "Row $rowNum: failed to store '$title'."; + return $result; + } + + $contentId = $obj->mContentId; + + // Group tag — shared across both types via 'stock' package-level stgrp + if( $group >= 1 && $group <= 99 ) { + $xrefId = $gBitDb->GenID( 'liberty_xref_seq' ); + $gBitDb->associateInsert( BIT_DB_PREFIX.'liberty_xref', [ + 'xref_id' => $xrefId, + 'content_id' => $contentId, + 'item' => sprintf( 'KLG%02d', $group ), + 'xorder' => 0, + 'last_update_date' => $gBitDb->NOW(), + ] ); + } + + // Assembly-specific kitlocker xrefs + if( $type === 'A' ) { + foreach( [ 'KLID' => $klid, 'KLSGL' => $klsgl, 'KL3M' => $kl3m ] as $item => $value ) { + if( $value !== '' ) { + $xrefId = $gBitDb->GenID( 'liberty_xref_seq' ); + $gBitDb->associateInsert( BIT_DB_PREFIX.'liberty_xref', [ + 'xref_id' => $xrefId, + 'content_id' => $contentId, + 'item' => $item, + 'xorder' => 0, + 'xkey' => substr( $value, 0, 32 ), + 'last_update_date' => $gBitDb->NOW(), + ] ); + } + } + } + + $result['loaded']++; + return $result; +} diff --git a/import/ImportSimpleComponent.php b/import/ImportSimpleComponent.php index 83161ce..7db7c52 100644 --- a/import/ImportSimpleComponent.php +++ b/import/ImportSimpleComponent.php @@ -33,7 +33,7 @@ function stockImportFindSupplier( string $name ): ?int { $contentId = $gBitDb->getOne( "SELECT `content_id` FROM `".BIT_DB_PREFIX."liberty_xref` - WHERE `item` = 'SCREF' AND UPPER( `xkey_ext` ) = UPPER( ? )", + WHERE `item` = 'SCREF' AND UPPER( `xkey` ) = UPPER( ? )", [ trim( $name ) ] ); @@ -115,44 +115,11 @@ function stockImportSimpleComponent( array $data, int $rowNum ): array { 'item' => '#SUP', 'xorder' => 1, 'xref' => $supplierContentId, + 'xkey' => substr( $supplierPn, 0, 32 ), + 'xkey_ext' => substr( $supplierPrice, 0, 250 ), + 'data' => $supplierUrl ?: null, 'last_update_date' => $gBitDb->NOW(), ] ); - - if( !empty( $supplierPn ) ) { - $xrefId = $gBitDb->GenID( 'liberty_xref_seq' ); - $gBitDb->associateInsert( BIT_DB_PREFIX.'liberty_xref', [ - 'xref_id' => $xrefId, - 'content_id' => $contentId, - 'item' => '#PN', - 'xorder' => 1, - 'xkey_ext' => substr( $supplierPn, 0, 250 ), - 'last_update_date' => $gBitDb->NOW(), - ] ); - } - - if( !empty( $supplierPrice ) ) { - $xrefId = $gBitDb->GenID( 'liberty_xref_seq' ); - $gBitDb->associateInsert( BIT_DB_PREFIX.'liberty_xref', [ - 'xref_id' => $xrefId, - 'content_id' => $contentId, - 'item' => '#PR', - 'xorder' => 1, - 'xkey' => substr( $supplierPrice, 0, 32 ), - 'last_update_date' => $gBitDb->NOW(), - ] ); - } - - if( !empty( $supplierUrl ) ) { - $xrefId = $gBitDb->GenID( 'liberty_xref_seq' ); - $gBitDb->associateInsert( BIT_DB_PREFIX.'liberty_xref', [ - 'xref_id' => $xrefId, - 'content_id' => $contentId, - 'item' => '#URL', - 'xorder' => 1, - 'xkey_ext' => substr( $supplierUrl, 0, 250 ), - 'last_update_date' => $gBitDb->NOW(), - ] ); - } } } diff --git a/import/load_component_list.php b/import/load_component_list.php new file mode 100644 index 0000000..e3aff8e --- /dev/null +++ b/import/load_component_list.php @@ -0,0 +1,68 @@ +<?php +/** + * Load components from storage/stock/Component List.csv. + * Columns: Title, Description, supplier, supplier PN, Supplier price, Supplier URL. + * First row is a header and is skipped. Append ?clear=y to wipe and re-import. + * + * @package stock + */ + +namespace Bitweaver\Stock; + +require_once '../../kernel/includes/setup_inc.php'; + +global $gBitSystem, $gBitSmarty, $gBitDb; + +$gBitSystem->verifyPackage( 'stock' ); +$gBitSystem->verifyPermission( 'p_stock_admin' ); + +require_once __DIR__.'/ImportSimpleComponent.php'; + +$csvFile = STOCK_IMPORT_PATH . 'Component List.csv'; +$doClear = ( ( $_REQUEST['clear'] ?? '' ) === 'y' ); +$loaded = 0; +$skipped = 0; +$deleted = 0; +$errors = []; + +if( !file_exists( $csvFile ) ) { + $errors[] = 'CSV file not found: '.$csvFile; +} else { + $handle = fopen( $csvFile, 'r' ); + if( $handle === false ) { + $errors[] = 'Cannot open CSV file.'; + } else { + $rows = []; + $rowNum = 0; + while( ( $data = fgetcsv( $handle, 0, ',', '"', '' ) ) !== false ) { + $rowNum++; + if( $rowNum === 1 ) continue; + $rows[] = $data; + } + fclose( $handle ); + + if( $doClear ) { + foreach( $rows as $data ) { + $title = trim( $data[0] ?? '' ); + if( !empty( $title ) && stockExpungeComponentByTitle( $title ) ) { + $deleted++; + } + } + } + + foreach( $rows as $idx => $data ) { + $result = stockImportSimpleComponent( $data, $idx + 2 ); + $loaded += $result['loaded']; + $skipped += $result['skipped']; + $errors = array_merge( $errors, $result['errors'] ); + } + } +} + +$gBitSmarty->assign( 'loaded', $loaded ); +$gBitSmarty->assign( 'skipped', $skipped ); +$gBitSmarty->assign( 'deleted', $deleted ); +$gBitSmarty->assign( 'errors', $errors ); +$gBitSmarty->assign( 'csvFile', $csvFile ); + +$gBitSystem->display( 'bitpackage:stock/import_results.tpl', 'Import Component List' ); diff --git a/import/load_kitlocker_assemblies.php b/import/load_kitlocker_assemblies.php new file mode 100644 index 0000000..856aed7 --- /dev/null +++ b/import/load_kitlocker_assemblies.php @@ -0,0 +1,73 @@ +<?php +/** + * Load assemblies and components from KitlockerAssemblies.csv. + * + * Reads storage/stock/KitlockerAssemblies.csv (header row present). + * Each row is routed to StockAssembly or StockComponent based on the Type column. + * Append ?clear=y to delete existing records by title before re-importing. + * + * @package stock + */ + +namespace Bitweaver\Stock; + +require_once '../../kernel/includes/setup_inc.php'; + +global $gBitSystem, $gBitSmarty, $gBitDb; + +$gBitSystem->verifyPackage( 'stock' ); +$gBitSystem->verifyPermission( 'p_stock_admin' ); + +require_once __DIR__.'/ImportKitlockerAssemblies.php'; + +$csvFile = STOCK_IMPORT_PATH . 'KitlockerAssemblies.csv'; +$doClear = ( ( $_REQUEST['clear'] ?? '' ) === 'y' ); +$loaded = 0; +$skipped = 0; +$deleted = 0; +$errors = []; + +if( !file_exists( $csvFile ) ) { + $errors[] = 'CSV file not found: '.$csvFile; +} else { + $handle = fopen( $csvFile, 'r' ); + if( $handle === false ) { + $errors[] = 'Cannot open CSV file.'; + } else { + $rows = []; + while( ( $data = fgetcsv( $handle, 0, ',', '"', '' ) ) !== false ) { + $rows[] = $data; + } + fclose( $handle ); + + // Skip header row + $dataRows = array_slice( $rows, 1 ); + + if( $doClear ) { + foreach( $dataRows as $data ) { + $title = trim( $data[0] ?? '' ); + $type = strtoupper( trim( $data[6] ?? '' ) ); + if( !empty( $title ) && in_array( $type, [ 'A', 'C' ] ) ) { + if( stockExpungeKitlockerItemByTitle( $title, $type ) ) { + $deleted++; + } + } + } + } + + foreach( $dataRows as $idx => $data ) { + $result = stockImportKitlockerItem( $data, $idx + 2 ); + $loaded += $result['loaded']; + $skipped += $result['skipped']; + $errors = array_merge( $errors, $result['errors'] ); + } + } +} + +$gBitSmarty->assign( 'loaded', $loaded ); +$gBitSmarty->assign( 'skipped', $skipped ); +$gBitSmarty->assign( 'deleted', $deleted ); +$gBitSmarty->assign( 'errors', $errors ); +$gBitSmarty->assign( 'csvFile', $csvFile ); + +$gBitSystem->display( 'bitpackage:stock/import_results.tpl', 'Import Kitlocker Assemblies & Components' ); |
