docs: 定价重设计原型同步(stock-in.js 双模式/详情三列)+ CLAUDE.md 定价口径节 + db-schema/CONTRACT 更新
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
This commit is contained in:
@@ -170,6 +170,7 @@
|
||||
|
||||
<div class="form-foot">
|
||||
<span class="rows-n" id="rowsN">明细 0 行</span>
|
||||
<div class="total" id="profitWrap" style="display:none">合计利润 <b id="grandProfit">¥0.00</b></div>
|
||||
<div class="total">合计金额 <b id="grandTotal">¥0.00</b></div>
|
||||
<div class="foot-act" id="footActions"></div>
|
||||
</div>
|
||||
|
||||
+20
-14
@@ -54,7 +54,7 @@ const MODES = {
|
||||
in:{ title:'入库单', partnerLabel:'供应商', partnerDict:'suppliers', handler:'入库员',
|
||||
cols:['idx','name','series','spec','prodDate','batch','qty','price','sale','amount','act'] },
|
||||
out:{ title:'出库单', partnerLabel:'客户', partnerDict:'customers', handler:'出库员',
|
||||
cols:['idx','name','series','spec','avail','qty','price','sale','amount','act'] },
|
||||
cols:['idx','name','series','spec','avail','qty','price','sale','profit','act'] },
|
||||
};
|
||||
const COLDEF = {
|
||||
idx:{type:'idx',w:38},
|
||||
@@ -65,9 +65,10 @@ const COLDEF = {
|
||||
batch:{type:'text',label:'批次号',req:true,w:108,ph:'必填'},
|
||||
avail:{type:'avail',label:'可用库存',w:92,num:true},
|
||||
qty:{type:'num',label:'数量',req:true,w:78,num:true},
|
||||
price:{type:'num',label:'单价',req:true,w:96,num:true,money:true},
|
||||
sale:{type:'num',label:'出售价格',req:true,w:104,num:true,money:true},
|
||||
amount:{type:'amount',label:'金额',w:104,num:true},
|
||||
price:{type:'num',label:'进价',req:true,w:96,num:true,money:true},
|
||||
sale:{type:'num',label:'售价',req:true,w:104,num:true,money:true},
|
||||
amount:{type:'amount',label:'总进价',w:104,num:true},
|
||||
profit:{type:'profit',label:'利润',w:104,num:true},
|
||||
act:{type:'act',w:62},
|
||||
};
|
||||
// 录单可聚焦字段顺序(出库的 series/spec/avail 只读,不进流)
|
||||
@@ -83,8 +84,12 @@ function newRow(){ return {id:'r'+(++SEQ), name:null,series:null,spec:null,prodD
|
||||
/* ---------------- 工具 ---------------- */
|
||||
function idName(dictKey,id){ if(id==null) return ''; const o=(DICT[dictKey]||[]).find(x=>x.id===id); return o?o.name:''; }
|
||||
function fmt(n){ return '¥'+(Number(n)||0).toLocaleString('zh-CN',{minimumFractionDigits:2,maximumFractionDigits:2}); }
|
||||
function rowAmount(r){ return (parseFloat(r.qty)||0)*(parseFloat(r.price)||0); }
|
||||
function rowAmount(r){ const unit=mode==='out'?(parseFloat(r.sale)||0):(parseFloat(r.price)||0); return (parseFloat(r.qty)||0)*unit; }
|
||||
function grandTotal(){ return state.rows.reduce((s,r)=>s+rowAmount(r),0); }
|
||||
// 出库行利润 =(售价 − 进价)× 数量;售价未填不计
|
||||
function rowProfit(r){ const sale=parseFloat(r.sale)||0; if(sale<=0) return 0; return (sale-(parseFloat(r.price)||0))*(parseFloat(r.qty)||0); }
|
||||
function grandProfit(){ return state.rows.reduce((s,r)=>s+rowProfit(r),0); }
|
||||
function profitHTML(r){ const sale=parseFloat(r.sale)||0; if(sale<=0) return '<span style="color:var(--faint)">—</span>'; const v=rowProfit(r); return `<span style="color:${v<0?'var(--danger)':'var(--success)'}">${fmt(v)}</span>`; }
|
||||
// 入库进价留空/0 = 待定价(暂估),金额显示「待定价」橙色;确认进价后回填
|
||||
function isPending(r){ return mode==='in' && (!r.price || (parseFloat(r.price)||0)<=0); }
|
||||
function amtHTML(r){ return isPending(r) ? '<span class="pending-price">待定价</span>' : fmt(rowAmount(r)); }
|
||||
@@ -198,7 +203,7 @@ function pickCombo(combo,item){
|
||||
r[field]=id; r._err.delete(field);
|
||||
if(field==='name'){ const nm=NAMES.find(n=>n.id===id);
|
||||
if(mode==='in'){ if(!r.series)r.series=nm.dSeries; if(!r.spec)r.spec=nm.dSpec; if(!r.price)r.price=String(nm.cost); if(!r.sale)r.sale=String(nm.sale); }
|
||||
else { r.series=nm.dSeries; r.spec=nm.dSpec; r.price=String(nm.sale); r.sale=String(nm.sale); r.avail=nm.stock; }
|
||||
else { r.series=nm.dSeries; r.spec=nm.dSpec; r.price=String(nm.cost); r.sale=String(nm.sale); r.avail=nm.stock; }
|
||||
}
|
||||
closePop();
|
||||
renderDetail(); updateTotals(); draftSave();
|
||||
@@ -268,20 +273,21 @@ function renderDetailHead(){ const el=document.getElementById('dhAct'); if(!el)
|
||||
function toggleExpand(i){ state.rows[i].expanded=!state.rows[i].expanded; renderDetail(); }
|
||||
function gridHTML(){
|
||||
const cols=MODES[mode].cols; const ro=readonlyMode();
|
||||
let head='<tr>'+cols.map(k=>{ const d=COLDEF[k]; if(k==='idx')return '<th class="gidx">#</th>'; if(k==='act')return '<th class="gact"></th>'; const req=d.req&&!(mode==='in'&&k==='price'); const cls=(d.num?'num ':'')+(req?'req':''); const style=d.w?`style="width:${d.w}px"`:''; const lbl=(mode==='in'&&k==='price')?'单价<small style="color:var(--faint);font-weight:400">(可待定)</small>':(d.label||''); return `<th class="${cls}" ${style}>${lbl}</th>`; }).join('')+'</tr>';
|
||||
let head='<tr>'+cols.map(k=>{ const d=COLDEF[k]; if(k==='idx')return '<th class="gidx">#</th>'; if(k==='act')return '<th class="gact"></th>'; const req=d.req&&!(mode==='in'&&k==='price'); const cls=(d.num?'num ':'')+(req?'req':''); const style=d.w?`style="width:${d.w}px"`:''; const IN_LBL={price:'进价(单瓶)',sale:'参考售价'}; const lbl=(mode==='in'&&k==='price')?'进价(单瓶)<small style="color:var(--faint);font-weight:400">(可待定)</small>':((mode==='in'&&IN_LBL[k])||d.label||''); return `<th class="${cls}" ${style}>${lbl}</th>`; }).join('')+'</tr>';
|
||||
let body=state.rows.map((r,i)=>{
|
||||
const rerr=r._err.size?'error':'';
|
||||
const tds=cols.map(k=>{ const d=COLDEF[k];
|
||||
if(k==='idx') return `<td class="gidx">${i+1}</td>`;
|
||||
if(k==='name') return `<td class="cpad">${comboCellHTML(i,'name',true)}</td>`;
|
||||
if(k==='name'){ const nm=(DICT.names||[]).find(x=>x.id===r.name); const code=(mode==='out'&&nm&&nm.code)?`<div style="color:var(--muted);font-size:var(--fs-body)">${esc(nm.code)}</div>`:''; return `<td class="cpad">${comboCellHTML(i,'name',true)}${code}</td>`; }
|
||||
if(k==='series'||k==='spec'){ if(mode==='out') return `<td class="ro">${esc(idName(d.dict,r[k]))||'<span style="color:var(--faint)">—</span>'}</td>`; return `<td class="cpad">${comboCellHTML(i,k,true)}</td>`; }
|
||||
if(k==='avail'){ const v=r.avail; const c=v==null?'color:var(--faint)':(v<=0?'color:var(--danger)':''); return `<td class="ro num" style="${c}">${v==null?'—':v}</td>`; }
|
||||
if(k==='prodDate'){ const ph=r.prodDate?'':' ph'; return `<td class="cpad"><div class="datefield gci-date ${r._err.has('prodDate')?'error':''} ${ro?'disabled':''}" tabindex="0" data-row="${i}" data-field="prodDate" data-val="${esc(r.prodDate)}"><span class="dval${ph}">${r.prodDate?esc(r.prodDate):'选择日期'}</span><svg viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="17" rx="2"/><path d="M3 9h18M8 2v4M16 2v4"/></svg></div></td>`; }
|
||||
if(k==='batch') return `<td class="cpad"><input class="gci ${r._err.has('batch')?'error':''}" data-row="${i}" data-field="batch" value="${esc(r.batch)}" placeholder="批次号" ${ro?'disabled':''}></td>`;
|
||||
if(k==='qty') return `<td class="cpad num"><input class="gci ${r._err.has('qty')?'error':''}" inputmode="decimal" data-row="${i}" data-field="qty" value="${esc(r.qty)}" ${ro?'disabled':''}></td>`;
|
||||
if(k==='price') return `<td class="cpad num"><input class="gci ${r._err.has('price')?'error':''}" inputmode="decimal" data-row="${i}" data-field="price" value="${esc(r.price)}" placeholder="0.00" ${ro?'disabled':''}></td>`;
|
||||
if(k==='price'){ if(mode==='out') return `<td class="ro num">${r.price?fmt(parseFloat(r.price)||0):'—'}</td>`; return `<td class="cpad num"><input class="gci ${r._err.has('price')?'error':''}" inputmode="decimal" data-row="${i}" data-field="price" value="${esc(r.price)}" placeholder="0.00" ${ro?'disabled':''}></td>`; }
|
||||
if(k==='sale') return `<td class="cpad num"><input class="gci ${r._err.has('sale')?'error':''}" inputmode="decimal" data-row="${i}" data-field="sale" value="${esc(r.sale)}" placeholder="0.00" ${ro?'disabled':''}></td>`;
|
||||
if(k==='amount') return `<td class="amt num" data-amt="${i}">${amtHTML(r)}</td>`;
|
||||
if(k==='profit') return `<td class="amt num" data-profit="${i}">${profitHTML(r)}</td>`;
|
||||
if(k==='act'){ const exp = mode==='in'?`<svg class="gexp ${r.expanded?'on':''}" title="选填项(产地/保质期/储存/介绍)" onclick="toggleExpand(${i})" viewBox="0 0 24 24"><path d="M6 9l6 6 6-6"/></svg>`:''; return `<td class="gact"><div class="gact-in">${exp}<svg title="复制本行" onclick="copyRow(${i})" viewBox="0 0 24 24"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 012-2h10"/></svg><svg class="gdel" title="删除" onclick="delRow(${i})" viewBox="0 0 24 24"><path d="M4 7h16M9 7V5a2 2 0 012-2h2a2 2 0 012 2v2M6 7l1 13a2 2 0 002 2h6a2 2 0 002-2l1-13"/></svg></div></td>`; }
|
||||
return '<td></td>';
|
||||
}).join('');
|
||||
@@ -308,9 +314,9 @@ function cardsHTML(){
|
||||
rows+=mrow('可用库存',false,`<span style="color:${r.avail<=0?'var(--danger)':'var(--muted)'}">${r.avail==null?'—':r.avail}</span>`);
|
||||
}
|
||||
rows+=mrow('数量',true,`<input inputmode="decimal" data-row="${i}" data-field="qty" value="${esc(r.qty)}" class="${r._err.has('qty')?'error':''}" ${ro?'disabled':''}>`);
|
||||
rows+=mrow(mode==='in'?'单价(可待定)':'单价',mode!=='in',`<input inputmode="decimal" data-row="${i}" data-field="price" value="${esc(r.price)}" placeholder="${mode==='in'?'留空=待定价':'0.00'}" class="${r._err.has('price')?'error':''}" ${ro?'disabled':''}>`);
|
||||
rows+=mrow('出售价格',true,`<input inputmode="decimal" data-row="${i}" data-field="sale" value="${esc(r.sale)}" placeholder="0.00" class="${r._err.has('sale')?'error':''}" ${ro?'disabled':''}>`);
|
||||
rows+=`<div class="mrow"><label>金额</label><div class="mv amt" data-amt="${i}">${amtHTML(r)}</div></div>`;
|
||||
rows+=mrow(mode==='in'?'进价·单瓶(可待定)':'进价',false,`<input inputmode="decimal" data-row="${i}" data-field="price" value="${esc(r.price)}" placeholder="${mode==='in'?'留空=待定价':'0.00'}" class="${r._err.has('price')?'error':''}" ${ro?'disabled':''}>`);
|
||||
rows+=mrow(mode==='in'?'参考售价':'售价',true,`<input inputmode="decimal" data-row="${i}" data-field="sale" value="${esc(r.sale)}" placeholder="0.00" class="${r._err.has('sale')?'error':''}" ${ro?'disabled':''}>`);
|
||||
rows+= mode==='out' ? `<div class="mrow"><label>利润</label><div class="mv amt" data-profit="${i}">${profitHTML(r)}</div></div>` : `<div class="mrow"><label>总进价</label><div class="mv amt" data-amt="${i}">${amtHTML(r)}</div></div>`;
|
||||
const act=ro?'':`<div class="mact"><svg title="复制" onclick="copyRow(${i})" viewBox="0 0 24 24"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 012-2h10"/></svg><svg class="gdel" title="删除" onclick="delRow(${i})" viewBox="0 0 24 24"><path d="M4 7h16M9 7V5a2 2 0 012-2h2a2 2 0 012 2v2M6 7l1 13a2 2 0 002 2h6a2 2 0 002-2l1-13"/></svg></div>`;
|
||||
return `<div class="mcard ${rerr}"><div class="mcard-head"><span class="mt">商品 ${i+1}</span>${sub?`<span class="msub">${esc(sub)}</span>`:''}${act}</div><div class="mcard-body">${rows}</div></div>`;
|
||||
}).join('');
|
||||
@@ -320,8 +326,9 @@ function cardsHTML(){
|
||||
function mrow(label,req,inner){ return `<div class="mrow"><label class="${req?'req':''}">${label}</label><div class="mv">${inner}</div></div>`; }
|
||||
|
||||
function updateTotals(){
|
||||
state.rows.forEach((r,i)=>{ const el=document.querySelector(`[data-amt="${i}"]`); if(el) el.innerHTML=amtHTML(r); });
|
||||
state.rows.forEach((r,i)=>{ const el=document.querySelector(`[data-amt="${i}"]`); if(el) el.innerHTML=amtHTML(r); const pl=document.querySelector(`[data-profit="${i}"]`); if(pl) pl.innerHTML=profitHTML(r); });
|
||||
document.getElementById('grandTotal').textContent=fmt(grandTotal());
|
||||
const pw=document.getElementById('profitWrap'); if(pw){ pw.style.display=mode==='out'?'':'none'; const gp=document.getElementById('grandProfit'); if(gp){ const v=grandProfit(); gp.textContent=fmt(v); gp.style.color=v<0?'var(--danger)':'var(--success)'; } }
|
||||
document.getElementById('rowsN').textContent='明细 '+state.rows.length+' 行';
|
||||
}
|
||||
|
||||
@@ -356,7 +363,6 @@ function validate(asDraft){
|
||||
} else {
|
||||
if(r.avail!=null && (parseFloat(r.qty)||0)>r.avail){ r._err.add('qty'); flag(`第 ${i+1} 行数量超出可用库存(${r.avail})`,()=>focusCell(i,'qty')); }
|
||||
}
|
||||
if(mode!=='in' && (!r.price || (parseFloat(r.price)||0)<=0)){ r._err.add('price'); flag(`第 ${i+1} 行请填写单价`,()=>focusCell(i,'price')); }
|
||||
if(!r.sale || (parseFloat(r.sale)||0)<=0){ r._err.add('sale'); flag(`第 ${i+1} 行请填写出售价格`,()=>focusCell(i,'sale')); }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -295,11 +295,12 @@ function auditBtns(o,idx){
|
||||
return '';
|
||||
}
|
||||
function openDetail(idx){ const o=ORDERS[idx]; document.getElementById('dTitle').textContent=o.no;
|
||||
let lh=`<div class="lines"><div class="lh" style="grid-template-columns:1fr 124px 40px 64px 78px"><span>商品 · 编码</span><span>系列 / 规格</span><span style="text-align:right">数量</span><span style="text-align:right">单价</span><span style="text-align:right">金额</span></div>`;
|
||||
o.lines.forEach(l=>{ const pi=PINFO[l.name]||{}; const pend=(l.price||0)<=0; const priceC=pend?'<span class="pending-price">待定价</span>':money(l.price); const amtC=pend?'<span class="pending-price">待定价</span>':money(l.qty*l.price); lh+=`<div class="lr" style="grid-template-columns:1fr 124px 40px 64px 78px;align-items:start"><span class="lcell"><span class="ln">${l.name}</span><span class="lcode">${pi.code||'—'}</span></span><span class="lcell"><span class="lser">${pi.series||'—'}</span><span class="lspec">${pi.spec||''}</span></span><span style="text-align:right;font-family:var(--font-mono)">${l.qty}</span><span style="text-align:right;font-family:var(--font-mono)">${priceC}</span><span class="amt">${amtC}</span></div>`; }); lh+='</div>';
|
||||
const GRID='1fr 108px 36px 60px 60px 64px';
|
||||
let lh=`<div class="lines"><div class="lh" style="grid-template-columns:${GRID}"><span>商品 · 编码</span><span>系列 / 规格</span><span style="text-align:right">数量</span><span style="text-align:right">成本价</span><span style="text-align:right">售价</span><span style="text-align:right">利润</span></div>`;
|
||||
o.lines.forEach(l=>{ const pi=PINFO[l.name]||{}; const cost=l.cost!=null?l.cost:Math.round((l.price||0)*0.85); const pend=(l.price||0)<=0; const saleC=pend?'<span class="pending-price">待定价</span>':money(l.price); const profit=(l.price-cost)*l.qty; const profitC=pend?'<span class="pending-price">待定价</span>':`<span style="color:${profit<0?'var(--danger)':'var(--success)'}">${money(profit)}</span>`; lh+=`<div class="lr" style="grid-template-columns:${GRID};align-items:start"><span class="lcell"><span class="ln">${l.name}</span><span class="lcode">${pi.code||'—'}</span></span><span class="lcell"><span class="lser">${pi.series||'—'}</span><span class="lspec">${pi.spec||''}</span></span><span style="text-align:right;font-family:var(--font-mono)">${l.qty}</span><span style="text-align:right;font-family:var(--font-mono)">${money(cost)}</span><span style="text-align:right;font-family:var(--font-mono)">${saleC}</span><span class="amt">${profitC}</span></div>`; }); lh+='</div>';
|
||||
const pendCount=o.lines.filter(l=>(l.price||0)<=0).length;
|
||||
const auditor=o.auditor?o.auditor:'<span style="color:var(--faint)">—</span>';
|
||||
document.getElementById('drawerBody').innerHTML=`<div class="drow"><span>单据日期</span><b>${o.date}</b></div><div class="drow"><span>${PARTY_LABEL}</span><b>${o.party}</b></div><div class="drow"><span>仓库</span><b>${o.wh}</b></div><div class="drow"><span>商品数</span><b>${o.count}</b></div><div class="drow"><span>出库员</span><b>${o.op}</b></div><div class="drow"><span>审核员</span><b>${auditor}</b></div><div class="drow"><span>状态</span><b><span class="badge b-${o.status}">${o.status}</span>${o.ret?` <span class="badge b-${o.ret}">${o.ret}</span>`:''}</b></div><label style="display:block;font-size:var(--fs-sm);color:var(--muted);margin:16px 0 8px;">出库明细</label>${lh}<div class="ltotal">合计金额 <b>${money(o.amount)}</b></div>`+
|
||||
document.getElementById('drawerBody').innerHTML=`<div class="drow"><span>单据日期</span><b>${o.date}</b></div><div class="drow"><span>${PARTY_LABEL}</span><b>${o.party}</b></div><div class="drow"><span>仓库</span><b>${o.wh}</b></div><div class="drow"><span>商品数</span><b>${o.count}</b></div><div class="drow"><span>出库员</span><b>${o.op}</b></div><div class="drow"><span>审核员</span><b>${auditor}</b></div><div class="drow"><span>状态</span><b><span class="badge b-${o.status}">${o.status}</span>${o.ret?` <span class="badge b-${o.ret}">${o.ret}</span>`:''}</b></div><label style="display:block;font-size:var(--fs-sm);color:var(--muted);margin:16px 0 8px;">出库明细</label>${lh}<div class="ltotal">合计金额 <b>${money(o.amount)}</b></div><div class="ltotal" style="padding-top:4px">合计利润 <b style="color:var(--success)">${money(o.lines.reduce((s,l)=>{const c=l.cost!=null?l.cost:Math.round((l.price||0)*0.85);return s+((l.price||0)>0?((l.price-c)*l.qty):0);},0))}</b></div>`+
|
||||
`<div class="dactions"><div class="dact-group"><span class="dact-label">单据审核</span><div class="dact-row">${auditBtns(o,idx)}</div></div>${(o.status==='已审核'&&pendCount>0)?`<div class="dact-group"><span class="dact-label">售价 · ${pendCount} 项待定价</span><div class="dact-row"><div class="btn primary sm" onclick="openConfirmSale(${idx})"><svg viewBox="0 0 24 24"><use href="#i-edit"/></svg>确认售价</div></div></div>`:''}<div class="dact-group"><span class="dact-label">打印</span><div class="dact-row"><div class="btn ghost sm" onclick="toast('打印标签(原型)')"><svg viewBox="0 0 24 24"><use href="#i-ic49"/></svg>打印标签</div><div class="btn ghost sm" onclick="toast('打印单据(原型)')"><svg viewBox="0 0 24 24"><use href="#i-ic50"/></svg>打印单据</div></div></div></div>`;
|
||||
document.getElementById('drawer').classList.add('show'); document.getElementById('drawerMask').classList.add('show'); }
|
||||
function closeDrawer(){ document.getElementById('drawer').classList.remove('show'); document.getElementById('drawerMask').classList.remove('show'); }
|
||||
|
||||
@@ -212,6 +212,12 @@ cd client && flutter test
|
||||
- 基础数据字典(品牌/型号/版本 + 产地/保质期/储存/介绍)在「基础数据」页管理,入库选文本后据此建 product。
|
||||
- 库存(`inventories`)/出入库明细 = 指向 product 的引用 + **快照列**(`product_code/name/series/spec/...`,导入/审核时拷贝,作历史保真+显示兜底)。显示优先 product、回退快照(`COALESCE(p.*, 快照)` / 前端 `lineOrProduct`)。**快照列保留,勿擅自删**。
|
||||
|
||||
### 定价字段口径(2026-07 消歧后,唯一口径)
|
||||
- 明细两侧价格分列:`cost_price`(成本/进价单价)+ `cost_amount`(成本小计);出库另有 `sale_price`(售价)+ `sale_amount`(售价小计,待定价=0)。**旧列 unit_price/total_price/total_amount 已弃用**(值已回填新列,观察一版后 DROP),新代码禁止读写旧列。
|
||||
- 单据合计:入库 `cost_total`(应付=Σ总进价);出库 `sale_total`(应收=Σ售价小计)+ `profit_total`(总利润=Σ(售价>0?(售价-成本)×数量:0),建单落库,确认售价/确认进价联动重算,重算函数 `recalcStockOutProfit`)。
|
||||
- **成本与利润仅管理员可见**:出库 List/Get 对 operator/readonly 服务端抹零(`stripStockOutCost`),前端同时隐藏对应列——新增出库相关展示时两侧都要遵守。
|
||||
- 财务口径不变:应收/应付、退单冲账均按上述合计列;出库退单展示与冲账同为售价口径。
|
||||
|
||||
### 库存变更
|
||||
- 入库/出库审核通过时,必须在同一事务中同时完成:
|
||||
1. 更新 `inventories` 表数量
|
||||
|
||||
@@ -150,6 +150,13 @@ AppTokens 目前**仅颜色**。原型还驱动:
|
||||
> - **注册已知差异(用户拍板 2026-07-03:先不加)**:无「门店编号(留空自动分配)」「授权兑换券」两字段——后端 RegisterInput 暂不支持;门店地址必填(后端 binding:required,原型选填);密码下限 6 位(后端 min=6,原型文案 8 位)。表单因此少两行 → **fidelity 暂撤册**(screens.mjs 有注释存根),golden ×3 锁回归;后端补 shop_code/voucher 后恢复入闸。
|
||||
> - 校验口径 = 原型:提交时逐项 toast(DsToast),无内联错误文案,38 高盒式字段不抖动。
|
||||
>
|
||||
> **出入库定价重设计(2026-07-03 用户拍板,plan 批准)**:
|
||||
> - 出库详情明细:单价/金额 两列 → **成本价/售价/利润** 三列(管理员);operator 见 售价/小计。合计区双行:合计金额(总售价)+ 合计利润(管理员)。
|
||||
> - 出库建单:金额列(=售价×数量,qty=1 与售价重复)→ **利润**列(实时,负数 danger);商品名称下带编码(字体同系列列);底栏加合计利润;进价/利润列仅管理员。
|
||||
> - 入库建单列头:进价 → **进价(单瓶)**、售价 → **参考售价**、金额 → **总进价**。
|
||||
> - 原型已同步(stock-in.js 双模式 + stock-out-list.html 详情抽屉),与真实页一致。
|
||||
> - 底层字段消歧:unit_price/total_price/total_amount → cost_price/cost_amount/cost_total(入)/sale_amount/sale_total/profit_total(出),见 CLAUDE.md「定价字段口径」。
|
||||
>
|
||||
> **全局对话框统一(2026-07-03 用户要求遍历)**:
|
||||
> - 主题层钉真相源:`DialogTheme`(surface/r-xl/边框/标题 fs-h2·700)、全局 `InputDecorationTheme`(.input 盒式,禁下划线兜底)、Elevated/FilledButton 主题(.btn.primary 规格)。
|
||||
> - 全部 AlertDialog 动作行换 `DsButton`(取消=ghost / 确认=primary / 删除·拒绝=danger / 通过=success / 撤回·结清=accent,`DsBtnVariant` 扩 success/accent);表单字段改 `DsField`(label 在上)+ 盒式输入(添加设备等)。
|
||||
|
||||
+9
-7
@@ -432,7 +432,7 @@
|
||||
<tr><td class="mono">reviewer_id</td><td class="mono ty">BIGINT UNSIGNED</td><td class="ctr">是</td><td class="mono df">NULL</td><td>审核人</td></tr>
|
||||
<tr><td class="mono">status</td><td class="mono ty">ENUM('draft','pending','approved','rejected')</td><td class="ctr">否</td><td class="mono df">draft</td><td></td></tr>
|
||||
<tr><td class="mono">order_date</td><td class="mono ty">DATE</td><td class="ctr">否</td><td class="mono df">—</td><td></td></tr>
|
||||
<tr><td class="mono">total_amount</td><td class="mono ty">DECIMAL(16,2)</td><td class="ctr">否</td><td class="mono df">0</td><td></td></tr>
|
||||
<tr><td class="mono">cost_total</td><td class="mono ty">DECIMAL(16,2)</td><td class="ctr">否</td><td class="mono df">0</td><td>应付合计=Σ总进价(2026-07 消歧,旧列 total_amount 弃用待删)</td></tr>
|
||||
<tr><td class="mono">reviewed_at</td><td class="mono ty">DATETIME</td><td class="ctr">是</td><td class="mono df">NULL</td><td></td></tr>
|
||||
<tr><td class="mono">custom_fields</td><td class="mono ty">JSON</td><td class="ctr">是</td><td class="mono df">NULL</td><td></td></tr>
|
||||
<tr><td class="mono">remark</td><td class="mono ty">VARCHAR(500)</td><td class="ctr">是</td><td class="mono df">NULL</td><td></td></tr>
|
||||
@@ -456,8 +456,8 @@
|
||||
<tr><td class="mono">series</td><td class="mono ty">VARCHAR(100)</td><td class="ctr">是</td><td class="mono df">NULL</td><td>系列(历史导入快照)</td></tr>
|
||||
<tr><td class="mono">spec</td><td class="mono ty">VARCHAR(100)</td><td class="ctr">是</td><td class="mono df">NULL</td><td>规格(历史导入快照)</td></tr>
|
||||
<tr><td class="mono">quantity</td><td class="mono ty">DECIMAL(12,3)</td><td class="ctr">否</td><td class="mono df">—</td><td>数量</td></tr>
|
||||
<tr><td class="mono">unit_price</td><td class="mono ty">DECIMAL(16,2)</td><td class="ctr">否</td><td class="mono df">0</td><td>单价</td></tr>
|
||||
<tr><td class="mono">total_price</td><td class="mono ty">DECIMAL(16,2)</td><td class="ctr">否</td><td class="mono df">0</td><td></td></tr>
|
||||
<tr><td class="mono">cost_price</td><td class="mono ty">DECIMAL(16,2)</td><td class="ctr">否</td><td class="mono df">0</td><td>进价·单瓶(旧列 unit_price 弃用待删)</td></tr>
|
||||
<tr><td class="mono">cost_amount</td><td class="mono ty">DECIMAL(16,2)</td><td class="ctr">否</td><td class="mono df">0</td><td>总进价=quantity×cost_price(旧列 total_price 弃用待删)</td></tr>
|
||||
<tr><td class="mono">batch_no</td><td class="mono ty">VARCHAR(50)</td><td class="ctr">是</td><td class="mono df">NULL</td><td>批次号</td></tr>
|
||||
<tr><td class="mono">production_date</td><td class="mono ty">DATE</td><td class="ctr">是</td><td class="mono df">NULL</td><td>生产日期</td></tr>
|
||||
<tr><td class="mono">expire_date</td><td class="mono ty">DATE</td><td class="ctr">是</td><td class="mono df">NULL</td><td>有效期</td></tr>
|
||||
@@ -482,7 +482,8 @@
|
||||
<tr><td class="mono">reviewer_id</td><td class="mono ty">BIGINT UNSIGNED</td><td class="ctr">是</td><td class="mono df">NULL</td><td></td></tr>
|
||||
<tr><td class="mono">status</td><td class="mono ty">ENUM('draft','pending','approved','rejected')</td><td class="ctr">否</td><td class="mono df">draft</td><td></td></tr>
|
||||
<tr><td class="mono">order_date</td><td class="mono ty">DATE</td><td class="ctr">否</td><td class="mono df">—</td><td></td></tr>
|
||||
<tr><td class="mono">total_amount</td><td class="mono ty">DECIMAL(16,2)</td><td class="ctr">否</td><td class="mono df">0</td><td></td></tr>
|
||||
<tr><td class="mono">sale_total</td><td class="mono ty">DECIMAL(16,2)</td><td class="ctr">否</td><td class="mono df">0</td><td>应收合计=Σ售价小计(旧列 total_amount 弃用待删)</td></tr>
|
||||
<tr><td class="mono">profit_total</td><td class="mono ty">DECIMAL(16,2)</td><td class="ctr">否</td><td class="mono df">0</td><td>总利润=Σ(售价>0?(售价-成本)×数量:0),建单落库、确认售价/进价联动重算;仅管理员可见(operator 响应抹零)</td></tr>
|
||||
<tr><td class="mono">reviewed_at</td><td class="mono ty">DATETIME</td><td class="ctr">是</td><td class="mono df">NULL</td><td></td></tr>
|
||||
<tr><td class="mono">custom_fields</td><td class="mono ty">JSON</td><td class="ctr">是</td><td class="mono df">NULL</td><td></td></tr>
|
||||
<tr><td class="mono">remark</td><td class="mono ty">VARCHAR(500)</td><td class="ctr">是</td><td class="mono df">NULL</td><td></td></tr>
|
||||
@@ -494,7 +495,7 @@
|
||||
</div>
|
||||
<div class="card" id="stock_out_items">
|
||||
<h3><code class="tname">stock_out_items</code> <span class="tcomment">出库单明细</span></h3>
|
||||
<div class="callout danger"><b>当前问题相关:</b>出库行快照 <code>batch_no/production_date</code> 在 App 建单时常为空(选货按 product 聚合、未带批次)。打印回退到 <code>products</code> 主记录——若该 product 也没填,就显示空白,但库存页仍能显示(取自入库明细)。<code>unit_price</code>=成本、<code>sale_price</code>=售价。</div>
|
||||
<div class="callout danger"><b>当前问题相关:</b>出库行快照 <code>batch_no/production_date</code> 在 App 建单时常为空(选货按 product 聚合、未带批次)。打印回退到 <code>products</code> 主记录——若该 product 也没填,就显示空白,但库存页仍能显示(取自入库明细)。<code>cost_price</code>=成本、<code>sale_price</code>=售价(2026-07 已消歧改名)。</div>
|
||||
<table>
|
||||
<thead><tr><th style="width:23%">列名</th><th style="width:20%">类型</th><th style="width:7%">可空</th><th style="width:16%">默认</th><th>说明</th></tr></thead>
|
||||
<tbody><tr><td class="mono"><b>id</b> <span class="tag pk">PK</span></td><td class="mono ty">BIGINT UNSIGNED</td><td class="ctr">否</td><td class="mono df">AUTO_INC</td><td></td></tr>
|
||||
@@ -506,9 +507,10 @@
|
||||
<tr><td class="mono">series</td><td class="mono ty">VARCHAR(100)</td><td class="ctr">是</td><td class="mono df">NULL</td><td>系列(历史导入快照)</td></tr>
|
||||
<tr><td class="mono">spec</td><td class="mono ty">VARCHAR(100)</td><td class="ctr">是</td><td class="mono df">NULL</td><td>规格(历史导入快照)</td></tr>
|
||||
<tr><td class="mono">quantity</td><td class="mono ty">DECIMAL(12,3)</td><td class="ctr">否</td><td class="mono df">—</td><td></td></tr>
|
||||
<tr><td class="mono">unit_price</td><td class="mono ty">DECIMAL(16,2)</td><td class="ctr">否</td><td class="mono df">0</td><td>成本单价(入库成本快照)</td></tr>
|
||||
<tr><td class="mono">cost_price</td><td class="mono ty">DECIMAL(16,2)</td><td class="ctr">否</td><td class="mono df">0</td><td>成本单价·入库成本快照(旧列 unit_price 弃用待删;仅管理员可见)</td></tr>
|
||||
<tr><td class="mono">sale_price</td><td class="mono ty">DECIMAL(16,2)</td><td class="ctr">否</td><td class="mono df">0</td><td>售价(出库实际销售单价,应收按售价×数量)</td></tr>
|
||||
<tr><td class="mono">total_price</td><td class="mono ty">DECIMAL(16,2)</td><td class="ctr">否</td><td class="mono df">0</td><td>成本小计(unit_price×quantity)</td></tr>
|
||||
<tr><td class="mono">cost_amount</td><td class="mono ty">DECIMAL(16,2)</td><td class="ctr">否</td><td class="mono df">0</td><td>成本小计=quantity×cost_price(旧列弃用待删;仅管理员可见)</td></tr>
|
||||
<tr><td class="mono">sale_amount</td><td class="mono ty">DECIMAL(16,2)</td><td class="ctr">否</td><td class="mono df">0</td><td>售价小计=quantity×sale_price(待定价=0)</td></tr>
|
||||
<tr><td class="mono">batch_no</td><td class="mono ty">VARCHAR(50)</td><td class="ctr">是</td><td class="mono df">NULL</td><td>批次号</td></tr>
|
||||
<tr><td class="mono">production_date</td><td class="mono ty">DATE</td><td class="ctr">是</td><td class="mono df">NULL</td><td>生产日期</td></tr>
|
||||
<tr><td class="mono">custom_fields</td><td class="mono ty">JSON</td><td class="ctr">是</td><td class="mono df">NULL</td><td></td></tr>
|
||||
|
||||
Reference in New Issue
Block a user