Merge remote-tracking branch 'origin/master'

This commit is contained in:
M_Kececi
2026-04-15 15:54:23 +03:00
parent c925af5ba1
commit 5be7315bdb
13 changed files with 1412 additions and 730 deletions

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

View File

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

View File

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

View File

@@ -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)
}
}
}
}