diff options
| author | Lester Caine <lester@lsces.co.uk> | 2026-06-06 18:34:11 +0100 |
|---|---|---|
| committer | Lester Caine <lester@lsces.co.uk> | 2026-06-06 18:34:11 +0100 |
| commit | d75d099650de58ede08583e3dac27986544f799f (patch) | |
| tree | 8d1943e2673d745d41fa97534f00113c3125a5ac /includes | |
| parent | 20d282c1bbb6385150486a6c88ad65ce97e746d7 (diff) | |
| download | stock-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>
Diffstat (limited to 'includes')
| -rwxr-xr-x | includes/classes/StockAssembly.php | 160 | ||||
| -rwxr-xr-x | includes/classes/StockBase.php | 70 | ||||
| -rwxr-xr-x | includes/classes/StockComponent.php | 74 | ||||
| -rw-r--r-- | includes/classes/StockMovement.php | 104 |
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; |
