订单管理功能优化

This commit is contained in:
Codex 2026-10-28 15:38:36 +08:00
parent db13e15fbf
commit 862f868979
4 changed files with 52 additions and 11 deletions

View File

@ -13,6 +13,7 @@ from flask import Blueprint, request, jsonify, session, redirect, url_for, rende
from services.db import query, query_iter, get_db from services.db import query, query_iter, get_db
from routes.auth import login_required, role_required, operation_required from routes.auth import login_required, role_required, operation_required
from services.field_perms import filter_rows as _filter_field_rows from services.field_perms import filter_rows as _filter_field_rows
from services.inventory_calc import active_warehouse_condition
inventory_bp = Blueprint('inventory', __name__) inventory_bp = Blueprint('inventory', __name__)
@ -411,6 +412,7 @@ def inventory_list():
{join_type} {join_type}
{warehouse_join} {warehouse_join}
WHERE {where_detail_clause} WHERE {where_detail_clause}
AND {active_warehouse_condition('d')}
""", params)[0] """, params)[0]
# 获取采购跟单、采购计划、供应商信息 # 获取采购跟单、采购计划、供应商信息
prod_fields = ", p.procurement_follower AS 采购跟单, p.procurement_planner AS 采购计划" prod_fields = ", p.procurement_follower AS 采购跟单, p.procurement_planner AS 采购计划"

View File

@ -80,18 +80,19 @@ def warehouse_save():
if not code or not name: if not code or not name:
return jsonify({"code": 1, "msg": "仓库编号和名称不能为空"}) return jsonify({"code": 1, "msg": "仓库编号和名称不能为空"})
exists = query("SELECT id, warehouse_property FROM warehouses WHERE warehouse_code=%s", (code,)) exists = query("SELECT id, warehouse_property, disabled FROM warehouses WHERE warehouse_code=%s", (code,))
property_changed = not exists or (exists[0].get('warehouse_property') or '').strip() != warehouse_property property_changed = not exists or (exists[0].get('warehouse_property') or '').strip() != warehouse_property
disabled_changed = not exists or (exists[0].get('disabled') or '否').strip() != disabled
if exists: if exists:
query("UPDATE warehouses SET warehouse_name=%s, category=%s, remark=%s, warehouse_property=%s, disabled=%s WHERE warehouse_code=%s", query("UPDATE warehouses SET warehouse_name=%s, category=%s, remark=%s, warehouse_property=%s, disabled=%s WHERE warehouse_code=%s",
(name, category, remark, warehouse_property, disabled, code)) (name, category, remark, warehouse_property, disabled, code))
else: else:
query("INSERT INTO warehouses (warehouse_code, warehouse_name, category, remark, warehouse_property, disabled) VALUES (%s,%s,%s,%s,%s,%s)", query("INSERT INTO warehouses (warehouse_code, warehouse_name, category, remark, warehouse_property, disabled) VALUES (%s,%s,%s,%s,%s,%s)",
(code, name, category, remark, warehouse_property, disabled)) (code, name, category, remark, warehouse_property, disabled))
if property_changed: if property_changed or disabled_changed:
from services.cache import refresh_all_caches from services.cache import refresh_all_caches
refresh_all_caches() refresh_all_caches()
return jsonify({"code": 0, "msg": "保存成功", "cache_refreshed": property_changed}) return jsonify({"code": 0, "msg": "保存成功", "cache_refreshed": property_changed or disabled_changed})
@warehouse_bp.route('/api/warehouse/delete', methods=['POST']) @warehouse_bp.route('/api/warehouse/delete', methods=['POST'])
@ -125,16 +126,17 @@ def warehouse_update_field():
if field not in allowed: if field not in allowed:
return jsonify({"code": 400, "msg": "不允许的字段"}) return jsonify({"code": 400, "msg": "不允许的字段"})
property_changed = False property_changed = False
disabled_changed = field == 'disabled'
if field == 'warehouse_property': if field == 'warehouse_property':
old_rows = query("SELECT warehouse_property FROM warehouses WHERE warehouse_code=%s", (code,)) old_rows = query("SELECT warehouse_property FROM warehouses WHERE warehouse_code=%s", (code,))
old_value = (old_rows[0].get('warehouse_property') or '').strip() if old_rows else '' old_value = (old_rows[0].get('warehouse_property') or '').strip() if old_rows else ''
property_changed = old_value != str(value or '').strip() property_changed = old_value != str(value or '').strip()
try: try:
query(f"UPDATE warehouses SET {field}=%s WHERE warehouse_code=%s", (value, code)) query(f"UPDATE warehouses SET {field}=%s WHERE warehouse_code=%s", (value, code))
if property_changed: if property_changed or disabled_changed:
from services.cache import refresh_all_caches from services.cache import refresh_all_caches
refresh_all_caches() refresh_all_caches()
return jsonify({"code": 0, "msg": "更新成功", "cache_refreshed": property_changed}) return jsonify({"code": 0, "msg": "更新成功", "cache_refreshed": property_changed or disabled_changed})
except Exception as e: except Exception as e:
return jsonify({"code": 500, "msg": str(e)}) return jsonify({"code": 500, "msg": str(e)})

View File

@ -36,6 +36,28 @@ FORCE_FINISHED_PRODUCTS_SQL = ', '.join(
) )
def active_warehouse_condition(inventory_alias='inventory_detail'):
"""SQL condition that excludes warehouses marked disabled in warehouse management.
Inventory rows whose warehouse is not maintained in ``warehouses`` remain eligible;
only an explicit ``disabled='是'`` removes a warehouse from calculations. Numeric
warehouse codes are compared by value so 29/029 and 35/035 behave consistently.
"""
alias = inventory_alias
return f"""NOT EXISTS (
SELECT 1 FROM warehouses disabled_wh
WHERE COALESCE(disabled_wh.disabled, '否') = '是'
AND (
disabled_wh.warehouse_code = {alias}.warehouse_code
OR (
disabled_wh.warehouse_code REGEXP '^[0-9]+$'
AND {alias}.warehouse_code REGEXP '^[0-9]+$'
AND CAST(disabled_wh.warehouse_code AS UNSIGNED) = CAST({alias}.warehouse_code AS UNSIGNED)
)
)
)"""
def resolve_cost_price(product_code, inventory_price): def resolve_cost_price(product_code, inventory_price):
"""解析产品成本价:命中特殊规则的用单独指定的正确成本价,否则用库存表成本价。""" """解析产品成本价:命中特殊规则的用单独指定的正确成本价,否则用库存表成本价。"""
if product_code in SPECIAL_COST_PRICES: if product_code in SPECIAL_COST_PRICES:
@ -54,10 +76,11 @@ def _get_latest_inv_date():
def _compute_product_inventory(): def _compute_product_inventory():
"""计算每个产品的最新库存(排除15号仓库)""" """计算每个产品的最新库存(排除15号仓库)"""
latest_str = _get_latest_inv_date() latest_str = _get_latest_inv_date()
rows = query(""" rows = query(f"""
SELECT product_code, SUM(available_qty) AS total_qty SELECT product_code, SUM(available_qty) AS total_qty
FROM inventory_detail FROM inventory_detail
WHERE data_date = %s AND warehouse_code != '15' WHERE data_date = %s AND warehouse_code != '15'
AND {active_warehouse_condition('inventory_detail')}
AND NOT (product_code IN ('POZ-03033', 'POZ-DXPW-1668-PDD', 'AH-YS-0584-TMGJ', 'POZ-JHY-7022', 'SEDATE-YMT-1682-PDD') AND NOT (product_code IN ('POZ-03033', 'POZ-DXPW-1668-PDD', 'AH-YS-0584-TMGJ', 'POZ-JHY-7022', 'SEDATE-YMT-1682-PDD')
AND LPAD(warehouse_code, 2, '0') = '20') AND LPAD(warehouse_code, 2, '0') = '20')
GROUP BY product_code GROUP BY product_code
@ -76,6 +99,7 @@ def _compute_raw_inventory_15():
"SELECT product_code, SUM(available_qty) AS total_qty " "SELECT product_code, SUM(available_qty) AS total_qty "
"FROM inventory_detail " "FROM inventory_detail "
"WHERE data_date = %s AND warehouse_code = '15' " "WHERE data_date = %s AND warehouse_code = '15' "
f"AND {active_warehouse_condition('inventory_detail')} "
"GROUP BY product_code", "GROUP BY product_code",
(latest_str,)) (latest_str,))
inv15_map = {} inv15_map = {}
@ -97,7 +121,8 @@ def compute_product_inventory_15():
def compute_product_inventory_by_property(property_keyword): def compute_product_inventory_by_property(property_keyword):
"""根据仓库属性标签计算每个产品的库存(如云仓、外仓)""" """根据仓库属性标签计算每个产品的库存(如云仓、外仓)"""
wh_codes = query( wh_codes = query(
"SELECT warehouse_code FROM warehouses WHERE FIND_IN_SET(%s, warehouse_property) > 0", "SELECT warehouse_code FROM warehouses WHERE FIND_IN_SET(%s, warehouse_property) > 0 "
"AND COALESCE(disabled, '否') != '是'",
(property_keyword,) (property_keyword,)
) )
# 仓库管理和库存明细的编号格式可能不同(例如管理表为035、明细为35)。 # 仓库管理和库存明细的编号格式可能不同(例如管理表为035、明细为35)。
@ -171,7 +196,7 @@ def compute_product_in_transit():
"SELECT product_code, SUM(production_in_transit) AS prod_in_transit, " "SELECT product_code, SUM(production_in_transit) AS prod_in_transit, "
"SUM(purchase_in_transit) AS purch_in_transit " "SUM(purchase_in_transit) AS purch_in_transit "
"FROM inventory_detail " "FROM inventory_detail "
"WHERE data_date = %s " f"WHERE data_date = %s AND {active_warehouse_condition('inventory_detail')} "
"GROUP BY product_code", "GROUP BY product_code",
(latest_str,)) (latest_str,))
transit_map = {} transit_map = {}
@ -272,7 +297,7 @@ def compute_inventory_cost_summary(latest_date=None):
_cost_map = {r['product_code']: resolve_cost_price(r['product_code'], float(r['pr']) if r['pr'] else 0) for r in _latest_cost} _cost_map = {r['product_code']: resolve_cost_price(r['product_code'], float(r['pr']) if r['pr'] else 0) for r in _latest_cost}
# 成品/包材分类汇总 # 成品/包材分类汇总
_inventory_rows = query(""" _inventory_rows = query(f"""
SELECT id.product_code, SELECT id.product_code,
SUM(CASE WHEN SUM(CASE WHEN
(p.product_type IN ('成品', '清库存', '现货', '赠品') AND p.deleted_at IS NULL) (p.product_type IN ('成品', '清库存', '现货', '赠品') AND p.deleted_at IS NULL)
@ -302,6 +327,7 @@ def compute_inventory_cost_summary(latest_date=None):
FROM inventory_detail id FROM inventory_detail id
LEFT JOIN products p ON id.product_code = p.product_code LEFT JOIN products p ON id.product_code = p.product_code
WHERE id.data_date = %s WHERE id.data_date = %s
AND {active_warehouse_condition('id')}
AND id.product_code NOT LIKE %s AND id.product_code NOT LIKE %s
AND id.product_name NOT LIKE %s AND id.product_name NOT LIKE %s
AND NOT (id.product_code IN ('POZ-03033', 'POZ-DXPW-1668-PDD', 'AH-YS-0584-TMGJ', 'POZ-JHY-7022', 'SEDATE-YMT-1682-PDD') AND NOT (id.product_code IN ('POZ-03033', 'POZ-DXPW-1668-PDD', 'AH-YS-0584-TMGJ', 'POZ-JHY-7022', 'SEDATE-YMT-1682-PDD')

View File

@ -20786,7 +20786,10 @@ window.viewBackupDetail = function(bid, detailItem) {
}); });
var html = '<div style="padding:24px 24px 8px">'; var html = '<div style="padding:24px 24px 8px">';
html += '<h3 style="margin-bottom:16px;font-size:17px">📋 下单详情 - ' + escHtml(code) + '</h3>'; html += '<div style="display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:16px">';
html += '<h3 style="margin:0;font-size:17px">📋 下单详情 - ' + escHtml(code) + '</h3>';
html += '<button type="button" aria-label="关闭下单详情" title="关闭" onclick="this.closest(\'.modal-overlay\').remove()" style="width:32px;height:32px;flex:0 0 32px;display:flex;align-items:center;justify-content:center;border:0;border-radius:6px;background:transparent;color:#8c8c8c;font-size:24px;line-height:1;cursor:pointer" onmouseenter="this.style.background=\'#f5f5f5\';this.style.color=\'#262626\'" onmouseleave="this.style.background=\'transparent\';this.style.color=\'#8c8c8c\'">×</button>';
html += '</div>';
// ─── 板块1:成品信息 ─── // ─── 板块1:成品信息 ───
html += '<h4 style="font-size:14px;color:#333;margin:0 0 10px 0;padding:8px 12px;background:#e6f7ff;border-radius:6px">📦 成品信息</h4>'; html += '<h4 style="font-size:14px;color:#333;margin:0 0 10px 0;padding:8px 12px;background:#e6f7ff;border-radius:6px">📦 成品信息</h4>';
@ -21499,7 +21502,6 @@ function showOrderDetail(encodedStr, title) {
{label:'货品名称', field:'名称', editable:false}, {label:'货品名称', field:'名称', editable:false},
{label:'到货仓库', field:'仓库到货', editable:true, type:'supplier'}, {label:'到货仓库', field:'仓库到货', editable:true, type:'supplier'},
{label:'实际下单数量', field:'数量', editable:true}, {label:'实际下单数量', field:'数量', editable:true},
{label:'下单备注', field:'下单备注', editable:true},
{label:'计划下单日期', field:'计划下单时间', editable:true, type:'date'}, {label:'计划下单日期', field:'计划下单时间', editable:true, type:'date'},
{label:'跟单下单日期', field:'日期', editable:true, type:'date'}, {label:'跟单下单日期', field:'日期', editable:true, type:'date'},
{label:'是否更换稿件', field:'有无新稿件', editable:true, type:'select', options:['是','否']}, {label:'是否更换稿件', field:'有无新稿件', editable:true, type:'select', options:['是','否']},
@ -21658,6 +21660,15 @@ function showOrderDetail(encodedStr, title) {
html += '</div>'; html += '</div>';
html += '</div></div>'; html += '</div></div>';
// 三类订单的下单备注取自外层列表同一行,整行展示并放在进度上方。
// 使用与进度相同的文本域样式,保存时仍走各订单现有更新接口。
if (!isFinishedDetail) {
var orderRemarkVal = row['下单备注'];
if (orderRemarkVal === null || orderRemarkVal === undefined) orderRemarkVal = '';
var remarkEditable = window._hasPagePerm(detailPermissionPage, 'edit');
html += '<div style="margin-top:6px"><div class="detail-pair" style="min-height:70px"><span class="detail-lbl" style="min-height:68px">下单备注</span><div class="detail-val-wrap" style="padding:4px 12px"><textarea class="detail-textarea" data-orig="' + escHtml(String(orderRemarkVal)) + '" data-id="' + row.id + '" data-field="下单备注"' + (remarkEditable ? '' : ' readonly') + ' style="width:100%;height:60px;box-sizing:border-box;font-size:13px;border:1px solid #d9d9d9;border-radius:4px;padding:6px 8px;color:#333;background:' + (remarkEditable ? '#fff' : '#f5f5f5') + ';resize:vertical;font-family:inherit">' + escHtml(String(orderRemarkVal)) + '</textarea></div></div></div>';
}
// 成品提货的进度字段整行显示,数据来自国内订单中最早的未完结订单,只读。 // 成品提货的进度字段整行显示,数据来自国内订单中最早的未完结订单,只读。
if (isFinishedDetail) { if (isFinishedDetail) {
var finishedProgress = row['产品进度'] || row['进度'] || ''; var finishedProgress = row['产品进度'] || row['进度'] || '';