Merge remote-tracking branch 'origin/master'

This commit is contained in:
M_Kececi
2026-02-20 08:52:06 +03:00
parent f6b9793c41
commit d9c527d13f
10 changed files with 1254 additions and 0 deletions

View File

@@ -0,0 +1,125 @@
/* eslint-disable */
/**
* THIS FILE IS GENERATED AUTOMATICALLY.
* 1. DO NOT edit this file directly as it won't do anything.
* 2. EDIT the original quasar.config file INSTEAD.
* 3. DO NOT git commit this file. It should be ignored.
*
* This file is still here because there was an error in
* the original quasar.config file and this allows you to
* investigate the Node.js stack error.
*
* After you fix the original file, this file will be
* deleted automatically.
**/
// quasar.config.js
import { defineConfig } from "@quasar/app-webpack/wrappers";
var quasar_config_default = defineConfig(() => {
const apiBaseUrl = (process.env.VITE_API_BASE_URL || "/api").trim();
return {
/* =====================================================
APP INFO
===================================================== */
productName: "Baggi BSS",
productDescription: "Baggi Tekstil Business Support System",
/* =====================================================
BOOT FILES
===================================================== */
boot: ["dayjs"],
/* =====================================================
GLOBAL CSS
===================================================== */
css: ["app.css"],
/* =====================================================
ICONS / FONTS
===================================================== */
extras: [
"roboto-font",
"material-icons"
],
/* =====================================================
BUILD (PRODUCTION)
===================================================== */
build: {
vueRouterMode: "hash",
env: {
VITE_API_BASE_URL: apiBaseUrl
},
esbuildTarget: {
browser: ["es2022", "firefox115", "chrome115", "safari14"],
node: "node20"
},
// Cache & performance
gzip: true,
preloadChunks: true
},
/* =====================================================
DEV SERVER (LOCAL)
===================================================== */
devServer: {
server: { type: "http" },
port: 9e3,
open: true,
// DEV proxy (CORS'suz)
proxy: [
{
context: ["/api"],
target: "http://localhost:8080",
changeOrigin: true,
secure: false
}
]
},
/* =====================================================
QUASAR FRAMEWORK
===================================================== */
framework: {
config: {
notify: {
position: "top",
timeout: 2500
}
},
lang: "tr",
plugins: [
"Loading",
"Dialog",
"Notify"
]
},
animations: [],
/* =====================================================
SSR / PWA (DISABLED)
===================================================== */
ssr: {
prodPort: 3e3,
middlewares: ["render"],
pwa: false
},
pwa: {
workboxMode: "GenerateSW"
},
/* =====================================================
MOBILE / DESKTOP
===================================================== */
capacitor: {
hideSplashscreen: true
},
electron: {
preloadScripts: ["electron-preload"],
inspectPort: 5858,
bundler: "packager",
builder: {
appId: "baggisowtfaresystem"
}
},
bex: {
extraScripts: []
}
};
});
export {
quasar_config_default as default
};

View File

@@ -0,0 +1,57 @@
<template>
<q-page class="q-pa-md">
<div class="text-h6 text-weight-bold">Üretime Verilen Ürünler</div>
<div class="text-caption text-grey-7 q-mt-xs">
OrderHeaderID: {{ orderHeaderID || '-' }}
</div>
<q-table
class="q-mt-md"
flat
bordered
dense
separator="cell"
row-key="OrderLineID"
:rows="store.items"
:columns="columns"
:loading="store.loading"
no-data-label="Üretime verilecek ürün bulunamadı"
:rows-per-page-options="[0]"
hide-bottom
/>
<q-banner v-if="store.error" class="bg-red text-white q-mt-sm">
Hata: {{ store.error }}
</q-banner>
</q-page>
</template>
<script setup>
import { computed, onMounted, watch } from 'vue'
import { useRoute } from 'vue-router'
import { useOrderProductionItemStore } from 'src/stores/OrderProductionItemStore'
const route = useRoute()
const store = useOrderProductionItemStore()
const orderHeaderID = computed(() => String(route.params.orderHeaderID || '').trim())
const columns = [
{ name: 'OldItemCode', label: 'Eski Ürün Kodu', field: 'OldItemCode', align: 'left', sortable: true, style: 'min-width:140px;white-space:nowrap', headerStyle: 'min-width:140px;white-space:nowrap' },
{ name: 'OldColor', label: 'Eski Ürün Rengi', field: 'OldColor', align: 'left', sortable: true, style: 'min-width:120px;white-space:nowrap', headerStyle: 'min-width:120px;white-space:nowrap' },
{ name: 'OldDim2', label: 'Eski 2. Renk', field: 'OldDim2', align: 'left', sortable: true, style: 'min-width:110px;white-space:nowrap', headerStyle: 'min-width:110px;white-space:nowrap' },
{ name: 'OldDesc', label: 'Eski Açıklama', field: 'OldDesc', align: 'left', sortable: false, style: 'min-width:180px;white-space:nowrap', headerStyle: 'min-width:180px;white-space:nowrap' },
{ name: 'NewItemCode', label: 'Yeni Ürün Kodu', field: 'NewItemCode', align: 'left', sortable: true, style: 'min-width:140px;white-space:nowrap', headerStyle: 'min-width:140px;white-space:nowrap' },
{ name: 'NewColor', label: 'Yeni Ürün Rengi', field: 'NewColor', align: 'left', sortable: true, style: 'min-width:120px;white-space:nowrap', headerStyle: 'min-width:120px;white-space:nowrap' },
{ name: 'NewDim2', label: 'Yeni 2. Renk', field: 'NewDim2', align: 'left', sortable: true, style: 'min-width:110px;white-space:nowrap', headerStyle: 'min-width:110px;white-space:nowrap' },
{ name: 'NewDesc', label: 'Yeni Açıklama', field: 'NewDesc', align: 'left', sortable: false, style: 'min-width:180px;white-space:nowrap', headerStyle: 'min-width:180px;white-space:nowrap' }
]
onMounted(() => {
store.fetchItems(orderHeaderID.value)
})
watch(orderHeaderID, (id) => {
store.fetchItems(id)
})
</script>

View File

@@ -0,0 +1,357 @@
<template>
<q-page v-if="canReadOrder" class="ol-page">
<div class="ol-filter-bar">
<div class="ol-filter-row">
<q-input
v-model="store.filters.search"
class="ol-filter-input ol-search"
dense
filled
debounce="300"
clearable
label="Arama (Sipariş No / Cari / Açıklama)"
>
<template #append>
<q-icon name="search" />
</template>
</q-input>
<q-input
v-model="store.filters.CurrAccCode"
class="ol-filter-input"
dense
filled
clearable
label="Cari Kodu"
/>
<q-input
v-model="store.filters.OrderDate"
class="ol-filter-input"
dense
filled
type="date"
label="Sipariş Tarihi"
/>
<div class="ol-filter-actions">
<q-btn
label="Temizle"
icon="clear"
color="grey-7"
flat
:disable="store.loading"
@click="clearFilters"
>
<q-tooltip>Tüm filtreleri temizle</q-tooltip>
</q-btn>
<q-btn
label="Yenile"
color="primary"
icon="refresh"
:loading="store.loading"
@click="store.fetchOrders"
/>
<q-btn
label="Excel'e Aktar"
icon="download"
color="primary"
outline
:disable="store.loading || productionOrders.length === 0"
@click="exportExcel"
/>
</div>
<div class="ol-filter-total">
<div class="ol-total-line">
<span class="ol-total-label">Toplam USD:</span>
<strong class="ol-total-value">
{{ totalVisibleUSD.toLocaleString('tr-TR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) }}
</strong>
</div>
</div>
</div>
</div>
<q-table
title="Üretime Verilecek Ürünleri Olan Siparişler"
class="ol-table"
flat
bordered
dense
separator="cell"
row-key="OrderHeaderID"
:rows="productionOrders"
:columns="columns"
:loading="store.loading"
no-data-label="Üretime verilecek sipariş bulunamadı"
:rows-per-page-options="[0]"
hide-bottom
>
<template #body-cell-open="props">
<q-td :props="props" class="text-center">
<q-btn
icon="open_in_new"
color="primary"
flat
round
dense
@click="selectOrder(props.row)"
>
<q-tooltip>Siparişi </q-tooltip>
</q-btn>
</q-td>
</template>
<template #body-cell-IsCreditableConfirmed="props">
<q-td :props="props" class="text-center q-gutter-sm">
<q-btn
icon="picture_as_pdf"
color="red"
flat
round
dense
@click="printPDF(props.row)"
>
<q-tooltip>Siparişi PDF olarak </q-tooltip>
</q-btn>
<q-icon
:name="props.row.IsCreditableConfirmed ? 'check_circle' : 'cancel'"
:color="props.row.IsCreditableConfirmed ? 'green' : 'red'"
size="20px"
>
<q-tooltip>
{{ props.row.IsCreditableConfirmed ? 'Onaylı' : 'Onaysız' }}
</q-tooltip>
</q-icon>
</q-td>
</template>
<template #body-cell-OrderDate="props">
<q-td :props="props" class="text-center">
{{ formatDate(props.row.OrderDate) }}
</q-td>
</template>
<template #body-cell-CreditableConfirmedDate="props">
<q-td :props="props" class="text-center">
{{ formatDate(props.row.CreditableConfirmedDate) }}
</q-td>
</template>
<template #body-cell-CurrAccDescription="props">
<q-td :props="props" class="ol-col-cari">
<div class="ol-col-multiline">{{ props.value }}</div>
<q-tooltip v-if="props.value">
{{ props.value }}
</q-tooltip>
</q-td>
</template>
<template #body-cell-MusteriTemsilcisi="props">
<q-td :props="props" class="ol-col-short">
<div class="ol-col-multiline">{{ props.value }}</div>
<q-tooltip v-if="props.value">
{{ props.value }}
</q-tooltip>
</q-td>
</template>
<template #body-cell-Piyasa="props">
<q-td :props="props" class="ol-col-short">
<div class="ol-col-multiline">{{ props.value }}</div>
<q-tooltip v-if="props.value">
{{ props.value }}
</q-tooltip>
</q-td>
</template>
<template #body-cell-Description="props">
<q-td :props="props" class="ol-col-desc">
<div class="ol-col-multiline">{{ props.value }}</div>
<q-tooltip v-if="props.value">
{{ props.value }}
</q-tooltip>
</q-td>
</template>
<template #body-cell-HasUretimUrunu="props">
<q-td :props="props" class="text-center">
<q-icon
:name="props.row.HasUretimUrunu ? 'check_circle' : 'cancel'"
:color="props.row.HasUretimUrunu ? 'green' : 'grey-5'"
size="18px"
/>
</q-td>
</template>
</q-table>
<q-banner v-if="store.error" class="bg-red text-white q-mt-sm">
Hata: {{ store.error }}
</q-banner>
</q-page>
<q-page v-else class="q-pa-md flex flex-center">
<div class="text-negative text-subtitle1">
Bu modüle erişim yetkiniz yok.
</div>
</q-page>
</template>
<script setup>
import { computed, onMounted, watch } from 'vue'
import { useRouter } from 'vue-router'
import { useQuasar } from 'quasar'
import { useOrderProductionUpdateStore } from 'src/stores/OrderProductionUpdateStore'
import { useAuthStore } from 'src/stores/authStore'
import { usePermission } from 'src/composables/usePermission'
import api, { extractApiErrorDetail } from 'src/services/api'
const { canRead } = usePermission()
const canReadOrder = canRead('order')
const router = useRouter()
const $q = useQuasar()
const store = useOrderProductionUpdateStore()
let searchTimer = null
watch(
() => store.filters.search,
() => {
clearTimeout(searchTimer)
searchTimer = setTimeout(() => {
store.fetchOrders()
}, 400)
}
)
const productionOrders = computed(() => store.filteredOrders)
const totalVisibleUSD = computed(() =>
productionOrders.value.reduce((sum, o) => {
const v = Number(o.TotalAmountUSD || 0)
return sum + (Number.isFinite(v) ? v : 0)
}, 0)
)
function clearFilters () {
store.filters.search = ''
store.filters.CurrAccCode = ''
store.filters.OrderDate = ''
store.fetchOrders()
}
function formatDate (v) {
if (!v) return ''
return String(v)
}
function selectOrder (row) {
if (!row?.OrderHeaderID) {
$q.notify({ type: 'warning', message: 'OrderHeaderID bulunamadı' })
return
}
router.push({
name: 'orderproductionupdate',
params: { orderHeaderID: row.OrderHeaderID }
})
}
async function printPDF (row) {
try {
const auth = useAuthStore()
if (!auth?.token) {
$q.notify({ type: 'warning', message: 'Oturum bulunamadı' })
return
}
const res = await api.get(`/order/pdf/${row.OrderHeaderID}`, {
responseType: 'blob'
})
const blob = new Blob([res.data], { type: 'application/pdf' })
const url = URL.createObjectURL(blob)
window.open(url, '_blank')
setTimeout(() => URL.revokeObjectURL(url), 2000)
} catch (err) {
const status = err?.response?.status
const detail = extractApiErrorDetail(err)
console.error(`PDF load error [${status}] /order/pdf/${row?.OrderHeaderID}: ${detail}`)
$q.notify({ type: 'negative', message: `PDF alınamadı: ${detail}` })
}
}
function exportExcel () {
const auth = useAuthStore()
if (!auth?.token) {
$q.notify({
type: 'negative',
message: 'Oturum bulunamadı'
})
return
}
const params = new URLSearchParams()
if (store.filters.search) params.append('search', store.filters.search)
api.get(`/orders/production-list?${params.toString()}`, {
responseType: 'blob'
}).then((res) => {
const blob = new Blob([res.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'uretime_verilecek_siparisler.xlsx'
a.click()
URL.revokeObjectURL(url)
}).catch((err) => {
const detail = extractApiErrorDetail(err)
$q.notify({ type: 'negative', message: `Excel alınamadı: ${detail}` })
})
}
onMounted(() => {
store.fetchOrders()
})
const columns = [
{ name: 'open', label: '', field: 'open', align: 'center', sortable: false },
{ name: 'OrderNumber', label: 'Sipariş No', field: 'OrderNumber', align: 'left', sortable: true, style: 'min-width:108px;white-space:nowrap', headerStyle: 'min-width:108px;white-space:nowrap' },
{ name: 'OrderDate', label: 'Tarih', field: 'OrderDate', align: 'center', sortable: true, style: 'min-width:82px;white-space:nowrap', headerStyle: 'min-width:82px;white-space:nowrap' },
{ name: 'CurrAccCode', label: 'Cari Kod', field: 'CurrAccCode', align: 'left', sortable: true, style: 'min-width:82px;white-space:nowrap', headerStyle: 'min-width:82px;white-space:nowrap' },
{ name: 'CurrAccDescription', label: 'Cari Adı', field: 'CurrAccDescription', align: 'left', sortable: true, classes: 'ol-col-cari', headerClasses: 'ol-col-cari', style: 'width:160px;max-width:160px', headerStyle: 'width:160px;max-width:160px' },
{ name: 'MusteriTemsilcisi', label: 'Temsilci', field: 'MusteriTemsilcisi', align: 'left', sortable: true, classes: 'ol-col-short', headerClasses: 'ol-col-short', style: 'width:88px;max-width:88px', headerStyle: 'width:88px;max-width:88px' },
{ name: 'Piyasa', label: 'Piyasa', field: 'Piyasa', align: 'left', sortable: true, classes: 'ol-col-short', headerClasses: 'ol-col-short', style: 'width:72px;max-width:72px', headerStyle: 'width:72px;max-width:72px' },
{ name: 'CreditableConfirmedDate', label: 'Onay', field: 'CreditableConfirmedDate', align: 'center', sortable: true, style: 'min-width:86px;white-space:nowrap', headerStyle: 'min-width:86px;white-space:nowrap' },
{ name: 'DocCurrencyCode', label: 'PB', field: 'DocCurrencyCode', align: 'center', sortable: true, style: 'min-width:46px;white-space:nowrap', headerStyle: 'min-width:46px;white-space:nowrap' },
{
name: 'TotalAmount',
label: 'Tutar',
field: 'TotalAmount',
align: 'right',
sortable: true,
style: 'min-width:120px;white-space:nowrap',
headerStyle: 'min-width:120px;white-space:nowrap',
format: (val, row) => Number(val || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 }) + ' ' + row.DocCurrencyCode
},
{
name: 'TotalAmountUSD',
label: 'Tutar (USD)',
field: 'TotalAmountUSD',
align: 'right',
sortable: true,
style: 'min-width:120px;white-space:nowrap',
headerStyle: 'min-width:120px;white-space:nowrap',
format: val => Number(val || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 }) + ' USD'
},
{ name: 'IsCreditableConfirmed', label: 'Durum', field: 'IsCreditableConfirmed', align: 'center', sortable: true },
{ name: 'HasUretimUrunu', label: 'Üretim', field: 'HasUretimUrunu', align: 'left', sortable: true, style: 'min-width:190px;white-space:nowrap', headerStyle: 'min-width:190px;white-space:nowrap' },
{ name: 'Description', label: 'Açıklama', field: 'Description', align: 'left', sortable: false, classes: 'ol-col-desc', headerClasses: 'ol-col-desc', style: 'width:160px;max-width:160px', headerStyle: 'width:160px;max-width:160px' }
]
</script>

View File

@@ -0,0 +1,34 @@
// src/stores/OrderProductionItemStore.js
import { defineStore } from 'pinia'
import api from 'src/services/api'
export const useOrderProductionItemStore = defineStore('orderproductionitems', {
state: () => ({
items: [],
loading: false,
error: null
}),
actions: {
async fetchItems (orderHeaderID) {
if (!orderHeaderID) {
this.items = []
return
}
this.loading = true
this.error = null
try {
const res = await api.get(`/orders/production-items/${encodeURIComponent(orderHeaderID)}`)
const data = res?.data
this.items = Array.isArray(data) ? data : []
} catch (err) {
this.items = []
this.error = err?.response?.data || err?.message || 'Liste alınamadı'
} finally {
this.loading = false
}
}
}
})

View File

@@ -0,0 +1,166 @@
// src/stores/OrderProductionUpdateStore.js
import { defineStore } from 'pinia'
import api from 'src/services/api'
let lastRequestId = 0
export const useOrderProductionUpdateStore = defineStore('orderproductionupdate', {
state: () => ({
orders: [],
loading: false,
error: null,
filters: {
search: '',
CurrAccCode: '',
OrderDate: ''
}
}),
getters: {
filteredOrders (state) {
let result = state.orders
if (state.filters.CurrAccCode) {
result = result.filter(o => o.CurrAccCode === state.filters.CurrAccCode)
}
if (state.filters.OrderDate) {
result = result.filter(o =>
o.OrderDate?.startsWith(state.filters.OrderDate)
)
}
return result
},
totalVisibleUSD () {
return this.filteredOrders.reduce((sum, o) => {
const value = Number(o.TotalAmountUSD || 0)
return sum + (Number.isFinite(value) ? value : 0)
}, 0)
},
totalPackedVisibleUSD () {
return this.filteredOrders.reduce((sum, o) => {
const value = Number(o.PackedUSD || 0)
return sum + (Number.isFinite(value) ? value : 0)
}, 0)
},
packedVisibleRatePct () {
if (!this.totalVisibleUSD) return 0
return (this.totalPackedVisibleUSD / this.totalVisibleUSD) * 100
}
},
actions: {
async fetchOrders () {
// ==============================
// 📌 REQUEST ID
// ==============================
const rid = ++lastRequestId
// ==============================
// 📌 SEARCH SNAPSHOT
// ==============================
const raw = this.filters.search ?? ''
const trimmed = String(raw).trim()
// ==============================
// 📌 REQUEST LOG
// ==============================
console.groupCollapsed(
`%c[orders-prod] FETCH rid=${rid}`,
'color:#1976d2;font-weight:bold'
)
console.log('raw =', JSON.stringify(raw), 'len=', String(raw).length)
console.log('trimmed =', JSON.stringify(trimmed), 'len=', trimmed.length)
console.log('filters =', JSON.parse(JSON.stringify(this.filters)))
console.log('lastRID =', lastRequestId)
console.groupEnd()
this.loading = true
this.error = null
try {
// ==============================
// 📌 PARAMS
// ==============================
const params = {}
if (trimmed) params.search = trimmed
// ==============================
// 📌 API CALL
// ==============================
const res = await api.get('/orders/production-list', { params })
// ==============================
// 📌 STALE CHECK
// ==============================
if (rid !== lastRequestId) {
console.warn(
`[orders-prod] IGNORE stale response rid=${rid} last=${lastRequestId}`
)
return
}
// ==============================
// 📌 DATA
// ==============================
const data = res?.data
this.orders = Array.isArray(data) ? data : []
// ==============================
// 📌 RESPONSE LOG
// ==============================
console.groupCollapsed(
`%c[orders-prod] RESPONSE rid=${rid} count=${this.orders.length}`,
'color:#2e7d32;font-weight:bold'
)
console.log('status =', res?.status)
console.log(
'sample =',
this.orders.slice(0, 5).map(o => ({
id: o.OrderHeaderID,
no: o.OrderNumber,
code: o.CurrAccCode,
name: o.CurrAccDescription
}))
)
console.groupEnd()
} catch (err) {
if (rid !== lastRequestId) return
console.error(
'[orders-prod] FETCH FAILED',
err?.response?.status,
err?.response?.data || err
)
this.orders = []
this.error =
err?.response?.data ||
err?.message ||
'Sipariş listesi alınamadı'
} finally {
if (rid === lastRequestId) {
this.loading = false
}
}
}
}
})