summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLester Caine <lester@lsces.co.uk>2026-06-06 18:34:11 +0100
committerLester Caine <lester@lsces.co.uk>2026-06-06 18:34:11 +0100
commitd75d099650de58ede08583e3dac27986544f799f (patch)
tree8d1943e2673d745d41fa97534f00113c3125a5ac
parent20d282c1bbb6385150486a6c88ad65ce97e746d7 (diff)
downloadstock-d75d099650de58ede08583e3dac27986544f799f.tar.gz
stock-d75d099650de58ede08583e3dac27986544f799f.tar.bz2
stock-d75d099650de58ede08583e3dac27986544f799f.zip
stock: docblock tidy across all four class files
Add/update docblocks for StockBase, StockComponent, StockAssembly, and StockMovement. File-level comments describe the content model; method comments document params, return types, and non-obvious behaviour (BOM enrichment, circular-assembly guard, movement direction inference, CSV import format, recursive CTE tree, xorder appending). Remove stale/empty @package duplicates on class declarations. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
-rwxr-xr-xincludes/classes/StockAssembly.php160
-rwxr-xr-xincludes/classes/StockBase.php70
-rwxr-xr-xincludes/classes/StockComponent.php74
-rw-r--r--includes/classes/StockMovement.php104
4 files changed, 355 insertions, 53 deletions
diff --git a/includes/classes/StockAssembly.php b/includes/classes/StockAssembly.php
index caf34c4..1055c3c 100755
--- a/includes/classes/StockAssembly.php
+++ b/includes/classes/StockAssembly.php
@@ -1,11 +1,14 @@
<?php
/**
+ * A BOM / kit / assembly — a named group of components with quantities.
+ *
+ * Stored as a pure liberty_content record (content_type_guid='stockassembly').
+ * Components are linked via stock_assembly_map with an item_position for ordering.
+ * BOM quantities live in liberty_xref (x_group='quantity', items SGL/PCK/SHT/VOL).
+ * Assemblies can be nested; breadcrumb/tree queries use a Firebird recursive CTE.
+ *
* @package stock
*/
-
-/**
- * required setup
- */
namespace Bitweaver\Stock;
use Bitweaver\BitBase;
@@ -18,18 +21,19 @@ define( 'STOCK_PAGINATION_AUTO_FLOW', 'auto_flow' );
define( 'STOCK_PAGINATION_POSITION_NUMBER', 'position_number' );
define( 'STOCK_PAGINATION_SIMPLE_LIST', 'simple_list' );
-
-/**
- * @package stock
- */
#[\AllowDynamicProperties]
class StockAssembly extends StockBase {
- public $mItems; // Array of StockComponent class instances which belong to this gallery
+ /** @var StockComponent[] Components belonging to this assembly, keyed by content_id. */
+ public $mItems;
public $mPaginationLookup;
public $mPreviewImage;
public $pRecursiveDelete;
protected $mXrefTypeKey = 'stockassembly_types';
+ /**
+ * @param int|null $pAssemblyId Legacy param — use $pContentId instead.
+ * @param int|null $pContentId liberty_content.content_id to load.
+ */
public function __construct($pAssemblyId = null, $pContentId = null) {
parent::__construct();
$this->mContentTypeGuid = STOCKASSEMBLY_CONTENT_TYPE_GUID;
@@ -66,10 +70,19 @@ class StockAssembly extends StockBase {
return parent::__sleep();
}
+ /** @return bool TRUE when mContentId is a valid positive integer. */
public function isValid() {
return @$this->verifyId( $this->mContentId );
}
+ /**
+ * Enrich a BOM xref row with component title, description, and pack size.
+ *
+ * Calls parent for supplier enrichment, then adds xref_title, xref_data,
+ * pack_size, and pack_size_ext from the linked component's liberty_content + PCK xref.
+ *
+ * @param array $pXrefInfo Xref display row; modified in place.
+ */
public function enrichXrefDisplay( array &$pXrefInfo ): void {
parent::enrichXrefDisplay( $pXrefInfo );
if( !empty( $pXrefInfo['xref'] ) ) {
@@ -88,6 +101,10 @@ class StockAssembly extends StockBase {
}
}
+ /**
+ * Load xref groups then enrich the 'quantity' BOM group — sorts by xorder and
+ * resolves each component content_id to title, description, and pack size.
+ */
public function loadXrefInfo(): void {
parent::loadXrefInfo();
if( empty( $this->mXrefInfo ) ) return;
@@ -114,6 +131,11 @@ class StockAssembly extends StockBase {
unset( $row );
}
+ /**
+ * @param array $pLookupHash Must contain 'content_id'.
+ * @param bool $pLoadFromCache Whether to use LibertyContent's object cache.
+ * @return static|null Loaded object, or null if not found.
+ */
public static function lookup( $pLookupHash, $pLoadFromCache=true ) {
global $gBitDb;
$ret = null;
@@ -130,6 +152,13 @@ class StockAssembly extends StockBase {
return $ret;
}
+ /**
+ * Load assembly record into $this->mInfo, including pagination config and component count.
+ *
+ * @param int|null $pContentId Unused; mContentId must be set before calling.
+ * @param array|null $pPluginParams Unused.
+ * @return bool TRUE on success, FALSE if no record found or mContentId invalid.
+ */
public function load( $pContentId = null, $pPluginParams = null ) {
global $gBitSystem;
$bindVars = [];
@@ -185,6 +214,15 @@ class StockAssembly extends StockBase {
return !empty( $this->mInfo );
}
+ /**
+ * Load a page of component items into $this->mItems.
+ *
+ * Respects the assembly's pagination layout preference. Pass $pListHash['page'] = -1
+ * to load all items without paging.
+ *
+ * @param array $pListHash Pagination params; cant is set from $this->mInfo['num_components'].
+ * @return bool|null TRUE if items were loaded, FALSE/null otherwise.
+ */
public function loadComponents( &$pListHash = [] ) {
global $gLibertySystem, $gBitSystem, $gBitUser;
if( !$this->isValid() ) {
@@ -259,6 +297,11 @@ class StockAssembly extends StockBase {
return \count ( $this->mItems ) > 0;
}
+ /**
+ * Return all component rows for this assembly without pagination.
+ *
+ * @return array|null content_id-keyed rows, or null if not valid.
+ */
public function getComponentList() {
global $gLibertySystem, $gBitSystem, $gBitUser;
$ret = null;
@@ -305,6 +348,12 @@ class StockAssembly extends StockBase {
return $ret;
}
+ /**
+ * Return the page number (floor of item_position) for a given item.
+ *
+ * @param int $pItemContentId
+ * @return int|null Page number, or null if item not in this assembly.
+ */
public function getItemPage( $pItemContentId ) {
$ret = null;
if( empty( $this->mPaginationLookup ) ) {
@@ -326,6 +375,7 @@ class StockAssembly extends StockBase {
return $ret;
}
+ /** @return int Number of items in stock_assembly_map for this assembly. */
public function getComponentCount() {
$ret = 0;
@@ -345,6 +395,12 @@ class StockAssembly extends StockBase {
return $ret;
}
+ /**
+ * Validate $pParamHash before storing — requires a non-empty title.
+ *
+ * @param array $pParamHash Modified in place to set content_type_guid.
+ * @return bool
+ */
public function verifyGalleryData(&$pParamHash) {
if( empty($pParamHash['title']) ) {
$this->mErrors[] = "You must specify a title for this assembly";
@@ -447,6 +503,12 @@ class StockAssembly extends StockBase {
return false;
}
+ /**
+ * Persist assembly data inside a transaction via LibertyContent::store().
+ *
+ * @param array $pParamHash Data to persist; modified in place.
+ * @return bool
+ */
public function store( array &$pParamHash ): bool {
if( $this->verifyGalleryData( $pParamHash ) ) {
$this->StartTrans();
@@ -462,6 +524,12 @@ class StockAssembly extends StockBase {
return count($this->mErrors) == 0;
}
+ /**
+ * Return all stock_assembly_map rows for this assembly with lc.title included.
+ *
+ * @param string $pSortMode 'item_position_asc' (default), 'item_position_desc', 'title_asc', 'title_desc'.
+ * @return array item_content_id-keyed rows.
+ */
public function getComponentMapList( string $pSortMode = 'item_position_asc' ): array {
$ret = [];
if( $this->verifyId( $this->mContentId ) ) {
@@ -487,6 +555,12 @@ class StockAssembly extends StockBase {
return $ret;
}
+ /**
+ * Remove an item from this assembly's stock_assembly_map.
+ *
+ * @param int $pContentId item_content_id to remove.
+ * @return bool TRUE on success, FALSE if not valid or item id invalid.
+ */
public function removeItem( $pContentId ) {
$ret = false;
if( $this->isValid() && @$this->verifyId( $pContentId ) ) {
@@ -499,10 +573,15 @@ class StockAssembly extends StockBase {
}
/**
- * Adds a new item (image or gallery) to this gallery. We check to make sure we are not a member
- * of this gallery and this gallery is not a member of the new item to avoid infinite recursion scenarios
- * @return bool wheter or not the item was added
- */
+ * Add an item to this assembly, guarding against circular membership.
+ *
+ * Checks that neither this assembly is already in the item nor the item is
+ * already in this assembly, to prevent infinite recursion in tree queries.
+ *
+ * @param int $pContentId Item content_id to add.
+ * @param int|null $pPosition item_position value; null lets the DB default.
+ * @return bool TRUE if added, FALSE if the guard check failed.
+ */
public function addItem( $pContentId, $pPosition=null ) {
global $gBitSystem;
$ret = false;
@@ -516,6 +595,12 @@ class StockAssembly extends StockBase {
return $ret;
}
+ /**
+ * Delete this assembly and recursively expunge any child assemblies not
+ * shared with other parents. Removes all stock_assembly_map rows for this assembly.
+ *
+ * @return bool Always TRUE (errors recorded in $this->mErrors).
+ */
public function expunge(): bool {
if( $this->isValid() ) {
$this->StartTrans();
@@ -554,14 +639,14 @@ class StockAssembly extends StockBase {
/**
- * Returns the layout of the gallery accounting for various defaults
- * @return string the layout string preference
- */
+ * @return string Pagination layout preference (one of STOCK_PAGINATION_* constants).
+ */
public function getLayout() {
global $gBitSystem;
return $this->getPreference( 'assembly_pagination', $gBitSystem->getConfig( 'default_assembly_pagination', STOCK_PAGINATION_FIXED_GRID ) );
}
+ /** @return array Map of STOCK_PAGINATION_* constant → human-readable label. */
public static function getAllLayouts() {
return [
STOCK_PAGINATION_FIXED_GRID => 'Fixed Grid',
@@ -571,27 +656,17 @@ class StockAssembly extends StockBase {
];
}
- /**
- * Returns include file that will setup the object for rendering
- * @return string the fully specified path to file to be included
- */
+ /** @return string Absolute path to the display_stock_assembly_inc.php setup file. */
public function getRenderFile() {
return STOCK_PKG_INCLUDE_PATH.'display_stock_assembly_inc.php';
}
- /**
- * Returns template file used for display
- * @return string the fully specified path to file to be included
- */
+ /** @return string Smarty bitpackage: path to the assembly view template. */
public function getRenderTemplate() {
return 'bitpackage:stock/view_assembly.tpl';
}
- /**
- * Function that returns link to display a piece of content
- * @param array pAssemblyId id of gallery to link
- * @return string the url to display the gallery.
- */
+ /** @return string URL to edit_assembly.php for this assembly. */
public function getEditUrl( $pContentId = null, $pMixed = null ): string {
if( $this->verifyId( $this->mContentId ) ) {
return STOCK_PKG_URL.'edit_assembly.php?content_id='.$this->mContentId;
@@ -599,6 +674,10 @@ class StockAssembly extends StockBase {
return STOCK_PKG_URL.'edit_assembly.php';
}
+ /**
+ * @param array $pParamHash Must contain 'content_id'.
+ * @return string URL to view_assembly.php (or pretty URL).
+ */
public static function getDisplayUrlFromHash( &$pParamHash ) {
$ret = '';
global $gBitSystem;
@@ -611,6 +690,16 @@ class StockAssembly extends StockBase {
return $ret;
}
+ /**
+ * Return the full assembly hierarchy as a nested tree.
+ *
+ * On Firebird uses a recursive CTE; falls back to a flat list with
+ * splitConnectByTree() for other databases. Optionally marks which assemblies
+ * contain a given item via $pListHash['contain_item'].
+ *
+ * @param array $pListHash Filter hash; 'contain_item' marks in-gallery status.
+ * @return array Nested array: each node has 'content' and 'children'.
+ */
public function getTree( $pListHash ) {
global $gBitDb;
@@ -725,6 +814,7 @@ class StockAssembly extends StockBase {
return $ret;
}
+ /** Recursively sort a tree array by title using getTreeSortCmp(). */
public function getTreeSort( &$pTree ) {
if( $pTree ) {
foreach( array_keys( $pTree ) as $k ) {
@@ -863,6 +953,16 @@ class StockAssembly extends StockBase {
return $ret;
}
+ /**
+ * Return a paged, keyed list of assemblies.
+ *
+ * Recognised filter keys: root_only, contain_item, user_id, find, parent_content_id,
+ * show_public, show_empty, sort_mode, no_thumbnails, thumbnail_size.
+ * Sets $pListHash['cant'] on return.
+ *
+ * @param array $pListHash Filter and pagination params; modified in place.
+ * @return array content_id-keyed result rows.
+ */
public function getList( &$pListHash ) {
global $gBitUser,$gBitSystem, $gBitDbType;
@@ -996,10 +1096,12 @@ class StockAssembly extends StockBase {
return $data;
}
+ /** @return string Font-Awesome icon HTML for use in service menus. */
public static function getServiceIcon() {
return '<i class="fa fal fa-camera"></i>';
}
+ /** @return string Always 'stock'. */
public static function getServiceKey() {
return 'stock';
}
diff --git a/includes/classes/StockBase.php b/includes/classes/StockBase.php
index 62d5294..c31e2e7 100755
--- a/includes/classes/StockBase.php
+++ b/includes/classes/StockBase.php
@@ -1,11 +1,12 @@
<?php
/**
+ * Abstract base for StockAssembly and StockComponent.
+ *
+ * Provides shared assembly-hierarchy navigation (parent lookup, breadcrumb building),
+ * assembly membership queries, and supplier xref enrichment with contact titles.
+ *
* @package stock
*/
-
-/**
- * required setup
- */
namespace Bitweaver\Stock;
use Bitweaver\Liberty\LibertyContent;
@@ -13,15 +14,13 @@ use Bitweaver\Liberty\LibertyContent;
defined( 'STOCKASSEMBLY_CONTENT_TYPE_GUID' ) || define( 'STOCKASSEMBLY_CONTENT_TYPE_GUID', 'stockassembly' );
defined( 'STOCKCOMPONENT_CONTENT_TYPE_GUID' ) || define( 'STOCKCOMPONENT_CONTENT_TYPE_GUID', 'stockcomponent' );
-/**
- * @package stock
- */
#[\AllowDynamicProperties]
abstract class StockBase extends LibertyContent
{
- // Path of gallery images to get breadcrumbs
+ /** @var string Slash-separated content_id ancestry path; set by setGalleryPath(). */
public $mAssemblyPath;
+ /** @return string Service key used by LibertyContent service hooks (always 'stock'). */
abstract public static function getServiceKey();
public function __sleep() {
@@ -33,6 +32,10 @@ abstract class StockBase extends LibertyContent
parent::__construct();
}
+ /**
+ * Load xref groups then enrich the 'supplier' group by resolving each contact
+ * content_id (xref column) to the contact's lc.title.
+ */
public function loadXrefInfo(): void {
parent::loadXrefInfo();
if( empty( $this->mXrefInfo ) ) return;
@@ -52,6 +55,14 @@ abstract class StockBase extends LibertyContent
unset( $row );
}
+ /**
+ * Enrich a single xref display row with resolved title data.
+ *
+ * Base implementation handles the 'supplier' group (contact title).
+ * Subclasses override to add component title / pack size for BOM rows.
+ *
+ * @param array $pXrefInfo Xref display row; modified in place.
+ */
public function enrichXrefDisplay( array &$pXrefInfo ): void {
if( !empty( $pXrefInfo['xref'] ) && ( $pXrefInfo['x_group'] ?? '' ) === 'supplier' ) {
if( $contact = $this->mDb->getRow(
@@ -63,7 +74,12 @@ abstract class StockBase extends LibertyContent
}
}
- // Gets a list of galleries which this item is attached to
+ /**
+ * Return the parent assemblies this item belongs to, with prev/next sibling pointers.
+ *
+ * @param int|null $pContentId Defaults to $this->mContentId.
+ * @return array|null Assoc array keyed by content_id, or null if none.
+ */
public function getParentAssemblies( $pContentId=null ) {
if( !$this->verifyId( $pContentId ) ) {
$pContentId = $this->mContentId;
@@ -106,20 +122,30 @@ abstract class StockBase extends LibertyContent
return $ret;
}
+ /** Populate $this->mInfo['parent_galleries'] via getParentAssemblies(). */
public function loadParentAssemblies() {
if( $this->isValid() ) {
$this->mInfo['parent_galleries'] = $this->getParentAssemblies();
}
}
+ /** @param string $pPath Slash-separated ancestry path; trailing slash is stripped. */
public function setGalleryPath( $pPath ) {
$this->setField( 'gallery_path', rtrim( $pPath, '/' ) );
}
+ /** @return int|null content_id of the thumbnail item, or null if none loaded. */
public function getThumbnailContentId() {
// PURE VIRTUAL
}
+ /**
+ * Load thumbnail image data into $this->mInfo['preview_content'].
+ * Base implementation is a no-op; subclasses override.
+ *
+ * @param string $pSize Thumbnail size hint ('small', 'medium', etc.).
+ * @param int|null $pContentId Override which content to use as thumbnail source.
+ */
public function loadThumbnail( $pSize='small', $pContentId=null ) {
// Default does nothing
}
@@ -153,6 +179,16 @@ not ready for primetime
}
*/
+ /**
+ * Return an ordered map of content_id → title for all ancestor assemblies.
+ *
+ * Walks stock_assembly_map upward (max 10 hops), caches the path via
+ * setGalleryPath(), then falls back to loading from the cached path on
+ * subsequent calls.
+ *
+ * @param bool $pIncludeSelf Append this assembly's own entry when TRUE.
+ * @return array content_id → title, ordered root → parent.
+ */
public function getBreadcrumbLinks( $pIncludeSelf = false ) {
$ret = [];
if( !$this->getField( 'gallery_path' ) ) {
@@ -206,6 +242,14 @@ not ready for primetime
return $ret;
}
+ /**
+ * Sync this item's assembly membership to match $pAssemblyArray.
+ *
+ * Adds to assemblies not yet in the map; removes from assemblies no longer listed.
+ * Checks p_stock_upload permission before adding to each assembly.
+ *
+ * @param int[] $pAssemblyArray List of assembly content_ids to be a member of.
+ */
public function addToAssemblies( $pAssemblyArray ) {
global $gBitSystem;
if( $this->isValid() ) {
@@ -256,6 +300,7 @@ not ready for primetime
}
}
+ /** @return bool TRUE when the 'is_public' preference is set to 'y'. */
public function isPublic() {
if( $this->isValid() ) {
return $this->getPreference( 'is_public' ) == 'y';
@@ -263,6 +308,13 @@ not ready for primetime
return false;
}
+ /**
+ * Check whether an item is a member (direct or recursive) of an assembly.
+ *
+ * @param int $pAssemblyContentId Assembly to test membership in.
+ * @param int|null $pItemContentId Item to test; defaults to $this->mContentId.
+ * @return bool
+ */
public function isInAssembly( $pAssemblyContentId, $pItemContentId = null) {
if( !$this->verifyId( $pItemContentId ) ) {
$pItemContentId = $this->mContentId;
diff --git a/includes/classes/StockComponent.php b/includes/classes/StockComponent.php
index 929920e..6a67318 100755
--- a/includes/classes/StockComponent.php
+++ b/includes/classes/StockComponent.php
@@ -1,22 +1,25 @@
<?php
/**
+ * A single stock component — a physical part, material, or consumable.
+ *
+ * Stored as a pure liberty_content record (content_type_guid='stockcomponent').
+ * Components are linked into assemblies via stock_assembly_map and carry quantity
+ * type xrefs (SGL/PCK/SHT/VOL) used by BOM and movement calculations.
+ *
* @package stock
*/
-
-/**
- * required setup
- */
namespace Bitweaver\Stock;
use Bitweaver\Liberty\LibertyContent;
use Bitweaver\BitBase;
-/**
- * @package stock
- */
class StockComponent extends StockBase {
protected $mXrefTypeKey = 'stockcomponent_types';
+ /**
+ * @param int|null $pComponentId Legacy param — use $pContentId instead.
+ * @param int|null $pContentId liberty_content.content_id to load.
+ */
public function __construct($pComponentId = null, $pContentId = null) {
parent::__construct();
$this->mContentTypeGuid = STOCKCOMPONENT_CONTENT_TYPE_GUID;
@@ -40,6 +43,10 @@ class StockComponent extends StockBase {
$this->mAdminContentPerm = 'p_stock_admin';
}
+ /**
+ * @param array $pLookupHash Must contain 'content_id'.
+ * @return static|null Loaded object, or null if not found.
+ */
public static function lookup( $pLookupHash ) {
$ret = null;
$lookupContentId = null;
@@ -52,6 +59,11 @@ class StockComponent extends StockBase {
return $ret;
}
+ /**
+ * Load component data into $this->mInfo from liberty_content.
+ *
+ * @return int|null Row count (> 0) on success, or null if mContentId is not set.
+ */
public function load() {
if( $this->isValid() ) {
$selectSql = $joinSql = $whereSql = '';
@@ -81,6 +93,12 @@ class StockComponent extends StockBase {
return null;
}
+ /**
+ * Validate $pParamHash before storing — requires a non-empty title.
+ *
+ * @param array $pParamHash Data to validate; modified in place.
+ * @return bool
+ */
protected function verifyComponentData( array &$pParamHash ): bool {
$pParamHash['content_type_guid'] = STOCKCOMPONENT_CONTENT_TYPE_GUID;
if( $this->isValid() ) {
@@ -92,6 +110,12 @@ class StockComponent extends StockBase {
return count( $this->mErrors ) == 0;
}
+ /**
+ * Persist component data inside a transaction via LibertyContent::store().
+ *
+ * @param array $pParamHash Data to persist; modified in place.
+ * @return bool
+ */
public function store( array &$pParamHash ): bool {
if( $this->verifyComponentData( $pParamHash ) ) {
$this->StartTrans();
@@ -106,6 +130,11 @@ class StockComponent extends StockBase {
return count( $this->mErrors ) == 0;
}
+ /**
+ * Delete the component, removing it from all assemblies first.
+ *
+ * @return bool Always TRUE (errors are recorded in $this->mErrors).
+ */
public function expunge(): bool {
if( $this->isValid() ) {
$this->StartTrans();
@@ -120,10 +149,20 @@ class StockComponent extends StockBase {
return true;
}
+ /** @return bool TRUE when mContentId is a valid positive integer. */
public function isValid() {
return @$this->verifyId( $this->mContentId );
}
+ /**
+ * Return a paged, keyed list of components.
+ *
+ * Recognised filter keys: user_id, assembly_content_id, search, sort_mode.
+ * Sets $pListHash['cant'] on return.
+ *
+ * @param array $pListHash Filter and pagination params; modified in place.
+ * @return array content_id-keyed result rows.
+ */
public function getList( &$pListHash ) {
global $gBitUser, $gBitSystem;
@@ -184,10 +223,15 @@ class StockComponent extends StockBase {
return $ret;
}
+ /** @return string Smarty bitpackage: path to the component view template. */
public function getRenderTemplate() {
return 'bitpackage:stock/view_component.tpl';
}
+ /**
+ * @param array $pParamHash Must contain 'content_id'.
+ * @return string URL to view_component.php (or pretty URL).
+ */
public static function getDisplayUrlFromHash( &$pParamHash ) {
global $gBitSystem;
$ret = '';
@@ -200,10 +244,12 @@ class StockComponent extends StockBase {
return $ret;
}
+ /** @return string Display URL for this component. */
public function getDisplayUrl() {
return static::getDisplayUrlFromHash( $this->mInfo );
}
+ /** @return string URL to edit_component.php for this component. */
public function getEditUrl( $pContentId = null, $pMixed = null ): string {
if( $this->verifyId( $this->mContentId ) ) {
return STOCK_PKG_URL.'edit_component.php?content_id='.$this->mContentId;
@@ -211,6 +257,12 @@ class StockComponent extends StockBase {
return STOCK_PKG_URL.'edit_component.php';
}
+ /**
+ * @param array $pParamHash Must contain 'content_id'; used to build the URL.
+ * @param string $pTitle Link text; falls back to getTitleFromHash() if empty.
+ * @param null $pAnchor Unused.
+ * @return string HTML anchor, or plain-text title if stock is inactive.
+ */
public static function getDisplayLinkFromHash( &$pParamHash, $pTitle='', $pAnchor=null ) {
global $gBitSystem;
$pTitle = trim( $pTitle );
@@ -223,6 +275,11 @@ class StockComponent extends StockBase {
return htmlspecialchars( $pTitle );
}
+ /**
+ * @param array $pHash mInfo-style hash; must contain 'content_type_guid'.
+ * @param bool $pDefault When TRUE, falls back to content type name + content_id.
+ * @return string
+ */
public static function getTitleFromHash( &$pHash, $pDefault=true ) {
if( !empty( $pHash ) ) {
$ret = trim( parent::getTitleFromHash( $pHash, $pDefault ) );
@@ -238,10 +295,12 @@ class StockComponent extends StockBase {
return 'empty_component';
}
+ /** @return string|null Component title, or null if not yet loaded. */
public function getTitle() {
return $this->isValid() ? static::getTitleFromHash( $this->mInfo ) : null;
}
+ /** @return bool TRUE if the first parent assembly allows comments. */
public function isCommentable() {
global $gGallery;
if( is_object( $gGallery ) ) {
@@ -258,6 +317,7 @@ class StockComponent extends StockBase {
return $ret;
}
+ /** @return string Always 'stock'. */
public static function getServiceKey() {
return 'stock';
}
diff --git a/includes/classes/StockMovement.php b/includes/classes/StockMovement.php
index 48d0bd5..543c588 100644
--- a/includes/classes/StockMovement.php
+++ b/includes/classes/StockMovement.php
@@ -1,8 +1,17 @@
<?php
/**
+ * A stock movement — an inbound order/transfer or outbound requisition.
+ *
+ * Stored as a pure liberty_content record (content_type_guid='stockmovement').
+ * Direction is inferred from the reference xref item: REQN = outbound,
+ * TRANS/ORDER = inbound. Status (open vs received) is stored in lc.event_time:
+ * 0 = open, positive Unix timestamp = received.
+ *
+ * Component lines live in liberty_xref (x_group='quantity', items SGL/PCK/SHT/VOL).
+ * The reference xref (x_group='reference') carries the from/ref/date/contact data.
+ *
* @package stock
*/
-
namespace Bitweaver\Stock;
use Bitweaver\BitBase;
@@ -10,13 +19,14 @@ use Bitweaver\Liberty\LibertyContent;
defined( 'STOCKMOVEMENT_CONTENT_TYPE_GUID' ) || define( 'STOCKMOVEMENT_CONTENT_TYPE_GUID', 'stockmovement' );
-/**
- * @package stock
- */
#[\AllowDynamicProperties]
class StockMovement extends LibertyContent {
protected $mXrefTypeKey = 'stockmovement_types';
+ /**
+ * @param int|null $pMovementId Legacy param — use $pContentId instead.
+ * @param int|null $pContentId liberty_content.content_id to load.
+ */
public function __construct( $pMovementId = null, $pContentId = null ) {
parent::__construct();
$this->mContentTypeGuid = STOCKMOVEMENT_CONTENT_TYPE_GUID;
@@ -40,10 +50,16 @@ class StockMovement extends LibertyContent {
$this->mAdminContentPerm = 'p_stock_admin';
}
+ /** @return bool TRUE when mContentId is a valid positive integer. */
public function isValid(): bool {
return (bool)$this->verifyId( $this->mContentId );
}
+ /**
+ * @param array $pLookupHash Must contain 'content_id'.
+ * @param bool $pLoadFromCache Whether to use LibertyContent's object cache.
+ * @return static|null Loaded object, or null if not found.
+ */
public static function lookup( $pLookupHash, $pLoadFromCache = true ) {
$lookupContentId = null;
if( !empty( $pLookupHash['content_id'] ) && is_numeric( $pLookupHash['content_id'] ) ) {
@@ -55,6 +71,14 @@ class StockMovement extends LibertyContent {
return null;
}
+ /**
+ * Load movement record into $this->mInfo, including reference xref scalars
+ * (ref_type, ref_key, ref_start_date, ref_contact_id, ref_contact_name, ref_from_data).
+ *
+ * @param int|null $pContentId Override mContentId for this load.
+ * @param array|null $pPluginParams Passed through to getServicesSql().
+ * @return bool TRUE on success, FALSE if not found or mContentId invalid.
+ */
public function load( $pContentId = null, $pPluginParams = null ) {
if( $pContentId ) $this->mContentId = (int)$pContentId;
if( !$this->verifyId( $this->mContentId ) ) return false;
@@ -105,6 +129,13 @@ class StockMovement extends LibertyContent {
return !empty( $this->mInfo );
}
+ /**
+ * Persist movement data inside a transaction via LibertyContent::store().
+ * Requires a non-empty title (the movement reference).
+ *
+ * @param array $pParamHash Data to persist; modified in place.
+ * @return bool
+ */
public function store( array &$pParamHash ): bool {
$pParamHash['content_type_guid'] = STOCKMOVEMENT_CONTENT_TYPE_GUID;
if( empty( $pParamHash['title'] ) ) {
@@ -123,6 +154,11 @@ class StockMovement extends LibertyContent {
return count( $this->mErrors ) == 0;
}
+ /**
+ * Delete this movement. LibertyContent::expunge() handles xref cleanup.
+ *
+ * @return bool Always TRUE (errors recorded in $this->mErrors).
+ */
public function expunge(): bool {
if( $this->isValid() ) {
$this->StartTrans();
@@ -136,6 +172,10 @@ class StockMovement extends LibertyContent {
return true;
}
+ /**
+ * Load xref groups then enrich the 'quantity' group — resolves each component
+ * content_id to xref_title and xref_data (component description).
+ */
public function loadXrefInfo(): void {
parent::loadXrefInfo();
if( empty( $this->mXrefInfo ) ) return;
@@ -156,7 +196,11 @@ class StockMovement extends LibertyContent {
unset( $row );
}
- // Direction inferred from reference xref: REQN = out, TRANS/ORDER = in
+ /**
+ * Infer movement direction from the reference xref item code.
+ *
+ * @return string 'O' (outbound/REQN) or 'I' (inbound/TRANS or ORDER).
+ */
public function getDirection(): string {
$refType = $this->mInfo['ref_type'] ?? null;
if( $refType === 'REQN' ) return 'O';
@@ -164,11 +208,16 @@ class StockMovement extends LibertyContent {
return 'O';
}
- // Movement is received/fulfilled when lc.event_time is set
+ /** @return bool TRUE when lc.event_time is non-zero (movement has been received). */
public function isReceived(): bool {
return !empty( $this->mInfo['event_time'] );
}
+ /**
+ * Set lc.event_time to the current Unix timestamp, marking this movement as received.
+ *
+ * @return bool FALSE if the movement is not valid; TRUE otherwise.
+ */
public function markReceived(): bool {
if( !$this->isValid() ) return false;
$now = time();
@@ -180,8 +229,17 @@ class StockMovement extends LibertyContent {
return true;
}
- // Append movement quantity xrefs from an assembly BOM (liberty_xref), scaled by $pKitCount.
- // Appends to any existing items — safe to call multiple times for multi-assembly requisitions.
+ /**
+ * Append quantity xrefs from an assembly BOM into this movement, scaled by kit count.
+ *
+ * Reads SGL/PCK/SHT/VOL xrefs from the assembly and inserts them as new movement
+ * quantity lines, starting xorder after any existing items. Safe to call multiple
+ * times (e.g. for multi-assembly requisitions) — lines are only appended.
+ *
+ * @param int $pAssemblyContentId Source assembly content_id.
+ * @param float $pKitCount Multiplier applied to each BOM quantity.
+ * @return bool FALSE if movement or assembly id is invalid.
+ */
public function explodeFromAssembly( int $pAssemblyContentId, float $pKitCount = 1 ): bool {
if( !$this->isValid() || !$this->verifyId( $pAssemblyContentId ) ) {
return false;
@@ -219,6 +277,17 @@ class StockMovement extends LibertyContent {
return true;
}
+ /**
+ * Return a paged, keyed list of movements.
+ *
+ * Recognised filter keys: ref_type (REQN/TRANS/ORDER), assembly_content_id,
+ * component_content_id, user_id, find, sort_mode.
+ * When component_content_id is set, cmp_qty_type and cmp_qty are included per row.
+ * Sets $pListHash['cant'] on return.
+ *
+ * @param array $pListHash Filter and pagination params; modified in place.
+ * @return array content_id-keyed result rows.
+ */
public function getList( array &$pListHash ): array {
global $gBitUser;
LibertyContent::prepGetList( $pListHash );
@@ -309,6 +378,10 @@ class StockMovement extends LibertyContent {
return $ret;
}
+ /**
+ * @param array $pParamHash Must contain 'content_id'.
+ * @return string URL to view_movement.php, or '' if id invalid.
+ */
public static function getDisplayUrlFromHash( &$pParamHash ) {
if( BitBase::verifyId( $pParamHash['content_id'] ?? 0 ) ) {
return STOCK_PKG_URL.'view_movement.php?content_id='.$pParamHash['content_id'];
@@ -316,10 +389,12 @@ class StockMovement extends LibertyContent {
return '';
}
+ /** @return string Display URL for this movement. */
public function getDisplayUrl(): string {
return static::getDisplayUrlFromHash( $this->mInfo );
}
+ /** @return string URL to edit_movement.php for this movement. */
public function getEditUrl( $pContentId = null, $pMixed = null ): string {
if( $this->verifyId( $this->mContentId ) ) {
return STOCK_PKG_URL.'edit_movement.php?content_id='.$this->mContentId;
@@ -327,10 +402,23 @@ class StockMovement extends LibertyContent {
return STOCK_PKG_URL.'edit_movement.php';
}
+ /** @return string Always 'stock'. */
public static function getServiceKey(): string {
return 'stock';
}
+ /**
+ * Process an uploaded CSV file into this movement's reference and quantity xrefs.
+ *
+ * Row 1: from (SCREF), ref, order_date (dd/mm/yy), received_date (dd/mm/yy optional).
+ * Rows 2+: component_title, quantity, [qty_type override (SGL/PCK/SHT/VOL)].
+ *
+ * The reference row updates or creates the REQN/TRANS/ORDER xref. Order date sets
+ * xref.start_date; received date sets lc.event_time. Unknown components are skipped.
+ *
+ * @param string[] $pQtyTypes Allowed qty type codes (e.g. ['SGL','PCK','SHT','VOL']).
+ * @return array{loaded:int, skipped:int, errors:string[]}
+ */
public function importCsv( array $pQtyTypes ): array {
$result = [ 'loaded' => 0, 'skipped' => 0, 'errors' => [] ];
$file = $_FILES['csv_file'] ?? null;