#feature: update SQL generator
This commit is contained in:
@@ -33,7 +33,7 @@
|
||||
<textarea
|
||||
v-model="inputText"
|
||||
rows="8"
|
||||
placeholder="示例: X0X60F 17141\nC01008446046, 11894"
|
||||
:placeholder="currentTool.placeholder"
|
||||
class="w-full flex-1 min-h-[200px] px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm font-mono"
|
||||
></textarea>
|
||||
<div class="flex items-center justify-between mt-2">
|
||||
@@ -63,7 +63,7 @@
|
||||
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 flex flex-col min-h-0">
|
||||
<div class="flex flex-col gap-4 h-full min-h-0">
|
||||
<div>
|
||||
<div v-if="showQuerySql">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h3 class="text-sm lg:text-base font-semibold text-gray-900">查询SQL</h3>
|
||||
<button
|
||||
@@ -83,7 +83,50 @@
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col flex-1 min-h-0">
|
||||
<div v-if="showSplitOutput" class="flex flex-col flex-1 min-h-0">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h3 class="text-sm lg:text-base font-semibold text-gray-900">生成结果</h3>
|
||||
<div v-if="stats.total" class="text-xs text-gray-500">
|
||||
共 {{ stats.total }} 条,更新 {{ stats.update }} 条
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-rows-3 gap-3 flex-1 min-h-0">
|
||||
<div
|
||||
v-for="section in splitOutputSections"
|
||||
:key="section.key"
|
||||
class="flex flex-col min-h-0 border border-gray-200 rounded-lg overflow-hidden bg-gray-50"
|
||||
>
|
||||
<div class="flex items-center justify-between px-3 py-2 border-b border-gray-200 bg-white">
|
||||
<div class="text-sm font-semibold text-gray-900">{{ section.label }}</div>
|
||||
<button
|
||||
@click="copySplitOutput(section.key)"
|
||||
:disabled="!splitOutputSql[section.key]"
|
||||
class="px-3 py-1.5 text-xs text-gray-600 border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-60"
|
||||
type="button"
|
||||
>
|
||||
复制{{ section.label }}
|
||||
</button>
|
||||
</div>
|
||||
<textarea
|
||||
v-model="splitOutputSql[section.key]"
|
||||
readonly
|
||||
class="w-full flex-1 min-h-[120px] px-3 py-2 border-0 text-sm font-mono bg-gray-50 resize-none focus:ring-0"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between mt-2">
|
||||
<p class="text-xs text-gray-400">结果仅用于复制执行,请确认无误后使用</p>
|
||||
</div>
|
||||
<div
|
||||
v-if="copyStatus.message"
|
||||
class="mt-2 text-xs"
|
||||
:class="copyStatus.type === 'success' ? 'text-green-600' : 'text-red-600'"
|
||||
>
|
||||
{{ copyStatus.message }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="flex flex-col flex-1 min-h-0">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h3 class="text-sm lg:text-base font-semibold text-gray-900">生成结果</h3>
|
||||
<div v-if="stats.total" class="text-xs text-gray-500">
|
||||
@@ -143,6 +186,11 @@ export default {
|
||||
selectedTool: 'ob-external-id',
|
||||
inputText: '',
|
||||
outputSql: '',
|
||||
splitOutputSql: {
|
||||
sp: '',
|
||||
ppCn: '',
|
||||
ppUs: ''
|
||||
},
|
||||
querySql: '',
|
||||
loading: false,
|
||||
errors: [],
|
||||
@@ -161,7 +209,14 @@ export default {
|
||||
{
|
||||
value: 'ob-external-id',
|
||||
label: 'OB外部ID',
|
||||
description: '每行输入 case_id 与 ob_id,系统会判断是否生成更新或插入 SQL。'
|
||||
description: '每行输入 case_id 与 ob_id,系统会判断是否生成更新或插入 SQL。',
|
||||
placeholder: '示例: X0X60F 17141\nC01008446046, 11894'
|
||||
},
|
||||
{
|
||||
value: 'new-factory-return-redelivery',
|
||||
label: '新区工厂退库重新出库',
|
||||
description: '每行输入加工单、病例号、正确运单,只读取前三列;根据 CRM 加工单关联地址国家拆分 PP-CN / PP-US SQL。',
|
||||
placeholder: '示例: M20260508006905 225KF9 SF123456789\nM20260509005949 C01005934247 UPS987654321'
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -169,12 +224,29 @@ export default {
|
||||
computed: {
|
||||
currentTool() {
|
||||
return this.toolOptions.find((tool) => tool.value === this.selectedTool) || this.toolOptions[0];
|
||||
},
|
||||
|
||||
showSplitOutput() {
|
||||
return this.selectedTool === 'new-factory-return-redelivery';
|
||||
},
|
||||
|
||||
showQuerySql() {
|
||||
return !this.showSplitOutput;
|
||||
},
|
||||
|
||||
splitOutputSections() {
|
||||
return [
|
||||
{ key: 'sp', label: 'SP' },
|
||||
{ key: 'ppCn', label: 'PP-CN' },
|
||||
{ key: 'ppUs', label: 'PP-US' }
|
||||
];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clearInput() {
|
||||
this.inputText = '';
|
||||
this.outputSql = '';
|
||||
this.resetSplitOutputSql();
|
||||
this.querySql = '';
|
||||
this.errors = [];
|
||||
this.warnings = [];
|
||||
@@ -249,6 +321,7 @@ export default {
|
||||
async generateSql() {
|
||||
this.errors = [];
|
||||
this.outputSql = '';
|
||||
this.resetSplitOutputSql();
|
||||
this.querySql = '';
|
||||
this.warnings = [];
|
||||
this.resetCopyStatus();
|
||||
@@ -259,6 +332,11 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.selectedTool === 'new-factory-return-redelivery') {
|
||||
await this.generateNewFactoryReturnRedeliverySql();
|
||||
return;
|
||||
}
|
||||
|
||||
this.errors = ['未识别的功能类型,请重新选择。'];
|
||||
},
|
||||
|
||||
@@ -321,6 +399,113 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
parseNewFactoryReturnRedeliveryInput() {
|
||||
const lines = this.inputText.split(/\r?\n/);
|
||||
const errors = [];
|
||||
const rows = [];
|
||||
|
||||
lines.forEach((line, index) => {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parts = trimmed.split(/[\s,,]+/).filter(Boolean);
|
||||
if (parts.length < 3) {
|
||||
errors.push(`第 ${index + 1} 行格式不正确,请提供加工单、病例号、正确运单三列`);
|
||||
return;
|
||||
}
|
||||
|
||||
rows.push({
|
||||
productionCode: parts[0],
|
||||
caseCode: parts[1],
|
||||
expressNo: parts[2],
|
||||
lineNumber: index + 1
|
||||
});
|
||||
});
|
||||
|
||||
if (rows.length === 0 && errors.length === 0) {
|
||||
errors.push('请输入至少一行加工单、病例号、正确运单。');
|
||||
}
|
||||
|
||||
return {
|
||||
rows,
|
||||
errors
|
||||
};
|
||||
},
|
||||
|
||||
async generateNewFactoryReturnRedeliverySql() {
|
||||
const { rows, errors } = this.parseNewFactoryReturnRedeliveryInput();
|
||||
if (errors.length) {
|
||||
this.errors = errors;
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
const productionCodes = [...new Set(rows.map((row) => row.productionCode))];
|
||||
this.querySql = this.buildProductionCountriesSelect(productionCodes);
|
||||
const response = await fetch('/api/sql-generator/production-countries/check', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
||||
},
|
||||
body: JSON.stringify({
|
||||
production_codes: productionCodes
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok || !data.success) {
|
||||
this.errors = [data.message || '查询 CRM 加工单国家失败,请稍后重试。'];
|
||||
return;
|
||||
}
|
||||
|
||||
const productionCountries = data.data.production_countries || {};
|
||||
const missingRows = rows.filter((row) => !productionCountries[row.productionCode] || productionCountries[row.productionCode].length === 0);
|
||||
if (missingRows.length) {
|
||||
this.errors = missingRows.map((row) => `第 ${row.lineNumber} 行加工单 ${row.productionCode} 未在 CRM 查询到地址国家`);
|
||||
return;
|
||||
}
|
||||
|
||||
const spSql = [];
|
||||
const ppCnSql = [];
|
||||
const ppUsSql = [];
|
||||
|
||||
rows.forEach((row) => {
|
||||
const productionCode = this.escapeSqlValue(row.productionCode);
|
||||
const expressNo = this.escapeSqlValue(row.expressNo);
|
||||
const ppSql = this.isUsProductionCountry(productionCountries[row.productionCode]) ? ppUsSql : ppCnSql;
|
||||
|
||||
spSql.push(`update case_deliveries set express_no = '${expressNo}' where production_code = '${productionCode}';`);
|
||||
ppSql.push(`update delivery_records set express_no = '${expressNo}' where production_code = '${productionCode}';`);
|
||||
ppSql.push(`update receives set express_no = '${expressNo}' where production_code = '${productionCode}';`);
|
||||
});
|
||||
|
||||
const sections = [
|
||||
this.buildSqlSection('SP', spSql),
|
||||
this.buildSqlSection('PP-CN', ppCnSql),
|
||||
this.buildSqlSection('PP-US', ppUsSql)
|
||||
].filter(Boolean);
|
||||
|
||||
this.stats.update = spSql.length + ppCnSql.length + ppUsSql.length;
|
||||
this.stats.total = this.stats.update;
|
||||
this.splitOutputSql = {
|
||||
sp: spSql.join('\n'),
|
||||
ppCn: ppCnSql.join('\n'),
|
||||
ppUs: ppUsSql.join('\n')
|
||||
};
|
||||
this.outputSql = sections.join('\n\n');
|
||||
} catch (error) {
|
||||
this.errors = ['网络请求失败: ' + error.message];
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
escapeSqlValue(value) {
|
||||
return String(value).replace(/'/g, "''");
|
||||
},
|
||||
@@ -334,6 +519,32 @@ export default {
|
||||
return `select * from case_extras where case_code in (${inValues})`;
|
||||
},
|
||||
|
||||
buildProductionCountriesSelect(productionCodes) {
|
||||
if (!productionCodes.length) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const inValues = productionCodes.map((productionCode) => `'${this.escapeSqlValue(productionCode)}'`).join(', ');
|
||||
return [
|
||||
'select ep.name, epc.ea_case_id_c, epc.ea_businessorder_id_c, epc.ea_salesorder_id_c',
|
||||
'from ea_production ep',
|
||||
'join ea_production_cstm epc on ep.id = epc.id_c',
|
||||
`where ep.deleted = 0 and ep.name in (${inValues})`
|
||||
].join('\n');
|
||||
},
|
||||
|
||||
isUsProductionCountry(countryCodes) {
|
||||
return (countryCodes || []).some((countryCode) => ['US', 'GU', 'PR'].includes(String(countryCode).toUpperCase()));
|
||||
},
|
||||
|
||||
buildSqlSection(title, sqlLines) {
|
||||
if (!sqlLines.length) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return [`-- ${title}`, ...sqlLines].join('\n');
|
||||
},
|
||||
|
||||
buildDuplicateWarnings(duplicates) {
|
||||
return duplicates.map((duplicate) => {
|
||||
const lines = duplicate.lineNumbers.join('、');
|
||||
@@ -348,6 +559,14 @@ export default {
|
||||
};
|
||||
},
|
||||
|
||||
resetSplitOutputSql() {
|
||||
this.splitOutputSql = {
|
||||
sp: '',
|
||||
ppCn: '',
|
||||
ppUs: ''
|
||||
};
|
||||
},
|
||||
|
||||
setCopyStatus(message, type) {
|
||||
this.copyStatus = {
|
||||
message,
|
||||
@@ -433,6 +652,10 @@ export default {
|
||||
await this.copyToClipboard(this.outputSql, '复制失败,请手动复制结果。', 'outputTextarea');
|
||||
},
|
||||
|
||||
async copySplitOutput(key) {
|
||||
await this.copyToClipboard(this.splitOutputSql[key], '复制失败,请手动复制结果。');
|
||||
},
|
||||
|
||||
async copyQuery() {
|
||||
await this.copyToClipboard(this.querySql, '复制失败,请手动复制查询SQL。', 'queryTextarea');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user