partkeepr

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

commit 60ea3d1d1f98a2731dcee47386273b715aeb4428
parent 71edf8a730bd0638db047fcb8b18d81d7677504a
Author: Felicitus <felicitus@felicitus.org>
Date:   Thu, 24 Sep 2015 22:46:04 +0200

Migrated Project Parts to Symfony2. Removed obsolete manager classes.

Diffstat:
Mapp/AppKernel.php | 1+
Mapp/config/config.yml | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mapp/config/partkeepr.yml | 4++--
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Footprint/FootprintAttachmentGrid.js | 13++++++-------
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectAttachmentGrid.js | 13++++++-------
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectEditor.js | 260++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectEditorComponent.js | 38++++++++++++++++++++------------------
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectPartGrid.js | 276++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/CategoryComboBox.js | 38++++++++++++++++++++------------------
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/RemotePartComboBox.js | 271++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/PartKeepr/PartBundle/DataFixtures/PartDataLoader.php | 25+++++++++++++++++++++++--
Msrc/PartKeepr/PartBundle/Entity/Part.php | 7++++---
Msrc/PartKeepr/PartBundle/Tests/StockTest.php | 11++++++++---
Asrc/PartKeepr/ProjectBundle/Controller/ProjectAttachmentController.php | 15+++++++++++++++
Asrc/PartKeepr/ProjectBundle/DataFixtures/ProjectFixtureLoader.php | 36++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/ProjectBundle/Entity/Project.php | 197+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/ProjectBundle/Entity/ProjectAttachment.php | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/ProjectBundle/Entity/ProjectPart.php | 133+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/ProjectBundle/PartKeeprProjectBundle.php | 9+++++++++
Asrc/PartKeepr/ProjectBundle/Tests/ProjectTest.php | 166+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/PartKeepr/StorageLocationBundle/DataFixtures/StorageLocationLoader.php | 1+
Dsrc/backend/PartKeepr/Project/Project.php | 164-------------------------------------------------------------------------------
Dsrc/backend/PartKeepr/Project/ProjectAttachment.php | 50--------------------------------------------------
Dsrc/backend/PartKeepr/Project/ProjectManager.php | 34----------------------------------
Dsrc/backend/PartKeepr/Project/ProjectPart.php | 138-------------------------------------------------------------------------------
Dsrc/backend/PartKeepr/Project/ProjectService.php | 65-----------------------------------------------------------------
Dsrc/backend/PartKeepr/ProjectAttachment/ProjectAttachmentManager.php | 69---------------------------------------------------------------------
Dsrc/backend/PartKeepr/ProjectAttachment/ProjectAttachmentService.php | 103-------------------------------------------------------------------------------
28 files changed, 1202 insertions(+), 1080 deletions(-)

diff --git a/app/AppKernel.php b/app/AppKernel.php @@ -94,6 +94,7 @@ class AppKernel extends Kernel $bundles[] = new PartKeepr\DistributorBundle\PartKeeprDistributorBundle(); $bundles[] = new PartKeepr\ManufacturerBundle\PartKeeprManufacturerBundle(); $bundles[] = new PartKeepr\ImageBundle\PartKeeprImageBundle(); + $bundles[] = new PartKeepr\ProjectBundle\PartKeeprProjectBundle(); $bundles[] = new PartKeepr\StorageLocationBundle\PartKeeprStorageLocationBundle(); $bundles[] = new PartKeepr\TipOfTheDayBundle\PartKeeprTipOfTheDayBundle(); diff --git a/app/config/config.yml b/app/config/config.yml @@ -1000,3 +1000,94 @@ services: - { groups: [ "default" ] } + resource.project: + parent: "api.resource" + arguments: [ "PartKeepr\\ProjectBundle\\Entity\\Project" ] + tags: [ { name: "api.resource" } ] + calls: + - method: "initFilters" + arguments: [ [ "@doctrine_reflection_service.search_filter" ] ] + - method: "initNormalizationContext" + arguments: [ { groups: [ "default" ] } ] + - method: "initDenormalizationContext" + arguments: + - { groups: [ "default" ] } + + resource.project_part: + parent: "api.resource" + arguments: [ "PartKeepr\\ProjectBundle\\Entity\\ProjectPart" ] + tags: [ { name: "api.resource" } ] + calls: + - method: "initFilters" + arguments: [ [ "@doctrine_reflection_service.search_filter" ] ] + - method: "initNormalizationContext" + arguments: [ { groups: [ "default" ] } ] + - method: "initDenormalizationContext" + arguments: + - { groups: [ "default" ] } + + resource.project_attachment.item_operation.custom_get_image: + class: "Dunglas\ApiBundle\Api\Operation\Operation" + public: false + factory: [ "@api.operation_factory", "createItemOperation" ] + arguments: + - "@resource.project_attachment" # Resource + - [ "GET" ] # Methods + - "/project_attachments/{id}/getImage" # Path + - "PartKeeprProjectBundle:ProjectAttachment:getImage" # Controller + - "ProjectAttachmentGetImage" # Route name + - # Context (will be present in Hydra documentation) + "@type": "hydra:Operation" + "hydra:title": "A custom operation" + "returns": "xmls:string" + + resource.project_attachment.item_operation.custom_get: + class: "Dunglas\ApiBundle\Api\Operation\Operation" + public: false + factory: [ "@api.operation_factory", "createItemOperation" ] + arguments: + - "@resource.project_attachment" # Resource + - [ "GET" ] # Methods + - "/project_attachments/{id}/getFile" # Path + - "PartKeeprProjectBundle:ProjectAttachment:getFile" # Controller + - "ProjectAttachmentGet" # Route name + - # Context (will be present in Hydra documentation) + "@type": "hydra:Operation" + "hydra:title": "A custom operation" + "returns": "xmls:string" + + resource.project_attachment.item_operation.custom_get_mime: + class: "Dunglas\ApiBundle\Api\Operation\Operation" + public: false + factory: [ "@api.operation_factory", "createItemOperation" ] + arguments: + - "@resource.project_attachment" # Resource + - [ "GET" ] # Methods + - "/project_attachments/{id}/getMimeTypeIcon" # Path + - "PartKeeprProjectBundle:ProjectAttachment:getMimeTypeIcon" # Controller + - "ProjectAttachmentMimeTypeIcon" # Route name + - # Context (will be present in Hydra documentation) + "@type": "hydra:Operation" + "hydra:title": "A custom operation" + "returns": "xmls:string" + + resource.project_attachment.item_operation.get: + class: "Dunglas\ApiBundle\Api\Operation\Operation" + public: false + factory: [ "@api.operation_factory", "createItemOperation" ] + arguments: [ "@resource.project_attachment", "GET" ] + + resource.project_attachment: + parent: "api.resource" + arguments: [ "PartKeepr\\ProjectBundle\\Entity\\ProjectAttachment" ] + tags: [ { name: "api.resource" } ] + calls: + - method: "initItemOperations" + arguments: [ [ "@resource.project_attachment.item_operation.get", "@resource.project_attachment.item_operation.custom_get", "@resource.project_attachment.item_operation.custom_get_mime", "@resource.project_attachment.item_operation.custom_get_image" ] ] + - method: "initFilters" + arguments: [ [ "@doctrine_reflection_service.search_filter" ] ] + - method: "initNormalizationContext" + arguments: [ { groups: [ "default" ] } ] + - method: "initDenormalizationContext" + arguments: + - { groups: [ "default" ] } diff --git a/app/config/partkeepr.yml b/app/config/partkeepr.yml @@ -9,4 +9,5 @@ partkeepr: footprint: %kernel.root_dir%/../data/images/footprint/ partattachment: %kernel.root_dir%/../data/files/PartAttachment/ storagelocation: %kernel.root_dir%/../data/images/storagelocation/ - mimetype_icons: %kernel.root_dir%/../src/PartKeepr/MimetypeIconsBundle/Resources/public/images/mimes/- \ No newline at end of file + mimetype_icons: %kernel.root_dir%/../src/PartKeepr/MimetypeIconsBundle/Resources/public/images/mimes/ + projectattachment: %kernel.root_dir%/../data/files/ProjectAttachment/ diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Footprint/FootprintAttachmentGrid.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Footprint/FootprintAttachmentGrid.js @@ -1,6 +1,6 @@ -Ext.define('PartKeepr.ProjectAttachmentGrid', { - extend: 'PartKeepr.AttachmentGrid', - alias: 'widget.ProjectAttachmentGrid', - - model: "PartKeepr.ProjectAttachment" -});- \ No newline at end of file +Ext.define('PartKeepr.FootprintAttachmentGrid', { + extend: 'PartKeepr.AttachmentGrid', + alias: 'widget.FootprintAttachmentGrid', + + model: "PartKeepr.FootprintBundle.Entity.FootprintAttachment" +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectAttachmentGrid.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectAttachmentGrid.js @@ -1,6 +1,6 @@ -Ext.define('PartKeepr.FootprintAttachmentGrid', { - extend: 'PartKeepr.AttachmentGrid', - alias: 'widget.FootprintAttachmentGrid', - - model: "PartKeepr.FootprintBundle.Entity.FootprintAttachment" -});- \ No newline at end of file +Ext.define('PartKeepr.ProjectAttachmentGrid', { + extend: 'PartKeepr.AttachmentGrid', + alias: 'widget.ProjectAttachmentGrid', + + model: "PartKeepr.ProjectBundle.Entity.ProjectAttachment" +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectEditor.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectEditor.js @@ -1,133 +1,138 @@ /** - * Represents the project editor view + * Represents the project editor view */ Ext.define('PartKeepr.ProjectEditor', { - extend: 'PartKeepr.Editor', - alias: 'widget.ProjectEditor', - - // Various style configurations - saveText: i18n("Save Project"), - defaults: { + extend: 'PartKeepr.Editor', + alias: 'widget.ProjectEditor', + + // Various style configurations + saveText: i18n("Save Project"), + defaults: { anchor: '100%', labelWidth: 110 }, - layout: { - type: 'vbox', - align : 'stretch', - pack : 'start' - }, - - /** - * Initializes the component - */ - initComponent: function () { - /** - * Due to an ExtJS issue, we need to delay the event - * for a bit. - * - * @todo Fix this in a cleaner way - */ - this.on("startEdit", this.onEditStart, this,{ - delay: 200 - }); - - this.on("itemSaved", this._onItemSaved, this); - - var config = {}; - - // Build the initial (empty) store for the project parts - Ext.Object.merge(config, { - autoLoad: false, - model: "PartKeepr.ProjectPart", - autoSync: false, // Do not change. If true, new (empty) records would be immediately committed to the database. - remoteFilter: false, - remoteSort: false - }); - - this.store = Ext.create('Ext.data.Store', config); - - this.partGrid = Ext.create("PartKeepr.ProjectPartGrid", { - store: this.store, - listeners: { - edit: this.onProjectGridEdit - } - }); - - var container = Ext.create("Ext.form.FieldContainer", { - fieldLabel: i18n("Project Parts"), - labelWidth: 110, - layout: 'fit', - flex: 1, - items: this.partGrid - }); - - this.attachmentGrid = Ext.create("PartKeepr.ProjectAttachmentGrid", { - border: true - }); - - var container2 = Ext.create("Ext.form.FieldContainer", { - fieldLabel: i18n("Attachments"), - labelWidth: 110, - layout: 'fit', - flex: 1, - items: this.attachmentGrid - }); - - this.items = [{ - xtype: 'textfield', - name: 'name', - height: 20, - fieldLabel: i18n("Project Name") - },{ - xtype: 'textarea', - name: 'description', - fieldLabel: i18n("Project Description"), - height: 70 - }, - container, - container2 - ]; - this.callParent(); - - }, - /** - * Handle transparent setting of the part name after a value was selected from the combobox - */ - onProjectGridEdit: function (editor, e) { - if (e.field == "part_id") { - /** - * If the user cancelled the editing, set the field to the original value - */ - if (e.value === null) { - e.record.set("part_id", e.originalValue); - } - - /** - * Inject the name into the record - */ - var rec = e.column.getEditor().store.getById(e.value); - if (rec) { - e.record.set("part_name", rec.get("name")); - } - } - }, - /** - * Re-bind the store after an item was saved - */ - _onItemSaved: function (record) { - this.partGrid.bindStore(record.parts()); - this.attachmentGrid.bindStore(record.attachments()); - }, - /** - * Bind the store as soon as the view was rendered. - * - * @todo This is a hack, because invocation of this method is delayed. - */ - onEditStart: function () { - var store = this.record.parts(); - this.partGrid.bindStore(store); - - var store2 = this.record.attachments(); - this.attachmentGrid.bindStore(store2); - } -});- \ No newline at end of file + layout: { + type: 'vbox', + align: 'stretch', + pack: 'start' + }, + + /** + * Initializes the component + */ + initComponent: function () + { + /** + * Due to an ExtJS issue, we need to delay the event + * for a bit. + * + * @todo Fix this in a cleaner way + */ + this.on("startEdit", this.onEditStart, this, { + delay: 200 + }); + + this.on("itemSaved", this._onItemSaved, this); + + var config = {}; + + // Build the initial (empty) store for the project parts + Ext.Object.merge(config, { + autoLoad: false, + model: "PartKeepr.ProjectBundle.Entity.ProjectPart", + autoSync: false, // Do not change. If true, new (empty) records would be immediately committed to the database. + remoteFilter: false, + remoteSort: false + }); + + this.store = Ext.create('Ext.data.Store', config); + + this.partGrid = Ext.create("PartKeepr.ProjectPartGrid", { + store: this.store, + listeners: { + edit: this.onProjectGridEdit + } + }); + + var container = Ext.create("Ext.form.FieldContainer", { + fieldLabel: i18n("Project Parts"), + labelWidth: 110, + layout: 'fit', + flex: 1, + items: this.partGrid + }); + + this.attachmentGrid = Ext.create("PartKeepr.ProjectAttachmentGrid", { + border: true + }); + + var container2 = Ext.create("Ext.form.FieldContainer", { + fieldLabel: i18n("Attachments"), + labelWidth: 110, + layout: 'fit', + flex: 1, + items: this.attachmentGrid + }); + + this.items = [ + { + xtype: 'textfield', + name: 'name', + height: 20, + fieldLabel: i18n("Project Name") + }, { + xtype: 'textarea', + name: 'description', + fieldLabel: i18n("Project Description"), + height: 70 + }, + container, + container2 + ]; + this.callParent(); + + }, + /** + * Handle transparent setting of the part name after a value was selected from the combobox + */ + onProjectGridEdit: function (editor, e) + { + if (e.field == "part_id") { + /** + * If the user cancelled the editing, set the field to the original value + */ + if (e.value === null) { + e.record.set("part_id", e.originalValue); + } + + /** + * Inject the name into the record + */ + var rec = e.column.getEditor().store.getById(e.value); + if (rec) { + e.record.set("part_name", rec.get("name")); + } + } + }, + /** + * Re-bind the store after an item was saved + */ + _onItemSaved: function (record) + { + this.partGrid.bindStore(record.parts()); + this.attachmentGrid.bindStore(record.attachments()); + }, + /** + * Bind the store as soon as the view was rendered. + * + * @todo This is a hack, because invocation of this method is delayed. + */ + onEditStart: function () + { + var store = this.record.parts(); + this.partGrid.bindStore(store); + + var store2 = this.record.attachments(); + this.attachmentGrid.bindStore(store2); + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectEditorComponent.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectEditorComponent.js @@ -2,20 +2,23 @@ * Represents the project editor component */ Ext.define('PartKeepr.ProjectEditorComponent', { - extend: 'PartKeepr.EditorComponent', - alias: 'widget.ProjectEditorComponent', - navigationClass: 'PartKeepr.ProjectGrid', - editorClass: 'PartKeepr.ProjectEditor', - newItemText: i18n("New Project"), - model: 'PartKeepr.Project', - initComponent: function () { - this.createStore({ - sorters: [{ - property: 'name', - direction:'ASC' - }] - }); - - this.callParent(); - } -});- \ No newline at end of file + extend: 'PartKeepr.EditorComponent', + alias: 'widget.ProjectEditorComponent', + navigationClass: 'PartKeepr.ProjectGrid', + editorClass: 'PartKeepr.ProjectEditor', + newItemText: i18n("New Project"), + model: 'PartKeepr.ProjectBundle.Entity.Project', + initComponent: function () + { + this.createStore({ + sorters: [ + { + property: 'name', + direction: 'ASC' + } + ] + }); + + this.callParent(); + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectPartGrid.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectPartGrid.js @@ -2,63 +2,56 @@ * Represents an editable list of project parts. */ Ext.define('PartKeepr.ProjectPartGrid', { - extend: 'PartKeepr.BaseGrid', - - /* Column definitions */ - columns: [{ - header: i18n("Quantity"), dataIndex: 'quantity', - wdith: 50, - editor: { - xtype: 'numberfield', - allowBlank: false, - minValue: 1 + extend: 'PartKeepr.BaseGrid', + + /* Column definitions */ + columns: [ + { + header: i18n("Quantity"), dataIndex: 'quantity', + wdith: 50, + editor: { + xtype: 'numberfield', + allowBlank: false, + minValue: 1 + } + }, { + header: i18n("Part"), + dataIndex: 'part', + flex: 1, + editor: { + xtype: 'RemotePartComboBox' + }, + renderer: function (val, p, rec) + { + var part = rec.getPart(); + + if (part !== null) { + return Ext.util.Format.htmlEncode(part.get("name")); + } + } + }, { + header: i18n("Remarks"), dataIndex: 'remarks', + flex: 1, + editor: { + xtype: 'textfield' + } } - }, { - header: i18n("Part"), dataIndex: 'part_id', - flex: 1, - editor: { - xtype: 'RemotePartComboBox' - }, - renderer: function (val,p,rec) { - return Ext.util.Format.htmlEncode(rec.get("part_name")); - } - },{ - header: i18n("Remarks"), dataIndex: 'remarks', - flex: 1, - editor: { - xtype: 'textfield', - listeners: { - focus: function () { - console.log("TEXTFIELDFOCUS"); - } - } - } - }], - - /** - * Initializes the component - */ - initComponent: function () { - - this.editing = Ext.create('Ext.grid.plugin.CellEditing', { - clicksToEdit: 1, - listeners: { - beforeedit: function (editor, e, eOpts) { - if (e.field == "part_id") { - e.value = e.record.get("part_name"); - var header = this.headerCt.getHeaderAtIndex(e.colIdx); - var edit = this.editing.getEditor(editor.record, header); - - edit.field.setDisplayValue(e.record.get("part_name")); - } - }, - scope: this - } - }); - - this.plugins = [ this.editing ]; - - this.deleteButton = Ext.create("Ext.button.Button", { + ], + + /** + * Initializes the component + */ + initComponent: function () + { + + this.editing = Ext.create('Ext.grid.plugin.CellEditing', { + clicksToEdit: 1, + + }); + + this.plugins = [this.editing]; + + this.deleteButton = Ext.create("Ext.button.Button", { text: i18n('Delete'), disabled: true, itemId: 'delete', @@ -66,8 +59,8 @@ Ext.define('PartKeepr.ProjectPartGrid', { iconCls: 'web-icon brick_delete', handler: this.onDeleteClick }); - - this.viewButton = Ext.create("Ext.button.Button", { + + this.viewButton = Ext.create("Ext.button.Button", { text: i18n('View Part'), disabled: true, itemId: 'view', @@ -75,89 +68,101 @@ Ext.define('PartKeepr.ProjectPartGrid', { iconCls: 'web-icon brick_go', handler: this.onViewClick }); - - this.dockedItems = [{ - xtype: 'toolbar', - items: [{ - text: i18n('Add'), - scope: this, - iconCls: 'web-icon brick_add', - handler: this.onAddClick - },{ - text: i18n("Create new Part"), - scope: this, - iconCls: 'web-icon brick_add', - handler: this.onAddPartClick - }, - this.deleteButton, - this.viewButton - ] - }]; - - this.callParent(); - - this.getSelectionModel().on('selectionchange', this.onSelectChange, this); - }, - /** - * Creates a new row and sets the default quantity to 1. - */ - onAddClick: function () { - this.editing.cancelEdit(); - - var rec = new PartKeepr.ProjectPart({ - quantity: 1 - }); - - this.store.insert(this.store.count(), rec); - - this.editing.startEdit(rec, this.columns[0]); - }, - /** - * Creates a new part, adds it to the list and sets the default quantity to 1. - */ - onAddPartClick: function () { - var win = Ext.getCmp("partkeepr-partmanager").onItemAdd(); - win.editor.on("editorClose", function (context) { - // End this if the record is a phatom and thus hasn't been saved yet - if (context.record.phantom) { return; } - - // Insert the new record - this.editing.cancelEdit(); - - var rec = new PartKeepr.ProjectPart({ - quantity: 1, - part_id: context.record.get("id"), - part_name: context.record.get("name") - }); - - this.store.insert(this.store.count(), rec); - - this.editing.startEdit(rec, this.columns[0]); - }, this); - }, - /** - * Removes the currently selected row - */ - onDeleteClick: function () { - var selection = this.getView().getSelectionModel().getSelection()[0]; + + this.dockedItems = [ + { + xtype: 'toolbar', + items: [ + { + text: i18n('Add'), + scope: this, + iconCls: 'web-icon brick_add', + handler: this.onAddClick + }, { + text: i18n("Create new Part"), + scope: this, + iconCls: 'web-icon brick_add', + handler: this.onAddPartClick + }, + this.deleteButton, + this.viewButton + ] + } + ]; + + this.callParent(); + + this.getSelectionModel().on('selectionchange', this.onSelectChange, this); + }, + /** + * Creates a new row and sets the default quantity to 1. + */ + onAddClick: function () + { + this.editing.cancelEdit(); + + var rec = Ext.create("PartKeepr.ProjectBundle.Entity.ProjectPart", { + quantity: 1 + }); + + this.store.insert(this.store.count(), rec); + + this.editing.startEdit(rec, this.columns[0]); + }, + /** + * Creates a new part, adds it to the list and sets the default quantity to 1. + */ + onAddPartClick: function () + { + var win = Ext.getCmp("partkeepr-partmanager").onItemAdd(); + win.editor.on("editorClose", function (context) + { + // End this if the record is a phatom and thus hasn't been saved yet + if (context.record.phantom) { + return; + } + + // Insert the new record + this.editing.cancelEdit(); + + var rec = Ext.create("PartKeepr.ProjectBundle.Entity.ProjectPart", { + quantity: 1, + part_id: context.record.get("id"), + part_name: context.record.get("name") + }); + + this.store.insert(this.store.count(), rec); + + this.editing.startEdit(rec, this.columns[0]); + }, this); + }, + /** + * Removes the currently selected row + */ + onDeleteClick: function () + { + var selection = this.getView().getSelectionModel().getSelection()[0]; if (selection) { this.store.remove(selection); } - }, - /** - * Removes the currently selected row - */ - onViewClick: function () { - var selection = this.getView().getSelectionModel().getSelection()[0]; + }, + /** + * Removes the currently selected row + */ + onViewClick: function () + { + var selection = this.getView().getSelectionModel().getSelection()[0]; if (selection) { Ext.getCmp("partkeepr-partmanager").onEditPart(selection.get("part_id")); } - }, - /** - * Enables or disables the delete button, depending on the row selection - */ - onSelectChange: function(selModel, selections){ + }, + + /** + * Enables or disables the delete button, depending on the row selection + */ + onSelectChange: function (selModel, selections) + { this.deleteButton.setDisabled(selections.length === 0); this.viewButton.setDisabled(selections.length === 0); } -});- \ No newline at end of file +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/CategoryComboBox.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/CategoryComboBox.js @@ -1,5 +1,5 @@ -Ext.define("PartKeepr.CategoryComboBox",{ - extend:"Ext.ux.TreePicker", +Ext.define("PartKeepr.CategoryComboBox", { + extend: "Ext.ux.TreePicker", alias: 'widget.CategoryComboBox', triggers: { @@ -16,21 +16,24 @@ Ext.define("PartKeepr.CategoryComboBox",{ _oldValue: null, - initComponent: function () { - this.listenersStore = this.store.on({ - scope: this, - // Workaround to remember the value when loading - beforeload: function () { - this._oldValue = this.getValue(); - }, - // Set the old value when load is complete - load: function () { - if (this._oldValue !== null) { - this.setValue(this._oldValue); - } + initComponent: function () + { + this.listenersStore = this.store.on({ + scope: this, + // Workaround to remember the value when loading + beforeload: function () + { + this._oldValue = this.getValue(); + }, + // Set the old value when load is complete + load: function () + { + if (this._oldValue !== null) { + this.setValue(this._oldValue); } - }); + } + }); - this.callParent(); + this.callParent(); } -});- \ No newline at end of file +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/RemotePartComboBox.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Widgets/RemotePartComboBox.js @@ -1,163 +1,169 @@ /** * A part picker with an attached grid. */ -Ext.define("PartKeepr.RemotePartComboBox",{ - extend:"Ext.form.field.Picker", +Ext.define("PartKeepr.RemotePartComboBox", { + extend: "Ext.form.field.Picker", alias: 'widget.RemotePartComboBox', - requires:["Ext.grid.Panel"], + requires: ["Ext.grid.Panel"], selectedValue: null, - + editable: false, + /** * Initializes the component. */ - initComponent: function(){ - Ext.apply(this,{ - pickerAlign:"tl-bl?", - editable: false + initComponent: function () + { + /** + * Create the store with the default sorter "name ASC" + */ + this.createStore({ + model: 'PartKeepr.PartBundle.Entity.Part', + groupField: 'categoryPath', + sorters: [ + { + property: 'name', + direction: 'ASC' + } + ] }); - /** - * Create the store with the default sorter "name ASC" - */ - this.createStore({ - model: 'PartKeepr.PartBundle.Entity.Part', - groupField: 'categoryPath', - sorters: [{ - property: 'name', - direction:'ASC' - }] - }); - this.callParent(); this.createPicker(); - - // @todo This is currently buggy due to EXTJSIV-5364. - this.on("focus", function () { this.onTriggerClick();}, this); + + // Automatically expand field when focused + this.on("focus", function () + { + this.onTriggerClick(); + }, this); }, - // Creates a store. To be called from child's initComponent - createStore: function (config) { - Ext.Object.merge(config, { - autoLoad: true, - autoSync: false, // Do not change. If true, new (empty) records would be immediately commited to the database. - remoteFilter: true, - remoteSort: true, - pageSize: 15}); - - this.store = Ext.create('Ext.data.Store', config); - - // Workaround for bug http://www.sencha.com/forum/showthread.php?133767-Store.sync()-does-not-update-dirty-flag&p=607093#post607093 - this.store.on('write', function(store, operation) { - var success=operation.wasSuccessful(); - if (success) { - Ext.each(operation.records, function(record){ - if (record.dirty) { - record.commit(); - } - }); - } - }); - }, - onTrigger1Click: function () { - this.onTriggerClick(); + // Creates a store. To be called from child's initComponent + createStore: function (config) + { + Ext.Object.merge(config, { + autoLoad: true, + autoSync: false, // Do not change. If true, new (empty) records would be immediately commited to the database. + remoteFilter: true, + remoteSort: true, + pageSize: 15 + }); + + this.store = Ext.create('Ext.data.Store', config); + + // Workaround for bug http://www.sencha.com/forum/showthread.php?133767-Store.sync()-does-not-update-dirty-flag&p=607093#post607093 + this.store.on('write', function (store, operation) + { + var success = operation.wasSuccessful(); + if (success) { + Ext.each(operation.records, function (record) + { + if (record.dirty) { + record.commit(); + } + }); + } + }); }, - createPicker: function(){ - this.partsGrid = Ext.create("PartKeepr.PartsGrid", { + createPicker: function () + { + this.partsGrid = Ext.create("PartKeepr.PartsGrid", { enableTopToolbar: true, - enableEditing: false, - store: this.store, - region: 'center' + enableEditing: false, + store: this.store, + region: 'center' }); - - this.filter = Ext.create("PartKeepr.PartFilterPanel", { - region: 'south', - floatable: false, - titleCollapse: true, - height: 225, - autoScroll: true, - store: this.store, - title: i18n("Part Filter"), - split: true, - collapsed: true, - collapsible: true - }); - + + this.filter = Ext.create("PartKeepr.PartFilterPanel", { + region: 'south', + floatable: false, + titleCollapse: true, + height: 225, + autoScroll: true, + store: this.store, + title: i18n("Part Filter"), + split: true, + collapsed: true, + collapsible: true, + listeners: { + beforeCollapse: function () + { + this.partsGrid.focus(); + }, + scope: this + } + }); + this.picker = Ext.create("Ext.panel.Panel", { - layout: 'border', - floating: true, + shrinkWrapDock: 2, + layout: 'border', + floating: true, focusOnToFront: false, - height:300, - minWidth: 350, + manageHeight: false, + height: 300, + minWidth: 350, shadow: false, - ownerCt: this.ownerCt, - items: [ this.partsGrid, this.filter ] - }); + ownerCmp: this, + items: [this.partsGrid, this.filter] + }); this.picker.on({ - show: function () { - this.partsGrid.searchField.setValue(this.getDisplayValue()); - this.partsGrid.searchField.startSearch(); + show: function () + { + this.partsGrid.searchField.setValue(this.getDisplayValue()); + this.partsGrid.searchField.startSearch(); }, - scope: this + scope: this }); - - this.partsGrid.on("select", - function (selModel, record) { - this.setSelectedValue(record.get("id")); - this.setDisplayValue(record.get("name")); - this.collapse(); - }, this); - - return self.picker; + + this.partsGrid.on("select", + function (selModel, record) + { + this.setSelectedValue(record); + this.setDisplayValue(record.get("name")); + this.collapse(); + }, this); + + return this.picker; }, - getDisplayValue: function () { - return this.displayValue; - }, - setSelectedValue: function (id) { - this.selectedValue = id; + getDisplayValue: function () + { + return this.displayValue; }, - getValue: function () { - return this.selectedValue; + setSelectedValue: function (data) + { + this.selectedValue = data; }, - setDisplayValue: function (value) { - this.setRawValue(value); - this.displayValue = value; - }, - setValue: function () { - - }, - _selectRecords: function (r) { - this.picker.getView().select(r); - this.picker.getView().ensureVisible(r); - this.picker.getView().scrollIntoView(r); + getValue: function () + { + return this.selectedValue; }, - alignPicker: function() { - // override the original method because otherwise the height of the treepanel would be always 0 - var me = this, - picker, isAbove, - aboveSfx = '-above'; - - if (this.isExpanded) { - picker = me.getPicker(); - if (me.matchFieldWidth) { - // Auto the height (it will be constrained by min and max width) unless there are no records to display. - picker.setWidth( me.bodyEl.getWidth()); - } - if (picker.isFloating()) { - picker.alignTo(me.inputEl, me.pickerAlign, me.pickerOffset); + setDisplayValue: function (value) + { + this.setRawValue(value); + this.displayValue = value; + }, + setValue: function (data) + { + this.selectedValue = data; - // add the {openCls}-above class if the picker was aligned above - // the field due to hitting the bottom of the viewport - isAbove = picker.el.getY() < me.inputEl.getY(); - me.bodyEl[isAbove ? 'addCls' : 'removeCls'](me.openCls + aboveSfx); - picker.el[isAbove ? 'addCls' : 'removeCls'](picker.baseCls + aboveSfx); - } + if (data instanceof Ext.data.Model) { + this.setDisplayValue(data.get("name")); + } else { + this.setDisplayValue(""); } + + }, + _selectRecords: function (r) + { + this.picker.getView().select(r); + this.picker.getView().ensureVisible(r); + this.picker.getView().scrollIntoView(r); }, - getErrors: function(value) { - if (this.getValue() === null) { - return [ i18n("You need to select a category")]; - } - - return []; + getErrors: function (value) + { + if (this.getValue() === null) { + return [i18n("You need to select a part")]; + } + + return []; } -});- \ No newline at end of file +}); diff --git a/src/PartKeepr/PartBundle/DataFixtures/PartDataLoader.php b/src/PartKeepr/PartBundle/DataFixtures/PartDataLoader.php @@ -5,23 +5,44 @@ namespace PartKeepr\PartBundle\DataFixtures; use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Common\Persistence\ObjectManager; use PartKeepr\PartBundle\Entity\Part; +use PartKeepr\PartBundle\Entity\PartMeasurementUnit; class PartDataLoader extends AbstractFixture { public function load(ObjectManager $manager) { + $partUnit = new PartMeasurementUnit(); + $partUnit->setName("pieces"); + $partUnit->setShortName("pcs"); + $partUnit->setDefault(true); + $part = new Part(); $part->setName("FOOBAR"); - + $part->setPartUnit($partUnit); $category = $this->getReference("partcategory.first"); $storageLocation = $this->getReference("storagelocation.first"); $part->setCategory($category); $part->setStorageLocation($storageLocation); + + $part2 = new Part(); + $part2->setName("FOOBAR2"); + + $category = $this->getReference("partcategory.first"); + $storageLocation = $this->getReference("storagelocation.first"); + + $part2->setCategory($category); + $part2->setStorageLocation($storageLocation); + $part2->setPartUnit($partUnit); + + $manager->persist($partUnit); $manager->persist($part); + $manager->persist($part2); + $manager->flush(); - $this->addReference("part", $part); + $this->addReference("part.1", $part); + $this->addReference("part.2", $part2); } } diff --git a/src/PartKeepr/PartBundle/Entity/Part.php b/src/PartKeepr/PartBundle/Entity/Part.php @@ -8,12 +8,13 @@ use PartKeepr\FootprintBundle\Entity\Footprint; use PartKeepr\Part\Exceptions\CategoryNotAssignedException; use PartKeepr\Part\Exceptions\StorageLocationNotAssignedException; use PartKeepr\PartKeepr; +use PartKeepr\ProjectBundle\Entity\ProjectPart; use PartKeepr\StockBundle\Entity\StockEntry; use PartKeepr\StorageLocationBundle\Entity\StorageLocation; +use PartKeepr\UploadedFileBundle\Annotation\UploadedFileCollection; use PartKeepr\Util\BaseEntity; use PartKeepr\Util\Exceptions\OutOfRangeException; use Symfony\Component\Serializer\Annotation\Groups; -use PartKeepr\UploadedFileBundle\Annotation\UploadedFileCollection; /** * Represents a part in the database. The heart of our project. Handle with care! @@ -207,8 +208,8 @@ class Part extends BaseEntity private $createDate; /** - * @ORM\OneToMany(targetEntity="PartKeepr\Project\ProjectPart", mappedBy="part") - * @var ArrayCollection + * @ORM\OneToMany(targetEntity="PartKeepr\ProjectBundle\Entity\ProjectPart", mappedBy="part") + * @var ProjectPart[] **/ private $projectParts; diff --git a/src/PartKeepr/PartBundle/Tests/StockTest.php b/src/PartKeepr/PartBundle/Tests/StockTest.php @@ -2,6 +2,7 @@ namespace PartKeepr\PartBundle\Tests; use Doctrine\Common\DataFixtures\ProxyReferenceRepository; +use Doctrine\ORM\Query; use Dunglas\ApiBundle\Api\IriConverter; use Liip\FunctionalTestBundle\Test\WebTestCase; use PartKeepr\PartBundle\Entity\Part; @@ -17,6 +18,7 @@ class StockTest extends WebTestCase { $this->fixtures = $this->loadFixtures( array( + 'PartKeepr\StorageLocationBundle\DataFixtures\CategoryDataLoader', 'PartKeepr\StorageLocationBundle\DataFixtures\StorageLocationLoader', 'PartKeepr\PartBundle\DataFixtures\CategoryDataLoader', 'PartKeepr\PartBundle\DataFixtures\PartDataLoader', @@ -26,6 +28,9 @@ class StockTest extends WebTestCase private function getStockLevel(Part $part) { + /** + * @var Query $query + */ $query = $this->getContainer()->get("doctrine")->getManager()->createQuery("SELECT p.stockLevel FROM PartKeeprPartBundle:Part p WHERE p.id = :id")->setParameter("id", $part->getId()); @@ -39,7 +44,7 @@ class StockTest extends WebTestCase /** * @var $part Part */ - $part = $this->fixtures->getReference("part"); + $part = $this->fixtures->getReference("part.1"); $oldStockLevel = $this->getStockLevel($part); /** @@ -79,7 +84,7 @@ class StockTest extends WebTestCase /** * @var $part Part */ - $part = $this->fixtures->getReference("part"); + $part = $this->fixtures->getReference("part.1"); $oldStockLevel = $this->getStockLevel($part); /** @@ -119,7 +124,7 @@ class StockTest extends WebTestCase /** * @var $part Part */ - $part = $this->fixtures->getReference("part"); + $part = $this->fixtures->getReference("part.1"); /** * @var $iriConverter IriConverter diff --git a/src/PartKeepr/ProjectBundle/Controller/ProjectAttachmentController.php b/src/PartKeepr/ProjectBundle/Controller/ProjectAttachmentController.php @@ -0,0 +1,15 @@ +<?php +namespace PartKeepr\ProjectBundle\Controller; + +use PartKeepr\ImageBundle\Controller\ImageController; + +class ProjectAttachmentController extends ImageController +{ + /** + * @inheritdoc + */ + protected function getEntityClass() + { + return "PartKeepr\\ProjectBundle\\Entity\\ProjectAttachment"; + } +} diff --git a/src/PartKeepr/ProjectBundle/DataFixtures/ProjectFixtureLoader.php b/src/PartKeepr/ProjectBundle/DataFixtures/ProjectFixtureLoader.php @@ -0,0 +1,36 @@ +<?php +namespace PartKeepr\ProjectBundle\DataFixtures; + +use Doctrine\Common\DataFixtures\AbstractFixture; +use Doctrine\Common\Persistence\ObjectManager; +use PartKeepr\ProjectBundle\Entity\Project; +use PartKeepr\ProjectBundle\Entity\ProjectPart; + +class ProjectFixtureLoader extends AbstractFixture +{ + public function load(ObjectManager $manager) + { + $projectPart1 = new ProjectPart(); + $projectPart1->setPart($this->getReference("part.1")); + $projectPart1->setQuantity(1); + + $projectPart2 = new ProjectPart(); + $projectPart2->setPart($this->getReference("part.2")); + $projectPart2->setQuantity(1); + + $project = new Project(); + $project->setName("FOOBAR"); + $project->setDescription("none"); + $project->addPart($projectPart1); + $project->addPart($projectPart2); + + $manager->persist($project); + $manager->persist($projectPart1); + $manager->persist($projectPart2); + $manager->flush(); + + $this->addReference("project", $project); + $this->addReference("projectpart.1", $projectPart1); + $this->addReference("projectpart.2", $projectPart2); + } +} diff --git a/src/PartKeepr/ProjectBundle/Entity/Project.php b/src/PartKeepr/ProjectBundle/Entity/Project.php @@ -0,0 +1,197 @@ +<?php +namespace PartKeepr\ProjectBundle\Entity; + +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\ORM\Mapping as ORM; +use PartKeepr\AuthBundle\Entity\User; +use PartKeepr\DoctrineReflectionBundle\Annotation\TargetService; +use PartKeepr\UploadedFileBundle\Annotation\UploadedFileCollection; +use PartKeepr\Util\BaseEntity; +use Symfony\Component\Serializer\Annotation\Groups; + +/** + * Represents a project + * + * @ORM\Entity + * @TargetService("/api/projects") + */ +class Project extends BaseEntity +{ + /** + * Specifies the name of the project + * + * @ORM\Column(type="string") + * @Groups({"default"}) + * @var string + */ + private $name; + + /** + * Specifies the user this project belongs to + * + * @ORM\ManyToOne(targetEntity="PartKeepr\AuthBundle\Entity\User") + * @var User + */ + private $user; + + /** + * Holds the parts needed for this project + * + * @ORM\OneToMany(targetEntity="PartKeepr\ProjectBundle\Entity\ProjectPart",mappedBy="project",cascade={"persist", "remove"}) + * @Groups({"default"}) + * @var ArrayCollection + */ + private $parts; + + /** + * Holds the description of this project + * + * @ORM\Column(type="string",nullable=true) + * @Groups({"default"}) + * @var string + */ + private $description; + + /** + * Holds the project attachments + * + * @ORM\OneToMany(targetEntity="PartKeepr\ProjectBundle\Entity\ProjectAttachment",mappedBy="project",cascade={"persist", "remove"}) + * @UploadedFileCollection() + * @Groups({"default"}) + * @var ArrayCollection + */ + private $attachments; + + public function __construct() + { + $this->parts = new ArrayCollection(); + $this->attachments = new ArrayCollection(); + } + + /** + * Sets the user for this project + * + * @param User $user + */ + public function setUser(User $user) + { + $this->user = $user; + } + + /** + * Gets the user for this project + * + * @return User + */ + public function getUser() + { + return $this->user; + } + + /** + * Sets the name for this project + * + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * Returns the name of this project + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the description of this project + * + * @param string $description The description to set + */ + public function setDescription($description) + { + $this->description = $description; + } + + /** + * Returns the description of this project + * + * @return string The description + */ + public function getDescription() + { + return $this->description; + } + + /** + * Returns the project parts array + * + * @return ProjectPart[] An array of ProjectPart objects + */ + public function getParts() + { + return $this->parts; + } + + /** + * Adds a Project Part + * + * @param ProjectPart $projectPart A part to add + */ + public function addPart($projectPart) + { + $projectPart->setProject($this); + $this->parts->add($projectPart); + } + + /** + * Removes a Project Part + * + * @param ProjectPart $projectPart A part to remove + */ + public function removePart($projectPart) + { + $projectPart->setProject(null); + $this->parts->removeElement($projectPart); + } + + + /** + * Returns the attachments for this project + * + * @return ProjectAttachment[] The attachments + */ + public function getAttachments() + { + return $this->attachments; + } + + /** + * Adds a Project Attachment + * + * @param ProjectAttachment $projectAttachment An attachment to add + */ + public function addAttachment($projectAttachment) + { + if ($projectAttachment instanceof ProjectAttachment) { + $projectAttachment->setProject($this); + } + $this->attachments->add($projectAttachment); + } + + /** + * Removes a Project Attachment + * + * @param ProjectAttachment $projectAttachment An attachment to remove + */ + public function removeAttachment($projectAttachment) + { + $projectAttachment->setProject(null); + $this->attachments->removeElement($projectAttachment); + } +} diff --git a/src/PartKeepr/ProjectBundle/Entity/ProjectAttachment.php b/src/PartKeepr/ProjectBundle/Entity/ProjectAttachment.php @@ -0,0 +1,54 @@ +<?php +namespace PartKeepr\ProjectBundle\Entity; + +use Doctrine\ORM\Mapping as ORM; +use PartKeepr\DoctrineReflectionBundle\Annotation\TargetService; +use PartKeepr\UploadedFileBundle\Entity\UploadedFile; +use Symfony\Component\Serializer\Annotation\Groups; + +/** + * Holds a project attachment + * + * @ORM\Entity + * @TargetService("/api/project_attachments") + **/ +class ProjectAttachment extends UploadedFile +{ + /** + * Creates a new project attachment + */ + public function __construct() + { + parent::__construct(); + $this->setType("ProjectAttachment"); + } + + /** + * The project object + * + * @ORM\ManyToOne(targetEntity="PartKeepr\ProjectBundle\Entity\Project", inversedBy="attachments") + * @Groups({"default"}) + * @var Project + */ + private $project = null; + + /** + * Sets the project + * + * @param Project $project The project to set + */ + public function setProject(Project $project = null) + { + $this->project = $project; + } + + /** + * Returns the roject + * + * @return Project the project + */ + public function getProject() + { + return $this->project; + } +} diff --git a/src/PartKeepr/ProjectBundle/Entity/ProjectPart.php b/src/PartKeepr/ProjectBundle/Entity/ProjectPart.php @@ -0,0 +1,133 @@ +<?php +namespace PartKeepr\ProjectBundle\Entity; + +use Doctrine\ORM\Mapping as ORM; +use PartKeepr\DoctrineReflectionBundle\Annotation\TargetService; +use PartKeepr\PartBundle\Entity\Part; +use PartKeepr\Util\BaseEntity; +use Symfony\Component\Serializer\Annotation\Groups; + +/** + * Represents a project part + * + * @ORM\Entity + * @TargetService("/api/project_parts") + */ +class ProjectPart extends BaseEntity +{ + /** + * The part this project part refers to + * + * @ORM\ManyToOne(targetEntity="PartKeepr\PartBundle\Entity\Part", inversedBy="projectParts") + * @Groups({"default"}) + * @var Part + */ + private $part; + + /** + * Specifies the amount of parts + * + * @ORM\Column(type="integer") + * @Groups({"default"}) + * @var integer + */ + private $quantity; + + /** + * Specifies the project which belongs to this project part + * + * @ORM\ManyToOne(targetEntity="PartKeepr\ProjectBundle\Entity\Project", inversedBy="parts") + * @Groups({"default"}) + * @var Project + */ + private $project; + + /** + * Specifies the remarks for this entry + * + * @ORM\Column(type="string",nullable=true) + * @Groups({"default"}) + * @var string + */ + private $remarks; + + /** + * Sets the part which belongs to this entry + * + * @param Part $part + */ + public function setPart(Part $part) + { + $this->part = $part; + } + + /** + * Returns the part which belongs to this entry + * + * @return Part + */ + public function getPart() + { + return $this->part; + } + + /** + * Sets the quantity for this entry + * + * @param int $quantity + */ + public function setQuantity($quantity) + { + $this->quantity = intval($quantity); + } + + /** + * Returns the quantity for this project + * + * @return int the amount of parts needed + */ + public function getQuantity() + { + return $this->quantity; + } + + /** + * Sets the project assigned to this entry + * + * @param Project $project + */ + public function setProject(Project $project = null) + { + $this->project = $project; + } + + /** + * Returns the project assigned to this entry + * + * @return Project + */ + public function getProject() + { + return $this->project; + } + + /** + * Sets the remarks for this entry + * + * @param string $remarks + */ + public function setRemarks($remarks) + { + $this->remarks = $remarks; + } + + /** + * Returns the remarks for this entry + * + * @return string + */ + public function getRemarks() + { + return $this->remarks; + } +} diff --git a/src/PartKeepr/ProjectBundle/PartKeeprProjectBundle.php b/src/PartKeepr/ProjectBundle/PartKeeprProjectBundle.php @@ -0,0 +1,9 @@ +<?php +namespace PartKeepr\ProjectBundle; + +use Symfony\Component\HttpKernel\Bundle\Bundle; + +class PartKeeprProjectBundle extends Bundle +{ + +} diff --git a/src/PartKeepr/ProjectBundle/Tests/ProjectTest.php b/src/PartKeepr/ProjectBundle/Tests/ProjectTest.php @@ -0,0 +1,166 @@ +<?php +namespace PartKeepr\ProjectBundle\Tests; + +use Doctrine\Common\DataFixtures\ProxyReferenceRepository; +use Liip\FunctionalTestBundle\Test\WebTestCase; +use PartKeepr\PartBundle\Entity\Part; +use Symfony\Component\HttpFoundation\File\UploadedFile; + +class ProjectTest extends WebTestCase +{ + /** + * @var ProxyReferenceRepository + */ + protected $fixtures; + + public function setUp() + { + $this->fixtures = $this->loadFixtures( + array( + 'PartKeepr\StorageLocationBundle\DataFixtures\CategoryDataLoader', + 'PartKeepr\StorageLocationBundle\DataFixtures\StorageLocationLoader', + 'PartKeepr\PartBundle\DataFixtures\CategoryDataLoader', + 'PartKeepr\PartBundle\DataFixtures\PartDataLoader', + 'PartKeepr\ProjectBundle\DataFixtures\ProjectFixtureLoader', + ) + )->getReferenceRepository(); + } + + public function testCreateProject() + { + $client = static::makeClient(true); + + $file = __DIR__."/../../UploadedFileBundle/Tests/Fixtures/files/uploadtest.png"; + $originalFilename = 'uploadtest.png'; + $mimeType = "image/png"; + + $image = new UploadedFile( + $file, + $originalFilename, + $mimeType, + filesize($file) + ); + + $client->request( + 'POST', + '/api/temp_uploaded_files/upload', + array(), + array('userfile' => $image) + ); + + $uploadedFile = json_decode($client->getResponse()->getContent()); + + /** + * @var $part Part + */ + $part = $this->fixtures->getReference("part.1"); + $part2 = $this->fixtures->getReference("part.2"); + + + $serializedPart1 = $this->getContainer()->get("serializer")->normalize( + $part, + 'jsonld' + ); + + $serializedPart2 = $this->getContainer()->get("serializer")->normalize( + $part2, + 'jsonld' + ); + + $project = array( + "name" => "foobar", + "description" => "testdescription", + "attachments" => array( + $uploadedFile->image, + ), + "parts" => array( + array( + "quantity" => 1, + "part" => $serializedPart1, + "remarks" => "testremark", + ), + array( + "quantity" => 2, + "part" => $serializedPart2, + "remarks" => "testremark2", + ), + ), + ); + + $client->request( + 'POST', + '/api/projects', + array(), + array(), + array(), + json_encode($project) + ); + + + $response = json_decode($client->getResponse()->getContent()); + + $this->assertObjectHasAttribute("@type", $response); + $this->assertEquals("Project", $response->{"@type"}); + + $this->assertObjectHasAttribute("name", $response); + $this->assertEquals("foobar", $response->name); + + $this->assertObjectHasAttribute("description", $response); + $this->assertEquals("testdescription", $response->description); + + $this->assertObjectHasAttribute("parts", $response); + $this->assertInternalType("array", $response->parts); + + $this->assertEquals(2, count($response->parts)); + $this->assertArrayHasKey(0, $response->parts); + $this->assertEquals("ProjectPart", $response->parts[0]->{"@type"}); + $this->assertEquals(1, $response->parts[0]->quantity); + $this->assertEquals("testremark", $response->parts[0]->remarks); + $this->assertEquals("Part", $response->parts[0]->part->{"@type"}); + + $this->assertObjectHasAttribute("attachments", $response); + $this->assertEquals(1, count($response->attachments)); + $this->assertArrayHasKey(0, $response->attachments); + $this->assertEquals("ProjectAttachment", $response->attachments[0]->{"@type"}); + + unset($response->parts[0]); + + + } + + public function testProjectPartRemoval() + { + $client = static::makeClient(true); + + $project = $this->fixtures->getReference("project"); + $projectPart = $this->fixtures->getReference("projectpart.1"); + $project->removePart($projectPart); + + $serializedProject = $this->getContainer()->get("serializer")->normalize( + $project, + 'jsonld' + ); + + $iriConverter = $this->getContainer()->get("api.iri_converter"); + $iri = $iriConverter->getIriFromItem($project); + + $client->request( + 'PUT', + $iri, + array(), + array(), + array(), + json_encode($serializedProject) + ); + + $response = json_decode($client->getResponse()->getContent()); + + $this->markTestIncomplete( + "This test can't be completed yet. See https://github.com/dunglas/DunglasApiBundle/issues/285" + ); + + $this->assertInternalType("array", $response->parts); + $this->assertArrayNotHasKey(1, $response->parts, + "When removing an entry from the ArrayCollection, the array must be resorted!"); + } +} diff --git a/src/PartKeepr/StorageLocationBundle/DataFixtures/StorageLocationLoader.php b/src/PartKeepr/StorageLocationBundle/DataFixtures/StorageLocationLoader.php @@ -11,6 +11,7 @@ class StorageLocationLoader extends AbstractFixture { $storageLocation = new StorageLocation(); $storageLocation->setName("test"); + $storageLocation->setCategory($this->getReference("storagelocationcategory.first")); $manager->persist($storageLocation); $manager->flush(); diff --git a/src/backend/PartKeepr/Project/Project.php b/src/backend/PartKeepr/Project/Project.php @@ -1,163 +0,0 @@ -<?php -namespace PartKeepr\Project; - -use Doctrine\ORM\Mapping as ORM; -use PartKeepr\AuthBundle\Entity\User; -use PartKeepr\Util\BaseEntity; -use PartKeepr\Util\Deserializable; -use PartKeepr\Util\Serializable; - -/** - * Represents a part in the database. The heart of our project. Handle with care! - * @ORM\Entity **/ -class Project extends BaseEntity implements Serializable, Deserializable { - /** - * Specifies the name of the project - * @ORM\Column(type="string") - */ - private $name; - - /** - * Specifies the user this project belongs to - * @ORM\ManyToOne(targetEntity="PartKeepr\AuthBundle\Entity\User") - */ - private $user; - - /** - * Holds the parts needed for this project - * @ORM\OneToMany(targetEntity="PartKeepr\Project\ProjectPart",mappedBy="project",cascade={"persist", "remove"}) - * @var ArrayCollection - */ - private $parts; - - /** - * Holds the description of this project - * @ORM\Column(type="string",nullable=true) - * @var string - */ - private $description; - - /** - * Holds the project attachments - * @ORM\OneToMany(targetEntity="PartKeepr\Project\ProjectAttachment",mappedBy="project",cascade={"persist", "remove"}) - * @var ProjectAttachment - */ - private $attachments; - - - /** - * Constructs a new project - */ - public function __construct () { - $this->parts = new \Doctrine\Common\Collections\ArrayCollection(); - $this->attachments = new \Doctrine\Common\Collections\ArrayCollection(); - } - - /** - * Sets the user for this project - * @param User $user - */ - public function setUser (User $user) { - $this->user = $user; - } - - /** - * Gets the user for this project - * -*@return \PartKeepr\AuthBundle\Entity\User - */ - public function getUser () { - return $this->user; - } - - /** - * Sets the name for this project - * @param string $name - */ - public function setName ($name) { - $this->name = $name; - } - - /** - * Returns the name of this project - */ - public function getName () { - return $this->name; - } - - /** - * Sets the description of this project - * @param string $description The description to set - */ - public function setDescription ($description) { - $this->description = $description; - } - - /** - * Returns the description of this project - * @return string The description - */ - public function getDescription () { - return $this->description; - } - - /** - * Returns the parts array - * @return ArrayCollection An array of ProjectPart objects - */ - public function getParts () { - return $this->parts; - } - - /** - * Returns the attachments for this project - * @return ArrayCollection The attachments - */ - public function getAttachments () { - return $this->attachments; - } - - /** - * (non-PHPdoc) - * @see PartKeepr\Util.Serializable::serialize() - */ - public function serialize () { - return array( - "id" => $this->getId(), - "name" => $this->getName(), - "description" => $this->getDescription(), - "parts" => $this->serializeChildren($this->getParts()), - "attachments" => $this->serializeChildren($this->getAttachments()) - ); - } - - /** - * Deserializes the project - * @param array $parameters The array with the parameters to set - */ - public function deserialize (array $parameters) { - foreach ($parameters as $key => $value) { - switch ($key) { - case "name": - $this->setName($value); - break; - case "description": - $this->setDescription($value); - break; - case "parts": - $this->deserializeChildren($value, $this->getParts(), "PartKeepr\Project\ProjectPart"); - foreach ($this->getParts() as $part) { - $part->setProject($this); - } - break; - case "attachments": - $this->deserializeChildren($value, $this->getAttachments(), "PartKeepr\Project\ProjectAttachment"); - foreach ($this->getAttachments() as $attachment) { - $attachment->setProject($this); - } - break; - } - } - } - -}- \ No newline at end of file diff --git a/src/backend/PartKeepr/Project/ProjectAttachment.php b/src/backend/PartKeepr/Project/ProjectAttachment.php @@ -1,50 +0,0 @@ -<?php -namespace PartKeepr\Project; - -use Doctrine\ORM\Mapping as ORM; -use PartKeepr\UploadedFileBundle\Entity\UploadedFile; - -/** - * Holds a project attachment - * - * @ORM\Entity - **/ -class ProjectAttachment extends UploadedFile -{ - /** - * Creates a new project attachment - */ - public function __construct() - { - parent::__construct(); - $this->setType("ProjectAttachment"); - } - - /** - * The project object - * @ORM\ManyToOne(targetEntity="PartKeepr\Project\Project", inversedBy="attachments") - * - * @var Project - */ - private $project = null; - - /** - * Sets the project - * - * @param Project $project The project to set - */ - public function setProject(Project $project) - { - $this->project = $project; - } - - /** - * Returns the roject - * - * @return Project the project - */ - public function getProject() - { - return $this->project; - } -} diff --git a/src/backend/PartKeepr/Project/ProjectManager.php b/src/backend/PartKeepr/Project/ProjectManager.php @@ -1,33 +0,0 @@ -<?php -namespace PartKeepr\Project; - -use PartKeepr\Manager\AbstractManager, - PartKeepr\Project\Project, - PartKeepr\PartKeepr; - -class ProjectManager extends AbstractManager { - /** - * Returns the FQCN for the target entity to operate on. - * @return string The FQCN, e.g. PartKeepr\Part - */ - public function getEntityName () { - return 'PartKeepr\Project\Project'; - } - - /** - * Returns all fields which need to appear in the getList ResultSet. - * @return array An array of all fields which should be returned - */ - public function getQueryFields () { - return array("id", "name", "description"); - } - - /** - * Returns the default sort field - * - * @return string The default sort field - */ - public function getDefaultSortField () { - return "name"; - } -}- \ No newline at end of file diff --git a/src/backend/PartKeepr/Project/ProjectPart.php b/src/backend/PartKeepr/Project/ProjectPart.php @@ -1,137 +0,0 @@ -<?php -namespace PartKeepr\Project; - -use PartKeepr\PartBundle\Entity\Part, - PartKeepr\Util\Serializable, - PartKeepr\Util\Deserializable, - PartKeepr\Util\BaseEntity, - Doctrine\ORM\Mapping as ORM; - -/** - * Represents a part in the database. The heart of our project. Handle with care! - * @ORM\Entity **/ -class ProjectPart extends BaseEntity implements Serializable, Deserializable { - /** - * @ORM\ManyToOne(targetEntity="PartKeepr\PartBundle\Entity\Part", inversedBy="projectParts") - */ - private $part; - - /** - * Specifies the amount of parts - * @ORM\Column(type="integer") - */ - private $quantity; - - /** - * Specifies the project which belongs to this project part - * @ORM\ManyToOne(targetEntity="PartKeepr\Project\Project", inversedBy="parts") - */ - private $project; - - /** - * Specifies the remarks for this entry - * @ORM\Column(type="string",nullable=true) - */ - private $remarks; - - /** - * Sets the part which belongs to this entry - * @param \PartKeepr\PartBundle\Entity\Part $part - */ - public function setPart (Part $part) { - $this->part = $part; - } - - /** - * Returns the part which belongs to this entry - * @return \PartKeepr\PartBundle\Entity\Part - */ - public function getPart () { - return $this->part; - } - - /** - * Sets the quantity for this entry - * @param int $quantity - */ - public function setQuantity ($quantity) { - $this->quantity = intval($quantity); - } - - /** - * Returns the quantity for this project - * @return int the amount of parts needed - */ - public function getQuantity () { - return $this->quantity; - } - - /** - * Sets the project assigned to this entry - * @param Project $project - */ - public function setProject (Project $project) { - $this->project = $project; - } - - /** - * Returns the project assigned to this entry - * @return Project - */ - public function getProject () { - return $this->project; - } - - /** - * Sets the remarks for this entry - * @param string $remarks - */ - public function setRemarks ($remarks) { - $this->remarks = $remarks; - } - - /** - * Returns the remarks for this entry - * @return string - */ - public function getRemarks () { - return $this->remarks; - } - - /** - * (non-PHPdoc) - * @see PartKeepr\Util.Serializable::serialize() - */ - public function serialize () { - return array( - "id" => $this->getId(), - "quantity" => $this->getQuantity(), - "part_id" => is_object($this->getPart()) ? $this->getPart()->getId() : 0, - "part_name" => is_object($this->getPart()) ? $this->getPart()->getName() : 0, - "project_id" => $this->getProject()->getId(), - "remarks" => $this->getRemarks() - ); - } - - /** - * Deserializes the project - * @param array $parameters The array with the parameters to set - */ - public function deserialize (array $parameters) { - foreach ($parameters as $key => $value) { - switch ($key) { - case "remarks": - $this->setRemarks($value); - break; - case "quantity": - $this->setQuantity($value); - break; - case "part_id": - $part = Part::loadById($value); - $this->setPart($part); - break; - } - } - } - -}- \ No newline at end of file diff --git a/src/backend/PartKeepr/Project/ProjectService.php b/src/backend/PartKeepr/Project/ProjectService.php @@ -1,64 +0,0 @@ -<?php -namespace PartKeepr\Project; - -use PartKeepr\Service\RestfulService, - PartKeepr\Service\Service, - PartKeepr\Project\ProjectManager, - PartKeepr\PartKeepr, - PartKeepr\Manager\ManagerFilter; - -class ProjectService extends Service implements RestfulService { - /** - * (non-PHPdoc) - * @see PartKeepr\Service.RestfulService::get() - */ - public function get () { - if ($this->hasParameter("id")) { - return array("data" => ProjectManager::getInstance()->getEntity($this->getParameter("id"))->serialize()); - } else { - $parameters = new ManagerFilter($this); - $parameters->setFilterField("name"); - return ProjectManager::getInstance()->getList($parameters); - } - } - - /** - * (non-PHPdoc) - * @see PartKeepr\Service.RestfulService::create() - */ - public function create () { - $this->requireParameter("name"); - - $entity = ProjectManager::getInstance()->createEntity($this->getParameters()); - - return array("data" => $entity->serialize()); - } - - /** - * (non-PHPdoc) - * @see PartKeepr\Service.RestfulService::update() - */ - public function update () { - $this->requireParameter("id"); - $this->requireParameter("name"); - $entity = ProjectManager::getInstance()->getEntity($this->getParameter("id")); - $entity->deserialize($this->getParameters()); - - PartKeepr::getEM()->flush(); - - return array("data" => $entity->serialize()); - - } - - /** - * (non-PHPdoc) - * @see PartKeepr\Service.RestfulService::destroy() - */ - public function destroy () { - $this->requireParameter("id"); - - ProjectManager::getInstance()->deleteEntity($this->getParameter("id")); - - return array("data" => null); - } -}- \ No newline at end of file diff --git a/src/backend/PartKeepr/ProjectAttachment/ProjectAttachmentManager.php b/src/backend/PartKeepr/ProjectAttachment/ProjectAttachmentManager.php @@ -1,68 +0,0 @@ -<?php -namespace PartKeepr\ProjectAttachment; - -use PartKeepr\Util\Singleton, - PartKeepr\Project\Project, - PartKeepr\PartKeepr; - -class ProjectAttachmentManager extends Singleton { - /** - * Returns a list of project attachments - * - * @param int $start Start of the list, default 0 - * @param int $limit Number of users to list, default 10 - * @param string $sort The field to sort by, default "name" - * @param string $dir The direction to sort (ASC or DESC), default ASC - * @param string $filter The project id - */ - public function getProjectAttachments ($start = 0, $limit = 10, $sort = "name", $dir = "asc", $filter = "") { - - $qb = PartKeepr::getEM()->createQueryBuilder(); - $qb->select("st")->from("PartKeepr\Project\ProjectAttachment","st") - ->leftJoin('st.project', "fp"); - - if ($filter != "") { - $project = Project::loadById($filter); - $qb = $qb->where("st.project = :project"); - $qb->setParameter("project", $project); - } - - if ($limit > -1) { - $qb->setMaxResults($limit); - $qb->setFirstResult($start); - } - - $qb->orderBy("st.".$sort, $dir); - - $query = $qb->getQuery(); - - $result = $query->getResult(); - - $totalQueryBuilder = PartKeepr::getEM()->createQueryBuilder(); - $totalQueryBuilder->select("COUNT(st.id)")->from("PartKeepr\Project\ProjectAttachment","st"); - - - - if ($filter != "") { - $totalQueryBuilder = $totalQueryBuilder->where("st.project = :project"); - $totalQueryBuilder->setParameter("project", $project); - } - - $totalQuery = $totalQueryBuilder->getQuery(); - - $aData = array(); - foreach ($result as $item) { - $aData[] = $item->serialize(); - } - return array("data" => $aData, "totalCount" => $totalQuery->getSingleScalarResult()); - } - - /** - * Returns a project attachment by id - * @param int $id The project attachment id - */ - public function getProjectAttachment ($id) { - return ProjectAttachment::loadById($id); - } - -}- \ No newline at end of file diff --git a/src/backend/PartKeepr/ProjectAttachment/ProjectAttachmentService.php b/src/backend/PartKeepr/ProjectAttachment/ProjectAttachmentService.php @@ -1,102 +0,0 @@ -<?php -namespace PartKeepr\ProjectAttachment; - -use PartKeepr\Project\ProjectAttachment, - PartKeepr\UploadedFileBundle\Entity\TempUploadedFile, - PartKeepr\Service\RestfulService, - PartKeepr\Service\Service, - PartKeepr\PartKeepr, - PartKeepr\Project\Project, - PartKeepr\Session\SessionManager; - -class ProjectAttachmentService extends Service implements RestfulService { - /** - * (non-PHPdoc) - * @see PartKeepr\Service.RestfulService::get() - */ - public function get () { - if ($this->hasParameter("id")) { - return ProjectAttachmentManager::getInstance()->getProjectAttachment($this->getParameter("id"))->serialize(); - } else { - if ($this->hasParameter("sort")) { - $tmp = json_decode($this->getParameter("sort"), true); - - $aSortParams = $tmp[0]; - } else { - $aSortParams = array( - "property" => "id", - "direction" => "ASC"); - } - - $filter = ""; - - if ($this->hasParameter("filter")) { - $tmp = json_decode($this->getParameter("filter"), true); - - foreach ($tmp as $item) { - if (array_key_exists("property", $item)) { - if ($item["property"] == "project_id") { - if (array_key_exists("value", $item)) { - $filter = $item["value"]; - } - } - } - } - } - return ProjectAttachmentManager::getInstance()->getProjectAttachments( - $this->getParameter("start", $this->getParameter("start", 0)), - $this->getParameter("limit", $this->getParameter("limit", 25)), - $this->getParameter("sortby", $aSortParams["property"]), - $this->getParameter("dir", $aSortParams["direction"]), - $filter); - } - } - - /** - * (non-PHPdoc) - * @see PartKeepr\Service.RestfulService::create() - */ - public function create () { - $this->requireParameter("tmp_id"); - $this->requireParameter("project_id"); - - $tmpImage = TempUploadedFile::loadById($this->getParameter("tmp_id")); - - $file = new ProjectAttachment(); - - $project = Project::loadById($this->getParameter("project_id")); - - $file->setProject($project); - $file->replace($tmpImage->getFilename()); - $file->setOriginalFilename($tmpImage->getOriginalFilename()); - $file->setDescription($this->getParameter("description")); - PartKeepr::getEM()->persist($file); - PartKeepr::getEM()->flush(); - - return $file->serialize(); - } - - /** - * (non-PHPdoc) - * @see PartKeepr\Service.RestfulService::update() - */ - public function update () { - - } - - /** - * (non-PHPdoc) - * @see PartKeepr\Service.RestfulService::destroy() - */ - public function destroy () { - $this->requireParameter("id"); - - $file = ProjectAttachment::loadById($this->getParameter("id")); - - PartKeepr::getEM()->remove($file); - PartKeepr::getEM()->flush(); - - return array("data" => null); - } - -}- \ No newline at end of file