Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -1,75 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* THIS FILE IS GENERATED AUTOMATICALLY.
|
||||
* DO NOT EDIT.
|
||||
*
|
||||
* You are probably looking on adding startup/initialization code.
|
||||
* Use "quasar new boot <name>" and add it there.
|
||||
* One boot file per concern. Then reference the file(s) in quasar.config file > boot:
|
||||
* boot: ['file', ...] // do not add ".js" extension to it.
|
||||
*
|
||||
* Boot files are your "main.js"
|
||||
**/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
import { Quasar } from 'quasar'
|
||||
import { markRaw } from 'vue'
|
||||
import RootComponent from 'app/src/App.vue'
|
||||
|
||||
import createStore from 'app/src/stores/index'
|
||||
import createRouter from 'app/src/router/index'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export default async function (createAppFn, quasarUserOptions) {
|
||||
|
||||
|
||||
// Create the app instance.
|
||||
// Here we inject into it the Quasar UI, the router & possibly the store.
|
||||
const app = createAppFn(RootComponent)
|
||||
|
||||
|
||||
|
||||
app.use(Quasar, quasarUserOptions)
|
||||
|
||||
|
||||
|
||||
|
||||
const store = typeof createStore === 'function'
|
||||
? await createStore({})
|
||||
: createStore
|
||||
|
||||
|
||||
app.use(store)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const router = markRaw(
|
||||
typeof createRouter === 'function'
|
||||
? await createRouter({store})
|
||||
: createRouter
|
||||
)
|
||||
|
||||
|
||||
// make router instance available in store
|
||||
|
||||
store.use(({ store }) => { store.router = router })
|
||||
|
||||
|
||||
|
||||
// Expose the app, the router and the store.
|
||||
// Note that we are not mounting the app here, since bootstrapping will be
|
||||
// different depending on whether we are in a browser or on the server.
|
||||
return {
|
||||
app,
|
||||
store,
|
||||
router
|
||||
}
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* THIS FILE IS GENERATED AUTOMATICALLY.
|
||||
* DO NOT EDIT.
|
||||
*
|
||||
* You are probably looking on adding startup/initialization code.
|
||||
* Use "quasar new boot <name>" and add it there.
|
||||
* One boot file per concern. Then reference the file(s) in quasar.config file > boot:
|
||||
* boot: ['file', ...] // do not add ".js" extension to it.
|
||||
*
|
||||
* Boot files are your "main.js"
|
||||
**/
|
||||
|
||||
|
||||
import { createApp } from 'vue'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
import '@quasar/extras/roboto-font/roboto-font.css'
|
||||
|
||||
import '@quasar/extras/material-icons/material-icons.css'
|
||||
|
||||
|
||||
|
||||
|
||||
// We load Quasar stylesheet file
|
||||
import 'quasar/dist/quasar.sass'
|
||||
|
||||
|
||||
|
||||
|
||||
import 'src/css/app.css'
|
||||
|
||||
|
||||
import createQuasarApp from './app.js'
|
||||
import quasarUserOptions from './quasar-user-options.js'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const publicPath = `/`
|
||||
|
||||
|
||||
async function start ({
|
||||
app,
|
||||
router
|
||||
, store
|
||||
}, bootFiles) {
|
||||
|
||||
let hasRedirected = false
|
||||
const getRedirectUrl = url => {
|
||||
try { return router.resolve(url).href }
|
||||
catch (err) {}
|
||||
|
||||
return Object(url) === url
|
||||
? null
|
||||
: url
|
||||
}
|
||||
const redirect = url => {
|
||||
hasRedirected = true
|
||||
|
||||
if (typeof url === 'string' && /^https?:\/\//.test(url)) {
|
||||
window.location.href = url
|
||||
return
|
||||
}
|
||||
|
||||
const href = getRedirectUrl(url)
|
||||
|
||||
// continue if we didn't fail to resolve the url
|
||||
if (href !== null) {
|
||||
window.location.href = href
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
|
||||
const urlPath = window.location.href.replace(window.location.origin, '')
|
||||
|
||||
for (let i = 0; hasRedirected === false && i < bootFiles.length; i++) {
|
||||
try {
|
||||
await bootFiles[i]({
|
||||
app,
|
||||
router,
|
||||
store,
|
||||
ssrContext: null,
|
||||
redirect,
|
||||
urlPath,
|
||||
publicPath
|
||||
})
|
||||
}
|
||||
catch (err) {
|
||||
if (err && err.url) {
|
||||
redirect(err.url)
|
||||
return
|
||||
}
|
||||
|
||||
console.error('[Quasar] boot error:', err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (hasRedirected === true) return
|
||||
|
||||
|
||||
app.use(router)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
app.mount('#q-app')
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
createQuasarApp(createApp, quasarUserOptions)
|
||||
|
||||
.then(app => {
|
||||
// eventually remove this when Cordova/Capacitor/Electron support becomes old
|
||||
const [ method, mapFn ] = Promise.allSettled !== void 0
|
||||
? [
|
||||
'allSettled',
|
||||
bootFiles => bootFiles.map(result => {
|
||||
if (result.status === 'rejected') {
|
||||
console.error('[Quasar] boot error:', result.reason)
|
||||
return
|
||||
}
|
||||
return result.value.default
|
||||
})
|
||||
]
|
||||
: [
|
||||
'all',
|
||||
bootFiles => bootFiles.map(entry => entry.default)
|
||||
]
|
||||
|
||||
return Promise[ method ]([
|
||||
|
||||
import(/* webpackMode: "eager" */ 'boot/dayjs'),
|
||||
|
||||
import(/* webpackMode: "eager" */ 'boot/locale'),
|
||||
|
||||
import(/* webpackMode: "eager" */ 'boot/resizeObserverGuard')
|
||||
|
||||
]).then(bootFiles => {
|
||||
const boot = mapFn(bootFiles).filter(entry => typeof entry === 'function')
|
||||
start(app, boot)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* THIS FILE IS GENERATED AUTOMATICALLY.
|
||||
* DO NOT EDIT.
|
||||
*
|
||||
* You are probably looking on adding startup/initialization code.
|
||||
* Use "quasar new boot <name>" and add it there.
|
||||
* One boot file per concern. Then reference the file(s) in quasar.config file > boot:
|
||||
* boot: ['file', ...] // do not add ".js" extension to it.
|
||||
*
|
||||
* Boot files are your "main.js"
|
||||
**/
|
||||
|
||||
|
||||
|
||||
import App from 'app/src/App.vue'
|
||||
let appPrefetch = typeof App.preFetch === 'function'
|
||||
? App.preFetch
|
||||
: (
|
||||
// Class components return the component options (and the preFetch hook) inside __c property
|
||||
App.__c !== void 0 && typeof App.__c.preFetch === 'function'
|
||||
? App.__c.preFetch
|
||||
: false
|
||||
)
|
||||
|
||||
|
||||
function getMatchedComponents (to, router) {
|
||||
const route = to
|
||||
? (to.matched ? to : router.resolve(to).route)
|
||||
: router.currentRoute.value
|
||||
|
||||
if (!route) { return [] }
|
||||
|
||||
const matched = route.matched.filter(m => m.components !== void 0)
|
||||
|
||||
if (matched.length === 0) { return [] }
|
||||
|
||||
return Array.prototype.concat.apply([], matched.map(m => {
|
||||
return Object.keys(m.components).map(key => {
|
||||
const comp = m.components[key]
|
||||
return {
|
||||
path: m.path,
|
||||
c: comp
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
export function addPreFetchHooks ({ router, store, publicPath }) {
|
||||
// Add router hook for handling preFetch.
|
||||
// Doing it after initial route is resolved so that we don't double-fetch
|
||||
// the data that we already have. Using router.beforeResolve() so that all
|
||||
// async components are resolved.
|
||||
router.beforeResolve((to, from, next) => {
|
||||
const
|
||||
urlPath = window.location.href.replace(window.location.origin, ''),
|
||||
matched = getMatchedComponents(to, router),
|
||||
prevMatched = getMatchedComponents(from, router)
|
||||
|
||||
let diffed = false
|
||||
const preFetchList = matched
|
||||
.filter((m, i) => {
|
||||
return diffed || (diffed = (
|
||||
!prevMatched[i] ||
|
||||
prevMatched[i].c !== m.c ||
|
||||
m.path.indexOf('/:') > -1 // does it has params?
|
||||
))
|
||||
})
|
||||
.filter(m => m.c !== void 0 && (
|
||||
typeof m.c.preFetch === 'function'
|
||||
// Class components return the component options (and the preFetch hook) inside __c property
|
||||
|| (m.c.__c !== void 0 && typeof m.c.__c.preFetch === 'function')
|
||||
))
|
||||
.map(m => m.c.__c !== void 0 ? m.c.__c.preFetch : m.c.preFetch)
|
||||
|
||||
|
||||
if (appPrefetch !== false) {
|
||||
preFetchList.unshift(appPrefetch)
|
||||
appPrefetch = false
|
||||
}
|
||||
|
||||
|
||||
if (preFetchList.length === 0) {
|
||||
return next()
|
||||
}
|
||||
|
||||
let hasRedirected = false
|
||||
const redirect = url => {
|
||||
hasRedirected = true
|
||||
next(url)
|
||||
}
|
||||
const proceed = () => {
|
||||
|
||||
if (hasRedirected === false) { next() }
|
||||
}
|
||||
|
||||
|
||||
|
||||
preFetchList.reduce(
|
||||
(promise, preFetch) => promise.then(() => hasRedirected === false && preFetch({
|
||||
store,
|
||||
currentRoute: to,
|
||||
previousRoute: from,
|
||||
redirect,
|
||||
urlPath,
|
||||
publicPath
|
||||
})),
|
||||
Promise.resolve()
|
||||
)
|
||||
.then(proceed)
|
||||
.catch(e => {
|
||||
console.error(e)
|
||||
proceed()
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* THIS FILE IS GENERATED AUTOMATICALLY.
|
||||
* DO NOT EDIT.
|
||||
*
|
||||
* You are probably looking on adding startup/initialization code.
|
||||
* Use "quasar new boot <name>" and add it there.
|
||||
* One boot file per concern. Then reference the file(s) in quasar.config file > boot:
|
||||
* boot: ['file', ...] // do not add ".js" extension to it.
|
||||
*
|
||||
* Boot files are your "main.js"
|
||||
**/
|
||||
|
||||
import lang from 'quasar/lang/tr.js'
|
||||
|
||||
|
||||
|
||||
import {Loading,Dialog,Notify} from 'quasar'
|
||||
|
||||
|
||||
|
||||
export default { config: {"notify":{"position":"top","timeout":2500}},lang,plugins: {Loading,Dialog,Notify} }
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
flat
|
||||
bordered
|
||||
dense
|
||||
row-key="urun_ilk_grubu"
|
||||
row-key="group_code"
|
||||
:loading="store.loading"
|
||||
:rows="store.rows"
|
||||
:columns="columns"
|
||||
@@ -25,8 +25,8 @@
|
||||
<template #body-cell-mail_selector="props">
|
||||
<q-td :props="props">
|
||||
<q-select
|
||||
:model-value="editableByGroup[props.row.urun_ilk_grubu] || []"
|
||||
:options="mailOptionsByGroup[props.row.urun_ilk_grubu] || allMailOptions"
|
||||
:model-value="editableByGroup[props.row.group_code] || []"
|
||||
:options="mailOptionsByGroup[props.row.group_code] || allMailOptions"
|
||||
option-value="id"
|
||||
option-label="label"
|
||||
emit-value
|
||||
@@ -39,8 +39,8 @@
|
||||
dense
|
||||
outlined
|
||||
label="Mail ara ve sec"
|
||||
@filter="(val, update) => filterMailOptions(props.row.urun_ilk_grubu, val, update)"
|
||||
@update:model-value="(val) => updateRowSelection(props.row.urun_ilk_grubu, val)"
|
||||
@filter="(val, update) => filterMailOptions(props.row.group_code, val, update)"
|
||||
@update:model-value="(val) => updateRowSelection(props.row.group_code, val)"
|
||||
/>
|
||||
</q-td>
|
||||
</template>
|
||||
@@ -71,7 +71,8 @@ const originalByGroup = ref({})
|
||||
const mailOptionsByGroup = ref({})
|
||||
|
||||
const columns = [
|
||||
{ name: 'urun_ilk_grubu', label: 'Urun Ilk Grubu', field: 'urun_ilk_grubu', align: 'left' },
|
||||
{ name: 'group_code', label: 'Urun Ilk Grup Kodu', field: 'group_code', align: 'left' },
|
||||
{ name: 'group_title', label: 'Urun Ilk Grup Aciklama', field: 'group_title', align: 'left' },
|
||||
{ name: 'mail_selector', label: 'Maliyet Mail Eslestirme', field: 'mail_selector', align: 'left' }
|
||||
]
|
||||
|
||||
@@ -81,7 +82,7 @@ const allMailOptions = computed(() =>
|
||||
|
||||
const changedGroups = computed(() => {
|
||||
return (store.rows || [])
|
||||
.map((r) => String(r.urun_ilk_grubu || '').trim())
|
||||
.map((r) => String(r.group_code || r.urun_ilk_grubu || '').trim())
|
||||
.filter(Boolean)
|
||||
.filter((g) => {
|
||||
const current = normalizeList(editableByGroup.value[g] || [])
|
||||
@@ -115,7 +116,7 @@ function initEditableState () {
|
||||
const original = {}
|
||||
|
||||
;(store.rows || []).forEach((row) => {
|
||||
const g = String(row.urun_ilk_grubu || '').trim()
|
||||
const g = String(row.group_code || row.urun_ilk_grubu || '').trim()
|
||||
const selected = normalizeList(row.mail_ids || [])
|
||||
editable[g] = [...selected]
|
||||
original[g] = [...selected]
|
||||
@@ -169,4 +170,3 @@ async function saveChanges () {
|
||||
|
||||
onMounted(() => { init() })
|
||||
</script>
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
flat
|
||||
bordered
|
||||
dense
|
||||
row-key="urun_ilk_grubu"
|
||||
row-key="group_code"
|
||||
:loading="store.loading"
|
||||
:rows="store.rows"
|
||||
:columns="columns"
|
||||
@@ -25,8 +25,8 @@
|
||||
<template #body-cell-mail_selector="props">
|
||||
<q-td :props="props">
|
||||
<q-select
|
||||
:model-value="editableByGroup[props.row.urun_ilk_grubu] || []"
|
||||
:options="mailOptionsByGroup[props.row.urun_ilk_grubu] || allMailOptions"
|
||||
:model-value="editableByGroup[props.row.group_code] || []"
|
||||
:options="mailOptionsByGroup[props.row.group_code] || allMailOptions"
|
||||
option-value="id"
|
||||
option-label="label"
|
||||
emit-value
|
||||
@@ -39,8 +39,8 @@
|
||||
dense
|
||||
outlined
|
||||
label="Mail ara ve sec"
|
||||
@filter="(val, update) => filterMailOptions(props.row.urun_ilk_grubu, val, update)"
|
||||
@update:model-value="(val) => updateRowSelection(props.row.urun_ilk_grubu, val)"
|
||||
@filter="(val, update) => filterMailOptions(props.row.group_code, val, update)"
|
||||
@update:model-value="(val) => updateRowSelection(props.row.group_code, val)"
|
||||
/>
|
||||
</q-td>
|
||||
</template>
|
||||
@@ -71,7 +71,8 @@ const originalByGroup = ref({})
|
||||
const mailOptionsByGroup = ref({})
|
||||
|
||||
const columns = [
|
||||
{ name: 'urun_ilk_grubu', label: 'Urun Ilk Grubu', field: 'urun_ilk_grubu', align: 'left' },
|
||||
{ name: 'group_code', label: 'Urun Ilk Grup Kodu', field: 'group_code', align: 'left' },
|
||||
{ name: 'group_title', label: 'Urun Ilk Grup Aciklama', field: 'group_title', align: 'left' },
|
||||
{ name: 'mail_selector', label: 'Fiyatlandirma Mail Eslestirme', field: 'mail_selector', align: 'left' }
|
||||
]
|
||||
|
||||
@@ -81,7 +82,7 @@ const allMailOptions = computed(() =>
|
||||
|
||||
const changedGroups = computed(() => {
|
||||
return (store.rows || [])
|
||||
.map((r) => String(r.urun_ilk_grubu || '').trim())
|
||||
.map((r) => String(r.group_code || r.urun_ilk_grubu || '').trim())
|
||||
.filter(Boolean)
|
||||
.filter((g) => {
|
||||
const current = normalizeList(editableByGroup.value[g] || [])
|
||||
@@ -115,7 +116,7 @@ function initEditableState () {
|
||||
const original = {}
|
||||
|
||||
;(store.rows || []).forEach((row) => {
|
||||
const g = String(row.urun_ilk_grubu || '').trim()
|
||||
const g = String(row.group_code || row.urun_ilk_grubu || '').trim()
|
||||
const selected = normalizeList(row.mail_ids || [])
|
||||
editable[g] = [...selected]
|
||||
original[g] = [...selected]
|
||||
@@ -169,4 +170,3 @@ async function saveChanges () {
|
||||
|
||||
onMounted(() => { init() })
|
||||
</script>
|
||||
|
||||
|
||||
@@ -3,37 +3,65 @@
|
||||
<div ref="stickyStackRef" class="sticky-stack pcd-sticky-stack">
|
||||
<div ref="saveToolbarRef" class="save-toolbar pcd-save-toolbar q-px-md">
|
||||
<div class="pcd-toolbar-row">
|
||||
<div class="pcd-toolbar-left">
|
||||
<div class="pcd-toolbar-title">Maliyet Detay Sayfasi</div>
|
||||
<div v-if="detailHeader && !detailLoading" class="pcd-toolbar-summary">
|
||||
<div class="pcd-toolbar-pill pcd-toolbar-pill-emphasis">
|
||||
<span class="pcd-toolbar-pill-label">USD</span>
|
||||
<span class="pcd-toolbar-pill-value">{{ formatMoney(toolbarSummary.usdTotal) }}</span>
|
||||
</div>
|
||||
<div class="pcd-toolbar-pill pcd-toolbar-pill-emphasis">
|
||||
<span class="pcd-toolbar-pill-label">EUR</span>
|
||||
<span class="pcd-toolbar-pill-value">{{ formatMoney(toolbarSummary.eurTotal) }}</span>
|
||||
</div>
|
||||
<div class="pcd-toolbar-pill pcd-toolbar-pill-emphasis">
|
||||
<span class="pcd-toolbar-pill-label">GBP</span>
|
||||
<span class="pcd-toolbar-pill-value">{{ formatMoney(toolbarSummary.gbpTotal) }}</span>
|
||||
</div>
|
||||
<div class="pcd-toolbar-pill pcd-toolbar-pill-neutral">
|
||||
<span class="pcd-toolbar-pill-label">USD Kur</span>
|
||||
<span class="pcd-toolbar-pill-value">{{ formatMoney(exchangeRates.usdRate) }}</span>
|
||||
</div>
|
||||
<div class="pcd-toolbar-pill pcd-toolbar-pill-neutral">
|
||||
<span class="pcd-toolbar-pill-label">EUR Kur</span>
|
||||
<span class="pcd-toolbar-pill-value">{{ formatMoney(exchangeRates.eurRate) }}</span>
|
||||
</div>
|
||||
<div class="pcd-toolbar-pill pcd-toolbar-pill-neutral">
|
||||
<span class="pcd-toolbar-pill-label">GBP Kur</span>
|
||||
<span class="pcd-toolbar-pill-value">{{ formatMoney(exchangeRates.gbpRate) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pcd-toolbar-left">
|
||||
<div class="pcd-toolbar-title">Maliyet Detay Sayfasi</div>
|
||||
<div v-if="detailHeader && !detailLoading" class="pcd-toolbar-summary">
|
||||
<!-- tbStok kontrolu (exists-bulk) manuel giris kapandigi icin devre disi -->
|
||||
<div class="pcd-toolbar-summary-row">
|
||||
<div class="pcd-toolbar-pill pcd-toolbar-pill-emphasis">
|
||||
<span class="pcd-toolbar-pill-label">USD</span>
|
||||
<span class="pcd-toolbar-pill-value">{{ formatMoney(toolbarSummary.usdTotal) }}</span>
|
||||
</div>
|
||||
<div class="pcd-toolbar-pill pcd-toolbar-pill-emphasis">
|
||||
<span class="pcd-toolbar-pill-label">TRY</span>
|
||||
<span class="pcd-toolbar-pill-value">{{ formatMoney(toolbarSummary.tryTotal) }}</span>
|
||||
</div>
|
||||
<div class="pcd-toolbar-pill pcd-toolbar-pill-emphasis">
|
||||
<span class="pcd-toolbar-pill-label">EUR</span>
|
||||
<span class="pcd-toolbar-pill-value">{{ formatMoney(toolbarSummary.eurTotal) }}</span>
|
||||
</div>
|
||||
<div class="pcd-toolbar-pill pcd-toolbar-pill-emphasis">
|
||||
<span class="pcd-toolbar-pill-label">GBP</span>
|
||||
<span class="pcd-toolbar-pill-value">{{ formatMoney(toolbarSummary.gbpTotal) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pcd-toolbar-summary-row">
|
||||
<div class="pcd-toolbar-pill pcd-toolbar-pill-neutral">
|
||||
<span class="pcd-toolbar-pill-label">USD Kur</span>
|
||||
<span class="pcd-toolbar-pill-value">{{ formatMoney(exchangeRates.usdRate) }}</span>
|
||||
</div>
|
||||
<div class="pcd-toolbar-pill pcd-toolbar-pill-neutral">
|
||||
<span class="pcd-toolbar-pill-label">EUR Kur</span>
|
||||
<span class="pcd-toolbar-pill-value">{{ formatMoney(exchangeRates.eurRate) }}</span>
|
||||
</div>
|
||||
<div class="pcd-toolbar-pill pcd-toolbar-pill-neutral">
|
||||
<span class="pcd-toolbar-pill-label">GBP Kur</span>
|
||||
<span class="pcd-toolbar-pill-value">{{ formatMoney(exchangeRates.gbpRate) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pcd-toolbar-actions">
|
||||
<div class="pcd-toolbar-actions">
|
||||
<div
|
||||
v-if="last10WarningCount > 0"
|
||||
class="pcd-toolbar-pill pcd-toolbar-pill-warn"
|
||||
style="cursor:pointer;"
|
||||
title="Son 10 ort. fiyata gore %10+ sapma var"
|
||||
@click="last10WarningDialogOpen = true"
|
||||
>
|
||||
<span class="pcd-toolbar-pill-label">Fiyat Uyari</span>
|
||||
<span class="pcd-toolbar-pill-value">{{ last10WarningCount }}</span>
|
||||
</div>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
color="grey-7"
|
||||
class="pcd-toolbar-btn"
|
||||
:label="summaryOpen ? 'OZET GIZLE' : 'OZET GOSTER'"
|
||||
:icon="summaryOpen ? 'visibility_off' : 'visibility'"
|
||||
@click="summaryOpen = !summaryOpen"
|
||||
/>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
@@ -114,6 +142,178 @@
|
||||
Hata: {{ detailError }}
|
||||
</q-banner>
|
||||
|
||||
<q-expansion-item
|
||||
v-if="detailHeader && !detailLoading"
|
||||
v-model="summaryOpen"
|
||||
dense
|
||||
expand-separator
|
||||
class="pcd-summary-expansion q-mx-md q-mb-md"
|
||||
icon="summarize"
|
||||
label="Ozet"
|
||||
>
|
||||
<div class="row q-col-gutter-md q-pa-sm">
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="pcd-summary-title">Maliyetlere Islenen Toplam Tutar</div>
|
||||
<q-markup-table dense flat bordered class="pcd-summary-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="pcd-summary-k">USD</td>
|
||||
<td class="pcd-summary-v">{{ formatMoney(mailSummary.headerTotals.usd) }}</td>
|
||||
<td class="pcd-summary-k">TRY</td>
|
||||
<td class="pcd-summary-v">{{ formatMoney(mailSummary.headerTotals.try) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="pcd-summary-k">EUR</td>
|
||||
<td class="pcd-summary-v">{{ formatMoney(mailSummary.headerTotals.eur) }}</td>
|
||||
<td class="pcd-summary-k">GBP</td>
|
||||
<td class="pcd-summary-v">{{ formatMoney(mailSummary.headerTotals.gbp) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</q-markup-table>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="pcd-summary-title">Iscilik Fiyatlari (CM2)</div>
|
||||
<q-markup-table dense flat bordered class="pcd-summary-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left">Parca</th>
|
||||
<th class="text-right">Giris</th>
|
||||
<th class="text-left">Pr.Br.</th>
|
||||
<th class="text-right">USD Tutar</th>
|
||||
<th class="text-right">TRY Tutar</th>
|
||||
<th class="text-center">CMT/Malzemeli</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="r in mailSummary.laborRows" :key="'labor-'+r.part">
|
||||
<td class="text-left">{{ r.part }}</td>
|
||||
<td class="text-right">{{ r.inputAmountLabel }}</td>
|
||||
<td class="text-left">{{ r.inputCurrencyLabel }}</td>
|
||||
<td class="text-right">{{ formatMoney(r.usd) }}</td>
|
||||
<td class="text-right">{{ formatMoney(r.try) }}</td>
|
||||
<td class="text-center">{{ r.hasCmtOrMalzemeli ? '✓' : '' }}</td>
|
||||
</tr>
|
||||
<tr class="pcd-summary-total-row">
|
||||
<td class="text-left text-weight-bold">TOPLAM</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td class="text-right text-weight-bold">{{ formatMoney(mailSummary.laborRows.reduce((a, x) => a + (x.usd || 0), 0)) }}</td>
|
||||
<td class="text-right text-weight-bold">{{ formatMoney(mailSummary.laborRows.reduce((a, x) => a + (x.try || 0), 0)) }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</q-markup-table>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="pcd-summary-title">Malzeme Fiyatlari (DT/TP, maliyete dahil)</div>
|
||||
<q-markup-table dense flat bordered class="pcd-summary-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left">Parca</th>
|
||||
<th class="text-right">USD Tutar</th>
|
||||
<th class="text-right">TRY Tutar</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="r in mailSummary.materialRows" :key="'mat-'+r.part">
|
||||
<td class="text-left">{{ r.part }}</td>
|
||||
<td class="text-right">{{ formatMoney(r.usd) }}</td>
|
||||
<td class="text-right">{{ formatMoney(r.try) }}</td>
|
||||
</tr>
|
||||
<tr class="pcd-summary-total-row">
|
||||
<td class="text-left text-weight-bold">TOPLAM</td>
|
||||
<td class="text-right text-weight-bold">{{ formatMoney(mailSummary.materialRows.reduce((a, x) => a + (x.usd || 0), 0)) }}</td>
|
||||
<td class="text-right text-weight-bold">{{ formatMoney(mailSummary.materialRows.reduce((a, x) => a + (x.try || 0), 0)) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</q-markup-table>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="pcd-summary-title">Kumas Fiyatlari (FABRIC, maliyete dahil)</div>
|
||||
<q-markup-table dense flat bordered class="pcd-summary-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left">Parca</th>
|
||||
<th class="text-right">Metraj</th>
|
||||
<th class="text-right">MT Giris Fiyat</th>
|
||||
<th class="text-left">Pr.Br.</th>
|
||||
<th class="text-right">USD Tutar</th>
|
||||
<th class="text-right">TRY Tutar</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="r in mailSummary.fabricRows" :key="'fab-'+r.part">
|
||||
<td class="text-left">{{ r.part }}</td>
|
||||
<td class="text-right">{{ r.meterLabel }}</td>
|
||||
<td class="text-right">{{ r.inputUnitLabel }}</td>
|
||||
<td class="text-left">{{ r.inputCurrencyLabel }}</td>
|
||||
<td class="text-right">{{ formatMoney(r.usd) }}</td>
|
||||
<td class="text-right">{{ formatMoney(r.try) }}</td>
|
||||
</tr>
|
||||
<tr class="pcd-summary-total-row">
|
||||
<td class="text-left text-weight-bold">TOPLAM</td>
|
||||
<td class="text-right text-weight-bold">
|
||||
{{
|
||||
(() => {
|
||||
const total = mailSummary.fabricRows.reduce((a, x) => a + (Number(x.meterQty || 0) || 0), 0)
|
||||
return total > 0 ? `${formatBarQuantity(total)} MT` : '-'
|
||||
})()
|
||||
}}
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td class="text-right text-weight-bold">{{ formatMoney(mailSummary.fabricRows.reduce((a, x) => a + (x.usd || 0), 0)) }}</td>
|
||||
<td class="text-right text-weight-bold">{{ formatMoney(mailSummary.fabricRows.reduce((a, x) => a + (x.try || 0), 0)) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</q-markup-table>
|
||||
</div>
|
||||
</div>
|
||||
</q-expansion-item>
|
||||
|
||||
<q-dialog v-model="last10WarningDialogOpen" persistent>
|
||||
<q-card style="min-width: 860px; max-width: 92vw;">
|
||||
<q-card-section class="row items-center">
|
||||
<div class="text-h6">Son 10 Ort. Fiyat Sapmalari</div>
|
||||
<q-space />
|
||||
<q-btn icon="close" flat round dense v-close-popup />
|
||||
</q-card-section>
|
||||
<q-separator />
|
||||
<q-card-section style="max-height: 70vh; overflow: auto;">
|
||||
<q-markup-table dense flat bordered>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left">Kod</th>
|
||||
<th class="text-left">Doviz</th>
|
||||
<th class="text-right">Giris</th>
|
||||
<th class="text-right">Ort10</th>
|
||||
<th class="text-right">Sapma %</th>
|
||||
<th class="text-right">Sample</th>
|
||||
<th class="text-left">Tarih Araligi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<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.currency_code }}</td>
|
||||
<td class="text-right">{{ formatMoney(w.input_price) }}</td>
|
||||
<td class="text-right">{{ formatMoney(w.avg_doc_price) }}</td>
|
||||
<td class="text-right">{{ formatPercent(w.diff_ratio) }}</td>
|
||||
<td class="text-right">{{ w.sample_count }}</td>
|
||||
<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>
|
||||
</tr>
|
||||
</tbody>
|
||||
</q-markup-table>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
|
||||
<div v-if="detailHeader && !detailLoading && !headerInfoCollapsed" class="filter-bar pcd-detail-header-bar q-mx-md q-mb-md">
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col-12 col-md-3">
|
||||
@@ -189,7 +389,7 @@
|
||||
<q-input dense filled readonly label="nUrtReceteID" :model-value="detailHeader.nUrtReceteID || '-'" />
|
||||
</div>
|
||||
|
||||
<div v-if="partSummary && partSummary.length > 0" class="col-12">
|
||||
<div v-if="false && partSummary && partSummary.length > 0" class="col-12">
|
||||
<div class="pcd-part-summary-card">
|
||||
<div class="pcd-part-summary-title">Parça Bazlı Maliyet Özellikleri</div>
|
||||
<q-markup-table dense flat bordered separator="cell" class="pcd-part-summary-table">
|
||||
@@ -386,7 +586,7 @@
|
||||
<template #body-cell-sKodu="props">
|
||||
<q-td
|
||||
:props="props"
|
||||
:class="resolveAutoOrICodeHighlightClass(props.row)"
|
||||
:class="[resolveAutoOrICodeHighlightClass(props.row), resolveMissingStockCodeClass(props.row)]"
|
||||
>
|
||||
<span>{{ props.value }}</span>
|
||||
</q-td>
|
||||
@@ -395,7 +595,7 @@
|
||||
<template #body-cell-sAciklama="props">
|
||||
<q-td
|
||||
:props="props"
|
||||
:class="resolveAutoOrICodeHighlightClass(props.row)"
|
||||
:class="[resolveAutoOrICodeHighlightClass(props.row), resolveMissingStockCodeClass(props.row)]"
|
||||
>
|
||||
<span>{{ props.value }}</span>
|
||||
</q-td>
|
||||
@@ -880,6 +1080,7 @@ const rowEditorHammaddeLoading = ref(false)
|
||||
const rowEditorItemOptions = ref([])
|
||||
const rowEditorItemAllOptions = ref([])
|
||||
const rowEditorItemLoading = ref(false)
|
||||
const rowEditorLastValidItemValue = ref('')
|
||||
const rowEditorColorOptions = ref([])
|
||||
const rowEditorColorAllOptions = ref([])
|
||||
const rowEditorColorLoading = ref(false)
|
||||
@@ -894,7 +1095,89 @@ const lineHistoryTargetSummary = ref('')
|
||||
const lineHistorySearchMode = ref('exact')
|
||||
const lineHistoryLastPurchaseMatchStage = ref('')
|
||||
const lineHistoryLastRecipeMatchStage = ref('')
|
||||
|
||||
// tbStok validation (missing stock cards)
|
||||
// Manuel giris kapandigi icin tbStok exists-bulk kontrolunu devre disi biraktik.
|
||||
// Eski UI/CSS hook'lari kalsin diye state'i tutuyoruz ama request atmayacagiz.
|
||||
const missingTbStokCodesMap = ref({})
|
||||
const tbStokValidationLastError = ref('')
|
||||
const tbStokMissingDialogShown = ref(false)
|
||||
let tbStokValidationTimer = null
|
||||
|
||||
function normalizeStockCodeKey (code) {
|
||||
return String(code || '').trim().toUpperCase()
|
||||
}
|
||||
|
||||
function openMissingTbStokDialog () {
|
||||
const missing = Object.keys(missingTbStokCodesMap.value || {})
|
||||
if (missing.length === 0) return
|
||||
$q.dialog({
|
||||
title: 'tbStok Eksik Kodlar',
|
||||
message: `Asagidaki kodlar tbStok'ta yok. Stok kartlarini duzeltmeden maliyet kaydetmeyin:\n\n${missing.slice(0, 80).join(', ')}${missing.length > 80 ? `\n(+${missing.length - 80} daha)` : ''}`,
|
||||
ok: { label: 'Tamam' }
|
||||
})
|
||||
}
|
||||
|
||||
function isTbStokMissingCode (code) {
|
||||
code = normalizeStockCodeKey(code)
|
||||
if (!code) return false
|
||||
return Boolean(missingTbStokCodesMap.value?.[code])
|
||||
}
|
||||
|
||||
function resolveMissingStockCodeClass (row) {
|
||||
const code = normalizeStockCodeKey(row?.sKodu)
|
||||
if (!code) return ''
|
||||
if (isTbStokMissingCode(code)) return 'pcd-missing-stock-code'
|
||||
return ''
|
||||
}
|
||||
|
||||
function collectUniqueCodesFromRows () {
|
||||
const out = []
|
||||
const seen = new Set()
|
||||
for (const r of flatDetailRows.value || []) {
|
||||
const code = normalizeStockCodeKey(r?.sKodu)
|
||||
if (!code) continue
|
||||
if (seen.has(code)) continue
|
||||
seen.add(code)
|
||||
out.push(code)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
async function refreshTbStokMissingCodes () {
|
||||
tbStokValidationLastError.value = ''
|
||||
missingTbStokCodesMap.value = {}
|
||||
}
|
||||
|
||||
function formatPercent (ratio) {
|
||||
const n = Number(ratio)
|
||||
if (!Number.isFinite(n)) return '-'
|
||||
return `${(n * 100).toFixed(0)}%`
|
||||
}
|
||||
|
||||
async function refreshLast10Warnings () {
|
||||
if (!onMLNo.value) {
|
||||
last10Warnings.value = []
|
||||
return
|
||||
}
|
||||
try {
|
||||
const resp = await get('/pricing/production-product-costing/last10-warnings', { n_onml_no: onMLNo.value })
|
||||
const items = Array.isArray(resp?.items) ? resp.items : []
|
||||
last10Warnings.value = items
|
||||
} catch (err) {
|
||||
// non-blocking
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleTbStokValidation () {
|
||||
// no-op (tbStok kontrolu devre disi)
|
||||
if (tbStokValidationTimer) clearTimeout(tbStokValidationTimer)
|
||||
tbStokValidationTimer = null
|
||||
}
|
||||
const purchaseAvgUSDCachedByCode = ref({})
|
||||
const last10Warnings = ref([])
|
||||
const last10WarningDialogOpen = ref(false)
|
||||
const last10WarningCount = computed(() => (Array.isArray(last10Warnings.value) ? last10Warnings.value.length : 0))
|
||||
const headerInfoCollapsed = ref(false)
|
||||
const subHeaderTop = ref(140)
|
||||
const stickyStackRef = ref(null)
|
||||
@@ -1114,6 +1397,167 @@ const toolbarSummary = computed(() => flatDetailRows.value.reduce((acc, row) =>
|
||||
gbpTotal: 0
|
||||
}))
|
||||
|
||||
const summaryOpen = ref(false)
|
||||
|
||||
function makeEmptySummaryRow (part) {
|
||||
return {
|
||||
part,
|
||||
inputAmountLabel: '-',
|
||||
inputCurrencyLabel: '-',
|
||||
inputUnitLabel: '-',
|
||||
meterLabel: '-',
|
||||
meterQty: 0,
|
||||
meterUom: '',
|
||||
usd: 0,
|
||||
try: 0,
|
||||
hasCmtOrMalzemeli: false
|
||||
}
|
||||
}
|
||||
|
||||
function accumulateInputAmount (bucket, row) {
|
||||
const qty = resolveNumericRowQuantity(row)
|
||||
const inputPrice = resolveNumericRowInputPrice(row)
|
||||
const cur = resolveInputCurrency(row) || 'USD'
|
||||
const amount = (Number.isFinite(inputPrice) ? inputPrice : 0) * (Number.isFinite(qty) ? qty : 0)
|
||||
if (!Number.isFinite(amount) || amount === 0) return
|
||||
if (!bucket.inputByCur) bucket.inputByCur = {}
|
||||
bucket.inputByCur[cur] = (bucket.inputByCur[cur] || 0) + amount
|
||||
}
|
||||
|
||||
function accumulateUnitPrice (bucket, row) {
|
||||
// For fabric summary: show a representative unit input price (first non-zero).
|
||||
const inputPrice = resolveNumericRowInputPrice(row)
|
||||
const cur = resolveInputCurrency(row) || 'USD'
|
||||
if (!Number.isFinite(inputPrice) || inputPrice <= 0) return
|
||||
if (bucket.unitPrice === undefined || bucket.unitPrice === null) {
|
||||
bucket.unitPrice = inputPrice
|
||||
bucket.unitCur = cur
|
||||
}
|
||||
}
|
||||
|
||||
function finalizeInputLabel (bucket) {
|
||||
if (Number(bucket.meterQty || 0) > 0) {
|
||||
const uom = String(bucket.meterUom || '').trim()
|
||||
bucket.meterLabel = `${formatBarQuantity(bucket.meterQty)}${uom ? ' ' + uom : ''}`
|
||||
} else {
|
||||
bucket.meterLabel = bucket.meterLabel || '-'
|
||||
}
|
||||
const byCur = bucket.inputByCur || {}
|
||||
const currencies = Object.keys(byCur).filter(Boolean)
|
||||
if (currencies.length === 0) {
|
||||
bucket.inputAmountLabel = '-'
|
||||
bucket.inputCurrencyLabel = '-'
|
||||
bucket.inputUnitLabel = bucket.inputUnitLabel || '-'
|
||||
return bucket
|
||||
}
|
||||
if (currencies.length === 1) {
|
||||
const cur = currencies[0]
|
||||
bucket.inputCurrencyLabel = cur
|
||||
bucket.inputAmountLabel = formatMoney(byCur[cur] || 0)
|
||||
if (bucket.unitPrice !== undefined && bucket.unitCur) {
|
||||
bucket.inputUnitLabel = formatMoney(bucket.unitPrice)
|
||||
bucket.inputCurrencyLabel = String(bucket.unitCur || cur).toUpperCase()
|
||||
}
|
||||
return bucket
|
||||
}
|
||||
// Multiple currencies mixed: keep labels short.
|
||||
bucket.inputCurrencyLabel = 'MIX'
|
||||
bucket.inputAmountLabel = formatMoney(currencies.reduce((acc, c) => acc + (byCur[c] || 0), 0))
|
||||
return bucket
|
||||
}
|
||||
|
||||
const mailSummary = computed(() => {
|
||||
// Parts must be dynamic. Use sParcaAdi coming from backend (spUrtMTBolum.sAdi),
|
||||
// and keep a stable order: common parts first, then the rest by first appearance.
|
||||
const preferredParts = ['CEKET', 'PANTOLON', 'YELEK', 'AKSESUAR', 'YAKA']
|
||||
const seenParts = new Set()
|
||||
const dynamicParts = []
|
||||
flatDetailRows.value.forEach((row) => {
|
||||
const partRaw = normalizeGroupName(row?.sParcaAdi)
|
||||
const part = String(partRaw || '').trim().toUpperCase()
|
||||
if (!part) return
|
||||
if (seenParts.has(part)) return
|
||||
seenParts.add(part)
|
||||
dynamicParts.push(part)
|
||||
})
|
||||
const parts = [
|
||||
...preferredParts.filter(p => seenParts.has(p)),
|
||||
...dynamicParts.filter(p => !preferredParts.includes(p))
|
||||
]
|
||||
const base = {
|
||||
headerTotals: {
|
||||
usd: toolbarSummary.value.usdTotal || 0,
|
||||
try: toolbarSummary.value.tryTotal || 0,
|
||||
eur: toolbarSummary.value.eurTotal || 0,
|
||||
gbp: toolbarSummary.value.gbpTotal || 0
|
||||
},
|
||||
laborByPart: {},
|
||||
materialByPart: {},
|
||||
fabricByPart: {}
|
||||
}
|
||||
|
||||
parts.forEach((p) => {
|
||||
base.laborByPart[p] = makeEmptySummaryRow(p)
|
||||
base.materialByPart[p] = makeEmptySummaryRow(p)
|
||||
base.fabricByPart[p] = makeEmptySummaryRow(p)
|
||||
})
|
||||
|
||||
flatDetailRows.value.forEach((row) => {
|
||||
const partRaw = normalizeGroupName(row?.sParcaAdi)
|
||||
const part = String(partRaw || '').trim().toUpperCase()
|
||||
if (!part || !parts.includes(part)) return
|
||||
|
||||
const group = String(normalizeGroupName(row?.sAciklama3)).trim().toUpperCase()
|
||||
const included = Boolean(row?.maliyeteDahil) || isCMGroupName(group)
|
||||
if (!included) return
|
||||
|
||||
const usdAmount = resolveRowUSDTutar(row)
|
||||
const tryAmount = resolveRowTRYTutar(row)
|
||||
|
||||
if (isCMGroupName(group)) {
|
||||
const b = base.laborByPart[part]
|
||||
b.usd += usdAmount
|
||||
b.try += tryAmount
|
||||
accumulateInputAmount(b, row)
|
||||
// Tick should reflect actual checkbox state (only when user selected "malzemeli"/type=2).
|
||||
if (resolveCMPriceTypeChecked(row)) b.hasCmtOrMalzemeli = true
|
||||
return
|
||||
}
|
||||
|
||||
if (group === 'DT' || group.includes(' DT') || group === 'TP' || group.includes(' TP')) {
|
||||
const b = base.materialByPart[part]
|
||||
b.usd += usdAmount
|
||||
b.try += tryAmount
|
||||
accumulateInputAmount(b, row)
|
||||
return
|
||||
}
|
||||
|
||||
if (group === 'FABRIC' || group.includes('FABRIC')) {
|
||||
const b = base.fabricByPart[part]
|
||||
b.usd += usdAmount
|
||||
b.try += tryAmount
|
||||
accumulateInputAmount(b, row)
|
||||
accumulateUnitPrice(b, row)
|
||||
const qty = resolveNumericRowQuantity(row)
|
||||
if (Number.isFinite(qty) && qty > 0) {
|
||||
b.meterQty += qty
|
||||
if (!b.meterUom) b.meterUom = String(row?.sBirim || '').trim()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const laborRows = parts.map((p) => finalizeInputLabel(base.laborByPart[p]))
|
||||
const materialRows = parts.map((p) => finalizeInputLabel(base.materialByPart[p]))
|
||||
const fabricRows = parts.map((p) => finalizeInputLabel(base.fabricByPart[p]))
|
||||
|
||||
return {
|
||||
headerTotals: base.headerTotals,
|
||||
laborRows,
|
||||
materialRows,
|
||||
fabricRows
|
||||
}
|
||||
})
|
||||
|
||||
const partSummary = computed(() => {
|
||||
const summary = {}
|
||||
flatDetailRows.value.forEach(row => {
|
||||
@@ -1129,25 +1573,27 @@ const partSummary = computed(() => {
|
||||
})
|
||||
return Object.entries(summary).map(([name, totals]) => ({ name, ...totals }))
|
||||
})
|
||||
const lineHistoryColumns = [
|
||||
{ name: 'sourceLabel', label: 'Kaynak', field: 'sourceLabel', align: 'left', sortable: false, style: 'width:6%', headerStyle: 'width:6%' },
|
||||
{ name: 'dateLabel', label: 'Tarih', field: 'dateLabel', align: 'left', sortable: true, style: 'width:7%', headerStyle: 'width:7%' },
|
||||
{ name: 'invoiceCode', label: 'Fatura/OnML', field: 'invoiceCode', align: 'left', sortable: true, style: 'width:8%', headerStyle: 'width:8%' },
|
||||
{ name: 'companyCode', label: 'Firma Kodu', field: 'companyCode', align: 'left', sortable: true, style: 'width:8%', headerStyle: 'width:8%' },
|
||||
{ name: 'companyDescription', label: 'Firma Aciklama', field: 'companyDescription', align: 'left', sortable: true, style: 'width:12%', headerStyle: 'width:12%' },
|
||||
{ name: 'itemCode', label: 'Masraf/sKodu', field: 'itemCode', align: 'left', sortable: true, style: 'width:8%', headerStyle: 'width:8%' },
|
||||
{ name: 'itemDescription', label: 'Masraf Detay', field: 'itemDescription', align: 'left', sortable: true, style: 'width:11%', headerStyle: 'width:11%' },
|
||||
{ name: 'colorCode', label: 'Renk', field: 'colorCode', align: 'left', sortable: true, style: 'width:5%', headerStyle: 'width:5%' },
|
||||
{ name: 'colorDescription', label: 'Renk Aciklama', field: 'colorDescription', align: 'left', sortable: true, style: 'width:8%', headerStyle: 'width:8%' },
|
||||
{ name: 'itemDim1Code', label: 'Dim1', field: 'itemDim1Code', align: 'left', sortable: true, style: 'width:5%', headerStyle: 'width:5%' },
|
||||
{ name: 'itemDim1Description', label: 'Dim1 Aciklama', field: 'itemDim1Description', align: 'left', sortable: true, style: 'width:8%', headerStyle: 'width:8%' },
|
||||
{ name: 'quantity', label: 'Miktar', field: 'quantity', align: 'right', sortable: true, style: 'width:6%', headerStyle: 'width:6%' },
|
||||
{ name: 'unit', label: 'Birim', field: 'unit', align: 'left', sortable: true, style: 'width:4%', headerStyle: 'width:4%' },
|
||||
{ name: 'price', label: 'Fiyat', field: 'price', align: 'right', sortable: true, style: 'width:6%', headerStyle: 'width:6%' },
|
||||
{ name: 'amount', label: 'Tutar', field: 'amount', align: 'right', sortable: true, style: 'width:6%', headerStyle: 'width:6%' },
|
||||
{ name: 'currency', label: 'Pr. Br.', field: 'currency', align: 'left', sortable: true, style: 'width:5%', headerStyle: 'width:5%' },
|
||||
{ name: 'select', label: '', field: 'select', align: 'right', sortable: false, style: 'width:6%', headerStyle: 'width:6%' }
|
||||
]
|
||||
const lineHistoryColumns = [
|
||||
// Not: fixed percentage widths were overflowing at 100% zoom.
|
||||
// Let the table layout decide widths so the "Sec" column stays visible.
|
||||
{ name: 'sourceLabel', label: 'Kaynak', field: 'sourceLabel', align: 'left', sortable: false },
|
||||
{ name: 'dateLabel', label: 'Tarih', field: 'dateLabel', align: 'left', sortable: true },
|
||||
{ name: 'invoiceCode', label: 'Fatura/OnML', field: 'invoiceCode', align: 'left', sortable: true },
|
||||
{ name: 'companyCode', label: 'Firma Kodu', field: 'companyCode', align: 'left', sortable: true },
|
||||
{ name: 'companyDescription', label: 'Firma Aciklama', field: 'companyDescription', align: 'left', sortable: true },
|
||||
{ name: 'itemCode', label: 'Masraf/sKodu', field: 'itemCode', align: 'left', sortable: true },
|
||||
{ name: 'itemDescription', label: 'Masraf Detay', field: 'itemDescription', align: 'left', sortable: true },
|
||||
{ name: 'colorCode', label: 'Renk', field: 'colorCode', align: 'left', sortable: true },
|
||||
{ name: 'colorDescription', label: 'Renk Aciklama', field: 'colorDescription', align: 'left', sortable: true },
|
||||
{ name: 'itemDim1Code', label: 'Dim1', field: 'itemDim1Code', align: 'left', sortable: true },
|
||||
{ name: 'itemDim1Description', label: 'Dim1 Aciklama', field: 'itemDim1Description', align: 'left', sortable: true },
|
||||
{ name: 'quantity', label: 'Miktar', field: 'quantity', align: 'right', sortable: true },
|
||||
{ name: 'unit', label: 'Birim', field: 'unit', align: 'left', sortable: true },
|
||||
{ name: 'price', label: 'Fiyat', field: 'price', align: 'right', sortable: true },
|
||||
{ name: 'amount', label: 'Tutar', field: 'amount', align: 'right', sortable: true },
|
||||
{ name: 'currency', label: 'Pr. Br.', field: 'currency', align: 'left', sortable: true },
|
||||
{ name: 'select', label: '', field: 'select', align: 'right', sortable: false }
|
||||
]
|
||||
|
||||
function resolveLineHistoryRowClass (row) {
|
||||
if (row?.priceType === 'BNZ') return 'pcd-history-row-similar'
|
||||
@@ -1166,7 +1612,7 @@ const detailColumns = [
|
||||
{ name: 'sRenk', label: 'Renk', field: 'sRenk', align: 'left', sortable: true },
|
||||
{ name: 'lMiktar', label: 'Miktar', field: 'lMiktar', align: 'right', sortable: true, format: val => formatQuantity(val), style: 'width: 80px', headerStyle: 'width: 80px' },
|
||||
{ name: 'inputPrice', label: 'Fiyat Giriş', field: 'inputPrice', align: 'right', sortable: false, style: 'width: 80px', headerStyle: 'width: 80px' },
|
||||
{ name: 'inputPricePrBr', label: 'Fiyat Giriş Pr.Br.', field: 'inputPricePrBr', align: 'left', sortable: false, style: 'width: 80px', headerStyle: 'width: 80px' },
|
||||
{ name: 'inputPricePrBr', label: 'Fiyat Giriş Pr.Br.', field: 'inputPricePrBr', align: 'left', sortable: false, style: 'width: 92px', headerStyle: 'width: 92px' },
|
||||
{ name: 'maliyeteDahil', label: 'Maliyete Dahil', field: 'maliyeteDahil', align: 'center', sortable: false },
|
||||
{ name: 'cmPriceType', label: 'CMT', field: 'cm_price_type_id', align: 'center', sortable: false, style: 'width: 72px', headerStyle: 'width: 72px' },
|
||||
{ name: 'lFiyat', label: 'lFiyat', field: 'lFiyat', align: 'right', sortable: true, format: val => formatMoney(val) },
|
||||
@@ -1290,14 +1736,22 @@ function formatQuantity (value) {
|
||||
})
|
||||
}
|
||||
|
||||
function formatQuantity2 (value) {
|
||||
return parseMoneyInput(value).toLocaleString('tr-TR', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
})
|
||||
}
|
||||
|
||||
function formatBarMoney (value) {
|
||||
const roundedValue = Math.round((Number(value || 0) + Number.EPSILON) * 100) / 100
|
||||
return formatMoney(roundedValue)
|
||||
}
|
||||
|
||||
function formatBarQuantity (value) {
|
||||
const roundedValue = Math.round((Number(value || 0) + Number.EPSILON) * 10000) / 10000
|
||||
return formatQuantity(roundedValue)
|
||||
// Bar/summary quantities: show 2 decimals (requested for MT totals).
|
||||
const roundedValue = Math.round((Number(value || 0) + Number.EPSILON) * 100) / 100
|
||||
return formatQuantity2(roundedValue)
|
||||
}
|
||||
|
||||
function convertPriceToUSD (price, currency) {
|
||||
@@ -1630,8 +2084,15 @@ async function loadRowEditorColorOptions () {
|
||||
function primeRowEditorOptionsFromForm () {
|
||||
upsertEditorOption(rowEditorHammaddeAllOptions, buildRowEditorHammaddeOption(rowEditorForm.value))
|
||||
upsertEditorOption(rowEditorHammaddeOptions, buildRowEditorHammaddeOption(rowEditorForm.value))
|
||||
upsertEditorOption(rowEditorItemAllOptions, buildRowEditorItemOption(rowEditorForm.value))
|
||||
upsertEditorOption(rowEditorItemOptions, buildRowEditorItemOption(rowEditorForm.value))
|
||||
// Do not inject invalid free-typed values (e.g. "GAMBOÇ") into the item select options.
|
||||
// Item options must represent real tbStok model codes (usually like "M.X", "K.X", "I.X").
|
||||
const itemCandidate = buildRowEditorItemOption(rowEditorForm.value)
|
||||
const itemKey = String(itemCandidate?.value || '').trim()
|
||||
const looksLikeModel = /^[A-Za-z0-9]\./.test(itemKey)
|
||||
if (looksLikeModel) {
|
||||
upsertEditorOption(rowEditorItemAllOptions, itemCandidate)
|
||||
upsertEditorOption(rowEditorItemOptions, itemCandidate)
|
||||
}
|
||||
upsertEditorOption(rowEditorColorAllOptions, buildRowEditorColorOption(rowEditorForm.value))
|
||||
upsertEditorOption(rowEditorColorOptions, buildRowEditorColorOption(rowEditorForm.value))
|
||||
}
|
||||
@@ -2402,7 +2863,8 @@ async function fetchDetail (options = {}) {
|
||||
])
|
||||
detailHeader.value = headerData && typeof headerData === 'object' ? headerData : null
|
||||
productionTypes.value = Array.isArray(typesData) ? typesData : []
|
||||
costDate.value = normalizeDateInput(detailHeader.value?.dteKayitTarihi)
|
||||
// Prefer true costing date (spUrtOnMLMas.Tarihi) over record create/update timestamps.
|
||||
costDate.value = normalizeDateInput(detailHeader.value?.maliyetTarihi || detailHeader.value?.dteKayitTarihi)
|
||||
detailGroups.value = normalizeDetailGroups(groupsData)
|
||||
initialHeaderSnapshot.value = currentHeaderSnapshot.value
|
||||
// Optional: hydrate local draft after base data load.
|
||||
@@ -2437,7 +2899,11 @@ async function fetchDetail (options = {}) {
|
||||
urun_kodu: detailHeader.value?.UrunKodu || productCode.value,
|
||||
n_urt_recete_id: detailHeader.value?.nUrtReceteID || ''
|
||||
})
|
||||
} catch (err) {
|
||||
// tbStok exists-bulk kontrolu devre disi (manual giris kapali).
|
||||
// Load last10 avg deviation warnings panel (non-blocking).
|
||||
await refreshLast10Warnings()
|
||||
// (eski tbStok missing dialog/notify kaldirildi)
|
||||
} catch (err) {
|
||||
detailError.value = await extractApiErrorDetail(err)
|
||||
slog.error('production-product-costing.detail', 'fetch-detail:error', {
|
||||
trace_id: traceId.value,
|
||||
@@ -2908,6 +3374,8 @@ function resolveDetailRowClass (row) {
|
||||
const key = String(row?.__rowKey || '').trim()
|
||||
if (key && requiredAttentionRowKeys.value?.[key]) return 'pcd-detail-row-required'
|
||||
if (row?.requiredPlaceholder) return 'pcd-detail-row-required'
|
||||
const code = String(row?.sKodu || '').trim()
|
||||
if (code && isTbStokMissingCode(code)) return 'pcd-detail-row-missing-stock'
|
||||
return row?.draftChanged ? 'pcd-detail-row-secondary' : ''
|
||||
}
|
||||
|
||||
@@ -3011,8 +3479,27 @@ async function applyFabricCopySelection () {
|
||||
|
||||
async function onRowEditorItemChange (value) {
|
||||
const selected = rowEditorItemOptions.value.find(opt => String(opt?.value || '') === String(value || ''))
|
||||
// Prevent free-typed values from being accepted as "code". User must pick from list.
|
||||
if (!selected) {
|
||||
const typed = String(value || '').trim()
|
||||
// If user cleared the field, allow clearing (other validations will catch blank-on-save if needed).
|
||||
if (!typed) {
|
||||
rowEditorForm.value.sKodu = ''
|
||||
rowEditorLastValidItemValue.value = ''
|
||||
return
|
||||
}
|
||||
// Revert to last valid selection.
|
||||
rowEditorForm.value.sKodu = String(rowEditorLastValidItemValue.value || '').trim()
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
message: 'Kod secilmeden serbest metin girilemez. Listeden secim yapin.',
|
||||
position: 'top-right'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
rowEditorForm.value.sKodu = String(value || '').trim()
|
||||
if (!selected) return
|
||||
rowEditorLastValidItemValue.value = String(value || '').trim()
|
||||
const previousColorCode = String(rowEditorForm.value.ColorCode || '').trim()
|
||||
rowEditorForm.value.nStokID = String(selected.nStokID || '').trim()
|
||||
rowEditorForm.value.sModel = String(selected.sModel || '').trim()
|
||||
@@ -3129,7 +3616,8 @@ function applyEditorRowToGroups (nextRow) {
|
||||
detailGroups.value = sortDetailGroups(nextGroups)
|
||||
syncAllGroupsOpen()
|
||||
schedulePersistLocalDraft()
|
||||
}
|
||||
// tbStok validation devre disi
|
||||
}
|
||||
|
||||
function syncAllGroupsOpen () {
|
||||
const openState = {}
|
||||
@@ -3704,6 +4192,7 @@ function openNewRowDialog () {
|
||||
maliyeteDahil: true,
|
||||
sBirim: 'AD'
|
||||
})
|
||||
rowEditorLastValidItemValue.value = ''
|
||||
primeRowEditorOptionsFromForm()
|
||||
rowEditorDialogOpen.value = true
|
||||
void bootstrapRowEditorOptions()
|
||||
@@ -3716,6 +4205,7 @@ function openRowEditorForEdit (row) {
|
||||
...row,
|
||||
sAciklama3: row?.sAciklama3 || ''
|
||||
})
|
||||
rowEditorLastValidItemValue.value = String(rowEditorForm.value?.sKodu || '').trim()
|
||||
primeRowEditorOptionsFromForm()
|
||||
rowEditorDialogOpen.value = true
|
||||
void bootstrapRowEditorOptions()
|
||||
@@ -4113,6 +4603,7 @@ async function saveChanges () {
|
||||
saveLoading.value = true
|
||||
try {
|
||||
requiredAttentionRowKeys.value = {}
|
||||
// tbStok exists-bulk kontrolu devre disi (manual giris kapali): save bloke edilmez.
|
||||
if (isNoCostDetail.value) {
|
||||
const missing = computeMissingRequiredSlots()
|
||||
slog.info('production-product-costing.detail', 'required:missing:computed', {
|
||||
@@ -4319,6 +4810,10 @@ async function saveChanges () {
|
||||
|
||||
// For existing costing, just refresh the detail.
|
||||
await fetchDetail({ clearDraft: true, hydrateDraft: false })
|
||||
// last10 warnings are computed async on backend; re-check shortly after save.
|
||||
window.setTimeout(() => {
|
||||
try { refreshLast10Warnings() } catch {}
|
||||
}, 1200)
|
||||
} catch (e) {
|
||||
// Surface backend message (http.Error text) when available.
|
||||
const msg = String(
|
||||
@@ -4485,14 +4980,22 @@ watch(
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pcd-toolbar-summary {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.pcd-toolbar-summary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.pcd-toolbar-summary-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.pcd-toolbar-pill {
|
||||
display: flex;
|
||||
@@ -4511,9 +5014,67 @@ watch(
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.pcd-toolbar-pill-neutral {
|
||||
background: #f5f7fa;
|
||||
.pcd-toolbar-pill-neutral {
|
||||
background: #f5f7fa;
|
||||
color: #2b3c54;
|
||||
}
|
||||
|
||||
.pcd-toolbar-pill-warn {
|
||||
background: #ef6c00;
|
||||
border-color: #ef6c00;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.pcd-summary-expansion :deep(.q-expansion-item__container) {
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.pcd-missing-stock-code {
|
||||
background: #c62828 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.pcd-missing-stock-code :deep(*) {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.pcd-detail-row-missing-stock td {
|
||||
background: #c62828 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.pcd-detail-row-missing-stock td :deep(*) {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.pcd-summary-title {
|
||||
font-weight: 800;
|
||||
font-size: 12px;
|
||||
color: #2b3c54;
|
||||
margin: 2px 0 6px;
|
||||
}
|
||||
|
||||
.pcd-summary-table td,
|
||||
.pcd-summary-table th {
|
||||
font-size: 12px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.pcd-summary-total-row td {
|
||||
border-top: 2px solid rgba(0, 0, 0, 0.15);
|
||||
background: #fbfbfd;
|
||||
}
|
||||
|
||||
.pcd-summary-k {
|
||||
width: 60px;
|
||||
color: #6b7680;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.pcd-summary-v {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.pcd-toolbar-pill-label {
|
||||
|
||||
@@ -191,7 +191,8 @@ import api from 'src/services/api'
|
||||
import { usePermission } from 'src/composables/usePermission'
|
||||
|
||||
const { canUpdate } = usePermission()
|
||||
const canUpdateUser = canUpdate('user')
|
||||
// This screen manages system-wide permission sets; gate by system:update.
|
||||
const canUpdateUser = canUpdate('system')
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
@@ -394,13 +395,22 @@ async function save () {
|
||||
|
||||
const payload = []
|
||||
|
||||
// UI action keys -> backend action codes
|
||||
const toBackendAction = {
|
||||
write: 'insert',
|
||||
read: 'view',
|
||||
delete: 'delete',
|
||||
update: 'update',
|
||||
export: 'export'
|
||||
}
|
||||
|
||||
rows.value.forEach(r => {
|
||||
|
||||
actions.forEach(a => {
|
||||
|
||||
payload.push({
|
||||
module: r.module,
|
||||
action: a.key,
|
||||
action: toBackendAction[a.key] || a.key,
|
||||
allowed: r[a.key]
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user