Merge pull request #270 from dod-ccpo/internal-comments2

Request Internal comments
This commit is contained in:
richard-dds 2018-09-11 16:50:51 -04:00 committed by GitHub
commit 67f4b6bb23
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 122 additions and 26 deletions

View File

@ -0,0 +1,36 @@
"""add request.internal_comments
Revision ID: 2572be7fb7fc
Revises: dea6b8e09d63
Create Date: 2018-09-11 15:28:27.252248
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '2572be7fb7fc'
down_revision = 'dea6b8e09d63'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('request_internal_comments',
sa.Column('id', sa.BigInteger(), nullable=False),
sa.Column('text', sa.String(), nullable=True),
sa.Column('user_id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('request_id', postgresql.UUID(as_uuid=True), nullable=True),
sa.ForeignKeyConstraint(['request_id'], ['requests.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('request_internal_comments')
# ### end Alembic commands ###

View File

@ -13,6 +13,7 @@ from atst.models.request import Request
from atst.models.request_revision import RequestRevision from atst.models.request_revision import RequestRevision
from atst.models.request_status_event import RequestStatusEvent, RequestStatus from atst.models.request_status_event import RequestStatusEvent, RequestStatus
from atst.models.request_review import RequestReview from atst.models.request_review import RequestReview
from atst.models.request_internal_comment import RequestInternalComment
from atst.utils import deep_merge from atst.utils import deep_merge
from .exceptions import NotFoundError, UnauthorizedError from .exceptions import NotFoundError, UnauthorizedError
@ -311,3 +312,13 @@ WHERE requests_with_status.status = :status
Requests.set_status(request, RequestStatus.CHANGES_REQUESTED_TO_FINVER) Requests.set_status(request, RequestStatus.CHANGES_REQUESTED_TO_FINVER)
return Requests._add_review(user, request, review_data) return Requests._add_review(user, request, review_data)
@classmethod
def update_internal_comments(cls, user, request, comment_text):
Authorization.check_can_approve_request(user)
request.internal_comments = RequestInternalComment(text=comment_text, user=user)
db.session.add(request)
db.session.commit()
return request

View File

@ -0,0 +1,8 @@
from wtforms.fields import TextAreaField
from wtforms.validators import Optional
from .forms import ValidatedForm
class InternalCommentForm(ValidatedForm):
text = TextAreaField(validators=[Optional()])

View File

@ -16,3 +16,4 @@ from .environment import Environment
from .attachment import Attachment from .attachment import Attachment
from .request_revision import RequestRevision from .request_revision import RequestRevision
from .request_review import RequestReview from .request_review import RequestReview
from .request_internal_comment import RequestInternalComment

View File

@ -46,6 +46,8 @@ class Request(Base):
"RequestRevision", back_populates="request", order_by="RequestRevision.sequence" "RequestRevision", back_populates="request", order_by="RequestRevision.sequence"
) )
internal_comments = relationship("RequestInternalComment", uselist=False)
@property @property
def latest_revision(self): def latest_revision(self):
if self.revisions: if self.revisions:
@ -163,3 +165,7 @@ class Request(Base):
@property @property
def reviews(self): def reviews(self):
return [status.review for status in self.status_events if status.review] return [status.review for status in self.status_events if status.review]
@property
def internal_comments_text(self):
return self.internal_comments.text if self.internal_comments else ""

View File

@ -0,0 +1,16 @@
from sqlalchemy import Column, BigInteger, String, ForeignKey
from sqlalchemy.orm import relationship
from atst.models import Base
class RequestInternalComment(Base):
__tablename__ = "request_internal_comments"
id = Column(BigInteger, primary_key=True)
text = Column(String())
user_id = Column(ForeignKey("users.id"), nullable=False)
user = relationship("User")
request_id = Column(ForeignKey("requests.id", ondelete="CASCADE"))

View File

@ -12,6 +12,7 @@ from . import requests_bp
from atst.domain.requests import Requests from atst.domain.requests import Requests
from atst.domain.exceptions import NotFoundError from atst.domain.exceptions import NotFoundError
from atst.forms.ccpo_review import CCPOReviewForm from atst.forms.ccpo_review import CCPOReviewForm
from atst.forms.internal_comment import InternalCommentForm
def task_order_dictionary(task_order): def task_order_dictionary(task_order):
@ -31,16 +32,19 @@ def render_approval(request, form=None):
if pending_final_approval and request.task_order: if pending_final_approval and request.task_order:
data["task_order"] = task_order_dictionary(request.task_order) data["task_order"] = task_order_dictionary(request.task_order)
internal_comment_form = InternalCommentForm(text=request.internal_comments_text)
return render_template( return render_template(
"requests/approval.html", "requests/approval.html",
data=data, data=data,
status_events=reversed(request.status_events), status_events=reversed(request.status_events),
request_id=request.id, request=request,
current_status=request.status.value, current_status=request.status.value,
pending_review=pending_review, pending_review=pending_review,
financial_review=pending_final_approval, financial_review=pending_final_approval,
pdf_available=request.task_order and request.task_order.pdf, pdf_available=request.task_order and request.task_order.pdf,
f=form or CCPOReviewForm(), f=form or CCPOReviewForm(),
internal_comment_form=internal_comment_form,
) )
@ -83,3 +87,13 @@ def task_order_pdf_download(request_id):
else: else:
raise NotFoundError("task_order pdf") raise NotFoundError("task_order pdf")
@requests_bp.route("/requests/internal_comments/<string:request_id>", methods=["POST"])
def create_internal_comment(request_id):
form = InternalCommentForm(http_request.form)
if form.validate():
request = Requests.get(g.current_user, request_id)
Requests.update_internal_comments(g.current_user, request, form.data["text"])
return redirect(url_for("requests.approval", request_id=request_id))

View File

@ -15,17 +15,15 @@
) }} ) }}
{% endif %} {% endif %}
<form method="POST" action="{{ url_for("requests.submit_approval", request_id=request_id) }}" autocomplete="off">
{{ f.csrf_token }}
<section class='panel'> <section class='panel'>
<header class='panel__heading request-approval__heading'> <header class='panel__heading request-approval__heading'>
<h1 class='h2'>Request #{{ request_id }}</h1> <h1 class='h2'>Request #{{ request.id }}</h1>
<span class='label label--info'>{{ current_status }}</span> <span class='label label--info'>{{ current_status }}</span>
</header> </header>
<div class='panel__content'> <div class='panel__content'>
{% with data=data, request_id=request_id %} {% with data=data, request_id=request.id %}
{% include "requests/_review.html" %} {% include "requests/_review.html" %}
{% endwith %} {% endwith %}
@ -34,6 +32,8 @@
</section> </section>
{% if pending_review %} {% if pending_review %}
<form method="POST" action="{{ url_for("requests.submit_approval", request_id=request.id) }}" autocomplete="off">
{{ f.csrf_token }}
<section class='panel'> <section class='panel'>
<header class='panel__heading'> <header class='panel__heading'>
<h2 class='h3'>Approval Notes</h2> <h2 class='h3'>Approval Notes</h2>
@ -104,25 +104,6 @@
<h4 class="h3">CCPO Internal Notes</h4>
<p>You may add additional comments and notes for internal CCPO reference and follow-up here.</p>
<div class='form-row'>
<div class='form-col'>
<div class='usa-input'>
<label for='notes'>Internal Comments <em>(optional)</em></label>
<textarea id='notes' placeholder='Add notes or comments for internal CCPO reference.'/></textarea>
</div>
</div>
</div>
</div>
</section> </section>
<section class='action-group'> <section class='action-group'>
@ -133,8 +114,24 @@
<span>Cancel</span> <span>Cancel</span>
</a> </a>
</section> </section>
</form>
{% endif %} {% endif %}
<section class='panel'>
<h4 class="h3">CCPO Internal Notes</h4>
<p>You may add additional comments and notes for internal CCPO reference and follow-up here.</p>
<div class='form-row'>
<div class='form-col'>
<form method="POST" action="{{ url_for('requests.create_internal_comment', request_id=request.id) }}">
{{ internal_comment_form.csrf_token }}
{{ TextInput(internal_comment_form.text, paragraph=True) }}
<button type="submit">Leave comment</button>
</form>
</div>
</div>
</div>
</section>
<section class='panel'> <section class='panel'>
<header class='panel__heading'> <header class='panel__heading'>
<h2 class='h3 request-approval__columns__heading'>Approval Log</h2> <h2 class='h3 request-approval__columns__heading'>Approval Log</h2>
@ -182,8 +179,6 @@
</div> </div>
</div> </div>
</section> </section>
</form>
</article> </article>
{% endblock %} {% endblock %}

View File

@ -221,3 +221,12 @@ def test_request_changes_to_financial_verification_info():
assert request.status == RequestStatus.CHANGES_REQUESTED_TO_FINVER assert request.status == RequestStatus.CHANGES_REQUESTED_TO_FINVER
current_review = request.latest_status.review current_review = request.latest_status.review
assert current_review.fname_mao == review_data["fname_mao"] assert current_review.fname_mao == review_data["fname_mao"]
def test_update_internal_comments():
request = RequestFactory.create()
ccpo = UserFactory.from_atat_role("ccpo")
request = Requests.update_internal_comments(ccpo, request, "this is my comment")
assert request.internal_comments.text == "this is my comment"