summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLester Caine <lester@lsces.co.uk>2026-06-03 22:07:34 +0100
committerLester Caine <lester@lsces.co.uk>2026-06-03 22:07:34 +0100
commit8fd8add7673c2440ea8f7653f1c3b44e9518cf38 (patch)
treeaa759a432e86368a21b80c9df0fa2be7f7b09f40
parent4bbff18f76c601324b865a2084b3d1e4bf8dc7e5 (diff)
downloadstock-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.php64
-rw-r--r--includes/classes/StockMovement.php18
-rw-r--r--templates/edit_movement.tpl89
-rw-r--r--templates/view_movement.tpl33
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}