Merge remote-tracking branch 'origin/master'

This commit is contained in:
M_Kececi
2026-04-14 16:17:43 +03:00
parent b1a3bbd3c5
commit 214677da1e
21 changed files with 3265 additions and 339 deletions

View File

@@ -6,12 +6,16 @@ function extractApiErrorMessage (err, fallback) {
const data = err?.response?.data
if (typeof data === 'string' && data.trim()) return data
if (data && typeof data === 'object') {
const validationMessages = Array.isArray(data.barcodeValidations)
? data.barcodeValidations.map(v => String(v?.message || '').trim()).filter(Boolean)
: []
const msg = String(data.message || '').trim()
const step = String(data.step || '').trim()
const detail = String(data.detail || '').trim()
const parts = [msg]
if (step) parts.push(`step=${step}`)
if (detail) parts.push(detail)
if (validationMessages.length) parts.push(validationMessages.join(' | '))
const merged = parts.filter(Boolean).join(' | ')
if (merged) return merged
}
@@ -36,6 +40,51 @@ function nowMs () {
return Date.now()
}
const YAS_NUMERIC_SIZES = new Set(['2', '4', '6', '8', '10', '12', '14'])
function safeStr (value) {
return value == null ? '' : String(value).trim()
}
function normalizeProductionDim1Label (value) {
let text = safeStr(value)
if (!text) return ''
text = text.toUpperCase()
const yasMatch = text.match(/^(\d+)\s*(Y|YAS|YAŞ)$/)
if (yasMatch?.[1] && YAS_NUMERIC_SIZES.has(yasMatch[1])) {
return yasMatch[1]
}
return text
}
function pickPreferredProductionYasPayloadLabel (currentRaw, nextRaw) {
const current = safeStr(currentRaw).toUpperCase()
const next = safeStr(nextRaw).toUpperCase()
if (!next) return current
if (!current) return next
const currentHasYas = /YAS$|YAŞ$/.test(current)
const nextHasYas = /YAS$|YAŞ$/.test(next)
if (!currentHasYas && nextHasYas) return next
return current
}
function toProductionPayloadDim1 (row, value) {
const base = normalizeProductionDim1Label(value)
if (!base) return ''
if (!YAS_NUMERIC_SIZES.has(base)) return base
const map =
row?.yasPayloadMap && typeof row.yasPayloadMap === 'object'
? row.yasPayloadMap
: {}
const mapped = safeStr(map[base]).toUpperCase()
if (mapped) return mapped
return `${base}Y`
}
export const useOrderProductionItemStore = defineStore('orderproductionitems', {
state: () => ({
items: [],
@@ -54,6 +103,7 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', {
cdItemLookups: null,
cdItemDraftsByCode: {},
productAttributeDraftsByCode: {},
knownExistingItemCodes: {},
loading: false,
saving: false,
error: null
@@ -71,18 +121,35 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', {
},
actions: {
normalizeDim1ForUi (value) {
return normalizeProductionDim1Label(value)
},
pickPreferredYasPayloadLabel (currentRaw, nextRaw) {
return pickPreferredProductionYasPayloadLabel(currentRaw, nextRaw)
},
toPayloadDim1Code (row, value) {
return toProductionPayloadDim1(row, value)
},
classifyItemCode (value) {
const normalized = String(value || '').trim().toUpperCase()
if (!normalized) {
return { normalized: '', mode: 'empty', exists: false }
}
const exists = this.productCodeSet.has(normalized)
const exists = this.productCodeSet.has(normalized) || !!this.knownExistingItemCodes[normalized]
return {
normalized,
mode: exists ? 'existing' : 'new',
exists
}
},
markItemCodeKnownExisting (itemCode, exists = true) {
const code = String(itemCode || '').trim().toUpperCase()
if (!code) return
this.knownExistingItemCodes = {
...this.knownExistingItemCodes,
[code]: !!exists
}
},
async fetchHeader (orderHeaderID) {
if (!orderHeaderID) {
@@ -134,6 +201,20 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', {
this.error = err?.response?.data || err?.message || 'Urun listesi alinamadi'
}
},
async fetchCdItemByCode (code) {
if (!code) return null
try {
const res = await api.get('/product-cditem', { params: { code } })
const data = res?.data || null
if (data) {
this.markItemCodeKnownExisting(code, true)
}
return data
} catch (err) {
console.error('[OrderProductionItemStore] fetchCdItemByCode failed', err)
return null
}
},
async fetchColors (productCode) {
const code = String(productCode || '').trim()
if (!code) return []
@@ -152,6 +233,7 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', {
const res = await api.get('/product-colors', { params: { code } })
const data = res?.data
const list = Array.isArray(data) ? data : []
if (list.length) this.markItemCodeKnownExisting(code, true)
this.colorOptionsByCode[code] = list
console.info('[OrderProductionItemStore] fetchColors done', { code, count: list.length, durationMs: Math.round(nowMs() - t0) })
return list
@@ -284,6 +366,7 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', {
try {
const res = await api.get('/product-item-attributes', { params: { itemTypeCode: itc, itemCode: code } })
const list = Array.isArray(res?.data) ? res.data : []
if (list.length) this.markItemCodeKnownExisting(code, true)
this.productItemAttributesByKey[key] = list
return list
} catch (err) {
@@ -359,6 +442,7 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', {
orderHeaderID,
lineCount: lines?.length || 0,
missingCount: Number(data?.missingCount || 0),
barcodeValidationCount: Number(data?.barcodeValidationCount || 0),
requestId: rid,
durationMs: Math.round(nowMs() - t0)
})
@@ -371,7 +455,7 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', {
this.saving = false
}
},
async applyUpdates (orderHeaderID, lines, insertMissing, cdItems = [], productAttributes = []) {
async applyUpdates (orderHeaderID, lines, insertMissing, cdItems = [], productAttributes = [], headerAverageDueDate = null) {
if (!orderHeaderID) return { updated: 0, inserted: 0 }
this.saving = true
@@ -384,11 +468,18 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', {
lineCount: lines?.length || 0,
insertMissing: !!insertMissing,
cdItemCount: cdItems?.length || 0,
attributeCount: productAttributes?.length || 0
attributeCount: productAttributes?.length || 0,
headerAverageDueDate
})
const res = await api.post(
`/orders/production-items/${encodeURIComponent(orderHeaderID)}/apply`,
{ lines, insertMissing, cdItems, productAttributes }
{
lines,
insertMissing,
cdItems,
productAttributes,
HeaderAverageDueDate: headerAverageDueDate
}
)
const data = res?.data || { updated: 0, inserted: 0 }
const rid = res?.headers?.['x-debug-request-id'] || ''
@@ -396,7 +487,9 @@ export const useOrderProductionItemStore = defineStore('orderproductionitems', {
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)
})

View File

@@ -0,0 +1,88 @@
import { defineStore } from 'pinia'
import api from 'src/services/api'
function toText (value) {
return String(value ?? '').trim()
}
function toNumber (value) {
const n = Number(value)
return Number.isFinite(n) ? Number(n.toFixed(2)) : 0
}
function mapRow (raw, index) {
return {
id: index + 1,
productCode: toText(raw?.ProductCode),
stockQty: toNumber(raw?.StockQty),
stockEntryDate: toText(raw?.StockEntryDate),
lastPricingDate: toText(raw?.LastPricingDate),
askiliYan: toText(raw?.AskiliYan),
kategori: toText(raw?.Kategori),
urunIlkGrubu: toText(raw?.UrunIlkGrubu),
urunAnaGrubu: toText(raw?.UrunAnaGrubu),
urunAltGrubu: toText(raw?.UrunAltGrubu),
icerik: toText(raw?.Icerik),
karisim: toText(raw?.Karisim),
marka: toText(raw?.Marka),
brandGroupSelection: toText(raw?.BrandGroupSec),
costPrice: toNumber(raw?.CostPrice),
expenseForBasePrice: 0,
basePriceUsd: 0,
basePriceTry: 0,
usd1: 0,
usd2: 0,
usd3: 0,
usd4: 0,
usd5: 0,
usd6: 0,
eur1: 0,
eur2: 0,
eur3: 0,
eur4: 0,
eur5: 0,
eur6: 0,
try1: 0,
try2: 0,
try3: 0,
try4: 0,
try5: 0,
try6: 0
}
}
export const useProductPricingStore = defineStore('product-pricing-store', {
state: () => ({
rows: [],
loading: false,
error: ''
}),
actions: {
async fetchRows () {
this.loading = true
this.error = ''
try {
const res = await api.get('/pricing/products')
const data = Array.isArray(res?.data) ? res.data : []
this.rows = data.map((x, i) => mapRow(x, i))
} catch (err) {
this.rows = []
const msg = err?.response?.data || err?.message || 'Urun fiyatlandirma listesi alinamadi'
this.error = toText(msg)
} finally {
this.loading = false
}
},
updateCell (row, field, val) {
if (!row || !field) return
row[field] = toNumber(String(val ?? '').replace(',', '.'))
},
updateBrandGroupSelection (row, val) {
if (!row) return
row.brandGroupSelection = toText(val)
}
}
})

View File

@@ -212,6 +212,8 @@ export const useOrderEntryStore = defineStore('orderentry', {
orders: [],
header: {},
summaryRows: [],
originalHeader: {},
originalLines: [],
lastSavedAt: null,
@@ -534,6 +536,54 @@ export const useOrderEntryStore = defineStore('orderentry', {
const normalized = Array.isArray(lines) ? lines : []
const mapLabel = (ln) => this.buildMailLineLabel(ln)
const formatDate = (d) => {
if (!d) return ''
const s = String(d).split('T')[0]
return s
}
const oldDate = formatDate(this.originalHeader?.AverageDueDate)
const newDate = formatDate(this.header?.AverageDueDate)
const origMap = new Map()
if (Array.isArray(this.originalLines)) {
this.originalLines.forEach(ln => {
if (ln.OrderLineID) origMap.set(String(ln.OrderLineID), ln)
})
}
const buildDueDateChanges = () => {
const out = []
const seen = new Set()
normalized.forEach(ln => {
if (ln?._deleteSignal || !ln?.OrderLineID || ln?._dirty !== true) return
const orig = origMap.get(String(ln.OrderLineID))
if (!orig) return
const itemCode = String(ln?.ItemCode || '').trim().toUpperCase()
const colorCode = String(ln?.ColorCode || '').trim().toUpperCase()
const itemDim2Code = String(ln?.ItemDim2Code || '').trim().toUpperCase()
const oldLnDate = formatDate(orig?.DueDate)
const newLnDate = formatDate(ln?.DueDate)
if (!itemCode || !newLnDate || oldLnDate === newLnDate) return
const key = [itemCode, colorCode, itemDim2Code, oldLnDate, newLnDate].join('||')
if (seen.has(key)) return
seen.add(key)
out.push({
itemCode,
colorCode,
itemDim2Code,
oldDueDate: oldLnDate,
newDueDate: newLnDate
})
})
return out
}
if (isNew) {
return {
operation: 'create',
@@ -543,7 +593,10 @@ export const useOrderEntryStore = defineStore('orderentry', {
normalized
.filter(ln => !ln?._deleteSignal)
.map(mapLabel)
)
),
oldDueDate: '',
newDueDate: '',
dueDateChanges: []
}
}
@@ -553,11 +606,22 @@ export const useOrderEntryStore = defineStore('orderentry', {
.map(mapLabel)
)
const updatedItems = uniq(
normalized
.filter(ln => !ln?._deleteSignal && !!ln?.OrderLineID && ln?._dirty === true)
.map(mapLabel)
)
const updatedItems = []
normalized.forEach(ln => {
if (!ln?._deleteSignal && !!ln?.OrderLineID && ln?._dirty === true) {
let label = mapLabel(ln)
const orig = origMap.get(String(ln.OrderLineID))
if (orig) {
const oldLnDate = formatDate(orig.DueDate)
const newLnDate = formatDate(ln.DueDate)
if (newLnDate && oldLnDate !== newLnDate) {
label += ` (Termin: ${oldLnDate} -> ${newLnDate})`
}
}
updatedItems.push(label)
}
})
const addedItems = uniq(
normalized
@@ -568,8 +632,11 @@ export const useOrderEntryStore = defineStore('orderentry', {
return {
operation: 'update',
deletedItems,
updatedItems,
addedItems
updatedItems: uniq(updatedItems),
addedItems,
oldDueDate: oldDate,
newDueDate: newDate,
dueDateChanges: buildDueDateChanges()
}
}
,
@@ -586,7 +653,10 @@ export const useOrderEntryStore = defineStore('orderentry', {
operation: payload?.operation || 'create',
deletedItems: Array.isArray(payload?.deletedItems) ? payload.deletedItems : [],
updatedItems: Array.isArray(payload?.updatedItems) ? payload.updatedItems : [],
addedItems: Array.isArray(payload?.addedItems) ? payload.addedItems : []
addedItems: Array.isArray(payload?.addedItems) ? payload.addedItems : [],
oldDueDate: payload?.oldDueDate || '',
newDueDate: payload?.newDueDate || '',
dueDateChanges: Array.isArray(payload?.dueDateChanges) ? payload.dueDateChanges : []
})
return res?.data || {}
} catch (err) {
@@ -1113,6 +1183,10 @@ export const useOrderEntryStore = defineStore('orderentry', {
this.orders = Array.isArray(normalized) ? normalized : []
this.summaryRows = [...this.orders]
// 💾 Snapshot for email comparison (v3.5)
this.originalHeader = JSON.parse(JSON.stringify(this.header))
this.originalLines = JSON.parse(JSON.stringify(this.summaryRows))
/* =======================================================
🔹 MODE KARARI (BACKEND SATIRLARI ÜZERİNDEN)
- herhangi bir isClosed=true → view
@@ -3202,6 +3276,7 @@ export const useOrderEntryStore = defineStore('orderentry', {
// 📧 Piyasa eşleşen alıcılara sipariş PDF gönderimi (kayıt başarılı olduktan sonra)
try {
const mailPayload = this.buildOrderMailPayload(lines, isNew)
// UPDATE durumunda da mail gönderimi istendiği için isNew kontrolü kaldırıldı (v3.5)
const mailRes = await this.sendOrderToMarketMails(serverOrderId, mailPayload)
const sentCount = Number(mailRes?.sentCount || 0)
$q.notify({