订单管理功能优化
This commit is contained in:
parent
db13e15fbf
commit
862f868979
@ -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 采购计划"
|
||||||
|
|||||||
@ -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)})
|
||||||
|
|
||||||
|
|||||||
@ -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')
|
||||||
|
|||||||
@ -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['进度'] || '';
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user