// 采购相关功能 let editPid = null; async function loadPurchases() { if (!Store.currentRoom) return; const token = Store.getToken(); const res = await fetch('/api/rooms/' + Store.currentRoom + '/purchases', { headers: { 'Authorization': 'Bearer ' + token } }); const items = await res.json(); renderPurchasePanel(items); } function renderPurchasePanel(items) { const container = document.getElementById('purchase-panel-body'); if (items.length === 0) { container.innerHTML = '

\u6682\u65e0\u91c7\u8d2d\u8bb0\u5f55

'; return; } const groups = {}; items.forEach(item => { const month = formatMonth(item.created_at); if (!groups[month]) groups[month] = []; groups[month].push(item); }); let html = ''; Object.keys(groups).sort().reverse().forEach(month => { const monthItems = groups[month]; const monthTotal = monthItems.reduce((s, i) => s + (i.amount||0), 0); html += '
' + month + '\uffe5' + monthTotal + ''; monthItems.forEach(item => { var t = item.created_at.split(' ')[1]; var timeShort = t ? t.substring(0,5) : ''; var datePart = item.created_at.split(' ')[0]; var dateShort = datePart ? datePart.substring(5) : ''; html += '
' + '
' + escapeHtml(item.item) + '\uffe5' + item.amount + '
' + '
' + '' + dateShort + ' ' + timeShort + '' + 'x' + item.quantity + (item.invoice_type !== '\u65e0\u7968' ? ' \u00b7 ' + item.invoice_type : '') + '' + '' + item.status + '' + '
' + '
'; }); html += '
'; }); container.innerHTML = html; } function formatMonth(dateStr) { var clean = dateStr.replace(/-/g, '/'); var parts = clean.split('/'); if (parts.length >= 2) return parts[0] + '\u5e74' + parseInt(parts[1], 10) + '\u6708'; return dateStr.substring(0,7); } async function openDetail(pid) { var token = Store.getToken(); var res = await fetch('/api/purchases/' + pid, { headers: { 'Authorization': 'Bearer ' + token } }); var p = await res.json(); editPid = pid; document.getElementById('edit-item').value = p.item || ''; document.getElementById('edit-quantity').value = p.quantity || 0; document.getElementById('edit-unit-price').value = p.unit_price || 0; document.getElementById('edit-freight').value = p.freight || 0; updateAmountSummary(); document.getElementById('edit-payment-method').value = p.payment_method || '\u672a\u6307\u5b9a'; document.getElementById('edit-invoice-type').value = p.invoice_type || '\u65e0\u7968'; document.getElementById('edit-status').value = p.status || '\u5f85\u4ed8\u6b3e'; updateStatusSummary(); document.getElementById('edit-applicant').value = p.applicant || ''; document.getElementById('edit-time').value = p.created_at || ''; document.getElementById('edit-remarks').value = p.remarks || ''; renderEditAttachments(p.attachments || []); var historyHtml = (p.history || []).map(function(h) { return '
  • ' + h.timestamp + ' ' + escapeHtml(h.user) + ': ' + escapeHtml(h.action) + '
  • '; }).join(''); document.getElementById('edit-history').innerHTML = historyHtml || '
  • \u6682\u65e0\u64cd\u4f5c\u8bb0\u5f55
  • '; document.getElementById('edit-history-section').open = false; document.getElementById('delete-purchase-btn').style.display = ''; document.getElementById('delete-purchase-btn').style.marginLeft = ''; document.querySelectorAll('.edit-group-detail').forEach(function(el) { el.classList.add('hidden'); }); document.querySelectorAll('.edit-group-summary').forEach(function(el) { el.classList.remove('hidden'); }); document.getElementById('detail-modal').classList.remove('hidden'); } function renderEditAttachments(attachments) { var container = document.getElementById('edit-attachments'); if (!attachments || attachments.length === 0) { container.innerHTML = '\u6682\u65e0\u9644\u4ef6'; return; } container.innerHTML = attachments.map(function(a) { return '
    ' + '' + '' + '
    '; }).join(''); } function updateAmountSummary() { var q = parseFloat(document.getElementById('edit-quantity').value) || 0; var up = parseFloat(document.getElementById('edit-unit-price').value) || 0; var f = parseFloat(document.getElementById('edit-freight').value) || 0; var total = (q * up + f).toFixed(2); document.getElementById('amount-summary').textContent = total + ', \u6570\u91cf' + q + ', \u5355\u4ef7' + up + ', \u90ae\u8d39' + f; } function updateStatusSummary() { var pm = document.getElementById('edit-payment-method').value; var iv = document.getElementById('edit-invoice-type').value; var st = document.getElementById('edit-status').value; document.getElementById('status-summary').textContent = st + ', ' + pm + ', ' + iv; } function toggleEditGroup(groupId) { var detail = document.getElementById(groupId); var summary = detail.previousElementSibling; var isOpen = !detail.classList.contains('hidden'); if (isOpen) { detail.classList.add('hidden'); summary.classList.remove('hidden'); } else { detail.classList.remove('hidden'); summary.classList.add('hidden'); } } async function savePurchase() { if (!editPid) return; var token = Store.getToken(); var body = { item: document.getElementById('edit-item').value, quantity: parseFloat(document.getElementById('edit-quantity').value) || 0, unit_price: parseFloat(document.getElementById('edit-unit-price').value) || 0, freight: parseFloat(document.getElementById('edit-freight').value) || 0, payment_method: document.getElementById('edit-payment-method').value, invoice_type: document.getElementById('edit-invoice-type').value, status: document.getElementById('edit-status').value, applicant: document.getElementById('edit-applicant').value, remarks: document.getElementById('edit-remarks').value, created_at: document.getElementById('edit-time').value }; var res = await fetch('/api/purchases/' + editPid, { method: 'PUT', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); var result = await res.json(); if (result.success) { closeDetailModal(); loadPurchases(); } else { alert('\u4fdd\u5b58\u5931\u8d25\uff1a' + (result.error || '\u672a\u77e5\u9519\u8bef')); } } async function deletePurchaseDetail() { if (!editPid) return; if (!confirm('\u786e\u5b9a\u8981\u5220\u9664\u6b64\u91c7\u8d2d\u8bb0\u5f55\u5417\uff1f')) return; var token = Store.getToken(); var res = await fetch('/api/purchases/' + editPid, { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token } }); if (res.ok) { closeDetailModal(); loadPurchases(); } else { var err = await res.json(); alert('\u5220\u9664\u5931\u8d25\uff1a' + (err.error || '\u672a\u77e5\u9519\u8bef')); } } async function deleteAttachment(event, encodedPath) { event.stopPropagation(); var filePath = decodeURIComponent(encodedPath); if (!confirm('\u786e\u5b9a\u5220\u9664\u6b64\u9644\u4ef6\u5417\uff1f')) return; var token = Store.getToken(); var res = await fetch('/api/purchases/' + editPid + '/attachments', { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify({ file_path: filePath }) }); if (res.ok) openDetail(editPid); } async function handleDetailFileUpload(event) { var files = event.target.files; if (!files.length) return; var token = Store.getToken(); var paths = []; try { for (var i = 0; i < files.length; i++) { var file = files[i]; var blob = file; if (file.size > 1 * 1024 * 1024) blob = await compressImage(file); var formData = new FormData(); formData.append('file', blob, file.name || 'image.jpg'); var res = await fetch('/api/upload', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token }, body: formData }); var data = await res.json(); if (data.path) paths.push(data.path); } if (paths.length) { await fetch('/api/purchases/' + editPid + '/attachments', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify({ file_paths: paths }) }); openDetail(editPid); } } catch(e) { alert('\u4e0a\u4f20\u5931\u8d25\uff0c\u8bf7\u91cd\u8bd5'); } } function closeDetailModal() { document.getElementById('detail-modal').classList.add('hidden'); editPid = null; } async function handlePurchaseFileSelect(event) { var files = event.target.files; if (!files.length) return; var token = Store.getToken(); try { for (var i = 0; i < files.length; i++) { var file = files[i]; var blob = file; if (file.size > 1 * 1024 * 1024) blob = await compressImage(file); var formData = new FormData(); formData.append('file', blob, file.name || 'image.jpg'); var res = await fetch('/api/upload', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token }, body: formData }); var data = await res.json(); if (data.path) Store.pendingUploads.push(data.path); } } catch(e) { alert('\u56fe\u7247\u4e0a\u4f20\u5931\u8d25'); } } function sendFromPurchase() { var input = document.getElementById('purchase-msg-input'); var text = input.value.trim(); if (!text && Store.pendingUploads.length === 0) return; Store.pendingPurchaseText = text; input.value = ''; openChatFromPurchase(); } async function openSummary() { var token = Store.getToken(); var res = await fetch('/api/summary', { headers: { 'Authorization': 'Bearer ' + token } }); var summary = await res.json(); var html = ''; if (summary.length === 0) html = '

    \u6682\u65e0\u91c7\u8d2d\u8bb0\u5f55

    '; else { summary.forEach(function(s) { html += '

    ' + escapeHtml(s.room_name) + '

    ' + s.purchase_count + ' \u6761\uff0c\u5408\u8ba1 \uffe5' + s.total_amount + '

    '; }); } document.getElementById('summary-title').textContent = '\u91c7\u8d2d\u6c47\u603b'; document.getElementById('summary-content').innerHTML = html; document.getElementById('summary-modal').classList.remove('hidden'); } function closeSummaryModal() { document.getElementById('summary-modal').classList.add('hidden'); } function openCreateRoom() { document.getElementById('create-modal').classList.remove('hidden'); var isAdmin = localStorage.getItem('isAdmin') === 'true'; document.getElementById('white-list').style.display = isAdmin ? '' : 'none'; } function closeCreateModal() { document.getElementById('create-modal').classList.add('hidden'); } async function confirmCreateRoom() { var name = document.getElementById('new-room-name').value.trim(); if (!name) return alert('\u8bf7\u8f93\u5165\u540d\u79f0'); var isAdmin = localStorage.getItem('isAdmin') === 'true'; var whiteList = isAdmin ? document.getElementById('white-list').value.trim() : localStorage.getItem('currentUser'); var token = Store.getToken(); await fetch('/api/rooms', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: name, whiteList: whiteList }) }); closeCreateModal(); loadRooms(); } function openManageRoom(roomId) { var room = Store.rooms.find(function(r) { return r.id === roomId; }); if (!room) return; var isAdmin = localStorage.getItem('isAdmin') === 'true'; document.getElementById('manage-title').textContent = '\u7f16\u8f91\u7fa4\u804a'; document.getElementById('manage-room-name').value = room.name; document.getElementById('manage-white-list').value = room.white_list || ''; document.getElementById('manage-white-list').style.display = isAdmin ? '' : 'none'; document.getElementById('manage-modal').dataset.roomId = roomId; document.getElementById('manage-modal').classList.remove('hidden'); } function closeManageModal() { document.getElementById('manage-modal').classList.add('hidden'); } async function saveRoom() { var roomId = document.getElementById('manage-modal').dataset.roomId; var name = document.getElementById('manage-room-name').value.trim(); var isAdmin = localStorage.getItem('isAdmin') === 'true'; var whiteList = isAdmin ? document.getElementById('manage-white-list').value.trim() : undefined; if (!name) return alert('\u8bf7\u8f93\u5165\u7fa4\u804a\u540d\u79f0'); var token = Store.getToken(); var res = await fetch('/api/rooms/' + roomId, { method: 'PUT', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: name, whiteList: whiteList }) }); if (res.ok) { closeManageModal(); loadRooms(); } else { var err = await res.json(); alert(err.error || '\u66f4\u65b0\u5931\u8d25'); } } async function deleteRoom() { if (!confirm('\u786e\u5b9a\u8981\u5220\u9664\u8be5\u7fa4\u804a\u5417\uff1f\u6240\u6709\u6d88\u606f\u548c\u91c7\u8d2d\u6570\u636e\u5c06\u88ab\u6c38\u4e45\u5220\u9664\u3002')) return; var roomId = document.getElementById('manage-modal').dataset.roomId; var token = Store.getToken(); var res = await fetch('/api/rooms/' + roomId, { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token } }); if (res.ok) { closeManageModal(); loadRooms(); if (Store.currentRoom === roomId) showList(); } else { var err = await res.json(); alert(err.error || '\u5220\u9664\u5931\u8d25'); } } function initSwipeGestures() { var purchasePage = document.getElementById('purchase-page'); var chatPage = document.getElementById('chat-page'); var startX = 0; purchasePage.addEventListener('touchstart', function(e) { startX = e.touches[0].clientX; }, { passive: true }); purchasePage.addEventListener('touchend', function(e) { if (!startX) return; var diffX = e.changedTouches[0].clientX - startX; if (Math.abs(diffX) > 50) { if (diffX > 30) showList(); else if (diffX < -30) openChatFromPurchase(); } startX = 0; }, { passive: true }); chatPage.addEventListener('touchstart', function(e) { startX = e.touches[0].clientX; }, { passive: true }); chatPage.addEventListener('touchend', function(e) { if (!startX) return; var diffX = e.changedTouches[0].clientX - startX; if (Math.abs(diffX) > 50) backToPurchase(); startX = 0; }, { passive: true }); }