summaryrefslogtreecommitdiff
path: root/import
diff options
context:
space:
mode:
authorLester Caine <lester@lsces.co.uk>2026-06-07 16:51:15 +0100
committerLester Caine <lester@lsces.co.uk>2026-06-07 16:51:15 +0100
commit9f5cb5855df61554e38a6da5d036bd78d421a2a9 (patch)
tree403cdd171269e8037cb8113f0735665ec916215c /import
parentd75d099650de58ede08583e3dac27986544f799f (diff)
downloadstock-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.php120
-rw-r--r--import/ImportSimpleComponent.php41
-rw-r--r--import/load_component_list.php68
-rw-r--r--import/load_kitlocker_assemblies.php73
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' );