diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index 08d9084cfc..57f80f1be7 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,32 +353,6 @@ class AjaxCreateView(AjaxMixin, CreateView): - Handles form validation via AJAX POST requests """ - def validate(self, request, form, cleaned_data, **kwargs): - """ - Hook for performing any extra validation, over and above the regular form.is_valid - - If any errors exist, add them to the form, using form.add_error - - """ - pass - - def pre_save(self, form, request, **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 - - kwargs: - request - The request object - new_object - The newly created object - - """ - pass - def get(self, request, *args, **kwargs): """ Creates form with initial data, and renders JSON response """ @@ -355,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. @@ -365,13 +383,12 @@ class AjaxCreateView(AjaxMixin, CreateView): self.request = request self.form = self.get_form() - # Perform regular form validation - valid = self.form.is_valid() + # Perform initial form validation + self.form.is_valid() + + # Perform custom validation (no object can be provided yet) + self.validate(None, self.form) - # Perform any extra validation steps - self.validate(request, self.form, self.form.cleaned_data) - - # Check if form is valid again (after performing any custom validation) valid = self.form.is_valid() # Extra JSON data sent alongside form @@ -379,11 +396,20 @@ 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: - self.pre_save(self.form, request) - self.object = self.form.save() - self.post_save(new_object=self.object, request=request, form=self.form, data=self.form.cleaned_data) + # Perform (optional) pre-save step + self.pre_save(None, self.form) + + # Save the object to the database + self.do_save(self.form) + + # 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 @@ -414,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. @@ -423,22 +460,44 @@ 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(): - obj = form.save() + # 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 = self.do_save(form) + + # Perform (optional) post-save step + self.post_save(obj, form) # Include context data about the updated object - data['pk'] = obj.id + data['pk'] = obj.pk - self.post_save(new_object=obj, request=request) + self.post_save(obj, form) try: data['url'] = obj.get_absolute_url() @@ -447,13 +506,6 @@ class AjaxUpdateView(AjaxMixin, UpdateView): return self.renderJsonResponse(request, form, data) - def post_save(self, **kwargs): - """ - Hook called after the form data is saved. - (Optional) - """ - pass - class AjaxDeleteView(AjaxMixin, UpdateView): diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py index c46d4e0cf3..57df745d6d 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/locale/de/LC_MESSAGES/django.mo b/InvenTree/locale/de/LC_MESSAGES/django.mo index 29d7859bec..0daf5476b6 100644 Binary files a/InvenTree/locale/de/LC_MESSAGES/django.mo and b/InvenTree/locale/de/LC_MESSAGES/django.mo differ diff --git a/InvenTree/locale/de/LC_MESSAGES/django.po b/InvenTree/locale/de/LC_MESSAGES/django.po index 187ba51fef..76eb524a83 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 04:20+0000\n" +"POT-Creation-Date: 2020-10-30 11:43+0000\n" "PO-Revision-Date: 2020-05-03 11:32+0200\n" "Last-Translator: Christian Schlüter \n" "Language-Team: C \n" @@ -49,7 +49,7 @@ msgstr "" msgid "Apply Theme" msgstr "" -#: InvenTree/helpers.py:361 order/models.py:187 order/models.py:261 +#: InvenTree/helpers.py:361 order/models.py:187 order/models.py:269 msgid "Invalid quantity provided" msgstr "Keine gültige Menge" @@ -214,35 +214,35 @@ 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:466 +#: InvenTree/views.py:518 #, fuzzy #| msgid "Delete BOM Item" msgid "Delete Item" msgstr "BOM-Position löschen" -#: InvenTree/views.py:515 +#: InvenTree/views.py:567 #, fuzzy #| msgid "Confim BOM item deletion" msgid "Check box to confirm item deletion" msgstr "Löschung von BOM-Position bestätigen" -#: InvenTree/views.py:530 templates/InvenTree/settings/user.html:18 +#: InvenTree/views.py:582 templates/InvenTree/settings/user.html:18 #, fuzzy #| msgid "No user information" msgid "Edit User Information" msgstr "Keine Benutzerinformation" -#: InvenTree/views.py:541 templates/InvenTree/settings/user.html:22 +#: InvenTree/views.py:593 templates/InvenTree/settings/user.html:22 #, fuzzy #| msgid "Select part" msgid "Set Password" msgstr "Teil auswählen" -#: InvenTree/views.py:560 +#: InvenTree/views.py:612 msgid "Password fields must match" msgstr "" -#: InvenTree/views.py:730 +#: InvenTree/views.py:782 msgid "Database Statistics" msgstr "Datenbankstatistiken" @@ -340,7 +340,7 @@ msgstr "Eindeutige Seriennummern eingeben (oder leer lassen)" msgid "Confirm build completion" msgstr "Bau-Fertigstellung bestätigen" -#: build/forms.py:159 build/views.py:77 +#: build/forms.py:159 build/views.py:68 msgid "Confirm build cancellation" msgstr "Bauabbruch bestätigen" @@ -408,7 +408,7 @@ msgstr "Bestellung, die diesem Bau zugwiesen ist" #: build/models.py:98 build/templates/build/allocate.html:366 #: build/templates/build/auto_allocate.html:25 #: build/templates/build/build_base.html:73 -#: build/templates/build/detail.html:24 order/models.py:501 +#: build/templates/build/detail.html:24 order/models.py:519 #: order/templates/order/order_wizard/select_parts.html:30 #: order/templates/order/purchase_order_detail.html:148 #: order/templates/order/receive_parts.html:19 part/models.py:293 @@ -510,7 +510,7 @@ msgstr "Link zu einer externen URL" #: order/templates/order/purchase_order_detail.html:203 #: 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:461 -#: stock/models.py:1418 stock/templates/stock/tabs.html:26 +#: stock/models.py:1434 stock/templates/stock/tabs.html:26 #: templates/js/barcode.js:391 templates/js/bom.js:264 #: templates/js/stock.js:116 templates/js/stock.js:571 msgid "Notes" @@ -551,11 +551,11 @@ msgid "Allocated quantity ({n}) must not exceed available quantity ({q})" msgstr "" "zugewiesene Anzahl ({n}) darf nicht die verfügbare ({q}) Anzahl überschreiten" -#: build/models.py:803 order/models.py:585 +#: build/models.py:803 order/models.py:603 msgid "StockItem is over-allocated" msgstr "Zu viele Lagerobjekte zugewiesen" -#: build/models.py:807 order/models.py:588 +#: build/models.py:807 order/models.py:606 msgid "Allocation quantity must be greater than zero" msgstr "Anzahl muss größer null sein" @@ -610,7 +610,7 @@ msgid "Assigned Stock" msgstr "Zugewiesen" #: build/templates/build/allocate.html:28 -#: company/templates/company/detail_part.html:28 order/views.py:804 +#: company/templates/company/detail_part.html:28 order/views.py:774 #: part/templates/part/category.html:125 msgid "Order Parts" msgstr "Teile bestellen" @@ -632,7 +632,7 @@ msgstr "Zuweisung aufheben" msgid "New Stock Item" msgstr "Neues Lagerobjekt" -#: build/templates/build/allocate.html:125 stock/views.py:1459 +#: build/templates/build/allocate.html:125 stock/views.py:1450 #: templates/js/build.js:226 msgid "Create new Stock Item" msgstr "Neues Lagerobjekt hinzufügen" @@ -691,7 +691,7 @@ msgstr "Zuweisung löschen" msgid "No BOM items found" msgstr "Keine BOM-Einträge gefunden" -#: build/templates/build/allocate.html:384 part/models.py:1448 +#: build/templates/build/allocate.html:384 part/models.py:1490 #: templates/js/part.js:569 templates/js/table_filters.js:172 msgid "Required" msgstr "benötigt" @@ -803,7 +803,7 @@ msgstr "Admin" msgid "Edit Build" msgstr "Bau bearbeitet" -#: build/templates/build/build_base.html:50 build/views.py:300 +#: build/templates/build/build_base.html:50 build/views.py:295 msgid "Complete Build" msgstr "Bau fertigstellen" @@ -811,7 +811,7 @@ msgstr "Bau fertigstellen" msgid "Cancel Build" msgstr "Bau abbrechen" -#: build/templates/build/build_base.html:59 build/views.py:632 +#: build/templates/build/build_base.html:59 build/views.py:627 msgid "Delete Build" msgstr "Bau entfernt" @@ -835,7 +835,7 @@ msgid "Progress" msgstr "" #: build/templates/build/build_base.html:101 -#: build/templates/build/detail.html:82 order/models.py:499 +#: build/templates/build/detail.html:82 order/models.py:517 #: order/templates/order/sales_order_base.html:9 #: order/templates/order/sales_order_base.html:33 #: order/templates/order/sales_order_notes.html:10 @@ -984,7 +984,7 @@ msgstr "Fertig" msgid "Alter the quantity of stock allocated to the build output" msgstr "Lagerobjekt-Anzahl dem Bau zuweisen" -#: build/templates/build/index.html:25 build/views.py:507 +#: build/templates/build/index.html:25 build/views.py:502 msgid "New Build Order" msgstr "Neuer Bauauftrag" @@ -1028,153 +1028,153 @@ msgstr "" msgid "All incomplete stock allocations will be removed from the build" msgstr "Folgende Lagerobjekte werden dem Bau automatisch zugewiesen:" -#: build/views.py:82 +#: build/views.py:79 msgid "Build was cancelled" msgstr "Bau wurde abgebrochen" -#: build/views.py:98 +#: build/views.py:93 msgid "Allocate Stock" msgstr "Lagerbestand zuweisen" -#: build/views.py:126 +#: build/views.py:121 msgid "No matching build found" msgstr "Kein passender Bau gefunden" -#: build/views.py:157 +#: build/views.py:152 msgid "Confirm stock allocation" msgstr "Lagerbestandszuordnung bestätigen" -#: build/views.py:158 +#: build/views.py:153 msgid "Check the confirmation box at the bottom of the list" msgstr "Bestätigunsbox am Ende der Liste bestätigen" -#: build/views.py:176 templates/js/build.js:85 +#: build/views.py:171 templates/js/build.js:85 #, fuzzy #| msgid "Delete Build" msgid "Delete build output" msgstr "Bau entfernt" -#: build/views.py:210 +#: build/views.py:205 msgid "Build or output not specified" msgstr "" -#: build/views.py:212 build/views.py:276 +#: build/views.py:207 build/views.py:271 msgid "Confirm unallocation of build stock" msgstr "Zuweisungsaufhebung bestätigen" -#: build/views.py:213 build/views.py:277 stock/views.py:421 +#: build/views.py:208 build/views.py:272 stock/views.py:412 msgid "Check the confirmation box" msgstr "Bestätigungsbox bestätigen" -#: build/views.py:230 build/views.py:643 +#: build/views.py:225 build/views.py:638 msgid "Unallocate Stock" msgstr "Zuweisung aufheben" -#: build/views.py:379 +#: build/views.py:374 msgid "Confirm completion of build" msgstr "Baufertigstellung bestätigen" -#: build/views.py:385 +#: build/views.py:380 msgid "Invalid location selected" msgstr "Ungültige Ortsauswahl" -#: build/views.py:406 +#: build/views.py:401 #, python-brace-format msgid "The following serial numbers already exist: ({sn})" msgstr "Die folgende Seriennummer existiert bereits: ({sn})" -#: build/views.py:415 +#: build/views.py:410 #, fuzzy #| msgid "Build order allocation is complete" msgid "Build could not be completed" msgstr "Bau-Zuweisung ist vollständig" -#: build/views.py:427 +#: build/views.py:422 msgid "Build marked as COMPLETE" msgstr "Bau als FERTIG markiert" -#: build/views.py:556 +#: build/views.py:551 msgid "Created new build" msgstr "Neuen Bau angelegt" -#: build/views.py:573 +#: build/views.py:568 msgid "Trackable part must have serial numbers specified" msgstr "" -#: build/views.py:594 stock/models.py:838 stock/views.py:1650 +#: build/views.py:589 stock/models.py:838 stock/views.py:1641 #, fuzzy #| msgid "Serial numbers already exist: " msgid "Serial numbers already exist" msgstr "Seriennummern existieren bereits:" -#: build/views.py:617 +#: build/views.py:612 msgid "Edit Build Details" msgstr "Baudetails bearbeiten" -#: build/views.py:623 +#: build/views.py:618 msgid "Edited build" msgstr "Bau bearbeitet" -#: build/views.py:649 +#: build/views.py:644 msgid "Removed parts from build allocation" msgstr "Teile von Bauzuordnung entfernt" -#: build/views.py:661 +#: build/views.py:656 #, fuzzy #| msgid "Allocate Stock to Build" msgid "Allocate stock to build output" msgstr "Lagerbestand dem Bau zuweisen" -#: build/views.py:703 +#: build/views.py:698 #, fuzzy #| msgid "This stock item is allocated to Build" msgid "Item must be currently in stock" msgstr "Dieses Lagerobjekt ist dem Bau zugewiesen" -#: build/views.py:709 +#: build/views.py:704 #, fuzzy #| msgid "StockItem is over-allocated" msgid "Stock item is over-allocated" msgstr "Zu viele Lagerobjekte zugewiesen" -#: build/views.py:710 +#: build/views.py:705 #, fuzzy #| msgid "Available" msgid "Avaialabe" msgstr "verfügbar" -#: build/views.py:872 +#: build/views.py:867 msgid "Edit Stock Allocation" msgstr "Teilzuordnung bearbeiten" -#: build/views.py:877 +#: build/views.py:872 msgid "Updated Build Item" msgstr "Bauobjekt aktualisiert" -#: build/views.py:906 +#: build/views.py:901 #, fuzzy #| msgid "Add Sales Order Attachment" msgid "Add Build Order Attachment" msgstr "Auftragsanhang hinzufügen" -#: build/views.py:915 order/views.py:109 order/views.py:157 part/views.py:92 +#: build/views.py:910 order/views.py:109 order/views.py:157 part/views.py:92 #: stock/views.py:175 msgid "Added attachment" msgstr "Anhang hinzugefügt" -#: build/views.py:951 order/views.py:184 order/views.py:206 +#: build/views.py:946 order/views.py:184 order/views.py:206 msgid "Edit Attachment" msgstr "Anhang bearbeiten" -#: build/views.py:962 order/views.py:189 order/views.py:211 +#: build/views.py:957 order/views.py:189 order/views.py:211 msgid "Attachment updated" msgstr "Anhang aktualisiert" -#: build/views.py:972 order/views.py:226 order/views.py:241 +#: build/views.py:967 order/views.py:226 order/views.py:241 msgid "Delete Attachment" msgstr "Anhang löschen" -#: build/views.py:978 order/views.py:233 order/views.py:248 stock/views.py:233 +#: build/views.py:973 order/views.py:233 order/views.py:248 stock/views.py:233 msgid "Deleted attachment" msgstr "Anhang gelöscht" @@ -1571,7 +1571,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" @@ -1690,7 +1690,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:2230 +#: part/templates/part/sale_prices.html:13 part/views.py:2282 msgid "Add Price Break" msgstr "Preisstaffel hinzufügen" @@ -1823,17 +1823,17 @@ msgstr "Neues Zuliefererteil anlegen" msgid "Delete Supplier Part" msgstr "Zuliefererteil entfernen" -#: company/views.py:416 part/views.py:2236 +#: company/views.py:416 part/views.py:2288 #, fuzzy #| msgid "Add Price Break" msgid "Added new price break" msgstr "Preisstaffel hinzufügen" -#: company/views.py:453 part/views.py:2281 +#: company/views.py:453 part/views.py:2333 msgid "Edit Price Break" msgstr "Preisstaffel bearbeiten" -#: company/views.py:469 part/views.py:2297 +#: company/views.py:469 part/views.py:2349 msgid "Delete Price Break" msgstr "Preisstaffel löschen" @@ -1914,7 +1914,7 @@ msgstr "Link auf externe Seite" msgid "Order notes" msgstr "Bestell-Notizen" -#: order/models.py:140 order/models.py:318 +#: order/models.py:140 order/models.py:326 #, fuzzy #| msgid "Purchase Order Details" msgid "Purchase order status" @@ -1938,7 +1938,7 @@ msgstr "" msgid "Date order was completed" msgstr "Bestellung als vollständig markieren" -#: order/models.py:185 order/models.py:259 part/views.py:1347 +#: order/models.py:185 order/models.py:267 part/views.py:1399 #: stock/models.py:249 stock/models.py:822 msgid "Quantity must be greater than zero" msgstr "Anzahl muss größer Null sein" @@ -1947,69 +1947,69 @@ msgstr "Anzahl muss größer Null sein" msgid "Part supplier must match PO supplier" msgstr "Teile-Zulieferer muss dem Zulieferer des Kaufvertrags entsprechen" -#: order/models.py:254 +#: order/models.py:262 msgid "Lines can only be received against an order marked as 'Placed'" msgstr "Nur Teile aufgegebener Bestllungen können empfangen werden" -#: order/models.py:314 +#: order/models.py:322 msgid "Company to which the items are being sold" msgstr "" -#: order/models.py:320 +#: order/models.py:328 msgid "Customer order reference code" msgstr "Bestellreferenz" -#: order/models.py:359 +#: order/models.py:367 msgid "SalesOrder cannot be shipped as it is not currently pending" msgstr "Bestellung kann nicht versendet werden weil sie nicht anhängig ist" -#: order/models.py:436 +#: order/models.py:454 msgid "Item quantity" msgstr "Anzahl" -#: order/models.py:438 +#: order/models.py:456 msgid "Line item reference" msgstr "Position - Referenz" -#: order/models.py:440 +#: order/models.py:458 msgid "Line item notes" msgstr "Position - Notizen" -#: order/models.py:466 order/templates/order/order_base.html:9 +#: order/models.py:484 order/templates/order/order_base.html:9 #: order/templates/order/order_base.html:24 #: stock/templates/stock/item_base.html:265 templates/js/order.js:139 msgid "Purchase Order" msgstr "Kaufvertrag" -#: order/models.py:479 +#: order/models.py:497 msgid "Supplier part" msgstr "Zulieferer-Teil" -#: order/models.py:482 +#: order/models.py:500 msgid "Number of items received" msgstr "Empfangene Objekt-Anzahl" -#: order/models.py:576 +#: order/models.py:594 msgid "Cannot allocate stock item to a line with a different part" msgstr "Kann Lagerobjekt keiner Zeile mit einem anderen Teil hinzufügen" -#: order/models.py:578 +#: order/models.py:596 msgid "Cannot allocate stock to a line without a part" msgstr "Kann Lagerobjekt keiner Zeile ohne Teil hinzufügen" -#: order/models.py:581 +#: order/models.py:599 msgid "Allocation quantity cannot exceed stock quantity" msgstr "zugewiesene Anzahl darf nicht die verfügbare Anzahl überschreiten" -#: order/models.py:591 +#: order/models.py:609 msgid "Quantity must be 1 for serialized stock item" msgstr "Anzahl muss 1 für Objekte mit Seriennummer sein" -#: order/models.py:608 +#: order/models.py:626 msgid "Select stock item to allocate" msgstr "Lagerobjekt für Zuordnung auswählen" -#: order/models.py:611 +#: order/models.py:629 msgid "Enter stock allocation quantity" msgstr "Zuordnungsanzahl eingeben" @@ -2136,8 +2136,8 @@ msgid "Line Items" msgstr "Position hinzufügen" #: order/templates/order/purchase_order_detail.html:17 -#: order/templates/order/sales_order_detail.html:19 order/views.py:1117 -#: order/views.py:1232 +#: order/templates/order/sales_order_detail.html:19 order/views.py:1087 +#: order/views.py:1171 msgid "Add Line Item" msgstr "Position hinzufügen" @@ -2221,6 +2221,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" @@ -2311,99 +2312,115 @@ msgstr "Auftrag bearbeiten" msgid "Cancel Order" msgstr "Bestellung stornieren" -#: order/views.py:418 order/views.py:451 +#: order/views.py:412 order/views.py:436 msgid "Confirm order cancellation" msgstr "Bestellstornierung bestätigen" -#: order/views.py:436 +#: order/views.py:415 order/views.py:439 +msgid "Order cannot be cancelled" +msgstr "" + +#: order/views.py:426 msgid "Cancel sales order" msgstr "Auftrag stornieren" -#: order/views.py:457 -msgid "Could not cancel order" -msgstr "Stornierung fehlgeschlagen" - -#: order/views.py:471 +#: order/views.py:450 msgid "Issue Order" msgstr "Bestellung aufgeben" -#: order/views.py:487 +#: order/views.py:460 msgid "Confirm order placement" msgstr "Bestellungstätigung bestätigen" -#: order/views.py:508 +#: order/views.py:468 +#, fuzzy +#| msgid "Purchase Order Details" +msgid "Purchase order issued" +msgstr "Bestelldetails" + +#: order/views.py:479 msgid "Complete Order" msgstr "Auftrag fertigstellen" -#: order/views.py:544 +#: order/views.py:496 +#, fuzzy +#| msgid "Confirm build completion" +msgid "Confirm order completion" +msgstr "Bau-Fertigstellung bestätigen" + +#: order/views.py:504 +#, fuzzy +#| msgid "Mark order as complete" +msgid "Purchase order completed" +msgstr "Bestellung als vollständig markieren" + +#: order/views.py:514 msgid "Ship Order" msgstr "Versenden" -#: order/views.py:561 +#: order/views.py:531 msgid "Confirm order shipment" msgstr "Versand bestätigen" -#: order/views.py:567 +#: order/views.py:537 msgid "Could not ship order" msgstr "Versand fehlgeschlagen" -#: order/views.py:619 +#: order/views.py:589 msgid "Receive Parts" msgstr "Teile empfangen" -#: order/views.py:687 +#: order/views.py:657 msgid "Items received" msgstr "Anzahl empfangener Positionen" -#: order/views.py:701 +#: order/views.py:671 msgid "No destination set" msgstr "Kein Ziel gesetzt" -#: order/views.py:746 +#: order/views.py:716 msgid "Error converting quantity to number" msgstr "Fehler beim Konvertieren zu Zahl" -#: order/views.py:752 +#: order/views.py:722 msgid "Receive quantity less than zero" msgstr "Anzahl kleiner null empfangen" -#: order/views.py:758 +#: order/views.py:728 msgid "No lines specified" msgstr "Keine Zeilen angegeben" -#: order/views.py:1138 -msgid "Invalid Purchase Order" -msgstr "Ungültige Bestellung" +#: order/views.py:1097 +#, fuzzy +#| msgid "Supplier part description" +msgid "Supplier part must be specified" +msgstr "Zuliefererbeschreibung des Teils" -#: order/views.py:1146 +#: order/views.py:1103 msgid "Supplier must match for Part and Order" msgstr "Zulieferer muss zum Teil und zur Bestellung passen" -#: order/views.py:1151 -msgid "Invalid SupplierPart selection" -msgstr "Ungültige Wahl des Zulieferer-Teils" - -#: order/views.py:1284 order/views.py:1303 +#: order/views.py:1223 order/views.py:1242 msgid "Edit Line Item" msgstr "Position bearbeiten" -#: order/views.py:1320 order/views.py:1333 +#: order/views.py:1259 order/views.py:1272 msgid "Delete Line Item" msgstr "Position löschen" -#: order/views.py:1326 order/views.py:1339 +#: order/views.py:1265 order/views.py:1278 msgid "Deleted line item" msgstr "Position gelöscht" -#: order/views.py:1348 +#: order/views.py:1287 msgid "Allocate Stock to Order" msgstr "Lagerbestand dem Auftrag zuweisen" -#: order/views.py:1418 +#: order/views.py:1357 msgid "Edit Allocation Quantity" msgstr "Zuordnung bearbeiten" -#: order/views.py:1434 +#: order/views.py:1373 msgid "Remove allocation" msgstr "Zuordnung entfernen" @@ -2429,75 +2446,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:1569 +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" @@ -2505,29 +2544,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" @@ -2657,13 +2696,13 @@ msgstr "Bemerkungen - unterstüzt Markdown-Formatierung" msgid "Stored BOM checksum" msgstr "Prüfsumme der Stückliste gespeichert" -#: part/models.py:1400 +#: part/models.py:1442 #, 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:1417 +#: part/models.py:1459 #, fuzzy #| msgid "" #| "A stock item with this serial number already exists for template part " @@ -2673,120 +2712,116 @@ msgstr "" "Ein Teil mit dieser Seriennummer existiert bereits für die Teilevorlage " "{part}" -#: part/models.py:1436 templates/js/part.js:560 templates/js/stock.js:92 +#: part/models.py:1478 templates/js/part.js:560 templates/js/stock.js:92 #, fuzzy #| msgid "Instance Name" msgid "Test Name" msgstr "Instanzname" -#: part/models.py:1437 +#: part/models.py:1479 #, fuzzy #| msgid "Serial number for this item" msgid "Enter a name for the test" msgstr "Seriennummer für dieses Teil" -#: part/models.py:1442 +#: part/models.py:1484 #, fuzzy #| msgid "Description" msgid "Test Description" msgstr "Beschreibung" -#: part/models.py:1443 +#: part/models.py:1485 #, fuzzy #| msgid "Brief description of the build" msgid "Enter description for this test" msgstr "Kurze Beschreibung des Baus" -#: part/models.py:1449 +#: part/models.py:1491 msgid "Is this test required to pass?" msgstr "" -#: part/models.py:1454 templates/js/part.js:577 +#: part/models.py:1496 templates/js/part.js:577 #, fuzzy #| msgid "Required Parts" msgid "Requires Value" msgstr "benötigte Teile" -#: part/models.py:1455 +#: part/models.py:1497 msgid "Does this test require a value when adding a test result?" msgstr "" -#: part/models.py:1460 templates/js/part.js:584 +#: part/models.py:1502 templates/js/part.js:584 #, fuzzy #| msgid "Delete Attachment" msgid "Requires Attachment" msgstr "Anhang löschen" -#: part/models.py:1461 +#: part/models.py:1503 msgid "Does this test require a file attachment when adding a test result?" msgstr "" -#: part/models.py:1494 +#: part/models.py:1536 msgid "Parameter template name must be unique" msgstr "Vorlagen-Name des Parameters muss eindeutig sein" -#: part/models.py:1499 +#: part/models.py:1541 msgid "Parameter Name" msgstr "Name des Parameters" -#: part/models.py:1501 +#: part/models.py:1543 msgid "Parameter Units" msgstr "Parameter Einheit" -#: part/models.py:1527 -msgid "Parent Part" -msgstr "Ausgangsteil" - -#: part/models.py:1529 +#: part/models.py:1571 msgid "Parameter Template" msgstr "Parameter Vorlage" -#: part/models.py:1531 +#: part/models.py:1573 msgid "Parameter Value" msgstr "Parameter Wert" -#: part/models.py:1568 +#: part/models.py:1610 msgid "Select parent part" msgstr "Ausgangsteil auswählen" -#: part/models.py:1576 +#: part/models.py:1618 msgid "Select part to be used in BOM" msgstr "Teil für die Nutzung in der Stückliste auswählen" -#: part/models.py:1582 +#: part/models.py:1624 msgid "BOM quantity for this BOM item" msgstr "Stücklisten-Anzahl für dieses Stücklisten-Teil" -#: part/models.py:1584 +#: part/models.py:1626 #, fuzzy #| msgid "Confim BOM item deletion" msgid "This BOM item is optional" msgstr "Löschung von BOM-Position bestätigen" -#: part/models.py:1587 +#: part/models.py:1629 msgid "Estimated build wastage quantity (absolute or percentage)" msgstr "Geschätzter Ausschuss (absolut oder prozentual)" -#: part/models.py:1590 +#: part/models.py:1632 msgid "BOM item reference" msgstr "Referenz des Objekts auf der Stückliste" -#: part/models.py:1593 +#: part/models.py:1635 msgid "BOM item notes" msgstr "Notizen zum Stücklisten-Objekt" -#: part/models.py:1595 +#: part/models.py:1637 msgid "BOM line checksum" msgstr "Prüfsumme der Stückliste" -#: part/models.py:1662 part/views.py:1353 part/views.py:1405 +#: part/models.py:1704 part/views.py:1405 part/views.py:1457 #: stock/models.py:239 #, 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:1678 +#: part/models.py:1720 #, fuzzy #| msgid "New BOM Item" msgid "BOM Item" @@ -2830,64 +2865,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:1644 +#: part/templates/part/bom.html:66 part/views.py:1696 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 @@ -2988,7 +3039,7 @@ msgstr "Neuen Bau beginnen" msgid "All parts" msgstr "Alle Teile" -#: part/templates/part/category.html:24 part/views.py:2047 +#: part/templates/part/category.html:24 part/views.py:2099 msgid "Create new part category" msgstr "Teilkategorie anlegen" @@ -3078,7 +3129,7 @@ msgstr "Teilkategorie anlegen" msgid "Create new Part Category" msgstr "Teilkategorie anlegen" -#: part/templates/part/category.html:216 stock/views.py:1343 +#: part/templates/part/category.html:216 stock/views.py:1334 msgid "Create new Stock Location" msgstr "Neuen Lager-Standort erstellen" @@ -3281,7 +3332,7 @@ msgstr "Parameter hinzufügen" msgid "New Parameter" msgstr "Neuer Parameter" -#: part/templates/part/params.html:25 stock/models.py:1405 +#: part/templates/part/params.html:25 stock/models.py:1421 #: templates/js/stock.js:112 msgid "Value" msgstr "Wert" @@ -3571,149 +3622,167 @@ msgstr "Kategorie für {n} Teile setzen" msgid "Create Variant" msgstr "Variante anlegen" -#: part/views.py:388 +#: part/views.py:390 msgid "Duplicate Part" msgstr "Teil duplizieren" -#: part/views.py:395 +#: part/views.py:397 msgid "Copied part" msgstr "Teil kopiert" -#: part/views.py:449 part/views.py:579 +#: part/views.py:451 part/views.py:581 msgid "Possible matches exist - confirm creation of new part" msgstr "" -#: part/views.py:514 templates/js/stock.js:833 +#: part/views.py:516 templates/js/stock.js:833 msgid "Create New Part" msgstr "Neues Teil anlegen" -#: part/views.py:521 +#: part/views.py:523 msgid "Created new part" msgstr "Neues Teil angelegt" -#: part/views.py:737 +#: part/views.py:739 msgid "Part QR Code" msgstr "Teil-QR-Code" -#: part/views.py:756 +#: part/views.py:758 msgid "Upload Part Image" msgstr "Teilbild hochladen" -#: part/views.py:764 part/views.py:801 +#: part/views.py:766 part/views.py:803 msgid "Updated part image" msgstr "Teilbild aktualisiert" -#: part/views.py:773 +#: part/views.py:775 msgid "Select Part Image" msgstr "Teilbild auswählen" -#: part/views.py:804 +#: part/views.py:806 msgid "Part image not found" msgstr "Teilbild nicht gefunden" -#: part/views.py:815 +#: part/views.py:817 msgid "Edit Part Properties" msgstr "Teileigenschaften bearbeiten" -#: part/views.py:839 +#: part/views.py:844 +#, fuzzy +#| msgid "Duplicate Part" +msgid "Duplicate BOM" +msgstr "Teil duplizieren" + +#: part/views.py:875 +#, fuzzy +#| msgid "Confirm unallocation of build stock" +msgid "Confirm duplication of BOM from parent" +msgstr "Zuweisungsaufhebung bestätigen" + +#: part/views.py:893 msgid "Validate BOM" msgstr "BOM validieren" -#: part/views.py:867 +#: part/views.py:916 #, fuzzy #| msgid "Confirm that the BOM is correct" msgid "Confirm that the BOM is valid" msgstr "Bestätigen, dass die Stückliste korrekt ist" -#: part/views.py:1006 +#: part/views.py:924 +#, fuzzy +#| msgid "Validate Bill of Materials" +msgid "Validated Bill of Materials" +msgstr "Stückliste validieren" + +#: part/views.py:1058 msgid "No BOM file provided" msgstr "Keine Stückliste angegeben" -#: part/views.py:1356 +#: part/views.py:1408 msgid "Enter a valid quantity" msgstr "Bitte eine gültige Anzahl eingeben" -#: part/views.py:1381 part/views.py:1384 +#: part/views.py:1433 part/views.py:1436 msgid "Select valid part" msgstr "Bitte ein gültiges Teil auswählen" -#: part/views.py:1390 +#: part/views.py:1442 msgid "Duplicate part selected" msgstr "Teil doppelt ausgewählt" -#: part/views.py:1428 +#: part/views.py:1480 msgid "Select a part" msgstr "Teil auswählen" -#: part/views.py:1434 +#: part/views.py:1486 #, 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:1438 +#: part/views.py:1490 msgid "Specify quantity" msgstr "Anzahl angeben" -#: part/views.py:1694 +#: part/views.py:1746 msgid "Confirm Part Deletion" msgstr "Löschen des Teils bestätigen" -#: part/views.py:1703 +#: part/views.py:1755 msgid "Part was deleted" msgstr "Teil wurde gelöscht" -#: part/views.py:1712 +#: part/views.py:1764 msgid "Part Pricing" msgstr "Teilbepreisung" -#: part/views.py:1838 +#: part/views.py:1890 msgid "Create Part Parameter Template" msgstr "Teilparametervorlage anlegen" -#: part/views.py:1848 +#: part/views.py:1900 msgid "Edit Part Parameter Template" msgstr "Teilparametervorlage bearbeiten" -#: part/views.py:1857 +#: part/views.py:1909 msgid "Delete Part Parameter Template" msgstr "Teilparametervorlage löschen" -#: part/views.py:1867 +#: part/views.py:1919 msgid "Create Part Parameter" msgstr "Teilparameter anlegen" -#: part/views.py:1919 +#: part/views.py:1971 msgid "Edit Part Parameter" msgstr "Teilparameter bearbeiten" -#: part/views.py:1935 +#: part/views.py:1987 msgid "Delete Part Parameter" msgstr "Teilparameter löschen" -#: part/views.py:1994 +#: part/views.py:2046 msgid "Edit Part Category" msgstr "Teilkategorie bearbeiten" -#: part/views.py:2031 +#: part/views.py:2083 msgid "Delete Part Category" msgstr "Teilkategorie löschen" -#: part/views.py:2039 +#: part/views.py:2091 msgid "Part category was deleted" msgstr "Teilekategorie wurde gelöscht" -#: part/views.py:2102 +#: part/views.py:2154 #, fuzzy #| msgid "Create BOM item" msgid "Create BOM Item" msgstr "BOM-Position anlegen" -#: part/views.py:2170 +#: part/views.py:2222 msgid "Edit BOM item" msgstr "BOM-Position beaarbeiten" -#: part/views.py:2220 +#: part/views.py:2272 msgid "Confim BOM item deletion" msgstr "Löschung von BOM-Position bestätigen" @@ -3817,7 +3886,7 @@ msgstr "Ziel-Lagerbestand" msgid "Add note (required)" msgstr "" -#: stock/forms.py:370 stock/views.py:921 stock/views.py:1119 +#: stock/forms.py:370 stock/views.py:912 stock/views.py:1110 msgid "Confirm stock adjustment" msgstr "Bestands-Anpassung bestätigen" @@ -4028,65 +4097,65 @@ msgstr "{n} Teile serialisiert" msgid "StockItem cannot be moved as it is not in stock" msgstr "Lagerobjekt kann nicht bewegt werden, da kein Bestand vorhanden ist" -#: stock/models.py:1306 +#: stock/models.py:1322 msgid "Tracking entry title" msgstr "Name des Eintrags-Trackings" -#: stock/models.py:1308 +#: stock/models.py:1324 msgid "Entry notes" msgstr "Eintrags-Notizen" -#: stock/models.py:1310 +#: stock/models.py:1326 msgid "Link to external page for further information" msgstr "Link auf externe Seite für weitere Informationen" -#: stock/models.py:1370 +#: stock/models.py:1386 #, fuzzy #| msgid "Serial number for this item" msgid "Value must be provided for this test" msgstr "Seriennummer für dieses Teil" -#: stock/models.py:1376 +#: stock/models.py:1392 msgid "Attachment must be uploaded for this test" msgstr "" -#: stock/models.py:1393 +#: stock/models.py:1409 msgid "Test" msgstr "" -#: stock/models.py:1394 +#: stock/models.py:1410 #, fuzzy #| msgid "Part name" msgid "Test name" msgstr "Name des Teils" -#: stock/models.py:1399 +#: stock/models.py:1415 #, fuzzy #| msgid "Search Results" msgid "Result" msgstr "Suchergebnisse" -#: stock/models.py:1400 templates/js/table_filters.js:162 +#: stock/models.py:1416 templates/js/table_filters.js:162 msgid "Test result" msgstr "" -#: stock/models.py:1406 +#: stock/models.py:1422 msgid "Test output value" msgstr "" -#: stock/models.py:1412 +#: stock/models.py:1428 #, fuzzy #| msgid "Attachments" msgid "Attachment" msgstr "Anhänge" -#: stock/models.py:1413 +#: stock/models.py:1429 #, fuzzy #| msgid "Delete attachment" msgid "Test result attachment" msgstr "Anhang löschen" -#: stock/models.py:1419 +#: stock/models.py:1435 #, fuzzy #| msgid "Edit notes" msgid "Test notes" @@ -4420,7 +4489,7 @@ msgstr "Sind Sie sicher, dass Sie diesen Anhang löschen wollen?" msgid "The following stock items will be uninstalled" msgstr "Die folgenden Objekte werden erstellt" -#: stock/templates/stock/stockitem_convert.html:7 stock/views.py:1315 +#: stock/templates/stock/stockitem_convert.html:7 stock/views.py:1306 #, fuzzy #| msgid "Count Stock Items" msgid "Convert Stock Item" @@ -4490,220 +4559,224 @@ msgstr "Teilanhang löschen" msgid "Assign to Customer" msgstr "Ist dieses Objekt einem Kunden zugeteilt?" -#: stock/views.py:281 +#: stock/views.py:253 +msgid "Customer must be specified" +msgstr "" + +#: stock/views.py:277 #, fuzzy #| msgid "Part Stock" msgid "Return to Stock" msgstr "Teilbestand" -#: stock/views.py:301 +#: stock/views.py:287 #, fuzzy #| msgid "Include sublocations" msgid "Specify a valid location" msgstr "Unterlagerorte einschließen" -#: stock/views.py:305 +#: stock/views.py:298 msgid "Stock item returned from customer" msgstr "" -#: stock/views.py:317 +#: stock/views.py:308 #, fuzzy #| msgid "Select valid part" msgid "Select Label Template" msgstr "Bitte ein gültiges Teil auswählen" -#: stock/views.py:340 +#: stock/views.py:331 #, fuzzy #| msgid "Select valid part" msgid "Select valid label" msgstr "Bitte ein gültiges Teil auswählen" -#: stock/views.py:404 +#: stock/views.py:395 #, fuzzy #| msgid "Delete Template" msgid "Delete All Test Data" msgstr "Vorlage löschen" -#: stock/views.py:420 +#: stock/views.py:411 #, fuzzy #| msgid "Confirm Part Deletion" msgid "Confirm test data deletion" msgstr "Löschen des Teils bestätigen" -#: stock/views.py:440 +#: stock/views.py:431 msgid "Add Test Result" msgstr "" -#: stock/views.py:478 +#: stock/views.py:469 #, fuzzy #| msgid "Edit Template" msgid "Edit Test Result" msgstr "Vorlage bearbeiten" -#: stock/views.py:496 +#: stock/views.py:487 #, fuzzy #| msgid "Delete Template" msgid "Delete Test Result" msgstr "Vorlage löschen" -#: stock/views.py:508 +#: stock/views.py:499 #, fuzzy #| msgid "Delete Template" msgid "Select Test Report Template" msgstr "Vorlage löschen" -#: stock/views.py:523 +#: stock/views.py:514 #, fuzzy #| msgid "Select valid part" msgid "Select valid template" msgstr "Bitte ein gültiges Teil auswählen" -#: stock/views.py:576 +#: stock/views.py:567 msgid "Stock Export Options" msgstr "Lagerbestandsexportoptionen" -#: stock/views.py:698 +#: stock/views.py:689 msgid "Stock Item QR Code" msgstr "Lagerobjekt-QR-Code" -#: stock/views.py:724 +#: stock/views.py:715 #, fuzzy #| msgid "Installed in Stock Item" msgid "Install Stock Item" msgstr "In Lagerobjekt installiert" -#: stock/views.py:824 +#: stock/views.py:815 #, fuzzy #| msgid "Installed in Stock Item" msgid "Uninstall Stock Items" msgstr "In Lagerobjekt installiert" -#: stock/views.py:932 +#: stock/views.py:923 #, fuzzy #| msgid "Installed in Stock Item" msgid "Uninstalled stock items" msgstr "In Lagerobjekt installiert" -#: stock/views.py:957 +#: stock/views.py:948 msgid "Adjust Stock" msgstr "Lagerbestand anpassen" -#: stock/views.py:1067 +#: stock/views.py:1058 msgid "Move Stock Items" msgstr "Lagerobjekte bewegen" -#: stock/views.py:1068 +#: stock/views.py:1059 msgid "Count Stock Items" msgstr "Lagerobjekte zählen" -#: stock/views.py:1069 +#: stock/views.py:1060 msgid "Remove From Stock" msgstr "Aus Lagerbestand entfernen" -#: stock/views.py:1070 +#: stock/views.py:1061 msgid "Add Stock Items" msgstr "Lagerobjekte hinzufügen" -#: stock/views.py:1071 +#: stock/views.py:1062 msgid "Delete Stock Items" msgstr "Lagerobjekte löschen" -#: stock/views.py:1099 +#: stock/views.py:1090 msgid "Must enter integer value" msgstr "Nur Ganzzahl eingeben" -#: stock/views.py:1104 +#: stock/views.py:1095 msgid "Quantity must be positive" msgstr "Anzahl muss positiv sein" -#: stock/views.py:1111 +#: stock/views.py:1102 #, python-brace-format msgid "Quantity must not exceed {x}" msgstr "Anzahl darf {x} nicht überschreiten" -#: stock/views.py:1190 +#: stock/views.py:1181 #, python-brace-format msgid "Added stock to {n} items" msgstr "Vorrat zu {n} Lagerobjekten hinzugefügt" -#: stock/views.py:1205 +#: stock/views.py:1196 #, python-brace-format msgid "Removed stock from {n} items" msgstr "Vorrat von {n} Lagerobjekten entfernt" -#: stock/views.py:1218 +#: stock/views.py:1209 #, python-brace-format msgid "Counted stock for {n} items" msgstr "Bestand für {n} Objekte erfasst" -#: stock/views.py:1246 +#: stock/views.py:1237 msgid "No items were moved" msgstr "Keine Lagerobjekte wurden bewegt" -#: stock/views.py:1249 +#: stock/views.py:1240 #, python-brace-format msgid "Moved {n} items to {dest}" msgstr "{n} Teile nach {dest} bewegt" -#: stock/views.py:1268 +#: stock/views.py:1259 #, python-brace-format msgid "Deleted {n} stock items" msgstr "{n} Teile im Lager gelöscht" -#: stock/views.py:1280 +#: stock/views.py:1271 msgid "Edit Stock Item" msgstr "Lagerobjekt bearbeiten" -#: stock/views.py:1365 +#: stock/views.py:1356 msgid "Serialize Stock" msgstr "Lagerbestand erfassen" -#: stock/views.py:1558 +#: stock/views.py:1549 #, fuzzy #| msgid "Count stock items" msgid "Duplicate Stock Item" msgstr "Lagerobjekte zählen" -#: stock/views.py:1624 +#: stock/views.py:1615 msgid "Invalid quantity" msgstr "Ungültige Menge" -#: stock/views.py:1627 +#: stock/views.py:1618 #, fuzzy #| msgid "Quantity must be greater than zero" msgid "Quantity cannot be less than zero" msgstr "Anzahl muss größer Null sein" -#: stock/views.py:1631 +#: stock/views.py:1622 msgid "Invalid part selection" msgstr "Ungültige Teileauswahl" -#: stock/views.py:1679 +#: stock/views.py:1670 #, python-brace-format msgid "Created {n} new stock items" msgstr "{n} neue Lagerobjekte erstellt" -#: stock/views.py:1698 stock/views.py:1714 +#: stock/views.py:1689 stock/views.py:1705 msgid "Created new stock item" msgstr "Neues Lagerobjekt erstellt" -#: stock/views.py:1733 +#: stock/views.py:1724 msgid "Delete Stock Location" msgstr "Standort löschen" -#: stock/views.py:1747 +#: stock/views.py:1738 msgid "Delete Stock Item" msgstr "Lagerobjekt löschen" -#: stock/views.py:1759 +#: stock/views.py:1750 msgid "Delete Stock Tracking Entry" msgstr "Lagerbestands-Tracking-Eintrag löschen" -#: stock/views.py:1778 +#: stock/views.py:1769 msgid "Edit Stock Tracking Entry" msgstr "Lagerbestands-Tracking-Eintrag bearbeiten" -#: stock/views.py:1788 +#: stock/views.py:1779 msgid "Add Stock Tracking Entry" msgstr "Lagerbestands-Tracking-Eintrag hinzufügen" @@ -4937,9 +5010,9 @@ msgstr "Keine Benutzerinformation" #: templates/InvenTree/settings/user.html:21 #, fuzzy -#| msgid "Select part" +#| msgid "Create new part" msgid "Change Password" -msgstr "Teil auswählen" +msgstr "Neues Teil anlegen" #: templates/InvenTree/settings/user.html:28 #, fuzzy @@ -5146,10 +5219,9 @@ msgstr "nachverfolgbar" msgid "Virtual part" msgstr "Virtuell" -#: templates/js/bom.js:164 -#, 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:169 @@ -5280,11 +5352,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" @@ -5840,16 +5907,6 @@ msgstr "" msgid "Permission to delete items" msgstr "Ausgewählte Stücklistenpositionen entfernen" -#~ msgid "" -#~ "Stock Items are selected for automatic allocation if there is only a " -#~ "single stock item available." -#~ msgstr "" -#~ "Teile werden automatisch zugewiesen, wenn nur ein Lagerobjekt verfügbar " -#~ "ist" - -#~ msgid "The following stock items will be allocated to the build:" -#~ msgstr "Folgende Lagerobjekte werden dem Bau automatisch zugewiesen:" - #, fuzzy #~| msgid "Overage must be an integer value or a percentage" #~ msgid "Build quantity must be integer value for trackable parts" @@ -5858,17 +5915,38 @@ msgstr "Ausgewählte Stücklistenpositionen entfernen" #~ msgid "Parent build to which this build is allocated" #~ msgstr "Eltern-Bau, dem dieser Bau zugewiesen ist" +#~ msgid "" +#~ "Stock Items are selected for automatic allocation if there is only a " +#~ "single stock item available." +#~ msgstr "" +#~ "Teile werden automatisch zugewiesen, wenn nur ein Lagerobjekt verfügbar " +#~ "ist" + #~ msgid "Title" #~ msgstr "Titel" #~ msgid "Allocate new Part" #~ msgstr "Neues Teil zuordnen" +#~ msgid "Could not cancel order" +#~ msgstr "Stornierung fehlgeschlagen" + +#~ msgid "Invalid Purchase Order" +#~ msgstr "Ungültige Bestellung" + +#~ msgid "Invalid SupplierPart selection" +#~ msgstr "Ungültige Wahl des Zulieferer-Teils" + #, fuzzy #~| msgid "This stock item is allocated to Sales Order" #~ msgid "Stock item was assigned to a build order" #~ msgstr "Dieses Lagerobjekt ist dem Auftrag zugewiesen" +#, 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 55616d2c88..446564bb29 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 04:20+0000\n" +"POT-Creation-Date: 2020-10-30 11:43+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -46,7 +46,7 @@ msgstr "" msgid "Apply Theme" msgstr "" -#: InvenTree/helpers.py:361 order/models.py:187 order/models.py:261 +#: InvenTree/helpers.py:361 order/models.py:187 order/models.py:269 msgid "Invalid quantity provided" msgstr "" @@ -202,27 +202,27 @@ msgstr "" msgid "Overage must be an integer value or a percentage" msgstr "" -#: InvenTree/views.py:466 +#: InvenTree/views.py:518 msgid "Delete Item" msgstr "" -#: InvenTree/views.py:515 +#: InvenTree/views.py:567 msgid "Check box to confirm item deletion" msgstr "" -#: InvenTree/views.py:530 templates/InvenTree/settings/user.html:18 +#: InvenTree/views.py:582 templates/InvenTree/settings/user.html:18 msgid "Edit User Information" msgstr "" -#: InvenTree/views.py:541 templates/InvenTree/settings/user.html:22 +#: InvenTree/views.py:593 templates/InvenTree/settings/user.html:22 msgid "Set Password" msgstr "" -#: InvenTree/views.py:560 +#: InvenTree/views.py:612 msgid "Password fields must match" msgstr "" -#: InvenTree/views.py:730 +#: InvenTree/views.py:782 msgid "Database Statistics" msgstr "" @@ -302,7 +302,7 @@ msgstr "" msgid "Confirm build completion" msgstr "" -#: build/forms.py:159 build/views.py:77 +#: build/forms.py:159 build/views.py:68 msgid "Confirm build cancellation" msgstr "" @@ -364,7 +364,7 @@ msgstr "" #: build/models.py:98 build/templates/build/allocate.html:366 #: build/templates/build/auto_allocate.html:25 #: build/templates/build/build_base.html:73 -#: build/templates/build/detail.html:24 order/models.py:501 +#: build/templates/build/detail.html:24 order/models.py:519 #: order/templates/order/order_wizard/select_parts.html:30 #: order/templates/order/purchase_order_detail.html:148 #: order/templates/order/receive_parts.html:19 part/models.py:293 @@ -456,7 +456,7 @@ msgstr "" #: order/templates/order/purchase_order_detail.html:203 #: 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:461 -#: stock/models.py:1418 stock/templates/stock/tabs.html:26 +#: stock/models.py:1434 stock/templates/stock/tabs.html:26 #: templates/js/barcode.js:391 templates/js/bom.js:264 #: templates/js/stock.js:116 templates/js/stock.js:571 msgid "Notes" @@ -492,11 +492,11 @@ msgstr "" msgid "Allocated quantity ({n}) must not exceed available quantity ({q})" msgstr "" -#: build/models.py:803 order/models.py:585 +#: build/models.py:803 order/models.py:603 msgid "StockItem is over-allocated" msgstr "" -#: build/models.py:807 order/models.py:588 +#: build/models.py:807 order/models.py:606 msgid "Allocation quantity must be greater than zero" msgstr "" @@ -543,7 +543,7 @@ msgid "Assigned Stock" msgstr "" #: build/templates/build/allocate.html:28 -#: company/templates/company/detail_part.html:28 order/views.py:804 +#: company/templates/company/detail_part.html:28 order/views.py:774 #: part/templates/part/category.html:125 msgid "Order Parts" msgstr "" @@ -565,7 +565,7 @@ msgstr "" msgid "New Stock Item" msgstr "" -#: build/templates/build/allocate.html:125 stock/views.py:1459 +#: build/templates/build/allocate.html:125 stock/views.py:1450 #: templates/js/build.js:226 msgid "Create new Stock Item" msgstr "" @@ -624,7 +624,7 @@ msgstr "" msgid "No BOM items found" msgstr "" -#: build/templates/build/allocate.html:384 part/models.py:1448 +#: build/templates/build/allocate.html:384 part/models.py:1490 #: templates/js/part.js:569 templates/js/table_filters.js:172 msgid "Required" msgstr "" @@ -716,7 +716,7 @@ msgstr "" msgid "Edit Build" msgstr "" -#: build/templates/build/build_base.html:50 build/views.py:300 +#: build/templates/build/build_base.html:50 build/views.py:295 msgid "Complete Build" msgstr "" @@ -724,7 +724,7 @@ msgstr "" msgid "Cancel Build" msgstr "" -#: build/templates/build/build_base.html:59 build/views.py:632 +#: build/templates/build/build_base.html:59 build/views.py:627 msgid "Delete Build" msgstr "" @@ -748,7 +748,7 @@ msgid "Progress" msgstr "" #: build/templates/build/build_base.html:101 -#: build/templates/build/detail.html:82 order/models.py:499 +#: build/templates/build/detail.html:82 order/models.py:517 #: order/templates/order/sales_order_base.html:9 #: order/templates/order/sales_order_base.html:33 #: order/templates/order/sales_order_notes.html:10 @@ -879,7 +879,7 @@ msgstr "" msgid "Alter the quantity of stock allocated to the build output" msgstr "" -#: build/templates/build/index.html:25 build/views.py:507 +#: build/templates/build/index.html:25 build/views.py:502 msgid "New Build Order" msgstr "" @@ -918,137 +918,137 @@ msgstr "" msgid "All incomplete stock allocations will be removed from the build" msgstr "" -#: build/views.py:82 +#: build/views.py:79 msgid "Build was cancelled" msgstr "" -#: build/views.py:98 +#: build/views.py:93 msgid "Allocate Stock" msgstr "" -#: build/views.py:126 +#: build/views.py:121 msgid "No matching build found" msgstr "" -#: build/views.py:157 +#: build/views.py:152 msgid "Confirm stock allocation" msgstr "" -#: build/views.py:158 +#: build/views.py:153 msgid "Check the confirmation box at the bottom of the list" msgstr "" -#: build/views.py:176 templates/js/build.js:85 +#: build/views.py:171 templates/js/build.js:85 msgid "Delete build output" msgstr "" -#: build/views.py:210 +#: build/views.py:205 msgid "Build or output not specified" msgstr "" -#: build/views.py:212 build/views.py:276 +#: build/views.py:207 build/views.py:271 msgid "Confirm unallocation of build stock" msgstr "" -#: build/views.py:213 build/views.py:277 stock/views.py:421 +#: build/views.py:208 build/views.py:272 stock/views.py:412 msgid "Check the confirmation box" msgstr "" -#: build/views.py:230 build/views.py:643 +#: build/views.py:225 build/views.py:638 msgid "Unallocate Stock" msgstr "" -#: build/views.py:379 +#: build/views.py:374 msgid "Confirm completion of build" msgstr "" -#: build/views.py:385 +#: build/views.py:380 msgid "Invalid location selected" msgstr "" -#: build/views.py:406 +#: build/views.py:401 #, python-brace-format msgid "The following serial numbers already exist: ({sn})" msgstr "" -#: build/views.py:415 +#: build/views.py:410 msgid "Build could not be completed" msgstr "" -#: build/views.py:427 +#: build/views.py:422 msgid "Build marked as COMPLETE" msgstr "" -#: build/views.py:556 +#: build/views.py:551 msgid "Created new build" msgstr "" -#: build/views.py:573 +#: build/views.py:568 msgid "Trackable part must have serial numbers specified" msgstr "" -#: build/views.py:594 stock/models.py:838 stock/views.py:1650 +#: build/views.py:589 stock/models.py:838 stock/views.py:1641 msgid "Serial numbers already exist" msgstr "" -#: build/views.py:617 +#: build/views.py:612 msgid "Edit Build Details" msgstr "" -#: build/views.py:623 +#: build/views.py:618 msgid "Edited build" msgstr "" -#: build/views.py:649 +#: build/views.py:644 msgid "Removed parts from build allocation" msgstr "" -#: build/views.py:661 +#: build/views.py:656 msgid "Allocate stock to build output" msgstr "" -#: build/views.py:703 +#: build/views.py:698 msgid "Item must be currently in stock" msgstr "" -#: build/views.py:709 +#: build/views.py:704 msgid "Stock item is over-allocated" msgstr "" -#: build/views.py:710 +#: build/views.py:705 msgid "Avaialabe" msgstr "" -#: build/views.py:872 +#: build/views.py:867 msgid "Edit Stock Allocation" msgstr "" -#: build/views.py:877 +#: build/views.py:872 msgid "Updated Build Item" msgstr "" -#: build/views.py:906 +#: build/views.py:901 msgid "Add Build Order Attachment" msgstr "" -#: build/views.py:915 order/views.py:109 order/views.py:157 part/views.py:92 +#: build/views.py:910 order/views.py:109 order/views.py:157 part/views.py:92 #: stock/views.py:175 msgid "Added attachment" msgstr "" -#: build/views.py:951 order/views.py:184 order/views.py:206 +#: build/views.py:946 order/views.py:184 order/views.py:206 msgid "Edit Attachment" msgstr "" -#: build/views.py:962 order/views.py:189 order/views.py:211 +#: build/views.py:957 order/views.py:189 order/views.py:211 msgid "Attachment updated" msgstr "" -#: build/views.py:972 order/views.py:226 order/views.py:241 +#: build/views.py:967 order/views.py:226 order/views.py:241 msgid "Delete Attachment" msgstr "" -#: build/views.py:978 order/views.py:233 order/views.py:248 stock/views.py:233 +#: build/views.py:973 order/views.py:233 order/views.py:248 stock/views.py:233 msgid "Deleted attachment" msgstr "" @@ -1403,7 +1403,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" @@ -1521,7 +1521,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:2230 +#: part/templates/part/sale_prices.html:13 part/views.py:2282 msgid "Add Price Break" msgstr "" @@ -1648,15 +1648,15 @@ msgstr "" msgid "Delete Supplier Part" msgstr "" -#: company/views.py:416 part/views.py:2236 +#: company/views.py:416 part/views.py:2288 msgid "Added new price break" msgstr "" -#: company/views.py:453 part/views.py:2281 +#: company/views.py:453 part/views.py:2333 msgid "Edit Price Break" msgstr "" -#: company/views.py:469 part/views.py:2297 +#: company/views.py:469 part/views.py:2349 msgid "Delete Price Break" msgstr "" @@ -1729,7 +1729,7 @@ msgstr "" msgid "Order notes" msgstr "" -#: order/models.py:140 order/models.py:318 +#: order/models.py:140 order/models.py:326 msgid "Purchase order status" msgstr "" @@ -1749,7 +1749,7 @@ msgstr "" msgid "Date order was completed" msgstr "" -#: order/models.py:185 order/models.py:259 part/views.py:1347 +#: order/models.py:185 order/models.py:267 part/views.py:1399 #: stock/models.py:249 stock/models.py:822 msgid "Quantity must be greater than zero" msgstr "" @@ -1758,69 +1758,69 @@ msgstr "" msgid "Part supplier must match PO supplier" msgstr "" -#: order/models.py:254 +#: order/models.py:262 msgid "Lines can only be received against an order marked as 'Placed'" msgstr "" -#: order/models.py:314 +#: order/models.py:322 msgid "Company to which the items are being sold" msgstr "" -#: order/models.py:320 +#: order/models.py:328 msgid "Customer order reference code" msgstr "" -#: order/models.py:359 +#: order/models.py:367 msgid "SalesOrder cannot be shipped as it is not currently pending" msgstr "" -#: order/models.py:436 +#: order/models.py:454 msgid "Item quantity" msgstr "" -#: order/models.py:438 +#: order/models.py:456 msgid "Line item reference" msgstr "" -#: order/models.py:440 +#: order/models.py:458 msgid "Line item notes" msgstr "" -#: order/models.py:466 order/templates/order/order_base.html:9 +#: order/models.py:484 order/templates/order/order_base.html:9 #: order/templates/order/order_base.html:24 #: stock/templates/stock/item_base.html:265 templates/js/order.js:139 msgid "Purchase Order" msgstr "" -#: order/models.py:479 +#: order/models.py:497 msgid "Supplier part" msgstr "" -#: order/models.py:482 +#: order/models.py:500 msgid "Number of items received" msgstr "" -#: order/models.py:576 +#: order/models.py:594 msgid "Cannot allocate stock item to a line with a different part" msgstr "" -#: order/models.py:578 +#: order/models.py:596 msgid "Cannot allocate stock to a line without a part" msgstr "" -#: order/models.py:581 +#: order/models.py:599 msgid "Allocation quantity cannot exceed stock quantity" msgstr "" -#: order/models.py:591 +#: order/models.py:609 msgid "Quantity must be 1 for serialized stock item" msgstr "" -#: order/models.py:608 +#: order/models.py:626 msgid "Select stock item to allocate" msgstr "" -#: order/models.py:611 +#: order/models.py:629 msgid "Enter stock allocation quantity" msgstr "" @@ -1938,8 +1938,8 @@ msgid "Line Items" msgstr "" #: order/templates/order/purchase_order_detail.html:17 -#: order/templates/order/sales_order_detail.html:19 order/views.py:1117 -#: order/views.py:1232 +#: order/templates/order/sales_order_detail.html:19 order/views.py:1087 +#: order/views.py:1171 msgid "Add Line Item" msgstr "" @@ -2021,6 +2021,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 "" @@ -2107,99 +2108,107 @@ msgstr "" msgid "Cancel Order" msgstr "" -#: order/views.py:418 order/views.py:451 +#: order/views.py:412 order/views.py:436 msgid "Confirm order cancellation" msgstr "" -#: order/views.py:436 +#: order/views.py:415 order/views.py:439 +msgid "Order cannot be cancelled" +msgstr "" + +#: order/views.py:426 msgid "Cancel sales order" msgstr "" -#: order/views.py:457 -msgid "Could not cancel order" -msgstr "" - -#: order/views.py:471 +#: order/views.py:450 msgid "Issue Order" msgstr "" -#: order/views.py:487 +#: order/views.py:460 msgid "Confirm order placement" msgstr "" -#: order/views.py:508 +#: order/views.py:468 +msgid "Purchase order issued" +msgstr "" + +#: order/views.py:479 msgid "Complete Order" msgstr "" -#: order/views.py:544 +#: order/views.py:496 +msgid "Confirm order completion" +msgstr "" + +#: order/views.py:504 +msgid "Purchase order completed" +msgstr "" + +#: order/views.py:514 msgid "Ship Order" msgstr "" -#: order/views.py:561 +#: order/views.py:531 msgid "Confirm order shipment" msgstr "" -#: order/views.py:567 +#: order/views.py:537 msgid "Could not ship order" msgstr "" -#: order/views.py:619 +#: order/views.py:589 msgid "Receive Parts" msgstr "" -#: order/views.py:687 +#: order/views.py:657 msgid "Items received" msgstr "" -#: order/views.py:701 +#: order/views.py:671 msgid "No destination set" msgstr "" -#: order/views.py:746 +#: order/views.py:716 msgid "Error converting quantity to number" msgstr "" -#: order/views.py:752 +#: order/views.py:722 msgid "Receive quantity less than zero" msgstr "" -#: order/views.py:758 +#: order/views.py:728 msgid "No lines specified" msgstr "" -#: order/views.py:1138 -msgid "Invalid Purchase Order" +#: order/views.py:1097 +msgid "Supplier part must be specified" msgstr "" -#: order/views.py:1146 +#: order/views.py:1103 msgid "Supplier must match for Part and Order" msgstr "" -#: order/views.py:1151 -msgid "Invalid SupplierPart selection" -msgstr "" - -#: order/views.py:1284 order/views.py:1303 +#: order/views.py:1223 order/views.py:1242 msgid "Edit Line Item" msgstr "" -#: order/views.py:1320 order/views.py:1333 +#: order/views.py:1259 order/views.py:1272 msgid "Delete Line Item" msgstr "" -#: order/views.py:1326 order/views.py:1339 +#: order/views.py:1265 order/views.py:1278 msgid "Deleted line item" msgstr "" -#: order/views.py:1348 +#: order/views.py:1287 msgid "Allocate Stock to Order" msgstr "" -#: order/views.py:1418 +#: order/views.py:1357 msgid "Edit Allocation Quantity" msgstr "" -#: order/views.py:1434 +#: order/views.py:1373 msgid "Remove allocation" msgstr "" @@ -2225,91 +2234,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:1569 +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 "" @@ -2435,112 +2460,108 @@ msgstr "" msgid "Stored BOM checksum" msgstr "" -#: part/models.py:1400 +#: part/models.py:1442 msgid "Test templates can only be created for trackable parts" msgstr "" -#: part/models.py:1417 +#: part/models.py:1459 msgid "Test with this name already exists for this part" msgstr "" -#: part/models.py:1436 templates/js/part.js:560 templates/js/stock.js:92 +#: part/models.py:1478 templates/js/part.js:560 templates/js/stock.js:92 msgid "Test Name" msgstr "" -#: part/models.py:1437 +#: part/models.py:1479 msgid "Enter a name for the test" msgstr "" -#: part/models.py:1442 +#: part/models.py:1484 msgid "Test Description" msgstr "" -#: part/models.py:1443 +#: part/models.py:1485 msgid "Enter description for this test" msgstr "" -#: part/models.py:1449 +#: part/models.py:1491 msgid "Is this test required to pass?" msgstr "" -#: part/models.py:1454 templates/js/part.js:577 +#: part/models.py:1496 templates/js/part.js:577 msgid "Requires Value" msgstr "" -#: part/models.py:1455 +#: part/models.py:1497 msgid "Does this test require a value when adding a test result?" msgstr "" -#: part/models.py:1460 templates/js/part.js:584 +#: part/models.py:1502 templates/js/part.js:584 msgid "Requires Attachment" msgstr "" -#: part/models.py:1461 +#: part/models.py:1503 msgid "Does this test require a file attachment when adding a test result?" msgstr "" -#: part/models.py:1494 +#: part/models.py:1536 msgid "Parameter template name must be unique" msgstr "" -#: part/models.py:1499 +#: part/models.py:1541 msgid "Parameter Name" msgstr "" -#: part/models.py:1501 +#: part/models.py:1543 msgid "Parameter Units" msgstr "" -#: part/models.py:1527 -msgid "Parent Part" -msgstr "" - -#: part/models.py:1529 +#: part/models.py:1571 msgid "Parameter Template" msgstr "" -#: part/models.py:1531 +#: part/models.py:1573 msgid "Parameter Value" msgstr "" -#: part/models.py:1568 +#: part/models.py:1610 msgid "Select parent part" msgstr "" -#: part/models.py:1576 +#: part/models.py:1618 msgid "Select part to be used in BOM" msgstr "" -#: part/models.py:1582 +#: part/models.py:1624 msgid "BOM quantity for this BOM item" msgstr "" -#: part/models.py:1584 +#: part/models.py:1626 msgid "This BOM item is optional" msgstr "" -#: part/models.py:1587 +#: part/models.py:1629 msgid "Estimated build wastage quantity (absolute or percentage)" msgstr "" -#: part/models.py:1590 +#: part/models.py:1632 msgid "BOM item reference" msgstr "" -#: part/models.py:1593 +#: part/models.py:1635 msgid "BOM item notes" msgstr "" -#: part/models.py:1595 +#: part/models.py:1637 msgid "BOM line checksum" msgstr "" -#: part/models.py:1662 part/views.py:1353 part/views.py:1405 +#: part/models.py:1704 part/views.py:1405 part/views.py:1457 #: stock/models.py:239 msgid "Quantity must be integer value for trackable parts" msgstr "" -#: part/models.py:1678 +#: part/models.py:1720 msgid "BOM Item" msgstr "" @@ -2582,54 +2603,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:1644 +#: part/templates/part/bom.html:66 part/views.py:1696 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 @@ -2710,7 +2743,7 @@ msgstr "" msgid "All parts" msgstr "" -#: part/templates/part/category.html:24 part/views.py:2047 +#: part/templates/part/category.html:24 part/views.py:2099 msgid "Create new part category" msgstr "" @@ -2782,7 +2815,7 @@ msgstr "" msgid "Create new Part Category" msgstr "" -#: part/templates/part/category.html:216 stock/views.py:1343 +#: part/templates/part/category.html:216 stock/views.py:1334 msgid "Create new Stock Location" msgstr "" @@ -2967,7 +3000,7 @@ msgstr "" msgid "New Parameter" msgstr "" -#: part/templates/part/params.html:25 stock/models.py:1405 +#: part/templates/part/params.html:25 stock/models.py:1421 #: templates/js/stock.js:112 msgid "Value" msgstr "" @@ -3215,143 +3248,155 @@ msgstr "" msgid "Create Variant" msgstr "" -#: part/views.py:388 +#: part/views.py:390 msgid "Duplicate Part" msgstr "" -#: part/views.py:395 +#: part/views.py:397 msgid "Copied part" msgstr "" -#: part/views.py:449 part/views.py:579 +#: part/views.py:451 part/views.py:581 msgid "Possible matches exist - confirm creation of new part" msgstr "" -#: part/views.py:514 templates/js/stock.js:833 +#: part/views.py:516 templates/js/stock.js:833 msgid "Create New Part" msgstr "" -#: part/views.py:521 +#: part/views.py:523 msgid "Created new part" msgstr "" -#: part/views.py:737 +#: part/views.py:739 msgid "Part QR Code" msgstr "" -#: part/views.py:756 +#: part/views.py:758 msgid "Upload Part Image" msgstr "" -#: part/views.py:764 part/views.py:801 +#: part/views.py:766 part/views.py:803 msgid "Updated part image" msgstr "" -#: part/views.py:773 +#: part/views.py:775 msgid "Select Part Image" msgstr "" -#: part/views.py:804 +#: part/views.py:806 msgid "Part image not found" msgstr "" -#: part/views.py:815 +#: part/views.py:817 msgid "Edit Part Properties" msgstr "" -#: part/views.py:839 +#: part/views.py:844 +msgid "Duplicate BOM" +msgstr "" + +#: part/views.py:875 +msgid "Confirm duplication of BOM from parent" +msgstr "" + +#: part/views.py:893 msgid "Validate BOM" msgstr "" -#: part/views.py:867 +#: part/views.py:916 msgid "Confirm that the BOM is valid" msgstr "" -#: part/views.py:1006 +#: part/views.py:924 +msgid "Validated Bill of Materials" +msgstr "" + +#: part/views.py:1058 msgid "No BOM file provided" msgstr "" -#: part/views.py:1356 +#: part/views.py:1408 msgid "Enter a valid quantity" msgstr "" -#: part/views.py:1381 part/views.py:1384 +#: part/views.py:1433 part/views.py:1436 msgid "Select valid part" msgstr "" -#: part/views.py:1390 +#: part/views.py:1442 msgid "Duplicate part selected" msgstr "" -#: part/views.py:1428 +#: part/views.py:1480 msgid "Select a part" msgstr "" -#: part/views.py:1434 +#: part/views.py:1486 msgid "Selected part creates a circular BOM" msgstr "" -#: part/views.py:1438 +#: part/views.py:1490 msgid "Specify quantity" msgstr "" -#: part/views.py:1694 +#: part/views.py:1746 msgid "Confirm Part Deletion" msgstr "" -#: part/views.py:1703 +#: part/views.py:1755 msgid "Part was deleted" msgstr "" -#: part/views.py:1712 +#: part/views.py:1764 msgid "Part Pricing" msgstr "" -#: part/views.py:1838 +#: part/views.py:1890 msgid "Create Part Parameter Template" msgstr "" -#: part/views.py:1848 +#: part/views.py:1900 msgid "Edit Part Parameter Template" msgstr "" -#: part/views.py:1857 +#: part/views.py:1909 msgid "Delete Part Parameter Template" msgstr "" -#: part/views.py:1867 +#: part/views.py:1919 msgid "Create Part Parameter" msgstr "" -#: part/views.py:1919 +#: part/views.py:1971 msgid "Edit Part Parameter" msgstr "" -#: part/views.py:1935 +#: part/views.py:1987 msgid "Delete Part Parameter" msgstr "" -#: part/views.py:1994 +#: part/views.py:2046 msgid "Edit Part Category" msgstr "" -#: part/views.py:2031 +#: part/views.py:2083 msgid "Delete Part Category" msgstr "" -#: part/views.py:2039 +#: part/views.py:2091 msgid "Part category was deleted" msgstr "" -#: part/views.py:2102 +#: part/views.py:2154 msgid "Create BOM Item" msgstr "" -#: part/views.py:2170 +#: part/views.py:2222 msgid "Edit BOM item" msgstr "" -#: part/views.py:2220 +#: part/views.py:2272 msgid "Confim BOM item deletion" msgstr "" @@ -3431,7 +3476,7 @@ msgstr "" msgid "Add note (required)" msgstr "" -#: stock/forms.py:370 stock/views.py:921 stock/views.py:1119 +#: stock/forms.py:370 stock/views.py:912 stock/views.py:1110 msgid "Confirm stock adjustment" msgstr "" @@ -3619,55 +3664,55 @@ msgstr "" msgid "StockItem cannot be moved as it is not in stock" msgstr "" -#: stock/models.py:1306 +#: stock/models.py:1322 msgid "Tracking entry title" msgstr "" -#: stock/models.py:1308 +#: stock/models.py:1324 msgid "Entry notes" msgstr "" -#: stock/models.py:1310 +#: stock/models.py:1326 msgid "Link to external page for further information" msgstr "" -#: stock/models.py:1370 +#: stock/models.py:1386 msgid "Value must be provided for this test" msgstr "" -#: stock/models.py:1376 +#: stock/models.py:1392 msgid "Attachment must be uploaded for this test" msgstr "" -#: stock/models.py:1393 +#: stock/models.py:1409 msgid "Test" msgstr "" -#: stock/models.py:1394 +#: stock/models.py:1410 msgid "Test name" msgstr "" -#: stock/models.py:1399 +#: stock/models.py:1415 msgid "Result" msgstr "" -#: stock/models.py:1400 templates/js/table_filters.js:162 +#: stock/models.py:1416 templates/js/table_filters.js:162 msgid "Test result" msgstr "" -#: stock/models.py:1406 +#: stock/models.py:1422 msgid "Test output value" msgstr "" -#: stock/models.py:1412 +#: stock/models.py:1428 msgid "Attachment" msgstr "" -#: stock/models.py:1413 +#: stock/models.py:1429 msgid "Test result attachment" msgstr "" -#: stock/models.py:1419 +#: stock/models.py:1435 msgid "Test notes" msgstr "" @@ -3937,7 +3982,7 @@ msgstr "" msgid "The following stock items will be uninstalled" msgstr "" -#: stock/templates/stock/stockitem_convert.html:7 stock/views.py:1315 +#: stock/templates/stock/stockitem_convert.html:7 stock/views.py:1306 msgid "Convert Stock Item" msgstr "" @@ -3993,190 +4038,194 @@ msgstr "" msgid "Assign to Customer" msgstr "" -#: stock/views.py:281 +#: stock/views.py:253 +msgid "Customer must be specified" +msgstr "" + +#: stock/views.py:277 msgid "Return to Stock" msgstr "" -#: stock/views.py:301 +#: stock/views.py:287 msgid "Specify a valid location" msgstr "" -#: stock/views.py:305 +#: stock/views.py:298 msgid "Stock item returned from customer" msgstr "" -#: stock/views.py:317 +#: stock/views.py:308 msgid "Select Label Template" msgstr "" -#: stock/views.py:340 +#: stock/views.py:331 msgid "Select valid label" msgstr "" -#: stock/views.py:404 +#: stock/views.py:395 msgid "Delete All Test Data" msgstr "" -#: stock/views.py:420 +#: stock/views.py:411 msgid "Confirm test data deletion" msgstr "" -#: stock/views.py:440 +#: stock/views.py:431 msgid "Add Test Result" msgstr "" -#: stock/views.py:478 +#: stock/views.py:469 msgid "Edit Test Result" msgstr "" -#: stock/views.py:496 +#: stock/views.py:487 msgid "Delete Test Result" msgstr "" -#: stock/views.py:508 +#: stock/views.py:499 msgid "Select Test Report Template" msgstr "" -#: stock/views.py:523 +#: stock/views.py:514 msgid "Select valid template" msgstr "" -#: stock/views.py:576 +#: stock/views.py:567 msgid "Stock Export Options" msgstr "" -#: stock/views.py:698 +#: stock/views.py:689 msgid "Stock Item QR Code" msgstr "" -#: stock/views.py:724 +#: stock/views.py:715 msgid "Install Stock Item" msgstr "" -#: stock/views.py:824 +#: stock/views.py:815 msgid "Uninstall Stock Items" msgstr "" -#: stock/views.py:932 +#: stock/views.py:923 msgid "Uninstalled stock items" msgstr "" -#: stock/views.py:957 +#: stock/views.py:948 msgid "Adjust Stock" msgstr "" -#: stock/views.py:1067 +#: stock/views.py:1058 msgid "Move Stock Items" msgstr "" -#: stock/views.py:1068 +#: stock/views.py:1059 msgid "Count Stock Items" msgstr "" -#: stock/views.py:1069 +#: stock/views.py:1060 msgid "Remove From Stock" msgstr "" -#: stock/views.py:1070 +#: stock/views.py:1061 msgid "Add Stock Items" msgstr "" -#: stock/views.py:1071 +#: stock/views.py:1062 msgid "Delete Stock Items" msgstr "" -#: stock/views.py:1099 +#: stock/views.py:1090 msgid "Must enter integer value" msgstr "" -#: stock/views.py:1104 +#: stock/views.py:1095 msgid "Quantity must be positive" msgstr "" -#: stock/views.py:1111 +#: stock/views.py:1102 #, python-brace-format msgid "Quantity must not exceed {x}" msgstr "" -#: stock/views.py:1190 +#: stock/views.py:1181 #, python-brace-format msgid "Added stock to {n} items" msgstr "" -#: stock/views.py:1205 +#: stock/views.py:1196 #, python-brace-format msgid "Removed stock from {n} items" msgstr "" -#: stock/views.py:1218 +#: stock/views.py:1209 #, python-brace-format msgid "Counted stock for {n} items" msgstr "" -#: stock/views.py:1246 +#: stock/views.py:1237 msgid "No items were moved" msgstr "" -#: stock/views.py:1249 +#: stock/views.py:1240 #, python-brace-format msgid "Moved {n} items to {dest}" msgstr "" -#: stock/views.py:1268 +#: stock/views.py:1259 #, python-brace-format msgid "Deleted {n} stock items" msgstr "" -#: stock/views.py:1280 +#: stock/views.py:1271 msgid "Edit Stock Item" msgstr "" -#: stock/views.py:1365 +#: stock/views.py:1356 msgid "Serialize Stock" msgstr "" -#: stock/views.py:1558 +#: stock/views.py:1549 msgid "Duplicate Stock Item" msgstr "" -#: stock/views.py:1624 +#: stock/views.py:1615 msgid "Invalid quantity" msgstr "" -#: stock/views.py:1627 +#: stock/views.py:1618 msgid "Quantity cannot be less than zero" msgstr "" -#: stock/views.py:1631 +#: stock/views.py:1622 msgid "Invalid part selection" msgstr "" -#: stock/views.py:1679 +#: stock/views.py:1670 #, python-brace-format msgid "Created {n} new stock items" msgstr "" -#: stock/views.py:1698 stock/views.py:1714 +#: stock/views.py:1689 stock/views.py:1705 msgid "Created new stock item" msgstr "" -#: stock/views.py:1733 +#: stock/views.py:1724 msgid "Delete Stock Location" msgstr "" -#: stock/views.py:1747 +#: stock/views.py:1738 msgid "Delete Stock Item" msgstr "" -#: stock/views.py:1759 +#: stock/views.py:1750 msgid "Delete Stock Tracking Entry" msgstr "" -#: stock/views.py:1778 +#: stock/views.py:1769 msgid "Edit Stock Tracking Entry" msgstr "" -#: stock/views.py:1788 +#: stock/views.py:1779 msgid "Add Stock Tracking Entry" msgstr "" @@ -4525,8 +4574,9 @@ msgstr "" msgid "Virtual part" msgstr "" -#: templates/js/bom.js:164 -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:169 @@ -4633,11 +4683,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 55616d2c88..446564bb29 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 04:20+0000\n" +"POT-Creation-Date: 2020-10-30 11:43+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -46,7 +46,7 @@ msgstr "" msgid "Apply Theme" msgstr "" -#: InvenTree/helpers.py:361 order/models.py:187 order/models.py:261 +#: InvenTree/helpers.py:361 order/models.py:187 order/models.py:269 msgid "Invalid quantity provided" msgstr "" @@ -202,27 +202,27 @@ msgstr "" msgid "Overage must be an integer value or a percentage" msgstr "" -#: InvenTree/views.py:466 +#: InvenTree/views.py:518 msgid "Delete Item" msgstr "" -#: InvenTree/views.py:515 +#: InvenTree/views.py:567 msgid "Check box to confirm item deletion" msgstr "" -#: InvenTree/views.py:530 templates/InvenTree/settings/user.html:18 +#: InvenTree/views.py:582 templates/InvenTree/settings/user.html:18 msgid "Edit User Information" msgstr "" -#: InvenTree/views.py:541 templates/InvenTree/settings/user.html:22 +#: InvenTree/views.py:593 templates/InvenTree/settings/user.html:22 msgid "Set Password" msgstr "" -#: InvenTree/views.py:560 +#: InvenTree/views.py:612 msgid "Password fields must match" msgstr "" -#: InvenTree/views.py:730 +#: InvenTree/views.py:782 msgid "Database Statistics" msgstr "" @@ -302,7 +302,7 @@ msgstr "" msgid "Confirm build completion" msgstr "" -#: build/forms.py:159 build/views.py:77 +#: build/forms.py:159 build/views.py:68 msgid "Confirm build cancellation" msgstr "" @@ -364,7 +364,7 @@ msgstr "" #: build/models.py:98 build/templates/build/allocate.html:366 #: build/templates/build/auto_allocate.html:25 #: build/templates/build/build_base.html:73 -#: build/templates/build/detail.html:24 order/models.py:501 +#: build/templates/build/detail.html:24 order/models.py:519 #: order/templates/order/order_wizard/select_parts.html:30 #: order/templates/order/purchase_order_detail.html:148 #: order/templates/order/receive_parts.html:19 part/models.py:293 @@ -456,7 +456,7 @@ msgstr "" #: order/templates/order/purchase_order_detail.html:203 #: 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:461 -#: stock/models.py:1418 stock/templates/stock/tabs.html:26 +#: stock/models.py:1434 stock/templates/stock/tabs.html:26 #: templates/js/barcode.js:391 templates/js/bom.js:264 #: templates/js/stock.js:116 templates/js/stock.js:571 msgid "Notes" @@ -492,11 +492,11 @@ msgstr "" msgid "Allocated quantity ({n}) must not exceed available quantity ({q})" msgstr "" -#: build/models.py:803 order/models.py:585 +#: build/models.py:803 order/models.py:603 msgid "StockItem is over-allocated" msgstr "" -#: build/models.py:807 order/models.py:588 +#: build/models.py:807 order/models.py:606 msgid "Allocation quantity must be greater than zero" msgstr "" @@ -543,7 +543,7 @@ msgid "Assigned Stock" msgstr "" #: build/templates/build/allocate.html:28 -#: company/templates/company/detail_part.html:28 order/views.py:804 +#: company/templates/company/detail_part.html:28 order/views.py:774 #: part/templates/part/category.html:125 msgid "Order Parts" msgstr "" @@ -565,7 +565,7 @@ msgstr "" msgid "New Stock Item" msgstr "" -#: build/templates/build/allocate.html:125 stock/views.py:1459 +#: build/templates/build/allocate.html:125 stock/views.py:1450 #: templates/js/build.js:226 msgid "Create new Stock Item" msgstr "" @@ -624,7 +624,7 @@ msgstr "" msgid "No BOM items found" msgstr "" -#: build/templates/build/allocate.html:384 part/models.py:1448 +#: build/templates/build/allocate.html:384 part/models.py:1490 #: templates/js/part.js:569 templates/js/table_filters.js:172 msgid "Required" msgstr "" @@ -716,7 +716,7 @@ msgstr "" msgid "Edit Build" msgstr "" -#: build/templates/build/build_base.html:50 build/views.py:300 +#: build/templates/build/build_base.html:50 build/views.py:295 msgid "Complete Build" msgstr "" @@ -724,7 +724,7 @@ msgstr "" msgid "Cancel Build" msgstr "" -#: build/templates/build/build_base.html:59 build/views.py:632 +#: build/templates/build/build_base.html:59 build/views.py:627 msgid "Delete Build" msgstr "" @@ -748,7 +748,7 @@ msgid "Progress" msgstr "" #: build/templates/build/build_base.html:101 -#: build/templates/build/detail.html:82 order/models.py:499 +#: build/templates/build/detail.html:82 order/models.py:517 #: order/templates/order/sales_order_base.html:9 #: order/templates/order/sales_order_base.html:33 #: order/templates/order/sales_order_notes.html:10 @@ -879,7 +879,7 @@ msgstr "" msgid "Alter the quantity of stock allocated to the build output" msgstr "" -#: build/templates/build/index.html:25 build/views.py:507 +#: build/templates/build/index.html:25 build/views.py:502 msgid "New Build Order" msgstr "" @@ -918,137 +918,137 @@ msgstr "" msgid "All incomplete stock allocations will be removed from the build" msgstr "" -#: build/views.py:82 +#: build/views.py:79 msgid "Build was cancelled" msgstr "" -#: build/views.py:98 +#: build/views.py:93 msgid "Allocate Stock" msgstr "" -#: build/views.py:126 +#: build/views.py:121 msgid "No matching build found" msgstr "" -#: build/views.py:157 +#: build/views.py:152 msgid "Confirm stock allocation" msgstr "" -#: build/views.py:158 +#: build/views.py:153 msgid "Check the confirmation box at the bottom of the list" msgstr "" -#: build/views.py:176 templates/js/build.js:85 +#: build/views.py:171 templates/js/build.js:85 msgid "Delete build output" msgstr "" -#: build/views.py:210 +#: build/views.py:205 msgid "Build or output not specified" msgstr "" -#: build/views.py:212 build/views.py:276 +#: build/views.py:207 build/views.py:271 msgid "Confirm unallocation of build stock" msgstr "" -#: build/views.py:213 build/views.py:277 stock/views.py:421 +#: build/views.py:208 build/views.py:272 stock/views.py:412 msgid "Check the confirmation box" msgstr "" -#: build/views.py:230 build/views.py:643 +#: build/views.py:225 build/views.py:638 msgid "Unallocate Stock" msgstr "" -#: build/views.py:379 +#: build/views.py:374 msgid "Confirm completion of build" msgstr "" -#: build/views.py:385 +#: build/views.py:380 msgid "Invalid location selected" msgstr "" -#: build/views.py:406 +#: build/views.py:401 #, python-brace-format msgid "The following serial numbers already exist: ({sn})" msgstr "" -#: build/views.py:415 +#: build/views.py:410 msgid "Build could not be completed" msgstr "" -#: build/views.py:427 +#: build/views.py:422 msgid "Build marked as COMPLETE" msgstr "" -#: build/views.py:556 +#: build/views.py:551 msgid "Created new build" msgstr "" -#: build/views.py:573 +#: build/views.py:568 msgid "Trackable part must have serial numbers specified" msgstr "" -#: build/views.py:594 stock/models.py:838 stock/views.py:1650 +#: build/views.py:589 stock/models.py:838 stock/views.py:1641 msgid "Serial numbers already exist" msgstr "" -#: build/views.py:617 +#: build/views.py:612 msgid "Edit Build Details" msgstr "" -#: build/views.py:623 +#: build/views.py:618 msgid "Edited build" msgstr "" -#: build/views.py:649 +#: build/views.py:644 msgid "Removed parts from build allocation" msgstr "" -#: build/views.py:661 +#: build/views.py:656 msgid "Allocate stock to build output" msgstr "" -#: build/views.py:703 +#: build/views.py:698 msgid "Item must be currently in stock" msgstr "" -#: build/views.py:709 +#: build/views.py:704 msgid "Stock item is over-allocated" msgstr "" -#: build/views.py:710 +#: build/views.py:705 msgid "Avaialabe" msgstr "" -#: build/views.py:872 +#: build/views.py:867 msgid "Edit Stock Allocation" msgstr "" -#: build/views.py:877 +#: build/views.py:872 msgid "Updated Build Item" msgstr "" -#: build/views.py:906 +#: build/views.py:901 msgid "Add Build Order Attachment" msgstr "" -#: build/views.py:915 order/views.py:109 order/views.py:157 part/views.py:92 +#: build/views.py:910 order/views.py:109 order/views.py:157 part/views.py:92 #: stock/views.py:175 msgid "Added attachment" msgstr "" -#: build/views.py:951 order/views.py:184 order/views.py:206 +#: build/views.py:946 order/views.py:184 order/views.py:206 msgid "Edit Attachment" msgstr "" -#: build/views.py:962 order/views.py:189 order/views.py:211 +#: build/views.py:957 order/views.py:189 order/views.py:211 msgid "Attachment updated" msgstr "" -#: build/views.py:972 order/views.py:226 order/views.py:241 +#: build/views.py:967 order/views.py:226 order/views.py:241 msgid "Delete Attachment" msgstr "" -#: build/views.py:978 order/views.py:233 order/views.py:248 stock/views.py:233 +#: build/views.py:973 order/views.py:233 order/views.py:248 stock/views.py:233 msgid "Deleted attachment" msgstr "" @@ -1403,7 +1403,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" @@ -1521,7 +1521,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:2230 +#: part/templates/part/sale_prices.html:13 part/views.py:2282 msgid "Add Price Break" msgstr "" @@ -1648,15 +1648,15 @@ msgstr "" msgid "Delete Supplier Part" msgstr "" -#: company/views.py:416 part/views.py:2236 +#: company/views.py:416 part/views.py:2288 msgid "Added new price break" msgstr "" -#: company/views.py:453 part/views.py:2281 +#: company/views.py:453 part/views.py:2333 msgid "Edit Price Break" msgstr "" -#: company/views.py:469 part/views.py:2297 +#: company/views.py:469 part/views.py:2349 msgid "Delete Price Break" msgstr "" @@ -1729,7 +1729,7 @@ msgstr "" msgid "Order notes" msgstr "" -#: order/models.py:140 order/models.py:318 +#: order/models.py:140 order/models.py:326 msgid "Purchase order status" msgstr "" @@ -1749,7 +1749,7 @@ msgstr "" msgid "Date order was completed" msgstr "" -#: order/models.py:185 order/models.py:259 part/views.py:1347 +#: order/models.py:185 order/models.py:267 part/views.py:1399 #: stock/models.py:249 stock/models.py:822 msgid "Quantity must be greater than zero" msgstr "" @@ -1758,69 +1758,69 @@ msgstr "" msgid "Part supplier must match PO supplier" msgstr "" -#: order/models.py:254 +#: order/models.py:262 msgid "Lines can only be received against an order marked as 'Placed'" msgstr "" -#: order/models.py:314 +#: order/models.py:322 msgid "Company to which the items are being sold" msgstr "" -#: order/models.py:320 +#: order/models.py:328 msgid "Customer order reference code" msgstr "" -#: order/models.py:359 +#: order/models.py:367 msgid "SalesOrder cannot be shipped as it is not currently pending" msgstr "" -#: order/models.py:436 +#: order/models.py:454 msgid "Item quantity" msgstr "" -#: order/models.py:438 +#: order/models.py:456 msgid "Line item reference" msgstr "" -#: order/models.py:440 +#: order/models.py:458 msgid "Line item notes" msgstr "" -#: order/models.py:466 order/templates/order/order_base.html:9 +#: order/models.py:484 order/templates/order/order_base.html:9 #: order/templates/order/order_base.html:24 #: stock/templates/stock/item_base.html:265 templates/js/order.js:139 msgid "Purchase Order" msgstr "" -#: order/models.py:479 +#: order/models.py:497 msgid "Supplier part" msgstr "" -#: order/models.py:482 +#: order/models.py:500 msgid "Number of items received" msgstr "" -#: order/models.py:576 +#: order/models.py:594 msgid "Cannot allocate stock item to a line with a different part" msgstr "" -#: order/models.py:578 +#: order/models.py:596 msgid "Cannot allocate stock to a line without a part" msgstr "" -#: order/models.py:581 +#: order/models.py:599 msgid "Allocation quantity cannot exceed stock quantity" msgstr "" -#: order/models.py:591 +#: order/models.py:609 msgid "Quantity must be 1 for serialized stock item" msgstr "" -#: order/models.py:608 +#: order/models.py:626 msgid "Select stock item to allocate" msgstr "" -#: order/models.py:611 +#: order/models.py:629 msgid "Enter stock allocation quantity" msgstr "" @@ -1938,8 +1938,8 @@ msgid "Line Items" msgstr "" #: order/templates/order/purchase_order_detail.html:17 -#: order/templates/order/sales_order_detail.html:19 order/views.py:1117 -#: order/views.py:1232 +#: order/templates/order/sales_order_detail.html:19 order/views.py:1087 +#: order/views.py:1171 msgid "Add Line Item" msgstr "" @@ -2021,6 +2021,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 "" @@ -2107,99 +2108,107 @@ msgstr "" msgid "Cancel Order" msgstr "" -#: order/views.py:418 order/views.py:451 +#: order/views.py:412 order/views.py:436 msgid "Confirm order cancellation" msgstr "" -#: order/views.py:436 +#: order/views.py:415 order/views.py:439 +msgid "Order cannot be cancelled" +msgstr "" + +#: order/views.py:426 msgid "Cancel sales order" msgstr "" -#: order/views.py:457 -msgid "Could not cancel order" -msgstr "" - -#: order/views.py:471 +#: order/views.py:450 msgid "Issue Order" msgstr "" -#: order/views.py:487 +#: order/views.py:460 msgid "Confirm order placement" msgstr "" -#: order/views.py:508 +#: order/views.py:468 +msgid "Purchase order issued" +msgstr "" + +#: order/views.py:479 msgid "Complete Order" msgstr "" -#: order/views.py:544 +#: order/views.py:496 +msgid "Confirm order completion" +msgstr "" + +#: order/views.py:504 +msgid "Purchase order completed" +msgstr "" + +#: order/views.py:514 msgid "Ship Order" msgstr "" -#: order/views.py:561 +#: order/views.py:531 msgid "Confirm order shipment" msgstr "" -#: order/views.py:567 +#: order/views.py:537 msgid "Could not ship order" msgstr "" -#: order/views.py:619 +#: order/views.py:589 msgid "Receive Parts" msgstr "" -#: order/views.py:687 +#: order/views.py:657 msgid "Items received" msgstr "" -#: order/views.py:701 +#: order/views.py:671 msgid "No destination set" msgstr "" -#: order/views.py:746 +#: order/views.py:716 msgid "Error converting quantity to number" msgstr "" -#: order/views.py:752 +#: order/views.py:722 msgid "Receive quantity less than zero" msgstr "" -#: order/views.py:758 +#: order/views.py:728 msgid "No lines specified" msgstr "" -#: order/views.py:1138 -msgid "Invalid Purchase Order" +#: order/views.py:1097 +msgid "Supplier part must be specified" msgstr "" -#: order/views.py:1146 +#: order/views.py:1103 msgid "Supplier must match for Part and Order" msgstr "" -#: order/views.py:1151 -msgid "Invalid SupplierPart selection" -msgstr "" - -#: order/views.py:1284 order/views.py:1303 +#: order/views.py:1223 order/views.py:1242 msgid "Edit Line Item" msgstr "" -#: order/views.py:1320 order/views.py:1333 +#: order/views.py:1259 order/views.py:1272 msgid "Delete Line Item" msgstr "" -#: order/views.py:1326 order/views.py:1339 +#: order/views.py:1265 order/views.py:1278 msgid "Deleted line item" msgstr "" -#: order/views.py:1348 +#: order/views.py:1287 msgid "Allocate Stock to Order" msgstr "" -#: order/views.py:1418 +#: order/views.py:1357 msgid "Edit Allocation Quantity" msgstr "" -#: order/views.py:1434 +#: order/views.py:1373 msgid "Remove allocation" msgstr "" @@ -2225,91 +2234,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:1569 +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 "" @@ -2435,112 +2460,108 @@ msgstr "" msgid "Stored BOM checksum" msgstr "" -#: part/models.py:1400 +#: part/models.py:1442 msgid "Test templates can only be created for trackable parts" msgstr "" -#: part/models.py:1417 +#: part/models.py:1459 msgid "Test with this name already exists for this part" msgstr "" -#: part/models.py:1436 templates/js/part.js:560 templates/js/stock.js:92 +#: part/models.py:1478 templates/js/part.js:560 templates/js/stock.js:92 msgid "Test Name" msgstr "" -#: part/models.py:1437 +#: part/models.py:1479 msgid "Enter a name for the test" msgstr "" -#: part/models.py:1442 +#: part/models.py:1484 msgid "Test Description" msgstr "" -#: part/models.py:1443 +#: part/models.py:1485 msgid "Enter description for this test" msgstr "" -#: part/models.py:1449 +#: part/models.py:1491 msgid "Is this test required to pass?" msgstr "" -#: part/models.py:1454 templates/js/part.js:577 +#: part/models.py:1496 templates/js/part.js:577 msgid "Requires Value" msgstr "" -#: part/models.py:1455 +#: part/models.py:1497 msgid "Does this test require a value when adding a test result?" msgstr "" -#: part/models.py:1460 templates/js/part.js:584 +#: part/models.py:1502 templates/js/part.js:584 msgid "Requires Attachment" msgstr "" -#: part/models.py:1461 +#: part/models.py:1503 msgid "Does this test require a file attachment when adding a test result?" msgstr "" -#: part/models.py:1494 +#: part/models.py:1536 msgid "Parameter template name must be unique" msgstr "" -#: part/models.py:1499 +#: part/models.py:1541 msgid "Parameter Name" msgstr "" -#: part/models.py:1501 +#: part/models.py:1543 msgid "Parameter Units" msgstr "" -#: part/models.py:1527 -msgid "Parent Part" -msgstr "" - -#: part/models.py:1529 +#: part/models.py:1571 msgid "Parameter Template" msgstr "" -#: part/models.py:1531 +#: part/models.py:1573 msgid "Parameter Value" msgstr "" -#: part/models.py:1568 +#: part/models.py:1610 msgid "Select parent part" msgstr "" -#: part/models.py:1576 +#: part/models.py:1618 msgid "Select part to be used in BOM" msgstr "" -#: part/models.py:1582 +#: part/models.py:1624 msgid "BOM quantity for this BOM item" msgstr "" -#: part/models.py:1584 +#: part/models.py:1626 msgid "This BOM item is optional" msgstr "" -#: part/models.py:1587 +#: part/models.py:1629 msgid "Estimated build wastage quantity (absolute or percentage)" msgstr "" -#: part/models.py:1590 +#: part/models.py:1632 msgid "BOM item reference" msgstr "" -#: part/models.py:1593 +#: part/models.py:1635 msgid "BOM item notes" msgstr "" -#: part/models.py:1595 +#: part/models.py:1637 msgid "BOM line checksum" msgstr "" -#: part/models.py:1662 part/views.py:1353 part/views.py:1405 +#: part/models.py:1704 part/views.py:1405 part/views.py:1457 #: stock/models.py:239 msgid "Quantity must be integer value for trackable parts" msgstr "" -#: part/models.py:1678 +#: part/models.py:1720 msgid "BOM Item" msgstr "" @@ -2582,54 +2603,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:1644 +#: part/templates/part/bom.html:66 part/views.py:1696 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 @@ -2710,7 +2743,7 @@ msgstr "" msgid "All parts" msgstr "" -#: part/templates/part/category.html:24 part/views.py:2047 +#: part/templates/part/category.html:24 part/views.py:2099 msgid "Create new part category" msgstr "" @@ -2782,7 +2815,7 @@ msgstr "" msgid "Create new Part Category" msgstr "" -#: part/templates/part/category.html:216 stock/views.py:1343 +#: part/templates/part/category.html:216 stock/views.py:1334 msgid "Create new Stock Location" msgstr "" @@ -2967,7 +3000,7 @@ msgstr "" msgid "New Parameter" msgstr "" -#: part/templates/part/params.html:25 stock/models.py:1405 +#: part/templates/part/params.html:25 stock/models.py:1421 #: templates/js/stock.js:112 msgid "Value" msgstr "" @@ -3215,143 +3248,155 @@ msgstr "" msgid "Create Variant" msgstr "" -#: part/views.py:388 +#: part/views.py:390 msgid "Duplicate Part" msgstr "" -#: part/views.py:395 +#: part/views.py:397 msgid "Copied part" msgstr "" -#: part/views.py:449 part/views.py:579 +#: part/views.py:451 part/views.py:581 msgid "Possible matches exist - confirm creation of new part" msgstr "" -#: part/views.py:514 templates/js/stock.js:833 +#: part/views.py:516 templates/js/stock.js:833 msgid "Create New Part" msgstr "" -#: part/views.py:521 +#: part/views.py:523 msgid "Created new part" msgstr "" -#: part/views.py:737 +#: part/views.py:739 msgid "Part QR Code" msgstr "" -#: part/views.py:756 +#: part/views.py:758 msgid "Upload Part Image" msgstr "" -#: part/views.py:764 part/views.py:801 +#: part/views.py:766 part/views.py:803 msgid "Updated part image" msgstr "" -#: part/views.py:773 +#: part/views.py:775 msgid "Select Part Image" msgstr "" -#: part/views.py:804 +#: part/views.py:806 msgid "Part image not found" msgstr "" -#: part/views.py:815 +#: part/views.py:817 msgid "Edit Part Properties" msgstr "" -#: part/views.py:839 +#: part/views.py:844 +msgid "Duplicate BOM" +msgstr "" + +#: part/views.py:875 +msgid "Confirm duplication of BOM from parent" +msgstr "" + +#: part/views.py:893 msgid "Validate BOM" msgstr "" -#: part/views.py:867 +#: part/views.py:916 msgid "Confirm that the BOM is valid" msgstr "" -#: part/views.py:1006 +#: part/views.py:924 +msgid "Validated Bill of Materials" +msgstr "" + +#: part/views.py:1058 msgid "No BOM file provided" msgstr "" -#: part/views.py:1356 +#: part/views.py:1408 msgid "Enter a valid quantity" msgstr "" -#: part/views.py:1381 part/views.py:1384 +#: part/views.py:1433 part/views.py:1436 msgid "Select valid part" msgstr "" -#: part/views.py:1390 +#: part/views.py:1442 msgid "Duplicate part selected" msgstr "" -#: part/views.py:1428 +#: part/views.py:1480 msgid "Select a part" msgstr "" -#: part/views.py:1434 +#: part/views.py:1486 msgid "Selected part creates a circular BOM" msgstr "" -#: part/views.py:1438 +#: part/views.py:1490 msgid "Specify quantity" msgstr "" -#: part/views.py:1694 +#: part/views.py:1746 msgid "Confirm Part Deletion" msgstr "" -#: part/views.py:1703 +#: part/views.py:1755 msgid "Part was deleted" msgstr "" -#: part/views.py:1712 +#: part/views.py:1764 msgid "Part Pricing" msgstr "" -#: part/views.py:1838 +#: part/views.py:1890 msgid "Create Part Parameter Template" msgstr "" -#: part/views.py:1848 +#: part/views.py:1900 msgid "Edit Part Parameter Template" msgstr "" -#: part/views.py:1857 +#: part/views.py:1909 msgid "Delete Part Parameter Template" msgstr "" -#: part/views.py:1867 +#: part/views.py:1919 msgid "Create Part Parameter" msgstr "" -#: part/views.py:1919 +#: part/views.py:1971 msgid "Edit Part Parameter" msgstr "" -#: part/views.py:1935 +#: part/views.py:1987 msgid "Delete Part Parameter" msgstr "" -#: part/views.py:1994 +#: part/views.py:2046 msgid "Edit Part Category" msgstr "" -#: part/views.py:2031 +#: part/views.py:2083 msgid "Delete Part Category" msgstr "" -#: part/views.py:2039 +#: part/views.py:2091 msgid "Part category was deleted" msgstr "" -#: part/views.py:2102 +#: part/views.py:2154 msgid "Create BOM Item" msgstr "" -#: part/views.py:2170 +#: part/views.py:2222 msgid "Edit BOM item" msgstr "" -#: part/views.py:2220 +#: part/views.py:2272 msgid "Confim BOM item deletion" msgstr "" @@ -3431,7 +3476,7 @@ msgstr "" msgid "Add note (required)" msgstr "" -#: stock/forms.py:370 stock/views.py:921 stock/views.py:1119 +#: stock/forms.py:370 stock/views.py:912 stock/views.py:1110 msgid "Confirm stock adjustment" msgstr "" @@ -3619,55 +3664,55 @@ msgstr "" msgid "StockItem cannot be moved as it is not in stock" msgstr "" -#: stock/models.py:1306 +#: stock/models.py:1322 msgid "Tracking entry title" msgstr "" -#: stock/models.py:1308 +#: stock/models.py:1324 msgid "Entry notes" msgstr "" -#: stock/models.py:1310 +#: stock/models.py:1326 msgid "Link to external page for further information" msgstr "" -#: stock/models.py:1370 +#: stock/models.py:1386 msgid "Value must be provided for this test" msgstr "" -#: stock/models.py:1376 +#: stock/models.py:1392 msgid "Attachment must be uploaded for this test" msgstr "" -#: stock/models.py:1393 +#: stock/models.py:1409 msgid "Test" msgstr "" -#: stock/models.py:1394 +#: stock/models.py:1410 msgid "Test name" msgstr "" -#: stock/models.py:1399 +#: stock/models.py:1415 msgid "Result" msgstr "" -#: stock/models.py:1400 templates/js/table_filters.js:162 +#: stock/models.py:1416 templates/js/table_filters.js:162 msgid "Test result" msgstr "" -#: stock/models.py:1406 +#: stock/models.py:1422 msgid "Test output value" msgstr "" -#: stock/models.py:1412 +#: stock/models.py:1428 msgid "Attachment" msgstr "" -#: stock/models.py:1413 +#: stock/models.py:1429 msgid "Test result attachment" msgstr "" -#: stock/models.py:1419 +#: stock/models.py:1435 msgid "Test notes" msgstr "" @@ -3937,7 +3982,7 @@ msgstr "" msgid "The following stock items will be uninstalled" msgstr "" -#: stock/templates/stock/stockitem_convert.html:7 stock/views.py:1315 +#: stock/templates/stock/stockitem_convert.html:7 stock/views.py:1306 msgid "Convert Stock Item" msgstr "" @@ -3993,190 +4038,194 @@ msgstr "" msgid "Assign to Customer" msgstr "" -#: stock/views.py:281 +#: stock/views.py:253 +msgid "Customer must be specified" +msgstr "" + +#: stock/views.py:277 msgid "Return to Stock" msgstr "" -#: stock/views.py:301 +#: stock/views.py:287 msgid "Specify a valid location" msgstr "" -#: stock/views.py:305 +#: stock/views.py:298 msgid "Stock item returned from customer" msgstr "" -#: stock/views.py:317 +#: stock/views.py:308 msgid "Select Label Template" msgstr "" -#: stock/views.py:340 +#: stock/views.py:331 msgid "Select valid label" msgstr "" -#: stock/views.py:404 +#: stock/views.py:395 msgid "Delete All Test Data" msgstr "" -#: stock/views.py:420 +#: stock/views.py:411 msgid "Confirm test data deletion" msgstr "" -#: stock/views.py:440 +#: stock/views.py:431 msgid "Add Test Result" msgstr "" -#: stock/views.py:478 +#: stock/views.py:469 msgid "Edit Test Result" msgstr "" -#: stock/views.py:496 +#: stock/views.py:487 msgid "Delete Test Result" msgstr "" -#: stock/views.py:508 +#: stock/views.py:499 msgid "Select Test Report Template" msgstr "" -#: stock/views.py:523 +#: stock/views.py:514 msgid "Select valid template" msgstr "" -#: stock/views.py:576 +#: stock/views.py:567 msgid "Stock Export Options" msgstr "" -#: stock/views.py:698 +#: stock/views.py:689 msgid "Stock Item QR Code" msgstr "" -#: stock/views.py:724 +#: stock/views.py:715 msgid "Install Stock Item" msgstr "" -#: stock/views.py:824 +#: stock/views.py:815 msgid "Uninstall Stock Items" msgstr "" -#: stock/views.py:932 +#: stock/views.py:923 msgid "Uninstalled stock items" msgstr "" -#: stock/views.py:957 +#: stock/views.py:948 msgid "Adjust Stock" msgstr "" -#: stock/views.py:1067 +#: stock/views.py:1058 msgid "Move Stock Items" msgstr "" -#: stock/views.py:1068 +#: stock/views.py:1059 msgid "Count Stock Items" msgstr "" -#: stock/views.py:1069 +#: stock/views.py:1060 msgid "Remove From Stock" msgstr "" -#: stock/views.py:1070 +#: stock/views.py:1061 msgid "Add Stock Items" msgstr "" -#: stock/views.py:1071 +#: stock/views.py:1062 msgid "Delete Stock Items" msgstr "" -#: stock/views.py:1099 +#: stock/views.py:1090 msgid "Must enter integer value" msgstr "" -#: stock/views.py:1104 +#: stock/views.py:1095 msgid "Quantity must be positive" msgstr "" -#: stock/views.py:1111 +#: stock/views.py:1102 #, python-brace-format msgid "Quantity must not exceed {x}" msgstr "" -#: stock/views.py:1190 +#: stock/views.py:1181 #, python-brace-format msgid "Added stock to {n} items" msgstr "" -#: stock/views.py:1205 +#: stock/views.py:1196 #, python-brace-format msgid "Removed stock from {n} items" msgstr "" -#: stock/views.py:1218 +#: stock/views.py:1209 #, python-brace-format msgid "Counted stock for {n} items" msgstr "" -#: stock/views.py:1246 +#: stock/views.py:1237 msgid "No items were moved" msgstr "" -#: stock/views.py:1249 +#: stock/views.py:1240 #, python-brace-format msgid "Moved {n} items to {dest}" msgstr "" -#: stock/views.py:1268 +#: stock/views.py:1259 #, python-brace-format msgid "Deleted {n} stock items" msgstr "" -#: stock/views.py:1280 +#: stock/views.py:1271 msgid "Edit Stock Item" msgstr "" -#: stock/views.py:1365 +#: stock/views.py:1356 msgid "Serialize Stock" msgstr "" -#: stock/views.py:1558 +#: stock/views.py:1549 msgid "Duplicate Stock Item" msgstr "" -#: stock/views.py:1624 +#: stock/views.py:1615 msgid "Invalid quantity" msgstr "" -#: stock/views.py:1627 +#: stock/views.py:1618 msgid "Quantity cannot be less than zero" msgstr "" -#: stock/views.py:1631 +#: stock/views.py:1622 msgid "Invalid part selection" msgstr "" -#: stock/views.py:1679 +#: stock/views.py:1670 #, python-brace-format msgid "Created {n} new stock items" msgstr "" -#: stock/views.py:1698 stock/views.py:1714 +#: stock/views.py:1689 stock/views.py:1705 msgid "Created new stock item" msgstr "" -#: stock/views.py:1733 +#: stock/views.py:1724 msgid "Delete Stock Location" msgstr "" -#: stock/views.py:1747 +#: stock/views.py:1738 msgid "Delete Stock Item" msgstr "" -#: stock/views.py:1759 +#: stock/views.py:1750 msgid "Delete Stock Tracking Entry" msgstr "" -#: stock/views.py:1778 +#: stock/views.py:1769 msgid "Edit Stock Tracking Entry" msgstr "" -#: stock/views.py:1788 +#: stock/views.py:1779 msgid "Add Stock Tracking Entry" msgstr "" @@ -4525,8 +4574,9 @@ msgstr "" msgid "Virtual part" msgstr "" -#: templates/js/bom.js:164 -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:169 @@ -4633,11 +4683,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/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/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/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 b4d1271752..2db5b44c4b 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): @@ -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(self.request.POST.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,23 +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 """ @@ -1117,52 +1087,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/api.py b/InvenTree/part/api.py index beab2ffa79..496153a6d0 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -786,12 +786,11 @@ class BomList(generics.ListCreateAPIView): 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) 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 4f0ef0e3e3..f2d04f40cb 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -1134,6 +1134,60 @@ 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() + + @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(): + + # 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, @@ -1153,24 +1207,12 @@ 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): - # 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/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 2c00c0af3e..2e75f22aac 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_setting('PART_COPY_PARAMETERS') return initials @@ -832,8 +834,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") @@ -854,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 da07bda446..e0eb0ec83e 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -1143,6 +1143,22 @@ class StockItem(MPTTModel): return s + @transaction.atomic + def clear_test_results(self, **kwargs): + """ + Remove all test results + + kwargs: + TODO + """ + + # 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 563ac582df..b25022fbdc 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 { @@ -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): """ @@ -440,11 +431,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): diff --git a/InvenTree/templates/js/bom.js b/InvenTree/templates/js/bom.js index a96014610a..64703a5097 100644 --- a/InvenTree/templates/js/bom.js +++ b/InvenTree/templates/js/bom.js @@ -102,7 +102,7 @@ function loadBomTable(table, options) { * * BOM data are retrieved from the server via AJAX query */ - + var params = { part: options.parent_id, ordering: 'name', @@ -111,22 +111,22 @@ function loadBomTable(table, options) { if (options.part_detail) { params.part_detail = true; } - - if (options.sub_part_detail) { - params.sub_part_detail = true; - } - + + params.sub_part_detail = true; + var filters = {}; if (!options.disableFilters) { - filters = loadTableFilters("bom"); + filters = loadTableFilters('bom'); } for (var key in params) { filters[key] = params[key]; } - setupFilterList("bom", $(table)); + setupFilterList('bom', $(table)); + + // Construct the table columns var cols = []; @@ -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 @@ -357,7 +357,7 @@ 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: filters, original: params,