diff options
| author | Lester Caine <lester@lsces.co.uk> | 2026-06-02 17:53:10 +0100 |
|---|---|---|
| committer | Lester Caine <lester@lsces.co.uk> | 2026-06-02 17:53:10 +0100 |
| commit | 3bb1b60e08fa4382e4f715d7b94fde875d11cfd1 (patch) | |
| tree | 3ba2404490c8767d6a92eefc09f94694f1e1b172 /edit_movement.php | |
| parent | a6629ea4f5404490e63f4488e5a294ec49854200 (diff) | |
| download | stock-3bb1b60e08fa4382e4f715d7b94fde875d11cfd1.tar.gz stock-3bb1b60e08fa4382e4f715d7b94fde875d11cfd1.tar.bz2 stock-3bb1b60e08fa4382e4f715d7b94fde875d11cfd1.zip | |
edit_movement: support CSV upload at create time
Extract CSV processing into stockProcessMovementCsv() used by both
fSave (new movement) and upload_csv (existing movement).
Template: CSV file input shown on create form; enctype set to multipart.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'edit_movement.php')
| -rw-r--r-- | edit_movement.php | 236 |
1 files changed, 104 insertions, 132 deletions
diff --git a/edit_movement.php b/edit_movement.php index 239b7f5..429d390 100644 --- a/edit_movement.php +++ b/edit_movement.php @@ -32,20 +32,112 @@ $refTypes = $gBitDb->getAssoc( ORDER BY xi.`item`" ); +function stockProcessMovementCsv( StockMovement $pContent, array $pQtyTypes ): array { + global $gBitDb; + $result = [ 'loaded' => 0, 'skipped' => 0, 'errors' => [] ]; + + $file = $_FILES['csv_file'] ?? null; + if( !$file || $file['error'] !== UPLOAD_ERR_OK ) { + return $result; + } + $handle = fopen( $file['tmp_name'], 'r' ); + if( $handle === false ) { + return $result; + } + + $nextXorder = (int)$gBitDb->getOne( + "SELECT COALESCE( MAX(x.`xorder`) + 1, 1 ) FROM `".BIT_DB_PREFIX."liberty_xref` x + WHERE x.`content_id` = ? AND x.`item` IN ('SGL','PCK','SHT','VOL')", + [ $pContent->mContentId ] + ) ?: 1; + + $rowNum = 0; + while( ( $data = fgetcsv( $handle, 1000, ',', '"', '' ) ) !== false ) { + $rowNum++; + if( $rowNum === 1 ) { + $from = trim( $data[0] ?? '' ); + $ref = trim( $data[1] ?? '' ); + $dateStr = trim( $data[2] ?? '' ); + if( $ref !== '' ) { + $existingXrefId = $gBitDb->getOne( + "SELECT `xref_id` FROM `".BIT_DB_PREFIX."liberty_xref` + WHERE `content_id` = ? AND `item` IN ('REQN','TRANS','ORDER') ORDER BY `xorder`", + [ $pContent->mContentId ] + ); + $refHash = [ 'content_id' => $pContent->mContentId, 'item' => 'TRANS', 'xkey' => $ref, 'edit' => $from ]; + $existingXrefId ? $refHash['xref_id'] = $existingXrefId : $refHash['fAddXref'] = 1; + $pContent->storeXref( $refHash ); + } + if( $dateStr !== '' ) { + $parts = explode( '/', $dateStr ); + if( count( $parts ) === 3 ) { + $year = (int)$parts[2] < 100 ? 2000 + (int)$parts[2] : (int)$parts[2]; + $ts = mktime( 0, 0, 0, (int)$parts[1], (int)$parts[0], $year ); + if( $ts ) { + $gBitDb->query( "UPDATE `".BIT_DB_PREFIX."liberty_content` SET `event_time` = ? WHERE `content_id` = ?", [ $ts, $pContent->mContentId ] ); + } + } + } + continue; + } + + $componentName = trim( $data[0] ?? '' ); + $qty = (float)trim( $data[1] ?? '' ); + $qtyOverride = strtoupper( trim( $data[2] ?? '' ) ); + + if( $componentName === '' ) { $result['skipped']++; continue; } + if( $qty <= 0 ) { + $result['errors'][] = "Row $rowNum: '$componentName' — invalid quantity, skipped."; + $result['skipped']++; + continue; + } + + $contentId = $gBitDb->getOne( + "SELECT lc.`content_id` FROM `".BIT_DB_PREFIX."liberty_content` lc + WHERE lc.`content_type_guid` = 'stockcomponent' AND lc.`title` = ?", + [ $componentName ] + ); + if( !$contentId ) { + $result['errors'][] = "Row $rowNum: '$componentName' not found, skipped."; + $result['skipped']++; + continue; + } + + $qtySrc = in_array( $qtyOverride, $pQtyTypes ) ? $qtyOverride : null; + if( !$qtySrc ) { + $placeholders = implode( ',', array_fill( 0, count( $pQtyTypes ), '?' ) ); + $qtySrc = $gBitDb->getOne( + "SELECT x.`item` FROM `".BIT_DB_PREFIX."liberty_xref` x + WHERE x.`content_id` = ? AND x.`item` IN ($placeholders) ORDER BY x.`xorder`", + array_merge( [ (int)$contentId ], $pQtyTypes ) + ) ?: 'SGL'; + } + + $pContent->storeXref( [ 'content_id' => $pContent->mContentId, 'item' => $qtySrc, 'xref' => (int)$contentId, 'xkey' => $qty, 'xorder' => $nextXorder ] ); + $nextXorder++; + $result['loaded']++; + } + fclose( $handle ); + $pContent->load(); + return $result; +} + if( !empty( $_REQUEST['fSave'] ) ) { $isNew = !$gContent->isValid(); if( $gContent->store( $_REQUEST ) ) { - // On creation, immediately store the chosen movement type xref if( $isNew && !empty( $_REQUEST['movement_type'] ) && isset( $refTypes[$_REQUEST['movement_type']] ) ) { - $typeHash = [ - 'content_id' => $gContent->mContentId, - 'item' => $_REQUEST['movement_type'], - 'fAddXref' => 1, - ]; + $typeHash = [ 'content_id' => $gContent->mContentId, 'item' => $_REQUEST['movement_type'], 'fAddXref' => 1 ]; $gContent->storeXref( $typeHash ); } - header( 'Location: '.STOCK_PKG_URL.'edit_movement.php?content_id='.$gContent->mContentId ); - die; + if( $isNew && !empty( $_FILES['csv_file']['tmp_name'] ) && $_FILES['csv_file']['error'] === UPLOAD_ERR_OK ) { + $csvResult = stockProcessMovementCsv( $gContent, $qtyTypes ); + $gBitSmarty->assign( 'csvLoaded', $csvResult['loaded'] ); + $gBitSmarty->assign( 'csvSkipped', $csvResult['skipped'] ); + $gBitSmarty->assign( 'csvErrors', $csvResult['errors'] ); + } else { + header( 'Location: '.STOCK_PKG_URL.'edit_movement.php?content_id='.$gContent->mContentId ); + die; + } } } elseif( !empty( $_REQUEST['fReceived'] ) && $gContent->isValid() ) { @@ -58,130 +150,10 @@ if( !empty( $_REQUEST['fSave'] ) ) { if( !$file || $file['error'] !== UPLOAD_ERR_OK ) { $gContent->mErrors[] = KernelTools::tra( 'No file uploaded or upload error.' ); } else { - $handle = fopen( $file['tmp_name'], 'r' ); - if( $handle === false ) { - $gContent->mErrors[] = KernelTools::tra( 'Could not read uploaded file.' ); - } else { - $csvLoaded = 0; - $csvSkipped = 0; - $csvErrors = []; - $rowNum = 0; - - // Seed xorder from any existing movement items so re-uploads append cleanly - $nextXorder = (int)$gBitDb->getOne( - "SELECT COALESCE( MAX(x.`xorder`) + 1, 1 ) FROM `".BIT_DB_PREFIX."liberty_xref` x - WHERE x.`content_id` = ? AND x.`item` IN ('SGL','PCK','SHT','VOL')", - [ $gContent->mContentId ] - ) ?: 1; - - while( ( $data = fgetcsv( $handle, 1000, ',', '"', '' ) ) !== false ) { - $rowNum++; - - if( $rowNum === 1 ) { - // Header: from, ref, start_date (dd/mm/yy) - $from = trim( $data[0] ?? '' ); - $ref = trim( $data[1] ?? '' ); - $dateStr = trim( $data[2] ?? '' ); - - if( $ref !== '' ) { - // Update existing reference xref if already set (type selected on create), else insert - $existingXrefId = $gBitDb->getOne( - "SELECT `xref_id` FROM `".BIT_DB_PREFIX."liberty_xref` - WHERE `content_id` = ? AND `item` IN ('REQN','TRANS','ORDER') - ORDER BY `xorder`", - [ $gContent->mContentId ] - ); - $refHash = [ - 'content_id' => $gContent->mContentId, - 'item' => 'TRANS', - 'xkey' => $ref, - 'edit' => $from, - ]; - if( $existingXrefId ) { - $refHash['xref_id'] = $existingXrefId; - } else { - $refHash['fAddXref'] = 1; - } - $gContent->storeXref( $refHash ); - } - - if( $dateStr !== '' ) { - $parts = explode( '/', $dateStr ); - if( count( $parts ) === 3 ) { - $year = (int)$parts[2] < 100 ? 2000 + (int)$parts[2] : (int)$parts[2]; - $ts = mktime( 0, 0, 0, (int)$parts[1], (int)$parts[0], $year ); - if( $ts ) { - $gBitDb->query( - "UPDATE `".BIT_DB_PREFIX."liberty_content` SET `event_time` = ? WHERE `content_id` = ?", - [ $ts, $gContent->mContentId ] - ); - } - } - } - continue; - } - - // Data rows: component name, quantity, [optional qty type] - $componentName = trim( $data[0] ?? '' ); - $rawQty = trim( $data[1] ?? '' ); - $qtyOverride = strtoupper( trim( $data[2] ?? '' ) ); - - if( $componentName === '' ) { - $csvSkipped++; - continue; - } - - // Take numeric part before any space - $qty = (float)$rawQty; - if( $qty <= 0 ) { - $csvErrors[] = "Row $rowNum: '$componentName' — invalid quantity '$rawQty', skipped."; - $csvSkipped++; - continue; - } - - $contentId = $gBitDb->getOne( - "SELECT lc.`content_id` - FROM `".BIT_DB_PREFIX."liberty_content` lc - WHERE lc.`content_type_guid` = 'stockcomponent' - AND lc.`title` = ?", - [ $componentName ] - ); - if( !$contentId ) { - $csvErrors[] = "Row $rowNum: '$componentName' not found, skipped."; - $csvSkipped++; - continue; - } - - // Qty type: use CSV column 3 if valid, else read from component's existing xref - $qtySrc = in_array( $qtyOverride, $qtyTypes ) ? $qtyOverride : null; - if( !$qtySrc ) { - $placeholders = implode( ',', array_fill( 0, count( $qtyTypes ), '?' ) ); - $qtySrc = $gBitDb->getOne( - "SELECT x.`item` FROM `".BIT_DB_PREFIX."liberty_xref` x - WHERE x.`content_id` = ? AND x.`item` IN ($placeholders) - ORDER BY x.`xorder`", - array_merge( [ (int)$contentId ], $qtyTypes ) - ) ?: 'SGL'; - } - - $itemHash = [ - 'content_id' => $gContent->mContentId, - 'item' => $qtySrc, - 'xref' => (int)$contentId, - 'xkey' => $qty, - 'xorder' => $nextXorder, - ]; - $gContent->storeXref( $itemHash ); - $nextXorder++; - $csvLoaded++; - } - fclose( $handle ); - $gContent->load(); - - $gBitSmarty->assign( 'csvLoaded', $csvLoaded ); - $gBitSmarty->assign( 'csvSkipped', $csvSkipped ); - $gBitSmarty->assign( 'csvErrors', $csvErrors ); - } + $csvResult = stockProcessMovementCsv( $gContent, $qtyTypes ); + $gBitSmarty->assign( 'csvLoaded', $csvResult['loaded'] ); + $gBitSmarty->assign( 'csvSkipped', $csvResult['skipped'] ); + $gBitSmarty->assign( 'csvErrors', $csvResult['errors'] ); } } elseif( !empty( $_REQUEST['delete'] ) ) { |
