chore: sync remaining local plan, form mapping and graphify cache

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-07-28 10:05:03 +08:00
1 parent 44a57b86ca
commit dd4aa734ee
28 files changed
+3215 -5

No files matched your search

@@ -0,0 +1,586 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>运输计划 · 多选合打单据 Demo</title>
<style>
:root {
--primary: #10b981;
--primary-dark: #059669;
--primary-bg: #ecfdf5;
--text: #0f172a;
--muted: #64748b;
--border: #e2e8f0;
--warn: #d97706;
--danger: #dc2626;
--card: #fff;
--bg: #f1f5f9;
}
* { box-sizing: border-box; }
body {
margin: 0;
font: 14px/1.5 Inter, "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
color: var(--text);
background: var(--bg);
}
.wrap { max-width: 1180px; margin: 0 auto; padding: 24px; }
h1 { font-size: 20px; margin: 0 0 6px; }
.sub { color: var(--muted); margin-bottom: 20px; }
.panel {
background: var(--card);
border: 1px solid var(--border);
border-radius: 10px;
padding: 16px 20px 20px;
margin-bottom: 16px;
}
.panel h2 {
margin: 0 0 12px;
font-size: 15px;
display: flex;
align-items: center;
gap: 8px;
}
.panel h2::before {
content: "";
width: 3px;
height: 14px;
background: var(--primary);
border-radius: 2px;
}
.rules {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
margin-bottom: 8px;
}
.rule {
border: 1px solid var(--border);
border-radius: 8px;
padding: 10px 12px;
background: #fafafa;
}
.rule strong { display: block; margin-bottom: 4px; }
.rule.ok { border-color: #a7f3d0; background: var(--primary-bg); }
.rule.loose { border-color: #fde68a; background: #fffbeb; }
.toolbar {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid var(--border);
}
.selected-tip { color: var(--muted); min-width: 72px; }
.btn {
height: 36px;
padding: 0 14px;
border-radius: 8px;
border: 1px solid var(--border);
background: #fff;
cursor: pointer;
font-size: 14px;
display: inline-flex;
align-items: center;
gap: 6px;
}
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn-main {
background: var(--primary);
border-color: var(--primary);
color: #fff;
}
.dropdown { position: relative; }
.menu {
display: none;
position: absolute;
top: 40px;
left: 0;
z-index: 20;
min-width: 180px;
background: #fff;
border: 1px solid var(--border);
border-radius: 8px;
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
padding: 6px;
}
.menu.open { display: block; }
.menu button {
width: 100%;
text-align: left;
border: 0;
background: transparent;
padding: 8px 10px;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
}
.menu button:hover:not(:disabled) { background: var(--primary-bg); color: var(--primary-dark); }
.menu button:disabled { color: #94a3b8; cursor: not-allowed; }
.menu .hint {
font-size: 11px;
color: var(--muted);
padding: 0 10px 6px;
line-height: 1.35;
}
table.list {
width: 100%;
border-collapse: collapse;
}
table.list th, table.list td {
padding: 10px 8px;
border-bottom: 1px solid var(--border);
text-align: left;
font-size: 13px;
}
table.list th { color: var(--muted); font-weight: 600; background: #f8fafc; }
table.list tr:hover td { background: #f8fafc; }
table.list tr.picked td { background: var(--primary-bg); }
.tag {
display: inline-block;
padding: 1px 8px;
border-radius: 999px;
font-size: 12px;
background: #e2e8f0;
color: #334155;
}
.tag.in { background: #d1fae5; color: #065f46; }
.tag.out { background: #dbeafe; color: #1e40af; }
.msg {
margin-top: 12px;
padding: 10px 12px;
border-radius: 8px;
font-size: 13px;
display: none;
}
.msg.show { display: block; }
.msg.err { background: #fef2f2; color: var(--danger); border: 1px solid #fecaca; }
.msg.warn { background: #fffbeb; color: #92400e; border: 1px solid #fde68a; }
.msg.ok { background: var(--primary-bg); color: #065f46; border: 1px solid #a7f3d0; }
.grid2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
@media (max-width: 900px) {
.rules, .grid2 { grid-template-columns: 1fr; }
}
.slip {
border: 1px solid #cbd5e1;
border-radius: 8px;
padding: 14px;
background: #fff;
min-height: 280px;
}
.slip h3 {
margin: 0 0 10px;
text-align: center;
font-size: 16px;
letter-spacing: 2px;
}
.slip .meta {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px 12px;
margin-bottom: 12px;
font-size: 12px;
}
.slip .meta span { color: var(--muted); }
.slip table {
width: 100%;
border-collapse: collapse;
font-size: 12px;
}
.slip th, .slip td {
border: 1px solid #94a3b8;
padding: 6px 4px;
text-align: center;
}
.slip th { background: #f1f5f9; }
.empty {
color: var(--muted);
text-align: center;
padding: 48px 12px;
}
.legend { font-size: 12px; color: var(--muted); margin-top: 8px; }
code { background: #f1f5f9; padding: 1px 5px; border-radius: 4px; font-size: 12px; }
</style>
</head>
<body>
<div class="wrap">
<h1>运输计划 · 多选合打单据 Demo</h1>
<p class="sub">
勾选多条计划 →「打印单据」→ 按规则校验 → 右侧预览「产品合并到一张单据」。
本页仅演示交互与合打规则,非最终生产版式。
</p>
<section class="panel">
<h2>合打规则(客户确认稿)</h2>
<div class="rules">
<div class="rule ok">
<strong>入库单</strong>
勾选计划的<strong>客户名称一致</strong>且<strong>计划日期一致</strong>才可合打;产品行合并到一张入库单。
</div>
<div class="rule ok">
<strong>送(提)货单</strong>
同入库单:客户名称 + 计划日期均须一致。
</div>
<div class="rule loose">
<strong>分装单</strong>
<strong>不校验</strong>客户名称、计划日期;勾选即可合打产品到一张分装单。
</div>
</div>
<p class="legend">
演示数据已覆盖:同客户同日可合打、跨客户/跨日拦截、分装单放行。另按现有能力:入库类计划只能打入库单,出库类只能打送(提)货单/分装单。
</p>
</section>
<div class="grid2">
<section class="panel">
<h2>运输计划列表(多选)</h2>
<div class="toolbar">
<span class="selected-tip" id="selCount">已选 0 项</span>
<div class="dropdown">
<button class="btn" id="printBtn" type="button" disabled>
🖨 打印单据 ▾
</button>
<div class="menu" id="printMenu">
<div class="hint" id="menuHint">请先勾选计划</div>
<button type="button" data-cmd="inbound" disabled>入库单</button>
<button type="button" data-cmd="delivery" disabled>送(提)货单</button>
<button type="button" data-cmd="subpack" disabled>分装单</button>
</div>
</div>
<button class="btn" type="button" id="clearBtn">清空选择</button>
</div>
<table class="list">
<thead>
<tr>
<th style="width:36px"><input type="checkbox" id="checkAll" title="全选当前页" /></th>
<th>计划号</th>
<th>计划日期</th>
<th>客户名称</th>
<th>类型</th>
<th>产品摘要</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
<div class="msg" id="toast"></div>
</section>
<section class="panel">
<h2>合打预览</h2>
<div class="slip" id="preview">
<div class="empty">勾选计划并选择打印类型后,在此预览合并产品明细</div>
</div>
</section>
</div>
</div>
<script>
/** @typedef {{ id: string, planNo: string, planDate: string, customerName: string, planType: 'inbound'|'outbound'|'direct', products: {name:string, model:string, qty:number, unit:string}[] }} Plan */
/** @type {Plan[]} */
const PLANS = [
{
id: '1', planNo: 'PO20260727001', planDate: '2026-07-27', customerName: '安徽云栋化工有限公司',
planType: 'inbound',
products: [
{ name: '亚硫酸钠', model: '液体', qty: 12, unit: '吨' },
{ name: '试剂硝酸', model: 'AR', qty: 2, unit: '吨' }
]
},
{
id: '2', planNo: 'PO20260727002', planDate: '2026-07-27', customerName: '安徽云栋化工有限公司',
planType: 'inbound',
products: [
{ name: '次氯酸钠', model: '固体', qty: 8, unit: '吨' }
]
},
{
id: '3', planNo: 'PO20260727003', planDate: '2026-07-27', customerName: '合肥市皖和化工有限公司',
planType: 'inbound',
products: [
{ name: '98% 工业硫酸', model: 'AR', qty: 20, unit: '吨' }
]
},
{
id: '4', planNo: 'PO20260728001', planDate: '2026-07-28', customerName: '安徽云栋化工有限公司',
planType: 'inbound',
products: [
{ name: '试剂氨水', model: 'AR', qty: 5, unit: '吨' }
]
},
{
id: '5', planNo: 'PO20260727011', planDate: '2026-07-27', customerName: '中国长江实业',
planType: 'outbound',
products: [
{ name: 'TEST液碱', model: '31%', qty: 30, unit: '吨' }
]
},
{
id: '6', planNo: 'PO20260727012', planDate: '2026-07-27', customerName: '中国长江实业',
planType: 'outbound',
products: [
{ name: 'TEST盐酸', model: '30%', qty: 15, unit: '吨' },
{ name: 'TEST液碱', model: '31%', qty: 10, unit: '吨' }
]
},
{
id: '7', planNo: 'PO20260729001', planDate: '2026-07-29', customerName: '中国长江实业',
planType: 'outbound',
products: [
{ name: 'TEST液碱', model: '31%', qty: 6, unit: '吨' }
]
}
]
const selected = new Set()
let menuOpen = false
function typeLabel(t) {
return t === 'inbound' ? '入库单' : t === 'outbound' ? '出库单' : '直销单'
}
function typeClass(t) {
return t === 'inbound' ? 'in' : t === 'outbound' ? 'out' : ''
}
function printableCommands(planType) {
if (planType === 'inbound') return ['inbound']
if (planType === 'outbound') return ['delivery', 'subpack']
return []
}
function selectedPlans() {
return PLANS.filter(p => selected.has(p.id))
}
function sameCustomerAndDate(plans) {
if (!plans.length) return false
const c = plans[0].customerName
const d = plans[0].planDate
return plans.every(p => p.customerName === c && p.planDate === d)
}
function canMerge(cmd, plans) {
if (plans.length < 1) return { ok: false, reason: '请至少勾选 1 条运输计划' }
if (plans.length === 1) {
const allow = printableCommands(plans[0].planType)
if (!allow.includes(cmd)) {
return { ok: false, reason: `当前单据类型(${typeLabel(plans[0].planType)})不支持打印该单据` }
}
return { ok: true, reason: '' }
}
// multi
if (cmd === 'subpack') {
const bad = plans.filter(p => !printableCommands(p.planType).includes('subpack'))
if (bad.length) {
return { ok: false, reason: `分装单仅支持出库类计划;以下不可打:${bad.map(p => p.planNo).join('、')}` }
}
return { ok: true, reason: '' }
}
if (cmd === 'inbound') {
const bad = plans.filter(p => p.planType !== 'inbound')
if (bad.length) {
return { ok: false, reason: `入库单仅支持入库类计划;冲突:${bad.map(p => p.planNo).join('、')}` }
}
if (!sameCustomerAndDate(plans)) {
return { ok: false, reason: '合打入库单要求:客户名称一致 且 计划日期一致' }
}
return { ok: true, reason: '' }
}
if (cmd === 'delivery') {
const bad = plans.filter(p => p.planType !== 'outbound')
if (bad.length) {
return { ok: false, reason: `送(提)货单仅支持出库类计划;冲突:${bad.map(p => p.planNo).join('、')}` }
}
if (!sameCustomerAndDate(plans)) {
return { ok: false, reason: '合打送(提)货单要求:客户名称一致 且 计划日期一致' }
}
return { ok: true, reason: '' }
}
return { ok: false, reason: '未知单据类型' }
}
function renderList() {
const tb = document.getElementById('tbody')
tb.innerHTML = PLANS.map(p => {
const checked = selected.has(p.id) ? 'checked' : ''
const picked = selected.has(p.id) ? 'picked' : ''
const prod = p.products.map(x => `${x.name}×${x.qty}`).join(',')
return `<tr class="${picked}" data-id="${p.id}">
<td><input type="checkbox" class="row-check" data-id="${p.id}" ${checked} /></td>
<td>${p.planNo}</td>
<td>${p.planDate}</td>
<td>${p.customerName}</td>
<td><span class="tag ${typeClass(p.planType)}">${typeLabel(p.planType)}</span></td>
<td>${prod}</td>
</tr>`
}).join('')
document.getElementById('selCount').textContent = `已选 ${selected.size} 项`
document.getElementById('printBtn').disabled = selected.size === 0
document.getElementById('checkAll').checked = selected.size === PLANS.length && PLANS.length > 0
updateMenuState()
}
function updateMenuState() {
const plans = selectedPlans()
const hint = document.getElementById('menuHint')
const cmds = [
['inbound', '入库单'],
['delivery', '送(提)货单'],
['subpack', '分装单']
]
cmds.forEach(([cmd]) => {
const btn = document.querySelector(`#printMenu button[data-cmd="${cmd}"]`)
const r = canMerge(cmd, plans)
btn.disabled = !r.ok
btn.title = r.ok ? '可合打' : r.reason
})
if (!plans.length) {
hint.textContent = '请先勾选计划'
} else if (plans.length === 1) {
hint.textContent = '单条:沿用原打印能力'
} else {
const okAny = cmds.some(([c]) => canMerge(c, plans).ok)
hint.textContent = okAny
? `已选 ${plans.length} 条 · 灰色项表示不满足合打条件`
: `已选 ${plans.length} 条 · 当前选择不满足任一合打条件`
}
}
function showToast(text, type) {
const el = document.getElementById('toast')
el.className = 'msg show ' + type
el.textContent = text
}
function renderPreview(cmd, plans) {
const title =
cmd === 'inbound' ? '入库单(合打预览)' :
cmd === 'delivery' ? '送(提)货单(合打预览)' : '分装单(合打预览)'
const customer = plans[0].customerName
const date = plans[0].planDate
const planNos = plans.map(p => p.planNo).join('、')
const rows = []
plans.forEach(p => {
p.products.forEach(prod => {
rows.push({
planNo: p.planNo,
name: prod.name,
model: prod.model,
qty: prod.qty,
unit: prod.unit
})
})
})
const metaExtra = cmd === 'subpack' && !sameCustomerAndDate(plans)
? `<div><span>说明:</span>分装单允许跨客户/跨日期合打</div>
<div><span>客户(多值):</span>${[...new Set(plans.map(p => p.customerName))].join('、')}</div>
<div><span>计划日期(多值):</span>${[...new Set(plans.map(p => p.planDate))].join('、')}</div>`
: `<div><span>客户名称:</span>${customer}</div>
<div><span>计划日期:</span>${date}</div>`
document.getElementById('preview').innerHTML = `
<h3>${title}</h3>
<div class="meta">
${metaExtra}
<div><span>来源计划:</span>${planNos}</div>
<div><span>产品行数:</span>${rows.length}</div>
</div>
<table>
<thead>
<tr>
<th>单据编号</th>
<th>物料名称</th>
<th>规格</th>
<th>单位</th>
<th>数量</th>
</tr>
</thead>
<tbody>
${rows.map(r => `<tr>
<td>${r.planNo}</td>
<td>${r.name}</td>
<td>${r.model}</td>
<td>${r.unit}</td>
<td>${r.qty}</td>
</tr>`).join('')}
</tbody>
</table>
`
}
function tryPrint(cmd) {
const plans = selectedPlans()
const r = canMerge(cmd, plans)
menuOpen = false
document.getElementById('printMenu').classList.remove('open')
if (!r.ok) {
showToast(r.reason, 'err')
document.getElementById('preview').innerHTML = `<div class="empty">${r.reason}</div>`
return
}
const label = cmd === 'inbound' ? '入库单' : cmd === 'delivery' ? '送(提)货单' : '分装单'
showToast(
plans.length === 1
? `将打开单计划「${label}」打印(与现网一致)`
: `将合并 ${plans.length} 条计划的产品到一张「${label}」`,
'ok'
)
renderPreview(cmd, plans)
}
document.getElementById('tbody').addEventListener('change', (e) => {
const t = e.target
if (!(t instanceof HTMLInputElement) || !t.classList.contains('row-check')) return
if (t.checked) selected.add(t.dataset.id)
else selected.delete(t.dataset.id)
renderList()
document.getElementById('toast').classList.remove('show')
})
document.getElementById('checkAll').addEventListener('change', (e) => {
const on = e.target.checked
selected.clear()
if (on) PLANS.forEach(p => selected.add(p.id))
renderList()
})
document.getElementById('clearBtn').addEventListener('click', () => {
selected.clear()
renderList()
document.getElementById('preview').innerHTML = '<div class="empty">勾选计划并选择打印类型后,在此预览合并产品明细</div>'
document.getElementById('toast').classList.remove('show')
})
document.getElementById('printBtn').addEventListener('click', (e) => {
e.stopPropagation()
menuOpen = !menuOpen
document.getElementById('printMenu').classList.toggle('open', menuOpen)
updateMenuState()
})
document.getElementById('printMenu').addEventListener('click', (e) => {
const btn = e.target.closest('button[data-cmd]')
if (!btn || btn.disabled) return
tryPrint(btn.dataset.cmd)
})
document.addEventListener('click', () => {
if (!menuOpen) return
menuOpen = false
document.getElementById('printMenu').classList.remove('open')
})
// 预设:勾选同客户同日的两条入库计划,方便演示合打
selected.add('1')
selected.add('2')
renderList()
showToast('已预勾选 PO…001 + PO…002(同客户、同计划日期)。可点「打印单据 → 入库单」看合打预览;再勾选 003/004 可看拦截提示。', 'warn')
</script>
</body>
</html>
@@ -0,0 +1,748 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>运输计划 · 多选合打单据 Demo</title>
<style>
:root {
--primary: #10b981;
--primary-dark: #059669;
--primary-bg: #ecfdf5;
--text: #0f172a;
--muted: #64748b;
--border: #e2e8f0;
--warn: #d97706;
--danger: #dc2626;
--card: #fff;
--bg: #f1f5f9;
}
* { box-sizing: border-box; }
body {
margin: 0;
font: 14px/1.5 Inter, "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
color: var(--text);
background: var(--bg);
}
.wrap { max-width: 1180px; margin: 0 auto; padding: 24px; }
h1 { font-size: 20px; margin: 0 0 6px; }
.sub { color: var(--muted); margin-bottom: 20px; }
.panel {
background: var(--card);
border: 1px solid var(--border);
border-radius: 10px;
padding: 16px 20px 20px;
margin-bottom: 16px;
}
.panel h2 {
margin: 0 0 12px;
font-size: 15px;
display: flex;
align-items: center;
gap: 8px;
}
.panel h2::before {
content: "";
width: 3px;
height: 14px;
background: var(--primary);
border-radius: 2px;
}
.rules {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
margin-bottom: 8px;
}
.rule {
border: 1px solid var(--border);
border-radius: 8px;
padding: 10px 12px;
background: #fafafa;
}
.rule strong { display: block; margin-bottom: 4px; }
.rule.ok { border-color: #a7f3d0; background: var(--primary-bg); }
.rule.loose { border-color: #fde68a; background: #fffbeb; }
.toolbar {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid var(--border);
}
.selected-tip { color: var(--muted); min-width: 72px; }
.btn {
height: 36px;
padding: 0 14px;
border-radius: 8px;
border: 1px solid var(--border);
background: #fff;
cursor: pointer;
font-size: 14px;
display: inline-flex;
align-items: center;
gap: 6px;
}
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn-main {
background: var(--primary);
border-color: var(--primary);
color: #fff;
}
.dropdown { position: relative; }
.menu {
display: none;
position: absolute;
top: 40px;
left: 0;
z-index: 20;
min-width: 180px;
background: #fff;
border: 1px solid var(--border);
border-radius: 8px;
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
padding: 6px;
}
.menu.open { display: block; }
.menu button {
width: 100%;
text-align: left;
border: 0;
background: transparent;
padding: 8px 10px;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
}
.menu button:hover:not(:disabled) { background: var(--primary-bg); color: var(--primary-dark); }
.menu button:disabled { color: #94a3b8; cursor: not-allowed; }
.menu .hint {
font-size: 11px;
color: var(--muted);
padding: 0 10px 6px;
line-height: 1.35;
}
table.list {
width: 100%;
border-collapse: collapse;
}
table.list th, table.list td {
padding: 10px 8px;
border-bottom: 1px solid var(--border);
text-align: left;
font-size: 13px;
}
table.list th { color: var(--muted); font-weight: 600; background: #f8fafc; }
table.list tr:hover td { background: #f8fafc; }
table.list tr.picked td { background: var(--primary-bg); }
.tag {
display: inline-block;
padding: 1px 8px;
border-radius: 999px;
font-size: 12px;
background: #e2e8f0;
color: #334155;
}
.tag.in { background: #d1fae5; color: #065f46; }
.tag.out { background: #dbeafe; color: #1e40af; }
.msg {
margin-top: 12px;
padding: 10px 12px;
border-radius: 8px;
font-size: 13px;
display: none;
}
.msg.show { display: block; }
.msg.err { background: #fef2f2; color: var(--danger); border: 1px solid #fecaca; }
.msg.warn { background: #fffbeb; color: #92400e; border: 1px solid #fde68a; }
.msg.ok { background: var(--primary-bg); color: #065f46; border: 1px solid #a7f3d0; }
.grid2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
@media (max-width: 900px) {
.rules, .grid2 { grid-template-columns: 1fr; }
}
.slip {
border: 1px solid #cbd5e1;
border-radius: 8px;
padding: 14px;
background: #fff;
min-height: 280px;
overflow: auto;
}
.slip.inbound-like {
font-family: SimSun, "Songti SC", serif;
color: #111;
max-width: 960px;
margin: 0 auto;
padding: 8px 10px 24px;
}
.inbound-title {
position: relative;
min-height: 56px;
margin-bottom: 14px;
text-align: center;
padding: 4px 40px 0;
}
.inbound-title .co {
font-size: 22px;
font-weight: 700;
letter-spacing: 6px;
line-height: 1.35;
}
.inbound-title .doc {
font-size: 22px;
font-weight: 700;
letter-spacing: 10px;
margin-top: 2px;
}
.inbound-info {
width: 100%;
border-collapse: collapse;
margin-bottom: 10px;
font-size: 15px;
}
.inbound-info td { padding: 4px 0; vertical-align: middle; }
.inbound-info .lab { white-space: nowrap; }
.inbound-product {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
font-size: 13px;
}
.inbound-product th,
.inbound-product td {
border: 1px solid #333 !important;
padding: 8px 4px;
text-align: center;
word-break: break-all;
}
.inbound-product th {
font-weight: bold;
background: #fff;
}
.inbound-product .remark {
text-align: left;
padding-left: 8px;
}
.inbound-sign {
width: 100%;
margin-top: 16px;
font-size: 13px;
border-collapse: collapse;
}
.inbound-sign td {
padding: 8px 4px;
vertical-align: top;
}
.slip h3 {
margin: 0 0 10px;
text-align: center;
font-size: 16px;
letter-spacing: 2px;
}
.slip .meta {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px 12px;
margin-bottom: 12px;
font-size: 12px;
}
.slip .meta span { color: var(--muted); }
.slip table.simple {
width: 100%;
border-collapse: collapse;
font-size: 12px;
}
.slip table.simple th, .slip table.simple td {
border: 1px solid #94a3b8;
padding: 6px 4px;
text-align: center;
}
.slip table.simple th { background: #f1f5f9; }
.empty {
color: var(--muted);
text-align: center;
padding: 48px 12px;
}
.legend { font-size: 12px; color: var(--muted); margin-top: 8px; }
code { background: #f1f5f9; padding: 1px 5px; border-radius: 4px; font-size: 12px; }
</style>
</head>
<body>
<div class="wrap">
<h1>运输计划 · 多选合打单据 Demo</h1>
<p class="sub">
勾选多条计划 →「打印单据」→ 按规则校验 → 右侧预览「产品合并到一张单据」。
本页仅演示交互与合打规则,非最终生产版式。
</p>
<section class="panel">
<h2>合打规则(客户确认稿)</h2>
<div class="rules">
<div class="rule ok">
<strong>入库单</strong>
勾选计划的<strong>客户名称一致</strong>且<strong>计划日期一致</strong>才可合打;产品行合并到一张入库单。
</div>
<div class="rule ok">
<strong>送(提)货单</strong>
同入库单:客户名称 + 计划日期均须一致。
</div>
<div class="rule loose">
<strong>分装单</strong>
<strong>不校验</strong>客户名称、计划日期;勾选即可合打产品到一张分装单。
</div>
</div>
<p class="legend">
演示数据已覆盖:同客户同日可合打、跨客户/跨日拦截、分装单放行。另按现有能力:入库类计划只能打入库单,出库类只能打送(提)货单/分装单。
</p>
</section>
<div class="grid2">
<section class="panel">
<h2>运输计划列表(多选)</h2>
<div class="toolbar">
<span class="selected-tip" id="selCount">已选 0 项</span>
<div class="dropdown">
<button class="btn" id="printBtn" type="button" disabled>
🖨 打印单据 ▾
</button>
<div class="menu" id="printMenu">
<div class="hint" id="menuHint">请先勾选计划</div>
<button type="button" data-cmd="inbound" disabled>入库单</button>
<button type="button" data-cmd="delivery" disabled>送(提)货单</button>
<button type="button" data-cmd="subpack" disabled>分装单</button>
</div>
</div>
<button class="btn" type="button" id="clearBtn">清空选择</button>
</div>
<table class="list">
<thead>
<tr>
<th style="width:36px"><input type="checkbox" id="checkAll" title="全选当前页" /></th>
<th>计划号</th>
<th>计划日期</th>
<th>客户名称</th>
<th>类型</th>
<th>产品摘要</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
<div class="msg" id="toast"></div>
</section>
<section class="panel">
<h2>合打预览</h2>
<div class="slip" id="preview">
<div class="empty">勾选计划并选择打印类型后,在此预览合并产品明细</div>
</div>
</section>
</div>
</div>
<script>
/** @typedef {{ id: string, planNo: string, planDate: string, customerName: string, planType: 'inbound'|'outbound'|'direct', products: {name:string, model:string, qty:number, unit:string}[] }} Plan */
/** @type {Plan[]} */
const PLANS = [
{
id: '1', planNo: 'PO20260727001', planDate: '2026-07-27', customerName: '安徽云栋化工有限公司',
planType: 'inbound',
products: [
{ name: '亚硫酸钠', model: '液体', qty: 12, unit: '吨' },
{ name: '试剂硝酸', model: 'AR', qty: 2, unit: '吨' }
]
},
{
id: '2', planNo: 'PO20260727002', planDate: '2026-07-27', customerName: '安徽云栋化工有限公司',
planType: 'inbound',
products: [
{ name: '次氯酸钠', model: '固体', qty: 8, unit: '吨' }
]
},
{
id: '3', planNo: 'PO20260727003', planDate: '2026-07-27', customerName: '合肥市皖和化工有限公司',
planType: 'inbound',
products: [
{ name: '98% 工业硫酸', model: 'AR', qty: 20, unit: '吨' }
]
},
{
id: '4', planNo: 'PO20260728001', planDate: '2026-07-28', customerName: '安徽云栋化工有限公司',
planType: 'inbound',
products: [
{ name: '试剂氨水', model: 'AR', qty: 5, unit: '吨' }
]
},
{
id: '5', planNo: 'PO20260727011', planDate: '2026-07-27', customerName: '中国长江实业',
planType: 'outbound',
products: [
{ name: 'TEST液碱', model: '31%', qty: 30, unit: '吨' }
]
},
{
id: '6', planNo: 'PO20260727012', planDate: '2026-07-27', customerName: '中国长江实业',
planType: 'outbound',
products: [
{ name: 'TEST盐酸', model: '30%', qty: 15, unit: '吨' },
{ name: 'TEST液碱', model: '31%', qty: 10, unit: '吨' }
]
},
{
id: '7', planNo: 'PO20260729001', planDate: '2026-07-29', customerName: '中国长江实业',
planType: 'outbound',
products: [
{ name: 'TEST液碱', model: '31%', qty: 6, unit: '吨' }
]
}
]
const selected = new Set()
let menuOpen = false
function typeLabel(t) {
return t === 'inbound' ? '入库单' : t === 'outbound' ? '出库单' : '直销单'
}
function typeClass(t) {
return t === 'inbound' ? 'in' : t === 'outbound' ? 'out' : ''
}
function printableCommands(planType) {
if (planType === 'inbound') return ['inbound']
if (planType === 'outbound') return ['delivery', 'subpack']
return []
}
function selectedPlans() {
return PLANS.filter(p => selected.has(p.id))
}
function sameCustomerAndDate(plans) {
if (!plans.length) return false
const c = plans[0].customerName
const d = plans[0].planDate
return plans.every(p => p.customerName === c && p.planDate === d)
}
function canMerge(cmd, plans) {
if (plans.length < 1) return { ok: false, reason: '请至少勾选 1 条运输计划' }
if (plans.length === 1) {
const allow = printableCommands(plans[0].planType)
if (!allow.includes(cmd)) {
return { ok: false, reason: `当前单据类型(${typeLabel(plans[0].planType)})不支持打印该单据` }
}
return { ok: true, reason: '' }
}
// multi
if (cmd === 'subpack') {
const bad = plans.filter(p => !printableCommands(p.planType).includes('subpack'))
if (bad.length) {
return { ok: false, reason: `分装单仅支持出库类计划;以下不可打:${bad.map(p => p.planNo).join('、')}` }
}
return { ok: true, reason: '' }
}
if (cmd === 'inbound') {
const bad = plans.filter(p => p.planType !== 'inbound')
if (bad.length) {
return { ok: false, reason: `入库单仅支持入库类计划;冲突:${bad.map(p => p.planNo).join('、')}` }
}
if (!sameCustomerAndDate(plans)) {
return { ok: false, reason: '合打入库单要求:客户名称一致 且 计划日期一致' }
}
return { ok: true, reason: '' }
}
if (cmd === 'delivery') {
const bad = plans.filter(p => p.planType !== 'outbound')
if (bad.length) {
return { ok: false, reason: `送(提)货单仅支持出库类计划;冲突:${bad.map(p => p.planNo).join('、')}` }
}
if (!sameCustomerAndDate(plans)) {
return { ok: false, reason: '合打送(提)货单要求:客户名称一致 且 计划日期一致' }
}
return { ok: true, reason: '' }
}
return { ok: false, reason: '未知单据类型' }
}
function renderList() {
const tb = document.getElementById('tbody')
tb.innerHTML = PLANS.map(p => {
const checked = selected.has(p.id) ? 'checked' : ''
const picked = selected.has(p.id) ? 'picked' : ''
const prod = p.products.map(x => `${x.name}×${x.qty}`).join(',')
return `<tr class="${picked}" data-id="${p.id}">
<td><input type="checkbox" class="row-check" data-id="${p.id}" ${checked} /></td>
<td>${p.planNo}</td>
<td>${p.planDate}</td>
<td>${p.customerName}</td>
<td><span class="tag ${typeClass(p.planType)}">${typeLabel(p.planType)}</span></td>
<td>${prod}</td>
</tr>`
}).join('')
document.getElementById('selCount').textContent = `已选 ${selected.size} 项`
document.getElementById('printBtn').disabled = selected.size === 0
document.getElementById('checkAll').checked = selected.size === PLANS.length && PLANS.length > 0
updateMenuState()
}
function updateMenuState() {
const plans = selectedPlans()
const hint = document.getElementById('menuHint')
const cmds = [
['inbound', '入库单'],
['delivery', '送(提)货单'],
['subpack', '分装单']
]
cmds.forEach(([cmd]) => {
const btn = document.querySelector(`#printMenu button[data-cmd="${cmd}"]`)
const r = canMerge(cmd, plans)
btn.disabled = !r.ok
btn.title = r.ok ? '可合打' : r.reason
})
if (!plans.length) {
hint.textContent = '请先勾选计划'
} else if (plans.length === 1) {
hint.textContent = '单条:沿用原打印能力'
} else {
const okAny = cmds.some(([c]) => canMerge(c, plans).ok)
hint.textContent = okAny
? `已选 ${plans.length} 条 · 灰色项表示不满足合打条件`
: `已选 ${plans.length} 条 · 当前选择不满足任一合打条件`
}
}
function showToast(text, type) {
const el = document.getElementById('toast')
el.className = 'msg show ' + type
el.textContent = text
}
function formatPlanDateChinese(ymd) {
if (!ymd) return ''
const [y, m, d] = ymd.split('-')
return `${Number(y)}年${Number(m)}月${Number(d)}日`
}
function productNoStub(name) {
// demo only
const map = {
'亚硫酸钠': 'WL-001',
'试剂硝酸': 'WL-002',
'次氯酸钠': 'WL-003',
'98% 工业硫酸': 'WL-004',
'试剂氨水': 'WL-005',
'TEST液碱': 'WL-101',
'TEST盐酸': 'WL-102'
}
return map[name] || '—'
}
function renderInboundSlip(plans) {
const customer = plans[0].customerName
const dateCn = formatPlanDateChinese(plans[0].planDate)
const rows = []
plans.forEach(p => {
p.products.forEach(prod => {
rows.push({
planNo: p.planNo,
productNo: productNoStub(prod.name),
name: prod.name,
model: prod.model,
qty: prod.qty,
unit: prod.unit
})
})
})
const body = rows.map(r => `<tr>
<td>${r.planNo}</td>
<td>${r.productNo}</td>
<td>${r.name}</td>
<td>${r.model}</td>
<td>${r.unit}</td>
<td>${r.qty}</td>
<td></td>
<td></td>
</tr>`).join('')
document.getElementById('preview').innerHTML = `
<div class="inbound-like">
<div class="inbound-title">
<div class="co">安庆市山峡化工有限公司</div>
<div class="doc">入&nbsp;&nbsp;库&nbsp;&nbsp;单</div>
</div>
<table class="inbound-info">
<tr>
<td style="width:58%"><span class="lab">送货单位:</span>${customer}</td>
<td style="width:42%;text-align:right"><span class="lab">日&nbsp;期:</span>${dateCn}</td>
</tr>
</table>
<table class="inbound-product">
<colgroup>
<col style="width:14%" /><col style="width:14%" /><col style="width:20%" />
<col style="width:8%" /><col style="width:6%" /><col style="width:10%" />
<col style="width:14%" /><col style="width:14%" />
</colgroup>
<thead>
<tr>
<th>单据编号</th>
<th>物料编号</th>
<th>物料名称</th>
<th>规格</th>
<th>单位</th>
<th>数量</th>
<th>产品规格</th>
<th>实收数量</th>
</tr>
</thead>
<tbody>
${body}
<tr><td colspan="8" class="remark">备注:</td></tr>
</tbody>
</table>
<table class="inbound-sign">
<tr>
<td>仓库:</td>
<td>审核:</td>
<td>客户签收:</td>
<td>司机/送货人(附车牌):</td>
</tr>
</table>
</div>
`
}
function renderPreview(cmd, plans) {
if (cmd === 'inbound') {
renderInboundSlip(plans)
return
}
const title =
cmd === 'delivery' ? '送(提)货单(合打预览)' : '分装单(合打预览)'
const customer = plans[0].customerName
const date = plans[0].planDate
const rows = []
plans.forEach(p => {
p.products.forEach(prod => {
rows.push({
planNo: p.planNo,
name: prod.name,
model: prod.model,
qty: prod.qty,
unit: prod.unit
})
})
})
const metaExtra = cmd === 'subpack' && !sameCustomerAndDate(plans)
? `<div><span>说明:</span>分装单允许跨客户/跨日期合打</div>
<div><span>客户(多值):</span>${[...new Set(plans.map(p => p.customerName))].join('、')}</div>
<div><span>计划日期(多值):</span>${[...new Set(plans.map(p => p.planDate))].join('、')}</div>`
: `<div><span>客户名称:</span>${customer}</div>
<div><span>计划日期:</span>${date}</div>`
document.getElementById('preview').innerHTML = `
<h3>${title}</h3>
<div class="meta">
${metaExtra}
</div>
<table class="simple">
<thead>
<tr>
<th>单据编号</th>
<th>物料名称</th>
<th>规格</th>
<th>单位</th>
<th>数量</th>
</tr>
</thead>
<tbody>
${rows.map(r => `<tr>
<td>${r.planNo}</td>
<td>${r.name}</td>
<td>${r.model}</td>
<td>${r.unit}</td>
<td>${r.qty}</td>
</tr>`).join('')}
</tbody>
</table>
`
}
function tryPrint(cmd) {
const plans = selectedPlans()
const r = canMerge(cmd, plans)
menuOpen = false
document.getElementById('printMenu').classList.remove('open')
if (!r.ok) {
showToast(r.reason, 'err')
document.getElementById('preview').innerHTML = `<div class="empty">${r.reason}</div>`
return
}
const label = cmd === 'inbound' ? '入库单' : cmd === 'delivery' ? '送(提)货单' : '分装单'
showToast(
plans.length === 1
? `将打开单计划「${label}」打印(与现网一致)`
: `将合并 ${plans.length} 条计划的产品到一张「${label}」`,
'ok'
)
renderPreview(cmd, plans)
}
document.getElementById('tbody').addEventListener('change', (e) => {
const t = e.target
if (!(t instanceof HTMLInputElement) || !t.classList.contains('row-check')) return
if (t.checked) selected.add(t.dataset.id)
else selected.delete(t.dataset.id)
renderList()
document.getElementById('toast').classList.remove('show')
})
document.getElementById('checkAll').addEventListener('change', (e) => {
const on = e.target.checked
selected.clear()
if (on) PLANS.forEach(p => selected.add(p.id))
renderList()
})
document.getElementById('clearBtn').addEventListener('click', () => {
selected.clear()
renderList()
document.getElementById('preview').innerHTML = '<div class="empty">勾选计划并选择打印类型后,在此预览合并产品明细</div>'
document.getElementById('toast').classList.remove('show')
})
document.getElementById('printBtn').addEventListener('click', (e) => {
e.stopPropagation()
menuOpen = !menuOpen
document.getElementById('printMenu').classList.toggle('open', menuOpen)
updateMenuState()
})
document.getElementById('printMenu').addEventListener('click', (e) => {
const btn = e.target.closest('button[data-cmd]')
if (!btn || btn.disabled) return
tryPrint(btn.dataset.cmd)
})
document.addEventListener('click', () => {
if (!menuOpen) return
menuOpen = false
document.getElementById('printMenu').classList.remove('open')
})
// 预设:勾选同客户同日的两条入库计划,方便演示合打
selected.add('1')
selected.add('2')
renderList()
tryPrint('inbound')
showToast('已预勾选同客户同日两条入库计划,并按现网入库单版式展示合打预览(无「来源计划」「产品行数」)。', 'warn')
</script>
</body>
</html>
@@ -0,0 +1,865 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>运输计划 · 多选合打单据 Demo</title>
<style>
:root {
--primary: #10b981;
--primary-dark: #059669;
--primary-bg: #ecfdf5;
--text: #0f172a;
--muted: #64748b;
--border: #e2e8f0;
--warn: #d97706;
--danger: #dc2626;
--card: #fff;
--bg: #f1f5f9;
}
* { box-sizing: border-box; }
body {
margin: 0;
font: 14px/1.5 Inter, "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
color: var(--text);
background: var(--bg);
}
.wrap { max-width: 1180px; margin: 0 auto; padding: 24px; }
h1 { font-size: 20px; margin: 0 0 6px; }
.sub { color: var(--muted); margin-bottom: 20px; }
.panel {
background: var(--card);
border: 1px solid var(--border);
border-radius: 10px;
padding: 16px 20px 20px;
margin-bottom: 16px;
}
.panel h2 {
margin: 0 0 12px;
font-size: 15px;
display: flex;
align-items: center;
gap: 8px;
}
.panel h2::before {
content: "";
width: 3px;
height: 14px;
background: var(--primary);
border-radius: 2px;
}
.rules {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
margin-bottom: 8px;
}
.rule {
border: 1px solid var(--border);
border-radius: 8px;
padding: 10px 12px;
background: #fafafa;
}
.rule strong { display: block; margin-bottom: 4px; }
.rule.ok { border-color: #a7f3d0; background: var(--primary-bg); }
.rule.loose { border-color: #fde68a; background: #fffbeb; }
.toolbar {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid var(--border);
}
.selected-tip { color: var(--muted); min-width: 72px; }
.btn {
height: 36px;
padding: 0 14px;
border-radius: 8px;
border: 1px solid var(--border);
background: #fff;
cursor: pointer;
font-size: 14px;
display: inline-flex;
align-items: center;
gap: 6px;
}
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn-main {
background: var(--primary);
border-color: var(--primary);
color: #fff;
}
.dropdown { position: relative; }
.menu {
display: none;
position: absolute;
top: 40px;
left: 0;
z-index: 20;
min-width: 180px;
background: #fff;
border: 1px solid var(--border);
border-radius: 8px;
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
padding: 6px;
}
.menu.open { display: block; }
.menu button {
width: 100%;
text-align: left;
border: 0;
background: transparent;
padding: 8px 10px;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
}
.menu button:hover:not(:disabled) { background: var(--primary-bg); color: var(--primary-dark); }
.menu button:disabled { color: #94a3b8; cursor: not-allowed; }
.menu .hint {
font-size: 11px;
color: var(--muted);
padding: 0 10px 6px;
line-height: 1.35;
}
table.list {
width: 100%;
border-collapse: collapse;
}
table.list th, table.list td {
padding: 10px 8px;
border-bottom: 1px solid var(--border);
text-align: left;
font-size: 13px;
}
table.list th { color: var(--muted); font-weight: 600; background: #f8fafc; }
table.list tr:hover td { background: #f8fafc; }
table.list tr.picked td { background: var(--primary-bg); }
.tag {
display: inline-block;
padding: 1px 8px;
border-radius: 999px;
font-size: 12px;
background: #e2e8f0;
color: #334155;
}
.tag.in { background: #d1fae5; color: #065f46; }
.tag.out { background: #dbeafe; color: #1e40af; }
.msg {
margin-top: 12px;
padding: 10px 12px;
border-radius: 8px;
font-size: 13px;
display: none;
}
.msg.show { display: block; }
.msg.err { background: #fef2f2; color: var(--danger); border: 1px solid #fecaca; }
.msg.warn { background: #fffbeb; color: #92400e; border: 1px solid #fde68a; }
.msg.ok { background: var(--primary-bg); color: #065f46; border: 1px solid #a7f3d0; }
.grid2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
@media (max-width: 900px) {
.rules, .grid2 { grid-template-columns: 1fr; }
}
.slip {
border: 1px solid #cbd5e1;
border-radius: 8px;
padding: 14px;
background: #fff;
min-height: 280px;
overflow: auto;
}
.slip .inbound-like,
.delivery-like,
.subpack-like {
font-family: SimSun, "Songti SC", serif;
color: #111;
max-width: 960px;
margin: 0 auto;
padding: 8px 10px 24px;
}
.slip-title {
position: relative;
min-height: 56px;
margin-bottom: 14px;
text-align: center;
padding: 4px 40px 0;
}
.slip-title .co {
font-size: 22px;
font-weight: 700;
letter-spacing: 6px;
line-height: 1.35;
}
.slip-title .doc {
font-size: 22px;
font-weight: 700;
letter-spacing: 10px;
margin-top: 2px;
}
.slip-info {
width: 100%;
border-collapse: collapse;
margin-bottom: 10px;
font-size: 14px;
}
.slip-info td { padding: 4px 0; vertical-align: middle; }
.slip-info .lab { white-space: nowrap; }
.type-checks {
display: flex;
gap: 12px;
font-size: 14px;
margin-bottom: 4px;
justify-content: flex-start;
}
.type-checks label { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
.slip-product {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
font-size: 13px;
}
.slip-product th,
.slip-product td {
border: 1px solid #333 !important;
padding: 8px 4px;
text-align: center;
word-break: break-all;
}
.slip-product th {
font-weight: bold;
background: #fff;
}
.slip-product .remark,
.slip-product .terms {
text-align: left;
padding-left: 8px;
font-size: 12px;
line-height: 1.45;
}
.slip-sign {
width: 100%;
margin-top: 16px;
font-size: 13px;
border-collapse: collapse;
}
.slip-sign td {
padding: 8px 4px;
vertical-align: top;
}
.bucket-checks {
display: inline-flex;
gap: 14px;
margin-left: 12px;
flex-wrap: wrap;
font-size: 13px;
}
.subpack-no {
text-align: right;
font-size: 13px;
margin-bottom: 6px;
color: #333;
}
.empty {
color: var(--muted);
text-align: center;
padding: 48px 12px;
}
.legend { font-size: 12px; color: var(--muted); margin-top: 8px; }
code { background: #f1f5f9; padding: 1px 5px; border-radius: 4px; font-size: 12px; }
</style>
</head>
<body>
<div class="wrap">
<h1>运输计划 · 多选合打单据 Demo</h1>
<p class="sub">
勾选多条计划 →「打印单据」→ 按规则校验 → 右侧预览「产品合并到一张单据」。
本页仅演示交互与合打规则,非最终生产版式。
</p>
<section class="panel">
<h2>合打规则(客户确认稿)</h2>
<div class="rules">
<div class="rule ok">
<strong>入库单</strong>
勾选计划的<strong>客户名称一致</strong>且<strong>计划日期一致</strong>才可合打;产品行合并到一张入库单。
</div>
<div class="rule ok">
<strong>送(提)货单</strong>
同入库单:客户名称 + 计划日期均须一致。
</div>
<div class="rule loose">
<strong>分装单</strong>
<strong>不校验</strong>客户名称、计划日期;勾选即可合打产品到一张分装单。
</div>
</div>
<p class="legend">
合打产品行策略:<strong>分行列出</strong>(不合并数量);入库单/送(提)货单第一列为「单据编号」填各计划号;分装单第一列为「序号」按行递增。
演示数据已覆盖:同客户同日可合打、跨客户/跨日拦截、分装单放行。另按现有能力:入库类计划只能打入库单,出库类只能打送(提)货单/分装单。
</p>
</section>
<div class="grid2">
<section class="panel">
<h2>运输计划列表(多选)</h2>
<div class="toolbar">
<span class="selected-tip" id="selCount">已选 0 项</span>
<div class="dropdown">
<button class="btn" id="printBtn" type="button" disabled>
🖨 打印单据 ▾
</button>
<div class="menu" id="printMenu">
<div class="hint" id="menuHint">请先勾选计划</div>
<button type="button" data-cmd="inbound" disabled>入库单</button>
<button type="button" data-cmd="delivery" disabled>送(提)货单</button>
<button type="button" data-cmd="subpack" disabled>分装单</button>
</div>
</div>
<button class="btn" type="button" id="clearBtn">清空选择</button>
</div>
<table class="list">
<thead>
<tr>
<th style="width:36px"><input type="checkbox" id="checkAll" title="全选当前页" /></th>
<th>计划号</th>
<th>计划日期</th>
<th>客户名称</th>
<th>类型</th>
<th>产品摘要</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
<div class="msg" id="toast"></div>
</section>
<section class="panel">
<h2>合打预览</h2>
<div class="slip" id="preview">
<div class="empty">勾选计划并选择打印类型后,在此预览合并产品明细</div>
</div>
</section>
</div>
</div>
<script>
/** @typedef {{ id: string, planNo: string, planDate: string, customerName: string, planType: 'inbound'|'outbound'|'direct', products: {name:string, model:string, qty:number, unit:string}[] }} Plan */
/** @type {Plan[]} */
const PLANS = [
{
id: '1', planNo: 'PO20260727001', planDate: '2026-07-27', customerName: '安徽云栋化工有限公司',
planType: 'inbound',
products: [
{ name: '亚硫酸钠', model: '液体', qty: 12, unit: '吨' },
{ name: '试剂硝酸', model: 'AR', qty: 2, unit: '吨' }
]
},
{
id: '2', planNo: 'PO20260727002', planDate: '2026-07-27', customerName: '安徽云栋化工有限公司',
planType: 'inbound',
products: [
{ name: '次氯酸钠', model: '固体', qty: 8, unit: '吨' }
]
},
{
id: '3', planNo: 'PO20260727003', planDate: '2026-07-27', customerName: '合肥市皖和化工有限公司',
planType: 'inbound',
products: [
{ name: '98% 工业硫酸', model: 'AR', qty: 20, unit: '吨' }
]
},
{
id: '4', planNo: 'PO20260728001', planDate: '2026-07-28', customerName: '安徽云栋化工有限公司',
planType: 'inbound',
products: [
{ name: '试剂氨水', model: 'AR', qty: 5, unit: '吨' }
]
},
{
id: '5', planNo: 'PO20260727011', planDate: '2026-07-27', customerName: '中国长江实业',
planType: 'outbound',
products: [
{ name: 'TEST液碱', model: '31%', qty: 30, unit: '吨' }
]
},
{
id: '6', planNo: 'PO20260727012', planDate: '2026-07-27', customerName: '中国长江实业',
planType: 'outbound',
products: [
{ name: 'TEST盐酸', model: '30%', qty: 15, unit: '吨' },
{ name: 'TEST液碱', model: '31%', qty: 10, unit: '吨' }
]
},
{
id: '7', planNo: 'PO20260729001', planDate: '2026-07-29', customerName: '中国长江实业',
planType: 'outbound',
products: [
{ name: 'TEST液碱', model: '31%', qty: 6, unit: '吨' }
]
}
]
const selected = new Set()
let menuOpen = false
function typeLabel(t) {
return t === 'inbound' ? '入库单' : t === 'outbound' ? '出库单' : '直销单'
}
function typeClass(t) {
return t === 'inbound' ? 'in' : t === 'outbound' ? 'out' : ''
}
function printableCommands(planType) {
if (planType === 'inbound') return ['inbound']
if (planType === 'outbound') return ['delivery', 'subpack']
return []
}
function selectedPlans() {
return PLANS.filter(p => selected.has(p.id))
}
function sameCustomerAndDate(plans) {
if (!plans.length) return false
const c = plans[0].customerName
const d = plans[0].planDate
return plans.every(p => p.customerName === c && p.planDate === d)
}
function canMerge(cmd, plans) {
if (plans.length < 1) return { ok: false, reason: '请至少勾选 1 条运输计划' }
if (plans.length === 1) {
const allow = printableCommands(plans[0].planType)
if (!allow.includes(cmd)) {
return { ok: false, reason: `当前单据类型(${typeLabel(plans[0].planType)})不支持打印该单据` }
}
return { ok: true, reason: '' }
}
// multi
if (cmd === 'subpack') {
const bad = plans.filter(p => !printableCommands(p.planType).includes('subpack'))
if (bad.length) {
return { ok: false, reason: `分装单仅支持出库类计划;以下不可打:${bad.map(p => p.planNo).join('、')}` }
}
return { ok: true, reason: '' }
}
if (cmd === 'inbound') {
const bad = plans.filter(p => p.planType !== 'inbound')
if (bad.length) {
return { ok: false, reason: `入库单仅支持入库类计划;冲突:${bad.map(p => p.planNo).join('、')}` }
}
if (!sameCustomerAndDate(plans)) {
return { ok: false, reason: '合打入库单要求:客户名称一致 且 计划日期一致' }
}
return { ok: true, reason: '' }
}
if (cmd === 'delivery') {
const bad = plans.filter(p => p.planType !== 'outbound')
if (bad.length) {
return { ok: false, reason: `送(提)货单仅支持出库类计划;冲突:${bad.map(p => p.planNo).join('、')}` }
}
if (!sameCustomerAndDate(plans)) {
return { ok: false, reason: '合打送(提)货单要求:客户名称一致 且 计划日期一致' }
}
return { ok: true, reason: '' }
}
return { ok: false, reason: '未知单据类型' }
}
function renderList() {
const tb = document.getElementById('tbody')
tb.innerHTML = PLANS.map(p => {
const checked = selected.has(p.id) ? 'checked' : ''
const picked = selected.has(p.id) ? 'picked' : ''
const prod = p.products.map(x => `${x.name}×${x.qty}`).join(',')
return `<tr class="${picked}" data-id="${p.id}">
<td><input type="checkbox" class="row-check" data-id="${p.id}" ${checked} /></td>
<td>${p.planNo}</td>
<td>${p.planDate}</td>
<td>${p.customerName}</td>
<td><span class="tag ${typeClass(p.planType)}">${typeLabel(p.planType)}</span></td>
<td>${prod}</td>
</tr>`
}).join('')
document.getElementById('selCount').textContent = `已选 ${selected.size} 项`
document.getElementById('printBtn').disabled = selected.size === 0
document.getElementById('checkAll').checked = selected.size === PLANS.length && PLANS.length > 0
updateMenuState()
}
function updateMenuState() {
const plans = selectedPlans()
const hint = document.getElementById('menuHint')
const cmds = [
['inbound', '入库单'],
['delivery', '送(提)货单'],
['subpack', '分装单']
]
cmds.forEach(([cmd]) => {
const btn = document.querySelector(`#printMenu button[data-cmd="${cmd}"]`)
const r = canMerge(cmd, plans)
btn.disabled = !r.ok
btn.title = r.ok ? '可合打' : r.reason
})
if (!plans.length) {
hint.textContent = '请先勾选计划'
} else if (plans.length === 1) {
hint.textContent = '单条:沿用原打印能力'
} else {
const okAny = cmds.some(([c]) => canMerge(c, plans).ok)
hint.textContent = okAny
? `已选 ${plans.length} 条 · 灰色项表示不满足合打条件`
: `已选 ${plans.length} 条 · 当前选择不满足任一合打条件`
}
}
function showToast(text, type) {
const el = document.getElementById('toast')
el.className = 'msg show ' + type
el.textContent = text
}
function formatPlanDateChinese(ymd) {
if (!ymd) return ''
const [y, m, d] = ymd.split('-')
return `${Number(y)}年${Number(m)}月${Number(d)}日`
}
function productNoStub(name) {
// demo only
const map = {
'亚硫酸钠': 'WL-001',
'试剂硝酸': 'WL-002',
'次氯酸钠': 'WL-003',
'98% 工业硫酸': 'WL-004',
'试剂氨水': 'WL-005',
'TEST液碱': 'WL-101',
'TEST盐酸': 'WL-102'
}
return map[name] || '—'
}
function collectProductRows(plans) {
const rows = []
plans.forEach(p => {
p.products.forEach(prod => {
rows.push({
planNo: p.planNo,
productNo: productNoStub(prod.name),
name: prod.name,
model: prod.model,
qty: prod.qty,
unit: prod.unit
})
})
})
return rows
}
function renderInboundSlip(plans) {
const customer = plans[0].customerName
const dateCn = formatPlanDateChinese(plans[0].planDate)
const rows = collectProductRows(plans)
const body = rows.map(r => `<tr>
<td>${r.planNo}</td>
<td>${r.productNo}</td>
<td>${r.name}</td>
<td>${r.model}</td>
<td>${r.unit}</td>
<td>${r.qty}</td>
<td></td>
<td></td>
</tr>`).join('')
document.getElementById('preview').innerHTML = `
<div class="inbound-like">
<div class="slip-title">
<div class="co">安庆市山峡化工有限公司</div>
<div class="doc">入&nbsp;&nbsp;库&nbsp;&nbsp;单</div>
</div>
<table class="slip-info">
<tr>
<td style="width:58%"><span class="lab">送货单位:</span>${customer}</td>
<td style="width:42%;text-align:right"><span class="lab">日&nbsp;期:</span>${dateCn}</td>
</tr>
</table>
<table class="slip-product">
<colgroup>
<col style="width:14%" /><col style="width:14%" /><col style="width:20%" />
<col style="width:8%" /><col style="width:6%" /><col style="width:10%" />
<col style="width:14%" /><col style="width:14%" />
</colgroup>
<thead>
<tr>
<th>单据编号</th>
<th>物料编号</th>
<th>物料名称</th>
<th>规格</th>
<th>单位</th>
<th>数量</th>
<th>产品规格</th>
<th>实收数量</th>
</tr>
</thead>
<tbody>
${body}
<tr><td colspan="8" class="remark">备注:</td></tr>
</tbody>
</table>
<table class="slip-sign">
<tr>
<td>仓库:</td>
<td>审核:</td>
<td>客户签收:</td>
<td>司机/送货人(附车牌):</td>
</tr>
</table>
</div>
`
}
function renderDeliverySlip(plans) {
const customer = plans[0].customerName
const dateCn = formatPlanDateChinese(plans[0].planDate)
const rows = collectProductRows(plans)
const body = rows.map(r => `<tr>
<td>${r.planNo}</td>
<td>${r.productNo}</td>
<td>${r.name}</td>
<td>${r.model}</td>
<td>${r.unit}</td>
<td>${r.qty}</td>
<td></td>
<td></td>
</tr>`).join('')
document.getElementById('preview').innerHTML = `
<div class="delivery-like">
<div class="slip-title">
<div class="co">安庆市山峡化工有限公司</div>
<div class="doc">送&nbsp;(提)&nbsp;货&nbsp;单</div>
</div>
<table class="slip-info">
<tr>
<td style="width:34%"><span class="lab">收货单位:</span>${customer}</td>
<td style="width:33%"><span class="lab">收货联系方式:</span></td>
<td style="width:33%;text-align:right;vertical-align:top;">
<div class="type-checks">
<label><input type="checkbox" />自提</label>
<label><input type="checkbox" checked />出库</label>
<label><input type="checkbox" />直销</label>
</div>
<div><span class="lab">日&nbsp;期:</span>${dateCn}</div>
</td>
</tr>
</table>
<table class="slip-product">
<colgroup>
<col style="width:14%" /><col style="width:14%" /><col style="width:20%" />
<col style="width:8%" /><col style="width:6%" /><col style="width:10%" />
<col style="width:14%" /><col style="width:14%" />
</colgroup>
<thead>
<tr>
<th>单据编号</th>
<th>物料编号</th>
<th>物料名称</th>
<th>规格</th>
<th>单位</th>
<th>数量</th>
<th>产品规格</th>
<th>实收数量</th>
</tr>
</thead>
<tbody>
${body}
<tr><td colspan="8" class="remark">备注:</td></tr>
<tr>
<td colspan="5" class="terms">
1.本送货单如购销双方未签订合同,视为有效;<br />
2.本次送货含桶____个,回收____个,结存____个;
</td>
<td colspan="3" class="remark" style="font-size:11px;line-height:1.5;">
地址:安庆市高新区霞虹路5号<br />
送货单位固话:0556-5999710<br />
联系人电话:方总13395560066
</td>
</tr>
</tbody>
</table>
<table class="slip-sign">
<tr>
<td>仓库:</td>
<td>审核:</td>
<td>客户签收:</td>
<td>司机/送货人(附车牌):</td>
</tr>
</table>
</div>
`
}
function renderSubpackSlip(plans) {
// 分装单允许跨客户/跨日期:信息区用首条计划演示;产品分行保留各计划行
const first = plans[0]
const orderDate = formatPlanDateChinese(first.planDate)
const rows = collectProductRows(plans)
const body = rows.map((r, idx) => `<tr>
<td>${idx + 1}</td>
<td>${r.name}</td>
<td>${r.model}</td>
<td></td>
<td>${r.unit}</td>
<td>${r.qty}</td>
<td><input type="checkbox" /></td>
</tr>`).join('')
const slipNo = `SXFZ${first.planDate.replace(/-/g, '')}-001`
document.getElementById('preview').innerHTML = `
<div class="subpack-like">
<div class="slip-title">
<div class="co">安庆市山峡化工有限公司</div>
<div class="doc">分&nbsp;装&nbsp;单</div>
</div>
<div class="subpack-no">${slipNo}</div>
<table class="slip-info">
<tr>
<td><span class="lab">下单日期:</span>${orderDate}</td>
<td><span class="lab">交货日期:</span>${orderDate}</td>
<td><span class="lab">下单人:</span></td>
<td><span class="lab">经手人:</span></td>
</tr>
</table>
<table class="slip-product">
<colgroup>
<col style="width:8%" /><col style="width:22%" /><col style="width:16%" />
<col style="width:16%" /><col style="width:10%" /><col style="width:12%" />
<col style="width:16%" />
</colgroup>
<thead>
<tr>
<th>序号</th>
<th>产品名称</th>
<th>产品型号</th>
<th>包装规格</th>
<th>单位</th>
<th>数量</th>
<th>是否完成</th>
</tr>
</thead>
<tbody>
${body}
<tr>
<td colspan="7" class="remark">
备注:
<span class="bucket-checks">
<label><input type="checkbox" />全新桶</label>
<label><input type="checkbox" />是否回收桶</label>
<label><input type="checkbox" />桶循环</label>
</span>
</td>
</tr>
</tbody>
</table>
<table class="slip-sign">
<tr>
<td>仓库:</td>
<td>审核:</td>
<td>客户签收:</td>
<td>司机/送货人(附车牌):</td>
</tr>
</table>
</div>
`
}
function renderPreview(cmd, plans) {
if (cmd === 'inbound') {
renderInboundSlip(plans)
return
}
if (cmd === 'delivery') {
renderDeliverySlip(plans)
return
}
renderSubpackSlip(plans)
}
function tryPrint(cmd) {
const plans = selectedPlans()
const r = canMerge(cmd, plans)
menuOpen = false
document.getElementById('printMenu').classList.remove('open')
if (!r.ok) {
showToast(r.reason, 'err')
document.getElementById('preview').innerHTML = `<div class="empty">${r.reason}</div>`
return
}
const label = cmd === 'inbound' ? '入库单' : cmd === 'delivery' ? '送(提)货单' : '分装单'
showToast(
plans.length === 1
? `将打开单计划「${label}」打印(与现网一致)`
: `将合并 ${plans.length} 条计划的产品到一张「${label}」`,
'ok'
)
renderPreview(cmd, plans)
}
document.getElementById('tbody').addEventListener('change', (e) => {
const t = e.target
if (!(t instanceof HTMLInputElement) || !t.classList.contains('row-check')) return
if (t.checked) selected.add(t.dataset.id)
else selected.delete(t.dataset.id)
renderList()
document.getElementById('toast').classList.remove('show')
})
document.getElementById('checkAll').addEventListener('change', (e) => {
const on = e.target.checked
selected.clear()
if (on) PLANS.forEach(p => selected.add(p.id))
renderList()
})
document.getElementById('clearBtn').addEventListener('click', () => {
selected.clear()
renderList()
document.getElementById('preview').innerHTML = '<div class="empty">勾选计划并选择打印类型后,在此预览合并产品明细</div>'
document.getElementById('toast').classList.remove('show')
})
document.getElementById('printBtn').addEventListener('click', (e) => {
e.stopPropagation()
menuOpen = !menuOpen
document.getElementById('printMenu').classList.toggle('open', menuOpen)
updateMenuState()
})
document.getElementById('printMenu').addEventListener('click', (e) => {
const btn = e.target.closest('button[data-cmd]')
if (!btn || btn.disabled) return
tryPrint(btn.dataset.cmd)
})
document.addEventListener('click', () => {
if (!menuOpen) return
menuOpen = false
document.getElementById('printMenu').classList.remove('open')
})
// 预设:勾选同客户同日的两条入库计划,方便演示合打
selected.add('1')
selected.add('2')
renderList()
tryPrint('inbound')
showToast('已预勾选同客户同日两条入库计划,并按现网入库单版式展示合打预览(无「来源计划」「产品行数」)。', 'warn')
</script>
</body>
</html>
@@ -0,0 +1,881 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>运输计划 · 多选合打单据 Demo</title>
<style>
:root {
--primary: #10b981;
--primary-dark: #059669;
--primary-bg: #ecfdf5;
--text: #0f172a;
--muted: #64748b;
--border: #e2e8f0;
--warn: #d97706;
--danger: #dc2626;
--card: #fff;
--bg: #f1f5f9;
}
* { box-sizing: border-box; }
body {
margin: 0;
font: 14px/1.5 Inter, "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
color: var(--text);
background: var(--bg);
}
.wrap { max-width: 1180px; margin: 0 auto; padding: 24px; }
h1 { font-size: 20px; margin: 0 0 6px; }
.sub { color: var(--muted); margin-bottom: 20px; }
.panel {
background: var(--card);
border: 1px solid var(--border);
border-radius: 10px;
padding: 16px 20px 20px;
margin-bottom: 16px;
}
.panel h2 {
margin: 0 0 12px;
font-size: 15px;
display: flex;
align-items: center;
gap: 8px;
}
.panel h2::before {
content: "";
width: 3px;
height: 14px;
background: var(--primary);
border-radius: 2px;
}
.rules {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
margin-bottom: 8px;
}
.rule {
border: 1px solid var(--border);
border-radius: 8px;
padding: 10px 12px;
background: #fafafa;
}
.rule strong { display: block; margin-bottom: 4px; }
.rule.ok { border-color: #a7f3d0; background: var(--primary-bg); }
.rule.loose { border-color: #fde68a; background: #fffbeb; }
.toolbar {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid var(--border);
}
.selected-tip { color: var(--muted); min-width: 72px; }
.btn {
height: 36px;
padding: 0 14px;
border-radius: 8px;
border: 1px solid var(--border);
background: #fff;
cursor: pointer;
font-size: 14px;
display: inline-flex;
align-items: center;
gap: 6px;
}
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn-main {
background: var(--primary);
border-color: var(--primary);
color: #fff;
}
.dropdown { position: relative; }
.menu {
display: none;
position: absolute;
top: 40px;
left: 0;
z-index: 20;
min-width: 180px;
background: #fff;
border: 1px solid var(--border);
border-radius: 8px;
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
padding: 6px;
}
.menu.open { display: block; }
.menu button {
width: 100%;
text-align: left;
border: 0;
background: transparent;
padding: 8px 10px;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
}
.menu button:hover:not(:disabled) { background: var(--primary-bg); color: var(--primary-dark); }
.menu button:disabled { color: #94a3b8; cursor: not-allowed; }
.menu .hint {
font-size: 11px;
color: var(--muted);
padding: 0 10px 6px;
line-height: 1.35;
}
table.list {
width: 100%;
border-collapse: collapse;
}
table.list th, table.list td {
padding: 10px 8px;
border-bottom: 1px solid var(--border);
text-align: left;
font-size: 13px;
}
table.list th { color: var(--muted); font-weight: 600; background: #f8fafc; }
table.list tr:hover td { background: #f8fafc; }
table.list tr.picked td { background: var(--primary-bg); }
.tag {
display: inline-block;
padding: 1px 8px;
border-radius: 999px;
font-size: 12px;
background: #e2e8f0;
color: #334155;
}
.tag.in { background: #d1fae5; color: #065f46; }
.tag.out { background: #dbeafe; color: #1e40af; }
.msg {
margin-top: 12px;
padding: 10px 12px;
border-radius: 8px;
font-size: 13px;
display: none;
}
.msg.show { display: block; }
.msg.err { background: #fef2f2; color: var(--danger); border: 1px solid #fecaca; }
.msg.warn { background: #fffbeb; color: #92400e; border: 1px solid #fde68a; }
.msg.ok { background: var(--primary-bg); color: #065f46; border: 1px solid #a7f3d0; }
.grid2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
@media (max-width: 900px) {
.rules, .grid2 { grid-template-columns: 1fr; }
}
.slip {
border: 1px solid #cbd5e1;
border-radius: 8px;
padding: 14px;
background: #fff;
min-height: 280px;
overflow: auto;
}
.slip .inbound-like,
.delivery-like,
.subpack-like {
font-family: SimSun, "Songti SC", serif;
color: #111;
max-width: 960px;
margin: 0 auto;
padding: 8px 10px 24px;
}
.slip-title {
position: relative;
min-height: 56px;
margin-bottom: 14px;
text-align: center;
padding: 4px 40px 0;
}
.slip-title .co {
font-size: 22px;
font-weight: 700;
letter-spacing: 6px;
line-height: 1.35;
}
.slip-title .doc {
font-size: 22px;
font-weight: 700;
letter-spacing: 10px;
margin-top: 2px;
}
.slip-info {
width: 100%;
border-collapse: collapse;
margin-bottom: 10px;
font-size: 14px;
}
.slip-info td { padding: 4px 0; vertical-align: middle; }
.slip-info .lab { white-space: nowrap; }
.type-checks {
display: flex;
gap: 12px;
font-size: 14px;
margin-bottom: 4px;
justify-content: flex-start;
}
.type-checks label { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
.slip-product {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
font-size: 13px;
}
.slip-product th,
.slip-product td {
border: 1px solid #333 !important;
padding: 8px 4px;
text-align: center;
word-break: break-all;
}
.slip-product th {
font-weight: bold;
background: #fff;
}
.slip-product .remark,
.slip-product .terms {
text-align: left;
padding-left: 8px;
font-size: 12px;
line-height: 1.45;
}
.slip-sign {
width: 100%;
margin-top: 16px;
font-size: 13px;
border-collapse: collapse;
}
.slip-sign td {
padding: 8px 4px;
vertical-align: top;
}
.bucket-checks {
display: inline-flex;
gap: 14px;
margin-left: 12px;
flex-wrap: wrap;
font-size: 13px;
}
.subpack-no {
text-align: right;
font-size: 13px;
margin-bottom: 6px;
color: #333;
}
.empty {
color: var(--muted);
text-align: center;
padding: 48px 12px;
}
.legend { font-size: 12px; color: var(--muted); margin-top: 8px; }
code { background: #f1f5f9; padding: 1px 5px; border-radius: 4px; font-size: 12px; }
</style>
</head>
<body>
<div class="wrap">
<h1>运输计划 · 多选合打单据 Demo</h1>
<p class="sub">
勾选多条计划 →「打印单据」→ 按规则校验 → 右侧预览「产品合并到一张单据」。
本页仅演示交互与合打规则,非最终生产版式。
</p>
<section class="panel">
<h2>合打规则(客户确认稿)</h2>
<div class="rules">
<div class="rule ok">
<strong>入库单</strong>
勾选计划的<strong>客户名称一致</strong>且<strong>计划日期一致</strong>才可合打;产品行合并到一张入库单。
</div>
<div class="rule ok">
<strong>送(提)货单</strong>
同入库单:客户名称 + 计划日期均须一致。
</div>
<div class="rule loose">
<strong>分装单</strong>
<strong>不校验</strong>客户名称、计划日期;勾选即可合打产品到一张分装单。
</div>
</div>
<p class="legend">
合打策略:沿用现网三类单据模版,<strong>只合并物料表格行</strong>(分行列出,不合并数量);
入库/送货「单据编号」= 各计划号;分装「序号」递增。
表头:客户/日期取共有值(入库、送货须一致);<strong>备注</strong>= 各计划备注换行拼接(一行一计划);
<strong>收货联系方式留空</strong>;不填车辆信息。
</p>
</section>
<div class="grid2">
<section class="panel">
<h2>运输计划列表(多选)</h2>
<div class="toolbar">
<span class="selected-tip" id="selCount">已选 0 项</span>
<div class="dropdown">
<button class="btn" id="printBtn" type="button" disabled>
🖨 打印单据 ▾
</button>
<div class="menu" id="printMenu">
<div class="hint" id="menuHint">请先勾选计划</div>
<button type="button" data-cmd="inbound" disabled>入库单</button>
<button type="button" data-cmd="delivery" disabled>送(提)货单</button>
<button type="button" data-cmd="subpack" disabled>分装单</button>
</div>
</div>
<button class="btn" type="button" id="clearBtn">清空选择</button>
</div>
<table class="list">
<thead>
<tr>
<th style="width:36px"><input type="checkbox" id="checkAll" title="全选当前页" /></th>
<th>计划号</th>
<th>计划日期</th>
<th>客户名称</th>
<th>类型</th>
<th>产品摘要</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
<div class="msg" id="toast"></div>
</section>
<section class="panel">
<h2>合打预览</h2>
<div class="slip" id="preview">
<div class="empty">勾选计划并选择打印类型后,在此预览合并产品明细</div>
</div>
</section>
</div>
</div>
<script>
/** @typedef {{ id: string, planNo: string, planDate: string, customerName: string, planType: 'inbound'|'outbound'|'direct', remark?: string, products: {name:string, model:string, qty:number, unit:string}[] }} Plan */
/** @type {Plan[]} */
const PLANS = [
{
id: '1', planNo: 'PO20260727001', planDate: '2026-07-27', customerName: '安徽云栋化工有限公司',
planType: 'inbound', remark: '上午到厂,注意卸货安全',
products: [
{ name: '亚硫酸钠', model: '液体', qty: 12, unit: '吨' },
{ name: '试剂硝酸', model: 'AR', qty: 2, unit: '吨' }
]
},
{
id: '2', planNo: 'PO20260727002', planDate: '2026-07-27', customerName: '安徽云栋化工有限公司',
planType: 'inbound', remark: '附带质检报告',
products: [
{ name: '次氯酸钠', model: '固体', qty: 8, unit: '吨' }
]
},
{
id: '3', planNo: 'PO20260727003', planDate: '2026-07-27', customerName: '合肥市皖和化工有限公司',
planType: 'inbound', remark: '',
products: [
{ name: '98% 工业硫酸', model: 'AR', qty: 20, unit: '吨' }
]
},
{
id: '4', planNo: 'PO20260728001', planDate: '2026-07-28', customerName: '安徽云栋化工有限公司',
planType: 'inbound', remark: '改期后计划',
products: [
{ name: '试剂氨水', model: 'AR', qty: 5, unit: '吨' }
]
},
{
id: '5', planNo: 'PO20260727011', planDate: '2026-07-27', customerName: '中国长江实业',
planType: 'outbound', remark: '客户自提点卸货',
products: [
{ name: 'TEST液碱', model: '31%', qty: 30, unit: '吨' }
]
},
{
id: '6', planNo: 'PO20260727012', planDate: '2026-07-27', customerName: '中国长江实业',
planType: 'outbound', remark: '需带防护用品',
products: [
{ name: 'TEST盐酸', model: '30%', qty: 15, unit: '吨' },
{ name: 'TEST液碱', model: '31%', qty: 10, unit: '吨' }
]
},
{
id: '7', planNo: 'PO20260729001', planDate: '2026-07-29', customerName: '中国长江实业',
planType: 'outbound', remark: '',
products: [
{ name: 'TEST液碱', model: '31%', qty: 6, unit: '吨' }
]
}
]
const selected = new Set()
let menuOpen = false
function typeLabel(t) {
return t === 'inbound' ? '入库单' : t === 'outbound' ? '出库单' : '直销单'
}
function typeClass(t) {
return t === 'inbound' ? 'in' : t === 'outbound' ? 'out' : ''
}
function printableCommands(planType) {
if (planType === 'inbound') return ['inbound']
if (planType === 'outbound') return ['delivery', 'subpack']
return []
}
function selectedPlans() {
return PLANS.filter(p => selected.has(p.id))
}
function sameCustomerAndDate(plans) {
if (!plans.length) return false
const c = plans[0].customerName
const d = plans[0].planDate
return plans.every(p => p.customerName === c && p.planDate === d)
}
function canMerge(cmd, plans) {
if (plans.length < 1) return { ok: false, reason: '请至少勾选 1 条运输计划' }
if (plans.length === 1) {
const allow = printableCommands(plans[0].planType)
if (!allow.includes(cmd)) {
return { ok: false, reason: `当前单据类型(${typeLabel(plans[0].planType)})不支持打印该单据` }
}
return { ok: true, reason: '' }
}
// multi
if (cmd === 'subpack') {
const bad = plans.filter(p => !printableCommands(p.planType).includes('subpack'))
if (bad.length) {
return { ok: false, reason: `分装单仅支持出库类计划;以下不可打:${bad.map(p => p.planNo).join('、')}` }
}
return { ok: true, reason: '' }
}
if (cmd === 'inbound') {
const bad = plans.filter(p => p.planType !== 'inbound')
if (bad.length) {
return { ok: false, reason: `入库单仅支持入库类计划;冲突:${bad.map(p => p.planNo).join('、')}` }
}
if (!sameCustomerAndDate(plans)) {
return { ok: false, reason: '合打入库单要求:客户名称一致 且 计划日期一致' }
}
return { ok: true, reason: '' }
}
if (cmd === 'delivery') {
const bad = plans.filter(p => p.planType !== 'outbound')
if (bad.length) {
return { ok: false, reason: `送(提)货单仅支持出库类计划;冲突:${bad.map(p => p.planNo).join('、')}` }
}
if (!sameCustomerAndDate(plans)) {
return { ok: false, reason: '合打送(提)货单要求:客户名称一致 且 计划日期一致' }
}
return { ok: true, reason: '' }
}
return { ok: false, reason: '未知单据类型' }
}
function renderList() {
const tb = document.getElementById('tbody')
tb.innerHTML = PLANS.map(p => {
const checked = selected.has(p.id) ? 'checked' : ''
const picked = selected.has(p.id) ? 'picked' : ''
const prod = p.products.map(x => `${x.name}×${x.qty}`).join(',')
return `<tr class="${picked}" data-id="${p.id}">
<td><input type="checkbox" class="row-check" data-id="${p.id}" ${checked} /></td>
<td>${p.planNo}</td>
<td>${p.planDate}</td>
<td>${p.customerName}</td>
<td><span class="tag ${typeClass(p.planType)}">${typeLabel(p.planType)}</span></td>
<td>${prod}</td>
</tr>`
}).join('')
document.getElementById('selCount').textContent = `已选 ${selected.size} 项`
document.getElementById('printBtn').disabled = selected.size === 0
document.getElementById('checkAll').checked = selected.size === PLANS.length && PLANS.length > 0
updateMenuState()
}
function updateMenuState() {
const plans = selectedPlans()
const hint = document.getElementById('menuHint')
const cmds = [
['inbound', '入库单'],
['delivery', '送(提)货单'],
['subpack', '分装单']
]
cmds.forEach(([cmd]) => {
const btn = document.querySelector(`#printMenu button[data-cmd="${cmd}"]`)
const r = canMerge(cmd, plans)
btn.disabled = !r.ok
btn.title = r.ok ? '可合打' : r.reason
})
if (!plans.length) {
hint.textContent = '请先勾选计划'
} else if (plans.length === 1) {
hint.textContent = '单条:沿用原打印能力'
} else {
const okAny = cmds.some(([c]) => canMerge(c, plans).ok)
hint.textContent = okAny
? `已选 ${plans.length} 条 · 灰色项表示不满足合打条件`
: `已选 ${plans.length} 条 · 当前选择不满足任一合打条件`
}
}
function showToast(text, type) {
const el = document.getElementById('toast')
el.className = 'msg show ' + type
el.textContent = text
}
function formatPlanDateChinese(ymd) {
if (!ymd) return ''
const [y, m, d] = ymd.split('-')
return `${Number(y)}年${Number(m)}月${Number(d)}日`
}
function productNoStub(name) {
// demo only
const map = {
'亚硫酸钠': 'WL-001',
'试剂硝酸': 'WL-002',
'次氯酸钠': 'WL-003',
'98% 工业硫酸': 'WL-004',
'试剂氨水': 'WL-005',
'TEST液碱': 'WL-101',
'TEST盐酸': 'WL-102'
}
return map[name] || '—'
}
function mergePlanRemarks(plans) {
// 一行一个计划备注;空备注跳过;前缀计划号便于对照
return plans
.map(p => {
const text = (p.remark || '').trim()
if (!text) return ''
return `${p.planNo}:${text}`
})
.filter(Boolean)
.join('<br />')
}
function collectProductRows(plans) {
const rows = []
plans.forEach(p => {
p.products.forEach(prod => {
rows.push({
planNo: p.planNo,
productNo: productNoStub(prod.name),
name: prod.name,
model: prod.model,
qty: prod.qty,
unit: prod.unit
})
})
})
return rows
}
function renderInboundSlip(plans) {
const customer = plans[0].customerName
const dateCn = formatPlanDateChinese(plans[0].planDate)
const rows = collectProductRows(plans)
const body = rows.map(r => `<tr>
<td>${r.planNo}</td>
<td>${r.productNo}</td>
<td>${r.name}</td>
<td>${r.model}</td>
<td>${r.unit}</td>
<td>${r.qty}</td>
<td></td>
<td></td>
</tr>`).join('')
document.getElementById('preview').innerHTML = `
<div class="inbound-like">
<div class="slip-title">
<div class="co">安庆市山峡化工有限公司</div>
<div class="doc">入&nbsp;&nbsp;库&nbsp;&nbsp;单</div>
</div>
<table class="slip-info">
<tr>
<td style="width:58%"><span class="lab">送货单位:</span>${customer}</td>
<td style="width:42%;text-align:right"><span class="lab">日&nbsp;期:</span>${dateCn}</td>
</tr>
</table>
<table class="slip-product">
<colgroup>
<col style="width:14%" /><col style="width:14%" /><col style="width:20%" />
<col style="width:8%" /><col style="width:6%" /><col style="width:10%" />
<col style="width:14%" /><col style="width:14%" />
</colgroup>
<thead>
<tr>
<th>单据编号</th>
<th>物料编号</th>
<th>物料名称</th>
<th>规格</th>
<th>单位</th>
<th>数量</th>
<th>产品规格</th>
<th>实收数量</th>
</tr>
</thead>
<tbody>
${body}
<tr><td colspan="8" class="remark">备注:${mergePlanRemarks(plans) || ''}</td></tr>
</tbody>
</table>
<table class="slip-sign">
<tr>
<td>仓库:</td>
<td>审核:</td>
<td>客户签收:</td>
<td>司机/送货人(附车牌):</td>
</tr>
</table>
</div>
`
}
function renderDeliverySlip(plans) {
const customer = plans[0].customerName
const dateCn = formatPlanDateChinese(plans[0].planDate)
const rows = collectProductRows(plans)
const remarkHtml = mergePlanRemarks(plans)
const body = rows.map(r => `<tr>
<td>${r.planNo}</td>
<td>${r.productNo}</td>
<td>${r.name}</td>
<td>${r.model}</td>
<td>${r.unit}</td>
<td>${r.qty}</td>
<td></td>
<td></td>
</tr>`).join('')
document.getElementById('preview').innerHTML = `
<div class="delivery-like">
<div class="slip-title">
<div class="co">安庆市山峡化工有限公司</div>
<div class="doc">送&nbsp;(提)&nbsp;货&nbsp;单</div>
</div>
<table class="slip-info">
<tr>
<td style="width:34%"><span class="lab">收货单位:</span>${customer}</td>
<td style="width:33%"><span class="lab">收货联系方式:</span></td>
<td style="width:33%;text-align:right;vertical-align:top;">
<div class="type-checks">
<label><input type="checkbox" />自提</label>
<label><input type="checkbox" checked />出库</label>
<label><input type="checkbox" />直销</label>
</div>
<div><span class="lab">日&nbsp;期:</span>${dateCn}</div>
</td>
</tr>
</table>
<table class="slip-product">
<colgroup>
<col style="width:14%" /><col style="width:14%" /><col style="width:20%" />
<col style="width:8%" /><col style="width:6%" /><col style="width:10%" />
<col style="width:14%" /><col style="width:14%" />
</colgroup>
<thead>
<tr>
<th>单据编号</th>
<th>物料编号</th>
<th>物料名称</th>
<th>规格</th>
<th>单位</th>
<th>数量</th>
<th>产品规格</th>
<th>实收数量</th>
</tr>
</thead>
<tbody>
${body}
<tr><td colspan="8" class="remark">备注:${remarkHtml || ''}</td></tr>
<tr>
<td colspan="5" class="terms">
1.本送货单如购销双方未签订合同,视为有效;<br />
2.本次送货含桶____个,回收____个,结存____个;
</td>
<td colspan="3" class="remark" style="font-size:11px;line-height:1.5;">
地址:安庆市高新区霞虹路5号<br />
送货单位固话:0556-5999710<br />
联系人电话:方总13395560066
</td>
</tr>
</tbody>
</table>
<table class="slip-sign">
<tr>
<td>仓库:</td>
<td>审核:</td>
<td>客户签收:</td>
<td>司机/送货人(附车牌):</td>
</tr>
</table>
</div>
`
}
function renderSubpackSlip(plans) {
// 分装单允许跨客户/跨日期:沿用现网模版字段;仅扩展物料表行
const first = plans[0]
const orderDate = formatPlanDateChinese(first.planDate)
const rows = collectProductRows(plans)
const remarkHtml = mergePlanRemarks(plans)
const body = rows.map((r, idx) => `<tr>
<td>${idx + 1}</td>
<td>${r.name}</td>
<td>${r.model}</td>
<td></td>
<td>${r.unit}</td>
<td>${r.qty}</td>
<td><input type="checkbox" /></td>
</tr>`).join('')
const slipNo = `SXFZ${first.planDate.replace(/-/g, '')}-001`
document.getElementById('preview').innerHTML = `
<div class="subpack-like">
<div class="slip-title">
<div class="co">安庆市山峡化工有限公司</div>
<div class="doc">分&nbsp;装&nbsp;单</div>
</div>
<div class="subpack-no">${slipNo}</div>
<table class="slip-info">
<tr>
<td><span class="lab">下单日期:</span>${orderDate}</td>
<td><span class="lab">交货日期:</span>${orderDate}</td>
<td><span class="lab">下单人:</span></td>
<td><span class="lab">经手人:</span></td>
</tr>
</table>
<table class="slip-product">
<colgroup>
<col style="width:8%" /><col style="width:22%" /><col style="width:16%" />
<col style="width:16%" /><col style="width:10%" /><col style="width:12%" />
<col style="width:16%" />
</colgroup>
<thead>
<tr>
<th>序号</th>
<th>产品名称</th>
<th>产品型号</th>
<th>包装规格</th>
<th>单位</th>
<th>数量</th>
<th>是否完成</th>
</tr>
</thead>
<tbody>
${body}
<tr>
<td colspan="7" class="remark">
备注:${remarkHtml || ''}
<span class="bucket-checks">
<label><input type="checkbox" />全新桶</label>
<label><input type="checkbox" />是否回收桶</label>
<label><input type="checkbox" />桶循环</label>
</span>
</td>
</tr>
</tbody>
</table>
<table class="slip-sign">
<tr>
<td>仓库:</td>
<td>审核:</td>
<td>客户签收:</td>
<td>司机/送货人(附车牌):</td>
</tr>
</table>
</div>
`
}
function renderPreview(cmd, plans) {
if (cmd === 'inbound') {
renderInboundSlip(plans)
return
}
if (cmd === 'delivery') {
renderDeliverySlip(plans)
return
}
renderSubpackSlip(plans)
}
function tryPrint(cmd) {
const plans = selectedPlans()
const r = canMerge(cmd, plans)
menuOpen = false
document.getElementById('printMenu').classList.remove('open')
if (!r.ok) {
showToast(r.reason, 'err')
document.getElementById('preview').innerHTML = `<div class="empty">${r.reason}</div>`
return
}
const label = cmd === 'inbound' ? '入库单' : cmd === 'delivery' ? '送(提)货单' : '分装单'
showToast(
plans.length === 1
? `将打开单计划「${label}」打印(与现网一致)`
: `将合并 ${plans.length} 条计划的产品到一张「${label}」`,
'ok'
)
renderPreview(cmd, plans)
}
document.getElementById('tbody').addEventListener('change', (e) => {
const t = e.target
if (!(t instanceof HTMLInputElement) || !t.classList.contains('row-check')) return
if (t.checked) selected.add(t.dataset.id)
else selected.delete(t.dataset.id)
renderList()
document.getElementById('toast').classList.remove('show')
})
document.getElementById('checkAll').addEventListener('change', (e) => {
const on = e.target.checked
selected.clear()
if (on) PLANS.forEach(p => selected.add(p.id))
renderList()
})
document.getElementById('clearBtn').addEventListener('click', () => {
selected.clear()
renderList()
document.getElementById('preview').innerHTML = '<div class="empty">勾选计划并选择打印类型后,在此预览合并产品明细</div>'
document.getElementById('toast').classList.remove('show')
})
document.getElementById('printBtn').addEventListener('click', (e) => {
e.stopPropagation()
menuOpen = !menuOpen
document.getElementById('printMenu').classList.toggle('open', menuOpen)
updateMenuState()
})
document.getElementById('printMenu').addEventListener('click', (e) => {
const btn = e.target.closest('button[data-cmd]')
if (!btn || btn.disabled) return
tryPrint(btn.dataset.cmd)
})
document.addEventListener('click', () => {
if (!menuOpen) return
menuOpen = false
document.getElementById('printMenu').classList.remove('open')
})
// 预设:勾选同客户同日的两条入库计划,方便演示合打
selected.add('1')
selected.add('2')
renderList()
tryPrint('inbound')
showToast('已预勾选同客户同日两条入库计划,并按现网入库单版式展示合打预览(无「来源计划」「产品行数」)。', 'warn')
</script>
</body>
</html>
-1
View File
@@ -1 +0,0 @@
87807
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_product_py", "label": "contract_product.py", "file_type": "code", "source_file": "app/crud/crm/contract_product.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_product_row_to_dict", "label": "_row_to_dict()", "file_type": "code", "source_file": "app/crud/crm/contract_product.py", "source_location": "L21"}, {"id": "crmcontractproduct", "label": "CrmContractProduct", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/crud/crm/contract_product.py"}, {"id": "any", "label": "Any", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/crud/crm/contract_product.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_product_tier_row_to_dict", "label": "_tier_row_to_dict()", "file_type": "code", "source_file": "app/crud/crm/contract_product.py", "source_location": "L54"}, {"id": "crmcontractproductpricetier", "label": "CrmContractProductPriceTier", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/crud/crm/contract_product.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_product_list_contract_products", "label": "list_contract_products()", "file_type": "code", "source_file": "app/crud/crm/contract_product.py", "source_location": "L78"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/crud/crm/contract_product.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_product_decimal_value", "label": "_decimal_value()", "file_type": "code", "source_file": "app/crud/crm/contract_product.py", "source_location": "L122"}, {"id": "decimal", "label": "Decimal", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/crud/crm/contract_product.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_product_get_field", "label": "_get_field()", "file_type": "code", "source_file": "app/crud/crm/contract_product.py", "source_location": "L130"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_product_parse_optional_date", "label": "_parse_optional_date()", "file_type": "code", "source_file": "app/crud/crm/contract_product.py", "source_location": "L137"}, {"id": "date", "label": "date", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/crud/crm/contract_product.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_product_calculate_subtotal", "label": "calculate_subtotal()", "file_type": "code", "source_file": "app/crud/crm/contract_product.py", "source_location": "L155"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_product_letter_oss_ids_to_json", "label": "_letter_oss_ids_to_json()", "file_type": "code", "source_file": "app/crud/crm/contract_product.py", "source_location": "L164"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_product_insert_contract_product", "label": "insert_contract_product()", "file_type": "code", "source_file": "app/crud/crm/contract_product.py", "source_location": "L174"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_product_batch_insert_contract_products", "label": "batch_insert_contract_products()", "file_type": "code", "source_file": "app/crud/crm/contract_product.py", "source_location": "L262"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_product_delete_price_tiers_by_contract_id", "label": "delete_price_tiers_by_contract_id()", "file_type": "code", "source_file": "app/crud/crm/contract_product.py", "source_location": "L306"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_product_delete_products_by_contract_id", "label": "delete_products_by_contract_id()", "file_type": "code", "source_file": "app/crud/crm/contract_product.py", "source_location": "L323"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_product_save_price_tiers_for_product", "label": "save_price_tiers_for_product()", "file_type": "code", "source_file": "app/crud/crm/contract_product.py", "source_location": "L339"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_product_rationale_1", "label": "CRM \u5408\u540c\u4ea7\u54c1\u660e\u7ec6 CRUD \u2014 \u5bf9\u7167 CrmContractProductServiceImpl.java", "file_type": "rationale", "source_file": "app/crud/crm/contract_product.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00Line truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_io_order_py", "label": "io_order.py", "file_type": "code", "source_file": "app/schemas/pl/io_order.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_io_order_ioorderitemread", "label": "IoOrderItemRead", "file_type": "code", "source_file": "app/schemas/pl/io_order.py", "source_location": "L11"}, {"id": "basemodel", "label": "BaseModel", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_io_order_ioorderitempatch", "label": "IoOrderItemPatch", "file_type": "code", "source_file": "app/schemas/pl/io_order.py", "source_location": "L30"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_io_order_ioorderpatch", "label": "IoOrderPatch", "file_type": "code", "source_file": "app/schemas/pl/io_order.py", "source_location": "L36"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_io_order_ioorderread", "label": "IoOrderRead", "file_type": "code", "source_file": "app/schemas/pl/io_order.py", "source_location": "L45"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_io_order_ioorderlistresponse", "label": "IoOrderListResponse", "file_type": "code", "source_file": "app/schemas/pl/io_order.py", "source_location": "L84"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_io_order_publicioorderitemfeedback", "label": "PublicIoOrderItemFeedback", "file_type": "code", "source_file": "app/schemas/pl/io_order.py", "source_location": "L89"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_io_order_publicioorderfeedback", "label": "PublicIoOrderFeedback", "file_type": "code", "source_file": "app/schemas/pl/io_order.py", "source_location": "L95"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_io_order_rationale_1", "label": "\u5165\u5e93\u5355 / \u51fa\u5e93\u5355 Pydantic schemas", "file_type": "rationale", "source_file": "app/schemas/pl/io_order.py", "source_location": "L1"}], "edges": [{"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_io_order_py", "target": "datetime", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/schemas/pl/io_order.py", "source_location": "L4", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_io_order_py", "target": "decimal", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/schemas/pl/io_order.py", "source_location": "L5", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_io_order_py", "target": "typing", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/schemas/pl/io_order.py", "source_location": "L6", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_io_order_py", "target": "pydantic", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/schemas/pl/io_order.py", "source_location": "L8", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_io_order_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_io_order_ioorderitemread", "relation": "contains", "confidence": "EXTRACTED", "source_file": "app/schemas/pl/io_order.py", "source_location": "L11", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_io_order_ioorderitemread", "target": "basemodel", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "app/schemas/pl/io_order.py", "source_location": "L11", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_io_order_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_io_order_ioorderitempatch", "relation": "contains", "confidence": "EXTRACTED", "source_file": "app/schemas/pl/io_order.py", "source_location": "L30", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_io_order_ioorderitempatch", "target": "basemodel", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "app/schemas/pl/io_order.py", "source_location": "L30", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_io_order_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_io_order_ioorderpatch", "relation": "contains", "confidence": "EXTRACTED", "source_file": "app/schemas/pl/io_order.py", "source_location": "L36", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_io_order_ioorderpatch", "target": "basemodel", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "app/schemas/pl/io_order.py", "source_location": "L36", "weight": 1.0}, {"source": "users_wuzilong_work_00cLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_pl_models_py", "label": "pl_models.py", "file_type": "code", "source_file": "app/models/pl_models.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_pl_models_plcarrier", "label": "PlCarrier", "file_type": "code", "source_file": "app/models/pl_models.py", "source_location": "L28"}, {"id": "base", "label": "Base", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_pl_models_plvehicle", "label": "PlVehicle", "file_type": "code", "source_file": "app/models/pl_models.py", "source_location": "L57"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_pl_models_plpersonnel", "label": "PlPersonnel", "file_type": "code", "source_file": "app/models/pl_models.py", "source_location": "L83"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_pl_models_pltransportaddress", "label": "PlTransportAddress", "file_type": "code", "source_file": "app/models/pl_models.py", "source_location": "L117"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_pl_models_plcert", "label": "PlCert", "file_type": "code", "source_file": "app/models/pl_models.py", "source_location": "L141"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_pl_models_pldriver", "label": "PlDriver", "file_type": "code", "source_file": "app/models/pl_models.py", "source_location": "L167"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_pl_models_plvehiclepersonnel", "label": "PlVehiclePersonnel", "file_type": "code", "source_file": "app/models/pl_models.py", "source_location": "L193"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_pl_models_plvehicledriver", "label": "PlVehicleDriver", "file_type": "code", "source_file": "app/models/pl_models.py", "source_location": "L214"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_pl_models_plplan", "label": "PlPlan", "file_type": "code", "source_file": "app/models/pl_models.py", "source_location": "L239"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_pl_models_plplanproduct", "label": "PlPlanProduct", "file_type": "code", "source_file": "app/models/pl_models.py", "source_location": "L339"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_pl_models_plplanproductbrandqty", "label": "PlPlanProductBrandQty", "file_type": "code", "source_file": "app/models/pl_models.py", "source_location": "L398"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_pl_models_plplanchange", "label": "PlPlanChange", "file_type": "code", "source_file": "app/models/pl_models.py", "source_location": "L420"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_pl_models_pltransport", "label": "PlTransport", "file_type": "code", "source_file": "app/models/pl_models.py", "source_location": "L447"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_pl_models_pltransportdblink", "label": "PlTransportDbLink", "file_type": "code", "source_file": "app/models/pl_models.py", "source_location": "L509"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_pl_models_plplantransport", "label": "PlPlanTransport", "file_type": "code", "source_file": "app/models/pl_models.py", "source_location": "L532"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_pl_models_pltransportsitelink", "label": "PlTransportSiteLink", "file_type": "code", "source_file": "app/models/pl_models.py", "source_location": "L551"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_pl_models_plinboundorder", "label": "PlInboundOrder", "file_type": "code", "source_file": "app/models/pl_models.py", "source_location": "L584"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_pl_models_plinboundorderitem", "label": "PlInboundOrderItem", "file_type": "code", "source_file": "app/models/pl_models.py", "source_location": "L614"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_pl_models_ploutboundorder", "label": "PlOutboundOrder", "file_type": "code", "source_file": "app/models/pl_models.py", "source_location": "L640"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_pl_models_ploutboundorderitem", "label": "PlOutboundOrderItem", "file_type": "code", "source_file": "app/models/pl_models.py", "source_location": "L670"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_pl_models_pltransportapproval", "label": "PlTransportApproval", "file_type": "code", "source_file": "app/models/pl_models.py", "source_location": "L696"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_pl_models_plfreightsettlement", "label": "PlFreightSettlement", "file_type": "code", "source_file": "app/models/pl_models.py", "source_location": "L725"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_pl_modelLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_brand_archive_py", "label": "pl_brand_archive.py", "file_type": "code", "source_file": "app/services/pl_brand_archive.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_brand_archive_validate_brand_qty_rows", "label": "validate_brand_qty_rows()", "file_type": "code", "source_file": "app/services/pl_brand_archive.py", "source_location": "L11"}, {"id": "decimal", "label": "Decimal", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_brand_archive.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_brand_archive_persist_plan_product_brand_qtys", "label": "persist_plan_product_brand_qtys()", "file_type": "code", "source_file": "app/services/pl_brand_archive.py", "source_location": "L36"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_brand_archive.py"}, {"id": "any", "label": "Any", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_brand_archive.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_brand_archive_rationale_47", "label": "\u8f6f\u5220\u65e7\u54c1\u724c\u91cf\u884c\u540e\u6309\u5f52\u6863\u7ed3\u679c\u63d2\u5165\uff1b\u65e0\u54c1\u724c\u7ea6\u5b9a\u5219\u53ea\u8f6f\u5220\u4e0d\u63d2\u5165\u3002", "file_type": "rationale", "source_file": "app/services/pl_brand_archive.py", "source_location": "L47"}], "edges": [{"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_brand_archive_py", "target": "decimal", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/services/pl_brand_archive.py", "source_location": "L2", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_brand_archive_py", "target": "typing", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/services/pl_brand_archive.py", "source_location": "L3", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_brand_archive_py", "target": "sqlalchemy_orm", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/services/pl_brand_archive.py", "source_location": "L5", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_brand_archive_py", "target": "app_core_utils", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/services/pl_brand_archive.py", "source_location": "L7", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_brand_archive_py", "target": "app_models_pl_models", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/services/pl_brand_archive.py", "source_location": "L8", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_brand_archive_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_brand_archive_validate_brand_qty_rows", "relation": "contains", "confidence": "EXTRACTED", "source_file": "app/services/pl_brand_archive.py", "source_location": "L11", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_brand_archive_validate_brand_qty_rows", "target": "decimal", "relation": "references", "context": "parameter_type", "confidence": "EXTRACTED", "source_file": "app/services/pl_brand_archive.py", "source_location": "L11", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_brand_archive_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_brand_archive_persist_plan_product_brand_qtys", "relation": "contains", "confidence": "EXTRACTED", "source_file": "app/services/pl_brand_archive.py", "source_location": "L36", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_brand_archive_persist_plan_product_brand_qtys", "target": "session", "relation": "references", "context": "parameter_type", "confidence": "EXTRACTED", "source_file": "app/services/pl_brand_archive.py", "source_location": "L36", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_brand_archive_persist_plan_product_brand_qtys", "target": "any", "relation": "references", "context": "generic_arg", "confidence": "EXTRACTED", "source_file": "app/services/pl_brand_archive.py", "source_location": "L36", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_brand_archive_validate_brand_qty_rows", "target": "decimal", "relation": "calls", "context": "call", "confideLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_quote_product_py", "label": "quote_product.py", "file_type": "code", "source_file": "app/crud/crm/quote_product.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_quote_product_ensure_quote_parent_scope", "label": "_ensure_quote_parent_scope()", "file_type": "code", "source_file": "app/crud/crm/quote_product.py", "source_location": "L18"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/crud/crm/quote_product.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_quote_product_decimal_value", "label": "_decimal_value()", "file_type": "code", "source_file": "app/crud/crm/quote_product.py", "source_location": "L26"}, {"id": "any", "label": "Any", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/crud/crm/quote_product.py"}, {"id": "decimal", "label": "Decimal", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/crud/crm/quote_product.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_quote_product_get_field", "label": "_get_field()", "file_type": "code", "source_file": "app/crud/crm/quote_product.py", "source_location": "L34"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_quote_product_calculate_subtotal", "label": "calculate_subtotal()", "file_type": "code", "source_file": "app/crud/crm/quote_product.py", "source_location": "L41"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_quote_product_normalize_line_brand", "label": "normalize_line_brand()", "file_type": "code", "source_file": "app/crud/crm/quote_product.py", "source_location": "L51"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_quote_product_product_has_brands", "label": "product_has_brands()", "file_type": "code", "source_file": "app/crud/crm/quote_product.py", "source_location": "L64"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_quote_product_resolve_line_brand_fields", "label": "_resolve_line_brand_fields()", "file_type": "code", "source_file": "app/crud/crm/quote_product.py", "source_location": "L76"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_quote_product_row_to_dict", "label": "_row_to_dict()", "file_type": "code", "source_file": "app/crud/crm/quote_product.py", "source_location": "L97"}, {"id": "crmquoteproduct", "label": "CrmQuoteProduct", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/crud/crm/quote_product.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_quote_product_product_row_to_dict", "label": "product_row_to_dict()", "file_type": "code", "source_file": "app/crud/crm/quote_product.py", "source_location": "L128"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_quote_product_list_quote_products", "label": "list_quote_products()", "file_type": "code", "source_file": "app/crud/crm/quote_product.py", "source_location": "L132"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_quote_product_get_quote_product", "label": "get_quote_product()", "file_type": "code", "source_file": "app/crud/crm/quote_product.py", "source_location": "L154"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_quote_product_insert_quote_product", "label": "insert_quote_product()", "file_type": "code", "source_file": "app/crud/crm/quote_product.py", "source_location": "L162"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_quote_product_update_quote_product", "label": "update_quote_product()", "file_type": "code", "source_file": "app/crud/crm/quote_product.py", "source_location": "L241"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_quote_product_soft_delete_quote_products", "label": "soft_delete_quote_products()", "file_type": "code", "source_file": "app/crud/crm/quote_product.py", "source_location": "L305"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_quote_product_batch_insert_quote_products", "label": "batch_insert_quote_products()", "file_type": "code", "source_file": "app/crud/crm/quote_product.py", "source_location": "L339"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_quote_product_delete_products_by_quote_id", "label": "delete_products_by_quote_id()", "file_type": "code", "source_file": "app/crud/crm/quote_product.py", "source_location": "L367"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_quote_product_rationale_1", "label": "CRM \u62a5\u4ef7\u5355\u4ea7\u54c1\u660e\u7ec6 CRUD \u2014 \u5bf9\u7167 CrmQuoteProductServiceImpl.java", "file_typeLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_inventory_service_py", "label": "pl_inventory_service.py", "file_type": "code", "source_file": "app/services/pl_inventory_service.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_inventory_service_qty_delta_for_plan", "label": "qty_delta_for_plan()", "file_type": "code", "source_file": "app/services/pl_inventory_service.py", "source_location": "L24"}, {"id": "decimal", "label": "Decimal", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_inventory_service.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_inventory_service_assert_outbound_enough", "label": "assert_outbound_enough()", "file_type": "code", "source_file": "app/services/pl_inventory_service.py", "source_location": "L32"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_inventory_service_get_or_create_inventory", "label": "get_or_create_inventory()", "file_type": "code", "source_file": "app/services/pl_inventory_service.py", "source_location": "L37"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_inventory_service.py"}, {"id": "plinventory", "label": "PlInventory", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_inventory_service.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_inventory_service_apply_brand_stock_change", "label": "apply_brand_stock_change()", "file_type": "code", "source_file": "app/services/pl_inventory_service.py", "source_location": "L85"}, {"id": "plplan", "label": "PlPlan", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_inventory_service.py"}, {"id": "plplanproduct", "label": "PlPlanProduct", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_inventory_service.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_inventory_service_reverse_plan_inventory_postings", "label": "reverse_plan_inventory_postings()", "file_type": "code", "source_file": "app/services/pl_inventory_service.py", "source_location": "L154"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_inventory_service_apply_archive_inventory_for_plan", "label": "apply_archive_inventory_for_plan()", "file_type": "code", "source_file": "app/services/pl_inventory_service.py", "source_location": "L193"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_inventory_service_rationale_1", "label": "\u8ba1\u5212\u5f52\u6863\u6309\u4ea7\u54c1+\u54c1\u724c\u5199\u5165\u5e93\u5b58\u53f0\u8d26\u4e0e\u6d41\u6c34\u3002 \u76f4\u9500\u5355 (direct) \u9996\u671f\u4e0d\u8bb0\u8d26\uff1b\u5165\u5e93\u589e\u52a0\u3001\u51fa\u5e93\u6263\u51cf\uff1b\u51fa\u5e93\u4e0d\u8db3\u786c\u62e6\u622a\u3002 \u8c03\u6574\u5f52\u6863\u65f6\u5148\u6309 plan_id \u51b2\u9500\u65e7\u6d41", "file_type": "rationale", "source_file": "app/services/pl_inventory_service.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_inventory_service_rationale_161", "label": "\u51b2\u9500\u8be5\u8ba1\u5212\u5df2\u4ea7\u751f\u7684\u5e93\u5b58\u6d41\u6c34\uff08\u8c03\u6574\u5f52\u6863\u524d\u8c03\u7528\uff09\u3002", "file_type": "rationale", "source_file": "app/services/pl_inventory_service.py", "source_location": "L161"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_inventory_service_rationale_201", "label": "\u5f52\u6863\u540e\u6309\u54c1\u724c\u91cf\uff08\u6216\u65e0\u54c1\u724c actual_qty\uff09\u843d\u8d26\uff1b\u5148\u51b2\u9500\u65e7\u6d41\u6c34\u518d\u91cd\u8bb0\u3002", "file_type": "rationale", "source_file": "app/services/pl_inventory_service.py", "source_location": "L201"}], "edges": [{"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_inventory_service_py", "target": "decimal", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/services/pl_inventory_service.py", "source_location": "L6", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_inventory_service_py", "target": "typing", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/services/pl_inventory_service.py", "source_location": "L7", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_inventory_service_py", "target": "fastapi", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/services/pl_invLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_inventory_py", "label": "inventory.py", "file_type": "code", "source_file": "app/schemas/pl/inventory.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_inventory_inventoryread", "label": "InventoryRead", "file_type": "code", "source_file": "app/schemas/pl/inventory.py", "source_location": "L14"}, {"id": "basemodel", "label": "BaseModel", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_inventory_inventorytransactionread", "label": "InventoryTransactionRead", "file_type": "code", "source_file": "app/schemas/pl/inventory.py", "source_location": "L38"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_inventory_inventorywarningread", "label": "InventoryWarningRead", "file_type": "code", "source_file": "app/schemas/pl/inventory.py", "source_location": "L61"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_inventory_inventorywarningupdate", "label": "InventoryWarningUpdate", "file_type": "code", "source_file": "app/schemas/pl/inventory.py", "source_location": "L78"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_inventory_inventorywarningthreshold", "label": "InventoryWarningThreshold", "file_type": "code", "source_file": "app/schemas/pl/inventory.py", "source_location": "L84"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_inventory_documentreminderread", "label": "DocumentReminderRead", "file_type": "code", "source_file": "app/schemas/pl/inventory.py", "source_location": "L98"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_inventory_documentreminderupdate", "label": "DocumentReminderUpdate", "file_type": "code", "source_file": "app/schemas/pl/inventory.py", "source_location": "L116"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_inventory_documentreminderstatsread", "label": "DocumentReminderStatsRead", "file_type": "code", "source_file": "app/schemas/pl/inventory.py", "source_location": "L122"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_inventory_inventoryledgerquery", "label": "InventoryLedgerQuery", "file_type": "code", "source_file": "app/schemas/pl/inventory.py", "source_location": "L132"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_inventory_inventoryledgerresponse", "label": "InventoryLedgerResponse", "file_type": "code", "source_file": "app/schemas/pl/inventory.py", "source_location": "L139"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_inventory_inventorytransactionquery", "label": "InventoryTransactionQuery", "file_type": "code", "source_file": "app/schemas/pl/inventory.py", "source_location": "L144"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_inventory_inventorytransactionresponse", "label": "InventoryTransactionResponse", "file_type": "code", "source_file": "app/schemas/pl/inventory.py", "source_location": "L154"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_inventory_inventorysummaryquery", "label": "InventorySummaryQuery", "file_type": "code", "source_file": "app/schemas/pl/inventory.py", "source_location": "L159"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_inventory_inventorysummaryitem", "label": "InventorySummaryItem", "file_type": "code", "source_file": "app/schemas/pl/inventory.py", "source_location": "L167"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_inventory_inventorysummaryresponse", "label": "InventorySummaryResponse", "file_type": "code", "source_file": "app/schemas/pl/inventory.py", "source_location": "L176"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_inventory_transportstatsquery", "label": "TransportStatsQuery", "file_type": "code", "source_file": "app/schemas/pl/inventory.py", "source_location": "L180"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_inventory_transportstatsitem", "label": "TransportStatsItem", "file_type": "code", "source_file": "app/schemas/pl/inventory.py", "source_location": "L187"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_inventory_transportstatsresponse", "label": "TransportStatsResponse", "file_type": "code", "source_file": "app/schemas/pl/inventory.py", "source_location": "L194"}], "edges": [{"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_inventory_py", "target": "datetime", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/schemas/pl/inventory.py", "source_location": "L2", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_inventory_py", "target": "decimal", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": Line truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_service_py", "label": "pl_plan_service.py", "file_type": "code", "source_file": "app/services/pl_plan_service.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_service_determine_plan_type", "label": "determine_plan_type()", "file_type": "code", "source_file": "app/services/pl_plan_service.py", "source_location": "L43"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_service_is_ton_product_unit", "label": "is_ton_product_unit()", "file_type": "code", "source_file": "app/services/pl_plan_service.py", "source_location": "L51"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_service_product_quantity", "label": "_product_quantity()", "file_type": "code", "source_file": "app/services/pl_plan_service.py", "source_location": "L58"}, {"id": "planproductcreate", "label": "PlanProductCreate", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py"}, {"id": "decimal", "label": "Decimal", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_service_product_create_actual_qty", "label": "_product_create_actual_qty()", "file_type": "code", "source_file": "app/services/pl_plan_service.py", "source_location": "L68"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_service_plan_product_actual_qty", "label": "_plan_product_actual_qty()", "file_type": "code", "source_file": "app/services/pl_plan_service.py", "source_location": "L74"}, {"id": "plplanproduct", "label": "PlPlanProduct", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_service_sum_product_actual_quantities", "label": "sum_product_actual_quantities()", "file_type": "code", "source_file": "app/services/pl_plan_service.py", "source_location": "L80"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_service_sum_plan_product_actual_quantities", "label": "sum_plan_product_actual_quantities()", "file_type": "code", "source_file": "app/services/pl_plan_service.py", "source_location": "L84"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_service_estimate_qty_fields", "label": "_estimate_qty_fields()", "file_type": "code", "source_file": "app/services/pl_plan_service.py", "source_location": "L88"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_service_build_plan_product", "label": "build_plan_product()", "file_type": "code", "source_file": "app/services/pl_plan_service.py", "source_location": "L99"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_service_apply_vehicle_snapshot_to_plan_transport", "label": "_apply_vehicle_snapshot_to_plan_transport()", "file_type": "code", "source_file": "app/services/pl_plan_service.py", "source_location": "L149"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py"}, {"id": "plplan", "label": "PlPlan", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py"}, {"id": "pltransport", "label": "PlTransport", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py"}, {"id": "plvehicle", "label": "PlVehicle", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_plan_service.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_service_resolve_driver", "label": "_resolve_driver()", "file_type": "code", "source_file": "app/services/pl_plan_service.py", "source_location": "L167"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_service_calc_freight_amount", "label": "_calc_freight_amount()", "file_type": "code", "source_file": "app/services/pl_plan_service.py", "source_location": "L212"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_service_fetch_hazard_flags_map", "label": "fetch_hazard_flags_map()", "file_type": "code", "source_file": "app/services/pl_plan_service.py", "source_location": "L233"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_plan_service_resolve_hazard_flags_from_crm", "label": "resolve_hazard_flagsLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_py", "label": "plan.py", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_resolve_create_date", "label": "_resolve_create_date()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L91"}, {"id": "date", "label": "date", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_generate_plan_no", "label": "_generate_plan_no()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L95"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_format_subpack_slip_no", "label": "_format_subpack_slip_no()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L127"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_normalize_subpack_slip_no", "label": "_normalize_subpack_slip_no()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L131"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_subpack_slip_no_seq", "label": "_subpack_slip_no_seq()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L156"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_ensure_subpack_slip_no", "label": "_ensure_subpack_slip_no()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L164"}, {"id": "plplan", "label": "PlPlan", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_determine_plan_type", "label": "_determine_plan_type()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L196"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_get_plan_type_label", "label": "_get_plan_type_label()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L201"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_generate_notify_message", "label": "_generate_notify_message()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L210"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_list_plans", "label": "list_plans()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L231"}, {"id": "dbusercontext", "label": "DbUserContext", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "planlistresponse", "label": "PlanListResponse", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_count_plans_by_contract", "label": "count_plans_by_contract()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L358"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_create_plan", "label": "create_plan()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L386"}, {"id": "plancreate", "label": "PlanCreate", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "planread", "label": "PlanRead", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_create_plan_full", "label": "create_plan_full()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L470"}, {"id": "plancreatefull", "label": "PlanCreateFull", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "planwithproducts", "label": "PlanWithProducts", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_create_from_contract", "label": "create_from_contract()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L578"}, {"id": "createfromcontractrequest", "label": "CreateFromContractRequest", "file_type": "code", "sourceLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_brand_archive_py", "label": "test_pl_brand_archive.py", "file_type": "code", "source_file": "tests/test_pl_brand_archive.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_brand_archive_test_required_allows_only_that_brand", "label": "test_required_allows_only_that_brand()", "file_type": "code", "source_file": "tests/test_pl_brand_archive.py", "source_location": "L8"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_brand_archive_test_any_sum_must_match", "label": "test_any_sum_must_match()", "file_type": "code", "source_file": "tests/test_pl_brand_archive.py", "source_location": "L18"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_brand_archive_test_any_ok", "label": "test_any_ok()", "file_type": "code", "source_file": "tests/test_pl_brand_archive.py", "source_location": "L31"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_brand_archive_test_no_brand_ignores_rows", "label": "test_no_brand_ignores_rows()", "file_type": "code", "source_file": "tests/test_pl_brand_archive.py", "source_location": "L43"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_brand_archive_test_apply_stock_change_outbound_insufficient", "label": "test_apply_stock_change_outbound_insufficient()", "file_type": "code", "source_file": "tests/test_pl_brand_archive.py", "source_location": "L52"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_brand_archive_test_delta_for_plan_type", "label": "test_delta_for_plan_type()", "file_type": "code", "source_file": "tests/test_pl_brand_archive.py", "source_location": "L59"}], "edges": [{"source": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_brand_archive_py", "target": "decimal", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "tests/test_pl_brand_archive.py", "source_location": "L1", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_brand_archive_py", "target": "pytest", "relation": "imports", "context": "import", "confidence": "EXTRACTED", "source_file": "tests/test_pl_brand_archive.py", "source_location": "L3", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_brand_archive_py", "target": "app_services_pl_brand_archive", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "tests/test_pl_brand_archive.py", "source_location": "L5", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_brand_archive_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_brand_archive_test_required_allows_only_that_brand", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tests/test_pl_brand_archive.py", "source_location": "L8", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_brand_archive_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_brand_archive_test_any_sum_must_match", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tests/test_pl_brand_archive.py", "source_location": "L18", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_brand_archive_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_brand_archive_test_any_ok", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tests/test_pl_brand_archive.py", "source_location": "L31", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_brand_archive_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_brand_archive_test_no_brand_ignores_rows", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tests/test_pl_brand_archive.py", "source_location": "L43", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_brand_archive_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_brand_archive_test_apply_stock_change_outbound_insufficient", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tests/test_pl_brand_archive.py", "source_location": "L52", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_brand_archive_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_brand_archive_test_delta_for_plan_type", "relation": "contains", "confidence": "EXTRACTED", "source_file": "tests/test_pl_brand_archive.py", "source_location": "L59", "weight": 1.0}], "raw_calls": [{"caller_nid": "users_wuzilong_work_00code_shanxia2_0_backend_tests_test_pl_brand_archive_test_required_allows_only_that_brand", "callee": "raises", "is_member_call": true, "source_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/tests/test_pl_brand_archive.py", "source_location": "L9Line truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_io_order_service_py", "label": "pl_io_order_service.py", "file_type": "code", "source_file": "app/services/pl_io_order_service.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_io_order_service_ioordercreateresult", "label": "IoOrderCreateResult", "file_type": "code", "source_file": "app/services/pl_io_order_service.py", "source_location": "L37"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_io_order_service_build_io_order_site_url", "label": "build_io_order_site_url()", "file_type": "code", "source_file": "app/services/pl_io_order_service.py", "source_location": "L45"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_io_order_service_normalize_plan_date", "label": "_normalize_plan_date()", "file_type": "code", "source_file": "app/services/pl_io_order_service.py", "source_location": "L51"}, {"id": "plplan", "label": "PlPlan", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_io_order_service.py"}, {"id": "date", "label": "date", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_io_order_service.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_io_order_service_next_order_no", "label": "_next_order_no()", "file_type": "code", "source_file": "app/services/pl_io_order_service.py", "source_location": "L62"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_io_order_service.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_io_order_service_dibang_qty_by_plan_product", "label": "_dibang_qty_by_plan_product()", "file_type": "code", "source_file": "app/services/pl_io_order_service.py", "source_location": "L80"}, {"id": "decimal", "label": "Decimal", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_io_order_service.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_io_order_service_create_io_order_for_transport", "label": "create_io_order_for_transport()", "file_type": "code", "source_file": "app/services/pl_io_order_service.py", "source_location": "L112"}, {"id": "plplanproduct", "label": "PlPlanProduct", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_io_order_service.py"}, {"id": "pltransport", "label": "PlTransport", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_io_order_service.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_io_order_service_apply_dibang_prefill_to_order", "label": "apply_dibang_prefill_to_order()", "file_type": "code", "source_file": "app/services/pl_io_order_service.py", "source_location": "L194"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_io_order_service_void_io_orders_for_plan", "label": "void_io_orders_for_plan()", "file_type": "code", "source_file": "app/services/pl_io_order_service.py", "source_location": "L239"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_io_order_service_get_active_io_order_for_plan", "label": "get_active_io_order_for_plan()", "file_type": "code", "source_file": "app/services/pl_io_order_service.py", "source_location": "L263"}, {"id": "orderheader", "label": "OrderHeader", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_io_order_service.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_io_order_service_parse_feedback_images", "label": "parse_feedback_images()", "file_type": "code", "source_file": "app/services/pl_io_order_service.py", "source_location": "L293"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_io_order_service_dump_feedback_images", "label": "dump_feedback_images()", "file_type": "code", "source_file": "app/services/pl_io_order_service.py", "source_location": "L305"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_io_order_service_apply_site_feedback", "label": "apply_site_feedback()", "file_type": "code", "source_file": "app/services/pl_io_order_service.py", "source_location": "L312"}, {"id": "orderitem", "label": "OrderItem", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/services/pl_io_order_service.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_services_pl_io_order_serLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_inventory_py", "label": "inventory.py", "file_type": "code", "source_file": "app/api/pl/inventory.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_inventory_get_inventory_ledger", "label": "get_inventory_ledger()", "file_type": "code", "source_file": "app/api/pl/inventory.py", "source_location": "L50"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/inventory.py"}, {"id": "dbusercontext", "label": "DbUserContext", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/inventory.py"}, {"id": "inventoryledgerresponse", "label": "InventoryLedgerResponse", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/inventory.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_inventory_set_warning_threshold", "label": "set_warning_threshold()", "file_type": "code", "source_file": "app/api/pl/inventory.py", "source_location": "L81"}, {"id": "inventorywarningthreshold", "label": "InventoryWarningThreshold", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/inventory.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_inventory_list_warnings", "label": "list_warnings()", "file_type": "code", "source_file": "app/api/pl/inventory.py", "source_location": "L136"}, {"id": "inventorywarningread", "label": "InventoryWarningRead", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/inventory.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_inventory_update_warning", "label": "update_warning()", "file_type": "code", "source_file": "app/api/pl/inventory.py", "source_location": "L155"}, {"id": "inventorywarningupdate", "label": "InventoryWarningUpdate", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/inventory.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_inventory_list_transactions", "label": "list_transactions()", "file_type": "code", "source_file": "app/api/pl/inventory.py", "source_location": "L195"}, {"id": "date", "label": "date", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/inventory.py"}, {"id": "inventorytransactionresponse", "label": "InventoryTransactionResponse", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/inventory.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_inventory_get_summary", "label": "get_summary()", "file_type": "code", "source_file": "app/api/pl/inventory.py", "source_location": "L238"}, {"id": "inventorysummaryresponse", "label": "InventorySummaryResponse", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/inventory.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_inventory_document_reminder_stats", "label": "document_reminder_stats()", "file_type": "code", "source_file": "app/api/pl/inventory.py", "source_location": "L300"}, {"id": "documentreminderstatsread", "label": "DocumentReminderStatsRead", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/inventory.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_inventory_list_document_reminders", "label": "list_document_reminders()", "file_type": "code", "source_file": "app/api/pl/inventory.py", "source_location": "L320"}, {"id": "documentreminderread", "label": "DocumentReminderRead", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/inventory.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_inventory_update_document_reminder", "label": "update_document_reminder()", "file_type": "code", "source_file": "app/api/pl/inventory.py", "source_location": "L342"}, {"id": "documentreminderupdate", "label": "DocumentReminderUpdate", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/inventory.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_inventory_check_document_expiry", "label": "check_document_expiry()", "file_type": "code", "source_file": "app/api/pl/inventory.py", "source_location": "Line truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_crm_models_py", "label": "crm_models.py", "file_type": "code", "source_file": "app/models/crm_models.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_crm_models_crmproductcategory", "label": "CrmProductCategory", "file_type": "code", "source_file": "app/models/crm_models.py", "source_location": "L21"}, {"id": "base", "label": "Base", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_crm_models_crmproduct", "label": "CrmProduct", "file_type": "code", "source_file": "app/models/crm_models.py", "source_location": "L43"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_crm_models_crmproductbrand", "label": "CrmProductBrand", "file_type": "code", "source_file": "app/models/crm_models.py", "source_location": "L78"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_crm_models_crmproductalias", "label": "CrmProductAlias", "file_type": "code", "source_file": "app/models/crm_models.py", "source_location": "L98"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_crm_models_sysoss", "label": "SysOss", "file_type": "code", "source_file": "app/models/crm_models.py", "source_location": "L117"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_crm_models_crmcustomer", "label": "CrmCustomer", "file_type": "code", "source_file": "app/models/crm_models.py", "source_location": "L149"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_crm_models_crmcontact", "label": "CrmContact", "file_type": "code", "source_file": "app/models/crm_models.py", "source_location": "L232"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_crm_models_crminvoiceinfo", "label": "CrmInvoiceInfo", "file_type": "code", "source_file": "app/models/crm_models.py", "source_location": "L267"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_crm_models_crmcustomerfollow", "label": "CrmCustomerFollow", "file_type": "code", "source_file": "app/models/crm_models.py", "source_location": "L295"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_crm_models_crmcustomerpool", "label": "CrmCustomerPool", "file_type": "code", "source_file": "app/models/crm_models.py", "source_location": "L326"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_crm_models_crmcustomerpoolrule", "label": "CrmCustomerPoolRule", "file_type": "code", "source_file": "app/models/crm_models.py", "source_location": "L352"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_crm_models_crmopportunity", "label": "CrmOpportunity", "file_type": "code", "source_file": "app/models/crm_models.py", "source_location": "L375"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_crm_models_crmopportunityfollow", "label": "CrmOpportunityFollow", "file_type": "code", "source_file": "app/models/crm_models.py", "source_location": "L418"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_crm_models_crmopportunityproduct", "label": "CrmOpportunityProduct", "file_type": "code", "source_file": "app/models/crm_models.py", "source_location": "L449"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_crm_models_crmopportunitystagelog", "label": "CrmOpportunityStageLog", "file_type": "code", "source_file": "app/models/crm_models.py", "source_location": "L479"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_crm_models_crmcontract", "label": "CrmContract", "file_type": "code", "source_file": "app/models/crm_models.py", "source_location": "L510"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_crm_models_crmcontractapproval", "label": "CrmContractApproval", "file_type": "code", "source_file": "app/models/crm_models.py", "source_location": "L593"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_crm_models_crmcontractchange", "label": "CrmContractChange", "file_type": "code", "source_file": "app/models/crm_models.py", "source_location": "L624"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_crm_models_crmcontracttemplate", "label": "CrmContractTemplate", "file_type": "code", "source_file": "app/models/crm_models.py", "source_location": "L655"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_crm_models_crmcontractproduct", "label": "CrmContractProduct", "file_type": "code", "source_file": "app/models/crm_models.py", "source_location": "L689"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_crm_models_crmcontractproductpricetier", "label": "CrmContractProductPriceTier", "file_type": "code", "source_file": "app/models/crm_models.py", "source_location": "L730"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_crm_models_crmquote", "label": "CrmQuote", "file_type": "code", "source_file": "app/models/crmLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_crm_py", "label": "crm.py", "file_type": "code", "source_file": "app/api/crm.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_crm_category_tree", "label": "category_tree()", "file_type": "code", "source_file": "app/api/crm.py", "source_location": "L328"}, {"id": "productcategorytreebody", "label": "ProductCategoryTreeBody", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/crm.py"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/crm.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_crm_customer_list", "label": "customer_list()", "file_type": "code", "source_file": "app/api/crm.py", "source_location": "L356"}, {"id": "customerlistbody", "label": "CustomerListBody", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/crm.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_crm_customer_detail", "label": "customer_detail()", "file_type": "code", "source_file": "app/api/crm.py", "source_location": "L390"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_crm_customer_create", "label": "customer_create()", "file_type": "code", "source_file": "app/api/crm.py", "source_location": "L417"}, {"id": "customercreatebody", "label": "CustomerCreateBody", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/crm.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_crm_customer_update", "label": "customer_update()", "file_type": "code", "source_file": "app/api/crm.py", "source_location": "L447"}, {"id": "customerupdatebody", "label": "CustomerUpdateBody", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/crm.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_crm_customer_delete", "label": "customer_delete()", "file_type": "code", "source_file": "app/api/crm.py", "source_location": "L474"}, {"id": "customerdeletebody", "label": "CustomerDeleteBody", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/crm.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_crm_customer_generate_no", "label": "customer_generate_no()", "file_type": "code", "source_file": "app/api/crm.py", "source_location": "L502"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_crm_category_add", "label": "category_add()", "file_type": "code", "source_file": "app/api/crm.py", "source_location": "L518"}, {"id": "productcategorycreatebody", "label": "ProductCategoryCreateBody", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/crm.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_crm_category_update", "label": "category_update()", "file_type": "code", "source_file": "app/api/crm.py", "source_location": "L537"}, {"id": "productcategoryupdatebody", "label": "ProductCategoryUpdateBody", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/crm.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_crm_category_delete", "label": "category_delete()", "file_type": "code", "source_file": "app/api/crm.py", "source_location": "L557"}, {"id": "categorydeletebody", "label": "CategoryDeleteBody", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/crm.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_crm_product_list", "label": "product_list()", "file_type": "code", "source_file": "app/api/crm.py", "source_location": "L577"}, {"id": "productlistbody", "label": "ProductListBody", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/crm.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_crm_product_detail", "label": "product_detail()", "file_type": "code", "source_file": "app/api/crm.py", "source_location": "L614"}, {"id": "productdetailbody", "label": "ProductDetailBody", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/crm.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_crm_product_add", "label": "product_add()", "file_type": "code", "source_file": "app/api/crm.py", "source_location": "L635"}, {"id": "productcreatebody", "label": "ProdLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_py", "label": "contract.py", "file_type": "code", "source_file": "app/crud/crm/contract.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_ensure_contract_in_scope", "label": "_ensure_contract_in_scope()", "file_type": "code", "source_file": "app/crud/crm/contract.py", "source_location": "L49"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/crud/crm/contract.py"}, {"id": "crmcontract", "label": "CrmContract", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/crud/crm/contract.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_get_contract_row_in_scope", "label": "_get_contract_row_in_scope()", "file_type": "code", "source_file": "app/crud/crm/contract.py", "source_location": "L53"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_total_pages", "label": "total_pages()", "file_type": "code", "source_file": "app/crud/crm/contract.py", "source_location": "L70"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_decimal_value", "label": "_decimal_value()", "file_type": "code", "source_file": "app/crud/crm/contract.py", "source_location": "L76"}, {"id": "any", "label": "Any", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/crud/crm/contract.py"}, {"id": "decimal", "label": "Decimal", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/crud/crm/contract.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_parse_optional_date", "label": "_parse_optional_date()", "file_type": "code", "source_file": "app/crud/crm/contract.py", "source_location": "L84"}, {"id": "date", "label": "date", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/crud/crm/contract.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_parse_optional_datetime", "label": "_parse_optional_datetime()", "file_type": "code", "source_file": "app/crud/crm/contract.py", "source_location": "L102"}, {"id": "datetime", "label": "datetime", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/crud/crm/contract.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_get_field", "label": "_get_field()", "file_type": "code", "source_file": "app/crud/crm/contract.py", "source_location": "L118"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_resolve_user_display_name", "label": "_resolve_user_display_name()", "file_type": "code", "source_file": "app/crud/crm/contract.py", "source_location": "L125"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_is_blank", "label": "_is_blank()", "file_type": "code", "source_file": "app/crud/crm/contract.py", "source_location": "L135"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_first_non_blank", "label": "_first_non_blank()", "file_type": "code", "source_file": "app/crud/crm/contract.py", "source_location": "L139"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_get_default_invoice_info", "label": "_get_default_invoice_info()", "file_type": "code", "source_file": "app/crud/crm/contract.py", "source_location": "L147"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_build_customer_address", "label": "_build_customer_address()", "file_type": "code", "source_file": "app/crud/crm/contract.py", "source_location": "L157"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_row_to_dict", "label": "_row_to_dict()", "file_type": "code", "source_file": "app/crud/crm/contract.py", "source_location": "L169"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_contract_row_to_dict", "label": "contract_row_to_dict()", "file_type": "code", "source_file": "app/crud/crm/contract.py", "source_location": "L246"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_build_contract_filters", "label": "_build_contract_filters()", "file_type": "code", "source_file": "app/crud/crm/contract.py", "source_location": "L250"}, {"id": "query", "label": "Query", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/crud/crm/contract.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_contract_list_contracts", "label": "list_contractsLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_py", "label": "plan.py", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planproductbase", "label": "PlanProductBase", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L16"}, {"id": "basemodel", "label": "BaseModel", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planproductcreate", "label": "PlanProductCreate", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L49"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planproductupdate", "label": "PlanProductUpdate", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L53"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planproductread", "label": "PlanProductRead", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L75"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planbase", "label": "PlanBase", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L89"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_plancreate", "label": "PlanCreate", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L129"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_plantransportcreateblock", "label": "PlanTransportCreateBlock", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L138"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planupdate", "label": "PlanUpdate", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L152"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_hazardcertreadybody", "label": "HazardCertReadyBody", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L193"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planread", "label": "PlanRead", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L198"}, {"id": "planresourcesnapshotmixin", "label": "PlanResourceSnapshotMixin", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planwithproducts", "label": "PlanWithProducts", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L226"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planinboundsliptemplatesave", "label": "PlanInboundSlipTemplateSave", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L230"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_plandeliverysliptemplatesave", "label": "PlanDeliverySlipTemplateSave", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L235"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_plansubpacksliptemplatesave", "label": "PlanSubpackSlipTemplateSave", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L242"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_createfromcontractrequest", "label": "CreateFromContractRequest", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L254"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_plancreatefull", "label": "PlanCreateFull", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L265"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planlistquery", "label": "PlanListQuery", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L276"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planproductcertitem", "label": "PlanProductCertItem", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L291"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planproductcertsupdate", "label": "PlanProductCertsUpdate", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L299"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planarchivebrandqtyitem", "label": "PlanArchiveBrandQtyItem", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L304"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_pl_plan_planarchiveproductitem", "label": "PlanArchiveProductItem", "file_type": "code", "source_file": "app/schemas/pl/plan.py", "source_location": "L311"}, {"id": "users_wuzilong_wLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_scripts_migrate_20260727_split_brand_from_product_name_py", "label": "migrate_20260727_split_brand_from_product_name.py", "file_type": "code", "source_file": "scripts/migrate_20260727_split_brand_from_product_name.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_scripts_migrate_20260727_split_brand_from_product_name_parse_name", "label": "parse_name()", "file_type": "code", "source_file": "scripts/migrate_20260727_split_brand_from_product_name.py", "source_location": "L39"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_scripts_migrate_20260727_split_brand_from_product_name_collect_candidates", "label": "collect_candidates()", "file_type": "code", "source_file": "scripts/migrate_20260727_split_brand_from_product_name.py", "source_location": "L51"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_scripts_migrate_20260727_split_brand_from_product_name_write_csv", "label": "write_csv()", "file_type": "code", "source_file": "scripts/migrate_20260727_split_brand_from_product_name.py", "source_location": "L77"}, {"id": "path", "label": "Path", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/scripts/migrate_20260727_split_brand_from_product_name.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_scripts_migrate_20260727_split_brand_from_product_name_apply_updates", "label": "apply_updates()", "file_type": "code", "source_file": "scripts/migrate_20260727_split_brand_from_product_name.py", "source_location": "L88"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_scripts_migrate_20260727_split_brand_from_product_name_main", "label": "main()", "file_type": "code", "source_file": "scripts/migrate_20260727_split_brand_from_product_name.py", "source_location": "L133"}], "edges": [{"source": "users_wuzilong_work_00code_shanxia2_0_backend_scripts_migrate_20260727_split_brand_from_product_name_py", "target": "argparse", "relation": "imports", "context": "import", "confidence": "EXTRACTED", "source_file": "scripts/migrate_20260727_split_brand_from_product_name.py", "source_location": "L18", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_scripts_migrate_20260727_split_brand_from_product_name_py", "target": "csv", "relation": "imports", "context": "import", "confidence": "EXTRACTED", "source_file": "scripts/migrate_20260727_split_brand_from_product_name.py", "source_location": "L19", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_scripts_migrate_20260727_split_brand_from_product_name_py", "target": "re", "relation": "imports", "context": "import", "confidence": "EXTRACTED", "source_file": "scripts/migrate_20260727_split_brand_from_product_name.py", "source_location": "L20", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_scripts_migrate_20260727_split_brand_from_product_name_py", "target": "sys", "relation": "imports", "context": "import", "confidence": "EXTRACTED", "source_file": "scripts/migrate_20260727_split_brand_from_product_name.py", "source_location": "L21", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_scripts_migrate_20260727_split_brand_from_product_name_py", "target": "pathlib", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "scripts/migrate_20260727_split_brand_from_product_name.py", "source_location": "L22", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_scripts_migrate_20260727_split_brand_from_product_name_py", "target": "app_core_database", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "scripts/migrate_20260727_split_brand_from_product_name.py", "source_location": "L29", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_scripts_migrate_20260727_split_brand_from_product_name_py", "target": "app_core_utils", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "scripts/migrate_20260727_split_brand_from_product_name.py", "source_location": "L30", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_scripts_migrate_20260727_split_brand_from_product_name_py", "target": "app_models_crm_models", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "scripts/migrate_20260727_split_brand_from_product_name.py", "source_location": "L31", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_scripts_migrate_20260727_split_brand_from_product_name_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_scripts_migrate_20260727_split_brand_from_product_name_parse_name", "relation": "contains", "confidence": "EXTRACTED", "source_file": "scripts/migrate_20260727_split_brand_from_product_name.py", "source_location": "L39", "weight": 1.0}, {"source": "users_wuLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_py", "label": "plan.py", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_resolve_create_date", "label": "_resolve_create_date()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L90"}, {"id": "date", "label": "date", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_generate_plan_no", "label": "_generate_plan_no()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L94"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_format_subpack_slip_no", "label": "_format_subpack_slip_no()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L126"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_normalize_subpack_slip_no", "label": "_normalize_subpack_slip_no()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L130"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_subpack_slip_no_seq", "label": "_subpack_slip_no_seq()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L155"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_ensure_subpack_slip_no", "label": "_ensure_subpack_slip_no()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L163"}, {"id": "plplan", "label": "PlPlan", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_determine_plan_type", "label": "_determine_plan_type()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L195"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_get_plan_type_label", "label": "_get_plan_type_label()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L200"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_generate_notify_message", "label": "_generate_notify_message()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L209"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_list_plans", "label": "list_plans()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L230"}, {"id": "dbusercontext", "label": "DbUserContext", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "planlistresponse", "label": "PlanListResponse", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_count_plans_by_contract", "label": "count_plans_by_contract()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L357"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_create_plan", "label": "create_plan()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L385"}, {"id": "plancreate", "label": "PlanCreate", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "planread", "label": "PlanRead", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_create_plan_full", "label": "create_plan_full()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L469"}, {"id": "plancreatefull", "label": "PlanCreateFull", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "planwithproducts", "label": "PlanWithProducts", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/api/pl/plan.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_api_pl_plan_create_from_contract", "label": "create_from_contract()", "file_type": "code", "source_file": "app/api/pl/plan.py", "source_location": "L577"}, {"id": "createfromcontractrequest", "label": "CreateFromContractRequest", "file_type": "code", "sourceLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_product_py", "label": "product.py", "file_type": "code", "source_file": "app/crud/crm/product.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_product_total_pages", "label": "total_pages()", "file_type": "code", "source_file": "app/crud/crm/product.py", "source_location": "L32"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_product_category_row_to_dict", "label": "_category_row_to_dict()", "file_type": "code", "source_file": "app/crud/crm/product.py", "source_location": "L42"}, {"id": "crmproductcategory", "label": "CrmProductCategory", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/crud/crm/product.py"}, {"id": "any", "label": "Any", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/crud/crm/product.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_product_list_categories", "label": "list_categories()", "file_type": "code", "source_file": "app/crud/crm/product.py", "source_location": "L58"}, {"id": "session", "label": "Session", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/crud/crm/product.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_product_build_category_tree", "label": "_build_category_tree()", "file_type": "code", "source_file": "app/crud/crm/product.py", "source_location": "L85"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_product_collect_category_with_ancestors", "label": "_collect_category_with_ancestors()", "file_type": "code", "source_file": "app/crud/crm/product.py", "source_location": "L96"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_product_list_category_tree", "label": "list_category_tree()", "file_type": "code", "source_file": "app/crud/crm/product.py", "source_location": "L112"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_product_get_category", "label": "get_category()", "file_type": "code", "source_file": "app/crud/crm/product.py", "source_location": "L145"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_product_create_category", "label": "create_category()", "file_type": "code", "source_file": "app/crud/crm/product.py", "source_location": "L157"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_product_update_category", "label": "update_category()", "file_type": "code", "source_file": "app/crud/crm/product.py", "source_location": "L194"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_product_soft_delete_category", "label": "soft_delete_category()", "file_type": "code", "source_file": "app/crud/crm/product.py", "source_location": "L233"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_product_category_code_exists", "label": "category_code_exists()", "file_type": "code", "source_file": "app/crud/crm/product.py", "source_location": "L273"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_product_brand_row_to_dict", "label": "_brand_row_to_dict()", "file_type": "code", "source_file": "app/crud/crm/product.py", "source_location": "L293"}, {"id": "crmproductbrand", "label": "CrmProductBrand", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/crud/crm/product.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_product_alias_row_to_dict", "label": "_alias_row_to_dict()", "file_type": "code", "source_file": "app/crud/crm/product.py", "source_location": "L302"}, {"id": "crmproductalias", "label": "CrmProductAlias", "file_type": "code", "source_file": "", "source_location": "", "origin_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/crud/crm/product.py"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_product_load_brands_aliases_maps", "label": "_load_brands_aliases_maps()", "file_type": "code", "source_file": "app/crud/crm/product.py", "source_location": "L310"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_product_assert_brand_deletable", "label": "_assert_brand_deletable()", "file_type": "code", "source_file": "app/crud/crm/product.py", "source_location": "L344"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_product_replace_product_brands", "label": "_replace_product_brands()", "file_type": "code", "source_file": "app/crud/crm/product.py", "source_location": "L381"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_crud_crm_product_replace_product_aliases", "label": "_replace_product_aliases()", "file_type": "code", "source_file": "app/crud/crm/product.py", "source_location": "L450"}, {"id": "users_wuzilonLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_crm_product_py", "label": "product.py", "file_type": "code", "source_file": "app/schemas/crm/product.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_crm_product_productcategorycreatebody", "label": "ProductCategoryCreateBody", "file_type": "code", "source_file": "app/schemas/crm/product.py", "source_location": "L12"}, {"id": "basemodel", "label": "BaseModel", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_crm_product_productcategoryupdatebody", "label": "ProductCategoryUpdateBody", "file_type": "code", "source_file": "app/schemas/crm/product.py", "source_location": "L26"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_crm_product_categorydeletebody", "label": "CategoryDeleteBody", "file_type": "code", "source_file": "app/schemas/crm/product.py", "source_location": "L41"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_crm_product_productcategorytreebody", "label": "ProductCategoryTreeBody", "file_type": "code", "source_file": "app/schemas/crm/product.py", "source_location": "L47"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_crm_product_productupdatestatusbody", "label": "ProductUpdateStatusBody", "file_type": "code", "source_file": "app/schemas/crm/product.py", "source_location": "L56"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_crm_product_productlistbody", "label": "ProductListBody", "file_type": "code", "source_file": "app/schemas/crm/product.py", "source_location": "L66"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_crm_product_productbranditem", "label": "ProductBrandItem", "file_type": "code", "source_file": "app/schemas/crm/product.py", "source_location": "L79"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_crm_product_productaliasitem", "label": "ProductAliasItem", "file_type": "code", "source_file": "app/schemas/crm/product.py", "source_location": "L90"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_crm_product_productcreatebody", "label": "ProductCreateBody", "file_type": "code", "source_file": "app/schemas/crm/product.py", "source_location": "L100"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_crm_product_productupdatebody", "label": "ProductUpdateBody", "file_type": "code", "source_file": "app/schemas/crm/product.py", "source_location": "L134"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_crm_product_productdetailbody", "label": "ProductDetailBody", "file_type": "code", "source_file": "app/schemas/crm/product.py", "source_location": "L169"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_crm_product_productdeletebody", "label": "ProductDeleteBody", "file_type": "code", "source_file": "app/schemas/crm/product.py", "source_location": "L175"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_crm_product_productoptionsbody", "label": "ProductOptionsBody", "file_type": "code", "source_file": "app/schemas/crm/product.py", "source_location": "L182"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_crm_product_rationale_1", "label": "CRM \u4ea7\u54c1\u6a21\u5757 Pydantic Schemas", "file_type": "rationale", "source_file": "app/schemas/crm/product.py", "source_location": "L1"}, {"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_crm_product_rationale_48", "label": "\u4ea7\u54c1\u5206\u7c7b\u6811\u67e5\u8be2\uff08\u5bc6\u6587\u89e3\u5bc6\u540e\u987b\u7528 Pydantic \u7ed1\u5b9a\uff0c\u88f8 dict=None \u5728\u90e8\u5206\u94fe\u8def\u4e0b\u4f1a\u4e22 body\uff09", "file_type": "rationale", "source_file": "app/schemas/crm/product.py", "source_location": "L48"}], "edges": [{"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_crm_product_py", "target": "decimal", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/schemas/crm/product.py", "source_location": "L3", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_crm_product_py", "target": "typing", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/schemas/crm/product.py", "source_location": "L4", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_crm_product_py", "target": "pydantic", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/schemas/crm/product.py", "source_location": "L6", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_crm_product_py", "target": "users_wuzilong_work_00code_shanxia2_0_backend_app_schemas_crm_product_productcategorycreatebody", "relation": "contains", "confidence": "EXTRACTED", "source_file": "app/schemas/crm/product.pLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_init_py", "label": "__init__.py", "file_type": "code", "source_file": "app/models/__init__.py", "source_location": "L1"}], "edges": [{"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_init_py", "target": "app_models_base", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/models/__init__.py", "source_location": "L2", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_init_py", "target": "app_models_models", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/models/__init__.py", "source_location": "L3", "weight": 1.0}, {"source": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_init_py", "target": "app_models_pl_models", "relation": "imports_from", "context": "import", "confidence": "EXTRACTED", "source_file": "app/models/__init__.py", "source_location": "L30", "weight": 1.0}], "raw_calls": [{"caller_nid": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_init_py", "callee": "SysUser", "is_member_call": false, "indirect": true, "context": "assignment", "source_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/models/__init__.py", "source_location": "L56"}, {"caller_nid": "users_wuzilong_work_00code_shanxia2_0_backend_app_models_init_py", "callee": "SysApiRequestLog", "is_member_call": false, "indirect": true, "context": "assignment", "source_file": "/Users/wuzilong/work/00code/shanxia2.0/backend/app/models/__init__.py", "source_location": "L57"}]}
@@ -76,11 +76,121 @@ TS
Expected: `pl-batch-print: ok`
(实现文件内容见原 Step 2 代码块;可删去 `pl-batch-print.selftest.mjs` 双写方案,只保留 `.ts`。)
(Step 2 写出 `.ts` 后再跑本命令;顺序可先 Step 2 再 Step 1。)
- [ ] **Step 2: 实现 `web-admin/src/utils/pl-batch-print.ts`**
(完整代码同上一版 Step 2 中的 TypeScript 模块。)
```ts
// web-admin/src/utils/pl-batch-print.ts
import type { Plan, PlanProduct } from '@/api/pl'
import type { PlPrintSlipCommand } from '@/utils/pl-display'
import { canPrintSlipCommand } from '@/utils/pl-display'
export type SlipPlanProduct = PlanProduct & { slipPlanNo?: string }
export function normalizeCustomerName(name?: string | null) {
return String(name || '').trim()
}
export function canBatchPrintSlip(
plans: Array<Pick<Plan, 'planType' | 'customerName' | 'planDate'>>,
command: PlPrintSlipCommand
): { ok: boolean; reason?: string } {
if (!plans?.length) return { ok: false, reason: '请先勾选运输计划' }
if (plans.length === 1) {
if (!canPrintSlipCommand(plans[0].planType, command)) {
return { ok: false, reason: '当前单据类型不支持打印该单据' }
}
return { ok: true }
}
if (command === 'inbound') {
if (!plans.every((p) => p.planType === 'inbound')) {
return { ok: false, reason: '合打入库单要求:均为入库类计划' }
}
const c = normalizeCustomerName(plans[0].customerName)
const d = plans[0].planDate
if (!c || !d || !plans.every((p) => normalizeCustomerName(p.customerName) === c && p.planDate === d)) {
return {
ok: false,
reason: '合打入库单要求:客户名称一致且计划日期一致,且均为入库类计划'
}
}
return { ok: true }
}
if (command === 'delivery') {
if (!plans.every((p) => p.planType === 'outbound')) {
return { ok: false, reason: '合打送(提)货单要求:均为出库类计划' }
}
const c = normalizeCustomerName(plans[0].customerName)
const d = plans[0].planDate
if (!c || !d || !plans.every((p) => normalizeCustomerName(p.customerName) === c && p.planDate === d)) {
return {
ok: false,
reason: '合打送(提)货单要求:客户名称一致且计划日期一致,且均为出库类计划'
}
}
return { ok: true }
}
if (command === 'subpack') {
if (!plans.every((p) => canPrintSlipCommand(p.planType, 'subpack'))) {
return { ok: false, reason: '分装单仅支持出库类计划' }
}
return { ok: true }
}
return { ok: false, reason: '未知单据类型' }
}
export function mergeBatchPrintRemarks(plans: Array<Pick<Plan, 'planNo' | 'remark'>>) {
return plans
.map((p) => {
const text = String(p.remark || '').trim()
if (!text) return ''
return `${p.planNo}:${text}`
})
.filter(Boolean)
.join('\n')
}
/** 备注写入 HTML 模版时换行 */
export function formatRemarkForSlipHtml(remark?: string | null) {
return String(remark || '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\n/g, '<br />')
}
/** 合打视图:首条为抬头基准;产品展开并打 slipPlanNo;清空车辆;备注合并 */
export function buildBatchPrintPlanView(plans: Plan[]): Plan {
if (!plans.length) throw new Error('plans empty')
const base = { ...plans[0] }
const products: SlipPlanProduct[] = []
for (const p of plans) {
for (const prod of p.products || []) {
products.push({ ...prod, slipPlanNo: p.planNo })
}
}
base.products = products
base.remark = mergeBatchPrintRemarks(plans)
base.licensePlate = undefined
base.vehicleId = undefined
base.driverId = undefined
base.driverName = undefined
base.trailerNo = undefined
base.personnelSummary = undefined
return base
}
export function normalizePlanIds(planIdOrIds: string | string[]): string[] {
const ids = Array.isArray(planIdOrIds) ? planIdOrIds : [planIdOrIds]
return ids.map((id) => String(id || '').trim()).filter(Boolean)
}
```
- [ ] **Step 3: 再跑 Step 1 命令,确认 PASS**
@@ -75,9 +75,9 @@ export function mapContractProducts(products?: ContractProductForm[]) {
}
export function applyContractToPlanForm(contract: ContractVO) {
// 供方/需方以合同甲乙方可章名为准;supplierName/customerName 仅作冗余,供地磅按 planType 取用
// 供方可章名取 partyA;客户名称严格跟合同「客户名称」,不取供/需方可章名
const supplierName = contract.partyA || contract.supplierName || ''
const customerName = contract.partyB || contract.customerName || ''
const customerName = contract.customerName || ''
const destinationName = customerName
return {