xxx
This commit is contained in:
@@ -0,0 +1,463 @@
|
||||
import { defineStore } from 'pinia'
|
||||
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'
|
||||
import type { Schema } from '@/types/schemas'
|
||||
|
||||
declare const AirDatepicker: any
|
||||
|
||||
// Dashboard has no Pydantic schema in backend, define it here
|
||||
interface DashboardData {
|
||||
finished_product_count: number
|
||||
total_stock: number
|
||||
total_value: number
|
||||
supplier_count: number
|
||||
customer_count: number
|
||||
warehouse_count: number
|
||||
}
|
||||
|
||||
export const useInventoryStore = defineStore('inventory', () => {
|
||||
// ── state ──
|
||||
const activeTab = ref('dashboard')
|
||||
const backendDbReady = ref(true)
|
||||
const backendDbMessage = ref('')
|
||||
const productCategory = ref('finished')
|
||||
const dashboard = ref<DashboardData | null>(null)
|
||||
const financeSummary = ref<Schema<'FinanceSummaryResponse'> | null>(null)
|
||||
const financePeriod = reactive({ year: new Date().getFullYear(), quarter: '' as string })
|
||||
const financeTransactions = ref<Schema<'FinanceTransactionResponse'>[]>([])
|
||||
const receivables = ref<Schema<'ReceivableItemResponse'>[]>([])
|
||||
const payables = ref<Schema<'PayableItemResponse'>[]>([])
|
||||
const customerFinanceStatement = ref<Schema<'PartnerStatementItemResponse'>[]>([])
|
||||
const supplierFinanceStatement = ref<Schema<'PartnerStatementItemResponse'>[]>([])
|
||||
const customerProductStatement = ref<Schema<'PartnerProductStatementItemResponse'>[]>([])
|
||||
const supplierProductStatement = ref<Schema<'PartnerProductStatementItemResponse'>[]>([])
|
||||
const products = ref<Schema<'ProductResponse'>[]>([])
|
||||
const materials = ref<Schema<'ProductResponse'>[]>([])
|
||||
const finishedProducts = ref<Schema<'ProductResponse'>[]>([])
|
||||
const purchaseOrders = ref<Schema<'PurchaseOrderResponse'>[]>([])
|
||||
const purchaseWarehouseId = ref<number | null>(null)
|
||||
const purchaseReceiveItems = ref<any[]>([])
|
||||
const productionOrders = ref<Schema<'SalesOrderResponse'>[]>([])
|
||||
const productionPlan = ref<any>(null)
|
||||
const productionWarehouseId = ref<number | null>(null)
|
||||
const suppliers = ref<Schema<'SupplierResponse'>[]>([])
|
||||
const customers = ref<Schema<'CustomerResponse'>[]>([])
|
||||
const warehouses = ref<Schema<'WarehouseResponse'>[]>([])
|
||||
const inventory = ref<Schema<'InventoryResponse'>[]>([])
|
||||
const movements = ref<Schema<'StockMovementResponse'>[]>([])
|
||||
const loading = ref(false)
|
||||
const showModal = ref(false)
|
||||
const modalType = ref('')
|
||||
const editingItem = ref<any>(null)
|
||||
const productBomItems = ref<any[]>([])
|
||||
const materialConsumptionItems = ref<any[]>([])
|
||||
const showMaterialConsumptionModal = ref(false)
|
||||
const consumedMaterials = ref<any[]>([])
|
||||
const restockItems = ref<any[]>([])
|
||||
const showRestockModal = ref(false)
|
||||
const form = ref<any>({})
|
||||
|
||||
// ── date-picker refs ──
|
||||
let deliveryPicker: any = null
|
||||
let expectedPicker: any = null
|
||||
const deliveryDateInput = ref<HTMLElement | null>(null)
|
||||
const expectedDateInput = ref<HTMLElement | null>(null)
|
||||
const deliveryDateNativeInput = ref<HTMLElement | null>(null)
|
||||
const expectedDateNativeInput = ref<HTMLElement | null>(null)
|
||||
|
||||
// ── legacy state object (backward-compat for components still using `state.xxx`) ──
|
||||
const state = reactive({
|
||||
get activeTab() { return activeTab.value }, set activeTab(v) { activeTab.value = v },
|
||||
get backendDbReady() { return backendDbReady.value }, set backendDbReady(v) { backendDbReady.value = v },
|
||||
get backendDbMessage() { return backendDbMessage.value }, set backendDbMessage(v) { backendDbMessage.value = v },
|
||||
get productCategory() { return productCategory.value }, set productCategory(v) { productCategory.value = v },
|
||||
get dashboard() { return dashboard.value }, set dashboard(v) { dashboard.value = v },
|
||||
get financeSummary() { return financeSummary.value }, set financeSummary(v) { financeSummary.value = v },
|
||||
financePeriod,
|
||||
get financeTransactions() { return financeTransactions.value }, set financeTransactions(v) { financeTransactions.value = v },
|
||||
get receivables() { return receivables.value }, set receivables(v) { receivables.value = v },
|
||||
get payables() { return payables.value }, set payables(v) { payables.value = v },
|
||||
get customerFinanceStatement() { return customerFinanceStatement.value }, set customerFinanceStatement(v) { customerFinanceStatement.value = v },
|
||||
get supplierFinanceStatement() { return supplierFinanceStatement.value }, set supplierFinanceStatement(v) { supplierFinanceStatement.value = v },
|
||||
get customerProductStatement() { return customerProductStatement.value }, set customerProductStatement(v) { customerProductStatement.value = v },
|
||||
get supplierProductStatement() { return supplierProductStatement.value }, set supplierProductStatement(v) { supplierProductStatement.value = v },
|
||||
get products() { return products.value }, set products(v) { products.value = v },
|
||||
get materials() { return materials.value }, set materials(v) { materials.value = v },
|
||||
get finishedProducts() { return finishedProducts.value }, set finishedProducts(v) { finishedProducts.value = v },
|
||||
get purchaseOrders() { return purchaseOrders.value }, set purchaseOrders(v) { purchaseOrders.value = v },
|
||||
get purchaseWarehouseId() { return purchaseWarehouseId.value }, set purchaseWarehouseId(v) { purchaseWarehouseId.value = v },
|
||||
get purchaseReceiveItems() { return purchaseReceiveItems.value }, set purchaseReceiveItems(v) { purchaseReceiveItems.value = v },
|
||||
get productionOrders() { return productionOrders.value }, set productionOrders(v) { productionOrders.value = v },
|
||||
get productionPlan() { return productionPlan.value }, set productionPlan(v) { productionPlan.value = v },
|
||||
get productionWarehouseId() { return productionWarehouseId.value }, set productionWarehouseId(v) { productionWarehouseId.value = v },
|
||||
get suppliers() { return suppliers.value }, set suppliers(v) { suppliers.value = v },
|
||||
get customers() { return customers.value }, set customers(v) { customers.value = v },
|
||||
get warehouses() { return warehouses.value }, set warehouses(v) { warehouses.value = v },
|
||||
get inventory() { return inventory.value }, set inventory(v) { inventory.value = v },
|
||||
get movements() { return movements.value }, set movements(v) { movements.value = v },
|
||||
get loading() { return loading.value }, set loading(v) { loading.value = v },
|
||||
get showModal() { return showModal.value }, set showModal(v) { showModal.value = v },
|
||||
get modalType() { return modalType.value }, set modalType(v) { modalType.value = v },
|
||||
get editingItem() { return editingItem.value }, set editingItem(v) { editingItem.value = v },
|
||||
get productBomItems() { return productBomItems.value }, set productBomItems(v) { productBomItems.value = v },
|
||||
get materialConsumptionItems() { return materialConsumptionItems.value }, set materialConsumptionItems(v) { materialConsumptionItems.value = v },
|
||||
get showMaterialConsumptionModal() { return showMaterialConsumptionModal.value }, set showMaterialConsumptionModal(v) { showMaterialConsumptionModal.value = v },
|
||||
get consumedMaterials() { return consumedMaterials.value }, set consumedMaterials(v) { consumedMaterials.value = v },
|
||||
get restockItems() { return restockItems.value }, set restockItems(v) { restockItems.value = v },
|
||||
get showRestockModal() { return showRestockModal.value }, set showRestockModal(v) { showRestockModal.value = v },
|
||||
get form() { return form.value }, set form(v) { form.value = v },
|
||||
})
|
||||
|
||||
// ── helpers ──
|
||||
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)
|
||||
}
|
||||
|
||||
// ── pickers ──
|
||||
const destroyPickers = () => {
|
||||
if (deliveryPicker) { deliveryPicker.destroy(); deliveryPicker = null }
|
||||
if (expectedPicker) { expectedPicker.destroy(); expectedPicker = null }
|
||||
}
|
||||
|
||||
const initPickers = () => {
|
||||
destroyPickers()
|
||||
if (typeof AirDatepicker !== 'function') return
|
||||
if (modalType.value === 'salesOrder' && deliveryDateInput.value) {
|
||||
deliveryPicker = new AirDatepicker(deliveryDateInput.value, {
|
||||
timepicker: false, autoClose: true, zIndex: 2005, dateFormat: 'yyyy-MM-dd',
|
||||
onSelect: ({ formattedDate }: any) => {
|
||||
form.value.delivery_date = formattedDate || ''
|
||||
form.value.delivery_date_native = toNativeValue(formattedDate || '')
|
||||
}
|
||||
})
|
||||
const initial = parseDateTimeLocal(form.value.delivery_date)
|
||||
if (initial) deliveryPicker.selectDate(initial, { silent: true })
|
||||
}
|
||||
if (modalType.value === 'purchaseOrder' && expectedDateInput.value) {
|
||||
expectedPicker = new AirDatepicker(expectedDateInput.value, {
|
||||
timepicker: false, autoClose: true, zIndex: 2005, dateFormat: 'yyyy-MM-dd',
|
||||
onSelect: ({ formattedDate }: any) => {
|
||||
form.value.expected_date = formattedDate || ''
|
||||
form.value.expected_date_native = toNativeValue(formattedDate || '')
|
||||
}
|
||||
})
|
||||
const initial = parseDateTimeLocal(form.value.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()
|
||||
}
|
||||
|
||||
// ── label helpers ──
|
||||
const getMovementTypeLabel = (movementType: string): string => {
|
||||
const map: Record<string, string> = {
|
||||
in: '其他入库', out: '其他出库', adjust: '库存调整',
|
||||
purchase_in: '采购入库', return_from_production: '生产退料入库',
|
||||
outsource_return: '外协回库', finish_in: '完工入库',
|
||||
issue_to_production: '生产领料出库', outsource_send: '外协发料出库',
|
||||
shipment_out: '销售出库', scrap_out: '报废出库'
|
||||
}
|
||||
return map[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 map: Record<string, string> = { draft: '已下单', pending: '已下单', partial_received: '部分收货', received: '已收货', paid: '已付款', cancelled: '已作废' }
|
||||
return map[status] || status
|
||||
}
|
||||
|
||||
const isPurchaseOrderLocked = (status: string): boolean => ['received', 'paid', 'cancelled'].includes(status)
|
||||
|
||||
const getSalesOrderStatusLabel = (status: string): string => {
|
||||
const map: Record<string, string> = { manufacturing: '制造中', delivered: '已交付', paid: '已收款', cancelled: '已作废' }
|
||||
return map[status] || status
|
||||
}
|
||||
|
||||
const getDeliveryStatusLabel = (ds: string): string => {
|
||||
const map: Record<string, string> = { manufacturing: '制造中', delivered: '已交付', cancelled: '已作废' }
|
||||
return map[ds] || ds
|
||||
}
|
||||
|
||||
const getPaymentStatusLabel = (ps: string): string => {
|
||||
const map: Record<string, string> = { unpaid: '未收款', paid: '已收款' }
|
||||
return map[ps] || ps
|
||||
}
|
||||
|
||||
const getReceiptStatusLabel = (rs: string): string => {
|
||||
const map: Record<string, string> = { pending: '已下单', partial_received: '部分收货', received: '已收货', cancelled: '已作废' }
|
||||
return map[rs] || rs
|
||||
}
|
||||
|
||||
// ── data loading ──
|
||||
const checkBackendHealth = async () => {
|
||||
try {
|
||||
const resp = await fetch('/health', { method: 'GET' })
|
||||
if (!resp.ok) { backendDbReady.value = false; backendDbMessage.value = '后端服务异常,暂无法加载业务数据'; return }
|
||||
const health = await resp.json().catch(() => null)
|
||||
if (health && health.database_connected === false) { backendDbReady.value = false; backendDbMessage.value = '数据库未连接,当前仅可浏览界面,业务数据暂不可用'; return }
|
||||
backendDbReady.value = true; backendDbMessage.value = ''
|
||||
} catch { backendDbReady.value = false; backendDbMessage.value = '无法连接后端服务' }
|
||||
}
|
||||
|
||||
const loadDashboard = async () => {
|
||||
loading.value = true
|
||||
try { dashboard.value = await apiRequest('/api/dashboard') }
|
||||
catch (e) { handleApiError(e, '加载仪表盘') }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
|
||||
const loadFinishedProducts = async () => {
|
||||
loading.value = true
|
||||
try { finishedProducts.value = await apiRequest('/api/products?item_type=finished&limit=100') }
|
||||
catch (e) { handleApiError(e, '加载成品') }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
|
||||
const loadProducts = async () => { await loadFinishedProducts() }
|
||||
|
||||
const loadMaterials = async () => {
|
||||
loading.value = true
|
||||
try { materials.value = await apiRequest('/api/products?item_type=material&limit=100') }
|
||||
catch (e) { handleApiError(e, '加载物料') }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
|
||||
const loadWarehouses = async () => {
|
||||
loading.value = true
|
||||
try { warehouses.value = await apiRequest('/api/warehouses') }
|
||||
catch (e) { handleApiError(e, '加载仓库') }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
|
||||
const ensureStockBaseData = async () => {
|
||||
if (!materials.value.length) await loadMaterials()
|
||||
if (!warehouses.value.length) await loadWarehouses()
|
||||
if (!warehouses.value.length) {
|
||||
try {
|
||||
await apiRequest('/api/warehouses', { method: 'POST', body: JSON.stringify({ name: '默认仓库' }) })
|
||||
await loadWarehouses()
|
||||
addNotification('已自动创建默认仓库', 'success')
|
||||
} catch (e) { handleApiError(e, '自动创建默认仓库') }
|
||||
}
|
||||
}
|
||||
|
||||
const loadSuppliers = async () => {
|
||||
loading.value = true
|
||||
try { suppliers.value = await apiRequest('/api/suppliers') }
|
||||
catch (e) { handleApiError(e, '加载供应商') }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
|
||||
const loadProductionOrders = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const [orders, wh] = await Promise.all([apiRequest('/api/sales-orders?limit=100'), apiRequest('/api/warehouses')])
|
||||
productionOrders.value = orders?.items || []
|
||||
warehouses.value = wh || []
|
||||
if (!productionWarehouseId.value) {
|
||||
productionWarehouseId.value = warehouses.value.find((w: any) => w.is_default)?.id || warehouses.value[0]?.id || null
|
||||
}
|
||||
} catch (e) { handleApiError(e, '加载按单生产数据') }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
|
||||
const loadPurchaseOrders = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const [orders, wh] = await Promise.all([apiRequest('/api/purchase-orders?limit=100'), apiRequest('/api/warehouses')])
|
||||
purchaseOrders.value = orders?.items || []
|
||||
warehouses.value = wh || []
|
||||
if (!purchaseWarehouseId.value) {
|
||||
purchaseWarehouseId.value = warehouses.value.find((w: any) => w.is_default)?.id || warehouses.value[0]?.id || null
|
||||
}
|
||||
} catch (e) { handleApiError(e, '加载采购订单') }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
|
||||
const loadCustomers = async () => {
|
||||
loading.value = true
|
||||
try { customers.value = await apiRequest('/api/customers') }
|
||||
catch (e) { handleApiError(e, '加载客户') }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
|
||||
const loadInventory = async () => {
|
||||
loading.value = true
|
||||
try { inventory.value = (await apiRequest('/api/inventory'))?.items || [] }
|
||||
catch (e) { handleApiError(e, '加载库存') }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
|
||||
const loadMovements = async () => {
|
||||
loading.value = true
|
||||
try { movements.value = (await apiRequest('/api/stock-movements'))?.items || [] }
|
||||
catch (e) { handleApiError(e, '加载变动记录') }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
|
||||
const loadFinance = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const selectedYear = Number(financePeriod.year) || new Date().getFullYear()
|
||||
const selectedQuarter = financePeriod.quarter ? Number(financePeriod.quarter) : null
|
||||
const periodQuery = selectedQuarter ? `year=${selectedYear}&quarter=${selectedQuarter}` : `year=${selectedYear}`
|
||||
const [summary, transactions, recv, pay, custStmt, suppStmt, custProdStmt, suppProdStmt] = 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}`)
|
||||
])
|
||||
financeSummary.value = summary
|
||||
financeTransactions.value = transactions?.items || []
|
||||
receivables.value = recv
|
||||
payables.value = pay
|
||||
customerFinanceStatement.value = custStmt.items || []
|
||||
supplierFinanceStatement.value = suppStmt.items || []
|
||||
customerProductStatement.value = custProdStmt.items || []
|
||||
supplierProductStatement.value = suppProdStmt.items || []
|
||||
} catch (e) { handleApiError(e, '加载财务数据') }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
|
||||
const refreshFinanceByPeriod = () => { if (activeTab.value === 'finance') loadFinance() }
|
||||
|
||||
const switchTab = (tab: string) => { activeTab.value = tab }
|
||||
|
||||
const closeModal = () => {
|
||||
showModal.value = false; modalType.value = ''; editingItem.value = null
|
||||
productBomItems.value = []; purchaseReceiveItems.value = []; form.value = {}
|
||||
destroyPickers()
|
||||
}
|
||||
|
||||
const modalTitle = computed(() => {
|
||||
const prefix = editingItem.value ? '编辑' : '新增'
|
||||
const typeMap: Record<string, string> = {
|
||||
product: form.value.item_type === 'finished' ? '成品' : '物料',
|
||||
inventoryItem: '物料库存', salesOrder: '销售订单', purchaseOrder: '采购订单',
|
||||
purchaseReceive: '采购到货入库', supplier: '供应商', customer: '客户'
|
||||
}
|
||||
return prefix + (typeMap[modalType.value] || '')
|
||||
})
|
||||
|
||||
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<Record<string, boolean>>(
|
||||
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 === activeTab.value)
|
||||
if (item) return { group, item }
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
const handleMenuClick = (itemKey: string) => { switchTab(itemKey) }
|
||||
const switchProductCategory = (category: string) => { productCategory.value = category }
|
||||
|
||||
return {
|
||||
// state refs (new Pinia style)
|
||||
activeTab, backendDbReady, backendDbMessage, productCategory, dashboard,
|
||||
financeSummary, financePeriod, financeTransactions, receivables, payables,
|
||||
customerFinanceStatement, supplierFinanceStatement, customerProductStatement, supplierProductStatement,
|
||||
products, materials, finishedProducts, purchaseOrders, purchaseWarehouseId, purchaseReceiveItems,
|
||||
productionOrders, productionPlan, productionWarehouseId, suppliers, customers, warehouses,
|
||||
inventory, movements, loading, showModal, modalType, editingItem, productBomItems,
|
||||
materialConsumptionItems, showMaterialConsumptionModal, consumedMaterials,
|
||||
restockItems, showRestockModal, form,
|
||||
// legacy state object (backward compat)
|
||||
state,
|
||||
// refs
|
||||
deliveryDateInput, expectedDateInput, deliveryDateNativeInput, expectedDateNativeInput,
|
||||
// computed
|
||||
menuGroups, openGroups, activeMenu, modalTitle,
|
||||
// actions
|
||||
parseDateTimeLocal, toPickerValue, toApiDateTime, toNativeValue, fromNativeValue,
|
||||
destroyPickers, initPickers, openDateTimePicker,
|
||||
getMovementTypeLabel, getMovementBadgeClass, getPurchaseOrderStatusLabel, isPurchaseOrderLocked,
|
||||
getSalesOrderStatusLabel, getDeliveryStatusLabel, getPaymentStatusLabel, getReceiptStatusLabel,
|
||||
checkBackendHealth, loadDashboard, loadFinishedProducts, loadProducts, loadMaterials,
|
||||
loadWarehouses, ensureStockBaseData, loadSuppliers, loadProductionOrders, loadPurchaseOrders,
|
||||
loadCustomers, loadInventory, loadMovements, loadFinance, refreshFinanceByPeriod,
|
||||
switchTab, closeModal, toggleGroup, handleMenuClick, switchProductCategory,
|
||||
formatCurrency, formatNumber, formatDateTime, formatDate
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user