Merge remote-tracking branch 'origin/master'

This commit is contained in:
M_Kececi
2026-05-06 11:07:55 +03:00
parent 05a2a03a6a
commit 77fe2b04b6
38 changed files with 12676 additions and 8 deletions

View File

@@ -0,0 +1,75 @@
/* 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
}
}

View File

@@ -0,0 +1,158 @@
/* 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)
})
})

View File

@@ -0,0 +1,116 @@
/* 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()
})
})
}

View File

@@ -0,0 +1,23 @@
/* 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} }

View File

@@ -0,0 +1,97 @@
npm warn Unknown project config "shamefully-hoist". This will stop working in the next major version of npm. See `npm help npmrc` for supported config options.
npm warn Unknown project config "strict-peer-dependencies". This will stop working in the next major version of npm. See `npm help npmrc` for supported config options.
npm warn Unknown project config "resolution-mode". This will stop working in the next major version of npm. See `npm help npmrc` for supported config options.
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue?vue&type=script&setup=true&lang=js
Syntax Error: TypeError: Cannot read properties of null (reading 'content')
at selectBlock (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\select.js:23:45)
at Object.loader (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\index.js:93:41)
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue
Module Error (from ./node_modules/vue-loader/dist/index.js):
VueCompilerError: Element is missing end tag.
at D:\baggitekstilas\software projects\bssapp\bssapp\ui\src\pages\ProductionProductCostingHasCostDetail.vue:2617:1
2614|
2615| </script>
2616|
| ^
2617| <style scoped>
2618| .pcd-page {
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?

296
ui/quasar-dev-9102.out.log Normal file
View File

@@ -0,0 +1,296 @@
> baggisowtfaresystem@0.0.1 dev
> quasar dev --port 9102 --hostname 127.0.0.1
.d88888b.
d88P" "Y88b
888 888
888 888 888 888 8888b. .d8888b 8888b. 888d888
888 888 888 888 "88b 88K "88b 888P"
888 Y8b 888 888 888 .d888888 "Y8888b. .d888888 888
Y88b.Y8b88P Y88b 888 888 888 X88 888 888 888
"Y888888" "Y88888 "Y888888 88888P' "Y888888 888
Y8b
App • Using quasar.config.js in "esm" format
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • DONE • "SPA UI" compiled by Webpack with errors • 16663ms
App • COMPILATION FAILED • Please check the log above for details.
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 4708ms
» Reported at............... 29.04.2026 19:59:28
» App dir................... D:\baggitekstilas\software projects\bssapp\bssapp\ui
» App URL................... http://127.0.0.1:9102/
» Dev mode.................. spa
» Pkg quasar................ v2.18.6
» Pkg @quasar/app-webpack... v4.3.2
» Webpack transpiled JS..... yes (Babel)
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2252ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 4291ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 60ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 713ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 8605ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 2753ms
App • COMPILATION FAILED • Please check the log above for details.
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3927ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1249ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1265ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1128ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 4600ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2824ms
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 54ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 617ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3332ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2947ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 80ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 611ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 4786ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 4916ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 59ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 733ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 32ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 539ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 26ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 566ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 47ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 840ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2536ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2803ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3245ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1962ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3271ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2830ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 39ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 605ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 22ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 253ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3930ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1727ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • DONE • "SPA UI" compiled by Webpack with errors • 94ms
App • COMPILATION FAILED • Please check the log above for details.
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 296ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 4152ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2614ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 30ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 289ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • Applying quasar.config file changes...
App • DONE • "SPA UI" compiled with success by Webpack • 205ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3249ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2064ms
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 23ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 238ms
App • Applying quasar.config file changes...

View File

@@ -0,0 +1,75 @@
npm warn Unknown project config "shamefully-hoist". This will stop working in the next major version of npm. See `npm help npmrc` for supported config options.
npm warn Unknown project config "strict-peer-dependencies". This will stop working in the next major version of npm. See `npm help npmrc` for supported config options.
npm warn Unknown project config "resolution-mode". This will stop working in the next major version of npm. See `npm help npmrc` for supported config options.
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?

282
ui/quasar-dev-9103.out.log Normal file
View File

@@ -0,0 +1,282 @@
> baggisowtfaresystem@0.0.1 dev
> quasar dev --port 9103 --hostname 127.0.0.1
.d88888b.
d88P" "Y88b
888 888
888 888 888 888 8888b. .d8888b 8888b. 888d888
888 888 888 888 "88b 88K "88b 888P"
888 Y8b 888 888 888 .d888888 "Y8888b. .d888888 888
Y88b.Y8b88P Y88b 888 888 888 X88 888 888 888
"Y888888" "Y88888 "Y888888 88888P' "Y888888 888
Y8b
App • Using quasar.config.js in "esm" format
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 30114ms
» Reported at............... 29.04.2026 20:02:54
» App dir................... D:\baggitekstilas\software projects\bssapp\bssapp\ui
» App URL................... http://127.0.0.1:9103/
» Dev mode.................. spa
» Pkg quasar................ v2.18.6
» Pkg @quasar/app-webpack... v4.3.2
» Webpack transpiled JS..... yes (Babel)
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 4662ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 44ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 667ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 9844ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 4855ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1320ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1362ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1175ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3993ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2864ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • DONE • "SPA UI" compiled by Webpack with errors • 74ms
App • COMPILATION FAILED • Please check the log above for details.
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 667ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3306ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2984ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 87ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 617ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 4879ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 5001ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 74ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 666ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 56ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 525ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 27ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 587ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 97ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 898ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2810ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2801ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3683ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1934ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3294ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2827ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 37ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 636ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 530ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 27ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 237ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3974ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1801ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • DONE • "SPA UI" compiled by Webpack with errors • 57ms
App • COMPILATION FAILED • Please check the log above for details.
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 286ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3989ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2476ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 29ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 301ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3558ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1850ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 20ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 225ms
App • Applying quasar.config file changes...

310
ui/quasar-dev.err.log Normal file
View File

@@ -0,0 +1,310 @@
npm warn Unknown project config "shamefully-hoist". This will stop working in the next major version of npm. See `npm help npmrc` for supported config options.
npm warn Unknown project config "strict-peer-dependencies". This will stop working in the next major version of npm. See `npm help npmrc` for supported config options.
npm warn Unknown project config "resolution-mode". This will stop working in the next major version of npm. See `npm help npmrc` for supported config options.
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue
Module Error (from ./node_modules/vue-loader/dist/index.js):
VueCompilerError: Element is missing end tag.
at D:\baggitekstilas\software projects\bssapp\bssapp\ui\src\pages\ProductionProductCostingHasCostDetail.vue:629:9
627| </div>
628|
629| <div v-else>
| ^
630| <div class="row items-center justify-end q-mb-sm" v-if="lineHistoryTargetHammaddeTuruNo">
631| <q-btn
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue
Module Error (from ./node_modules/vue-loader/dist/index.js):
VueCompilerError: Element is missing end tag.
at D:\baggitekstilas\software projects\bssapp\bssapp\ui\src\pages\ProductionProductCostingHasCostDetail.vue:629:9
627| </div>
628|
629| <div v-else>
| ^
630| <div class="row items-center justify-end q-mb-sm" v-if="lineHistoryTargetHammaddeTuruNo">
631| <q-btn
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue
Module Error (from ./node_modules/vue-loader/dist/index.js):
VueCompilerError: Element is missing end tag.
at D:\baggitekstilas\software projects\bssapp\bssapp\ui\src\pages\ProductionProductCostingHasCostDetail.vue:629:9
627| </div>
628|
629| <div v-else>
| ^
630| <div class="row items-center justify-end q-mb-sm" v-if="lineHistoryTargetHammaddeTuruNo">
631| <q-btn
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue
Module Error (from ./node_modules/vue-loader/dist/index.js):
VueCompilerError: Element is missing end tag.
at D:\baggitekstilas\software projects\bssapp\bssapp\ui\src\pages\ProductionProductCostingHasCostDetail.vue:629:9
627| </div>
628|
629| <div v-else>
| ^
630| <div class="row items-center justify-end q-mb-sm" v-if="lineHistoryTargetHammaddeTuruNo">
631| <q-btn
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue?vue&type=script&setup=true&lang=js
Syntax Error: TypeError: Cannot read properties of null (reading 'content')
at selectBlock (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\select.js:23:45)
at Object.loader (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\index.js:93:41)
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue?vue&type=script&setup=true&lang=js
Syntax Error: TypeError: Cannot read properties of null (reading 'content')
at selectBlock (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\select.js:23:45)
at Object.loader (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\index.js:93:41)
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue?vue&type=script&setup=true&lang=js
Syntax Error: TypeError: Cannot read properties of null (reading 'content')
at selectBlock (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\select.js:23:45)
at Object.loader (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\index.js:93:41)
Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'D:\baggitekstilas\software projects\bssapp\bssapp\ui\quasar.config.js.temporary.compiled.1777459195929.mjs' imported from D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\@quasar\app-webpack\lib\quasar-config-file.js
at finalizeResolution (node:internal/modules/esm/resolve:274:11)
at moduleResolve (node:internal/modules/esm/resolve:859:10)
at defaultResolve (node:internal/modules/esm/resolve:983:11)
at #cachedDefaultResolve (node:internal/modules/esm/loader:717:20)
at ModuleLoader.resolve (node:internal/modules/esm/loader:694:38)
at ModuleLoader.getModuleJobForImport (node:internal/modules/esm/loader:308:38)
at onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:650:36)
at TracingChannel.tracePromise (node:diagnostics_channel:344:14)
at ModuleLoader.import (node:internal/modules/esm/loader:649:21)
at defaultImportModuleDynamicallyForScript (node:internal/modules/esm/utils:235:31)
at importModuleDynamicallyCallback (node:internal/modules/esm/utils:257:12)
at D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\@quasar\app-webpack\lib\quasar-config-file.js:377:62
at D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\esbuild\lib\main.js:1317:33
at runOnEndCallbacks (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\esbuild\lib\main.js:1346:9)
at buildResponseToResult (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\esbuild\lib\main.js:924:7)
at D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\esbuild\lib\main.js:936:9
at new Promise (<anonymous>)
at requestCallbacks.on-end (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\esbuild\lib\main.js:935:54)
at handleRequest (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\esbuild\lib\main.js:628:17)
at handleIncomingPacket (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\esbuild\lib\main.js:653:7)
at Socket.readFromStdout (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\esbuild\lib\main.js:581:7)
at Socket.emit (node:events:519:28)
at addChunk (node:internal/streams/readable:561:12)
at readableAddChunkPushByteMode (node:internal/streams/readable:512:3)
at Readable.push (node:internal/streams/readable:392:5)
at Pipe.onStreamRead (node:internal/stream_base_commons:189:23) {
code: 'ERR_MODULE_NOT_FOUND',
url: 'file:///D:/baggitekstilas/software%20projects/bssapp/bssapp/ui/quasar.config.js.temporary.compiled.1777459195929.mjs?t=1777474212686'
}
App • ⚠️ Importing quasar.config file results in error. Please check the Node.js stack above against the temporarily created quasar.config.js.temporary.compiled.1777459195929.mjs file and fix the original file then DELETE the temporary one ("quasar clean --qconf" can be used).
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue?vue&type=script&setup=true&lang=js
Syntax Error: TypeError: Cannot read properties of null (reading 'content')
at selectBlock (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\select.js:23:45)
at Object.loader (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\index.js:93:41)
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue?vue&type=script&setup=true&lang=js
Syntax Error: TypeError: Cannot read properties of null (reading 'content')
at selectBlock (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\select.js:23:45)
at Object.loader (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\index.js:93:41)
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue?vue&type=script&setup=true&lang=js
Syntax Error: TypeError: Cannot read properties of null (reading 'content')
at selectBlock (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\select.js:23:45)
at Object.loader (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\index.js:93:41)
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue?vue&type=script&setup=true&lang=js
Syntax Error: TypeError: Cannot read properties of null (reading 'content')
at selectBlock (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\select.js:23:45)
at Object.loader (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\index.js:93:41)
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue?vue&type=script&setup=true&lang=js
Syntax Error: TypeError: Cannot read properties of null (reading 'content')
at selectBlock (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\select.js:23:45)
at Object.loader (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\index.js:93:41)
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue?vue&type=script&setup=true&lang=js
Syntax Error: TypeError: Cannot read properties of null (reading 'content')
at selectBlock (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\select.js:23:45)
at Object.loader (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\index.js:93:41)
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue?vue&type=script&setup=true&lang=js
Syntax Error: TypeError: Cannot read properties of null (reading 'content')
at selectBlock (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\select.js:23:45)
at Object.loader (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\index.js:93:41)
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue?vue&type=script&setup=true&lang=js
Syntax Error: TypeError: Cannot read properties of null (reading 'content')
at selectBlock (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\select.js:23:45)
at Object.loader (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\index.js:93:41)
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?

578
ui/quasar-dev.out.log Normal file
View File

@@ -0,0 +1,578 @@
> baggisowtfaresystem@0.0.1 dev
> quasar dev --port 9100 --hostname 127.0.0.1
.d88888b.
d88P" "Y88b
888 888
888 888 888 888 8888b. .d8888b 8888b. 888d888
888 888 888 888 "88b 88K "88b 888P"
888 Y8b 888 888 888 .d888888 "Y8888b. .d888888 888
Y88b.Y8b88P Y88b 888 888 888 X88 888 888 888
"Y888888" "Y88888 "Y888888 88888P' "Y888888 888
Y8b
App • Using quasar.config.js in "esm" format
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 9782ms
» Reported at............... 29.04.2026 13:40:07
» App dir................... D:\baggitekstilas\software projects\bssapp\bssapp\ui
» App URL................... http://127.0.0.1:9100/
» Dev mode.................. spa
» Pkg quasar................ v2.18.6
» Pkg @quasar/app-webpack... v4.3.2
» Webpack transpiled JS..... yes (Babel)
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 252ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 32ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 219ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 907ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 32ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 212ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 459ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 711ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 578ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 987ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 618ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 433ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 431ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1103ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2147ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 340ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 453ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 721ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 392ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 685ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1348ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1650ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 22ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 196ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 111ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 52ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 224ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2054ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 381ms
App • COMPILATION FAILED • Please check the log above for details.
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 656ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 61ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 420ms
App • COMPILATION FAILED • Please check the log above for details.
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • Applying quasar.config file changes...
App • DONE • "SPA UI" compiled by Webpack with errors • 355ms
App • COMPILATION FAILED • Please check the log above for details.
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 2573ms
App • COMPILATION FAILED • Please check the log above for details.
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • DONE • "SPA UI" compiled by Webpack with errors • 35ms
App • COMPILATION FAILED • Please check the log above for details.
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 355ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 2168ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 58ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 445ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 1914ms
App • COMPILATION FAILED • Please check the log above for details.
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 55ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 443ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 2288ms
App • COMPILATION FAILED • Please check the log above for details.
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 56ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 348ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 38ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 339ms
App • COMPILATION FAILED • Please check the log above for details.
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • Applying quasar.config file changes...
App • DONE • "SPA UI" compiled by Webpack with errors • 277ms
App • COMPILATION FAILED • Please check the log above for details.
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 47ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 489ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3903ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2020ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 4095ms
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 42ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 661ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 8717ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 5079ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2476ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1230ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1203ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1129ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 5848ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2848ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 115ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 612ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3207ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2894ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 73ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 451ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 256ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 5443ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 4432ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 56ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 774ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • DONE • "SPA UI" compiled by Webpack with errors • 35ms
App • COMPILATION FAILED • Please check the log above for details.
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 509ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 31ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 576ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • DONE • "SPA UI" compiled by Webpack with errors • 50ms
App • COMPILATION FAILED • Please check the log above for details.
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 833ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2630ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2299ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3191ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1905ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3218ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2310ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 37ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 624ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 647ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 26ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 234ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 4120ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1714ms
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 26ms
App • COMPILATION FAILED • Please check the log above for details.
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 243ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 219ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 4317ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2456ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 18ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 251ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3959ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1742ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 19ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 226ms
App • Applying quasar.config file changes...

View File

@@ -325,7 +325,7 @@ const menuItems = [
},
{
label: 'Fiyatlandırma',
label: 'Fiyatlandırma/Maliyetlendirme',
icon: 'request_quote',
children: [
@@ -333,6 +333,16 @@ const menuItems = [
label: 'Ürün Fiyatlandırma',
to: '/app/pricing/product-pricing',
permission: 'order:view'
},
{
label: "Üretim'den Ürün Maliyetlendirme",
to: '/app/pricing/production-product-costing',
permission: 'order:view'
},
{
label: 'Maliyet Parça Eşleştirme',
to: '/app/pricing/production-product-costing/maliyet-parca-eslestirme',
permission: 'order:view'
}
]
},

View File

@@ -0,0 +1,110 @@
<template>
<q-page v-if="canReadOrder" class="costing-gateway-page flex flex-center">
<div class="gateway-container">
<div class="gateway-header">
<div class="text-h5">Üretim'den Ürün Maliyetlendirme</div>
<div class="text-subtitle2 text-grey-7">
İşlem seçerek maliyetlendirme ekranına geçin
</div>
</div>
<div class="gateway-actions row q-col-gutter-lg q-mt-lg">
<q-card
class="gateway-card cursor-pointer"
flat
bordered
@click="goHasCostProducts"
>
<q-card-section class="text-center">
<q-icon name="fact_check" size="48px" color="primary" />
<div class="text-h6 q-mt-sm">Mevcut Maliyeti Olan Ürünleri Göster</div>
</q-card-section>
</q-card>
<q-card
class="gateway-card cursor-pointer"
flat
bordered
@click="goNoCostProducts"
>
<q-card-section class="text-center">
<q-icon name="price_change" size="48px" color="primary" />
<div class="text-h6 q-mt-sm">Maliyeti Olmayan Ürünler İçin Maliyet Oluştur</div>
</q-card-section>
</q-card>
<q-card
class="gateway-card cursor-pointer"
flat
bordered
@click="goMTBolumMapping"
>
<q-card-section class="text-center">
<q-icon name="hub" size="48px" color="primary" />
<div class="text-h6 q-mt-sm">Maliyet Parça Eşleştirme</div>
</q-card-section>
</q-card>
</div>
</div>
</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 { useRouter } from 'vue-router'
import { usePermission } from 'src/composables/usePermission'
const { canRead } = usePermission()
const canReadOrder = canRead('order')
const router = useRouter()
function goHasCostProducts () {
if (!canReadOrder.value) return
router.push({ name: 'production-product-costing-has-cost' })
}
function goNoCostProducts () {
if (!canReadOrder.value) return
router.push({ name: 'production-product-costing-no-cost' })
}
function goMTBolumMapping () {
if (!canReadOrder.value) return
router.push({ name: 'production-product-costing-maliyet-parca-eslestirme' })
}
</script>
<style scoped>
.costing-gateway-page {
background: #fafafa;
}
.gateway-container {
width: 100%;
max-width: 1000px;
padding: 24px;
}
.gateway-header {
text-align: center;
}
.gateway-actions {
justify-content: center;
}
.gateway-card {
width: 360px;
transition: all 0.2s ease;
}
.gateway-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
}
</style>

View File

@@ -0,0 +1,478 @@
<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 (Maliyet No / Urun Kodu / Urun Adi / Aciklama)"
>
<template #append>
<q-icon name="search" />
</template>
</q-input>
<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="Mevcut Maliyeti Olan Urunler"
class="ol-table npc-table"
flat
bordered
dense
virtual-scroll
:virtual-scroll-item-size="34"
table-style="max-height: calc(100vh - 190px)"
separator="cell"
row-key="__rowKey"
:rows="rows"
:columns="columns"
:loading="loading"
no-data-label="Kayit bulunamadi"
:rows-per-page-options="[0]"
v-model:pagination="tablePagination"
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="columnDistinctOptions[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" :title="String(props.value || '')">{{ props.value }}</div>
</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 { useRouter } from 'vue-router'
import { usePermission } from 'src/composables/usePermission'
import { get, extractApiErrorDetail } from 'src/services/api'
const { canRead } = usePermission()
const canReadOrder = canRead('order')
const router = useRouter()
const loading = ref(false)
const error = ref('')
const allRows = ref([])
const tablePagination = ref({
sortBy: 'Tarihi',
descending: true,
page: 1,
rowsPerPage: 0
})
const filters = reactive({
search: ''
})
const dateColumns = new Set(['Tarihi', 'dteKayitTarihi', 'dteGuncellemeTarihi', 'SonSiparisTarihi'])
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, style: 'width:9%', headerStyle: 'width:9%' },
{ name: 'nOnMLNo', label: 'nOnMLNo', field: 'nOnMLNo', align: 'left', sortable: true, style: 'width:6%', headerStyle: 'width:6%' },
{ name: 'UrunKodu', label: 'UrunKodu', field: 'UrunKodu', align: 'left', sortable: true, style: 'width:7%', headerStyle: 'width:7%' },
{ name: 'UrunAdi', label: 'UrunAdi', field: 'UrunAdi', align: 'left', sortable: true, style: 'width:8%', headerStyle: 'width:8%' },
{ name: 'Tarihi', label: 'Tarihi', field: 'Tarihi', align: 'center', sortable: true, format: val => formatDateTR(val), style: 'width:6%', headerStyle: 'width:6%' },
{ name: 'dteKayitTarihi', label: 'dteKayitTarihi', field: 'dteKayitTarihi', align: 'center', sortable: true, format: val => formatDateTR(val), style: 'width:6%', headerStyle: 'width:6%' },
{ name: 'sKullaniciAdi', label: 'sKullaniciAdi', field: 'sKullaniciAdi', align: 'left', sortable: true, style: 'width:7%', headerStyle: 'width:7%' },
{ name: 'lTutarTL', label: 'lTutarTL', field: 'lTutarTL', align: 'right', sortable: true, format: val => formatMoney(val), style: 'width:6%', headerStyle: 'width:6%' },
{ name: 'lTutarUSD', label: 'lTutarUSD', field: 'lTutarUSD', align: 'right', sortable: true, format: val => formatMoney(val), style: 'width:6%', headerStyle: 'width:6%' },
{ name: 'lTutarEURO', label: 'lTutarEURO', field: 'lTutarEURO', align: 'right', sortable: true, format: val => formatMoney(val), style: 'width:6%', headerStyle: 'width:6%' },
{ name: 'dteGuncellemeTarihi', label: 'dteGuncellemeTarihi', field: 'dteGuncellemeTarihi', align: 'center', sortable: true, format: val => formatDateTR(val), style: 'width:7%', headerStyle: 'width:7%' },
{ name: 'sGuncellemeKullaniciAdi', label: 'sGuncellemeKullaniciAdi', field: 'sGuncellemeKullaniciAdi', align: 'left', sortable: true, style: 'width:7%', headerStyle: 'width:7%' },
{ name: 'nUrtReceteID', label: 'nUrtReceteID', field: 'nUrtReceteID', align: 'left', sortable: true, style: 'width:6%', headerStyle: 'width:6%' },
{ name: 'sAciklama', label: 'sAciklama', field: 'sAciklama', align: 'left', sortable: true, style: 'width:8%', headerStyle: 'width:8%' },
{ name: 'SonSiparisTarihi', label: 'SonSiparisTarihi', field: 'SonSiparisTarihi', align: 'center', sortable: true, format: val => formatDateTR(val), style: 'width:7%', headerStyle: 'width:7%' },
{ name: 'MaliyetDurumu', label: 'MaliyetDurumu', field: 'MaliyetDurumu', align: 'left', sortable: true, style: 'width:8%', headerStyle: 'width:8%' }
]
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]}`
}
function formatMoney (value) {
return Number(value || 0).toLocaleString('tr-TR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})
}
const columnDistinctOptions = computed(() => {
const optionsByColumn = {}
for (const col of columns) {
if (col.name === 'open') continue
const set = new Set()
for (const row of allRows.value) {
const val = getColumnComparableValue(row, col.name)
if (val) set.add(val)
}
optionsByColumn[col.name] = Array.from(set)
.sort((a, b) => a.localeCompare(b, 'tr'))
.map(v => ({ label: v, value: v }))
}
return optionsByColumn
})
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 (row?.__cmp?.[colName] !== undefined) return row.__cmp[colName]
if (dateColumns.has(colName)) return formatDateTR(row?.[colName])
return String(row?.[colName] ?? '').trim()
}
function buildComparableMap (row) {
const cmp = {}
for (const col of columns) {
if (col.name === 'open') continue
cmp[col.name] = dateColumns.has(col.name)
? formatDateTR(row?.[col.name])
: String(row?.[col.name] ?? '').trim()
}
return cmp
}
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)
}
)
async function fetchRows () {
loading.value = true
error.value = ''
try {
const data = await get('/pricing/production-product-costing/has-cost-products', {
search: filters.search || '',
offset: 0,
limit: 300
})
const list = Array.isArray(data) ? data : []
const sortedList = list.slice().sort((a, b) => {
const aDate = Date.parse(String(a?.Tarihi || ''))
const bDate = Date.parse(String(b?.Tarihi || ''))
const aTs = Number.isNaN(aDate) ? 0 : aDate
const bTs = Number.isNaN(bDate) ? 0 : bDate
return bTs - aTs
})
allRows.value = sortedList.map((x, i) => ({
__rowKey: `${x?.nOnMLNo || ''}-${x?.UrunKodu || ''}-${i}`,
__cmp: buildComparableMap(x),
...x
}))
tablePagination.value = {
...tablePagination.value,
sortBy: 'Tarihi',
descending: true,
page: 1
}
} catch (err) {
error.value = await extractApiErrorDetail(err)
allRows.value = []
} finally {
loading.value = false
}
}
function clearFilters () {
filters.search = ''
clearAllColumnFilters()
fetchRows()
}
function openRow (row) {
const urunKodu = String(row?.UrunKodu || '').trim()
if (!urunKodu) return
router.push({
name: 'production-product-costing-has-cost-history',
query: { urun_kodu: urunKodu }
})
}
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>

View File

@@ -0,0 +1,3474 @@
<template>
<q-page v-if="canReadOrder" class="pcd-page" :style="pageVars">
<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-actions">
<q-btn
flat
dense
color="grey-7"
class="pcd-toolbar-btn"
:label="headerInfoCollapsed ? 'HEADER GOSTER' : 'HEADER DARALT'"
:icon="headerInfoCollapsed ? 'expand_more' : 'expand_less'"
@click="toggleHeaderInfo"
/>
<q-btn icon="arrow_back" label="Geri" dense flat color="grey-8" class="pcd-toolbar-btn" @click="goBack" />
<q-btn label="Yenile" icon="refresh" dense color="primary" class="pcd-toolbar-btn" :loading="detailLoading" @click="fetchDetail" />
<q-btn
label="Toplu Fiyat Cagir"
icon="playlist_add_check"
dense
color="secondary"
outline
class="pcd-toolbar-btn"
:loading="bulkPriceLoading"
:disable="!detailHeader || detailLoading || saveLoading || bulkPriceLoading"
@click="fetchBulkItemPrices"
/>
<q-btn
label="SATIR EKLE"
dense
color="secondary"
icon="add"
class="pcd-toolbar-btn"
:disable="!detailHeader || detailLoading || saveLoading || bulkPriceLoading"
@click="openNewRowDialog"
/>
<q-btn
label="Kaydet"
icon="save"
dense
color="primary"
outline
class="pcd-toolbar-btn"
:loading="saveLoading"
:disable="!detailHeader || detailLoading || saveLoading || bulkPriceLoading"
@click="saveChanges"
/>
</div>
</div>
</div>
<q-banner v-if="detailError" class="bg-red text-white q-mb-md">
Hata: {{ detailError }}
</q-banner>
<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">
<q-input
dense
filled
readonly
label="Maliyet Tarihi"
:model-value="formatDateTR(costDate)"
class="pcd-emphasis-field-alt"
>
<template #append>
<q-icon name="event" class="cursor-pointer">
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
<q-date v-model="costDate" mask="YYYY-MM-DD" locale="tr-TR" />
</q-popup-proxy>
</q-icon>
</template>
</q-input>
</div>
<div class="col-12 col-md-3">
<q-select
v-if="!isNoCostDetail"
v-model="detailHeader.UretimSekliID"
:options="productionTypes"
option-value="id"
option-label="aciklama"
emit-value
map-options
dense
filled
label="Uretim Sekli"
class="pcd-emphasis-field-alt"
@update:model-value="onUretimSekliChange"
/>
<q-input
v-else
dense
filled
readonly
label="Uretim Sekli"
:model-value="detailHeader.UretimSekli || '-'"
class="pcd-emphasis-field-alt"
/>
</div>
<div class="col-12 col-md-6">
<div class="row q-col-gutter-xs">
<div class="col-4">
<q-input dense filled readonly label="USD Kuru" :model-value="formatMoney(exchangeRates.usdRate)" class="pcd-emphasis-field-alt" />
</div>
<div class="col-4">
<q-input dense filled readonly label="EUR Kuru" :model-value="formatMoney(exchangeRates.eurRate)" class="pcd-emphasis-field-alt" />
</div>
<div class="col-4">
<q-input dense filled readonly label="GBP Kuru" :model-value="formatMoney(exchangeRates.gbpRate)" class="pcd-emphasis-field-alt" />
</div>
</div>
</div>
<div class="col-12 col-md-4">
<q-input dense filled readonly label="Uretimi Yapan Firma" :model-value="detailHeader.UretimiYapanFirma || '-'" />
</div>
<div class="col-12 col-md-4">
<q-input dense filled readonly label="2.Firma" :model-value="detailHeader.SonIsEmriVeren || '-'" />
</div>
<div class="col-6 col-md-2">
<q-input dense filled readonly label="nOnMLNo" :model-value="detailHeader.nOnMLNo || '-'" />
</div>
<div class="col-6 col-md-2">
<q-input dense filled readonly label="UrunKodu" :model-value="detailHeader.UrunKodu || '-'" />
</div>
<div class="col-12 col-md-3">
<q-input dense filled readonly label="UrunAdi" :model-value="detailHeader.UrunAdi || '-'" />
</div>
<div class="col-12 col-md-3">
<q-input dense filled readonly label="Urun Ana Grubu" :model-value="detailHeader.UrunAnaGrubu || '-'" />
</div>
<div class="col-12 col-md-3">
<q-input dense filled readonly label="Urun Alt Grubu" :model-value="detailHeader.UrunAltGrubu || '-'" />
</div>
<div class="col-6 col-md-2">
<q-input dense filled readonly label="sKullaniciAdi" :model-value="detailHeader.sKullaniciAdi || '-'" />
</div>
<div class="col-12 col-md-3">
<q-input dense filled readonly label="Son Guncelleme Tarihi" :model-value="formatDateTR(detailHeader.dteGuncellemeTarihi)" />
</div>
<div class="col-12 col-md-2">
<q-input dense filled readonly label="sGuncellemeKullaniciAdi" :model-value="detailHeader.sGuncellemeKullaniciAdi || '-'" />
</div>
<div class="col-6 col-md-2">
<q-input dense filled readonly label="nUrtReceteID" :model-value="detailHeader.nUrtReceteID || '-'" />
</div>
<div v-if="!isNoCostDetail && partSummary && partSummary.length > 0" class="col-12">
<div class="pcd-part-summary-card">
<div class="pcd-part-summary-title">Parça Bazlı Maliyet Özetleri</div>
<q-markup-table dense flat bordered separator="cell" class="pcd-part-summary-table">
<thead>
<tr>
<th class="text-left">Parça</th>
<th class="text-right">TRY</th>
<th class="text-right">USD</th>
<th class="text-right">EUR</th>
</tr>
</thead>
<tbody>
<tr v-for="ps in partSummary" :key="ps.name">
<td class="text-left text-weight-bold">{{ ps.name }}</td>
<td class="text-right">{{ formatMoney(ps.try) }}</td>
<td class="text-right">{{ formatMoney(ps.usd) }}</td>
<td class="text-right">{{ formatMoney(ps.eur) }}</td>
</tr>
</tbody>
</q-markup-table>
</div>
</div>
</div>
</div>
<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-6 col-md-2">
<q-input dense filled readonly label="nOnMLNo" :model-value="detailHeader.nOnMLNo || '-'" />
</div>
<div class="col-12 col-md-3">
<q-input dense filled readonly label="Uretimi Yapan Firma" :model-value="detailHeader.UretimiYapanFirma || '-'" />
</div>
<div class="col-12 col-md-3">
<q-input dense filled readonly label="2.Firma" :model-value="detailHeader.SonIsEmriVeren || '-'" />
</div>
<div class="col-6 col-md-2">
<q-input dense filled readonly label="UrunKodu" :model-value="detailHeader.UrunKodu || '-'" />
</div>
<div class="col-12 col-md-2">
<q-input dense filled readonly label="Uretim Sekli" :model-value="formatUretimSekli(detailHeader)" />
</div>
</div>
</div>
</div>
<div v-if="detailLoading" class="row justify-center q-pa-lg">
<q-spinner color="primary" size="36px" />
</div>
<div v-else-if="detailGroups.length === 0" class="text-grey-7 q-pa-md">
Kayit bulunamadi.
</div>
<div v-else class="column q-gutter-md pcd-content-body">
<div
v-for="(grp, gi) in detailGroups"
:key="groupKey(grp, gi)"
class="pcd-group-card"
>
<div class="order-sub-header pcd-sub-header">
<div class="sub-left">
{{ grp.sAciklama3 || 'TANIMSIZ' }}
</div>
<div class="sub-right pcd-sub-right-clickable" @click="toggleGroup(grp, gi)">
Grup Toplami TRY: {{ formatBarMoney(resolveGroupTRYTutar(grp)) }} | USD: {{ formatBarMoney(resolveGroupUSDTutar(grp)) }}
<q-icon
:name="isGroupOpen(grp, gi) ? 'expand_less' : 'expand_more'"
size="18px"
class="q-ml-sm"
/>
</div>
</div>
<q-table
v-if="isGroupOpen(grp, gi)"
class="pcd-detail-table"
dense
flat
bordered
separator="cell"
row-key="__rowKey"
:rows="grp.items"
:columns="detailColumns"
:table-row-class-fn="resolveDetailRowClass"
@row-click="onDetailRowClick"
hide-bottom
:rows-per-page-options="[0]"
>
<template #header-cell-lMiktar="props">
<q-th :props="props" class="pcd-entry-header">
{{ props.col.label }}
</q-th>
</template>
<template #header-cell-inputPrice="props">
<q-th :props="props" class="pcd-entry-header">
{{ props.col.label }}
</q-th>
</template>
<template #header-cell-inputPricePrBr="props">
<q-th :props="props" class="pcd-entry-header">
{{ props.col.label }}
</q-th>
</template>
<template #header-cell-maliyeteDahil="props">
<q-th :props="props" class="pcd-secondary-header">
{{ props.col.label }}
</q-th>
</template>
<template #header-cell-cmPriceType="props">
<q-th :props="props" class="pcd-secondary-header">
{{ props.col.label }}
</q-th>
</template>
<template #body-cell-actions="props">
<q-td :props="props" class="q-gutter-xs no-wrap">
<q-btn
flat
round
dense
size="sm"
color="primary"
icon="history"
@click.stop="openLineHistory(props.row)"
>
<q-tooltip>Eski Fiyatlar / Geçmiş</q-tooltip>
</q-btn>
<q-icon
name="edit"
size="xs"
color="grey-7"
class="cursor-pointer"
@click.stop="openRowEditorForEdit(props.row)"
>
<q-tooltip>Satırı Düzenle</q-tooltip>
</q-icon>
</q-td>
</template>
<template #body-cell-maliyeteDahil="props">
<q-td :props="props" class="text-center pcd-secondary-cell">
<q-checkbox
v-model="props.row.maliyeteDahil"
color="primary"
keep-color
dense
@update:model-value="value => onRowMaliyeteDahilChange(props.row, value)"
/>
</q-td>
</template>
<template #body-cell-cmPriceType="props">
<q-td :props="props" class="text-center pcd-secondary-cell">
<q-checkbox
v-if="isCMGroupName(props.row.sAciklama3)"
:model-value="resolveCMPriceTypeChecked(props.row)"
color="secondary"
keep-color
dense
@update:model-value="value => onRowCMPriceTypeChange(props.row, value)"
/>
<span v-else class="text-grey-6">-</span>
</q-td>
</template>
<template #body-cell-nOnMLDetNo="props">
<q-td :props="props">
<div v-if="props.row.isNew" class="text-primary text-weight-bold">YENI</div>
<div>{{ props.value }}</div>
</q-td>
</template>
<template #body-cell-sParcaAdi="props">
<q-td :props="props">
{{ props.value || props.row.sAciklama3 || '-' }}
</q-td>
</template>
<template #body-cell-nHammaddeTuruNo="props">
<q-td :props="props">
<div class="text-weight-medium" style="font-size: 1.1em;">
{{ props.value }}<span v-if="props.row.sHammaddeTuruAdi"> - {{ props.row.sHammaddeTuruAdi }}</span>
</div>
</q-td>
</template>
<template #body-cell-sKodu="props">
<q-td :props="props">
<span>{{ props.value }}</span>
</q-td>
</template>
<template #body-cell-sAciklama="props">
<q-td :props="props">
<span>{{ props.value }}</span>
</q-td>
</template>
<template #body-cell-sRenk="props">
<q-td :props="props">
<span>{{ props.value }}</span>
</q-td>
</template>
<template #body-cell-lMiktar="props">
<q-td :props="props">
<q-input
v-model="props.row.miktarInput"
class="pcd-inline-input pcd-entry-input"
color="primary"
dense
filled
@update:model-value="value => onRowQuantityInput(props.row, value)"
@blur="normalizeRowQuantityDisplay(props.row)"
input-class="text-right"
inputmode="decimal"
placeholder="0,0000"
/>
</q-td>
</template>
<template #body-cell-inputPrice="props">
<q-td :props="props">
<q-input
v-model="props.row.inputPrice"
class="pcd-inline-input pcd-entry-input"
color="primary"
dense
filled
@update:model-value="value => onRowInputPriceChange(props.row, value)"
@blur="normalizeRowPriceDisplay(props.row)"
input-class="text-right"
inputmode="decimal"
placeholder="0,00"
/>
</q-td>
</template>
<template #body-cell-inputPricePrBr="props">
<q-td :props="props">
<q-select
v-model="props.row.inputPricePrBr"
class="pcd-inline-input pcd-entry-input"
:options="priceCurrencyOptions"
color="primary"
dense
filled
@update:model-value="value => onRowInputPriceCurrencyChange(props.row, value)"
emit-value
map-options
options-dense
/>
</q-td>
</template>
<template #body-cell-sBirim="props">
<q-td :props="props">
<span>{{ props.value }}</span>
</q-td>
</template>
</q-table>
</div>
</div>
</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>
<q-dialog v-model="rowEditorDialogOpen" persistent>
<q-card class="pcd-row-editor-dialog">
<q-card-section class="row items-center justify-between q-pb-sm">
<div class="text-subtitle1 text-weight-bold">
{{ rowEditorMode === 'edit' ? 'Satir Duzenle' : 'Yeni Satir Ekle' }}
</div>
<q-btn flat round dense icon="close" v-close-popup />
</q-card-section>
<q-separator />
<q-card-section class="q-pa-md">
<div class="row q-col-gutter-md">
<div class="col-12 col-md-2">
<q-input dense filled readonly label="No" v-model="rowEditorForm.nOnMLDetNo" />
</div>
<div class="col-12 col-md-3">
<q-input dense filled readonly label="Parca Adi" v-model="rowEditorForm.sParcaAdi" />
</div>
<div class="col-12 col-md-3">
<q-input dense filled readonly label="Parca Grubu" v-model="rowEditorForm.sAciklama3" />
</div>
<div class="col-12 col-md-4">
<q-select
v-model="rowEditorForm.nHammaddeTuruNo"
class="pcd-row-editor-entry"
:options="rowEditorHammaddeOptions"
:loading="rowEditorHammaddeLoading"
option-value="value"
option-label="label"
emit-value
map-options
use-input
fill-input
hide-selected
input-debounce="250"
dense
filled
label="Hammadde Turu"
placeholder="Aramak icin yazin"
@filter="filterRowEditorHammaddeOptions"
@update:model-value="onRowEditorHammaddeChange"
/>
</div>
<div class="col-12 col-md-6">
<q-select
v-model="rowEditorForm.sKodu"
class="pcd-row-editor-entry"
:options="rowEditorItemOptions"
:loading="rowEditorItemLoading"
option-value="value"
option-label="label"
emit-value
map-options
use-input
fill-input
hide-selected
input-debounce="350"
dense
filled
label="Kod / Aciklama"
placeholder="En az 2 karakter yazin"
@filter="filterRowEditorItemOptions"
@update:model-value="onRowEditorItemChange"
/>
</div>
<div class="col-12 col-md-6">
<q-select
v-model="rowEditorForm.ColorCode"
class="pcd-row-editor-entry"
:options="rowEditorColorOptions"
:loading="rowEditorColorLoading"
option-value="value"
option-label="label"
emit-value
map-options
use-input
fill-input
hide-selected
input-debounce="250"
dense
filled
label="Renk"
placeholder="Aramak icin yazin"
@filter="filterRowEditorColorOptions"
@update:model-value="onRowEditorColorChange"
/>
</div>
<div class="col-12 col-md-2">
<q-input v-model="rowEditorForm.miktarInput" class="pcd-row-editor-entry" dense filled label="Miktar" input-class="text-right" inputmode="decimal" />
</div>
<div class="col-12 col-md-2">
<q-input v-model="rowEditorForm.inputPrice" class="pcd-row-editor-entry" dense filled label="Fiyat Giris" input-class="text-right" inputmode="decimal" />
</div>
<div class="col-12 col-md-2">
<q-select
v-model="rowEditorForm.inputPricePrBr"
class="pcd-row-editor-entry"
:options="priceCurrencyOptions"
emit-value
map-options
options-dense
dense
filled
label="Pr.Br."
/>
</div>
<div class="col-12 col-md-3">
<q-input dense filled readonly label="Birim" v-model="rowEditorForm.sBirim" />
</div>
<div class="col-12 col-md-2 pcd-row-editor-flag" style="display:flex;align-items:center;">
<q-checkbox v-model="rowEditorForm.maliyeteDahil" label="Maliyete Dahil" color="primary" keep-color />
</div>
<div v-if="isCMGroupName(rowEditorForm.sAciklama3)" class="col-12 col-md-3 pcd-row-editor-flag" style="display:flex;align-items:center;">
<q-checkbox v-model="rowEditorForm.cmPriceTypeChecked" label="CMT Malzeme Dahil" color="secondary" keep-color />
</div>
</div>
</q-card-section>
<q-card-actions align="right" class="q-pa-md">
<q-btn
v-if="rowEditorMode === 'edit'"
flat
color="negative"
icon="delete"
label="Satiri Sil"
class="q-mr-auto"
@click="deleteRowEditor"
/>
<q-btn flat label="Vazgec" color="grey-7" v-close-popup />
<q-btn color="secondary" icon="save" label="Satira Uygula" @click="saveRowEditor" />
</q-card-actions>
</q-card>
</q-dialog>
<q-dialog v-model="lineHistoryDialogOpen" maximized>
<q-card class="pcd-history-dialog">
<q-card-section class="row items-center justify-between q-gutter-sm">
<div>
<div class="text-subtitle1 text-weight-bold">Eski Satinalma ve Recete Fiyatlari</div>
<div class="text-caption text-grey-7">
{{ lineHistoryTargetSummary }}
</div>
</div>
<q-btn flat round dense icon="close" v-close-popup />
</q-card-section>
<q-separator />
<q-card-section class="q-pa-md">
<q-banner v-if="lineHistoryError" class="bg-red text-white q-mb-md">
Hata: {{ lineHistoryError }}
</q-banner>
<q-banner v-if="lineHistoryShowingFallback" class="bg-amber-1 text-amber-9 q-mb-md rounded-borders border-amber">
<template v-slot:avatar>
<q-icon name="info" color="amber-9" />
</template>
Tam eşleşme bulunamadı. Benzer kodlara (prefix) ait geçmiş fiyatlar gösteriliyor.
</q-banner>
<div v-if="lineHistoryCanFetchSimilar || lineHistoryCanFetchAlternative" class="row q-col-gutter-sm q-mb-md">
<div v-if="lineHistorySearchMode !== 'exact'" class="col-auto">
<q-btn
label="Geri (Tam Eslesme)"
icon="arrow_back"
color="grey-7"
outline
no-caps
@click="fetchSimilarItemHistory('exact')"
/>
</div>
<div v-if="lineHistoryCanFetchSimilar" class="col-auto">
<q-btn
label="Benzer Kodları Göster"
icon="travel_explore"
color="secondary"
outline
no-caps
:loading="lineHistoryLoading && lineHistorySearchMode === 'prefix'"
@click="fetchSimilarItemHistory('prefix')"
/>
</div>
<div v-if="lineHistoryCanFetchAlternative" class="col-auto">
<q-btn
label="Diğer Alternatifleri Göster"
icon="alt_route"
color="primary"
outline
no-caps
:loading="lineHistoryLoading && lineHistorySearchMode === 'alternative'"
@click="fetchSimilarItemHistory('alternative')"
/>
</div>
</div>
<div v-if="lineHistoryLoading" class="row justify-center q-pa-lg">
<q-spinner color="primary" size="32px" />
</div>
<div v-else-if="lineHistoryRows.length === 0" class="column items-center q-pa-xl">
<q-icon name="history" size="64px" color="grey-4" />
<div class="text-grey-7 q-mt-md text-center">
Bu kalem için geçmiş hareket bulunamadı.
<div v-if="lineHistoryTargetHammaddeTuruNo" class="q-mt-sm">
<q-btn
label="Hammadde Türü Bazında Benzer Ürünleri Göster"
icon="travel_explore"
color="secondary"
outline
no-caps
:loading="lineHistoryLoading"
@click="fetchSimilarItemHistory('prefix')"
/>
</div>
</div>
</div>
<div v-else>
<q-table
class="pcd-history-table"
dense
flat
bordered
row-key="__historyKey"
:rows="lineHistoryRows"
:columns="lineHistoryColumns"
:table-row-class-fn="resolveLineHistoryRowClass"
:rows-per-page-options="[0]"
hide-bottom
>
<template v-slot:body-cell-sourceLabel="props">
<q-td :props="props">
<span class="pcd-history-source-chip" :class="`pcd-history-source-chip--${String(props.row.sourceType || '').toLowerCase()}`">
{{ props.row.sourceLabel }}
</span>
</q-td>
</template>
<template v-slot:body-cell-price="props">
<q-td :props="props" class="text-right">
{{ formatMoney(props.row.price) }}
</q-td>
</template>
<template v-slot:body-cell-quantity="props">
<q-td :props="props" class="text-right">
{{ formatMoney(props.row.quantity) }}
</q-td>
</template>
<template v-slot:body-cell-amount="props">
<q-td :props="props" class="text-right">
{{ formatMoney(props.row.amount) }}
</q-td>
</template>
<template v-slot:body-cell-select="props">
<q-td :props="props" class="text-right">
<q-btn
label="Sec"
color="primary"
dense
unelevated
@click="applyLineHistorySelection(props.row)"
/>
</q-td>
</template>
</q-table>
</div>
</q-card-section>
</q-card>
</q-dialog>
</template>
<script setup>
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { useQuasar } from 'quasar'
import { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router'
import { usePermission } from 'src/composables/usePermission'
import { get, post, extractApiErrorDetail } from 'src/services/api'
import { createTraceId, slog } from 'src/utils/slog'
const route = useRoute()
const router = useRouter()
const $q = useQuasar()
const { canRead } = usePermission()
const canReadOrder = canRead('order')
const detailLoading = ref(false)
const detailError = ref('')
const detailGroups = ref([])
const detailHeader = ref(null)
const costDate = ref('')
const exchangeRates = ref(createEmptyExchangeRates())
const deletedDetailRows = ref([])
const localDraftTimer = ref(0)
const initialHeaderSnapshot = ref('')
const saveLoading = ref(false)
const bulkPriceLoading = ref(false)
const newRowSequence = ref(0)
const ROW_EDITOR_OPTIONS_LIMIT = 100
const ROW_EDITOR_ITEM_MIN_SEARCH_LENGTH = 2
const LINE_HISTORY_ROW_LIMIT = 500
const LINE_HISTORY_COMBINED_ROW_LIMIT = LINE_HISTORY_ROW_LIMIT * 2
const rowEditorDialogOpen = ref(false)
const rowEditorMode = ref('new')
const rowEditorTargetRowKey = ref('')
const rowEditorForm = ref(createRowEditorForm())
const rowEditorHammaddeOptions = ref([])
const rowEditorHammaddeAllOptions = ref([])
const rowEditorHammaddeLoading = ref(false)
const rowEditorItemOptions = ref([])
const rowEditorItemAllOptions = ref([])
const rowEditorItemLoading = ref(false)
const rowEditorColorOptions = ref([])
const rowEditorColorAllOptions = ref([])
const rowEditorColorLoading = ref(false)
const lineHistoryDialogOpen = ref(false)
const lineHistoryLoading = ref(false)
const lineHistoryError = ref('')
const lineHistoryRows = ref([])
const lineHistoryTargetRowKey = ref('')
const lineHistoryTargetHammaddeTuruNo = ref('')
const lineHistoryTargetItemCode = ref('')
const lineHistoryTargetSummary = ref('')
const lineHistorySearchMode = ref('exact')
const lineHistoryLastPurchaseMatchStage = ref('')
const lineHistoryLastRecipeMatchStage = ref('')
const headerInfoCollapsed = ref(false)
const subHeaderTop = ref(140)
const stickyStackRef = ref(null)
const saveToolbarRef = ref(null)
const groupOpenState = ref({})
const productionTypes = ref([])
const onMLNo = computed(() => String(route.query?.n_onml_no || '').trim())
const productCode = computed(() => String(route.query?.urun_kodu || '').trim())
const recipeCode = computed(() => String(route.query?.recete_kodu || '').trim())
const detailSource = computed(() => String(route.query?.detail_source || '').trim().toLowerCase())
const isNoCostDetail = computed(() => detailSource.value === 'no-cost')
const generatedTraceId = ref(createTraceId('pcd-detail'))
const traceId = computed(() => String(route.query?.trace_id || generatedTraceId.value).trim())
const pageMode = computed(() => (isNoCostDetail.value ? 'new' : 'edit'))
const pageVars = computed(() => ({
'--pcd-subheader-top': `${subHeaderTop.value}px`
}))
const lineHistoryCanFetchSimilar = computed(() => (
Boolean(lineHistoryTargetItemCode.value) &&
lineHistorySearchMode.value !== 'prefix'
))
const lineHistoryCanFetchAlternative = computed(() => (
Boolean(lineHistoryTargetHammaddeTuruNo.value) &&
lineHistorySearchMode.value !== 'alternative'
))
const lineHistoryShowingFallback = computed(() => (
lineHistorySearchMode.value === 'exact' &&
lineHistoryRows.value.some(r => r.priceType === 'BNZ')
))
const priceCurrencyOptions = [
{ label: 'USD', value: 'USD' },
{ label: 'TRY', value: 'TRY' },
{ label: 'EUR', value: 'EUR' },
{ label: 'GBP', value: 'GBP' }
]
const flatDetailRows = computed(() => detailGroups.value.flatMap(grp => Array.isArray(grp?.items) ? grp.items : []))
// no-cost: required parca slots (from Maliyet Parca Eslestirme)
const requiredParcaMappings = ref([])
const requiredAttentionRowKeys = ref({})
const draftStorageKey = computed(() => {
if (isNoCostDetail.value) {
if (!recipeCode.value) return ''
return `pcd-costing:no-cost:${String(productCode.value || '').trim()}:${String(recipeCode.value || '').trim()}`
}
if (!onMLNo.value) return ''
return `pcd-costing:has-cost:${String(onMLNo.value).trim()}`
})
const currentHeaderSnapshot = computed(() => JSON.stringify({
UretimSekliID: String(detailHeader.value?.UretimSekliID || '').trim(),
UretimSekli: String(detailHeader.value?.UretimSekli || '').trim()
}))
const headerHasUnsavedChanges = computed(() => {
if (!initialHeaderSnapshot.value) return false
return currentHeaderSnapshot.value !== initialHeaderSnapshot.value
})
const hasUnsavedChanges = computed(() => {
if (deletedDetailRows.value.length > 0) return true
if (headerHasUnsavedChanges.value) return true
return flatDetailRows.value.some(row => Boolean(row?.draftChanged))
})
function persistLocalDraftNow () {
const key = draftStorageKey.value
if (!key) return
try {
const payload = {
version: 1,
savedAt: new Date().toISOString(),
mode: pageMode.value,
detail_source: detailSource.value || 'has-cost',
n_onml_no: onMLNo.value,
urun_kodu: productCode.value,
recete_kodu: recipeCode.value,
header: detailHeader.value ? {
UretimSekliID: String(detailHeader.value?.UretimSekliID || '').trim(),
UretimSekli: String(detailHeader.value?.UretimSekli || '').trim()
} : null,
deletedDetailRows: Array.isArray(deletedDetailRows.value) ? deletedDetailRows.value : [],
detailGroups: Array.isArray(detailGroups.value) ? detailGroups.value : []
}
localStorage.setItem(key, JSON.stringify(payload))
} catch (err) {
slog.error('production-product-costing.detail', 'draft:persist:error', {
trace_id: traceId.value,
key: draftStorageKey.value,
error: String(err?.message || err)
})
}
}
function schedulePersistLocalDraft () {
try {
if (localDraftTimer.value) window.clearTimeout(localDraftTimer.value)
localDraftTimer.value = window.setTimeout(() => {
localDraftTimer.value = 0
persistLocalDraftNow()
}, 400)
} catch {
persistLocalDraftNow()
}
}
function clearLocalDraft () {
const key = draftStorageKey.value
if (!key) return
try {
localStorage.removeItem(key)
} catch {
// ignore
}
}
function tryHydrateFromLocalDraft () {
const key = draftStorageKey.value
if (!key) return false
try {
const raw = localStorage.getItem(key)
if (!raw) return false
const payload = JSON.parse(raw)
if (!payload || typeof payload !== 'object') return false
if (Array.isArray(payload.deletedDetailRows)) deletedDetailRows.value = payload.deletedDetailRows
if (Array.isArray(payload.detailGroups)) detailGroups.value = normalizeDetailGroups(payload.detailGroups)
if (payload.header && detailHeader.value) {
detailHeader.value.UretimSekliID = String(payload.header.UretimSekliID || '').trim()
detailHeader.value.UretimSekli = String(payload.header.UretimSekli || '').trim()
}
return true
} catch {
return false
}
}
function ensureBeforeUnloadGuard (enabled) {
if (!enabled) {
window.onbeforeunload = null
return
}
window.onbeforeunload = (e) => {
e.preventDefault()
e.returnValue = ''
return ''
}
}
const toolbarSummary = computed(() => flatDetailRows.value.reduce((acc, row) => {
if (!row?.maliyeteDahil) return acc
acc.tryTotal += resolveRowTRYTutar(row)
acc.usdTotal += resolveRowUSDTutar(row)
acc.eurTotal += resolveRowEURTutar(row)
acc.gbpTotal += resolveRowGBPTutar(row)
return acc
}, {
tryTotal: 0,
usdTotal: 0,
eurTotal: 0,
gbpTotal: 0
}))
const partSummary = computed(() => {
const summary = {}
flatDetailRows.value.forEach(row => {
if (!row?.maliyeteDahil) return
const part = String(row?.sParcaAdi || 'TANIMSIZ').trim() || 'TANIMSIZ'
if (!summary[part]) {
summary[part] = { try: 0, usd: 0, eur: 0, gbp: 0 }
}
summary[part].try += resolveRowTRYTutar(row)
summary[part].usd += resolveRowUSDTutar(row)
summary[part].eur += resolveRowEURTutar(row)
summary[part].gbp += resolveRowGBPTutar(row)
})
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%' }
]
function resolveLineHistoryRowClass (row) {
if (row?.priceType === 'BNZ') return 'pcd-history-row-similar'
if (row?.sourceType === 'recipe') return 'pcd-history-row-recipe'
if (row?.sourceType === 'purchase') return 'pcd-history-row-purchase'
return ''
}
const detailColumns = [
{ name: 'actions', label: '', field: 'actions', align: 'left', style: 'width:60px', headerStyle: 'width:60px' },
{ name: 'nOnMLDetNo', label: 'No', field: 'nOnMLDetNo', align: 'left', sortable: true },
{ name: 'sParcaAdi', label: 'Parça Adı', field: 'sParcaAdi', align: 'left', sortable: true },
{ name: 'nHammaddeTuruNo', label: 'Hammadde Türü', field: 'nHammaddeTuruNo', align: 'left', sortable: true },
{ name: 'sKodu', label: 'Kod', field: 'sKodu', align: 'left', sortable: true },
{ name: 'sAciklama', label: 'Açıklama', field: 'sAciklama', align: 'left', sortable: true },
{ 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: '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) },
{ name: 'lTutar', label: 'lTutar', field: 'lTutar', align: 'right', sortable: true, format: val => formatMoney(val) },
{ name: 'sFiyatTipi', label: 'sFiyatTipi', field: 'sFiyatTipi', align: 'left', sortable: true },
{ name: 'sDovizCinsi', label: 'Döviz', field: 'sDovizCinsi', align: 'left', sortable: true },
{ name: 'lDovizKuru', label: 'Kur', field: 'lDovizKuru', align: 'right', sortable: true, format: val => formatMoney(val) },
{ name: 'lDovizFiyati', label: 'Döviz Fiyatı', field: 'lDovizFiyati', align: 'right', sortable: true, format: val => formatMoney(val) },
{ name: 'priceUpdateState', label: 'Fiyat Tipi', field: 'priceUpdateState', align: 'left', sortable: true },
{
name: 'usdTutar',
label: 'USD TUTAR',
field: row => resolveRowUSDTutar(row),
align: 'right',
sortable: true,
format: val => formatMoney(val)
},
{ name: 'sBirim', label: 'Birim', field: 'sBirim', align: 'left', sortable: true }
]
function formatDateTR (value) {
const s = String(value || '').trim()
if (!s) return ''
const m = /^(\d{4})-(\d{2})-(\d{2})(?:\s+(\d{2}):(\d{2}))?/.exec(s)
if (!m) return s
const datePart = `${m[3]}.${m[2]}.${m[1]}`
if (m[4] && m[5]) return `${datePart} ${m[4]}:${m[5]}`
return datePart
}
function formatUretimSekli (header) {
const id = String(header?.UretimSekliID || '').trim()
const aciklama = String(header?.UretimSekli || '').trim()
if (id && aciklama) return `${id}-${aciklama}`
return id || aciklama || '-'
}
function normalizeDateInput (value) {
const s = String(value || '').trim()
const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(s)
if (!m) return ''
return `${m[1]}-${m[2]}-${m[3]}`
}
function isCMGroupName (value) {
const normalizedValue = String(value || '').trim().toUpperCase()
return normalizedValue.includes('CM1') || normalizedValue.includes('CM2')
}
function createEmptyExchangeRates () {
return {
rateDate: '',
tryRate: 1,
usdRate: 0,
eurRate: 0,
gbpRate: 0
}
}
function createRowEditorForm (seed = {}) {
const defaultCurrency = normalizePriceCurrency(seed?.inputPricePrBr || seed?.fiyat_doviz || detailHeader.value?.sDovizCinsi) || 'USD'
const cmPriceTypeId = normalizeCMPriceTypeId(seed?.cmPriceTypeId ?? seed?.cm_price_type_id, seed?.sAciklama3 ?? seed?.sParcaAdi)
return {
__rowKey: String(seed?.__rowKey || '').trim(),
isNew: Boolean(seed?.isNew),
nStokID: String(seed?.nStokID || '').trim(),
sModel: String(seed?.sModel || '').trim(),
nOnMLDetNo: String(seed?.nOnMLDetNo || '').trim(),
sParcaAdi: String(seed?.sParcaAdi || seed?.sAciklama3 || '').trim(),
nHammaddeTuruNo: String(seed?.nHammaddeTuruNo || '').trim(),
sHammaddeTuruAdi: String(seed?.sHammaddeTuruAdi || '').trim(),
sAciklama3: String(seed?.sAciklama3 || seed?.sParcaAdi || '').trim(),
sKodu: String(seed?.sKodu || '').trim(),
sAciklama: String(seed?.sAciklama || '').trim(),
sRenk: String(seed?.sRenk || seed?.ColorCode || '').trim(),
ColorCode: String(seed?.ColorCode || seed?.sRenk || '').trim(),
ColorDescription: String(seed?.ColorDescription || '').trim(),
miktarInput: normalizeQuantityInput(seed?.miktarInput ?? seed?.lMiktar ?? 1),
inputPrice: normalizeInputPrice(seed?.inputPrice ?? seed?.fiyat_girilen ?? 0),
inputPricePrBr: defaultCurrency,
maliyeteDahil: seed?.maliyeteDahil ?? normalizeBooleanFlag(seed?.maliyete_dahil ?? true),
cmPriceTypeChecked: cmPriceTypeId === 2,
sBirim: extractPrimaryUnitValue(seed?.sBirim || 'AD') || 'AD'
}
}
function formatMoney (value) {
return parseMoneyInput(value).toLocaleString('tr-TR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})
}
function formatQuantity (value) {
return parseMoneyInput(value).toLocaleString('tr-TR', {
minimumFractionDigits: 4,
maximumFractionDigits: 4
})
}
function formatBarMoney (value) {
const roundedValue = Math.round((Number(value || 0) + Number.EPSILON) * 100) / 100
return formatMoney(roundedValue)
}
function normalizePriceCurrency (value) {
const normalizedValue = String(value || '').trim().toUpperCase()
return ['USD', 'TRY', 'EUR', 'GBP'].includes(normalizedValue) ? normalizedValue : ''
}
function normalizeCodeValue (value) {
return String(value ?? '').trim().toUpperCase()
}
function extractPrimaryUnitValue (value) {
const normalizedValue = String(value || '').trim()
if (!normalizedValue) return ''
return normalizedValue.split(/[\s,;|/]+/).filter(Boolean)[0] || normalizedValue
}
function normalizeInputPrice (value) {
const numericValue = parseMoneyInput(value)
if (value === null || value === undefined || value === '' || !Number.isFinite(numericValue)) {
return formatMoney(0)
}
return formatMoney(numericValue)
}
function normalizeQuantityInput (value) {
const numericValue = parseMoneyInput(value)
if (value === null || value === undefined || value === '' || !Number.isFinite(numericValue)) {
return formatQuantity(0)
}
return formatQuantity(numericValue)
}
function normalizeSingleSeparatorNumber (rawValue, separator) {
const parts = String(rawValue || '').split(separator)
if (parts.length <= 1) return String(rawValue || '')
if (parts.length === 2) {
return separator === ',' ? `${parts[0]}.${parts[1]}` : `${parts[0]}.${parts[1]}`
}
const headParts = parts.slice(0, -1)
const lastPart = parts[parts.length - 1]
const looksLikeGroupedInteger = lastPart.length === 3 && headParts.every((part, index) => {
if (index === 0) return part.length >= 1 && part.length <= 3
return part.length === 3
})
if (looksLikeGroupedInteger) {
return parts.join('')
}
return `${headParts.join('')}.${lastPart}`
}
function parseMoneyInput (value) {
const rawValue = String(value ?? '').trim()
if (!rawValue) return 0
const compactValue = rawValue.replace(/\s+/g, '')
const lastDotIndex = compactValue.lastIndexOf('.')
const lastCommaIndex = compactValue.lastIndexOf(',')
const hasDot = lastDotIndex >= 0
const hasComma = lastCommaIndex >= 0
let normalizedValue = compactValue
if (hasDot && hasComma) {
const decimalSeparator = lastDotIndex > lastCommaIndex ? '.' : ','
const thousandSeparator = decimalSeparator === '.' ? ',' : '.'
normalizedValue = compactValue.replace(new RegExp(`\\${thousandSeparator}`, 'g'), '')
if (decimalSeparator === ',') {
normalizedValue = normalizedValue.replace(',', '.')
}
} else if (hasComma) {
normalizedValue = normalizeSingleSeparatorNumber(compactValue, ',')
} else if (hasDot) {
normalizedValue = normalizeSingleSeparatorNumber(compactValue, '.')
}
const numericValue = Number(normalizedValue)
return Number.isFinite(numericValue) ? numericValue : 0
}
function normalizeBooleanFlag (value) {
if (typeof value === 'boolean') return value
if (typeof value === 'number') return value === 1
const normalizedValue = String(value ?? '').trim().toLowerCase()
return normalizedValue === '1' || normalizedValue === 'true' || normalizedValue === 'evet'
}
function normalizeCMPriceTypeId (value, groupName) {
if (!isCMGroupName(groupName)) return null
if (value === null || value === undefined || value === '') return 1
const numericValue = Number(value)
if (numericValue === 2) return 2
return 1
}
function resolveCMPriceTypeChecked (row) {
return normalizeCMPriceTypeId(row?.cmPriceTypeId ?? row?.cm_price_type_id, row?.sAciklama3) === 2
}
function firstDefinedValue (source, keys) {
for (const key of keys) {
const value = source?.[key]
if (value !== undefined && value !== null && String(value).trim() !== '') {
return value
}
}
return ''
}
function normalizeExchangeRatesPayload (payload, fallbackDate = '') {
return {
rateDate: normalizeDateInput(firstDefinedValue(payload, ['rateDate', 'RateDate', 'date', 'Date'])) || fallbackDate,
tryRate: 1,
usdRate: parseMoneyInput(firstDefinedValue(payload, ['usdRate', 'USDRate', 'usd', 'USD'])),
eurRate: parseMoneyInput(firstDefinedValue(payload, ['eurRate', 'EURRate', 'eur', 'EUR'])),
gbpRate: parseMoneyInput(firstDefinedValue(payload, ['gbpRate', 'GBPRate', 'gbp', 'GBP']))
}
}
function buildRowEditorHammaddeOption (source) {
const value = String(source?.value || source?.nHammaddeTuruNo || '').trim()
const name = String(source?.sHammaddeTuruAdi || '').trim()
const groupName = String(source?.sAciklama3 || '').trim()
return {
kind: 'hammadde',
value,
label: String(source?.label || `${value}${name ? ` - ${name}` : ''}`).trim(),
nHammaddeTuruNo: value,
sHammaddeTuruAdi: name,
sAciklama3: groupName,
sParcaAdi: String(source?.sParcaAdi || groupName).trim()
}
}
function buildRowEditorItemOption (source) {
const value = String(source?.value || source?.sKodu || '').trim()
const desc = String(source?.sAciklama || '').trim()
return {
kind: 'item',
value,
nStokID: String(source?.nStokID || '').trim(),
sModel: String(source?.sModel || '').trim(),
label: String(source?.label || `${value}${desc ? ` - ${desc}` : ''}`).trim(),
sKodu: value,
sAciklama: desc,
sBirim: extractPrimaryUnitValue(source?.sBirim)
}
}
function buildRowEditorColorOption (source) {
const value = String(source?.value || source?.colorCode || source?.ColorCode || source?.sRenk || '').trim()
const desc = String(source?.colorDescription || source?.ColorDescription || '').trim()
return {
kind: 'color',
value,
label: String(source?.label || `${value}${desc ? ` - ${desc}` : ''}`).trim(),
colorCode: value,
colorDescription: desc
}
}
function upsertEditorOption (optionsRef, option) {
if (!option?.value) return
const nextOptions = [...optionsRef.value]
const idx = nextOptions.findIndex(x => String(x?.value || '').trim() === option.value)
if (idx >= 0) {
nextOptions[idx] = { ...nextOptions[idx], ...option }
} else {
nextOptions.unshift(option)
}
optionsRef.value = nextOptions
}
async function fetchRowEditorOptions (kind, search = '', extraParams = {}) {
const response = await get('/pricing/production-product-costing/detail-editor-options', {
kind,
search,
limit: ROW_EDITOR_OPTIONS_LIMIT,
trace_id: traceId.value,
...extraParams
})
return Array.isArray(response) ? response : []
}
function setRowEditorOptionsByKind (kind, rows) {
if (kind === 'hammadde') {
rowEditorHammaddeAllOptions.value = rows
rowEditorHammaddeOptions.value = rows
return
}
if (kind === 'item') {
rowEditorItemAllOptions.value = rows
rowEditorItemOptions.value = rows
return
}
rowEditorColorAllOptions.value = rows
rowEditorColorOptions.value = rows
}
function setRowEditorLookupLoading (kind, isLoading) {
if (kind === 'hammadde') {
rowEditorHammaddeLoading.value = isLoading
return
}
if (kind === 'item') {
rowEditorItemLoading.value = isLoading
return
}
rowEditorColorLoading.value = isLoading
}
async function refreshRowEditorOptions (kind, search = '') {
const extraParams = kind === 'color'
? { model_code: String(rowEditorForm.value.sModel || '').trim() }
: {}
if (kind === 'color' && !extraParams.model_code) {
setRowEditorOptionsByKind(kind, [])
primeRowEditorOptionsFromForm()
return []
}
setRowEditorLookupLoading(kind, true)
try {
const rows = await fetchRowEditorOptions(kind, search, extraParams)
setRowEditorOptionsByKind(kind, rows)
primeRowEditorOptionsFromForm()
return rows
} finally {
setRowEditorLookupLoading(kind, false)
}
}
async function filterRowEditorOptions (kind, val, update, abort) {
const normalizedSearch = String(val || '').trim()
if (kind === 'item' && normalizedSearch.length < ROW_EDITOR_ITEM_MIN_SEARCH_LENGTH) {
update(() => {
rowEditorItemAllOptions.value = []
rowEditorItemOptions.value = []
primeRowEditorOptionsFromForm()
})
return
}
try {
const rows = await refreshRowEditorOptions(kind, normalizedSearch)
update(() => {
setRowEditorOptionsByKind(kind, rows)
primeRowEditorOptionsFromForm()
})
} catch (err) {
abort()
$q.notify({
type: 'negative',
message: `Lookup getirilemedi: ${await extractApiErrorDetail(err)}`,
position: 'top-right'
})
}
}
function filterRowEditorHammaddeOptions (val, update, abort) {
return filterRowEditorOptions('hammadde', val, update, abort)
}
function filterRowEditorItemOptions (val, update, abort) {
return filterRowEditorOptions('item', val, update, abort)
}
function filterRowEditorColorOptions (val, update, abort) {
const modelCode = String(rowEditorForm.value.sModel || '').trim()
if (!modelCode) {
update(() => {
rowEditorColorAllOptions.value = []
rowEditorColorOptions.value = []
})
return
}
return filterRowEditorOptions('color', val, update, abort)
}
async function loadRowEditorColorOptions () {
const modelCode = String(rowEditorForm.value.sModel || '').trim()
if (!modelCode) return []
try {
return await fetchRowEditorOptions('color', '', { model_code: modelCode })
} catch (err) {
$q.notify({
type: 'negative',
message: `Renk lookup getirilemedi: ${await extractApiErrorDetail(err)}`,
position: 'top-right'
})
return []
}
}
function primeRowEditorOptionsFromForm () {
upsertEditorOption(rowEditorHammaddeAllOptions, buildRowEditorHammaddeOption(rowEditorForm.value))
upsertEditorOption(rowEditorHammaddeOptions, buildRowEditorHammaddeOption(rowEditorForm.value))
upsertEditorOption(rowEditorItemAllOptions, buildRowEditorItemOption(rowEditorForm.value))
upsertEditorOption(rowEditorItemOptions, buildRowEditorItemOption(rowEditorForm.value))
upsertEditorOption(rowEditorColorAllOptions, buildRowEditorColorOption(rowEditorForm.value))
upsertEditorOption(rowEditorColorOptions, buildRowEditorColorOption(rowEditorForm.value))
}
function getNextDetailNo () {
const maxNo = flatDetailRows.value.reduce((acc, row) => {
const rowNo = parseInt(String(row?.nOnMLDetNo || '').trim(), 10)
return Number.isFinite(rowNo) ? Math.max(acc, rowNo) : acc
}, 0)
return String(maxNo + 1)
}
function resolveRowColorCode (row) {
return normalizeCodeValue(firstDefinedValue(row, [
'ColorCode',
'colorCode',
'sRenk',
's_renk',
'renk'
]))
}
function resolveRowItemDim1Code (row) {
return normalizeCodeValue(firstDefinedValue(row, [
'ItemDim1Code',
'itemDim1Code',
'sBeden',
's_beden'
]))
}
function extractNamedArray (payload, keys) {
for (const key of keys) {
if (Array.isArray(payload?.[key])) return payload[key]
if (Array.isArray(payload?.data?.[key])) return payload.data[key]
}
return []
}
function parseDateSortValue (value) {
const rawValue = String(value || '').trim()
if (!rawValue) return 0
const normalizedValue = rawValue.replace(' ', 'T')
const timeValue = Date.parse(normalizedValue)
return Number.isFinite(timeValue) ? timeValue : 0
}
function sortHistoryRowsByDateDesc (rows) {
return [...rows].sort((a, b) => parseDateSortValue(b?.dateValue) - parseDateSortValue(a?.dateValue))
}
function resolveHistorySourceType (item, forcedType = '') {
const normalizedType = String(forcedType || firstDefinedValue(item, [
'sourceType',
'source',
'kaynak',
'historyType',
'tip'
])).trim().toLowerCase()
if (['recipe', 'recete', 'uretim'].includes(normalizedType)) return 'recipe'
if (['purchase', 'invoice', 'satinalma', 'baggi_v3', 'baggi-v3'].includes(normalizedType)) return 'purchase'
if (firstDefinedValue(item, ['MasrafKodu', 'FaturaKodu', 'EvrakFiyat', 'ItemDim1Code']) !== '') return 'purchase'
if (firstDefinedValue(item, ['nOnMLNo', 'dteIslemTarihi', 'lDovizFiyati', 'lDovizTutari']) !== '') return 'recipe'
return normalizedType || 'history'
}
function resolveHistorySourceLabel (sourceType) {
if (sourceType === 'purchase') return 'BAGGI_V3'
if (sourceType === 'recipe') return 'URETIM'
return 'HISTORY'
}
function normalizeHistoryRowFromItem (item, index, forcedType = '') {
const sourceType = resolveHistorySourceType(item, forcedType)
const price = parseMoneyInput(firstDefinedValue(item, [
'EvrakFiyat',
'evrakFiyat',
'fiyat_girilen',
'fiyatGirilen',
'price',
'fiyat',
'birimFiyat',
'unitPrice',
'lDovizFiyati',
'lFiyat'
]))
const quantity = parseMoneyInput(firstDefinedValue(item, [
'Miktar',
'miktar',
'quantity',
'qty',
'adet',
'lMiktar'
]))
const amountValue = firstDefinedValue(item, [
'EvrakTutar',
'evrakTutar',
'tutar',
'amount',
'toplamTutar',
'lTutar',
'lDovizTutari'
])
const currency = normalizePriceCurrency(firstDefinedValue(item, [
'EvrakDoviz',
'evrakDoviz',
'fiyat_doviz',
'fiyatDoviz',
'currency',
'priceCurrency',
'prBr',
'pb',
'sDovizCinsi',
'USD'
]))
const dateValue = String(firstDefinedValue(item, [
'Tarih',
'tarih',
'InvoiceDate',
'invoiceDate',
'dteIslemTarihi',
'islemTarihi',
'dteKayitTarihi',
'date'
])).trim()
const amount = amountValue === ''
? price * quantity
: parseMoneyInput(amountValue)
return {
__historyKey: String(firstDefinedValue(item, ['id', '__historyKey', 'historyKey'])).trim() || `${sourceType}-${index}`,
raw: item,
sourceType,
sourceLabel: resolveHistorySourceLabel(sourceType),
dateValue,
dateLabel: formatDateTR(dateValue),
invoiceCode: String(firstDefinedValue(item, [
'FaturaKodu',
'faturaKodu',
'InvoiceNumber',
'invoiceCode',
'FaturaNo',
'nOnMLNo',
'n_onml_no'
])).trim(),
companyCode: String(firstDefinedValue(item, [
'FirmaKodu',
'firmaKodu',
'CurrAccCode',
'currAccCode',
'accountCode',
'vendorCode'
])).trim(),
companyDescription: String(firstDefinedValue(item, [
'FirmaAciklama',
'firmaAciklama',
'CurrAccDescription',
'currAccDescription',
'companyDescription',
'vendorDescription'
])).trim(),
itemCode: String(firstDefinedValue(item, [
'MasrafKodu',
'masrafKodu',
'sKodu',
's_kodu',
'ItemCode',
'itemCode'
])).trim(),
itemDescription: String(firstDefinedValue(item, [
'MasrafDetay',
'masrafDetay',
'sAciklama',
's_aciklama',
'ItemDescription',
'itemDescription',
'description'
])).trim(),
colorCode: String(firstDefinedValue(item, [
'ColorCode',
'colorCode',
'sRenk',
's_renk'
])).trim(),
colorDescription: String(firstDefinedValue(item, [
'ColorDescription',
'colorDescription',
'sRenkAdi',
's_renk_adi'
])).trim(),
itemDim1Code: String(firstDefinedValue(item, [
'ItemDim1Code',
'itemDim1Code',
'sBeden',
's_beden'
])).trim(),
itemDim1Description: String(firstDefinedValue(item, [
'ItemDim1Description',
'itemDim1Description',
'sAciklama2',
's_aciklama2'
])).trim(),
priceType: String(item?.priceType || '').trim(),
quantity,
unit: String(firstDefinedValue(item, [
'BIRIM',
'birim',
'unit',
'Unit',
'sBirim',
's_birim'
])).trim(),
price,
amount,
currency: currency || 'USD',
recipeInfo: String(firstDefinedValue(item, [
'recipeInfo',
'receteBilgisi',
'recete',
'DUMMY',
'nUrtReceteID',
'sAciklama3',
'Description',
'Aciklama',
'Aciklama1',
'LineDescription',
'note'
])).trim()
}
}
function normalizeBulkPriceItems (payload) {
const list = Array.isArray(payload)
? payload
: extractNamedArray(payload, ['items', 'data', 'updates', 'matchedItems', 'rows'])
return list.map((item, index) => {
const price = parseMoneyInput(firstDefinedValue(item, [
'EvrakFiyat',
'evrakFiyat',
'fiyat_girilen',
'fiyatGirilen',
'price',
'fiyat',
'unitPrice',
'birimFiyat',
'lDovizFiyati',
'lFiyat'
]))
const currency = normalizePriceCurrency(firstDefinedValue(item, [
'EvrakDoviz',
'evrakDoviz',
'fiyat_doviz',
'fiyatDoviz',
'currency',
'priceCurrency',
'prBr',
'pb',
'sDovizCinsi'
]))
return {
__updateKey: `upd-${index}`,
__rowKey: String(firstDefinedValue(item, ['__rowKey', 'rowKey'])).trim(),
nOnMLDetNo: String(firstDefinedValue(item, ['nOnMLDetNo', 'n_onml_det_no'])).trim(),
nHammaddeTuruNo: String(firstDefinedValue(item, ['nHammaddeTuruNo', 'n_hammadde_turu_no'])).trim(),
sKodu: normalizeCodeValue(firstDefinedValue(item, ['sKodu', 's_kodu', 'stokKodu', 'MasrafKodu', 'masrafKodu', 'ItemCode', 'itemCode'])),
colorCode: normalizeCodeValue(firstDefinedValue(item, ['ColorCode', 'colorCode', 'sRenk', 's_renk', 'renk'])),
itemDim1Code: normalizeCodeValue(firstDefinedValue(item, ['ItemDim1Code', 'itemDim1Code', 'item_dim1_code', 'sBeden', 's_beden'])),
inputPrice: normalizeInputPrice(price),
fiyat_girilen: price,
inputPricePrBr: currency || 'USD',
fiyat_doviz: currency || 'USD'
}
})
}
function normalizeLineHistoryRows (payload) {
const purchaseRows = extractNamedArray(payload, [
'purchaseRows',
'purchase_items',
'purchaseItems',
'invoiceRows',
'invoice_items',
'invoiceItems',
'baggiV3Rows',
'baggi_v3_rows',
'baggiRows'
])
const recipeRows = extractNamedArray(payload, [
'recipeRows',
'recipe_items',
'recipeItems',
'uretimRows',
'uretim_rows',
'productionRows'
])
if (purchaseRows.length > 0 || recipeRows.length > 0) {
const normalizedPurchaseRows = sortHistoryRowsByDateDesc(
purchaseRows.map((item, index) => normalizeHistoryRowFromItem(item, index, 'purchase'))
)
const normalizedRecipeRows = sortHistoryRowsByDateDesc(
recipeRows.map((item, index) => normalizeHistoryRowFromItem(item, index, 'recipe'))
)
return sortHistoryRowsByDateDesc([
...normalizedPurchaseRows,
...normalizedRecipeRows
]).slice(0, LINE_HISTORY_COMBINED_ROW_LIMIT)
}
const flatList = Array.isArray(payload)
? payload
: extractNamedArray(payload, ['items', 'data', 'rows'])
const normalizedRows = flatList.map((item, index) => normalizeHistoryRowFromItem(item, index))
const purchaseHistoryRows = sortHistoryRowsByDateDesc(normalizedRows.filter(row => row.sourceType === 'purchase'))
const recipeHistoryRows = sortHistoryRowsByDateDesc(normalizedRows.filter(row => row.sourceType === 'recipe'))
const otherHistoryRows = sortHistoryRowsByDateDesc(normalizedRows.filter(row => !['purchase', 'recipe'].includes(row.sourceType)))
return sortHistoryRowsByDateDesc([
...purchaseHistoryRows,
...recipeHistoryRows,
...otherHistoryRows
]).slice(0, LINE_HISTORY_COMBINED_ROW_LIMIT)
}
function rowMatchesBulkUpdate (row, update) {
if (update.__rowKey && update.__rowKey === row.__rowKey) return true
if (update.nOnMLDetNo && String(row?.nOnMLDetNo || '').trim() === update.nOnMLDetNo) return true
const rowKodu = normalizeCodeValue(row?.sKodu)
const rowRenk = resolveRowColorCode(row)
const rowTur = String(row?.nHammaddeTuruNo || '').trim()
const rowItemDim1Code = resolveRowItemDim1Code(row)
if (update.nHammaddeTuruNo && update.sKodu) {
if (rowTur === update.nHammaddeTuruNo && rowKodu === update.sKodu) return true
}
if (update.sKodu && update.colorCode && update.itemDim1Code) {
if (rowKodu === update.sKodu && rowRenk === update.colorCode && rowItemDim1Code === update.itemDim1Code) return true
}
if (update.sKodu && update.colorCode) {
if (rowKodu === update.sKodu && rowRenk === update.colorCode) return true
}
if (update.sKodu && update.itemDim1Code) {
if (rowKodu === update.sKodu && rowItemDim1Code === update.itemDim1Code) return true
}
if (update.sKodu) {
return rowKodu === update.sKodu
}
return false
}
function resolveExchangeRateValue (currency) {
const normalizedCurrency = normalizePriceCurrency(currency)
if (normalizedCurrency === 'TRY') return 1
if (normalizedCurrency === 'USD') return parseMoneyInput(exchangeRates.value?.usdRate)
if (normalizedCurrency === 'EUR') return parseMoneyInput(exchangeRates.value?.eurRate)
if (normalizedCurrency === 'GBP') return parseMoneyInput(exchangeRates.value?.gbpRate)
return 0
}
function resolveInputCurrency (row) {
return normalizePriceCurrency(row?.inputPricePrBr || row?.fiyat_doviz) || 'USD'
}
function resolveNumericRowQuantity (row) {
return parseMoneyInput(row?.miktarInput ?? row?.lMiktar)
}
function resolveNumericRowInputPrice (row) {
return parseMoneyInput(row?.inputPrice ?? row?.fiyat_girilen)
}
function resolveTRYUnitPriceByInput (inputPrice, inputCurrency, usdRate, eurRate, gbpRate) {
if (inputPrice <= 0) return 0
if (inputCurrency === 'TRY') return inputPrice
if (inputCurrency === 'USD') return usdRate > 0 ? inputPrice * usdRate : 0
if (inputCurrency === 'EUR') return eurRate > 0 ? inputPrice * eurRate : 0
if (inputCurrency === 'GBP') return gbpRate > 0 ? inputPrice * gbpRate : 0
return 0
}
function resolveUSDUnitPriceByInput (inputPrice, inputCurrency, usdRate, eurRate, gbpRate) {
if (inputPrice <= 0) return 0
if (inputCurrency === 'USD') return inputPrice
if (inputCurrency === 'TRY') return usdRate > 0 ? inputPrice / usdRate : 0
if (inputCurrency === 'EUR' || inputCurrency === 'GBP') {
const tryEquivalent = resolveTRYUnitPriceByInput(inputPrice, inputCurrency, usdRate, eurRate, gbpRate)
return usdRate > 0 ? tryEquivalent / usdRate : 0
}
return 0
}
function normalizeDetailRows (items, groupName = '') {
const list = Array.isArray(items) ? items : []
return list.map((x, i) => ({
...x,
__rowKey: x?.__rowKey || `${x?.nOnMLNo || ''}-${x?.nOnMLDetNo || ''}-${i}`,
miktarInput: x?.miktarInput ?? normalizeQuantityInput(x?.lMiktar),
inputPrice: x?.inputPrice ?? normalizeInputPrice(x?.fiyat_girilen),
inputPricePrBr: normalizePriceCurrency(x?.inputPricePrBr || x?.fiyat_doviz || x?.sDovizCinsi) || 'USD',
maliyeteDahil: x?.maliyeteDahil ?? normalizeBooleanFlag(x?.maliyete_dahil ?? x?.Maliyete_dahil),
cmPriceTypeId: normalizeCMPriceTypeId(x?.cmPriceTypeId ?? x?.cm_price_type_id, groupName || x?.sAciklama3),
draftChanged: Boolean(x?.draftChanged),
priceUpdateState: String(x?.priceUpdateState || '').trim()
}))
}
function normalizeDetailGroups (groups) {
const list = Array.isArray(groups) ? groups : []
return list.map(grp => {
const groupName = String(grp?.sAciklama3 || '').trim()
const items = normalizeDetailRows(grp?.items, groupName).map(row => ({
...row,
sAciklama3: String(grp?.sAciklama3 || row?.sAciklama3 || '').trim(),
cmPriceTypeId: normalizeCMPriceTypeId(row?.cmPriceTypeId ?? row?.cm_price_type_id, groupName || row?.sAciklama3)
}))
// USD TUTAR (DESC) sıralama
items.sort((a, b) => {
const valA = resolveRowUSDTutar(a)
const valB = resolveRowUSDTutar(b)
return valB - valA
})
return {
...grp,
items
}
})
}
function recalculateDetailRow (row, options = {}) {
const quantity = resolveNumericRowQuantity(row)
const inputPrice = resolveNumericRowInputPrice(row)
const inputCurrency = resolveInputCurrency(row)
const usdRate = resolveExchangeRateValue('USD')
const eurRate = resolveExchangeRateValue('EUR')
const gbpRate = resolveExchangeRateValue('GBP')
const unitTRY = resolveTRYUnitPriceByInput(inputPrice, inputCurrency, usdRate, eurRate, gbpRate)
const unitUSD = resolveUSDUnitPriceByInput(inputPrice, inputCurrency, usdRate, eurRate, gbpRate)
const unitEUR = eurRate > 0 ? unitTRY / eurRate : 0
const unitGBP = gbpRate > 0 ? unitTRY / gbpRate : 0
const maliyeteDahil = normalizeBooleanFlag(row?.maliyeteDahil ?? row?.maliyete_dahil ?? row?.Maliyete_dahil)
const cmPriceTypeId = normalizeCMPriceTypeId(row?.cmPriceTypeId ?? row?.cm_price_type_id, row?.sAciklama3)
row.lMiktar = quantity
row.fiyat_girilen = inputPrice
row.fiyat_doviz = inputCurrency
row.inputPricePrBr = inputCurrency
row.maliyeteDahil = maliyeteDahil
row.maliyete_dahil = maliyeteDahil ? 1 : 0
row.Maliyete_dahil = maliyeteDahil ? 1 : 0
row.cmPriceTypeId = cmPriceTypeId
row.cm_price_type_id = cmPriceTypeId
row.sDovizCinsi = 'USD'
row.lDovizKuru = usdRate
row.lDovizFiyati = unitUSD
row.lFiyat = unitTRY
row.lTutar = unitTRY * quantity
row.usdTutar = unitUSD * quantity
row.eurTutar = unitEUR * quantity
row.gbpTutar = unitGBP * quantity
row.lTutarUSD = row.usdTutar
row.lTutarEURO = row.eurTutar
row.lTutarGBP = row.gbpTutar
row.lTutarTL = row.lTutar
if (!options.preserveInputs) {
row.miktarInput = normalizeQuantityInput(quantity)
row.inputPrice = normalizeInputPrice(inputPrice)
}
if (options.markChanged) {
row.draftChanged = true
}
if (options.priceType !== undefined) {
row.sFiyatTipi = options.priceType
}
if (options.updateState !== undefined) {
row.priceUpdateState = options.updateState
}
return row
}
function recalculateAllDetailRows () {
detailGroups.value = detailGroups.value.map(grp => ({
...grp,
items: (Array.isArray(grp?.items) ? grp.items : []).map(row => recalculateDetailRow({ ...row }, { preserveInputs: true }))
}))
}
function resolveRowTRYTutar (row) {
const tryAmount = Number(row?.lTutar || 0)
return Number.isFinite(tryAmount) ? tryAmount : 0
}
function resolveRowEURTutar (row) {
const eurAmount = Number(row?.eurTutar || 0)
return Number.isFinite(eurAmount) ? eurAmount : 0
}
function resolveRowGBPTutar (row) {
const gbpAmount = Number(row?.gbpTutar || 0)
return Number.isFinite(gbpAmount) ? gbpAmount : 0
}
function resolveRowUSDTutar (row) {
const usdAmount = Number(row?.usdTutar || 0)
if (Number.isFinite(usdAmount)) return usdAmount
const miktar = resolveNumericRowQuantity(row)
const dovizFiyati = Number(row?.lDovizFiyati || 0)
const calc = miktar * dovizFiyati
return Number.isFinite(calc) ? calc : 0
}
function shouldIgnoreGroupMaliyeteDahil (grp) {
return isCMGroupName(grp?.sAciklama3)
}
function shouldIncludeRowInGroupTotal (grp, row) {
return shouldIgnoreGroupMaliyeteDahil(grp) || normalizeBooleanFlag(row?.maliyeteDahil)
}
function resolveGroupTRYTutar (grp) {
const items = Array.isArray(grp?.items) ? grp.items : []
return items.reduce((acc, row) => acc + (shouldIncludeRowInGroupTotal(grp, row) ? resolveRowTRYTutar(row) : 0), 0)
}
function resolveGroupUSDTutar (grp) {
const items = Array.isArray(grp?.items) ? grp.items : []
return items.reduce((acc, row) => acc + (shouldIncludeRowInGroupTotal(grp, row) ? resolveRowUSDTutar(row) : 0), 0)
}
function groupKey (grp, gi) {
return `${String(grp?.sAciklama3 || 'TANIMSIZ')}-${gi}`
}
function isGroupOpen (grp, gi) {
return groupOpenState.value[groupKey(grp, gi)] !== false
}
function toggleGroup (grp, gi) {
const key = groupKey(grp, gi)
groupOpenState.value = {
...groupOpenState.value,
[key]: !isGroupOpen(grp, gi)
}
}
function resolveElHeight (refVal) {
const el = refVal?.$el || refVal
return Number(el?.offsetHeight || 0)
}
function updateStickyTop () {
const stackH = resolveElHeight(stickyStackRef.value)
// Quasar default header height is usually around 50px
// If we are in a sub-layout or context where top header is not 50px, this might need adjustment
const layoutHeader = document.querySelector('.q-header')
const layoutHeaderH = layoutHeader ? layoutHeader.offsetHeight : 50
subHeaderTop.value = (stackH || 0) + layoutHeaderH
}
function toggleHeaderInfo () {
headerInfoCollapsed.value = !headerInfoCollapsed.value
nextTick(() => {
updateStickyTop()
})
}
function onUretimSekliChange (newId) {
if (!detailHeader.value) return
const found = productionTypes.value.find(t => String(t.id) === String(newId))
if (found) {
detailHeader.value.UretimSekliID = found.id
detailHeader.value.UretimSekli = found.aciklama
schedulePersistLocalDraft()
}
}
function buildDetailFetchParams () {
if (isNoCostDetail.value) {
return {
detail_source: 'no-cost',
urun_kodu: productCode.value,
recete_kodu: recipeCode.value,
trace_id: traceId.value
}
}
return {
n_onml_no: onMLNo.value,
urun_kodu: productCode.value,
trace_id: traceId.value
}
}
async function fetchExchangeRatesForCostDate (targetDate = costDate.value) {
const normalizedDate = normalizeDateInput(targetDate)
if (!normalizedDate) {
exchangeRates.value = createEmptyExchangeRates()
recalculateAllDetailRows()
return
}
try {
const response = await get('/pricing/production-product-costing/has-cost-detail-exchange-rates', {
maliyet_tarihi: normalizedDate,
trace_id: traceId.value
})
exchangeRates.value = normalizeExchangeRatesPayload(response, normalizedDate)
} catch (err) {
exchangeRates.value = {
...createEmptyExchangeRates(),
rateDate: normalizedDate
}
slog.error('production-product-costing.detail', 'exchange-rates:error', {
trace_id: traceId.value,
maliyet_tarihi: normalizedDate,
detail: await extractApiErrorDetail(err)
})
$q.notify({
type: 'negative',
message: await extractApiErrorDetail(err),
position: 'top-right'
})
} finally {
recalculateAllDetailRows()
}
}
async function fetchDetail () {
if (isNoCostDetail.value && !recipeCode.value) {
detailError.value = 'Recete kodu bulunamadi'
detailGroups.value = []
detailHeader.value = null
exchangeRates.value = createEmptyExchangeRates()
return
}
if (!isNoCostDetail.value && !onMLNo.value) {
detailError.value = 'nOnMLNo bulunamadi'
detailGroups.value = []
detailHeader.value = null
exchangeRates.value = createEmptyExchangeRates()
return
}
detailLoading.value = true
detailError.value = ''
detailGroups.value = []
detailHeader.value = null
costDate.value = ''
exchangeRates.value = createEmptyExchangeRates()
newRowSequence.value = 0
rowEditorDialogOpen.value = false
rowEditorMode.value = 'new'
rowEditorTargetRowKey.value = ''
rowEditorForm.value = createRowEditorForm()
rowEditorHammaddeAllOptions.value = []
rowEditorHammaddeOptions.value = []
rowEditorHammaddeLoading.value = false
rowEditorItemAllOptions.value = []
rowEditorItemOptions.value = []
rowEditorItemLoading.value = false
rowEditorColorAllOptions.value = []
rowEditorColorOptions.value = []
rowEditorColorLoading.value = false
lineHistoryDialogOpen.value = false
lineHistoryLoading.value = false
lineHistoryError.value = ''
lineHistoryRows.value = []
lineHistoryTargetRowKey.value = ''
lineHistoryTargetHammaddeTuruNo.value = ''
lineHistoryTargetItemCode.value = ''
lineHistoryTargetSummary.value = ''
lineHistorySearchMode.value = 'exact'
lineHistoryLastPurchaseMatchStage.value = ''
lineHistoryLastRecipeMatchStage.value = ''
try {
const detailParams = buildDetailFetchParams()
slog.info('production-product-costing.detail', 'fetch-detail:start', {
trace_id: traceId.value,
detail_source: detailSource.value || 'has-cost',
n_onml_no: onMLNo.value,
urun_kodu: productCode.value,
recete_kodu: recipeCode.value
})
const [headerData, groupsData, typesData] = await Promise.all([
get('/pricing/production-product-costing/has-cost-detail-header', detailParams),
get('/pricing/production-product-costing/has-cost-detail-groups', detailParams),
get('/pricing/production-product-costing/production-types', {
trace_id: traceId.value
})
])
detailHeader.value = headerData && typeof headerData === 'object' ? headerData : null
productionTypes.value = Array.isArray(typesData) ? typesData : []
costDate.value = normalizeDateInput(detailHeader.value?.dteKayitTarihi)
detailGroups.value = normalizeDetailGroups(groupsData)
initialHeaderSnapshot.value = currentHeaderSnapshot.value
// Optional: hydrate local draft after base data load.
tryHydrateFromLocalDraft()
// ensure required placeholder rows exist (based on mapping screen)
try {
const mappings = await fetchRequiredParcaMappings()
ensureNoCostRequiredRowsFromMappings(mappings)
} catch (err) {
slog.error('production-product-costing.detail', 'required-mapping:error', {
trace_id: traceId.value,
detail: await extractApiErrorDetail(err)
})
}
const initialOpen = {}
detailGroups.value.forEach((grp, gi) => {
initialOpen[groupKey(grp, gi)] = true
})
groupOpenState.value = initialOpen
slog.info('production-product-costing.detail', 'fetch-detail:ok', {
trace_id: traceId.value,
detail_source: detailSource.value || 'has-cost',
group_count: detailGroups.value.length,
item_count: flatDetailRows.value.length,
urun_kodu: detailHeader.value?.UrunKodu || productCode.value,
n_urt_recete_id: detailHeader.value?.nUrtReceteID || ''
})
} catch (err) {
detailError.value = await extractApiErrorDetail(err)
slog.error('production-product-costing.detail', 'fetch-detail:error', {
trace_id: traceId.value,
detail_source: detailSource.value || 'has-cost',
n_onml_no: onMLNo.value,
urun_kodu: productCode.value,
recete_kodu: recipeCode.value,
detail: detailError.value
})
} finally {
detailLoading.value = false
}
}
function goBack () {
if (isNoCostDetail.value) {
router.push({ name: 'production-product-costing-no-cost' })
return
}
const urunKodu = productCode.value
if (!urunKodu) {
router.push({ name: 'production-product-costing-has-cost' })
return
}
router.push({
name: 'production-product-costing-has-cost-history',
query: { urun_kodu: urunKodu }
})
}
function buildDetailItemRequestPayload (row) {
return {
__rowKey: row.__rowKey,
n_onml_no: String(row?.nOnMLNo || detailHeader.value?.nOnMLNo || onMLNo.value || '').trim(),
n_onml_det_no: String(row?.nOnMLDetNo || '').trim(),
n_hammadde_turu_no: String(row?.nHammaddeTuruNo || '').trim(),
s_kodu: String(row?.sKodu || '').trim(),
s_aciklama: String(row?.sAciklama || '').trim(),
s_renk: String(row?.sRenk || '').trim(),
color_code: String(firstDefinedValue(row, ['ColorCode', 'colorCode', 'sRenk', 's_renk'])).trim(),
color_description: String(firstDefinedValue(row, ['ColorDescription', 'colorDescription', 'sRenkAdi', 's_renk_adi'])).trim(),
item_dim1_code: String(firstDefinedValue(row, ['ItemDim1Code', 'itemDim1Code', 'sBeden', 's_beden'])).trim(),
item_dim1_description: String(firstDefinedValue(row, ['ItemDim1Description', 'itemDim1Description', 'sAciklama2', 's_aciklama2'])).trim(),
s_birim: String(row?.sBirim || '').trim(),
l_miktar: resolveNumericRowQuantity(row),
fiyat_girilen: resolveNumericRowInputPrice(row),
fiyat_doviz: resolveInputCurrency(row),
maliyete_dahil: row?.maliyeteDahil ? 1 : 0,
cm_price_type_id: normalizeCMPriceTypeId(row?.cmPriceTypeId ?? row?.cm_price_type_id, row?.sAciklama3)
}
}
function applyPriceSelectionToRow (targetRowKey, price, currency, priceType) {
const normalizedCurrency = normalizePriceCurrency(currency) || 'USD'
const normalizedPrice = parseMoneyInput(price)
const finalPriceType = priceType || 'SAF'
detailGroups.value = detailGroups.value.map(grp => ({
...grp,
items: (Array.isArray(grp?.items) ? grp.items : []).map(row => {
if (row.__rowKey !== targetRowKey) return row
return recalculateDetailRow({
...row,
inputPrice: normalizeInputPrice(normalizedPrice),
fiyat_girilen: normalizedPrice,
inputPricePrBr: normalizedCurrency,
fiyat_doviz: normalizedCurrency
}, {
priceType: finalPriceType,
updateState: 'selection',
markChanged: true
})
})
}))
}
async function fetchSimilarItemHistory (mode = 'prefix') {
if (mode === 'exact') {
const row = flatDetailRows.value.find(r => r.__rowKey === lineHistoryTargetRowKey.value)
if (row) {
await openLineHistory(row)
}
return
}
if (!lineHistoryTargetHammaddeTuruNo.value && !lineHistoryTargetItemCode.value) return
const normalizedMode = mode === 'alternative' ? 'alternative' : 'prefix'
lineHistorySearchMode.value = normalizedMode
lineHistoryLoading.value = true
lineHistoryError.value = ''
try {
slog.info('production-product-costing.detail', 'line-history-similar:start', {
trace_id: traceId.value,
search_mode: normalizedMode,
n_hammadde_turu_no: lineHistoryTargetHammaddeTuruNo.value,
s_kodu: lineHistoryTargetItemCode.value,
maliyet_tarihi: costDate.value,
limit: LINE_HISTORY_ROW_LIMIT
})
const response = await get('/pricing/production-product-costing/has-cost-detail-similar-history', {
n_hammadde_turu_no: lineHistoryTargetHammaddeTuruNo.value,
s_kodu: lineHistoryTargetItemCode.value,
maliyet_tarihi: costDate.value,
limit: LINE_HISTORY_ROW_LIMIT,
search_mode: normalizedMode,
trace_id: traceId.value
})
lineHistoryRows.value = normalizeLineHistoryRows(response)
lineHistoryLastPurchaseMatchStage.value = String(response?.purchase_match_stage || '').trim()
lineHistoryLastRecipeMatchStage.value = String(response?.recipe_match_stage || '').trim()
slog.info('production-product-costing.detail', 'line-history-similar:ok', {
trace_id: traceId.value,
search_mode: normalizedMode,
purchase_match_stage: response?.purchase_match_stage || '',
recipe_match_stage: response?.recipe_match_stage || '',
similar_code_prefix: response?.similar_code_prefix || '',
row_count: lineHistoryRows.value.length
})
if (lineHistoryRows.value.length === 0) {
$q.notify({
type: 'warning',
message: 'Bu hammadde türü için de benzer ürün kaydı bulunamadı.',
position: 'top-right'
})
}
} catch (err) {
lineHistoryError.value = await extractApiErrorDetail(err)
slog.error('production-product-costing.detail', 'line-history-similar:error', {
trace_id: traceId.value,
search_mode: normalizedMode,
n_hammadde_turu_no: lineHistoryTargetHammaddeTuruNo.value,
s_kodu: lineHistoryTargetItemCode.value,
error: lineHistoryError.value
})
} finally {
lineHistoryLoading.value = false
}
}
function onRowQuantityInput (row, value) {
row.miktarInput = value
recalculateDetailRow(row, {
preserveInputs: true,
markChanged: true
})
schedulePersistLocalDraft()
triggerUIUpdate()
}
function triggerUIUpdate () {
detailGroups.value = [...detailGroups.value]
}
function normalizeRowQuantityDisplay (row) {
row.miktarInput = normalizeQuantityInput(row?.miktarInput)
recalculateDetailRow(row, {
preserveInputs: true
})
schedulePersistLocalDraft()
triggerUIUpdate()
}
function onRowInputPriceChange (row, value) {
row.inputPrice = value
recalculateDetailRow(row, {
preserveInputs: true,
priceType: 'MAN',
updateState: 'manual',
markChanged: true
})
schedulePersistLocalDraft()
triggerUIUpdate()
}
function normalizeRowPriceDisplay (row) {
row.inputPrice = normalizeInputPrice(row?.inputPrice)
recalculateDetailRow(row, {
preserveInputs: true
})
schedulePersistLocalDraft()
triggerUIUpdate()
}
function onRowInputPriceCurrencyChange (row, value) {
row.inputPricePrBr = normalizePriceCurrency(value) || 'USD'
recalculateDetailRow(row, {
preserveInputs: true,
priceType: 'MAN',
updateState: 'manual',
markChanged: true
})
schedulePersistLocalDraft()
triggerUIUpdate()
}
function onRowMaliyeteDahilChange (row, value) {
row.maliyeteDahil = Boolean(value)
row.maliyete_dahil = value ? 1 : 0
row.Maliyete_dahil = value ? 1 : 0
row.draftChanged = true
schedulePersistLocalDraft()
triggerUIUpdate()
}
function onRowCMPriceTypeChange (row, value) {
row.cmPriceTypeId = isCMGroupName(row?.sAciklama3) ? (value ? 2 : 1) : null
row.cm_price_type_id = row.cmPriceTypeId
row.draftChanged = true
schedulePersistLocalDraft()
triggerUIUpdate()
}
async function fetchBulkItemPrices () {
const flatRows = detailGroups.value.flatMap(grp => Array.isArray(grp?.items) ? grp.items : [])
if (flatRows.length === 0) {
$q.notify({
type: 'warning',
message: 'Toplu fiyat cagrisi icin satir bulunamadi.',
position: 'top-right'
})
return
}
bulkPriceLoading.value = true
try {
const response = await post('/pricing/production-product-costing/has-cost-detail-bulk-prices', {
n_onml_no: onMLNo.value,
urun_kodu: detailHeader.value?.UrunKodu || productCode.value,
n_urt_recete_id: detailHeader.value?.nUrtReceteID || '',
maliyet_tarihi: costDate.value,
items: flatRows.map(buildDetailItemRequestPayload)
}, {
params: {
trace_id: traceId.value
}
})
const updates = normalizeBulkPriceItems(response)
if (updates.length === 0) {
$q.notify({
type: 'warning',
message: 'Toplu fiyat cagrisindan uygulanacak veri donmedi.',
position: 'top-right'
})
return
}
let appliedCount = 0
detailGroups.value = detailGroups.value.map(grp => ({
...grp,
items: (Array.isArray(grp?.items) ? grp.items : []).map(row => {
const match = updates.find(update => rowMatchesBulkUpdate(row, update))
if (!match) return row
appliedCount += 1
return recalculateDetailRow({
...row,
inputPrice: match.inputPrice,
fiyat_girilen: match.fiyat_girilen,
inputPricePrBr: match.inputPricePrBr,
fiyat_doviz: match.fiyat_doviz
}, {
priceType: match.priceType || 'SAF',
updateState: 'bulk',
markChanged: true
})
})
}))
$q.notify({
type: appliedCount > 0 ? 'positive' : 'warning',
message: appliedCount > 0
? `${appliedCount} kalemin fiyat ve pr. br. bilgisi guncellendi.`
: 'Donen veriler satirlarla eslestirilemedi.',
position: 'top-right'
})
} catch (err) {
$q.notify({
type: 'negative',
message: await extractApiErrorDetail(err),
position: 'top-right'
})
} finally {
bulkPriceLoading.value = false
}
}
async function openLineHistory (row) {
const rowCode = String(row?.sKodu || '').trim()
if (!rowCode) {
$q.notify({
type: 'warning',
message: 'History acmak icin satirda kod bilgisi olmali.',
position: 'top-right'
})
return
}
lineHistoryDialogOpen.value = true
lineHistoryLoading.value = true
lineHistoryError.value = ''
lineHistoryRows.value = []
lineHistoryTargetRowKey.value = row.__rowKey
lineHistoryTargetHammaddeTuruNo.value = String(row?.nHammaddeTuruNo || '').trim()
lineHistoryTargetItemCode.value = rowCode
lineHistoryTargetSummary.value = `${rowCode} | ${String(row?.sAciklama || '').trim() || 'TANIMSIZ'}`
lineHistorySearchMode.value = 'exact'
lineHistoryLastPurchaseMatchStage.value = ''
lineHistoryLastRecipeMatchStage.value = ''
try {
const response = await get('/pricing/production-product-costing/has-cost-detail-line-history', {
n_onml_no: onMLNo.value,
urun_kodu: detailHeader.value?.UrunKodu || productCode.value,
n_urt_recete_id: detailHeader.value?.nUrtReceteID || '',
n_onml_det_no: String(row?.nOnMLDetNo || '').trim(),
n_hammadde_turu_no: String(row?.nHammaddeTuruNo || '').trim(),
s_kodu: rowCode,
s_renk: String(row?.sRenk || '').trim(),
color_code: String(firstDefinedValue(row, ['ColorCode', 'colorCode', 'sRenk', 's_renk'])).trim(),
item_dim1_code: String(firstDefinedValue(row, ['ItemDim1Code', 'itemDim1Code', 'sBeden', 's_beden'])).trim(),
maliyet_tarihi: costDate.value,
trace_id: traceId.value
})
lineHistoryRows.value = normalizeLineHistoryRows(response)
} catch (err) {
lineHistoryError.value = await extractApiErrorDetail(err)
} finally {
lineHistoryLoading.value = false
}
}
function onDetailRowClick (evt, row) {
// Grid satırı tıklanabilir değil, artık ikonlar ve butonlar kullanılıyor.
}
function applyLineHistorySelection (historyRow) {
applyPriceSelectionToRow(lineHistoryTargetRowKey.value, historyRow?.price, historyRow?.currency, historyRow?.priceType)
lineHistoryDialogOpen.value = false
$q.notify({
type: 'positive',
message: 'Secilen history fiyati satira uygulandi.',
position: 'top-right'
})
}
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'
return row?.draftChanged ? 'pcd-detail-row-secondary' : ''
}
async function bootstrapRowEditorOptions () {
try {
const hammaddeSearch = String(rowEditorForm.value.nHammaddeTuruNo || '').trim()
const itemSearch = String(rowEditorForm.value.sKodu || '').trim()
const colorSearch = String(rowEditorForm.value.ColorCode || '').trim()
const [hammaddeRows, itemRows] = await Promise.all([
refreshRowEditorOptions('hammadde', hammaddeSearch),
itemSearch.length >= ROW_EDITOR_ITEM_MIN_SEARCH_LENGTH
? refreshRowEditorOptions('item', itemSearch)
: Promise.resolve([])
])
if (itemSearch.length < ROW_EDITOR_ITEM_MIN_SEARCH_LENGTH) {
rowEditorItemAllOptions.value = []
rowEditorItemOptions.value = []
primeRowEditorOptionsFromForm()
}
const selectedHammadde = hammaddeRows.find(opt => String(opt?.value || '') === hammaddeSearch)
if (selectedHammadde) {
rowEditorForm.value.sHammaddeTuruAdi = String(selectedHammadde.sHammaddeTuruAdi || rowEditorForm.value.sHammaddeTuruAdi || '').trim()
rowEditorForm.value.sAciklama3 = String(selectedHammadde.sAciklama3 || rowEditorForm.value.sAciklama3 || 'TANIMSIZ').trim() || 'TANIMSIZ'
rowEditorForm.value.sParcaAdi = String(selectedHammadde.sParcaAdi || selectedHammadde.sAciklama3 || rowEditorForm.value.sParcaAdi || '').trim()
}
const selectedItem = itemRows.find(opt => String(opt?.value || '') === itemSearch)
if (selectedItem) {
rowEditorForm.value.nStokID = String(selectedItem.nStokID || rowEditorForm.value.nStokID || '').trim()
rowEditorForm.value.sModel = String(selectedItem.sModel || rowEditorForm.value.sModel || '').trim()
rowEditorForm.value.sAciklama = String(selectedItem.sAciklama || rowEditorForm.value.sAciklama || '').trim()
rowEditorForm.value.sBirim = extractPrimaryUnitValue(selectedItem.sBirim || rowEditorForm.value.sBirim || 'AD') || 'AD'
}
await refreshRowEditorOptions('color', colorSearch)
primeRowEditorOptionsFromForm()
} catch (err) {
rowEditorHammaddeAllOptions.value = []
rowEditorHammaddeOptions.value = []
rowEditorItemAllOptions.value = []
rowEditorItemOptions.value = []
rowEditorColorAllOptions.value = []
rowEditorColorOptions.value = []
$q.notify({
type: 'negative',
message: await extractApiErrorDetail(err),
position: 'top-right'
})
}
}
function onRowEditorHammaddeChange (value) {
const selected = rowEditorHammaddeOptions.value.find(opt => String(opt?.value || '') === String(value || ''))
rowEditorForm.value.nHammaddeTuruNo = String(value || '').trim()
if (!selected) return
rowEditorForm.value.sHammaddeTuruAdi = String(selected.sHammaddeTuruAdi || '').trim()
rowEditorForm.value.sAciklama3 = String(selected.sAciklama3 || 'TANIMSIZ').trim() || 'TANIMSIZ'
rowEditorForm.value.sParcaAdi = String(selected.sParcaAdi || selected.sAciklama3 || '').trim()
if (!isCMGroupName(rowEditorForm.value.sAciklama3)) {
rowEditorForm.value.cmPriceTypeChecked = false
}
}
async function onRowEditorItemChange (value) {
const selected = rowEditorItemOptions.value.find(opt => String(opt?.value || '') === String(value || ''))
rowEditorForm.value.sKodu = String(value || '').trim()
if (!selected) return
const previousColorCode = String(rowEditorForm.value.ColorCode || '').trim()
rowEditorForm.value.nStokID = String(selected.nStokID || '').trim()
rowEditorForm.value.sModel = String(selected.sModel || '').trim()
rowEditorForm.value.sAciklama = String(selected.sAciklama || '').trim()
rowEditorForm.value.sBirim = extractPrimaryUnitValue(selected.sBirim || rowEditorForm.value.sBirim || 'AD') || 'AD'
rowEditorForm.value.ColorCode = ''
rowEditorForm.value.sRenk = ''
rowEditorForm.value.ColorDescription = ''
try {
await refreshRowEditorOptions('color', previousColorCode)
} catch (err) {
$q.notify({
type: 'negative',
message: `Renk lookup getirilemedi: ${await extractApiErrorDetail(err)}`,
position: 'top-right'
})
}
const matchedColor = rowEditorColorOptions.value.find(opt => String(opt?.value || '') === previousColorCode)
if (matchedColor) {
rowEditorForm.value.ColorCode = previousColorCode
rowEditorForm.value.sRenk = previousColorCode
rowEditorForm.value.ColorDescription = String(matchedColor.colorDescription || '').trim()
}
}
function onRowEditorColorChange (value) {
const selected = rowEditorColorOptions.value.find(opt => String(opt?.value || '') === String(value || ''))
const normalizedValue = String(value || '').trim()
rowEditorForm.value.ColorCode = normalizedValue
rowEditorForm.value.sRenk = normalizedValue
if (!selected) return
rowEditorForm.value.ColorDescription = String(selected.colorDescription || '').trim()
}
function buildRowFromEditorForm () {
const form = rowEditorForm.value
const existingRow = flatDetailRows.value.find(row => row.__rowKey === rowEditorTargetRowKey.value)
const cmPriceTypeId = normalizeCMPriceTypeId(form.cmPriceTypeChecked ? 2 : 1, form.sAciklama3)
if (!existingRow) {
newRowSequence.value += 1
}
return recalculateDetailRow({
...(existingRow || {}),
__rowKey: existingRow?.__rowKey || `new-editor-row-${newRowSequence.value}`,
isNew: Boolean(existingRow?.isNew) || rowEditorMode.value === 'new',
nOnMLNo: detailHeader.value?.nOnMLNo || onMLNo.value || '',
nStokID: String(form.nStokID || '').trim(),
sModel: String(form.sModel || '').trim(),
nOnMLDetNo: String(form.nOnMLDetNo || '').trim(),
sParcaAdi: String(form.sParcaAdi || form.sAciklama3 || '').trim(),
sAciklama3: String(form.sAciklama3 || form.sParcaAdi || 'TANIMSIZ').trim() || 'TANIMSIZ',
nHammaddeTuruNo: String(form.nHammaddeTuruNo || '').trim(),
sHammaddeTuruAdi: String(form.sHammaddeTuruAdi || '').trim(),
sKodu: String(form.sKodu || '').trim(),
sAciklama: String(form.sAciklama || '').trim(),
sRenk: String(form.sRenk || form.ColorCode || '').trim(),
ColorCode: String(form.ColorCode || form.sRenk || '').trim(),
ColorDescription: String(form.ColorDescription || '').trim(),
lMiktar: parseMoneyInput(form.miktarInput),
miktarInput: normalizeQuantityInput(form.miktarInput),
inputPrice: normalizeInputPrice(form.inputPrice),
inputPricePrBr: normalizePriceCurrency(form.inputPricePrBr) || 'USD',
fiyat_girilen: parseMoneyInput(form.inputPrice),
fiyat_doviz: normalizePriceCurrency(form.inputPricePrBr) || 'USD',
maliyeteDahil: Boolean(form.maliyeteDahil),
maliyete_dahil: form.maliyeteDahil ? 1 : 0,
Maliyete_dahil: form.maliyeteDahil ? 1 : 0,
cmPriceTypeId,
cm_price_type_id: cmPriceTypeId,
sBirim: String(form.sBirim || 'AD').trim() || 'AD',
draftChanged: true
}, {
preserveInputs: true,
priceType: 'MAN',
updateState: 'editor',
markChanged: true
})
}
function applyEditorRowToGroups (nextRow) {
const targetGroupName = String(nextRow?.sAciklama3 || 'TANIMSIZ').trim() || 'TANIMSIZ'
const nextGroups = detailGroups.value
.map(grp => ({
...grp,
items: (Array.isArray(grp?.items) ? grp.items : []).filter(row => row.__rowKey !== nextRow.__rowKey)
}))
.filter(grp => (Array.isArray(grp?.items) ? grp.items.length : 0) > 0 || String(grp?.sAciklama3 || '').trim() === targetGroupName)
const targetIndex = nextGroups.findIndex(grp => String(grp?.sAciklama3 || '').trim() === targetGroupName)
if (targetIndex >= 0) {
nextGroups[targetIndex] = {
...nextGroups[targetIndex],
items: [...(Array.isArray(nextGroups[targetIndex].items) ? nextGroups[targetIndex].items : []), nextRow]
.sort((left, right) => parseInt(String(left?.nOnMLDetNo || '0'), 10) - parseInt(String(right?.nOnMLDetNo || '0'), 10))
}
} else {
nextGroups.push({
sAciklama3: targetGroupName,
totalTutar: 0,
totalUSDTutar: 0,
items: [nextRow]
})
}
detailGroups.value = nextGroups
syncAllGroupsOpen()
schedulePersistLocalDraft()
}
function syncAllGroupsOpen () {
const openState = {}
detailGroups.value.forEach((grp, gi) => {
openState[groupKey(grp, gi)] = true
})
groupOpenState.value = openState
}
function normalizeHammaddeNo (value) {
return String(value || '').trim()
}
function normalizeGroupName (value) {
return String(value || 'TANIMSIZ').trim() || 'TANIMSIZ'
}
async function fetchRequiredParcaMappings () {
const ana = String(detailHeader.value?.UrunAnaGrubu || '').trim()
const alt = String(detailHeader.value?.UrunAltGrubu || '').trim()
if (!ana || !alt) return []
const data = await get('/pricing/production-product-costing/maliyet-parca-eslestirme', {
trace_id: traceId.value,
only_active: 1,
urun_ana_grubu: ana,
urun_alt_grubu: alt
})
return Array.isArray(data) ? data : []
}
function ensureNoCostRequiredRowsFromMappings (mappings) {
const list = Array.isArray(mappings) ? mappings : []
requiredParcaMappings.value = list
if (list.length === 0) return
// Add missing placeholder rows (qty=1, price=0) to remind user
list.forEach(mapping => {
const groupName = normalizeGroupName(mapping?.parcaBolumAdi || mapping?.mtBolumAdi || mapping?.sAciklama3)
const hList = Array.isArray(mapping?.nHammaddeTurleri) ? mapping.nHammaddeTurleri : []
hList.forEach(hNoRaw => {
const hNo = normalizeHammaddeNo(hNoRaw)
if (!hNo) return
const exists = flatDetailRows.value.some(r =>
normalizeGroupName(r?.sAciklama3) === groupName &&
normalizeHammaddeNo(r?.nHammaddeTuruNo) === hNo
)
if (exists) return
newRowSequence.value += 1
const rowKey = `req-auto-row-${newRowSequence.value}`
const placeholder = recalculateDetailRow({
__rowKey: rowKey,
isNew: true,
nOnMLNo: detailHeader.value?.nOnMLNo || onMLNo.value || '',
nOnMLDetNo: '',
sParcaAdi: groupName,
sAciklama3: groupName,
nHammaddeTuruNo: hNo,
sHammaddeTuruAdi: '',
sKodu: '',
sAciklama: '',
sRenk: '',
ColorCode: '',
ColorDescription: '',
lMiktar: 1,
miktarInput: '1',
inputPrice: '0',
inputPricePrBr: 'USD',
fiyat_girilen: 0,
fiyat_doviz: 'USD',
maliyeteDahil: true,
maliyete_dahil: 1,
Maliyete_dahil: 1,
cmPriceTypeId: normalizeCMPriceTypeId(1, groupName),
cm_price_type_id: normalizeCMPriceTypeId(1, groupName),
sBirim: 'AD',
draftChanged: true,
requiredPlaceholder: true
}, {
preserveInputs: true,
priceType: 'REQ',
updateState: 'required',
markChanged: true
})
applyEditorRowToGroups(placeholder)
})
})
}
function computeMissingRequiredSlots () {
const list = Array.isArray(requiredParcaMappings.value) ? requiredParcaMappings.value : []
const missing = []
if (list.length === 0) return missing
list.forEach(mapping => {
const groupName = normalizeGroupName(mapping?.parcaBolumAdi || mapping?.mtBolumAdi || mapping?.sAciklama3)
const hList = Array.isArray(mapping?.nHammaddeTurleri) ? mapping.nHammaddeTurleri : []
hList.forEach(hNoRaw => {
const hNo = normalizeHammaddeNo(hNoRaw)
if (!hNo) return
const match = flatDetailRows.value.find(r =>
normalizeGroupName(r?.sAciklama3) === groupName &&
normalizeHammaddeNo(r?.nHammaddeTuruNo) === hNo
)
const price = resolveNumericRowInputPrice(match)
if (!match || !(price > 0)) {
missing.push({ groupName, nHammaddeTuruNo: hNo, rowKey: match?.__rowKey || '' })
}
})
})
return missing
}
function removeDetailRowByKey (rowKey) {
const normalizedRowKey = String(rowKey || '').trim()
if (!normalizedRowKey) return false
const existingRow = flatDetailRows.value.find(r => String(r?.__rowKey || '').trim() === normalizedRowKey)
const nextGroups = detailGroups.value
.map(grp => ({
...grp,
items: (Array.isArray(grp?.items) ? grp.items : []).filter(row => row.__rowKey !== normalizedRowKey)
}))
.filter(grp => (Array.isArray(grp?.items) ? grp.items.length : 0) > 0)
const hasChanged = nextGroups.length !== detailGroups.value.length ||
nextGroups.some((grp, index) => (grp?.items?.length || 0) !== (detailGroups.value[index]?.items?.length || 0))
if (!hasChanged) return false
detailGroups.value = nextGroups
syncAllGroupsOpen()
if (existingRow && !existingRow?.isNew) {
deletedDetailRows.value = [
...(Array.isArray(deletedDetailRows.value) ? deletedDetailRows.value : []),
{
__rowKey: normalizedRowKey,
nOnMLNo: String(existingRow?.nOnMLNo || '').trim(),
nOnMLDetNo: String(existingRow?.nOnMLDetNo || '').trim(),
nHammaddeTuruNo: String(existingRow?.nHammaddeTuruNo || '').trim(),
sKodu: String(existingRow?.sKodu || '').trim(),
sRenk: String(existingRow?.sRenk || '').trim()
}
]
}
schedulePersistLocalDraft()
return true
}
function openNewRowDialog () {
if (!detailHeader.value) {
$q.notify({
type: 'warning',
message: 'Yeni satir eklemek icin once detay verisi olmali.',
position: 'top-right'
})
return
}
rowEditorMode.value = 'new'
rowEditorTargetRowKey.value = ''
rowEditorForm.value = createRowEditorForm({
isNew: true,
nOnMLDetNo: getNextDetailNo(),
inputPricePrBr: normalizePriceCurrency(detailHeader.value?.sDovizCinsi) || 'USD',
maliyeteDahil: true,
sBirim: 'AD'
})
primeRowEditorOptionsFromForm()
rowEditorDialogOpen.value = true
void bootstrapRowEditorOptions()
}
function openRowEditorForEdit (row) {
rowEditorMode.value = 'edit'
rowEditorTargetRowKey.value = String(row?.__rowKey || '').trim()
rowEditorForm.value = createRowEditorForm({
...row,
sAciklama3: row?.sAciklama3 || ''
})
primeRowEditorOptionsFromForm()
rowEditorDialogOpen.value = true
void bootstrapRowEditorOptions()
}
function deleteRowEditor () {
const targetRowKey = String(rowEditorTargetRowKey.value || rowEditorForm.value.__rowKey || '').trim()
if (!targetRowKey) return
$q.dialog({
title: 'Satiri Sil',
message: 'Bu satir gridden kaldirilacak. Devam edilsin mi?',
cancel: {
flat: true,
color: 'grey-7',
label: 'Vazgec'
},
ok: {
unelevated: true,
color: 'negative',
label: 'Sil'
},
persistent: true
}).onOk(() => {
if (!removeDetailRowByKey(targetRowKey)) {
$q.notify({
type: 'warning',
message: 'Silinecek satir bulunamadi.',
position: 'top-right'
})
return
}
rowEditorDialogOpen.value = false
$q.notify({
type: 'positive',
message: 'Satir gridden kaldirildi.',
position: 'top-right'
})
})
}
function saveRowEditor () {
if (!String(rowEditorForm.value.nOnMLDetNo || '').trim()) {
rowEditorForm.value.nOnMLDetNo = getNextDetailNo()
}
if (!String(rowEditorForm.value.nHammaddeTuruNo || '').trim()) {
$q.notify({
type: 'warning',
message: 'Hammadde Turu secilmeden satir kaydedilemez.',
position: 'top-right'
})
return
}
if (!String(rowEditorForm.value.sKodu || '').trim()) {
$q.notify({
type: 'warning',
message: 'Kod secilmeden satir kaydedilemez.',
position: 'top-right'
})
return
}
const nextRow = buildRowFromEditorForm()
applyEditorRowToGroups(nextRow)
rowEditorDialogOpen.value = false
}
async function saveChanges () {
saveLoading.value = true
try {
requiredAttentionRowKeys.value = {}
if (isNoCostDetail.value) {
const missing = computeMissingRequiredSlots()
if (missing.length > 0) {
const ok = await new Promise(resolve => {
$q.dialog({
title: 'Eksik Maliyet Parcalari',
message: `Eslestirilen parcalarda (fiyat > 0) girilmemis satirlar var. Devam etmek istiyor musunuz? (Eksik: ${missing.length})`,
cancel: true,
persistent: true
}).onOk(() => resolve(true)).onCancel(() => resolve(false))
})
if (!ok) {
const next = {}
missing.forEach(x => {
if (x.rowKey) next[String(x.rowKey)] = true
})
requiredAttentionRowKeys.value = next
$q.notify({
type: 'warning',
message: 'Eksik parcalar isaretlendi. Fiyat girip tekrar deneyin.',
position: 'top-right'
})
return
}
}
}
$q.notify({
type: 'warning',
message: 'Kaydetme endpointi henuz eklenmedi. Buton hazir, backend baglantisi bir sonraki adimda eklenebilir.',
position: 'top-right'
})
} finally {
saveLoading.value = false
}
}
watch(
() => [onMLNo.value, productCode.value, recipeCode.value, detailSource.value],
() => {
fetchDetail()
}
)
watch(
() => costDate.value,
value => {
const normalizedDate = normalizeDateInput(value)
if (detailHeader.value) {
detailHeader.value.dteKayitTarihi = normalizedDate
}
fetchExchangeRatesForCostDate(normalizedDate)
}
)
onMounted(() => {
if (!canReadOrder.value) return
fetchDetail()
nextTick(() => {
updateStickyTop()
})
window.addEventListener('resize', updateStickyTop)
})
onBeforeUnmount(() => {
window.removeEventListener('resize', updateStickyTop)
ensureBeforeUnloadGuard(false)
if (localDraftTimer.value) window.clearTimeout(localDraftTimer.value)
})
watch(
() => hasUnsavedChanges.value,
(value) => {
ensureBeforeUnloadGuard(Boolean(value))
}
)
onBeforeRouteLeave((to, from, next) => {
if (!hasUnsavedChanges.value) {
next()
return
}
$q.dialog({
title: 'Sayfadan ayriliyorsunuz',
message: pageMode.value === 'edit'
? 'Yaptiginiz degisiklikler kaybolacak. Devam edilsin mi?'
: 'Taslak korunacak. Sayfadan cikmak istiyor musunuz?',
ok: { label: 'Evet', color: 'negative' },
cancel: { label: 'Hayir' },
persistent: true
})
.onOk(() => {
// NEW (no-cost): always persist draft so it can be resumed
if (pageMode.value === 'new') {
persistLocalDraftNow()
next()
return
}
// EDIT (has-cost): allow exit, keep draft for now (later we can clear it after successful save)
next()
})
.onCancel(() => next(false))
})
watch(
() => [detailLoading.value, detailError.value, !!detailHeader.value, headerInfoCollapsed.value],
() => {
nextTick(() => {
updateStickyTop()
})
}
)
</script>
<style scoped>
.pcd-page {
padding: 0;
}
.pcd-content-body {
margin-top: 4px;
}
.pcd-sticky-stack {
position: sticky !important;
top: 50px !important;
z-index: 1000 !important;
background: #fff;
margin-bottom: 0;
border-bottom: 1px solid #ddd;
width: 100%;
}
.pcd-save-toolbar {
border-top: 0;
}
.pcd-toolbar-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
min-width: 0;
}
.pcd-toolbar-left {
display: flex;
align-items: center;
gap: 10px;
flex: 1 1 auto;
min-width: 0;
}
.pcd-toolbar-title {
flex: 0 0 auto;
font-size: 15px;
font-weight: 800;
color: #223048;
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-pill {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
padding: 5px 8px;
border-radius: 8px;
border: 1px solid #d7e0ea;
white-space: nowrap;
}
.pcd-toolbar-pill-emphasis {
background: var(--q-primary);
border-color: #145ea8;
color: #ffffff;
}
.pcd-toolbar-pill-neutral {
background: #f5f7fa;
color: #2b3c54;
}
.pcd-toolbar-pill-label {
font-size: 10px;
font-weight: 800;
letter-spacing: 0.02em;
opacity: 0.88;
}
.pcd-toolbar-pill-value {
font-size: 12px;
font-weight: 800;
min-width: 0;
}
.pcd-toolbar-actions {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 6px;
flex: 0 0 auto;
margin-left: auto;
}
.pcd-toolbar-btn {
font-size: 12px;
}
.pcd-toolbar-btn :deep(.q-btn__content) {
min-height: 34px;
padding: 0 4px;
}
.pcd-add-row-dialog {
min-width: 360px;
}
.pcd-row-editor-dialog {
width: min(960px, 96vw);
max-width: 96vw;
}
.pcd-row-editor-dialog :deep(.pcd-row-editor-entry .q-field__control) {
background: color-mix(in srgb, var(--q-secondary) 18%, white) !important;
border: 1px solid var(--q-secondary) !important;
border-radius: 6px;
}
.pcd-row-editor-dialog :deep(.pcd-row-editor-entry .q-field__label),
.pcd-row-editor-dialog :deep(.pcd-row-editor-entry .q-field__native),
.pcd-row-editor-dialog :deep(.pcd-row-editor-entry .q-field__input),
.pcd-row-editor-dialog :deep(.pcd-row-editor-entry .q-field__marginal),
.pcd-row-editor-dialog :deep(.pcd-row-editor-entry .q-select__dropdown-icon),
.pcd-row-editor-dialog :deep(.pcd-row-editor-entry .q-field__selection) {
color: var(--q-secondary) !important;
font-weight: 700;
}
.pcd-row-editor-dialog :deep(.pcd-row-editor-entry.q-field--focused .q-field__control) {
box-shadow: 0 0 0 1px var(--q-secondary), 0 0 0 3px color-mix(in srgb, var(--q-secondary) 24%, white);
}
.pcd-row-editor-dialog :deep(.pcd-row-editor-entry .q-placeholder) {
color: var(--q-secondary) !important;
opacity: 0.76;
}
.pcd-row-editor-flag {
background: color-mix(in srgb, var(--q-secondary) 10%, white);
border: 1px solid color-mix(in srgb, var(--q-secondary) 35%, white);
border-radius: 8px;
padding: 6px 10px;
}
.pcd-history-dialog {
background: #fff;
}
.pcd-detail-header-bar {
border: 1px solid #ddd;
border-radius: 6px;
}
.pcd-emphasis-field :deep(.q-field__control) {
background: var(--q-primary) !important;
border: 1px solid #145ea8;
}
.pcd-emphasis-field :deep(.q-field__label) {
color: #eaf3ff !important;
font-weight: 700;
}
.pcd-emphasis-field :deep(.q-field__native),
.pcd-emphasis-field :deep(.q-field__input) {
color: #ffffff !important;
font-weight: 800;
}
.pcd-emphasis-field-alt :deep(.q-field__control) {
background: color-mix(in srgb, var(--q-secondary) 25%, white) !important;
border: 1px solid var(--q-secondary);
}
.pcd-emphasis-field-alt :deep(.q-field__label) {
color: var(--q-primary) !important;
font-weight: 700;
}
.pcd-emphasis-field-alt :deep(.q-field__native),
.pcd-emphasis-field-alt :deep(.q-field__input),
.pcd-emphasis-field-alt :deep(.q-field__selection) {
color: var(--q-primary) !important;
font-weight: 800;
}
.pcd-readonly-header-input :deep(.q-field__control) {
background: #f8f9fa !important;
}
.pcd-group-card {
border: 1px solid #dfe3e8;
border-radius: 6px;
overflow: visible;
position: relative;
margin: 0 10px 15px;
}
.pcd-sub-header {
position: sticky !important;
top: var(--pcd-subheader-top) !important;
z-index: 990 !important;
display: flex !important;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 8px 10px;
min-height: 42px;
height: 42px;
border-top: 1px solid #d6c06a;
border-bottom: 1px solid #d6c06a;
background: linear-gradient(90deg, #fffbe9 0%, #fff4c4 50%, #fff1b0 100%);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
}
.pcd-sub-header .sub-left {
font-weight: 800;
color: #2b1f05;
text-transform: uppercase;
}
.pcd-sub-header .sub-right {
font-weight: 900;
color: #3b2f09;
font-size: 12px;
text-transform: uppercase;
text-align: right;
}
.pcd-sub-right-clickable {
cursor: pointer;
user-select: none;
display: inline-flex;
align-items: center;
gap: 8px;
}
.pcd-detail-table :deep(.q-table__middle) {
overflow: visible !important;
}
.pcd-detail-table :deep(.q-table thead) {
display: table-header-group !important;
}
.pcd-detail-table :deep(.q-table thead tr:first-child th) {
position: sticky !important;
top: calc(var(--pcd-subheader-top) + 42px) !important;
z-index: 980 !important;
background: #f8f9fa !important;
opacity: 1 !important;
}
.pcd-detail-table :deep(.q-table thead th) {
font-size: 11px;
padding: 3px 4px;
white-space: normal;
line-height: 1.2;
vertical-align: bottom;
border-bottom: 1px solid rgba(0,0,0,0.12);
}
.pcd-detail-table :deep(.q-table tbody td) {
font-size: 11px;
padding: 2px 4px;
}
.pcd-detail-table :deep(.q-table tbody tr) {
cursor: pointer;
}
.pcd-detail-table :deep(.pcd-detail-row-secondary td) {
background: color-mix(in srgb, var(--q-secondary) 14%, white) !important;
}
.pcd-detail-table :deep(.pcd-detail-row-required td) {
background: color-mix(in srgb, #ff9800 18%, white) !important;
}
.pcd-detail-table :deep(.pcd-entry-header) {
background: color-mix(in srgb, var(--q-primary) 16%, #f8f9fa) !important;
color: var(--q-primary) !important;
font-weight: 800;
}
.pcd-detail-table :deep(.pcd-secondary-header) {
background: color-mix(in srgb, var(--q-secondary) 18%, #f8f9fa) !important;
color: var(--q-secondary) !important;
font-weight: 800;
}
.pcd-detail-table :deep(.pcd-secondary-cell) {
background: color-mix(in srgb, var(--q-secondary) 10%, white) !important;
}
.pcd-detail-table :deep(.pcd-entry-input .q-field__control) {
background: color-mix(in srgb, var(--q-primary) 12%, white) !important;
border: 1px solid var(--q-primary) !important;
border-radius: 6px;
}
.pcd-detail-table :deep(.pcd-entry-input .q-field__native),
.pcd-detail-table :deep(.pcd-entry-input .q-field__input),
.pcd-detail-table :deep(.pcd-entry-input .q-field__marginal),
.pcd-detail-table :deep(.pcd-entry-input .q-select__dropdown-icon) {
color: var(--q-primary) !important;
font-weight: 700;
}
.pcd-detail-table :deep(.pcd-entry-input.q-field--focused .q-field__control) {
box-shadow: 0 0 0 1px var(--q-primary), 0 0 0 3px color-mix(in srgb, var(--q-primary) 28%, white);
}
.pcd-detail-table :deep(.pcd-entry-input .q-placeholder) {
color: var(--q-primary) !important;
opacity: 0.72;
}
.pcd-detail-table :deep(.pcd-inline-input .q-field__control) {
min-height: 30px;
}
.pcd-detail-table :deep(.pcd-inline-input .q-field__native),
.pcd-detail-table :deep(.pcd-inline-input .q-field__input) {
padding-top: 0;
padding-bottom: 0;
}
.pcd-detail-table {
position: relative;
z-index: 1;
}
.pcd-part-summary-card {
margin-top: 8px;
border: 1px solid #d7e0ea;
border-radius: 8px;
background: #fcfdfe;
padding: 8px;
}
.pcd-part-summary-title {
font-size: 13px;
font-weight: 800;
color: #2b3c54;
margin-bottom: 6px;
padding-left: 4px;
border-left: 3px solid var(--q-primary);
}
.pcd-part-summary-table {
background: transparent !important;
}
.pcd-part-summary-table thead th {
background: #f5f7fa;
font-weight: 800;
color: #44556c;
}
.pcd-part-summary-table tbody td {
font-size: 12px;
}
.pcd-history-table :deep(.q-table thead th) {
font-size: 12px;
}
.pcd-history-table :deep(.q-table tbody td) {
font-size: 12px;
vertical-align: top;
}
.pcd-history-table :deep(.pcd-history-row-purchase td) {
background: #ffffff;
}
.pcd-history-table :deep(.pcd-history-row-recipe td) {
background: color-mix(in srgb, var(--q-secondary) 10%, white);
}
.pcd-history-table :deep(.pcd-history-row-similar td) {
background: #fff8e1 !important;
border-bottom: 1px solid #ffe082 !important;
}
.pcd-history-source-chip {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 72px;
padding: 2px 8px;
border-radius: 999px;
font-size: 11px;
font-weight: 800;
letter-spacing: 0.02em;
}
.pcd-history-source-chip--purchase {
background: color-mix(in srgb, var(--q-primary) 12%, white);
color: var(--q-primary);
}
.pcd-history-source-chip--bnz-v3,
.pcd-history-source-chip--bnz-rec {
background: #f3e5f5;
color: #7b1fa2;
font-weight: bold;
}
.pcd-history-source-chip--recipe {
background: color-mix(in srgb, var(--q-secondary) 18%, white);
color: color-mix(in srgb, var(--q-secondary) 82%, black);
}
.pcd-history-source-chip--history {
background: #eceff3;
color: #44556c;
}
</style>

View File

@@ -0,0 +1,259 @@
<template>
<q-page v-if="canReadOrder" class="pch-page">
<div class="sticky-stack pch-sticky-stack">
<div class="ol-filter-bar filter-bar pch-filter-bar">
<div class="row items-center q-col-gutter-md">
<div class="col-12 col-md-4">
<q-input
:model-value="productCode || '-'"
label="Urun Kodu"
dense
filled
readonly
/>
</div>
<div class="col-12 col-md-8">
<q-input
:model-value="rows.length"
label="Toplam Maliyet Kaydi"
dense
filled
readonly
/>
</div>
</div>
</div>
<div class="save-toolbar pch-save-toolbar">
<div class="text-subtitle2 text-weight-bold">Secili Urunun Tum Maliyetleri</div>
<div class="row items-center q-gutter-sm">
<q-btn
icon="arrow_back"
label="Geri"
flat
color="grey-8"
@click="goBack"
/>
<q-btn
label="Yenile"
icon="refresh"
color="primary"
:loading="loading"
@click="fetchRows"
/>
</div>
</div>
</div>
<q-table
title=""
class="ol-table pch-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 #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="pch-wrap-col">
<div class="pch-wrap-2" :title="String(props.value ?? '')">{{ props.value }}</div>
</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, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { usePermission } from 'src/composables/usePermission'
import { get, extractApiErrorDetail } from 'src/services/api'
const route = useRoute()
const router = useRouter()
const { canRead } = usePermission()
const canReadOrder = canRead('order')
const loading = ref(false)
const error = ref('')
const allRows = ref([])
const productCode = computed(() => String(route.query?.urun_kodu || '').trim())
const columns = [
{ name: 'open', label: '', field: 'open', align: 'center', sortable: false, style: 'width:3%', headerStyle: 'width:3%' },
{ name: 'nOnMLNo', label: 'nOnMLNo', field: 'nOnMLNo', align: 'left', sortable: true, style: 'width:7%', headerStyle: 'width:7%' },
{ name: 'UrunKodu', label: 'UrunKodu', field: 'UrunKodu', align: 'left', sortable: true, style: 'width:8%', headerStyle: 'width:8%' },
{ name: 'UrunAdi', label: 'UrunAdi', field: 'UrunAdi', align: 'left', sortable: true, style: 'width:12%', headerStyle: 'width:12%' },
{ name: 'Tarihi', label: 'Tarihi', field: 'Tarihi', align: 'center', sortable: true, format: val => formatDateTR(val), style: 'width:8%', headerStyle: 'width:8%' },
{ name: 'sKullaniciAdi', label: 'sKullaniciAdi', field: 'sKullaniciAdi', align: 'left', sortable: true, style: 'width:8%', headerStyle: 'width:8%' },
{ name: 'lTutarUSD', label: 'lTutarUSD', field: 'lTutarUSD', align: 'right', sortable: true, format: val => formatMoney(val), style: 'width:7%', headerStyle: 'width:7%' },
{ name: 'lTutarTL', label: 'lTutarTL', field: 'lTutarTL', align: 'right', sortable: true, format: val => formatMoney(val), style: 'width:7%', headerStyle: 'width:7%' },
{ name: 'lTutarEURO', label: 'lTutarEURO', field: 'lTutarEURO', align: 'right', sortable: true, format: val => formatMoney(val), style: 'width:7%', headerStyle: 'width:7%' },
{ name: 'sDovizCinsi', label: 'sDovizCinsi', field: 'sDovizCinsi', align: 'left', sortable: true, style: 'width:7%', headerStyle: 'width:7%' },
{ name: 'lTutarDoviz', label: 'lTutarDoviz', field: 'lTutarDoviz', align: 'right', sortable: true, format: val => formatMoney(val), style: 'width:7%', headerStyle: 'width:7%' },
{ name: 'dteGuncellemeTarihi', label: 'dteGuncellemeTarihi', field: 'dteGuncellemeTarihi', align: 'center', sortable: true, format: val => formatDateTR(val), style: 'width:9%', headerStyle: 'width:9%' },
{ name: 'sGuncellemeKullaniciAdi', label: 'sGuncellemeKullaniciAdi', field: 'sGuncellemeKullaniciAdi', align: 'left', sortable: true, style: 'width:9%', headerStyle: 'width:9%' },
{ name: 'nUrtReceteID', label: 'nUrtReceteID', field: 'nUrtReceteID', align: 'left', sortable: true, style: 'width:7%', headerStyle: 'width:7%' },
{ name: 'sAciklama', label: 'sAciklama', field: 'sAciklama', align: 'left', sortable: true, style: 'width:10%', headerStyle: 'width:10%' }
]
const rows = computed(() => allRows.value)
function formatDateTR (value) {
const s = String(value || '').trim()
if (!s) return ''
const m = /^(\d{4})-(\d{2})-(\d{2})(?:\s+(\d{2}):(\d{2}))?/.exec(s)
if (!m) return s
const datePart = `${m[3]}.${m[2]}.${m[1]}`
if (m[4] && m[5]) return `${datePart} ${m[4]}:${m[5]}`
return datePart
}
function formatMoney (value) {
return Number(value || 0).toLocaleString('tr-TR', {
minimumFractionDigits: 2,
maximumFractionDigits: 4
})
}
async function fetchRows () {
const urunKodu = productCode.value
if (!urunKodu) {
error.value = 'Urun kodu bulunamadi'
allRows.value = []
return
}
loading.value = true
error.value = ''
try {
const data = await get('/pricing/production-product-costing/has-cost-history', {
urun_kodu: urunKodu
})
const list = Array.isArray(data) ? data : []
allRows.value = list.map((x, i) => ({
__rowKey: `${x?.nOnMLNo || ''}-${x?.UrunKodu || ''}-${i}`,
...x
}))
} catch (err) {
error.value = await extractApiErrorDetail(err)
allRows.value = []
} finally {
loading.value = false
}
}
function goBack () {
router.push({ name: 'production-product-costing-has-cost' })
}
function openRow (row) {
const onMLNo = String(row?.nOnMLNo || '').trim()
if (!onMLNo) return
router.push({
name: 'production-product-costing-has-cost-detail',
query: {
n_onml_no: onMLNo,
urun_kodu: productCode.value || String(row?.UrunKodu || '').trim()
}
})
}
watch(
() => productCode.value,
() => {
fetchRows()
}
)
onMounted(() => {
if (!canReadOrder.value) return
fetchRows()
})
</script>
<style scoped>
.pch-page {
padding: 10px;
}
.pch-sticky-stack {
margin-bottom: 8px;
}
.pch-filter-bar {
min-height: auto !important;
padding-top: 8px !important;
padding-bottom: 8px !important;
}
.pch-save-toolbar {
border-top: 0;
}
.pch-table :deep(.q-table thead th) {
font-size: 11px;
padding: 3px 4px;
white-space: normal !important;
vertical-align: top !important;
line-height: 1.15;
}
.pch-table :deep(.q-table tbody td) {
font-size: 11px;
padding: 2px 4px;
white-space: normal !important;
vertical-align: top !important;
line-height: 1.15;
}
.pch-table :deep(.q-table) {
width: 100%;
table-layout: fixed;
}
.pch-wrap-col {
white-space: normal !important;
}
.pch-wrap-2 {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
line-height: 1.15;
white-space: normal;
word-break: break-word;
}
</style>

View File

@@ -0,0 +1,744 @@
<template>
<q-page v-if="canReadOrder" class="pcmm-page q-pa-md">
<div class="pcmm-header row items-center q-col-gutter-md">
<div class="col">
<div class="text-h6">Maliyet Parca Eslestirme</div>
<div class="text-caption text-grey-7">
V3 Urun Ilk Grubu (42. ozellik) + Urun Ana/Alt Grup + URETIM Parca Bolum + Hammadde Turleri eslestirmesi (URETIM mk_ tablolarinda tutulur)
</div>
</div>
<div class="col-auto">
<q-btn
color="primary"
icon="refresh"
label="Yenile"
:loading="loading"
@click="refreshAll"
/>
</div>
</div>
<q-separator class="q-my-md" />
<q-table
class="ol-table pcmm-table"
flat
bordered
dense
separator="cell"
row-key="__key"
:rows="mappings"
:columns="columns"
:loading="loading"
no-data-label="Kayit bulunamadi"
:rows-per-page-options="[0]"
hide-bottom
>
<template #top-right>
<div class="row items-center q-gutter-sm">
<q-input
v-model="filters.search"
dense
filled
clearable
debounce="250"
label="Ara (Ana/Alt)"
style="min-width: 220px"
@update:model-value="fetchSheet"
@clear="onSearchCleared"
/>
<q-btn
color="secondary"
icon="content_copy"
label="Kopyala"
:disable="loading || saving || !canCopySelected"
@click="copySelectedToSelected"
/>
<q-btn
color="secondary"
icon="save"
label="Secilenleri Kaydet"
:loading="saving"
:disable="loading || saving || !canSaveSelected"
@click="saveSelected"
/>
<q-btn
color="primary"
icon="save"
label="Degisenleri Kaydet"
:loading="saving"
:disable="loading || saving || dirtyCount === 0"
@click="saveAll"
/>
<div class="text-caption text-grey-7 q-pl-sm">
Satir: {{ mappings.length }} | Degisen: {{ dirtyCount }} | Kopya: {{ copySelectedCount }} | Secili: {{ saveSelectedCount }}
</div>
</div>
</template>
<template #body-cell-copy_select="props">
<q-td :props="props" style="width: 54px">
<div class="row items-center no-wrap">
<q-checkbox
:model-value="isCopySelected(props.row.__key)"
@update:model-value="(v) => toggleCopySelected(props.row.__key, v)"
/>
<q-badge
v-if="copyRoleLabel(props.row.__key)"
color="grey-8"
class="q-ml-xs"
style="max-width: 110px"
>
{{ copyRoleLabel(props.row.__key) }}
<q-tooltip anchor="center left" self="center right">
{{ copyRoleLabel(props.row.__key) }}
</q-tooltip>
</q-badge>
</div>
</q-td>
</template>
<template #body-cell-save_select="props">
<q-td :props="props" style="width: 54px">
<q-checkbox
:model-value="isSaveSelected(props.row.__key)"
@update:model-value="(v) => toggleSaveSelected(props.row.__key, v)"
/>
</q-td>
</template>
<template #body-cell-parcaBolumAdi="props">
<q-td :props="props">
<q-select
:model-value="bolumByKey[props.row.__key] || []"
dense
filled
multiple
use-chips
clearable
use-input
input-debounce="0"
:options="mtBolumOptions"
option-label="label"
option-value="value"
emit-value
map-options
class="pcmm-multi-select"
behavior="menu"
@filter="onFilterMTBolum"
@update:model-value="(val) => { updateBolumSelection(props.row.__key, val); markDirty(props.row) }"
style="min-width: 260px"
>
<template #before-options>
<q-item clickable @click="selectAllMTBolum(props.row.__key)">
<q-item-section>Tumunu Sec</q-item-section>
</q-item>
<q-item clickable @click="clearMTBolum(props.row.__key)">
<q-item-section>Temizle</q-item-section>
</q-item>
<q-separator />
</template>
<template #selected-item="scope">
<q-chip
class="q-mr-xs"
dense
removable
@remove="scope.removeAtIndex(scope.index)"
>
{{ scope.opt.label }}
</q-chip>
</template>
<template #option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section avatar>
<q-checkbox
:model-value="scope.selected"
tabindex="-1"
@update:model-value="() => scope.toggleOption(scope.opt)"
@click.stop
/>
</q-item-section>
<q-item-section>
<q-item-label>{{ scope.opt.label }}</q-item-label>
</q-item-section>
</q-item>
</template>
</q-select>
</q-td>
</template>
<template #body-cell-nHammaddeTurleri="props">
<q-td :props="props">
<q-select
:model-value="hammaddeByKey[props.row.__key] || []"
dense
filled
multiple
use-chips
clearable
use-input
input-debounce="0"
:options="hammaddeOptions"
option-label="label"
option-value="value"
emit-value
map-options
class="pcmm-multi-select"
behavior="menu"
@filter="onFilterHammadde"
@update:model-value="(val) => { updateHammaddeSelection(props.row.__key, val); markDirty(props.row) }"
style="min-width: 320px"
>
<template #before-options>
<q-item clickable @click="selectAllHammadde(props.row.__key)">
<q-item-section>Tumunu Sec</q-item-section>
</q-item>
<q-item clickable @click="clearHammadde(props.row.__key)">
<q-item-section>Temizle</q-item-section>
</q-item>
<q-separator />
</template>
<template #selected-item="scope">
<q-chip
class="q-mr-xs"
dense
removable
@remove="scope.removeAtIndex(scope.index)"
>
{{ scope.opt.label }}
</q-chip>
</template>
<template #option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section avatar>
<q-checkbox
:model-value="scope.selected"
tabindex="-1"
@update:model-value="() => scope.toggleOption(scope.opt)"
@click.stop
/>
</q-item-section>
<q-item-section>
<q-item-label>{{ scope.opt.label }}</q-item-label>
</q-item-section>
</q-item>
</template>
</q-select>
</q-td>
</template>
</q-table>
</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, ref } from 'vue'
import { useQuasar } from 'quasar'
import { get, post, del, extractApiErrorDetail } from 'src/services/api'
import { usePermission } from 'src/composables/usePermission'
import { slog } from 'src/utils/slog'
const $q = useQuasar()
const { canRead } = usePermission()
const canReadOrder = canRead('order')
const traceId = `pcd-mtbolum-map-${crypto?.randomUUID?.() || String(Date.now())}`
const loading = ref(false)
const saving = ref(false)
const mappings = ref([])
const copySelectedKeys = ref([]) // ordered
const saveSelectedKeyMap = ref({}) // key -> true
const filters = ref({
search: ''
})
const mtBolumOptions = ref([])
const hammaddeOptions = ref([])
const mtBolumLoading = ref(false)
const hammaddeLoading = ref(false)
const bolumByKey = ref({})
const hammaddeByKey = ref({})
const columns = [
{ name: 'copy_select', label: 'Kopya', field: 'copy_select', align: 'center' },
{ name: 'save_select', label: 'Sec', field: 'save_select', align: 'center' },
{ name: 'urunIlkGrubu', label: 'Urun Ilk Grubu', field: 'urunIlkGrubu', align: 'left', sortable: true },
{ name: 'urunAnaGrubu', label: 'Urun Ana Grubu', field: 'urunAnaGrubu', align: 'left', sortable: true },
{ name: 'urunAltGrubu', label: 'Urun Alt Grubu', field: 'urunAltGrubu', align: 'left', sortable: true },
{ name: 'parcaBolumAdi', label: 'Parca Bolum', field: row => (Array.isArray(row?.nUrtMTBolumIDs) ? row.nUrtMTBolumIDs.join(', ') : ''), align: 'left' },
{ name: 'nHammaddeTurleri', label: 'Hammadde Turleri', field: row => (Array.isArray(row?.nHammaddeTurleri) ? row.nHammaddeTurleri.join(', ') : ''), align: 'left' }
]
const dirtyMap = ref({})
const dirtyCount = computed(() => Object.keys(dirtyMap.value || {}).length)
const copySelectedCount = computed(() => (Array.isArray(copySelectedKeys.value) ? copySelectedKeys.value.length : 0))
const canCopySelected = computed(() => copySelectedCount.value >= 2)
const saveSelectedCount = computed(() => Object.keys(saveSelectedKeyMap.value || {}).length)
const canSaveSelected = computed(() => saveSelectedCount.value > 0)
function markDirty (row) {
const key = String(row?.__key || '').trim()
if (!key) return
dirtyMap.value = { ...(dirtyMap.value || {}), [key]: true }
}
function clearDirty () {
dirtyMap.value = {}
}
function isCopySelected (rowKey) {
const key = String(rowKey || '').trim()
if (!key) return false
return (Array.isArray(copySelectedKeys.value) ? copySelectedKeys.value : []).includes(key)
}
function toggleCopySelected (rowKey, enabled) {
const key = String(rowKey || '').trim()
if (!key) return
const current = Array.isArray(copySelectedKeys.value) ? [...copySelectedKeys.value] : []
const idx = current.indexOf(key)
if (enabled) {
if (idx === -1) current.push(key)
} else {
if (idx >= 0) current.splice(idx, 1)
}
copySelectedKeys.value = current
}
function copyRoleLabel (rowKey) {
const key = String(rowKey || '').trim()
if (!key) return ''
const keys = Array.isArray(copySelectedKeys.value) ? copySelectedKeys.value : []
const idx = keys.indexOf(key)
if (idx === 0) return 'Kopyalanacak'
if (idx > 0) return 'Yapistirilacak'
return ''
}
function isSaveSelected (rowKey) {
const key = String(rowKey || '').trim()
if (!key) return false
return !!saveSelectedKeyMap.value?.[key]
}
function toggleSaveSelected (rowKey, enabled) {
const key = String(rowKey || '').trim()
if (!key) return
const next = { ...(saveSelectedKeyMap.value || {}) }
if (enabled) next[key] = true
else delete next[key]
saveSelectedKeyMap.value = next
}
function copySelectedToSelected () {
const keys = Array.isArray(copySelectedKeys.value) ? copySelectedKeys.value : []
if (keys.length < 2) return
const srcKey = String(keys[0] || '').trim()
if (!srcKey) return
const srcBolum = bolumByKey.value?.[srcKey] || []
const srcHam = hammaddeByKey.value?.[srcKey] || []
for (let i = 1; i < keys.length; i++) {
const key = String(keys[i] || '').trim()
if (!key) continue
updateBolumSelection(key, srcBolum)
updateHammaddeSelection(key, srcHam)
const row = (Array.isArray(mappings.value) ? mappings.value : []).find(r => String(r?.__key || '') === key)
if (row) markDirty(row)
}
$q.notify({ type: 'positive', message: 'Kopyala / Yapistir tamamlandi' })
}
async function saveSelected () {
const keys = Object.keys(saveSelectedKeyMap.value || {})
await saveKeys(keys)
}
function selectAllMTBolum (rowKey) {
const key = String(rowKey || '').trim()
if (!key) return
const all = (Array.isArray(mtBolumOptions.value) ? mtBolumOptions.value : [])
.map(o => Number(o?.value))
.filter(n => Number.isFinite(n) && n > 0)
updateBolumSelection(key, all)
const row = (Array.isArray(mappings.value) ? mappings.value : []).find(r => String(r?.__key || '') === key)
if (row) markDirty(row)
}
function clearMTBolum (rowKey) {
const key = String(rowKey || '').trim()
if (!key) return
updateBolumSelection(key, [])
const row = (Array.isArray(mappings.value) ? mappings.value : []).find(r => String(r?.__key || '') === key)
if (row) markDirty(row)
}
function selectAllHammadde (rowKey) {
const key = String(rowKey || '').trim()
if (!key) return
const all = (Array.isArray(hammaddeOptions.value) ? hammaddeOptions.value : [])
.map(o => Number(o?.value))
.filter(n => Number.isFinite(n) && n > 0)
updateHammaddeSelection(key, all)
const row = (Array.isArray(mappings.value) ? mappings.value : []).find(r => String(r?.__key || '') === key)
if (row) markDirty(row)
}
function clearHammadde (rowKey) {
const key = String(rowKey || '').trim()
if (!key) return
updateHammaddeSelection(key, [])
const row = (Array.isArray(mappings.value) ? mappings.value : []).find(r => String(r?.__key || '') === key)
if (row) markDirty(row)
}
function normalizeIntList (list) {
const arr = Array.isArray(list) ? list : []
const nums = arr
.map(x => Number(String(x).trim()))
.filter(n => Number.isFinite(n) && n > 0)
return Array.from(new Set(nums))
}
function initEditableStateFromRows (rows) {
const bolum = {}
const ham = {}
;(Array.isArray(rows) ? rows : []).forEach(r => {
const key = String(r?.__key || '').trim()
if (!key) return
bolum[key] = normalizeIntList(r?.nUrtMTBolumIDs || [])
ham[key] = normalizeIntList(r?.nHammaddeTurleri || [])
})
bolumByKey.value = bolum
hammaddeByKey.value = ham
}
function updateBolumSelection (key, newValue) {
const k = String(key || '').trim()
if (!k) return
bolumByKey.value = {
...(bolumByKey.value || {}),
[k]: normalizeIntList(newValue)
}
}
function updateHammaddeSelection (key, newValue) {
const k = String(key || '').trim()
if (!k) return
hammaddeByKey.value = {
...(hammaddeByKey.value || {}),
[k]: normalizeIntList(newValue)
}
}
// label resolution now handled by options' `label` field + selected-item slot (see UserDetail.vue "Piyasalar").
async function fetchMappings () {
loading.value = true
try {
const data = await get('/pricing/production-product-costing/maliyet-parca-eslestirme', {
trace_id: traceId,
only_active: 1
})
return Array.isArray(data) ? data : []
} catch (e) {
const detail = await extractApiErrorDetail(e)
$q.notify({ type: 'negative', message: detail || 'Liste okunamadi' })
return []
} finally {
loading.value = false
}
}
async function fetchSheet () {
loading.value = true
try {
// Search changes should not carry over row-level selections from a previous sheet.
copySelectedKeys.value = []
saveSelectedKeyMap.value = {}
const [combos, existing] = await Promise.all([
get('/pricing/production-product-costing/options/urun-ana-alt-combos', {
trace_id: traceId,
search: String(filters.value.search || '').trim(),
limit: 5000
}),
fetchMappings()
])
const existingByKey = new Map()
;(Array.isArray(existing) ? existing : []).forEach(x => {
const k = `${String(x?.urunIlkGrubu || '').trim()}|${String(x?.urunAltGrubu || '').trim()}|${String(x?.urunAnaGrubu || '').trim()}`
const prev = existingByKey.get(k)
const nextList = Array.isArray(prev) ? prev : (prev ? [prev] : [])
nextList.push(x)
existingByKey.set(k, nextList)
})
const rows = (Array.isArray(combos) ? combos : []).map((c, idx) => {
const ilk = String(c?.urunIlkGrubu || '').trim()
const ana = String(c?.urunAnaGrubu || '').trim()
const alt = String(c?.urunAltGrubu || '').trim()
const k = `${ilk}|${alt}|${ana}`
const hitList = existingByKey.get(k)
const list = Array.isArray(hitList) ? hitList : []
const mtIds = list
.map(x => String(x?.nUrtMTBolumID || '').trim())
.filter(Boolean)
const uniqMt = Array.from(new Set(mtIds))
// If multiple mappings exist, we merge hammadde types (union) for editing convenience.
const hSet = new Set()
list.forEach(x => {
if (Array.isArray(x?.nHammaddeTurleri)) {
x.nHammaddeTurleri.forEach(v => {
v = String(v || '').trim()
if (v) hSet.add(v)
})
}
})
return {
__key: k,
__rowIndex: idx,
__existingIDs: list.map(x => Number(x?.id || 0)).filter(n => n > 0),
urunIlkGrubu: ilk,
urunAnaGrubu: ana,
urunAltGrubu: alt,
nUrtMTBolumIDs: uniqMt,
nHammaddeTurleri: Array.from(hSet)
}
})
mappings.value = rows
initEditableStateFromRows(rows)
clearDirty()
} catch (e) {
const detail = await extractApiErrorDetail(e)
$q.notify({ type: 'negative', message: detail || 'Carsaf yuklenemedi' })
} finally {
loading.value = false
}
}
function onSearchCleared () {
filters.value.search = ''
copySelectedKeys.value = []
saveSelectedKeyMap.value = {}
clearDirty()
fetchSheet()
}
async function refreshAll () {
await Promise.all([
fetchMTBolumOptions(''),
fetchHammaddeOptions('')
])
await fetchSheet()
}
async function fetchMTBolumOptions (search) {
mtBolumLoading.value = true
try {
const data = await get('/pricing/production-product-costing/options/mtbolum', {
trace_id: traceId,
search: search || '',
limit: 200
})
mtBolumOptions.value = Array.isArray(data)
? data.map(x => ({
value: Number(x?.value ?? x?.nUrtMTBolumID ?? x?.id ?? 0),
label: (() => {
const v = Number(x?.value ?? x?.nUrtMTBolumID ?? x?.id ?? 0)
const name = String(x?.label || x?.sAdi || '').trim()
if (Number.isFinite(v) && v > 0 && name) return `${v} - ${name}`
if (Number.isFinite(v) && v > 0) return String(v)
return name
})()
}))
.filter(x => Number.isFinite(x.value) && x.value > 0)
.sort((a, b) => a.value - b.value)
: []
} finally {
mtBolumLoading.value = false
}
}
async function fetchHammaddeOptions (search) {
hammaddeLoading.value = true
try {
const data = await get('/pricing/production-product-costing/detail-editor-options', {
trace_id: traceId,
kind: 'hammadde',
search: search || '',
limit: 200
})
hammaddeOptions.value = Array.isArray(data)
? data.map(x => ({
value: Number(String(x?.nHammaddeTuruNo ?? x?.value ?? '').trim()),
label: (() => {
const v = Number(String(x?.nHammaddeTuruNo ?? x?.value ?? '').trim())
const name = String(x?.sHammaddeTuruAdi || x?.label || '').trim()
if (Number.isFinite(v) && v > 0 && name) return `${v} - ${name}`
if (Number.isFinite(v) && v > 0) return String(v)
return name
})()
}))
.filter(x => Number.isFinite(x.value) && x.value > 0)
.sort((a, b) => a.value - b.value)
: []
} finally {
hammaddeLoading.value = false
}
}
function onFilterMTBolum (val, update) {
update(async () => {
await fetchMTBolumOptions(val)
})
}
function onFilterHammadde (val, update) {
update(async () => {
await fetchHammaddeOptions(val)
})
}
async function saveAll () {
const dirtyKeys = Object.keys(dirtyMap.value || {})
await saveKeys(dirtyKeys)
}
async function saveKeys (keys) {
const list = Array.isArray(keys) ? keys.map(k => String(k || '').trim()).filter(Boolean) : []
if (list.length === 0) return
saving.value = true
try {
const rowsToSave = mappings.value.filter(r => list.includes(String(r.__key || '').trim()))
for (const row of rowsToSave) {
const key = String(row.__key || '').trim()
const mtIds = normalizeIntList(bolumByKey.value?.[key] || [])
const hList = normalizeIntList(hammaddeByKey.value?.[key] || [])
// If hammadde cleared OR no bölüm selected: delete all existing mappings for this combo
const existingIDs = Array.isArray(row.__existingIDs) ? row.__existingIDs : []
if (existingIDs.length > 0 && (mtIds.length === 0 || hList.length === 0)) {
for (const id of existingIDs) {
await del('/pricing/production-product-costing/maliyet-parca-eslestirme', { trace_id: traceId, id })
}
row.__existingIDs = []
bolumByKey.value = { ...(bolumByKey.value || {}), [key]: [] }
hammaddeByKey.value = { ...(hammaddeByKey.value || {}), [key]: [] }
continue
}
if (mtIds.length > 0 && hList.length > 0) {
const existing = await fetchMappings()
const ilk = String(row.urunIlkGrubu || '').trim()
const ana = String(row.urunAnaGrubu || '').trim()
const alt = String(row.urunAltGrubu || '').trim()
const existingForCombo = (Array.isArray(existing) ? existing : []).filter(x =>
String(x?.urunIlkGrubu || '').trim() === ilk &&
String(x?.urunAnaGrubu || '').trim() === ana &&
String(x?.urunAltGrubu || '').trim() === alt
)
const selectedSet = new Set(mtIds.map(String))
for (const ex of existingForCombo) {
const exMt = String(ex?.nUrtMTBolumID || '').trim()
if (exMt && !selectedSet.has(exMt) && Number(ex?.id || 0) > 0) {
await del('/pricing/production-product-costing/maliyet-parca-eslestirme', { trace_id: traceId, id: Number(ex.id) })
}
}
const idsAfter = []
for (const mtId of mtIds) {
const payload = {
urunIlkGrubu: ilk,
urunAnaGrubu: ana,
urunAltGrubu: alt,
nUrtMTBolumID: mtId,
nHammaddeTurleri: hList,
bAktif: true
}
const resp = await post('/pricing/production-product-costing/maliyet-parca-eslestirme/upsert', payload, { trace_id: traceId })
if (resp?.id) idsAfter.push(Number(resp.id))
}
row.__existingIDs = idsAfter
}
}
$q.notify({ type: 'positive', message: 'Degisiklikler kaydedildi' })
clearDirty()
// after saving, clear save selection to avoid accidental re-save
saveSelectedKeyMap.value = {}
await refreshAll()
} catch (e) {
const detail = await extractApiErrorDetail(e)
$q.notify({ type: 'negative', message: detail || 'Kaydetme basarisiz' })
} finally {
saving.value = false
}
}
onMounted(async () => {
await Promise.all([
fetchMTBolumOptions(''),
fetchHammaddeOptions(''),
fetchSheet()
])
})
</script>
<style scoped>
.pcmm-page {
background: #fafafa;
}
.pcmm-header {
max-width: 1200px;
margin: 0 auto;
}
.pcmm-form {
max-width: 1200px;
margin: 0 auto;
}
.pcmm-table {
max-width: 1200px;
margin: 0 auto;
}
/* Allow multi-select chips to wrap and grow vertically (PowerBI-like) */
.pcmm-table :deep(.pcmm-multi-select .q-field__control) {
height: auto !important;
min-height: 38px;
}
.pcmm-table :deep(.pcmm-multi-select .q-field__native) {
align-items: flex-start;
}
.pcmm-table :deep(.pcmm-multi-select .q-select__chips) {
flex-wrap: wrap;
}
.pcmm-table :deep(.pcmm-multi-select .q-chip) {
max-width: 100%;
}
</style>

View 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>

View File

@@ -324,6 +324,42 @@ const routes = [
component: () => import('pages/ProductPricing.vue'),
meta: { permission: 'order:view' }
},
{
path: 'pricing/production-product-costing',
name: 'production-product-costing',
component: () => import('pages/ProductionProductCosting.vue'),
meta: { permission: 'order:view' }
},
{
path: 'pricing/production-product-costing/has-cost',
name: 'production-product-costing-has-cost',
component: () => import('pages/ProductionProductCostingHasCost.vue'),
meta: { permission: 'order:view' }
},
{
path: 'pricing/production-product-costing/has-cost/history',
name: 'production-product-costing-has-cost-history',
component: () => import('pages/ProductionProductCostingHasCostHistory.vue'),
meta: { permission: 'order:view' }
},
{
path: 'pricing/production-product-costing/has-cost/detail',
name: 'production-product-costing-has-cost-detail',
component: () => import('pages/ProductionProductCostingHasCostDetail.vue'),
meta: { permission: 'order:view' }
},
{
path: 'pricing/production-product-costing/no-cost',
name: 'production-product-costing-no-cost',
component: () => import('pages/ProductionProductCostingNoCost.vue'),
meta: { permission: 'order:view' }
},
{
path: 'pricing/production-product-costing/maliyet-parca-eslestirme',
name: 'production-product-costing-maliyet-parca-eslestirme',
component: () => import('pages/ProductionProductCostingMTBolumMapping.vue'),
meta: { permission: 'order:view' }
},
/* ================= PASSWORD ================= */

View File

@@ -2,6 +2,7 @@ import axios from 'axios'
import qs from 'qs'
import { useAuthStore } from 'stores/authStore'
import { DEFAULT_LOCALE, normalizeLocale } from 'src/i18n/languages'
import { slog } from 'src/utils/slog'
const rawBaseUrl =
(typeof process !== 'undefined' && process.env?.VITE_API_BASE_URL) || '/api'
@@ -81,6 +82,20 @@ function getRequestLocale() {
return normalizeLocale(window.localStorage.getItem(LOCALE_STORAGE_KEY))
}
function extractTraceIdFromConfig(config) {
const rawTraceId =
config?.params?.trace_id ||
config?.headers?.['X-Trace-ID'] ||
config?.headers?.['x-trace-id'] ||
''
return String(rawTraceId || '').trim()
}
function shouldStructuredLogRequest(config) {
return extractTraceIdFromConfig(config) !== ''
}
api.interceptors.request.use((config) => {
const auth = useAuthStore()
const url = config.url || ''
@@ -92,6 +107,22 @@ api.interceptors.request.use((config) => {
config.headers ||= {}
config.headers['Accept-Language'] = getRequestLocale()
const traceId = extractTraceIdFromConfig(config)
config.__traceId = traceId
config.__startedAt = Date.now()
if (traceId) {
config.headers['X-Trace-ID'] = traceId
}
if (shouldStructuredLogRequest(config)) {
slog.info('production-product-costing.api', 'request:start', {
trace_id: traceId,
method: String(config.method || 'GET').toUpperCase(),
url,
params: config.params || {}
})
}
return config
})
@@ -148,7 +179,19 @@ function clearSessionAndRedirect() {
}
api.interceptors.response.use(
r => r,
(response) => {
const requestConfig = response?.config || {}
if (shouldStructuredLogRequest(requestConfig)) {
slog.info('production-product-costing.api', 'request:ok', {
trace_id: requestConfig.__traceId || extractTraceIdFromConfig(requestConfig),
method: String(requestConfig.method || 'GET').toUpperCase(),
url: String(requestConfig.url || ''),
status: response?.status || 200,
duration_ms: Math.max(0, Date.now() - Number(requestConfig.__startedAt || Date.now()))
})
}
return response
},
async (error) => {
const requestConfig = error?.config || {}
const status = error?.response?.status
@@ -169,6 +212,22 @@ api.interceptors.response.use(
console.error(`API ${status || '-'} ${method} ${requestUrl}: ${detail}`)
}
if (shouldStructuredLogRequest(requestConfig)) {
const detail = sanitizeApiErrorDetail(
await extractApiErrorDetail(error),
status
)
error.parsedMessage ||= detail
slog.error('production-product-costing.api', 'request:error', {
trace_id: requestConfig.__traceId || extractTraceIdFromConfig(requestConfig),
method: String(requestConfig.method || 'GET').toUpperCase(),
url: requestUrl,
status: status || 0,
duration_ms: Math.max(0, Date.now() - Number(requestConfig.__startedAt || Date.now())),
detail
})
}
const shouldTryRefresh =
status === 401 &&
!requestConfig._retry &&

53
ui/src/utils/slog.js Normal file
View File

@@ -0,0 +1,53 @@
const isDev =
typeof process !== 'undefined' &&
Boolean(process.env?.DEV)
function emit(level, scope, message, data = {}) {
const payload = {
time: new Date().toISOString(),
level,
scope,
message,
...data
}
const text = `[${scope}] ${message}`
if (level === 'error') {
console.error(text, payload)
return
}
if (level === 'warn') {
console.warn(text, payload)
return
}
if (level === 'debug' && !isDev) {
return
}
console.info(text, payload)
}
export function createTraceId(prefix = 'trace') {
const rawId =
typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
? crypto.randomUUID()
: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`
return `${prefix}-${rawId}`
}
export const slog = {
debug(scope, message, data = {}) {
emit('debug', scope, message, data)
},
info(scope, message, data = {}) {
emit('info', scope, message, data)
},
warn(scope, message, data = {}) {
emit('warn', scope, message, data)
},
error(scope, message, data = {}) {
emit('error', scope, message, data)
}
}