Index: b/apps/app_queue.c
===================================================================
--- a/apps/app_queue.c
+++ b/apps/app_queue.c
@@ -1719,6 +1719,9 @@ static int log_membername_as_agent;
 /*! \brief queues.conf [general] option */
 static int force_longest_waiting_caller;
 
+/*! \brief queues.conf [general] option */
+static int allow_wrapup_termination = 0;
+
 /*! \brief name of the ringinuse field in the realtime database */
 static char *realtime_ringinuse_field;
 
@@ -1945,6 +1948,13 @@ struct member {
 	unsigned int delme:1;                /*!< Flag to delete entry on reload */
 	char rt_uniqueid[80];                /*!< Unique id of realtime member entry */
 	unsigned int ringinuse:1;            /*!< Flag to ring queue members even if their status is 'inuse' */
+	unsigned int wrapup_terminated:1;    /*!< Flag marking agent wrapup timeout has been terminated */
+};
+
+
+struct wrapup_sched {
+	char interface[AST_CHANNEL_NAME];
+	int sched_id;
 };
 
 enum empty_conditions {
@@ -2096,6 +2106,7 @@ struct rule_list {
 static AST_LIST_HEAD_STATIC(rule_lists, rule_list);
 
 static struct ao2_container *queues;
+static struct ast_sched_context *wrapup_sched_ctx;
 
 static void update_realtime_members(struct call_queue *q);
 static struct member *interface_exists(struct call_queue *q, const char *interface);
@@ -2103,6 +2114,7 @@ static int set_member_paused(const char
 static int update_queue_ent_skills_next_check(struct call_queue *q);
 static int member_is_selected(struct queue_ent *qe, struct member *mem);
 static int update_queue(struct call_queue *q, struct member *member, int callcompletedinsl, time_t starttime);
+static int publish_queue_member_wrapup(struct member *member, int paused);
 
 static struct member *find_member_by_queuename_and_interface(const char *queuename, const char *interface);
 /*! \brief sets the QUEUESTATUS channel variable */
@@ -2400,6 +2412,12 @@ static struct ast_manager_event_blob *qu
 	return queue_member_to_ami("QueueMemberRinginuse", message);
 }
 
+static struct ast_manager_event_blob *queue_member_wrapup_start_to_ami(struct stasis_message *message)
+{
+	return queue_member_to_ami("QueueMemberWrapupStart", message);
+}
+
+
 STASIS_MESSAGE_TYPE_DEFN_LOCAL(queue_member_status_type,
 	.to_ami = queue_member_status_to_ami,
 	);
@@ -2418,6 +2436,10 @@ STASIS_MESSAGE_TYPE_DEFN_LOCAL(queue_mem
 STASIS_MESSAGE_TYPE_DEFN_LOCAL(queue_member_ringinuse_type,
 	.to_ami = queue_member_ringinuse_to_ami,
 	);
+STASIS_MESSAGE_TYPE_DEFN_LOCAL(queue_member_wrapup_start_type,
+	.to_ami = queue_member_wrapup_start_to_ami,
+	);
+
 
 static struct ast_manager_event_blob *queue_multi_channel_to_ami(const char *type, struct stasis_message *message)
 {
@@ -2594,7 +2616,7 @@ static void queue_publish_member_blob(st
 static struct ast_json *queue_member_blob_create(struct call_queue *q, struct member *mem)
 {
 	return ast_json_pack("{s: s, s: s, s: s, s: s, s: s, s: i, s: i, s: i, s: i, s: i, s: i, s: i, s: i, s: s, s: i, s: i, s: s}",
-		"Queue", q->name,
+		"Queue", q ? q->name : "",
 		"MemberName", mem->membername,
 		"Interface", mem->interface,
 		"StateInterface", mem->state_interface,
@@ -2682,6 +2704,7 @@ static int get_member_status(struct call
 				ast_debug(4, "%s is unavailable because he is paused'\n", member->membername);
 				break;
 			} else if ((conditions & QUEUE_EMPTY_WRAPUP)
+				&& !member->wrapup_terminated
 				&& member->lastcall
 				&& get_wrapuptime(q, member)
 				&& (time(NULL) - get_wrapuptime(q, member) < member->lastcall)) {
@@ -2708,6 +2731,66 @@ static int get_member_status(struct call
 	return -1;
 }
 
+
+/*
+ * Contains objects of type struct wrapup_sched
+ * Used for canceling scheduled wrapup time
+ * when wrapup is teriminated by external action (AMI, CLI).
+ */
+
+static struct ao2_container *pending_wrapups;
+#define MAX_PENDING_WRAPUPS_BUCKETS 353
+
+static int pending_wrapups_hash(const void *obj, const int flags)
+{
+	const struct member *object;
+	const char *key;
+
+	switch (flags & OBJ_SEARCH_MASK) {
+	case OBJ_SEARCH_KEY:
+		key = obj;
+		break;
+	case OBJ_SEARCH_OBJECT:
+		object = obj;
+		key = object->interface;
+		break;
+	default:
+		ast_assert(0);
+		return 0;
+	}
+	return ast_str_case_hash(key);
+}
+
+static int pending_wrapups_cmp(void *obj, void *arg, int flags)
+{
+	const struct member *object_left = obj;
+	const struct member *object_right = arg;
+	const char *right_key = arg;
+	int cmp;
+
+	switch (flags & OBJ_SEARCH_MASK) {
+	case OBJ_SEARCH_OBJECT:
+		right_key = object_right->interface;
+		/* Fall through */
+	case OBJ_SEARCH_KEY:
+		cmp = strcasecmp(object_left->interface, right_key);
+		break;
+	case OBJ_SEARCH_PARTIAL_KEY:
+		/* Not supported by container. */
+		ast_assert(0);
+		return 0;
+	default:
+		cmp = 0;
+		break;
+	}
+	if (cmp) {
+		return 0;
+	}
+	return CMP_MATCH;
+}
+
+
+
 /*
  * A "pool" of member objects that calls are currently pending on. If an
  * agent is a member of multiple queues it's possible for that agent to be
@@ -2839,7 +2922,7 @@ static int is_member_available(struct ca
 
 	/* Let wrapuptimes override device state availability */
 	wrapuptime = get_wrapuptime(q, mem);
-	if (mem->lastcall && wrapuptime && (time(NULL) - wrapuptime < mem->lastcall)) {
+	if (!mem->wrapup_terminated && mem->lastcall && wrapuptime && (time(NULL) - wrapuptime < mem->lastcall)) {
 		available = 0;
 	}
 	return available;
@@ -3276,6 +3359,7 @@ static void clear_queue(struct call_queu
 			mem->calls = 0;
 			mem->callcompletedinsl = 0;
 			mem->lastcall = 0;
+			mem->wrapup_terminated = 0;
 			mem->starttime = 0;
 			ao2_ref(mem, -1);
 		}
@@ -5829,7 +5913,7 @@ static int can_ring_entry(struct queue_e
 	} else {
 		wrapuptime = get_wrapuptime(qe->parent, memberp);
 	}
-	if (wrapuptime && (time(NULL) - memberp->lastcall) < wrapuptime) {
+	if (!memberp->wrapup_terminated && (time(NULL) - memberp->lastcall) < wrapuptime) {
 		ast_debug(1, "Wrapuptime not yet expired on queue %s for %s\n",
 			(memberp->lastqueue ? memberp->lastqueue->name : qe->parent->name),
 			call->interface);
@@ -7134,6 +7218,98 @@ static int wait_our_turn(struct queue_en
 	return res;
 }
 
+
+
+static void wrapup_set(char* interface, int paused, const char* reason) {
+	int found = 0;
+	struct call_queue *q;
+	struct ao2_iterator queue_iter;
+
+	queue_iter = ao2_iterator_init(queues, 0);
+	while ((q = ao2_t_iterator_next(&queue_iter, "Iterate over queues"))) {
+		struct member *mem;
+		ao2_lock(q);
+
+		if ((mem = interface_exists(q, interface))) {
+			mem->wrapup_terminated = !paused;
+			++found;
+			if (found == 1) {
+				if (!paused) {
+					ast_queue_log("NONE", "NONE", mem->membername, "UNPAUSEALL", "%s|%s", "Wrapup", reason);
+				}
+				publish_queue_member_wrapup(mem, paused);
+			}
+
+			ao2_ref(mem, -1);
+		}
+
+		ao2_unlock(q);
+		queue_t_unref(q, "Done with iterator");
+	}
+	ao2_iterator_destroy(&queue_iter);
+}
+
+
+static int wrapup_terminate(const char* interface) {
+	struct wrapup_sched *wsched;
+	int ret;
+
+	ao2_lock(pending_wrapups);
+	wsched = ao2_find(pending_wrapups, interface, OBJ_SEARCH_KEY);
+	if (wsched) {
+		ret = ast_sched_del(wrapup_sched_ctx, wsched->sched_id);
+		if (ret > -1) {
+			// deleted before fired
+			wrapup_set(wsched->interface, 0, "terminated");
+			ao2_unlink(pending_wrapups, wsched);
+		}
+		ao2_ref(wsched, -1);
+	}
+	ao2_unlock(pending_wrapups);
+	return 0;
+}
+
+
+static int wrapup_clean_after_logout(const char* interface) {
+	int found = 0;
+	struct call_queue *q;
+	struct ao2_iterator queue_iter;
+
+	queue_iter = ao2_iterator_init(queues, 0);
+	while ((q = ao2_t_iterator_next(&queue_iter, "Iterate over queues"))) {
+		struct member *mem;
+		ao2_lock(q);
+
+		mem = interface_exists(q, interface);
+		ao2_unlock(q);
+		queue_t_unref(q, "Done with iterator");
+		if (mem) {
+			found = 1;
+			ao2_ref(mem, -1);
+			break;
+		}
+	}
+	ao2_iterator_destroy(&queue_iter);
+
+	if (!found) {
+		wrapup_terminate(interface);
+	}
+
+	return !found;
+}
+
+
+static int wrapup_expired_cb(const void* data) {
+	struct wrapup_sched *wsched = (struct wrapup_sched *)data;
+
+	wrapup_set(wsched->interface, 0, "expired");
+	ao2_unlink(pending_wrapups, wsched);
+
+	// do not reschedule
+	return 0;
+}
+
+
 /*!
  * \brief update the queue status
  * \retval 0 always
@@ -7142,6 +7318,9 @@ static int update_queue(struct call_queu
 {
 	int oldtalktime;
 	int newtalktime = time(NULL) - starttime;
+	int sched_id;
+	int wrapup_terminate;
+	int member_found = 0;
 	struct member *mem;
 	struct call_queue *qtmp;
 	struct ao2_iterator queue_iter;
@@ -7155,15 +7334,21 @@ static int update_queue(struct call_queu
 	}
 
 	if (shared_lastcall) {
+		// if agent is paused during call terminate wrapup immediately
+		wrapup_terminate = q->wrapuptime && member->paused;
 		queue_iter = ao2_iterator_init(queues, 0);
 		while ((qtmp = ao2_t_iterator_next(&queue_iter, "Iterate through queues"))) {
 			ao2_lock(qtmp);
 			if ((mem = ao2_find(qtmp->members, member, OBJ_POINTER))) {
+				member_found = 1;
 				time(&mem->lastcall);
 				mem->calls++;
 				mem->callcompletedinsl = 0;
 				mem->starttime = 0;
 				mem->lastqueue = q;
+				if (wrapup_terminate) {
+				         mem->wrapup_terminated = 1;
+				}
 				ao2_ref(mem, -1);
 			}
 			ao2_unlock(qtmp);
@@ -7179,6 +7364,34 @@ static int update_queue(struct call_queu
 		member->lastqueue = q;
 		ao2_unlock(q);
 	}
+
+	if (q->wrapuptime && !member->paused && member_found) {
+		struct wrapup_sched *wsched_new, *wsched_old;
+		wsched_new = ao2_alloc((sizeof(*wsched_new)), NULL);
+		if (wsched_new) {
+			ast_copy_string(wsched_new->interface, member->interface, AST_CHANNEL_NAME);
+			ao2_lock(pending_wrapups);
+			wsched_old = ao2_find(pending_wrapups, member->interface, OBJ_SEARCH_KEY);
+			if (!wsched_old) {
+				sched_id = ast_sched_add(wrapup_sched_ctx, 1000 * q->wrapuptime, wrapup_expired_cb, wsched_new);
+				if (sched_id > -1) {
+					wsched_new->sched_id = sched_id;
+					ao2_link(pending_wrapups, wsched_new);
+				} else {
+					ast_log(LOG_ERROR, "Failed to add event to wrapup scheduler. Wrapup expiration will not be reported");
+				}
+			} else {
+				ast_debug(1, "Wrapup timer id=%d already exists for interface %s\n", wsched_old->sched_id, member->interface);
+				ao2_ref(wsched_old, -1);
+			}
+			ao2_unlock(pending_wrapups);
+			ao2_ref(wsched_new, -1);
+		} else {
+			ast_log(LOG_ERROR, "wrapup_sched allocation failed. Wrapup expiration will not be reported");
+		}
+		wrapup_set(member->interface, 1, "Call-finished");
+	}
+
 	/* Member might never experience any direct status change (local
 	 * channel with forwarding in particular). If that's the case,
 	 * this is the last chance to remove it from pending or subsequent
@@ -7297,7 +7510,7 @@ enum agent_complete_reason {
 /*! \brief Send out AMI message with member call completion status information */
 static void send_agent_complete(const char *queuename, struct ast_channel_snapshot *caller,
 	struct ast_channel_snapshot *peer, const struct member *member, time_t holdstart,
-	time_t callstart, enum agent_complete_reason rsn)
+	time_t callstart, const char *caller_uniqueid, int wrapuptime, enum agent_complete_reason rsn)
 {
 	const char *reason = NULL;	/* silence dumb compilers */
 	RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
@@ -7314,16 +7527,26 @@ static void send_agent_complete(const ch
 		break;
 	}
 
-	blob = ast_json_pack("{s: s, s: s, s: s, s: I, s: I, s: s}",
+	blob = ast_json_pack("{s: s, s: s, s: s, s: I, s: I, s: I, s: s}",
 		"Queue", queuename,
 		"Interface", member->interface,
 		"MemberName", member->membername,
 		"HoldTime", (ast_json_int_t)(callstart - holdstart),
 		"TalkTime", (ast_json_int_t)(time(NULL) - callstart),
+		"WrapupTime", (ast_json_int_t)wrapuptime,
 		"Reason", reason ?: "");
 
 	queue_publish_multi_channel_snapshot_blob(ast_queue_topic(queuename), caller, peer,
 			queue_agent_complete_type(), blob);
+
+	if (wrapuptime > 0 && !member->paused) {
+		struct member* mem;
+		/* check agent not logged out */
+		if ((mem = find_member_by_queuename_and_interface(queuename, member->interface))) {
+			ao2_ref(mem, -1);
+			ast_queue_log(queuename, caller_uniqueid, member->membername, "WRAPUPSTART", "%d", wrapuptime);
+		}
+	}
 }
 
 static void queue_agent_cb(void *userdata, struct stasis_subscription *sub,
@@ -7625,7 +7848,7 @@ static void handle_blind_transfer(void *
 			(long) (time(NULL) - queue_data->starttime), queue_data->caller_pos);
 
 	send_agent_complete(queue_data->queue->name, caller_snapshot, member_snapshot, queue_data->member,
-			queue_data->holdstart, queue_data->starttime, TRANSFER);
+			queue_data->holdstart, queue_data->starttime, queue_data->caller_uniqueid, queue_data->queue->wrapuptime, TRANSFER);
 	update_queue(queue_data->queue, queue_data->member, queue_data->callcompletedinsl,
 			queue_data->starttime);
 	remove_stasis_subscriptions(queue_data);
@@ -7683,7 +7906,7 @@ static void handle_attended_transfer(voi
 	log_attended_transfer(queue_data, atxfer_msg);
 
 	send_agent_complete(queue_data->queue->name, caller_snapshot, member_snapshot, queue_data->member,
-			queue_data->holdstart, queue_data->starttime, TRANSFER);
+			queue_data->holdstart, queue_data->starttime, queue_data->caller_uniqueid, queue_data->queue->wrapuptime, TRANSFER);
 	update_queue(queue_data->queue, queue_data->member, queue_data->callcompletedinsl,
 			queue_data->starttime);
 	remove_stasis_subscriptions(queue_data);
@@ -7881,7 +8104,7 @@ static void handle_hangup(void *userdata
 		(long) (time(NULL) - queue_data->starttime), queue_data->caller_pos);
 
 	send_agent_complete(queue_data->queue->name, caller_snapshot, member_snapshot, queue_data->member,
-			queue_data->holdstart, queue_data->starttime, reason);
+			queue_data->holdstart, queue_data->starttime, queue_data->caller_uniqueid, queue_data->queue->wrapuptime, reason);
 	update_queue(queue_data->queue, queue_data->member, queue_data->callcompletedinsl,
 			queue_data->starttime);
 	remove_stasis_subscriptions(queue_data);
@@ -8958,6 +9181,29 @@ static int publish_queue_member_pause(st
 	return 0;
 }
 
+static int publish_queue_member_wrapup(struct member *member, int paused)
+{
+	struct ast_json *json_blob = queue_member_blob_create(NULL, member);
+
+	if (!json_blob) {
+		return -1;
+	}
+
+	ast_json_object_set(json_blob, "Reason", ast_json_string_create("Wrapup"));
+	ast_json_object_set(json_blob, "Paused", ast_json_integer_create(!!paused));
+
+	if (paused) {
+        	queue_publish_member_blob(queue_member_wrapup_start_type(), json_blob);
+	} else {
+		queue_publish_member_blob(queue_member_pause_type(), json_blob);
+	}
+
+	return 0;
+}
+
+
+
+
 /*!
  * \internal
  * \brief Set the pause status of the specific queue member.
@@ -9047,11 +9293,16 @@ static int set_member_paused(const char
 		load_realtime_queues(queuename);
 	}
 
+	/* Both PAUSE ALL and UNPAUSE ALL terminate wrapup */
+	if (allow_wrapup_termination && ast_strlen_zero(queuename)) {
+		wrapup_terminate(interface);
+	}
+
 	queue_iter = ao2_iterator_init(queues, 0);
 	while ((q = ao2_t_iterator_next(&queue_iter, "Iterate over queues"))) {
 		ao2_lock(q);
 		if (ast_strlen_zero(queuename) || !strcasecmp(q->name, queuename)) {
-			struct member *mem;
+		    struct member *mem;
 
 			if ((mem = interface_exists(q, interface))) {
 				/*
@@ -9499,14 +9750,13 @@ static int rqm_exec(struct ast_channel *
 		}
 	}
 
-	ast_debug(1, "queue: %s, member: %s\n", args.queuename, args.interface);
-
 	if (log_membername_as_agent) {
 		mem = find_member_by_queuename_and_interface(args.queuename, args.interface);
 	}
 
 	switch (remove_from_queue(args.queuename, args.interface)) {
 	case RES_OKAY:
+		wrapup_clean_after_logout(args.interface);
 		if (!mem || ast_strlen_zero(mem->membername)) {
 			ast_queue_log(args.queuename, ast_channel_uniqueid(chan), args.interface, "REMOVEMEMBER", "%s", "");
 		} else {
@@ -10297,7 +10547,7 @@ static int queue_function_mem_read(struc
 			while ((m = ao2_iterator_next(&mem_iter))) {
 				/* Count the agents who are logged in, not paused and not wrapping up */
 				if ((m->status == AST_DEVICE_NOT_INUSE) && (!m->paused) &&
-						!(m->lastcall && get_wrapuptime(q, m) && ((now - get_wrapuptime(q, m)) < m->lastcall))) {
+						(m->wrapup_terminated || !(m->lastcall && get_wrapuptime(q, m) && ((now - get_wrapuptime(q, m)) < m->lastcall)))) {
 					count++;
 				}
 				ao2_ref(m, -1);
@@ -10949,6 +11199,10 @@ static void queue_set_global_params(stru
 	if ((general_val = ast_variable_retrieve(cfg, "general", "force_longest_waiting_caller"))) {
 		force_longest_waiting_caller = ast_true(general_val);
 	}
+	allow_wrapup_termination = 0;
+	if ((general_val = ast_variable_retrieve(cfg, "general", "allow_wrapup_termination"))) {
+		allow_wrapup_termination = ast_true(general_val);
+	}
 }
 
 /*! \brief reload information pertaining to a single member
@@ -13276,6 +13530,7 @@ static int unload_module(void)
 	STASIS_MESSAGE_TYPE_CLEANUP(queue_member_added_type);
 	STASIS_MESSAGE_TYPE_CLEANUP(queue_member_removed_type);
 	STASIS_MESSAGE_TYPE_CLEANUP(queue_member_pause_type);
+	STASIS_MESSAGE_TYPE_CLEANUP(queue_member_wrapup_start_type);
 	STASIS_MESSAGE_TYPE_CLEANUP(queue_member_penalty_type);
 	STASIS_MESSAGE_TYPE_CLEANUP(queue_member_ringinuse_type);
 
@@ -13321,6 +13576,11 @@ static int unload_module(void)
 	ast_unload_realtime("queue_members");
 	ao2_cleanup(queues);
 	ao2_cleanup(pending_members);
+	ao2_cleanup(pending_wrapups);
+
+	if (wrapup_sched_ctx) {
+		ast_sched_context_destroy(wrapup_sched_ctx);
+	}
 
 	queues = NULL;
 	return 0;
@@ -13357,6 +13617,19 @@ static int load_module(void)
 		return AST_MODULE_LOAD_DECLINE;
 	}
 
+	pending_wrapups = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0,
+		MAX_PENDING_WRAPUPS_BUCKETS, pending_wrapups_hash, NULL, pending_wrapups_cmp);
+	if (!pending_wrapups) {
+		unload_module();
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	wrapup_sched_ctx = ast_sched_context_create();
+	if (!wrapup_sched_ctx || ast_sched_start_thread(wrapup_sched_ctx) != 0) {
+		unload_module();
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
 	use_weight = 0;
 
 	if (reload_handler(0, &mask, NULL)) {
@@ -13476,6 +13749,7 @@ static int load_module(void)
 	err |= STASIS_MESSAGE_TYPE_INIT(queue_member_added_type);
 	err |= STASIS_MESSAGE_TYPE_INIT(queue_member_removed_type);
 	err |= STASIS_MESSAGE_TYPE_INIT(queue_member_pause_type);
+	err |= STASIS_MESSAGE_TYPE_INIT(queue_member_wrapup_start_type);
 	err |= STASIS_MESSAGE_TYPE_INIT(queue_member_penalty_type);
 	err |= STASIS_MESSAGE_TYPE_INIT(queue_member_ringinuse_type);
 
@@ -13489,6 +13763,7 @@ static int load_module(void)
 		unload_module();
 		return AST_MODULE_LOAD_DECLINE;
 	}
+
 	return AST_MODULE_LOAD_SUCCESS;
 }
 
Index: b/configs/samples/queues.conf.sample
===================================================================
--- a/configs/samples/queues.conf.sample
+++ b/configs/samples/queues.conf.sample
@@ -197,6 +197,12 @@ monitor-type = MixMonitor
 ;
 ;wrapuptime=15
 ;
+; If set to 'yes', wrapup termination patch is activated.
+; Agent wrapup time can be terminated by sending QueueMemberUnpauseAll
+; Defaults to 'no'.
+;
+;allow_wrapup_termination = no
+;
 ; Autofill will follow queue strategy but push multiple calls through
 ; at same time until there are no more waiting callers or no more
 ; available members. The per-queue setting of autofill allows you
