mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Replace "addTrasactionNote" function with "add_tracking_entry"
- Does not add translated strings to the database
This commit is contained in:
parent
82c6d10c33
commit
af53b341f0
@ -22,7 +22,7 @@ from markdownx.models import MarkdownxField
|
||||
|
||||
from mptt.models import MPTTModel, TreeForeignKey
|
||||
|
||||
from InvenTree.status_codes import BuildStatus, StockStatus
|
||||
from InvenTree.status_codes import BuildStatus, StockStatus, StockHistoryCode
|
||||
from InvenTree.helpers import increment, getSetting, normalize, MakeBarcode
|
||||
from InvenTree.validators import validate_build_order_reference
|
||||
from InvenTree.models import InvenTreeAttachment
|
||||
@ -811,6 +811,7 @@ class Build(MPTTModel):
|
||||
# Select the location for the build output
|
||||
location = kwargs.get('location', self.destination)
|
||||
status = kwargs.get('status', StockStatus.OK)
|
||||
notes = kwargs.get('notes', '')
|
||||
|
||||
# List the allocated BuildItem objects for the given output
|
||||
allocated_items = output.items_to_install.all()
|
||||
@ -834,10 +835,13 @@ class Build(MPTTModel):
|
||||
|
||||
output.save()
|
||||
|
||||
output.addTransactionNote(
|
||||
_('Completed build output'),
|
||||
output.add_tracking_entry(
|
||||
StockHistoryCode.BUILD_OUTPUT_COMPLETED,
|
||||
user,
|
||||
system=True
|
||||
notes=notes,
|
||||
deltas={
|
||||
'status': status,
|
||||
}
|
||||
)
|
||||
|
||||
# Increase the completed quantity for this build
|
||||
|
@ -28,7 +28,7 @@ from company.models import Company, SupplierPart
|
||||
|
||||
from InvenTree.fields import RoundingDecimalField
|
||||
from InvenTree.helpers import decimal2string, increment, getSetting
|
||||
from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus, StockStatus
|
||||
from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus, StockStatus, StockHistoryCode
|
||||
from InvenTree.models import InvenTreeAttachment
|
||||
|
||||
|
||||
@ -336,10 +336,12 @@ class PurchaseOrder(Order):
|
||||
return self.pending_line_items().count() == 0
|
||||
|
||||
@transaction.atomic
|
||||
def receive_line_item(self, line, location, quantity, user, status=StockStatus.OK, purchase_price=None):
|
||||
def receive_line_item(self, line, location, quantity, user, status=StockStatus.OK, purchase_price=None, **kwargs):
|
||||
""" Receive a line item (or partial line item) against this PO
|
||||
"""
|
||||
|
||||
notes = kwargs.get('notes', '')
|
||||
|
||||
if not self.status == PurchaseOrderStatus.PLACED:
|
||||
raise ValidationError({"status": _("Lines can only be received against an order marked as 'Placed'")})
|
||||
|
||||
@ -369,8 +371,22 @@ class PurchaseOrder(Order):
|
||||
text = _("Received items")
|
||||
note = _('Received {n} items against order {name}').format(n=quantity, name=str(self))
|
||||
|
||||
# Add a new transaction note to the newly created stock item
|
||||
stock.addTransactionNote(text, user, note)
|
||||
tracking_info = {
|
||||
'status': status,
|
||||
'purchaseorder': self.pk,
|
||||
'quantity': quantity,
|
||||
}
|
||||
|
||||
if location:
|
||||
tracking_info['location'] = location.pk
|
||||
|
||||
stock.add_tracking_entry(
|
||||
StockHistoryCode.RECEIVED_AGAINST_PURCHASE_ORDER,
|
||||
user,
|
||||
notes=notes,
|
||||
url=self.get_absolute_url(),
|
||||
deltas=tracking_info
|
||||
)
|
||||
|
||||
# Update the number of parts received against the particular line item
|
||||
line.received += quantity
|
||||
|
28
InvenTree/stock/migrations/0060_auto_20210511_1713.py
Normal file
28
InvenTree/stock/migrations/0060_auto_20210511_1713.py
Normal file
@ -0,0 +1,28 @@
|
||||
# Generated by Django 3.2 on 2021-05-11 07:13
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('stock', '0059_auto_20210404_2016'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='stockitemtracking',
|
||||
name='deltas',
|
||||
field=models.JSONField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='stockitemtracking',
|
||||
name='tracking_type',
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='stockitemtracking',
|
||||
name='title',
|
||||
field=models.CharField(blank=True, help_text='Tracking entry title', max_length=250, verbose_name='Title'),
|
||||
),
|
||||
]
|
59
InvenTree/stock/migrations/0061_auto_20210511_0911.py
Normal file
59
InvenTree/stock/migrations/0061_auto_20210511_0911.py
Normal file
@ -0,0 +1,59 @@
|
||||
# Generated by Django 3.2 on 2021-05-10 23:11
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def update_history(apps, schema_editor):
|
||||
"""
|
||||
Update each existing StockItemTracking object,
|
||||
convert the recorded "quantity" to a delta
|
||||
"""
|
||||
|
||||
StockItem = apps.get_model('stock', 'stockitem')
|
||||
StockItemTracking = apps.get_model('stock', 'stockitemtracking')
|
||||
|
||||
update_count = 0
|
||||
|
||||
for item in StockItem.objects.all():
|
||||
|
||||
history = StockItemTracking.objects.filter(item=item).order_by('date')
|
||||
|
||||
if history.count() == 0:
|
||||
continue
|
||||
|
||||
quantity = history[0].quantity
|
||||
|
||||
for entry in history:
|
||||
|
||||
q = entry.quantity
|
||||
|
||||
if not q == quantity:
|
||||
|
||||
entry.deltas = {
|
||||
'quantity': float(q),
|
||||
}
|
||||
|
||||
entry.save()
|
||||
|
||||
update_count += 1
|
||||
|
||||
quantity = q
|
||||
|
||||
print(f"Updated {update_count} StockItemHistory entries")
|
||||
|
||||
|
||||
def reverse_update(apps, schema_editor):
|
||||
"""
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('stock', '0060_auto_20210511_1713'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(update_history, reverse_code=reverse_update)
|
||||
]
|
@ -34,7 +34,7 @@ import common.models
|
||||
import report.models
|
||||
import label.models
|
||||
|
||||
from InvenTree.status_codes import StockStatus
|
||||
from InvenTree.status_codes import StockStatus, StockHistoryCode
|
||||
from InvenTree.models import InvenTreeTree, InvenTreeAttachment
|
||||
from InvenTree.fields import InvenTreeURLField
|
||||
|
||||
@ -198,14 +198,18 @@ class StockItem(MPTTModel):
|
||||
|
||||
if add_note:
|
||||
|
||||
note = _('Created new stock item for {part}').format(part=str(self.part))
|
||||
tracking_info = {
|
||||
'quantity': self.quantity,
|
||||
'status': self.status,
|
||||
}
|
||||
|
||||
# This StockItem is being saved for the first time
|
||||
self.addTransactionNote(
|
||||
_('Created stock item'),
|
||||
if self.location:
|
||||
tracking_info['location'] = self.location.pk
|
||||
|
||||
self.add_tracking_entry(
|
||||
StockHistoryCode.CREATED,
|
||||
user,
|
||||
note,
|
||||
system=True
|
||||
deltas=tracking_info
|
||||
)
|
||||
|
||||
@property
|
||||
@ -610,31 +614,45 @@ class StockItem(MPTTModel):
|
||||
|
||||
# TODO - Remove any stock item allocations from this stock item
|
||||
|
||||
item.addTransactionNote(
|
||||
_("Assigned to Customer"),
|
||||
item.add_tracking_entry(
|
||||
StockHistoryCode.SENT_TO_CUSTOMER,
|
||||
user,
|
||||
notes=_("Manually assigned to customer {name}").format(name=customer.name),
|
||||
system=True
|
||||
{
|
||||
'customer': customer.id,
|
||||
'customer_name': customer.name,
|
||||
},
|
||||
notes=notes,
|
||||
)
|
||||
|
||||
# Return the reference to the stock item
|
||||
return item
|
||||
|
||||
def returnFromCustomer(self, location, user=None):
|
||||
def returnFromCustomer(self, location, user=None, **kwargs):
|
||||
"""
|
||||
Return stock item from customer, back into the specified location.
|
||||
"""
|
||||
|
||||
self.addTransactionNote(
|
||||
_("Returned from customer {name}").format(name=self.customer.name),
|
||||
notes = kwargs.get('notes', '')
|
||||
|
||||
tracking_info = {}
|
||||
|
||||
if location:
|
||||
tracking_info['location'] = location.id
|
||||
tracking_info['location_name'] = location.name
|
||||
|
||||
if self.customer:
|
||||
tracking_info['customer'] = customer.id
|
||||
tracking_info['customer_name'] = customer.name
|
||||
|
||||
self.add_tracking_entry(
|
||||
StockHistoryCode.RETURNED_FROM_CUSTOMER,
|
||||
user,
|
||||
notes=_("Returned to location {loc}").format(loc=location.name),
|
||||
system=True
|
||||
notes=notes,
|
||||
deltas=tracking_info
|
||||
)
|
||||
|
||||
self.customer = None
|
||||
self.location = location
|
||||
self.sales_order = None
|
||||
|
||||
self.save()
|
||||
|
||||
@ -788,18 +806,25 @@ class StockItem(MPTTModel):
|
||||
stock_item.save()
|
||||
|
||||
# Add a transaction note to the other item
|
||||
stock_item.addTransactionNote(
|
||||
_('Installed into stock item {pk}').format(str(self.pk)),
|
||||
stock_item.add_tracking_entry(
|
||||
StockHistoryCode.INSTALLED_INTO_ASSEMBLY,
|
||||
user,
|
||||
notes=notes,
|
||||
url=self.get_absolute_url()
|
||||
url=self.get_absolute_url(),
|
||||
deltas={
|
||||
'assembly': self.pk,
|
||||
}
|
||||
)
|
||||
|
||||
# Add a transaction note to this item
|
||||
self.addTransactionNote(
|
||||
_('Installed stock item {pk}').format(str(stock_item.pk)),
|
||||
user, notes=notes,
|
||||
url=stock_item.get_absolute_url()
|
||||
# Add a transaction note to this item (the assembly)
|
||||
self.add_tracking_entry(
|
||||
StockHistoryCode.INSTALLED_CHILD_ITEM,
|
||||
user,
|
||||
notes=notes,
|
||||
url=stock_item.get_absolute_url(),
|
||||
deltas={
|
||||
'stockitem': stock_item.pk,
|
||||
}
|
||||
)
|
||||
|
||||
@transaction.atomic
|
||||
@ -820,32 +845,41 @@ class StockItem(MPTTModel):
|
||||
# TODO - Are there any other checks that need to be performed at this stage?
|
||||
|
||||
# Add a transaction note to the parent item
|
||||
self.belongs_to.addTransactionNote(
|
||||
_("Uninstalled stock item {pk}").format(pk=str(self.pk)),
|
||||
self.belongs_to.add_tracking_entry(
|
||||
StockHistoryCode.REMOVED_CHILD_ITEM,
|
||||
user,
|
||||
deltas={
|
||||
'stockitem': self.pk,
|
||||
},
|
||||
notes=notes,
|
||||
url=self.get_absolute_url(),
|
||||
)
|
||||
|
||||
tracking_info = {
|
||||
'assembly': self.belongs_to.pk
|
||||
}
|
||||
|
||||
if location:
|
||||
tracking_info['location'] = location.pk
|
||||
tracking_info['location_name'] = location.name
|
||||
url = location.get_absolute_url()
|
||||
else:
|
||||
url = ''
|
||||
|
||||
self.add_tracking_entry(
|
||||
StockHistoryCode.REMOVED_FROM_ASSEMBLY,
|
||||
user,
|
||||
notes=notes,
|
||||
url=url,
|
||||
deltas=tracking_info
|
||||
)
|
||||
|
||||
# Mark this stock item as *not* belonging to anyone
|
||||
self.belongs_to = None
|
||||
self.location = location
|
||||
|
||||
self.save()
|
||||
|
||||
if location:
|
||||
url = location.get_absolute_url()
|
||||
else:
|
||||
url = ''
|
||||
|
||||
# Add a transaction note!
|
||||
self.addTransactionNote(
|
||||
_('Uninstalled into location {loc}').formaT(loc=str(location)),
|
||||
user,
|
||||
notes=notes,
|
||||
url=url
|
||||
)
|
||||
|
||||
@property
|
||||
def children(self):
|
||||
""" Return a list of the child items which have been split from this stock item """
|
||||
@ -901,24 +935,30 @@ class StockItem(MPTTModel):
|
||||
def has_tracking_info(self):
|
||||
return self.tracking_info_count > 0
|
||||
|
||||
def addTransactionNote(self, title, user, notes='', url='', system=True):
|
||||
""" Generation a stock transaction note for this item.
|
||||
def add_tracking_entry(self, entry_type, user, deltas={}, notes='', url=''):
|
||||
"""
|
||||
Add a history tracking entry for this StockItem
|
||||
|
||||
Brief automated note detailing a movement or quantity change.
|
||||
Args:
|
||||
entry_type - Integer code describing the "type" of historical action (see StockHistoryCode)
|
||||
user - The user performing this action
|
||||
deltas - A map of the changes made to the model
|
||||
notes - User notes associated with this tracking entry
|
||||
url - Optional URL associated with this tracking entry
|
||||
"""
|
||||
|
||||
track = StockItemTracking.objects.create(
|
||||
entry = StockItemTracking.objects.create(
|
||||
item=self,
|
||||
title=title,
|
||||
tracking_type=entry_type,
|
||||
user=user,
|
||||
quantity=self.quantity,
|
||||
date=datetime.now().date(),
|
||||
date=datetime.now(),
|
||||
notes=notes,
|
||||
deltas=deltas,
|
||||
link=url,
|
||||
system=system
|
||||
system=True
|
||||
)
|
||||
|
||||
track.save()
|
||||
entry.save()
|
||||
|
||||
@transaction.atomic
|
||||
def serializeStock(self, quantity, serials, user, notes='', location=None):
|
||||
@ -991,10 +1031,17 @@ class StockItem(MPTTModel):
|
||||
new_item.copyTestResultsFrom(self)
|
||||
|
||||
# Create a new stock tracking item
|
||||
new_item.addTransactionNote(_('Add serial number'), user, notes=notes)
|
||||
new_item.add_tracking_entry(
|
||||
StockHistoryCode.ASSIGNED_SERIAL,
|
||||
user,
|
||||
notes=notes,
|
||||
deltas={
|
||||
'serial': serial,
|
||||
}
|
||||
)
|
||||
|
||||
# Remove the equivalent number of items
|
||||
self.take_stock(quantity, user, notes=_('Serialized {n} items').format(n=quantity))
|
||||
self.take_stock(quantity, user, notes=notes)
|
||||
|
||||
@transaction.atomic
|
||||
def copyHistoryFrom(self, other):
|
||||
@ -1018,7 +1065,7 @@ class StockItem(MPTTModel):
|
||||
result.save()
|
||||
|
||||
@transaction.atomic
|
||||
def splitStock(self, quantity, location, user):
|
||||
def splitStock(self, quantity, location, user, **kwargs):
|
||||
""" Split this stock item into two items, in the same location.
|
||||
Stock tracking notes for this StockItem will be duplicated,
|
||||
and added to the new StockItem.
|
||||
@ -1032,6 +1079,8 @@ class StockItem(MPTTModel):
|
||||
The new item will have a different StockItem ID, while this will remain the same.
|
||||
"""
|
||||
|
||||
notes = kwargs.get('notes', '')
|
||||
|
||||
# Do not split a serialized part
|
||||
if self.serialized:
|
||||
return self
|
||||
@ -1071,17 +1120,20 @@ class StockItem(MPTTModel):
|
||||
new_stock.copyTestResultsFrom(self)
|
||||
|
||||
# Add a new tracking item for the new stock item
|
||||
new_stock.addTransactionNote(
|
||||
_("Split from existing stock"),
|
||||
new_stock.add_tracking_entry(
|
||||
StockHistoryCode.SPLIT_FROM_PARENT,
|
||||
user,
|
||||
_('Split {n} items').format(n=helpers.normalize(quantity))
|
||||
notes=notes,
|
||||
deltas={
|
||||
'stockitem': self.pk,
|
||||
}
|
||||
)
|
||||
|
||||
# Remove the specified quantity from THIS stock item
|
||||
self.take_stock(
|
||||
quantity,
|
||||
user,
|
||||
f"{_('Split')} {quantity} {_('items into new stock item')}"
|
||||
notes=notes
|
||||
)
|
||||
|
||||
# Return a copy of the "new" stock item
|
||||
@ -1138,11 +1190,21 @@ class StockItem(MPTTModel):
|
||||
|
||||
self.location = location
|
||||
|
||||
self.addTransactionNote(
|
||||
msg,
|
||||
tracking_info = {}
|
||||
|
||||
if location:
|
||||
tracking_info['location'] = location.pk
|
||||
url = location.get_absolute_url()
|
||||
else:
|
||||
url = ''
|
||||
|
||||
self.add_tracking_entry(
|
||||
StockHistoryCode.STOCK_MOVE,
|
||||
user,
|
||||
notes=notes,
|
||||
system=True)
|
||||
deltas=tracking_info,
|
||||
url=url,
|
||||
)
|
||||
|
||||
self.save()
|
||||
|
||||
@ -1202,13 +1264,13 @@ class StockItem(MPTTModel):
|
||||
|
||||
if self.updateQuantity(count):
|
||||
|
||||
text = _('Counted {n} items').format(n=helpers.normalize(count))
|
||||
|
||||
self.addTransactionNote(
|
||||
text,
|
||||
self.add_tracking_entry(
|
||||
StockHistoryCode.STOCK_COUNT,
|
||||
user,
|
||||
notes=notes,
|
||||
system=True
|
||||
deltas={
|
||||
'quantity': self.quantity,
|
||||
}
|
||||
)
|
||||
|
||||
return True
|
||||
@ -1234,13 +1296,15 @@ class StockItem(MPTTModel):
|
||||
return False
|
||||
|
||||
if self.updateQuantity(self.quantity + quantity):
|
||||
text = _('Added {n} items').format(n=helpers.normalize(quantity))
|
||||
|
||||
self.addTransactionNote(
|
||||
text,
|
||||
self.add_tracking_entry(
|
||||
StockHistoryCode.STOCK_ADD,
|
||||
user,
|
||||
notes=notes,
|
||||
system=True
|
||||
deltas={
|
||||
'added': quantity,
|
||||
'quantity': self.quantity
|
||||
}
|
||||
)
|
||||
|
||||
return True
|
||||
@ -1264,12 +1328,15 @@ class StockItem(MPTTModel):
|
||||
|
||||
if self.updateQuantity(self.quantity - quantity):
|
||||
|
||||
text = _('Removed {n1} items').format(n1=helpers.normalize(quantity))
|
||||
|
||||
self.addTransactionNote(text,
|
||||
user,
|
||||
notes=notes,
|
||||
system=True)
|
||||
self.add_tracking_entry(
|
||||
StockHistoryCode.STOCK_REMOVE,
|
||||
user,
|
||||
notes=notes,
|
||||
deltas={
|
||||
'removed': quantity,
|
||||
'quantity': self.quantity,
|
||||
}
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
@ -1527,30 +1594,57 @@ class StockItemAttachment(InvenTreeAttachment):
|
||||
|
||||
|
||||
class StockItemTracking(models.Model):
|
||||
""" Stock tracking entry - breacrumb for keeping track of automated stock transactions
|
||||
"""
|
||||
Stock tracking entry - used for tracking history of a particular StockItem
|
||||
|
||||
Note: 2021-05-11
|
||||
The legacy StockTrackingItem model contained very litle information about the "history" of the item.
|
||||
In fact, only the "quantity" of the item was recorded at each interaction.
|
||||
Also, the "title" was translated at time of generation, and thus was not really translateable.
|
||||
The "new" system tracks all 'delta' changes to the model,
|
||||
and tracks change "type" which can then later be translated
|
||||
|
||||
|
||||
Attributes:
|
||||
item: Link to StockItem
|
||||
item: ForeignKey reference to a particular StockItem
|
||||
date: Date that this tracking info was created
|
||||
title: Title of this tracking info (generated by system)
|
||||
title: Title of this tracking info (legacy, no longer used!)
|
||||
tracking_type: The type of tracking information
|
||||
notes: Associated notes (input by user)
|
||||
link: Optional URL to external page
|
||||
user: The user associated with this tracking info
|
||||
deltas: The changes associated with this history item
|
||||
quantity: The StockItem quantity at this point in time
|
||||
"""
|
||||
|
||||
def get_absolute_url(self):
|
||||
return '/stock/track/{pk}'.format(pk=self.id)
|
||||
# return reverse('stock-tracking-detail', kwargs={'pk': self.id})
|
||||
|
||||
item = models.ForeignKey(StockItem, on_delete=models.CASCADE,
|
||||
related_name='tracking_info')
|
||||
tracking_type = models.IntegerField(
|
||||
default=StockHistoryCode.LEGACY,
|
||||
)
|
||||
|
||||
item = models.ForeignKey(
|
||||
StockItem,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='tracking_info'
|
||||
)
|
||||
|
||||
date = models.DateTimeField(auto_now_add=True, editable=False)
|
||||
|
||||
title = models.CharField(blank=False, max_length=250, verbose_name=_('Title'), help_text=_('Tracking entry title'))
|
||||
title = models.CharField(
|
||||
blank=True,
|
||||
max_length=250,
|
||||
verbose_name=_('Title'),
|
||||
help_text=_('Tracking entry title')
|
||||
)
|
||||
|
||||
notes = models.CharField(blank=True, max_length=512, verbose_name=_('Notes'), help_text=_('Entry notes'))
|
||||
notes = models.CharField(
|
||||
blank=True,
|
||||
max_length=512,
|
||||
verbose_name=_('Notes'),
|
||||
help_text=_('Entry notes')
|
||||
)
|
||||
|
||||
link = InvenTreeURLField(blank=True, verbose_name=_('Link'), help_text=_('Link to external page for further information'))
|
||||
|
||||
@ -1558,13 +1652,15 @@ class StockItemTracking(models.Model):
|
||||
|
||||
system = models.BooleanField(default=False)
|
||||
|
||||
quantity = models.DecimalField(max_digits=15, decimal_places=5, validators=[MinValueValidator(0)], default=1, verbose_name=_('Quantity'))
|
||||
deltas = models.JSONField(null=True, blank=True)
|
||||
|
||||
# TODO
|
||||
# image = models.ImageField(upload_to=func, max_length=255, null=True, blank=True)
|
||||
|
||||
# TODO
|
||||
# file = models.FileField()
|
||||
quantity = models.DecimalField(
|
||||
max_digits=15,
|
||||
decimal_places=5,
|
||||
validators=[MinValueValidator(0)],
|
||||
default=1,
|
||||
verbose_name=_('Quantity')
|
||||
)
|
||||
|
||||
|
||||
def rename_stock_item_test_result_attachment(instance, filename):
|
||||
|
Loading…
Reference in New Issue
Block a user