Merge remote-tracking branch 'origin/master'
This commit is contained in:
516
ui/src/pages/ProductionProductCostingNoCost.vue
Normal file
516
ui/src/pages/ProductionProductCostingNoCost.vue
Normal file
@@ -0,0 +1,516 @@
|
||||
<template>
|
||||
<q-page v-if="canReadOrder" class="npc-page">
|
||||
<div class="ol-filter-bar npc-filter-bar">
|
||||
<div class="npc-filter-row">
|
||||
<q-input
|
||||
v-model="filters.search"
|
||||
class="npc-filter-input npc-search"
|
||||
dense
|
||||
filled
|
||||
clearable
|
||||
debounce="300"
|
||||
label="Arama (Model Kodu / Firma / Veren)"
|
||||
>
|
||||
<template #append>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<q-input
|
||||
v-model="filters.fromDate"
|
||||
class="npc-filter-input"
|
||||
dense
|
||||
filled
|
||||
type="date"
|
||||
label="Baslangic Tarihi"
|
||||
/>
|
||||
|
||||
<div class="ol-filter-actions npc-filter-actions">
|
||||
<q-btn
|
||||
label="Temizle"
|
||||
icon="clear"
|
||||
color="grey-7"
|
||||
flat
|
||||
:disable="loading"
|
||||
@click="clearFilters"
|
||||
/>
|
||||
<q-btn
|
||||
label="Kolon Filtreleri"
|
||||
icon="filter_alt_off"
|
||||
color="grey-7"
|
||||
flat
|
||||
:disable="loading"
|
||||
@click="clearAllColumnFilters"
|
||||
/>
|
||||
<q-btn
|
||||
label="Yenile"
|
||||
icon="refresh"
|
||||
color="primary"
|
||||
:loading="loading"
|
||||
@click="fetchRows"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<q-table
|
||||
title="Maliyeti Olmayan Urunler"
|
||||
class="ol-table npc-table"
|
||||
flat
|
||||
bordered
|
||||
dense
|
||||
separator="cell"
|
||||
row-key="__rowKey"
|
||||
:rows="rows"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
no-data-label="Kayit bulunamadi"
|
||||
:rows-per-page-options="[0]"
|
||||
hide-bottom
|
||||
>
|
||||
<template #header-cell="props">
|
||||
<q-th :props="props">
|
||||
<div class="npc-header-cell">
|
||||
<div class="npc-head-wrap-3">{{ props.col.label }}</div>
|
||||
<q-btn
|
||||
v-if="props.col.name !== 'open'"
|
||||
dense
|
||||
flat
|
||||
round
|
||||
size="sm"
|
||||
icon="filter_alt"
|
||||
:color="isColumnFilterActive(props.col.name) ? 'primary' : 'grey-6'"
|
||||
>
|
||||
<q-menu class="npc-filter-menu" fit>
|
||||
<div class="npc-filter-menu-content">
|
||||
<div class="text-caption text-weight-bold q-mb-sm">{{ props.col.label }}</div>
|
||||
<q-input
|
||||
v-model="getColumnFilter(props.col.name).text"
|
||||
dense
|
||||
outlined
|
||||
clearable
|
||||
label="Icerir"
|
||||
/>
|
||||
<q-select
|
||||
v-model="getColumnFilter(props.col.name).selected"
|
||||
class="q-mt-sm"
|
||||
dense
|
||||
outlined
|
||||
multiple
|
||||
use-chips
|
||||
use-input
|
||||
emit-value
|
||||
map-options
|
||||
:options="getColumnDistinctOptions(props.col.name)"
|
||||
label="Deger Sec"
|
||||
/>
|
||||
<div class="row justify-end q-gutter-sm q-mt-sm">
|
||||
<q-btn dense flat color="grey-7" label="Temizle" @click="clearColumnFilter(props.col.name)" />
|
||||
</div>
|
||||
</div>
|
||||
</q-menu>
|
||||
</q-btn>
|
||||
</div>
|
||||
</q-th>
|
||||
</template>
|
||||
|
||||
<template #body-cell="props">
|
||||
<q-td v-if="props.col.name === 'open'" :props="props" class="text-center">
|
||||
<q-btn
|
||||
icon="open_in_new"
|
||||
color="primary"
|
||||
flat
|
||||
round
|
||||
dense
|
||||
@click="openRow(props.row)"
|
||||
>
|
||||
<q-tooltip>Ac</q-tooltip>
|
||||
</q-btn>
|
||||
</q-td>
|
||||
<q-td v-else :props="props" class="npc-wrap-col">
|
||||
<div class="npc-wrap-3">{{ props.value }}</div>
|
||||
<q-tooltip v-if="props.value">{{ props.value }}</q-tooltip>
|
||||
</q-td>
|
||||
</template>
|
||||
</q-table>
|
||||
|
||||
<q-banner v-if="error" class="bg-red text-white q-mt-sm">
|
||||
Hata: {{ error }}
|
||||
</q-banner>
|
||||
</q-page>
|
||||
|
||||
<q-page v-else class="q-pa-md flex flex-center">
|
||||
<div class="text-negative text-subtitle1">
|
||||
Bu module erisim yetkiniz yok.
|
||||
</div>
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { useQuasar } from 'quasar'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { usePermission } from 'src/composables/usePermission'
|
||||
import { get, extractApiErrorDetail } from 'src/services/api'
|
||||
import { createTraceId, slog } from 'src/utils/slog'
|
||||
|
||||
const { canRead } = usePermission()
|
||||
const canReadOrder = canRead('order')
|
||||
const $q = useQuasar()
|
||||
const router = useRouter()
|
||||
|
||||
const loading = ref(false)
|
||||
const error = ref('')
|
||||
const allRows = ref([])
|
||||
|
||||
const filters = reactive({
|
||||
search: '',
|
||||
fromDate: '2025-06-01'
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{ name: 'open', label: '', field: 'open', align: 'center', sortable: false, style: 'width:3%', headerStyle: 'width:3%' },
|
||||
{
|
||||
name: 'UretimSekli',
|
||||
label: 'Uretim Sekli',
|
||||
field: 'UretimSekli',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
classes: 'npc-wrap-col',
|
||||
headerClasses: 'npc-wrap-col',
|
||||
style: 'width:12%',
|
||||
headerStyle: 'width:12%'
|
||||
},
|
||||
{ name: 'nUrtSiparisNo', label: 'Uretim Siparis No', field: 'nUrtSiparisNo', align: 'left', sortable: true, style: 'width:7%', headerStyle: 'width:7%' },
|
||||
{
|
||||
name: 'dteIslemTarihi',
|
||||
label: 'Islem Tarihi',
|
||||
field: 'dteIslemTarihi',
|
||||
align: 'center',
|
||||
sortable: true,
|
||||
format: val => formatDateTR(val),
|
||||
style: 'width:7%',
|
||||
headerStyle: 'width:7%'
|
||||
},
|
||||
{
|
||||
name: 'FirmaKodu',
|
||||
label: 'Firma Kodu',
|
||||
field: 'FirmaKodu',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
classes: 'npc-wrap-col',
|
||||
headerClasses: 'npc-wrap-col',
|
||||
style: 'width:8%',
|
||||
headerStyle: 'width:8%'
|
||||
},
|
||||
{ name: 'FirmaAdi', label: 'Firma Adi', field: 'FirmaAdi', align: 'left', sortable: true, style: 'width:10%', headerStyle: 'width:10%' },
|
||||
{ name: 'SonIsEmriVeren', label: '2.Firma', field: 'SonIsEmriVeren', align: 'left', sortable: true, style: 'width:9%', headerStyle: 'width:9%' },
|
||||
{
|
||||
name: 'lMMiktar_G',
|
||||
label: 'Miktar (G)',
|
||||
field: 'lMMiktar_G',
|
||||
align: 'right',
|
||||
sortable: true,
|
||||
format: val => Number(val || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }),
|
||||
style: 'width:7%',
|
||||
headerStyle: 'width:7%'
|
||||
},
|
||||
{
|
||||
name: 'sMModelKodu',
|
||||
label: 'Model Kodu',
|
||||
field: 'sMModelKodu',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
classes: 'npc-wrap-col',
|
||||
headerClasses: 'npc-wrap-col',
|
||||
style: 'width:8%',
|
||||
headerStyle: 'width:8%'
|
||||
},
|
||||
{ name: 'sAdi', label: 'Model Adi', field: 'sAdi', align: 'left', sortable: true, style: 'width:9%', headerStyle: 'width:9%' },
|
||||
{ name: 'sKodu', label: 'Recete Kodu', field: 'sKodu', align: 'left', sortable: true, style: 'width:7%', headerStyle: 'width:7%' },
|
||||
{ name: 'sKullaniciAdi', label: 'Receteyi Acan Kullanici', field: 'sKullaniciAdi', align: 'left', sortable: true, style: 'width:7%', headerStyle: 'width:7%' },
|
||||
{ name: 'sKullaniciAdiGunc', label: 'Receteyi Son Guncelleyen Kullanici', field: 'sKullaniciAdiGunc', align: 'left', sortable: true, style: 'width:6%', headerStyle: 'width:6%' }
|
||||
]
|
||||
|
||||
const columnFilters = reactive({})
|
||||
|
||||
function getColumnFilter (name) {
|
||||
if (!columnFilters[name]) {
|
||||
columnFilters[name] = {
|
||||
text: '',
|
||||
selected: []
|
||||
}
|
||||
}
|
||||
return columnFilters[name]
|
||||
}
|
||||
|
||||
function formatDateTR (value) {
|
||||
const s = String(value || '').trim()
|
||||
if (!s) return ''
|
||||
const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(s)
|
||||
if (!m) return s
|
||||
return `${m[3]}.${m[2]}.${m[1]}`
|
||||
}
|
||||
|
||||
const rows = computed(() => {
|
||||
let result = allRows.value
|
||||
|
||||
for (const col of columns) {
|
||||
if (col.name === 'open') continue
|
||||
|
||||
const cf = getColumnFilter(col.name)
|
||||
const text = String(cf.text || '').trim().toLowerCase()
|
||||
const selected = Array.isArray(cf.selected) ? cf.selected : []
|
||||
|
||||
if (!text && selected.length === 0) continue
|
||||
|
||||
result = result.filter((row) => {
|
||||
const value = getColumnComparableValue(row, col.name)
|
||||
const valueLC = value.toLowerCase()
|
||||
|
||||
if (text && !valueLC.includes(text)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (selected.length > 0 && !selected.includes(value)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
function getColumnComparableValue (row, colName) {
|
||||
if (colName === 'dteIslemTarihi') {
|
||||
return formatDateTR(row?.dteIslemTarihi)
|
||||
}
|
||||
return String(row?.[colName] ?? '').trim()
|
||||
}
|
||||
|
||||
function getColumnDistinctOptions (colName) {
|
||||
const set = new Set()
|
||||
for (const row of allRows.value) {
|
||||
const val = getColumnComparableValue(row, colName)
|
||||
if (val) set.add(val)
|
||||
}
|
||||
return Array.from(set)
|
||||
.sort((a, b) => a.localeCompare(b, 'tr'))
|
||||
.map(v => ({ label: v, value: v }))
|
||||
}
|
||||
|
||||
function isColumnFilterActive (name) {
|
||||
const cf = getColumnFilter(name)
|
||||
return !!String(cf.text || '').trim() || (Array.isArray(cf.selected) && cf.selected.length > 0)
|
||||
}
|
||||
|
||||
function clearColumnFilter (name) {
|
||||
const cf = getColumnFilter(name)
|
||||
cf.text = ''
|
||||
cf.selected = []
|
||||
}
|
||||
|
||||
function clearAllColumnFilters () {
|
||||
for (const col of columns) {
|
||||
if (col.name === 'open') continue
|
||||
clearColumnFilter(col.name)
|
||||
}
|
||||
}
|
||||
|
||||
let searchTimer = null
|
||||
watch(
|
||||
() => filters.search,
|
||||
() => {
|
||||
clearTimeout(searchTimer)
|
||||
searchTimer = setTimeout(() => {
|
||||
fetchRows()
|
||||
}, 400)
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => filters.fromDate,
|
||||
() => {
|
||||
fetchRows()
|
||||
}
|
||||
)
|
||||
|
||||
async function fetchRows () {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
try {
|
||||
const data = await get('/pricing/production-product-costing/no-cost-products', {
|
||||
search: filters.search || '',
|
||||
from_date: filters.fromDate || ''
|
||||
})
|
||||
|
||||
const list = Array.isArray(data) ? data : []
|
||||
allRows.value = list.map((x, i) => ({
|
||||
__rowKey: `${x?.sMModelKodu || ''}-${x?.nUrtSiparisNo || 0}-${i}`,
|
||||
...x
|
||||
}))
|
||||
} catch (err) {
|
||||
error.value = await extractApiErrorDetail(err)
|
||||
allRows.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function clearFilters () {
|
||||
filters.search = ''
|
||||
filters.fromDate = '2025-06-01'
|
||||
clearAllColumnFilters()
|
||||
fetchRows()
|
||||
}
|
||||
|
||||
function openRow (row) {
|
||||
const productCode = String(row?.sMModelKodu || '').trim()
|
||||
const recipeCode = String(row?.sKodu || '').trim()
|
||||
const traceId = createTraceId('pcd-no-cost')
|
||||
if (!productCode || !recipeCode) {
|
||||
$q.notify({
|
||||
type: 'warning',
|
||||
message: 'Detay acmak icin urun ve recete kodu gerekli.',
|
||||
position: 'top-right'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
slog.info('production-product-costing.no-cost', 'navigate:detail', {
|
||||
trace_id: traceId,
|
||||
product_code: productCode,
|
||||
recipe_code: recipeCode
|
||||
})
|
||||
|
||||
router.push({
|
||||
name: 'production-product-costing-has-cost-detail',
|
||||
query: {
|
||||
detail_source: 'no-cost',
|
||||
urun_kodu: productCode,
|
||||
recete_kodu: recipeCode,
|
||||
trace_id: traceId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!canReadOrder.value) return
|
||||
fetchRows()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.npc-page {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.npc-filter-bar {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.npc-filter-row {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.npc-filter-input {
|
||||
min-width: 118px;
|
||||
width: 136px;
|
||||
flex: 0 0 136px;
|
||||
}
|
||||
|
||||
.npc-search {
|
||||
min-width: 240px;
|
||||
max-width: 420px;
|
||||
flex: 1 1 360px;
|
||||
}
|
||||
|
||||
.npc-filter-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: nowrap;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.npc-filter-menu {
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.npc-filter-menu-content {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.npc-table :deep(.q-table thead th) {
|
||||
font-size: 11px;
|
||||
padding: 3px 4px;
|
||||
white-space: normal !important;
|
||||
vertical-align: top !important;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.npc-table :deep(.q-table tbody td) {
|
||||
font-size: 11px;
|
||||
padding: 2px 4px;
|
||||
white-space: normal !important;
|
||||
vertical-align: top !important;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.npc-table :deep(.q-table) {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.npc-wrap-col {
|
||||
white-space: normal !important;
|
||||
}
|
||||
|
||||
.npc-header-cell {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.npc-head-wrap-3 {
|
||||
min-width: 0;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
overflow: hidden;
|
||||
line-height: 1.15;
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.npc-wrap-3 {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
overflow: hidden;
|
||||
line-height: 1.15;
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
@media (max-width: 1440px) {
|
||||
.npc-filter-row {
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.npc-filter-actions {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.npc-filter-input {
|
||||
flex: 1 1 140px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user