summaryrefslogtreecommitdiff
path: root/edit_movement.php
diff options
context:
space:
mode:
authorLester Caine <lester@lsces.co.uk>2026-06-02 17:53:10 +0100
committerLester Caine <lester@lsces.co.uk>2026-06-02 17:53:10 +0100
commit3bb1b60e08fa4382e4f715d7b94fde875d11cfd1 (patch)
tree3ba2404490c8767d6a92eefc09f94694f1e1b172 /edit_movement.php
parenta6629ea4f5404490e63f4488e5a294ec49854200 (diff)
downloadstock-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.php236
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'] ) ) {