From 36fe64ed8dc756ef5cec9eeed17d22a2f2cc8379 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Thu, 15 Nov 2018 21:13:36 +0300 Subject: [PATCH 01/12] Add new migration --- .../migrations/0005_auto_20181115_2112.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 small_small_hr/migrations/0005_auto_20181115_2112.py diff --git a/small_small_hr/migrations/0005_auto_20181115_2112.py b/small_small_hr/migrations/0005_auto_20181115_2112.py new file mode 100644 index 0000000..765c5c4 --- /dev/null +++ b/small_small_hr/migrations/0005_auto_20181115_2112.py @@ -0,0 +1,28 @@ +# Generated by Django 2.1.2 on 2018-11-15 18:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('small_small_hr', '0004_auto_20180725_2127'), + ] + + operations = [ + migrations.AlterField( + model_name='staffdocument', + name='public', + field=models.BooleanField( + blank=True, + default=False, + help_text='If public, it will be available to everyone.', + verbose_name='Public'), + ), + migrations.AlterField( + model_name='staffprofile', + name='overtime_allowed', + field=models.BooleanField( + blank=True, default=False, verbose_name='Overtime allowed'), + ), + ] From 0057028a317ae58045b7d253392ddc54f42d35be Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Thu, 15 Nov 2018 21:24:47 +0300 Subject: [PATCH 02/12] Add support for one day leave --- small_small_hr/forms.py | 2 +- tests/test_forms.py | 47 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/small_small_hr/forms.py b/small_small_hr/forms.py index e7fd49b..bf9e996 100644 --- a/small_small_hr/forms.py +++ b/small_small_hr/forms.py @@ -315,7 +315,7 @@ def clean(self): self.add_error('end', msg) # end must be later than start - if end <= start: + if end < start: self.add_error('end', _("end must be greater than start")) # staff profile must have sufficient sick days diff --git a/tests/test_forms.py b/tests/test_forms.py index e12a979..eb0cd18 100644 --- a/tests/test_forms.py +++ b/tests/test_forms.py @@ -306,6 +306,53 @@ def test_leaveform_apply(self, mock): self.assertEqual('', leave.comments) mock.assert_called_with(leave_obj=leave) + @override_settings(SSHR_DEFAULT_TIME=7) + @patch('small_small_hr.forms.leave_application_email') + def test_one_day_leave(self, mock): + """ + Test application for one day leave + """ + user = mommy.make('auth.User', first_name='Bob', last_name='Ndoe') + staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + staffprofile.leave_days = 21 + staffprofile.sick_days = 10 + staffprofile.save() + + request = self.factory.get('/') + request.session = {} + request.user = AnonymousUser() + + # 6 days of leave + start = datetime( + 2017, 6, 5, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime( + 2017, 6, 5, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + + mommy.make('small_small_hr.AnnualLeave', staff=staffprofile, year=2017, + leave_type=Leave.REGULAR, carried_over_days=12) + + data = { + 'staff': staffprofile.id, + 'leave_type': Leave.REGULAR, + 'start': start, + 'end': end, + 'reason': 'Need a break', + } + + form = ApplyLeaveForm(data=data) + self.assertTrue(form.is_valid()) + leave = form.save() + self.assertEqual(staffprofile, leave.staff) + self.assertEqual(Leave.REGULAR, leave.leave_type) + self.assertEqual(start, leave.start) + self.assertEqual(end, leave.end) + self.assertEqual( + timedelta(days=0).days, (leave.end - leave.start).days) + self.assertEqual('Need a break', leave.reason) + self.assertEqual(Leave.PENDING, leave.status) + self.assertEqual('', leave.comments) + mock.assert_called_with(leave_obj=leave) + @override_settings(SSHR_DEFAULT_TIME=7) def test_leaveform_no_overlap(self): """ From f635d43ffca34a49bce7d6312b38a09fe00f6e23 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Fri, 16 Nov 2018 00:34:53 +0300 Subject: [PATCH 03/12] Add settings module --- small_small_hr/settings.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 small_small_hr/settings.py diff --git a/small_small_hr/settings.py b/small_small_hr/settings.py new file mode 100644 index 0000000..6bcb6c4 --- /dev/null +++ b/small_small_hr/settings.py @@ -0,0 +1,13 @@ +""" +Configurable options +""" +SSHR_MAX_CARRY_OVER = 10 +SSHR_DAY_LEAVE_VALUES = { + 1: 1, # Monday + 2: 1, # Tuesday + 3: 1, # Wednesday + 4: 1, # Thursday + 5: 1, # Friday + 6: 0, # Saturday + 7: 0, # Sunday +} From f8d9d3e05dac86d405396b508357f5e109b21666 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Fri, 16 Nov 2018 00:35:28 +0300 Subject: [PATCH 04/12] Update requirements --- Pipfile | 2 +- Pipfile.lock | 234 ++++++++++++++++++++++++--------------------------- 2 files changed, 113 insertions(+), 123 deletions(-) diff --git a/Pipfile b/Pipfile index c483912..3b416dd 100644 --- a/Pipfile +++ b/Pipfile @@ -19,5 +19,5 @@ ipdb = "*" model-mommy = "*" tblib = "*" pylint-django = {git = "https://github.com/PyCQA/pylint-django.git", ref = "4316c3d90f4ac6cbbeddfc8d431f5d4e031d5cf1"} -"pep8" = "*" isort = "*" +"pep8" = "*" diff --git a/Pipfile.lock b/Pipfile.lock index b852902..405cc1d 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -25,10 +25,10 @@ }, "django": { "hashes": [ - "sha256:acdcc1f61fdb0a0c82a1d3bf1879a414e7732ea894a7632af7f6d66ec7ab5bb3", - "sha256:efbcad7ebb47daafbcead109b38a5bd519a3c3cd92c6ed0f691ff97fcdd16b45" + "sha256:1ffab268ada3d5684c05ba7ce776eaeedef360712358d6a6b340ae9f16486916", + "sha256:dd46d87af4c1bf54f4c926c3cfa41dc2b5c15782f15e4329752ce65f5dad1c37" ], - "version": "==2.1.2" + "version": "==2.1.3" }, "django-crispy-forms": { "hashes": [ @@ -39,9 +39,9 @@ }, "django-phonenumber-field": { "hashes": [ - "sha256:08257904750d7329b2b11dd9d8d6b7ceb261980db555bb426d9900fa390e2a4c" + "sha256:07f18e3a26310453c03c7e67dc9743335dc19a645fcb38e84284a50903f506c0" ], - "version": "==2.0.1" + "version": "==2.1.0" }, "django-private-storage": { "hashes": [ @@ -52,28 +52,22 @@ }, "djangorestframework": { "hashes": [ - "sha256:b6714c3e4b0f8d524f193c91ecf5f5450092c2145439ac2769711f7eba89a9d9", - "sha256:c375e4f95a3a64fccac412e36fb42ba36881e52313ec021ef410b40f67cddca4" + "sha256:607865b0bb1598b153793892101d881466bd5a991de12bd6229abb18b1c86136", + "sha256:63f76cbe1e7d12b94c357d7e54401103b2e52aef0f7c1650d6c820ad708776e5" ], - "version": "==3.8.2" + "markers": "python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.3.*'", + "version": "==3.9.0" }, "e1839a8": { "editable": true, "path": "." }, - "phonenumbers": { - "hashes": [ - "sha256:670ccaa36eb2c4c05902e461f78e480403257507351b3c98660f2c7f15eb9474", - "sha256:8e9664ce0a838c81f4fb3e4d271c76859d26bde57242d64fe1632ab636f5319f" - ], - "version": "==8.9.15" - }, "phonenumberslite": { "hashes": [ - "sha256:570ec0853a2b501bc5e383c935311932d3a3f39bfb7c544dfcc0d0070032ff42", - "sha256:c78b269aff4dd731cd8c99960bc67dcd2ac830e23354c5f6d27b0512b8a129be" + "sha256:2db0b001d9103488fc7a2c844c5b2b565557652e1b75607315652063769a2aea", + "sha256:fa01b5ef3581dcc66ba8fcb91b3d3e4e2a82b0b232d71e24b8e345915b0b3318" ], - "version": "==8.9.15" + "version": "==8.9.16" }, "pillow": { "hashes": [ @@ -113,45 +107,45 @@ }, "psycopg2-binary": { "hashes": [ - "sha256:04afb59bbbd2eab3148e6816beddc74348078b8c02a1113ea7f7822f5be4afe3", - "sha256:098b18f4d8857a8f9b206d1dc54db56c2255d5d26458917e7bcad61ebfe4338f", - "sha256:0bf855d4a7083e20ead961fda4923887094eaeace0ab2d76eb4aa300f4bbf5bd", - "sha256:197dda3ffd02057820be83fe4d84529ea70bf39a9a4daee1d20ffc74eb3d042e", - "sha256:278ef63afb4b3d842b4609f2c05ffbfb76795cf6a184deeb8707cd5ed3c981a5", - "sha256:3cbf8c4fc8f22f0817220891cf405831559f4d4c12c4f73913730a2ea6c47a47", - "sha256:4305aed922c4d9d6163ab3a41d80b5a1cfab54917467da8168552c42cad84d32", - "sha256:47ee296f704fb8b2a616dec691cdcfd5fa0f11943955e88faa98cbd1dc3b3e3d", - "sha256:4a0e38cb30457e70580903367161173d4a7d1381eb2f2cfe4e69b7806623f484", - "sha256:4d6c294c6638a71cafb82a37f182f24321f1163b08b5d5ca076e11fe838a3086", - "sha256:4f3233c366500730f839f92833194fd8f9a5c4529c8cd8040aa162c3740de8e5", - "sha256:5221f5a3f4ca2ddf0d58e8b8a32ca50948be9a43351fda797eb4e72d7a7aa34d", - "sha256:5c6ca0b507540a11eaf9e77dee4f07c131c2ec80ca0cffa146671bf690bc1c02", - "sha256:789bd89d71d704db2b3d5e67d6d518b158985d791d3b2dec5ab85457cfc9677b", - "sha256:7b94d29239efeaa6a967f3b5971bd0518d2a24edd1511edbf4a2c8b815220d07", - "sha256:89bc65ef3301c74cf32db25334421ea6adbe8f65601ea45dcaaf095abed910bb", - "sha256:89d6d3a549f405c20c9ae4dc94d7ed2de2fa77427a470674490a622070732e62", - "sha256:97521704ac7127d7d8ba22877da3c7bf4a40366587d238ec679ff38e33177498", - "sha256:a395b62d5f44ff6f633231abe568e2203b8fabf9797cd6386aa92497df912d9a", - "sha256:a6d32c37f714c3f34158f3fa659f3a8f2658d5f53c4297d45579b9677cc4d852", - "sha256:a89ee5c26f72f2d0d74b991ce49e42ddeb4ac0dc2d8c06a0f2770a1ab48f4fe0", - "sha256:b4c8b0ef3608e59317bfc501df84a61e48b5445d45f24d0391a24802de5f2d84", - "sha256:b5fcf07140219a1f71e18486b8dc28e2e1b76a441c19374805c617aa6d9a9d55", - "sha256:b86f527f00956ecebad6ab3bb30e3a75fedf1160a8716978dd8ce7adddedd86f", - "sha256:be4c4aa22ba22f70de36c98b06480e2f1697972d49eb20d525f400d204a6d272", - "sha256:c2ac7aa1a144d4e0e613ac7286dae85671e99fe7a1353954d4905629c36b811c", - "sha256:de26ef4787b5e778e8223913a3e50368b44e7480f83c76df1f51d23bd21cea16", - "sha256:e70ebcfc5372dc7b699c0110454fc4263967f30c55454397e5769eb72c0eb0ce", - "sha256:eadbd32b6bc48b67b0457fccc94c86f7ccc8178ab839f684eb285bb592dc143e", - "sha256:ecbc6dfff6db06b8b72ae8a2f25ff20fbdcb83cb543811a08f7cb555042aa729" - ], - "version": "==2.7.5" + "sha256:036bcb198a7cc4ce0fe43344f8c2c9a8155aefa411633f426c8c6ed58a6c0426", + "sha256:1d770fcc02cdf628aebac7404d56b28a7e9ebec8cfc0e63260bd54d6edfa16d4", + "sha256:1fdc6f369dcf229de6c873522d54336af598b9470ccd5300e2f58ee506f5ca13", + "sha256:21f9ddc0ff6e07f7d7b6b484eb9da2c03bc9931dd13e36796b111d631f7135a3", + "sha256:247873cda726f7956f745a3e03158b00de79c4abea8776dc2f611d5ba368d72d", + "sha256:3aa31c42f29f1da6f4fd41433ad15052d5ff045f2214002e027a321f79d64e2c", + "sha256:475f694f87dbc619010b26de7d0fc575a4accf503f2200885cc21f526bffe2ad", + "sha256:4b5e332a24bf6e2fda1f51ca2a57ae1083352293a08eeea1fa1112dc7dd542d1", + "sha256:570d521660574aca40be7b4d532dfb6f156aad7b16b5ed62d1534f64f1ef72d8", + "sha256:59072de7def0690dd13112d2bdb453e20570a97297070f876fbbb7cbc1c26b05", + "sha256:5f0b658989e918ef187f8a08db0420528126f2c7da182a7b9f8bf7f85144d4e4", + "sha256:649199c84a966917d86cdc2046e03d536763576c0b2a756059ae0b3a9656bc20", + "sha256:6645fc9b4705ae8fbf1ef7674f416f89ae1559deec810f6dd15197dfa52893da", + "sha256:6872dd54d4e398d781efe8fe2e2d7eafe4450d61b5c4898aced7610109a6df75", + "sha256:6ce34fbc251fc0d691c8d131250ba6f42fd2b28ef28558d528ba8c558cb28804", + "sha256:73920d167a0a4d1006f5f3b9a3efce6f0e5e883a99599d38206d43f27697df00", + "sha256:8a671732b87ae423e34b51139628123bc0306c2cb85c226e71b28d3d57d7e42a", + "sha256:8d517e8fda2efebca27c2018e14c90ed7dc3f04d7098b3da2912e62a1a5585fe", + "sha256:9475a008eb7279e20d400c76471843c321b46acacc7ee3de0b47233a1e3fa2cf", + "sha256:96947b8cd7b3148fb0e6549fcb31258a736595d6f2a599f8cd450e9a80a14781", + "sha256:abf229f24daa93f67ac53e2e17c8798a71a01711eb9fcdd029abba8637164338", + "sha256:b1ab012f276df584beb74f81acb63905762c25803ece647016613c3d6ad4e432", + "sha256:b22b33f6f0071fe57cb4e9158f353c88d41e739a3ec0d76f7b704539e7076427", + "sha256:b3b2d53274858e50ad2ffdd6d97ce1d014e1e530f82ec8b307edd5d4c921badf", + "sha256:bab26a729befc7b9fab9ded1bba9c51b785188b79f8a2796ba03e7e734269e2e", + "sha256:daa1a593629aa49f506eddc9d23dc7f89b35693b90e1fbcd4480182d1203ea90", + "sha256:dd111280ce40e89fd17b19c1269fd1b74a30fce9d44a550840e86edb33924eb8", + "sha256:e0b86084f1e2e78c451994410de756deba206884d6bed68d5a3d7f39ff5fea1d", + "sha256:eb86520753560a7e89639500e2a254bb6f683342af598088cb72c73edcad21e6", + "sha256:ff18c5c40a38d41811c23e2480615425c97ea81fd7e9118b8b899c512d97c737" + ], + "version": "==2.7.6.1" }, "pytz": { "hashes": [ - "sha256:a061aa0a9e06881eb8b3b2b43f05b9439d6583c206d0a6c340ff72a7b6669053", - "sha256:ffb9ef1de172603304d9d2819af6f5ece76f2e85ec10692a524dd876e72bf277" + "sha256:31cb35c89bd7d333cd32c5f278fca91b523b0834369e757f4c5641ea252236ca", + "sha256:8e0f8568c118d3077b46be7d654cc8167fa916092e28320cde048e54bfc9f1e6" ], - "version": "==2018.5" + "version": "==2018.7" }, "sorl-thumbnail": { "hashes": [ @@ -185,40 +179,40 @@ }, "coverage": { "hashes": [ - "sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba", - "sha256:0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed", - "sha256:10a46017fef60e16694a30627319f38a2b9b52e90182dddb6e37dcdab0f4bf95", - "sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640", - "sha256:23d341cdd4a0371820eb2b0bd6b88f5003a7438bbedb33688cd33b8eae59affd", - "sha256:28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162", - "sha256:2a5b73210bad5279ddb558d9a2bfedc7f4bf6ad7f3c988641d83c40293deaec1", - "sha256:2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508", - "sha256:337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249", - "sha256:3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694", - "sha256:3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a", - "sha256:3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287", - "sha256:3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1", - "sha256:4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000", - "sha256:56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1", - "sha256:5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e", - "sha256:69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5", - "sha256:6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062", - "sha256:701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba", - "sha256:7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc", - "sha256:76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc", - "sha256:7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99", - "sha256:7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653", - "sha256:7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c", - "sha256:8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558", - "sha256:9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f", - "sha256:be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9", - "sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd", - "sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d", - "sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6", - "sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80" + "sha256:09e47c529ff77bf042ecfe858fb55c3e3eb97aac2c87f0349ab5a7efd6b3939f", + "sha256:0a1f9b0eb3aa15c990c328535655847b3420231af299386cfe5efc98f9c250fe", + "sha256:0cc941b37b8c2ececfed341444a456912e740ecf515d560de58b9a76562d966d", + "sha256:10e8af18d1315de936d67775d3a814cc81d0747a1a0312d84e27ae5610e313b0", + "sha256:1b4276550b86caa60606bd3572b52769860a81a70754a54acc8ba789ce74d607", + "sha256:1e8a2627c48266c7b813975335cfdea58c706fe36f607c97d9392e61502dc79d", + "sha256:2b224052bfd801beb7478b03e8a66f3f25ea56ea488922e98903914ac9ac930b", + "sha256:447c450a093766744ab53bf1e7063ec82866f27bcb4f4c907da25ad293bba7e3", + "sha256:46101fc20c6f6568561cdd15a54018bb42980954b79aa46da8ae6f008066a30e", + "sha256:4710dc676bb4b779c4361b54eb308bc84d64a2fa3d78e5f7228921eccce5d815", + "sha256:510986f9a280cd05189b42eee2b69fecdf5bf9651d4cd315ea21d24a964a3c36", + "sha256:5535dda5739257effef56e49a1c51c71f1d37a6e5607bb25a5eee507c59580d1", + "sha256:5a7524042014642b39b1fcae85fb37556c200e64ec90824ae9ecf7b667ccfc14", + "sha256:5f55028169ef85e1fa8e4b8b1b91c0b3b0fa3297c4fb22990d46ff01d22c2d6c", + "sha256:6694d5573e7790a0e8d3d177d7a416ca5f5c150742ee703f3c18df76260de794", + "sha256:6831e1ac20ac52634da606b658b0b2712d26984999c9d93f0c6e59fe62ca741b", + "sha256:77f0d9fa5e10d03aa4528436e33423bfa3718b86c646615f04616294c935f840", + "sha256:828ad813c7cdc2e71dcf141912c685bfe4b548c0e6d9540db6418b807c345ddd", + "sha256:85a06c61598b14b015d4df233d249cd5abfa61084ef5b9f64a48e997fd829a82", + "sha256:8cb4febad0f0b26c6f62e1628f2053954ad2c555d67660f28dfb1b0496711952", + "sha256:a5c58664b23b248b16b96253880b2868fb34358911400a7ba39d7f6399935389", + "sha256:aaa0f296e503cda4bc07566f592cd7a28779d433f3a23c48082af425d6d5a78f", + "sha256:ab235d9fe64833f12d1334d29b558aacedfbca2356dfb9691f2d0d38a8a7bfb4", + "sha256:b3b0c8f660fae65eac74fbf003f3103769b90012ae7a460863010539bb7a80da", + "sha256:bab8e6d510d2ea0f1d14f12642e3f35cefa47a9b2e4c7cea1852b52bc9c49647", + "sha256:c45297bbdbc8bb79b02cf41417d63352b70bcb76f1bbb1ee7d47b3e89e42f95d", + "sha256:d19bca47c8a01b92640c614a9147b081a1974f69168ecd494687c827109e8f42", + "sha256:d64b4340a0c488a9e79b66ec9f9d77d02b99b772c8b8afd46c1294c1d39ca478", + "sha256:da969da069a82bbb5300b59161d8d7c8d423bc4ccd3b410a9b4d8932aeefc14b", + "sha256:ed02c7539705696ecb7dc9d476d861f3904a8d2b7e894bd418994920935d36bb", + "sha256:ee5b8abc35b549012e03a7b1e86c09491457dba6c94112a2482b18589cc2bdb9" ], "index": "pypi", - "version": "==4.5.1" + "version": "==4.5.2" }, "decorator": { "hashes": [ @@ -229,25 +223,25 @@ }, "django": { "hashes": [ - "sha256:acdcc1f61fdb0a0c82a1d3bf1879a414e7732ea894a7632af7f6d66ec7ab5bb3", - "sha256:efbcad7ebb47daafbcead109b38a5bd519a3c3cd92c6ed0f691ff97fcdd16b45" + "sha256:1ffab268ada3d5684c05ba7ce776eaeedef360712358d6a6b340ae9f16486916", + "sha256:dd46d87af4c1bf54f4c926c3cfa41dc2b5c15782f15e4329752ce65f5dad1c37" ], - "version": "==2.1.2" + "version": "==2.1.3" }, "filelock": { "hashes": [ - "sha256:86fe6af56ae08ebc9c66d54ba3398c35b98916d0862d782b276a65816ff39392", - "sha256:97694f181bdf58f213cca0a7cb556dc7bf90e2f8eb9aa3151260adac56701afb" + "sha256:b8d5ca5ca1c815e1574aee746650ea7301de63d87935b3463d26368b76e31633", + "sha256:d610c1bb404daf85976d7a82eb2ada120f04671007266b708606565dd03b5be6" ], - "version": "==3.0.9" + "version": "==3.0.10" }, "flake8": { "hashes": [ - "sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0", - "sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37" + "sha256:6a35f5b8761f45c5513e3405f110a86bea57982c3b75b766ce7b65217abe1670", + "sha256:c01f8a3963b3571a8e6bd7a4063359aff90749e160778e03817cd9b71c9e07d2" ], "index": "pypi", - "version": "==3.5.0" + "version": "==3.6.0" }, "ipdb": { "hashes": [ @@ -258,10 +252,10 @@ }, "ipython": { "hashes": [ - "sha256:47b17ea874454a5c2eacc2732b04a750d260b01ba479323155ac8a39031f5535", - "sha256:9fed506c3772c875a3048bc134a25e6f5e997b1569b2636f6a5d891f34cbfd46" + "sha256:a5781d6934a3341a1f9acb4ea5acdc7ea0a0855e689dbe755d070ca51e995435", + "sha256:b10a7ddd03657c761fc503495bc36471c8158e3fc948573fb9fe82a7029d8efd" ], - "version": "==7.0.1" + "version": "==7.1.1" }, "ipython-genutils": { "hashes": [ @@ -375,11 +369,11 @@ }, "prompt-toolkit": { "hashes": [ - "sha256:646b3401b3b0bb7752100bc9b7aeecb36cb09cdfc63652b5856708b5ba8db7da", - "sha256:82766ffd7397e6661465e20bd1390db0781ca4fbbab4cf6c2578cacdd8b09754", - "sha256:ccad8461b5d912782726af17122113e196085e7e11d57cf0c9b982bf1ab2c7be" + "sha256:c1d6aff5252ab2ef391c2fe498ed8c088066f66bc64a8d5c095bbf795d9fec34", + "sha256:d4c47f79b635a0e70b84fdb97ebd9a274203706b1ee5ed44c10da62755cf3ec9", + "sha256:fd17048d8335c1e6d5ee403c3569953ba3eb8555d710bfc548faf0712666ea39" ], - "version": "==2.0.6" + "version": "==2.0.7" }, "ptyprocess": { "hashes": [ @@ -398,17 +392,18 @@ }, "pycodestyle": { "hashes": [ - "sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766", - "sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9" + "sha256:cbc619d09254895b0d12c2c691e237b2e91e9b2ecf5e84c26b35400f93dcfb83", + "sha256:cbfca99bd594a10f674d0cd97a3d802a1fdef635d4361e1a2658de47ed261e3a" ], - "version": "==2.3.1" + "version": "==2.4.0" }, "pyflakes": { "hashes": [ - "sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f", - "sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805" + "sha256:9a7662ec724d0120012f6e29d6248ae3727d821bba522a0e6b356eff19126a49", + "sha256:f661252913bc1dbe7fcfcbf0af0db3f42ab65aabd1a6ca68fe5d466bace94dae" ], - "version": "==1.6.0" + "markers": "python_version != '3.0.*' and python_version != '3.1.*' and python_version >= '2.7' and python_version != '3.2.*' and python_version != '3.3.*'", + "version": "==2.0.0" }, "pygments": { "hashes": [ @@ -431,16 +426,10 @@ }, "pytz": { "hashes": [ - "sha256:a061aa0a9e06881eb8b3b2b43f05b9439d6583c206d0a6c340ff72a7b6669053", - "sha256:ffb9ef1de172603304d9d2819af6f5ece76f2e85ec10692a524dd876e72bf277" - ], - "version": "==2018.5" - }, - "simplegeneric": { - "hashes": [ - "sha256:dc972e06094b9af5b855b3df4a646395e43d1c9d0d39ed345b7393560d0b9173" + "sha256:31cb35c89bd7d333cd32c5f278fca91b523b0834369e757f4c5641ea252236ca", + "sha256:8e0f8568c118d3077b46be7d654cc8167fa916092e28320cde048e54bfc9f1e6" ], - "version": "==0.8.1" + "version": "==2018.7" }, "six": { "hashes": [ @@ -466,11 +455,11 @@ }, "tox": { "hashes": [ - "sha256:217fb84aecf9792a98f93f07cfcaf014205a76c64e52bd7c2b4135458e6ad2a1", - "sha256:4baeb3d8ebdcd9f43afce38aa67d06f1165a87d221d5bb21e8b39a0d4880c134" + "sha256:513e32fdf2f9e2d583c2f248f47ba9886428c949f068ac54a0469cac55df5862", + "sha256:75fa30e8329b41b664585f5fb837e23ce1d7e6fa1f7811f2be571c990f9d911b" ], "index": "pypi", - "version": "==3.5.2" + "version": "==3.5.3" }, "traitlets": { "hashes": [ @@ -510,10 +499,11 @@ }, "virtualenv": { "hashes": [ - "sha256:2ce32cd126117ce2c539f0134eb89de91a8413a29baac49cbab3eb50e2026669", - "sha256:ca07b4c0b54e14a91af9f34d0919790b016923d157afda5efdde55c96718f752" + "sha256:686176c23a538ecc56d27ed9d5217abd34644823d6391cbeb232f42bf722baad", + "sha256:f899fafcd92e1150f40c8215328be38ff24b519cd95357fa6e78e006c7638208" ], - "version": "==16.0.0" + "markers": "python_version != '3.3.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.2.*'", + "version": "==16.1.0" }, "wcwidth": { "hashes": [ From 126c94156f80f5b3066309a50f0d84c287efba43 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Fri, 16 Nov 2018 00:35:44 +0300 Subject: [PATCH 05/12] Loadd app settings --- small_small_hr/apps.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/small_small_hr/apps.py b/small_small_hr/apps.py index d423e46..2cad889 100644 --- a/small_small_hr/apps.py +++ b/small_small_hr/apps.py @@ -13,4 +13,11 @@ class SmallSmallHrConfig(AppConfig): def ready(self): # pylint: disable=unused-variable - import small_small_hr.signals # noqa + import small_small_hr.signals # noqa + + # set up app settings + from django.conf import settings + import small_small_hr.settings as defaults + for name in dir(defaults): + if name.isupper() and not hasattr(settings, name): + setattr(settings, name, getattr(defaults, name)) From 03167c21901d737f0cacfba4b1cfc1f988f66f70 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Fri, 16 Nov 2018 00:36:23 +0300 Subject: [PATCH 06/12] Add ultils to get leave days taken --- small_small_hr/utils.py | 45 +++++++++++++- tests/test_utils.py | 127 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 156 insertions(+), 16 deletions(-) diff --git a/small_small_hr/utils.py b/small_small_hr/utils.py index b1779b1..13dc319 100644 --- a/small_small_hr/utils.py +++ b/small_small_hr/utils.py @@ -1,11 +1,50 @@ """ Utils module for small small hr """ +from datetime import timedelta +from decimal import Decimal +from django.db.models import Q from django.conf import settings +from django.utils import timezone from small_small_hr.models import AnnualLeave, Leave -MAX_CARRY_OVER = getattr(settings, 'SSHR_MAX_CARRY_OVER', 10) + +def get_days(start: object, end: object): + """ + Yield the days between two datetime objects + """ + current_tz = timezone.get_current_timezone() + local_start = current_tz.normalize(start) + local_end = current_tz.normalize(end) + span = local_end.date() - local_start.date() + for i in range(span.days + 1): + yield local_start.date() + timedelta(days=i) + + +def get_taken_leave_days( + staffprofile: object, + status: str, + leave_type: str, + start_year: int, + end_year: int): + """ + Calculate the number of leave days actually taken, + taking into account weekends and weekend policy + """ + count = Decimal(0) + queryset = Leave.objects.filter( + staff=staffprofile, + status=status, + leave_type=leave_type).filter( + Q(start__year__gte=start_year) | Q(end__year__lte=end_year)) + for leave_obj in queryset: + days = get_days(start=leave_obj.start, end=leave_obj.end) + for day in days: + if day.year >= start_year and day.year <= end_year: + day_value = settings.SSHR_DAY_LEAVE_VALUES[day.isoweekday()] + count = count + Decimal(day_value) + return count def get_carry_over(staffprofile: object, year: int, leave_type: str): @@ -15,10 +54,10 @@ def get_carry_over(staffprofile: object, year: int, leave_type: str): # pylint: disable=no-member if leave_type == Leave.REGULAR: previous_obj = AnnualLeave.objects.filter( - staff=staffprofile, year=year-1, leave_type=leave_type).first() + staff=staffprofile, year=year - 1, leave_type=leave_type).first() if previous_obj: remaining = previous_obj.get_available_leave_days() - max_carry_over = MAX_CARRY_OVER + max_carry_over = settings.SSHR_MAX_CARRY_OVER if remaining > max_carry_over: carry_over = max_carry_over else: diff --git a/tests/test_utils.py b/tests/test_utils.py index 2de72aa..757e666 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -3,14 +3,14 @@ """ from datetime import datetime -from django.conf import settings -from django.test import TestCase - import pytz +from django.conf import settings +from django.test import TestCase, override_settings from model_mommy import mommy from small_small_hr.models import Leave, StaffProfile -from small_small_hr.utils import create_annual_leave, get_carry_over +from small_small_hr.utils import (create_annual_leave, get_carry_over, + get_taken_leave_days) class TestUtils(TestCase): @@ -18,6 +18,18 @@ class TestUtils(TestCase): Test class for utils """ + @override_settings( + SSHR_MAX_CARRY_OVER=10, + SSHR_DAY_LEAVE_VALUES={ + 1: 1, # Monday + 2: 1, # Tuesday + 3: 1, # Wednesday + 4: 1, # Thursday + 5: 1, # Friday + 6: 0, # Saturday + 7: 0, # Sunday + } + ) def test_get_carry_over(self): """ Test get_carry_over @@ -31,38 +43,49 @@ def test_get_carry_over(self): get_carry_over(staffprofile, 2017, Leave.REGULAR) ) - create_annual_leave(staffprofile, 2016, Leave.REGULAR) + create_annual_leave(staffprofile, 2017, Leave.REGULAR) # carry over should be 10 because the balance is 21 self.assertEqual( 10, - get_carry_over(staffprofile, 2017, Leave.REGULAR) + get_carry_over(staffprofile, 2018, Leave.REGULAR) ) - # 12 days of leave + # 12 days of leave, Sat & Sun not counted start = datetime( - 2016, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) end = datetime( - 2016, 6, 16, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + 2017, 6, 20, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) mommy.make('small_small_hr.Leave', leave_type=Leave.REGULAR, start=start, end=end, status=Leave.APPROVED, staff=staffprofile) - # carry over should be 9 => 21-12 + # carry over should be 9 => 21-12 self.assertEqual( 9, - get_carry_over(staffprofile, 2017, Leave.REGULAR) + get_carry_over(staffprofile, 2018, Leave.REGULAR) ) # no sick leave carry over - create_annual_leave(staffprofile, 2016, Leave.SICK) + create_annual_leave(staffprofile, 2017, Leave.SICK) self.assertEqual( 0, - get_carry_over(staffprofile, 2017, Leave.SICK) + get_carry_over(staffprofile, 2018, Leave.SICK) ) + @override_settings( + SSHR_DAY_LEAVE_VALUES={ + 1: 1, # Monday + 2: 1, # Tuesday + 3: 1, # Wednesday + 4: 1, # Thursday + 5: 1, # Friday + 6: 1, # Saturday + 7: 1, # Sunday + } + ) def test_create_annual_leave(self): """ Test create_annual_leave @@ -102,3 +125,81 @@ def test_create_annual_leave(self): self.assertEqual(0, obj3.carried_over_days) self.assertEqual(10, obj3.allowed_days) self.assertEqual(Leave.SICK, obj3.leave_type) + + @override_settings( + SSHR_DAY_LEAVE_VALUES={ + 1: 1, # Monday + 2: 1, # Tuesday + 3: 1, # Wednesday + 4: 1, # Thursday + 5: 1, # Friday + 6: 0, # Saturday + 7: 0, # Sunday + } + ) + def test_get_taken_leave_days(self): + """ + Test get_taken_leave_days + """ + user = mommy.make('auth.User', first_name='Mosh', last_name='Pitt') + staff = user.staffprofile + + # 10 days of leave because Saturday and Sunday are not counted + start = datetime( + 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime( + 2017, 6, 16, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + + # add some approved leave days + mommy.make( + 'small_small_hr.Leave', staff=staff, start=start, + end=end, leave_type=Leave.REGULAR, + status=Leave.APPROVED) + + leave_days = get_taken_leave_days( + staffprofile=staff, + status=Leave.APPROVED, + leave_type=Leave.REGULAR, + start_year=2017, + end_year=2017) + + self.assertEqual(10, leave_days) + + @override_settings( + SSHR_DAY_LEAVE_VALUES={ + 1: 1, # Monday + 2: 1, # Tuesday + 3: 1, # Wednesday + 4: 1, # Thursday + 5: 1, # Friday + 6: 0.5, # Saturday + 7: 0, # Sunday + } + ) + def test_get_taken_leave_days_half_saturday(self): + """ + Test get_taken_leave_days + """ + user = mommy.make('auth.User', first_name='Kel', last_name='Vin') + staff = user.staffprofile + + # 10.5 days of leave because Saturday==0.5 and Sunday not counted + start = datetime( + 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime( + 2017, 6, 16, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + + # add some approved leave days + mommy.make( + 'small_small_hr.Leave', staff=staff, start=start, + end=end, leave_type=Leave.REGULAR, + status=Leave.APPROVED) + + leave_days = get_taken_leave_days( + staffprofile=staff, + status=Leave.APPROVED, + leave_type=Leave.REGULAR, + start_year=2017, + end_year=2017) + + self.assertEqual(10.5, leave_days) From 35235df0e4ea1a9478302426afede2975bb3bfcb Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Fri, 16 Nov 2018 00:36:55 +0300 Subject: [PATCH 07/12] Refactor to use get_taken_leave_days --- small_small_hr/models.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/small_small_hr/models.py b/small_small_hr/models.py index e73fb76..f7d9a3e 100644 --- a/small_small_hr/models.py +++ b/small_small_hr/models.py @@ -372,20 +372,14 @@ def get_cumulative_leave_taken(self): Returns a timedelta """ - # we add one day to make end and start inclusive - leave_queryset = Leave.objects.filter( - staff=self.staff, + from small_small_hr.utils import get_taken_leave_days + return get_taken_leave_days( + staffprofile=self.staff, status=Leave.APPROVED, leave_type=self.leave_type, - start__year=self.year, - end__year=self.year).annotate( - duration=models.ExpressionWrapper( - models.F('end') - models.F('start') + timedelta(days=1), - output_field=models.DurationField())) - - return leave_queryset.aggregate( - leave=Coalesce(Sum('duration'), - V(timedelta(days=0))))['leave'] + start_year=self.year, + end_year=self.year + ) def get_available_leave_days(self, month: int = 12): """ @@ -406,7 +400,7 @@ def get_available_leave_days(self, month: int = 12): earned = Decimal(month) * per_month # the days taken - taken = self.get_cumulative_leave_taken().days + taken = self.get_cumulative_leave_taken() # the starting balance starting_balance = self.carried_over_days From a6fb7f959ae2a236492ed7153e8f9d25df6036cc Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Fri, 16 Nov 2018 00:37:10 +0300 Subject: [PATCH 08/12] Fix broken tests --- tests/test_models.py | 105 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 88 insertions(+), 17 deletions(-) diff --git a/tests/test_models.py b/tests/test_models.py index a18e86c..85eb550 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -4,7 +4,7 @@ from datetime import datetime, timedelta from django.conf import settings -from django.test import TestCase +from django.test import TestCase, override_settings from django.utils import timezone import pytz @@ -31,6 +31,17 @@ def test_annualleave_str(self): leave_type=Leave.REGULAR).__str__() ) + @override_settings( + SSHR_DAY_LEAVE_VALUES={ + 1: 1, # Monday + 2: 1, # Tuesday + 3: 1, # Wednesday + 4: 1, # Thursday + 5: 1, # Friday + 6: 1, # Saturday + 7: 1, # Sunday + } + ) def test_annualleave_get_available_leave_days(self): """ Test get_available_leave_days @@ -41,7 +52,7 @@ def test_annualleave_get_available_leave_days(self): 'small_small_hr.AnnualLeave', staff=staff, year=2017, leave_type=Leave.REGULAR) - # 12 days of leave + # 12 days of leave ==> Sat and Sun are counted start = datetime( 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) end = datetime( @@ -71,6 +82,17 @@ def test_annualleave_get_available_leave_days(self): annual_leave.get_available_leave_days() ) + @override_settings( + SSHR_DAY_LEAVE_VALUES={ + 1: 1, # Monday + 2: 1, # Tuesday + 3: 1, # Wednesday + 4: 1, # Thursday + 5: 1, # Friday + 6: 0, # Saturday + 7: 0, # Sunday + } + ) def test_annualleave_cumulative_leave_taken(self): """ Test get_cumulative_leave_taken @@ -86,7 +108,7 @@ def test_annualleave_cumulative_leave_taken(self): end = datetime( 2017, 6, 7, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - # add some approved leave days + # add some 3 approved leave days mommy.make( 'small_small_hr.Leave', staff=staff, start=start, end=end, leave_type=Leave.REGULAR, @@ -105,14 +127,16 @@ def test_annualleave_cumulative_leave_taken(self): annual_leave.refresh_from_db() self.assertEqual( 3, - annual_leave.get_cumulative_leave_taken().days + annual_leave.get_cumulative_leave_taken() ) - # add some approved leave days that fall between years + # add some 2 approved leave days that fall between years + # 1 day in 2017 and one in 2018 + # Dec 30 and Dec 31 are Sat, Sun which are not counted start = datetime( - 2017, 12, 30, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + 2017, 12, 29, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) end = datetime( - 2017, 12, 31, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + 2018, 1, 1, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) mommy.make( 'small_small_hr.Leave', staff=staff, start=start, end=end, leave_type=Leave.REGULAR, @@ -120,26 +144,29 @@ def test_annualleave_cumulative_leave_taken(self): annual_leave.refresh_from_db() # we should have 5 days in 2017 + # import ipdb; ipdb.set_trace() self.assertEqual( - 5, - annual_leave.get_cumulative_leave_taken().days + 4, + annual_leave.get_cumulative_leave_taken() ) + # add 4 days of leave start = datetime( - 2018, 1, 1, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = start + timedelta(days=4) + 2018, 1, 2, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime( + 2018, 1, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) mommy.make( 'small_small_hr.Leave', staff=staff, start=start, end=end, leave_type=Leave.REGULAR, status=Leave.APPROVED) - # we should have 4 days in 2018 + # we should have 5 days in 2018 annual_leave_2018 = mommy.make( 'small_small_hr.AnnualLeave', staff=staff, year=2018, leave_type=Leave.REGULAR) self.assertEqual( 5, - annual_leave_2018.get_cumulative_leave_taken().days + annual_leave_2018.get_cumulative_leave_taken() ) def test_available_leave_days(self): @@ -168,6 +195,17 @@ def test_staffprofile_str(self): staff = mommy.make('small_small_hr.StaffProfile', user=user) self.assertEqual('Mosh Pitt', staff.__str__()) + @override_settings( + SSHR_DAY_LEAVE_VALUES={ + 1: 1, # Monday + 2: 1, # Tuesday + 3: 1, # Wednesday + 4: 1, # Thursday + 5: 1, # Friday + 6: 0, # Saturday + 7: 0, # Sunday + } + ) def test_get_approved_leave_days(self): """ Test get_approved_leave_days @@ -189,6 +227,17 @@ def test_get_approved_leave_days(self): self.assertEqual(timedelta(days=10).days, staff.get_approved_leave_days(year=start.year).days) + @override_settings( + SSHR_DAY_LEAVE_VALUES={ + 1: 1, # Monday + 2: 1, # Tuesday + 3: 1, # Wednesday + 4: 1, # Thursday + 5: 1, # Friday + 6: 0, # Saturday + 7: 0, # Sunday + } + ) def test_get_approved_sick_days(self): """ Test get_approved_sick_days @@ -210,6 +259,17 @@ def test_get_approved_sick_days(self): self.assertEqual(timedelta(days=9).days, staff.get_approved_sick_days(year=start.year).days) + @override_settings( + SSHR_DAY_LEAVE_VALUES={ + 1: 1, # Monday + 2: 1, # Tuesday + 3: 1, # Wednesday + 4: 1, # Thursday + 5: 1, # Friday + 6: 0, # Saturday + 7: 0, # Sunday + } + ) def test_staffprofile_get_available_leave_days(self): """ Test StaffProfile get_available_leave_days @@ -219,7 +279,7 @@ def test_staffprofile_get_available_leave_days(self): mommy.make('small_small_hr.AnnualLeave', staff=staff, year=2017, leave_type=Leave.REGULAR, allowed_days=21) - # 12 days of leave + # 10 days of leave because Saturday and Sunday are not counted start = datetime( 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) end = datetime( @@ -233,9 +293,20 @@ def test_staffprofile_get_available_leave_days(self): staff.refresh_from_db() - # remaining should be 21 - 12 - self.assertEqual(21 - 12, staff.get_available_leave_days(year=2017)) - + # remaining should be 21 - 10 + self.assertEqual(21 - 10, staff.get_available_leave_days(year=2017)) + + @override_settings( + SSHR_DAY_LEAVE_VALUES={ + 1: 1, # Monday + 2: 1, # Tuesday + 3: 1, # Wednesday + 4: 1, # Thursday + 5: 1, # Friday + 6: 0.5, # Saturday + 7: 0, # Sunday + } + ) def test_staffprofile_get_available_sick_days(self): """ Test StaffProfile get_available_leave_days From 02876c384e1e12ed2910608371e6a3c79cc58650 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Fri, 16 Nov 2018 00:49:45 +0300 Subject: [PATCH 09/12] Bump version --- small_small_hr/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/small_small_hr/__init__.py b/small_small_hr/__init__.py index 19529b5..acd251c 100644 --- a/small_small_hr/__init__.py +++ b/small_small_hr/__init__.py @@ -1,5 +1,5 @@ """ Main init file for small_small_hr """ -VERSION = (0, 1, 1) +VERSION = (0, 1, 2) __version__ = '.'.join(str(v) for v in VERSION) From 5a7729d270862c72182c9a825476e9836550b3c2 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Fri, 16 Nov 2018 00:50:17 +0300 Subject: [PATCH 10/12] Code cleanup --- small_small_hr/apps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/small_small_hr/apps.py b/small_small_hr/apps.py index 2cad889..68ac393 100644 --- a/small_small_hr/apps.py +++ b/small_small_hr/apps.py @@ -13,7 +13,7 @@ class SmallSmallHrConfig(AppConfig): def ready(self): # pylint: disable=unused-variable - import small_small_hr.signals # noqa + import small_small_hr.signals # noqa # set up app settings from django.conf import settings From 94cafe0bb685ed3511c54cb6e849f54062dad27c Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Fri, 16 Nov 2018 01:06:27 +0300 Subject: [PATCH 11/12] Move test_one_leave_day to models.py --- small_small_hr/models.py | 68 +++++++++++++++------ small_small_hr/utils.py | 41 ------------- tests/test_models.py | 128 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 171 insertions(+), 66 deletions(-) diff --git a/small_small_hr/models.py b/small_small_hr/models.py index f7d9a3e..d9ead5d 100644 --- a/small_small_hr/models.py +++ b/small_small_hr/models.py @@ -7,9 +7,8 @@ from django.conf import settings from django.contrib.postgres.fields import JSONField from django.db import models -from django.db.models import Sum -from django.db.models import Value as V -from django.db.models.functions import Coalesce +from django.db.models import Q +from django.utils import timezone from django.utils.translation import ugettext as _ from phonenumber_field.modelfields import PhoneNumberField @@ -134,30 +133,25 @@ def get_approved_leave_days(self, year: int = datetime.today().year): Get approved leave days in the current year """ # pylint: disable=no-member - queryset = self.leave_set.filter( + return get_taken_leave_days( + staffprofile=self, status=Leave.APPROVED, leave_type=Leave.REGULAR, - start__year=year, - end__year=year).annotate( - duration=models.F('end')-models.F('start')) - return queryset.aggregate( - leave=Coalesce(Sum('duration'), - V(timedelta(days=0))))['leave'] + start_year=year, + end_year=year + ) def get_approved_sick_days(self, year: int = datetime.today().year): """ Get approved leave days in the current year """ - # pylint: disable=no-member - queryset = self.leave_set.filter( + return get_taken_leave_days( + staffprofile=self, status=Leave.APPROVED, leave_type=Leave.SICK, - start__year=year, - end__year=year).annotate( - duration=models.F('end')-models.F('start')) - return queryset.aggregate( - leave=Coalesce(Sum('duration'), - V(timedelta(days=0))))['leave'] + start_year=year, + end_year=year + ) def get_available_leave_days(self, year: int = datetime.today().year): """ @@ -372,7 +366,6 @@ def get_cumulative_leave_taken(self): Returns a timedelta """ - from small_small_hr.utils import get_taken_leave_days return get_taken_leave_days( staffprofile=self.staff, status=Leave.APPROVED, @@ -406,3 +399,40 @@ def get_available_leave_days(self, month: int = 12): starting_balance = self.carried_over_days return Decimal(earned + starting_balance - taken) + + +def get_days(start: object, end: object): + """ + Yield the days between two datetime objects + """ + current_tz = timezone.get_current_timezone() + local_start = current_tz.normalize(start) + local_end = current_tz.normalize(end) + span = local_end.date() - local_start.date() + for i in range(span.days + 1): + yield local_start.date() + timedelta(days=i) + + +def get_taken_leave_days( + staffprofile: object, + status: str, + leave_type: str, + start_year: int, + end_year: int): + """ + Calculate the number of leave days actually taken, + taking into account weekends and weekend policy + """ + count = Decimal(0) + queryset = Leave.objects.filter( + staff=staffprofile, + status=status, + leave_type=leave_type).filter( + Q(start__year__gte=start_year) | Q(end__year__lte=end_year)) + for leave_obj in queryset: + days = get_days(start=leave_obj.start, end=leave_obj.end) + for day in days: + if day.year >= start_year and day.year <= end_year: + day_value = settings.SSHR_DAY_LEAVE_VALUES[day.isoweekday()] + count = count + Decimal(day_value) + return count diff --git a/small_small_hr/utils.py b/small_small_hr/utils.py index 13dc319..7431378 100644 --- a/small_small_hr/utils.py +++ b/small_small_hr/utils.py @@ -1,52 +1,11 @@ """ Utils module for small small hr """ -from datetime import timedelta -from decimal import Decimal -from django.db.models import Q from django.conf import settings -from django.utils import timezone from small_small_hr.models import AnnualLeave, Leave -def get_days(start: object, end: object): - """ - Yield the days between two datetime objects - """ - current_tz = timezone.get_current_timezone() - local_start = current_tz.normalize(start) - local_end = current_tz.normalize(end) - span = local_end.date() - local_start.date() - for i in range(span.days + 1): - yield local_start.date() + timedelta(days=i) - - -def get_taken_leave_days( - staffprofile: object, - status: str, - leave_type: str, - start_year: int, - end_year: int): - """ - Calculate the number of leave days actually taken, - taking into account weekends and weekend policy - """ - count = Decimal(0) - queryset = Leave.objects.filter( - staff=staffprofile, - status=status, - leave_type=leave_type).filter( - Q(start__year__gte=start_year) | Q(end__year__lte=end_year)) - for leave_obj in queryset: - days = get_days(start=leave_obj.start, end=leave_obj.end) - for day in days: - if day.year >= start_year and day.year <= end_year: - day_value = settings.SSHR_DAY_LEAVE_VALUES[day.isoweekday()] - count = count + Decimal(day_value) - return count - - def get_carry_over(staffprofile: object, year: int, leave_type: str): """ Get carried over leave days diff --git a/tests/test_models.py b/tests/test_models.py index 85eb550..63c6139 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -10,7 +10,7 @@ import pytz from model_mommy import mommy -from small_small_hr.models import Leave +from small_small_hr.models import Leave, get_taken_leave_days class TestModels(TestCase): @@ -202,8 +202,8 @@ def test_staffprofile_str(self): 3: 1, # Wednesday 4: 1, # Thursday 5: 1, # Friday - 6: 0, # Saturday - 7: 0, # Sunday + 6: 1, # Saturday + 7: 1, # Sunday } ) def test_get_approved_leave_days(self): @@ -224,8 +224,8 @@ def test_get_approved_leave_days(self): start=start + timedelta(days=10), end=start + timedelta(days=14), leave_type=Leave.REGULAR, status=Leave.APPROVED) - self.assertEqual(timedelta(days=10).days, - staff.get_approved_leave_days(year=start.year).days) + self.assertEqual(timedelta(days=12).days, + staff.get_approved_leave_days(year=start.year)) @override_settings( SSHR_DAY_LEAVE_VALUES={ @@ -257,7 +257,7 @@ def test_get_approved_sick_days(self): end=start + timedelta(days=15), leave_type=Leave.SICK, status=Leave.APPROVED) self.assertEqual(timedelta(days=9).days, - staff.get_approved_sick_days(year=start.year).days) + staff.get_approved_sick_days(year=start.year)) @override_settings( SSHR_DAY_LEAVE_VALUES={ @@ -296,6 +296,44 @@ def test_staffprofile_get_available_leave_days(self): # remaining should be 21 - 10 self.assertEqual(21 - 10, staff.get_available_leave_days(year=2017)) + @override_settings( + SSHR_DAY_LEAVE_VALUES={ + 1: 1, # Monday + 2: 1, # Tuesday + 3: 1, # Wednesday + 4: 1, # Thursday + 5: 1, # Friday + 6: 0, # Saturday + 7: 0, # Sunday + } + ) + def test_one_leave_day(self): + """ + Test one leave day + """ + user = mommy.make('auth.User', first_name='Mosh', last_name='Pitt') + staff = mommy.make('small_small_hr.StaffProfile', user=user) + mommy.make('small_small_hr.AnnualLeave', staff=staff, year=2017, + leave_type=Leave.REGULAR, allowed_days=21) + + # ONE DAY of leave because Saturday and Sunday are not counted + start = datetime( + 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime( + 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + + # add some approved leave days + mommy.make( + 'small_small_hr.Leave', staff=staff, start=start, + end=end, leave_type=Leave.REGULAR, + status=Leave.APPROVED) + + staff.refresh_from_db() + + # remaining should be 21 - 10 + self.assertEqual(1, staff.get_approved_leave_days(year=2017)) + self.assertEqual(20, staff.get_available_leave_days(year=2017)) + @override_settings( SSHR_DAY_LEAVE_VALUES={ 1: 1, # Monday @@ -399,3 +437,81 @@ def test_overtime_duration(self): 'small_small_hr.OverTime', date=now.date(), start=now.time(), end=end.time(), staff=staff).get_duration().seconds) + + @override_settings( + SSHR_DAY_LEAVE_VALUES={ + 1: 1, # Monday + 2: 1, # Tuesday + 3: 1, # Wednesday + 4: 1, # Thursday + 5: 1, # Friday + 6: 0, # Saturday + 7: 0, # Sunday + } + ) + def test_get_taken_leave_days(self): + """ + Test get_taken_leave_days + """ + user = mommy.make('auth.User', first_name='Mosh', last_name='Pitt') + staff = mommy.make('small_small_hr.StaffProfile', user=user) + + # 10 days of leave because Saturday and Sunday are not counted + start = datetime( + 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime( + 2017, 6, 16, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + + # add some approved leave days + mommy.make( + 'small_small_hr.Leave', staff=staff, start=start, + end=end, leave_type=Leave.REGULAR, + status=Leave.APPROVED) + + leave_days = get_taken_leave_days( + staffprofile=staff, + status=Leave.APPROVED, + leave_type=Leave.REGULAR, + start_year=2017, + end_year=2017) + + self.assertEqual(10, leave_days) + + @override_settings( + SSHR_DAY_LEAVE_VALUES={ + 1: 1, # Monday + 2: 1, # Tuesday + 3: 1, # Wednesday + 4: 1, # Thursday + 5: 1, # Friday + 6: 0.5, # Saturday + 7: 0, # Sunday + } + ) + def test_get_taken_leave_days_half_saturday(self): + """ + Test get_taken_leave_days + """ + user = mommy.make('auth.User', first_name='Kel', last_name='Vin') + staff = mommy.make('small_small_hr.StaffProfile', user=user) + + # 10.5 days of leave because Saturday==0.5 and Sunday not counted + start = datetime( + 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime( + 2017, 6, 16, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + + # add some approved leave days + mommy.make( + 'small_small_hr.Leave', staff=staff, start=start, + end=end, leave_type=Leave.REGULAR, + status=Leave.APPROVED) + + leave_days = get_taken_leave_days( + staffprofile=staff, + status=Leave.APPROVED, + leave_type=Leave.REGULAR, + start_year=2017, + end_year=2017) + + self.assertEqual(10.5, leave_days) From caba74f4347a983a2c8b204e74c9d893d700a14f Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Fri, 16 Nov 2018 01:07:16 +0300 Subject: [PATCH 12/12] Remove tests for test_one_leave_day --- tests/test_utils.py | 81 +-------------------------------------------- 1 file changed, 1 insertion(+), 80 deletions(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index 757e666..9774c3d 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -9,8 +9,7 @@ from model_mommy import mommy from small_small_hr.models import Leave, StaffProfile -from small_small_hr.utils import (create_annual_leave, get_carry_over, - get_taken_leave_days) +from small_small_hr.utils import create_annual_leave, get_carry_over class TestUtils(TestCase): @@ -125,81 +124,3 @@ def test_create_annual_leave(self): self.assertEqual(0, obj3.carried_over_days) self.assertEqual(10, obj3.allowed_days) self.assertEqual(Leave.SICK, obj3.leave_type) - - @override_settings( - SSHR_DAY_LEAVE_VALUES={ - 1: 1, # Monday - 2: 1, # Tuesday - 3: 1, # Wednesday - 4: 1, # Thursday - 5: 1, # Friday - 6: 0, # Saturday - 7: 0, # Sunday - } - ) - def test_get_taken_leave_days(self): - """ - Test get_taken_leave_days - """ - user = mommy.make('auth.User', first_name='Mosh', last_name='Pitt') - staff = user.staffprofile - - # 10 days of leave because Saturday and Sunday are not counted - start = datetime( - 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 16, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - - # add some approved leave days - mommy.make( - 'small_small_hr.Leave', staff=staff, start=start, - end=end, leave_type=Leave.REGULAR, - status=Leave.APPROVED) - - leave_days = get_taken_leave_days( - staffprofile=staff, - status=Leave.APPROVED, - leave_type=Leave.REGULAR, - start_year=2017, - end_year=2017) - - self.assertEqual(10, leave_days) - - @override_settings( - SSHR_DAY_LEAVE_VALUES={ - 1: 1, # Monday - 2: 1, # Tuesday - 3: 1, # Wednesday - 4: 1, # Thursday - 5: 1, # Friday - 6: 0.5, # Saturday - 7: 0, # Sunday - } - ) - def test_get_taken_leave_days_half_saturday(self): - """ - Test get_taken_leave_days - """ - user = mommy.make('auth.User', first_name='Kel', last_name='Vin') - staff = user.staffprofile - - # 10.5 days of leave because Saturday==0.5 and Sunday not counted - start = datetime( - 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 16, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - - # add some approved leave days - mommy.make( - 'small_small_hr.Leave', staff=staff, start=start, - end=end, leave_type=Leave.REGULAR, - status=Leave.APPROVED) - - leave_days = get_taken_leave_days( - staffprofile=staff, - status=Leave.APPROVED, - leave_type=Leave.REGULAR, - start_year=2017, - end_year=2017) - - self.assertEqual(10.5, leave_days)