summaryrefslogtreecommitdiff
path: root/import
diff options
context:
space:
mode:
authorLester Caine <lester@lsces.co.uk>2026-06-18 20:32:50 +0100
committerLester Caine <lester@lsces.co.uk>2026-06-18 20:32:50 +0100
commit673ac631795c6706671ef80353cb053917626c6d (patch)
treedbe2cf63ab1a0d5c75a3bc8348b725596d14f046 /import
parentf5e154c8672be3a4ccbde95f06b5901f8b0ebace (diff)
downloadstock-673ac631795c6706671ef80353cb053917626c6d.tar.gz
stock-673ac631795c6706671ef80353cb053917626c6d.tar.bz2
stock-673ac631795c6706671ef80353cb053917626c6d.zip
Add MERG group BOM importer; fix component_order linked_title
load_merg_bom.php reads a group CSV with assembly content_ids in the header row and creates SGL BOM xrefs for each non-zero quantity cell. Supports dry=1 preview and clear=1 to wipe existing BOM lines first. component_order.tpl was using xref_title/xref_data (the item type label, e.g. "Single Unit-1") instead of linked_title/linked_data (the actual component title and description from the joined content row). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'import')
-rw-r--r--import/load_merg_bom.php158
1 files changed, 158 insertions, 0 deletions
diff --git a/import/load_merg_bom.php b/import/load_merg_bom.php
new file mode 100644
index 0000000..4b857ae
--- /dev/null
+++ b/import/load_merg_bom.php
@@ -0,0 +1,158 @@
+<?php
+/**
+ * Import MERG group BOM quantities into existing kitlocker assemblies.
+ *
+ * Reads merg_group_<X>.csv from storage/stock/. Row 1 is the title (skipped).
+ * Row 2 is the header: cols 0-2 are Component/Order code/Source; any col from 3
+ * onwards whose header is a plain integer is treated as an assembly content_id.
+ * Non-numeric header cells (e.g. "Notes") are ignored.
+ *
+ * For each data row with Source='R' and a non-empty Rapid order code:
+ * - looks up the component via #SUP xref (xref=109, xkey=order code)
+ * - for each assembly column with a non-zero quantity, writes a BOM xref
+ *
+ * Usage: load_merg_bom.php?group=E
+ * load_merg_bom.php?group=E&dry=1 preview only
+ * load_merg_bom.php?group=E&clear=1 remove existing BOM lines first
+ *
+ * @package stock
+ */
+
+namespace Bitweaver\Stock;
+
+require_once '../../kernel/includes/setup_inc.php';
+
+use Bitweaver\Liberty\LibertyXref;
+
+global $gBitSystem, $gBitDb;
+
+$gBitSystem->verifyPackage( 'stock' );
+$gBitSystem->verifyPermission( 'p_stock_admin' );
+
+const RAPID_SUPPLIER_ID = 109;
+
+$group = preg_replace( '/[^A-Za-z]/', '', $_REQUEST['group'] ?? '' );
+$dryRun = !empty( $_REQUEST['dry'] );
+$doClear = !empty( $_REQUEST['clear'] );
+
+$csvFile = STOCK_IMPORT_PATH . 'merg_group_' . strtoupper( $group ) . '.csv';
+
+$errors = [];
+$assemblies = []; // keyed by content_id: [ 'title'=>, 'loaded'=>, 'skipped'=>, 'cleared'=> ]
+$asmCols = []; // col index => assembly content_id
+
+if( empty( $group ) ) {
+ $errors[] = 'No group specified — append ?group=A through ?group=E to the URL.';
+} elseif( !file_exists( $csvFile ) ) {
+ $errors[] = 'CSV not found: ' . $csvFile;
+} else {
+ $handle = fopen( $csvFile, 'r' );
+ if( $handle === false ) {
+ $errors[] = 'Cannot open: ' . $csvFile;
+ } else {
+ $rowNum = 0;
+ while( ( $data = fgetcsv( $handle, 2000, ',', '"', '' ) ) !== false ) {
+ $rowNum++;
+
+ if( $rowNum === 1 ) {
+ continue; // title row
+ }
+
+ if( $rowNum === 2 ) {
+ // Header row — detect assembly columns by numeric content_id
+ foreach( $data as $col => $cell ) {
+ $cell = trim( $cell );
+ if( $col >= 3 && ctype_digit( $cell ) && (int)$cell > 0 ) {
+ $asmId = (int)$cell;
+ $asmTitle = $gBitDb->getOne(
+ "SELECT `title` FROM `".BIT_DB_PREFIX."liberty_content` WHERE `content_id` = ?",
+ [ $asmId ]
+ );
+ if( !$asmTitle ) {
+ $errors[] = "Header col $col: assembly content_id $asmId not found.";
+ continue;
+ }
+ $asmCols[$col] = $asmId;
+ $assemblies[$asmId] = [ 'title' => $asmTitle, 'loaded' => 0, 'skipped' => 0, 'cleared' => 0 ];
+
+ if( $doClear && !$dryRun ) {
+ $cleared = $gBitDb->query(
+ "DELETE FROM `".BIT_DB_PREFIX."liberty_xref`
+ WHERE `content_id` = ? AND `item` IN ('SGL','PRT','PCK','SHT','VOL')",
+ [ $asmId ]
+ );
+ $assemblies[$asmId]['cleared'] = $gBitDb->Affected_Rows();
+ }
+ }
+ }
+ continue;
+ }
+
+ // Data rows
+ $component = trim( $data[0] ?? '' );
+ $orderCode = trim( $data[1] ?? '' );
+ $source = strtoupper( trim( $data[2] ?? '' ) );
+
+ if( empty( $component ) || $source !== 'R' || empty( $orderCode ) ) {
+ continue;
+ }
+
+ $compId = $gBitDb->getOne(
+ "SELECT `content_id` FROM `".BIT_DB_PREFIX."liberty_xref`
+ WHERE `item` = '#SUP' AND `xref` = ? AND `xkey` = ?",
+ [ RAPID_SUPPLIER_ID, $orderCode ]
+ );
+
+ if( !$compId ) {
+ $errors[] = "Row $rowNum ($orderCode): component not found — run stub import first.";
+ continue;
+ }
+
+ foreach( $asmCols as $col => $asmId ) {
+ $qty = trim( $data[$col] ?? '' );
+ if( $qty === '' || $qty === '0' || (float)$qty == 0 ) {
+ continue;
+ }
+
+ // xorder = next available for this assembly
+ $xorder = (int)$gBitDb->getOne(
+ "SELECT COALESCE(MAX(`xorder`), 0) + 1 FROM `".BIT_DB_PREFIX."liberty_xref`
+ WHERE `content_id` = ? AND `item` IN ('SGL','PRT','PCK','SHT','VOL')",
+ [ $asmId ]
+ );
+
+ if( !$dryRun ) {
+ $xrefObj = new LibertyXref();
+ $xrefObj->mContentTypeGuid = 'stockassembly';
+ $pHash = [
+ 'content_id' => $asmId,
+ 'item' => 'SGL',
+ 'xorder' => $xorder,
+ 'xref' => $compId,
+ 'xkey' => $qty,
+ ];
+ if( $xrefObj->store( $pHash ) ) {
+ $assemblies[$asmId]['loaded']++;
+ } else {
+ $errors[] = "Row $rowNum ($orderCode) → assembly $asmId: xref store failed.";
+ $assemblies[$asmId]['skipped']++;
+ }
+ } else {
+ $assemblies[$asmId]['loaded']++;
+ }
+ }
+ }
+ fclose( $handle );
+ }
+}
+
+global $gBitSmarty;
+$gBitSmarty->assign( 'group', $group );
+$gBitSmarty->assign( 'dryRun', $dryRun );
+$gBitSmarty->assign( 'doClear', $doClear );
+$gBitSmarty->assign( 'csvFile', $csvFile );
+$gBitSmarty->assign( 'asmCols', $asmCols );
+$gBitSmarty->assign( 'assemblies', $assemblies );
+$gBitSmarty->assign( 'errors', $errors );
+
+$gBitSystem->display( 'bitpackage:stock/load_merg_bom.tpl', 'MERG BOM Import — Group ' . strtoupper( $group ) );