partkeepr

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

commit 1c42293e9ea31654cb856b2f99c0309d2e22ac10
parent 00dd50296ae9461e48d310055e567d117cf0f514
Author: Felicitus <felicitus@felicitus.org>
Date:   Fri,  2 Oct 2015 20:53:51 +0200

Migrated parts filter and added helper fields for querying the data

Diffstat:
Asrc/PartKeepr/CoreBundle/DoctrineMigrations/Version20151002183125.php | 41+++++++++++++++++++++++++++++++++++++++++
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/PartFilterPanel.js | 938+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/PartsGrid.js | 19++++++++++---------
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/PartsManager.js | 58++++++++++++++++++++++++++++++++++++----------------------
Msrc/PartKeepr/FrontendBundle/Resources/public/js/form/field/SearchField.js | 73++++++++++++++++++++++++++++++++++++++-----------------------------------
Msrc/PartKeepr/PartBundle/Entity/Part.php | 374+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/PartKeepr/PartBundle/Listeners/StockLevelListener.php | 8+-------
7 files changed, 866 insertions(+), 645 deletions(-)

diff --git a/src/PartKeepr/CoreBundle/DoctrineMigrations/Version20151002183125.php b/src/PartKeepr/CoreBundle/DoctrineMigrations/Version20151002183125.php @@ -0,0 +1,41 @@ +<?php + +namespace PartKeepr\CoreBundle\DoctrineMigrations; + +use Doctrine\DBAL\Schema\Schema; + +/** + * Re-saves all parts in order to re-generate the averagePrice and removals field + */ +class Version20151002183125 extends BaseMigration +{ + /** + * @param Schema $schema + */ + public function up(Schema $schema) + { + $this->performDatabaseUpgrade(); + + $userProviderRepository = $this->getEM()->getRepository( + 'PartKeeprPartBundle:Part' + ); + + $parts = $userProviderRepository->findAll(); + + foreach ($parts as $part) { + $part->recomputeStockLevels(); + } + + + $this->getEM()->flush(); + } + + /** + * @param Schema $schema + */ + public function down(Schema $schema) + { + // this down() migration is auto-generated, please modify it to your needs + + } +} diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/PartFilterPanel.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/PartFilterPanel.js @@ -1,346 +1,392 @@ /** * Defines the part filter panel. - * - * + * + * */ Ext.define('PartKeepr.PartFilterPanel', { - extend: 'Ext.form.Panel', - alias: 'widget.PartFilterPanel', - - /** - * Define a padding of 10px - */ - bodyPadding: '10px', - - /** - * The items are aligned in a wrappable column layout - */ - layout: 'column', - - /** - * Automatically scroll the container if the items exceed the container size. - */ - autoScroll: true, - - /** - * Fixed body background color style - */ - bodyStyle: 'background:#DBDBDB;', - - /** - * Initializes the component - */ - initComponent: function () { - - // Create the filter fields - this.createFilterFields(); - - // Creates the left column of the filter panel - this.leftColumn = { - xtype: 'container', - anchor: '100%', - layout: 'anchor', - minWidth: 340, - style: 'margin-right: 10px', - columnWidth: 0.5, - items: [ - this.storageLocationContainer, - this.categoryFilter, - this.partsWithoutPrice, - this.createDateFilter, - this.partsWithoutStockRemovals, - this.needsReview - ] - }; - - // Creates the right column of the filter panel - this.rightColumn = { - xtype: 'container', - anchor: '100%', - minWidth: 340, - columnWidth: 0.5, - layout: 'anchor', - items: [ - this.stockFilter, - this.distributorOrderNumberFilter, - this.distributorFilter, - this.manufacturerFilter, - this.footprintFilter, - this.statusFilter, - this.conditionFilter - ] - }; - - // Apply both columns to this panel - this.items = [ this.leftColumn, this.rightColumn ]; - - // Create the reset button - this.resetButton = Ext.create("Ext.button.Button", { - text: i18n("Reset"), - handler: this.onReset, - iconCls: 'web-icon cancel', - scope: this - }); - - // Create the apply button - this.applyButton = Ext.create("Ext.button.Button", { - text: i18n("Apply"), - iconCls: 'web-icon accept', - handler: this.onApply, - scope: this - }); - - // Append both buttons to a toolbar - this.dockedItems = [{ - xtype: 'toolbar', - enableOverflow: true, - dock: 'bottom', - defaults: {minWidth: 100}, - items: [ this.applyButton, this.resetButton ] - }]; - - this.callParent(); - }, - /** - * Applies the parameters from the filter panel to the proxy, then - * reload the store to refresh the grid. - * - * @param none - * @return nothing - */ - onApply: function () { - this.applyFilterParameters(this.store.getProxy().extraParams); - this.store.currentPage = 1; - this.store.load({ start: 0}); - }, - /** - * Resets the fields to their original values, then call onApply() - * to reload the store. - */ - onReset: function () { - this.storageLocationFilter.setValue(""); - this.storageLocationFilterCheckbox.setValue(false); - - this.categoryFilter.setValue({ category: 'all'}); - this.stockFilter.setValue({ stock: 'any'}); - this.distributorOrderNumberFilter.setValue(""); - - this.createDateFilterSelect.setValue(""); - this.createDateField.setValue(""); - this.partsWithoutStockRemovals.setValue(false); - this.needsReview.setValue(false); - this.partsWithoutPrice.setValue(false); - - this.distributorFilterCombo.setValue(""); - this.distributorFilterCheckbox.setValue(false); - - this.manufacturerFilterCombo.setValue(""); - this.manufacturerFilterCheckbox.setValue(false); + extend: 'Ext.form.Panel', + alias: 'widget.PartFilterPanel', + + /** + * Define a padding of 10px + */ + bodyPadding: '10px', + + /** + * The items are aligned in a wrappable column layout + */ + layout: 'column', + + /** + * Automatically scroll the container if the items exceed the container size. + */ + autoScroll: true, + + /** + * Fixed body background color style + */ + bodyStyle: 'background:#DBDBDB;', + + partManager: null, + storageLocationFilter: null, + storageLocationFilterCheckbox: null, + storageLocationContainer: null, + categoryFilter: null, + stockFilter: null, + partsWithoutPrice: null, + distributorOrderNumberFilter: null, + createDateField: null, + createDateFilterSelect: null, + createDateFilter: null, + partsWithoutStockRemovals: null, + needsReview: null, + manufacturerFilterCheckbox: null, + manufacturerFilterCombo: null, + manufacturerFilter: null, + distributorFilterCombo: null, + distributorFilter: null, + footprintFilterCheckbox: null, + footprintFilterCombo: null, + footprintFilter: null, + statusFilter: null, + conditionFilter: null, + + /** + * Initializes the component + */ + initComponent: function () + { + + // Create the filter fields + this.createFilterFields(); + + // Creates the left column of the filter panel + this.leftColumn = { + xtype: 'container', + anchor: '100%', + layout: 'anchor', + minWidth: 340, + style: 'margin-right: 10px', + columnWidth: 0.5, + items: [ + this.storageLocationContainer, + this.categoryFilter, + this.partsWithoutPrice, + this.createDateFilter, + this.partsWithoutStockRemovals, + this.needsReview + ] + }; + + // Creates the right column of the filter panel + this.rightColumn = { + xtype: 'container', + anchor: '100%', + minWidth: 340, + columnWidth: 0.5, + layout: 'anchor', + items: [ + this.stockFilter, + this.distributorOrderNumberFilter, + this.distributorFilter, + this.manufacturerFilter, + this.footprintFilter, + this.statusFilter, + this.conditionFilter + ] + }; + + // Apply both columns to this panel + this.items = [this.leftColumn, this.rightColumn]; + + // Create the reset button + this.resetButton = Ext.create("Ext.button.Button", { + text: i18n("Reset"), + handler: this.onReset, + iconCls: 'web-icon cancel', + scope: this + }); + + // Create the apply button + this.applyButton = Ext.create("Ext.button.Button", { + text: i18n("Apply"), + iconCls: 'web-icon accept', + handler: this.onApply, + scope: this + }); + + // Append both buttons to a toolbar + this.dockedItems = [ + { + xtype: 'toolbar', + enableOverflow: true, + dock: 'bottom', + defaults: {minWidth: 100}, + items: [this.applyButton, this.resetButton] + } + ]; + + this.callParent(); + }, + /** + * Applies the parameters from the filter panel to the proxy, then + * reload the store to refresh the grid. + * + * @param none + * @return nothing + */ + onApply: function () + { + var filters = this.getFilters(); + + this.store.clearFilter(true); + + if (filters.length !== 0) { + this.store.addFilter(this.getFilters(), true); + } + + this.store.load(); + }, + /** + * Resets the fields to their original values, then call onApply() + * to reload the store. + */ + onReset: function () + { + this.storageLocationFilter.setValue(""); + this.storageLocationFilterCheckbox.setValue(false); + + this.categoryFilter.setValue({category: 'all'}); + this.stockFilter.setValue({stock: 'any'}); + this.distributorOrderNumberFilter.setValue(""); + + this.createDateFilterSelect.setValue(""); + this.createDateField.setValue(""); + this.partsWithoutStockRemovals.setValue(false); + this.needsReview.setValue(false); + this.partsWithoutPrice.setValue(false); + + this.distributorFilterCombo.setValue(""); + this.distributorFilterCheckbox.setValue(false); + + this.manufacturerFilterCombo.setValue(""); + this.manufacturerFilterCheckbox.setValue(false); this.footprintFilterCombo.setValue(""); this.footprintFilterCheckbox.setValue(false); - - this.statusFilter.setValue(""); - - this.conditionFilter.setValue(""); - - this.onApply(); - }, - /** - * Creates the filter fields required for this filter panel - */ - createFilterFields: function () { - - // Create the storage location filter field - this.storageLocationFilter = Ext.create("PartKeepr.StorageLocationComboBox", { - flex: 1, - forceSelection: true, - listeners: { - select: function () { - this.storageLocationFilterCheckbox.setValue(true); - }, - scope: this - } - }); - - this.storageLocationFilterCheckbox = Ext.create("Ext.form.field.Checkbox", { - width: "20px", - listeners: { - change: function (obj, value) { - - if (!value) { - this.storageLocationFilter.setValue(""); - } - }, - scope: this - } - }); - - this.storageLocationContainer = Ext.create("Ext.form.FieldContainer", { - layout: 'hbox', - items: [ this.storageLocationFilterCheckbox, this.storageLocationFilter ], - anchor: '100%', - minWidth: 300, - fieldLabel: i18n("Storage Location") - }); - - // Create the category scope field - this.categoryFilter = Ext.create("Ext.form.RadioGroup", { - fieldLabel: i18n("Category Scope"), - columns: 1, - items: [{ - boxLabel: i18n("All Subcategories"), - name: 'category', - inputValue: "all", - checked: true - }, - { - boxLabel: i18n("Selected Category"), - name: 'category', - inputValue: "selected" - }] - }); - - // Create the stock level filter field - this.stockFilter = Ext.create("Ext.form.RadioGroup", { - fieldLabel: i18n("Stock Mode"), - columns: 1, - items: [{ - boxLabel: i18n("Any Stock Level"), - name: 'stock', - inputValue: "any", - checked: true - },{ - boxLabel: i18n("Stock Level = 0"), - name: 'stock', - inputValue: "zero" - },{ - boxLabel: i18n("Stock Level > 0"), - name: 'stock', - inputValue: "nonzero" - },{ - boxLabel: i18n("Stock Level < Minimum Stock Level"), - name: 'stock', - inputValue: "below" - }] - }); - - this.partsWithoutPrice = Ext.create("Ext.form.field.Checkbox", { - fieldLabel: i18n("Item Price"), - boxLabel: i18n("Show Parts without Price only") - }); - - this.distributorOrderNumberFilter = Ext.create("Ext.form.field.Text", { - fieldLabel: i18n("Order Number"), - anchor: '100%' - }); - - this.createDateField = Ext.create("Ext.form.field.Date", { - flex: 1 - }); - - var filter = Ext.create('Ext.data.Store', { - fields: ['type', 'name'], - data : [ - {"type":"<", "name":"before"}, - {"type":">", "name":"after"}, - {"type":"=", "name":"on"}, - {"type":"", "name": "- none -"} - ] - }); - - this.createDateFilterSelect = Ext.create('Ext.form.ComboBox', { - store: filter, - queryMode: 'local', - forceSelection: true, - editable: false, - width: 60, - value: '', - triggerAction: 'all', - displayField: 'name', - valueField: 'type' - }); - - this.createDateFilter = { - xtype: 'fieldcontainer', - anchor: '100%', - fieldLabel: i18n("Create date"), - layout: 'hbox', - border: false, - items: [ this.createDateFilterSelect, this.createDateField ] - }; - - this.partsWithoutStockRemovals = Ext.create("Ext.form.field.Checkbox", { - fieldLabel: i18n("Stock Settings"), - boxLabel: i18n("Show Parts without stock removals only") - }); - - this.needsReview = Ext.create("Ext.form.field.Checkbox", { - fieldLabel: i18n("Needs Review"), - boxLabel: i18n("Show Parts that need to reviewed only") - }); - - this.manufacturerFilterCheckbox = Ext.create("Ext.form.field.Checkbox", { - width: "20px", - listeners: { - change: function (obj, value) { - - if (!value) { - this.manufacturerFilterCombo.setValue(""); - } - }, - scope: this - } - }); - - this.manufacturerFilterCombo = Ext.create("PartKeepr.ManufacturerComboBox", { - flex: 1, - listeners: { - select: function () { - this.manufacturerFilterCheckbox.setValue(true); - }, - scope: this - } - }); - - this.manufacturerFilter = Ext.create("Ext.form.FieldContainer", { - layout: 'hbox', - items: [ this.manufacturerFilterCheckbox, this.manufacturerFilterCombo ], - fieldLabel: i18n("Manufacturer") - }); - - this.distributorFilterCheckbox = Ext.create("Ext.form.field.Checkbox", { - width: "20px", - listeners: { - change: function (obj, value) { - if (!value) { - this.distributorFilterCombo.setValue(""); - } - }, - scope: this - } - }); - - this.distributorFilterCombo = Ext.create("PartKeepr.DistributorComboBox",{ - flex: 1, - listeners: { - select: function () { - this.distributorFilterCheckbox.setValue(true); - }, - scope: this - } - }); - - this.distributorFilter = Ext.create("Ext.form.FieldContainer", { - layout: 'hbox', - items: [ this.distributorFilterCheckbox, this.distributorFilterCombo ], - fieldLabel: i18n("Distributor") - }); + + this.statusFilter.setValue(""); + + this.conditionFilter.setValue(""); + + this.onApply(); + }, + /** + * Creates the filter fields required for this filter panel + */ + createFilterFields: function () + { + + // Create the storage location filter field + this.storageLocationFilter = Ext.create("PartKeepr.StorageLocationComboBox", { + flex: 1, + forceSelection: true, + listeners: { + select: function () + { + this.storageLocationFilterCheckbox.setValue(true); + }, + scope: this + } + }); + + this.storageLocationFilterCheckbox = Ext.create("Ext.form.field.Checkbox", { + width: "20px", + listeners: { + change: function (obj, value) + { + + if (!value) { + this.storageLocationFilter.setValue(""); + } + }, + scope: this + } + }); + + this.storageLocationContainer = Ext.create("Ext.form.FieldContainer", { + layout: 'hbox', + items: [this.storageLocationFilterCheckbox, this.storageLocationFilter], + anchor: '100%', + minWidth: 300, + fieldLabel: i18n("Storage Location") + }); + + // Create the category scope field + this.categoryFilter = Ext.create("Ext.form.RadioGroup", { + fieldLabel: i18n("Category Scope"), + columns: 1, + items: [ + { + boxLabel: i18n("All Subcategories"), + name: 'category', + inputValue: "all", + checked: true + }, + { + boxLabel: i18n("Selected Category"), + name: 'category', + inputValue: "selected" + } + ] + }); + + // Create the stock level filter field + this.stockFilter = Ext.create("Ext.form.RadioGroup", { + fieldLabel: i18n("Stock Mode"), + columns: 1, + items: [ + { + boxLabel: i18n("Any Stock Level"), + name: 'stock', + inputValue: "any", + checked: true + }, { + boxLabel: i18n("Stock Level = 0"), + name: 'stock', + inputValue: "zero" + }, { + boxLabel: i18n("Stock Level > 0"), + name: 'stock', + inputValue: "nonzero" + }, { + boxLabel: i18n("Stock Level < Minimum Stock Level"), + name: 'stock', + inputValue: "below" + } + ] + }); + + this.partsWithoutPrice = Ext.create("Ext.form.field.Checkbox", { + fieldLabel: i18n("Item Price"), + boxLabel: i18n("Show Parts without Price only") + }); + + this.distributorOrderNumberFilter = Ext.create("Ext.form.field.Text", { + fieldLabel: i18n("Order Number"), + anchor: '100%' + }); + + this.createDateField = Ext.create("Ext.form.field.Date", { + flex: 1 + }); + + var filter = Ext.create('Ext.data.Store', { + fields: ['type', 'name'], + data: [ + {"type": "<", "name": "before"}, + {"type": ">", "name": "after"}, + {"type": "", "name": "- none -"} + ] + }); + + this.createDateFilterSelect = Ext.create('Ext.form.ComboBox', { + store: filter, + queryMode: 'local', + forceSelection: true, + editable: false, + width: 60, + value: '', + triggerAction: 'all', + displayField: 'name', + valueField: 'type' + }); + + this.createDateFilter = { + xtype: 'fieldcontainer', + anchor: '100%', + fieldLabel: i18n("Create date"), + layout: 'hbox', + border: false, + items: [this.createDateFilterSelect, this.createDateField] + }; + + this.partsWithoutStockRemovals = Ext.create("Ext.form.field.Checkbox", { + fieldLabel: i18n("Stock Settings"), + boxLabel: i18n("Show Parts without stock removals only") + }); + + this.needsReview = Ext.create("Ext.form.field.Checkbox", { + fieldLabel: i18n("Needs Review"), + boxLabel: i18n("Show Parts that need to reviewed only") + }); + + this.manufacturerFilterCheckbox = Ext.create("Ext.form.field.Checkbox", { + width: "20px", + listeners: { + change: function (obj, value) + { + + if (!value) { + this.manufacturerFilterCombo.setValue(""); + } + }, + scope: this + } + }); + + this.manufacturerFilterCombo = Ext.create("PartKeepr.ManufacturerComboBox", { + flex: 1, + listeners: { + select: function () + { + this.manufacturerFilterCheckbox.setValue(true); + }, + scope: this + } + }); + + this.manufacturerFilter = Ext.create("Ext.form.FieldContainer", { + layout: 'hbox', + items: [this.manufacturerFilterCheckbox, this.manufacturerFilterCombo], + fieldLabel: i18n("Manufacturer") + }); + + this.distributorFilterCheckbox = Ext.create("Ext.form.field.Checkbox", { + width: "20px", + listeners: { + change: function (obj, value) + { + if (!value) { + this.distributorFilterCombo.setValue(""); + } + }, + scope: this + } + }); + + this.distributorFilterCombo = Ext.create("PartKeepr.DistributorComboBox", { + flex: 1, + listeners: { + select: function () + { + this.distributorFilterCheckbox.setValue(true); + }, + scope: this + } + }); + + this.distributorFilter = Ext.create("Ext.form.FieldContainer", { + layout: 'hbox', + items: [this.distributorFilterCheckbox, this.distributorFilterCombo], + fieldLabel: i18n("Distributor") + }); this.footprintFilterCheckbox = Ext.create("Ext.form.field.Checkbox", { width: "20px", listeners: { - change: function (obj, value) { + change: function (obj, value) + { if (!value) { this.footprintFilterCombo.setValue(""); } @@ -349,10 +395,11 @@ Ext.define('PartKeepr.PartFilterPanel', { } }); - this.footprintFilterCombo = Ext.create("PartKeepr.FootprintComboBox", { + this.footprintFilterCombo = Ext.create("PartKeepr.FootprintComboBox", { flex: 1, listeners: { - select: function () { + select: function () + { this.footprintFilterCheckbox.setValue(true); }, scope: this @@ -361,76 +408,163 @@ Ext.define('PartKeepr.PartFilterPanel', { this.footprintFilter = Ext.create("Ext.form.FieldContainer", { layout: 'hbox', - items: [ this.footprintFilterCheckbox, this.footprintFilterCombo ], + items: [this.footprintFilterCheckbox, this.footprintFilterCombo], fieldLabel: i18n("Footprint") }); - - /** **/ - - this.statusFilter = Ext.create("Ext.form.field.Text", { - fieldLabel: i18n("Status"), - anchor: '100%' - }); - - this.conditionFilter = Ext.create("Ext.form.field.Text", { - fieldLabel: i18n("Condition"), - anchor: '100%' - }); - - }, - /** - * Applies the filter parameters to the passed extraParams object. - * @param extraParams An object containing the extraParams from a proxy. - */ - applyFilterParameters: function (extraParams) { - extraParams.withoutPrice = this.partsWithoutPrice.getValue(); - extraParams.categoryScope = this.categoryFilter.getValue().category; - extraParams.stockMode = this.stockFilter.getValue().stock; - extraParams.distributorOrderNumber = this.distributorOrderNumberFilter.getValue(); - /** - * Get the raw (=text) value. I really wish that ExtJS would handle selected values (from a store) - * distinct than entered values. - */ - if (this.storageLocationFilter.getRawValue() !== "") { - extraParams.storageLocation = this.storageLocationFilter.getValue(); - } else { - delete extraParams.storageLocation; - } - - if (this.manufacturerFilterCombo.getRawValue() !== "") { - extraParams.manufacturer = this.manufacturerFilterCombo.getValue(); - } else { - delete extraParams.manufacturer; - } - - if (this.distributorFilterCombo.getRawValue() !== "") { - extraParams.distributor = this.distributorFilterCombo.getValue(); - } else { - delete extraParams.distributor; - } - - if (this.footprintFilterCombo.getRawValue() !== "") { - extraParams.footprint = this.footprintFilterCombo.getValue(); + + /** **/ + + this.statusFilter = Ext.create("Ext.form.field.Text", { + fieldLabel: i18n("Status"), + anchor: '100%' + }); + + this.conditionFilter = Ext.create("Ext.form.field.Text", { + fieldLabel: i18n("Condition"), + anchor: '100%' + }); + + }, + /** + * Applies the filter parameters to the passed extraParams object. + * @param extraParams An object containing the extraParams from a proxy. + */ + getFilters: function () + { + var filters = []; + + if (this.storageLocationFilterCheckbox.getValue() === true) { + filters.push(Ext.create("Ext.util.Filter", { + property: 'storageLocation', + operator: "=", + value: this.storageLocationFilter.getValue() + })); + } + + if (this.categoryFilter.getValue().category === "all") { + if (this.partManager.getSelectedCategory() !== null) { + filters.push(Ext.create("Ext.util.Filter", { + id: 'categoryFilter', + property: 'category', + operator: 'IN', + value: this.partManager.getChildrenIds(this.partManager.getSelectedCategory()) + })); + } } else { - delete extraParams.footprint; + filters.push(Ext.create("Ext.util.Filter", { + id: 'categoryFilter', + property: 'category', + operator: '=', + value: this.partManager.getSelectedCategory() + })); + } + + if (this.partsWithoutPrice.getValue() === true) { + filters.push(Ext.create("Ext.util.Filter", { + property: 'averagePrice', + operator: '=', + value: 0 + })); + } + + if (this.createDateFilterSelect.getValue() !== "") { + filters.push(Ext.create("Ext.util.Filter", { + property: 'createDate', + operator: this.createDateFilterSelect.getValue(), + value: this.createDateField.getValue() + })); + } + + if (this.partsWithoutStockRemovals.getValue() === true) { + filters.push(Ext.create("Ext.util.Filter", { + property: 'removals', + operator: "=", + value: false + })); + } + + if (this.needsReview.getValue() === true) { + filters.push(Ext.create("Ext.util.Filter", { + property: 'needsReview', + operator: "=", + value: true + })); + } + + + if (this.stockFilter.getValue().stock !== "any") { + switch (this.stockFilter.getValue().stock) { + case "zero": + filters.push(Ext.create("Ext.util.Filter", { + property: 'stockLevel', + operator: "=", + value: 0 + })); + break; + case "nonzero": + filters.push(Ext.create("Ext.util.Filter", { + property: 'stockLevel', + operator: ">", + value: 0 + })); + break; + case "below": + filters.push(Ext.create("Ext.util.Filter", { + property: 'lowStock', + operator: "=", + value: true + })); + break; + } + } + + if (this.distributorOrderNumberFilter.getValue() !== "") { + filters.push(Ext.create("Ext.util.Filter", { + property: 'distributors.orderNumber', + operator: "LIKE", + value: "%" + this.distributorOrderNumberFilter.getValue() + "%" + })); + } + + if (this.distributorFilterCheckbox.getValue() === true) { + filters.push(Ext.create("Ext.util.Filter", { + property: 'distributors.distributor', + operator: "=", + value: this.distributorFilterCombo.getValue() + })); + } + + if (this.manufacturerFilterCheckbox.getValue() === true) { + filters.push(Ext.create("Ext.util.Filter", { + property: 'manufacturers.manufacturer', + operator: "=", + value: this.manufacturerFilterCombo.getValue() + })); + } + + if (this.footprintFilterCheckbox.getValue() === true) { + filters.push(Ext.create("Ext.util.Filter", { + property: 'footprint', + operator: "=", + value: this.footprintFilterCombo.getValue() + })); + } + + if (this.statusFilter.getValue() !== "") { + filters.push(Ext.create("Ext.util.Filter", { + property: 'status', + operator: "LIKE", + value: "%" + this.statusFilter.getValue() + "%" + })); + } + + if (this.conditionFilter.getValue() !== "") { + filters.push(Ext.create("Ext.util.Filter", { + property: 'condition', + operator: "LIKE", + value: "%" + this.conditionFilter.getValue() + "%" + })); } - - extraParams.status = this.statusFilter.getValue(); - extraParams.condition = this.conditionFilter.getValue(); - - - extraParams.createDateRestriction = this.createDateFilterSelect.getValue(); - var createDate = Ext.util.Format.date(this.createDateField.getValue(), "Y-m-d H:i:s"); - - if (createDate !== "") { - extraParams.createDate = createDate; - } else { - delete extraParams.createDate; - } - - extraParams.withoutStockRemovals = this.partsWithoutStockRemovals.getValue(); - - extraParams.needsReview = this.needsReview.getValue(); - } - + return filters; + } }); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/PartsGrid.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/PartsGrid.js @@ -57,6 +57,7 @@ Ext.define('PartKeepr.PartsGrid', { multiSelect: true, autoScroll: false, invalidateScrollerOnRefresh: true, + titleProperty: 'name', initComponent: function () { @@ -175,7 +176,7 @@ Ext.define('PartKeepr.PartsGrid', { key: 'x', ctrl: false, alt: true, - fn: function (e) + fn: function () { var searchBox = this.searchField; if (Ext.get(document).activeElement != searchBox) { @@ -190,7 +191,7 @@ Ext.define('PartKeepr.PartsGrid', { /** * Called when an item was selected. Enables/disables the delete button. */ - _updateAddTemplateButton: function (selectionModel, record) + _updateAddTemplateButton: function () { /* Right now, we support delete on a single record only */ if (this.getSelectionModel().getCount() == 1) { @@ -446,9 +447,6 @@ Ext.define('PartKeepr.PartsGrid', { var confirmText = ""; var headerText = ""; - var n = e.value.indexOf("+"); - - switch (mode) { case "removal": confirmText = sprintf( @@ -520,8 +518,9 @@ Ext.define('PartKeepr.PartsGrid', { { var mode = this.getStockChangeMode(e.value); var value = Math.abs(parseInt(e.value)); + var call; - if (e.value == 0) { + if (e.value === 0) { return; } @@ -535,10 +534,12 @@ Ext.define('PartKeepr.PartsGrid', { case "fixed": call = "setStock"; break; + default: + return; } e.record.callPutAction(call, { - quantity: e.value + quantity: value }, Ext.bind(this.reloadPart, this, [e])); }, /** @@ -551,7 +552,7 @@ Ext.define('PartKeepr.PartsGrid', { /** * Load the part from the database. */ - loadPart: function (id, opts) + loadPart: function (id) { PartKeepr.PartBundle.Entity.Part.load(id, { scope: this, @@ -561,7 +562,7 @@ Ext.define('PartKeepr.PartsGrid', { /** * Callback after the part is loaded */ - onPartLoaded: function (record, opts) + onPartLoaded: function (record) { var rec = this.store.findRecord("id", record.getId()); if (rec) { diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/PartsManager.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Part/PartsManager.js @@ -22,6 +22,8 @@ Ext.define('PartKeepr.PartManager', { */ compactLayout: false, + selectedCategory: null, + initComponent: function () { @@ -33,6 +35,10 @@ Ext.define('PartKeepr.PartManager', { groupField: 'categoryPath', sorters: [ { + property: 'category.categoryPath', + direction: 'ASC' + }, + { property: 'name', direction: 'ASC' } @@ -114,7 +120,8 @@ Ext.define('PartKeepr.PartManager', { split: true, collapsed: true, collapsible: true, - store: this.store + store: this.store, + partManager: this }); if (this.compactLayout) { @@ -162,7 +169,10 @@ Ext.define('PartKeepr.PartManager', { */ onCategoryClick: function (tree, record) { + this.selectedCategory = record; + var filter = Ext.create("Ext.util.Filter", { + id: 'categoryFilter', property: 'category', operator: 'IN', value: this.getChildrenIds(record) @@ -170,6 +180,10 @@ Ext.define('PartKeepr.PartManager', { this.store.addFilter(filter); }, + getSelectedCategory: function () + { + return this.selectedCategory; + }, /** * Returns the ID for this node and all child nodes * @@ -277,7 +291,7 @@ Ext.define('PartKeepr.PartManager', { { var data = rec.getData(); - newItem = Ext.create("PartKeepr.PartBundle.Entity.Part"); + var newItem = Ext.create("PartKeepr.PartBundle.Entity.Part"); newItem.set(data); newItem.setAssociationData(rec.getAssociationData()); @@ -322,7 +336,7 @@ Ext.define('PartKeepr.PartManager', { defaults.partUnit = defaultPartUnit.getId(); defaults.category = this.grid.currentCategory; - record = Ext.create("PartKeepr.PartBundle.Entity.Part", defaults); + var record = Ext.create("PartKeepr.PartBundle.Entity.Part", defaults); // Inject the defaults to the editor, so the editor can create a new item on its own j.editor.partDefaults = defaults; @@ -351,25 +365,25 @@ Ext.define('PartKeepr.PartManager', { */ onItemSelect: function () { - if (this.grid.getSelection().length > 1) { - this.detailPanel.collapse(); - this.tree.syncButton.disable(); - } else { - if (this.grid.getSelection().length == 1) { - var selection = this.grid.getSelection(); - - var r = selection[0]; - - this.detailPanel.setActiveTab(this.detail); - this.detailPanel.expand(); - this.detail.setValues(r); - this.stockLevel.part = r.getId(); - - this.tree.syncButton.enable(); - } else { - this.tree.syncButton.disable(); - } - } + if (this.grid.getSelection().length > 1) { + this.detailPanel.collapse(); + this.tree.syncButton.disable(); + } else { + if (this.grid.getSelection().length == 1) { + var selection = this.grid.getSelection(); + + var r = selection[0]; + + this.detailPanel.setActiveTab(this.detail); + this.detailPanel.expand(); + this.detail.setValues(r); + this.stockLevel.part = r.getId(); + + this.tree.syncButton.enable(); + } else { + this.tree.syncButton.disable(); + } + } }, /** diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/form/field/SearchField.js b/src/PartKeepr/FrontendBundle/Resources/public/js/form/field/SearchField.js @@ -1,6 +1,6 @@ /** * Defines a search field, which automatically hooks into the passed store. - * + * * The "clear" trigger is shown only when text is entered. */ Ext.define('PartKeepr.form.field.SearchField', { @@ -21,12 +21,12 @@ Ext.define('PartKeepr.form.field.SearchField', { } }, - hasSearch : false, - - /** - * Defines the parameter name which is being passed to the store's proxy. - */ - targetField : 'query', + hasSearch: false, + + /** + * Defines the parameter name which is being passed to the store's proxy. + */ + targetField: 'query', filter: null, @@ -36,11 +36,12 @@ Ext.define('PartKeepr.form.field.SearchField', { scope: 'this' } }, - - /** - * Initializes the component. Binds the enter key to startSearch. - */ - initComponent: function(){ + + /** + * Initializes the component. Binds the enter key to startSearch. + */ + initComponent: function () + { this.callParent(arguments); this.filter = Ext.create("Ext.util.Filter", { @@ -55,7 +56,8 @@ Ext.define('PartKeepr.form.field.SearchField', { * Enter: Starts the search * Escape: Removes the search and clears the field contents */ - keyHandler: function (field, e) { + keyHandler: function (field, e) + { switch (e.getKey()) { case e.ENTER: this.startSearch(); @@ -65,13 +67,13 @@ Ext.define('PartKeepr.form.field.SearchField', { break; } }, - /** - * Resets the search field to empty and re-triggers the store to load the matching records. - */ - resetSearch: function () { - var me = this, - store = me.store, - val; + /** + * Resets the search field to empty and re-triggers the store to load the matching records. + */ + resetSearch: function () + { + var me = this, + store = me.store; me.setValue(''); @@ -80,34 +82,36 @@ Ext.define('PartKeepr.form.field.SearchField', { store.removeFilter(this.filter); store.currentPage = 1; - store.load({ start: 0 }); + store.load({start: 0}); me.hasSearch = false; this.getTrigger("clear").hide(); } - }, - /** - * Starts the search with the entered value. - */ - startSearch: function () { - var me = this, + }, + /** + * Starts the search with the entered value. + */ + startSearch: function () + { + var me = this, store = me.store, value = me.getValue(); - + if (value.length < 1) { me.resetSearch(); return; } - this.filter.setValue("%"+value+"%"); + this.filter.setValue("%" + value + "%"); store.addFilter(this.filter); store.currentPage = 1; - store.load({ start: 0 }); - + store.load({start: 0}); + me.hasSearch = true; this.getTrigger("clear").show(); - }, - setStore: function (store) { + }, + setStore: function (store) + { this.store = store; } -});- \ No newline at end of file +}); diff --git a/src/PartKeepr/PartBundle/Entity/Part.php b/src/PartKeepr/PartBundle/Entity/Part.php @@ -228,20 +228,10 @@ class Part extends BaseEntity private $removals = false; /** - * @return mixed - */ - public function hetRemovals() - { - return $this->removals; - } - - /** - * @param mixed $removals + * @ORM\Column(type="boolean",nullable=false) + * @var boolean */ - public function setRemovals($removals = false) - { - $this->removals = $removals; - } + private $lowStock = false; public function __construct() { @@ -250,18 +240,43 @@ class Part extends BaseEntity $this->parameters = new ArrayCollection(); $this->images = new ArrayCollection(); $this->attachments = new ArrayCollection(); + $this->stockLevels = new ArrayCollection(); $this->setCreateDate(new \DateTime()); $this->setNeedsReview(false); } /** - * Sets the name for this part + * Sets the create date for this part * - * @param string $name The part's name + * @param \DateTime $dateTime The create date+time */ - public function setName($name) + private function setCreateDate(\DateTime $dateTime) { - $this->name = $name; + $this->createDate = $dateTime; + } + + /** + * @return boolean + */ + public function isLowStock() + { + return $this->lowStock; + } + + /** + * @param boolean $lowStock + */ + public function setLowStock($lowStock) + { + $this->lowStock = $lowStock; + } + + /** + * @return mixed + */ + public function hasRemovals() + { + return $this->removals; } /** @@ -275,13 +290,13 @@ class Part extends BaseEntity } /** - * Sets the internal part number for this part + * Sets the name for this part * - * @param string $partNumber + * @param string $name The part's name */ - public function setInternalPartNumber($partNumber) + public function setName($name) { - $this->internalPartNumber = $partNumber; + $this->name = $name; } /** @@ -295,13 +310,13 @@ class Part extends BaseEntity } /** - * Sets the description for this part + * Sets the internal part number for this part * - * @param string $description The part's short description + * @param string $partNumber */ - public function setDescription($description) + public function setInternalPartNumber($partNumber) { - $this->description = $description; + $this->internalPartNumber = $partNumber; } /** @@ -315,14 +330,13 @@ class Part extends BaseEntity } /** - * Sets the part unit - * - * @param PartMeasurementUnit $partUnit The part unit object to set + * Sets the description for this part * + * @param string $description The part's short description */ - public function setPartUnit(PartMeasurementUnit $partUnit) + public function setDescription($description) { - $this->partUnit = $partUnit; + $this->description = $description; } /** @@ -338,25 +352,14 @@ class Part extends BaseEntity } /** - * Sets the average price for this unit - * - * @todo Is this actually used? + * Sets the part unit * - * @param float $price The price to set - */ - public function setAveragePrice($price) - { - $this->averagePrice = $price; - } - - /** - * Sets the review flag + * @param PartMeasurementUnit $partUnit The part unit object to set * - * @param boolean $bReview True if the part needs review, false otherwise */ - public function setNeedsReview($bReview) + public function setPartUnit(PartMeasurementUnit $partUnit) { - $this->needsReview = $bReview; + $this->partUnit = $partUnit; } /** @@ -370,13 +373,13 @@ class Part extends BaseEntity } /** - * Sets the condition for this part + * Sets the review flag * - * @param string $partCondition The part's condition + * @param boolean $bReview True if the part needs review, false otherwise */ - public function setPartCondition($partCondition) + public function setNeedsReview($bReview) { - $this->partCondition = $partCondition; + $this->needsReview = $bReview; } /** @@ -389,36 +392,14 @@ class Part extends BaseEntity return $this->partCondition; } - - /** - * Set the minimum stock level for this part - * - * Only positive values are allowed. - * - * @param int $minStockLevel A minimum stock level, only values >= 0 are allowed. - * - * @throws \PartKeepr\Util\Exceptions\OutOfRangeException If the passed stock level is not in range (>=0) - */ - public function setMinStockLevel($minStockLevel) - { - $minStockLevel = intval($minStockLevel); - - if ($minStockLevel < 0) { - $exception = new OutOfRangeException(PartKeepr::i18n("Minimum Stock Level is out of range")); - $exception->setDetail(PartKeepr::i18n("The minimum stock level must be 0 or higher")); - throw $exception; - } - $this->minStockLevel = $minStockLevel; - } - /** - * Returns the minimum stock level + * Sets the condition for this part * - * @return int + * @param string $partCondition The part's condition */ - public function getMinStockLevel() + public function setPartCondition($partCondition) { - return $this->minStockLevel; + $this->partCondition = $partCondition; } /** @@ -437,43 +418,11 @@ class Part extends BaseEntity } /** - * Sets the category for this part - * - * @param PartCategory $category The category - */ - public function setCategory(PartCategory $category) - { - $this->category = $category; - } - - /** - * Returns the assigned category - * - * @return PartCategory - */ - public function getCategory() - { - return $this->category; - } - - /** - * Sets the storage location for this part - * - * @param \PartKeepr\StorageLocationBundle\Entity\StorageLocation $storageLocation The storage location - */ - public function setStorageLocation(StorageLocation $storageLocation) - { - $this->storageLocation = $storageLocation; - } - - /** - * Returns the storage location for this part - * - * @return \PartKeepr\StorageLocationBundle\Entity\StorageLocation $storageLocation The storage location + * Retrieves the footprint */ - public function getStorageLocation() + public function getFootprint() { - return $this->storageLocation; + return $this->footprint; } /** @@ -487,11 +436,13 @@ class Part extends BaseEntity } /** - * Retrieves the footprint + * Returns the comment for this part + * + * @return string The comment */ - public function getFootprint() + public function getComment() { - return $this->footprint; + return $this->comment; } /** @@ -505,16 +456,6 @@ class Part extends BaseEntity } /** - * Returns the comment for this part - * - * @return string The comment - */ - public function getComment() - { - return $this->comment; - } - - /** * Returns the distributors array * * @return ArrayCollection the distributors @@ -565,23 +506,23 @@ class Part extends BaseEntity } /** - * Sets the create date for this part + * Returns the create date * - * @param \DateTime $dateTime The create date+time + * @return \DateTime The create date+time */ - private function setCreateDate(\DateTime $dateTime) + public function getCreateDate() { - $this->createDate = $dateTime; + return $this->createDate; } /** - * Returns the create date + * Returns the status for this part. * - * @return \DateTime The create date+time + * @return string The status */ - public function getCreateDate() + public function getStatus() { - return $this->createDate; + return $this->status; } /** @@ -596,13 +537,22 @@ class Part extends BaseEntity } /** - * Returns the status for this part. + * Checks if the requirements for database persisting are given. * - * @return string The status + * @throws CategoryNotAssignedException Thrown if no category is set + * @throws StorageLocationNotAssignedException Thrown if no storage location is set + * + * @ORM\PrePersist */ - public function getStatus() + public function onPrePersist() { - return $this->status; + $this->executeSaveListener(); + } + + public function executeSaveListener() + { + $this->checkCategoryConsistency(); + $this->checkStorageLocationConsistency(); } /** @@ -618,6 +568,26 @@ class Part extends BaseEntity } /** + * Returns the assigned category + * + * @return PartCategory + */ + public function getCategory() + { + return $this->category; + } + + /** + * Sets the category for this part + * + * @param PartCategory $category The category + */ + public function setCategory(PartCategory $category) + { + $this->category = $category; + } + + /** * Checks if the part storage location is set. * * @throws StorageLocationNotAssignedException @@ -630,59 +600,99 @@ class Part extends BaseEntity } /** - * Checks if the requirements for database persisting are given. + * Returns the storage location for this part * - * @throws CategoryNotAssignedException Thrown if no category is set - * @throws StorageLocationNotAssignedException Thrown if no storage location is set + * @return \PartKeepr\StorageLocationBundle\Entity\StorageLocation $storageLocation The storage location + */ + public function getStorageLocation() + { + return $this->storageLocation; + } + + /** + * @param mixed $removals + */ + public function setRemovals($removals = false) + { + $this->removals = $removals; + } + + /** + * Returns all stock entries * - * @ORM\PrePersist + * @return ArrayCollection */ - public function onPrePersist() + public function getStockLevels() { - $this->executeSaveListener(); + return $this->stockLevels; } /** + * Returns the minimum stock level * - * Checks if the requirements for database persisting are given. + * @return int + */ + public function getMinStockLevel() + { + return $this->minStockLevel; + } + + /** + * Set the minimum stock level for this part * - * For a list of exceptions, see + * Only positive values are allowed. * - * @see PartKeepr\Part.Part::onPrePersist() + * @param int $minStockLevel A minimum stock level, only values >= 0 are allowed. * - * @ORM\PreUpdate + * @throws \PartKeepr\Util\Exceptions\OutOfRangeException If the passed stock level is not in range (>=0) */ - public function onPreUpdate() + public function setMinStockLevel($minStockLevel) { - $this->executeSaveListener(); - } - - public function executeSaveListener () { - $this->checkCategoryConsistency(); - $this->checkStorageLocationConsistency(); - - $price = 0; - - $this->setRemovals(false); + $minStockLevel = intval($minStockLevel); - foreach ($this->getStockLevels() as $stockEntry) { - $price += $stockEntry->getPrice(); - if ($stockEntry->getStockLevel() < 0) { - $this->setRemovals(true); - } + if ($minStockLevel < 0) { + $exception = new OutOfRangeException(PartKeepr::i18n("Minimum Stock Level is out of range")); + $exception->setDetail(PartKeepr::i18n("The minimum stock level must be 0 or higher")); + throw $exception; } + $this->minStockLevel = $minStockLevel; + } - $this->setAveragePrice($price); + /** + * Sets the average price for this unit + * + * @todo Is this actually used? + * + * @param float $price The price to set + */ + public function setAveragePrice($price) + { + $this->averagePrice = $price; } /** - * Sets the stock level + * Sets the storage location for this part * - * @param $stockLevel int The stock level to set + * @param \PartKeepr\StorageLocationBundle\Entity\StorageLocation $storageLocation The storage location */ - public function setStockLevel($stockLevel) + public function setStorageLocation(StorageLocation $storageLocation) { - $this->stockLevel = $stockLevel; + $this->storageLocation = $storageLocation; + } + + /** + * + * Checks if the requirements for database persisting are given. + * + * For a list of exceptions, see + * + * @see PartKeepr\Part.Part::onPrePersist() + * + * @ORM\PreUpdate + */ + public function onPreUpdate() + { + $this->executeSaveListener(); } /** @@ -696,13 +706,13 @@ class Part extends BaseEntity } /** - * Returns all stock entries + * Sets the stock level * - * @return StockEntry[] + * @param $stockLevel int The stock level to set */ - public function getStockLevels() + public function setStockLevel($stockLevel) { - return $this->stockLevels; + $this->stockLevel = $stockLevel; } /** @@ -712,6 +722,7 @@ class Part extends BaseEntity */ public function addStockEntry(StockEntry $stockEntry) { + $this->executeSaveListener(); $stockEntry->setPart($this); $this->getStockLevels()->add($stockEntry); } @@ -793,4 +804,27 @@ class Part extends BaseEntity { return $this->projectParts; } + + public function recomputeStockLevels () { + $sum = 0; + $price = 0; + + foreach ($this->getStockLevels() as $stockLevel) { + $price += $stockLevel->getPrice(); + + if ($stockLevel->getStockLevel() < 0) { + $this->setRemovals(true); + } + $sum += $stockLevel->getStockLevel(); + } + + $this->setAveragePrice($price); + $this->setStockLevel($sum); + + if ($sum < $this->getMinStockLevel()) { + $this->setLowStock(true); + } else { + $this->setLowStock(false); + } + } } diff --git a/src/PartKeepr/PartBundle/Listeners/StockLevelListener.php b/src/PartKeepr/PartBundle/Listeners/StockLevelListener.php @@ -51,13 +51,7 @@ class StockLevelListener extends ContainerAware { $entityManager = $eventArgs->getEntityManager(); - $sum = 0; - - foreach ($part->getStockLevels() as $stockLevel) { - $sum += $stockLevel->getStockLevel(); - } - - $part->setStockLevel($sum); + $part->recomputeStockLevels(); $entityManager->getUnitOfWork()->recomputeSingleEntityChangeSet( $entityManager->getClassMetadata(get_class($part)),