This commit is contained in:
hangyu.tao 2026-01-16 18:59:19 +08:00
parent af769258d2
commit 8cbad66b32
8 changed files with 98 additions and 105 deletions

View File

@ -58,7 +58,7 @@ func main() {
for i, tp := range trialPeriods {
serviceTrialPeriods[i] = services.TrialPeriod{
ID: tp.ID,
CustomerID: tp.CustomerID,
CustomerName: tp.CustomerName,
StartTime: tp.StartTime,
EndTime: tp.EndTime,
CreatedAt: tp.CreatedAt,
@ -98,7 +98,7 @@ func main() {
for i, tp := range trialPeriods {
serviceTrialPeriods[i] = services.TrialPeriod{
ID: tp.ID,
CustomerID: tp.CustomerID,
CustomerName: tp.CustomerName,
StartTime: tp.StartTime,
EndTime: tp.EndTime,
CreatedAt: tp.CreatedAt,

View File

@ -253,8 +253,7 @@
<!-- Trial Periods Section -->
<section id="trialPeriodsSection" class="content-section">
<div class="section-title">
<h2><i class="fas fa-clock"></i> 客户信息</h2>
<div class="section-title" style="justify-content: flex-end;">
<button id="addTrialBtn" class="btn-primary">
<i class="fas fa-plus"></i>
添加试用时间

View File

@ -1096,34 +1096,30 @@ document.addEventListener('DOMContentLoaded', function () {
const customersData = await customersResponse.json();
const customersMap = customersData.customerMap || {};
// Collect unique trial customer names - only include valid customer names
// Collect unique trial customer names - directly use customerName field
const trialCustomerNames = new Set();
trialPeriods.forEach(period => {
// Only add if customerId exists in map (valid customer)
const customerName = customersMap[period.customerId];
if (customerName) {
trialCustomerNames.add(customerName);
// 直接使用 customerName 字段
if (period.customerName) {
trialCustomerNames.add(period.customerName);
}
// If customerId not in map, skip it (deleted customer) - don't add UUID as name
});
// Update total customers to include trial customers
// Update total customers display (数据仪表盘的总客户数从每周进度获取)
// This function will be called after loading dashboard customers
updateTotalCustomersWithTrialData(trialCustomerNames);
} catch (error) {
console.error('Error loading trial customers for dashboard:', error);
}
}
// Update total customers count including trial customers
// Update total customers count - 数据仪表盘的总客户数取的是每周进度表里的客户数量
function updateTotalCustomersWithTrialData(trialCustomerNames) {
// Get current dashboard customers
// Get current dashboard customers (from customers.json / 每周进度)
const progressCustomerNames = new Set(dashboardCustomers.map(c => c.customerName).filter(c => c));
// Merge both sets
const allCustomerNames = new Set([...progressCustomerNames, ...trialCustomerNames]);
// Update the total customers display
document.getElementById('totalCustomers').textContent = allCustomerNames.size;
// 数据仪表盘的总客户数取的是每周进度表里的客户数量,支持去重
document.getElementById('totalCustomers').textContent = progressCustomerNames.size;
}
// Update dashboard statistics

View File

@ -94,11 +94,31 @@ async function loadCustomersMap() {
// Use the complete customer map for ID->Name lookups
customersMap = data.customerMap || {};
// Also populate the customer select dropdown
populateCustomerSelect();
} catch (error) {
console.error('Error loading customers map:', error);
}
}
// Populate the customer select dropdown
function populateCustomerSelect() {
const select = document.getElementById('trialCustomerSelect');
if (!select) return;
select.innerHTML = '<option value="">-- 选择已有客户或输入新客户 --</option>';
// Get unique names from customersMap
const names = [...new Set(Object.values(customersMap))].sort();
names.forEach(name => {
const option = document.createElement('option');
option.value = name;
option.textContent = name;
select.appendChild(option);
});
}
// Helper function to get customer name - returns friendly placeholder if not found
function getCustomerName(customerId) {
if (!customerId) return '未知客户';
@ -182,7 +202,8 @@ function renderExpiryWarnings() {
endTime.setHours(0, 0, 0, 0); // Set to start of day
const daysUntilExpiry = Math.ceil((endTime - now) / (1000 * 60 * 60 * 24));
const customerName = getCustomerName(period.customerId);
// 直接使用 customerName 字段
const customerName = period.customerName || '未知客户';
if (daysUntilExpiry >= 0 && daysUntilExpiry <= 3) {
warnings.push({
@ -272,8 +293,8 @@ function renderTrialPeriodsTable() {
const row = document.createElement('tr');
row.classList.add('trial-row');
// 显示客户名称如果找不到则显示ID
const customerName = getCustomerName(period.customerId);
// 直接使用 customerName 字段
const customerName = period.customerName || '未知客户';
// 计算状态和到期天数
const now = new Date();
@ -379,7 +400,9 @@ function updateTrialPagination() {
// Open add trial modal
function openAddTrialModal() {
document.getElementById('trialCustomerInput').value = '';
const inputEl = document.getElementById('trialCustomerInput');
if (inputEl) inputEl.value = '';
document.querySelector('input[name="isTrial"][value="true"]').checked = true;
document.getElementById('trialStartTime').value = '';
document.getElementById('trialEndTime').value = '';
@ -434,14 +457,18 @@ async function deleteTrialPeriodFromPage(periodId) {
// Create trial period from page
async function createTrialPeriodFromPage() {
const customerName = document.getElementById('trialCustomerInput').value.trim();
const inputEl = document.getElementById('trialCustomerInput');
// Get customer name from input
const customerName = inputEl ? inputEl.value.trim() : '';
const isTrialValue = document.querySelector('input[name="isTrial"]:checked').value;
const isTrial = isTrialValue === 'true';
const startTime = document.getElementById('trialStartTime').value;
const endTime = document.getElementById('trialEndTime').value;
if (!customerName) {
alert('请输入客户名称');
alert('请选择或输入客户名称');
return;
}
@ -450,22 +477,9 @@ async function createTrialPeriodFromPage() {
return;
}
// Find or create customer ID from customer name
let customerId = null;
for (const [id, name] of Object.entries(customersMap)) {
if (name === customerName) {
customerId = id;
break;
}
}
// If customer doesn't exist in map, generate a new ID
if (!customerId) {
customerId = customerName; // Use customer name as ID for now
}
// 直接使用 customerName不再需要查找或创建 customerId
const formData = {
customerId: customerId,
customerName: customerName,
startTime: new Date(startTime).toISOString(),
endTime: new Date(endTime).toISOString(),
isTrial: isTrial

View File

@ -83,7 +83,7 @@ func (h *TrialPeriodHandler) CreateTrialPeriod(w http.ResponseWriter, r *http.Re
}
trialPeriod := models.TrialPeriod{
CustomerID: req.CustomerID,
CustomerName: req.CustomerName,
StartTime: startTime,
EndTime: endTime,
IsTrial: req.IsTrial,
@ -98,7 +98,7 @@ func (h *TrialPeriodHandler) CreateTrialPeriod(w http.ResponseWriter, r *http.Re
// Send Feishu notification
if h.feishuWebhook != "" {
go h.sendTrialNotification(req.CustomerID, startTime, endTime)
go h.sendTrialNotification(req.CustomerName, startTime, endTime)
}
w.Header().Set("Content-Type", "application/json")
@ -152,7 +152,7 @@ func (h *TrialPeriodHandler) UpdateTrialPeriod(w http.ResponseWriter, r *http.Re
} else {
endTime = existingPeriod.EndTime
}
go h.sendTrialNotification(existingPeriod.CustomerID, startTime, endTime)
go h.sendTrialNotification(existingPeriod.CustomerName, startTime, endTime)
}
w.WriteHeader(http.StatusOK)
@ -182,7 +182,7 @@ func (h *TrialPeriodHandler) DeleteTrialPeriod(w http.ResponseWriter, r *http.Re
}
// sendTrialNotification sends a rich Feishu card notification
func (h *TrialPeriodHandler) sendTrialNotification(customerID string, startTime, endTime time.Time) {
func (h *TrialPeriodHandler) sendTrialNotification(customerName string, startTime, endTime time.Time) {
if h.feishuWebhook == "" {
return
}
@ -198,12 +198,8 @@ func (h *TrialPeriodHandler) sendTrialNotification(customerID string, startTime,
return
}
// Get customer name
customerName := customerID
customer, err := h.customerStorage.GetCustomerByID(customerID)
if err == nil && customer != nil {
customerName = customer.CustomerName
}
// customerName is already passed as parameter, no need to lookup
// Just use it directly for notification
// Determine urgency level and message based on days until expiry
var title, urgencyLevel, urgencyColor, expiryText string
@ -324,42 +320,29 @@ func (h *TrialPeriodHandler) sendTrialNotification(customerID string, startTime,
return
}
log.Printf("Sent trial notification for customer: %s (expires in %d days)", customerID, daysUntilExpiry)
log.Printf("Sent trial notification for customer: %s (expires in %d days)", customerName, daysUntilExpiry)
}
// GetTrialCustomerList returns a list of unique customer names from trial periods
// GetTrialCustomerList returns a list of unique customer names from trial_periods
// This is the data source for dropdown lists in 每周进度 and 客户跟进
func (h *TrialPeriodHandler) GetTrialCustomerList(w http.ResponseWriter, r *http.Request) {
// Get all trial periods
trialPeriods, err := h.storage.GetAllTrialPeriods()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Create a set of unique customer IDs
customerIDs := make(map[string]bool)
for _, period := range trialPeriods {
if period.CustomerID != "" {
customerIDs[period.CustomerID] = true
}
}
// Get unique customer names (only include customers that exist in storage)
// Get unique customer names from trial periods
seenNames := make(map[string]bool)
var customerNames []string
for customerID := range customerIDs {
// Try to get customer name from customer storage
customer, err := h.customerStorage.GetCustomerByID(customerID)
if err == nil && customer != nil && customer.CustomerName != "" {
customerName := customer.CustomerName
// Add to list if not seen before
if !seenNames[customerName] {
customerNames = append(customerNames, customerName)
seenNames[customerName] = true
for _, period := range trialPeriods {
if period.CustomerName != "" && !seenNames[period.CustomerName] {
customerNames = append(customerNames, period.CustomerName)
seenNames[period.CustomerName] = true
}
}
// Skip customers that don't exist in storage (don't show ID as name)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{

View File

@ -64,7 +64,7 @@ func (ts *trialPeriodStorage) GetTrialPeriodsByCustomerID(customerID string) ([]
var customerPeriods []models.TrialPeriod
for _, period := range allPeriods {
if period.CustomerID == customerID {
if period.CustomerName == customerID {
customerPeriods = append(customerPeriods, period)
}
}

View File

@ -2,10 +2,10 @@ package models
import "time"
// TrialPeriod represents a trial period for a customer
// TrialPeriod represents a trial period for a customer (客户信息)
type TrialPeriod struct {
ID string `json:"id"`
CustomerID string `json:"customerId"`
CustomerName string `json:"customerName"` // 直接存储客户名称
StartTime time.Time `json:"startTime"`
EndTime time.Time `json:"endTime"`
IsTrial bool `json:"isTrial"`
@ -14,7 +14,7 @@ type TrialPeriod struct {
// CreateTrialPeriodRequest represents the request to create a trial period
type CreateTrialPeriodRequest struct {
CustomerID string `json:"customerId"`
CustomerName string `json:"customerName"` // 直接使用客户名称
StartTime string `json:"startTime"`
EndTime string `json:"endTime"`
IsTrial bool `json:"isTrial"`
@ -22,6 +22,7 @@ type CreateTrialPeriodRequest struct {
// UpdateTrialPeriodRequest represents the request to update a trial period
type UpdateTrialPeriodRequest struct {
CustomerName *string `json:"customerName,omitempty"`
StartTime *string `json:"startTime,omitempty"`
EndTime *string `json:"endTime,omitempty"`
IsTrial *bool `json:"isTrial,omitempty"`

View File

@ -41,7 +41,7 @@ type Customer struct {
// TrialPeriod represents a trial period record
type TrialPeriod struct {
ID string `json:"id"`
CustomerID string `json:"customerId"`
CustomerName string `json:"customerName"`
StartTime time.Time `json:"startTime"`
EndTime time.Time `json:"endTime"`
CreatedAt time.Time `json:"createdAt"`
@ -151,9 +151,9 @@ func (t *TrialExpiryChecker) CheckTrialPeriodsAndNotify(trialPeriods []TrialPeri
// Send notification for trials expiring today, tomorrow, or within 3 days
if daysUntilExpiry >= 0 && daysUntilExpiry <= 3 {
customerName := customersMap[period.CustomerID]
customerName := period.CustomerName
if customerName == "" {
customerName = period.CustomerID
customerName = "未知客户"
}
trialPeriodStr := fmt.Sprintf("%s%s",