diff --git a/README.md b/README.md
index 811a9dc4..ed087951 100644
--- a/README.md
+++ b/README.md
@@ -40,6 +40,14 @@ This repository contains the API endpoints and models for the ART project implem
| **RETRY_TIMEOUT** | **Optional** - Number of seconds to wait before retrying an external request (currently to AIS) if an error other than 401 is received. | **10** |
| **LOGLEVEL** | **Optional** - Default log level - error, warning, info, debug. | **info** |
| **ADMINS** | **Optional** - Email addresses to send error logs to. | **art:art.andela@andela.com,art_group:art@andela.com** |
+| **DEFAULT_THRESHOLD** | **REQUIRED** - Minimal asset threshold | **Integer value EG 20**|
+| **EMAIL_HOST** | **REQUIRED** - email host. | **smtp.gmail.com** |
+| **EMAIL_HOST_USER** | **REQUIRED** - email host user account | |
+| **EMAIL_HOST_PASSWORD** | **REQUIRED** - email host user account password | |
+| **EMAIL_PORT** | **REQUIRED** - email port. | **587** |
+| **EMAIL_USE_TLS** | **REQUIRED** - email TLS. | **True** |
+| **EMAIL_SENDER** | **REQUIRED** - email sender's address. | |
+
### Project setup
#### Installation script
@@ -60,6 +68,7 @@ The easiest way to set up is to run `. ./install_art.sh` which does the followin
- Install the project dependencies stored in [Pipfile](/Pipfile). Run `pipenv install --dev`.
- Run migrations - `python manage.py migrate`
- Create cache table `python manage.py createcachetable`
+-Open a terminal tab then run the command `python manage.py qcluster` to start django q to enable in sending email notifications
#### Development using Docker
To use the Docker setup, ensure you have Docker installed then run the following commands:
diff --git a/api/send_email.py b/api/send_email.py
new file mode 100644
index 00000000..d7befbd8
--- /dev/null
+++ b/api/send_email.py
@@ -0,0 +1,19 @@
+# Third-Party Imports
+from decouple import config
+from django_q.tasks import async_task
+
+# App Imports
+from core.constants import MSG, SUBJECT
+from core.models.user import User
+
+
+def send_email(data):
+ """"This method sends emails to users"""
+ for user in User.objects.filter(is_staff=True):
+ async_task(
+ "django.core.mail.send_mail",
+ SUBJECT,
+ MSG.format(data.name),
+ config("EMAIL_SENDER"),
+ [user.email],
+ )
diff --git a/api/serializers/assets.py b/api/serializers/assets.py
index 443f7ef8..db484751 100644
--- a/api/serializers/assets.py
+++ b/api/serializers/assets.py
@@ -365,6 +365,7 @@ class Meta:
"created_at",
"last_modified",
"asset_type",
+ "threshold",
)
def to_internal_value(self, data):
diff --git a/api/tests/__init__.py b/api/tests/__init__.py
index 1b67ad13..6112a383 100644
--- a/api/tests/__init__.py
+++ b/api/tests/__init__.py
@@ -139,10 +139,12 @@ def setUpClass(cls):
name="Sub Category nameseses", asset_category=cls.asset_category
)
cls.asset_type = apps.get_model("core", "AssetType").objects.create(
- name="Asset Types", asset_sub_category=cls.asset_sub_category
+ name="Asset Types", asset_sub_category=cls.asset_sub_category, threshold=50
)
cls.test_asset_type = apps.get_model("core", "AssetType").objects.create(
- name="Other Asset Types", asset_sub_category=cls.asset_sub_category
+ name="Other Asset Types",
+ asset_sub_category=cls.asset_sub_category,
+ threshold=50,
)
cls.asset_make = apps.get_model("core", "AssetMake").objects.create(
name="Asset Makes", asset_type=cls.asset_type
diff --git a/api/tests/test_asset_type_api.py b/api/tests/test_asset_type_api.py
index f4748394..3be3d5fa 100644
--- a/api/tests/test_asset_type_api.py
+++ b/api/tests/test_asset_type_api.py
@@ -1,12 +1,15 @@
# Standard Library
+import os
from unittest.mock import patch
# Third-Party Imports
+from django.test import override_settings
from rest_framework.test import APIClient
# App Imports
from api.tests import APIBaseTestCase
-from core.models import AssetType
+from core import constants
+from core.models import AllocationHistory, AssetType, User
client = APIClient()
@@ -26,6 +29,7 @@ def test_can_post_asset_type(self, mock_verify_token):
data = {
"name": "Asset Type Example",
"asset_sub_category": self.asset_sub_category.id,
+ "threshold": 50,
}
response = client.post(
self.asset_type_url,
@@ -112,3 +116,29 @@ def test_asset_type_api_orders_asset_types_by_type(self, mock_verify_id_token):
# since the asset types are ordered.
self.assertEqual(AssetType.objects.count(), len(response.data.get("results")))
self.assertEqual(response.data.get("results")[-1].get("name"), "Samsung")
+
+ @patch("api.send_email.async_task")
+ @patch("api.authentication.auth.verify_id_token")
+ @override_settings(EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend")
+ def test_email_is_sent_when_asset_type_threshold_critical(
+ self, mock_verify_token, mail
+ ):
+ mock_verify_token.return_value = {"email": self.admin_user.email}
+ assettype = AssetType(
+ name="Andelaheadsets",
+ asset_sub_category=self.asset_sub_category,
+ threshold=20,
+ )
+ assettype.save()
+ self.asset_make.asset_type = assettype
+ self.asset_make.save()
+ allocate = AllocationHistory(
+ asset=self.asset_1, current_assignee=self.asset_assignee
+ )
+ allocate.save()
+ data = User.objects.filter(is_staff=True)
+ emails = mail._mock_call_args_list[0]
+ self.assertEqual(emails[0][1], constants.SUBJECT)
+ self.assertEqual(emails[0][3], os.getenv("EMAIL_SENDER"))
+ self.assertEqual(emails[0][2], constants.MSG.format(assettype.name))
+ self.assertEqual(emails[0][4][0], data[0].email)
diff --git a/core/constants.py b/core/constants.py
index bc2c3059..86da9314 100644
--- a/core/constants.py
+++ b/core/constants.py
@@ -141,3 +141,6 @@
(WARNING_NOTIFICATION, WARNING_NOTIFICATION),
(INFO_NOTIFICATION, INFO_NOTIFICATION),
)
+SUBJECT = "STOCK LEVEL CRITICAL"
+
+MSG = "Dear Admin, The stock level for the Asset type {} have gone below the minimum threshold."
diff --git a/core/migrations/0053_assettype_threshold.py b/core/migrations/0053_assettype_threshold.py
new file mode 100644
index 00000000..ac3b142f
--- /dev/null
+++ b/core/migrations/0053_assettype_threshold.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.1.10 on 2019-08-26 12:56
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('core', '0052_notifications'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='assettype',
+ name='threshold',
+ field=models.IntegerField(default=0),
+ ),
+ ]
diff --git a/core/models/asset.py b/core/models/asset.py
index 59c1b8ec..c8b2c458 100644
--- a/core/models/asset.py
+++ b/core/models/asset.py
@@ -5,11 +5,13 @@
from datetime import datetime
# Third-Party Imports
+from decouple import config
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
# App Imports
+from api.send_email import send_email
from core import constants
from core.managers import CaseInsensitiveManager
from core.slack_bot import SlackIntegration
@@ -26,7 +28,7 @@ def user_abstract(user, filename):
:params: user -> user object
:params: filename -> string
"""
- return f'user_{user}_{filename}'
+ return f"user_{user}_{filename}"
class AssetCategory(models.Model):
@@ -92,6 +94,7 @@ class AssetType(models.Model):
last_modified = models.DateTimeField(auto_now=True, editable=False)
asset_sub_category = models.ForeignKey("AssetSubCategory", on_delete=models.PROTECT)
has_specs = models.BooleanField(default=False)
+ threshold = models.IntegerField(default=0)
objects = CaseInsensitiveManager()
@@ -542,6 +545,13 @@ def save(self, *args, **kwargs):
self.previous_assignee = None
try:
super().save(*args, **kwargs)
+ if self.previous_assignee is None:
+ threshold_data = self.asset.model_number.asset_make.asset_type
+ threshold_data.threshold = threshold_data.threshold - 1
+ if threshold_data.threshold <= int(config("DEFAULT_THRESHOLD")):
+ send_email(threshold_data)
+ threshold_data.save()
+
except Exception:
raise
else:
@@ -619,10 +629,10 @@ class AssetIncidentReport(models.Model):
loss_of_property = models.TextField(null=True, blank=True)
witnesses = models.TextField(null=True, blank=True)
police_abstract_obtained = models.CharField(max_length=255)
- submitted_by = models.ForeignKey('User', null=True, on_delete=models.PROTECT)
+ submitted_by = models.ForeignKey("User", null=True, on_delete=models.PROTECT)
created_at = models.DateTimeField(default=datetime.now, editable=False)
police_abstract = models.FileField(
- 'Police Abstract', upload_to=user_abstract, blank=True
+ "Police Abstract", upload_to=user_abstract, blank=True
)
def __str__(self):
diff --git a/settings/base.py b/settings/base.py
index 81cd410e..3fcc71cb 100644
--- a/settings/base.py
+++ b/settings/base.py
@@ -181,7 +181,7 @@
REDOC_SETTINGS = {"LAZY_RENDERING": True}
-# EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
+EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = config("EMAIL_HOST", None)
EMAIL_PORT = config("EMAIL_PORT", None)
@@ -205,5 +205,3 @@
'LOCATION': 'art_cache',
}
}
-# log emails in console rather than sending them
-EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'