Skip to content
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
alter table `phone_order_record` add column `parent_record_id` int null;
alter table `phone_order_record` add column `is_completed` tinyint(1) not null default 0;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
CREATE TABLE IF NOT EXISTS `phone_order_push_task`
(
`id` INT AUTO_INCREMENT PRIMARY KEY,
`record_id` INT NOT NULL,
`parent_record_id` INT NULL,
`assistant_id` INT NOT NULL,
`business_key` VARCHAR(128) NOT NULL,
`task_type` INT NOT NULL,
`request_json` LONGTEXT NOT NULL,
`status` INT NOT NULL,
`created_at` DATETIME(3) NOT NULL
) CHARSET = utf8mb4;

CREATE INDEX idx_record_id ON phone_order_push_task(record_id);

CREATE INDEX idx_parent_record_id ON phone_order_push_task(parent_record_id);

CREATE UNIQUE INDEX uk_record_business ON phone_order_push_task(record_id, business_key);
6 changes: 6 additions & 0 deletions src/SmartTalk.Core/Domain/PhoneOrder/PhoneOrderRecord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,10 @@ public class PhoneOrderRecord : IEntity

[NotMapped]
public Restaurant RestaurantInfo { get; set; }

[Column("parent_record_id")]
public int? ParentRecordId { get; set; }

[Column("is_completed")]
public bool IsCompleted { get; set; } = false;
}
38 changes: 38 additions & 0 deletions src/SmartTalk.Core/Domain/Sales/PhoneOrderPushTask.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using SmartTalk.Messages.Enums.Sales;

namespace SmartTalk.Core.Domain.Sales;

[Table("phone_order_push_task")]
public class PhoneOrderPushTask : IEntity
{
[Key]
[Column("id")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }

[Column("record_id")]
public int RecordId { get; set; }

[Column("parent_record_id")]
public int? ParentRecordId { get; set; }

[Column("assistant_id")]
public int AssistantId { get; set; }

[Column("business_key"), StringLength(128)]
public string BusinessKey { get; set; }

[Column("task_type")]
public PhoneOrderPushTaskType TaskType { get; set; }

[Column("request_json")]
public string RequestJson { get; set; }

[Column("status")]
public PhoneOrderPushTaskStatus Status { get; set; }

[Column("created_at")]
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.Now;
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ public async Task RecordAiSpeechAssistantCallAsync(AiSpeechAssistantStreamContex

if (agentAssistant == null || agentAssistant.Count == 0) throw new Exception("AgentAssistant is null");

var parentRecordId = await _phoneOrderDataProvider.GetLatestPhoneOrderRecordIdAsync(agentAssistant.First().AgentId, context.Assistant.Id, context.CallSid, cancellationToken).ConfigureAwait(false);

var record = new PhoneOrderRecord
{
AssistantId = context.Assistant.Id,
Expand All @@ -92,6 +94,7 @@ public async Task RecordAiSpeechAssistantCallAsync(AiSpeechAssistantStreamContex
IsTransfer = context.IsTransfer,
IncomingCallNumber = context.LastUserInfo.PhoneNumber,
OrderRecordType = orderRecordType,
ParentRecordId = parentRecordId
};

await _phoneOrderDataProvider.AddPhoneOrderRecordsAsync([record], cancellationToken: cancellationToken).ConfigureAwait(false);
Expand Down
7 changes: 7 additions & 0 deletions src/SmartTalk.Core/Services/Http/Clients/SalesClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public interface ISalesClient : IScopedDependency
Task<GetCustomerNumbersByNameResponseDto> GetCustomerNumbersByNameAsync(GetCustomerNumbersByNameRequestDto request, CancellationToken cancellationToken);

Task<GetCustomerLevel5HabitResponseDto> GetCustomerLevel5HabitAsync(GetCustomerLevel5HabitRequstDto request, CancellationToken cancellationToken);

Task<DeleteAiOrderResponseDto> DeleteAiOrderAsync(DeleteAiOrderRequestDto request, CancellationToken cancellationToken);
}

public class SalesClient : ISalesClient
Expand Down Expand Up @@ -103,4 +105,9 @@ public async Task<GetCustomerLevel5HabitResponseDto> GetCustomerLevel5HabitAsync

return await _httpClientFactory.PostAsJsonAsync<GetCustomerLevel5HabitResponseDto>($"{_salesCustomerHabitSetting.BaseUrl}/api/CustomerInfo/QueryHistoryCustomerLevel5Habit", request, headers: header, cancellationToken: cancellationToken).ConfigureAwait(false);
}

public async Task<DeleteAiOrderResponseDto> DeleteAiOrderAsync(DeleteAiOrderRequestDto request, CancellationToken cancellationToken)
{
return await _httpClientFactory.PostAsJsonAsync<DeleteAiOrderResponseDto>($"{_salesSetting.BaseUrl}/api/SalesOrder/DeleteAiOrder", request, headers: _headers, cancellationToken: cancellationToken).ConfigureAwait(false);
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using SmartTalk.Core.Domain.Account;
using SmartTalk.Core.Domain.AISpeechAssistant;
using SmartTalk.Core.Domain.PhoneOrder;
using SmartTalk.Core.Domain.Restaurants;
using SmartTalk.Core.Domain.Sales;
using SmartTalk.Core.Domain.System;
using SmartTalk.Messages.Dto.Agent;
using SmartTalk.Messages.Dto.PhoneOrder;
using SmartTalk.Messages.Dto.Restaurant;
using SmartTalk.Messages.Enums;
using SmartTalk.Messages.Enums.PhoneOrder;
using SmartTalk.Messages.Enums.Sales;
using SmartTalk.Messages.Enums.STT;

namespace SmartTalk.Core.Services.PhoneOrder;
Expand Down Expand Up @@ -56,6 +59,12 @@ Task<List<PhoneOrderRecord>> GetPhoneOrderRecordsAsync(
Task<List<PhoneOrderRecordReport>> GetPhoneOrderRecordReportByRecordIdAsync(List<int> recordId, CancellationToken cancellationToken);

Task UpdatePhoneOrderRecordReportsAsync(List<PhoneOrderRecordReport> reports, bool forceSave = true, CancellationToken cancellationToken = default);

Task<int?> GetLatestPhoneOrderRecordIdAsync(int agentId, int assistantId, string currentSessionId, CancellationToken cancellationToken);

Task UpdateOrderIdAsync(int recordId, Guid orderId, CancellationToken cancellationToken);

Task MarkRecordCompletedAsync(int recordId, CancellationToken cancellationToken = default);
}

public partial class PhoneOrderDataProvider
Expand Down Expand Up @@ -97,6 +106,18 @@ join agentAssistant in _repository.Query<AgentAssistant>() on agent.Id equals ag

public async Task UpdatePhoneOrderRecordsAsync(PhoneOrderRecord record, bool forceSave = true, CancellationToken cancellationToken = default)
{
var existing = await _repository.Query<PhoneOrderRecord>()
.Where(r => r.Id == record.Id)
.Select(r => new { r.IsCompleted, r.OrderId })
.FirstOrDefaultAsync(cancellationToken)
.ConfigureAwait(false);

if (existing != null)
{
record.IsCompleted = existing.IsCompleted;
record.OrderId = existing.OrderId;
}

await _repository.UpdateAsync(record, cancellationToken).ConfigureAwait(false);

if (forceSave) await _unitOfWork.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
Expand Down Expand Up @@ -328,4 +349,52 @@ public async Task UpdatePhoneOrderRecordReportsAsync(List<PhoneOrderRecordReport

if (forceSave) await _unitOfWork.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}

public async Task<int?> GetLatestPhoneOrderRecordIdAsync(int agentId, int assistantId, string currentSessionId, CancellationToken cancellationToken)
{
var records = await _repository.Query<PhoneOrderRecord>().Where(r => r.AgentId == agentId && r.AssistantId == assistantId && r.SessionId != currentSessionId)
.OrderByDescending(r => r.CreatedDate).ThenByDescending(r => r.Id).Select(r => r.Id).ToListAsync(cancellationToken).ConfigureAwait(false);

foreach (var recordId in records)
{
if (await IsRecordCompletedAsync(recordId, cancellationToken).ConfigureAwait(false))
return recordId;
}

return null;
}

public async Task UpdateOrderIdAsync(int recordId, Guid orderId, CancellationToken cancellationToken)
{
var record = await _repository.Query<PhoneOrderRecord>().Where(r => r.Id == recordId).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);

if (record == null) return;

var orderIds = string.IsNullOrEmpty(record.OrderId) ? new List<Guid>() : JsonConvert.DeserializeObject<List<Guid>>(record.OrderId)!;

orderIds.Add(orderId);
record.OrderId = JsonConvert.SerializeObject(orderIds);

await _repository.UpdateAsync(record, cancellationToken).ConfigureAwait(false);
await _unitOfWork.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}

private async Task<bool> IsRecordCompletedAsync(int recordId, CancellationToken cancellationToken)
{
var tasks = await _repository.Query<PhoneOrderPushTask>()
.Where(t => t.RecordId == recordId)
.Select(t => t.Status)
.ToListAsync(cancellationToken);

if (!tasks.Any())
return true;

return tasks.All(s => s == PhoneOrderPushTaskStatus.Sent);
}

public async Task MarkRecordCompletedAsync(int recordId, CancellationToken cancellationToken = default)
{
await _repository.Query<PhoneOrderRecord>().Where(r => r.Id == recordId && !r.IsCompleted)
.ExecuteUpdateAsync(setters => setters.SetProperty(r => r.IsCompleted, true), cancellationToken).ConfigureAwait(false);
}
}
79 changes: 79 additions & 0 deletions src/SmartTalk.Core/Services/Sale/SalesDataProvider.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using Microsoft.EntityFrameworkCore;
using SmartTalk.Core.Data;
using SmartTalk.Core.Domain.PhoneOrder;
using SmartTalk.Core.Domain.Sales;
using SmartTalk.Core.Ioc;
using SmartTalk.Messages.Enums.PhoneOrder;
using SmartTalk.Messages.Enums.Sales;

namespace SmartTalk.Core.Services.Sale;
Expand All @@ -19,6 +21,20 @@ public interface ISalesDataProvider : IScopedDependency
Task UpsertCustomerInfoCacheAsync(string phoneNumber, string itemsString, bool forceSave, CancellationToken cancellationToken);

Task<AiSpeechAssistantKnowledgeVariableCache> GetCustomerInfoCacheByPhoneNumberAsync(string phoneNumber, CancellationToken cancellationToken);

Task AddPhoneOrderPushTaskAsync(PhoneOrderPushTask task, bool forceSave = true, CancellationToken cancellationToken = default);

Task MarkSendingAsync(int taskId, bool forceSave, CancellationToken cancellationToken = default);

Task MarkSentAsync(int taskId, bool forceSave, CancellationToken cancellationToken = default);

Task MarkFailedAsync(int taskId, bool forceSave, CancellationToken cancellationToken = default);

Task<bool> IsParentCompletedAsync(int? parentRecordId, CancellationToken cancellationToken);

Task<bool> HasPendingTasksByRecordIdAsync(int recordId, CancellationToken cancellationToken);

Task<PhoneOrderPushTask> GetRecordPushTaskByRecordIdAsync(int recordId, CancellationToken cancellationToken);
}

public class SalesDataProvider : ISalesDataProvider
Expand Down Expand Up @@ -109,4 +125,67 @@ public async Task<AiSpeechAssistantKnowledgeVariableCache> GetCustomerInfoCacheB
{
return await _repository.Query<AiSpeechAssistantKnowledgeVariableCache>().Where(x => x.Filter == phoneNumber).FirstOrDefaultAsync(cancellationToken);
}

public async Task AddPhoneOrderPushTaskAsync(PhoneOrderPushTask task, bool forceSave = true, CancellationToken cancellationToken = default)
{
await _repository.InsertAsync(task, cancellationToken).ConfigureAwait(false);

if (forceSave)
await _unitOfWork.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}

public async Task MarkSendingAsync(int taskId, bool forceSave = true, CancellationToken cancellationToken = default)
{
var task = await _repository.Query<PhoneOrderPushTask>().Where(t => t.Id == taskId).FirstOrDefaultAsync(cancellationToken);

if (task == null) return;

task.Status = PhoneOrderPushTaskStatus.Sending;

await _repository.UpdateAsync(task, cancellationToken).ConfigureAwait(false);
if (forceSave) await _unitOfWork.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}

public async Task MarkSentAsync(int taskId, bool forceSave = true, CancellationToken cancellationToken = default)
{
var task = await _repository.Query<PhoneOrderPushTask>().Where(t => t.Id == taskId).FirstOrDefaultAsync(cancellationToken);

if (task == null) return;

task.Status = PhoneOrderPushTaskStatus.Sent;

await _repository.UpdateAsync(task, cancellationToken).ConfigureAwait(false);
if (forceSave) await _unitOfWork.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}

public async Task MarkFailedAsync(int taskId, bool forceSave, CancellationToken cancellationToken = default)
{
var task = await _repository.Query<PhoneOrderPushTask>().Where(t => t.Id == taskId).FirstOrDefaultAsync(cancellationToken);

if (task == null) return;

task.Status = PhoneOrderPushTaskStatus.Failed;

await _repository.UpdateAsync(task, cancellationToken).ConfigureAwait(false);
if (forceSave) await _unitOfWork.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}

public async Task<bool> IsParentCompletedAsync(int? parentRecordId, CancellationToken cancellationToken)
{
if (!parentRecordId.HasValue) return true;

return await _repository.Query<PhoneOrderRecord>().Where(r => r.Id == parentRecordId.Value).Select(r => r.IsCompleted).FirstOrDefaultAsync(cancellationToken);
}


public async Task<bool> HasPendingTasksByRecordIdAsync(int recordId, CancellationToken cancellationToken)
{
return await _repository.Query<PhoneOrderPushTask>().AnyAsync(t => t.RecordId == recordId && t.Status != PhoneOrderPushTaskStatus.Sent, cancellationToken).ConfigureAwait(false);
}

public async Task<PhoneOrderPushTask> GetRecordPushTaskByRecordIdAsync(int recordId, CancellationToken cancellationToken)
{
return await _repository.Query<PhoneOrderPushTask>().Where(t => t.RecordId == recordId && t.Status == PhoneOrderPushTaskStatus.Pending)
.OrderBy(t => t.CreatedAt).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
}
}
Loading