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")