不良品数量相关优化
This commit is contained in:
parent
c088fdb823
commit
1eb96ece68
@ -2580,16 +2580,11 @@ def _optional_int(data, key, label):
|
||||
|
||||
|
||||
def _arrival_batch_quantities(data, order_type='domestic'):
|
||||
"""Parse quantities; packaging defective quantity may be any integer."""
|
||||
"""Parse quantities; defective quantity may be any integer for all order types."""
|
||||
arrival_qty, arrival_qty_filled = _optional_int(data, 'arrival_qty', '到货数量')
|
||||
defective_qty, defective_qty_filled = _optional_int(data, 'defective_qty', '不良品数量')
|
||||
if arrival_qty_filled and arrival_qty < 0:
|
||||
raise ValueError('到货数量不能为负数')
|
||||
if order_type != 'packaging' and defective_qty_filled:
|
||||
if defective_qty < 0:
|
||||
raise ValueError('不良品数量不能为负数')
|
||||
if defective_qty > arrival_qty:
|
||||
raise ValueError('不良品数量不能大于到货数量')
|
||||
return arrival_qty, defective_qty, arrival_qty_filled, defective_qty_filled
|
||||
|
||||
|
||||
@ -2817,18 +2812,10 @@ def domestic_orders_update_batch():
|
||||
requested_defective = int(data.get('defective_qty'))
|
||||
except (TypeError, ValueError):
|
||||
return jsonify({"code": 400, "msg": "不良品数量必须为整数"}), 400
|
||||
if order_type != 'packaging' and requested_defective < 0:
|
||||
return jsonify({"code": 400, "msg": "不良品数量不能为负数"}), 400
|
||||
effective_arrival_qty = (
|
||||
requested_qty if 'requested_qty' in locals() and data.get('arrival_qty') not in (None, '')
|
||||
else int(current.get('arrival_qty') or 0)
|
||||
)
|
||||
if order_type != 'packaging' and requested_defective > effective_arrival_qty:
|
||||
return jsonify({"code": 400, "msg": "不良品数量不能大于到货数量"}), 400
|
||||
defective_locked = order_type != 'packaging' or current.get('defective_qty_filled')
|
||||
defective_locked = current.get('defective_qty_filled')
|
||||
if defective_locked and requested_defective != int(current.get('defective_qty') or 0):
|
||||
return jsonify({"code": 400, "msg": "不良品数量保存后不允许修改"}), 400
|
||||
if order_type == 'packaging' and not current.get('defective_qty_filled'):
|
||||
if not current.get('defective_qty_filled'):
|
||||
update_fields.extend(['defective_qty=%s', 'defective_qty_filled=1'])
|
||||
update_params.append(requested_defective)
|
||||
supplemented.append(f'不良品数量={requested_defective}')
|
||||
|
||||
@ -21755,7 +21755,7 @@ function showOrderDetail(encodedStr, title) {
|
||||
html += '<div style="display:flex;align-items:center;margin:0 0 8px 0">';
|
||||
html += '<div style="margin-left:auto;display:flex;align-items:center;gap:6px"><button id="addMultiBatchBtn" class="btn btn-sm btn-outline" onclick="promptAddArrivalBatchRows(this)" data-order-id="' + row.id + '" data-order-type="' + orderType + '" style="font-size:12px">➕ 添加多行</button><button id="addBatchBtn" class="btn btn-sm btn-outline" onclick="addArrivalBatchRow(this)" data-order-id="' + row.id + '" data-order-type="' + orderType + '" style="font-size:12px">➕ 添加一行</button></div>';
|
||||
html += '</div>';
|
||||
var showDefectiveQty = orderType === 'packaging';
|
||||
var showDefectiveQty = true;
|
||||
html += '<table id="arrivalBatchTable" style="width:100%;border-collapse:collapse;font-size:13px;table-layout:auto" data-order-qty="' + (parseInt(row['数量']) || 0) + '"><thead><tr>';
|
||||
html += '<th style="border:1.5px solid #b0b0b0;padding:6px 8px;background:#f5f5f5;color:#555;font-weight:600;text-align:center;white-space:nowrap;width:1%">批次编号</th>';
|
||||
html += '<th style="border:1.5px solid #b0b0b0;padding:6px 8px;background:#f5f5f5;color:#555;font-weight:600;text-align:center;white-space:nowrap;width:1%">操作人</th>';
|
||||
@ -21859,7 +21859,7 @@ function showOrderDetail(encodedStr, title) {
|
||||
.then(function(res) {
|
||||
if (res.code !== 0) {
|
||||
var abBody = overlay.querySelector('#arrivalBatchBody');
|
||||
if (abBody) abBody.innerHTML = '<tr><td colspan="' + (orderType === 'packaging' ? 8 : 7) + '" style="border:1.5px solid #b0b0b0;padding:20px;text-align:center;color:#bbb">暂无数据</td></tr>';
|
||||
if (abBody) abBody.innerHTML = '<tr><td colspan="8" style="border:1.5px solid #b0b0b0;padding:20px;text-align:center;color:#bbb">暂无数据</td></tr>';
|
||||
var pmBody = overlay.querySelector('#packagingMaterialBody');
|
||||
if (pmBody) pmBody.innerHTML = '<tr><td colspan="7" style="border:1.5px solid #b0b0b0;padding:20px;text-align:center;color:#bbb">暂无数据</td></tr>';
|
||||
return;
|
||||
@ -21872,7 +21872,7 @@ function showOrderDetail(encodedStr, title) {
|
||||
var abHtml = '';
|
||||
var totalQty = 0;
|
||||
var totalDefectiveQty = 0;
|
||||
var showDefectiveQty = orderType === 'packaging';
|
||||
var showDefectiveQty = true;
|
||||
var batchCount = data.arrival_batches ? data.arrival_batches.length : 0;
|
||||
// 数据行
|
||||
data.arrival_batches.forEach(function(b, idx) {
|
||||
@ -21892,7 +21892,7 @@ function showOrderDetail(encodedStr, title) {
|
||||
abHtml += '<td class="batch-operated-at" style="border:1.5px solid #b0b0b0;padding:6px 8px;text-align:center;white-space:nowrap">' + escHtml(b.operated_at || '-') + '</td>';
|
||||
abHtml += renderArrivalBatchStoredCell('date', 'batch-date', dateVal, dateFilled, allowBlankFill, '到货日期') +
|
||||
renderArrivalBatchStoredCell('number', 'batch-qty', qtyFilled ? qty : '', qtyFilled, allowBlankFill, '到货数量', true) +
|
||||
(showDefectiveQty ? renderArrivalBatchStoredCell('number', 'batch-defective-qty', defectiveFilled ? defectiveQty : '', defectiveFilled, true, '不良品数量', false) : '') +
|
||||
renderArrivalBatchStoredCell('number', 'batch-defective-qty', defectiveFilled ? defectiveQty : '', defectiveFilled, true, '不良品数量', false) +
|
||||
'<td class="batch-unfinished" style="border:1.5px solid #b0b0b0;padding:6px 10px;text-align:center">0</td>' +
|
||||
'<td style="border:1.5px solid #b0b0b0;padding:4px 6px"><div style="display:flex;gap:2px;align-items:center;justify-content:center">' +
|
||||
'<span class="arrival-remark-field" style="position:relative;display:block;flex:1;min-width:120px;max-width:220px"><textarea rows="2" class="batch-remark" data-orig="' + escHtml(b.remark || '') + '" style="width:100%;box-sizing:border-box;font-size:12px;text-align:left;border:1px solid #d9d9d9;border-radius:3px;padding:4px 27px 4px 6px;resize:vertical">' + escHtml(b.remark || '') + '</textarea><button type="button" class="arrival-remark-expand" title="展开编辑备注" aria-label="展开编辑备注" onclick="openArrivalRemarkViewer(this)" style="position:absolute;right:3px;bottom:3px;width:20px;height:20px;padding:0;border:0;border-radius:4px;background:#e6f7ff;color:#1677ff;line-height:20px;font-size:13px;cursor:pointer">⛶</button></span>' +
|
||||
@ -21909,7 +21909,7 @@ function showOrderDetail(encodedStr, title) {
|
||||
abHtml += '<td class="batch-operated-at" style="border:1.5px solid #b0b0b0;padding:6px 8px;text-align:center;color:#ccc">-</td>';
|
||||
abHtml += '<td style="border:1.5px solid #b0b0b0;padding:4px 6px;text-align:center"><input type="date" class="batch-date" style="width:66%;box-sizing:border-box;font-size:12px;color:#999;text-align:center;border:1px solid #d9d9d9;border-radius:3px;padding:4px 8px"></td>';
|
||||
abHtml += '<td style="border:1.5px solid #b0b0b0;padding:4px 6px;text-align:center"><input type="number" min="0" class="batch-qty" placeholder="数量" oninput="recalcArrivalUnfinished()" onkeydown="batchQtyKeydown(event)" style="width:66%;box-sizing:border-box;font-size:12px;text-align:center;border:1px solid #d9d9d9;border-radius:3px;padding:4px 8px"></td>';
|
||||
if (showDefectiveQty) abHtml += '<td style="border:1.5px solid #b0b0b0;padding:4px 6px;text-align:center"><input type="number" class="batch-defective-qty" placeholder="不良品" oninput="recalcArrivalUnfinished()" onkeydown="batchQtyKeydown(event)" style="width:66%;box-sizing:border-box;font-size:12px;text-align:center;border:1px solid #d9d9d9;border-radius:3px;padding:4px 8px"></td>';
|
||||
abHtml += '<td style="border:1.5px solid #b0b0b0;padding:4px 6px;text-align:center"><input type="number" class="batch-defective-qty" placeholder="不良品" oninput="recalcArrivalUnfinished()" onkeydown="batchQtyKeydown(event)" style="width:66%;box-sizing:border-box;font-size:12px;text-align:center;border:1px solid #d9d9d9;border-radius:3px;padding:4px 8px"></td>';
|
||||
abHtml += '<td class="batch-unfinished" style="border:1.5px solid #b0b0b0;padding:6px 10px;text-align:center;color:#ccc">-</td>';
|
||||
abHtml += '<td style="border:1.5px solid #b0b0b0;padding:4px 6px"><div style="display:flex;gap:2px;align-items:center;justify-content:center"><span class="arrival-remark-field" style="position:relative;display:block;flex:1;min-width:120px;max-width:220px"><textarea rows="2" class="batch-remark" placeholder="备注" style="width:100%;box-sizing:border-box;font-size:12px;text-align:left;border:1px solid #d9d9d9;border-radius:3px;padding:4px 27px 4px 6px;resize:vertical"></textarea><button type="button" class="arrival-remark-expand" title="展开编辑备注" aria-label="展开编辑备注" onclick="openArrivalRemarkViewer(this)" style="position:absolute;right:3px;bottom:3px;width:20px;height:20px;padding:0;border:0;border-radius:4px;background:#e6f7ff;color:#1677ff;line-height:20px;font-size:13px;cursor:pointer">⛶</button></span><button onclick="saveNewArrivalBatch(this,' + orderId + ',\'' + orderType + '\')" title="保存" style="padding:2px 6px;font-size:11px;border:1px solid #1890ff;background:#e6f7ff;color:#1890ff;border-radius:3px;cursor:pointer;white-space:nowrap">✅</button></div></td>';
|
||||
abHtml += '</tr>';
|
||||
@ -21946,7 +21946,7 @@ function showOrderDetail(encodedStr, title) {
|
||||
.catch(function(err) {
|
||||
console.error('加载详情额外数据失败:', err);
|
||||
var abBody = overlay.querySelector('#arrivalBatchBody');
|
||||
if (abBody) abBody.innerHTML = '<tr><td colspan="' + (orderType === 'packaging' ? 8 : 7) + '" style="border:1.5px solid #b0b0b0;padding:20px;text-align:center;color:#bbb">加载失败</td></tr>';
|
||||
if (abBody) abBody.innerHTML = '<tr><td colspan="8" style="border:1.5px solid #b0b0b0;padding:20px;text-align:center;color:#bbb">加载失败</td></tr>';
|
||||
var pmBody = overlay.querySelector('#packagingMaterialBody');
|
||||
if (pmBody) pmBody.innerHTML = '<tr><td colspan="' + (orderType === 'domestic' ? 8 : 7) + '" style="border:1.5px solid #b0b0b0;padding:20px;text-align:center;color:#bbb">加载失败</td></tr>';
|
||||
});
|
||||
@ -21996,7 +21996,7 @@ function appendArrivalBatchEditingRow(btn) {
|
||||
'<td class="batch-operated-at" style="border:1.5px solid #b0b0b0;padding:6px 8px;text-align:center;color:#ccc">-</td>' +
|
||||
'<td style="border:1.5px solid #b0b0b0;padding:4px 6px;text-align:center"><input type="date" class="batch-date" style="width:66%;box-sizing:border-box;font-size:12px;text-align:center;border:1px solid #d9d9d9;border-radius:3px;padding:4px 8px"></td>' +
|
||||
'<td style="border:1.5px solid #b0b0b0;padding:4px 6px;text-align:center"><input type="number" min="0" class="batch-qty" oninput="recalcArrivalUnfinished()" onkeydown="batchQtyKeydown(event)" style="width:66%;box-sizing:border-box;font-size:12px;text-align:center;border:1px solid #d9d9d9;border-radius:3px;padding:4px 8px" placeholder="数量"></td>' +
|
||||
(orderType === 'packaging' ? '<td style="border:1.5px solid #b0b0b0;padding:4px 6px;text-align:center"><input type="number" class="batch-defective-qty" oninput="recalcArrivalUnfinished()" onkeydown="batchQtyKeydown(event)" style="width:66%;box-sizing:border-box;font-size:12px;text-align:center;border:1px solid #d9d9d9;border-radius:3px;padding:4px 8px" placeholder="不良品"></td>' : '') +
|
||||
'<td style="border:1.5px solid #b0b0b0;padding:4px 6px;text-align:center"><input type="number" class="batch-defective-qty" oninput="recalcArrivalUnfinished()" onkeydown="batchQtyKeydown(event)" style="width:66%;box-sizing:border-box;font-size:12px;text-align:center;border:1px solid #d9d9d9;border-radius:3px;padding:4px 8px" placeholder="不良品"></td>' +
|
||||
'<td class="batch-unfinished" style="border:1.5px solid #b0b0b0;padding:4px 6px"></td>' +
|
||||
'<td style="border:1.5px solid #b0b0b0;padding:4px 6px"><div style="display:flex;gap:2px;align-items:center;justify-content:center">' +
|
||||
'<span class="arrival-remark-field" style="position:relative;display:block;flex:1;min-width:120px;max-width:220px"><textarea rows="2" class="batch-remark" style="width:100%;box-sizing:border-box;font-size:12px;text-align:left;border:1px solid #d9d9d9;border-radius:3px;padding:4px 27px 4px 6px;resize:vertical" placeholder="备注"></textarea><button type="button" class="arrival-remark-expand" title="展开编辑备注" aria-label="展开编辑备注" onclick="openArrivalRemarkViewer(this)" style="position:absolute;right:3px;bottom:3px;width:20px;height:20px;padding:0;border:0;border-radius:4px;background:#e6f7ff;color:#1677ff;line-height:20px;font-size:13px;cursor:pointer">⛶</button></span>' +
|
||||
@ -22102,7 +22102,6 @@ function saveNewArrivalBatch(btn, orderId, orderType) {
|
||||
if (!date && qtyRaw === '') { alert('请输入到货日期或数量'); return; }
|
||||
if (qtyRaw !== '' && (!Number.isInteger(qty) || qty < 0)) { alert('到货数量必须为非负整数'); return; }
|
||||
if (defectiveRaw !== '' && !Number.isInteger(defectiveQty)) { alert('不良品数量必须为整数'); return; }
|
||||
if (orderType !== 'packaging' && defectiveRaw !== '' && (defectiveQty < 0 || defectiveQty > (qty || 0))) { alert('不良品数量不能为负数或大于到货数量'); return; }
|
||||
btn.disabled = true;
|
||||
btn.textContent = '⋯';
|
||||
fetch('/api/domestic/orders/add_batch', {
|
||||
@ -22123,7 +22122,7 @@ function saveNewArrivalBatch(btn, orderId, orderType) {
|
||||
'<td class="batch-operated-at" style="border:1.5px solid #b0b0b0;padding:6px 8px;text-align:center;white-space:nowrap">' + escHtml(res.data && res.data.operated_at || '-') + '</td>' +
|
||||
renderArrivalBatchStoredCell('date', 'batch-date', date, !!(res.data && res.data.arrival_date_filled), orderType === 'packaging', '到货日期') +
|
||||
renderArrivalBatchStoredCell('number', 'batch-qty', qty, !!(res.data && res.data.arrival_qty_filled), orderType === 'packaging', '到货数量', true) +
|
||||
(orderType === 'packaging' ? renderArrivalBatchStoredCell('number', 'batch-defective-qty', defectiveQty, !!(res.data && res.data.defective_qty_filled), true, '不良品数量', false) : '') +
|
||||
renderArrivalBatchStoredCell('number', 'batch-defective-qty', defectiveQty, !!(res.data && res.data.defective_qty_filled), true, '不良品数量', false) +
|
||||
'<td class="batch-unfinished" style="border:1.5px solid #b0b0b0;padding:6px 10px;text-align:center">0</td>' +
|
||||
'<td style="border:1.5px solid #b0b0b0;padding:4px 6px"><div style="display:flex;gap:2px;align-items:center;justify-content:center"><span class="arrival-remark-field" style="position:relative;display:block;flex:1;min-width:120px;max-width:220px"><textarea rows="2" class="batch-remark" data-orig="' + escHtml(remark) + '" style="width:100%;box-sizing:border-box;font-size:12px;text-align:left;border:1px solid #d9d9d9;border-radius:3px;padding:4px 27px 4px 6px;resize:vertical">' + escHtml(remark) + '</textarea><button type="button" class="arrival-remark-expand" title="展开编辑备注" aria-label="展开编辑备注" onclick="openArrivalRemarkViewer(this)" style="position:absolute;right:3px;bottom:3px;width:20px;height:20px;padding:0;border:0;border-radius:4px;background:#e6f7ff;color:#1677ff;line-height:20px;font-size:13px;cursor:pointer">⛶</button></span><button onclick="saveArrivalBatch(this,' + batchId + ',' + orderId + ',\'' + orderType + '\')" title="保存" style="padding:2px 6px;font-size:11px;border:1px solid #1890ff;background:#e6f7ff;color:#1890ff;border-radius:3px;cursor:pointer;white-space:nowrap">✅</button><button onclick="deleteArrivalBatch(this,' + batchId + ',\'' + orderType + '\')" title="删除" style="padding:2px 6px;font-size:11px;border:1px solid #ff4d4f;background:#fff1f0;color:#ff4d4f;border-radius:3px;cursor:pointer;margin-left:2px;white-space:nowrap">✕</button></div></td>';
|
||||
recalcArrivalUnfinished();
|
||||
@ -22153,7 +22152,6 @@ function saveArrivalBatch(btn, batchId, orderId, orderType) {
|
||||
var remark = tr.querySelector('.batch-remark')?.value || '';
|
||||
if (qtyRaw !== '' && (!Number.isInteger(qty) || qty < 0)) { alert('到货数量必须为非负整数'); return; }
|
||||
if (defectiveRaw !== '' && !Number.isInteger(defectiveQty)) { alert('不良品数量必须为整数'); return; }
|
||||
if (orderType !== 'packaging' && defectiveRaw !== '' && (defectiveQty < 0 || defectiveQty > (qty || 0))) { alert('不良品数量不能为负数或大于到货数量'); return; }
|
||||
btn.disabled = true;
|
||||
btn.textContent = '⋯';
|
||||
fetch('/api/domestic/orders/update_batch', {
|
||||
@ -22182,11 +22180,11 @@ function saveArrivalBatch(btn, batchId, orderId, orderType) {
|
||||
if (orderType === 'packaging') {
|
||||
var dateCell = dd && dd.closest('td');
|
||||
var qtyCell = dq && dq.closest('td');
|
||||
var defectiveCell = df && df.closest('td');
|
||||
if (dateCell && res.data && res.data.arrival_date_filled) dateCell.outerHTML = renderArrivalBatchStoredCell('date', 'batch-date', date, true, false, '到货日期');
|
||||
if (qtyCell && res.data && res.data.arrival_qty_filled) qtyCell.outerHTML = renderArrivalBatchStoredCell('number', 'batch-qty', qty, true, false, '到货数量', true);
|
||||
if (defectiveCell && res.data && res.data.defective_qty_filled) defectiveCell.outerHTML = renderArrivalBatchStoredCell('number', 'batch-defective-qty', defectiveQty, true, false, '不良品数量', false);
|
||||
}
|
||||
var defectiveCell = df && df.closest('td');
|
||||
if (defectiveCell && res.data && res.data.defective_qty_filled) defectiveCell.outerHTML = renderArrivalBatchStoredCell('number', 'batch-defective-qty', defectiveQty, true, false, '不良品数量', false);
|
||||
recalcArrivalUnfinished();
|
||||
refreshOrderListFromArrivalModal(btn);
|
||||
setTimeout(function() { btn.textContent = '✅'; }, 500);
|
||||
@ -22370,11 +22368,10 @@ function saveDetailChanges(btn) {
|
||||
var invalidBatch = batchAdds.concat(batchUpdates).some(function(b) {
|
||||
if (b.arrival_qty !== '' && (!Number.isInteger(b.arrival_qty) || b.arrival_qty < 0)) return true;
|
||||
if (b.defective_qty !== '' && !Number.isInteger(b.defective_qty)) return true;
|
||||
return orderType !== 'packaging' && b.defective_qty !== '' &&
|
||||
(b.defective_qty < 0 || b.defective_qty > (b.arrival_qty || 0));
|
||||
return false;
|
||||
});
|
||||
if (invalidBatch) {
|
||||
showCenteredAlert('提示', orderType === 'packaging' ? '到货数量必须为非负整数,不良品数量必须为整数' : '数量格式错误,且不良品数量不能为负数或大于到货数量');
|
||||
showCenteredAlert('提示', '到货数量必须为非负整数,不良品数量必须为整数');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -18,21 +18,26 @@ class PackagingArrivalDefectiveTest(unittest.TestCase):
|
||||
self.assertEqual((10, 0, True, False), _arrival_batch_quantities(
|
||||
{'arrival_qty': '10', 'defective_qty': ''}, 'packaging'))
|
||||
|
||||
def test_other_order_types_keep_existing_validation(self):
|
||||
def test_domestic_and_overseas_use_same_defective_rules(self):
|
||||
from routes.purchase import _arrival_batch_quantities
|
||||
|
||||
with self.assertRaisesRegex(ValueError, '不能为负数'):
|
||||
_arrival_batch_quantities({'arrival_qty': '10', 'defective_qty': '-1'}, 'domestic')
|
||||
with self.assertRaisesRegex(ValueError, '不能大于到货数量'):
|
||||
_arrival_batch_quantities({'arrival_qty': '10', 'defective_qty': '11'}, 'overseas')
|
||||
self.assertEqual((10, -1, True, True), _arrival_batch_quantities(
|
||||
{'arrival_qty': '10', 'defective_qty': '-1'}, 'domestic'))
|
||||
self.assertEqual((10, 11, True, True), _arrival_batch_quantities(
|
||||
{'arrival_qty': '10', 'defective_qty': '11'}, 'overseas'))
|
||||
|
||||
def test_frontend_keeps_blank_and_only_validates_non_packaging_range(self):
|
||||
source = Path(__file__).resolve().parents[1].joinpath('static/js/app.js').read_text(encoding='utf-8')
|
||||
self.assertIn("defectiveRaw === '' ? ''", source)
|
||||
self.assertIn("orderType !== 'packaging' && defectiveRaw !== ''", source)
|
||||
self.assertNotIn("orderType !== 'packaging' && defectiveRaw !== ''", source)
|
||||
self.assertIn("renderArrivalBatchStoredCell('number', 'batch-defective-qty'", source)
|
||||
self.assertNotIn('min="0" class="batch-defective-qty"', source)
|
||||
|
||||
def test_defective_column_is_visible_for_all_three_order_types(self):
|
||||
source = Path(__file__).resolve().parents[1].joinpath('static/js/app.js').read_text(encoding='utf-8')
|
||||
self.assertIn('var showDefectiveQty = true;', source)
|
||||
self.assertIn("renderArrivalBatchStoredCell('number', 'batch-defective-qty'", source)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user