Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -146,7 +146,9 @@ createQuasarApp(createApp, quasarUserOptions)
|
||||
|
||||
return Promise[ method ]([
|
||||
|
||||
import(/* webpackMode: "eager" */ 'boot/dayjs')
|
||||
import(/* webpackMode: "eager" */ 'boot/dayjs'),
|
||||
|
||||
import(/* webpackMode: "eager" */ 'boot/resizeObserverGuard')
|
||||
|
||||
]).then(bootFiles => {
|
||||
const boot = mapFn(bootFiles).filter(entry => typeof entry === 'function')
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* THIS FILE IS GENERATED AUTOMATICALLY.
|
||||
* DO NOT EDIT.
|
||||
*
|
||||
* You are probably looking on adding startup/initialization code.
|
||||
* Use "quasar new boot <name>" and add it there.
|
||||
* One boot file per concern. Then reference the file(s) in quasar.config file > boot:
|
||||
* boot: ['file', ...] // do not add ".js" extension to it.
|
||||
*
|
||||
* Boot files are your "main.js"
|
||||
**/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
import { Quasar } from 'quasar'
|
||||
import { markRaw } from 'vue'
|
||||
import RootComponent from 'app/src/App.vue'
|
||||
|
||||
import createStore from 'app/src/stores/index'
|
||||
import createRouter from 'app/src/router/index'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export default async function (createAppFn, quasarUserOptions) {
|
||||
|
||||
|
||||
// Create the app instance.
|
||||
// Here we inject into it the Quasar UI, the router & possibly the store.
|
||||
const app = createAppFn(RootComponent)
|
||||
|
||||
|
||||
|
||||
app.use(Quasar, quasarUserOptions)
|
||||
|
||||
|
||||
|
||||
|
||||
const store = typeof createStore === 'function'
|
||||
? await createStore({})
|
||||
: createStore
|
||||
|
||||
|
||||
app.use(store)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const router = markRaw(
|
||||
typeof createRouter === 'function'
|
||||
? await createRouter({store})
|
||||
: createRouter
|
||||
)
|
||||
|
||||
|
||||
// make router instance available in store
|
||||
|
||||
store.use(({ store }) => { store.router = router })
|
||||
|
||||
|
||||
|
||||
// Expose the app, the router and the store.
|
||||
// Note that we are not mounting the app here, since bootstrapping will be
|
||||
// different depending on whether we are in a browser or on the server.
|
||||
return {
|
||||
app,
|
||||
store,
|
||||
router
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* THIS FILE IS GENERATED AUTOMATICALLY.
|
||||
* DO NOT EDIT.
|
||||
*
|
||||
* You are probably looking on adding startup/initialization code.
|
||||
* Use "quasar new boot <name>" and add it there.
|
||||
* One boot file per concern. Then reference the file(s) in quasar.config file > boot:
|
||||
* boot: ['file', ...] // do not add ".js" extension to it.
|
||||
*
|
||||
* Boot files are your "main.js"
|
||||
**/
|
||||
|
||||
|
||||
import { createApp } from 'vue'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
import '@quasar/extras/roboto-font/roboto-font.css'
|
||||
|
||||
import '@quasar/extras/material-icons/material-icons.css'
|
||||
|
||||
|
||||
|
||||
|
||||
// We load Quasar stylesheet file
|
||||
import 'quasar/dist/quasar.sass'
|
||||
|
||||
|
||||
|
||||
|
||||
import 'src/css/app.css'
|
||||
|
||||
|
||||
import createQuasarApp from './app.js'
|
||||
import quasarUserOptions from './quasar-user-options.js'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const publicPath = `/`
|
||||
|
||||
|
||||
async function start ({
|
||||
app,
|
||||
router
|
||||
, store
|
||||
}, bootFiles) {
|
||||
|
||||
let hasRedirected = false
|
||||
const getRedirectUrl = url => {
|
||||
try { return router.resolve(url).href }
|
||||
catch (err) {}
|
||||
|
||||
return Object(url) === url
|
||||
? null
|
||||
: url
|
||||
}
|
||||
const redirect = url => {
|
||||
hasRedirected = true
|
||||
|
||||
if (typeof url === 'string' && /^https?:\/\//.test(url)) {
|
||||
window.location.href = url
|
||||
return
|
||||
}
|
||||
|
||||
const href = getRedirectUrl(url)
|
||||
|
||||
// continue if we didn't fail to resolve the url
|
||||
if (href !== null) {
|
||||
window.location.href = href
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
|
||||
const urlPath = window.location.href.replace(window.location.origin, '')
|
||||
|
||||
for (let i = 0; hasRedirected === false && i < bootFiles.length; i++) {
|
||||
try {
|
||||
await bootFiles[i]({
|
||||
app,
|
||||
router,
|
||||
store,
|
||||
ssrContext: null,
|
||||
redirect,
|
||||
urlPath,
|
||||
publicPath
|
||||
})
|
||||
}
|
||||
catch (err) {
|
||||
if (err && err.url) {
|
||||
redirect(err.url)
|
||||
return
|
||||
}
|
||||
|
||||
console.error('[Quasar] boot error:', err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (hasRedirected === true) return
|
||||
|
||||
|
||||
app.use(router)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
app.mount('#q-app')
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
createQuasarApp(createApp, quasarUserOptions)
|
||||
|
||||
.then(app => {
|
||||
// eventually remove this when Cordova/Capacitor/Electron support becomes old
|
||||
const [ method, mapFn ] = Promise.allSettled !== void 0
|
||||
? [
|
||||
'allSettled',
|
||||
bootFiles => bootFiles.map(result => {
|
||||
if (result.status === 'rejected') {
|
||||
console.error('[Quasar] boot error:', result.reason)
|
||||
return
|
||||
}
|
||||
return result.value.default
|
||||
})
|
||||
]
|
||||
: [
|
||||
'all',
|
||||
bootFiles => bootFiles.map(entry => entry.default)
|
||||
]
|
||||
|
||||
return Promise[ method ]([
|
||||
|
||||
import(/* webpackMode: "eager" */ 'boot/dayjs')
|
||||
|
||||
]).then(bootFiles => {
|
||||
const boot = mapFn(bootFiles).filter(entry => typeof entry === 'function')
|
||||
start(app, boot)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* THIS FILE IS GENERATED AUTOMATICALLY.
|
||||
* DO NOT EDIT.
|
||||
*
|
||||
* You are probably looking on adding startup/initialization code.
|
||||
* Use "quasar new boot <name>" and add it there.
|
||||
* One boot file per concern. Then reference the file(s) in quasar.config file > boot:
|
||||
* boot: ['file', ...] // do not add ".js" extension to it.
|
||||
*
|
||||
* Boot files are your "main.js"
|
||||
**/
|
||||
|
||||
|
||||
|
||||
import App from 'app/src/App.vue'
|
||||
let appPrefetch = typeof App.preFetch === 'function'
|
||||
? App.preFetch
|
||||
: (
|
||||
// Class components return the component options (and the preFetch hook) inside __c property
|
||||
App.__c !== void 0 && typeof App.__c.preFetch === 'function'
|
||||
? App.__c.preFetch
|
||||
: false
|
||||
)
|
||||
|
||||
|
||||
function getMatchedComponents (to, router) {
|
||||
const route = to
|
||||
? (to.matched ? to : router.resolve(to).route)
|
||||
: router.currentRoute.value
|
||||
|
||||
if (!route) { return [] }
|
||||
|
||||
const matched = route.matched.filter(m => m.components !== void 0)
|
||||
|
||||
if (matched.length === 0) { return [] }
|
||||
|
||||
return Array.prototype.concat.apply([], matched.map(m => {
|
||||
return Object.keys(m.components).map(key => {
|
||||
const comp = m.components[key]
|
||||
return {
|
||||
path: m.path,
|
||||
c: comp
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
export function addPreFetchHooks ({ router, store, publicPath }) {
|
||||
// Add router hook for handling preFetch.
|
||||
// Doing it after initial route is resolved so that we don't double-fetch
|
||||
// the data that we already have. Using router.beforeResolve() so that all
|
||||
// async components are resolved.
|
||||
router.beforeResolve((to, from, next) => {
|
||||
const
|
||||
urlPath = window.location.href.replace(window.location.origin, ''),
|
||||
matched = getMatchedComponents(to, router),
|
||||
prevMatched = getMatchedComponents(from, router)
|
||||
|
||||
let diffed = false
|
||||
const preFetchList = matched
|
||||
.filter((m, i) => {
|
||||
return diffed || (diffed = (
|
||||
!prevMatched[i] ||
|
||||
prevMatched[i].c !== m.c ||
|
||||
m.path.indexOf('/:') > -1 // does it has params?
|
||||
))
|
||||
})
|
||||
.filter(m => m.c !== void 0 && (
|
||||
typeof m.c.preFetch === 'function'
|
||||
// Class components return the component options (and the preFetch hook) inside __c property
|
||||
|| (m.c.__c !== void 0 && typeof m.c.__c.preFetch === 'function')
|
||||
))
|
||||
.map(m => m.c.__c !== void 0 ? m.c.__c.preFetch : m.c.preFetch)
|
||||
|
||||
|
||||
if (appPrefetch !== false) {
|
||||
preFetchList.unshift(appPrefetch)
|
||||
appPrefetch = false
|
||||
}
|
||||
|
||||
|
||||
if (preFetchList.length === 0) {
|
||||
return next()
|
||||
}
|
||||
|
||||
let hasRedirected = false
|
||||
const redirect = url => {
|
||||
hasRedirected = true
|
||||
next(url)
|
||||
}
|
||||
const proceed = () => {
|
||||
|
||||
if (hasRedirected === false) { next() }
|
||||
}
|
||||
|
||||
|
||||
|
||||
preFetchList.reduce(
|
||||
(promise, preFetch) => promise.then(() => hasRedirected === false && preFetch({
|
||||
store,
|
||||
currentRoute: to,
|
||||
previousRoute: from,
|
||||
redirect,
|
||||
urlPath,
|
||||
publicPath
|
||||
})),
|
||||
Promise.resolve()
|
||||
)
|
||||
.then(proceed)
|
||||
.catch(e => {
|
||||
console.error(e)
|
||||
proceed()
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* THIS FILE IS GENERATED AUTOMATICALLY.
|
||||
* DO NOT EDIT.
|
||||
*
|
||||
* You are probably looking on adding startup/initialization code.
|
||||
* Use "quasar new boot <name>" and add it there.
|
||||
* One boot file per concern. Then reference the file(s) in quasar.config file > boot:
|
||||
* boot: ['file', ...] // do not add ".js" extension to it.
|
||||
*
|
||||
* Boot files are your "main.js"
|
||||
**/
|
||||
|
||||
import lang from 'quasar/lang/tr.js'
|
||||
|
||||
|
||||
|
||||
import {Loading,Dialog,Notify} from 'quasar'
|
||||
|
||||
|
||||
|
||||
export default { config: {"notify":{"position":"top","timeout":2500}},lang,plugins: {Loading,Dialog,Notify} }
|
||||
|
||||
@@ -15,7 +15,7 @@ export default defineConfig(() => {
|
||||
/* =====================================================
|
||||
BOOT FILES
|
||||
===================================================== */
|
||||
boot: ['dayjs'],
|
||||
boot: ['dayjs', 'resizeObserverGuard'],
|
||||
|
||||
/* =====================================================
|
||||
GLOBAL CSS
|
||||
@@ -56,6 +56,13 @@ export default defineConfig(() => {
|
||||
server: { type: 'http' },
|
||||
port: 9000,
|
||||
open: true,
|
||||
client: {
|
||||
overlay: {
|
||||
errors: true,
|
||||
warnings: false,
|
||||
runtimeErrors: false
|
||||
}
|
||||
},
|
||||
|
||||
// DEV proxy (CORS'suz)
|
||||
proxy: [
|
||||
|
||||
@@ -27,7 +27,7 @@ var quasar_config_default = defineConfig(() => {
|
||||
/* =====================================================
|
||||
BOOT FILES
|
||||
===================================================== */
|
||||
boot: ["dayjs"],
|
||||
boot: ["dayjs", "resizeObserverGuard"],
|
||||
/* =====================================================
|
||||
GLOBAL CSS
|
||||
===================================================== */
|
||||
@@ -62,6 +62,13 @@ var quasar_config_default = defineConfig(() => {
|
||||
server: { type: "http" },
|
||||
port: 9e3,
|
||||
open: true,
|
||||
client: {
|
||||
overlay: {
|
||||
errors: true,
|
||||
warnings: false,
|
||||
runtimeErrors: false
|
||||
}
|
||||
},
|
||||
// DEV proxy (CORS'suz)
|
||||
proxy: [
|
||||
{
|
||||
36
ui/src/boot/resizeObserverGuard.js
Normal file
36
ui/src/boot/resizeObserverGuard.js
Normal file
@@ -0,0 +1,36 @@
|
||||
export default () => {
|
||||
if (process.env.PROD || typeof window === 'undefined') return
|
||||
|
||||
const isResizeObserverOverlayError = (message) => {
|
||||
const text = String(message || '')
|
||||
return (
|
||||
text.includes('ResizeObserver loop completed with undelivered notifications') ||
|
||||
text.includes('ResizeObserver loop limit exceeded')
|
||||
)
|
||||
}
|
||||
|
||||
window.addEventListener(
|
||||
'error',
|
||||
(event) => {
|
||||
if (!isResizeObserverOverlayError(event?.message)) return
|
||||
event.preventDefault()
|
||||
event.stopImmediatePropagation()
|
||||
},
|
||||
true
|
||||
)
|
||||
|
||||
window.addEventListener(
|
||||
'unhandledrejection',
|
||||
(event) => {
|
||||
const reason = event?.reason
|
||||
const msg =
|
||||
typeof reason === 'string'
|
||||
? reason
|
||||
: (reason?.message || reason?.toString?.() || '')
|
||||
if (!isResizeObserverOverlayError(msg)) return
|
||||
event.preventDefault()
|
||||
event.stopImmediatePropagation()
|
||||
},
|
||||
true
|
||||
)
|
||||
}
|
||||
@@ -278,8 +278,8 @@
|
||||
color="primary"
|
||||
icon="save"
|
||||
class="q-ml-sm"
|
||||
:loading="orderStore.loading"
|
||||
:disable="!canSubmitOrder"
|
||||
:loading="orderStore.loading || isSubmitAllInFlight"
|
||||
:disable="!canSubmitOrder || orderStore.loading || isSubmitAllInFlight"
|
||||
@click="confirmAndSubmit"
|
||||
/>
|
||||
</div>
|
||||
@@ -773,16 +773,18 @@
|
||||
v-if="canMutateRows"
|
||||
:color="isEditing ? 'positive' : 'primary'"
|
||||
:label="isEditing ? 'Güncelle' : 'Kaydet'"
|
||||
:loading="isRowSaveInFlight"
|
||||
@click="onSaveOrUpdateRow"
|
||||
:disable="isClosedRow || isViewOnly || !canMutateRows"
|
||||
:disable="isClosedRow || isViewOnly || !canMutateRows || isRowSaveInFlight"
|
||||
|
||||
/>
|
||||
<q-btn
|
||||
v-if="canMutateRows"
|
||||
color="secondary"
|
||||
label="Kaydet ve Diğer Renge Geç"
|
||||
:loading="isRowSaveInFlight"
|
||||
@click="onSaveAndNextColor"
|
||||
:disable="isClosedRow || isViewOnly || !canMutateRows"
|
||||
:disable="isClosedRow || isViewOnly || !canMutateRows || isRowSaveInFlight"
|
||||
/>
|
||||
<q-btn
|
||||
v-if="isEditing && canMutateRows"
|
||||
@@ -930,8 +932,60 @@ const aktifPB = ref('USD') // Varsayılan para birimi (Cari seç
|
||||
const productCache = reactive({})
|
||||
const showBulkDueDateDialog = ref(false)
|
||||
const bulkDueDateValue = ref('')
|
||||
const isSubmitAllInFlight = ref(false)
|
||||
const isRowSaveInFlight = ref(false)
|
||||
|
||||
function showEditorQtyPriceBlockingDialog(message, details = '') {
|
||||
const detailHtml = details ? `<br><br><b>Detay:</b><br>${details}` : ''
|
||||
$q.dialog({
|
||||
title: 'Kayit Engellendi',
|
||||
message: `${message}${detailHtml}`,
|
||||
html: true,
|
||||
ok: { label: 'Tamam', color: 'negative' }
|
||||
})
|
||||
}
|
||||
|
||||
function validateEditorRowBeforeSave() {
|
||||
const adet = Number(form.adet || 0)
|
||||
const fiyatRaw = String(form.fiyat ?? '').trim()
|
||||
const fiyat = Number(form.fiyat || 0)
|
||||
|
||||
if (adet <= 0) {
|
||||
showEditorQtyPriceBlockingDialog('Siparis adeti toplam 0 olamaz.')
|
||||
return false
|
||||
}
|
||||
if (!fiyatRaw || !Number.isFinite(fiyat) || fiyat <= 0) {
|
||||
showEditorQtyPriceBlockingDialog('Urun fiyati girmeden ilerleyemezsiniz.')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function validateSummaryRowsBeforeSubmit() {
|
||||
const rows = Array.isArray(orderStore.summaryRows) ? orderStore.summaryRows : []
|
||||
const invalidRows = rows.filter(r => {
|
||||
const adet = Number(r?.adet || 0)
|
||||
const fiyatRaw = String(r?.fiyat ?? '').trim()
|
||||
const fiyat = Number(r?.fiyat || 0)
|
||||
return adet <= 0 || !fiyatRaw || !Number.isFinite(fiyat) || fiyat <= 0
|
||||
})
|
||||
|
||||
if (!invalidRows.length) return true
|
||||
|
||||
const preview = invalidRows
|
||||
.slice(0, 8)
|
||||
.map(r => `${String(r?.model || '').trim() || '-'} / ${String(r?.renk || '').trim() || '-'} (adet=${Number(r?.adet || 0)}, fiyat=${String(r?.fiyat ?? '')})`)
|
||||
.join('<br>')
|
||||
|
||||
showEditorQtyPriceBlockingDialog(
|
||||
'Urun fiyati girmeden ilerleyemezsiniz.',
|
||||
preview
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
const confirmAndSubmit = async () => {
|
||||
if (orderStore.loading) return
|
||||
if (orderStore.loading || isSubmitAllInFlight.value) return
|
||||
|
||||
if (!hasSubmitPermission()) {
|
||||
notifyNoPermission(
|
||||
@@ -951,6 +1005,11 @@ const confirmAndSubmit = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
if (!validateSummaryRowsBeforeSubmit()) {
|
||||
return
|
||||
}
|
||||
|
||||
isSubmitAllInFlight.value = true
|
||||
try {
|
||||
// NEW veya EDIT ayrımı store.mode üzerinden
|
||||
await orderStore.submitAllReal(
|
||||
@@ -962,6 +1021,8 @@ const confirmAndSubmit = async () => {
|
||||
)
|
||||
} catch (err) {
|
||||
console.error('❌ confirmAndSubmit hata:', err)
|
||||
} finally {
|
||||
isSubmitAllInFlight.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3077,6 +3138,8 @@ function warnIfSecondColorMissing() {
|
||||
}
|
||||
|
||||
const onSaveOrUpdateRow = async () => {
|
||||
if (isRowSaveInFlight.value) return
|
||||
|
||||
if (!hasRowMutationPermission()) {
|
||||
notifyNoPermission(
|
||||
isEditMode.value
|
||||
@@ -3086,23 +3149,32 @@ const onSaveOrUpdateRow = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
if (!validateEditorRowBeforeSave()) return
|
||||
|
||||
warnIfSecondColorMissing()
|
||||
|
||||
await orderStore.saveOrUpdateRowUnified({
|
||||
form,
|
||||
isRowSaveInFlight.value = true
|
||||
try {
|
||||
const ok = await orderStore.saveOrUpdateRowUnified({
|
||||
form,
|
||||
|
||||
recalcVat: typeof recalcVat === 'function' ? recalcVat : null,
|
||||
resetEditor: typeof resetEditor === 'function' ? resetEditor : null,
|
||||
loadProductSizes: async () => {
|
||||
await orderStore.loadProductSizes(form, true, $q, productCache)
|
||||
await loadOrderInventory(true)
|
||||
},
|
||||
recalcVat: typeof recalcVat === 'function' ? recalcVat : null,
|
||||
resetEditor: typeof resetEditor === 'function' ? resetEditor : null,
|
||||
loadProductSizes: async () => {
|
||||
await orderStore.loadProductSizes(form, true, $q, productCache)
|
||||
await loadOrderInventory(true)
|
||||
},
|
||||
|
||||
// gerekiyorsa pass edebilirsin (store tarafında zaten optional)
|
||||
stockMap,
|
||||
$q
|
||||
})
|
||||
showEditor.value = false
|
||||
// gerekiyorsa pass edebilirsin (store tarafında zaten optional)
|
||||
stockMap,
|
||||
$q
|
||||
})
|
||||
if (ok !== false) {
|
||||
showEditor.value = false
|
||||
}
|
||||
} finally {
|
||||
isRowSaveInFlight.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeColorValue(val) {
|
||||
@@ -3122,6 +3194,8 @@ function getNextColorValue() {
|
||||
}
|
||||
|
||||
const onSaveAndNextColor = async () => {
|
||||
if (isRowSaveInFlight.value) return
|
||||
|
||||
if (!hasRowMutationPermission()) {
|
||||
notifyNoPermission(
|
||||
isEditMode.value
|
||||
@@ -3141,19 +3215,27 @@ const onSaveAndNextColor = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
if (!validateEditorRowBeforeSave()) return
|
||||
|
||||
warnIfSecondColorMissing()
|
||||
|
||||
const ok = await orderStore.saveOrUpdateRowUnified({
|
||||
form,
|
||||
recalcVat: typeof recalcVat === 'function' ? recalcVat : null,
|
||||
resetEditor: () => {},
|
||||
loadProductSizes: async () => {
|
||||
await orderStore.loadProductSizes(form, true, $q, productCache)
|
||||
await loadOrderInventory(true)
|
||||
},
|
||||
stockMap,
|
||||
$q
|
||||
})
|
||||
isRowSaveInFlight.value = true
|
||||
let ok = false
|
||||
try {
|
||||
ok = await orderStore.saveOrUpdateRowUnified({
|
||||
form,
|
||||
recalcVat: typeof recalcVat === 'function' ? recalcVat : null,
|
||||
resetEditor: () => {},
|
||||
loadProductSizes: async () => {
|
||||
await orderStore.loadProductSizes(form, true, $q, productCache)
|
||||
await loadOrderInventory(true)
|
||||
},
|
||||
stockMap,
|
||||
$q
|
||||
})
|
||||
} finally {
|
||||
isRowSaveInFlight.value = false
|
||||
}
|
||||
|
||||
if (!ok) return
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
icon="save"
|
||||
label="Secili Degisiklikleri Kaydet"
|
||||
:loading="store.saving"
|
||||
:disable="store.loading"
|
||||
:disable="store.loading || store.saving || isBulkSubmitting"
|
||||
@click="onBulkSubmit"
|
||||
/>
|
||||
</div>
|
||||
@@ -391,7 +391,7 @@
|
||||
|
||||
<q-card-actions align="right">
|
||||
<q-btn flat label="Vazgec" color="grey-8" v-close-popup />
|
||||
<q-btn color="primary" label="Ozellikleri Kaydet" @click="saveAttributeDraft" />
|
||||
<q-btn color="primary" label="Ozellikleri Taslaga Kaydet" @click="saveAttributeDraft" />
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
@@ -441,10 +441,12 @@ const headerAverageDueDate = ref('')
|
||||
const cdItemDialogOpen = ref(false)
|
||||
const cdItemTargetCode = ref('')
|
||||
const copySourceCode = ref(null)
|
||||
const suppressAutoSetupDialogs = ref(false)
|
||||
const cdItemDraftForm = ref(createEmptyCdItemDraft(''))
|
||||
const attributeDialogOpen = ref(false)
|
||||
const attributeTargetCode = ref('')
|
||||
const attributeRows = ref([])
|
||||
const isBulkSubmitting = ref(false)
|
||||
|
||||
const columns = [
|
||||
{ name: 'select', label: '', field: 'select', align: 'center', sortable: false, style: 'width:44px;', headerStyle: 'width:44px;' },
|
||||
@@ -669,13 +671,14 @@ function onNewItemChange (row, val, source = 'typed') {
|
||||
row.NewColor = ''
|
||||
row.NewDim2 = ''
|
||||
row.NewDesc = mergeDescWithAutoNote(row, row.NewDesc || row.OldDesc)
|
||||
if (row.NewItemCode) {
|
||||
if (row.NewItemCode && isValidBaggiModelCode(row.NewItemCode)) {
|
||||
if (row.NewItemMode === 'new') {
|
||||
store.fetchNewColors(row.NewItemCode)
|
||||
} else {
|
||||
store.fetchColors(row.NewItemCode)
|
||||
}
|
||||
}
|
||||
if (suppressAutoSetupDialogs.value) return
|
||||
if (row.NewItemMode === 'new' && isValidBaggiModelCode(row.NewItemCode) && row.NewItemCode !== prevCode) {
|
||||
openNewCodeSetupFlow(row.NewItemCode)
|
||||
} else if (row.NewItemMode === 'existing' && isValidBaggiModelCode(row.NewItemCode) && row.NewItemCode !== prevCode) {
|
||||
@@ -902,6 +905,59 @@ function collectLinesFromRows (selectedRows) {
|
||||
return { errMsg: '', lines }
|
||||
}
|
||||
|
||||
function hasRowChange (row) {
|
||||
const newItemCode = String(row?.NewItemCode || '').trim().toUpperCase()
|
||||
const newColor = normalizeShortCode(row?.NewColor, 3)
|
||||
const newDim2 = normalizeShortCode(row?.NewDim2, 3)
|
||||
const newDesc = mergeDescWithAutoNote(row, row?.NewDesc || row?.OldDesc)
|
||||
const oldItemCode = String(row?.OldItemCode || '').trim().toUpperCase()
|
||||
const oldColor = normalizeShortCode(row?.OldColor, 3)
|
||||
const oldDim2 = normalizeShortCode(row?.OldDim2, 3)
|
||||
const oldDesc = String(row?.OldDesc || '').trim()
|
||||
const oldDueDateValue = row?.OldDueDate || ''
|
||||
const newDueDateValue = row?.NewDueDate || ''
|
||||
|
||||
return (
|
||||
newItemCode !== oldItemCode ||
|
||||
newColor !== oldColor ||
|
||||
newDim2 !== oldDim2 ||
|
||||
String(newDesc || '').trim() !== oldDesc ||
|
||||
newDueDateValue !== oldDueDateValue
|
||||
)
|
||||
}
|
||||
|
||||
function collectOptionalColorWarnings (rows) {
|
||||
const warnings = []
|
||||
for (const row of (rows || [])) {
|
||||
const code = String(row?.NewItemCode || '').trim().toUpperCase()
|
||||
if (!code) continue
|
||||
const color = normalizeShortCode(row?.NewColor, 3)
|
||||
const dim2 = normalizeShortCode(row?.NewDim2, 3)
|
||||
if (!color) {
|
||||
warnings.push(`${code} icin renk secmediniz.`)
|
||||
continue
|
||||
}
|
||||
if (!dim2) {
|
||||
warnings.push(`${code} icin 2. renk bos kalacak.`)
|
||||
}
|
||||
}
|
||||
return [...new Set(warnings)]
|
||||
}
|
||||
|
||||
function confirmOptionalColorWarnings (rows) {
|
||||
const warnings = collectOptionalColorWarnings(rows)
|
||||
if (!warnings.length) return Promise.resolve(true)
|
||||
return new Promise((resolve) => {
|
||||
$q.dialog({
|
||||
title: 'Renk Uyarisi',
|
||||
message: `${warnings.join('<br>')}<br><br>Devam etmek istiyor musunuz?`,
|
||||
html: true,
|
||||
ok: { label: 'Evet, Devam Et', color: 'warning' },
|
||||
cancel: { label: 'Vazgec', flat: true }
|
||||
}).onOk(() => resolve(true)).onCancel(() => resolve(false)).onDismiss(() => resolve(false))
|
||||
})
|
||||
}
|
||||
|
||||
function createEmptyCdItemDraft (itemCode) {
|
||||
return {
|
||||
ItemTypeCode: '1',
|
||||
@@ -970,13 +1026,15 @@ async function copyFromOldProduct (targetType = 'cdItem') {
|
||||
if (targetType === 'cdItem') {
|
||||
const data = await store.fetchCdItemByCode(sourceCode)
|
||||
if (data) {
|
||||
const targetCode = cdItemTargetCode.value
|
||||
const targetCode = String(cdItemTargetCode.value || '').trim().toUpperCase()
|
||||
const draft = createEmptyCdItemDraft(targetCode)
|
||||
for (const k of Object.keys(draft)) {
|
||||
if (data[k] !== undefined && data[k] !== null) {
|
||||
draft[k] = String(data[k])
|
||||
}
|
||||
}
|
||||
// Source item kopyalansa da hedef popup kodu degismemeli.
|
||||
draft.ItemCode = targetCode
|
||||
cdItemDraftForm.value = draft
|
||||
persistCdItemDraft()
|
||||
$q.notify({ type: 'positive', message: 'Boyutlandirma bilgileri kopyalandi.' })
|
||||
@@ -1038,7 +1096,11 @@ async function openCdItemDialog (itemCode) {
|
||||
}
|
||||
|
||||
function persistCdItemDraft () {
|
||||
const payload = normalizeCdItemDraftForPayload(cdItemDraftForm.value)
|
||||
const targetCode = String(cdItemTargetCode.value || '').trim().toUpperCase()
|
||||
const payload = normalizeCdItemDraftForPayload({
|
||||
...(cdItemDraftForm.value || {}),
|
||||
ItemCode: targetCode || String(cdItemDraftForm.value?.ItemCode || '').trim().toUpperCase()
|
||||
})
|
||||
if (!payload.ItemCode) return null
|
||||
store.setCdItemDraft(payload.ItemCode, payload)
|
||||
return payload
|
||||
@@ -1183,7 +1245,7 @@ async function openAttributeDialog (itemCode) {
|
||||
if (!code) return
|
||||
copySourceCode.value = null
|
||||
attributeTargetCode.value = code
|
||||
const existingDraft = store.getProductAttributeDraft(code)
|
||||
const existingDraft = JSON.parse(JSON.stringify(store.getProductAttributeDraft(code) || []))
|
||||
const modeInfo = store.classifyItemCode(code)
|
||||
const fetched = await store.fetchProductAttributes(1)
|
||||
const fromLookup = buildAttributeRowsFromLookup(fetched)
|
||||
@@ -1197,6 +1259,32 @@ async function openAttributeDialog (itemCode) {
|
||||
$q.notify({ type: 'negative', message: 'Urun ozellikleri listesi alinamadi. Lutfen daha sonra tekrar deneyin.' })
|
||||
return
|
||||
}
|
||||
|
||||
// Draft varsa popup her zaman draft'tan acilir (yeniden acinca secimler kaybolmasin).
|
||||
if (Array.isArray(existingDraft) && existingDraft.length) {
|
||||
attributeRows.value = JSON.parse(JSON.stringify(
|
||||
mergeAttributeDraftWithLookupOptions(existingDraft, fromLookup)
|
||||
))
|
||||
console.info('[OrderProductionUpdate] openAttributeDialog rowsPrepared', {
|
||||
code,
|
||||
mode: modeInfo.mode,
|
||||
useDraft: true,
|
||||
rowCount: Array.isArray(attributeRows.value) ? attributeRows.value.length : 0,
|
||||
optionCounts: (attributeRows.value || []).map(r => ({
|
||||
type: Number(r?.AttributeTypeCodeNumber || 0),
|
||||
options: Array.isArray(r?.Options) ? r.Options.length : 0,
|
||||
allOptions: Array.isArray(r?.AllOptions) ? r.AllOptions.length : 0,
|
||||
selected: String(r?.AttributeCode || '').trim()
|
||||
}))
|
||||
})
|
||||
for (const row of (attributeRows.value || [])) {
|
||||
if (!Array.isArray(row.AllOptions)) row.AllOptions = Array.isArray(row.Options) ? [...row.Options] : []
|
||||
if (!Array.isArray(row.Options)) row.Options = [...row.AllOptions]
|
||||
}
|
||||
attributeDialogOpen.value = true
|
||||
return
|
||||
}
|
||||
|
||||
const dbCurrent = await store.fetchProductItemAttributes(code, 1, true)
|
||||
console.info('[OrderProductionUpdate] openAttributeDialog dbCurrent', {
|
||||
code,
|
||||
@@ -1232,13 +1320,11 @@ async function openAttributeDialog (itemCode) {
|
||||
})
|
||||
|
||||
const useDraft = Array.isArray(existingDraft) && existingDraft.length
|
||||
attributeRows.value = useDraft
|
||||
? JSON.parse(JSON.stringify(mergeAttributeDraftWithLookupOptions(existingDraft, baseRows)))
|
||||
: JSON.parse(JSON.stringify(baseRows))
|
||||
attributeRows.value = JSON.parse(JSON.stringify(baseRows))
|
||||
console.info('[OrderProductionUpdate] openAttributeDialog rowsPrepared', {
|
||||
code,
|
||||
mode: modeInfo.mode,
|
||||
useDraft,
|
||||
useDraft: false,
|
||||
rowCount: Array.isArray(attributeRows.value) ? attributeRows.value.length : 0,
|
||||
optionCounts: (attributeRows.value || []).map(r => ({
|
||||
type: Number(r?.AttributeTypeCodeNumber || 0),
|
||||
@@ -1255,27 +1341,26 @@ async function openAttributeDialog (itemCode) {
|
||||
row.Options = [...row.AllOptions]
|
||||
}
|
||||
}
|
||||
if ((!existingDraft || !existingDraft.length) && baseRows.length) {
|
||||
store.setProductAttributeDraft(code, JSON.parse(JSON.stringify(baseRows)))
|
||||
}
|
||||
attributeDialogOpen.value = true
|
||||
}
|
||||
|
||||
function saveAttributeDraft () {
|
||||
const code = String(attributeTargetCode.value || '').trim().toUpperCase()
|
||||
if (!code) return
|
||||
for (const row of (attributeRows.value || [])) {
|
||||
const rows = JSON.parse(JSON.stringify(attributeRows.value || []))
|
||||
for (const row of rows) {
|
||||
const selected = String(row?.AttributeCode || '').trim()
|
||||
if (!selected) {
|
||||
$q.notify({ type: 'negative', message: `Urun ozelliklerinde secim zorunlu: ${row?.TypeLabel || ''}` })
|
||||
return
|
||||
}
|
||||
}
|
||||
store.setProductAttributeDraft(code, JSON.parse(JSON.stringify(attributeRows.value || [])))
|
||||
store.setProductAttributeDraft(code, rows)
|
||||
console.info('[OrderProductionUpdate] saveAttributeDraft', {
|
||||
code,
|
||||
rowCount: (attributeRows.value || []).length,
|
||||
selected: (attributeRows.value || []).map(r => ({
|
||||
rowCount: rows.length,
|
||||
selectedCount: rows.length,
|
||||
selected: rows.map(r => ({
|
||||
type: Number(r?.AttributeTypeCodeNumber || 0),
|
||||
code: String(r?.AttributeCode || '').trim()
|
||||
}))
|
||||
@@ -1293,17 +1378,6 @@ watch(
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
attributeRows,
|
||||
(rows) => {
|
||||
if (!attributeDialogOpen.value) return
|
||||
const code = String(attributeTargetCode.value || '').trim().toUpperCase()
|
||||
if (!code) return
|
||||
store.setProductAttributeDraft(code, JSON.parse(JSON.stringify(rows || [])))
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
async function collectProductAttributesFromSelectedRows (selectedRows) {
|
||||
const codeSet = [...new Set(
|
||||
(selectedRows || [])
|
||||
@@ -1315,22 +1389,24 @@ async function collectProductAttributesFromSelectedRows (selectedRows) {
|
||||
for (const code of codeSet) {
|
||||
const modeInfo = store.classifyItemCode(code)
|
||||
let rows = store.getProductAttributeDraft(code)
|
||||
let dbMap = new Map()
|
||||
const dbCurrent = await store.fetchProductItemAttributes(code, 1, true)
|
||||
const dbMap = new Map(
|
||||
(dbCurrent || []).map(x => [
|
||||
Number(x?.attribute_type_code || x?.AttributeTypeCode || 0),
|
||||
String(x?.attribute_code || x?.AttributeCode || '').trim()
|
||||
]).filter(x => x[0] > 0)
|
||||
)
|
||||
const hasDbAttributes = dbMap.size > 0
|
||||
const effectiveMode = hasDbAttributes ? 'existing' : modeInfo.mode
|
||||
console.info('[OrderProductionUpdate] collectProductAttributes start', {
|
||||
code,
|
||||
mode: modeInfo.mode,
|
||||
effectiveMode,
|
||||
hasDbAttributes,
|
||||
draftRowCount: Array.isArray(rows) ? rows.length : 0
|
||||
})
|
||||
|
||||
if (modeInfo.mode === 'existing') {
|
||||
const dbCurrent = await store.fetchProductItemAttributes(code, 1, true)
|
||||
dbMap = new Map(
|
||||
(dbCurrent || []).map(x => [
|
||||
Number(x?.attribute_type_code || x?.AttributeTypeCode || 0),
|
||||
String(x?.attribute_code || x?.AttributeCode || '').trim()
|
||||
]).filter(x => x[0] > 0)
|
||||
)
|
||||
|
||||
if (effectiveMode === 'existing') {
|
||||
// Existing kodda kullanıcı değişiklik yaptıysa draftı koru.
|
||||
// Draft yoksa DB'den zorunlu/fresh çek.
|
||||
if (!Array.isArray(rows) || !rows.length) {
|
||||
@@ -1360,26 +1436,7 @@ async function collectProductAttributesFromSelectedRows (selectedRows) {
|
||||
store.setProductAttributeDraft(code, JSON.parse(JSON.stringify(rows)))
|
||||
}
|
||||
} else if (!Array.isArray(rows) || !rows.length) {
|
||||
const lookup = await store.fetchProductAttributes(1)
|
||||
const baseRows = buildAttributeRowsFromLookup(lookup)
|
||||
const dbCurrent = await store.fetchProductItemAttributes(code, 1, true)
|
||||
const dbMap = new Map(
|
||||
(dbCurrent || []).map(x => [
|
||||
Number(x?.attribute_type_code || x?.AttributeTypeCode || 0),
|
||||
String(x?.attribute_code || x?.AttributeCode || '').trim()
|
||||
]).filter(x => x[0] > 0)
|
||||
)
|
||||
rows = baseRows.map(row => ({
|
||||
...row,
|
||||
AttributeCode: dbMap.get(Number(row.AttributeTypeCodeNumber || 0)) || ''
|
||||
}))
|
||||
console.info('[OrderProductionUpdate] collectProductAttributes new init', {
|
||||
code,
|
||||
lookupCount: Array.isArray(lookup) ? lookup.length : 0,
|
||||
baseRowCount: baseRows.length,
|
||||
dbCurrentCount: Array.isArray(dbCurrent) ? dbCurrent.length : 0
|
||||
})
|
||||
store.setProductAttributeDraft(code, JSON.parse(JSON.stringify(rows)))
|
||||
return { errMsg: `${code} icin urun ozellikleri taslagi kaydedilmedi`, productAttributes: [] }
|
||||
}
|
||||
|
||||
if (!Array.isArray(rows) || !rows.length) {
|
||||
@@ -1393,7 +1450,7 @@ async function collectProductAttributesFromSelectedRows (selectedRows) {
|
||||
return { errMsg: `${code} icin urun ozellikleri eksik`, productAttributes: [] }
|
||||
}
|
||||
|
||||
if (modeInfo.mode === 'existing') {
|
||||
if (effectiveMode === 'existing') {
|
||||
const originalCode =
|
||||
dbMap.get(attributeTypeCode) ||
|
||||
String(row?.OriginalAttributeCode || '').trim()
|
||||
@@ -1416,6 +1473,7 @@ async function collectProductAttributesFromSelectedRows (selectedRows) {
|
||||
console.info('[OrderProductionUpdate] collectProductAttributes done', {
|
||||
code,
|
||||
mode: modeInfo.mode,
|
||||
effectiveMode,
|
||||
outCount: out.filter(x => x.ItemCode === code).length,
|
||||
rowCount: rows.length,
|
||||
optionCounts: rows.map(r => ({
|
||||
@@ -1744,60 +1802,77 @@ async function refreshAll () {
|
||||
}
|
||||
|
||||
async function onBulkSubmit () {
|
||||
if (isBulkSubmitting.value || store.saving) {
|
||||
console.info('[OrderProductionUpdate] onBulkSubmit ignored (already running)', {
|
||||
orderHeaderID: orderHeaderID.value,
|
||||
isBulkSubmitting: isBulkSubmitting.value,
|
||||
storeSaving: store.saving
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
isBulkSubmitting.value = true
|
||||
const flowStart = nowMs()
|
||||
const selectedRows = rows.value.filter(r => !!selectedMap.value[r.RowKey])
|
||||
const headerAverageDueDateValue = normalizeDateInput(headerAverageDueDate.value)
|
||||
const headerDateChanged = hasHeaderAverageDueDateChange.value
|
||||
if (!selectedRows.length && !headerDateChanged) {
|
||||
$q.notify({ type: 'warning', message: 'Lutfen en az bir satir seciniz veya ustteki termin tarihini degistiriniz.' })
|
||||
return
|
||||
}
|
||||
|
||||
const prepStart = nowMs()
|
||||
const { errMsg, lines } = collectLinesFromRows(selectedRows)
|
||||
if (errMsg) {
|
||||
$q.notify({ type: 'negative', message: errMsg })
|
||||
return
|
||||
}
|
||||
if (!lines.length && !headerDateChanged) {
|
||||
$q.notify({ type: 'warning', message: 'Secili satirlarda degisiklik yok.' })
|
||||
return
|
||||
}
|
||||
|
||||
let cdItems = []
|
||||
let productAttributes = []
|
||||
if (lines.length > 0) {
|
||||
const { errMsg: cdErrMsg, cdItems: nextCdItems } = await collectCdItemsFromSelectedRows(selectedRows)
|
||||
if (cdErrMsg) {
|
||||
$q.notify({ type: 'negative', message: cdErrMsg })
|
||||
const firstCode = String(cdErrMsg.split(' ')[0] || '').trim()
|
||||
if (firstCode) openCdItemDialog(firstCode)
|
||||
return
|
||||
}
|
||||
cdItems = nextCdItems
|
||||
|
||||
const { errMsg: attrErrMsg, productAttributes: nextProductAttributes } = await collectProductAttributesFromSelectedRows(selectedRows)
|
||||
if (attrErrMsg) {
|
||||
$q.notify({ type: 'negative', message: attrErrMsg })
|
||||
const firstCode = String(attrErrMsg.split(' ')[0] || '').trim()
|
||||
if (firstCode) openAttributeDialog(firstCode)
|
||||
return
|
||||
}
|
||||
productAttributes = nextProductAttributes
|
||||
}
|
||||
|
||||
console.info('[OrderProductionUpdate] onBulkSubmit prepared', {
|
||||
orderHeaderID: orderHeaderID.value,
|
||||
selectedRowCount: selectedRows.length,
|
||||
lineCount: lines.length,
|
||||
cdItemCount: cdItems.length,
|
||||
attributeCount: productAttributes.length,
|
||||
headerAverageDueDate: headerAverageDueDateValue,
|
||||
headerDateChanged,
|
||||
prepDurationMs: Math.round(nowMs() - prepStart)
|
||||
})
|
||||
|
||||
try {
|
||||
suppressAutoSetupDialogs.value = true
|
||||
const selectedRows = rows.value.filter(r => !!selectedMap.value[r.RowKey])
|
||||
const headerAverageDueDateValue = normalizeDateInput(headerAverageDueDate.value)
|
||||
const headerDateChanged = hasHeaderAverageDueDateChange.value
|
||||
if (!selectedRows.length && !headerDateChanged) {
|
||||
$q.notify({ type: 'warning', message: 'Lutfen en az bir satir seciniz veya ustteki termin tarihini degistiriniz.' })
|
||||
return
|
||||
}
|
||||
|
||||
const prepStart = nowMs()
|
||||
const { errMsg, lines } = collectLinesFromRows(selectedRows)
|
||||
if (errMsg) {
|
||||
$q.notify({ type: 'negative', message: errMsg })
|
||||
return
|
||||
}
|
||||
if (!lines.length && !headerDateChanged) {
|
||||
$q.notify({ type: 'warning', message: 'Secili satirlarda degisiklik yok.' })
|
||||
return
|
||||
}
|
||||
|
||||
if (lines.length > 0) {
|
||||
const changedRows = selectedRows.filter(hasRowChange)
|
||||
const confirmed = await confirmOptionalColorWarnings(changedRows)
|
||||
if (!confirmed) return
|
||||
}
|
||||
|
||||
let cdItems = []
|
||||
let productAttributes = []
|
||||
if (lines.length > 0) {
|
||||
const { errMsg: cdErrMsg, cdItems: nextCdItems } = await collectCdItemsFromSelectedRows(selectedRows)
|
||||
if (cdErrMsg) {
|
||||
$q.notify({ type: 'negative', message: cdErrMsg })
|
||||
return
|
||||
}
|
||||
cdItems = nextCdItems
|
||||
|
||||
const { errMsg: attrErrMsg, productAttributes: nextProductAttributes } = await collectProductAttributesFromSelectedRows(selectedRows)
|
||||
if (attrErrMsg) {
|
||||
$q.notify({ type: 'negative', message: attrErrMsg })
|
||||
const firstCode = String(attrErrMsg.split(' ')[0] || '').trim().toUpperCase()
|
||||
if (isValidBaggiModelCode(firstCode)) {
|
||||
await openAttributeDialog(firstCode)
|
||||
}
|
||||
return
|
||||
}
|
||||
productAttributes = nextProductAttributes
|
||||
}
|
||||
|
||||
console.info('[OrderProductionUpdate] onBulkSubmit prepared', {
|
||||
orderHeaderID: orderHeaderID.value,
|
||||
selectedRowCount: selectedRows.length,
|
||||
lineCount: lines.length,
|
||||
cdItemCount: cdItems.length,
|
||||
attributeCount: productAttributes.length,
|
||||
headerAverageDueDate: headerAverageDueDateValue,
|
||||
headerDateChanged,
|
||||
prepDurationMs: Math.round(nowMs() - prepStart)
|
||||
})
|
||||
|
||||
const applyChanges = async (insertMissing) => {
|
||||
const applyStart = nowMs()
|
||||
const applyResult = await store.applyUpdates(
|
||||
@@ -1831,7 +1906,7 @@ async function onBulkSubmit () {
|
||||
|
||||
if (lines.length > 0) {
|
||||
const validateStart = nowMs()
|
||||
const validate = await store.validateUpdates(orderHeaderID.value, lines)
|
||||
const validate = await store.validateUpdates(orderHeaderID.value, lines, cdItems)
|
||||
console.info('[OrderProductionUpdate] validate finished', {
|
||||
orderHeaderID: orderHeaderID.value,
|
||||
lineCount: lines.length,
|
||||
@@ -1875,11 +1950,14 @@ async function onBulkSubmit () {
|
||||
return
|
||||
}
|
||||
$q.notify({ type: 'negative', message: store.error || 'Toplu kayit islemi basarisiz.' })
|
||||
} finally {
|
||||
isBulkSubmitting.value = false
|
||||
suppressAutoSetupDialogs.value = false
|
||||
console.info('[OrderProductionUpdate] onBulkSubmit total', {
|
||||
orderHeaderID: orderHeaderID.value,
|
||||
durationMs: Math.round(nowMs() - flowStart)
|
||||
})
|
||||
}
|
||||
console.info('[OrderProductionUpdate] onBulkSubmit total', {
|
||||
orderHeaderID: orderHeaderID.value,
|
||||
durationMs: Math.round(nowMs() - flowStart)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@ function nowMs () {
|
||||
return Date.now()
|
||||
}
|
||||
|
||||
const applyInFlightByOrder = new Map()
|
||||
|
||||
const YAS_NUMERIC_SIZES = new Set(['2', '4', '6', '8', '10', '12', '14'])
|
||||
|
||||
function safeStr (value) {
|
||||
@@ -423,7 +425,7 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', {
|
||||
if (!code) return []
|
||||
return this.productAttributeDraftsByCode[code] || []
|
||||
},
|
||||
async validateUpdates (orderHeaderID, lines) {
|
||||
async validateUpdates (orderHeaderID, lines, cdItems = []) {
|
||||
if (!orderHeaderID) return { missingCount: 0, missing: [] }
|
||||
|
||||
this.saving = true
|
||||
@@ -434,7 +436,7 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', {
|
||||
console.info('[OrderProductionItemStore] validateUpdates start', { orderHeaderID, lineCount: lines?.length || 0 })
|
||||
const res = await api.post(
|
||||
`/orders/production-items/${encodeURIComponent(orderHeaderID)}/validate`,
|
||||
{ lines }
|
||||
{ lines, cdItems }
|
||||
)
|
||||
const data = res?.data || { missingCount: 0, missing: [] }
|
||||
const rid = res?.headers?.['x-debug-request-id'] || ''
|
||||
@@ -458,48 +460,69 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', {
|
||||
async applyUpdates (orderHeaderID, lines, insertMissing, cdItems = [], productAttributes = [], headerAverageDueDate = null) {
|
||||
if (!orderHeaderID) return { updated: 0, inserted: 0 }
|
||||
|
||||
this.saving = true
|
||||
this.error = null
|
||||
const orderKey = String(orderHeaderID).trim().toUpperCase()
|
||||
if (applyInFlightByOrder.has(orderKey)) {
|
||||
console.warn('[OrderProductionItemStore] applyUpdates deduped (in-flight)', {
|
||||
orderHeaderID: orderKey,
|
||||
lineCount: lines?.length || 0
|
||||
})
|
||||
return await applyInFlightByOrder.get(orderKey)
|
||||
}
|
||||
|
||||
const applyPromise = (async () => {
|
||||
this.saving = true
|
||||
this.error = null
|
||||
|
||||
try {
|
||||
const t0 = nowMs()
|
||||
console.info('[OrderProductionItemStore] applyUpdates start', {
|
||||
orderHeaderID,
|
||||
lineCount: lines?.length || 0,
|
||||
insertMissing: !!insertMissing,
|
||||
cdItemCount: cdItems?.length || 0,
|
||||
attributeCount: productAttributes?.length || 0,
|
||||
headerAverageDueDate
|
||||
})
|
||||
const res = await api.post(
|
||||
`/orders/production-items/${encodeURIComponent(orderHeaderID)}/apply`,
|
||||
{
|
||||
lines,
|
||||
insertMissing,
|
||||
cdItems,
|
||||
productAttributes,
|
||||
HeaderAverageDueDate: headerAverageDueDate
|
||||
}
|
||||
)
|
||||
const data = res?.data || { updated: 0, inserted: 0 }
|
||||
const rid = res?.headers?.['x-debug-request-id'] || ''
|
||||
console.info('[OrderProductionItemStore] applyUpdates done', {
|
||||
orderHeaderID,
|
||||
updated: Number(data?.updated || 0),
|
||||
inserted: Number(data?.inserted || 0),
|
||||
barcodeInserted: Number(data?.barcodeInserted || 0),
|
||||
attributeUpserted: Number(data?.attributeUpserted || 0),
|
||||
headerUpdated: !!data?.headerUpdated,
|
||||
requestId: rid,
|
||||
durationMs: Math.round(nowMs() - t0)
|
||||
})
|
||||
return data
|
||||
} catch (err) {
|
||||
logApiError('applyUpdates', err, { orderHeaderID, lineCount: lines?.length || 0, insertMissing })
|
||||
this.error = extractApiErrorMessage(err, 'Guncelleme basarisiz')
|
||||
throw err
|
||||
} finally {
|
||||
this.saving = false
|
||||
}
|
||||
})()
|
||||
|
||||
applyInFlightByOrder.set(orderKey, applyPromise)
|
||||
|
||||
try {
|
||||
const t0 = nowMs()
|
||||
console.info('[OrderProductionItemStore] applyUpdates start', {
|
||||
orderHeaderID,
|
||||
lineCount: lines?.length || 0,
|
||||
insertMissing: !!insertMissing,
|
||||
cdItemCount: cdItems?.length || 0,
|
||||
attributeCount: productAttributes?.length || 0,
|
||||
headerAverageDueDate
|
||||
})
|
||||
const res = await api.post(
|
||||
`/orders/production-items/${encodeURIComponent(orderHeaderID)}/apply`,
|
||||
{
|
||||
lines,
|
||||
insertMissing,
|
||||
cdItems,
|
||||
productAttributes,
|
||||
HeaderAverageDueDate: headerAverageDueDate
|
||||
}
|
||||
)
|
||||
const data = res?.data || { updated: 0, inserted: 0 }
|
||||
const rid = res?.headers?.['x-debug-request-id'] || ''
|
||||
console.info('[OrderProductionItemStore] applyUpdates done', {
|
||||
orderHeaderID,
|
||||
updated: Number(data?.updated || 0),
|
||||
inserted: Number(data?.inserted || 0),
|
||||
barcodeInserted: Number(data?.barcodeInserted || 0),
|
||||
attributeUpserted: Number(data?.attributeUpserted || 0),
|
||||
headerUpdated: !!data?.headerUpdated,
|
||||
requestId: rid,
|
||||
durationMs: Math.round(nowMs() - t0)
|
||||
})
|
||||
return data
|
||||
} catch (err) {
|
||||
logApiError('applyUpdates', err, { orderHeaderID, lineCount: lines?.length || 0, insertMissing })
|
||||
this.error = extractApiErrorMessage(err, 'Guncelleme basarisiz')
|
||||
throw err
|
||||
return await applyPromise
|
||||
} finally {
|
||||
this.saving = false
|
||||
if (applyInFlightByOrder.get(orderKey) === applyPromise) {
|
||||
applyInFlightByOrder.delete(orderKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user