06/09 Update a customer can comment on it issue
This commit is contained in:
@@ -11,6 +11,7 @@ class IssueComment(db.Model):
|
||||
status_at_time = db.Column(db.String(20)) # snapshot of issue status when comment was made
|
||||
body = db.Column(db.Text, nullable=False)
|
||||
created_at = db.Column(db.DateTime, default=now_eastern, nullable=False)
|
||||
is_customer_visible = db.Column(db.Boolean, nullable=False, default=False)
|
||||
|
||||
# Relationships
|
||||
author = db.relationship('User', foreign_keys=[user_id])
|
||||
|
||||
@@ -227,7 +227,28 @@ def view(issue_id):
|
||||
flash('Access denied.', 'danger')
|
||||
return redirect(url_for('issues.index'))
|
||||
if request.method == 'POST':
|
||||
# Customers may only add a comment, and only on issues they follow or reported
|
||||
can_comment = (issue.is_followed_by(current_user) or issue.reported_by == current_user.id)
|
||||
if not can_comment:
|
||||
abort(403)
|
||||
comment_body = request.form.get('update_notes', '').strip()
|
||||
if not comment_body:
|
||||
flash('Comment cannot be empty.', 'warning')
|
||||
return redirect(url_for('issues.view', issue_id=issue_id))
|
||||
comment = IssueComment(
|
||||
issue_id=issue.id,
|
||||
user_id=current_user.id,
|
||||
status_at_time=issue.status,
|
||||
body=comment_body,
|
||||
is_customer_visible=True, # customer comments are always visible to all
|
||||
)
|
||||
db.session.add(comment)
|
||||
db.session.commit()
|
||||
log_action(ACTION_UPDATE, 'Issue', issue.id,
|
||||
f'#{issue.id}',
|
||||
'customer comment added')
|
||||
flash('Comment posted.', 'success')
|
||||
return redirect(url_for('issues.view', issue_id=issue_id))
|
||||
|
||||
form = IssueUpdateForm(obj=issue)
|
||||
staff = User.query.filter(User.role.in_(['admin', 'director', 'inspector'])).order_by(User.username).all()
|
||||
@@ -274,6 +295,7 @@ def view(issue_id):
|
||||
user_id = current_user.id,
|
||||
status_at_time = issue.status,
|
||||
body = comment_body,
|
||||
is_customer_visible = 'is_customer_visible' in request.form,
|
||||
)
|
||||
db.session.add(comment)
|
||||
|
||||
@@ -418,6 +440,11 @@ def view(issue_id):
|
||||
return redirect(url_for('issues.view', issue_id=issue_id))
|
||||
|
||||
is_following = issue.is_followed_by(current_user)
|
||||
if current_user.role == 'customer':
|
||||
comments = (issue.comments
|
||||
.filter_by(is_customer_visible=True)
|
||||
.order_by(IssueComment.created_at.asc()).all())
|
||||
else:
|
||||
comments = issue.comments.order_by(IssueComment.created_at.asc()).all()
|
||||
return render_template('issues/view.html',
|
||||
issue=issue,
|
||||
|
||||
@@ -183,6 +183,20 @@
|
||||
{% else %}
|
||||
<span class="badge bg-secondary" style="font-size:.65rem;">{{ c.author.role|replace('_',' ')|title }}</span>
|
||||
{% endif %}
|
||||
{# Visibility indicator — staff only #}
|
||||
{% if current_user.role != 'customer' %}
|
||||
{% if c.is_customer_visible %}
|
||||
<span class="badge bg-success bg-opacity-10 text-success border border-success"
|
||||
style="font-size:.6rem;" title="Customer can see this comment">
|
||||
<i class="bi bi-eye me-1"></i>Customer visible
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary bg-opacity-10 text-secondary border border-secondary"
|
||||
style="font-size:.6rem;" title="Hidden from customer">
|
||||
<i class="bi bi-eye-slash me-1"></i>Staff only
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="badge bg-{{ 'success' if c.status_at_time == 'resolved' else 'info text-dark' if c.status_at_time == 'pending_verification' else 'warning text-dark' if c.status_at_time == 'in_progress' else 'danger' }}"
|
||||
@@ -198,27 +212,67 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
<hr class="mx-3 my-0">
|
||||
{% elif current_user.role == 'customer' %}
|
||||
<div class="px-3 pt-3 pb-0">
|
||||
<p class="text-muted small"><i class="bi bi-chat-left me-1"></i>No comments yet.</p>
|
||||
</div>
|
||||
<hr class="mx-3 my-0">
|
||||
{% endif %}
|
||||
|
||||
{# Add Comment form — submits to the same view POST endpoint #}
|
||||
{# ── Add Comment form ─────────────────────────────────────────────── #}
|
||||
{% set can_customer_comment = current_user.role == 'customer' and (is_following or issue.reported_by == current_user.id) %}
|
||||
|
||||
{% if can_edit %}
|
||||
{# Staff comment form with visibility checkbox #}
|
||||
<div class="card-body">
|
||||
<p class="fw-semibold small mb-2">Add Comment</p>
|
||||
<form method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
{# Keep current status and assigned_to so the route doesn't change them #}
|
||||
<input type="hidden" name="status" value="{{ issue.status }}">
|
||||
<input type="hidden" name="assigned_to" value="{{ issue.assigned_to or 0 }}">
|
||||
<div class="mb-2">
|
||||
<textarea name="update_notes" class="form-control" rows="3"
|
||||
placeholder="Write a comment…" required></textarea>
|
||||
</div>
|
||||
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2">
|
||||
<div class="form-check form-check-inline mb-0">
|
||||
<input class="form-check-input" type="checkbox"
|
||||
name="is_customer_visible" id="is_customer_visible" value="1">
|
||||
<label class="form-check-label small text-muted" for="is_customer_visible">
|
||||
<i class="bi bi-eye me-1"></i>Share with customer
|
||||
</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-sm">
|
||||
<i class="bi bi-send me-1"></i>Post Comment
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% elif can_customer_comment %}
|
||||
{# Customer comment form — visible to all by design #}
|
||||
<div class="card-body">
|
||||
<p class="fw-semibold small mb-2">Add Comment</p>
|
||||
<form method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="mb-2">
|
||||
<textarea name="update_notes" class="form-control" rows="3"
|
||||
placeholder="Write a comment…" required></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-sm">
|
||||
<i class="bi bi-send me-1"></i>Post Comment
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% elif current_user.role != 'customer' %}
|
||||
|
||||
{% elif current_user.role == 'customer' %}
|
||||
<div class="card-body py-2">
|
||||
<p class="text-muted small mb-0">
|
||||
<i class="bi bi-bell me-1"></i>Follow this issue to add comments.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<div class="card-body py-2">
|
||||
<p class="text-muted small mb-0">
|
||||
<i class="bi bi-lock me-1"></i>Only assigned staff can add comments.
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
"""phase22 — add is_customer_visible to issue_comments
|
||||
|
||||
Staff comments default to hidden from customers (is_customer_visible=FALSE).
|
||||
Staff can tick a checkbox to share a comment with the customer.
|
||||
Customer comments are always visible (is_customer_visible=TRUE, set at write time).
|
||||
"""
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision = 'phase22_comment_visibility'
|
||||
down_revision = 'phase21_performance_indexes'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def _column_exists(bind, table: str, column: str) -> bool:
|
||||
result = bind.execute(sa.text(
|
||||
"SELECT COUNT(*) FROM information_schema.columns "
|
||||
"WHERE table_schema = DATABASE() "
|
||||
" AND table_name = :table "
|
||||
" AND column_name = :column"
|
||||
), {'table': table, 'column': column})
|
||||
return result.scalar() > 0
|
||||
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
if not _column_exists(bind, 'issue_comments', 'is_customer_visible'):
|
||||
op.execute(sa.text(
|
||||
'ALTER TABLE issue_comments '
|
||||
'ADD COLUMN is_customer_visible BOOLEAN NOT NULL DEFAULT FALSE'
|
||||
))
|
||||
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
if _column_exists(bind, 'issue_comments', 'is_customer_visible'):
|
||||
op.execute(sa.text(
|
||||
'ALTER TABLE issue_comments DROP COLUMN is_customer_visible'
|
||||
))
|
||||
Reference in New Issue
Block a user