Merge remote-tracking branch 'origin/master'

This commit is contained in:
M_Kececi
2026-05-22 17:12:45 +03:00
parent 1f90b9f9ce
commit 43bb76da9a
3 changed files with 202 additions and 106 deletions

View File

@@ -13,6 +13,7 @@ type ProductionCostingLast10WarningRow struct {
UrunKodu string `json:"urun_kodu"`
MaliyetTarihi string `json:"maliyet_tarihi"` // YYYY-MM-DD
ItemCode string `json:"item_code"`
ItemDescription string `json:"item_description,omitempty"`
CurrencyCode string `json:"currency_code"`
InputPrice float64 `json:"input_price"`
AvgDocPrice float64 `json:"avg_doc_price"`
@@ -37,6 +38,7 @@ CREATE TABLE IF NOT EXISTS mk_costing_last10_warning (
item_code TEXT NOT NULL,
currency_code TEXT NOT NULL,
urun_kodu TEXT NOT NULL DEFAULT '',
item_description TEXT NOT NULL DEFAULT '',
maliyet_tarihi DATE,
input_price DOUBLE PRECISION NOT NULL DEFAULT 0,
avg_doc_price DOUBLE PRECISION NOT NULL DEFAULT 0,
@@ -51,6 +53,8 @@ CREATE TABLE IF NOT EXISTS mk_costing_last10_warning (
PRIMARY KEY (n_onml_no, item_code, currency_code)
)
`,
// Best-effort forward migration for existing DBs.
`ALTER TABLE mk_costing_last10_warning ADD COLUMN IF NOT EXISTS item_description TEXT NOT NULL DEFAULT ''`,
`CREATE INDEX IF NOT EXISTS ix_costing_last10_warning_onml ON mk_costing_last10_warning (n_onml_no)`,
`CREATE INDEX IF NOT EXISTS ix_costing_last10_warning_item ON mk_costing_last10_warning (item_code, currency_code)`,
}
@@ -73,6 +77,7 @@ func ReplaceProductionCostingLast10Warnings(
inputByKey map[string]float64, // key = ITEM|CUR (input in doc currency)
inputUSDByKey map[string]float64, // key = ITEM|CUR (input converted to USD basis)
avgUSDByKey map[string]float64, // key = ITEM|CUR (avg converted to USD basis)
descByCode map[string]string, // code -> description (UI text)
) error {
if pg == nil {
return fmt.Errorf("pg db is nil")
@@ -128,22 +133,25 @@ func ReplaceProductionCostingLast10Warnings(
continue
}
desc := strings.TrimSpace(descByCode[code])
_, err := tx.ExecContext(ctx, `
INSERT INTO mk_costing_last10_warning (
n_onml_no, item_code, currency_code,
urun_kodu, maliyet_tarihi,
urun_kodu, item_description, maliyet_tarihi,
input_price, avg_doc_price, input_usd, avg_usd, diff_ratio,
sample_count, min_invoice_date, max_invoice_date,
created_by
) VALUES (
$1,$2,$3,
$4,$5,
$6,$7,$8,$9,$10,
$11,$12,$13,
$14
$4,$5,$6,
$7,$8,$9,$10,$11,
$12,$13,$14,
$15
)
ON CONFLICT (n_onml_no, item_code, currency_code) DO UPDATE SET
urun_kodu = EXCLUDED.urun_kodu,
item_description = EXCLUDED.item_description,
maliyet_tarihi = EXCLUDED.maliyet_tarihi,
input_price = EXCLUDED.input_price,
avg_doc_price = EXCLUDED.avg_doc_price,
@@ -155,7 +163,7 @@ ON CONFLICT (n_onml_no, item_code, currency_code) DO UPDATE SET
max_invoice_date = EXCLUDED.max_invoice_date,
created_at = now(),
created_by = EXCLUDED.created_by
`, nOnMLNo, code, cur, urunKodu, mtDate, in, ar.AvgDocPrice, inUSD, avgUSD, diff, ar.SampleCount, ar.MinInvoiceDate, ar.MaxInvoiceDate, createdBy)
`, nOnMLNo, code, cur, urunKodu, desc, mtDate, in, ar.AvgDocPrice, inUSD, avgUSD, diff, ar.SampleCount, ar.MinInvoiceDate, ar.MaxInvoiceDate, createdBy)
if err != nil {
return err
}
@@ -177,6 +185,7 @@ SELECT
COALESCE(urun_kodu,'') AS urun_kodu,
COALESCE(TO_CHAR(maliyet_tarihi, 'YYYY-MM-DD'),'') AS maliyet_tarihi,
item_code,
COALESCE(item_description,'') AS item_description,
currency_code,
input_price,
avg_doc_price,
@@ -205,6 +214,7 @@ ORDER BY diff_ratio DESC, item_code ASC, currency_code ASC
&r.UrunKodu,
&r.MaliyetTarihi,
&r.ItemCode,
&r.ItemDescription,
&r.CurrencyCode,
&r.InputPrice,
&r.AvgDocPrice,

View File

@@ -2266,6 +2266,7 @@ VALUES (
// dedupe input by code+currency (USD basis comparison)
inputByKey := map[string]float64{}
inputUSDByKey := map[string]float64{}
descByCode := map[string]string{}
codes := make([]string, 0, len(reqCopy.Detail.Upserts))
seenCode := map[string]struct{}{}
@@ -2310,6 +2311,13 @@ VALUES (
}
inputUSDByKey[key] = inUSD
// Best-effort description from UI payload (for excel export/readability).
if d := strings.TrimSpace(row.SAciklama); d != "" {
if _, ok := descByCode[code]; !ok {
descByCode[code] = d
}
}
if _, ok := seenCode[code]; !ok {
seenCode[code] = struct{}{}
codes = append(codes, code)
@@ -2366,6 +2374,7 @@ VALUES (
inputByKey,
inputUSDByKey,
avgUSDByKey,
descByCode,
)
cancelWrite()
if err != nil {

View File

@@ -279,6 +279,16 @@
<q-card-section class="row items-center">
<div class="text-h6">Son 10 Ort. Fiyat Sapmalari</div>
<q-space />
<q-btn
dense
outline
color="secondary"
icon="download"
label="Excel"
class="q-mr-sm"
:disable="!last10Warnings || last10Warnings.length === 0"
@click="downloadLast10WarningsExcel"
/>
<q-btn icon="close" flat round dense v-close-popup />
</q-card-section>
<q-separator />
@@ -287,6 +297,7 @@
<thead>
<tr>
<th class="text-left">Kod</th>
<th class="text-left">Aciklama</th>
<th class="text-left">Doviz</th>
<th class="text-right">Giris</th>
<th class="text-right">Ort10</th>
@@ -298,6 +309,7 @@
<tbody>
<tr v-for="w in last10Warnings" :key="w.item_code + '|' + w.currency_code">
<td class="text-left">{{ w.item_code }}</td>
<td class="text-left">{{ w.item_description || '' }}</td>
<td class="text-left">{{ w.currency_code }}</td>
<td class="text-right">{{ formatMoney(w.input_price) }}</td>
<td class="text-right">{{ formatMoney(w.avg_doc_price) }}</td>
@@ -306,7 +318,7 @@
<td class="text-left">{{ (w.min_invoice_date || '-') + ' / ' + (w.max_invoice_date || '-') }}</td>
</tr>
<tr v-if="last10Warnings.length === 0">
<td colspan="7" class="text-center text-grey-7">Kayit yok</td>
<td colspan="8" class="text-center text-grey-7">Kayit yok</td>
</tr>
</tbody>
</q-markup-table>
@@ -1155,6 +1167,71 @@ function formatPercent (ratio) {
return `${(n * 100).toFixed(0)}%`
}
function downloadLast10WarningsExcel () {
const rows = Array.isArray(last10Warnings.value) ? last10Warnings.value : []
if (rows.length === 0) return
// Excel-friendly CSV (UTF-8 with BOM).
const header = [
'Kod',
'Aciklama',
'Doviz',
'Giris',
'Ort10',
'SapmaOran',
'SapmaYuzde',
'Sample',
'MinTarih',
'MaxTarih'
]
const lines = [header.join(';')]
for (const w of rows) {
const code = String(w?.item_code || '').trim()
const desc = String(w?.item_description || '').replaceAll('\n', ' ').replaceAll('\r', ' ').trim()
const cur = String(w?.currency_code || '').trim()
const inP = Number(w?.input_price || 0)
const avgP = Number(w?.avg_doc_price || 0)
const diff = Number(w?.diff_ratio || 0)
const sample = Number(w?.sample_count || 0)
const minD = String(w?.min_invoice_date || '').trim()
const maxD = String(w?.max_invoice_date || '').trim()
const safe = (s) => {
s = String(s ?? '')
if (s.includes(';') || s.includes('\"')) {
s = '\"' + s.replaceAll('\"', '\"\"') + '\"'
}
return s
}
lines.push([
safe(code),
safe(desc),
safe(cur),
String(Number.isFinite(inP) ? inP : 0),
String(Number.isFinite(avgP) ? avgP : 0),
String(Number.isFinite(diff) ? diff : 0),
`${Number.isFinite(diff) ? (diff * 100).toFixed(0) : '0'}%`,
String(Number.isFinite(sample) ? sample : 0),
safe(minD),
safe(maxD)
].join(';'))
}
const bom = '\uFEFF'
const blob = new Blob([bom + lines.join('\n')], { type: 'text/csv;charset=utf-8;' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
const urun = String(detailHeader.value?.UrunKodu || productCode.value || '').trim()
const onml = String(onMLNo.value || '').trim()
const date = String(costDate.value || '').trim()
a.href = url
a.download = `fiyat_uyari_${urun || 'urun'}_${onml || 'onml'}_${date || 'tarih'}.csv`
document.body.appendChild(a)
a.click()
a.remove()
URL.revokeObjectURL(url)
}
async function refreshLast10Warnings () {
if (!onMLNo.value) {
last10Warnings.value = []