Skip to content

Commit

Permalink
Merge pull request #496 from MetaPhase-Consulting/feature/handshake-m…
Browse files Browse the repository at this point in the history
…odel

Add model and endpoints handshake
  • Loading branch information
elizabeth-jimenez authored Apr 7, 2021
2 parents c04d848 + 075aadd commit c961d8d
Show file tree
Hide file tree
Showing 9 changed files with 294 additions and 7 deletions.
33 changes: 33 additions & 0 deletions talentmap_api/bidding/migrations/0002_bidhandshake.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Generated by Django 2.2.18 on 2021-04-01 19:22

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('user_profile', '0002_savedsearch_is_bureau'),
('bidding', '0001_initial'),
]

operations = [
migrations.CreateModel(
name='BidHandshake',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('bidder_perdet', models.CharField(help_text='The bidder being offered a handshake', max_length=255)),
('cp_id', models.CharField(help_text='The cycle position ID', max_length=255)),
('status', models.CharField(choices=[('O', 'Handshake offered'), ('R', 'Handshake revoked'), ('A', 'Handshake accepted'), ('D', 'Handshake declined')], default='O', max_length=2)),
('date_created', models.DateTimeField(auto_now_add=True)),
('update_date', models.DateTimeField(auto_now_add=True)),
('is_cdo_update', models.BooleanField(default=False)),
('last_editing_bidder', models.ForeignKey(help_text='The last acceptee/cdo to edit', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='bidder', to='user_profile.UserProfile')),
('last_editing_user', models.ForeignKey(help_text='The last offerer user to edit', on_delete=django.db.models.deletion.DO_NOTHING, related_name='bureau_user', to='user_profile.UserProfile')),
],
options={
'managed': True,
'unique_together': {('cp_id', 'bidder_perdet')},
},
),
]
30 changes: 30 additions & 0 deletions talentmap_api/bidding/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,33 @@ class Status(DjangoChoices):

class Meta:
managed = False


class BidHandshake(models.Model):
'''
The bid handshake object represents a handshake offered to a bidder
'''

STATUS_CHOICES = [
('O', 'Handshake offered'),
('R', 'Handshake revoked'),
('A', 'Handshake accepted'),
('D', 'Handshake declined'),
]

bidder_perdet = models.CharField(max_length=255, null=False, help_text="The bidder being offered a handshake")
cp_id = models.CharField(max_length=255, null=False, help_text="The cycle position ID")
status = models.CharField(
max_length=2,
choices=STATUS_CHOICES,
default='O',
)
date_created = models.DateTimeField(auto_now_add=True)
update_date = models.DateTimeField(auto_now_add=True)
is_cdo_update = models.BooleanField(default=False)
last_editing_user = models.ForeignKey('user_profile.UserProfile', related_name='bureau_user', null=False, on_delete=models.DO_NOTHING, help_text="The last offerer user to edit")
last_editing_bidder = models.ForeignKey('user_profile.UserProfile', related_name='bidder', null=True, on_delete=models.DO_NOTHING, help_text="The last acceptee/cdo to edit")

class Meta:
managed = True
unique_together = ('cp_id', 'bidder_perdet',)
8 changes: 8 additions & 0 deletions talentmap_api/bidding/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from talentmap_api.common.serializers import PrefetchedSerializer, StaticRepresentationField
from talentmap_api.bidding.models import BidHandshake


class BidHandshakeSerializer(PrefetchedSerializer):
class Meta:
model = BidHandshake
fields = "__all__"
16 changes: 16 additions & 0 deletions talentmap_api/bidding/urls/bidding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from django.conf.urls import url
from rest_framework import routers

from talentmap_api.bidding.views import bidhandshake as views
from talentmap_api.common.urls import get_list, get_retrieve, patch_update

router = routers.SimpleRouter()

urlpatterns = [
url(r'^handshake/bureau/(?P<pk>[0-9]+)/(?P<cp_id>[0-9]+)/$', views.BidHandshakeBureauActionView.as_view({'put': 'put', 'delete': 'delete'}), name='bidding.CyclePosition-designation'),
url(r'^handshake/cdo/(?P<pk>[0-9]+)/(?P<cp_id>[0-9]+)/$', views.BidHandshakeCdoActionView.as_view({'put': 'put', 'delete': 'delete'}), name='bidding.CyclePosition-designation'),
url(r'^handshake/bidder/(?P<cp_id>[0-9]+)/$', views.BidHandshakeBidderActionView.as_view({'put': 'put', 'delete': 'delete'}), name='bidding.CyclePosition-designation'),
]


urlpatterns += router.urls
155 changes: 155 additions & 0 deletions talentmap_api/bidding/views/bidhandshake.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import logging
import coreapi
from django.shortcuts import get_object_or_404
from django.core.exceptions import PermissionDenied
from datetime import datetime


from rest_framework.schemas import AutoSchema
from rest_framework import mixins
from rest_framework.viewsets import GenericViewSet
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status

from rest_condition import Or

from talentmap_api.bidding.serializers import BidHandshakeSerializer
import talentmap_api.cdo.services.available_bidders as services
from talentmap_api.bidding.models import BidHandshake

from talentmap_api.user_profile.models import UserProfile

from talentmap_api.common.mixins import FieldLimitableSerializerMixin
from talentmap_api.common.common_helpers import in_group_or_403
from talentmap_api.common.permissions import isDjangoGroupMember
import talentmap_api.fsbid.services.client as client_services
import talentmap_api.fsbid.services.employee as empservices

logger = logging.getLogger(__name__)


class BidHandshakeBureauActionView(FieldLimitableSerializerMixin,
GenericViewSet,
mixins.ListModelMixin,
mixins.RetrieveModelMixin):
'''
add, remove, update an Available Bidder instance
'''
serializer_class = BidHandshakeSerializer
permission_classes = [Or(isDjangoGroupMember('bureau_user'), ) ]

def put(self, serializer, pk, cp_id, **ars):
'''
Offers a handshake to a bidder for a cp_id
'''
# TODO: should we limit this endpoint to only bidder perdets of those who have actually bid on this cp?
# Is it worth the extra network request for the extra validation?
hasBureauPermissions = empservices.has_bureau_permissions(cp_id, self.request.META['HTTP_JWT'])

if not hasBureauPermissions:
raise PermissionDenied()

user = UserProfile.objects.get(user=self.request.user)
hs = BidHandshake.objects.filter(bidder_perdet=pk, cp_id=cp_id)

if hs.exists():
hs.update(last_editing_user=user, status='O', update_date=datetime.now())
return Response(status=status.HTTP_204_NO_CONTENT)
else:
BidHandshake.objects.create(last_editing_user=user, bidder_perdet=pk, cp_id=cp_id, status='O')
return Response(status=status.HTTP_204_NO_CONTENT)

def delete(self, request, pk, cp_id, format=None):
'''
Revokes a handshake from a bidder for a cp_id
'''
hasBureauPermissions = empservices.has_bureau_permissions(cp_id, self.request.META['HTTP_JWT'])

if not hasBureauPermissions:
raise PermissionDenied()

user = UserProfile.objects.get(user=self.request.user)
hs = BidHandshake.objects.filter(bidder_perdet=pk, cp_id=cp_id)

if not hs.exists():
return Response(status=status.HTTP_404_NOT_FOUND)
else:
user = UserProfile.objects.get(user=self.request.user)
hs.update(last_editing_user=user, bidder_perdet=pk, cp_id=cp_id, status='R', update_date=datetime.now())
return Response(status=status.HTTP_204_NO_CONTENT)


class BidHandshakeCdoActionView(FieldLimitableSerializerMixin,
GenericViewSet,
mixins.ListModelMixin,
mixins.RetrieveModelMixin):
'''
add, remove, update an Available Bidder instance
'''
serializer_class = BidHandshakeSerializer
permission_classes = [Or(isDjangoGroupMember('cdo'), ) ]

def put(self, serializer, pk, cp_id, **ars):
'''
CDO accepts a handshake for a bidder for a cp_id
'''
user = UserProfile.objects.get(user=self.request.user)
hs = BidHandshake.objects.filter(bidder_perdet=pk, cp_id=cp_id)

if not BidHandshake.objects.filter(bidder_perdet=pk, cp_id=cp_id, status__in=['O', 'A', 'D']).exists():
return Response(status=status.HTTP_404_NOT_FOUND)
else:
hs.update(last_editing_bidder=user, status='A', is_cdo_update=True, update_date=datetime.now())
return Response(status=status.HTTP_204_NO_CONTENT)

def delete(self, request, pk, cp_id, format=None):
'''
CDO declines a handshake for a bidder for a cp_id
'''
user = UserProfile.objects.get(user=self.request.user)
hs = BidHandshake.objects.filter(bidder_perdet=pk, cp_id=cp_id)

if not BidHandshake.objects.filter(bidder_perdet=pk, cp_id=cp_id, status__in=['O', 'A', 'D']).exists():
return Response(status=status.HTTP_404_NOT_FOUND)
else:
hs.update(last_editing_bidder=user, status='D', is_cdo_update=True, update_date=datetime.now())
return Response(status=status.HTTP_204_NO_CONTENT)


class BidHandshakeBidderActionView(FieldLimitableSerializerMixin,
GenericViewSet,
mixins.ListModelMixin,
mixins.RetrieveModelMixin):
'''
add, remove, update an Available Bidder instance
'''
serializer_class = BidHandshakeSerializer
permission_classes = [Or(isDjangoGroupMember('bidder'), ) ]

def put(self, serializer, cp_id, **ars):
'''
Bidder accepts a handshake for a cp_id
'''
user = UserProfile.objects.get(user=self.request.user)
hs = BidHandshake.objects.filter(bidder_perdet=user.emp_id, cp_id=cp_id)

if not BidHandshake.objects.filter(bidder_perdet=user.emp_id, cp_id=cp_id, status__in=['O', 'A', 'D']).exists():
return Response(status=status.HTTP_404_NOT_FOUND)
else:
hs.update(last_editing_bidder=user, status='A', is_cdo_update=False, update_date=datetime.now())
return Response(status=status.HTTP_204_NO_CONTENT)

def delete(self, request, cp_id, format=None):
'''
Bidder declines handshake for a cp_id
'''
user = UserProfile.objects.get(user=self.request.user)
hs = BidHandshake.objects.filter(bidder_perdet=user.emp_id, cp_id=cp_id)

if not BidHandshake.objects.filter(bidder_perdet=user.emp_id, cp_id=cp_id, status__in=['O', 'A', 'D']).exists():
return Response(status=status.HTTP_404_NOT_FOUND)
else:
hs.update(last_editing_bidder=user, status='D', is_cdo_update=False, update_date=datetime.now())
return Response(status=status.HTTP_204_NO_CONTENT)
2 changes: 1 addition & 1 deletion talentmap_api/bureau/services/available_bidders.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def get_available_bidders_csv(request):
}

for record in data["results"]:
languages = f'' if pydash.get(record, ["languages"]) else "None listed"
languages = '' if pydash.get(record, ["languages"]) else "None listed"
if languages is not "None listed":
for language in record["languages"]:
languages += f'{language["custom_description"]}, '
Expand Down
49 changes: 44 additions & 5 deletions talentmap_api/fsbid/services/bid.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import logging
import jwt
import requests
import pydash

from django.conf import settings

from django.utils.encoding import smart_str

from talentmap_api.common.common_helpers import ensure_date

from talentmap_api.bidding.models import Bid
from talentmap_api.bidding.models import Bid, BidHandshake
import talentmap_api.fsbid.services.common as services

API_ROOT = settings.FSBID_API_URL
Expand All @@ -25,8 +26,9 @@ def user_bids(employee_id, jwt_token, position_id=None):
filteredBids = {}
# Filter out any bids with a status of "D" (deleted)
filteredBids['Data'] = [b for b in list(bids['Data']) if smart_str(b["bs_cd"]) != 'D']
return [fsbid_bid_to_talentmap_bid(bid) for bid in filteredBids.get('Data', []) if bid.get('cp_id') == int(position_id)] if position_id else map(fsbid_bid_to_talentmap_bid, filteredBids.get('Data', []))

mappedBids = [fsbid_bid_to_talentmap_bid(bid) for bid in filteredBids.get('Data', []) if bid.get('cp_id') == int(position_id)] if position_id else map(fsbid_bid_to_talentmap_bid, filteredBids.get('Data', []))
mappedBids = map_bids_handshake_status_by_perdet(mappedBids, employee_id)
return mappedBids

def get_user_bids_csv(employee_id, jwt_token, position_id=None):
'''
Expand All @@ -36,8 +38,6 @@ def get_user_bids_csv(employee_id, jwt_token, position_id=None):

response = services.get_bids_csv(list(data), "bids", jwt_token)

logger.info(response)

return response


Expand Down Expand Up @@ -94,6 +94,45 @@ def remove_bid(employeeId, cyclePositionId, jwt_token):
return requests.delete(url, headers={'JWTAuthorization': jwt_token, 'Content-Type': 'application/json'}, verify=False) # nosec


def map_bids_handshake_status(bids, query = {}):
clonedBids = list(pydash.clone(bids))
for idx, val in enumerate(clonedBids):
# default states
clonedBids[idx]['hs_status_code'] = 'not_offered'
clonedBids[idx]['hs_cdo_indicator'] = False
# look up cp id
cp_id = pydash.get(val, 'position.id', 0)
clonedQuery = { 'cp_id': cp_id, **query }
hsExists = BidHandshake.objects.filter(status__in=['O', 'A', 'D'], **clonedQuery).exists()
# if exists, bidder has a valid HS offer status
if hsExists:
hs = BidHandshake.objects.get(status__in=['O', 'A', 'D'], **clonedQuery)
hsStatus = pydash.get(hs, 'status')

isCDOUpdate = pydash.get(hs, 'is_cdo_update') == 1

if isCDOUpdate:
clonedBids[idx]['hs_cdo_indicator'] = True

hsStatuses = {
'O': "handshake_offered",
'A': "handshake_accepted",
'D': "handshake_declined",
}

clonedBids[idx]['hs_status_code'] = hsStatuses.get(hsStatus, "not_offered")

return clonedBids


def map_bids_handshake_status_by_cp_id(bids, cp_id):
return map_bids_handshake_status(bids, {'cp_id': cp_id})


def map_bids_handshake_status_by_perdet(bids, perdet):
return map_bids_handshake_status(bids, {'bidder_perdet': perdet})


def get_bid_status(statusCode, handshakeCode, assignmentCreateDate, panelMeetingStatus, handshakeAllowed):
'''
Map the FSBid status code and handshake code to a TalentMap status
Expand Down
5 changes: 4 additions & 1 deletion talentmap_api/fsbid/views/bureau.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import talentmap_api.fsbid.services.available_positions as ap_services
import talentmap_api.fsbid.services.employee as empservices
import talentmap_api.fsbid.services.common as com_services
import talentmap_api.fsbid.services.bid as bid_services

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -114,8 +115,10 @@ def get(self, request, pk):

for x in result:
x['has_competing_rank'] = com_services.has_competing_rank(self, x.get('emp_id'), pk)

mappedResult = bid_services.map_bids_handshake_status_by_cp_id(result, pk)

return Response(result)
return Response(mappedResult)

class FSBidBureauPositionBidsExportView(BaseView):
permission_classes = (IsAuthenticatedOrReadOnly,)
Expand Down
3 changes: 3 additions & 0 deletions talentmap_api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
url(r'^api/v1/available_position/', include('talentmap_api.available_positions.urls.available_positions')),
url(r'^api/v1/available_position/tandem/', include('talentmap_api.available_tandem.urls.available_tandem')),

# Bidding
url(r'^api/v1/bidding/', include('talentmap_api.bidding.urls.bidding')),

#CDO
url(r'^api/v1/cdo/', include('talentmap_api.cdo.urls.cdo')),

Expand Down

0 comments on commit c961d8d

Please sign in to comment.