From 2d583d19c26b20928f33e8cb82a06c3a2935f226 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 30 Oct 2020 10:08:06 +1100 Subject: [PATCH 1/7] Adds function to duplicate a BOM from a parent part - Improves form validation workflow - More 'djangoesque' --- InvenTree/InvenTree/views.py | 101 ++++-- InvenTree/locale/de/LC_MESSAGES/django.po | 287 ++++++++++-------- InvenTree/locale/en/LC_MESSAGES/django.po | 277 +++++++++-------- InvenTree/locale/es/LC_MESSAGES/django.po | 277 +++++++++-------- InvenTree/part/forms.py | 45 ++- InvenTree/part/models.py | 36 ++- InvenTree/part/templates/part/bom.html | 18 +- .../part/templates/part/bom_duplicate.html | 17 ++ InvenTree/part/urls.py | 1 + InvenTree/part/views.py | 54 +++- 10 files changed, 706 insertions(+), 407 deletions(-) create mode 100644 InvenTree/part/templates/part/bom_duplicate.html diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index b4bc6ebe06..0cb228836f 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -213,6 +213,39 @@ class AjaxMixin(InvenTreeRoleMixin): """ return {} + def pre_save(self, obj, form, **kwargs): + """ + Hook for doing something *before* an object is saved. + + obj: The object to be saved + form: The cleaned form + """ + + # Do nothing by default + pass + + def post_save(self, obj, form, **kwargs): + """ + Hook for doing something *after* an object is saved. + + """ + + # Do nothing by default + pass + + def validate(self, obj, form, **kwargs): + """ + Hook for performing custom form validation steps. + + If a form error is detected, add it to the form, + with 'form.add_error()' + + Ref: https://docs.djangoproject.com/en/dev/topics/forms/ + """ + + # Do nothing by default + pass + def renderJsonResponse(self, request, form=None, data={}, context=None): """ Render a JSON response based on specific class context. @@ -320,18 +353,6 @@ class AjaxCreateView(AjaxMixin, CreateView): - Handles form validation via AJAX POST requests """ - def pre_save(self, **kwargs): - """ - Hook for doing something before the form is validated - """ - pass - - def post_save(self, **kwargs): - """ - Hook for doing something with the created object after it is saved - """ - pass - def get(self, request, *args, **kwargs): """ Creates form with initial data, and renders JSON response """ @@ -351,16 +372,29 @@ class AjaxCreateView(AjaxMixin, CreateView): self.request = request self.form = self.get_form() + # Perform initial form validation + self.form.is_valid() + + # Perform custom validation (no object can be provided yet) + self.validate(None, self.form) + + valid = self.form.is_valid() + # Extra JSON data sent alongside form data = { - 'form_valid': self.form.is_valid(), + 'form_valid': valid } - if self.form.is_valid(): + if valid: - self.pre_save() + # Perform (optional) pre-save step + self.pre_save(None, self.form) + + # Save the object to the database self.object = self.form.save() - self.post_save() + + # Perform (optional) post-save step + self.post_save(self.object, self.form) # Return the PK of the newly-created object data['pk'] = self.object.pk @@ -400,22 +434,40 @@ class AjaxUpdateView(AjaxMixin, UpdateView): - Otherwise, return sucess status """ + self.request = request + # Make sure we have an object to point to self.object = self.get_object() form = self.get_form() + # Perform initial form validation + form.is_valid() + + # Perform custom validation + self.validate(self.object, form) + + valid = form.is_valid() + data = { - 'form_valid': form.is_valid() + 'form_valid': valid } - if form.is_valid(): + if valid: + + # Perform (optional) pre-save step + self.pre_save(self.object, form) + + # Save the updated objec to the database obj = form.save() - # Include context data about the updated object - data['pk'] = obj.id + # Perform (optional) post-save step + self.post_save(obj, form) - self.post_save(obj) + # Include context data about the updated object + data['pk'] = obj.pk + + self.post_save(obj, form) try: data['url'] = obj.get_absolute_url() @@ -424,13 +476,6 @@ class AjaxUpdateView(AjaxMixin, UpdateView): return self.renderJsonResponse(request, form, data) - def post_save(self, obj, *args, **kwargs): - """ - Hook called after the form data is saved. - (Optional) - """ - pass - class AjaxDeleteView(AjaxMixin, UpdateView): diff --git a/InvenTree/locale/de/LC_MESSAGES/django.po b/InvenTree/locale/de/LC_MESSAGES/django.po index 0914ad779a..e9d639e529 100644 --- a/InvenTree/locale/de/LC_MESSAGES/django.po +++ b/InvenTree/locale/de/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-10-29 02:25+0000\n" +"POT-Creation-Date: 2020-10-29 23:05+0000\n" "PO-Revision-Date: 2020-05-03 11:32+0200\n" "Last-Translator: Christian Schlüter \n" "Language-Team: C \n" @@ -214,7 +214,7 @@ msgstr "Überschuss darf 100% nicht überschreiten" msgid "Overage must be an integer value or a percentage" msgstr "Überschuss muss eine Ganzzahl oder ein Prozentwert sein" -#: InvenTree/views.py:707 +#: InvenTree/views.py:752 msgid "Database Statistics" msgstr "Datenbankstatistiken" @@ -547,7 +547,7 @@ msgstr "Zuweisung löschen" msgid "No BOM items found" msgstr "Keine BOM-Einträge gefunden" -#: build/templates/build/allocate.html:347 part/models.py:1401 +#: build/templates/build/allocate.html:347 part/models.py:1425 #: templates/js/part.js:569 templates/js/table_filters.js:167 msgid "Required" msgstr "benötigt" @@ -1279,7 +1279,7 @@ msgstr "Zuliefererbestand" #: company/templates/company/detail_stock.html:35 #: company/templates/company/supplier_part_stock.html:33 -#: part/templates/part/bom.html:62 part/templates/part/category.html:112 +#: part/templates/part/bom.html:67 part/templates/part/category.html:112 #: part/templates/part/category.html:126 part/templates/part/stock.html:51 #: templates/stock_table.html:7 msgid "Export" @@ -1398,7 +1398,7 @@ msgid "Pricing Information" msgstr "Preisinformationen ansehen" #: company/templates/company/supplier_part_pricing.html:17 company/views.py:410 -#: part/templates/part/sale_prices.html:13 part/views.py:2229 +#: part/templates/part/sale_prices.html:13 part/views.py:2278 msgid "Add Price Break" msgstr "Preisstaffel hinzufügen" @@ -1531,17 +1531,17 @@ msgstr "Neues Zuliefererteil anlegen" msgid "Delete Supplier Part" msgstr "Zuliefererteil entfernen" -#: company/views.py:416 part/views.py:2235 +#: company/views.py:416 part/views.py:2284 #, fuzzy #| msgid "Add Price Break" msgid "Added new price break" msgstr "Preisstaffel hinzufügen" -#: company/views.py:453 part/views.py:2280 +#: company/views.py:453 part/views.py:2329 msgid "Edit Price Break" msgstr "Preisstaffel bearbeiten" -#: company/views.py:469 part/views.py:2296 +#: company/views.py:469 part/views.py:2345 msgid "Delete Price Break" msgstr "Preisstaffel löschen" @@ -1646,7 +1646,7 @@ msgstr "" msgid "Date order was completed" msgstr "Bestellung als vollständig markieren" -#: order/models.py:185 order/models.py:259 part/views.py:1346 +#: order/models.py:185 order/models.py:259 part/views.py:1395 #: stock/models.py:241 stock/models.py:805 msgid "Quantity must be greater than zero" msgstr "Anzahl muss größer Null sein" @@ -1934,6 +1934,7 @@ msgstr "Kundenreferenz" #: order/templates/order/sales_order_cancel.html:8 #: order/templates/order/sales_order_ship.html:9 +#: part/templates/part/bom_duplicate.html:12 #: stock/templates/stock/stockitem_convert.html:13 msgid "Warning" msgstr "Warnung" @@ -2162,75 +2163,97 @@ msgstr "Fehler beim Lesen der Stückliste (ungültige Daten)" msgid "Error reading BOM file (incorrect row size)" msgstr "Fehler beim Lesen der Stückliste (ungültige Zeilengröße)" -#: part/forms.py:57 stock/forms.py:254 +#: part/forms.py:62 stock/forms.py:254 msgid "File Format" msgstr "Dateiformat" -#: part/forms.py:57 stock/forms.py:254 +#: part/forms.py:62 stock/forms.py:254 msgid "Select output file format" msgstr "Ausgabe-Dateiformat auswählen" -#: part/forms.py:59 +#: part/forms.py:64 msgid "Cascading" msgstr "Kaskadierend" -#: part/forms.py:59 +#: part/forms.py:64 msgid "Download cascading / multi-level BOM" msgstr "Kaskadierende Stückliste herunterladen" -#: part/forms.py:61 +#: part/forms.py:66 msgid "Levels" msgstr "" -#: part/forms.py:61 +#: part/forms.py:66 msgid "Select maximum number of BOM levels to export (0 = all levels)" msgstr "" -#: part/forms.py:63 +#: part/forms.py:68 #, fuzzy #| msgid "New Parameter" msgid "Include Parameter Data" msgstr "Neuer Parameter" -#: part/forms.py:63 +#: part/forms.py:68 msgid "Include part parameters data in exported BOM" msgstr "" -#: part/forms.py:65 +#: part/forms.py:70 #, fuzzy #| msgid "Include stock in sublocations" msgid "Include Stock Data" msgstr "Bestand in Unterlagerorten einschließen" -#: part/forms.py:65 +#: part/forms.py:70 #, fuzzy #| msgid "Include parts in subcategories" msgid "Include part stock data in exported BOM" msgstr "Teile in Unterkategorien einschließen" -#: part/forms.py:67 +#: part/forms.py:72 #, fuzzy #| msgid "New Supplier Part" msgid "Include Supplier Data" msgstr "Neues Zulieferer-Teil" -#: part/forms.py:67 +#: part/forms.py:72 msgid "Include part supplier data in exported BOM" msgstr "" -#: part/forms.py:86 +#: part/forms.py:93 part/models.py:1504 +msgid "Parent Part" +msgstr "Ausgangsteil" + +#: part/forms.py:94 part/templates/part/bom_duplicate.html:7 +#, fuzzy +#| msgid "Select parent part" +msgid "Select parent part to copy BOM from" +msgstr "Ausgangsteil auswählen" + +#: part/forms.py:100 +#, fuzzy +#| msgid "Select from existing images" +msgid "Clear existing BOM items" +msgstr "Aus vorhandenen Bildern auswählen" + +#: part/forms.py:105 +#, fuzzy +#| msgid "Confim BOM item deletion" +msgid "Confirm BOM duplication" +msgstr "Löschung von BOM-Position bestätigen" + +#: part/forms.py:123 msgid "Confirm that the BOM is correct" msgstr "Bestätigen, dass die Stückliste korrekt ist" -#: part/forms.py:98 +#: part/forms.py:135 msgid "Select BOM file to upload" msgstr "Stücklisten-Datei zum Upload auswählen" -#: part/forms.py:122 +#: part/forms.py:159 msgid "Select part category" msgstr "Teilekategorie wählen" -#: part/forms.py:136 +#: part/forms.py:173 #, fuzzy #| msgid "Perform 'deep copy' which will duplicate all BOM data for this part" msgid "Duplicate all BOM data for this part" @@ -2238,29 +2261,29 @@ msgstr "" "Tiefe Kopie ausführen. Dies wird alle Daten der Stückliste für dieses Teil " "duplizieren" -#: part/forms.py:137 +#: part/forms.py:174 msgid "Copy BOM" msgstr "" -#: part/forms.py:142 +#: part/forms.py:179 msgid "Duplicate all parameter data for this part" msgstr "" -#: part/forms.py:143 +#: part/forms.py:180 #, fuzzy #| msgid "Parameters" msgid "Copy Parameters" msgstr "Parameter" -#: part/forms.py:148 +#: part/forms.py:185 msgid "Confirm part creation" msgstr "Erstellen des Teils bestätigen" -#: part/forms.py:248 +#: part/forms.py:279 msgid "Input quantity for price calculation" msgstr "Eintragsmenge zur Preisberechnung" -#: part/forms.py:251 +#: part/forms.py:282 msgid "Select currency for price calculation" msgstr "Währung zur Preisberechnung wählen" @@ -2390,13 +2413,13 @@ msgstr "Bemerkungen - unterstüzt Markdown-Formatierung" msgid "Stored BOM checksum" msgstr "Prüfsumme der Stückliste gespeichert" -#: part/models.py:1353 +#: part/models.py:1377 #, fuzzy #| msgid "Stock item cannot be created for a template Part" msgid "Test templates can only be created for trackable parts" msgstr "Lagerobjekt kann nicht für Vorlagen-Teile angelegt werden" -#: part/models.py:1370 +#: part/models.py:1394 #, fuzzy #| msgid "" #| "A stock item with this serial number already exists for template part " @@ -2406,120 +2429,116 @@ msgstr "" "Ein Teil mit dieser Seriennummer existiert bereits für die Teilevorlage " "{part}" -#: part/models.py:1389 templates/js/part.js:560 templates/js/stock.js:92 +#: part/models.py:1413 templates/js/part.js:560 templates/js/stock.js:92 #, fuzzy #| msgid "Instance Name" msgid "Test Name" msgstr "Instanzname" -#: part/models.py:1390 +#: part/models.py:1414 #, fuzzy #| msgid "Serial number for this item" msgid "Enter a name for the test" msgstr "Seriennummer für dieses Teil" -#: part/models.py:1395 +#: part/models.py:1419 #, fuzzy #| msgid "Description" msgid "Test Description" msgstr "Beschreibung" -#: part/models.py:1396 +#: part/models.py:1420 #, fuzzy #| msgid "Brief description of the build" msgid "Enter description for this test" msgstr "Kurze Beschreibung des Baus" -#: part/models.py:1402 +#: part/models.py:1426 msgid "Is this test required to pass?" msgstr "" -#: part/models.py:1407 templates/js/part.js:577 +#: part/models.py:1431 templates/js/part.js:577 #, fuzzy #| msgid "Required Parts" msgid "Requires Value" msgstr "benötigte Teile" -#: part/models.py:1408 +#: part/models.py:1432 msgid "Does this test require a value when adding a test result?" msgstr "" -#: part/models.py:1413 templates/js/part.js:584 +#: part/models.py:1437 templates/js/part.js:584 #, fuzzy #| msgid "Delete Attachment" msgid "Requires Attachment" msgstr "Anhang löschen" -#: part/models.py:1414 +#: part/models.py:1438 msgid "Does this test require a file attachment when adding a test result?" msgstr "" -#: part/models.py:1447 +#: part/models.py:1471 msgid "Parameter template name must be unique" msgstr "Vorlagen-Name des Parameters muss eindeutig sein" -#: part/models.py:1452 +#: part/models.py:1476 msgid "Parameter Name" msgstr "Name des Parameters" -#: part/models.py:1454 +#: part/models.py:1478 msgid "Parameter Units" msgstr "Parameter Einheit" -#: part/models.py:1480 -msgid "Parent Part" -msgstr "Ausgangsteil" - -#: part/models.py:1482 +#: part/models.py:1506 msgid "Parameter Template" msgstr "Parameter Vorlage" -#: part/models.py:1484 +#: part/models.py:1508 msgid "Parameter Value" msgstr "Parameter Wert" -#: part/models.py:1521 +#: part/models.py:1545 msgid "Select parent part" msgstr "Ausgangsteil auswählen" -#: part/models.py:1529 +#: part/models.py:1553 msgid "Select part to be used in BOM" msgstr "Teil für die Nutzung in der Stückliste auswählen" -#: part/models.py:1535 +#: part/models.py:1559 msgid "BOM quantity for this BOM item" msgstr "Stücklisten-Anzahl für dieses Stücklisten-Teil" -#: part/models.py:1537 +#: part/models.py:1561 #, fuzzy #| msgid "Confim BOM item deletion" msgid "This BOM item is optional" msgstr "Löschung von BOM-Position bestätigen" -#: part/models.py:1540 +#: part/models.py:1564 msgid "Estimated build wastage quantity (absolute or percentage)" msgstr "Geschätzter Ausschuss (absolut oder prozentual)" -#: part/models.py:1543 +#: part/models.py:1567 msgid "BOM item reference" msgstr "Referenz des Objekts auf der Stückliste" -#: part/models.py:1546 +#: part/models.py:1570 msgid "BOM item notes" msgstr "Notizen zum Stücklisten-Objekt" -#: part/models.py:1548 +#: part/models.py:1572 msgid "BOM line checksum" msgstr "Prüfsumme der Stückliste" -#: part/models.py:1612 part/views.py:1352 part/views.py:1404 +#: part/models.py:1636 part/views.py:1401 part/views.py:1453 #: stock/models.py:231 #, fuzzy #| msgid "Overage must be an integer value or a percentage" msgid "Quantity must be integer value for trackable parts" msgstr "Überschuss muss eine Ganzzahl oder ein Prozentwert sein" -#: part/models.py:1621 +#: part/models.py:1645 #, fuzzy #| msgid "New BOM Item" msgid "BOM Item" @@ -2563,64 +2582,80 @@ msgid "Import BOM data" msgstr "Stückliste importieren" #: part/templates/part/bom.html:42 -msgid "Upload" +msgid "Import from File" msgstr "" -#: part/templates/part/bom.html:44 +#: part/templates/part/bom.html:45 +msgid "Copy BOM from parent part" +msgstr "" + +#: part/templates/part/bom.html:46 +#, fuzzy +#| msgid "Parameters" +msgid "Copy from Parent" +msgstr "Parameter" + +#: part/templates/part/bom.html:49 msgid "New BOM Item" msgstr "Neue Stücklistenposition" -#: part/templates/part/bom.html:45 +#: part/templates/part/bom.html:50 #, fuzzy #| msgid "Add Line Item" msgid "Add Item" msgstr "Position hinzufügen" -#: part/templates/part/bom.html:47 +#: part/templates/part/bom.html:52 msgid "Finish Editing" msgstr "Bearbeitung beenden" -#: part/templates/part/bom.html:48 +#: part/templates/part/bom.html:53 #, fuzzy #| msgid "Finish Editing" msgid "Finished" msgstr "Bearbeitung beenden" -#: part/templates/part/bom.html:52 +#: part/templates/part/bom.html:57 msgid "Edit BOM" msgstr "Stückliste bearbeiten" -#: part/templates/part/bom.html:53 part/templates/part/params.html:38 +#: part/templates/part/bom.html:58 part/templates/part/params.html:38 #: templates/InvenTree/settings/user.html:19 msgid "Edit" msgstr "Bearbeiten" -#: part/templates/part/bom.html:56 +#: part/templates/part/bom.html:61 msgid "Validate Bill of Materials" msgstr "Stückliste validieren" -#: part/templates/part/bom.html:57 +#: part/templates/part/bom.html:62 #, fuzzy #| msgid "Validate BOM" msgid "Validate" msgstr "BOM validieren" -#: part/templates/part/bom.html:61 part/views.py:1643 +#: part/templates/part/bom.html:66 part/views.py:1692 msgid "Export Bill of Materials" msgstr "Stückliste exportieren" -#: part/templates/part/bom.html:122 +#: part/templates/part/bom.html:127 #, fuzzy #| msgid "Remove selected BOM items" msgid "Delete selected BOM items?" msgstr "Ausgewählte Stücklistenpositionen entfernen" -#: part/templates/part/bom.html:123 +#: part/templates/part/bom.html:128 #, fuzzy #| msgid "Remove selected BOM items" msgid "All selected BOM items will be deleted" msgstr "Ausgewählte Stücklistenpositionen entfernen" +#: part/templates/part/bom_duplicate.html:13 +#, fuzzy +#| msgid "Export Bill of Materials" +msgid "This part already has a Bill of Materials" +msgstr "Stückliste exportieren" + #: part/templates/part/bom_upload/select_fields.html:8 #: part/templates/part/bom_upload/select_parts.html:8 #: part/templates/part/bom_upload/upload_file.html:10 @@ -2721,7 +2756,7 @@ msgstr "Neuen Bau beginnen" msgid "All parts" msgstr "Alle Teile" -#: part/templates/part/category.html:24 part/views.py:2046 +#: part/templates/part/category.html:24 part/views.py:2095 msgid "Create new part category" msgstr "Teilkategorie anlegen" @@ -2895,104 +2930,104 @@ msgstr "Erstellt von" msgid "Responsible User" msgstr "Verantwortlicher Benutzer" -#: part/templates/part/detail.html:136 templates/js/table_filters.js:27 +#: part/templates/part/detail.html:138 templates/js/table_filters.js:27 msgid "Virtual" msgstr "Virtuell" -#: part/templates/part/detail.html:139 +#: part/templates/part/detail.html:141 msgid "Part is virtual (not a physical part)" msgstr "Teil ist virtuell (kein physisches Teil)" -#: part/templates/part/detail.html:141 +#: part/templates/part/detail.html:143 msgid "Part is not a virtual part" msgstr "Teil ist nicht virtuell" -#: part/templates/part/detail.html:145 stock/forms.py:248 +#: part/templates/part/detail.html:148 stock/forms.py:248 #: templates/js/table_filters.js:23 templates/js/table_filters.js:243 msgid "Template" msgstr "Vorlage" -#: part/templates/part/detail.html:148 +#: part/templates/part/detail.html:151 #, fuzzy #| msgid "Part cannot be a template part if it is a variant of another part" msgid "Part is a template part (variants can be made from this part)" msgstr "Teil kann keine Vorlage sein wenn es Variante eines anderen Teils ist" -#: part/templates/part/detail.html:150 +#: part/templates/part/detail.html:153 #, fuzzy #| msgid "Part is not a virtual part" msgid "Part is not a template part" msgstr "Teil ist nicht virtuell" -#: part/templates/part/detail.html:154 templates/js/table_filters.js:255 +#: part/templates/part/detail.html:158 templates/js/table_filters.js:255 msgid "Assembly" msgstr "Baugruppe" -#: part/templates/part/detail.html:157 +#: part/templates/part/detail.html:161 msgid "Part can be assembled from other parts" msgstr "Teil kann aus anderen Teilen angefertigt werden" -#: part/templates/part/detail.html:159 +#: part/templates/part/detail.html:163 msgid "Part cannot be assembled from other parts" msgstr "Teil kann nicht aus anderen Teilen angefertigt werden" -#: part/templates/part/detail.html:163 templates/js/table_filters.js:259 +#: part/templates/part/detail.html:168 templates/js/table_filters.js:259 msgid "Component" msgstr "Komponente" -#: part/templates/part/detail.html:166 +#: part/templates/part/detail.html:171 msgid "Part can be used in assemblies" msgstr "Teil kann in Baugruppen benutzt werden" -#: part/templates/part/detail.html:168 +#: part/templates/part/detail.html:173 msgid "Part cannot be used in assemblies" msgstr "Teil kann nicht in Baugruppen benutzt werden" -#: part/templates/part/detail.html:172 templates/js/table_filters.js:31 +#: part/templates/part/detail.html:178 templates/js/table_filters.js:31 #: templates/js/table_filters.js:271 msgid "Trackable" msgstr "nachverfolgbar" -#: part/templates/part/detail.html:175 +#: part/templates/part/detail.html:181 msgid "Part stock is tracked by serial number" msgstr "Teilebestand in der Seriennummer hinterlegt" -#: part/templates/part/detail.html:177 +#: part/templates/part/detail.html:183 msgid "Part stock is not tracked by serial number" msgstr "Teilebestand ist nicht in der Seriennummer hinterlegt" -#: part/templates/part/detail.html:181 +#: part/templates/part/detail.html:188 msgid "Purchaseable" msgstr "Kaufbar" -#: part/templates/part/detail.html:184 part/templates/part/detail.html:186 +#: part/templates/part/detail.html:191 part/templates/part/detail.html:193 msgid "Part can be purchased from external suppliers" msgstr "Teil kann von externen Zulieferern gekauft werden" -#: part/templates/part/detail.html:190 templates/js/table_filters.js:267 +#: part/templates/part/detail.html:198 templates/js/table_filters.js:267 msgid "Salable" msgstr "Verkäuflich" -#: part/templates/part/detail.html:193 +#: part/templates/part/detail.html:201 msgid "Part can be sold to customers" msgstr "Teil kann an Kunden verkauft werden" -#: part/templates/part/detail.html:195 +#: part/templates/part/detail.html:203 msgid "Part cannot be sold to customers" msgstr "Teil kann nicht an Kunden verkauft werden" -#: part/templates/part/detail.html:199 templates/js/table_filters.js:19 +#: part/templates/part/detail.html:214 templates/js/table_filters.js:19 #: templates/js/table_filters.js:55 templates/js/table_filters.js:238 msgid "Active" msgstr "Aktiv" -#: part/templates/part/detail.html:202 +#: part/templates/part/detail.html:217 #, fuzzy #| msgid "This part is not active" msgid "Part is active" msgstr "Dieses Teil ist nicht aktiv" -#: part/templates/part/detail.html:204 +#: part/templates/part/detail.html:219 #, fuzzy #| msgid "This part is not active" msgid "Part is not active" @@ -3344,99 +3379,111 @@ msgstr "Teilbild nicht gefunden" msgid "Edit Part Properties" msgstr "Teileigenschaften bearbeiten" -#: part/views.py:838 +#: part/views.py:841 +#, fuzzy +#| msgid "Duplicate Part" +msgid "Duplicate BOM" +msgstr "Teil duplizieren" + +#: part/views.py:871 +#, fuzzy +#| msgid "Confirm unallocation of build stock" +msgid "Confirm duplication of BOM from parent" +msgstr "Zuweisungsaufhebung bestätigen" + +#: part/views.py:887 msgid "Validate BOM" msgstr "BOM validieren" -#: part/views.py:1005 +#: part/views.py:1054 msgid "No BOM file provided" msgstr "Keine Stückliste angegeben" -#: part/views.py:1355 +#: part/views.py:1404 msgid "Enter a valid quantity" msgstr "Bitte eine gültige Anzahl eingeben" -#: part/views.py:1380 part/views.py:1383 +#: part/views.py:1429 part/views.py:1432 msgid "Select valid part" msgstr "Bitte ein gültiges Teil auswählen" -#: part/views.py:1389 +#: part/views.py:1438 msgid "Duplicate part selected" msgstr "Teil doppelt ausgewählt" -#: part/views.py:1427 +#: part/views.py:1476 msgid "Select a part" msgstr "Teil auswählen" -#: part/views.py:1433 +#: part/views.py:1482 #, fuzzy #| msgid "Select part to be used in BOM" msgid "Selected part creates a circular BOM" msgstr "Teil für die Nutzung in der Stückliste auswählen" -#: part/views.py:1437 +#: part/views.py:1486 msgid "Specify quantity" msgstr "Anzahl angeben" -#: part/views.py:1693 +#: part/views.py:1742 msgid "Confirm Part Deletion" msgstr "Löschen des Teils bestätigen" -#: part/views.py:1702 +#: part/views.py:1751 msgid "Part was deleted" msgstr "Teil wurde gelöscht" -#: part/views.py:1711 +#: part/views.py:1760 msgid "Part Pricing" msgstr "Teilbepreisung" -#: part/views.py:1837 +#: part/views.py:1886 msgid "Create Part Parameter Template" msgstr "Teilparametervorlage anlegen" -#: part/views.py:1847 +#: part/views.py:1896 msgid "Edit Part Parameter Template" msgstr "Teilparametervorlage bearbeiten" -#: part/views.py:1856 +#: part/views.py:1905 msgid "Delete Part Parameter Template" msgstr "Teilparametervorlage löschen" -#: part/views.py:1866 +#: part/views.py:1915 msgid "Create Part Parameter" msgstr "Teilparameter anlegen" -#: part/views.py:1918 +#: part/views.py:1967 msgid "Edit Part Parameter" msgstr "Teilparameter bearbeiten" -#: part/views.py:1934 +#: part/views.py:1983 msgid "Delete Part Parameter" msgstr "Teilparameter löschen" -#: part/views.py:1993 +#: part/views.py:2042 msgid "Edit Part Category" msgstr "Teilkategorie bearbeiten" -#: part/views.py:2030 +#: part/views.py:2079 msgid "Delete Part Category" msgstr "Teilkategorie löschen" -#: part/views.py:2038 +#: part/views.py:2087 msgid "Part category was deleted" msgstr "Teilekategorie wurde gelöscht" -#: part/views.py:2101 +#: part/views.py:2150 #, fuzzy #| msgid "Create BOM item" msgid "Create BOM Item" msgstr "BOM-Position anlegen" -#: part/views.py:2169 +#: part/views.py:2218 msgid "Edit BOM item" msgstr "BOM-Position beaarbeiten" -#: part/views.py:2219 +#: part/views.py:2268 msgid "Confim BOM item deletion" msgstr "Löschung von BOM-Position bestätigen" diff --git a/InvenTree/locale/en/LC_MESSAGES/django.po b/InvenTree/locale/en/LC_MESSAGES/django.po index c1a4837c86..f9731db17b 100644 --- a/InvenTree/locale/en/LC_MESSAGES/django.po +++ b/InvenTree/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-10-29 02:25+0000\n" +"POT-Creation-Date: 2020-10-29 23:05+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -204,7 +204,7 @@ msgstr "" msgid "Overage must be an integer value or a percentage" msgstr "" -#: InvenTree/views.py:707 +#: InvenTree/views.py:752 msgid "Database Statistics" msgstr "" @@ -520,7 +520,7 @@ msgstr "" msgid "No BOM items found" msgstr "" -#: build/templates/build/allocate.html:347 part/models.py:1401 +#: build/templates/build/allocate.html:347 part/models.py:1425 #: templates/js/part.js:569 templates/js/table_filters.js:167 msgid "Required" msgstr "" @@ -1192,7 +1192,7 @@ msgstr "" #: company/templates/company/detail_stock.html:35 #: company/templates/company/supplier_part_stock.html:33 -#: part/templates/part/bom.html:62 part/templates/part/category.html:112 +#: part/templates/part/bom.html:67 part/templates/part/category.html:112 #: part/templates/part/category.html:126 part/templates/part/stock.html:51 #: templates/stock_table.html:7 msgid "Export" @@ -1310,7 +1310,7 @@ msgid "Pricing Information" msgstr "" #: company/templates/company/supplier_part_pricing.html:17 company/views.py:410 -#: part/templates/part/sale_prices.html:13 part/views.py:2229 +#: part/templates/part/sale_prices.html:13 part/views.py:2278 msgid "Add Price Break" msgstr "" @@ -1437,15 +1437,15 @@ msgstr "" msgid "Delete Supplier Part" msgstr "" -#: company/views.py:416 part/views.py:2235 +#: company/views.py:416 part/views.py:2284 msgid "Added new price break" msgstr "" -#: company/views.py:453 part/views.py:2280 +#: company/views.py:453 part/views.py:2329 msgid "Edit Price Break" msgstr "" -#: company/views.py:469 part/views.py:2296 +#: company/views.py:469 part/views.py:2345 msgid "Delete Price Break" msgstr "" @@ -1538,7 +1538,7 @@ msgstr "" msgid "Date order was completed" msgstr "" -#: order/models.py:185 order/models.py:259 part/views.py:1346 +#: order/models.py:185 order/models.py:259 part/views.py:1395 #: stock/models.py:241 stock/models.py:805 msgid "Quantity must be greater than zero" msgstr "" @@ -1815,6 +1815,7 @@ msgstr "" #: order/templates/order/sales_order_cancel.html:8 #: order/templates/order/sales_order_ship.html:9 +#: part/templates/part/bom_duplicate.html:12 #: stock/templates/stock/stockitem_convert.html:13 msgid "Warning" msgstr "" @@ -2039,91 +2040,107 @@ msgstr "" msgid "Error reading BOM file (incorrect row size)" msgstr "" -#: part/forms.py:57 stock/forms.py:254 +#: part/forms.py:62 stock/forms.py:254 msgid "File Format" msgstr "" -#: part/forms.py:57 stock/forms.py:254 +#: part/forms.py:62 stock/forms.py:254 msgid "Select output file format" msgstr "" -#: part/forms.py:59 +#: part/forms.py:64 msgid "Cascading" msgstr "" -#: part/forms.py:59 +#: part/forms.py:64 msgid "Download cascading / multi-level BOM" msgstr "" -#: part/forms.py:61 +#: part/forms.py:66 msgid "Levels" msgstr "" -#: part/forms.py:61 +#: part/forms.py:66 msgid "Select maximum number of BOM levels to export (0 = all levels)" msgstr "" -#: part/forms.py:63 +#: part/forms.py:68 msgid "Include Parameter Data" msgstr "" -#: part/forms.py:63 +#: part/forms.py:68 msgid "Include part parameters data in exported BOM" msgstr "" -#: part/forms.py:65 +#: part/forms.py:70 msgid "Include Stock Data" msgstr "" -#: part/forms.py:65 +#: part/forms.py:70 msgid "Include part stock data in exported BOM" msgstr "" -#: part/forms.py:67 +#: part/forms.py:72 msgid "Include Supplier Data" msgstr "" -#: part/forms.py:67 +#: part/forms.py:72 msgid "Include part supplier data in exported BOM" msgstr "" -#: part/forms.py:86 +#: part/forms.py:93 part/models.py:1504 +msgid "Parent Part" +msgstr "" + +#: part/forms.py:94 part/templates/part/bom_duplicate.html:7 +msgid "Select parent part to copy BOM from" +msgstr "" + +#: part/forms.py:100 +msgid "Clear existing BOM items" +msgstr "" + +#: part/forms.py:105 +msgid "Confirm BOM duplication" +msgstr "" + +#: part/forms.py:123 msgid "Confirm that the BOM is correct" msgstr "" -#: part/forms.py:98 +#: part/forms.py:135 msgid "Select BOM file to upload" msgstr "" -#: part/forms.py:122 +#: part/forms.py:159 msgid "Select part category" msgstr "" -#: part/forms.py:136 +#: part/forms.py:173 msgid "Duplicate all BOM data for this part" msgstr "" -#: part/forms.py:137 +#: part/forms.py:174 msgid "Copy BOM" msgstr "" -#: part/forms.py:142 +#: part/forms.py:179 msgid "Duplicate all parameter data for this part" msgstr "" -#: part/forms.py:143 +#: part/forms.py:180 msgid "Copy Parameters" msgstr "" -#: part/forms.py:148 +#: part/forms.py:185 msgid "Confirm part creation" msgstr "" -#: part/forms.py:248 +#: part/forms.py:279 msgid "Input quantity for price calculation" msgstr "" -#: part/forms.py:251 +#: part/forms.py:282 msgid "Select currency for price calculation" msgstr "" @@ -2249,112 +2266,108 @@ msgstr "" msgid "Stored BOM checksum" msgstr "" -#: part/models.py:1353 +#: part/models.py:1377 msgid "Test templates can only be created for trackable parts" msgstr "" -#: part/models.py:1370 +#: part/models.py:1394 msgid "Test with this name already exists for this part" msgstr "" -#: part/models.py:1389 templates/js/part.js:560 templates/js/stock.js:92 +#: part/models.py:1413 templates/js/part.js:560 templates/js/stock.js:92 msgid "Test Name" msgstr "" -#: part/models.py:1390 +#: part/models.py:1414 msgid "Enter a name for the test" msgstr "" -#: part/models.py:1395 +#: part/models.py:1419 msgid "Test Description" msgstr "" -#: part/models.py:1396 +#: part/models.py:1420 msgid "Enter description for this test" msgstr "" -#: part/models.py:1402 +#: part/models.py:1426 msgid "Is this test required to pass?" msgstr "" -#: part/models.py:1407 templates/js/part.js:577 +#: part/models.py:1431 templates/js/part.js:577 msgid "Requires Value" msgstr "" -#: part/models.py:1408 +#: part/models.py:1432 msgid "Does this test require a value when adding a test result?" msgstr "" -#: part/models.py:1413 templates/js/part.js:584 +#: part/models.py:1437 templates/js/part.js:584 msgid "Requires Attachment" msgstr "" -#: part/models.py:1414 +#: part/models.py:1438 msgid "Does this test require a file attachment when adding a test result?" msgstr "" -#: part/models.py:1447 +#: part/models.py:1471 msgid "Parameter template name must be unique" msgstr "" -#: part/models.py:1452 +#: part/models.py:1476 msgid "Parameter Name" msgstr "" -#: part/models.py:1454 +#: part/models.py:1478 msgid "Parameter Units" msgstr "" -#: part/models.py:1480 -msgid "Parent Part" -msgstr "" - -#: part/models.py:1482 +#: part/models.py:1506 msgid "Parameter Template" msgstr "" -#: part/models.py:1484 +#: part/models.py:1508 msgid "Parameter Value" msgstr "" -#: part/models.py:1521 +#: part/models.py:1545 msgid "Select parent part" msgstr "" -#: part/models.py:1529 +#: part/models.py:1553 msgid "Select part to be used in BOM" msgstr "" -#: part/models.py:1535 +#: part/models.py:1559 msgid "BOM quantity for this BOM item" msgstr "" -#: part/models.py:1537 +#: part/models.py:1561 msgid "This BOM item is optional" msgstr "" -#: part/models.py:1540 +#: part/models.py:1564 msgid "Estimated build wastage quantity (absolute or percentage)" msgstr "" -#: part/models.py:1543 +#: part/models.py:1567 msgid "BOM item reference" msgstr "" -#: part/models.py:1546 +#: part/models.py:1570 msgid "BOM item notes" msgstr "" -#: part/models.py:1548 +#: part/models.py:1572 msgid "BOM line checksum" msgstr "" -#: part/models.py:1612 part/views.py:1352 part/views.py:1404 +#: part/models.py:1636 part/views.py:1401 part/views.py:1453 #: stock/models.py:231 msgid "Quantity must be integer value for trackable parts" msgstr "" -#: part/models.py:1621 +#: part/models.py:1645 msgid "BOM Item" msgstr "" @@ -2396,54 +2409,66 @@ msgid "Import BOM data" msgstr "" #: part/templates/part/bom.html:42 -msgid "Upload" -msgstr "" - -#: part/templates/part/bom.html:44 -msgid "New BOM Item" +msgid "Import from File" msgstr "" #: part/templates/part/bom.html:45 +msgid "Copy BOM from parent part" +msgstr "" + +#: part/templates/part/bom.html:46 +msgid "Copy from Parent" +msgstr "" + +#: part/templates/part/bom.html:49 +msgid "New BOM Item" +msgstr "" + +#: part/templates/part/bom.html:50 msgid "Add Item" msgstr "" -#: part/templates/part/bom.html:47 +#: part/templates/part/bom.html:52 msgid "Finish Editing" msgstr "" -#: part/templates/part/bom.html:48 +#: part/templates/part/bom.html:53 msgid "Finished" msgstr "" -#: part/templates/part/bom.html:52 +#: part/templates/part/bom.html:57 msgid "Edit BOM" msgstr "" -#: part/templates/part/bom.html:53 part/templates/part/params.html:38 +#: part/templates/part/bom.html:58 part/templates/part/params.html:38 #: templates/InvenTree/settings/user.html:19 msgid "Edit" msgstr "" -#: part/templates/part/bom.html:56 +#: part/templates/part/bom.html:61 msgid "Validate Bill of Materials" msgstr "" -#: part/templates/part/bom.html:57 +#: part/templates/part/bom.html:62 msgid "Validate" msgstr "" -#: part/templates/part/bom.html:61 part/views.py:1643 +#: part/templates/part/bom.html:66 part/views.py:1692 msgid "Export Bill of Materials" msgstr "" -#: part/templates/part/bom.html:122 +#: part/templates/part/bom.html:127 msgid "Delete selected BOM items?" msgstr "" -#: part/templates/part/bom.html:123 +#: part/templates/part/bom.html:128 msgid "All selected BOM items will be deleted" msgstr "" +#: part/templates/part/bom_duplicate.html:13 +msgid "This part already has a Bill of Materials" +msgstr "" + #: part/templates/part/bom_upload/select_fields.html:8 #: part/templates/part/bom_upload/select_parts.html:8 #: part/templates/part/bom_upload/upload_file.html:10 @@ -2524,7 +2549,7 @@ msgstr "" msgid "All parts" msgstr "" -#: part/templates/part/category.html:24 part/views.py:2046 +#: part/templates/part/category.html:24 part/views.py:2095 msgid "Create new part category" msgstr "" @@ -2670,98 +2695,98 @@ msgstr "" msgid "Responsible User" msgstr "" -#: part/templates/part/detail.html:136 templates/js/table_filters.js:27 +#: part/templates/part/detail.html:138 templates/js/table_filters.js:27 msgid "Virtual" msgstr "" -#: part/templates/part/detail.html:139 +#: part/templates/part/detail.html:141 msgid "Part is virtual (not a physical part)" msgstr "" -#: part/templates/part/detail.html:141 +#: part/templates/part/detail.html:143 msgid "Part is not a virtual part" msgstr "" -#: part/templates/part/detail.html:145 stock/forms.py:248 +#: part/templates/part/detail.html:148 stock/forms.py:248 #: templates/js/table_filters.js:23 templates/js/table_filters.js:243 msgid "Template" msgstr "" -#: part/templates/part/detail.html:148 +#: part/templates/part/detail.html:151 msgid "Part is a template part (variants can be made from this part)" msgstr "" -#: part/templates/part/detail.html:150 +#: part/templates/part/detail.html:153 msgid "Part is not a template part" msgstr "" -#: part/templates/part/detail.html:154 templates/js/table_filters.js:255 +#: part/templates/part/detail.html:158 templates/js/table_filters.js:255 msgid "Assembly" msgstr "" -#: part/templates/part/detail.html:157 +#: part/templates/part/detail.html:161 msgid "Part can be assembled from other parts" msgstr "" -#: part/templates/part/detail.html:159 +#: part/templates/part/detail.html:163 msgid "Part cannot be assembled from other parts" msgstr "" -#: part/templates/part/detail.html:163 templates/js/table_filters.js:259 +#: part/templates/part/detail.html:168 templates/js/table_filters.js:259 msgid "Component" msgstr "" -#: part/templates/part/detail.html:166 +#: part/templates/part/detail.html:171 msgid "Part can be used in assemblies" msgstr "" -#: part/templates/part/detail.html:168 +#: part/templates/part/detail.html:173 msgid "Part cannot be used in assemblies" msgstr "" -#: part/templates/part/detail.html:172 templates/js/table_filters.js:31 +#: part/templates/part/detail.html:178 templates/js/table_filters.js:31 #: templates/js/table_filters.js:271 msgid "Trackable" msgstr "" -#: part/templates/part/detail.html:175 +#: part/templates/part/detail.html:181 msgid "Part stock is tracked by serial number" msgstr "" -#: part/templates/part/detail.html:177 +#: part/templates/part/detail.html:183 msgid "Part stock is not tracked by serial number" msgstr "" -#: part/templates/part/detail.html:181 +#: part/templates/part/detail.html:188 msgid "Purchaseable" msgstr "" -#: part/templates/part/detail.html:184 part/templates/part/detail.html:186 +#: part/templates/part/detail.html:191 part/templates/part/detail.html:193 msgid "Part can be purchased from external suppliers" msgstr "" -#: part/templates/part/detail.html:190 templates/js/table_filters.js:267 +#: part/templates/part/detail.html:198 templates/js/table_filters.js:267 msgid "Salable" msgstr "" -#: part/templates/part/detail.html:193 +#: part/templates/part/detail.html:201 msgid "Part can be sold to customers" msgstr "" -#: part/templates/part/detail.html:195 +#: part/templates/part/detail.html:203 msgid "Part cannot be sold to customers" msgstr "" -#: part/templates/part/detail.html:199 templates/js/table_filters.js:19 +#: part/templates/part/detail.html:214 templates/js/table_filters.js:19 #: templates/js/table_filters.js:55 templates/js/table_filters.js:238 msgid "Active" msgstr "" -#: part/templates/part/detail.html:202 +#: part/templates/part/detail.html:217 msgid "Part is active" msgstr "" -#: part/templates/part/detail.html:204 +#: part/templates/part/detail.html:219 msgid "Part is not active" msgstr "" @@ -3069,95 +3094,103 @@ msgstr "" msgid "Edit Part Properties" msgstr "" -#: part/views.py:838 +#: part/views.py:841 +msgid "Duplicate BOM" +msgstr "" + +#: part/views.py:871 +msgid "Confirm duplication of BOM from parent" +msgstr "" + +#: part/views.py:887 msgid "Validate BOM" msgstr "" -#: part/views.py:1005 +#: part/views.py:1054 msgid "No BOM file provided" msgstr "" -#: part/views.py:1355 +#: part/views.py:1404 msgid "Enter a valid quantity" msgstr "" -#: part/views.py:1380 part/views.py:1383 +#: part/views.py:1429 part/views.py:1432 msgid "Select valid part" msgstr "" -#: part/views.py:1389 +#: part/views.py:1438 msgid "Duplicate part selected" msgstr "" -#: part/views.py:1427 +#: part/views.py:1476 msgid "Select a part" msgstr "" -#: part/views.py:1433 +#: part/views.py:1482 msgid "Selected part creates a circular BOM" msgstr "" -#: part/views.py:1437 +#: part/views.py:1486 msgid "Specify quantity" msgstr "" -#: part/views.py:1693 +#: part/views.py:1742 msgid "Confirm Part Deletion" msgstr "" -#: part/views.py:1702 +#: part/views.py:1751 msgid "Part was deleted" msgstr "" -#: part/views.py:1711 +#: part/views.py:1760 msgid "Part Pricing" msgstr "" -#: part/views.py:1837 +#: part/views.py:1886 msgid "Create Part Parameter Template" msgstr "" -#: part/views.py:1847 +#: part/views.py:1896 msgid "Edit Part Parameter Template" msgstr "" -#: part/views.py:1856 +#: part/views.py:1905 msgid "Delete Part Parameter Template" msgstr "" -#: part/views.py:1866 +#: part/views.py:1915 msgid "Create Part Parameter" msgstr "" -#: part/views.py:1918 +#: part/views.py:1967 msgid "Edit Part Parameter" msgstr "" -#: part/views.py:1934 +#: part/views.py:1983 msgid "Delete Part Parameter" msgstr "" -#: part/views.py:1993 +#: part/views.py:2042 msgid "Edit Part Category" msgstr "" -#: part/views.py:2030 +#: part/views.py:2079 msgid "Delete Part Category" msgstr "" -#: part/views.py:2038 +#: part/views.py:2087 msgid "Part category was deleted" msgstr "" -#: part/views.py:2101 +#: part/views.py:2150 msgid "Create BOM Item" msgstr "" -#: part/views.py:2169 +#: part/views.py:2218 msgid "Edit BOM item" msgstr "" -#: part/views.py:2219 +#: part/views.py:2268 msgid "Confim BOM item deletion" msgstr "" diff --git a/InvenTree/locale/es/LC_MESSAGES/django.po b/InvenTree/locale/es/LC_MESSAGES/django.po index c1a4837c86..f9731db17b 100644 --- a/InvenTree/locale/es/LC_MESSAGES/django.po +++ b/InvenTree/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-10-29 02:25+0000\n" +"POT-Creation-Date: 2020-10-29 23:05+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -204,7 +204,7 @@ msgstr "" msgid "Overage must be an integer value or a percentage" msgstr "" -#: InvenTree/views.py:707 +#: InvenTree/views.py:752 msgid "Database Statistics" msgstr "" @@ -520,7 +520,7 @@ msgstr "" msgid "No BOM items found" msgstr "" -#: build/templates/build/allocate.html:347 part/models.py:1401 +#: build/templates/build/allocate.html:347 part/models.py:1425 #: templates/js/part.js:569 templates/js/table_filters.js:167 msgid "Required" msgstr "" @@ -1192,7 +1192,7 @@ msgstr "" #: company/templates/company/detail_stock.html:35 #: company/templates/company/supplier_part_stock.html:33 -#: part/templates/part/bom.html:62 part/templates/part/category.html:112 +#: part/templates/part/bom.html:67 part/templates/part/category.html:112 #: part/templates/part/category.html:126 part/templates/part/stock.html:51 #: templates/stock_table.html:7 msgid "Export" @@ -1310,7 +1310,7 @@ msgid "Pricing Information" msgstr "" #: company/templates/company/supplier_part_pricing.html:17 company/views.py:410 -#: part/templates/part/sale_prices.html:13 part/views.py:2229 +#: part/templates/part/sale_prices.html:13 part/views.py:2278 msgid "Add Price Break" msgstr "" @@ -1437,15 +1437,15 @@ msgstr "" msgid "Delete Supplier Part" msgstr "" -#: company/views.py:416 part/views.py:2235 +#: company/views.py:416 part/views.py:2284 msgid "Added new price break" msgstr "" -#: company/views.py:453 part/views.py:2280 +#: company/views.py:453 part/views.py:2329 msgid "Edit Price Break" msgstr "" -#: company/views.py:469 part/views.py:2296 +#: company/views.py:469 part/views.py:2345 msgid "Delete Price Break" msgstr "" @@ -1538,7 +1538,7 @@ msgstr "" msgid "Date order was completed" msgstr "" -#: order/models.py:185 order/models.py:259 part/views.py:1346 +#: order/models.py:185 order/models.py:259 part/views.py:1395 #: stock/models.py:241 stock/models.py:805 msgid "Quantity must be greater than zero" msgstr "" @@ -1815,6 +1815,7 @@ msgstr "" #: order/templates/order/sales_order_cancel.html:8 #: order/templates/order/sales_order_ship.html:9 +#: part/templates/part/bom_duplicate.html:12 #: stock/templates/stock/stockitem_convert.html:13 msgid "Warning" msgstr "" @@ -2039,91 +2040,107 @@ msgstr "" msgid "Error reading BOM file (incorrect row size)" msgstr "" -#: part/forms.py:57 stock/forms.py:254 +#: part/forms.py:62 stock/forms.py:254 msgid "File Format" msgstr "" -#: part/forms.py:57 stock/forms.py:254 +#: part/forms.py:62 stock/forms.py:254 msgid "Select output file format" msgstr "" -#: part/forms.py:59 +#: part/forms.py:64 msgid "Cascading" msgstr "" -#: part/forms.py:59 +#: part/forms.py:64 msgid "Download cascading / multi-level BOM" msgstr "" -#: part/forms.py:61 +#: part/forms.py:66 msgid "Levels" msgstr "" -#: part/forms.py:61 +#: part/forms.py:66 msgid "Select maximum number of BOM levels to export (0 = all levels)" msgstr "" -#: part/forms.py:63 +#: part/forms.py:68 msgid "Include Parameter Data" msgstr "" -#: part/forms.py:63 +#: part/forms.py:68 msgid "Include part parameters data in exported BOM" msgstr "" -#: part/forms.py:65 +#: part/forms.py:70 msgid "Include Stock Data" msgstr "" -#: part/forms.py:65 +#: part/forms.py:70 msgid "Include part stock data in exported BOM" msgstr "" -#: part/forms.py:67 +#: part/forms.py:72 msgid "Include Supplier Data" msgstr "" -#: part/forms.py:67 +#: part/forms.py:72 msgid "Include part supplier data in exported BOM" msgstr "" -#: part/forms.py:86 +#: part/forms.py:93 part/models.py:1504 +msgid "Parent Part" +msgstr "" + +#: part/forms.py:94 part/templates/part/bom_duplicate.html:7 +msgid "Select parent part to copy BOM from" +msgstr "" + +#: part/forms.py:100 +msgid "Clear existing BOM items" +msgstr "" + +#: part/forms.py:105 +msgid "Confirm BOM duplication" +msgstr "" + +#: part/forms.py:123 msgid "Confirm that the BOM is correct" msgstr "" -#: part/forms.py:98 +#: part/forms.py:135 msgid "Select BOM file to upload" msgstr "" -#: part/forms.py:122 +#: part/forms.py:159 msgid "Select part category" msgstr "" -#: part/forms.py:136 +#: part/forms.py:173 msgid "Duplicate all BOM data for this part" msgstr "" -#: part/forms.py:137 +#: part/forms.py:174 msgid "Copy BOM" msgstr "" -#: part/forms.py:142 +#: part/forms.py:179 msgid "Duplicate all parameter data for this part" msgstr "" -#: part/forms.py:143 +#: part/forms.py:180 msgid "Copy Parameters" msgstr "" -#: part/forms.py:148 +#: part/forms.py:185 msgid "Confirm part creation" msgstr "" -#: part/forms.py:248 +#: part/forms.py:279 msgid "Input quantity for price calculation" msgstr "" -#: part/forms.py:251 +#: part/forms.py:282 msgid "Select currency for price calculation" msgstr "" @@ -2249,112 +2266,108 @@ msgstr "" msgid "Stored BOM checksum" msgstr "" -#: part/models.py:1353 +#: part/models.py:1377 msgid "Test templates can only be created for trackable parts" msgstr "" -#: part/models.py:1370 +#: part/models.py:1394 msgid "Test with this name already exists for this part" msgstr "" -#: part/models.py:1389 templates/js/part.js:560 templates/js/stock.js:92 +#: part/models.py:1413 templates/js/part.js:560 templates/js/stock.js:92 msgid "Test Name" msgstr "" -#: part/models.py:1390 +#: part/models.py:1414 msgid "Enter a name for the test" msgstr "" -#: part/models.py:1395 +#: part/models.py:1419 msgid "Test Description" msgstr "" -#: part/models.py:1396 +#: part/models.py:1420 msgid "Enter description for this test" msgstr "" -#: part/models.py:1402 +#: part/models.py:1426 msgid "Is this test required to pass?" msgstr "" -#: part/models.py:1407 templates/js/part.js:577 +#: part/models.py:1431 templates/js/part.js:577 msgid "Requires Value" msgstr "" -#: part/models.py:1408 +#: part/models.py:1432 msgid "Does this test require a value when adding a test result?" msgstr "" -#: part/models.py:1413 templates/js/part.js:584 +#: part/models.py:1437 templates/js/part.js:584 msgid "Requires Attachment" msgstr "" -#: part/models.py:1414 +#: part/models.py:1438 msgid "Does this test require a file attachment when adding a test result?" msgstr "" -#: part/models.py:1447 +#: part/models.py:1471 msgid "Parameter template name must be unique" msgstr "" -#: part/models.py:1452 +#: part/models.py:1476 msgid "Parameter Name" msgstr "" -#: part/models.py:1454 +#: part/models.py:1478 msgid "Parameter Units" msgstr "" -#: part/models.py:1480 -msgid "Parent Part" -msgstr "" - -#: part/models.py:1482 +#: part/models.py:1506 msgid "Parameter Template" msgstr "" -#: part/models.py:1484 +#: part/models.py:1508 msgid "Parameter Value" msgstr "" -#: part/models.py:1521 +#: part/models.py:1545 msgid "Select parent part" msgstr "" -#: part/models.py:1529 +#: part/models.py:1553 msgid "Select part to be used in BOM" msgstr "" -#: part/models.py:1535 +#: part/models.py:1559 msgid "BOM quantity for this BOM item" msgstr "" -#: part/models.py:1537 +#: part/models.py:1561 msgid "This BOM item is optional" msgstr "" -#: part/models.py:1540 +#: part/models.py:1564 msgid "Estimated build wastage quantity (absolute or percentage)" msgstr "" -#: part/models.py:1543 +#: part/models.py:1567 msgid "BOM item reference" msgstr "" -#: part/models.py:1546 +#: part/models.py:1570 msgid "BOM item notes" msgstr "" -#: part/models.py:1548 +#: part/models.py:1572 msgid "BOM line checksum" msgstr "" -#: part/models.py:1612 part/views.py:1352 part/views.py:1404 +#: part/models.py:1636 part/views.py:1401 part/views.py:1453 #: stock/models.py:231 msgid "Quantity must be integer value for trackable parts" msgstr "" -#: part/models.py:1621 +#: part/models.py:1645 msgid "BOM Item" msgstr "" @@ -2396,54 +2409,66 @@ msgid "Import BOM data" msgstr "" #: part/templates/part/bom.html:42 -msgid "Upload" -msgstr "" - -#: part/templates/part/bom.html:44 -msgid "New BOM Item" +msgid "Import from File" msgstr "" #: part/templates/part/bom.html:45 +msgid "Copy BOM from parent part" +msgstr "" + +#: part/templates/part/bom.html:46 +msgid "Copy from Parent" +msgstr "" + +#: part/templates/part/bom.html:49 +msgid "New BOM Item" +msgstr "" + +#: part/templates/part/bom.html:50 msgid "Add Item" msgstr "" -#: part/templates/part/bom.html:47 +#: part/templates/part/bom.html:52 msgid "Finish Editing" msgstr "" -#: part/templates/part/bom.html:48 +#: part/templates/part/bom.html:53 msgid "Finished" msgstr "" -#: part/templates/part/bom.html:52 +#: part/templates/part/bom.html:57 msgid "Edit BOM" msgstr "" -#: part/templates/part/bom.html:53 part/templates/part/params.html:38 +#: part/templates/part/bom.html:58 part/templates/part/params.html:38 #: templates/InvenTree/settings/user.html:19 msgid "Edit" msgstr "" -#: part/templates/part/bom.html:56 +#: part/templates/part/bom.html:61 msgid "Validate Bill of Materials" msgstr "" -#: part/templates/part/bom.html:57 +#: part/templates/part/bom.html:62 msgid "Validate" msgstr "" -#: part/templates/part/bom.html:61 part/views.py:1643 +#: part/templates/part/bom.html:66 part/views.py:1692 msgid "Export Bill of Materials" msgstr "" -#: part/templates/part/bom.html:122 +#: part/templates/part/bom.html:127 msgid "Delete selected BOM items?" msgstr "" -#: part/templates/part/bom.html:123 +#: part/templates/part/bom.html:128 msgid "All selected BOM items will be deleted" msgstr "" +#: part/templates/part/bom_duplicate.html:13 +msgid "This part already has a Bill of Materials" +msgstr "" + #: part/templates/part/bom_upload/select_fields.html:8 #: part/templates/part/bom_upload/select_parts.html:8 #: part/templates/part/bom_upload/upload_file.html:10 @@ -2524,7 +2549,7 @@ msgstr "" msgid "All parts" msgstr "" -#: part/templates/part/category.html:24 part/views.py:2046 +#: part/templates/part/category.html:24 part/views.py:2095 msgid "Create new part category" msgstr "" @@ -2670,98 +2695,98 @@ msgstr "" msgid "Responsible User" msgstr "" -#: part/templates/part/detail.html:136 templates/js/table_filters.js:27 +#: part/templates/part/detail.html:138 templates/js/table_filters.js:27 msgid "Virtual" msgstr "" -#: part/templates/part/detail.html:139 +#: part/templates/part/detail.html:141 msgid "Part is virtual (not a physical part)" msgstr "" -#: part/templates/part/detail.html:141 +#: part/templates/part/detail.html:143 msgid "Part is not a virtual part" msgstr "" -#: part/templates/part/detail.html:145 stock/forms.py:248 +#: part/templates/part/detail.html:148 stock/forms.py:248 #: templates/js/table_filters.js:23 templates/js/table_filters.js:243 msgid "Template" msgstr "" -#: part/templates/part/detail.html:148 +#: part/templates/part/detail.html:151 msgid "Part is a template part (variants can be made from this part)" msgstr "" -#: part/templates/part/detail.html:150 +#: part/templates/part/detail.html:153 msgid "Part is not a template part" msgstr "" -#: part/templates/part/detail.html:154 templates/js/table_filters.js:255 +#: part/templates/part/detail.html:158 templates/js/table_filters.js:255 msgid "Assembly" msgstr "" -#: part/templates/part/detail.html:157 +#: part/templates/part/detail.html:161 msgid "Part can be assembled from other parts" msgstr "" -#: part/templates/part/detail.html:159 +#: part/templates/part/detail.html:163 msgid "Part cannot be assembled from other parts" msgstr "" -#: part/templates/part/detail.html:163 templates/js/table_filters.js:259 +#: part/templates/part/detail.html:168 templates/js/table_filters.js:259 msgid "Component" msgstr "" -#: part/templates/part/detail.html:166 +#: part/templates/part/detail.html:171 msgid "Part can be used in assemblies" msgstr "" -#: part/templates/part/detail.html:168 +#: part/templates/part/detail.html:173 msgid "Part cannot be used in assemblies" msgstr "" -#: part/templates/part/detail.html:172 templates/js/table_filters.js:31 +#: part/templates/part/detail.html:178 templates/js/table_filters.js:31 #: templates/js/table_filters.js:271 msgid "Trackable" msgstr "" -#: part/templates/part/detail.html:175 +#: part/templates/part/detail.html:181 msgid "Part stock is tracked by serial number" msgstr "" -#: part/templates/part/detail.html:177 +#: part/templates/part/detail.html:183 msgid "Part stock is not tracked by serial number" msgstr "" -#: part/templates/part/detail.html:181 +#: part/templates/part/detail.html:188 msgid "Purchaseable" msgstr "" -#: part/templates/part/detail.html:184 part/templates/part/detail.html:186 +#: part/templates/part/detail.html:191 part/templates/part/detail.html:193 msgid "Part can be purchased from external suppliers" msgstr "" -#: part/templates/part/detail.html:190 templates/js/table_filters.js:267 +#: part/templates/part/detail.html:198 templates/js/table_filters.js:267 msgid "Salable" msgstr "" -#: part/templates/part/detail.html:193 +#: part/templates/part/detail.html:201 msgid "Part can be sold to customers" msgstr "" -#: part/templates/part/detail.html:195 +#: part/templates/part/detail.html:203 msgid "Part cannot be sold to customers" msgstr "" -#: part/templates/part/detail.html:199 templates/js/table_filters.js:19 +#: part/templates/part/detail.html:214 templates/js/table_filters.js:19 #: templates/js/table_filters.js:55 templates/js/table_filters.js:238 msgid "Active" msgstr "" -#: part/templates/part/detail.html:202 +#: part/templates/part/detail.html:217 msgid "Part is active" msgstr "" -#: part/templates/part/detail.html:204 +#: part/templates/part/detail.html:219 msgid "Part is not active" msgstr "" @@ -3069,95 +3094,103 @@ msgstr "" msgid "Edit Part Properties" msgstr "" -#: part/views.py:838 +#: part/views.py:841 +msgid "Duplicate BOM" +msgstr "" + +#: part/views.py:871 +msgid "Confirm duplication of BOM from parent" +msgstr "" + +#: part/views.py:887 msgid "Validate BOM" msgstr "" -#: part/views.py:1005 +#: part/views.py:1054 msgid "No BOM file provided" msgstr "" -#: part/views.py:1355 +#: part/views.py:1404 msgid "Enter a valid quantity" msgstr "" -#: part/views.py:1380 part/views.py:1383 +#: part/views.py:1429 part/views.py:1432 msgid "Select valid part" msgstr "" -#: part/views.py:1389 +#: part/views.py:1438 msgid "Duplicate part selected" msgstr "" -#: part/views.py:1427 +#: part/views.py:1476 msgid "Select a part" msgstr "" -#: part/views.py:1433 +#: part/views.py:1482 msgid "Selected part creates a circular BOM" msgstr "" -#: part/views.py:1437 +#: part/views.py:1486 msgid "Specify quantity" msgstr "" -#: part/views.py:1693 +#: part/views.py:1742 msgid "Confirm Part Deletion" msgstr "" -#: part/views.py:1702 +#: part/views.py:1751 msgid "Part was deleted" msgstr "" -#: part/views.py:1711 +#: part/views.py:1760 msgid "Part Pricing" msgstr "" -#: part/views.py:1837 +#: part/views.py:1886 msgid "Create Part Parameter Template" msgstr "" -#: part/views.py:1847 +#: part/views.py:1896 msgid "Edit Part Parameter Template" msgstr "" -#: part/views.py:1856 +#: part/views.py:1905 msgid "Delete Part Parameter Template" msgstr "" -#: part/views.py:1866 +#: part/views.py:1915 msgid "Create Part Parameter" msgstr "" -#: part/views.py:1918 +#: part/views.py:1967 msgid "Edit Part Parameter" msgstr "" -#: part/views.py:1934 +#: part/views.py:1983 msgid "Delete Part Parameter" msgstr "" -#: part/views.py:1993 +#: part/views.py:2042 msgid "Edit Part Category" msgstr "" -#: part/views.py:2030 +#: part/views.py:2079 msgid "Delete Part Category" msgstr "" -#: part/views.py:2038 +#: part/views.py:2087 msgid "Part category was deleted" msgstr "" -#: part/views.py:2101 +#: part/views.py:2150 msgid "Create BOM Item" msgstr "" -#: part/views.py:2169 +#: part/views.py:2218 msgid "Edit BOM item" msgstr "" -#: part/views.py:2219 +#: part/views.py:2268 msgid "Confim BOM item deletion" msgstr "" diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index c64bbb8362..52c39bf3ba 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -19,10 +19,15 @@ from .models import PartParameterTemplate, PartParameter from .models import PartTestTemplate from .models import PartSellPriceBreak - from common.models import Currency +class PartModelChoiceField(forms.ModelChoiceField): + """ Extending string representation of Part instance with available stock """ + def label_from_instance(self, part): + return f'{part} - {part.available_stock}' + + class PartImageForm(HelperForm): """ Form for uploading a Part image """ @@ -77,6 +82,38 @@ class BomExportForm(forms.Form): self.fields['file_format'].choices = self.get_choices() +class BomDuplicateForm(HelperForm): + """ + Simple confirmation form for BOM duplication. + + Select which parent to select from. + """ + + parent = PartModelChoiceField( + label=_('Parent Part'), + help_text=_('Select parent part to copy BOM from'), + queryset=Part.objects.filter(is_template=True), + ) + + clear = forms.BooleanField( + required=False, initial=True, + help_text=_('Clear existing BOM items') + ) + + confirm = forms.BooleanField( + required=False, initial=False, + help_text=_('Confirm BOM duplication') + ) + + class Meta: + model = Part + fields = [ + 'parent', + 'clear', + 'confirm', + ] + + class BomValidateForm(HelperForm): """ Simple confirmation form for BOM validation. User is presented with a single checkbox input, @@ -210,12 +247,6 @@ class EditCategoryForm(HelperForm): ] -class PartModelChoiceField(forms.ModelChoiceField): - """ Extending string representation of Part instance with available stock """ - def label_from_instance(self, part): - return f'{part} - {part.available_stock}' - - class EditBomItemForm(HelperForm): """ Form for editing a BomItem object """ diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 2d654de129..a579547fba 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -1087,6 +1087,35 @@ class Part(MPTTModel): max(buy_price_range[1], bom_price_range[1]) ) + @transaction.atomic + def copy_bom_from(self, other, clear=True, **kwargs): + """ + Copy the BOM from another part. + + args: + other - The part to copy the BOM from + clear - Remove existing BOM items first (default=True) + """ + + if clear: + # Remove existing BOM items + self.bom_items.all().delete() + + for bom_item in other.bom_items.all(): + # If this part already has a BomItem pointing to the same sub-part, + # delete that BomItem from this part first! + + try: + existing = BomItem.objects.get(part=self, sub_part=bom_item.sub_part) + existing.delete() + except (BomItem.DoesNotExist): + pass + + bom_item.part = self + bom_item.pk = None + + bom_item.save() + def deepCopy(self, other, **kwargs): """ Duplicates non-field data from another part. Does not alter the normal fields of this part, @@ -1106,12 +1135,7 @@ class Part(MPTTModel): # Copy the BOM data if kwargs.get('bom', False): - for item in other.bom_items.all(): - # Point the item to THIS part. - # Set the pk to None so a new entry is created. - item.part = self - item.pk = None - item.save() + self.copy_bom_from(other) # Copy the parameters data if kwargs.get('parameters', True): diff --git a/InvenTree/part/templates/part/bom.html b/InvenTree/part/templates/part/bom.html index b0e7dde81d..a79e8b4dc2 100644 --- a/InvenTree/part/templates/part/bom.html +++ b/InvenTree/part/templates/part/bom.html @@ -39,8 +39,13 @@ + {% if part.variant_of %} + + {% endif %} @@ -157,6 +162,17 @@ location.href = "{% url 'upload-bom' part.id %}"; }); + $('#bom-duplicate').click(function() { + launchModalForm( + "{% url 'duplicate-bom' part.id %}", + { + success: function() { + $('#bom-table').bootstrapTable('refresh'); + } + } + ); + }); + $("#bom-item-new").click(function () { launchModalForm( "{% url 'bom-item-create' %}?parent={{ part.id }}", diff --git a/InvenTree/part/templates/part/bom_duplicate.html b/InvenTree/part/templates/part/bom_duplicate.html new file mode 100644 index 0000000000..7fd45afcbf --- /dev/null +++ b/InvenTree/part/templates/part/bom_duplicate.html @@ -0,0 +1,17 @@ +{% extends "modal_form.html" %} +{% load i18n %} + +{% block pre_form_content %} + +

+ {% trans "Select parent part to copy BOM from" %} +

+ +{% if part.has_bom %} +
+ {% trans "Warning" %}
+ {% trans "This part already has a Bill of Materials" %}
+
+{% endif %} + +{% endblock %} diff --git a/InvenTree/part/urls.py b/InvenTree/part/urls.py index 32cd1b0615..4d81faa2d6 100644 --- a/InvenTree/part/urls.py +++ b/InvenTree/part/urls.py @@ -46,6 +46,7 @@ part_detail_urls = [ url(r'^pricing/', views.PartPricing.as_view(), name='part-pricing'), url(r'^bom-upload/?', views.BomUpload.as_view(), name='upload-bom'), + url(r'^bom-duplicate/?', views.BomDuplicate.as_view(), name='duplicate-bom'), url(r'^params/', views.PartDetail.as_view(template_name='part/params.html'), name='part-params'), url(r'^variants/?', views.PartDetail.as_view(template_name='part/variants.html'), name='part-variants'), diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index bf6a4688ff..57a274b5bf 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -831,8 +831,60 @@ class PartEdit(AjaxUpdateView): return form +class BomDuplicate(AjaxUpdateView): + """ + View for duplicating BOM from a parent item. + """ + + model = Part + context_object_name = 'part' + ajax_form_title = _('Duplicate BOM') + ajax_template_name = 'part/bom_duplicate.html' + form_class = part_forms.BomDuplicateForm + role_required = 'part.change' + + def get_form(self): + + form = super().get_form() + + # Limit choices to parents of the current part + parents = self.get_object().get_ancestors() + + form.fields['parent'].queryset = parents + + return form + + def get_initial(self): + initials = super().get_initial() + + parents = self.get_object().get_ancestors() + + if parents.count() == 1: + initials['parent'] = parents[0] + + return initials + + def validate(self, part, form): + + confirm = str2bool(form.cleaned_data.get('confirm', False)) + + if not confirm: + form.add_error('confirm', _('Confirm duplication of BOM from parent')) + + def post_save(self, part, form): + + parent = form.cleaned_data.get('parent', None) + + clear = str2bool(form.cleaned_data.get('clear', True)) + + if parent: + part.copy_bom_from(parent, clear=clear) + + class BomValidate(AjaxUpdateView): - """ Modal form view for validating a part BOM """ + """ + Modal form view for validating a part BOM + """ model = Part ajax_form_title = _("Validate BOM") From 5c5641d884a5eeb9a66cfcd31d9dd8e3ab898e63 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 30 Oct 2020 10:12:42 +1100 Subject: [PATCH 2/7] Update calls to post_save --- InvenTree/order/views.py | 20 ++++++++++---------- InvenTree/stock/views.py | 12 ++++++------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/InvenTree/order/views.py b/InvenTree/order/views.py index d2d02bb2c9..3a5bec3a7f 100644 --- a/InvenTree/order/views.py +++ b/InvenTree/order/views.py @@ -100,9 +100,9 @@ class PurchaseOrderAttachmentCreate(AjaxCreateView): ajax_template_name = "modal_form.html" role_required = 'purchase_order.add' - def post_save(self, **kwargs): - self.object.user = self.request.user - self.object.save() + def post_save(self, attachment, form, **kwargs): + attachment.user = self.request.user + attachment.save() def get_data(self): return { @@ -148,7 +148,7 @@ class SalesOrderAttachmentCreate(AjaxCreateView): ajax_form_title = _('Add Sales Order Attachment') role_required = 'sales_order.add' - def post_save(self, **kwargs): + def post_save(self, attachment, form, **kwargs): self.object.user = self.request.user self.object.save() @@ -319,11 +319,11 @@ class PurchaseOrderCreate(AjaxCreateView): return initials - def post_save(self, **kwargs): + def post_save(self, order, form, **kwargs): # Record the user who created this purchase order - self.object.created_by = self.request.user - self.object.save() + order.created_by = self.request.user + order.save() class SalesOrderCreate(AjaxCreateView): @@ -351,10 +351,10 @@ class SalesOrderCreate(AjaxCreateView): return initials - def post_save(self, **kwargs): + def post_save(self, order, form, **kwargs): # Record the user who created this sales order - self.object.created_by = self.request.user - self.object.save() + order.created_by = self.request.user + order.save() class PurchaseOrderEdit(AjaxUpdateView): diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 65c8f30893..45c862da83 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -164,11 +164,11 @@ class StockItemAttachmentCreate(AjaxCreateView): ajax_template_name = "modal_form.html" role_required = 'stock.add' - def post_save(self, **kwargs): + def post_save(self, attachment, form, **kwargs): """ Record the user that uploaded the attachment """ - self.object.user = self.request.user - self.object.save() + attachment.user = self.request.user + attachment.save() def get_data(self): return { @@ -440,11 +440,11 @@ class StockItemTestResultCreate(AjaxCreateView): ajax_form_title = _("Add Test Result") role_required = 'stock.add' - def post_save(self, **kwargs): + def post_save(self, result, form, **kwargs): """ Record the user that uploaded the test result """ - self.object.user = self.request.user - self.object.save() + result.user = self.request.user + result.save() def get_initial(self): From 2428e779699dd600eae5341efe1053aa180d8b21 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 30 Oct 2020 11:45:54 +1100 Subject: [PATCH 3/7] Add filtering for BOM table --- InvenTree/part/api.py | 18 +++++++++++++++ InvenTree/templates/js/bom.js | 41 ++++++++++++++++++++++------------- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index 975a8af31d..496153a6d0 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -782,6 +782,24 @@ class BomList(generics.ListCreateAPIView): sub_part_trackable = str2bool(sub_part_trackable) queryset = queryset.filter(sub_part__trackable=sub_part_trackable) + # Filter by whether the BOM line has been validated + validated = params.get('validated', None) + + if validated is not None: + validated = str2bool(validated) + + # Work out which lines have actually been validated + pks = [] + + for bom_item in queryset.all(): + if bom_item.is_line_valid: + pks.append(bom_item.pk) + + if validated: + queryset = queryset.filter(pk__in=pks) + else: + queryset = queryset.exclude(pk__in=pks) + return queryset filter_backends = [ diff --git a/InvenTree/templates/js/bom.js b/InvenTree/templates/js/bom.js index d1d7d91346..c8da09da43 100644 --- a/InvenTree/templates/js/bom.js +++ b/InvenTree/templates/js/bom.js @@ -103,6 +103,29 @@ function loadBomTable(table, options) { * BOM data are retrieved from the server via AJAX query */ + var params = { + part: options.parent_id, + ordering: 'name', + } + + if (options.part_detail) { + params.part_detail = true; + } + + params.sub_part_detail = true; + + var filters = {}; + + if (!options.disableFilters) { + filters = loadTableFilters('bom'); + } + + for (var key in params) { + filters[key] = params[key]; + } + + setupFilterList('bom', $(table)); + // Construct the table columns var cols = []; @@ -286,19 +309,6 @@ function loadBomTable(table, options) { // Configure the table (bootstrap-table) - var params = { - part: options.parent_id, - ordering: 'name', - } - - if (options.part_detail) { - params.part_detail = true; - } - - if (options.sub_part_detail) { - params.sub_part_detail = true; - } - // Function to request BOM data for sub-items // This function may be called recursively for multi-level BOMs function requestSubItems(bom_pk, part_pk) { @@ -349,9 +359,10 @@ function loadBomTable(table, options) { return {classes: 'rowinvalid'}; } }, - formatNoMatches: function() { return "{% trans "No BOM items found" %}"; }, + formatNoMatches: function() { return '{% trans "No BOM items found" %}'; }, clickToSelect: true, - queryParams: params, + queryParams: filters, + original: params, columns: cols, url: options.bom_url, onPostBody: function() { From cabbdbb5cf7c053c66c8685f6b29edf0682c75c7 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 30 Oct 2020 15:53:13 +1100 Subject: [PATCH 4/7] Fixed a typo in bom.js --- InvenTree/locale/de/LC_MESSAGES/django.po | 131 +++++++++++----------- InvenTree/locale/en/LC_MESSAGES/django.po | 124 ++++++++++---------- InvenTree/locale/es/LC_MESSAGES/django.po | 124 ++++++++++---------- InvenTree/templates/js/bom.js | 2 +- 4 files changed, 186 insertions(+), 195 deletions(-) diff --git a/InvenTree/locale/de/LC_MESSAGES/django.po b/InvenTree/locale/de/LC_MESSAGES/django.po index e9d639e529..3ba6dde6d3 100644 --- a/InvenTree/locale/de/LC_MESSAGES/django.po +++ b/InvenTree/locale/de/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-10-29 23:05+0000\n" +"POT-Creation-Date: 2020-10-30 04:52+0000\n" "PO-Revision-Date: 2020-05-03 11:32+0200\n" "Last-Translator: Christian Schlüter \n" "Language-Team: C \n" @@ -315,7 +315,7 @@ msgid "Build Order Reference" msgstr "Bestellreferenz" #: build/models.py:87 build/templates/build/allocate.html:342 -#: order/templates/order/purchase_order_detail.html:173 templates/js/bom.js:172 +#: order/templates/order/purchase_order_detail.html:173 templates/js/bom.js:195 msgid "Reference" msgstr "Referenz" @@ -324,8 +324,8 @@ msgstr "Referenz" #: company/templates/company/supplier_part_detail.html:27 #: order/templates/order/purchase_order_detail.html:160 #: part/templates/part/detail.html:51 part/templates/part/set_category.html:14 -#: templates/InvenTree/search.html:147 templates/js/bom.js:165 -#: templates/js/bom.js:504 templates/js/build.js:56 templates/js/company.js:56 +#: templates/InvenTree/search.html:147 templates/js/bom.js:188 +#: templates/js/bom.js:515 templates/js/build.js:56 templates/js/company.js:56 #: templates/js/order.js:167 templates/js/order.js:249 templates/js/part.js:149 #: templates/js/part.js:232 templates/js/part.js:384 templates/js/part.js:565 #: templates/js/stock.js:445 templates/js/stock.js:672 @@ -353,7 +353,7 @@ msgstr "Eltern-Bau, dem dieser Bau zugewiesen ist" #: order/templates/order/receive_parts.html:19 part/models.py:293 #: part/templates/part/part_app_base.html:7 #: part/templates/part/set_category.html:13 templates/InvenTree/search.html:133 -#: templates/js/barcode.js:336 templates/js/bom.js:124 templates/js/bom.js:489 +#: templates/js/barcode.js:336 templates/js/bom.js:147 templates/js/bom.js:500 #: templates/js/build.js:61 templates/js/company.js:138 #: templates/js/part.js:213 templates/js/part.js:318 templates/js/stock.js:421 #: templates/js/stock.js:978 @@ -426,7 +426,7 @@ msgstr "Link zu einer externen URL" #: order/templates/order/so_tabs.html:23 part/templates/part/tabs.html:70 #: stock/forms.py:306 stock/forms.py:338 stock/forms.py:366 stock/models.py:453 #: stock/models.py:1404 stock/templates/stock/tabs.html:26 -#: templates/js/barcode.js:391 templates/js/bom.js:241 +#: templates/js/barcode.js:391 templates/js/bom.js:264 #: templates/js/stock.js:116 templates/js/stock.js:544 msgid "Notes" msgstr "Notizen" @@ -519,7 +519,7 @@ msgstr "Seriennummer" #: stock/templates/stock/item_base.html:32 #: stock/templates/stock/item_base.html:184 #: stock/templates/stock/stock_adjust.html:18 templates/js/barcode.js:338 -#: templates/js/bom.js:180 templates/js/build.js:72 templates/js/stock.js:691 +#: templates/js/bom.js:203 templates/js/build.js:72 templates/js/stock.js:691 #: templates/js/stock.js:906 msgid "Quantity" msgstr "Anzahl" @@ -543,7 +543,7 @@ msgstr "Lagerobjekt-Standort bearbeiten" msgid "Delete stock allocation" msgstr "Zuweisung löschen" -#: build/templates/build/allocate.html:238 templates/js/bom.js:352 +#: build/templates/build/allocate.html:238 templates/js/bom.js:362 msgid "No BOM items found" msgstr "Keine BOM-Einträge gefunden" @@ -1398,7 +1398,7 @@ msgid "Pricing Information" msgstr "Preisinformationen ansehen" #: company/templates/company/supplier_part_pricing.html:17 company/views.py:410 -#: part/templates/part/sale_prices.html:13 part/views.py:2278 +#: part/templates/part/sale_prices.html:13 part/views.py:2281 msgid "Add Price Break" msgstr "Preisstaffel hinzufügen" @@ -1410,7 +1410,7 @@ msgid "No price break information found" msgstr "Keine Firmeninformation gefunden" #: company/templates/company/supplier_part_pricing.html:80 -#: part/templates/part/sale_prices.html:85 templates/js/bom.js:225 +#: part/templates/part/sale_prices.html:85 templates/js/bom.js:248 msgid "Price" msgstr "Preis" @@ -1531,17 +1531,17 @@ msgstr "Neues Zuliefererteil anlegen" msgid "Delete Supplier Part" msgstr "Zuliefererteil entfernen" -#: company/views.py:416 part/views.py:2284 +#: company/views.py:416 part/views.py:2287 #, fuzzy #| msgid "Add Price Break" msgid "Added new price break" msgstr "Preisstaffel hinzufügen" -#: company/views.py:453 part/views.py:2329 +#: company/views.py:453 part/views.py:2332 msgid "Edit Price Break" msgstr "Preisstaffel bearbeiten" -#: company/views.py:469 part/views.py:2345 +#: company/views.py:469 part/views.py:2348 msgid "Delete Price Break" msgstr "Preisstaffel löschen" @@ -1646,7 +1646,7 @@ msgstr "" msgid "Date order was completed" msgstr "Bestellung als vollständig markieren" -#: order/models.py:185 order/models.py:259 part/views.py:1395 +#: order/models.py:185 order/models.py:259 part/views.py:1398 #: stock/models.py:241 stock/models.py:805 msgid "Quantity must be greater than zero" msgstr "Anzahl muss größer Null sein" @@ -2531,7 +2531,7 @@ msgstr "Notizen zum Stücklisten-Objekt" msgid "BOM line checksum" msgstr "Prüfsumme der Stückliste" -#: part/models.py:1636 part/views.py:1401 part/views.py:1453 +#: part/models.py:1636 part/views.py:1404 part/views.py:1456 #: stock/models.py:231 #, fuzzy #| msgid "Overage must be an integer value or a percentage" @@ -2634,7 +2634,7 @@ msgstr "Stückliste validieren" msgid "Validate" msgstr "BOM validieren" -#: part/templates/part/bom.html:66 part/views.py:1692 +#: part/templates/part/bom.html:66 part/views.py:1695 msgid "Export Bill of Materials" msgstr "Stückliste exportieren" @@ -2756,7 +2756,7 @@ msgstr "Neuen Bau beginnen" msgid "All parts" msgstr "Alle Teile" -#: part/templates/part/category.html:24 part/views.py:2095 +#: part/templates/part/category.html:24 part/views.py:2098 msgid "Create new part category" msgstr "Teilkategorie anlegen" @@ -3077,7 +3077,7 @@ msgstr "Dieses Teil ist eine Vorlage." msgid "This part is a variant of" msgstr "Dieses Teil ist eine Variante von" -#: part/templates/part/part_base.html:36 templates/js/bom.js:152 +#: part/templates/part/part_base.html:36 templates/js/bom.js:175 #: templates/js/company.js:155 templates/js/part.js:133 #: templates/js/part.js:375 msgid "Inactive" @@ -3209,7 +3209,7 @@ msgstr "Teil entfernen" msgid "Part Stock" msgstr "Teilbestand" -#: part/templates/part/stock_count.html:7 templates/js/bom.js:215 +#: part/templates/part/stock_count.html:7 templates/js/bom.js:238 #: templates/js/part.js:435 msgid "No Stock" msgstr "Kein Bestand" @@ -3385,105 +3385,105 @@ msgstr "Teileigenschaften bearbeiten" msgid "Duplicate BOM" msgstr "Teil duplizieren" -#: part/views.py:871 +#: part/views.py:872 #, fuzzy #| msgid "Confirm unallocation of build stock" msgid "Confirm duplication of BOM from parent" msgstr "Zuweisungsaufhebung bestätigen" -#: part/views.py:887 +#: part/views.py:890 msgid "Validate BOM" msgstr "BOM validieren" -#: part/views.py:1054 +#: part/views.py:1057 msgid "No BOM file provided" msgstr "Keine Stückliste angegeben" -#: part/views.py:1404 +#: part/views.py:1407 msgid "Enter a valid quantity" msgstr "Bitte eine gültige Anzahl eingeben" -#: part/views.py:1429 part/views.py:1432 +#: part/views.py:1432 part/views.py:1435 msgid "Select valid part" msgstr "Bitte ein gültiges Teil auswählen" -#: part/views.py:1438 +#: part/views.py:1441 msgid "Duplicate part selected" msgstr "Teil doppelt ausgewählt" -#: part/views.py:1476 +#: part/views.py:1479 msgid "Select a part" msgstr "Teil auswählen" -#: part/views.py:1482 +#: part/views.py:1485 #, fuzzy #| msgid "Select part to be used in BOM" msgid "Selected part creates a circular BOM" msgstr "Teil für die Nutzung in der Stückliste auswählen" -#: part/views.py:1486 +#: part/views.py:1489 msgid "Specify quantity" msgstr "Anzahl angeben" -#: part/views.py:1742 +#: part/views.py:1745 msgid "Confirm Part Deletion" msgstr "Löschen des Teils bestätigen" -#: part/views.py:1751 +#: part/views.py:1754 msgid "Part was deleted" msgstr "Teil wurde gelöscht" -#: part/views.py:1760 +#: part/views.py:1763 msgid "Part Pricing" msgstr "Teilbepreisung" -#: part/views.py:1886 +#: part/views.py:1889 msgid "Create Part Parameter Template" msgstr "Teilparametervorlage anlegen" -#: part/views.py:1896 +#: part/views.py:1899 msgid "Edit Part Parameter Template" msgstr "Teilparametervorlage bearbeiten" -#: part/views.py:1905 +#: part/views.py:1908 msgid "Delete Part Parameter Template" msgstr "Teilparametervorlage löschen" -#: part/views.py:1915 +#: part/views.py:1918 msgid "Create Part Parameter" msgstr "Teilparameter anlegen" -#: part/views.py:1967 +#: part/views.py:1970 msgid "Edit Part Parameter" msgstr "Teilparameter bearbeiten" -#: part/views.py:1983 +#: part/views.py:1986 msgid "Delete Part Parameter" msgstr "Teilparameter löschen" -#: part/views.py:2042 +#: part/views.py:2045 msgid "Edit Part Category" msgstr "Teilkategorie bearbeiten" -#: part/views.py:2079 +#: part/views.py:2082 msgid "Delete Part Category" msgstr "Teilkategorie löschen" -#: part/views.py:2087 +#: part/views.py:2090 msgid "Part category was deleted" msgstr "Teilekategorie wurde gelöscht" -#: part/views.py:2150 +#: part/views.py:2153 #, fuzzy #| msgid "Create BOM item" msgid "Create BOM Item" msgstr "BOM-Position anlegen" -#: part/views.py:2218 +#: part/views.py:2221 msgid "Edit BOM item" msgstr "BOM-Position beaarbeiten" -#: part/views.py:2268 +#: part/views.py:2271 msgid "Confim BOM item deletion" msgstr "Löschung von BOM-Position bestätigen" @@ -4912,73 +4912,72 @@ msgstr "Vorrat zu {n} Lagerobjekten hinzugefügt" msgid "Barcode does not match Stock Item" msgstr "Neues Lagerobjekt hinzufügen" -#: templates/js/bom.js:133 templates/js/part.js:117 templates/js/part.js:344 +#: templates/js/bom.js:156 templates/js/part.js:117 templates/js/part.js:344 #, fuzzy #| msgid "Trackable" msgid "Trackable part" msgstr "nachverfolgbar" -#: templates/js/bom.js:137 templates/js/part.js:121 templates/js/part.js:348 +#: templates/js/bom.js:160 templates/js/part.js:121 templates/js/part.js:348 #, fuzzy #| msgid "Virtual" msgid "Virtual part" msgstr "Virtuell" -#: templates/js/bom.js:141 -#, fuzzy -#| msgid "Template part" -msgid "Templat part" +#: templates/js/bom.js:164 templates/js/company.js:147 templates/js/part.js:125 +#: templates/js/part.js:353 +msgid "Template part" msgstr "Vorlagenteil" -#: templates/js/bom.js:146 +#: templates/js/bom.js:169 msgid "Open subassembly" msgstr "Unterbaugruppe öffnen" -#: templates/js/bom.js:191 +#: templates/js/bom.js:214 #, fuzzy #| msgid "Options" msgid "Optional" msgstr "Optionen" -#: templates/js/bom.js:206 templates/js/build.js:133 +#: templates/js/bom.js:229 templates/js/build.js:133 msgid "Available" msgstr "verfügbar" -#: templates/js/bom.js:231 +#: templates/js/bom.js:254 msgid "No pricing available" msgstr "Keine Preisinformation verfügbar" -#: templates/js/bom.js:250 +#: templates/js/bom.js:273 #, fuzzy #| msgid "Options" msgid "Actions" msgstr "Optionen" -#: templates/js/bom.js:258 +#: templates/js/bom.js:281 msgid "Validate BOM Item" msgstr "BOM-Position validieren" -#: templates/js/bom.js:260 +#: templates/js/bom.js:283 msgid "This line has been validated" msgstr "Diese Position wurde validiert" -#: templates/js/bom.js:262 +#: templates/js/bom.js:285 msgid "Edit BOM Item" msgstr "BOM-Position bearbeiten" -#: templates/js/bom.js:264 +#: templates/js/bom.js:287 msgid "Delete BOM Item" msgstr "BOM-Position löschen" -#: templates/js/bom.js:496 +#: templates/js/bom.js:507 msgid "INACTIVE" msgstr "INAKTIV" -#: templates/js/bom.js:510 +#: templates/js/bom.js:521 msgid "Uses" msgstr "" -#: templates/js/bom.js:521 +#: templates/js/bom.js:532 #, fuzzy #| msgid "No matching action found" msgid "No matching parts found" @@ -5012,11 +5011,6 @@ msgstr "Keine Firmeninformation gefunden" msgid "No supplier parts found" msgstr "Keine Zuliefererteile gefunden" -#: templates/js/company.js:147 templates/js/part.js:125 -#: templates/js/part.js:353 -msgid "Template part" -msgstr "Vorlagenteil" - #: templates/js/company.js:151 templates/js/part.js:129 #: templates/js/part.js:357 msgid "Assembled part" @@ -5547,6 +5541,11 @@ msgstr "" msgid "Permission to delete items" msgstr "Ausgewählte Stücklistenpositionen entfernen" +#, fuzzy +#~| msgid "Template part" +#~ msgid "Templat part" +#~ msgstr "Vorlagenteil" + #~ msgid "Link to extenal URL" #~ msgstr "Link zu einer Externen URL" diff --git a/InvenTree/locale/en/LC_MESSAGES/django.po b/InvenTree/locale/en/LC_MESSAGES/django.po index f9731db17b..3f07b7337e 100644 --- a/InvenTree/locale/en/LC_MESSAGES/django.po +++ b/InvenTree/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-10-29 23:05+0000\n" +"POT-Creation-Date: 2020-10-30 04:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -291,7 +291,7 @@ msgid "Build Order Reference" msgstr "" #: build/models.py:87 build/templates/build/allocate.html:342 -#: order/templates/order/purchase_order_detail.html:173 templates/js/bom.js:172 +#: order/templates/order/purchase_order_detail.html:173 templates/js/bom.js:195 msgid "Reference" msgstr "" @@ -300,8 +300,8 @@ msgstr "" #: company/templates/company/supplier_part_detail.html:27 #: order/templates/order/purchase_order_detail.html:160 #: part/templates/part/detail.html:51 part/templates/part/set_category.html:14 -#: templates/InvenTree/search.html:147 templates/js/bom.js:165 -#: templates/js/bom.js:504 templates/js/build.js:56 templates/js/company.js:56 +#: templates/InvenTree/search.html:147 templates/js/bom.js:188 +#: templates/js/bom.js:515 templates/js/build.js:56 templates/js/company.js:56 #: templates/js/order.js:167 templates/js/order.js:249 templates/js/part.js:149 #: templates/js/part.js:232 templates/js/part.js:384 templates/js/part.js:565 #: templates/js/stock.js:445 templates/js/stock.js:672 @@ -329,7 +329,7 @@ msgstr "" #: order/templates/order/receive_parts.html:19 part/models.py:293 #: part/templates/part/part_app_base.html:7 #: part/templates/part/set_category.html:13 templates/InvenTree/search.html:133 -#: templates/js/barcode.js:336 templates/js/bom.js:124 templates/js/bom.js:489 +#: templates/js/barcode.js:336 templates/js/bom.js:147 templates/js/bom.js:500 #: templates/js/build.js:61 templates/js/company.js:138 #: templates/js/part.js:213 templates/js/part.js:318 templates/js/stock.js:421 #: templates/js/stock.js:978 @@ -400,7 +400,7 @@ msgstr "" #: order/templates/order/so_tabs.html:23 part/templates/part/tabs.html:70 #: stock/forms.py:306 stock/forms.py:338 stock/forms.py:366 stock/models.py:453 #: stock/models.py:1404 stock/templates/stock/tabs.html:26 -#: templates/js/barcode.js:391 templates/js/bom.js:241 +#: templates/js/barcode.js:391 templates/js/bom.js:264 #: templates/js/stock.js:116 templates/js/stock.js:544 msgid "Notes" msgstr "" @@ -492,7 +492,7 @@ msgstr "" #: stock/templates/stock/item_base.html:32 #: stock/templates/stock/item_base.html:184 #: stock/templates/stock/stock_adjust.html:18 templates/js/barcode.js:338 -#: templates/js/bom.js:180 templates/js/build.js:72 templates/js/stock.js:691 +#: templates/js/bom.js:203 templates/js/build.js:72 templates/js/stock.js:691 #: templates/js/stock.js:906 msgid "Quantity" msgstr "" @@ -516,7 +516,7 @@ msgstr "" msgid "Delete stock allocation" msgstr "" -#: build/templates/build/allocate.html:238 templates/js/bom.js:352 +#: build/templates/build/allocate.html:238 templates/js/bom.js:362 msgid "No BOM items found" msgstr "" @@ -1310,7 +1310,7 @@ msgid "Pricing Information" msgstr "" #: company/templates/company/supplier_part_pricing.html:17 company/views.py:410 -#: part/templates/part/sale_prices.html:13 part/views.py:2278 +#: part/templates/part/sale_prices.html:13 part/views.py:2281 msgid "Add Price Break" msgstr "" @@ -1320,7 +1320,7 @@ msgid "No price break information found" msgstr "" #: company/templates/company/supplier_part_pricing.html:80 -#: part/templates/part/sale_prices.html:85 templates/js/bom.js:225 +#: part/templates/part/sale_prices.html:85 templates/js/bom.js:248 msgid "Price" msgstr "" @@ -1437,15 +1437,15 @@ msgstr "" msgid "Delete Supplier Part" msgstr "" -#: company/views.py:416 part/views.py:2284 +#: company/views.py:416 part/views.py:2287 msgid "Added new price break" msgstr "" -#: company/views.py:453 part/views.py:2329 +#: company/views.py:453 part/views.py:2332 msgid "Edit Price Break" msgstr "" -#: company/views.py:469 part/views.py:2345 +#: company/views.py:469 part/views.py:2348 msgid "Delete Price Break" msgstr "" @@ -1538,7 +1538,7 @@ msgstr "" msgid "Date order was completed" msgstr "" -#: order/models.py:185 order/models.py:259 part/views.py:1395 +#: order/models.py:185 order/models.py:259 part/views.py:1398 #: stock/models.py:241 stock/models.py:805 msgid "Quantity must be greater than zero" msgstr "" @@ -2362,7 +2362,7 @@ msgstr "" msgid "BOM line checksum" msgstr "" -#: part/models.py:1636 part/views.py:1401 part/views.py:1453 +#: part/models.py:1636 part/views.py:1404 part/views.py:1456 #: stock/models.py:231 msgid "Quantity must be integer value for trackable parts" msgstr "" @@ -2453,7 +2453,7 @@ msgstr "" msgid "Validate" msgstr "" -#: part/templates/part/bom.html:66 part/views.py:1692 +#: part/templates/part/bom.html:66 part/views.py:1695 msgid "Export Bill of Materials" msgstr "" @@ -2549,7 +2549,7 @@ msgstr "" msgid "All parts" msgstr "" -#: part/templates/part/category.html:24 part/views.py:2095 +#: part/templates/part/category.html:24 part/views.py:2098 msgid "Create new part category" msgstr "" @@ -2832,7 +2832,7 @@ msgstr "" msgid "This part is a variant of" msgstr "" -#: part/templates/part/part_base.html:36 templates/js/bom.js:152 +#: part/templates/part/part_base.html:36 templates/js/bom.js:175 #: templates/js/company.js:155 templates/js/part.js:133 #: templates/js/part.js:375 msgid "Inactive" @@ -2944,7 +2944,7 @@ msgstr "" msgid "Part Stock" msgstr "" -#: part/templates/part/stock_count.html:7 templates/js/bom.js:215 +#: part/templates/part/stock_count.html:7 templates/js/bom.js:238 #: templates/js/part.js:435 msgid "No Stock" msgstr "" @@ -3098,99 +3098,99 @@ msgstr "" msgid "Duplicate BOM" msgstr "" -#: part/views.py:871 +#: part/views.py:872 msgid "Confirm duplication of BOM from parent" msgstr "" -#: part/views.py:887 +#: part/views.py:890 msgid "Validate BOM" msgstr "" -#: part/views.py:1054 +#: part/views.py:1057 msgid "No BOM file provided" msgstr "" -#: part/views.py:1404 +#: part/views.py:1407 msgid "Enter a valid quantity" msgstr "" -#: part/views.py:1429 part/views.py:1432 +#: part/views.py:1432 part/views.py:1435 msgid "Select valid part" msgstr "" -#: part/views.py:1438 +#: part/views.py:1441 msgid "Duplicate part selected" msgstr "" -#: part/views.py:1476 +#: part/views.py:1479 msgid "Select a part" msgstr "" -#: part/views.py:1482 +#: part/views.py:1485 msgid "Selected part creates a circular BOM" msgstr "" -#: part/views.py:1486 +#: part/views.py:1489 msgid "Specify quantity" msgstr "" -#: part/views.py:1742 +#: part/views.py:1745 msgid "Confirm Part Deletion" msgstr "" -#: part/views.py:1751 +#: part/views.py:1754 msgid "Part was deleted" msgstr "" -#: part/views.py:1760 +#: part/views.py:1763 msgid "Part Pricing" msgstr "" -#: part/views.py:1886 +#: part/views.py:1889 msgid "Create Part Parameter Template" msgstr "" -#: part/views.py:1896 +#: part/views.py:1899 msgid "Edit Part Parameter Template" msgstr "" -#: part/views.py:1905 +#: part/views.py:1908 msgid "Delete Part Parameter Template" msgstr "" -#: part/views.py:1915 +#: part/views.py:1918 msgid "Create Part Parameter" msgstr "" -#: part/views.py:1967 +#: part/views.py:1970 msgid "Edit Part Parameter" msgstr "" -#: part/views.py:1983 +#: part/views.py:1986 msgid "Delete Part Parameter" msgstr "" -#: part/views.py:2042 +#: part/views.py:2045 msgid "Edit Part Category" msgstr "" -#: part/views.py:2079 +#: part/views.py:2082 msgid "Delete Part Category" msgstr "" -#: part/views.py:2087 +#: part/views.py:2090 msgid "Part category was deleted" msgstr "" -#: part/views.py:2150 +#: part/views.py:2153 msgid "Create BOM Item" msgstr "" -#: part/views.py:2218 +#: part/views.py:2221 msgid "Edit BOM item" msgstr "" -#: part/views.py:2268 +#: part/views.py:2271 msgid "Confim BOM item deletion" msgstr "" @@ -4360,63 +4360,64 @@ msgstr "" msgid "Barcode does not match Stock Item" msgstr "" -#: templates/js/bom.js:133 templates/js/part.js:117 templates/js/part.js:344 +#: templates/js/bom.js:156 templates/js/part.js:117 templates/js/part.js:344 msgid "Trackable part" msgstr "" -#: templates/js/bom.js:137 templates/js/part.js:121 templates/js/part.js:348 +#: templates/js/bom.js:160 templates/js/part.js:121 templates/js/part.js:348 msgid "Virtual part" msgstr "" -#: templates/js/bom.js:141 -msgid "Templat part" +#: templates/js/bom.js:164 templates/js/company.js:147 templates/js/part.js:125 +#: templates/js/part.js:353 +msgid "Template part" msgstr "" -#: templates/js/bom.js:146 +#: templates/js/bom.js:169 msgid "Open subassembly" msgstr "" -#: templates/js/bom.js:191 +#: templates/js/bom.js:214 msgid "Optional" msgstr "" -#: templates/js/bom.js:206 templates/js/build.js:133 +#: templates/js/bom.js:229 templates/js/build.js:133 msgid "Available" msgstr "" -#: templates/js/bom.js:231 +#: templates/js/bom.js:254 msgid "No pricing available" msgstr "" -#: templates/js/bom.js:250 +#: templates/js/bom.js:273 msgid "Actions" msgstr "" -#: templates/js/bom.js:258 +#: templates/js/bom.js:281 msgid "Validate BOM Item" msgstr "" -#: templates/js/bom.js:260 +#: templates/js/bom.js:283 msgid "This line has been validated" msgstr "" -#: templates/js/bom.js:262 +#: templates/js/bom.js:285 msgid "Edit BOM Item" msgstr "" -#: templates/js/bom.js:264 +#: templates/js/bom.js:287 msgid "Delete BOM Item" msgstr "" -#: templates/js/bom.js:496 +#: templates/js/bom.js:507 msgid "INACTIVE" msgstr "" -#: templates/js/bom.js:510 +#: templates/js/bom.js:521 msgid "Uses" msgstr "" -#: templates/js/bom.js:521 +#: templates/js/bom.js:532 msgid "No matching parts found" msgstr "" @@ -4444,11 +4445,6 @@ msgstr "" msgid "No supplier parts found" msgstr "" -#: templates/js/company.js:147 templates/js/part.js:125 -#: templates/js/part.js:353 -msgid "Template part" -msgstr "" - #: templates/js/company.js:151 templates/js/part.js:129 #: templates/js/part.js:357 msgid "Assembled part" diff --git a/InvenTree/locale/es/LC_MESSAGES/django.po b/InvenTree/locale/es/LC_MESSAGES/django.po index f9731db17b..3f07b7337e 100644 --- a/InvenTree/locale/es/LC_MESSAGES/django.po +++ b/InvenTree/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-10-29 23:05+0000\n" +"POT-Creation-Date: 2020-10-30 04:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -291,7 +291,7 @@ msgid "Build Order Reference" msgstr "" #: build/models.py:87 build/templates/build/allocate.html:342 -#: order/templates/order/purchase_order_detail.html:173 templates/js/bom.js:172 +#: order/templates/order/purchase_order_detail.html:173 templates/js/bom.js:195 msgid "Reference" msgstr "" @@ -300,8 +300,8 @@ msgstr "" #: company/templates/company/supplier_part_detail.html:27 #: order/templates/order/purchase_order_detail.html:160 #: part/templates/part/detail.html:51 part/templates/part/set_category.html:14 -#: templates/InvenTree/search.html:147 templates/js/bom.js:165 -#: templates/js/bom.js:504 templates/js/build.js:56 templates/js/company.js:56 +#: templates/InvenTree/search.html:147 templates/js/bom.js:188 +#: templates/js/bom.js:515 templates/js/build.js:56 templates/js/company.js:56 #: templates/js/order.js:167 templates/js/order.js:249 templates/js/part.js:149 #: templates/js/part.js:232 templates/js/part.js:384 templates/js/part.js:565 #: templates/js/stock.js:445 templates/js/stock.js:672 @@ -329,7 +329,7 @@ msgstr "" #: order/templates/order/receive_parts.html:19 part/models.py:293 #: part/templates/part/part_app_base.html:7 #: part/templates/part/set_category.html:13 templates/InvenTree/search.html:133 -#: templates/js/barcode.js:336 templates/js/bom.js:124 templates/js/bom.js:489 +#: templates/js/barcode.js:336 templates/js/bom.js:147 templates/js/bom.js:500 #: templates/js/build.js:61 templates/js/company.js:138 #: templates/js/part.js:213 templates/js/part.js:318 templates/js/stock.js:421 #: templates/js/stock.js:978 @@ -400,7 +400,7 @@ msgstr "" #: order/templates/order/so_tabs.html:23 part/templates/part/tabs.html:70 #: stock/forms.py:306 stock/forms.py:338 stock/forms.py:366 stock/models.py:453 #: stock/models.py:1404 stock/templates/stock/tabs.html:26 -#: templates/js/barcode.js:391 templates/js/bom.js:241 +#: templates/js/barcode.js:391 templates/js/bom.js:264 #: templates/js/stock.js:116 templates/js/stock.js:544 msgid "Notes" msgstr "" @@ -492,7 +492,7 @@ msgstr "" #: stock/templates/stock/item_base.html:32 #: stock/templates/stock/item_base.html:184 #: stock/templates/stock/stock_adjust.html:18 templates/js/barcode.js:338 -#: templates/js/bom.js:180 templates/js/build.js:72 templates/js/stock.js:691 +#: templates/js/bom.js:203 templates/js/build.js:72 templates/js/stock.js:691 #: templates/js/stock.js:906 msgid "Quantity" msgstr "" @@ -516,7 +516,7 @@ msgstr "" msgid "Delete stock allocation" msgstr "" -#: build/templates/build/allocate.html:238 templates/js/bom.js:352 +#: build/templates/build/allocate.html:238 templates/js/bom.js:362 msgid "No BOM items found" msgstr "" @@ -1310,7 +1310,7 @@ msgid "Pricing Information" msgstr "" #: company/templates/company/supplier_part_pricing.html:17 company/views.py:410 -#: part/templates/part/sale_prices.html:13 part/views.py:2278 +#: part/templates/part/sale_prices.html:13 part/views.py:2281 msgid "Add Price Break" msgstr "" @@ -1320,7 +1320,7 @@ msgid "No price break information found" msgstr "" #: company/templates/company/supplier_part_pricing.html:80 -#: part/templates/part/sale_prices.html:85 templates/js/bom.js:225 +#: part/templates/part/sale_prices.html:85 templates/js/bom.js:248 msgid "Price" msgstr "" @@ -1437,15 +1437,15 @@ msgstr "" msgid "Delete Supplier Part" msgstr "" -#: company/views.py:416 part/views.py:2284 +#: company/views.py:416 part/views.py:2287 msgid "Added new price break" msgstr "" -#: company/views.py:453 part/views.py:2329 +#: company/views.py:453 part/views.py:2332 msgid "Edit Price Break" msgstr "" -#: company/views.py:469 part/views.py:2345 +#: company/views.py:469 part/views.py:2348 msgid "Delete Price Break" msgstr "" @@ -1538,7 +1538,7 @@ msgstr "" msgid "Date order was completed" msgstr "" -#: order/models.py:185 order/models.py:259 part/views.py:1395 +#: order/models.py:185 order/models.py:259 part/views.py:1398 #: stock/models.py:241 stock/models.py:805 msgid "Quantity must be greater than zero" msgstr "" @@ -2362,7 +2362,7 @@ msgstr "" msgid "BOM line checksum" msgstr "" -#: part/models.py:1636 part/views.py:1401 part/views.py:1453 +#: part/models.py:1636 part/views.py:1404 part/views.py:1456 #: stock/models.py:231 msgid "Quantity must be integer value for trackable parts" msgstr "" @@ -2453,7 +2453,7 @@ msgstr "" msgid "Validate" msgstr "" -#: part/templates/part/bom.html:66 part/views.py:1692 +#: part/templates/part/bom.html:66 part/views.py:1695 msgid "Export Bill of Materials" msgstr "" @@ -2549,7 +2549,7 @@ msgstr "" msgid "All parts" msgstr "" -#: part/templates/part/category.html:24 part/views.py:2095 +#: part/templates/part/category.html:24 part/views.py:2098 msgid "Create new part category" msgstr "" @@ -2832,7 +2832,7 @@ msgstr "" msgid "This part is a variant of" msgstr "" -#: part/templates/part/part_base.html:36 templates/js/bom.js:152 +#: part/templates/part/part_base.html:36 templates/js/bom.js:175 #: templates/js/company.js:155 templates/js/part.js:133 #: templates/js/part.js:375 msgid "Inactive" @@ -2944,7 +2944,7 @@ msgstr "" msgid "Part Stock" msgstr "" -#: part/templates/part/stock_count.html:7 templates/js/bom.js:215 +#: part/templates/part/stock_count.html:7 templates/js/bom.js:238 #: templates/js/part.js:435 msgid "No Stock" msgstr "" @@ -3098,99 +3098,99 @@ msgstr "" msgid "Duplicate BOM" msgstr "" -#: part/views.py:871 +#: part/views.py:872 msgid "Confirm duplication of BOM from parent" msgstr "" -#: part/views.py:887 +#: part/views.py:890 msgid "Validate BOM" msgstr "" -#: part/views.py:1054 +#: part/views.py:1057 msgid "No BOM file provided" msgstr "" -#: part/views.py:1404 +#: part/views.py:1407 msgid "Enter a valid quantity" msgstr "" -#: part/views.py:1429 part/views.py:1432 +#: part/views.py:1432 part/views.py:1435 msgid "Select valid part" msgstr "" -#: part/views.py:1438 +#: part/views.py:1441 msgid "Duplicate part selected" msgstr "" -#: part/views.py:1476 +#: part/views.py:1479 msgid "Select a part" msgstr "" -#: part/views.py:1482 +#: part/views.py:1485 msgid "Selected part creates a circular BOM" msgstr "" -#: part/views.py:1486 +#: part/views.py:1489 msgid "Specify quantity" msgstr "" -#: part/views.py:1742 +#: part/views.py:1745 msgid "Confirm Part Deletion" msgstr "" -#: part/views.py:1751 +#: part/views.py:1754 msgid "Part was deleted" msgstr "" -#: part/views.py:1760 +#: part/views.py:1763 msgid "Part Pricing" msgstr "" -#: part/views.py:1886 +#: part/views.py:1889 msgid "Create Part Parameter Template" msgstr "" -#: part/views.py:1896 +#: part/views.py:1899 msgid "Edit Part Parameter Template" msgstr "" -#: part/views.py:1905 +#: part/views.py:1908 msgid "Delete Part Parameter Template" msgstr "" -#: part/views.py:1915 +#: part/views.py:1918 msgid "Create Part Parameter" msgstr "" -#: part/views.py:1967 +#: part/views.py:1970 msgid "Edit Part Parameter" msgstr "" -#: part/views.py:1983 +#: part/views.py:1986 msgid "Delete Part Parameter" msgstr "" -#: part/views.py:2042 +#: part/views.py:2045 msgid "Edit Part Category" msgstr "" -#: part/views.py:2079 +#: part/views.py:2082 msgid "Delete Part Category" msgstr "" -#: part/views.py:2087 +#: part/views.py:2090 msgid "Part category was deleted" msgstr "" -#: part/views.py:2150 +#: part/views.py:2153 msgid "Create BOM Item" msgstr "" -#: part/views.py:2218 +#: part/views.py:2221 msgid "Edit BOM item" msgstr "" -#: part/views.py:2268 +#: part/views.py:2271 msgid "Confim BOM item deletion" msgstr "" @@ -4360,63 +4360,64 @@ msgstr "" msgid "Barcode does not match Stock Item" msgstr "" -#: templates/js/bom.js:133 templates/js/part.js:117 templates/js/part.js:344 +#: templates/js/bom.js:156 templates/js/part.js:117 templates/js/part.js:344 msgid "Trackable part" msgstr "" -#: templates/js/bom.js:137 templates/js/part.js:121 templates/js/part.js:348 +#: templates/js/bom.js:160 templates/js/part.js:121 templates/js/part.js:348 msgid "Virtual part" msgstr "" -#: templates/js/bom.js:141 -msgid "Templat part" +#: templates/js/bom.js:164 templates/js/company.js:147 templates/js/part.js:125 +#: templates/js/part.js:353 +msgid "Template part" msgstr "" -#: templates/js/bom.js:146 +#: templates/js/bom.js:169 msgid "Open subassembly" msgstr "" -#: templates/js/bom.js:191 +#: templates/js/bom.js:214 msgid "Optional" msgstr "" -#: templates/js/bom.js:206 templates/js/build.js:133 +#: templates/js/bom.js:229 templates/js/build.js:133 msgid "Available" msgstr "" -#: templates/js/bom.js:231 +#: templates/js/bom.js:254 msgid "No pricing available" msgstr "" -#: templates/js/bom.js:250 +#: templates/js/bom.js:273 msgid "Actions" msgstr "" -#: templates/js/bom.js:258 +#: templates/js/bom.js:281 msgid "Validate BOM Item" msgstr "" -#: templates/js/bom.js:260 +#: templates/js/bom.js:283 msgid "This line has been validated" msgstr "" -#: templates/js/bom.js:262 +#: templates/js/bom.js:285 msgid "Edit BOM Item" msgstr "" -#: templates/js/bom.js:264 +#: templates/js/bom.js:287 msgid "Delete BOM Item" msgstr "" -#: templates/js/bom.js:496 +#: templates/js/bom.js:507 msgid "INACTIVE" msgstr "" -#: templates/js/bom.js:510 +#: templates/js/bom.js:521 msgid "Uses" msgstr "" -#: templates/js/bom.js:521 +#: templates/js/bom.js:532 msgid "No matching parts found" msgstr "" @@ -4444,11 +4445,6 @@ msgstr "" msgid "No supplier parts found" msgstr "" -#: templates/js/company.js:147 templates/js/part.js:125 -#: templates/js/part.js:353 -msgid "Template part" -msgstr "" - #: templates/js/company.js:151 templates/js/part.js:129 #: templates/js/part.js:357 msgid "Assembled part" diff --git a/InvenTree/templates/js/bom.js b/InvenTree/templates/js/bom.js index c8da09da43..35002ad05f 100644 --- a/InvenTree/templates/js/bom.js +++ b/InvenTree/templates/js/bom.js @@ -161,7 +161,7 @@ function loadBomTable(table, options) { } if (sub_part.is_template) { - html += makeIconBadge('fa-clone', '{% trans "Templat part" %}'); + html += makeIconBadge('fa-clone', '{% trans "Template part" %}'); } // Display an extra icon if this part is an assembly From c533f59405d8e2d23eaec8ec4a6bbcfa0db48791 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 28 Oct 2020 23:33:33 +1100 Subject: [PATCH 5/7] Refactor how form errors are handled - Use form.add_error (as the django gods intended) --- InvenTree/InvenTree/views.py | 14 +++++++------- InvenTree/build/views.py | 28 +++++++++++----------------- InvenTree/order/views.py | 18 +++++++++--------- InvenTree/part/models.py | 19 ++++++++++++++++++- InvenTree/part/views.py | 17 +++++++++-------- InvenTree/stock/models.py | 9 +++------ InvenTree/stock/tests.py | 7 +++++++ InvenTree/stock/views.py | 33 ++++++++++++++++----------------- 8 files changed, 80 insertions(+), 65 deletions(-) diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index 0cb228836f..43ec16c795 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -485,7 +485,7 @@ class AjaxDeleteView(AjaxMixin, UpdateView): """ form_class = DeleteForm - ajax_form_title = "Delete Item" + ajax_form_title = _("Delete Item") ajax_template_name = "modal_delete_form.html" context_object_name = 'item' @@ -534,7 +534,7 @@ class AjaxDeleteView(AjaxMixin, UpdateView): if confirmed: obj.delete() else: - form.errors['confirm_delete'] = ['Check box to confirm item deletion'] + form.add_error('confirm_delete', _('Check box to confirm item deletion')) context[self.context_object_name] = self.get_object() data = { @@ -549,7 +549,7 @@ class EditUserView(AjaxUpdateView): """ View for editing user information """ ajax_template_name = "modal_form.html" - ajax_form_title = "Edit User Information" + ajax_form_title = _("Edit User Information") form_class = EditUserForm def get_object(self): @@ -560,7 +560,7 @@ class SetPasswordView(AjaxUpdateView): """ View for setting user password """ ajax_template_name = "InvenTree/password.html" - ajax_form_title = "Set Password" + ajax_form_title = _("Set Password") form_class = SetPasswordForm def get_object(self): @@ -579,9 +579,9 @@ class SetPasswordView(AjaxUpdateView): # Passwords must match if not p1 == p2: - error = 'Password fields must match' - form.errors['enter_password'] = [error] - form.errors['confirm_password'] = [error] + error = _('Password fields must match') + form.add_error('enter_password', error) + form.add_error('confirm_password', error) valid = False diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py index 1296e42fae..dd6ad1a575 100644 --- a/InvenTree/build/views.py +++ b/InvenTree/build/views.py @@ -74,7 +74,7 @@ class BuildCancel(AjaxUpdateView): if confirm: build.cancelBuild(request.user) else: - form.errors['confirm_cancel'] = [_('Confirm build cancellation')] + form.add_error('confirm_cancel', _('Confirm build cancellation')) valid = False data = { @@ -128,8 +128,8 @@ class BuildAutoAllocate(AjaxUpdateView): valid = False if confirm is False: - form.errors['confirm'] = [_('Confirm stock allocation')] - form.non_field_errors = [_('Check the confirmation box at the bottom of the list')] + form.add_error('confirm', _('Confirm stock allocation')) + form.add_error(None, _('Check the confirmation box at the bottom of the list')) else: build.autoAllocate() valid = True @@ -163,8 +163,8 @@ class BuildUnallocate(AjaxUpdateView): valid = False if confirm is False: - form.errors['confirm'] = [_('Confirm unallocation of build stock')] - form.non_field_errors = [_('Check the confirmation box')] + form.add_error('confirm', _('Confirm unallocation of build stock')) + form.add_error(None, _('Check the confirmation box')) else: build.unallocateStock() valid = True @@ -266,15 +266,13 @@ class BuildComplete(AjaxUpdateView): valid = False if confirm is False: - form.errors['confirm'] = [ - _('Confirm completion of build'), - ] + form.add_error('confirm', _('Confirm completion of build')) else: try: location = StockLocation.objects.get(id=loc_id) valid = True except (ValueError, StockLocation.DoesNotExist): - form.errors['location'] = [_('Invalid location selected')] + form.add_error('location', _('Invalid location selected')) serials = [] @@ -291,24 +289,20 @@ class BuildComplete(AjaxUpdateView): # Exctract a list of provided serial numbers serials = ExtractSerialNumbers(sn, build.quantity) - existing = [] - - for serial in serials: - if build.part.checkIfSerialNumberExists(serial): - existing.append(serial) + existing = build.part.find_conflicting_serial_numbers(serials) if len(existing) > 0: exists = ",".join([str(x) for x in existing]) - form.errors['serial_numbers'] = [_('The following serial numbers already exist: ({sn})'.format(sn=exists))] + form.add_error('serial_numbers', _('The following serial numbers already exist: ({sn})'.format(sn=exists))) valid = False except ValidationError as e: - form.errors['serial_numbers'] = e.messages + form.add_error('serial_numbers', e.messages) valid = False if valid: if not build.completeBuild(location, serials, request.user): - form.non_field_errors = [('Build could not be completed')] + form.add_error(None, _('Build could not be completed')) valid = False data = { diff --git a/InvenTree/order/views.py b/InvenTree/order/views.py index 3a5bec3a7f..5d629df001 100644 --- a/InvenTree/order/views.py +++ b/InvenTree/order/views.py @@ -415,7 +415,7 @@ class PurchaseOrderCancel(AjaxUpdateView): valid = False if not confirm: - form.errors['confirm'] = [_('Confirm order cancellation')] + form.add_error('confirm', _('Confirm order cancellation')) else: valid = True @@ -448,13 +448,13 @@ class SalesOrderCancel(AjaxUpdateView): valid = False if not confirm: - form.errors['confirm'] = [_('Confirm order cancellation')] + form.add_error('confirm', _('Confirm order cancellation')) else: valid = True if valid: if not order.cancel_order(): - form.non_field_errors = [_('Could not cancel order')] + form.add_error(None, _('Could not cancel order')) valid = False data = { @@ -484,7 +484,7 @@ class PurchaseOrderIssue(AjaxUpdateView): valid = False if not confirm: - form.errors['confirm'] = [_('Confirm order placement')] + form.add_error('confirm', _('Confirm order placement')) else: valid = True @@ -558,13 +558,13 @@ class SalesOrderShip(AjaxUpdateView): valid = False if not confirm: - form.errors['confirm'] = [_('Confirm order shipment')] + form.add_error('confirm', _('Confirm order shipment')) else: valid = True if valid: if not order.ship_order(request.user): - form.non_field_errors = [_('Could not ship order')] + form.add_error(None, _('Could not ship order')) valid = False data = { @@ -1135,7 +1135,7 @@ class POLineItemCreate(AjaxCreateView): order = PurchaseOrder.objects.get(id=order_id) except (ValueError, PurchaseOrder.DoesNotExist): order = None - form.errors['order'] = [_('Invalid Purchase Order')] + form.add_error('order', _('Invalid Purchase Order')) valid = False try: @@ -1143,12 +1143,12 @@ class POLineItemCreate(AjaxCreateView): if order is not None: if not sp.supplier == order.supplier: - form.errors['part'] = [_('Supplier must match for Part and Order')] + form.add_error('part', _('Supplier must match for Part and Order')) valid = False except (SupplierPart.DoesNotExist, ValueError): valid = False - form.errors['part'] = [_('Invalid SupplierPart selection')] + form.add_error('part', _('Invalid SupplierPart selection')) data = { 'form_valid': valid, diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index a579547fba..beda7a0577 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -360,7 +360,7 @@ class Part(MPTTModel): # And recursively check too item.sub_part.checkAddToBOM(parent) - def checkIfSerialNumberExists(self, sn): + def checkIfSerialNumberExists(self, sn, exclude_self=False): """ Check if a serial number exists for this Part. @@ -369,10 +369,27 @@ class Part(MPTTModel): """ parts = Part.objects.filter(tree_id=self.tree_id) + stock = StockModels.StockItem.objects.filter(part__in=parts, serial=sn) + if exclude_self: + stock = stock.exclude(pk=self.pk) + return stock.exists() + def find_conflicting_serial_numbers(self, serials): + """ + For a provided list of serials, return a list of those which are conflicting. + """ + + conflicts = [] + + for serial in serials: + if self.checkIfSerialNumberExists(serial, exclude_self=True): + conflicts.append(serial) + + return conflicts + def getLatestSerialNumber(self): """ Return the "latest" serial number for this Part. diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 57a274b5bf..2347020c55 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -446,9 +446,9 @@ class PartDuplicate(AjaxCreateView): confirmed = str2bool(request.POST.get('confirm_creation', False)) if not confirmed: - form.errors['confirm_creation'] = ['Possible matches exist - confirm creation of new part'] - - form.pre_form_warning = 'Possible matches exist - confirm creation of new part' + msg = _('Possible matches exist - confirm creation of new part') + form.add_error('confirm_creation', msg) + form.pre_form_warning = msg valid = False data = { @@ -576,9 +576,10 @@ class PartCreate(AjaxCreateView): confirmed = str2bool(request.POST.get('confirm_creation', False)) if not confirmed: - form.errors['confirm_creation'] = ['Possible matches exist - confirm creation of new part'] + msg = _('Possible matches exist - confirm creation of new part') + form.add_error('confirm_creation', msg) - form.pre_form_warning = 'Possible matches exist - confirm creation of new part' + form.pre_form_warning = msg valid = False data = { @@ -915,7 +916,7 @@ class BomValidate(AjaxUpdateView): if confirmed: part.validate_bom(request.user) else: - form.errors['validate'] = ['Confirm that the BOM is valid'] + form.add_error('validate', _('Confirm that the BOM is valid')) data = { 'form_valid': confirmed @@ -1054,7 +1055,7 @@ class BomUpload(InvenTreeRoleMixin, FormView): bom_file_valid = False if bom_file is None: - self.form.errors['bom_file'] = [_('No BOM file provided')] + self.form.add_error('bom_file', _('No BOM file provided')) else: # Create a BomUploadManager object - will perform initial data validation # (and raise a ValidationError if there is something wrong with the file) @@ -1065,7 +1066,7 @@ class BomUpload(InvenTreeRoleMixin, FormView): errors = e.error_dict for k, v in errors.items(): - self.form.errors[k] = v + self.form.add_error(k, v) if bom_file_valid: # BOM file is valid? Proceed to the next step! diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 1535ded420..e4c6d67cb2 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -814,14 +814,11 @@ class StockItem(MPTTModel): raise ValidationError({"quantity": _("Quantity does not match serial numbers")}) # Test if each of the serial numbers are valid - existing = [] - - for serial in serials: - if self.part.checkIfSerialNumberExists(serial): - existing.append(serial) + existing = self.part.find_conflicting_serial_numbers(serials) if len(existing) > 0: - raise ValidationError({"serial_numbers": _("Serial numbers already exist: ") + str(existing)}) + exists = ','.join([str(x) for x in existing]) + raise ValidationError({"serial_numbers": _("Serial numbers already exist") + ': ' + exists}) # Create a new stock item for each unique serial number for serial in serials: diff --git a/InvenTree/stock/tests.py b/InvenTree/stock/tests.py index 34fe8877f8..4632505831 100644 --- a/InvenTree/stock/tests.py +++ b/InvenTree/stock/tests.py @@ -407,6 +407,13 @@ class VariantTest(StockTest): self.assertEqual(chair.getLatestSerialNumber(), '22') + # Check for conflicting serial numbers + to_check = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + + conflicts = chair.find_conflicting_serial_numbers(to_check) + + self.assertEqual(len(conflicts), 6) + # Same operations on a sub-item variant = Part.objects.get(pk=10003) self.assertEqual(variant.getLatestSerialNumber(), '22') diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 45c862da83..09c2d4b159 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -417,8 +417,8 @@ class StockItemDeleteTestData(AjaxUpdateView): confirm = str2bool(request.POST.get('confirm', False)) if confirm is not True: - form.errors['confirm'] = [_('Confirm test data deletion')] - form.non_field_errors = [_('Check the confirmation box')] + form.add_error('confirm', _('Confirm test data deletion')) + form.add_error(None, _('Check the confirmation box')) else: stock_item.test_results.all().delete() valid = True @@ -918,7 +918,7 @@ class StockItemUninstall(AjaxView, FormMixin): if not confirmed: valid = False - form.errors['confirm'] = [_('Confirm stock adjustment')] + form.add_error('confirm', _('Confirm stock adjustment')) data = { 'form_valid': valid, @@ -1116,7 +1116,7 @@ class StockAdjust(AjaxView, FormMixin): if not confirmed: valid = False - form.errors['confirm'] = [_('Confirm stock adjustment')] + form.add_error('confirm', _('Confirm stock adjustment')) data = { 'form_valid': valid, @@ -1416,7 +1416,7 @@ class StockItemSerialize(AjaxUpdateView): try: numbers = ExtractSerialNumbers(serials, quantity) except ValidationError as e: - form.errors['serial_numbers'] = e.messages + form.add_error('serial_numbers', e.messages) valid = False numbers = [] @@ -1428,9 +1428,9 @@ class StockItemSerialize(AjaxUpdateView): for k in messages.keys(): if k in ['quantity', 'destination', 'serial_numbers']: - form.errors[k] = messages[k] + form.add_error(k, messages[k]) else: - form.non_field_errors = [messages[k]] + form.add_error(None, messages[k]) valid = False @@ -1622,14 +1622,14 @@ class StockItemCreate(AjaxCreateView): part = None quantity = 1 valid = False - form.errors['quantity'] = [_('Invalid quantity')] + form.add_error('quantity', _('Invalid quantity')) if quantity < 0: - form.errors['quantity'] = [_('Quantity cannot be less than zero')] + form.add_error('quantity', _('Quantity cannot be less than zero')) valid = False if part is None: - form.errors['part'] = [_('Invalid part selection')] + form.add_error('part', _('Invalid part selection')) else: # A trackable part must provide serial numbesr if part.trackable: @@ -1642,15 +1642,14 @@ class StockItemCreate(AjaxCreateView): try: serials = ExtractSerialNumbers(sn, quantity) - existing = [] - - for serial in serials: - if part.checkIfSerialNumberExists(serial): - existing.append(serial) + existing = part.find_conflicting_serial_numbers(serials) if len(existing) > 0: exists = ",".join([str(x) for x in existing]) - form.errors['serial_numbers'] = [_('The following serial numbers already exist: ({sn})'.format(sn=exists))] + form.add_error( + 'serial_numbers', + _('Serial numbers already exist') + ': ' + exists + ) valid = False else: @@ -1682,7 +1681,7 @@ class StockItemCreate(AjaxCreateView): valid = True except ValidationError as e: - form.errors['serial_numbers'] = e.messages + form.add_error('serial_numbers', e.messages) valid = False else: From e049ca1a8521c70ec5d1ed4f76ae3fe92f1fe944 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 30 Oct 2020 16:54:05 +1100 Subject: [PATCH 6/7] More refactoring --- InvenTree/InvenTree/views.py | 34 +++++++- InvenTree/build/views.py | 29 +++---- InvenTree/order/models.py | 22 ++++- InvenTree/order/views.py | 150 ++++++++++------------------------- InvenTree/part/models.py | 36 ++++++--- InvenTree/part/views.py | 24 +++--- InvenTree/stock/models.py | 14 ++++ InvenTree/stock/views.py | 63 +++++++-------- 8 files changed, 188 insertions(+), 184 deletions(-) diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index 43ec16c795..57f80f1be7 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -362,6 +362,17 @@ class AjaxCreateView(AjaxMixin, CreateView): form = self.get_form() return self.renderJsonResponse(request, form) + def do_save(self, form): + """ + Method for actually saving the form to the database. + Default implementation is very simple, + but can be overridden if required. + """ + + self.object = form.save() + + return self.object + def post(self, request, *args, **kwargs): """ Responds to form POST. Validates POST data and returns status info. @@ -385,13 +396,17 @@ class AjaxCreateView(AjaxMixin, CreateView): 'form_valid': valid } + # Add in any extra class data + for value, key in enumerate(self.get_data()): + data[key] = value + if valid: # Perform (optional) pre-save step self.pre_save(None, self.form) # Save the object to the database - self.object = self.form.save() + self.do_save(self.form) # Perform (optional) post-save step self.post_save(self.object, self.form) @@ -425,6 +440,17 @@ class AjaxUpdateView(AjaxMixin, UpdateView): return self.renderJsonResponse(request, self.get_form(), context=self.get_context_data()) + def do_save(self, form): + """ + Method for updating the object in the database. + Default implementation is very simple, + but can be overridden if required. + """ + + self.object = form.save() + + return self.object + def post(self, request, *args, **kwargs): """ Respond to POST request. @@ -453,13 +479,17 @@ class AjaxUpdateView(AjaxMixin, UpdateView): 'form_valid': valid } + # Add in any extra class data + for value, key in enumerate(self.get_data()): + data[key] = value + if valid: # Perform (optional) pre-save step self.pre_save(self.object, form) # Save the updated objec to the database - obj = form.save() + obj = self.do_save(form) # Perform (optional) post-save step self.post_save(obj, form) diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py index dd6ad1a575..1a2bd0d4b7 100644 --- a/InvenTree/build/views.py +++ b/InvenTree/build/views.py @@ -60,30 +60,25 @@ class BuildCancel(AjaxUpdateView): form_class = forms.CancelBuildForm role_required = 'build.change' - def post(self, request, *args, **kwargs): - """ Handle POST request. Mark the build status as CANCELLED """ + def validate(self, build, form, **kwargs): - build = self.get_object() + confirm = str2bool(form.cleaned_data.get('confirm_cancel', False)) - form = self.get_form() - - valid = form.is_valid() - - confirm = str2bool(request.POST.get('confirm_cancel', False)) - - if confirm: - build.cancelBuild(request.user) - else: + if not confirm: form.add_error('confirm_cancel', _('Confirm build cancellation')) - valid = False - data = { - 'form_valid': valid, + def post_save(self, build, form, **kwargs): + """ + Cancel the build. + """ + + build.cancelBuild(self.request.user) + + def get_data(self): + return { 'danger': _('Build was cancelled') } - return self.renderJsonResponse(request, form, data=data) - class BuildAutoAllocate(AjaxUpdateView): """ View to auto-allocate parts for a build. diff --git a/InvenTree/order/models.py b/InvenTree/order/models.py index 7d561b95ba..b193454e1f 100644 --- a/InvenTree/order/models.py +++ b/InvenTree/order/models.py @@ -209,6 +209,7 @@ class PurchaseOrder(Order): line.save() + @transaction.atomic def place_order(self): """ Marks the PurchaseOrder as PLACED. Order must be currently PENDING. """ @@ -217,6 +218,7 @@ class PurchaseOrder(Order): self.issue_date = datetime.now().date() self.save() + @transaction.atomic def complete_order(self): """ Marks the PurchaseOrder as COMPLETE. Order must be currently PLACED. """ @@ -225,10 +227,16 @@ class PurchaseOrder(Order): self.complete_date = datetime.now().date() self.save() + def can_cancel(self): + return self.status not in [ + PurchaseOrderStatus.PLACED, + PurchaseOrderStatus.PENDING + ] + def cancel_order(self): """ Marks the PurchaseOrder as CANCELLED. """ - if self.status in [PurchaseOrderStatus.PLACED, PurchaseOrderStatus.PENDING]: + if self.can_cancel(): self.status = PurchaseOrderStatus.CANCELLED self.save() @@ -377,6 +385,16 @@ class SalesOrder(Order): return True + def can_cancel(self): + """ + Return True if this order can be cancelled + """ + + if not self.status == SalesOrderStatus.PENDING: + return False + + return True + @transaction.atomic def cancel_order(self): """ @@ -386,7 +404,7 @@ class SalesOrder(Order): - Delete any StockItems which have been allocated """ - if not self.status == SalesOrderStatus.PENDING: + if not self.can_cancel(): return False self.status = SalesOrderStatus.CANCELLED diff --git a/InvenTree/order/views.py b/InvenTree/order/views.py index 5d629df001..333509952b 100644 --- a/InvenTree/order/views.py +++ b/InvenTree/order/views.py @@ -404,29 +404,19 @@ class PurchaseOrderCancel(AjaxUpdateView): form_class = order_forms.CancelPurchaseOrderForm role_required = 'purchase_order.change' - def post(self, request, *args, **kwargs): - """ Mark the PO as 'CANCELLED' """ - - order = self.get_object() - form = self.get_form() - - confirm = str2bool(request.POST.get('confirm', False)) - - valid = False + def validate(self, order, form, **kwargs): + + confirm = str2bool(form.cleaned_data.get('confirm', False)) if not confirm: form.add_error('confirm', _('Confirm order cancellation')) - else: - valid = True - data = { - 'form_valid': valid - } + if not order.can_cancel(): + form.add_error(None, _('Order cannot be cancelled')) - if valid: - order.cancel_order() + def post_save(self, order, form, **kwargs): - return self.renderJsonResponse(request, form, data) + order.cancel_order() class SalesOrderCancel(AjaxUpdateView): @@ -438,30 +428,19 @@ class SalesOrderCancel(AjaxUpdateView): form_class = order_forms.CancelSalesOrderForm role_required = 'sales_order.change' - def post(self, request, *args, **kwargs): + def validate(self, order, form, **kwargs): - order = self.get_object() - form = self.get_form() - - confirm = str2bool(request.POST.get('confirm', False)) - - valid = False + confirm = str2bool(form.cleaned_data.get('confirm', False)) if not confirm: form.add_error('confirm', _('Confirm order cancellation')) - else: - valid = True - if valid: - if not order.cancel_order(): - form.add_error(None, _('Could not cancel order')) - valid = False + if not order.can_cancel(): + form.add_error(None, _('Order cannot be cancelled')) - data = { - 'form_valid': valid, - } + def post_save(self, order, form, **kwargs): - return self.renderJsonResponse(request, form, data) + order.cancel_order() class PurchaseOrderIssue(AjaxUpdateView): @@ -473,30 +452,22 @@ class PurchaseOrderIssue(AjaxUpdateView): form_class = order_forms.IssuePurchaseOrderForm role_required = 'purchase_order.change' - def post(self, request, *args, **kwargs): - """ Mark the purchase order as 'PLACED' """ + def validate(self, order, form, **kwargs): - order = self.get_object() - form = self.get_form() - - confirm = str2bool(request.POST.get('confirm', False)) - - valid = False + confirm = str2bool(form.cleaned_data.get('confirm', False)) if not confirm: form.add_error('confirm', _('Confirm order placement')) - else: - valid = True - data = { - 'form_valid': valid, + def post_save(self, order, form, **kwargs): + + order.place_order() + + def get_data(self): + return { + 'success': _('Purchase order issued') } - if valid: - order.place_order() - - return self.renderJsonResponse(request, form, data) - class PurchaseOrderComplete(AjaxUpdateView): """ View for marking a PurchaseOrder as complete. @@ -517,24 +488,22 @@ class PurchaseOrderComplete(AjaxUpdateView): return ctx - def post(self, request, *args, **kwargs): + def validate(self, order, form, **kwargs): - confirm = str2bool(request.POST.get('confirm', False)) + confirm = str2bool(form.cleaned_data.get('confirm', False)) - if confirm: - po = self.get_object() - po.status = PurchaseOrderStatus.COMPLETE - po.save() + if not confirm: + form.add_error('confirm', _('Confirm order completion')) - data = { - 'form_valid': confirm + def post_save(self, order, form, **kwargs): + + order.complete_order() + + def get_data(self): + return { + 'success': _('Purchase order completed') } - form = self.get_form() - - return self.renderJsonResponse(request, form, data) - - class SalesOrderShip(AjaxUpdateView): """ View for 'shipping' a SalesOrder """ form_class = order_forms.ShipSalesOrderForm @@ -1117,52 +1086,21 @@ class POLineItemCreate(AjaxCreateView): ajax_form_title = _('Add Line Item') role_required = 'purchase_order.add' - def post(self, request, *arg, **kwargs): + def validate(self, item, form, **kwargs): - self.request = request + order = form.cleaned_data.get('order', None) - form = self.get_form() + part = form.cleaned_data.get('part', None) - valid = form.is_valid() + if not part: + form.add_error('part', _('Supplier part must be specified')) - # Extract the SupplierPart ID from the form - part_id = form['part'].value() - - # Extract the Order ID from the form - order_id = form['order'].value() - - try: - order = PurchaseOrder.objects.get(id=order_id) - except (ValueError, PurchaseOrder.DoesNotExist): - order = None - form.add_error('order', _('Invalid Purchase Order')) - valid = False - - try: - sp = SupplierPart.objects.get(id=part_id) - - if order is not None: - if not sp.supplier == order.supplier: - form.add_error('part', _('Supplier must match for Part and Order')) - valid = False - - except (SupplierPart.DoesNotExist, ValueError): - valid = False - form.add_error('part', _('Invalid SupplierPart selection')) - - data = { - 'form_valid': valid, - } - - if valid: - self.object = form.save() - - data['pk'] = self.object.pk - data['text'] = str(self.object) - else: - self.object = None - - return self.renderJsonResponse(request, form, data,) + if part and order: + if not part.supplier == order.supplier: + form.add_error( + 'part', + _('Supplier must match for Part and Order') + ) def get_form(self): """ Limit choice options based on the selected order, etc diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index beda7a0577..34beb4b68e 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -1133,6 +1133,31 @@ class Part(MPTTModel): bom_item.save() + @transaction.atomic + def copy_parameters_from(self, other, **kwargs): + + clear = kwargs.get('clear', True) + + if clear: + self.get_parameters().delete() + + for parameter in other.get_parameters.all(): + + # If this part already has a parameter pointing to the same template, + # delete that parameter from this part first! + + try: + existing = PartParameter.objects.get(part=self, template=parameter.template) + existing.delete() + except (PartParameter.DoesNotExist): + pass + + parameter.part = self + parameter.pk = None + + parameter.save() + + @transaction.atomic def deepCopy(self, other, **kwargs): """ Duplicates non-field data from another part. Does not alter the normal fields of this part, @@ -1156,15 +1181,8 @@ class Part(MPTTModel): # Copy the parameters data if kwargs.get('parameters', True): - # Get template part parameters - parameters = other.get_parameters() - # Copy template part parameters to new variant part - for parameter in parameters: - PartParameter.create(part=self, - template=parameter.template, - data=parameter.data, - save=True) - + self.copy_parameters_from(other) + # Copy the fields that aren't available in the duplicate form self.salable = other.salable self.assembly = other.assembly diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 2347020c55..2dd3a47c3f 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -371,6 +371,8 @@ class MakePartVariant(AjaxCreateView): initials = model_to_dict(part_template) initials['is_template'] = False initials['variant_of'] = part_template + initials['bom_copy'] = InvenTreeSetting.get_setting('PART_COPY_BOM') + initials['parameters_copy'] = InvenTreeSetting.get_seting('PART_COPY_PARAMETERS') return initials @@ -906,23 +908,21 @@ class BomValidate(AjaxUpdateView): return self.renderJsonResponse(request, form, context=self.get_context()) - def post(self, request, *args, **kwargs): + def validate(self, part, form, **kwargs): - form = self.get_form() - part = self.get_object() + confirm = str2bool(form.cleaned_data.get('validate', False)) - confirmed = str2bool(request.POST.get('validate', False)) - - if confirmed: - part.validate_bom(request.user) - else: + if not confirm: form.add_error('validate', _('Confirm that the BOM is valid')) - data = { - 'form_valid': confirmed - } + def post_save(self, part, form, **kwargs): - return self.renderJsonResponse(request, form, data, context=self.get_context()) + part.validate_bom(self.request.user) + + def get_data(self): + return { + 'success': _('Validated Bill of Materials') + } class BomUpload(InvenTreeRoleMixin, FormView): diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index e4c6d67cb2..1a58592f26 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -1126,6 +1126,20 @@ class StockItem(MPTTModel): return s + @transaction.atomic + def clear_test_results(self, **kwargs): + """ + Remove all test results + """ + + # All test results + results = self.test_results.all() + + # TODO - Perhaps some filtering options supplied by kwargs? + + results.delete() + + def getTestResults(self, test=None, result=None, user=None): """ Return all test results associated with this StockItem. diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 09c2d4b159..aabf3ff7b9 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -245,32 +245,28 @@ class StockItemAssignToCustomer(AjaxUpdateView): form_class = StockForms.AssignStockItemToCustomerForm role_required = 'stock.change' - def post(self, request, *args, **kwargs): + def validate(self, item, form, **kwargs): - customer = request.POST.get('customer', None) + customer = form.cleaned_data.get('customer', None) + + if not customer: + form.add_error('customer', _('Customer must be specified')) + + def post_save(self, item, form, **kwargs): + """ + Assign the stock item to the customer. + """ + + customer = form.cleaned_data.get('customer', None) if customer: - try: - customer = Company.objects.get(pk=customer) - except (ValueError, Company.DoesNotExist): - customer = None - - if customer is not None: - stock_item = self.get_object() - - item = stock_item.allocateToCustomer( + item = item.allocateToCustomer( customer, - user=request.user + user=self.request.user ) item.clearAllocations() - data = { - 'form_valid': True, - } - - return self.renderJsonResponse(request, self.get_form(), data) - class StockItemReturnToStock(AjaxUpdateView): """ @@ -283,30 +279,25 @@ class StockItemReturnToStock(AjaxUpdateView): form_class = StockForms.ReturnStockItemForm role_required = 'stock.change' - def post(self, request, *args, **kwargs): + def validate(self, item, form, **kwargs): - location = request.POST.get('location', None) + location = form.cleaned_data.get('location', None) + + if not location: + form.add_error('location', _('Specify a valid location')) + + def post_save(self, item, form, **kwargs): + + location = form.cleaned_data.get('location', None) if location: - try: - location = StockLocation.objects.get(pk=location) - except (ValueError, StockLocation.DoesNotExist): - location = None + item.returnFromCustomer(location, self.request.user) - if location: - stock_item = self.get_object() - - stock_item.returnFromCustomer(location, request.user) - else: - raise ValidationError({'location': _("Specify a valid location")}) - - data = { - 'form_valid': True, - 'success': _("Stock item returned from customer") + def get_data(self): + return { + 'success': _('Stock item returned from customer') } - return self.renderJsonResponse(request, self.get_form(), data) - class StockItemSelectLabels(AjaxView): """ From 1caa341f8e588759f4375ded9e244da96ccbc95e Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 30 Oct 2020 21:34:56 +1100 Subject: [PATCH 7/7] Fixes for unit tests --- InvenTree/order/forms.py | 10 +++++----- InvenTree/order/test_views.py | 4 +--- InvenTree/order/views.py | 3 ++- InvenTree/part/models.py | 2 +- InvenTree/part/views.py | 2 +- InvenTree/stock/models.py | 4 +++- 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/InvenTree/order/forms.py b/InvenTree/order/forms.py index 1f412271e0..770dd392bf 100644 --- a/InvenTree/order/forms.py +++ b/InvenTree/order/forms.py @@ -21,7 +21,7 @@ from .models import SalesOrderAllocation class IssuePurchaseOrderForm(HelperForm): - confirm = forms.BooleanField(required=False, help_text=_('Place order')) + confirm = forms.BooleanField(required=True, initial=False, help_text=_('Place order')) class Meta: model = PurchaseOrder @@ -32,7 +32,7 @@ class IssuePurchaseOrderForm(HelperForm): class CompletePurchaseOrderForm(HelperForm): - confirm = forms.BooleanField(required=False, help_text=_("Mark order as complete")) + confirm = forms.BooleanField(required=True, help_text=_("Mark order as complete")) class Meta: model = PurchaseOrder @@ -43,7 +43,7 @@ class CompletePurchaseOrderForm(HelperForm): class CancelPurchaseOrderForm(HelperForm): - confirm = forms.BooleanField(required=False, help_text=_('Cancel order')) + confirm = forms.BooleanField(required=True, help_text=_('Cancel order')) class Meta: model = PurchaseOrder @@ -54,7 +54,7 @@ class CancelPurchaseOrderForm(HelperForm): class CancelSalesOrderForm(HelperForm): - confirm = forms.BooleanField(required=False, help_text=_('Cancel order')) + confirm = forms.BooleanField(required=True, help_text=_('Cancel order')) class Meta: model = SalesOrder @@ -65,7 +65,7 @@ class CancelSalesOrderForm(HelperForm): class ShipSalesOrderForm(HelperForm): - confirm = forms.BooleanField(required=False, help_text=_('Ship order')) + confirm = forms.BooleanField(required=True, help_text=_('Ship order')) class Meta: model = SalesOrder diff --git a/InvenTree/order/test_views.py b/InvenTree/order/test_views.py index 246cc2dd48..5c4e2dd405 100644 --- a/InvenTree/order/test_views.py +++ b/InvenTree/order/test_views.py @@ -113,6 +113,7 @@ class POTests(OrderViewTestCase): self.assertEqual(response.status_code, 200) data = json.loads(response.content) + self.assertFalse(data['form_valid']) # Test WITH confirmation @@ -151,7 +152,6 @@ class POTests(OrderViewTestCase): response = self.client.post(url, post_data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') data = json.loads(response.content) self.assertFalse(data['form_valid']) - self.assertIn('Invalid Purchase Order', str(data['html_form'])) # POST with a part that does not match the purchase order post_data['order'] = 1 @@ -159,14 +159,12 @@ class POTests(OrderViewTestCase): response = self.client.post(url, post_data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') data = json.loads(response.content) self.assertFalse(data['form_valid']) - self.assertIn('must match for Part and Order', str(data['html_form'])) # POST with an invalid part post_data['part'] = 12345 response = self.client.post(url, post_data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') data = json.loads(response.content) self.assertFalse(data['form_valid']) - self.assertIn('Invalid SupplierPart selection', str(data['html_form'])) # POST the form with valid data post_data['part'] = 100 diff --git a/InvenTree/order/views.py b/InvenTree/order/views.py index 333509952b..e411afbdad 100644 --- a/InvenTree/order/views.py +++ b/InvenTree/order/views.py @@ -454,7 +454,7 @@ class PurchaseOrderIssue(AjaxUpdateView): def validate(self, order, form, **kwargs): - confirm = str2bool(form.cleaned_data.get('confirm', False)) + confirm = str2bool(self.request.POST.get('confirm', False)) if not confirm: form.add_error('confirm', _('Confirm order placement')) @@ -504,6 +504,7 @@ class PurchaseOrderComplete(AjaxUpdateView): 'success': _('Purchase order completed') } + class SalesOrderShip(AjaxUpdateView): """ View for 'shipping' a SalesOrder """ form_class = order_forms.ShipSalesOrderForm diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 34beb4b68e..715d1423eb 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -1141,7 +1141,7 @@ class Part(MPTTModel): if clear: self.get_parameters().delete() - for parameter in other.get_parameters.all(): + for parameter in other.get_parameters(): # If this part already has a parameter pointing to the same template, # delete that parameter from this part first! diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 2dd3a47c3f..0b96addc84 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -372,7 +372,7 @@ class MakePartVariant(AjaxCreateView): initials['is_template'] = False initials['variant_of'] = part_template initials['bom_copy'] = InvenTreeSetting.get_setting('PART_COPY_BOM') - initials['parameters_copy'] = InvenTreeSetting.get_seting('PART_COPY_PARAMETERS') + initials['parameters_copy'] = InvenTreeSetting.get_setting('PART_COPY_PARAMETERS') return initials diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 1a58592f26..11ec0bd087 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -1130,6 +1130,9 @@ class StockItem(MPTTModel): def clear_test_results(self, **kwargs): """ Remove all test results + + kwargs: + TODO """ # All test results @@ -1139,7 +1142,6 @@ class StockItem(MPTTModel): results.delete() - def getTestResults(self, test=None, result=None, user=None): """ Return all test results associated with this StockItem.