diff options
| author | Lester Caine <lester@lsces.co.uk> | 2026-06-03 22:07:34 +0100 |
|---|---|---|
| committer | Lester Caine <lester@lsces.co.uk> | 2026-06-03 22:07:34 +0100 |
| commit | 8fd8add7673c2440ea8f7653f1c3b44e9518cf38 (patch) | |
| tree | aa759a432e86368a21b80c9df0fa2be7f7b09f40 | |
| parent | 4bbff18f76c601324b865a2084b3d1e4bf8dc7e5 (diff) | |
| download | stock-8fd8add7673c2440ea8f7653f1c3b44e9518cf38.tar.gz stock-8fd8add7673c2440ea8f7653f1c3b44e9518cf38.tar.bz2 stock-8fd8add7673c2440ea8f7653f1c3b44e9518cf38.zip | |
stock: movement contact linkage, note field, view/edit tidy
- importCsv: look up contact by SCREF xref.data, store content_id in xref.xref
- load(): add ref_contact_id, ref_contact_name, ref_type_title subqueries
- edit_movement.php: contact lookup URL; save xref.xref from manual picker;
fix refRow.edit → refRow.data fallback
- edit_movement.tpl: From field with contact autocomplete + hidden id; Note field
- view_movement.tpl: supplier above dates, reference tab removed, note shown,
Type shows cross_ref_title, contact links to contact record
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| -rw-r--r-- | edit_movement.php | 64 | ||||
| -rw-r--r-- | includes/classes/StockMovement.php | 18 | ||||
| -rw-r--r-- | templates/edit_movement.tpl | 89 | ||||
| -rw-r--r-- | templates/view_movement.tpl | 33 |
4 files changed, 178 insertions, 26 deletions
diff --git a/edit_movement.php b/edit_movement.php index e492cee..810d363 100644 --- a/edit_movement.php +++ b/edit_movement.php @@ -32,12 +32,51 @@ $refTypes = $gBitDb->getAssoc( ORDER BY xi.`item`" ); +// Helper: parse dd/mm/yy or dd/mm/yyyy → Unix timestamp, or 0 +function parseMovementDate( string $s ): int { + $parts = explode( '/', trim( $s ) ); + if( count( $parts ) !== 3 ) return 0; + $year = (int)$parts[2] < 100 ? 2000 + (int)$parts[2] : (int)$parts[2]; + return (int)mktime( 0, 0, 0, (int)$parts[1], (int)$parts[0], $year ); +} + if( !empty( $_REQUEST['fSave'] ) ) { $isNew = !$gContent->isValid(); if( $gContent->store( $_REQUEST ) ) { - if( $isNew && !empty( $_REQUEST['movement_type'] ) && isset( $refTypes[$_REQUEST['movement_type']] ) ) { - $typeHash = [ 'content_id' => $gContent->mContentId, 'item' => $_REQUEST['movement_type'], 'fAddXref' => 1 ]; - $gContent->storeXref( $typeHash ); + // Reference xref — create or update type/key/from + if( !empty( $_REQUEST['movement_type'] ) && isset( $refTypes[$_REQUEST['movement_type']] ) ) { + $existingRef = $gBitDb->getRow( + "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' => $_REQUEST['movement_type'], + 'xkey' => trim( $_REQUEST['ref_key'] ?? '' ), + 'edit' => trim( $_REQUEST['ref_from'] ?? '' ), + ]; + if( !empty( $_REQUEST['ref_contact_id'] ) && is_numeric( $_REQUEST['ref_contact_id'] ) ) { + $refHash['xref'] = (int)$_REQUEST['ref_contact_id']; + } + $existingRef ? $refHash['xref_id'] = $existingRef['xref_id'] : $refHash['fAddXref'] = 1; + $gContent->storeXref( $refHash ); + } + // Ordered date → xref.start_date + if( !empty( $_REQUEST['ordered_date'] ) && ($ts = parseMovementDate( $_REQUEST['ordered_date'] )) ) { + $gBitDb->query( + "UPDATE `".BIT_DB_PREFIX."liberty_xref` SET `start_date`=? + WHERE `content_id`=? AND `item` IN ('REQN','TRANS','ORDER')", + [ date( 'Y-m-d H:i:s', $ts ), $gContent->mContentId ] + ); + } + // Received date → lc.event_time + if( !empty( $_REQUEST['received_date'] ) && ($ts = parseMovementDate( $_REQUEST['received_date'] )) ) { + $gBitDb->query( + "UPDATE `".BIT_DB_PREFIX."liberty_content` SET `event_time`=? WHERE `content_id`=?", + [ $ts, $gContent->mContentId ] + ); + $gContent->mInfo['event_time'] = $ts; } if( $isNew && !empty( $_FILES['csv_file']['tmp_name'] ) && $_FILES['csv_file']['error'] === UPLOAD_ERR_OK ) { $csvResult = $gContent->importCsv( $qtyTypes ); @@ -88,9 +127,26 @@ if( !empty( $_REQUEST['fSave'] ) ) { } if( $gContent->isValid() ) { - $gContent->mInfo['movement_xref_groups'] = $gContent->getXrefGroupList(); + // Only quantity group in tabs; reference is handled directly in the form + $gContent->mInfo['movement_xref_groups'] = array_values( array_filter( + $gContent->getXrefGroupList(), + fn( $g ) => $g['x_group'] !== 'reference' + ) ); } +// Pre-populate reference row for the form +$refRow = !empty( $gContent->mInfo['reference'] ) ? reset( $gContent->mInfo['reference'] ) : []; +$gBitSmarty->assign( 'refRow', $refRow ); + +// Pre-format dates as dd/mm/yyyy for form fields +$orderedDateVal = !empty( $gContent->mInfo['ref_start_date'] ) + ? date( 'd/m/Y', strtotime( $gContent->mInfo['ref_start_date'] ) ) : ''; +$receivedDateVal = !empty( $gContent->mInfo['event_time'] ) && $gContent->mInfo['event_time'] > 0 + ? date( 'd/m/Y', (int)$gContent->mInfo['event_time'] ) : ''; +$gBitSmarty->assign( 'orderedDateVal', $orderedDateVal ); +$gBitSmarty->assign( 'receivedDateVal', $receivedDateVal ); +$gBitSmarty->assign( 'contactLookupUrl', CONTACT_PKG_URL.'includes/lookup_contact.php' ); + $gBitSmarty->assign( 'refTypes', $refTypes ); $gBitSmarty->assign( 'errors', $gContent->mErrors ); diff --git a/includes/classes/StockMovement.php b/includes/classes/StockMovement.php index 747099a..d478796 100644 --- a/includes/classes/StockMovement.php +++ b/includes/classes/StockMovement.php @@ -71,6 +71,17 @@ class StockMovement extends LibertyContent { , (SELECT FIRST 1 x.`start_date` FROM `{$X}liberty_xref` x WHERE x.`content_id` = lc.`content_id` AND x.`item` IN ('REQN','TRANS','ORDER') ORDER BY x.`xorder`) AS ref_start_date + , (SELECT FIRST 1 xi.`cross_ref_title` FROM `{$X}liberty_xref` x + JOIN `{$X}liberty_xref_item` xi ON xi.`item` = x.`item` AND xi.`content_type_guid` = 'stockmovement' + WHERE x.`content_id` = lc.`content_id` AND x.`item` IN ('REQN','TRANS','ORDER') + ORDER BY x.`xorder`) AS ref_type_title + , (SELECT FIRST 1 x.`xref` FROM `{$X}liberty_xref` x + WHERE x.`content_id` = lc.`content_id` AND x.`item` IN ('REQN','TRANS','ORDER') + ORDER BY x.`xorder`) AS ref_contact_id + , (SELECT FIRST 1 lc2.`title` FROM `{$X}liberty_xref` x + JOIN `{$X}liberty_content` lc2 ON lc2.`content_id` = x.`xref` + WHERE x.`content_id` = lc.`content_id` AND x.`item` IN ('REQN','TRANS','ORDER') + ORDER BY x.`xorder`) AS ref_contact_name FROM `".BIT_DB_PREFIX."liberty_content` lc LEFT JOIN `".BIT_DB_PREFIX."users_users` uue ON uue.`user_id` = lc.`modifier_user_id` LEFT JOIN `".BIT_DB_PREFIX."users_users` uuc ON uuc.`user_id` = lc.`user_id` @@ -354,7 +365,14 @@ class StockMovement extends LibertyContent { ); // Preserve existing type if already set; default to TRANS for new rows $refItem = $existingRow['item'] ?? 'TRANS'; + // Look up contact by SCREF short name + $contactId = $from !== '' ? (int)$this->mDb->getOne( + "SELECT `content_id` FROM `".BIT_DB_PREFIX."liberty_xref` + WHERE `item`='SCREF' AND `data`=?", + [ $from ] + ) : 0; $refHash = [ 'content_id' => $this->mContentId, 'item' => $refItem, 'xkey' => $ref, 'edit' => $from ]; + if( $contactId ) $refHash['xref'] = $contactId; $existingRow ? $refHash['xref_id'] = $existingRow['xref_id'] : $refHash['fAddXref'] = 1; $this->storeXref( $refHash ); } diff --git a/templates/edit_movement.tpl b/templates/edit_movement.tpl index 404590d..097aba2 100644 --- a/templates/edit_movement.tpl +++ b/templates/edit_movement.tpl @@ -21,24 +21,68 @@ {forminput} <input type="text" class="form-control input-xlarge" name="title" id="title" value="{$gContent->getTitle()|escape}" maxlength="160" /> - {formhelp note="Movement reference — e.g. REQ-2026-001"} {/forminput} </div> - {if !$gContent->isValid() && $refTypes} + {if $refTypes} <div class="form-group"> {formlabel label="Movement Type" mandatory="y"} {forminput} {foreach from=$refTypes key=item item=label} - <label class="radio"> + <label class="radio-inline"> <input type="radio" name="movement_type" value="{$item|escape}" - {if $smarty.foreach.default.first} checked="checked"{/if} /> {$label|escape} + {if $refRow.item eq $item} checked="checked" + {elseif !$refRow && $smarty.foreach.default.first} checked="checked"{/if} /> {$label|escape} </label> {/foreach} {/forminput} </div> {/if} + <div class="form-group"> + {formlabel label="From" for="ref_from"} + {forminput} + <input type="hidden" name="ref_contact_id" id="ref_contact_id" + value="{$gContent->mInfo.ref_contact_id|default:''|escape}" /> + <input type="text" class="form-control" name="ref_from" id="ref_from" + autocomplete="off" list="contact_suggestions" + value="{if $gContent->mInfo.ref_contact_name}{$gContent->mInfo.ref_contact_name|escape}{else}{$refRow.data|default:''|escape}{/if}" + maxlength="160" placeholder="Type to search contacts…" /> + <datalist id="contact_suggestions"></datalist> + {/forminput} + </div> + + <div class="form-group"> + {formlabel label="Ref Key" for="ref_key"} + {forminput} + <input type="text" class="form-control" name="ref_key" id="ref_key" + value="{$refRow.xkey|default:''|escape}" maxlength="160" /> + {/forminput} + </div> + + <div class="form-group"> + {formlabel label="Ordered" for="ordered_date"} + {forminput} + <input type="text" class="form-control input-small" name="ordered_date" id="ordered_date" + placeholder="dd/mm/yyyy" value="{$orderedDateVal|escape}" maxlength="10" /> + {/forminput} + </div> + + <div class="form-group"> + {formlabel label="Received" for="received_date"} + {forminput} + <input type="text" class="form-control input-small" name="received_date" id="received_date" + placeholder="dd/mm/yyyy" value="{$receivedDateVal|escape}" maxlength="10" /> + {/forminput} + </div> + + <div class="form-group"> + {formlabel label="Note" for="edit"} + {forminput} + <textarea class="form-control" name="edit" id="edit" rows="2">{$gContent->mInfo.data|default:''|escape}</textarea> + {/forminput} + </div> + {if !$gContent->isValid()} <div class="form-group"> {formlabel label="Load CSV"} @@ -52,12 +96,6 @@ <div class="form-group submit"> <input type="submit" class="btn btn-primary" name="fSave" value="{tr}Save{/tr}" /> {if $gContent->isValid()} - {if !$gContent->isReceived()} - <input type="submit" class="btn btn-success" name="fReceived" value="{tr}Mark Received{/tr}" - onclick="return confirm('{tr}Mark this movement as received?{/tr}')" /> - {else} - <span class="label label-success">{tr}Received{/tr}</span> - {/if} <input type="submit" class="btn btn-danger pull-right" name="delete" value="{tr}Delete{/tr}" /> {/if} </div> @@ -66,7 +104,6 @@ {if $gContent->isValid()} {* ── Upload CSV ── *} - {if !$gContent->isReceived()} <h4>{tr}Upload CSV{/tr}</h4> {form enctype="multipart/form-data" ipackage="stock" ifile="edit_movement.php"} <input type="hidden" name="content_id" value="{$gContent->mContentId|escape}" /> @@ -75,7 +112,6 @@ <input type="submit" class="btn btn-default" name="upload_csv" value="{tr}Upload{/tr}" /> </div> {/form} - {/if} {* ── Upload results ── *} {if isset($csvLoaded)} @@ -90,7 +126,7 @@ {/if} {/if} - {* ── Xref tabs — items and references ── *} + {* ── Xref tabs — quantity BOM only ── *} {if $gContent->mInfo.movement_xref_groups} {jstabs} {section name=xrefGroup loop=$gContent->mInfo.movement_xref_groups} @@ -108,4 +144,31 @@ </div><!-- end .body --> </div><!-- end .stock --> +<script> +(function($) { + var timer, contacts = []; + $('#ref_from').on('input', function() { + var q = $(this).val(); + clearTimeout(timer); + if (q.length < 2) { $('#contact_suggestions').empty(); contacts = []; return; } + timer = setTimeout(function() { + $.getJSON('{$contactLookupUrl}', {ldelim}q: q{rdelim}, function(data) { + contacts = data; + var dl = $('#contact_suggestions').empty(); + $.each(data, function(i, row) { + var label = row.title + (row.scref ? ' (' + row.scref + ')' : ''); + dl.append($('<option>').val(label).attr('data-id', row.content_id)); + }); + }); + }, 250); + }).on('change', function() { + var val = $(this).val(), found = null; + $.each(contacts, function(i, row) { + var label = row.title + (row.scref ? ' (' + row.scref + ')' : ''); + if (label === val || row.title === val || row.scref === val) { found = row; return false; } + }); + $('#ref_contact_id').val(found ? found.content_id : ''); + }); +}(jQuery)); +</script> {/strip} diff --git a/templates/view_movement.tpl b/templates/view_movement.tpl index 4c810b0..2c66659 100644 --- a/templates/view_movement.tpl +++ b/templates/view_movement.tpl @@ -14,7 +14,16 @@ <dl class="dl-horizontal"> <dt>{tr}Type{/tr}</dt> - <dd>{$gContent->getDirection()|escape}</dd> + <dd>{$gContent->mInfo.ref_type_title|default:$gContent->getDirection()|escape}</dd> + {if $gContent->mInfo.ref_contact_name} + <dt>{tr}Supplier{/tr}</dt> + <dd> + <a href="{$smarty.const.CONTACT_PKG_URL}display.php?content_id={$gContent->mInfo.ref_contact_id}">{$gContent->mInfo.ref_contact_name|escape}</a> + </dd> + {elseif $gContent->mInfo.reference.0.data} + <dt>{tr}From{/tr}</dt> + <dd>{$gContent->mInfo.reference.0.data|escape}</dd> + {/if} <dt>{tr}Created{/tr}</dt> <dd>{$gContent->mInfo.created|bit_short_datetime} {tr}by{/tr} {$gContent->mInfo.creator|escape}</dd> {if $gContent->mInfo.ref_start_date} @@ -27,19 +36,25 @@ <dt>{tr}Modified{/tr}</dt> <dd>{$gContent->mInfo.last_modified|bit_short_datetime} {tr}by{/tr} {$gContent->mInfo.editor|escape}</dd> {/if} + {if $gContent->mInfo.data} + <dt>{tr}Note{/tr}</dt> + <dd>{$gContent->mInfo.data|escape}</dd> + {/if} </dl> {assign var=movXrefGroups value=$gContent->getXrefGroupList()} {if $movXrefGroups} {jstabs} - {section name=xrefGroup loop=$movXrefGroups} - {include file=$gContent->getXrefListTemplate($movXrefGroups[xrefGroup].template) - source=$movXrefGroups[xrefGroup].source - source_title=$movXrefGroups[xrefGroup].title - group=$movXrefGroups[xrefGroup].sort_order - allow_add=false - allow_edit=false} - {/section} + {foreach from=$movXrefGroups item=xrefGroup} + {if $xrefGroup.x_group neq 'reference'} + {include file=$gContent->getXrefListTemplate($xrefGroup.template) + source=$xrefGroup.source + source_title=$xrefGroup.title + group=$xrefGroup.sort_order + allow_add=false + allow_edit=false} + {/if} + {/foreach} {/jstabs} {/if} |
