partkeepr

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

commit 4ef98fe1a9004bec65ec1c5af17727448b6ac7bf
parent 7795e16f7ae86678eb68903960b73529c82dc295
Author: Felicitus <felicitus@felicitus.org>
Date:   Sat, 26 Sep 2015 19:02:59 +0200

Migrated project reports

Diffstat:
Mapp/config/routing.yml | 13+++++++++++--
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectReport.js | 725+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Data/HydraModel.js | 8++++++++
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Data/HydraProxy.js | 30++++++++++++++++++++++++++++++
Asrc/PartKeepr/FrontendBundle/Resources/public/js/ExtJS/Bugfixes/Ext.grid.feature.Summary-selectorFix.js | 48++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Models/ProjectReport.js | 24++++++++++++++++++++++++
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Models/ProjectReportList.js | 37+++++++++++++++++++++++++++++++++++++
Msrc/PartKeepr/FrontendBundle/Resources/views/index.html.twig | 12++++++------
Asrc/PartKeepr/PartBundle/Controller/PartController.php | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/ProjectBundle/Controller/ProjectReportController.php | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
10 files changed, 723 insertions(+), 357 deletions(-)

diff --git a/app/config/routing.yml b/app/config/routing.yml @@ -13,10 +13,20 @@ PartKeeprImageBundle: type: annotation prefix: / +PartKeeprProjectBundle: + resource: "@PartKeeprProjectBundle/Controller/" + type: annotation + prefix: / + +PartKeeprPartBundle: + resource: "@PartKeeprPartBundle/Controller/" + type: annotation + prefix: / + _frontend: resource: "@PartKeeprFrontendBundle/Resources/config/routing.yml" api: resource: "." type: "api" - prefix: "/api" # Optional- \ No newline at end of file + prefix: "/api" # Optional diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectReport.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectReport.js @@ -1,352 +1,380 @@ /** - * Represents the project report view + * Represents the project report view */ Ext.define('PartKeepr.ProjectReportView', { - extend: 'Ext.panel.Panel', - alias: 'widget.ProjectReportView', - - bodyStyle: 'background:#DBDBDB;padding: 5px', - border: false, - - defaults: { - bodyStyle: 'padding:10px' - }, - - layout: 'border', - - initComponent: function () { - - this.createStores(); - - this.upperGridEditing = Ext.create('Ext.grid.plugin.CellEditing', { - clicksToEdit: 1 - }); - - this.reportList = Ext.create("PartKeepr.BaseGrid", { - selModel: { - mode: 'MULTI' - }, - selType: 'checkboxmodel', - flex: 1, - columns: [{ - header: i18n("Amount"), dataIndex: 'amount', - width: 50, - editor: { - xtype: 'numberfield' - } - },{ - header: i18n("Project Name"), dataIndex: 'name', - flex: 1 - },{ - header: i18n("Description"), dataIndex: 'description', - flex: 1 - }], - store: this.store, - plugins: [ this.upperGridEditing ] - }); - - this.editing = Ext.create('Ext.grid.plugin.CellEditing', { - clicksToEdit: 1 - }); - - this.reportResult = Ext.create("PartKeepr.BaseGrid", { - flex: 1, - features: [{ - ftype: 'summary' - }], - columns: [{ - header: i18n("Quantity"), dataIndex: 'quantity', - width: 50 - },{ - header: i18n("Part Name"), - renderer: function (val, p, rec) { - return rec.part().getAt(0).get("name"); - }, - flex: 1 - },{ - header: i18n("Part Description"), - renderer: function (val, p, rec) { - return rec.part().getAt(0).get("description"); + extend: 'Ext.panel.Panel', + alias: 'widget.ProjectReportView', + + bodyStyle: 'background:#DBDBDB;padding: 5px', + border: false, + + defaults: { + bodyStyle: 'padding:10px' + }, + + layout: 'border', + + initComponent: function () + { + + this.createStores(); + + this.upperGridEditing = Ext.create('Ext.grid.plugin.CellEditing', { + clicksToEdit: 1 + }); + + this.reportList = Ext.create("PartKeepr.BaseGrid", { + selModel: { + mode: 'MULTI' + }, + selType: 'checkboxmodel', + flex: 1, + columns: [ + { + header: i18n("Quantity"), dataIndex: 'quantity', + width: 50, + editor: { + xtype: 'numberfield' + } + }, { + header: i18n("Project Name"), dataIndex: 'name', + flex: 1 + }, { + header: i18n("Description"), dataIndex: 'description', + flex: 1 + } + ], + store: this.store, + plugins: [this.upperGridEditing] + }); + + this.editing = Ext.create('Ext.grid.plugin.CellEditing', { + clicksToEdit: 1, + listeners: { + beforeedit: this.onBeforeEdit, + edit: this.onEdit, + scope: this + } + }); + + this.reportResult = Ext.create("PartKeepr.BaseGrid", { + flex: 1, + features: [ + { + ftype: 'summary' + } + ], + columns: [ + { + header: i18n("Quantity"), dataIndex: 'quantity', + width: 50 + }, { + header: i18n("Part Name"), + renderer: function (val, p, rec) + { + return rec.getPart().get("name"); + }, + flex: 1 + }, { + header: i18n("Part Description"), + renderer: function (val, p, rec) + { + return rec.getPart().get("description"); + }, + flex: 1 + }, { + header: i18n("Remarks"), + dataIndex: 'remarks', + flex: 1 + }, { + header: i18n("Projects"), + dataIndex: 'projects', + flex: 1 + }, { + header: i18n("Storage Location"), dataIndex: 'storageLocation_name', + width: 100 + }, { + header: i18n("Available"), dataIndex: 'available', + width: 75 + }, { + header: i18n("Distributor"), + dataIndex: 'distributor', + renderer: function (val, p, rec) + { + if (rec.getDistributor() !== null) { + return rec.getDistributor().get("name"); + } + }, + flex: 1, + editor: { + xtype: 'DistributorComboBox', + returnObject: true, + triggerAction: 'query', + ignoreQuery: true, + forceSelection: true, + editable: false + } + }, { + header: i18n("Distributor Order Number"), dataIndex: 'distributor_order_number', + flex: 1, + editor: { + xtype: 'textfield' + } + }, { + header: i18n("Price per Item"), dataIndex: 'price', + renderer: PartKeepr.getApplication().formatCurrency, + width: 100 + }, { + header: i18n("Sum"), + dataIndex: 'sum', + renderer: PartKeepr.getApplication().formatCurrency, + summaryType: 'sum', + summaryRenderer: PartKeepr.getApplication().formatCurrency, + width: 100 + }, { + header: i18n("Amount to Order"), dataIndex: 'missing', + width: 100 + }, { + header: i18n("Sum (Order)"), + dataIndex: 'sum_order', + renderer: PartKeepr.getApplication().formatCurrency, + summaryType: 'sum', + summaryRenderer: PartKeepr.getApplication().formatCurrency, + width: 100 + } + ], + store: this.projectReportStore, + plugins: [this.editing] + }); + + this.createReportButton = Ext.create('Ext.button.Button', { + xtype: 'button', + text: i18n("Create Report"), + width: 120, + margins: { + right: 10 + }, + listeners: { + click: this.onCreateReportClick, + scope: this + } + }); + + this.autoFillButton = Ext.create('Ext.button.Button', { + text: i18n("Autofill"), + width: 120, + margins: { + right: 20 + }, + listeners: { + click: this.onAutoFillClick, + scope: this + } + }); + + this.removeStockButton = Ext.create('Ext.button.Button', { + text: i18n("Remove parts from stock"), + width: 160, + listeners: { + click: this.onStockRemovalClick, + scope: this + } + + }); + + this.items = [ + { + title: i18n("Choose Projects to create a report for"), + split: true, + minHeight: 300, + height: 300, + bodyStyle: 'background:#DBDBDB;padding: 10px;', + layout: { + type: 'vbox', + align: 'stretch', + pack: 'start' }, - flex: 1 - },{ - header: i18n("Remarks"), - dataIndex: 'remarks', - flex: 1 - },{ - header: i18n("Projects"), - dataIndex: 'projects', - flex: 1 - },{ - header: i18n("Storage Location"), dataIndex: 'storageLocation_name', - width: 100 - },{ - header: i18n("Available"), dataIndex: 'available', - width: 75 - },{ - header: i18n("Distributor"), dataIndex: 'distributor_id', - renderer: function (val,p,rec) { - return rec.get("distributor_name"); - }, - flex: 1, - editor: { - xtype: 'DistributorComboBox', - triggerAction: 'query', - ignoreQuery: true, - forceSelection: true, - editable: false - } - },{ - header: i18n("Distributor Order Number"), dataIndex: 'distributor_order_number', - flex: 1, - editor: { - xtype: 'textfield' - } - },{ - header: i18n("Price per Item"), dataIndex: 'price', - renderer: PartKeepr.getApplication().formatCurrency, - width: 100 - },{ - header: i18n("Sum"), - dataIndex: 'sum', - renderer: PartKeepr.getApplication().formatCurrency, - summaryType: 'sum', - summaryRenderer: PartKeepr.getApplication().formatCurrency, - width: 100 - },{ - header: i18n("Amount to Order"), dataIndex: 'missing', - width: 100 - },{ - header: i18n("Sum (Order)"), - dataIndex: 'sum_order', - renderer: PartKeepr.getApplication().formatCurrency, - summaryType: 'sum', - summaryRenderer: PartKeepr.getApplication().formatCurrency, - width: 100 - }], - store: this.projectReportStore, - plugins: [ this.editing ] - }); - - this.reportResult.on("beforeedit", this.onBeforeEdit, this); - this.reportResult.on("edit", this.onEdit, this); - - this.createReportButton = Ext.create('Ext.button.Button', { - xtype: 'button', - text: i18n("Create Report"), - width: 120, - margins: { - right: 10 - }, - listeners: { - click: this.onCreateReportClick, - scope: this - } - }); - - this.autoFillButton = Ext.create('Ext.button.Button', { - text: i18n("Autofill"), - width: 120, - margins: { - right: 20 - }, - listeners: { - click: this.onAutoFillClick, - scope: this - } - }); - - this.removeStockButton = Ext.create('Ext.button.Button', { - text: i18n("Remove parts from stock"), - width: 160, - listeners: { - click: this.onStockRemovalClick, - scope: this - } - - }); - - this.items = [ - { - title: i18n("Choose Projects to create a report for"), - split: true, - minHeight: 300, - height: 300, - bodyStyle: 'background:#DBDBDB;padding: 10px;', - layout: { - type: 'vbox', - align : 'stretch', - pack : 'start' - }, - region: 'north', - items: [ - this.reportList, - { - layout: { - type: 'hbox', - pack: 'start' - }, - margins: { - top: 10 - }, - border: false, - bodyStyle: 'background:#DBDBDB', - items: [ this.createReportButton , this.autoFillButton, { xtype: 'tbspacer'}, this.removeStockButton ] - } - ] - },{ - region: 'center', - layout: 'fit', - bodyStyle: 'background:#DBDBDB;padding: 10px;', - title: i18n("Project Report"), - items: this.reportResult - }]; - - - - - this.callParent(); - }, - /** - * Called when the distributor field is about to be edited. - * - * Filters the distributor list and show only distributors which are assigned to the particular item. - * @param e - */ - onBeforeEdit: function (e) { - if (e.field !== "distributor_id") { return; } - - var distributors = e.record.part().getAt(0).distributors(); - - var filterIds = new Array(); - for (var i=0;i<distributors.count();i++) { - filterIds.push(distributors.getAt(i).get("distributor_id")); - } - - e.column.getEditor().store.clearFilter(); - e.column.getEditor().store.filter({filterFn: function(item) { - for (var i=0;i<filterIds.length;i++) { - if (item.get("id") == filterIds[i]) { - return true; - } - } - return false; - }}); - }, - /** - * Removes all parts in the project view. - */ - onStockRemovalClick: function () { - Ext.Msg.confirm(i18n("Remove parts from stock"), - i18n("Do you really want to remove the parts in the project report from the stock?"), - this.removeStocks, this); - }, - removeStocks: function (btn) { - if (btn == "yes") { - - var store = this.reportResult.getStore(); - var removals = []; - - for (var i=0;i<store.count();i++) { - var item = store.getAt(i); - - removals.push({ - part: item.part().getAt(0).get("id"), - amount: item.get("quantity"), - comment: item.get("projects") - }); - } - - var call = new PartKeepr.ServiceCall( - "Part", - "massDeleteStock"); - - call.setParameter("removals", removals); - call.doCall(); - } - }, - onEdit: function (editor, e) { - if (e.field == "distributor_id") { - var distributors = e.record.part().getAt(0).distributors(); - - for (var i = 0; i < distributors.count(); i++) { - if (distributors.getAt(i).get("distributor_id") == e.value) { - e.record.set("distributor_name", distributors.getAt(i).get("distributor_name")); - e.record.set("price", distributors.getAt(i).get("price")); - e.record.set("distributor_order_number", distributors.getAt(i).get("orderNumber")); - - e.record.set("sum_order", e.record.get("missing") * e.record.get("price")); - - e.record.set("sum", e.record.get("quantity") * e.record.get("price")); - } - } - } - - this.reportResult.getView().refresh(true); - - }, - onAutoFillClick: function () { - for (var i=0;i<this.reportResult.store.count();i++) { - var activeRecord = this.reportResult.store.getAt(i); - var cheapest=null; - var cheapestPrice=null; - - for (var j=0;j<activeRecord.part().getAt(0).distributors().count();j++) { - var activeDistributor = activeRecord.part().getAt(0).distributors().getAt(j); - - if (cheapestPrice === null && parseFloat(activeDistributor.get("price")) !== 0) { - cheapestPrice = activeDistributor.get("price"); - cheapest = activeDistributor; - } else { - if (parseFloat(activeDistributor.get("price")) !== 0 && parseFloat(activeDistributor.get("price")) < cheapestPrice) { - cheapestPrice = activeDistributor.get("price"); - cheapest = activeDistributor; - } - } - - } - - if (cheapest !== null) { - activeRecord.set("distributor_name", cheapest.get("distributor_name")); - activeRecord.set("distributor_order_number", cheapest.get("orderNumber")); - activeRecord.set("price", cheapest.get("price")); - activeRecord.set("sum_order", activeRecord.get("missing") * activeRecord.get("price")); - activeRecord.set("sum", activeRecord.get("quantity") * activeRecord.get("price")); - } - } - - this.reportResult.getView().refresh(true); - }, - /** - * - */ - onCreateReportClick: function () { - selection = this.reportList.getSelectionModel().getSelection(); - - var params = new Array(); - - for (var i=0;i<selection.length;i++) { - params.push({ project: selection[i].get("id"), amount: selection[i].get("amount")}); - } - - this.projectReportStore.getProxy().extraParams.reports = Ext.encode(params); - this.projectReportStore.load(); - }, - /** - * Creates the store used in this view. - */ - createStores: function () { - var config = { - autoLoad: true, - model: "PartKeepr.ProjectReportList", - pageSize: -1 - }; - - this.store = Ext.create('Ext.data.Store', config); - - this.projectReportStore = Ext.create('Ext.data.Store', { - model: "PartKeepr.ProjectReport", - pageSize: -1 - }); - } -});- \ No newline at end of file + region: 'north', + items: [ + this.reportList, + { + layout: { + type: 'hbox', + pack: 'start' + }, + margins: { + top: 10 + }, + border: false, + bodyStyle: 'background:#DBDBDB', + items: [ + this.createReportButton, + this.autoFillButton, + {xtype: 'tbspacer'}, + this.removeStockButton + ] + } + ] + }, { + region: 'center', + layout: 'fit', + bodyStyle: 'background:#DBDBDB;padding: 10px;', + title: i18n("Project Report"), + items: this.reportResult + } + ]; + + + this.callParent(); + }, + /** + * Called when the distributor field is about to be edited. + * + * Filters the distributor list and show only distributors which are assigned to the particular item. + * @param e + */ + onBeforeEdit: function (e, context) + { + if (context.field !== "distributor") { + return; + } + + var distributors = context.record.getPart().distributors(); + + var filterIds = []; + for (var i = 0; i < distributors.count(); i++) { + filterIds.push(distributors.getAt(i).getDistributor().getId()); + } + + var filter = Ext.create("Ext.util.Filter", { + property: "@id", + operator: 'in', + value: filterIds + }); + + context.column.getEditor().store.clearFilter(); + context.column.getEditor().store.addFilter(filter); + }, + /** + * Removes all parts in the project view. + */ + onStockRemovalClick: function () + { + Ext.Msg.confirm(i18n("Remove parts from stock"), + i18n("Do you really want to remove the parts in the project report from the stock?"), + this.removeStocks, this); + }, + removeStocks: function (btn) + { + if (btn == "yes") { + + var store = this.reportResult.getStore(); + var removals = []; + + for (var i = 0; i < store.count(); i++) { + var item = store.getAt(i); + + removals.push({ + part: item.getPart().getId(), + amount: item.get("quantity"), + comment: item.get("projects") + }); + } + + PartKeepr.PartBundle.Entity.Part.callCollectionAction("massRemoveStock", + {"removals": Ext.encode(removals)}); + } + }, + onEdit: function (editor, context) + { + if (context.field == "distributor" && context.record.getDistributor() !== null) { + var partDistributors = context.record.getPart().distributors(); + + for (var i = 0; i < partDistributors.count(); i++) { + if (partDistributors.getAt(i).getDistributor().getId() == context.record.getDistributor().getId()) { + context.record.set("price", partDistributors.getAt(i).get("price")); + context.record.set("distributor_order_number", partDistributors.getAt(i).get("orderNumber")); + context.record.set("sum_order", context.record.get("missing") * context.record.get("price")); + context.record.set("sum", context.record.get("quantity") * context.record.get("price")); + } + } + } + + this.reportResult.getView().refresh(true); + + }, + onAutoFillClick: function () + { + var partCount = this.reportResult.store.count(); + var cheapest = null; + var cheapestPrice = null; + var activeRecord; + + for (var i = 0; i < partCount; i++) { + activeRecord = this.reportResult.store.getAt(i); + + for (var j = 0; j < activeRecord.getPart().distributors().count(); j++) { + var activeDistributor = activeRecord.getPart().distributors().getAt(j); + + if (cheapestPrice === null && parseFloat(activeDistributor.get("price")) !== 0) { + cheapestPrice = activeDistributor.get("price"); + cheapest = activeDistributor; + } else { + if (parseFloat(activeDistributor.get("price")) !== 0 && parseFloat( + activeDistributor.get("price")) < cheapestPrice) { + cheapestPrice = activeDistributor.get("price"); + cheapest = activeDistributor; + } + } + + } + + if (cheapest !== null) { + activeRecord.set("distributor_name", cheapest.get("distributor_name")); + activeRecord.set("distributor_order_number", cheapest.get("orderNumber")); + activeRecord.set("price", cheapest.get("price")); + activeRecord.set("sum_order", activeRecord.get("missing") * activeRecord.get("price")); + activeRecord.set("sum", activeRecord.get("quantity") * activeRecord.get("price")); + } + } + + this.reportResult.getView().refresh(true); + }, + /** + * + */ + onCreateReportClick: function () + { + var selection = this.reportList.getSelectionModel().getSelection(); + + var projects = []; + + for (var i = 0; i < selection.length; i++) { + projects.push({project: selection[i].getId(), quantity: selection[i].get("quantity")}); + } + + this.projectReportStore.load({ + params: { + projects: Ext.encode(projects) + } + }); + }, + /** + * Creates the store used in this view. + */ + createStores: function () + { + var config = { + autoLoad: true, + model: "PartKeepr.ProjectBundle.Entity.ProjectReportList", + pageSize: 999999999 + }; + + this.store = Ext.create('Ext.data.Store', config); + + this.projectReportStore = Ext.create('Ext.data.Store', { + model: "PartKeepr.ProjectBundle.Entity.ProjectReport", + pageSize: -1 + }); + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Data/HydraModel.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Data/HydraModel.js @@ -79,5 +79,13 @@ Ext.define("PartKeepr.data.HydraModel", { } } } + }, + inheritableStatics: { + callCollectionAction: function (action, parameters, callback) + { + var proxy = this.getProxy(); + + proxy.callCollectionAction(action, parameters, callback); + } } }); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Data/HydraProxy.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Data/HydraProxy.js @@ -116,6 +116,36 @@ Ext.define("PartKeepr.data.HydraProxy", { this.sendRequest(request); }, + /** + * Calls a specific action on the collection + * @todo Document on how we call actions on entities + * + * + */ + callCollectionAction: function (action, parameters, callback) + { + var url = this.url + "/" + action; + var request = Ext.create("Ext.data.Request"); + + request.setMethod("PUT"); + request.setUrl(url); + if (Ext.isObject(parameters)) { + request.setParams(parameters); + } + + request.setHeaders(this.getHeaders()); + + request.setCallback(function (options, success, response) + { + this.processCallActionResponse(options, success, response); + + if (Ext.isFunction(callback)) { + callback(options, success, response); + } + }.bind(this)); + + this.sendRequest(request); + }, processCallActionResponse: function (options, success, response) { if (success !== false) { diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/ExtJS/Bugfixes/Ext.grid.feature.Summary-selectorFix.js b/src/PartKeepr/FrontendBundle/Resources/public/js/ExtJS/Bugfixes/Ext.grid.feature.Summary-selectorFix.js @@ -0,0 +1,48 @@ +/** + * Fixeds an issue where the summaryRowSelector is null + */ +Ext.define('PartKeepr.grid.feature.Summary', { + override: 'Ext.grid.feature.Summary', + + fullSummaryTpl: [ + '{%', + 'var me = this.summaryFeature,', + ' record = me.summaryRecord,', + ' view = values.view,', + ' bufferedRenderer = view.bufferedRenderer;', + 'this.nextTpl.applyOut(values, out, parent);', + 'if (!me.disabled && me.showSummaryRow && view.store.isLast(values.record)) {', + 'if (bufferedRenderer) {', + ' bufferedRenderer.variableRowHeight = true;', + '}', + 'me.outputSummaryRecord((record && record.isModel) ? record : me.createSummaryRecord(view), values, out, parent);', + '}', + '%}', + { + priority: 300, + beginRowSync: function (rowSync) + { + rowSync.add('fullSummary', this.summaryFeature.summaryRowSelector); + }, + syncContent: function (destRow, sourceRow, columnsToUpdate) + { + destRow = Ext.fly(destRow, 'syncDest'); + sourceRow = Ext.fly(sourceRow, 'sycSrc'); + var selector = this.summaryFeature.summaryRowSelector; + + var + destSummaryRow = destRow.down(selector, true), + sourceSummaryRow = sourceRow.down(selector, true); + // Sync just the updated columns in the summary row. + if (destSummaryRow && sourceSummaryRow) { + // If we were passed a column set, only update those, otherwise do the entire row + if (columnsToUpdate) { + this.summaryFeature.view.updateColumns(destSummaryRow, sourceSummaryRow, columnsToUpdate); + } else { + Ext.fly(destSummaryRow).syncContent(sourceSummaryRow); + } + } + } + } + ], +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Models/ProjectReport.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Models/ProjectReport.js @@ -0,0 +1,24 @@ +/** + * Represents a project report + */ +Ext.define("PartKeepr.ProjectBundle.Entity.ProjectReport", { + extend: "PartKeepr.data.HydraModel", + fields: [ + {name: 'quantity', type: 'int'}, + {name: 'storageLocation_name', type: 'string'}, + {name: 'available', type: 'int'}, + {name: 'missing', type: 'int'}, + {name: 'distributor_order_number', type: 'string'}, + {name: 'sum_order', type: 'float'}, + {name: 'sum', type: 'float'}, + {name: 'projects', type: 'string'}, + {name: 'remarks', type: 'string'}, + {name: 'part', reference: 'PartKeepr.PartBundle.Entity.Part'}, + {name: 'distributor', reference: 'PartKeepr.DistributorBundle.Entity.Distributor'} + ], + + proxy: { + type: "Hydra", + url: '/api/project_reports' + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Models/ProjectReportList.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Models/ProjectReportList.js @@ -0,0 +1,37 @@ +Ext.define('PartKeepr.ProjectBundle.Entity.ProjectReportList', { + extend: 'PartKeepr.data.HydraModel', + alias: 'schema.PartKeepr.ProjectBundle.Entity.ProjectReportList', + + idProperty: "@id", + + fields: [ + {name: '@id', type: 'string'}, + {name: 'name', type: 'string'}, + {name: 'quantity', type: 'integer'}, + {name: 'description', type: 'string'}, + { + name: 'user', + reference: 'PartKeepr.AuthBundle.Entity.User' + } + + ], + + hasMany: [ + { + name: 'parts', + associationKey: 'parts', + model: 'PartKeepr.ProjectBundle.Entity.ProjectPart' + }, + { + name: 'attachments', + associationKey: 'attachments', + model: 'PartKeepr.ProjectBundle.Entity.ProjectAttachment' + } + ], + + + proxy: { + type: "Hydra", + url: '/api/projects' + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/views/index.html.twig b/src/PartKeepr/FrontendBundle/Resources/views/index.html.twig @@ -18,8 +18,7 @@ 'js/packages/extjs6/build/packages/ux/classic/classic/resources/ux-all.css' 'js/packages/extjs6/build/packages/charts/classic/neptune/resources/charts-all.css' 'atelierspierrot/famfamfam-silk-sprite/silk-icons-sprite.css' - 'bundles/partkeeprfrontend/css/PartKeepr.css' - %} + 'bundles/partkeeprfrontend/css/PartKeepr.css' %} <link rel="stylesheet" href="{{ asset_url }}"/> {% endstylesheets %} @@ -42,8 +41,7 @@ '@PartKeeprFrontendBundle/Resources/public/js/Data/field/Array.js' '@PartKeeprFrontendBundle/Resources/public/js/Data/HydraModel.js' '@PartKeeprFrontendBundle/Resources/public/js/Data/HydraTreeModel.js' - '@PartKeeprFrontendBundle/Resources/public/js/ExtJS/Enhancements/Ext.data.field.Date-ISO8601.js' - %} + '@PartKeeprFrontendBundle/Resources/public/js/ExtJS/Enhancements/Ext.data.field.Date-ISO8601.js' %} <script type="text/javascript" src="{{ asset_url }}"></script> {% endjavascripts %} @@ -53,6 +51,9 @@ {% endfor %} {% javascripts output='js/compiled/main2.js' + '@PartKeeprFrontendBundle/Resources/public/js/ExtJS/Bugfixes/Ext.grid.feature.Summary-selectorFix.js' + '@PartKeeprFrontendBundle/Resources/public/js/Models/ProjectReport.js' + '@PartKeeprFrontendBundle/Resources/public/js/Models/ProjectReportList.js' '@PartKeeprFrontendBundle/Resources/public/js/Models/SystemInformationRecord.js' '@PartKeeprFrontendBundle/Resources/public/js/Util/Crypto/isaac.js' '@PartKeeprFrontendBundle/Resources/public/js/Util/Crypto/bcrypt.js' @@ -211,8 +212,7 @@ '@PartKeeprFrontendBundle/Resources/public/js/Ext.ux/Ext.ux.Exporter/downloadify.min.js' '@PartKeeprFrontendBundle/Resources/public/js/Ext.ux/Ext.ux.Exporter/Exporter.js' '@PartKeeprFrontendBundle/Resources/public/js/php.default.min.js' - '@PartKeeprFrontendBundle/Resources/public/js/webcam.js' - %} + '@PartKeeprFrontendBundle/Resources/public/js/webcam.js' %} <script type="text/javascript" src="{{ asset_url }}"></script> {% endjavascripts %} </head> diff --git a/src/PartKeepr/PartBundle/Controller/PartController.php b/src/PartKeepr/PartBundle/Controller/PartController.php @@ -0,0 +1,61 @@ +<?php +namespace PartKeepr\PartBundle\Controller; + + +use Dunglas\ApiBundle\Api\IriConverter; +use FOS\RestBundle\Controller\Annotations\View; +use FOS\RestBundle\Controller\FOSRestController; +use PartKeepr\PartBundle\Entity\Part; +use PartKeepr\StockBundle\Entity\StockEntry; +use Sensio\Bundle\FrameworkExtraBundle\Configuration as Routing; +use Symfony\Component\HttpFoundation\Request; + +class PartController extends FOSRestController +{ + /** + * @Routing\Route("/api/parts/massRemoveStock", defaults={"method" = "get","_format" = "json"}) + * @View() + * @param Request $request + * @throws \Exception Thrown if parameters are formatted incorrectly + */ + public function massRemoveStockAction(Request $request) + { + $removals = json_decode($request->get("removals")); + + if (!is_array($removals)) { + throw new \Exception("removals parameter must be an array"); + } + + /** + * @var IriConverter $iriConverter + */ + $iriConverter = $this->get("api.iri_converter"); + + $user = $this->get("partkeepr.userservice")->getUser(); + + foreach ($removals as $removal) { + if (!property_exists($removal, "part")) { + throw new \Exception("Each removal must have the part property defined"); + } + + if (!property_exists($removal, "amount")) { + throw new \Exception("Each removal must have the amount property defined"); + } + + /** + * @var Part $part + */ + $part = $iriConverter->getItemFromIri($removal->part); + + $stock = new StockEntry(0 - intval($removal->amount), $user); + + if (!property_exists($removal, "comment")) { + $stock->setComment($removal->comment); + } + + $part->addStockEntry($stock); + } + + $this->get("doctrine.orm.entity_manager")->flush(); + } +} diff --git a/src/PartKeepr/ProjectBundle/Controller/ProjectReportController.php b/src/PartKeepr/ProjectBundle/Controller/ProjectReportController.php @@ -0,0 +1,122 @@ +<?php +namespace PartKeepr\ProjectBundle\Controller; + + +use Dunglas\ApiBundle\Api\IriConverter; +use FOS\RestBundle\Controller\Annotations\View; +use FOS\RestBundle\Controller\FOSRestController; +use Sensio\Bundle\FrameworkExtraBundle\Configuration as Routing; +use Symfony\Component\HttpFoundation\Request; + +class ProjectReportController extends FOSRestController +{ + /** + * @Routing\Route("/api/project_reports", defaults={"method" = "get","_format" = "json"}) + * @View() + * @param Request $request + * @return array + * @throws \Exception Thrown if parameters are formatted incorrectly + */ + public function getProjectReportAction(Request $request) + { + $projectsParameter = json_decode($request->get("projects")); + + if (!is_array($projectsParameter)) { + throw new \Exception("projects must be an array"); + } + + /** + * @var IriConverter $iriConverter + */ + $iriConverter = $this->get("api.iri_converter"); + + $projects = array(); + + foreach ($projectsParameter as $projectParameter) { + if (!is_object($projectParameter)) { + throw new \Exception("Each project in the projects array must be an object"); + } + + if (!property_exists($projectParameter, "quantity")) { + throw new \Exception("quantity must be present"); + } + + if (!property_exists($projectParameter, "project")) { + throw new \Exception("project ID must be present"); + } + + + $project = $iriConverter->getItemFromIri($projectParameter->project); + + $projects[] = array("project" => $project, "quantity" => $projectParameter->quantity); + } + + + $partRepository = $this->get("doctrine.orm.entity_manager")->getRepository("PartKeepr\\PartBundle\\Entity\\Part"); + $aPartResults = array(); + + foreach ($projects as $report) { + $dql = "SELECT pp.quantity, pro.name AS projectname, pp.remarks, p.id FROM "; + $dql .= "PartKeepr\\ProjectBundle\\Entity\\ProjectPart pp JOIN pp.part p "; + $dql .= "JOIN pp.project pro WHERE pp.project = :project"; + + $query = $this->get("doctrine.orm.entity_manager")->createQuery($dql); + $query->setParameter("project", $report["project"]); + + foreach ($query->getArrayResult() as $result) { + + $part = $partRepository->find($result["id"]); + + if (array_key_exists($result["id"], $aPartResults)) { + // Only update the quantity of the part + $aPartResults[$result["id"]]["quantity"] += $result["quantity"] * $report["quantity"]; + $aPartResults[$result["id"]]["projects"][] = $result["projectname"]; + + if ($result["remarks"] != "") { + $aPartResults[$result["id"]]["remarks"][] = $result["projectname"].": ".$result["remarks"]; + } + } else { + + $serializedData = $this->get('serializer')->normalize( + $part, + 'jsonld' + ); + // Create a full resultset + $aPartResults[$result["id"]] = array( + "quantity" => $result["quantity"] * $report["quantity"], + "part" => $serializedData, + "storageLocation_name" => $part->getStorageLocation()->getName(), + "available" => $part->getStockLevel(), + "sum_order" => 0, + "projects" => array($result["projectname"]), + "remarks" => array(), + ); + + if ($result["remarks"] != "") { + $aPartResults[$result["id"]]["remarks"] = array($result["projectname"].": ".$result["remarks"]); + } + } + } + } + + $aFinalResult = array(); + + // Iterate over all results and calculate how many parts are missing + foreach ($aPartResults as $key => $partResult) { + $missing = $partResult["quantity"] - $partResult["available"]; + + if ($missing < 0) { + $missing = 0; + } + + $partResult["missing"] = $missing; + $partResult["remarks"] = implode(", ", $partResult["remarks"]); + $partResult["projects"] = implode(", ", $partResult["projects"]); + + $aFinalResult[] = $partResult; + } + + + return $aFinalResult; + } +}