partkeepr

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

commit 589beae11687158d5ddd75fa96aa02804004bfdc
parent 81c469cd88272543cf9ae19488d29f3a57373bda
Author: Felicia Hummel <felicia@partkeepr.com>
Date:   Mon,  2 Oct 2017 14:29:57 +0200

Merge branch 'master' of github.com:partkeepr/PartKeepr

Diffstat:
Mapp/config/config_partkeepr.yml | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/PartKeepr/CoreBundle/DoctrineMigrations/Version20170601175559.php | 2--
Msrc/PartKeepr/CoreBundle/Services/SystemService.php | 29+++++++++++++++++++++--------
Msrc/PartKeepr/DistributorBundle/Entity/Distributor.php | 3++-
Asrc/PartKeepr/DoctrineReflectionBundle/Annotation/VirtualOneToMany.php | 20++++++++++++++++++++
Msrc/PartKeepr/DoctrineReflectionBundle/Services/ReflectionService.php | 45+++++++++++++++++++++++++++++++++++++++++++--
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Importer/Importer.js | 42++++++++++++++++++++++++++++++++----------
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Importer/ImporterEntityConfiguration.js | 2++
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Importer/ImporterManyToOneConfiguration.js | 2+-
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/MetaPartSubgrid.js | 34+++++++++++++++++++++++-----------
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectPartGrid.js | 54++++++++++++++++++++++++++++++++++++++----------------
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectReport.js | 107++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectReportGrid.js | 37+++++++++++++++++++++++++++++++++++++
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectReportResultGrid.js | 85++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/Renderers/QuantityRenderer.js | 47+++++++++++++++++++++++++++++------------------
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Data/HydraProxy.js | 2+-
Msrc/PartKeepr/FrontendBundle/Resources/public/js/Data/HydraReader.js | 150+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/FrontendBundle/Resources/public/js/ExtJS/Enhancements/Ext.data.Store.getFieldValue.js | 20++++++++++++++++++++
Dsrc/PartKeepr/FrontendBundle/Resources/public/js/Models/ProjectReport.js | 34----------------------------------
Dsrc/PartKeepr/FrontendBundle/Resources/public/js/Models/ProjectReportPart.js | 8--------
Msrc/PartKeepr/FrontendBundle/Resources/views/index.html.twig | 4+++-
Msrc/PartKeepr/ImportBundle/Configuration/BaseConfiguration.php | 53+++++++++++++++++++++++++++++++++++++++++------------
Msrc/PartKeepr/ImportBundle/Configuration/Configuration.php | 6+++++-
Msrc/PartKeepr/ImportBundle/Configuration/EntityConfiguration.php | 4+++-
Msrc/PartKeepr/ImportBundle/Configuration/FieldConfiguration.php | 4++--
Msrc/PartKeepr/ImportBundle/Configuration/ManyToOneConfiguration.php | 12++++++------
Msrc/PartKeepr/ImportBundle/Configuration/OneToManyConfiguration.php | 2+-
Msrc/PartKeepr/ImportBundle/Controller/ImportController.php | 7++++++-
Msrc/PartKeepr/ImportBundle/Service/ImporterService.php | 8++++++--
Msrc/PartKeepr/PartBundle/Controller/PartController.php | 1-
Msrc/PartKeepr/PartBundle/Entity/PartDistributor.php | 3++-
Msrc/PartKeepr/ProjectBundle/Controller/ProjectReportController.php | 217+++++++++++++++++++++++++++++++++----------------------------------------------
Msrc/PartKeepr/ProjectBundle/Entity/ProjectPart.php | 26++++++++++++++++++++++++++
Asrc/PartKeepr/ProjectBundle/Entity/Report.php | 214+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/ProjectBundle/Entity/ReportPart.php | 339+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/ProjectBundle/Entity/ReportProject.php | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
36 files changed, 1507 insertions(+), 307 deletions(-)

diff --git a/app/config/config_partkeepr.yml b/app/config/config_partkeepr.yml @@ -1256,6 +1256,91 @@ services: arguments: - { groups: [ "default" ] } + resource.project_report.collection_operation.get: + class: "Dunglas\ApiBundle\Api\Operation\Operation" + public: false + factory: [ "@api.operation_factory", "createCollectionOperation" ] + arguments: [ "@resource.project_report", "GET" ] + + resource.project_report.item_operation.custom_post: + class: "Dunglas\ApiBundle\Api\Operation\Operation" + public: false + factory: [ "@api.operation_factory", "createCollectionOperation" ] + arguments: + - "@resource.project_report" # Resource + - [ "POST" ] # Methods + - "/reports" # Path + - "PartKeeprProjectBundle:ProjectReport:createReport" # Controller + - "ProjectReportPost" # Route name + - # Context (will be present in Hydra documentation) + "@type": "hydra:Operation" + "hydra:title": "A custom operation" + "returns": "xmls:string" + + resource.project_report.item_operation.custom_get: + class: "Dunglas\ApiBundle\Api\Operation\Operation" + public: false + factory: [ "@api.operation_factory", "createItemOperation" ] + arguments: + - "@resource.project_report" # Resource + - [ "GET" ] # Methods + - "/reports/{id}" # Path + - "PartKeeprProjectBundle:ProjectReport:getReport" # Controller + - "ProjectReportGet" # Route name + - # Context (will be present in Hydra documentation) + "@type": "hydra:Operation" + "hydra:title": "A custom operation" + "returns": "xmls:string" + + resource.project_report.item_operation.put: + class: "Dunglas\ApiBundle\Api\Operation\Operation" + public: false + factory: [ "@api.operation_factory", "createItemOperation" ] + arguments: [ "@resource.project_report", "PUT" ] + + resource.project_report: + parent: "api.resource" + arguments: [ "PartKeepr\\ProjectBundle\\Entity\\Report" ] + tags: [ { name: "api.resource" } ] + calls: + - method: "initItemOperations" + arguments: [ [ "@resource.project_report.item_operation.custom_get", "@resource.project_report.item_operation.put" ] ] + - method: "initCollectionOperations" + arguments: [ [ "@resource.project_report.collection_operation.get", "@resource.project_report.item_operation.custom_post"]] + - method: "initFilters" + arguments: [ [ "@doctrine_reflection_service.search_filter" ] ] + - method: "initNormalizationContext" + arguments: [ { groups: [ "default", "readonly" ] } ] + - method: "initDenormalizationContext" + arguments: + - { groups: [ "default" ] } + + resource.project_report_project: + parent: "api.resource" + arguments: [ "PartKeepr\\ProjectBundle\\Entity\\ReportProject" ] + 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_report_part: + parent: "api.resource" + arguments: [ "PartKeepr\\ProjectBundle\\Entity\\ReportPart" ] + 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 diff --git a/src/PartKeepr/CoreBundle/DoctrineMigrations/Version20170601175559.php b/src/PartKeepr/CoreBundle/DoctrineMigrations/Version20170601175559.php @@ -31,7 +31,6 @@ class Version20170601175559 extends BaseMigration } $this->getEM()->flush(); - } /** @@ -40,6 +39,5 @@ class Version20170601175559 extends BaseMigration public function down(Schema $schema) { // this down() migration is auto-generated, please modify it to your needs - } } diff --git a/src/PartKeepr/CoreBundle/Services/SystemService.php b/src/PartKeepr/CoreBundle/Services/SystemService.php @@ -100,7 +100,11 @@ class SystemService extends ContainerAware 'PartKeepr' ); - $aData[] = new SystemInformationRecord('PartKeepr Version', $this->versionService->getCanonicalVersion(), 'PartKeepr'); + $aData[] = new SystemInformationRecord( + 'PartKeepr Version', + $this->versionService->getCanonicalVersion(), + 'PartKeepr' + ); return $aData; } @@ -127,7 +131,7 @@ class SystemService extends ContainerAware 'inactiveCronjobCount' => count($inactiveCronjobs), 'inactiveCronjobs' => $inactiveCronjobs, 'schemaStatus' => $this->getSchemaStatus(), - 'schemaQueries' => $this->getSchemaQueries() + 'schemaQueries' => $this->getSchemaQueries(), ]; } @@ -150,10 +154,12 @@ class SystemService extends ContainerAware } /** - * Returns all queries to be executed for a proper database update + * Returns all queries to be executed for a proper database update. + * * @return array */ - protected function getSchemaQueries () { + protected function getSchemaQueries() + { $metadatas = $this->entityManager->getMetadataFactory()->getAllMetadata(); $schemaTool = new SchemaTool($this->entityManager); @@ -265,10 +271,17 @@ class SystemService extends ContainerAware public function getBytesFromHumanReadable($size_str) { switch (substr($size_str, -1)) { - case 'M': case 'm': return (int) $size_str * 1048576; - case 'K': case 'k': return (int) $size_str * 1024; - case 'G': case 'g': return (int) $size_str * 1073741824; - default: return $size_str; + case 'M': + case 'm': + return (int) $size_str * 1048576; + case 'K': + case 'k': + return (int) $size_str * 1024; + case 'G': + case 'g': + return (int) $size_str * 1073741824; + default: + return $size_str; } } } diff --git a/src/PartKeepr/DistributorBundle/Entity/Distributor.php b/src/PartKeepr/DistributorBundle/Entity/Distributor.php @@ -96,10 +96,11 @@ class Distributor extends BaseEntity private $skuurl; /** - * Defines if the distributor is used for pricing calculations + * Defines if the distributor is used for pricing calculations. * * @ORM\Column(type="boolean") * @Groups({"default"}) + * * @var bool */ private $enabledForReports = true; diff --git a/src/PartKeepr/DoctrineReflectionBundle/Annotation/VirtualOneToMany.php b/src/PartKeepr/DoctrineReflectionBundle/Annotation/VirtualOneToMany.php @@ -0,0 +1,20 @@ +<?php + +namespace PartKeepr\DoctrineReflectionBundle\Annotation; + +use Doctrine\ORM\Mapping\Annotation; + +/** + * Defines a virtual one to many association, that is, an association which + * is not persisted into the database. + * + * @Annotation + * @Target("PROPERTY") + */ +final class VirtualOneToMany implements Annotation +{ + /** + * @var string + */ + public $target; +} diff --git a/src/PartKeepr/DoctrineReflectionBundle/Services/ReflectionService.php b/src/PartKeepr/DoctrineReflectionBundle/Services/ReflectionService.php @@ -75,6 +75,11 @@ class ReflectionService $associationMappings = $this->getDatabaseAssociationMappings($cm, $bTree); + $associationMappings["ONE_TO_MANY"] = array_merge( + $associationMappings["ONE_TO_MANY"], + $this->getVirtualOneToManyRelationMappings($cm) + ); + $renderParams = [ 'fields' => $fieldMappings, 'associations' => $associationMappings, @@ -118,7 +123,12 @@ class ReflectionService $associations = $cm->getAssociationMappings(); $byReferenceMappings = $this->getByReferenceMappings($cm); - $associationMappings = []; + $associationMappings = [ + "ONE_TO_ONE" => [], + "MANY_TO_ONE" => [], + "ONE_TO_MANY" => [], + "MANY_TO_MANY" => [], + ]; foreach ($associations as $association) { $getterPlural = false; @@ -154,7 +164,9 @@ class ReflectionService $getterField .= 's'; } - $propertyAnnotations = $this->reader->getPropertyAnnotations($cm->getReflectionProperty($association['fieldName'])); + $propertyAnnotations = $this->reader->getPropertyAnnotations( + $cm->getReflectionProperty($association['fieldName']) + ); $nullable = true; @@ -218,6 +230,35 @@ class ReflectionService } /** + * Returns all virtual relations mappings. + * + * @param ClassMetadata $cm + * + * @return array + */ + protected function getVirtualOneToManyRelationMappings(ClassMetadata $cm) + { + $virtualRelationMappings = []; + + foreach ($cm->getReflectionClass()->getProperties() as $property) { + $virtualOneToManyRelation = $this->reader->getPropertyAnnotation( + $property, + 'PartKeepr\DoctrineReflectionBundle\Annotation\VirtualOneToMany' + ); + + if ($virtualOneToManyRelation !== null) { + $virtualRelationMappings[] = + [ + 'name' => $property->getName(), + 'target' => $this->convertPHPToExtJSClassName($virtualOneToManyRelation->target), + ]; + } + } + + return $virtualRelationMappings; + } + + /** * Returns all by-reference associations. * * @param ClassMetadata $cm diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Importer/Importer.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Importer/Importer.js @@ -37,7 +37,7 @@ Ext.define("PartKeepr.Importer.Importer", { xtype: 'treecolumn', text: i18n("Field"), dataIndex: 'text', - width: 200, + width: 200 }, { xtype: 'checkcolumn', disabled: true, @@ -75,7 +75,7 @@ Ext.define("PartKeepr.Importer.Importer", { }, { xtype: 'importerEntityConfiguration', - itemId: 'importerEntityConfiguration', + itemId: 'importerEntityConfiguration' }, { xtype: 'importerFieldConfiguration', itemId: 'importerFieldConfiguration' @@ -113,9 +113,9 @@ Ext.define("PartKeepr.Importer.Importer", { text: i18n("Path"), flex: 1, dataIndex: "node", - renderer: function (val) + renderer: function (val,p,rec) { - return val.getPath("text", "/"); + return rec.get("node").getPath("text", "/"); } }, { text: i18n("Error"), @@ -124,7 +124,13 @@ Ext.define("PartKeepr.Importer.Importer", { } ], store: { - fields: ["node", "error"] + fields: [{ + name: "node", + type: 'auto' + }, { + name: "error", + type: 'auto' + }] } } ] @@ -181,6 +187,7 @@ Ext.define("PartKeepr.Importer.Importer", { this.down("#preview").on("afterrender", this.refreshPreview, this); this.down("#importerPresetCombo").setConfiguration(this.importConfiguration); this.validateConfig(); + }, applyConfiguration: function () { @@ -228,17 +235,21 @@ Ext.define("PartKeepr.Importer.Importer", { var responseData = Ext.decode(response.responseText); var j = Ext.create("Ext.window.Window", { - width: 400, + width: 800, height: 400, layout: "fit", + scrollable: true, + title: i18n("Import Results"), items: [ { - xtype: 'panel', + xtype: 'textareafield', itemId: 'resultPanel', listeners: { render: function (p) { p.getEl().dom.innerHTML = "<pre><strong>Import Results</strong>\n\n" + responseData.logs + "</pre>"; + p.getEl().dom.style.overflow = "auto"; + p.getEl().dom.style.userSelect = "initial"; } } } @@ -327,6 +338,7 @@ Ext.define("PartKeepr.Importer.Importer", { return !field.persist; }, /** + * Appends default configuration data while populating the tree * @param {Ext.data.field.Field} The model */ appendFieldData: function (field, node) @@ -374,8 +386,6 @@ Ext.define("PartKeepr.Importer.Importer", { fieldData.data.configuration = node.parentNode.data.data.configuration.onetomany[node.data.text]; break; default: - - if (!node.parentNode.data.data.configuration.fields.hasOwnProperty(node.data.text)) { node.parentNode.data.data.configuration.fields[node.data.text] = {}; } @@ -449,12 +459,19 @@ Ext.define("PartKeepr.Importer.Importer", { this.down("#sourceFileGrid").reconfigure(store, columns); this.validateConfig(); }, + /** + * Recursively validates all nodes within the importer configuration and populates the errors grid + */ validateConfig: function () { this.down("#errorsGrid").setTitle(i18n("Errors")); this.down("#errorsGrid").getStore().removeAll(); this.validateConfigNode(this.down("#fieldTree").getRootNode()); }, + /** + * Validates a given node in the tree + * @param {Ext.data.NodeInterface} node The node to validate + */ validateConfigNode: function (node) { var configuration = node.data.data.configuration; @@ -522,6 +539,12 @@ Ext.define("PartKeepr.Importer.Importer", { } } }, + /** + * Appends a specific error for a given node + * + * @param {Ext.data.NodeInterface} node The node to append an error message + * @param {String} error The error message to append + */ appendError: function (node, error) { this.down("#errorsGrid").getStore().add({node: node, error: error}); @@ -529,7 +552,6 @@ Ext.define("PartKeepr.Importer.Importer", { var title = i18n("Errors") + " (" + this.down("#errorsGrid").getStore().getCount() + ")"; - this.down("#errorsGrid").setTitle(title); } }); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Importer/ImporterEntityConfiguration.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Importer/ImporterEntityConfiguration.js @@ -115,6 +115,8 @@ Ext.define("PartKeepr.Importer.ImporterEntityConfiguration", { } else { this.down("#importFieldMatcherGrid").setImporterConfig({}); } + + Ext.apply(this.importerConfig, this.getImporterConfig()); }, reconfigureColumns: function (columnsStore) { diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Importer/ImporterManyToOneConfiguration.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Importer/ImporterManyToOneConfiguration.js @@ -113,7 +113,7 @@ Ext.define("PartKeepr.Importer.ImporterManyToOneConfiguration", { boxLabel: i18n("Update data if an item exists"), disabled: true, checked: true, - inputValue: 'update', + inputValue: 'updateData', itemId: 'updateData', name: 'updateBehaviour', listeners: { change: "onChange" diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/MetaPartSubgrid.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/MetaPartSubgrid.js @@ -40,7 +40,10 @@ Ext.define('PartKeepr.Components.Project.MetaPartSubgrid', { } }, { text: i18n("Stock Level"), - dataIndex: 'stockLevel' + dataIndex: 'stockLevel', + renderer: function (value, metaData, record) { + return value + " " + record.getPartUnit().get("shortName"); + } }, { text: i18n("Stock to use"), dataIndex: 'stockToUse', @@ -48,6 +51,12 @@ Ext.define('PartKeepr.Components.Project.MetaPartSubgrid', { field: { xtype: 'numberfield' } + }, + renderer: function (value, metaData, record) { + if (typeof(value) === "undefined") { + value = 0; + } + return value + " " + record.getPartUnit().get("shortName"); } } ], @@ -105,17 +114,11 @@ Ext.define('PartKeepr.Components.Project.MetaPartSubgrid', { missing = Math.abs(missing); } - projectReportItem = Ext.create("PartKeepr.ProjectBundle.Entity.ProjectReport"); - projectReportItem.set("quantity", subPart.get("stockToUse")); - projectReportItem.set("storageLocation_name", subPart.getStorageLocation().get("name")); - projectReportItem.set("available", subPart.get("stockLevel")); - projectReportItem.set("missing", missing); - projectReportItem.set("projects", record.get("projects")); - projectReportItem.set("projectNames", record.get("projectNames")); - projectReportItem.set("remarks", record.get("remarks")); - projectReportItem.set("productionRemarks", subPart.get("productionRemarks")); - projectReportItem.set("lotNumber", record.get("lotNumber")); + projectReportItem = Ext.create("PartKeepr.ProjectBundle.Entity.ReportPart"); projectReportItem.setPart(subPart); + projectReportItem.set("quantity", subPart.get("stockToUse")); + projectReportItem.setReport(this.up("#projectReportResult").projectReport); + record.store.add(projectReportItem); } @@ -123,6 +126,15 @@ Ext.define('PartKeepr.Components.Project.MetaPartSubgrid', { record.store.remove(record); }, + + /** + * Handles the change of the meta parts subgrid checkbox. + * + * @param check + * @param rowIndex + * @param checked + * @param record + */ onCheckStateChange: function (check, rowIndex, checked, record) { var grid = check.up("grid"); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectPartGrid.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectPartGrid.js @@ -13,6 +13,15 @@ Ext.define('PartKeepr.ProjectPartGrid', { xtype: 'numberfield', allowBlank: false, minValue: 1 + }, + renderer: function (v, m, rec) + { + if (rec.getPart() !== null) + { + return v + " " + rec.getPart().getPartUnit().get("shortName"); + } else { + return v; + } } }, { header: i18n("Overage Type"), dataIndex: 'overageType', @@ -20,7 +29,7 @@ Ext.define('PartKeepr.ProjectPartGrid', { editor: { xtype: 'combobox', store: { - fields: ["overageType", "description"], + fields: ["overageType", "description"], data: [{overageType: 'percent', description: i18n("Percent")}, {overageType: 'absolute', description: i18n("Absolute")}] }, @@ -31,14 +40,17 @@ Ext.define('PartKeepr.ProjectPartGrid', { forceSelection: true, allowBlank: false }, - renderer: function (v) { - if (v === "percent") { + renderer: function (v) + { + if (v === "percent") + { return i18n("Percent"); - } else { + } else + { return i18n("Absolute"); } } - },{ + }, { header: i18n("Overage"), dataIndex: 'overage', width: 50, editor: { @@ -46,16 +58,20 @@ Ext.define('PartKeepr.ProjectPartGrid', { allowBlank: false, minValue: 0 }, - renderer: function (v,m,rec) { - if (rec.get("overageType") === "percent") { + renderer: function (v, m, rec) + { + if (rec.get("overageType") === "percent") + { return v + " %"; - } else { - if (rec.getPart() !== null) { + } else + { + if (rec.getPart() !== null) + { return v + " " + rec.getPart().getPartUnit().get("shortName"); } } } - },{ + }, { header: i18n("Part"), dataIndex: 'part', flex: 1, @@ -66,10 +82,13 @@ Ext.define('PartKeepr.ProjectPartGrid', { { var part = rec.getPart(), icon; - if (part !== null) { - if (part.get("metaPart")) { + if (part !== null) + { + if (part.get("metaPart")) + { icon = "bricks"; - } else { + } else + { icon = "brick"; } return '<span class="web-icon ' + icon + '"></span> ' + Ext.util.Format.htmlEncode( @@ -187,7 +206,8 @@ Ext.define('PartKeepr.ProjectPartGrid', { 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) { + if (context.record.phantom) + { return; } @@ -211,7 +231,8 @@ Ext.define('PartKeepr.ProjectPartGrid', { onDeleteClick: function () { var selection = this.getView().getSelectionModel().getSelection()[0]; - if (selection) { + if (selection) + { this.store.remove(selection); } }, @@ -221,7 +242,8 @@ Ext.define('PartKeepr.ProjectPartGrid', { onViewClick: function () { var selection = this.getView().getSelectionModel().getSelection()[0]; - if (selection) { + if (selection) + { Ext.getCmp("partkeepr-partmanager").onEditPart(selection.getPart()); } }, diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectReport.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectReport.js @@ -23,22 +23,45 @@ Ext.define('PartKeepr.ProjectReportView', { { this.createStores(); - this.reportList = Ext.create("PartKeepr.Components.Project.ProjectReportList", { - region: 'west', - collapsible: true, + this.projectList = Ext.create("PartKeepr.Components.Project.ProjectReportList", { + region: 'north', title: i18n("Choose Projects to create a report for"), - split: true, - minWidth: 300, - width: 500 + height: 300, + maxHeight: 500, + split: true + + + }); + + this.reportList = Ext.create("PartKeepr.Components.Project.ProjectReportGrid", { + title: i18n("Previous Project Reports"), + region: 'center' }); this.reportResult = Ext.create("PartKeepr.Components.Project.ProjectReportResultGrid", { - store: this.projectReportStore + store: null, + itemId: "projectReportResult", + projectReportManager: this + }); + + this.emptyReportPartStore = Ext.create("Ext.data.Store", { + model: "PartKeepr.ProjectBundle.Entity.ReportPart" }); this.items = [ - this.reportList, { + { + region: 'west', + layout: 'border', + collapsible: true, + split: true, + minWidth: 300, + width: 500, + items: [ + this.reportList, + this.projectList + ] + }, { region: 'center', layout: 'fit', title: i18n("Project Report"), @@ -50,19 +73,69 @@ Ext.define('PartKeepr.ProjectReportView', { this.callParent(); this.down("#createReportButton").on("click", this.onCreateReportClick, this); + this.down("#loadReportButton").on("click", this.onLoadReportClick, this); + }, + onLoadReportClick: function () + { + this.reportResult.getView().mask(i18n("Loading…")); + var selection = this.reportList.getSelection(); + + if (selection.length === 1) + { + this.projectReport = PartKeepr.ProjectBundle.Entity.Report.load( + selection[0].getId(), + { + success: this.onProjectReportLoaded, + scope: this + }); + } }, /** * */ onCreateReportClick: function () { - this.reportResult.setProjectsToReport(this.reportList.getProjectsToReport()); + this.reportResult.getView().mask(i18n("Loading…")); + this.reportResult.setProjectsToReport(this.projectList.getProjectsToReport()); - this.projectReportStore.load({ - params: { - projects: Ext.encode(this.reportList.getProjectsToReport()) - } + var projectsToReport = this.projectList.getProjectsToReport(); + + this.projectReport = Ext.create("PartKeepr.ProjectBundle.Entity.Report"); + + for (var i = 0; i < projectsToReport.length; i++) + { + this.projectReport.reportProjects().add( + Ext.create("PartKeepr.ProjectBundle.Entity.ReportProject", { + project: projectsToReport[i].project, + quantity: projectsToReport[i].quantity + })); + } + + this.doSaveProjectReport(); + }, + doSaveProjectReport: function () + { + this.reportResult.getView().mask(i18n("Saving…")); + this.reportResult.reconfigure(this.emptyReportPartStore); + this.projectReport.save({ + success: this.onProjectReportSave, + scope: this + }); + }, + onProjectReportSave: function () + { + this.projectReport.load({ + success: this.onProjectReportLoaded, + scope: this }); + + this.reportList.getStore().reload(); + }, + onProjectReportLoaded: function () + { + this.reportResult.reconfigure(this.projectReport.reportParts()); + this.reportResult.projectReport = this.projectReport; + this.reportResult.getView().unmask(); }, /** * Creates the store used in this view. @@ -70,8 +143,12 @@ Ext.define('PartKeepr.ProjectReportView', { createStores: function () { this.projectReportStore = Ext.create('Ext.data.Store', { - model: "PartKeepr.ProjectBundle.Entity.ProjectReport", - pageSize: -1 + model: "PartKeepr.ProjectBundle.Entity.ReportPart", + pageSize: -1, + proxy: { + type: "Hydra", + url: '/api/project_reports' + } }); }, statics: { diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectReportGrid.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectReportGrid.js @@ -0,0 +1,37 @@ +/** + * Represents the project report grid + */ +Ext.define('PartKeepr.Components.Project.ProjectReportGrid', { + extend: 'PartKeepr.BaseGrid', + alias: 'widget.ProjectReportGrid', + columns: [ + {header: i18n("Name"), dataIndex: 'name', flex: 2}, + {header: i18n("Created"), dataIndex: 'createDateTime', flex: 1, xtype: 'datecolumn'} + ], + automaticPageSize: false, + enableEditing: false, + viewModel: {}, + store: { + autoLoad: true, + autoSync: false, + remoteFilter: true, + remoteSort: true, + pageSize: 10, + model: "PartKeepr.ProjectBundle.Entity.Report", + filters: [{ + property: "name", + operator: "!=", + value: "" + }] + }, + bbar: { + xtype: 'pagingtoolbar', + itemId: 'pager', + items: ['-', { + xtype: 'button', + text: i18n("Load Report"), + iconCls: "fugue-icon notification-counter", + itemId: 'loadReportButton' + }] + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectReportResultGrid.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/ProjectReportResultGrid.js @@ -25,20 +25,23 @@ Ext.define("PartKeepr.Components.Project.ProjectReportResultGrid", { this.columns = [ { header: i18n("Qty"), dataIndex: 'quantity', - width: 50, + width: 100, renderers: [{ - rtype: "projectReportQuantity" + rtype: "projectReportQuantity", + rendererConfig: { + quantityField: "quantity" + } }] }, { header: i18n("Part Name"), renderers: [{ rtype: "projectReportMetaPart" }], - flex: 1 + flex: 2 }, { header: i18n("Part Description"), dataIndex: "part.description", - flex: 1 + flex: 2 }, { header: i18n("Remarks"), dataIndex: 'remarks', @@ -52,10 +55,10 @@ Ext.define("PartKeepr.Components.Project.ProjectReportResultGrid", { dataIndex: 'projectNames', flex: 1 }, { - header: i18n("Storage Location"), dataIndex: 'storageLocation_name', + header: i18n("Storage Location"), dataIndex: 'part.storageLocation.name', width: 100 }, { - header: i18n("Available"), dataIndex: 'available', + header: i18n("Available"), dataIndex: 'part.stockLevel', width: 75 }, { header: i18n("Distributor"), @@ -76,20 +79,20 @@ Ext.define("PartKeepr.Components.Project.ProjectReportResultGrid", { editable: false } }, { - header: i18n("Distributor Order Number"), dataIndex: 'distributor_order_number', + header: i18n("Distributor Order Number"), dataIndex: 'distributorOrderNumber', flex: 1, editor: { xtype: 'textfield' } }, { - header: i18n("Price per Item"), dataIndex: 'price', + header: i18n("Item Price"), dataIndex: 'itemPrice', renderers: [{ rtype: 'currency' }], width: 100 }, { header: i18n("Sum"), - dataIndex: 'sum', + dataIndex: 'itemSum', renderers: [{ rtype: 'currency' }], @@ -97,11 +100,17 @@ Ext.define("PartKeepr.Components.Project.ProjectReportResultGrid", { summaryRenderer: PartKeepr.getApplication().formatCurrency, width: 100 }, { - header: i18n("Amount to Order"), dataIndex: 'missing', + header: i18n("Order Amount"), dataIndex: 'missing', + renderers: [{ + rtype: "projectReportQuantity", + rendererConfig: { + quantityField: "missing" + } + }], width: 100 }, { header: i18n("Sum (Order)"), - dataIndex: 'sum_order', + dataIndex: 'orderSum', renderers: [{ rtype: 'currency' }], @@ -138,6 +147,15 @@ Ext.define("PartKeepr.Components.Project.ProjectReportResultGrid", { } }); + this.saveReportButton = Ext.create('Ext.button.Button', { + text: i18n("Save Project Report"), + iconCls: 'fugue-icon notification-counter-04', + listeners: { + click: this.onSaveReportClick, + scope: this + } + }); + this.autoFillButton = Ext.create('Ext.button.Button', { text: i18n("Auto-Fill Distributors"), iconCls: 'fugue-icon notification-counter-02', @@ -167,6 +185,7 @@ Ext.define("PartKeepr.Components.Project.ProjectReportResultGrid", { this.bbar = [ this.autoFillButton, this.removeStockButton, + this.saveReportButton, {xtype: 'tbseparator'}, this.nextMetaPart, this.previousMetaPart, @@ -183,7 +202,6 @@ Ext.define("PartKeepr.Components.Project.ProjectReportResultGrid", { ]; - this.callParent(arguments); }, @@ -228,6 +246,25 @@ Ext.define("PartKeepr.Components.Project.ProjectReportResultGrid", { context.column.getEditor().store.clearFilter(); context.column.getEditor().store.addFilter(filter); }, + onSaveReportClick: function () + { + Ext.Msg.prompt( + i18n("Project Report Name"), + i18n("Please enter the project report name:"), + this.doSaveReport, + this, + false, + this.projectReport.get("name") + ); + }, + doSaveReport: function (button, value) + { + if (button === "ok") + { + this.projectReport.set("name", value); + this.projectReportManager.doSaveProjectReport(); + } + }, /** * Removes all parts in the project view. */ @@ -293,6 +330,7 @@ Ext.define("PartKeepr.Components.Project.ProjectReportResultGrid", { return 0; } }, + removeStocks: function (btn) { if (btn === "yes") @@ -305,13 +343,12 @@ Ext.define("PartKeepr.Components.Project.ProjectReportResultGrid", { { var item = store.getAt(i); - removals.push({ part: item.getPart().getId(), amount: item.get("quantity"), - comment: item.get("projectNames"), - lotNumber: item.get("lotNumber"), - projects: item.get("projects") + comment: item.getReport().reportProjects().getFieldValues("project.name").join(", "), + lotNumber: item.projectParts().getFieldValues("lotNumber").join(", "), + projects: [] // item.getReport().reportProjects() }); } @@ -343,10 +380,10 @@ Ext.define("PartKeepr.Components.Project.ProjectReportResultGrid", { { 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")); + context.record.set("itemPrice", partDistributors.getAt(i).get("price")); + context.record.set("distributorOrderNumber", partDistributors.getAt(i).get("orderNumber")); + context.record.set("orderSum", context.record.get("missing") * context.record.get("itemPrice")); + context.record.set("itemSum", context.record.get("quantity") * context.record.get("itemPrice")); } } } @@ -400,10 +437,10 @@ Ext.define("PartKeepr.Components.Project.ProjectReportResultGrid", { if (cheapestDistributor !== null) { projectPart.setDistributor(cheapestDistributor.getDistributor()); - projectPart.set("distributor_order_number", cheapestDistributor.get("orderNumber")); - projectPart.set("price", cheapestDistributor.get("price")); - projectPart.set("sum_order", projectPart.get("missing") * projectPart.get("price")); - projectPart.set("sum", projectPart.get("quantity") * projectPart.get("price")); + projectPart.set("distributorOrderNumber", cheapestDistributor.get("orderNumber")); + projectPart.set("itemPrice", cheapestDistributor.get("price")); + projectPart.set("orderSum", projectPart.get("missing") * projectPart.get("itemPrice")); + projectPart.set("itemSum", projectPart.get("quantity") * projectPart.get("itemPrice")); } }, getCheapestDistributor: function (part) diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/Renderers/QuantityRenderer.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Components/Project/Renderers/QuantityRenderer.js @@ -3,45 +3,56 @@ Ext.define("PartKeepr.Components.ProjectReport.Renderers.QuantityRenderer", { alias: 'columnRenderer.projectReportQuantity', - renderer: function (v, q, rec) + renderer: function (value, metaData, record, rowIndex, colIndex, store, view, renderObj) { - var i, total, titleParts = [], title, projectQuantities; + var quantityField = renderObj.getRendererConfigItem(renderObj, "quantityField", false); - if (rec.get("metaPart")) + if (record.get("metaPart")) { total = 0; - for (i = 0; i < rec.subParts().getCount(); i++) + for (i = 0; i < record.subParts().getCount(); i++) { - if (rec.subParts().getAt(i).get("use")) + if (record.subParts().getAt(i).get("use")) { - total += rec.subParts().getAt(i).get("stockToUse"); + total += record.subParts().getAt(i).get("stockToUse"); } } - return total + " / " + v; + return total + " / " + value; } else { - projectQuantities = rec.get("projectQuantities"); + title = renderObj.getProjectParts(record); + return '<span class="web-icon fugue-icon information-small-white" title="' + title + '"></span> '+ record.get(quantityField) + " " + record.getPart().getPartUnit().get("shortName"); + } + }, + getProjectParts: function (rec) { + var report = rec.getReport(), + i,j, project, projectPart, projectPartQuantities = []; - if (projectQuantities instanceof Array) - { - for (i = 0; i < projectQuantities.length; i++) - { + for (i=0;i<report.reportProjects().getCount();i++) { + project = report.reportProjects().getAt(i).getProject(); - titleParts.push(projectQuantities[i].projectName + ": " + projectQuantities[i].quantity); - } + for (j=0;j<project.parts().getCount();j++) { + projectPart = project.parts().getAt(j); - title = titleParts.join("&#013;&#010;"); - return '<span class="web-icon fugue-icon information-small-white" title="' + title + '"></span> ' + v; - } else { - return v; + if (projectPart.getPart().getId() === rec.getPart().getId() ) { + projectPartQuantities.push(project.get("name")+ ": "+projectPart.get("totalQuantity")); + } } } + + return projectPartQuantities.join("&#013;&#010;") }, statics: { rendererName: i18n("Project Report Quantity Renderer"), rendererDescription: i18n("Renders the amount of required metadata quantities"), + rendererConfigs: { + parameterName: { + type: 'quantityField', + title: i18n("Field name which denotes the quantity") + } + }, restrictToEntity: ["PartKeepr.PartBundle.Entity.ProjectReport"] } diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Data/HydraProxy.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Data/HydraProxy.js @@ -192,7 +192,7 @@ Ext.define("PartKeepr.data.HydraProxy", { this.processCallActionResponse(options, success, response, ignoreException, action); if (Ext.isFunction(callback)) { - callback(options, success, response); + callback(options, success, response, request); } }.bind(this)); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Data/HydraReader.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Data/HydraReader.js @@ -1,9 +1,17 @@ +/** + * JSON-LD / Hydra Reader. + * + * Supports in-line references. + */ Ext.define("PartKeepr.data.HydraReader", { extend: 'Ext.data.reader.Json', alias: 'reader.hydra', totalProperty: 'hydra:totalItems', + replacements: [], + loadedRecords: {}, + getResponseData: function (response) { var data = this.callParent([response]); @@ -13,5 +21,147 @@ Ext.define("PartKeepr.data.HydraReader", { this.setRootProperty(""); } return data; + }, + + read: function(response, readOptions) { + var data, result, responseText; + + if (response) { + responseText = response.responseText; + if (responseText) { + result = this.getResponseData(response); + if (result && result.__$isError) { + return new Ext.data.ResultSet({ + total : 0, + count : 0, + records: [], + success: false, + message: result.msg + }); + } else { + data = this.readRecords(result, readOptions); + } + } else if (responseText !== '') { + data = this.readRecords(response, readOptions); + } + } + + var replacement; + + for (var i=0;i<this.replacements.length;i++) { + replacement = this.replacements[i]; + + if (typeof this.loadedRecords[replacement.id] === "object") + { + replacement.record[replacement.setterName](this.loadedRecords[replacement.id]); + } + } + + return data || this.nullResultSet; + }, + /** + * Loads the record associations from the data object. Supports late-tree replacements. + * + * @param {Ext.data.Model} record The record to load associations for. + * @param {Object} data The raw data object. + * @param {Object} readOptions See {@link #read}. + * + * @private + */ + readAssociated: function(record, data, readOptions) { + var roles = record.associations, + key, role; + + for (key in roles) { + if (roles.hasOwnProperty(key)) { + role = roles[key]; + // The class for the other role may not have loaded yet + if (role.cls) { + if (typeof data[role.role] === "string") { + // Association is a string, save for later + this.replacements.push({ + record: record, + associationKey: role.role, + id: data[role.role], + setterName: role.setterName + }); + } else { + role.read(record, data, this, readOptions); + } + + } + } + } + }, + + /** + * Overrides the changes of the JsonReader to support referenced associations. + * @param root + * @param readOptions + * @returns {Array} + */ + extractData: function(root, readOptions) { + + var me = this, + entityType = readOptions && readOptions.model ? Ext.data.schema.Schema.lookupEntity(readOptions.model) : me.getModel(), + schema = entityType.schema, + includes = schema.hasAssociations(entityType) && me.getImplicitIncludes(), + fieldExtractorInfo = me.getFieldExtractorInfo(entityType.fieldExtractors), + length = root.length, + records = new Array(length), + typeProperty = me.getTypeProperty(), + reader, node, nodeType, record, i; + + if (!length && Ext.isObject(root)) { + root = [root]; + length = 1; + } + + for (i = 0; i < length; i++) { + record = root[i]; + if (!record.isModel) { + // If we're given a model instance in the data, just push it on + // without doing any conversion. Otherwise, create a record. + node = record; + + // This Reader may be configured to produce different model types based on + // a differentiator field in the incoming data: + // typeProperty name be a string, a function which yields the child type, or an object: { + // name: 'mtype', + // namespace: 'MyApp' + // } + if (typeProperty && (nodeType = me.getChildType(schema, node, typeProperty))) { + + reader = nodeType.getProxy().getReader(); + + record = reader.extractRecord(node, readOptions, nodeType, + schema.hasAssociations(nodeType) && reader.getImplicitIncludes(), + reader.getFieldExtractorInfo(nodeType.fieldExtractors)); + + } else { + record = me.extractRecord(node, readOptions, entityType, includes, + fieldExtractorInfo); + } + + var kk = {}; + + this.loadedRecords[record.getId()] = record; + + // Generally we don't want to have references to XML documents + // or XML nodes to hang around in memory but Trees need to be able + // to access the raw XML node data in order to process its children. + // See https://sencha.jira.com/browse/EXTJS-15785 and + // https://sencha.jira.com/browse/EXTJS-14286 + if (record.isModel && record.isNode) { + record.raw = node; + } + } + if (record.onLoad) { + record.onLoad(); + } + records[i] = record; + } + + return records; } }); \ No newline at end of file diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/ExtJS/Enhancements/Ext.data.Store.getFieldValue.js b/src/PartKeepr/FrontendBundle/Resources/public/js/ExtJS/Enhancements/Ext.data.Store.getFieldValue.js @@ -0,0 +1,20 @@ +Ext.define("PartKeepr.data.Store", { + override: "Ext.data.Store", + + /** + * Retrieves a specific field from all records in the store + * @param field + * @returns {Array} + */ + getFieldValues: function (field) { + var i; + var result = []; + + for (i=0;i<this.getCount();i++) { + console.log(this.getAt(i)); + result.push(this.getAt(i).get(field)); + } + + return result; + } +}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Models/ProjectReport.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Models/ProjectReport.js @@ -1,34 +0,0 @@ -/** - * 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: 'projectNames', type: 'string'}, - {name: 'projects', type: 'string'}, - {name: 'remarks', type: 'string'}, - {name: 'productionRemarks', type: 'string'}, - {name: 'part', reference: 'PartKeepr.PartBundle.Entity.Part'}, - {name: 'distributor', reference: 'PartKeepr.DistributorBundle.Entity.Distributor'} - ], - - hasMany: [ - { - name: 'subParts', - associationKey: 'subParts', - model: 'PartKeepr.PartBundle.Entity.Part' - } - ], - - proxy: { - type: "Hydra", - url: '/api/project_reports' - } -}); diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Models/ProjectReportPart.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Models/ProjectReportPart.js @@ -1,8 +0,0 @@ -Ext.define("PartKeepr.ProjectBundle.Entity.ProjectReportPart", { - extend: "PartKeepr.PartBundle.Entity.Part", - - constructor: function () { - this.fields.push({name: 'stockToUse', type: 'int'}); - this.callParent(arguments); - } -}); diff --git a/src/PartKeepr/FrontendBundle/Resources/views/index.html.twig b/src/PartKeepr/FrontendBundle/Resources/views/index.html.twig @@ -66,6 +66,7 @@ '@PartKeeprFrontendBundle/Resources/public/js/Data/CallActions.js' '@PartKeeprFrontendBundle/Resources/public/js/Data/field/Array.js' '@PartKeeprFrontendBundle/Resources/public/js/Data/HydraModel.js' + '@PartKeeprFrontendBundle/Resources/public/js/Data/HydraField.js' '@PartKeeprFrontendBundle/Resources/public/js/Data/HydraTreeModel.js' '@PartKeeprFrontendBundle/Resources/public/js/Data/store/ModelStore.js' '@PartKeeprFrontendBundle/Resources/public/js/ExtJS/Bugfixes/Ext.form.field.Checkbox.EXTJS-21886.js' @@ -85,6 +86,7 @@ {% endjavascripts %} {% javascripts output='js/compiled/main2.js' + '@PartKeeprFrontendBundle/Resources/public/js/ExtJS/Enhancements/Ext.data.Store.getFieldValue.js' '@PartKeeprFrontendBundle/Resources/public/js/Util/i18n.js' '@PartKeeprFrontendBundle/Resources/public/js/Data/store/CurrencyStore.js' '@PartKeeprFrontendBundle/Resources/public/js/ExtJS/Enhancements/Ext.grid.Column-multipleRendererSupport.js' @@ -143,7 +145,6 @@ '@PartKeeprFrontendBundle/Resources/public/js/Data/store/UserProvidersStore.js' '@PartKeeprFrontendBundle/Resources/public/js/Components/Project/Renderers/MetaPartRenderer.js' '@PartKeeprFrontendBundle/Resources/public/js/Components/Project/Renderers/QuantityRenderer.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/Models/StatisticSample.js' @@ -291,6 +292,7 @@ '@PartKeeprFrontendBundle/Resources/public/js/Components/Project/ProjectReportList.js' '@PartKeeprFrontendBundle/Resources/public/js/Components/Project/ProjectReport.js' '@PartKeeprFrontendBundle/Resources/public/js/Components/Project/ProjectReportResultGrid.js' + '@PartKeeprFrontendBundle/Resources/public/js/Components/Project/ProjectReportGrid.js' '@PartKeeprFrontendBundle/Resources/public/js/Components/Statistics/StatisticsChart.js' '@PartKeeprFrontendBundle/Resources/public/js/Components/Statistics/StatisticsChartPanel.js' '@PartKeeprFrontendBundle/Resources/public/js/Components/Statistics/SummaryStatisticsPanel.js' diff --git a/src/PartKeepr/ImportBundle/Configuration/BaseConfiguration.php b/src/PartKeepr/ImportBundle/Configuration/BaseConfiguration.php @@ -10,24 +10,24 @@ use PartKeepr\DoctrineReflectionBundle\Services\ReflectionService; class BaseConfiguration { + public static $logs = []; + public static $persistEntities = []; protected $baseEntity; - protected $classMetadata; - protected $reflectionService; - protected $em; - protected $advancedSearchFilter; - protected $iriConverter; - - public static $logs = []; - - public static $persistEntities = []; - - public function __construct(ClassMetadata $classMetadata, $baseEntity, ReflectionService $reflectionService, EntityManager $em, AdvancedSearchFilter $advancedSearchFilter, IriConverter $iriConverter) - { + private $path = []; + + public function __construct( + ClassMetadata $classMetadata, + $baseEntity, + ReflectionService $reflectionService, + EntityManager $em, + AdvancedSearchFilter $advancedSearchFilter, + IriConverter $iriConverter + ) { $this->classMetadata = $classMetadata; $this->baseEntity = $baseEntity; $this->reflectionService = $reflectionService; @@ -36,6 +36,35 @@ class BaseConfiguration $this->iriConverter = $iriConverter; } + /** + * Returns the path of this configuration node with an optional suffix. + * + * @param bool|string $suffix Set to any string to return an additional suffix, or false to skip + * + * @return array The individual path components + */ + public function getPath($suffix = false) + { + if ($suffix !== false) { + $path = $this->path; + $path[] = $suffix; + + return $path; + } else { + return $this->path; + } + } + + /** + * Sets a path for this configuration node. + * + * @param array $path + */ + public function setPath($path) + { + $this->path = $path; + } + public function import($row) { } diff --git a/src/PartKeepr/ImportBundle/Configuration/Configuration.php b/src/PartKeepr/ImportBundle/Configuration/Configuration.php @@ -29,6 +29,8 @@ class Configuration extends BaseConfiguration $fieldConfiguration = new FieldConfiguration($this->classMetadata, $this->baseEntity, $this->reflectionService, $this->em, $this->advancedSearchFilter, $this->iriConverter); $fieldConfiguration->setFieldName($field); + + $fieldConfiguration->setPath($this->getPath($field)); if ($fieldConfiguration->parseConfiguration($configuration) !== false) { $this->fields[] = $fieldConfiguration; } @@ -43,9 +45,11 @@ class Configuration extends BaseConfiguration if ($this->classMetadata->hasAssociation($manyToOne)) { $targetClass = $this->classMetadata->getAssociationTargetClass($manyToOne); $cm = $this->em->getClassMetadata($targetClass); + $manyToOneconfiguration = new ManyToOneConfiguration($cm, $targetClass, $this->reflectionService, $this->em, $this->advancedSearchFilter, $this->iriConverter); $manyToOneconfiguration->setAssociationName($manyToOne); + $manyToOneconfiguration->setPath($this->getPath($manyToOne)); if ($manyToOneconfiguration->parseConfiguration($configuration) !== false) { $this->manyToOneAssociations[] = $manyToOneconfiguration; @@ -64,7 +68,7 @@ class Configuration extends BaseConfiguration $oneToManyConfiguration = new OneToManyConfiguration($cm, $targetClass, $this->reflectionService, $this->em, $this->advancedSearchFilter, $this->iriConverter); $oneToManyConfiguration->setAssociationName($oneToMany); - + $oneToManyConfiguration->setPath($this->getPath($oneToMany)); if ($oneToManyConfiguration->parseConfiguration($configuration) !== false) { $this->oneToManyAssociations[] = $oneToManyConfiguration; } diff --git a/src/PartKeepr/ImportBundle/Configuration/EntityConfiguration.php b/src/PartKeepr/ImportBundle/Configuration/EntityConfiguration.php @@ -28,7 +28,7 @@ class EntityConfiguration extends Configuration public function parseConfiguration($importConfiguration) { if (!property_exists($importConfiguration, "importBehaviour")) { - throw new \Exception("The key importBehaviour does not exist!"); + throw new \Exception(sprintf("The key importBehaviour does not exist for path /%s!", implode("/", $this->getPath()))); } if (!in_array($importConfiguration->importBehaviour, self::importBehaviours)) { @@ -84,6 +84,8 @@ class EntityConfiguration extends Configuration case self::IMPORTBEHAVIOUR_ALWAYSIMPORT: $obj = new $this->baseEntity(); $this->persist($obj); + + parent::import($row, $obj); break; case self::IMPORTBEHAVIOUR_MATCHDATA: $configuration = []; diff --git a/src/PartKeepr/ImportBundle/Configuration/FieldConfiguration.php b/src/PartKeepr/ImportBundle/Configuration/FieldConfiguration.php @@ -81,7 +81,7 @@ class FieldConfiguration extends BaseConfiguration { switch ($this->fieldConfiguration) { case self::FIELDCONFIGURATION_FIXEDVALUE: - $this->log(sprintf("Would set field %s to fixed value %s", $this->fieldName, $this->fixedValue)); + $this->log(sprintf("Set field %s to fixed value %s", $this->fieldName, $this->fixedValue)); return $this->fixedValue; break; @@ -91,7 +91,7 @@ class FieldConfiguration extends BaseConfiguration return null; } - $this->log(sprintf("Would set field %s to value %s (import column %s)", $this->fieldName, $row[$this->copyFromField], $this->copyFromField)); + $this->log(sprintf("Set field %s to value %s (import column %s)", $this->fieldName, $row[$this->copyFromField], $this->copyFromField)); return $row[$this->copyFromField]; break; diff --git a/src/PartKeepr/ImportBundle/Configuration/ManyToOneConfiguration.php b/src/PartKeepr/ImportBundle/Configuration/ManyToOneConfiguration.php @@ -91,7 +91,7 @@ class ManyToOneConfiguration extends Configuration } if (!in_array($importConfiguration->updateBehaviour, self::updateBehaviours)) { - throw new \Exception("Invalid value for updateBehaviour"); + throw new \Exception(sprintf("Invalid value for updateBehaviour: %s", $importConfiguration->updateBehaviour)); } $this->updateBehaviour = $importConfiguration->updateBehaviour; @@ -128,7 +128,7 @@ class ManyToOneConfiguration extends Configuration switch ($this->importBehaviour) { case self::IMPORTBEHAVIOUR_ALWAYSSETTO: $targetEntity = $this->iriConverter->getItemFromIri($this->setToEntity); - $this->log(sprintf("Would set %s to %s#%s", $this->associationName, $this->baseEntity, $targetEntity->getId())); + $this->log(sprintf("Set %s to %s#%s", $this->associationName, $this->baseEntity, $targetEntity->getId())); return $targetEntity; break; @@ -161,7 +161,7 @@ class ManyToOneConfiguration extends Configuration // @todo Update the entity with the specified values } - $this->log(sprintf("Would set %s to %s#%s", $this->associationName, $this->baseEntity, $result->getId())); + $this->log(sprintf("Set %s to %s#%s", $this->associationName, $this->baseEntity, $result->getId())); return $result; } catch (\Exception $e) { @@ -169,16 +169,16 @@ class ManyToOneConfiguration extends Configuration switch ($this->notFoundBehaviour) { case self::NOTFOUNDBEHAVIOUR_STOPIMPORT: - $this->log(sprintf("Would stop import as the match %s for association %s was not found", implode(",", $descriptions), $this->getAssociationName())); + $this->log(sprintf("Stop import as the match %s for association %s was not found", implode(",", $descriptions), $this->getAssociationName())); break; case self::NOTFOUNDBEHAVIOUR_SETTOENTITY: $targetEntity = $this->iriConverter->getItemFromIri($this->notFoundSetToEntity); - $this->log(sprintf("Would set the association %s to %s, since the match %s for association %s was not found", $this->getAssociationName(), $this->notFoundSetToEntity, implode(",", $descriptions))); + $this->log(sprintf("Set the association %s to %s, since the match %s for association %s was not found", $this->getAssociationName(), $this->notFoundSetToEntity, implode(",", $descriptions))); return $targetEntity; break; case self::NOTFOUNDBEHAVIOUR_CREATEENTITY: - $this->log(sprintf("Would create a new entity of type %s", $this->baseEntity)); + $this->log(sprintf("Create a new entity of type %s", $this->baseEntity)); return parent::import($row); break; diff --git a/src/PartKeepr/ImportBundle/Configuration/OneToManyConfiguration.php b/src/PartKeepr/ImportBundle/Configuration/OneToManyConfiguration.php @@ -38,7 +38,7 @@ class OneToManyConfiguration extends Configuration return null; break; case self::IMPORTBEHAVIOUR_CREATENEW: - $this->log(sprintf("Would create a new entity of type %s for relation %s", $this->baseEntity, $this->getAssociationName())); + $this->log(sprintf("Create a new entity of type %s for relation %s", $this->baseEntity, $this->getAssociationName())); return parent::import($row); break; diff --git a/src/PartKeepr/ImportBundle/Controller/ImportController.php b/src/PartKeepr/ImportBundle/Controller/ImportController.php @@ -44,7 +44,12 @@ class ImportController extends Controller $importService->setBaseEntity($baseEntity); $importService->setImportConfiguration($configuration); $importService->setImportData($data); - list($entities, $logs) = $importService->import(); + + try { + list($entities, $logs) = $importService->import(true); + } catch (\Exception $e) { + $logs = [$e->getMessage()]; + } return new JsonResponse(["logs" => $logs]); } diff --git a/src/PartKeepr/ImportBundle/Service/ImporterService.php b/src/PartKeepr/ImportBundle/Service/ImporterService.php @@ -61,7 +61,7 @@ class ImporterService $this->importData = $importData; } - public function import() + public function import($preview = false) { $entities = []; $logs = []; @@ -90,7 +90,11 @@ class ImporterService $this->em->commit(); } - $this->em->commit(); + if ($preview) { + $this->em->rollback(); + } else { + $this->em->commit(); + } return [$configuration->getPersistEntities(), implode("<br/>", $logs)]; } diff --git a/src/PartKeepr/PartBundle/Controller/PartController.php b/src/PartKeepr/PartBundle/Controller/PartController.php @@ -64,7 +64,6 @@ class PartController extends FOSRestController $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'); } diff --git a/src/PartKeepr/PartBundle/Entity/PartDistributor.php b/src/PartKeepr/PartBundle/Entity/PartDistributor.php @@ -82,10 +82,11 @@ class PartDistributor extends BaseEntity private $sku; /** - * Defines if the distributor is ignored for pricing calculations + * Defines if the distributor is ignored for pricing calculations. * * @ORM\Column(type="boolean") * @Groups({"default"}) + * * @var bool */ private $ignoreForReports = 0; diff --git a/src/PartKeepr/ProjectBundle/Controller/ProjectReportController.php b/src/PartKeepr/ProjectBundle/Controller/ProjectReportController.php @@ -2,167 +2,130 @@ namespace PartKeepr\ProjectBundle\Controller; -use Dunglas\ApiBundle\Api\IriConverter; -use FOS\RestBundle\Controller\Annotations\View; +use Dunglas\ApiBundle\Action\ActionUtilTrait; +use Dunglas\ApiBundle\Api\ResourceInterface; use FOS\RestBundle\Controller\FOSRestController; -use PartKeepr\PartBundle\Entity\Part; use PartKeepr\ProjectBundle\Entity\ProjectPart; -use Sensio\Bundle\FrameworkExtraBundle\Configuration as Routing; +use PartKeepr\ProjectBundle\Entity\Report; use Symfony\Component\HttpFoundation\Request; class ProjectReportController extends FOSRestController { + use ActionUtilTrait; + /** - * @Routing\Route("/api/project_reports", defaults={"method" = "get","_format" = "json"}) - * @View() - * * @param Request $request * * @throws \Exception Thrown if parameters are formatted incorrectly * - * @return array + * @return \Symfony\Component\HttpFoundation\Response */ - public function getProjectReportAction(Request $request) + public function createReportAction(Request $request) { - $projectsParameter = json_decode($request->get('projects')); - - if (!is_array($projectsParameter)) { - throw new \Exception('projects must be an array'); - } - /** - * @var IriConverter + * @var ResourceInterface */ - $iriConverter = $this->get('api.iri_converter'); - - $projects = []; - - 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[] = ['project' => $project, 'quantity' => $projectParameter->quantity]; - } - - $partRepository = $this->get('doctrine.orm.entity_manager')->getRepository( - 'PartKeepr\\PartBundle\\Entity\\Part' + list($resourceType, $format) = $this->extractAttributes($request); + $report = $this->get("api.serializer")->deserialize( + $request->getContent(), + $resourceType->getEntityClass(), + $format, + $resourceType->getDenormalizationContext() ); - $aPartResults = []; - - foreach ($projects as $report) { - $dql = 'SELECT pp.quantity, pro.name AS projectname, pp.overage, pp.overageType, pp.remarks, pp.lotNumber, '; - $dql .= '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']); - $projectIRI = $iriConverter->getIriFromItem($report['project']); - - foreach ($query->getArrayResult() as $result) { - $partId = $result['id']; - - $part = $partRepository->find($partId); - /** - * @var Part $part - */ - if ($result["overageType"] === ProjectPart::OVERAGE_TYPE_PERCENT) { - $overage = $result['quantity'] * $report['quantity'] * ($result["overage"] / 100); + /** + * @var $report Report + */ + foreach ($report->getReportProjects() as $reportProject) { + foreach ($reportProject->getProject()->getParts() as $projectPart) { + if ($projectPart->getOverageType() === ProjectPart::OVERAGE_TYPE_PERCENT) { + $overage = $reportProject->getQuantity() * $projectPart->getQuantity() * ($projectPart->getOverage( + ) / 100); } else { - $overage = $result["overage"]; + $overage = $projectPart->getOverage(); } - if (array_key_exists($partId, $aPartResults)) { - // Only update the quantity of the part + $quantity = $reportProject->getQuantity() * $projectPart->getQuantity() + $overage; + $report->addPartQuantity($projectPart->getPart(), $projectPart, $quantity); + } + } - $aPartResults[$partId]['quantity'] += ($result['quantity'] * $report['quantity']) + $overage; - $aPartResults[$partId]['projectNames'][] = $result['projectname']; - $aPartResults[$partId]['projects'][] = $projectIRI; - } else { - $serializedData = $this->get('serializer')->normalize( - $part, - 'jsonld' - ); + $this->get("doctrine.orm.default_entity_manager")->persist($report); + $this->get("doctrine.orm.default_entity_manager")->flush(); - $storageLocationName = ""; - - if ($part->getStorageLocation() !== null) { - $storageLocationName = $part->getStorageLocation()->getName(); - } - - $subParts = []; - - if ($part->isMetaPart()) { - $matchingParts = $this->container->get("partkeepr.part_service")->getMatchingMetaParts($part); - foreach ($matchingParts as $matchingPart) { - $subParts[] = $this->get('serializer')->normalize( - $matchingPart, - 'jsonld' - ); - } - } - - // Create a full resultset - $aPartResults[$result['id']] = [ - 'quantity' => ($result['quantity'] * $report['quantity']) + $overage, - 'part' => $serializedData, - 'storageLocation_name' => $storageLocationName, - 'available' => $part->getStockLevel(), - 'sum_order' => 0, - 'projectNames' => [$result['projectname']], - 'projects' => [$projectIRI], - 'subParts' => $subParts, - 'metaPart' => $part->isMetaPart(), - 'productionRemarks' => $part->getProductionRemarks(), - 'lotNumber' => $result['lotNumber'], - 'remarks' => [], - ]; + $response = new \Symfony\Component\HttpFoundation\Response( + $this->get('serializer')->serialize( + $report, + 'jsonld' + ) + ); + $response->headers->set("Content-Type", "text/json"); - } + return $response; + } - $aPartResults[$partId]['projectQuantities'][] = [ - "project" => $projectIRI, - "projectName" => $result['projectname'], - "quantity" => ($result['quantity'] * $report['quantity']) + $overage - ]; + /** + * @param Request $request + * + * @throws \Exception Thrown if parameters are formatted incorrectly + * + * @return \Symfony\Component\HttpFoundation\Response + */ + public function getReportAction(Request $request, $id) + { + /** + * @var $report Report + */ + $report = $this->get("doctrine.orm.default_entity_manager")->getRepository( + "PartKeeprProjectBundle:Report" + )->find($id); + $this->calculateMissingParts($report); + $this->prepareMetaPartInformation($report); + + $response = new \Symfony\Component\HttpFoundation\Response( + $this->get('serializer')->serialize( + $report, + 'jsonld' + ) + ); - if ($result['remarks'] != '') { - $aPartResults[$result['id']]['remarks'] = [$result['projectname'].': '.$result['remarks']]; - } - } - } + $response->headers->set("Content-Type", "text/json"); - $aFinalResult = []; + return $response; + } - // Iterate over all results and calculate how many parts are missing - foreach ($aPartResults as $key => $partResult) { - $missing = $partResult['quantity'] - $partResult['available']; + public function calculateMissingParts(Report $report) + { + foreach ($report->getReportParts() as $reportPart) { + $missing = $reportPart->getQuantity() - $reportPart->getPart()->getStockLevel(); if ($missing < 0) { $missing = 0; } - $partResult['missing'] = $missing; - $partResult['remarks'] = implode(', ', $partResult['remarks']); - $partResult['projectNames'] = implode(', ', $partResult['projectNames']); - $partResult['projects'] = json_encode($partResult['projects']); - - $aFinalResult[] = $partResult; + $reportPart->setMissing($missing); } + } - return $aFinalResult; + public function prepareMetaPartInformation(Report $report) + { + foreach ($report->getReportParts() as $reportPart) { + $subParts = []; + + if ($reportPart->getPart()->isMetaPart()) { + $matchingParts = $this->container->get("partkeepr.part_service")->getMatchingMetaParts( + $reportPart->getPart() + ); + foreach ($matchingParts as $matchingPart) { + $subParts[] = $this->get('serializer')->normalize( + $matchingPart, + 'jsonld' + ); + } + $reportPart->setMetaPart(true); + $reportPart->setSubParts($subParts); + } + } } } diff --git a/src/PartKeepr/ProjectBundle/Entity/ProjectPart.php b/src/PartKeepr/ProjectBundle/Entity/ProjectPart.php @@ -97,6 +97,32 @@ class ProjectPart extends BaseEntity */ private $lotNumber; + /** + * The total quantity including overage. + * + * @Groups({"default"}) + * + * @var int + */ + private $totalQuantity; + + /** + * Retrieves the total quantity for a project part, including overage. + * + * @return int + */ + public function getTotalQuantity() + { + switch ($this->getOverageType()) { + case self::OVERAGE_TYPE_PERCENT: + return (int) $this->getQuantity() * (1 + $this->getOverage() / 100); + case self::OVERAGE_TYPE_ABSOLUTE: + return $this->getQuantity() + $this->getOverage(); + default: + return $this->getQuantity(); + } + } + public function __construct() { $this->setOverageType(self::OVERAGE_TYPE_ABSOLUTE); diff --git a/src/PartKeepr/ProjectBundle/Entity/Report.php b/src/PartKeepr/ProjectBundle/Entity/Report.php @@ -0,0 +1,214 @@ +<?php + +namespace PartKeepr\ProjectBundle\Entity; + +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\ORM\Mapping as ORM; +use PartKeepr\CoreBundle\Entity\BaseEntity; +use PartKeepr\DoctrineReflectionBundle\Annotation\TargetService; +use PartKeepr\PartBundle\Entity\Part; +use Symfony\Component\Serializer\Annotation\Groups; + +/** + * Represents a project part. + * + * @ORM\Entity + * @TargetService("/api/reports") + */ +class Report extends BaseEntity +{ + /** + * The report name. + * + * @ORM\Column(type="string",nullable=true) + * @Groups({"default"}) + * + * @var string + */ + private $name; + + /** + * @ORM\Column(type="datetime") + * @Groups({"default"}) + * + * @var \DateTime + */ + private $createDateTime; + + /** + * @ORM\OneToMany( + * targetEntity="PartKeepr\ProjectBundle\Entity\ReportProject", + * mappedBy="report", + * cascade={"persist", "remove"}, + * orphanRemoval=true + * ) + * @Groups({"default"}) + * + * @var ArrayCollection + */ + private $reportProjects; + + /** + * @ORM\OneToMany( + * targetEntity="PartKeepr\ProjectBundle\Entity\ReportPart", + * mappedBy="report", + * cascade={"persist", "remove"}, + * orphanRemoval=true + * ) + * @Groups({"default"}) + * + * @var ArrayCollection + */ + private $reportParts; + + public function __construct() + { + $this->reportProjects = new ArrayCollection(); + $this->reportParts = new ArrayCollection(); + $this->setCreateDateTime(new \DateTime()); + } + + /** + * @return \DateTime + */ + public function getCreateDateTime() + { + return $this->createDateTime; + } + + /** + * @param \DateTime $createDateTime + * + * @return Report + */ + public function setCreateDateTime($createDateTime) + { + $this->createDateTime = $createDateTime; + + return $this; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + * + * @return Report + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + /** + * @return ArrayCollection|ReportProject[] + */ + public function getReportProjects() + { + return $this->reportProjects; + } + + /** + * Adds a Report Project. + * + * @param ReportProject $reportProject A report project to add + * + * @return Report + */ + public function addReportProject($reportProject) + { + $reportProject->setReport($this); + $this->reportProjects->add($reportProject); + + return $this; + } + + /** + * Removes a Report Project. + * + * @param ReportProject $reportProject A report project to add + * + * @return Report + */ + public function removeReportProject($reportProject) + { + $reportProject->setReport(null); + $this->reportProjects->removeElement($reportProject); + + return $this; + } + + /** + * Removes a Report Part. + * + * @param ReportPart $reportPart A report project to add + * + * @return Report + */ + public function removeReportPart($reportPart) + { + $reportPart->setReport(null); + $this->reportProjects->removeElement($reportPart); + + return $this; + } + + public function addPartQuantity(Part $part, ProjectPart $projectPart, $quantity) + { + $reportPart = $this->getReportPartByPart($part); + + if ($reportPart === null) { + $reportPart = new ReportPart(); + $reportPart->setPart($part); + $reportPart->setReport($this); + + $this->addReportPart($reportPart); + } + + $reportPart->setQuantity($reportPart->getQuantity() + $quantity); + + $reportPart->getProjectParts()->add($projectPart); + } + + public function getReportPartByPart(Part $part) + { + foreach ($this->getReportParts() as $reportPart) { + if ($reportPart->getPart() === $part) { + return $reportPart; + } + } + + return null; + } + + /** + * @return ArrayCollection|ReportPart[] + */ + public function getReportParts() + { + return $this->reportParts; + } + + /** + * Adds a Report Part. + * + * @param ReportPart $reportPart report project to add + * + * @return Report + */ + public function addReportPart($reportPart) + { + $reportPart->setReport($this); + $this->reportParts->add($reportPart); + + return $this; + } +} diff --git a/src/PartKeepr/ProjectBundle/Entity/ReportPart.php b/src/PartKeepr/ProjectBundle/Entity/ReportPart.php @@ -0,0 +1,339 @@ +<?php + +namespace PartKeepr\ProjectBundle\Entity; + +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\ORM\Mapping as ORM; +use PartKeepr\CoreBundle\Entity\BaseEntity; +use PartKeepr\DistributorBundle\Entity\Distributor; +use PartKeepr\DoctrineReflectionBundle\Annotation\TargetService; +use PartKeepr\DoctrineReflectionBundle\Annotation\VirtualOneToMany; +use PartKeepr\PartBundle\Entity\Part; +use Symfony\Component\Serializer\Annotation\Groups; + +/** + * Represents a project part. + * + * @ORM\Entity + * @TargetService("/api/project_report_parts") + */ +class ReportPart extends BaseEntity +{ + /** + * @ORM\ManyToOne(targetEntity="PartKeepr\ProjectBundle\Entity\Report",inversedBy="reportParts") + * @Groups({"default"}) + * + * @var Report + */ + private $report; + + /** + * @ORM\ManyToOne(targetEntity="PartKeepr\PartBundle\Entity\Part") + * @Groups({"default"}) + * + * @var Part + */ + private $part; + + /** + * @ORM\Column(type="integer",nullable=false) + * @Groups({"default"}) + * + * @var int + */ + private $quantity; + + /** + * @ORM\ManyToOne(targetEntity="PartKeepr\DistributorBundle\Entity\Distributor") + * @Groups({"default"}) + * + * @var Distributor + */ + private $distributor; + + /** + * @Groups({"default"}) + * + * @var string + */ + private $distributorOrderNumber; + + /** + * @Groups({"default"}) + * + * @var string + */ + private $itemPrice; + + /** + * @Groups({"default"}) + * + * @var string + */ + private $orderSum; + + /** + * @Groups({"default"}) + * + * @var bool + */ + private $metaPart; + + /** + * @VirtualOneToMany(target="PartKeepr\PartBundle\Entity\Part") + * @Groups({"default"}) + * + * @var Part[] + */ + private $subParts; + + /** + * @VirtualOneToMany(target="PartKeepr\ProjectBundle\Entity\ProjectPart") + * @Groups({"default"}) + * + * @var ProjectPart[] + */ + private $projectParts; + /** + * @Groups({"default"}) + * + * @var string + */ + private $itemSum; + + /** + * @Groups({"default"}) + * + * @var int + */ + private $missing; + + public function __construct() + { + $this->projectParts = new ArrayCollection(); + } + + /** + * @return ArrayCollection|ProjectPart[] + */ + public function getProjectParts() + { + if (!$this->projectParts instanceof ArrayCollection) { + $this->projectParts = new ArrayCollection(); + } + + return $this->projectParts; + } + + /** + * @return Part[] + */ + public function getSubParts() + { + return $this->subParts; + } + + /** + * @param Part[] $subParts + */ + public function setSubParts($subParts) + { + $this->subParts = $subParts; + } + + /** + * @return bool + */ + public function isMetaPart() + { + return $this->metaPart; + } + + /** + * @param bool $metaPart + */ + public function setMetaPart($metaPart) + { + $this->metaPart = $metaPart; + } + + /** + * @return string + */ + public function getDistributorOrderNumber() + { + return $this->distributorOrderNumber; + } + + /** + * @param string $distributorOrderNumber + * + * @return ReportPart + */ + public function setDistributorOrderNumber($distributorOrderNumber) + { + $this->distributorOrderNumber = $distributorOrderNumber; + + return $this; + } + + /** + * @return string + */ + public function getItemPrice() + { + return $this->itemPrice; + } + + /** + * @param string $itemPrice + * + * @return ReportPart + */ + public function setItemPrice($itemPrice) + { + $this->itemPrice = $itemPrice; + + return $this; + } + + /** + * @return string + */ + public function getOrderSum() + { + return $this->orderSum; + } + + /** + * @param string $orderSum + * + * @return ReportPart + */ + public function setOrderSum($orderSum) + { + $this->orderSum = $orderSum; + + return $this; + } + + /** + * @return string + */ + public function getItemSum() + { + return $this->itemSum; + } + + /** + * @param string $itemSum + * + * @return ReportPart + */ + public function setItemSum($itemSum) + { + $this->itemSum = $itemSum; + + return $this; + } + + /** + * @return mixed + */ + public function getDistributor() + { + return $this->distributor; + } + + /** + * @param mixed $distributor + * + * @return ReportPart + */ + public function setDistributor($distributor) + { + $this->distributor = $distributor; + + return $this; + } + + /** + * @return int + */ + public function getMissing() + { + return $this->missing; + } + + /** + * @param int $missing + * + * @return ReportPart + */ + public function setMissing($missing) + { + $this->missing = $missing; + + return $this; + } + + /** + * @return int + */ + public function getQuantity() + { + return $this->quantity; + } + + /** + * @param int $quantity + * + * @return ReportPart + */ + public function setQuantity($quantity) + { + $this->quantity = $quantity; + + return $this; + } + + /** + * @return Report + */ + public function getReport() + { + return $this->report; + } + + /** + * @param Report $report + * + * @return ReportPart + */ + public function setReport($report) + { + $this->report = $report; + + return $this; + } + + /** + * @return Part + */ + public function getPart() + { + return $this->part; + } + + /** + * @param mixed $part + * + * @return ReportPart + */ + public function setPart($part) + { + $this->part = $part; + + return $this; + } +} diff --git a/src/PartKeepr/ProjectBundle/Entity/ReportProject.php b/src/PartKeepr/ProjectBundle/Entity/ReportProject.php @@ -0,0 +1,106 @@ +<?php + +namespace PartKeepr\ProjectBundle\Entity; + +use Doctrine\ORM\Mapping as ORM; +use PartKeepr\CoreBundle\Entity\BaseEntity; +use PartKeepr\DoctrineReflectionBundle\Annotation\TargetService; +use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Validator\Constraints as Assert; + +/** + * Represents one project and the quantity. + * + * @ORM\Entity + * @TargetService("/api/project_report_projects") + */ +class ReportProject extends BaseEntity +{ + /** + * @ORM\ManyToOne(targetEntity="PartKeepr\ProjectBundle\Entity\Report",inversedBy="reportProjects") + * + * @var Report + */ + private $report; + + /** + * The project the report refers to. + * + * @ORM\ManyToOne(targetEntity="PartKeepr\ProjectBundle\Entity\Project") + * @Groups({"default"}) + * @Assert\NotNull() + * + * @var Project + */ + private $project; + + /** + * Specifies the amount this project should be reported. + * + * @ORM\Column(type="integer") + * @Groups({"default"}) + * + * @var int + */ + private $quantity; + + /** + * @return mixed + */ + public function getReport() + { + return $this->report; + } + + /** + * @param mixed $report + * + * @return ReportProject + */ + public function setReport($report) + { + $this->report = $report; + + return $this; + } + + /** + * @return Project + */ + public function getProject() + { + return $this->project; + } + + /** + * @param Project $project + * + * @return ReportProject + */ + public function setProject($project) + { + $this->project = $project; + + return $this; + } + + /** + * @return int + */ + public function getQuantity() + { + return $this->quantity; + } + + /** + * @param int $quantity + * + * @return ReportProject + */ + public function setQuantity($quantity) + { + $this->quantity = $quantity; + + return $this; + } +}