import { request } from '@/api/request.js' /** * 进销存移动端接口封装,对应后端 /api/erp/*(路由表 go/routers/api/erp.go)。 * 与 Web 后台 /backend/erp/* 共用同一份统计口径:报表只认「已生效」单据与库存流水, * 所以手机端看到的金额、毛利、预警数与 Web 端永远一致。 * 商品/仓库/客商在本端只读,建档一律回 Web 端完成。 */ /** 单据状态机:草稿 →(提交审批) 审批中 →(引擎通过) 已生效;驳回/作废为终态 */ export const DOC_STATUS = { DRAFT: 0, APPROVING: 1, APPROVED: 2, REJECTED: 3, CANCELLED: 4 } export const DOC_STATUS_OPTIONS = [ { value: DOC_STATUS.DRAFT, label: '草稿', type: 'info' }, { value: DOC_STATUS.APPROVING, label: '审批中', type: 'primary' }, { value: DOC_STATUS.APPROVED, label: '已生效', type: 'success' }, { value: DOC_STATUS.REJECTED, label: '已驳回', type: 'error' }, { value: DOC_STATUS.CANCELLED, label: '已作废', type: 'warning' } ] /** 往来账核销状态 */ export const BILL_STATUS_OPTIONS = [ { value: 0, label: '未核销', type: 'warning' }, { value: 1, label: '部分核销', type: 'primary' }, { value: 2, label: '已结清', type: 'success' } ] /** 收付款方式:1收款 2付款 3其他收入 4费用支出 */ export const FUND_TYPES = [ { value: 1, label: '收款', color: 'success' }, { value: 2, label: '付款', color: 'error' }, { value: 3, label: '其他收入', color: 'primary' }, { value: 4, label: '费用支出', color: 'warning' } ] /** 收款/付款单的结算方式(与 Web 端 payment.vue methodOptions 同一套值) */ export const FUND_METHODS = [ { value: 'cash', label: '现金' }, { value: 'bank', label: '银行转账' }, { value: 'wechat', label: '微信' }, { value: 'alipay', label: '支付宝' } ] /** 收付款单状态:仅草稿可改,确认后驱动核销,作废会回滚已核销余额 */ export const FUND_STATUS_OPTIONS = [ { value: 0, label: '草稿', type: 'info' }, { value: 1, label: '已确认', type: 'success' }, { value: 4, label: '已作废', type: 'warning' } ] /** 往来账核销状态(列表筛选值,与后端 settle_state 参数一致) */ export const SETTLE_STATES = [ { value: 'open', label: '未结清' }, { value: 'part', label: '部分核销' }, { value: 'done', label: '已结清' } ] /** 单据类型 → 中文名(列表/流水里只有 doc_type 字符串时兜底用) */ export const DOC_TYPE_LABELS = { purchase_order: '采购订单', purchase_in: '采购入库', purchase_return: '采购退货', sales_order: '销售订单', sales_out: '销售出库', sales_return: '销售退货', other_in: '其他入库', other_out: '其他出库', transfer: '库存调拨', stock_take: '库存盘点' } export function docTypeLabel(type) { return DOC_TYPE_LABELS[type] || type } /** 往来账类型 */ export const BILL_TYPES = [ { value: 1, label: '应收', color: 'error' }, { value: 2, label: '应付', color: 'success' } ] export const PARTNER_TYPES = [ { value: 'supplier', label: '供应商' }, { value: 'customer', label: '客户' } ] /** 库存预警状态(后端 safety_state 三值) */ export const SAFETY_STATE_OPTIONS = [ { value: '', label: '全部' }, { value: 'low', label: '低于安全库存', type: 'warning' }, { value: 'over', label: '库存积压', type: 'primary' }, { value: 'normal', label: '正常', type: 'success' } ] /** 批次效期状态 */ export const EXPIRE_STATE_OPTIONS = [ { value: '', label: '全部' }, { value: 'expired', label: '已过期', type: 'error' }, { value: 'expiring', label: '30天内到期', type: 'warning' }, { value: 'normal', label: '正常', type: 'success' } ] /** 购销汇总维度 */ export const REPORT_DIMENSIONS = [ { value: 'product', label: '按商品' }, { value: 'supplier', label: '按供应商' }, { value: 'customer', label: '按客户' }, { value: 'month', label: '按月' } ] const LABEL_FALLBACK = { value: '未知' } export function findOption(options, value) { return options.find(item => String(item.value) === String(value ?? '')) || LABEL_FALLBACK } export function docStatusLabel(status) { return findOption(DOC_STATUS_OPTIONS, status).label || status } export function docStatusType(status) { return findOption(DOC_STATUS_OPTIONS, status).type || 'info' } export function billStatusLabel(status) { return findOption(BILL_STATUS_OPTIONS, status).label } export function billStatusType(status) { return findOption(BILL_STATUS_OPTIONS, status).type || 'info' } export function fundMethodLabel(method) { return findOption(FUND_METHODS, method).label || method } export function fundStatusLabel(status) { return findOption(FUND_STATUS_OPTIONS, status).label } export function fundStatusType(status) { return findOption(FUND_STATUS_OPTIONS, status).type || 'info' } export function partnerTypeLabel(type) { return findOption(PARTNER_TYPES, type).label } export function safetyStateLabel(state) { return findOption(SAFETY_STATE_OPTIONS, state).label || state } /** 金额展示:千分位可选,统一两位小数 */ export function moneyText(value) { const num = Number(value || 0) return num.toFixed(2) } /** 数量展示:去掉浮点尾巴,最多四位小数 */ export function qtyText(value) { const num = Number(value || 0) return String(Math.round(num * 10000) / 10000) } /** ISO 时间串取日期部分 */ export function dayText(value) { return String(value || '').slice(0, 10) } export function dateTimeText(value) { const s = String(value || '') return s ? `${s.slice(0, 10)} ${s.slice(11, 16)}` : '' } /** 分页参数归一:后端 pageSize 上限 100,越界回落 20 */ function pageArgs(params) { return { page: params.page || 1, pageSize: params.pageSize || 20 } } /** 日期区间参数:空值不传,避免覆盖后端默认口径 */ function rangeArgs(params) { const out = {} if (params.dateFrom) out.date_from = params.dateFrom if (params.dateTo) out.date_to = params.dateTo return out } // ----------------------------------------------------------------- 元数据 / 看板 /** * 开单字典:单据类型(含往来口径与是否动库存)、状态档、默认仓库、价格体系、负库存开关 */ export function fetchErpMeta() { return request({ url: '/api/erp/meta' }) } /** 单据类型字典(列表筛选、开单选择都用它,避免端上硬编码十种类型) */ export async function fetchDocTypes() { const meta = await fetchErpMeta() return meta.doc_types || [] } /** * 经营看板:本月购销/毛利、今日销售、库存价值、往来余额、待办、近 6 月趋势、预警计数 */ export async function fetchErpDashboard() { const data = await request({ url: '/api/erp/dashboard' }) return { month: data.month || {}, today: data.today || {}, stock: data.stock || {}, partnerBill: data.partner_bill || {}, todo: data.todo || {}, trend: data.trend || [], warning: data.warning || {} } } // ----------------------------------------------------------------- 单据 /** * 单据列表 * params: { docType | docTypes[], status, keyword, partnerId, warehouseId, sourceDocId, * dateFrom, dateTo, page, pageSize } */ export async function fetchDocs(params = {}) { const data = await request({ url: '/api/erp/document/list', data: { ...pageArgs(params), doc_type: params.docType || '', doc_types: Array.isArray(params.docTypes) ? params.docTypes.join(',') : (params.docTypes || ''), status: params.status ?? '', keyword: params.keyword || '', partner_id: params.partnerId || '', warehouse_id: params.warehouseId || '', source_doc_id: params.sourceDocId || '', ...rangeArgs(params) } }) return { list: (data.list || []).map(normalizeDoc), total: data.total || 0 } } /** 单据详情(含明细行、实时库存与审批实例摘要) */ export async function getDoc(id) { const data = await request({ url: `/api/erp/document/detail/${id}` }) if (!data) return null return normalizeDoc(data, true) } /** * 新建/覆盖保存单据(草稿),返回 { id, docNo } * doc: { id?, docType, bizDate, warehouseId, toWarehouseId, partnerId, sourceDocId, * discountAmount, remark, items: [{ productId, quantity, unitPrice, ... }] } */ export async function saveDoc(doc) { const res = await request({ url: '/api/erp/document/save', method: 'POST', data: buildDocPayload(doc) }) return { id: res?.id, docNo: res?.doc_no || '' } } /** 直接审核生效(跳过审批引擎,用于未绑定审批流的租户) */ export function approveDoc(id) { return request({ url: `/api/erp/document/approve/${id}`, method: 'POST', data: {} }) } /** 作废单据(草稿/审批中/已驳回可作废) */ export function cancelDoc(id) { return request({ url: `/api/erp/document/cancel/${id}`, method: 'POST', data: {} }) } /** 删除单据(仅草稿/已驳回) */ export async function deleteDoc(id) { await request({ url: `/api/erp/document/delete/${id}`, method: 'DELETE' }) return true } /** 提交审批:flowId 为空时后端按单据类型自动选流(flow_id 走 query,不进 body) */ export async function submitDoc(id, flowId) { const query = flowId ? `?flow_id=${flowId}` : '' const res = await request({ url: `/api/erp/document/submit/${id}${query}`, method: 'POST', data: {} }) return res?.instance_id } /** 单据类型可用的审批流 */ export async function fetchDocFlows(docType) { const data = await request({ url: '/api/erp/document/flows', data: { doc_type: docType } }) return { list: data.list || [], bizType: data.biz_type || '' } } /** * 来源订单的可执行明细行(下单量 - 已执行量),用于「订单转入库/出库」 */ export async function fetchExecutableDocs(docId) { const data = await request({ url: `/api/erp/document/executable/${docId}` }) return { doc: data.doc || null, items: (data.items || []).map(item => ({ ...item, remain_qty: Number(item.remain_qty || 0), quantity: Number(item.quantity || 0), executed_qty: Number(item.executed_qty || 0) })) } } /** 盘点表:带出仓库账面库存,供盘点录入对比差异 */ export async function fetchTakeSheet(warehouseId) { const data = await request({ url: '/api/erp/document/takeSheet', data: { warehouse_id: warehouseId } }) return (data.list || []).map(row => ({ ...row, book_qty: Number(row.book_qty || 0) })) } // ----------------------------------------------------------------- 库存 /** * 库存查询 * params: { warehouseId, productId, categoryId, batchNo, keyword, groupBy('product'|其它=分仓批次), * safetyState, onlyStock, page, pageSize } → { list, total, sumAmount } */ export async function fetchStocks(params = {}) { const data = await request({ url: '/api/erp/stock/list', data: { ...pageArgs(params), warehouse_id: params.warehouseId || '', product_id: params.productId || '', category_id: params.categoryId || '', batch_no: params.batchNo || '', keyword: params.keyword || '', group_by: params.groupBy || '', safety_state: params.safetyState || '', only_stock: params.onlyStock ? 1 : '' } }) return { list: data.list || [], total: data.total || 0, sumAmount: Number(data.sum_amount || 0) } } /** * 库存流水(收发存台账) * params: { productId, warehouseId, docType, batchNo, docNo, dateFrom, dateTo, page, pageSize } */ export async function fetchStockLedger(params = {}) { const data = await request({ url: '/api/erp/stock/ledger', data: { ...pageArgs(params), product_id: params.productId || '', warehouse_id: params.warehouseId || '', doc_type: params.docType || '', batch_no: params.batchNo || '', doc_no: params.docNo || '', ...rangeArgs(params) } }) return { list: (data.list || []).map(row => ({ ...row, create_time_text: dateTimeText(row.create_time), biz_date: dayText(row.create_time) })), total: data.total || 0 } } /** 库存预警四类分组 + 计数 */ export async function fetchStockWarning() { const data = await request({ url: '/api/erp/stock/warning' }) return { low: data.low || [], over: data.over || [], out: data.out || [], expiring: data.expiring || [], counts: data.counts || { low: 0, over: 0, out: 0, expiring: 0 } } } /** 智能补货建议(含在途、近 30 天销量、建议量与金额) */ export async function fetchReplenish() { const data = await request({ url: '/api/erp/stock/replenish' }) return { list: data.list || [], total: data.total || 0 } } /** * 批次台账 * params: { warehouseId, productId, keyword, expireState, onlyPositive, page, pageSize } */ export async function fetchBatches(params = {}) { const data = await request({ url: '/api/erp/stock/batch', data: { ...pageArgs(params), warehouse_id: params.warehouseId || '', product_id: params.productId || '', keyword: params.keyword || '', expire_state: params.expireState || '', only_positive: params.onlyPositive ? 1 : '' } }) return { list: data.list || [], total: data.total || 0 } } // ----------------------------------------------------------------- 档案(只读) /** * 商品列表 * params: { keyword, categoryId, brand, onlyEnabled, page, pageSize } */ export async function fetchProducts(params = {}) { const data = await request({ url: '/api/erp/product/list', data: { ...pageArgs(params), keyword: params.keyword || '', category_id: params.categoryId || '', brand: params.brand || '', only_enabled: params.onlyEnabled ? 1 : '' } }) return { list: data.list || [], total: data.total || 0 } } /** 商品详情(含分仓库存 stocks[]) */ export function getProduct(id) { return request({ url: `/api/erp/product/detail/${id}` }) } /** 商品分类:list 平铺 + tree 递归树 */ export async function fetchCategories() { const data = await request({ url: '/api/erp/product/category/list' }) return { list: data.list || [], tree: data.tree || [] } } /** 仓库列表(带在库品种数与货值,无分页) */ export async function fetchWarehouses(params = {}) { const data = await request({ url: '/api/erp/warehouse/list', data: { keyword: params.keyword || '', only_enabled: params.onlyEnabled ? 1 : '' } }) return (data.list || []).map(row => ({ ...(row.warehouse || {}), stock_kinds: row.stock_kinds || 0, stock_value: Number(row.stock_value || 0) })) } /** * 客商列表(统一出参) * params: { partnerType('supplier'|其它=客户), keyword, onlyEnabled, page, pageSize } */ export async function fetchPartners(params = {}) { const data = await request({ url: '/api/erp/partner/list', data: { ...pageArgs(params), partner_type: params.partnerType || 'customer', keyword: params.keyword || '', only_enabled: params.onlyEnabled ? 1 : '' } }) return { list: data.list || [], total: data.total || 0 } } /** * 客商详情:必须显式带 partner_type,缺省后端按客户查 */ export function getPartner(id, partnerType) { return request({ url: `/api/erp/partner/detail/${id}`, data: { partner_type: partnerType || 'customer' } }) } // ----------------------------------------------------------------- 往来账 / 收付款 /** * 应收应付列表 * params: { billType(1|2), partnerId, sourceDocId, settleState('open'|'part'|'done'), * keyword, dateFrom, dateTo, page, pageSize } * → { list, total, summary: { openReceivable, openPayable } } */ export async function fetchBills(params = {}) { const data = await request({ url: '/api/erp/finance/bill/list', data: { ...pageArgs(params), bill_type: params.billType ?? '', partner_id: params.partnerId || '', source_doc_id: params.sourceDocId || '', partner_type: params.partnerType || '', settle_state: params.settleState || '', keyword: params.keyword || '', ...rangeArgs(params) } }) return { list: (data.list || []).map(normalizeBillRow), total: data.total || 0, summary: { openReceivable: Number((data.summary || {}).open_receivable || 0), openPayable: Number((data.summary || {}).open_payable || 0) } } } /** 往来账详情:核销记录、关联收付款单、来源单据明细 */ export async function getBill(id) { const data = await request({ url: `/api/erp/finance/bill/detail/${id}` }) if (!data) return null return { bill: normalizeBillRow({ bill: data.bill, balance: Number(data.bill?.amount || 0) - Number(data.bill?.settled_amount || 0) }), settles: data.settles || [], funds: Object.keys(data.funds || {}).map(key => data.funds[key]), docItems: data.doc_items || [] } } /** 账龄分析:按客商分桶 + 应收/应付合计 */ export async function fetchAging() { const data = await request({ url: '/api/erp/finance/aging' }) return { list: data.list || [], summary: data.summary || {}, buckets: data.buckets || [] } } /** 资金流水(按日汇总,默认近 30 天) */ export async function fetchCashFlow(params = {}) { const data = await request({ url: '/api/erp/finance/cashflow', data: rangeArgs(params) }) return { list: data.list || [], totalIn: Number(data.total_in || 0), totalOut: Number(data.total_out || 0), net: Number(data.net || 0) } } /** 客商往来汇总(累计/未结,收付双向) */ export async function fetchPartnerSummary(partnerType) { const data = await request({ url: '/api/erp/finance/partner', data: { partner_type: partnerType || '' } }) return data.list || [] } /** * 收付款单列表 * params: { fundType(1..4), status, partnerId, keyword, dateFrom, dateTo, page, pageSize } */ export async function fetchFunds(params = {}) { const data = await request({ url: '/api/erp/finance/fund/list', data: { ...pageArgs(params), fund_type: params.fundType ?? '', status: params.status ?? '', partner_id: params.partnerId || '', keyword: params.keyword || '', ...rangeArgs(params) } }) return { list: (data.list || []).map(normalizeFund), total: data.total || 0 } } /** 收付款单详情(含核销行与被核销的往来账) */ export async function getFund(id) { const data = await request({ url: `/api/erp/finance/fund/detail/${id}` }) if (!data) return null return { fund: normalizeFund(data.fund || {}), settles: data.fund?.settles || [], bills: data.bills || [] } } /** 待核销的往来账(confirm 页面选账用):fundType=2 查应付,其余查应收 */ export async function fetchOpenBills(fundType, partnerId) { const data = await request({ url: '/api/erp/finance/fund/openBills', data: { fund_type: fundType, partner_id: partnerId || '' } }) return (data.list || []).map(row => ({ ...row, balance: Number(row.balance || 0) })) } /** 新建/修改收付款草稿,返回 { id, fundNo } */ export async function saveFund(fund) { const res = await request({ url: '/api/erp/finance/fund/save', method: 'POST', data: { id: fund.id || 0, fund_type: Number(fund.fundType), partner_id: fund.partnerId || 0, amount: Number(fund.amount) || 0, pay_method: fund.payMethod || 'cash', pay_account: fund.payAccount || '', biz_date: fund.bizDate || '', remark: fund.remark || '' } }) return { id: res?.id, fundNo: res?.fund_no || '' } } /** * 确认收付款并核销 * settles 为空 → 后端按到期日 FIFO 自动核销; * settles = [{ partnerBillId, amount }] → 按单核销 */ export function confirmFund(id, settles) { return request({ url: `/api/erp/finance/fund/confirm/${id}`, method: 'POST', data: { settles: (settles || []).map(s => ({ partner_bill_id: s.partnerBillId, amount: Number(s.amount) })) } }) } /** 作废收付款单(已发生核销的不可作废,需做反向冲销) */ export function voidFund(id) { return request({ url: `/api/erp/finance/fund/void/${id}`, method: 'POST', data: {} }) } /** 删除收付款草稿(仅草稿) */ export async function deleteFund(id) { await request({ url: `/api/erp/finance/fund/delete/${id}`, method: 'DELETE' }) return true } // ----------------------------------------------------------------- 报表 /** 采购汇总:dimension = product|supplier|month */ export async function fetchPurchaseSummary(params = {}) { const data = await request({ url: '/api/erp/report/purchase', data: { dimension: params.dimension || 'product', partner_id: params.partnerId || '', ...rangeArgs(params) } }) return normalizeSummary(data) } /** 销售汇总:dimension = product|customer|month */ export async function fetchSalesSummary(params = {}) { const data = await request({ url: '/api/erp/report/sales', data: { dimension: params.dimension || 'product', partner_id: params.partnerId || '', ...rangeArgs(params) } }) return normalizeSummary(data) } /** 热销(order=desc)/滞销(order=asc)排行 */ export async function fetchSalesRank(params = {}) { const data = await request({ url: '/api/erp/report/rank', data: { order: params.order || 'desc', top: params.top || 10, ...rangeArgs(params) } }) return { list: data.list || [], order: data.order || 'desc', range: data.range || [] } } /** 收发存汇总表:期初 = 期末 -(本期入 - 本期出),与库存结存账实一致 */ export async function fetchStockMove(params = {}) { const data = await request({ url: '/api/erp/report/stockmove', data: { ...pageArgs(params), warehouse_id: params.warehouseId || '', keyword: params.keyword || '', ...rangeArgs(params) } }) return { list: data.list || [], total: data.total || 0, summary: data.summary || {} } } /** 毛利分析:收入=销售出库-销售退货,成本取库存流水实际出库成本 */ export async function fetchProfit(params = {}) { const data = await request({ url: '/api/erp/report/profit', data: rangeArgs(params) }) return { list: data.list || [], summary: data.summary || {} } } // ----------------------------------------------------------------- 归一化 /** 单据主表归一;withItems=true 时明细行日期/金额也归一 */ function normalizeDoc(row, withItems) { const out = { ...row, biz_date: dayText(row.biz_date), create_time_text: dateTimeText(row.create_time), audit_time_text: dateTimeText(row.audit_time), total_qty: Number(row.total_qty || 0), total_amount: Number(row.total_amount || 0), discount_amount: Number(row.discount_amount || 0), actual_amount: Number(row.actual_amount || 0) } if (withItems) { out.items = (row.items || []).map(item => ({ ...item, quantity: Number(item.quantity || 0), unit_price: Number(item.unit_price || 0), amount: Number(item.amount || 0), stock_qty: Number(item.stock_qty || 0), production_date: dayText(item.production_date), expire_date: dayText(item.expire_date) })) out.instance = row.instance || null } return out } /** 组装后端要求的单据请求体(明细只传可编辑字段,单价缺省由后端按价格体系取) */ function buildDocPayload(doc) { return { id: doc.id || 0, doc_type: doc.docType, biz_date: doc.bizDate || '', warehouse_id: Number(doc.warehouseId) || 0, to_warehouse_id: Number(doc.toWarehouseId) || 0, partner_id: Number(doc.partnerId) || 0, source_doc_id: Number(doc.sourceDocId) || 0, discount_amount: Number(doc.discountAmount) || 0, remark: doc.remark || '', items: (doc.items || []).map(item => ({ id: item.id || 0, product_id: Number(item.productId || item.product_id) || 0, warehouse_id: Number(item.warehouseId || item.warehouse_id) || 0, batch_no: item.batchNo || item.batch_no || '', quantity: Number(item.quantity) || 0, book_qty: Number(item.bookQty ?? item.book_qty) || 0, ref_item_id: Number(item.refItemId || item.ref_item_id) || 0, unit_price: Number(item.unitPrice ?? item.unit_price) || 0, amount: Number(item.amount) || 0, production_date: item.productionDate || item.production_date || '', expire_date: item.expireDate || item.expire_date || '', remark: item.remark || '', sort_order: Number(item.sortOrder || item.sort_order) || 0 })) } } /** 往来账行:后端返回 { bill, balance, overdue_days, status_label },摊平给列表用 */ function normalizeBillRow(row) { const bill = row.bill || {} return { ...bill, balance: Number(row.balance ?? (Number(bill.amount || 0) - Number(bill.settled_amount || 0))), overdue_days: Number(row.overdue_days || 0), status_label: row.status_label || billStatusLabel(bill.status), bill_date: dayText(bill.bill_date), due_date: dayText(bill.due_date) } } function normalizeFund(row) { return { ...row, amount: Number(row.amount || 0), settled_amount: Number(row.settled_amount || 0), biz_date: dayText(row.biz_date), create_time_text: dateTimeText(row.create_time) } } function normalizeSummary(data) { return { list: (data.list || []).map(row => ({ ...row, qty: Number(row.qty || 0), amount: Number(row.amount || 0) })), dimension: data.dimension || 'product', summary: { qty: Number((data.summary || {}).qty || 0), amount: Number((data.summary || {}).amount || 0) } } }