zhizhi/modules/orders/order-manager.js

270 lines
9.5 KiB
JavaScript

/**
* order-manager.js - 订单核心管理模块
* 编号: ZT-YYYYMMDD-NNN, 子任务: -XX
* 状态: submitted → reviewing → accepted(开发中) → testing → completed → delivered
* 归档: 已完成/已交付订单可移入历史
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const DATA_FILE = path.join(__dirname, 'data', 'orders.json');
const COUNTER_FILE = path.join(__dirname, 'data', 'counter.json');
const HISTORY_FILE = path.join(__dirname, 'data', 'history.json');
fs.mkdirSync(path.dirname(DATA_FILE), { recursive: true });
function loadOrders() {
try { return JSON.parse(fs.readFileSync(DATA_FILE, 'utf-8')); }
catch { return []; }
}
function saveOrders(orders) {
fs.writeFileSync(DATA_FILE, JSON.stringify(orders, null, 2));
}
function loadCounter() {
try { return JSON.parse(fs.readFileSync(COUNTER_FILE, 'utf-8')); }
catch { return {}; }
}
function saveCounter(c) {
fs.writeFileSync(COUNTER_FILE, JSON.stringify(c, null, 2));
}
function loadHistory() {
try { return JSON.parse(fs.readFileSync(HISTORY_FILE, 'utf-8')); }
catch { return []; }
}
function saveHistory(h) {
fs.writeFileSync(HISTORY_FILE, JSON.stringify(h, null, 2));
}
/** 生成编号 */
export function nextOrderNumber() {
const counter = loadCounter();
const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
counter[today] = (counter[today] || 0) + 1;
saveCounter(counter);
return `ZT-${today}-${String(counter[today]).padStart(3, '0')}`;
}
/** 生成子编号 */
export function nextSubNumber(parentId) {
const orders = loadOrders();
const o = orders.find(x => x.id === parentId);
if (!o) return null;
return `${parentId}-${String((o.subs || []).length + 1).padStart(2, '0')}`;
}
/** 创建订单 */
export function createOrder({ name, contact, contactType, requirements, budget, expectedDays, source }) {
const now = new Date().toISOString();
const id = nextOrderNumber();
const order = {
id, name: name || '匿名客户', contact, contactType: contactType || '微信',
requirements: requirements || '', budget: budget || '',
expectedDays: expectedDays || 0,
expectedDelivery: '',
progress: 0, progressNote: '等待接单',
source: source || 'web',
status: 'submitted', subs: [], comments: [],
timeline: [{ at: now, action: 'created', detail: '订单已提交' }],
flagged: false, flagReason: '',
archived: false,
createdAt: now, updatedAt: now,
};
const orders = loadOrders();
orders.push(order);
saveOrders(orders);
return order;
}
/** 获取未归档订单 */
export function getActiveOrders() {
return loadOrders().filter(o => !o.archived).sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
}
/** 获取已归档订单(历史) */
export function getArchivedOrders() {
return loadHistory().sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt));
}
/** 获取全部(含归档) */
export function getAllOrders() {
return [...loadOrders(), ...loadHistory()].sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
}
/** 搜索 */
export function findOrder(query) {
const all = getAllOrders();
return all.filter(o =>
o.id.toLowerCase().includes(query.toLowerCase()) ||
(o.contact || '').includes(query) ||
(o.name || '').includes(query)
);
}
/** 按 ID 获取(查活跃+历史) */
export function getOrder(id) {
return loadOrders().find(o => o.id === id) || loadHistory().find(o => o.id === id) || null;
}
/** 更新状态 */
export function updateStatus(id, status) {
const orders = loadOrders();
const order = orders.find(o => o.id === id);
if (!order) return null;
const valid = ['submitted', 'reviewing', 'accepted', 'in-progress', 'testing', 'completed', 'delivered'];
if (!valid.includes(status)) return null;
const now = new Date().toISOString();
order.status = status;
order.updatedAt = now;
const labels = { submitted:'已提交', reviewing:'审核中', accepted:'已接单', 'in-progress':'制作中', testing:'测试中', completed:'已完成', delivered:'已交付' };
order.timeline.push({ at: now, action: 'status', detail: `状态 → ${labels[status] || status}` });
// 接单时算预计交付日
if (status === 'accepted' && order.expectedDays > 0) {
const d = new Date(); d.setDate(d.getDate() + order.expectedDays);
order.expectedDelivery = d.toISOString().slice(0, 10);
order.progressNote = `预计 ${order.expectedDays} 天内交付`;
}
saveOrders(orders);
return order;
}
/** 更新进度百分比 + 说明 */
export function updateProgress(id, progress, note = '') {
const orders = loadOrders();
const order = orders.find(o => o.id === id);
if (!order) return null;
const now = new Date().toISOString();
order.progress = Math.max(0, Math.min(100, progress));
order.progressNote = note || order.progressNote;
order.updatedAt = now;
order.timeline.push({ at: now, action: 'progress', detail: `进度更新: ${order.progress}% - ${order.progressNote}` });
saveOrders(orders);
return order;
}
/** 设定预期天数(接单时) */
export function setExpectedDays(id, days) {
const orders = loadOrders();
const order = orders.find(o => o.id === id);
if (!order) return null;
order.expectedDays = days;
const d = new Date(); d.setDate(d.getDate() + days);
order.expectedDelivery = d.toISOString().slice(0, 10);
order.progressNote = `预计 ${days} 天内交付`;
order.updatedAt = new Date().toISOString();
order.timeline.push({ at: order.updatedAt, action: 'estimate', detail: `设定预期 ${days} 天交付` });
saveOrders(orders);
return order;
}
/** 交付(标记为已交付) */
export function deliverOrder(id) {
const orders = loadOrders();
const order = orders.find(o => o.id === id);
if (!order) return null;
const now = new Date().toISOString();
order.status = 'delivered';
order.progress = 100;
order.progressNote = '已完成并交付';
order.updatedAt = now;
order.timeline.push({ at: now, action: 'deliver', detail: '订单已交付给客户' });
saveOrders(orders);
return order;
}
/** 归档订单(移入历史) */
export function archiveOrder(id) {
const orders = loadOrders();
const idx = orders.findIndex(o => o.id === id);
if (idx === -1) return null;
const order = orders[idx];
order.archived = true;
order.updatedAt = new Date().toISOString();
order.timeline.push({ at: order.updatedAt, action: 'archive', detail: '订单已归档' });
const history = loadHistory();
history.push(order);
saveHistory(history);
orders.splice(idx, 1);
saveOrders(orders);
return order;
}
/** 子任务管理 */
export function addSubTask(id, { title, description }) {
const orders = loadOrders();
const order = orders.find(o => o.id === id);
if (!order) return null;
const subId = nextSubNumber(id);
const now = new Date().toISOString();
const sub = { subId, title: title || '', description: description || '', status: 'pending', createdAt: now };
order.subs = order.subs || [];
order.subs.push(sub);
order.updatedAt = now;
order.timeline.push({ at: now, action: 'subtask', detail: `添加子任务: ${subId} - ${title}` });
saveOrders(orders);
return sub;
}
export function updateSubStatus(id, subId, status) {
const orders = loadOrders();
const order = orders.find(o => o.id === id);
if (!order) return null;
const sub = order.subs?.find(s => s.subId === subId);
if (!sub) return null;
sub.status = status;
sub.updatedAt = new Date().toISOString();
order.updatedAt = sub.updatedAt;
order.timeline.push({ at: sub.updatedAt, action: 'subtask', detail: `子任务 ${subId}${status}` });
saveOrders(orders);
return sub;
}
/** 评论 */
export function addComment(id, { author, content, side }) {
const orders = loadOrders();
const order = orders.find(o => o.id === id);
if (!order) return null;
const now = new Date().toISOString();
const comment = { id: `c-${Date.now()}`, author: author || '客户', content: content || '', side: side || 'client', createdAt: now };
order.comments = order.comments || [];
order.comments.push(comment);
order.updatedAt = now;
order.timeline.push({ at: now, action: 'comment', detail: `${side === 'dev' ? '开发者' : '客户'}留言` });
saveOrders(orders);
return comment;
}
/** 标记可疑 */
export function flagOrder(id, flagged, reason = '') {
const orders = loadOrders();
const order = orders.find(o => o.id === id);
if (!order) return null;
order.flagged = flagged;
order.flagReason = reason;
order.updatedAt = new Date().toISOString();
order.timeline.push({ at: order.updatedAt, action: 'flag', detail: flagged ? `标记可疑: ${reason}` : '解除可疑标记' });
saveOrders(orders);
return order;
}
/** 统计 */
export function getFlaggedOrders() { return loadOrders().filter(o => o.flagged); }
export function getRecentOrders(min = 60) {
const cutoff = Date.now() - min * 60000;
return loadOrders().filter(o => new Date(o.createdAt).getTime() > cutoff);
}
export function getPendingCount() {
return loadOrders().filter(o => !o.archived && ['submitted', 'reviewing'].includes(o.status)).length;
}
/** 删除订单 */
export function deleteOrder(id) {
let orders = loadOrders();
const idx = orders.findIndex(o => o.id === id);
if (idx !== -1) { orders.splice(idx, 1); saveOrders(orders); return true; }
let history = loadHistory();
const hidx = history.findIndex(o => o.id === id);
if (hidx !== -1) { history.splice(hidx, 1); saveHistory(history); return true; }
return false;
}