Merge remote-tracking branch 'origin/master'
This commit is contained in:
75
ui/.quasar/prod-spa/app.js
Normal file
75
ui/.quasar/prod-spa/app.js
Normal 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
|
||||
}
|
||||
}
|
||||
154
ui/.quasar/prod-spa/client-entry.js
Normal file
154
ui/.quasar/prod-spa/client-entry.js
Normal file
@@ -0,0 +1,154 @@
|
||||
/* 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)
|
||||
})
|
||||
})
|
||||
|
||||
116
ui/.quasar/prod-spa/client-prefetch.js
Normal file
116
ui/.quasar/prod-spa/client-prefetch.js
Normal 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()
|
||||
})
|
||||
})
|
||||
}
|
||||
23
ui/.quasar/prod-spa/quasar-user-options.js
Normal file
23
ui/.quasar/prod-spa/quasar-user-options.js
Normal 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} }
|
||||
|
||||
@@ -323,12 +323,12 @@
|
||||
======================================================== -->
|
||||
<div class="order-scroll-y" :class="{ 'compact-grid-header': compactGridHeader }"> <!-- ✅ YENİ: Grid + Editor ortak dikey scroll -->
|
||||
<div class="order-grid-body">
|
||||
<template v-for="grp in groupedRows" :key="grp.name">
|
||||
<template v-for="grp in groupedRows" :key="grp.groupKey">
|
||||
<div :class="['summary-group', grp.open ? 'open' : 'closed']">
|
||||
|
||||
<!-- 🟡 Sub-header -->
|
||||
<div class="order-sub-header" @click="toggleGroup(grp.name)">
|
||||
<div class="sub-left">{{ grp.name }}</div>
|
||||
<div class="order-sub-header" @click="toggleGroup(grp.groupKey)">
|
||||
<div class="sub-left">{{ grp.displayName }}</div>
|
||||
|
||||
<div class="sub-center">
|
||||
<div
|
||||
@@ -348,10 +348,10 @@
|
||||
|
||||
|
||||
<div class="order-text-caption">
|
||||
Toplam {{ grp.name }} Adet: {{ grp.toplamAdet }}
|
||||
Toplam {{ grp.displayName }} Adet: {{ grp.toplamAdet }}
|
||||
</div>
|
||||
<div class="order-text-caption">
|
||||
Toplam {{ grp.name }} Tutar:
|
||||
Toplam {{ grp.displayName }} Tutar:
|
||||
{{ Number(grp.toplamTutar || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 }) }}
|
||||
{{ form.pb || aktifPB }}
|
||||
</div>
|
||||
@@ -747,6 +747,14 @@
|
||||
@click="removeSelected"
|
||||
:disable="isClosedRow || isViewOnly || !canMutateRows"
|
||||
/>
|
||||
<q-btn
|
||||
v-if="canMutateRows"
|
||||
flat
|
||||
color="warning"
|
||||
label="Bedenleri Sıfırla"
|
||||
@click="onZeroBedenClick"
|
||||
:disable="isClosedRow || isViewOnly || !canMutateRows"
|
||||
/>
|
||||
<q-btn
|
||||
v-if="canMutateRows"
|
||||
flat
|
||||
@@ -1362,6 +1370,21 @@ const selectedRow = computed(() => {
|
||||
=========================================================== */
|
||||
const groupOpen = reactive({})
|
||||
|
||||
function normalizeYetiskinGarsonToken (row) {
|
||||
const raw = String(
|
||||
row?.yetiskinGarson ||
|
||||
row?.YETISKIN_GARSON ||
|
||||
row?.YetiskinGarson ||
|
||||
row?.kategori ||
|
||||
row?.Kategori ||
|
||||
''
|
||||
).toUpperCase()
|
||||
|
||||
if (raw.includes('GARSON')) return 'GARSON'
|
||||
if (raw.includes('YETISKIN') || raw.includes('YETİSKİN')) return 'YETISKIN'
|
||||
return 'GENEL'
|
||||
}
|
||||
|
||||
const groupedRows = computed(() => {
|
||||
const rows = Array.isArray(summaryRows.value) ? summaryRows.value : []
|
||||
const buckets = {}
|
||||
@@ -1376,33 +1399,44 @@ const groupedRows = computed(() => {
|
||||
const ana = (row?.urunAnaGrubu || 'GENEL')
|
||||
.toUpperCase()
|
||||
.trim()
|
||||
const yg = normalizeYetiskinGarsonToken(row)
|
||||
const grpKey = String(row?.grpKey || 'tak').trim() || 'tak'
|
||||
const bucketKey = `${yg}::${ana}::${grpKey}`
|
||||
|
||||
if (!buckets[ana]) {
|
||||
buckets[ana] = {
|
||||
if (!buckets[bucketKey]) {
|
||||
buckets[bucketKey] = {
|
||||
name: ana,
|
||||
yg,
|
||||
displayName: `${yg} ${ana}`,
|
||||
rows: [],
|
||||
toplamAdet: 0,
|
||||
toplamTutar: 0,
|
||||
open: groupOpen[ana] ?? true,
|
||||
open: groupOpen[bucketKey] ?? true,
|
||||
|
||||
// 🔑 TEK KAYNAK
|
||||
grpKey: row.grpKey
|
||||
grpKey
|
||||
}
|
||||
order.push(ana)
|
||||
order.push(bucketKey)
|
||||
}
|
||||
|
||||
const bucket = buckets[ana]
|
||||
const bucket = buckets[bucketKey]
|
||||
bucket.rows.push(row)
|
||||
bucket.toplamAdet += Number(row.adet || 0)
|
||||
bucket.toplamTutar += Number(row.tutar || 0)
|
||||
}
|
||||
|
||||
return order.map(name => {
|
||||
const grp = buckets[name]
|
||||
return order.map(bucketKey => {
|
||||
const grp = buckets[bucketKey]
|
||||
const schema = schemaMap?.[grp.grpKey]
|
||||
const schemaTitle = String(schema?.title || grp.grpKey || '').trim()
|
||||
const displayName = schemaTitle
|
||||
? `${grp.yg} ${grp.name} (${schemaTitle})`
|
||||
: `${grp.yg} ${grp.name}`
|
||||
|
||||
return {
|
||||
...grp,
|
||||
displayName,
|
||||
groupKey: bucketKey,
|
||||
bedenValues: schema?.values || []
|
||||
}
|
||||
})
|
||||
@@ -3016,6 +3050,25 @@ const onSaveAndNextColor = async () => {
|
||||
})
|
||||
}
|
||||
|
||||
function onZeroBedenClick () {
|
||||
if (!Array.isArray(form.bedenLabels) || !form.bedenLabels.length) {
|
||||
$q.notify({
|
||||
type: 'warning',
|
||||
message: 'Sıfırlanacak beden alanı bulunamadı.'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
form.bedenler = form.bedenLabels.map(() => 0)
|
||||
updateTotals(form)
|
||||
|
||||
$q.notify({
|
||||
type: 'info',
|
||||
message: 'Beden adetleri sıfırlandı.',
|
||||
position: 'top-right'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -114,7 +114,8 @@
|
||||
filled
|
||||
maxlength="13"
|
||||
label="Yeni Urun"
|
||||
@update:model-value="val => onNewItemChange(props.row, val)"
|
||||
:class="newItemInputClass(props.row)"
|
||||
@update:model-value="val => onNewItemChange(props.row, val, 'typed')"
|
||||
>
|
||||
<template #append>
|
||||
<q-icon name="arrow_drop_down" class="cursor-pointer" />
|
||||
@@ -145,6 +146,32 @@
|
||||
</div>
|
||||
</q-menu>
|
||||
</q-input>
|
||||
<div v-if="props.row.NewItemMode && props.row.NewItemMode !== 'empty'" class="q-mt-xs row items-center no-wrap">
|
||||
<q-badge :color="newItemBadgeColor(props.row)" text-color="white">
|
||||
{{ newItemBadgeLabel(props.row) }}
|
||||
</q-badge>
|
||||
<span class="text-caption q-ml-sm text-grey-8">{{ newItemHintText(props.row) }}</span>
|
||||
<q-btn
|
||||
v-if="props.row.NewItemMode === 'new'"
|
||||
class="q-ml-sm"
|
||||
dense
|
||||
flat
|
||||
size="sm"
|
||||
color="warning"
|
||||
label="cdItem Bilgisi"
|
||||
@click="openCdItemDialog(props.row.NewItemCode)"
|
||||
/>
|
||||
<q-btn
|
||||
v-if="props.row.NewItemMode === 'new'"
|
||||
class="q-ml-xs"
|
||||
dense
|
||||
flat
|
||||
size="sm"
|
||||
color="primary"
|
||||
label="Urun Ozellikleri"
|
||||
@click="openAttributeDialog(props.row.NewItemCode)"
|
||||
/>
|
||||
</div>
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
@@ -157,13 +184,10 @@
|
||||
option-value="color_code"
|
||||
emit-value
|
||||
map-options
|
||||
use-input
|
||||
new-value-mode="add-unique"
|
||||
dense
|
||||
filled
|
||||
label="Yeni Renk"
|
||||
@update:model-value="() => onNewColorChange(props.row)"
|
||||
@new-value="(val, done) => onCreateColorValue(props.row, val, done)"
|
||||
/>
|
||||
</q-td>
|
||||
</template>
|
||||
@@ -173,16 +197,13 @@
|
||||
<q-select
|
||||
v-model="props.row.NewDim2"
|
||||
:options="getSecondColorOptions(props.row)"
|
||||
option-label="item_dim2_code"
|
||||
option-label="item_dim2_label"
|
||||
option-value="item_dim2_code"
|
||||
emit-value
|
||||
map-options
|
||||
use-input
|
||||
new-value-mode="add-unique"
|
||||
dense
|
||||
filled
|
||||
label="Yeni 2. Renk"
|
||||
@new-value="(val, done) => onCreateSecondColorValue(props.row, val, done)"
|
||||
/>
|
||||
</q-td>
|
||||
</template>
|
||||
@@ -205,6 +226,127 @@
|
||||
<q-banner v-if="store.error" class="bg-red text-white q-mt-sm">
|
||||
Hata: {{ store.error }}
|
||||
</q-banner>
|
||||
|
||||
<q-dialog v-model="cdItemDialogOpen" persistent>
|
||||
<q-card style="min-width: 980px; max-width: 98vw;">
|
||||
<q-card-section class="row items-center q-pb-none">
|
||||
<div class="text-h6">Yeni Kod cdItem Bilgileri</div>
|
||||
<q-space />
|
||||
<q-badge color="warning" text-color="black">
|
||||
{{ cdItemTargetCode || '-' }}
|
||||
</q-badge>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-section class="q-pt-md">
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col-12 col-md-4">
|
||||
<q-select v-model="cdItemDraftForm.ItemDimTypeCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('itemDimTypeCodes')" label="ItemDimTypeCode" />
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<q-select v-model="cdItemDraftForm.ProductTypeCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('productTypeCodes')" label="ProductTypeCode" />
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<q-select v-model="cdItemDraftForm.ProductHierarchyID" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('productHierarchyIDs')" label="ProductHierarchyID" />
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-4">
|
||||
<q-select v-model="cdItemDraftForm.UnitOfMeasureCode1" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('unitOfMeasureCode1List')" label="UnitOfMeasureCode1" />
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<q-select v-model="cdItemDraftForm.ItemAccountGrCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('itemAccountGrCodes')" label="ItemAccountGrCode" />
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<q-select v-model="cdItemDraftForm.ItemTaxGrCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('itemTaxGrCodes')" label="ItemTaxGrCode" />
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-4">
|
||||
<q-select v-model="cdItemDraftForm.ItemPaymentPlanGrCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('itemPaymentPlanGrCodes')" label="ItemPaymentPlanGrCode" />
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<q-select v-model="cdItemDraftForm.ItemDiscountGrCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('itemDiscountGrCodes')" label="ItemDiscountGrCode" />
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<q-select v-model="cdItemDraftForm.ItemVendorGrCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('itemVendorGrCodes')" label="ItemVendorGrCode" />
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-4">
|
||||
<q-select v-model="cdItemDraftForm.PromotionGroupCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('promotionGroupCodes')" label="PromotionGroupCode" />
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<q-select v-model="cdItemDraftForm.ProductCollectionGrCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('productCollectionGrCodes')" label="ProductCollectionGrCode" />
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<q-select v-model="cdItemDraftForm.StorePriceLevelCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('storePriceLevelCodes')" label="StorePriceLevelCode" />
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-4">
|
||||
<q-select v-model="cdItemDraftForm.PerceptionOfFashionCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('perceptionOfFashionCodes')" label="PerceptionOfFashionCode" />
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<q-select v-model="cdItemDraftForm.CommercialRoleCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('commercialRoleCodes')" label="CommercialRoleCode" />
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<q-select v-model="cdItemDraftForm.StoreCapacityLevelCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('storeCapacityLevelCodes')" label="StoreCapacityLevelCode" />
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6">
|
||||
<q-select v-model="cdItemDraftForm.CustomsTariffNumberCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('customsTariffNumbers')" label="CustomsTariffNumberCode" />
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<q-select v-model="cdItemDraftForm.CompanyCode" dense filled emit-value map-options option-label="label" option-value="value" :options="lookupOptions('companyCodes')" label="CompanyCode" />
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-actions align="right">
|
||||
<q-btn flat label="Vazgec" color="grey-8" v-close-popup />
|
||||
<q-btn color="primary" label="Taslagi Kaydet" @click="saveCdItemDraft" />
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
|
||||
<q-dialog v-model="attributeDialogOpen" persistent>
|
||||
<q-card style="min-width: 1100px; max-width: 98vw;">
|
||||
<q-card-section class="row items-center q-pb-none">
|
||||
<div class="text-h6">Urun Ozellikleri (2. Pop-up)</div>
|
||||
<q-space />
|
||||
<q-badge color="primary">{{ attributeTargetCode || '-' }}</q-badge>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-section style="max-height: 68vh; overflow: auto;">
|
||||
<div class="text-caption text-grey-7 q-mb-sm">
|
||||
Ilk etap dummy: isBlocked=0 kabul edilmis satirlar gibi listelenir.
|
||||
</div>
|
||||
<div
|
||||
v-for="(row, idx) in attributeRows"
|
||||
:key="`${row.AttributeTypeCodeNumber}-${idx}`"
|
||||
class="row q-col-gutter-sm q-mb-xs items-center"
|
||||
>
|
||||
<div class="col-12 col-md-5">
|
||||
<q-input :model-value="row.TypeLabel" dense filled readonly />
|
||||
</div>
|
||||
<div class="col-12 col-md-7">
|
||||
<q-select
|
||||
v-model="row.AttributeCode"
|
||||
dense
|
||||
filled
|
||||
emit-value
|
||||
map-options
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
:options="row.Options"
|
||||
label="AttributeCode - AttributeDescription"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
|
||||
<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-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
@@ -219,6 +361,8 @@ import { normalizeSearchText } from 'src/utils/searchText'
|
||||
const route = useRoute()
|
||||
const $q = useQuasar()
|
||||
const store = useOrderProductionItemStore()
|
||||
const BAGGI_CODE_PATTERN = /^[A-Z][0-9]{3}-[A-Z]{3}[0-9]{5}$/
|
||||
const BAGGI_CODE_ERROR = 'Girdiginiz kod BAGGI kod sistemine uyumlu degil. Format: X999-XXX99999'
|
||||
|
||||
const orderHeaderID = computed(() => String(route.params.orderHeaderID || '').trim())
|
||||
const header = computed(() => store.header || {})
|
||||
@@ -235,6 +379,12 @@ const descFilter = ref('')
|
||||
const productOptions = ref([])
|
||||
const productSearch = ref('')
|
||||
const selectedMap = ref({})
|
||||
const cdItemDialogOpen = ref(false)
|
||||
const cdItemTargetCode = ref('')
|
||||
const cdItemDraftForm = ref(createEmptyCdItemDraft(''))
|
||||
const attributeDialogOpen = ref(false)
|
||||
const attributeTargetCode = ref('')
|
||||
const attributeRows = ref([])
|
||||
|
||||
const columns = [
|
||||
{ name: 'select', label: '', field: 'select', align: 'center', sortable: false, style: 'width:44px;', headerStyle: 'width:44px;' },
|
||||
@@ -300,24 +450,70 @@ const selectedVisibleCount = computed(() => visibleRowKeys.value.filter(k => !!s
|
||||
const allSelectedVisible = computed(() => visibleRowKeys.value.length > 0 && selectedVisibleCount.value === visibleRowKeys.value.length)
|
||||
const someSelectedVisible = computed(() => selectedVisibleCount.value > 0)
|
||||
|
||||
function onSelectProduct (row, code) {
|
||||
productSearch.value = ''
|
||||
onNewItemChange(row, code)
|
||||
function applyNewItemVisualState (row, source = 'typed') {
|
||||
const info = store.classifyItemCode(row?.NewItemCode || '')
|
||||
row.NewItemCode = info.normalized
|
||||
row.NewItemMode = info.mode
|
||||
row.NewItemSource = info.mode === 'empty' ? '' : source
|
||||
}
|
||||
|
||||
function onNewItemChange (row, val) {
|
||||
function newItemInputClass (row) {
|
||||
return {
|
||||
'new-item-existing': row?.NewItemMode === 'existing',
|
||||
'new-item-new': row?.NewItemMode === 'new'
|
||||
}
|
||||
}
|
||||
|
||||
function newItemBadgeColor (row) {
|
||||
return row?.NewItemMode === 'existing' ? 'positive' : 'warning'
|
||||
}
|
||||
|
||||
function newItemBadgeLabel (row) {
|
||||
return row?.NewItemMode === 'existing' ? 'MEVCUT KOD' : 'YENI KOD'
|
||||
}
|
||||
|
||||
function newItemHintText (row) {
|
||||
if (row?.NewItemMode === 'existing') {
|
||||
return row?.NewItemSource === 'selected'
|
||||
? 'Urun listesinden secildi'
|
||||
: 'Elle girildi (sistemde bulundu)'
|
||||
}
|
||||
if (row?.NewItemMode === 'new') {
|
||||
return store.getCdItemDraft(row?.NewItemCode) ? 'Yeni kod: cdItem taslagi hazir' : 'Yeni kod: cdItem taslagi gerekli'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function onSelectProduct (row, code) {
|
||||
productSearch.value = ''
|
||||
onNewItemChange(row, code, 'selected')
|
||||
}
|
||||
|
||||
function onNewItemChange (row, val, source = 'typed') {
|
||||
const prevCode = String(row?.NewItemCode || '').trim().toUpperCase()
|
||||
const next = String(val || '').trim().toUpperCase()
|
||||
if (next.length > 13) {
|
||||
$q.notify({ type: 'negative', message: 'Model kodu en fazla 13 karakter olabilir.' })
|
||||
row.NewItemCode = next.slice(0, 13)
|
||||
applyNewItemVisualState(row, source)
|
||||
return
|
||||
}
|
||||
if (next.length === 13 && !isValidBaggiModelCode(next)) {
|
||||
$q.notify({ type: 'negative', message: BAGGI_CODE_ERROR })
|
||||
row.NewItemCode = prevCode
|
||||
applyNewItemVisualState(row, source)
|
||||
return
|
||||
}
|
||||
row.NewItemCode = next ? next.toUpperCase() : ''
|
||||
applyNewItemVisualState(row, source)
|
||||
row.NewColor = ''
|
||||
row.NewDim2 = ''
|
||||
if (row.NewItemCode) {
|
||||
store.fetchColors(row.NewItemCode)
|
||||
}
|
||||
if (row.NewItemMode === 'new' && isValidBaggiModelCode(row.NewItemCode) && row.NewItemCode !== prevCode) {
|
||||
openCdItemDialog(row.NewItemCode)
|
||||
}
|
||||
}
|
||||
|
||||
function onNewColorChange (row) {
|
||||
@@ -341,7 +537,11 @@ function getSecondColorOptions (row) {
|
||||
const code = row?.NewItemCode || ''
|
||||
const color = row?.NewColor || ''
|
||||
const key = `${code}::${color}`
|
||||
return store.secondColorOptionsByKey[key] || []
|
||||
const list = store.secondColorOptionsByKey[key] || []
|
||||
return list.map(c => ({
|
||||
...c,
|
||||
item_dim2_label: `${c.item_dim2_code} - ${c.color_description || ''}`.trim()
|
||||
}))
|
||||
}
|
||||
|
||||
function toggleRowSelection (rowKey, checked) {
|
||||
@@ -360,33 +560,12 @@ function toggleSelectAllVisible (checked) {
|
||||
selectedMap.value = next
|
||||
}
|
||||
|
||||
function onCreateColorValue (row, val, done) {
|
||||
const code = normalizeShortCode(val, 3)
|
||||
if (!code) {
|
||||
done(null)
|
||||
return
|
||||
}
|
||||
row.NewColor = code
|
||||
onNewColorChange(row)
|
||||
done(code, 'add-unique')
|
||||
}
|
||||
|
||||
function onCreateSecondColorValue (row, val, done) {
|
||||
const code = normalizeShortCode(val, 3)
|
||||
if (!code) {
|
||||
done(null)
|
||||
return
|
||||
}
|
||||
row.NewDim2 = code
|
||||
done(code, 'add-unique')
|
||||
}
|
||||
|
||||
function normalizeShortCode (value, maxLen) {
|
||||
return String(value || '').trim().toUpperCase().slice(0, maxLen)
|
||||
}
|
||||
|
||||
function isValidBaggiModelCode (code) {
|
||||
return /^[A-Z][0-9]{3}-[A-Z]{3}[0-9]{5}$/.test(code)
|
||||
return BAGGI_CODE_PATTERN.test(code)
|
||||
}
|
||||
|
||||
function validateRowInput (row) {
|
||||
@@ -398,7 +577,7 @@ function validateRowInput (row) {
|
||||
|
||||
if (!newItemCode) return 'Yeni model kodu zorunludur.'
|
||||
if (!isValidBaggiModelCode(newItemCode)) {
|
||||
return 'Girdiginiz yapi BAGGI kod yapisina uygun degildir. Format: X999-XXX99999'
|
||||
return BAGGI_CODE_ERROR
|
||||
}
|
||||
if (oldColor && !newColor) return 'Eski kayitta 1. renk oldugu icin yeni 1. renk zorunludur.'
|
||||
if (newColor && newColor.length !== 3) return 'Yeni 1. renk kodu 3 karakter olmalidir.'
|
||||
@@ -437,6 +616,238 @@ function collectLinesFromRows (selectedRows) {
|
||||
return { errMsg: '', lines }
|
||||
}
|
||||
|
||||
function createEmptyCdItemDraft (itemCode) {
|
||||
return {
|
||||
ItemTypeCode: '1',
|
||||
ItemCode: String(itemCode || '').trim().toUpperCase(),
|
||||
ItemDimTypeCode: '',
|
||||
ProductTypeCode: '',
|
||||
ProductHierarchyID: '',
|
||||
UnitOfMeasureCode1: '',
|
||||
ItemAccountGrCode: '',
|
||||
ItemTaxGrCode: '',
|
||||
ItemPaymentPlanGrCode: '',
|
||||
ItemDiscountGrCode: '',
|
||||
ItemVendorGrCode: '',
|
||||
PromotionGroupCode: '',
|
||||
ProductCollectionGrCode: '',
|
||||
StorePriceLevelCode: '',
|
||||
PerceptionOfFashionCode: '',
|
||||
CommercialRoleCode: '',
|
||||
StoreCapacityLevelCode: '',
|
||||
CustomsTariffNumberCode: '',
|
||||
CompanyCode: ''
|
||||
}
|
||||
}
|
||||
|
||||
function lookupOptions (key) {
|
||||
const list = store.cdItemLookups?.[key] || []
|
||||
return list.map(x => {
|
||||
const code = String(x?.code || '').trim()
|
||||
const desc = String(x?.description || '').trim()
|
||||
return {
|
||||
value: code,
|
||||
label: desc ? `${code} - ${desc}` : code
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function openCdItemDialog (itemCode) {
|
||||
const code = String(itemCode || '').trim().toUpperCase()
|
||||
if (!code) return
|
||||
await store.fetchCdItemLookups()
|
||||
|
||||
cdItemTargetCode.value = code
|
||||
const existing = store.getCdItemDraft(code)
|
||||
const draft = createEmptyCdItemDraft(code)
|
||||
if (existing) {
|
||||
for (const [k, v] of Object.entries(existing)) {
|
||||
if (v == null) continue
|
||||
draft[k] = String(v)
|
||||
}
|
||||
}
|
||||
cdItemDraftForm.value = draft
|
||||
cdItemDialogOpen.value = true
|
||||
}
|
||||
|
||||
function normalizeCdItemDraftForPayload (draftRaw) {
|
||||
const d = draftRaw || {}
|
||||
const toIntOrNil = (v) => {
|
||||
const n = Number(v)
|
||||
return Number.isFinite(n) && n > 0 ? n : null
|
||||
}
|
||||
const toStrOrNil = (v) => {
|
||||
const s = String(v || '').trim()
|
||||
return s || null
|
||||
}
|
||||
return {
|
||||
ItemTypeCode: toIntOrNil(d.ItemTypeCode) || 1,
|
||||
ItemCode: String(d.ItemCode || '').trim().toUpperCase(),
|
||||
ItemDimTypeCode: toIntOrNil(d.ItemDimTypeCode),
|
||||
ProductTypeCode: toIntOrNil(d.ProductTypeCode),
|
||||
ProductHierarchyID: toIntOrNil(d.ProductHierarchyID),
|
||||
UnitOfMeasureCode1: toStrOrNil(d.UnitOfMeasureCode1),
|
||||
ItemAccountGrCode: toStrOrNil(d.ItemAccountGrCode),
|
||||
ItemTaxGrCode: toStrOrNil(d.ItemTaxGrCode),
|
||||
ItemPaymentPlanGrCode: toStrOrNil(d.ItemPaymentPlanGrCode),
|
||||
ItemDiscountGrCode: toStrOrNil(d.ItemDiscountGrCode),
|
||||
ItemVendorGrCode: toStrOrNil(d.ItemVendorGrCode),
|
||||
PromotionGroupCode: toStrOrNil(d.PromotionGroupCode),
|
||||
ProductCollectionGrCode: toStrOrNil(d.ProductCollectionGrCode),
|
||||
StorePriceLevelCode: toStrOrNil(d.StorePriceLevelCode),
|
||||
PerceptionOfFashionCode: toStrOrNil(d.PerceptionOfFashionCode),
|
||||
CommercialRoleCode: toStrOrNil(d.CommercialRoleCode),
|
||||
StoreCapacityLevelCode: toStrOrNil(d.StoreCapacityLevelCode),
|
||||
CustomsTariffNumberCode: toStrOrNil(d.CustomsTariffNumberCode),
|
||||
CompanyCode: toStrOrNil(d.CompanyCode)
|
||||
}
|
||||
}
|
||||
|
||||
function saveCdItemDraft () {
|
||||
const payload = normalizeCdItemDraftForPayload(cdItemDraftForm.value)
|
||||
if (!payload.ItemCode) {
|
||||
$q.notify({ type: 'negative', message: 'ItemCode bos olamaz.' })
|
||||
return
|
||||
}
|
||||
store.setCdItemDraft(payload.ItemCode, payload)
|
||||
cdItemDialogOpen.value = false
|
||||
}
|
||||
|
||||
function createDummyAttributeRows () {
|
||||
const sharedOptions = [
|
||||
{ value: 'DAMATLIK', label: 'DAMATLIK - DAMATLIK' },
|
||||
{ value: 'TAKIM', label: 'TAKIM - TAKIM ELBISE' },
|
||||
{ value: 'CEKET', label: 'CEKET - CEKET' },
|
||||
{ value: 'PANTOLON', label: 'PANTOLON - PANTOLON' }
|
||||
]
|
||||
const rows = [{
|
||||
AttributeTypeCodeNumber: 1,
|
||||
TypeLabel: '1-001 Urun Ana Grubu',
|
||||
AttributeCode: '',
|
||||
Options: sharedOptions
|
||||
}]
|
||||
for (let i = 2; i <= 50; i++) {
|
||||
const code = String(i).padStart(3, '0')
|
||||
rows.push({
|
||||
AttributeTypeCodeNumber: i,
|
||||
TypeLabel: `1-${code} Dummy Ozellik ${i}`,
|
||||
AttributeCode: '',
|
||||
Options: sharedOptions
|
||||
})
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
function buildAttributeRowsFromLookup (list) {
|
||||
const grouped = new Map()
|
||||
for (const it of (list || [])) {
|
||||
const typeCode = Number(it?.attribute_type_code || 0)
|
||||
if (!typeCode) continue
|
||||
if (!grouped.has(typeCode)) {
|
||||
grouped.set(typeCode, {
|
||||
typeCode,
|
||||
typeDesc: String(it?.attribute_type_description || '').trim() || String(typeCode),
|
||||
options: []
|
||||
})
|
||||
}
|
||||
const g = grouped.get(typeCode)
|
||||
const code = String(it?.attribute_code || '').trim()
|
||||
const desc = String(it?.attribute_description || '').trim()
|
||||
g.options.push({
|
||||
value: code,
|
||||
label: `${code} - ${desc || code}`
|
||||
})
|
||||
}
|
||||
|
||||
const rows = [...grouped.values()]
|
||||
.sort((a, b) => a.typeCode - b.typeCode)
|
||||
.map(g => ({
|
||||
AttributeTypeCodeNumber: g.typeCode,
|
||||
TypeLabel: `${g.typeCode}-${g.typeDesc}`,
|
||||
AttributeCode: '',
|
||||
Options: g.options
|
||||
}))
|
||||
return rows
|
||||
}
|
||||
|
||||
async function openAttributeDialog (itemCode) {
|
||||
const code = String(itemCode || '').trim().toUpperCase()
|
||||
if (!code) return
|
||||
attributeTargetCode.value = code
|
||||
const existing = store.getProductAttributeDraft(code)
|
||||
const fetched = await store.fetchProductAttributes(1)
|
||||
const fromLookup = buildAttributeRowsFromLookup(fetched)
|
||||
const baseRows = fromLookup.length ? fromLookup : createDummyAttributeRows()
|
||||
attributeRows.value = Array.isArray(existing) && existing.length
|
||||
? JSON.parse(JSON.stringify(existing))
|
||||
: baseRows
|
||||
attributeDialogOpen.value = true
|
||||
}
|
||||
|
||||
function saveAttributeDraft () {
|
||||
const code = String(attributeTargetCode.value || '').trim().toUpperCase()
|
||||
if (!code) return
|
||||
for (const row of (attributeRows.value || [])) {
|
||||
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 || [])))
|
||||
attributeDialogOpen.value = false
|
||||
$q.notify({ type: 'positive', message: 'Urun ozellikleri taslagi kaydedildi.' })
|
||||
}
|
||||
|
||||
function collectProductAttributesFromSelectedRows (selectedRows) {
|
||||
const codeSet = [...new Set(
|
||||
(selectedRows || [])
|
||||
.map(r => String(r?.NewItemCode || '').trim().toUpperCase())
|
||||
.filter(Boolean)
|
||||
)]
|
||||
const out = []
|
||||
|
||||
for (const code of codeSet) {
|
||||
const rows = store.getProductAttributeDraft(code)
|
||||
if (!Array.isArray(rows) || !rows.length) {
|
||||
return { errMsg: `${code} icin urun ozellikleri secilmedi`, productAttributes: [] }
|
||||
}
|
||||
for (const row of rows) {
|
||||
const attributeTypeCode = Number(row?.AttributeTypeCodeNumber || 0)
|
||||
const attributeCode = String(row?.AttributeCode || '').trim()
|
||||
if (!attributeTypeCode || !attributeCode) {
|
||||
return { errMsg: `${code} icin urun ozellikleri eksik`, productAttributes: [] }
|
||||
}
|
||||
out.push({
|
||||
ItemTypeCode: 1,
|
||||
ItemCode: code,
|
||||
AttributeTypeCode: attributeTypeCode,
|
||||
AttributeCode: attributeCode
|
||||
})
|
||||
}
|
||||
}
|
||||
return { errMsg: '', productAttributes: out }
|
||||
}
|
||||
|
||||
function collectCdItemsFromSelectedRows (selectedRows) {
|
||||
const codes = [...new Set(
|
||||
(selectedRows || [])
|
||||
.filter(r => r?.NewItemMode === 'new' && String(r?.NewItemCode || '').trim())
|
||||
.map(r => String(r.NewItemCode).trim().toUpperCase())
|
||||
)]
|
||||
if (!codes.length) return { errMsg: '', cdItems: [] }
|
||||
|
||||
const out = []
|
||||
for (const code of codes) {
|
||||
const draft = store.getCdItemDraft(code)
|
||||
if (!draft) {
|
||||
return { errMsg: `${code} icin cdItem bilgisi eksik`, cdItems: [] }
|
||||
}
|
||||
out.push(normalizeCdItemDraftForPayload(draft))
|
||||
}
|
||||
return { errMsg: '', cdItems: out }
|
||||
}
|
||||
|
||||
function buildMailLineLabelFromRow (row) {
|
||||
const item = String(row?.NewItemCode || row?.OldItemCode || '').trim().toUpperCase()
|
||||
const color1 = String(row?.NewColor || row?.OldColor || '').trim().toUpperCase()
|
||||
@@ -516,14 +927,23 @@ function formatSizes (sizeMap) {
|
||||
function groupItems (items, prevRows = []) {
|
||||
const prevMap = new Map()
|
||||
for (const r of prevRows || []) {
|
||||
if (r?.RowKey) prevMap.set(r.RowKey, String(r.NewDesc || '').trim())
|
||||
if (!r?.RowKey) continue
|
||||
prevMap.set(r.RowKey, {
|
||||
NewDesc: String(r.NewDesc || '').trim(),
|
||||
NewItemCode: String(r.NewItemCode || '').trim().toUpperCase(),
|
||||
NewColor: String(r.NewColor || '').trim().toUpperCase(),
|
||||
NewDim2: String(r.NewDim2 || '').trim().toUpperCase(),
|
||||
NewItemMode: String(r.NewItemMode || '').trim(),
|
||||
NewItemSource: String(r.NewItemSource || '').trim()
|
||||
})
|
||||
}
|
||||
const map = new Map()
|
||||
|
||||
for (const it of items) {
|
||||
const key = buildGroupKey(it)
|
||||
if (!map.has(key)) {
|
||||
const prevDesc = prevMap.get(key) || ''
|
||||
const prev = prevMap.get(key) || {}
|
||||
const prevDesc = prev.NewDesc || ''
|
||||
const fallbackDesc = String((it?.NewDesc || it?.OldDesc) || '').trim()
|
||||
map.set(key, {
|
||||
RowKey: key,
|
||||
@@ -536,10 +956,12 @@ function groupItems (items, prevRows = []) {
|
||||
OrderLineIDs: [],
|
||||
OldSizes: [],
|
||||
OldSizesLabel: '',
|
||||
NewItemCode: '',
|
||||
NewColor: '',
|
||||
NewDim2: '',
|
||||
NewItemCode: prev.NewItemCode || '',
|
||||
NewColor: prev.NewColor || '',
|
||||
NewDim2: prev.NewDim2 || '',
|
||||
NewDesc: prevDesc || fallbackDesc,
|
||||
NewItemMode: prev.NewItemMode || 'empty',
|
||||
NewItemSource: prev.NewItemSource || '',
|
||||
IsVariantMissing: !!it.IsVariantMissing
|
||||
})
|
||||
}
|
||||
@@ -560,6 +982,10 @@ function groupItems (items, prevRows = []) {
|
||||
const sizes = formatSizes(g.__sizeMap || {})
|
||||
g.OldSizes = sizes.list
|
||||
g.OldSizesLabel = sizes.label
|
||||
const info = store.classifyItemCode(g.NewItemCode)
|
||||
g.NewItemCode = info.normalized
|
||||
g.NewItemMode = info.mode
|
||||
if (info.mode === 'empty') g.NewItemSource = ''
|
||||
delete g.__sizeMap
|
||||
out.push(g)
|
||||
}
|
||||
@@ -589,6 +1015,20 @@ async function onBulkSubmit () {
|
||||
$q.notify({ type: 'negative', message: 'Secili satirlarda guncellenecek kayit bulunamadi.' })
|
||||
return
|
||||
}
|
||||
const { errMsg: cdErrMsg, cdItems } = collectCdItemsFromSelectedRows(selectedRows)
|
||||
if (cdErrMsg) {
|
||||
$q.notify({ type: 'negative', message: cdErrMsg })
|
||||
const firstCode = String(cdErrMsg.split(' ')[0] || '').trim()
|
||||
if (firstCode) openCdItemDialog(firstCode)
|
||||
return
|
||||
}
|
||||
const { errMsg: attrErrMsg, productAttributes } = collectProductAttributesFromSelectedRows(selectedRows)
|
||||
if (attrErrMsg) {
|
||||
$q.notify({ type: 'negative', message: attrErrMsg })
|
||||
const firstCode = String(attrErrMsg.split(' ')[0] || '').trim()
|
||||
if (firstCode) openAttributeDialog(firstCode)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const validate = await store.validateUpdates(orderHeaderID.value, lines)
|
||||
@@ -604,7 +1044,7 @@ async function onBulkSubmit () {
|
||||
ok: { label: 'Ekle ve Guncelle', color: 'primary' },
|
||||
cancel: { label: 'Vazgec', flat: true }
|
||||
}).onOk(async () => {
|
||||
await store.applyUpdates(orderHeaderID.value, lines, true)
|
||||
await store.applyUpdates(orderHeaderID.value, lines, true, cdItems, productAttributes)
|
||||
await store.fetchItems(orderHeaderID.value)
|
||||
selectedMap.value = {}
|
||||
await sendUpdateMailAfterApply(selectedRows)
|
||||
@@ -612,7 +1052,7 @@ async function onBulkSubmit () {
|
||||
return
|
||||
}
|
||||
|
||||
await store.applyUpdates(orderHeaderID.value, lines, false)
|
||||
await store.applyUpdates(orderHeaderID.value, lines, false, cdItems, productAttributes)
|
||||
await store.fetchItems(orderHeaderID.value)
|
||||
selectedMap.value = {}
|
||||
await sendUpdateMailAfterApply(selectedRows)
|
||||
@@ -738,6 +1178,16 @@ async function onBulkSubmit () {
|
||||
background: #e3f3ff;
|
||||
}
|
||||
|
||||
.prod-table :deep(.new-item-existing .q-field__control) {
|
||||
background: #eaf9ef !important;
|
||||
border-left: 3px solid #21ba45;
|
||||
}
|
||||
|
||||
.prod-table :deep(.new-item-new .q-field__control) {
|
||||
background: #fff5e9 !important;
|
||||
border-left: 3px solid #f2a100;
|
||||
}
|
||||
|
||||
.prod-table :deep(td.col-desc),
|
||||
.prod-table :deep(th.col-desc),
|
||||
.prod-table :deep(td.col-wrap),
|
||||
|
||||
@@ -2,40 +2,6 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import api from 'src/services/api'
|
||||
|
||||
function normalizeTextForMatch (v) {
|
||||
return String(v || '')
|
||||
.trim()
|
||||
.toUpperCase()
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
}
|
||||
|
||||
// Production ekranlari icin beden grup tespiti helper'i.
|
||||
// Ozel kural:
|
||||
// YETISKIN/GARSON = GARSON ve URUN ANA GRUBU "GOMLEK ATA YAKA" veya "GOMLEK KLASIK" ise => yas
|
||||
export function detectProductionBedenGroup (bedenList, urunAnaGrubu = '', urunKategori = '', yetiskinGarson = '') {
|
||||
const list = Array.isArray(bedenList) ? bedenList : []
|
||||
const hasLetterSizes = list
|
||||
.map(v => String(v || '').trim().toUpperCase())
|
||||
.some(v => ['XS', 'S', 'M', 'L', 'XL', '2XL', '3XL', '4XL', '5XL', '6XL', '7XL'].includes(v))
|
||||
|
||||
const ana = normalizeTextForMatch(urunAnaGrubu)
|
||||
const kat = normalizeTextForMatch(urunKategori)
|
||||
const yg = normalizeTextForMatch(yetiskinGarson)
|
||||
|
||||
if ((kat.includes('GARSON') || yg.includes('GARSON')) &&
|
||||
(ana.includes('GOMLEK ATAYAKA') || ana.includes('GOMLEK ATA YAKA') || ana.includes('GOMLEK KLASIK'))) {
|
||||
return 'yas'
|
||||
}
|
||||
|
||||
if (hasLetterSizes) return 'gom'
|
||||
if ((ana.includes('AYAKKABI') || kat.includes('AYAKKABI')) && (kat.includes('GARSON') || yg.includes('GARSON'))) return 'ayk_garson'
|
||||
if (kat.includes('GARSON') || yg.includes('GARSON') || ana.includes('GARSON')) return 'yas'
|
||||
if (ana.includes('PANTOLON') && kat.includes('YETISKIN')) return 'pan'
|
||||
if (ana.includes('AKSESUAR')) return 'aksbir'
|
||||
return 'tak'
|
||||
}
|
||||
|
||||
function extractApiErrorMessage (err, fallback) {
|
||||
const data = err?.response?.data
|
||||
if (typeof data === 'string' && data.trim()) return data
|
||||
@@ -70,12 +36,40 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', {
|
||||
products: [],
|
||||
colorOptionsByCode: {},
|
||||
secondColorOptionsByKey: {},
|
||||
productAttributesByItemType: {},
|
||||
cdItemLookups: null,
|
||||
cdItemDraftsByCode: {},
|
||||
productAttributeDraftsByCode: {},
|
||||
loading: false,
|
||||
saving: false,
|
||||
error: null
|
||||
}),
|
||||
|
||||
getters: {
|
||||
productCodeSet (state) {
|
||||
const set = new Set()
|
||||
for (const p of (state.products || [])) {
|
||||
const code = String(p?.ProductCode || '').trim().toUpperCase()
|
||||
if (code) set.add(code)
|
||||
}
|
||||
return set
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
classifyItemCode (value) {
|
||||
const normalized = String(value || '').trim().toUpperCase()
|
||||
if (!normalized) {
|
||||
return { normalized: '', mode: 'empty', exists: false }
|
||||
}
|
||||
const exists = this.productCodeSet.has(normalized)
|
||||
return {
|
||||
normalized,
|
||||
mode: exists ? 'existing' : 'new',
|
||||
exists
|
||||
}
|
||||
},
|
||||
|
||||
async fetchHeader (orderHeaderID) {
|
||||
if (!orderHeaderID) {
|
||||
this.header = null
|
||||
@@ -166,6 +160,62 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', {
|
||||
return []
|
||||
}
|
||||
},
|
||||
async fetchProductAttributes (itemTypeCode = 1) {
|
||||
const key = String(itemTypeCode || 1)
|
||||
if (this.productAttributesByItemType[key]) {
|
||||
return this.productAttributesByItemType[key]
|
||||
}
|
||||
try {
|
||||
const res = await api.get('/product-attributes', { params: { itemTypeCode } })
|
||||
const list = Array.isArray(res?.data) ? res.data : []
|
||||
this.productAttributesByItemType[key] = list
|
||||
return list
|
||||
} catch (err) {
|
||||
this.error = err?.response?.data || err?.message || 'Urun ozellikleri alinamadi'
|
||||
return []
|
||||
}
|
||||
},
|
||||
async fetchCdItemLookups (force = false) {
|
||||
if (this.cdItemLookups && !force) return this.cdItemLookups
|
||||
try {
|
||||
const res = await api.get('/orders/production-items/cditem-lookups')
|
||||
this.cdItemLookups = res?.data || null
|
||||
return this.cdItemLookups
|
||||
} catch (err) {
|
||||
this.error = err?.response?.data || err?.message || 'cdItem lookup listesi alinamadi'
|
||||
return null
|
||||
}
|
||||
},
|
||||
setCdItemDraft (itemCode, draft) {
|
||||
const code = String(itemCode || '').trim().toUpperCase()
|
||||
if (!code) return
|
||||
this.cdItemDraftsByCode = {
|
||||
...this.cdItemDraftsByCode,
|
||||
[code]: {
|
||||
...(draft || {}),
|
||||
ItemCode: code,
|
||||
ItemTypeCode: Number(draft?.ItemTypeCode || 1)
|
||||
}
|
||||
}
|
||||
},
|
||||
getCdItemDraft (itemCode) {
|
||||
const code = String(itemCode || '').trim().toUpperCase()
|
||||
if (!code) return null
|
||||
return this.cdItemDraftsByCode[code] || null
|
||||
},
|
||||
setProductAttributeDraft (itemCode, rows) {
|
||||
const code = String(itemCode || '').trim().toUpperCase()
|
||||
if (!code) return
|
||||
this.productAttributeDraftsByCode = {
|
||||
...this.productAttributeDraftsByCode,
|
||||
[code]: Array.isArray(rows) ? rows : []
|
||||
}
|
||||
},
|
||||
getProductAttributeDraft (itemCode) {
|
||||
const code = String(itemCode || '').trim().toUpperCase()
|
||||
if (!code) return []
|
||||
return this.productAttributeDraftsByCode[code] || []
|
||||
},
|
||||
async validateUpdates (orderHeaderID, lines) {
|
||||
if (!orderHeaderID) return { missingCount: 0, missing: [] }
|
||||
|
||||
@@ -186,7 +236,7 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', {
|
||||
this.saving = false
|
||||
}
|
||||
},
|
||||
async applyUpdates (orderHeaderID, lines, insertMissing) {
|
||||
async applyUpdates (orderHeaderID, lines, insertMissing, cdItems = [], productAttributes = []) {
|
||||
if (!orderHeaderID) return { updated: 0, inserted: 0 }
|
||||
|
||||
this.saving = true
|
||||
@@ -195,7 +245,7 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', {
|
||||
try {
|
||||
const res = await api.post(
|
||||
`/orders/production-items/${encodeURIComponent(orderHeaderID)}/apply`,
|
||||
{ lines, insertMissing }
|
||||
{ lines, insertMissing, cdItems, productAttributes }
|
||||
)
|
||||
return res?.data || { updated: 0, inserted: 0 }
|
||||
} catch (err) {
|
||||
|
||||
@@ -42,20 +42,21 @@ export function buildComboKey(row, beden) {
|
||||
|
||||
|
||||
|
||||
export const BEDEN_SCHEMA = [
|
||||
{ key: 'tak', title: 'TAKIM ELBISE', values: ['44','46','48','50','52','54','56','58','60','62','64','66','68','70','72','74'] },
|
||||
{ key: 'ayk', title: 'AYAKKABI', values: ['39','40','41','42','43','44','45'] },
|
||||
{ key: 'ayk_garson', title: 'AYAKKABI GARSON', values: ['22','23','24','25','26','27','28','29','30','31','32','33','34','35','STD'] },
|
||||
{ key: 'yas', title: 'YAS', values: ['2','4','6','8','10','12','14'] },
|
||||
{ key: 'pan', title: 'PANTOLON', values: ['38','40','42','44','46','48','50','52','54','56','58','60','62','64','66','68'] },
|
||||
{ key: 'gom', title: 'GOMLEK', values: ['XS','S','M','L','XL','2XL','3XL','4XL','5XL','6XL','7XL'] },
|
||||
{ key: 'aksbir', title: 'AKSESUAR', values: [' ', '44', 'STD', '110', '115', '120', '125', '130', '135'] }
|
||||
]
|
||||
const SIZE_GROUP_TITLES = {
|
||||
tak: 'TAKIM ELBISE',
|
||||
ayk: 'AYAKKABI',
|
||||
ayk_garson: 'AYAKKABI GARSON',
|
||||
yas: 'YAS',
|
||||
pan: 'PANTOLON',
|
||||
gom: 'GOMLEK',
|
||||
aksbir: 'AKSESUAR'
|
||||
}
|
||||
|
||||
export const schemaByKey = BEDEN_SCHEMA.reduce((m, g) => {
|
||||
m[g.key] = g
|
||||
return m
|
||||
}, {})
|
||||
const FALLBACK_SCHEMA_MAP = {
|
||||
tak: { key: 'tak', title: 'TAKIM ELBISE', values: ['44', '46', '48', '50', '52', '54', '56', '58', '60', '62', '64', '66', '68', '70', '72', '74'] }
|
||||
}
|
||||
|
||||
export const schemaByKey = { ...FALLBACK_SCHEMA_MAP }
|
||||
|
||||
const productSizeMatchCache = {
|
||||
loaded: false,
|
||||
@@ -111,6 +112,23 @@ function setProductSizeMatchCache(payload) {
|
||||
productSizeMatchCache.schemas = normalizedSchemas
|
||||
}
|
||||
|
||||
function buildSchemaMapFromCacheSchemas() {
|
||||
const out = {}
|
||||
const src = productSizeMatchCache.schemas || {}
|
||||
for (const [keyRaw, valuesRaw] of Object.entries(src)) {
|
||||
const key = String(keyRaw || '').trim()
|
||||
if (!key) continue
|
||||
const values = Array.isArray(valuesRaw) ? valuesRaw : []
|
||||
out[key] = {
|
||||
key,
|
||||
title: SIZE_GROUP_TITLES[key] || key.toUpperCase(),
|
||||
values: values.map(v => String(v == null ? '' : v))
|
||||
}
|
||||
}
|
||||
if (!out.tak) out.tak = { ...FALLBACK_SCHEMA_MAP.tak }
|
||||
return out
|
||||
}
|
||||
|
||||
|
||||
export const stockMap = ref({})
|
||||
export const bedenStock = ref([])
|
||||
@@ -257,25 +275,17 @@ export const useOrderEntryStore = defineStore('orderentry', {
|
||||
,
|
||||
/* ===========================================================
|
||||
🧩 initSchemaMap — BEDEN ŞEMA İNİT
|
||||
- TEK SOURCE OF TRUTH: BEDEN_SCHEMA
|
||||
- TEK SOURCE OF TRUTH: SQL mk_size_group (cache)
|
||||
=========================================================== */
|
||||
initSchemaMap() {
|
||||
if (this.schemaMap && Object.keys(this.schemaMap).length > 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const map = {}
|
||||
|
||||
for (const g of BEDEN_SCHEMA) {
|
||||
map[g.key] = {
|
||||
key: g.key,
|
||||
title: g.title,
|
||||
values: [...g.values]
|
||||
}
|
||||
this.schemaMap = buildSchemaMapFromCacheSchemas()
|
||||
if (!Object.keys(this.schemaMap).length) {
|
||||
this.schemaMap = { ...FALLBACK_SCHEMA_MAP }
|
||||
}
|
||||
|
||||
this.schemaMap = map
|
||||
|
||||
console.log(
|
||||
'🧩 schemaMap INIT edildi:',
|
||||
Object.keys(this.schemaMap)
|
||||
@@ -284,17 +294,20 @@ export const useOrderEntryStore = defineStore('orderentry', {
|
||||
|
||||
async ensureProductSizeMatchRules($q = null, force = false) {
|
||||
if (!force && productSizeMatchCache.loaded && productSizeMatchCache.rules.length > 0) {
|
||||
this.schemaMap = buildSchemaMapFromCacheSchemas()
|
||||
return true
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await api.get('/product-size-match/rules')
|
||||
setProductSizeMatchCache(res?.data || {})
|
||||
this.schemaMap = buildSchemaMapFromCacheSchemas()
|
||||
return true
|
||||
} catch (err) {
|
||||
if (force) {
|
||||
resetProductSizeMatchCache()
|
||||
}
|
||||
this.schemaMap = { ...FALLBACK_SCHEMA_MAP }
|
||||
console.warn('⚠ product-size-match rules alınamadı:', err)
|
||||
$q?.notify?.({
|
||||
type: 'warning',
|
||||
@@ -4044,6 +4057,8 @@ export function toSummaryRowFromForm(form) {
|
||||
|
||||
urunAnaGrubu: form.urunAnaGrubu || '',
|
||||
urunAltGrubu: form.urunAltGrubu || '',
|
||||
kategori: form.kategori || '',
|
||||
yetiskinGarson: form.yetiskinGarson || form.askiliyan || '',
|
||||
aciklama: form.aciklama || '',
|
||||
|
||||
fiyat: Number(form.fiyat || 0),
|
||||
|
||||
Reference in New Issue
Block a user