diff options
| author | Lester Caine <lester@lsces.co.uk> | 2026-05-26 19:39:53 +0100 |
|---|---|---|
| committer | Lester Caine <lester@lsces.co.uk> | 2026-05-26 19:39:53 +0100 |
| commit | feef58886997ee5760553cae784783cb991f2882 (patch) | |
| tree | 23138d8ac6ab561e74da12c8151f8bb9a779d746 /import | |
| parent | e373f82ced189ff5814e6591394b432933821dfc (diff) | |
| download | stock-feef58886997ee5760553cae784783cb991f2882.tar.gz stock-feef58886997ee5760553cae784783cb991f2882.tar.bz2 stock-feef58886997ee5760553cae784783cb991f2882.zip | |
Add simplified component importer (title, desc, supplier, PN, price)
Looks up supplier by contact title (case-insensitive, cached).
Inserts #SUP (xref=contact content_id), #PN (xkey_ext), #PR (xkey)
all at xorder=1 as one supplier group. Supplier xrefs skipped with
a warning if contact not found. Supports ?clear=y to wipe and reload.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'import')
| -rw-r--r-- | import/ImportSimpleComponent.php | 154 | ||||
| -rw-r--r-- | import/load_simple_components.php | 73 |
2 files changed, 227 insertions, 0 deletions
diff --git a/import/ImportSimpleComponent.php b/import/ImportSimpleComponent.php new file mode 100644 index 0000000..b4f615b --- /dev/null +++ b/import/ImportSimpleComponent.php @@ -0,0 +1,154 @@ +<?php +/** + * Simplified component CSV importer — title, description, supplier, PN, price. + * + * CSV column layout (0-based, header row skipped by loader): + * 0 title Component name + * 1 description Plain-text description (stored as bithtml content body) + * 2 supplier Supplier contact title, case-insensitive (optional) + * 3 supplier_pn Supplier part number → xref #PN in xkey_ext (optional) + * 4 supplier_price Supplier price → xref #PR in xkey (optional) + * + * Supplier name is matched against liberty_content.title for content_type_guid='contact'. + * #SUP stores the contact content_id in the xref column; #PN and #PR share xorder=1 + * so they are grouped with the #SUP entry as one supplier set. + * + * Existing components (matched by title) are skipped unless cleared first. + * + * @package stock + */ + +use Bitweaver\Stock\StockComponent; + +// Cache supplier lookups — only 4 or so suppliers in the CSV +$_stockSupplierCache = []; + +function stockImportFindSupplier( string $name ): ?int { + global $gBitDb, $_stockSupplierCache; + + $key = strtolower( trim( $name ) ); + if( array_key_exists( $key, $_stockSupplierCache ) ) { + return $_stockSupplierCache[$key]; + } + + $contentId = $gBitDb->getOne( + "SELECT lc.`content_id` + FROM `".BIT_DB_PREFIX."liberty_content` lc + INNER JOIN `".BIT_DB_PREFIX."contact` c ON c.`content_id` = lc.`content_id` + WHERE UPPER( lc.`title` ) = UPPER( ? )", + [ trim( $name ) ] + ); + + $_stockSupplierCache[$key] = $contentId ? (int)$contentId : null; + return $_stockSupplierCache[$key]; +} + +function stockExpungeComponentByTitle( string $title ): bool { + global $gBitDb; + + $contentId = $gBitDb->getOne( + "SELECT lc.`content_id` + FROM `".BIT_DB_PREFIX."stock_component` sc + INNER JOIN `".BIT_DB_PREFIX."liberty_content` lc ON lc.`content_id` = sc.`content_id` + WHERE lc.`title` = ?", + [ $title ] + ); + if( !$contentId ) { + return false; + } + + // StockComponent::expunge() handles component_map + stock_component; + // LibertyContent::expunge() now handles liberty_xref + liberty_content + $component = new StockComponent( null, (int)$contentId ); + $component->expunge(); + return true; +} + +function stockImportSimpleComponent( array $data, int $rowNum ): array { + global $gBitDb; + + $result = [ 'loaded' => 0, 'skipped' => 0, 'errors' => [] ]; + + $title = trim( $data[0] ?? '' ); + if( empty( $title ) ) { + $result['skipped']++; + $result['errors'][] = "Row $rowNum: empty title, skipped."; + return $result; + } + + $exists = $gBitDb->getOne( + "SELECT lc.`content_id` + FROM `".BIT_DB_PREFIX."stock_component` sc + INNER JOIN `".BIT_DB_PREFIX."liberty_content` lc ON lc.`content_id` = sc.`content_id` + WHERE lc.`title` = ?", + [ $title ] + ); + if( $exists ) { + $result['skipped']++; + $result['errors'][] = "Row $rowNum: '$title' already exists, skipped."; + return $result; + } + + $description = trim( $data[1] ?? '' ); + $supplierName = trim( $data[2] ?? '' ); + $supplierPn = trim( $data[3] ?? '' ); + $supplierPrice = trim( $data[4] ?? '' ); + + $component = new StockComponent(); + $pHash = [ + 'title' => $title, + 'edit' => $description, + 'format_guid' => 'bithtml', + ]; + if( !$component->store( $pHash ) ) { + $result['skipped']++; + $result['errors'][] = "Row $rowNum: failed to create component '$title'."; + return $result; + } + + $contentId = $component->mContentId; + + if( !empty( $supplierName ) ) { + $supplierContentId = stockImportFindSupplier( $supplierName ); + if( !$supplierContentId ) { + $result['errors'][] = "Row $rowNum: '$title' — supplier '$supplierName' not found in contacts, xrefs skipped."; + } else { + $xrefId = $gBitDb->GenID( 'liberty_xref_seq' ); + $gBitDb->associateInsert( BIT_DB_PREFIX.'liberty_xref', [ + 'xref_id' => $xrefId, + 'content_id' => $contentId, + 'item' => '#SUP', + 'xorder' => 1, + 'xref' => $supplierContentId, + '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(), + ] ); + } + } + } + + $result['loaded']++; + return $result; +} diff --git a/import/load_simple_components.php b/import/load_simple_components.php new file mode 100644 index 0000000..7aafe25 --- /dev/null +++ b/import/load_simple_components.php @@ -0,0 +1,73 @@ +<?php +/** + * Load components from a 5-column CSV (title, description, supplier, PN, price). + * First row is a header and is skipped. Existing components (by title) are skipped + * unless clear=y is passed. + * + * Place your CSV at: stock/import/data/simple_components.csv + * Append ?clear=y to the URL to delete and re-import all rows. + * + * @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 = __DIR__.'/data/simple_components.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, 1000, ',', '"', '' ) ) !== false ) { + $rowNum++; + if( $rowNum === 1 ) { + continue; // skip header + } + $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 ); // +2: 1-based + header + $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 Components' ); |
