v1.0.47: differ — начальный vs все допники (как Flask)
This commit is contained in:
+62
-149
@@ -2,167 +2,80 @@
|
|||||||
<cfheader name="Content-Type" value="application/json; charset=utf-8">
|
<cfheader name="Content-Type" value="application/json; charset=utf-8">
|
||||||
|
|
||||||
<cfparam name="url.contract_id" default="">
|
<cfparam name="url.contract_id" default="">
|
||||||
|
|
||||||
<cfset result = {ok: false, error: ""}>
|
<cfset result = {ok: false, error: ""}>
|
||||||
|
|
||||||
<cftry>
|
<cftry>
|
||||||
<cfif NOT len(url.contract_id)>
|
<cfif NOT len(url.contract_id)>
|
||||||
<cfset result.error = "contract_id required">
|
<cfset result.error = "contract_id required">
|
||||||
<cfelse>
|
<cfelse>
|
||||||
<!--- Берём два последних допника по дате --->
|
<cfquery name="initSup" datasource="baza">
|
||||||
<cfquery name="sups" datasource="baza">
|
SELECT id FROM supplements WHERE contract_id=<cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar"> AND type='initial' ORDER BY created_at LIMIT 1
|
||||||
SELECT id, type, created_at
|
|
||||||
FROM supplements
|
|
||||||
WHERE contract_id = <cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar">
|
|
||||||
ORDER BY created_at DESC
|
|
||||||
LIMIT 2
|
|
||||||
</cfquery>
|
</cfquery>
|
||||||
|
<cfif initSup.recordCount EQ 0>
|
||||||
<cfif sups.recordCount LT 2>
|
<cfquery name="initSup" datasource="baza">
|
||||||
<cfset result.error = "need at least 2 supplements, found " & sups.recordCount>
|
SELECT id FROM supplements WHERE contract_id=<cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar"> ORDER BY created_at LIMIT 1
|
||||||
|
</cfquery>
|
||||||
|
</cfif>
|
||||||
|
<cfquery name="amendments" datasource="baza">
|
||||||
|
SELECT id, type FROM supplements WHERE contract_id=<cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar"> AND id!=<cfqueryparam value="#initSup.id#" cfsqltype="cf_sql_varchar"> ORDER BY created_at
|
||||||
|
</cfquery>
|
||||||
|
<cfif amendments.recordCount EQ 0>
|
||||||
|
<cfset result.error = "no amendments">
|
||||||
<cfelse>
|
<cfelse>
|
||||||
<cfset supNew = sups.id[1]>
|
<cfquery name="rInit" datasource="baza">
|
||||||
<cfset supOld = sups.id[2]>
|
SELECT row_num, name, price, qty, sum, date_start FROM spec_rows WHERE supplement_id=<cfqueryparam value="#initSup.id#" cfsqltype="cf_sql_varchar"> ORDER BY row_num
|
||||||
|
|
||||||
<!--- Читаем spec_rows для обоих --->
|
|
||||||
<cfquery name="rowsOld" datasource="baza">
|
|
||||||
SELECT row_num, name, price, qty, sum, date_start
|
|
||||||
FROM spec_rows
|
|
||||||
WHERE supplement_id = <cfqueryparam value="#supOld#" cfsqltype="cf_sql_varchar">
|
|
||||||
ORDER BY row_num
|
|
||||||
</cfquery>
|
</cfquery>
|
||||||
|
<cfset initByNum = {}>
|
||||||
<cfquery name="rowsNew" datasource="baza">
|
<cfloop query="rInit">
|
||||||
SELECT row_num, name, price, qty, sum, date_start
|
<cfset initByNum[rInit.row_num] = {name: rInit.name, price: rInit.price, qty: rInit.qty, sum: rInit.sum, date_start: rInit.date_start}>
|
||||||
FROM spec_rows
|
|
||||||
WHERE supplement_id = <cfqueryparam value="#supNew#" cfsqltype="cf_sql_varchar">
|
|
||||||
ORDER BY row_num
|
|
||||||
</cfquery>
|
|
||||||
|
|
||||||
<!--- Индексация по row_num --->
|
|
||||||
<cfset oldByNum = {}>
|
|
||||||
<cfloop query="rowsOld">
|
|
||||||
<cfset oldByNum[rowsOld.row_num] = {
|
|
||||||
name: rowsOld.name,
|
|
||||||
price: rowsOld.price,
|
|
||||||
qty: rowsOld.qty,
|
|
||||||
sum: rowsOld.sum,
|
|
||||||
date_start: rowsOld.date_start
|
|
||||||
}>
|
|
||||||
</cfloop>
|
</cfloop>
|
||||||
|
<cfset allChanges = []>
|
||||||
<cfset newByNum = {}>
|
<cfset fields = ["name","price","qty","sum","date_start"]>
|
||||||
<cfloop query="rowsNew">
|
<cfloop query="amendments">
|
||||||
<cfset newByNum[rowsNew.row_num] = {
|
<cfquery name="rAmd" datasource="baza">
|
||||||
name: rowsNew.name,
|
SELECT row_num, name, price, qty, sum, date_start FROM spec_rows WHERE supplement_id=<cfqueryparam value="#amendments.id#" cfsqltype="cf_sql_varchar"> ORDER BY row_num
|
||||||
price: rowsNew.price,
|
|
||||||
qty: rowsNew.qty,
|
|
||||||
sum: rowsNew.sum,
|
|
||||||
date_start: rowsNew.date_start
|
|
||||||
}>
|
|
||||||
</cfloop>
|
|
||||||
|
|
||||||
<!--- Собираем все row_num --->
|
|
||||||
<cfset allNums = structKeyArray(oldByNum)>
|
|
||||||
<cfloop array="#structKeyArray(newByNum)#" index="n">
|
|
||||||
<cfif NOT structKeyExists(oldByNum, n)>
|
|
||||||
<cfset arrayAppend(allNums, n)>
|
|
||||||
</cfif>
|
|
||||||
</cfloop>
|
|
||||||
<cfset arraySort(allNums, "numeric")>
|
|
||||||
|
|
||||||
<!--- Сравниваем --->
|
|
||||||
<cfset changes = []>
|
|
||||||
<cfset fields = ["name", "price", "qty", "sum", "date_start"]>
|
|
||||||
|
|
||||||
<cfloop array="#allNums#" index="num">
|
|
||||||
<cfset inOld = structKeyExists(oldByNum, num)>
|
|
||||||
<cfset inNew = structKeyExists(newByNum, num)>
|
|
||||||
|
|
||||||
<cfif inOld AND NOT inNew>
|
|
||||||
<cfset arrayAppend(changes, {
|
|
||||||
row_num: num,
|
|
||||||
change_type: "deleted",
|
|
||||||
old_values: oldByNum[num],
|
|
||||||
new_values: {}
|
|
||||||
})>
|
|
||||||
<cfelseif NOT inOld AND inNew>
|
|
||||||
<cfset arrayAppend(changes, {
|
|
||||||
row_num: num,
|
|
||||||
change_type: "added",
|
|
||||||
old_values: {},
|
|
||||||
new_values: newByNum[num]
|
|
||||||
})>
|
|
||||||
<cfelse>
|
|
||||||
<cfset oldVals = oldByNum[num]>
|
|
||||||
<cfset newVals = newByNum[num]>
|
|
||||||
<cfset same = true>
|
|
||||||
<cfset changedFields = {}>
|
|
||||||
<cfloop array="#fields#" index="f">
|
|
||||||
<cfif structKeyExists(oldVals, f) AND structKeyExists(newVals, f)>
|
|
||||||
<cfset ov = oldVals[f]>
|
|
||||||
<cfset nv = newVals[f]>
|
|
||||||
<cfif toString(ov) NEQ toString(nv)>
|
|
||||||
<cfset same = false>
|
|
||||||
<cfset changedFields[f] = {old: ov, new: nv}>
|
|
||||||
</cfif>
|
|
||||||
<cfelseif structKeyExists(oldVals, f) OR structKeyExists(newVals, f)>
|
|
||||||
<cfset same = false>
|
|
||||||
<cfset changedFields[f] = {
|
|
||||||
old: structKeyExists(oldVals, f) ? oldVals[f] : "",
|
|
||||||
new: structKeyExists(newVals, f) ? newVals[f] : ""
|
|
||||||
}>
|
|
||||||
</cfif>
|
|
||||||
</cfloop>
|
|
||||||
|
|
||||||
<cfset arrayAppend(changes, {
|
|
||||||
row_num: num,
|
|
||||||
change_type: same ? "unchanged" : "changed",
|
|
||||||
old_values: oldVals,
|
|
||||||
new_values: newVals,
|
|
||||||
changed_fields: changedFields
|
|
||||||
})>
|
|
||||||
</cfif>
|
|
||||||
</cfloop>
|
|
||||||
|
|
||||||
<!--- Сохраняем в spec_history --->
|
|
||||||
<cfloop array="#changes#" index="ch">
|
|
||||||
<cfquery datasource="baza">
|
|
||||||
INSERT INTO spec_history (contract_id, supplement_id, row_num, change_type, old_values, new_values)
|
|
||||||
VALUES (
|
|
||||||
<cfqueryparam value="#url.contract_id#" cfsqltype="cf_sql_varchar">,
|
|
||||||
<cfqueryparam value="#supNew#" cfsqltype="cf_sql_varchar">,
|
|
||||||
<cfqueryparam value="#ch.row_num#" cfsqltype="cf_sql_integer">,
|
|
||||||
<cfqueryparam value="#ch.change_type#" cfsqltype="cf_sql_varchar">,
|
|
||||||
<cfqueryparam value="#serializeJSON(ch.old_values)#" cfsqltype="cf_sql_varchar">::jsonb,
|
|
||||||
<cfqueryparam value="#serializeJSON(ch.new_values)#" cfsqltype="cf_sql_varchar">::jsonb
|
|
||||||
)
|
|
||||||
</cfquery>
|
</cfquery>
|
||||||
|
<cfset amdByNum = {}>
|
||||||
|
<cfloop query="rAmd">
|
||||||
|
<cfset amdByNum[rAmd.row_num] = {name: rAmd.name, price: rAmd.price, qty: rAmd.qty, sum: rAmd.sum, date_start: rAmd.date_start}>
|
||||||
|
</cfloop>
|
||||||
|
<cfset allNums = structKeyArray(initByNum)>
|
||||||
|
<cfloop array="#structKeyArray(amdByNum)#" index="n">
|
||||||
|
<cfif NOT structKeyExists(initByNum, n)><cfset arrayAppend(allNums, n)></cfif>
|
||||||
|
</cfloop>
|
||||||
|
<cfset arraySort(allNums, "numeric")>
|
||||||
|
<cfset changes = []>
|
||||||
|
<cfloop array="#allNums#" index="num">
|
||||||
|
<cfset inOld = structKeyExists(initByNum, num)>
|
||||||
|
<cfset inNew = structKeyExists(amdByNum, num)>
|
||||||
|
<cfif inOld AND NOT inNew>
|
||||||
|
<cfset arrayAppend(changes, {row_num:num, change_type:"deleted", old_values:initByNum[num], new_values:{}})>
|
||||||
|
<cfelseif NOT inOld AND inNew>
|
||||||
|
<cfset arrayAppend(changes, {row_num:num, change_type:"added", old_values:{}, new_values:amdByNum[num]})>
|
||||||
|
<cfelse>
|
||||||
|
<cfset ov = initByNum[num]>
|
||||||
|
<cfset nv = amdByNum[num]>
|
||||||
|
<cfset same = true>
|
||||||
|
<cfset cf = {}>
|
||||||
|
<cfloop array="#fields#" index="f">
|
||||||
|
<cfif structKeyExists(ov,f) AND structKeyExists(nv,f)>
|
||||||
|
<cfif toString(ov[f]) NEQ toString(nv[f])>
|
||||||
|
<cfset same = false><cfset cf[f] = {old:ov[f], new:nv[f]}>
|
||||||
|
</cfif>
|
||||||
|
<cfelseif structKeyExists(ov,f) OR structKeyExists(nv,f)>
|
||||||
|
<cfset same = false><cfset cf[f] = {old: structKeyExists(ov,f)?ov[f]:"", new: structKeyExists(nv,f)?nv[f]:""}>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
<cfset arrayAppend(changes, {row_num:num, change_type:same?"unchanged":"changed", old_values:ov, new_values:nv, changed_fields:cf})>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
<cfset sm = {added:0, deleted:0, changed:0, unchanged:0}>
|
||||||
|
<cfloop array="#changes#" index="ch"><cfset sm[ch.change_type] = sm[ch.change_type] + 1></cfloop>
|
||||||
|
<cfset arrayAppend(allChanges, {supplement_id:amendments.id, type:amendments.type, changes:changes, summary:sm})>
|
||||||
</cfloop>
|
</cfloop>
|
||||||
|
<cfset result = {ok:true, contract_id:url.contract_id, initial_id:initSup.id, all_changes:allChanges}>
|
||||||
<cfset result = {
|
|
||||||
ok: true,
|
|
||||||
contract_id: url.contract_id,
|
|
||||||
supplement_old: supOld,
|
|
||||||
supplement_new: supNew,
|
|
||||||
changes: changes,
|
|
||||||
summary: {
|
|
||||||
added: 0,
|
|
||||||
deleted: 0,
|
|
||||||
changed: 0,
|
|
||||||
unchanged: 0
|
|
||||||
}
|
|
||||||
}>
|
|
||||||
|
|
||||||
<cfloop array="#changes#" index="ch">
|
|
||||||
<cfset result.summary[ch.change_type] = result.summary[ch.change_type] + 1>
|
|
||||||
</cfloop>
|
|
||||||
</cfif>
|
</cfif>
|
||||||
</cfif>
|
</cfif>
|
||||||
|
<cfcatch><cfset result = {ok:false, error:cfcatch.message, detail:cfcatch.detail}></cfcatch>
|
||||||
<cfcatch>
|
|
||||||
<cfset result = {ok: false, error: cfcatch.message, detail: cfcatch.detail}>
|
|
||||||
</cfcatch>
|
|
||||||
</cftry>
|
</cftry>
|
||||||
|
<cfoutput>#serializeJSON(result)#</cfoutput>
|
||||||
<cfoutput>#serializeJSON(result)#</cfoutput>
|
|
||||||
@@ -49,9 +49,6 @@
|
|||||||
</cfloop>
|
</cfloop>
|
||||||
<cfset parsedText = arrayToList(textLines, chr(10))>
|
<cfset parsedText = arrayToList(textLines, chr(10))>
|
||||||
|
|
||||||
<!--- Промпт --->
|
|
||||||
<cfset prompt = 'Ты — анализатор договоров. Ниже текст спецификации услуг из договора облачного провайдера.
|
|
||||||
|
|
||||||
<!--- Промпт: из БД или стандартный --->
|
<!--- Промпт: из БД или стандартный --->
|
||||||
<cfset promptText = "">
|
<cfset promptText = "">
|
||||||
<cfquery name="pq" datasource="baza">
|
<cfquery name="pq" datasource="baza">
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="topbar">
|
<div class="topbar">
|
||||||
<img src="/nubes-logo.svg" alt="Nubes">
|
<img src="/nubes-logo.svg" alt="Nubes">
|
||||||
<span class="title">Сверка договоров — LLM <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.0.46 — Lucee</span></span>
|
<span class="title">Сверка договоров — LLM <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.0.47 — Lucee</span></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
@@ -361,23 +361,31 @@ document.getElementById('llmBtn').addEventListener('click', async function() {
|
|||||||
try {
|
try {
|
||||||
var resp = await fetch('/differ.cfm?contract_id=' + contractId);
|
var resp = await fetch('/differ.cfm?contract_id=' + contractId);
|
||||||
var data = await resp.json();
|
var data = await resp.json();
|
||||||
if (data.OK) {
|
if (data.OK && data.ALL_CHANGES) {
|
||||||
var s = data.SUMMARY || {};
|
var all = data.ALL_CHANGES;
|
||||||
document.getElementById('diffStatus').textContent = 'изм:' + (s.CHANGED||0) + ' +' + (s.ADDED||0) + ' -' + (s.DELETED||0);
|
diffBody.innerHTML = '';
|
||||||
|
all.forEach(function(ch) {
|
||||||
|
var s = ch.SUMMARY || {};
|
||||||
|
diffBody.innerHTML += '<div style="font-weight:600;margin-top:12px;">📄 Допник (' + ch.TYPE + ')</div>';
|
||||||
|
diffBody.innerHTML += '<div style="font-size:12px;margin-left:8px;"><span style="color:var(--green);">+' + (s.ADDED||0) + '</span> <span style="color:#eab308;">~' + (s.CHANGED||0) + '</span> <span style="color:var(--destructive);">-' + (s.DELETED||0) + '</span></div>';
|
||||||
|
diffStatus.textContent = 'изм:' + (s.CHANGED||0) + ' +' + (s.ADDED||0) + ' -' + (s.DELETED||0);
|
||||||
|
|
||||||
var changes = data.CHANGES || [];
|
var changes = ch.CHANGES || [];
|
||||||
if (changes.length === 0) {
|
if (changes.length === 0) {
|
||||||
diffBody.innerHTML += '<div style="color:var(--muted);">Нет изменений</div>';
|
diffBody.innerHTML += '<div style="color:var(--muted);font-size:12px;">Нет изменений</div>';
|
||||||
} else {
|
} else {
|
||||||
var tbl = '<div class="table-wrap" style="margin-top:4px;"><table><thead><tr><th>№</th><th>Услуга</th><th>Цена</th><th>Объём</th><th>Сумма</th><th>Начало</th></tr></thead><tbody>';
|
var tbl = '<div class="table-wrap" style="margin-top:4px;"><table><thead><tr><th>№</th><th>Услуга</th><th>Цена</th><th>Объём</th><th>Сумма</th><th>Начало</th></tr></thead><tbody>';
|
||||||
changes.forEach(function(c) {
|
changes.forEach(function(c) {
|
||||||
var cls = c.CHANGE_TYPE === 'added' ? 'diff-added' : c.CHANGE_TYPE === 'deleted' ? 'diff-deleted' : c.CHANGE_TYPE === 'changed' ? 'diff-changed' : '';
|
var cls = c.CHANGE_TYPE === 'added' ? 'diff-added' : c.CHANGE_TYPE === 'deleted' ? 'diff-deleted' : c.CHANGE_TYPE === 'changed' ? 'diff-changed' : '';
|
||||||
var vals = c.NEW_VALUES && Object.keys(c.NEW_VALUES).length > 0 ? c.NEW_VALUES : (c.OLD_VALUES || {});
|
var vals = c.NEW_VALUES && Object.keys(c.NEW_VALUES).length > 0 ? c.NEW_VALUES : (c.OLD_VALUES || {});
|
||||||
tbl += '<tr class="' + cls + '"><td>' + (c.ROW_NUM||'') + '</td><td>' + (vals.NAME||'') + '</td><td class="num-cell">' + _dc(c,'PRICE') + '</td><td class="num-cell">' + _dc(c,'QTY') + '</td><td class="num-cell">' + _dc(c,'SUM') + '</td><td>' + (vals.DATE_START||'') + '</td></tr>';
|
tbl += '<tr class="' + cls + '"><td>' + (c.ROW_NUM||'') + '</td><td>' + (vals.NAME||'') + '</td><td class="num-cell">' + _dc(c,'PRICE') + '</td><td class="num-cell">' + _dc(c,'QTY') + '</td><td class="num-cell">' + _dc(c,'SUM') + '</td><td>' + (vals.DATE_START||'') + '</td></tr>';
|
||||||
});
|
});
|
||||||
tbl += '</tbody></table></div>';
|
tbl += '</tbody></table></div>';
|
||||||
diffBody.innerHTML += tbl;
|
diffBody.innerHTML += tbl;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
diffBody.innerHTML += '<div style="color:var(--destructive);">✗ ' + (data.ERROR||'') + '</div>';
|
||||||
}
|
}
|
||||||
} catch(e) { diffBody.innerHTML += '<div style="color:var(--destructive);">✗ ' + e.message + '</div>'; }
|
} catch(e) { diffBody.innerHTML += '<div style="color:var(--destructive);">✗ ' + e.message + '</div>'; }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user