import { reactive, ref, computed } from 'vue' import { apiRequest } from '@/shared/api' import { addNotification, handleApiError } from '@/shared/notification' import { formatCurrency, formatNumber, formatDateTime, formatDate } from '@/shared/utils' declare const AirDatepicker: any const state = reactive({ activeTab: 'dashboard' as string, backendDbReady: true, backendDbMessage: '' as string, productCategory: 'finished' as string, dashboard: null as any, financeSummary: null as any, financePeriod: { year: new Date().getFullYear(), quarter: '' as string }, financeTransactions: [] as any[], receivables: [] as any[], payables: [] as any[], customerFinanceStatement: [] as any[], supplierFinanceStatement: [] as any[], customerProductStatement: [] as any[], supplierProductStatement: [] as any[], products: [] as any[], materials: [] as any[], finishedProducts: [] as any[], purchaseOrders: [] as any[], purchaseWarehouseId: null as number | null, purchaseReceiveItems: [] as any[], productionOrders: [] as any[], productionPlan: null as any, productionWarehouseId: null as number | null, suppliers: [] as any[], customers: [] as any[], warehouses: [] as any[], inventory: [] as any[], movements: [] as any[], loading: false, showModal: false, modalType: '' as string, editingItem: null as any, productBomItems: [] as any[], materialConsumptionItems: [] as any[], showMaterialConsumptionModal: false, consumedMaterials: [] as any[], restockItems: [] as any[], showRestockModal: false, form: {} as any }) let deliveryPicker: any = null let expectedPicker: any = null const deliveryDateInput = ref(null) const expectedDateInput = ref(null) const deliveryDateNativeInput = ref(null) const expectedDateNativeInput = ref(null) export function useInventory() { const parseDateTimeLocal = (text: string | null | undefined): Date | null => { if (!text) return null const raw = String(text).trim() const normalized = raw.replace('T', ' ').slice(0, 16) const m = normalized.match(/^(\d{4})-(\d{2})-(\d{2})\s(\d{2}):(\d{2})$/) if (!m) return null const year = Number(m[1]) const month = Number(m[2]) const day = Number(m[3]) const hour = Number(m[4]) const minute = Number(m[5]) if (!Number.isFinite(year + month + day + hour + minute)) return null return new Date(year, month - 1, day, hour, minute, 0) } const toPickerValue = (value: any): string => { if (!value) return '' const raw = String(value).trim() if (/^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}/.test(raw)) return raw.slice(0, 16) if (raw.includes('T')) return raw.replace('T', ' ').slice(0, 16) const dt = new Date(raw) if (!Number.isFinite(dt.getTime())) return '' const pad = (n: number) => String(n).padStart(2, '0') return `${dt.getFullYear()}-${pad(dt.getMonth() + 1)}-${pad(dt.getDate())} ${pad(dt.getHours())}:${pad(dt.getMinutes())}` } const toApiDateTime = (value: any): string | null => { if (!value) return null const text = String(value).trim() if (text.includes('T')) return text.split('T')[0] if (text.includes(' ')) return text.split(' ')[0] if (text.length === 10) return text if (value instanceof Date) { const year = value.getFullYear() const month = String(value.getMonth() + 1).padStart(2, '0') const day = String(value.getDate()).padStart(2, '0') return `${year}-${month}-${day}` } return text } const toNativeValue = (value: any): string => { if (!value) return '' const text = String(value).trim() if (text.length === 10 && !text.includes('T') && !text.includes(' ')) return text const isoText = text.replace(' ', 'T') return isoText.length >= 16 ? isoText.slice(0, 16) : isoText } const fromNativeValue = (value: any): string => { if (!value) return '' const text = String(value).trim() if (text.length === 10 && !text.includes('T') && !text.includes(' ')) return text return text.replace('T', ' ').slice(0, 16) } const destroyPickers = () => { if (deliveryPicker) { deliveryPicker.destroy() deliveryPicker = null } if (expectedPicker) { expectedPicker.destroy() expectedPicker = null } } const initPickers = () => { destroyPickers() if (typeof AirDatepicker !== 'function') return if (state.modalType === 'salesOrder' && deliveryDateInput.value) { deliveryPicker = new AirDatepicker(deliveryDateInput.value, { timepicker: false, autoClose: true, zIndex: 2005, dateFormat: 'yyyy-MM-dd', onSelect: ({ formattedDate }: any) => { state.form.delivery_date = formattedDate || '' state.form.delivery_date_native = toNativeValue(formattedDate || '') } }) const initial = parseDateTimeLocal(state.form.delivery_date) if (initial) deliveryPicker.selectDate(initial, { silent: true }) } if (state.modalType === 'purchaseOrder' && expectedDateInput.value) { expectedPicker = new AirDatepicker(expectedDateInput.value, { timepicker: false, autoClose: true, zIndex: 2005, dateFormat: 'yyyy-MM-dd', onSelect: ({ formattedDate }: any) => { state.form.expected_date = formattedDate || '' state.form.expected_date_native = toNativeValue(formattedDate || '') } }) const initial = parseDateTimeLocal(state.form.expected_date) if (initial) expectedPicker.selectDate(initial, { silent: true }) } } const openDateTimePicker = (pickerKind: string) => { if (pickerKind === 'delivery' && deliveryPicker) { deliveryPicker.show() return } if (pickerKind === 'expected' && expectedPicker) { expectedPicker.show() return } const nativeInput = pickerKind === 'delivery' ? deliveryDateNativeInput.value : expectedDateNativeInput.value if (!nativeInput) return if (typeof (nativeInput as any).showPicker === 'function') { (nativeInput as any).showPicker() return } nativeInput.focus() nativeInput.click() } const getMovementTypeLabel = (movementType: string): string => { const movementLabelMap: Record = { in: '其他入库', out: '其他出库', adjust: '库存调整', purchase_in: '采购入库', return_from_production: '生产退料入库', outsource_return: '外协回库', finish_in: '完工入库', issue_to_production: '生产领料出库', outsource_send: '外协发料出库', shipment_out: '销售出库', scrap_out: '报废出库' } return movementLabelMap[movementType] || movementType } const getMovementBadgeClass = (movementType: string): string => { if (['purchase_in', 'return_from_production', 'outsource_return', 'finish_in', 'in'].includes(movementType)) { return 'badge-success' } if (['issue_to_production', 'outsource_send', 'shipment_out', 'scrap_out', 'out'].includes(movementType)) { return 'badge-error' } return 'badge-warning' } const getPurchaseOrderStatusLabel = (status: string): string => { const statusMap: Record = { draft: '已下单', pending: '已下单', received: '已收货', paid: '已付款' } return statusMap[status] || status } const isPurchaseOrderLocked = (status: string): boolean => { return ['received', 'paid'].includes(status) } const getSalesOrderStatusLabel = (status: string): string => { const statusMap: Record = { manufacturing: '制造中', delivered: '已交付', paid: '已收款' } return statusMap[status] || status } const checkBackendHealth = async () => { try { const resp = await fetch('/health', { method: 'GET' }) if (!resp.ok) { state.backendDbReady = false state.backendDbMessage = '后端服务异常,暂无法加载业务数据' return } const health = await resp.json().catch(() => null) if (health && health.database_connected === false) { state.backendDbReady = false state.backendDbMessage = '数据库未连接,当前仅可浏览界面,业务数据暂不可用' return } state.backendDbReady = true state.backendDbMessage = '' } catch { state.backendDbReady = false state.backendDbMessage = '无法连接后端服务' } } const loadDashboard = async () => { state.loading = true try { state.dashboard = await apiRequest('/api/dashboard') } catch (e) { handleApiError(e, '加载仪表盘') } finally { state.loading = false } } const loadFinishedProducts = async () => { state.loading = true try { state.finishedProducts = await apiRequest('/api/products?item_type=finished&limit=100') } catch (e) { handleApiError(e, '加载成品') } finally { state.loading = false } } const loadProducts = async () => { await loadFinishedProducts() } const loadMaterials = async () => { state.loading = true try { state.materials = await apiRequest('/api/products?item_type=material&limit=100') } catch (e) { handleApiError(e, '加载物料') } finally { state.loading = false } } const loadWarehouses = async () => { state.loading = true try { state.warehouses = await apiRequest('/api/warehouses') } catch (e) { handleApiError(e, '加载仓库') } finally { state.loading = false } } const ensureStockBaseData = async () => { if (!state.materials.length) await loadMaterials() if (!state.warehouses.length) await loadWarehouses() if (!state.warehouses.length) { try { await apiRequest('/api/warehouses', { method: 'POST', body: JSON.stringify({ name: '默认仓库' }) }) await loadWarehouses() addNotification('已自动创建默认仓库', 'success') } catch (e) { handleApiError(e, '自动创建默认仓库') } } } const loadSuppliers = async () => { state.loading = true try { state.suppliers = await apiRequest('/api/suppliers') } catch (e) { handleApiError(e, '加载供应商') } finally { state.loading = false } } const loadProductionOrders = async () => { state.loading = true try { const [orders, warehouses] = await Promise.all([ apiRequest('/api/sales-orders?limit=100'), apiRequest('/api/warehouses') ]) state.productionOrders = orders?.items || [] state.warehouses = warehouses || [] if (!state.productionWarehouseId) { state.productionWarehouseId = state.warehouses.find((w: any) => w.is_default)?.id || state.warehouses[0]?.id || null } } catch (e) { handleApiError(e, '加载按单生产数据') } finally { state.loading = false } } const loadPurchaseOrders = async () => { state.loading = true try { const [orders, warehouses] = await Promise.all([ apiRequest('/api/purchase-orders?limit=100'), apiRequest('/api/warehouses') ]) state.purchaseOrders = orders?.items || [] state.warehouses = warehouses || [] if (!state.purchaseWarehouseId) { state.purchaseWarehouseId = state.warehouses.find((w: any) => w.is_default)?.id || state.warehouses[0]?.id || null } } catch (e) { handleApiError(e, '加载采购订单') } finally { state.loading = false } } const loadCustomers = async () => { state.loading = true try { state.customers = await apiRequest('/api/customers') } catch (e) { handleApiError(e, '加载客户') } finally { state.loading = false } } const loadInventory = async () => { state.loading = true try { state.inventory = (await apiRequest('/api/inventory'))?.items || [] } catch (e) { handleApiError(e, '加载库存') } finally { state.loading = false } } const loadMovements = async () => { state.loading = true try { state.movements = (await apiRequest('/api/stock-movements'))?.items || [] } catch (e) { handleApiError(e, '加载变动记录') } finally { state.loading = false } } const loadFinance = async () => { state.loading = true try { const selectedYear = Number(state.financePeriod.year) || new Date().getFullYear() const selectedQuarter = state.financePeriod.quarter ? Number(state.financePeriod.quarter) : null const periodQuery = selectedQuarter ? `year=${selectedYear}&quarter=${selectedQuarter}` : `year=${selectedYear}` const [summary, transactions, receivables, payables, customerStatement, supplierStatement, customerProductStatement, supplierProductStatement] = await Promise.all([ apiRequest(`/api/finance/summary?${periodQuery}`), apiRequest(`/api/finance/transactions?status=confirmed&limit=20&${periodQuery}`), apiRequest(`/api/finance/receivables?limit=20&${periodQuery}`), apiRequest(`/api/finance/payables?limit=20&${periodQuery}`), apiRequest(`/api/finance/partner-statement/customer?${periodQuery}`), apiRequest(`/api/finance/partner-statement/supplier?${periodQuery}`), apiRequest(`/api/finance/partner-product-statement/customer?${periodQuery}`), apiRequest(`/api/finance/partner-product-statement/supplier?${periodQuery}`) ]) state.financeSummary = summary state.financeTransactions = transactions state.receivables = receivables state.payables = payables state.customerFinanceStatement = customerStatement.items || [] state.supplierFinanceStatement = supplierStatement.items || [] state.customerProductStatement = customerProductStatement.items || [] state.supplierProductStatement = supplierProductStatement.items || [] } catch (e) { handleApiError(e, '加载财务数据') } finally { state.loading = false } } const refreshFinanceByPeriod = () => { if (state.activeTab === 'finance') loadFinance() } const switchTab = (tab: string) => { state.activeTab = tab } const closeModal = () => { state.showModal = false state.modalType = '' state.editingItem = null state.productBomItems = [] state.purchaseReceiveItems = [] state.form = {} destroyPickers() } const modalTitle = computed(() => { const prefix = state.editingItem ? '编辑' : '新增' const typeMap: Record = { product: state.form.item_type === 'finished' ? '成品' : '物料', inventoryItem: '物料库存', salesOrder: '销售订单', purchaseOrder: '采购订单', purchaseReceive: '采购到货入库', supplier: '供应商', customer: '客户' } return prefix + (typeMap[state.modalType] || '') }) const menuGroups = [ { key: 'overview', title: '概览', items: [{ key: 'dashboard', label: '仪表盘' }] }, { key: 'sales', title: '销售', items: [{ key: 'sales_orders', label: '销售订单管理' }] }, { key: 'purchase', title: '采购', items: [{ key: 'purchases', label: '采购订单管理' }] }, { key: 'product', title: '产品', items: [{ key: 'products', label: '成品管理' }, { key: 'materials', label: '物料管理' }] }, { key: 'partner', title: '往来单位', items: [{ key: 'customers', label: '客户管理' }, { key: 'suppliers', label: '供应商管理' }] }, { key: 'warehouse', title: '仓库', items: [{ key: 'inventory', label: '库存管理' }, { key: 'movements', label: '库存变动记录' }] }, { key: 'finance', title: '财务', items: [{ key: 'finance', label: '财务概览' }] } ] const openGroups = reactive>( Object.fromEntries(menuGroups.map(g => [g.key, true])) ) const toggleGroup = (groupKey: string) => { openGroups[groupKey] = !openGroups[groupKey] } const activeMenu = computed(() => { for (const group of menuGroups) { const item = group.items.find(i => i.key === state.activeTab) if (item) return { group, item } } return null }) const handleMenuClick = (itemKey: string) => { switchTab(itemKey) } const switchProductCategory = (category: string) => { state.productCategory = category } return { state, deliveryDateInput, expectedDateInput, deliveryDateNativeInput, expectedDateNativeInput, menuGroups, openGroups, activeMenu, modalTitle, parseDateTimeLocal, toPickerValue, toApiDateTime, toNativeValue, fromNativeValue, destroyPickers, initPickers, openDateTimePicker, getMovementTypeLabel, getMovementBadgeClass, getPurchaseOrderStatusLabel, isPurchaseOrderLocked, getSalesOrderStatusLabel, checkBackendHealth, loadDashboard, loadFinishedProducts, loadProducts, loadMaterials, loadWarehouses, ensureStockBaseData, loadSuppliers, loadProductionOrders, loadPurchaseOrders, loadCustomers, loadInventory, loadMovements, loadFinance, refreshFinanceByPeriod, switchTab, closeModal, toggleGroup, handleMenuClick, switchProductCategory, formatCurrency, formatNumber, formatDateTime, formatDate } }