summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLester Caine <lester@lsces.co.uk>2026-06-07 16:51:15 +0100
committerLester Caine <lester@lsces.co.uk>2026-06-07 16:51:15 +0100
commit9f5cb5855df61554e38a6da5d036bd78d421a2a9 (patch)
tree403cdd171269e8037cb8113f0735665ec916215c
parentd75d099650de58ede08583e3dac27986544f799f (diff)
downloadstock-9f5cb5855df61554e38a6da5d036bd78d421a2a9.tar.gz
stock-9f5cb5855df61554e38a6da5d036bd78d421a2a9.tar.bz2
stock-9f5cb5855df61554e38a6da5d036bd78d421a2a9.zip
Stock package: assembly/component model, package-level xref, imports, print BOM
- LibertyXrefInfo/LibertyXrefGroup: package-level guid support ('stock') so stgrp, supplier, kitlocker groups are shared across SA and SC - schema_inc.php: stgrp group with KLG01-28, supplier consolidated to 'stock' level, kitlocker moved to 'stock', duplicate #PN/#PR removed - StockAssembly::getList(): non_root_only and show_empty fixes for standalone assemblies (no gallery hierarchy) - StockBase::loadXrefInfo(): instantiates with 'stock' package guid - list_assemblies.php: gallery_id param, show_empty default for flat list - edit_assembly.php: STOCKCOMPONENT_CONTENT_TYPE_GUID → string literal - ImportSimpleComponent: SCREF lookup fixed (xkey not xkey_ext), #SUP row now carries PN/price/URL directly; #PN #PR #URL separate rows removed - ImportKitlockerAssemblies, load_kitlocker_assemblies, load_component_list: new importers for KitlockerAssemblies.csv (A/C split) and Component List.csv - print_bom.php + print_bom.tpl: read-only printable BOM (p_stock_view only) with auto window.print() on load - component_order.tpl: print icon (floaticon), position number for print, hidden-print on form controls - list_stock.tpl: print icon - view_assembly.tpl: print icon → print_bom.php; permission gates tightened - ipackage duplicate attributes removed from templates Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
-rwxr-xr-xadmin/schema_inc.php106
-rwxr-xr-xedit_assembly.php2
-rw-r--r--import/ImportKitlockerAssemblies.php120
-rw-r--r--import/ImportSimpleComponent.php41
-rw-r--r--import/load_component_list.php68
-rw-r--r--import/load_kitlocker_assemblies.php73
-rwxr-xr-xincludes/classes/StockAssembly.php9
-rwxr-xr-xincludes/classes/StockBase.php3
-rwxr-xr-xlist_assemblies.php8
-rw-r--r--print_bom.php27
-rwxr-xr-xtemplates/assembly_icons_inc.tpl2
-rwxr-xr-xtemplates/component_order.tpl11
-rwxr-xr-xtemplates/list_assemblies2.tpl4
-rw-r--r--templates/list_assemblies_simple.tpl8
-rw-r--r--templates/list_components.tpl2
-rw-r--r--templates/list_stock.tpl3
-rw-r--r--templates/print_bom.tpl58
-rwxr-xr-xtemplates/view_assembly.tpl19
18 files changed, 458 insertions, 106 deletions
diff --git a/admin/schema_inc.php b/admin/schema_inc.php
index 508a707..8db4e41 100755
--- a/admin/schema_inc.php
+++ b/admin/schema_inc.php
@@ -90,54 +90,74 @@ $X = BIT_DB_PREFIX;
$xrefTypes = [];
$xrefItems = [];
-// ── stockcomponent xref groups (supplier=1, quantity=2, values=3) ─────────────
-$xrefTypes[] = "INSERT INTO `{$X}liberty_xref_group` (`x_group`,`content_type_guid`,`title`,`sort_order`,`role_id`,`type_href`,`template`) VALUES ('supplier','stockcomponent','Supplier', 1,3,'','sup')";
-$xrefTypes[] = "INSERT INTO `{$X}liberty_xref_group` (`x_group`,`content_type_guid`,`title`,`sort_order`,`role_id`,`type_href`,`template`) VALUES ('quantity','stockcomponent','Quantity', 2,3,'','')";
-$xrefTypes[] = "INSERT INTO `{$X}liberty_xref_group` (`x_group`,`content_type_guid`,`title`,`sort_order`,`role_id`,`type_href`,`template`) VALUES ('values', 'stockcomponent','Values', 3,3,'','')";
+// ── 'stock' package-level groups — shared across stockassembly and stockcomponent ──
+// sort_order=1: stgrp (group tags, multi-valued KLGnn items)
+// sort_order=3: supplier (replaces per-type duplicates)
+$xrefTypes[] = "INSERT INTO `{$X}liberty_xref_group` (`x_group`,`content_type_guid`,`title`,`sort_order`,`role_id`,`type_href`,`template`) VALUES ('stgrp', 'stock','Stock Groups', 1,3,'','')";
+$xrefTypes[] = "INSERT INTO `{$X}liberty_xref_group` (`x_group`,`content_type_guid`,`title`,`sort_order`,`role_id`,`type_href`,`template`) VALUES ('supplier', 'stock','Supplier', 3,3,'','sup')";
-// ── stockassembly xref groups (kitlocker=1, supplier=2, quantity/BOM=3) ─────────
-$xrefTypes[] = "INSERT INTO `{$X}liberty_xref_group` (`x_group`,`content_type_guid`,`title`,`sort_order`,`role_id`,`type_href`,`template`) VALUES ('kitlocker','stockassembly','KitLocker Details',1,3,'','')";
-$xrefTypes[] = "INSERT INTO `{$X}liberty_xref_group` (`x_group`,`content_type_guid`,`title`,`sort_order`,`role_id`,`type_href`,`template`) VALUES ('supplier', 'stockassembly','Supplier', 2,3,'','')";
-$xrefTypes[] = "INSERT INTO `{$X}liberty_xref_group` (`x_group`,`content_type_guid`,`title`,`sort_order`,`role_id`,`type_href`,`template`) VALUES ('quantity', 'stockassembly','Bill of Material', 3,3,'','bom')";
+// KLG01–KLG28 — Kitlocker group tags (multiple=0 per item; multiple items per record = multi-group tagging)
+$klGroups = [
+ 'KLG01'=>'General Kits', 'KLG02'=>'DCC Kits', 'KLG03'=>'CBUS Explorer',
+ 'KLG04'=>'Tools', 'KLG05'=>'Miscellaneous', 'KLG06'=>'ICs',
+ 'KLG07'=>'General Kit PCBs', 'KLG08'=>'Clearance', 'KLG09'=>'Power Supplies',
+ 'KLG10'=>'CBUS PCBs', 'KLG11'=>'SMD Starter Kits', 'KLG12'=>'Servo Mounts',
+ 'KLG13'=>'Pocket Money Projects','KLG14'=>'DCC PCBs', 'KLG15'=>'PICs',
+ 'KLG16'=>'CBUS Advanced', 'KLG17'=>'Test Modules', 'KLG18'=>'Test Module PCBs',
+ 'KLG19'=>'PMK PCBs', 'KLG20'=>"CBUS Beginner's Packs",'KLG21'=>'Pocket Money Kits',
+ 'KLG22'=>'EzyBus', 'KLG23'=>'DCC Traction', 'KLG24'=>'Bitz-in-Bag kits',
+ 'KLG25'=>'Connector Boards', 'KLG26'=>'CBUS Basic', 'KLG27'=>'ATC',
+ 'KLG28'=>'POSTAGE',
+];
+foreach( $klGroups as $item => $title ) {
+ $safeTitle = str_replace( "'", "''", $title );
+ $xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('{$item}','stock','stgrp','{$safeTitle}',0,3,'','',NULL)";
+}
-// ── xref items shared across both content types ───────────────────────────────
-foreach( [ 'stockcomponent', 'stockassembly' ] as $guid ) {
- // Supplier sources — multi=1 (multiple suppliers per item)
- $xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('#SUP','{$guid}','supplier','Supplier', 1,3,'../contact/?content_id=','sup', NULL)";
- $xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('#PN', '{$guid}','supplier','Part Number', 1,3,'', 'text',NULL)";
- $xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('#PR', '{$guid}','supplier','Price', 1,3,'', 'text',NULL)";
- $xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('#URL','{$guid}','supplier','Supplier URL', 1,3,'', 'text',NULL)";
+// Supplier items — defined once at package level (identical templates for SA and SC)
+$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('#SUP','stock','supplier','Supplier', 1,3,'../contact/?content_id=','sup', NULL)";
+$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('#URL','stock','supplier','Supplier URL', 1,3,'', 'text',NULL)";
- // Quantity sources — selectable types multi=0
- // stockassembly uses 'bom'/'bompck' templates (BOM grid); stockcomponent uses 'text'/'value'
- $sglTpl = ( $guid === 'stockassembly' ) ? 'bom' : 'text';
- $pckTpl = ( $guid === 'stockassembly' ) ? 'bompck' : 'value';
- $xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('SGL','{$guid}','quantity','Single unit', 0,3,'','{$sglTpl}',NULL)";
- $xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('PCK','{$guid}','quantity','Pack', 0,3,'','{$pckTpl}',NULL)";
- $xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('SHT','{$guid}','quantity','Sheet (H x W)', 0,3,'','{$sglTpl}',NULL)";
- $xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('VOL','{$guid}','quantity','Volume', 0,3,'','{$sglTpl}',NULL)";
-}
+// ── stockcomponent-specific groups (sort_order=2: quantity, sort_order=4: values) ─────
+$xrefTypes[] = "INSERT INTO `{$X}liberty_xref_group` (`x_group`,`content_type_guid`,`title`,`sort_order`,`role_id`,`type_href`,`template`) VALUES ('quantity','stockcomponent','Quantity',2,3,'','')";
+$xrefTypes[] = "INSERT INTO `{$X}liberty_xref_group` (`x_group`,`content_type_guid`,`title`,`sort_order`,`role_id`,`type_href`,`template`) VALUES ('values', 'stockcomponent','Values', 4,3,'','')";
-// Values sources — stockcomponent only; starter catalogue, all multi=0, add more via admin
-foreach( [ 'stockcomponent' ] as $guid ) {
- $xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('RES','{$guid}','values','Resistance', 0,3,'','text',NULL)";
- $xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('CAP','{$guid}','values','Capacitance', 0,3,'','text',NULL)";
- $xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('VLT','{$guid}','values','Voltage', 0,3,'','text',NULL)";
- $xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('TOL','{$guid}','values','Tolerance', 0,3,'','text',NULL)";
- $xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('PWR','{$guid}','values','Power', 0,3,'','text',NULL)";
- $xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('CUR','{$guid}','values','Current', 0,3,'','text',NULL)";
- $xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('FRQ','{$guid}','values','Frequency', 0,3,'','text',NULL)";
- $xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('IND','{$guid}','values','Inductance', 0,3,'','text',NULL)";
- $xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('TMP','{$guid}','values','Temperature', 0,3,'','text',NULL)";
- $xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('PKG','{$guid}','values','Package/Footprint', 0,3,'','text',NULL)";
-}
+// stockcomponent quantity types — different templates from SA (text/value not bom/bompck)
+$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('SGL','stockcomponent','quantity','Single unit', 0,3,'','text', NULL)";
+$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('PCK','stockcomponent','quantity','Pack', 0,3,'','value',NULL)";
+$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('SHT','stockcomponent','quantity','Sheet (H x W)', 0,3,'','text', NULL)";
+$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('VOL','stockcomponent','quantity','Volume', 0,3,'','text', NULL)";
+
+// ── 'stock' package-level kitlocker group (sort_order=2) — shared across SA and SC ───
+$xrefTypes[] = "INSERT INTO `{$X}liberty_xref_group` (`x_group`,`content_type_guid`,`title`,`sort_order`,`role_id`,`type_href`,`template`) VALUES ('kitlocker','stock','KitLocker Details',2,3,'','')";
+
+// ── stockassembly-specific group (sort_order=4: BOM) ────────────────────────────────
+$xrefTypes[] = "INSERT INTO `{$X}liberty_xref_group` (`x_group`,`content_type_guid`,`title`,`sort_order`,`role_id`,`type_href`,`template`) VALUES ('quantity','stockassembly','Bill of Material',4,3,'','bom')";
+
+// stockassembly quantity types — BOM grid templates
+$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('SGL','stockassembly','quantity','Single unit', 0,3,'','bom', NULL)";
+$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('PCK','stockassembly','quantity','Pack', 0,3,'','bompck',NULL)";
+$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('SHT','stockassembly','quantity','Sheet (H x W)', 0,3,'','bom', NULL)";
+$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('VOL','stockassembly','quantity','Volume', 0,3,'','bom', NULL)";
+
+// Values items — stockcomponent only; starter catalogue, all multi=0, add more via admin
+$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('RES','stockcomponent','values','Resistance', 0,3,'','text',NULL)";
+$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('CAP','stockcomponent','values','Capacitance', 0,3,'','text',NULL)";
+$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('VLT','stockcomponent','values','Voltage', 0,3,'','text',NULL)";
+$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('TOL','stockcomponent','values','Tolerance', 0,3,'','text',NULL)";
+$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('PWR','stockcomponent','values','Power', 0,3,'','text',NULL)";
+$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('CUR','stockcomponent','values','Current', 0,3,'','text',NULL)";
+$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('FRQ','stockcomponent','values','Frequency', 0,3,'','text',NULL)";
+$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('IND','stockcomponent','values','Inductance', 0,3,'','text',NULL)";
+$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('TMP','stockcomponent','values','Temperature', 0,3,'','text',NULL)";
+$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('PKG','stockcomponent','values','Package/Footprint', 0,3,'','text',NULL)";
-// stockassembly kitlocker items — KitLocker-specific fields
-$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('KLID', 'stockassembly','kitlocker','Kitlocker ID Code', 1,3,'','text',NULL)";
-$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('KLPR', 'stockassembly','kitlocker','Kitlocker Price', 2,3,'','text',NULL)";
-$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('KL3M', 'stockassembly','kitlocker','Kitlocker 3 Month Sales', 3,3,'','text',NULL)";
-$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('KLURL','stockassembly','kitlocker','Details URL', 4,3,'','text',NULL)";
-$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('KLSGL','stockassembly','kitlocker','Kitlocker Stock', 2,3,'','text',NULL)";
+// kitlocker items — package-level ('stock'), shared across SA and SC; all multiple=0 (one value per record)
+$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('KLID', 'stock','kitlocker','Kitlocker ID Code', 0,3,'','text',NULL)";
+$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('KLPR', 'stock','kitlocker','Kitlocker Price', 0,3,'','text',NULL)";
+$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('KL3M', 'stock','kitlocker','Kitlocker 3 Month Sales', 0,3,'','text',NULL)";
+$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('KLURL','stock','kitlocker','Details URL', 0,3,'','text',NULL)";
+$xrefItems[] = "INSERT INTO `{$X}liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`,`data`) VALUES ('KLSGL','stock','kitlocker','Kitlocker Stock', 0,3,'','text',NULL)";
// stockmovement xref groups
$xrefTypes[] = "INSERT INTO `{$X}liberty_xref_group` (`x_group`,`content_type_guid`,`title`,`sort_order`,`role_id`,`type_href`) VALUES ('reference','stockmovement','Reference',1,3,'')";
diff --git a/edit_assembly.php b/edit_assembly.php
index e86c5c4..29b2248 100755
--- a/edit_assembly.php
+++ b/edit_assembly.php
@@ -96,7 +96,7 @@ if( !empty( $_REQUEST['savegallery'] ) ) {
$compId = $gBitDb->getOne(
"SELECT lc.`content_id` FROM `".BIT_DB_PREFIX."liberty_content` lc
- WHERE lc.`content_type_guid` = '".STOCKCOMPONENT_CONTENT_TYPE_GUID."' AND lc.`title` = ?",
+ WHERE lc.`content_type_guid` = 'stockcomponent' AND lc.`title` = ?",
[ $componentTitle ]
);
if( !$compId ) {
diff --git a/import/ImportKitlockerAssemblies.php b/import/ImportKitlockerAssemblies.php
new file mode 100644
index 0000000..aa96a1e
--- /dev/null
+++ b/import/ImportKitlockerAssemblies.php
@@ -0,0 +1,120 @@
+<?php
+/**
+ * Kitlocker assemblies/components CSV importer.
+ *
+ * CSV column layout (0-based, header row skipped by loader):
+ * 0 Title MERG product code / designation (used as lc.title)
+ * 1 KLID Kitlocker numeric ID code (stored as KLID xref on assemblies)
+ * 2 Description Long description (stored as content body)
+ * 3 KLSGL Kitlocker single-unit stock count (assemblies only → KLSGL xref)
+ * 4 KL3M 3-month sales count (assemblies only → KL3M xref)
+ * 5 Group Group number 1–28 → KLG01–KLG28 stgrp xref (both types)
+ * 6 Type 'A' = StockAssembly, 'C' = StockComponent
+ *
+ * @package stock
+ */
+
+use Bitweaver\Stock\StockAssembly;
+use Bitweaver\Stock\StockComponent;
+
+function stockExpungeKitlockerItemByTitle( string $title, string $type ): bool {
+ global $gBitDb;
+
+ $guid = ( $type === 'A' ) ? 'stockassembly' : 'stockcomponent';
+ $contentId = $gBitDb->getOne(
+ "SELECT lc.`content_id` FROM `".BIT_DB_PREFIX."liberty_content` lc
+ WHERE lc.`content_type_guid` = ? AND lc.`title` = ?",
+ [ $guid, $title ]
+ );
+ if( !$contentId ) {
+ return false;
+ }
+
+ $obj = ( $type === 'A' ) ? new StockAssembly( (int)$contentId ) : new StockComponent( (int)$contentId );
+ $obj->expunge();
+ return true;
+}
+
+function stockImportKitlockerItem( array $data, int $rowNum ): array {
+ global $gBitDb;
+
+ $result = [ 'loaded' => 0, 'skipped' => 0, 'errors' => [] ];
+
+ $title = trim( $data[0] ?? '' );
+ if( empty( $title ) ) {
+ $result['skipped']++;
+ return $result;
+ }
+
+ $type = strtoupper( trim( $data[6] ?? '' ) );
+ if( !in_array( $type, [ 'A', 'C' ] ) ) {
+ $result['skipped']++;
+ $result['errors'][] = "Row $rowNum: '$title' — unknown type '$type', skipped.";
+ return $result;
+ }
+
+ $guid = ( $type === 'A' ) ? 'stockassembly' : 'stockcomponent';
+
+ $exists = $gBitDb->getOne(
+ "SELECT lc.`content_id` FROM `".BIT_DB_PREFIX."liberty_content` lc
+ WHERE lc.`content_type_guid` = ? AND lc.`title` = ?",
+ [ $guid, $title ]
+ );
+ if( $exists ) {
+ $result['skipped']++;
+ $result['errors'][] = "Row $rowNum: '$title' already exists, skipped.";
+ return $result;
+ }
+
+ $klid = trim( $data[1] ?? '' );
+ $desc = trim( $data[2] ?? '' );
+ $klsgl = trim( $data[3] ?? '' );
+ $kl3m = trim( $data[4] ?? '' );
+ $group = (int)( $data[5] ?? 0 );
+
+ $obj = ( $type === 'A' ) ? new StockAssembly() : new StockComponent();
+ $pHash = [
+ 'title' => $title,
+ 'edit' => $desc,
+ 'format_guid' => 'bithtml',
+ ];
+ if( !$obj->store( $pHash ) ) {
+ $result['skipped']++;
+ $result['errors'][] = "Row $rowNum: failed to store '$title'.";
+ return $result;
+ }
+
+ $contentId = $obj->mContentId;
+
+ // Group tag — shared across both types via 'stock' package-level stgrp
+ if( $group >= 1 && $group <= 99 ) {
+ $xrefId = $gBitDb->GenID( 'liberty_xref_seq' );
+ $gBitDb->associateInsert( BIT_DB_PREFIX.'liberty_xref', [
+ 'xref_id' => $xrefId,
+ 'content_id' => $contentId,
+ 'item' => sprintf( 'KLG%02d', $group ),
+ 'xorder' => 0,
+ 'last_update_date' => $gBitDb->NOW(),
+ ] );
+ }
+
+ // Assembly-specific kitlocker xrefs
+ if( $type === 'A' ) {
+ foreach( [ 'KLID' => $klid, 'KLSGL' => $klsgl, 'KL3M' => $kl3m ] as $item => $value ) {
+ if( $value !== '' ) {
+ $xrefId = $gBitDb->GenID( 'liberty_xref_seq' );
+ $gBitDb->associateInsert( BIT_DB_PREFIX.'liberty_xref', [
+ 'xref_id' => $xrefId,
+ 'content_id' => $contentId,
+ 'item' => $item,
+ 'xorder' => 0,
+ 'xkey' => substr( $value, 0, 32 ),
+ 'last_update_date' => $gBitDb->NOW(),
+ ] );
+ }
+ }
+ }
+
+ $result['loaded']++;
+ return $result;
+}
diff --git a/import/ImportSimpleComponent.php b/import/ImportSimpleComponent.php
index 83161ce..7db7c52 100644
--- a/import/ImportSimpleComponent.php
+++ b/import/ImportSimpleComponent.php
@@ -33,7 +33,7 @@ function stockImportFindSupplier( string $name ): ?int {
$contentId = $gBitDb->getOne(
"SELECT `content_id` FROM `".BIT_DB_PREFIX."liberty_xref`
- WHERE `item` = 'SCREF' AND UPPER( `xkey_ext` ) = UPPER( ? )",
+ WHERE `item` = 'SCREF' AND UPPER( `xkey` ) = UPPER( ? )",
[ trim( $name ) ]
);
@@ -115,44 +115,11 @@ function stockImportSimpleComponent( array $data, int $rowNum ): array {
'item' => '#SUP',
'xorder' => 1,
'xref' => $supplierContentId,
+ 'xkey' => substr( $supplierPn, 0, 32 ),
+ 'xkey_ext' => substr( $supplierPrice, 0, 250 ),
+ 'data' => $supplierUrl ?: null,
'last_update_date' => $gBitDb->NOW(),
] );
-
- if( !empty( $supplierPn ) ) {
- $xrefId = $gBitDb->GenID( 'liberty_xref_seq' );
- $gBitDb->associateInsert( BIT_DB_PREFIX.'liberty_xref', [
- 'xref_id' => $xrefId,
- 'content_id' => $contentId,
- 'item' => '#PN',
- 'xorder' => 1,
- 'xkey_ext' => substr( $supplierPn, 0, 250 ),
- 'last_update_date' => $gBitDb->NOW(),
- ] );
- }
-
- if( !empty( $supplierPrice ) ) {
- $xrefId = $gBitDb->GenID( 'liberty_xref_seq' );
- $gBitDb->associateInsert( BIT_DB_PREFIX.'liberty_xref', [
- 'xref_id' => $xrefId,
- 'content_id' => $contentId,
- 'item' => '#PR',
- 'xorder' => 1,
- 'xkey' => substr( $supplierPrice, 0, 32 ),
- 'last_update_date' => $gBitDb->NOW(),
- ] );
- }
-
- if( !empty( $supplierUrl ) ) {
- $xrefId = $gBitDb->GenID( 'liberty_xref_seq' );
- $gBitDb->associateInsert( BIT_DB_PREFIX.'liberty_xref', [
- 'xref_id' => $xrefId,
- 'content_id' => $contentId,
- 'item' => '#URL',
- 'xorder' => 1,
- 'xkey_ext' => substr( $supplierUrl, 0, 250 ),
- 'last_update_date' => $gBitDb->NOW(),
- ] );
- }
}
}
diff --git a/import/load_component_list.php b/import/load_component_list.php
new file mode 100644
index 0000000..e3aff8e
--- /dev/null
+++ b/import/load_component_list.php
@@ -0,0 +1,68 @@
+<?php
+/**
+ * Load components from storage/stock/Component List.csv.
+ * Columns: Title, Description, supplier, supplier PN, Supplier price, Supplier URL.
+ * First row is a header and is skipped. Append ?clear=y to wipe and re-import.
+ *
+ * @package stock
+ */
+
+namespace Bitweaver\Stock;
+
+require_once '../../kernel/includes/setup_inc.php';
+
+global $gBitSystem, $gBitSmarty, $gBitDb;
+
+$gBitSystem->verifyPackage( 'stock' );
+$gBitSystem->verifyPermission( 'p_stock_admin' );
+
+require_once __DIR__.'/ImportSimpleComponent.php';
+
+$csvFile = STOCK_IMPORT_PATH . 'Component List.csv';
+$doClear = ( ( $_REQUEST['clear'] ?? '' ) === 'y' );
+$loaded = 0;
+$skipped = 0;
+$deleted = 0;
+$errors = [];
+
+if( !file_exists( $csvFile ) ) {
+ $errors[] = 'CSV file not found: '.$csvFile;
+} else {
+ $handle = fopen( $csvFile, 'r' );
+ if( $handle === false ) {
+ $errors[] = 'Cannot open CSV file.';
+ } else {
+ $rows = [];
+ $rowNum = 0;
+ while( ( $data = fgetcsv( $handle, 0, ',', '"', '' ) ) !== false ) {
+ $rowNum++;
+ if( $rowNum === 1 ) continue;
+ $rows[] = $data;
+ }
+ fclose( $handle );
+
+ if( $doClear ) {
+ foreach( $rows as $data ) {
+ $title = trim( $data[0] ?? '' );
+ if( !empty( $title ) && stockExpungeComponentByTitle( $title ) ) {
+ $deleted++;
+ }
+ }
+ }
+
+ foreach( $rows as $idx => $data ) {
+ $result = stockImportSimpleComponent( $data, $idx + 2 );
+ $loaded += $result['loaded'];
+ $skipped += $result['skipped'];
+ $errors = array_merge( $errors, $result['errors'] );
+ }
+ }
+}
+
+$gBitSmarty->assign( 'loaded', $loaded );
+$gBitSmarty->assign( 'skipped', $skipped );
+$gBitSmarty->assign( 'deleted', $deleted );
+$gBitSmarty->assign( 'errors', $errors );
+$gBitSmarty->assign( 'csvFile', $csvFile );
+
+$gBitSystem->display( 'bitpackage:stock/import_results.tpl', 'Import Component List' );
diff --git a/import/load_kitlocker_assemblies.php b/import/load_kitlocker_assemblies.php
new file mode 100644
index 0000000..856aed7
--- /dev/null
+++ b/import/load_kitlocker_assemblies.php
@@ -0,0 +1,73 @@
+<?php
+/**
+ * Load assemblies and components from KitlockerAssemblies.csv.
+ *
+ * Reads storage/stock/KitlockerAssemblies.csv (header row present).
+ * Each row is routed to StockAssembly or StockComponent based on the Type column.
+ * Append ?clear=y to delete existing records by title before re-importing.
+ *
+ * @package stock
+ */
+
+namespace Bitweaver\Stock;
+
+require_once '../../kernel/includes/setup_inc.php';
+
+global $gBitSystem, $gBitSmarty, $gBitDb;
+
+$gBitSystem->verifyPackage( 'stock' );
+$gBitSystem->verifyPermission( 'p_stock_admin' );
+
+require_once __DIR__.'/ImportKitlockerAssemblies.php';
+
+$csvFile = STOCK_IMPORT_PATH . 'KitlockerAssemblies.csv';
+$doClear = ( ( $_REQUEST['clear'] ?? '' ) === 'y' );
+$loaded = 0;
+$skipped = 0;
+$deleted = 0;
+$errors = [];
+
+if( !file_exists( $csvFile ) ) {
+ $errors[] = 'CSV file not found: '.$csvFile;
+} else {
+ $handle = fopen( $csvFile, 'r' );
+ if( $handle === false ) {
+ $errors[] = 'Cannot open CSV file.';
+ } else {
+ $rows = [];
+ while( ( $data = fgetcsv( $handle, 0, ',', '"', '' ) ) !== false ) {
+ $rows[] = $data;
+ }
+ fclose( $handle );
+
+ // Skip header row
+ $dataRows = array_slice( $rows, 1 );
+
+ if( $doClear ) {
+ foreach( $dataRows as $data ) {
+ $title = trim( $data[0] ?? '' );
+ $type = strtoupper( trim( $data[6] ?? '' ) );
+ if( !empty( $title ) && in_array( $type, [ 'A', 'C' ] ) ) {
+ if( stockExpungeKitlockerItemByTitle( $title, $type ) ) {
+ $deleted++;
+ }
+ }
+ }
+ }
+
+ foreach( $dataRows as $idx => $data ) {
+ $result = stockImportKitlockerItem( $data, $idx + 2 );
+ $loaded += $result['loaded'];
+ $skipped += $result['skipped'];
+ $errors = array_merge( $errors, $result['errors'] );
+ }
+ }
+}
+
+$gBitSmarty->assign( 'loaded', $loaded );
+$gBitSmarty->assign( 'skipped', $skipped );
+$gBitSmarty->assign( 'deleted', $deleted );
+$gBitSmarty->assign( 'errors', $errors );
+$gBitSmarty->assign( 'csvFile', $csvFile );
+
+$gBitSystem->display( 'bitpackage:stock/import_results.tpl', 'Import Kitlocker Assemblies & Components' );
diff --git a/includes/classes/StockAssembly.php b/includes/classes/StockAssembly.php
index 1055c3c..ab151a4 100755
--- a/includes/classes/StockAssembly.php
+++ b/includes/classes/StockAssembly.php
@@ -1026,11 +1026,20 @@ class StockAssembly extends StockBase {
if( !empty( $pListHash['root_only'] ) ) {
$whereSql .= " AND NOT EXISTS (SELECT `assembly_content_id` FROM `".BIT_DB_PREFIX."stock_assembly_map` tfgim2 WHERE tfgim2.`item_content_id`=lc.`content_id`)";
}
+ if( !empty( $pListHash['non_root_only'] ) ) {
+ $whereSql .= " AND EXISTS (SELECT `assembly_content_id` FROM `".BIT_DB_PREFIX."stock_assembly_map` tfgim2 WHERE tfgim2.`item_content_id`=lc.`content_id`)";
+ }
} else {
// weed out empty galleries if we don't need them
if( empty( $pListHash['show_empty'] ) ) {
$mapJoin = "INNER JOIN `".BIT_DB_PREFIX."stock_assembly_map` fgim ON (fgim.`assembly_content_id`=lc.`content_id`)";
}
+ if( !empty( $pListHash['root_only'] ) ) {
+ // already handled above via LEFT OUTER JOIN + IS NULL
+ }
+ if( !empty( $pListHash['non_root_only'] ) ) {
+ $joinSql .= " INNER JOIN `".BIT_DB_PREFIX."stock_assembly_map` tfgim2nr ON (tfgim2nr.`item_content_id`=lc.`content_id`)";
+ }
}
if ( !empty( $pListHash['sort_mode'] ) ) {
diff --git a/includes/classes/StockBase.php b/includes/classes/StockBase.php
index c31e2e7..4becb5d 100755
--- a/includes/classes/StockBase.php
+++ b/includes/classes/StockBase.php
@@ -37,7 +37,8 @@ abstract class StockBase extends LibertyContent
* content_id (xref column) to the contact's lc.title.
*/
public function loadXrefInfo(): void {
- parent::loadXrefInfo();
+ $this->mXrefInfo = new \Bitweaver\Liberty\LibertyXrefInfo( $this->mContentTypeGuid, 'stock' );
+ $this->mXrefInfo->load( $this->mContentId );
if( empty( $this->mXrefInfo ) ) return;
$supplierGroup = $this->mXrefInfo->mGroups['supplier'] ?? null;
if( !$supplierGroup || empty( $supplierGroup->mXrefs ) ) return;
diff --git a/list_assemblies.php b/list_assemblies.php
index 6d7c154..1a2bee5 100755
--- a/list_assemblies.php
+++ b/list_assemblies.php
@@ -12,8 +12,8 @@ global $gBitSystem, $gBitSmarty, $gStockAssembly;
$gStockAssembly = new StockAssembly();
-if( !empty( $_REQUEST['content_id'] ) && is_numeric( $_REQUEST['content_id'] ) ) {
- $parentAssembly = new StockAssembly( (int)$_REQUEST['content_id'] );
+if( !empty( $_REQUEST['gallery_id'] ) && is_numeric( $_REQUEST['gallery_id'] ) ) {
+ $parentAssembly = new StockAssembly( (int)$_REQUEST['gallery_id'] );
$parentAssembly->load();
if( $parentAssembly->isValid() ) {
$_REQUEST['parent_content_id'] = $parentAssembly->mContentId;
@@ -21,8 +21,8 @@ if( !empty( $_REQUEST['content_id'] ) && is_numeric( $_REQUEST['content_id'] ) )
$gBitSmarty->assign( 'parentAssembly', $parentAssembly );
}
} else {
- $_REQUEST['root_only'] = true;
- $_REQUEST['show_empty'] = true;
+ $_REQUEST['show_empty'] = true;
+ $_REQUEST['no_thumbnails'] = true;
}
if (!empty($_REQUEST['user_id']) && is_numeric($_REQUEST['user_id'])) {
diff --git a/print_bom.php b/print_bom.php
new file mode 100644
index 0000000..4ad16c4
--- /dev/null
+++ b/print_bom.php
@@ -0,0 +1,27 @@
+<?php
+/**
+ * Read-only printable BOM for an assembly. Requires p_stock_view only.
+ *
+ * @package stock
+ */
+
+namespace Bitweaver\Stock;
+
+require_once '../kernel/includes/setup_inc.php';
+
+use Bitweaver\KernelTools;
+
+global $gBitSystem, $gBitSmarty;
+
+$gBitSystem->verifyPermission( 'p_stock_view' );
+
+include_once STOCK_PKG_INCLUDE_PATH.'assembly_lookup_inc.php';
+
+if( !$gContent->isValid() ) {
+ $gBitSystem->fatalError( 'Assembly not found.' );
+}
+
+$gContent->loadXrefInfo();
+$gBitSmarty->assign( 'gXrefInfo', $gContent->mXrefInfo );
+
+$gBitSystem->display( 'bitpackage:stock/print_bom.tpl', $gContent->getTitle().' — '.KernelTools::tra('Parts List'), [ 'display_mode' => 'display' ] );
diff --git a/templates/assembly_icons_inc.tpl b/templates/assembly_icons_inc.tpl
index 07eff67..e1dd64e 100755
--- a/templates/assembly_icons_inc.tpl
+++ b/templates/assembly_icons_inc.tpl
@@ -1,3 +1,3 @@
{if $gContent->hasUpdatePermission()}
- <a title="{tr}Add Component{/tr}" href="{$smarty.const.STOCK_PKG_URL}edit_component.php?content_id={$gContent->mContentId}">{biticon ipackage="icons" iname="list-add" ipackage="icons" iexplain="Add Component"}</a>
+ <a title="{tr}Add Component{/tr}" href="{$smarty.const.STOCK_PKG_URL}edit_component.php?content_id={$gContent->mContentId}">{biticon ipackage="icons" iname="list-add" iexplain="Add Component"}</a>
{/if}
diff --git a/templates/component_order.tpl b/templates/component_order.tpl
index a1ac4c5..817c84d 100755
--- a/templates/component_order.tpl
+++ b/templates/component_order.tpl
@@ -1,6 +1,9 @@
{strip}
<div class="edit stock">
<div class="header">
+ <div class="floaticon hidden-print">
+ <button type="button" class="btn btn-link" onclick="window.print()">{biticon ipackage="icons" iname="document-print" iexplain="Print"}</button>
+ </div>
<h1>{tr}Parts List{/tr}: <a href="{$smarty.const.STOCK_PKG_URL}view_assembly.php?content_id={$gContent->mContentId}">{$gContent->getTitle()|escape}</a></h1>
</div>
@@ -13,7 +16,7 @@
<table class="table table-striped table-condensed">
<thead>
<tr>
- <th style="width:6em;">{tr}Order{/tr}</th>
+ <th style="width:4em;">{tr}#{/tr}</th>
<th>{tr}Component{/tr}</th>
<th>{tr}Description{/tr}</th>
<th>{tr}Qty{/tr}</th>
@@ -25,6 +28,7 @@
{if $gXrefInfo->mGroups.quantity && $gXrefInfo->mGroups.quantity->mXrefs}
{foreach from=$gXrefInfo->mGroups.quantity->mXrefs item=row}
{math equation="floor(x/1000)" x=$row.xorder|default:0 assign=thisGroup}
+ {math equation="x % 1000" x=$row.xorder|default:0 assign=posInGroup}
{if $thisGroup != $lastGroup}
<tr class="active">
<th colspan="5">{tr}Group{/tr} {$thisGroup}</th>
@@ -33,9 +37,10 @@
{/if}
<tr>
<td>
- <input type="text" class="form-control input-sm" size="5"
+ <input type="text" class="form-control input-sm hidden-print" size="5"
name="xrefOrder[{$row.xref_id|escape}]"
value="{$row.xorder|escape}" />
+ <span class="visible-print-inline">{$posInGroup}</span>
</td>
<td>
{if $row.xref > 0}
@@ -58,7 +63,7 @@
</tbody>
</table>
- <div class="form-group submit">
+ <div class="form-group submit hidden-print">
<input type="submit" class="btn btn-default" name="cancel" value="{tr}Back{/tr}" />
<input type="submit" class="btn btn-primary" name="save_order" value="{tr}Save{/tr}" />
</div>
diff --git a/templates/list_assemblies2.tpl b/templates/list_assemblies2.tpl
index c7f2765..24169ba 100755
--- a/templates/list_assemblies2.tpl
+++ b/templates/list_assemblies2.tpl
@@ -9,7 +9,7 @@
<section class="body">
<ul class="list-inline sortby">
- <li>{biticon ipackage="icons" iname="go-next" ipackage="icons" iexplain="sort by" iforce="icon"}</li>
+ <li>{biticon ipackage="icons" iname="go-next" iexplain="sort by" iforce="icon"}</li>
{if $gBitSystem->isFeatureActive('stock_list_title')}
<li>{smartlink ititle="Name" isort="title"}</li>
{/if}
@@ -41,7 +41,7 @@
{/if}
</a>
{if $gal.is_hidden|default:'n' == 'y' || $gal.is_private|default:'n' == 'y' || $gal.access_answer|default:false}
- {biticon ipackage="icons" iname="lock" ipackage="icons" iexplain="Restricted"}
+ {biticon ipackage="icons" iname="lock" iexplain="Restricted"}
{/if}
</h3>
</div>
diff --git a/templates/list_assemblies_simple.tpl b/templates/list_assemblies_simple.tpl
index 7c3b9d6..7d2eddf 100644
--- a/templates/list_assemblies_simple.tpl
+++ b/templates/list_assemblies_simple.tpl
@@ -2,7 +2,7 @@
<div class="listing stock">
<header>
<div class="floaticon">
- {minifind prompt="Assemblies" content_id=$smarty.request.content_id}
+ {minifind prompt="Assemblies" gallery_id=$smarty.request.gallery_id}
</div>
<h1>{tr}Assemblies{/tr}{if $gQueryUserId} {tr}by{/tr} {displayname user_id=$gQueryUserId}{/if}</h1>
</header>
@@ -38,12 +38,12 @@
</td>
<td>
{if $gal.child_count > 0}
- <a href="{$smarty.const.STOCK_PKG_URL}list_assemblies.php?content_id={$gal.content_id}">{$gal.title|escape}</a>
+ <a href="{$smarty.const.STOCK_PKG_URL}list_assemblies.php?gallery_id={$gal.content_id}">{$gal.title|escape}</a>
{else}
<a href="{$gal.display_url|escape}">{$gal.title|escape}</a>
{/if}
{if $gal.is_hidden|default:'n' == 'y' || $gal.is_private|default:'n' == 'y' || $gal.access_answer|default:false}
- {biticon ipackage="icons" iname="lock" ipackage="icons" iexplain="Restricted"}
+ {biticon ipackage="icons" iname="lock" iexplain="Restricted"}
{/if}
{if $gal.data}
<br/><small class="text-muted">{$gal.data|truncate:250|escape}</small>
@@ -61,7 +61,7 @@
</table>
<nav>
- {pagination content_id=$smarty.request.content_id}
+ {pagination gallery_id=$smarty.request.gallery_id}
</nav>
</section>
diff --git a/templates/list_components.tpl b/templates/list_components.tpl
index 5495f84..f2de519 100644
--- a/templates/list_components.tpl
+++ b/templates/list_components.tpl
@@ -13,7 +13,7 @@
<section class="body">
{if $gBitSystem->isFeatureActive('stock_item_list_creator') || $gBitSystem->isFeatureActive('stock_item_list_date') || $gBitSystem->isFeatureActive('stock_item_list_hits')}
<ul class="list-inline sortby">
- <li>{biticon ipackage="icons" iname="go-next" ipackage="icons" iexplain="sort by" iforce="icon"}</li>
+ <li>{biticon ipackage="icons" iname="go-next" iexplain="sort by" iforce="icon"}</li>
{if $gBitSystem->isFeatureActive('stock_item_list_creator')}
<li>{smartlink ititle="Creator" isort=$gBitSystem->getConfig('users_display_name') icontrol=$listInfo}</li>
{/if}
diff --git a/templates/list_stock.tpl b/templates/list_stock.tpl
index 0afba5e..efbe222 100644
--- a/templates/list_stock.tpl
+++ b/templates/list_stock.tpl
@@ -1,6 +1,9 @@
{strip}
<div class="listing stock">
<header>
+ <div class="floaticon hidden-print">
+ <button type="button" class="btn btn-link" onclick="window.print()">{biticon ipackage="icons" iname="document-print" iexplain="Print"}</button>
+ </div>
<h1>{tr}Stock Levels{/tr}{if $assemblyTitle} — {$assemblyTitle|escape}{/if}</h1>
</header>
diff --git a/templates/print_bom.tpl b/templates/print_bom.tpl
new file mode 100644
index 0000000..e6c28e6
--- /dev/null
+++ b/templates/print_bom.tpl
@@ -0,0 +1,58 @@
+{strip}
+<div class="display stock">
+ <header>
+ <div class="floaticon hidden-print">
+ <button type="button" class="btn btn-link" onclick="window.print()">{biticon ipackage="icons" iname="document-print" iexplain="Print"}</button>
+ </div>
+ <h1>{$gContent->getTitle()|escape} &mdash; {tr}Parts List{/tr}</h1>
+ </header>
+
+ <section class="body">
+ <table class="table table-striped table-condensed">
+ <thead>
+ <tr>
+ <th style="width:3em">{tr}#{/tr}</th>
+ <th>{tr}Component{/tr}</th>
+ <th>{tr}Description{/tr}</th>
+ <th>{tr}Qty{/tr}</th>
+ <th>{tr}Ref designators{/tr}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {assign var=lastGroup value=-1}
+ {if $gXrefInfo->mGroups.quantity && $gXrefInfo->mGroups.quantity->mXrefs}
+ {foreach from=$gXrefInfo->mGroups.quantity->mXrefs item=row}
+ {math equation="floor(x/1000)" x=$row.xorder|default:0 assign=thisGroup}
+ {math equation="x % 1000" x=$row.xorder|default:0 assign=posInGroup}
+ {if $thisGroup != $lastGroup}
+ <tr class="active">
+ <th colspan="5">{tr}Group{/tr} {$thisGroup}</th>
+ </tr>
+ {assign var=lastGroup value=$thisGroup}
+ {/if}
+ <tr>
+ <td>{$posInGroup}</td>
+ <td>
+ {if $row.xref > 0}
+ <a href="{$smarty.const.STOCK_PKG_URL}view_component.php?content_id={$row.xref|escape}">{$row.xref_title|default:$row.xref|escape}</a>
+ {else}
+ &nbsp;
+ {/if}
+ </td>
+ <td>{$row.xref_data|escape}</td>
+ <td>
+ {$row.xkey|escape}
+ {if $row.item eq 'PCK' && $row.pack_size} of {$row.pack_size|escape}{if $row.pack_size_ext} {$row.pack_size_ext|escape}{/if}{/if}
+ </td>
+ <td>{$row.xkey_ext|escape}</td>
+ </tr>
+ {/foreach}
+ {else}
+ <tr class="norecords"><td colspan="5">{tr}No parts list found.{/tr}</td></tr>
+ {/if}
+ </tbody>
+ </table>
+ </section>
+</div>
+{/strip}
+<script>window.addEventListener('load', function(){ window.print(); });</script>
diff --git a/templates/view_assembly.tpl b/templates/view_assembly.tpl
index b1ef284..07b7ff9 100755
--- a/templates/view_assembly.tpl
+++ b/templates/view_assembly.tpl
@@ -1,16 +1,17 @@
{assign var=galLayout value=$gContent->getLayout()}
-{if $gContent->hasUpdatePermission()}
- <div class="floaticon">
+<div class="floaticon">
+ {if $gContent->hasUpdatePermission()}
{include file="bitpackage:liberty/services_inc.tpl" serviceLocation='icon' serviceHash=$gContent->mInfo}
<a title="{tr}Edit{/tr}" href="{$smarty.const.STOCK_PKG_URL}edit_assembly.php?content_id={$gContent->mContentId}">{biticon ipackage="icons" iname="edit" iexplain="Edit Assembly"}</a>
<a title="{tr}Component Order{/tr}" href="{$smarty.const.STOCK_PKG_URL}component_order.php?content_id={$gContent->mContentId}">{biticon ipackage="icons" iname="view-sort-ascending" iexplain="Component Order"}</a>
- <a title="{tr}View Stock{/tr}" href="{$smarty.const.STOCK_PKG_URL}list_stock.php?assembly_content_id={$gContent->mContentId}">{biticon ipackage="icons" iname="package-x-generic" iexplain="View Stock"}</a>
- <a title="{tr}View Movements{/tr}" href="{$smarty.const.STOCK_PKG_URL}list_movements.php?assembly_content_id={$gContent->mContentId}">{biticon ipackage="icons" iname="go-next" iexplain="View Movements"}</a>
- {if $gContent->hasAdminPermission()}
- <a title="{tr}Delete Assembly{/tr}" href="{$smarty.const.STOCK_PKG_URL}edit_assembly.php?content_id={$gContent->mContentId}&amp;delete=1">{biticon ipackage="icons" iname="user-trash" iexplain="Delete Assembly"}</a>
- {/if}
- </div>
-{/if}
+ {/if}
+ <a title="{tr}Print Parts List{/tr}" href="{$smarty.const.STOCK_PKG_URL}print_bom.php?content_id={$gContent->mContentId}">{biticon ipackage="icons" iname="document-print" iexplain="Print Parts List"}</a>
+ <a title="{tr}View Stock{/tr}" href="{$smarty.const.STOCK_PKG_URL}list_stock.php?assembly_content_id={$gContent->mContentId}">{biticon ipackage="icons" iname="package-x-generic" iexplain="View Stock"}</a>
+ <a title="{tr}View Movements{/tr}" href="{$smarty.const.STOCK_PKG_URL}list_movements.php?assembly_content_id={$gContent->mContentId}">{biticon ipackage="icons" iname="go-next" iexplain="View Movements"}</a>
+ {if $gContent->hasAdminPermission()}
+ <a title="{tr}Delete Assembly{/tr}" href="{$smarty.const.STOCK_PKG_URL}edit_assembly.php?content_id={$gContent->mContentId}&amp;delete=1">{biticon ipackage="icons" iname="user-trash" iexplain="Delete Assembly"}</a>
+ {/if}
+</div>
{include file="`$smarty.const.STOCK_PKG_PATH`assembly_views/`$galLayout`/stock_`$galLayout`_inc.tpl"}
{if $gXrefInfo->mGroups}