partkeepr

fork of partkeepr
git clone https://git.e1e0.net/partkeepr.git
Log | Files | Refs | Submodules | README | LICENSE

PartsGrid.js (19245B)


      1 /**
      2  * This class is the main part list grid.
      3  *
      4  */
      5 Ext.define('PartKeepr.PartsGrid', {
      6     extend: 'PartKeepr.EditorGrid',
      7     alias: 'widget.PartsGrid',
      8 
      9     /**
     10      * Display button texts by default
     11      */
     12     buttonTextMode: 'show',
     13 
     14     /**
     15      * @cfg {String} Defines the text of the "Add" button
     16      */
     17     addButtonText: i18n("Add Part"),
     18 
     19     /**
     20      * @cfg {String} Defines the icon of the "Add" button
     21      */
     22     addButtonIconCls: 'web-icon brick_add',
     23 
     24     /**
     25      * @cfg {String} Defines the text of the "Delete" button
     26      */
     27     deleteButtonText: i18n("Delete Part"),
     28 
     29     /**
     30      * @cfg {String} Defines the icon of the "Add" button
     31      */
     32     deleteButtonIconCls: 'web-icon brick_delete',
     33 
     34     /**
     35      * @cfg {String} Defines the icon of the "Expand Row" button
     36      */
     37     expandRowButtonIconCls: 'partkeepr-icon group-expand',
     38 
     39     /**
     40      * @cfg {String} Defines the icon of the "Collapse Row" button
     41      */
     42     collapseRowButtonIconCls: 'partkeepr-icon group-collapse',
     43 
     44     /**
     45      * Configure drag'n'drop.
     46      * @todo Check if this messes up with the Part drop down in the project view
     47      */
     48     viewConfig: {
     49         plugins: {
     50             ddGroup: 'CategoryTree',
     51             ptype: 'gridviewdragdrop',
     52             enableDrop: false
     53         }
     54     },
     55     enableDragDrop: true,
     56     stripeRows: true,
     57     multiSelect: true,
     58     autoScroll: false,
     59     invalidateScrollerOnRefresh: true,
     60     titleProperty: 'name',
     61     searchFieldSystemPreference: "partkeepr.part.search.field",
     62     searchFieldSystemPreferenceDefaults: ["name", "description", "comment", "internalPartNumber"],
     63     splitSearchTermSystemPreference: "partkeepr.part.search.split",
     64     splitSearchTermSystemPreferenceDefaults: true,
     65 
     66     initComponent: function ()
     67     {
     68 
     69         this.groupingFeature = Ext.create('Ext.grid.feature.Grouping', {
     70             //enableGroupingMenu: false,
     71             groupHeaderTpl: '{name} ({rows.length} ' + i18n("Part(s)") + ")"
     72         });
     73 
     74         // Create the columns
     75         this.defineColumns();
     76 
     77 
     78         this.features = [this.groupingFeature];
     79 
     80         this.on("itemdblclick", this.onDoubleClick, this);
     81 
     82         // Bugfix for scroller becoming detached.
     83         // @todo Remove with ExtJS 4.1
     84         this.on('scrollershow', function (scroller)
     85         {
     86             if (scroller && scroller.scrollEl) {
     87                 scroller.clearManagedListeners();
     88                 scroller.mon(scroller.scrollEl, 'scroll', scroller.onElScroll, scroller);
     89             }
     90         });
     91 
     92         if (this.enableEditing) {
     93             this.editing = Ext.create('Ext.grid.plugin.CellEditing', {
     94                 clicksToEdit: 1
     95             });
     96 
     97             this.editing.on("edit", this.onEdit, this);
     98 
     99             this.plugins = [this.editing];
    100         }
    101 
    102         // Initialize the panel
    103         this.callParent();
    104 
    105         this.bottomToolbar.add({
    106             xtype: 'button',
    107             tooltip: i18n("Expand all Groups"),
    108             iconCls: this.expandRowButtonIconCls,
    109             listeners: {
    110                 scope: this.groupingFeature,
    111                 click: this.groupingFeature.expandAll
    112             }
    113 
    114         });
    115 
    116         this.bottomToolbar.add({
    117             xtype: 'button',
    118             tooltip: i18n("Collapse all Groups"),
    119             iconCls: this.collapseRowButtonIconCls,
    120             listeners: {
    121                 scope: this.groupingFeature,
    122                 click: this.groupingFeature.collapseAll
    123             }
    124 
    125         });
    126 
    127         var insertPosition = this.bottomToolbar.items.indexOf(this.bottomToolbar.down("#addFilter"));
    128 
    129         this.bottomToolbar.insert(insertPosition, {
    130             xtype: 'button',
    131             tooltip: i18n("Filter by Part Parameter"),
    132             iconCls: "fugue-icon table--plus",
    133             listeners: {
    134                 scope: this,
    135                 click: this.addParameterFilter
    136             }
    137         });
    138 
    139         var duplicateBasicData = i18n(
    140             "Duplicates the selected part with the data found in the \"basic\" tab and opens the editor. Doesn't immediately saves the duplicate, in order to allow editing.");
    141         var duplicateAllData = i18n(
    142             "Duplicates the selected part with all data including attachments, distributors etc. Doesn't immediately saves the duplicate, in order to allow editing.");
    143 
    144         this.addFromTemplateButton = Ext.create("Ext.button.Split", {
    145             disabled: true,
    146             handler: Ext.bind(function ()
    147             {
    148                 this.fireEvent("duplicateItemWithBasicData");
    149             }, this),
    150             tooltip: duplicateBasicData,
    151             text: i18n("Duplicate"),
    152             iconCls: 'web-icon brick_link',
    153             menu: new Ext.menu.Menu({
    154                 items: [
    155                     {
    156                         text: i18n("Duplicate with all data"),
    157                         tooltip: duplicateAllData,
    158                         handler: function ()
    159                         {
    160                             this.fireEvent("duplicateItemWithAllData");
    161                         },
    162                         scope: this
    163                     }, {
    164                         text: i18n("Duplicate basic data only"),
    165                         tooltip: duplicateBasicData,
    166                         handler: function ()
    167                         {
    168                             this.fireEvent("duplicateItemWithBasicData");
    169                         },
    170                         scope: this
    171                     }
    172                 ]
    173             })
    174         });
    175 
    176         if (this.enableEditing) {
    177             this.topToolbar.insert(2, this.addFromTemplateButton);
    178         }
    179 
    180         this.createMetaPartButton = Ext.create("Ext.button.Button", {
    181             iconCls: 'web-icon bricks',
    182             text: i18n("Add Meta-Part"),
    183             handler: function ()
    184             {
    185                 this.fireEvent("addMetaPart");
    186             },
    187             scope: this
    188         });
    189 
    190         this.topToolbar.insert(1, this.createMetaPartButton);
    191 
    192         this.mapSearchHotkey();
    193     },
    194     /**
    195      * Maps a search hotkey to the search box.
    196      *
    197      * Right now, this is hardcoded to alt+x.
    198      *
    199      */
    200     mapSearchHotkey: function ()
    201     {
    202         this.searchKey = new Ext.util.KeyMap(Ext.get(document), {
    203             key: 'x',
    204             ctrl: false,
    205             alt: true,
    206             fn: function ()
    207             {
    208                 var searchBox = this.searchField;
    209                 if (Ext.get(document).activeElement !== searchBox) {
    210                     searchBox.focus('', 10);
    211                 }
    212                 searchBox.setValue('');
    213             },
    214             scope: this,
    215             stopEvent: true
    216         });
    217     },
    218     /**
    219      * Called when an item was selected. Enables/disables the delete button.
    220      */
    221     _updateAddTemplateButton: function ()
    222     {
    223         /* Right now, we support delete on a single record only */
    224         if (this.getSelectionModel().getCount() === 1) {
    225             this.addFromTemplateButton.enable();
    226         } else {
    227             this.addFromTemplateButton.disable();
    228         }
    229     },
    230     /**
    231      * Called when an item was selected
    232      */
    233     _onItemSelect: function (selectionModel, record)
    234     {
    235         this._updateAddTemplateButton(selectionModel, record);
    236         this.callParent(arguments);
    237     },
    238     /**
    239      * Called when an item was deselected
    240      */
    241     _onItemDeselect: function (selectionModel, record)
    242     {
    243         this._updateAddTemplateButton(selectionModel, record);
    244         this.callParent(arguments);
    245     },
    246     /**
    247      * Called when the record was double-clicked
    248      */
    249     onDoubleClick: function (view, record)
    250     {
    251         if (record) {
    252             this.fireEvent("editPart", record);
    253         }
    254     },
    255     /**
    256      * Defines the columns used in this grid.
    257      */
    258     defineColumns: function ()
    259     {
    260         this.columns = [
    261             {
    262                 header: '<span class="web-icon fugue-icon paper-clip"></span>',
    263                 dataIndex: "",
    264                 width: 30,
    265                 tooltip: i18n("Has attachments?"),
    266                 renderers: [{
    267                     rtype: 'partAttachment'
    268                 }]
    269             }, {
    270                 text: '<span class="web-icon flag_orange"></span>',
    271                 dataIndex: "needsReview",
    272                 width: 30,
    273                 tooltip: i18n("Needs Review?"),
    274                 renderers: [{
    275                     rtype: 'icon',
    276                     rendererConfig: {
    277                         iconCls: 'web-icon flag_orange'
    278                     }
    279                 }]
    280             }, {
    281                 text: '<span class="web-icon bricks"></span>',
    282                 dataIndex: "metaPart",
    283                 width: 30,
    284                 tooltip: i18n("Meta Part"),
    285                 renderers: [{
    286                     rtype: 'icon',
    287                     rendererConfig: {
    288                         iconCls: 'web-icon bricks'
    289                     }
    290                 }]
    291             }, {
    292                 header: i18n("Name"),
    293                 dataIndex: 'name',
    294                 flex: 1,
    295                 minWidth: 150
    296             }, {
    297                 header: i18n("Description"),
    298                 dataIndex: 'description',
    299                 flex: 2,
    300                 minWidth: 150
    301             }, {
    302                 header: i18n("Storage Location"),
    303                 dataIndex: 'storageLocation.name'
    304             }, {
    305                 header: i18n("Status"),
    306                 dataIndex: "status"},
    307             {
    308                 header: i18n("Condition"),
    309                 dataIndex: "partCondition"
    310             }, {
    311                 header: i18n("Stock"),
    312                 dataIndex: 'stockLevel',
    313                 renderers: [{
    314                     rtype: "stockLevel"
    315                 }],
    316                 editor: {
    317                     xtype: 'textfield',
    318                     allowBlank: false
    319                 }
    320             }, {
    321                 header: i18n("Min. Stock"),
    322                 dataIndex: 'minStockLevel',
    323                 renderers: [{
    324                     rtype: "stockLevel"
    325                 }]
    326             }, {
    327                 header: i18n("Avg. Price"),
    328                 dataIndex: 'averagePrice',
    329                 align: 'right',
    330                 renderers: [{
    331                     rtype: "currency"
    332                 }]
    333             }, {
    334                 header: i18n("Footprint"),
    335                 dataIndex: 'footprint.name'
    336             }, {
    337                 header: i18n("Category"),
    338                 dataIndex: "category.categoryPath",
    339                 hidden: true
    340             }, {
    341                 header: i18n("Create Date"),
    342                 dataIndex: 'createDate',
    343                 hidden: true
    344             }, {
    345                 header: i18n("Internal ID"),
    346                 dataIndex: '@id',
    347                  renderers: [{
    348                     rtype: "internalID"
    349                 }]
    350             }
    351 
    352         ];
    353     },
    354     /**
    355      * Sets the category. Triggers a store reload with a category filter.
    356      */
    357     setCategory: function (category)
    358     {
    359         var proxy = this.store.getProxy();
    360 
    361         proxy.extraParams.category = category;
    362 
    363         this.store.currentPage = 1;
    364         this.store.load({
    365             start: 0
    366         });
    367     },
    368     /**
    369      * Handles editing of the grid fields. Right now, only the stock level editing is supported.
    370      *
    371      * @param editor Not used
    372      * @param e An edit event, as documented in
    373      *            http://docs.sencha.com/ext-js/4-0/#!/api/Ext.grid.plugin.CellEditing-event-edit
    374      */
    375     onEdit: function (editor, e)
    376     {
    377         switch (e.field) {
    378             case "stockLevel":
    379                 if (e.value !== e.originalValue.toString()) {
    380                     this.handleStockFieldEdit(e);
    381                 }
    382                 break;
    383             default:
    384                 break;
    385         }
    386     },
    387     addParameterFilter: function () {
    388         this.addFilterWindow = Ext.create("PartKeepr.Components.Widgets.PartParameterSearchWindow", {
    389 
    390             sourceModel: this.getStore().getModel(),
    391             listeners: {
    392                 "apply": this.onAddParameterFilter,
    393                 scope: this
    394             }
    395         });
    396 
    397         this.addFilterWindow.show();
    398     },
    399     /**
    400      * @todo Refactor this function as well as the one in DataApplicator to a single central function
    401      *
    402      * Note that this function takes the input and multiplies it by the si prefix
    403      * @param value
    404      * @param siPrefix
    405      * @returns {*}
    406      */
    407     applySiPrefix: function (value, siPrefix)
    408     {
    409         if (siPrefix instanceof PartKeepr.SiPrefixBundle.Entity.SiPrefix) {
    410             var fractionValue = value * Math.pow(siPrefix.get("base"), siPrefix.get("exponent"));
    411 
    412             if (siPrefix.get("exponent") < 0)
    413             {
    414                 return fractionValue.toFixed(Math.abs(siPrefix.get("exponent")));
    415             } else
    416             {
    417                 return fractionValue;
    418             }
    419         } else {
    420             return value;
    421         }
    422     },
    423     onAddParameterFilter: function (rec) {
    424         var subFilters = [];
    425 
    426 
    427         subFilters.push( Ext.create("PartKeepr.util.Filter", {
    428             property: "parameters.name",
    429             operator: "=",
    430             value: rec.get("partParameterName")
    431         }));
    432 
    433         var value;
    434 
    435         if (rec.get("valueType") === "numeric") {
    436             value = this.applySiPrefix(rec.get("value"), rec.getSiPrefix());
    437 
    438             subFilters.push( Ext.create("PartKeepr.util.Filter", {
    439                 property: "parameters.normalizedValue",
    440                 operator: rec.get("operator"),
    441                 value: value
    442             }));
    443         } else {
    444             value = rec.get("stringValue");
    445 
    446             subFilters.push( Ext.create("PartKeepr.util.Filter", {
    447                 property: "parameters.stringValue",
    448                 operator: rec.get("operator"),
    449                 value: value
    450             }));
    451         }
    452 
    453 
    454         var filter = Ext.create("PartKeepr.util.Filter", {
    455             type: "AND",
    456             subfilters: subFilters
    457         });
    458         this.getStore().addFilter(filter);
    459     },
    460 
    461     /**
    462      * Handles the editing of the stock level field. Checks if the user has opted in to skip the
    463      * online stock edit confirm window, and runs the changes afterwards.
    464      *
    465      * @param e An edit event, as documented in
    466      *            http://docs.sencha.com/ext-js/4-0/#!/api/Ext.grid.plugin.CellEditing-event-edit
    467      */
    468     handleStockFieldEdit: function (e)
    469     {
    470         if (PartKeepr.getApplication().getUserPreference("partkeepr.inline-stock-change.confirm", true) === false) {
    471             this.handleStockChange(e);
    472         } else {
    473             this.confirmStockChange(e);
    474         }
    475     },
    476     getStockChangeMode: function (value)
    477     {
    478         var n = value.indexOf("+");
    479 
    480         if (n !== -1) {
    481             return "addition";
    482         }
    483 
    484         n = value.indexOf("-");
    485 
    486         if (n !== -1) {
    487             return "removal";
    488         }
    489 
    490         return "fixed";
    491     },
    492     /**
    493      * Opens the confirm dialog
    494      *
    495      * @param e An edit event, as documented in
    496      *            http://docs.sencha.com/ext-js/4-0/#!/api/Ext.grid.plugin.CellEditing-event-edit
    497      */
    498     confirmStockChange: function (e)
    499     {
    500         var mode = this.getStockChangeMode(e.value);
    501         var value = Math.abs(parseInt(e.value));
    502         var confirmText = "";
    503         var headerText = "";
    504 
    505         switch (mode) {
    506             case "removal":
    507                 confirmText = sprintf(
    508                     i18n("You wish to remove <b>%s %s</b> of the part <b>%s</b>. Is this correct?"),
    509                     value, e.record.getPartUnit().get("name"), e.record.get("name"));
    510 
    511                 // Set the stock level to a temporary calculated value.
    512                 e.record.set("stockLevel", (e.originalValue - value));
    513                 headerText = i18n("Remove Part(s)");
    514                 break;
    515             case "addition":
    516                 confirmText = sprintf(
    517                     i18n("You wish to add  <b>%s %s</b> of part <b>%s</b>. Is this correct?"),
    518                     value, e.record.getPartUnit().get("name"), e.record.get("name"));
    519 
    520                 e.record.set("stockLevel", (e.originalValue + value));
    521                 headerText = i18n("Add Part(s)");
    522                 break;
    523             case "fixed":
    524                 confirmText = sprintf(
    525                     i18n("You wish to set the stock level to <b>%s %s</b> for part <b>%s</b>. Is this correct?"),
    526                     value, e.record.getPartUnit().get("name"), e.record.get("name"));
    527 
    528                 e.record.set("stockLevel", value);
    529                 headerText = i18n("Set Stock Level for Part(s)");
    530                 break;
    531         }
    532 
    533 
    534         var j = Ext.create("PartKeepr.RememberChoiceMessageBox", {
    535             escButtonAction: "cancel",
    536             dontAskAgainProperty: "partkeepr.inline-stock-change.confirm",
    537             dontAskAgainValue: false
    538         });
    539 
    540         j.show({
    541             title: headerText,
    542             msg: confirmText,
    543             buttons: Ext.Msg.OKCANCEL,
    544             fn: this.afterConfirmStockChange,
    545             scope: this,
    546             originalOnEdit: e,
    547             dialog: j
    548         });
    549     },
    550     /**
    551      * Callback for the stock removal confirm window.
    552      *
    553      * The parameters are documented on:
    554      * http://docs.sencha.com/ext-js/4-0/#!/api/Ext.window.MessageBox-method-show
    555      */
    556     afterConfirmStockChange: function (buttonId, text, opts)
    557     {
    558         if (buttonId === "cancel") {
    559             opts.originalOnEdit.record.set("stockLevel", opts.originalOnEdit.originalValue);
    560             return;
    561         }
    562 
    563         this.handleStockChange(opts.originalOnEdit);
    564     },
    565     /**
    566      * Handles the stock change. Automatically figures out which method to call (deleteStock or addStock) and
    567      * sets the correct quantity.
    568      *
    569      * @param e An edit event, as documented in
    570      *            http://docs.sencha.com/ext-js/4-0/#!/api/Ext.grid.plugin.CellEditing-event-edit
    571      */
    572     handleStockChange: function (e)
    573     {
    574         var mode = this.getStockChangeMode(e.value);
    575         var value = Math.abs(parseInt(e.value));
    576         var call;
    577 
    578         if (e.value === 0) {
    579             return;
    580         }
    581 
    582         switch (mode) {
    583             case "removal":
    584                 call = "removeStock";
    585                 break;
    586             case "addition":
    587                 call = "addStock";
    588                 break;
    589             case "fixed":
    590                 call = "setStock";
    591                 break;
    592             default:
    593                 return;
    594         }
    595 
    596         e.record.callPutAction(call, {
    597             quantity: value
    598         }, Ext.bind(this.reloadPart, this, [e]));
    599     },
    600     /**
    601      * Reloads the current part
    602      */
    603     reloadPart: function (opts)
    604     {
    605         this.loadPart(opts.record.getId(), opts);
    606     },
    607     /**
    608      * Load the part from the database.
    609      */
    610     loadPart: function (id)
    611     {
    612         PartKeepr.PartBundle.Entity.Part.load(id, {
    613             scope: this,
    614             success: this.onPartLoaded
    615         });
    616     },
    617     /**
    618      * Callback after the part is loaded
    619      */
    620     onPartLoaded: function (record)
    621     {
    622         var rec = this.store.findRecord("id", record.getId());
    623         if (rec) {
    624             rec.set("stockLevel", record.get("stockLevel"));
    625         }
    626     }
    627 });