Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -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)
|
||||
})
|
||||
|
||||
88
ui/src/stores/ProductPricingStore.js
Normal file
88
ui/src/stores/ProductPricingStore.js
Normal 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)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user