Merge remote-tracking branch 'origin/master'
This commit is contained in:
18
svc/main.go
18
svc/main.go
@@ -555,6 +555,24 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router
|
||||
wrapV3(http.HandlerFunc(routes.GetProductSecondColorsHandler)),
|
||||
)
|
||||
|
||||
bindV3(r, pgDB,
|
||||
"/api/product-stock-query", "GET",
|
||||
"order", "view",
|
||||
wrapV3(http.HandlerFunc(routes.GetProductStockQueryHandler)),
|
||||
)
|
||||
|
||||
bindV3(r, pgDB,
|
||||
"/api/product-images", "GET",
|
||||
"order", "view",
|
||||
wrapV3(routes.GetProductImagesHandler(pgDB)),
|
||||
)
|
||||
|
||||
bindV3(r, pgDB,
|
||||
"/api/product-images/{id}/content", "GET",
|
||||
"order", "view",
|
||||
wrapV3(routes.GetProductImageContentHandler(pgDB)),
|
||||
)
|
||||
|
||||
// ============================================================
|
||||
// ROLE MANAGEMENT
|
||||
// ============================================================
|
||||
|
||||
201
svc/queries/productstockquery.go
Normal file
201
svc/queries/productstockquery.go
Normal file
@@ -0,0 +1,201 @@
|
||||
package queries
|
||||
|
||||
// GetProductStockQuery:
|
||||
// Ürün kodu bazlı, optimize stok + attribute sorgusu.
|
||||
const GetProductStockQuery = `
|
||||
DECLARE @ProductCode NVARCHAR(50) = @p1;
|
||||
|
||||
;WITH INV AS
|
||||
(
|
||||
SELECT
|
||||
CompanyCode,
|
||||
OfficeCode,
|
||||
StoreTypeCode,
|
||||
StoreCode,
|
||||
WarehouseCode,
|
||||
ItemTypeCode,
|
||||
ItemCode,
|
||||
ColorCode,
|
||||
ItemDim1Code,
|
||||
ItemDim2Code,
|
||||
ItemDim3Code,
|
||||
SUM(PickingQty1) AS PickingQty1,
|
||||
SUM(ReserveQty1) AS ReserveQty1,
|
||||
SUM(DispOrderQty1) AS DispOrderQty1,
|
||||
SUM(InventoryQty1) AS InventoryQty1
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
||||
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code,
|
||||
Qty1 AS PickingQty1, 0 AS ReserveQty1, 0 AS DispOrderQty1, 0 AS InventoryQty1
|
||||
FROM PickingStates
|
||||
WHERE ItemTypeCode = 1
|
||||
AND ItemCode = @ProductCode
|
||||
AND LEN(ItemCode) = 13
|
||||
AND LEN(@ProductCode) = 13
|
||||
|
||||
UNION ALL
|
||||
SELECT
|
||||
CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
||||
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code,
|
||||
0, Qty1, 0, 0
|
||||
FROM ReserveStates
|
||||
WHERE ItemTypeCode = 1
|
||||
AND ItemCode = @ProductCode
|
||||
AND LEN(ItemCode) = 13
|
||||
AND LEN(@ProductCode) = 13
|
||||
|
||||
UNION ALL
|
||||
SELECT
|
||||
CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
||||
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code,
|
||||
0, 0, Qty1, 0
|
||||
FROM DispOrderStates
|
||||
WHERE ItemTypeCode = 1
|
||||
AND ItemCode = @ProductCode
|
||||
AND LEN(ItemCode) = 13
|
||||
AND LEN(@ProductCode) = 13
|
||||
|
||||
UNION ALL
|
||||
SELECT
|
||||
CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
||||
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code,
|
||||
0, 0, 0, SUM(In_Qty1 - Out_Qty1)
|
||||
FROM trStock WITH (NOLOCK)
|
||||
WHERE ItemTypeCode = 1
|
||||
AND ItemCode = @ProductCode
|
||||
AND LEN(ItemCode) = 13
|
||||
AND LEN(@ProductCode) = 13
|
||||
GROUP BY
|
||||
CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
||||
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code
|
||||
) X
|
||||
GROUP BY
|
||||
CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
||||
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code
|
||||
),
|
||||
Attr AS
|
||||
(
|
||||
SELECT TOP 1
|
||||
ProductCode,
|
||||
ProductDescription,
|
||||
ProductAtt01Desc,
|
||||
ProductAtt02Desc,
|
||||
ProductAtt10Desc,
|
||||
ProductAtt11Desc,
|
||||
ProductAtt21Desc,
|
||||
ProductAtt22Desc,
|
||||
ProductAtt23Desc,
|
||||
ProductAtt24Desc,
|
||||
ProductAtt25Desc,
|
||||
ProductAtt26Desc,
|
||||
ProductAtt27Desc,
|
||||
ProductAtt28Desc,
|
||||
ProductAtt29Desc,
|
||||
ProductAtt30Desc,
|
||||
ProductAtt31Desc,
|
||||
ProductAtt32Desc,
|
||||
ProductAtt33Desc,
|
||||
ProductAtt34Desc,
|
||||
ProductAtt35Desc,
|
||||
ProductAtt36Desc,
|
||||
ProductAtt37Desc,
|
||||
ProductAtt38Desc,
|
||||
ProductAtt39Desc,
|
||||
ProductAtt40Desc,
|
||||
ProductAtt41Desc,
|
||||
ProductAtt42Desc,
|
||||
ProductAtt43Desc,
|
||||
ProductAtt44Desc,
|
||||
ProductAtt45Desc,
|
||||
ProductAtt46Desc
|
||||
FROM ProductFilterWithDescription('TR')
|
||||
WHERE ProductCode = @ProductCode
|
||||
AND LEN(ProductCode) = 13
|
||||
AND LEN(@ProductCode) = 13
|
||||
),
|
||||
Price AS
|
||||
(
|
||||
SELECT TOP 1
|
||||
Price
|
||||
FROM prItemBasePrice WITH (NOLOCK)
|
||||
WHERE ItemTypeCode = 1
|
||||
AND ItemCode = @ProductCode
|
||||
AND LEN(ItemCode) = 13
|
||||
AND LEN(@ProductCode) = 13
|
||||
ORDER BY PriceDate DESC
|
||||
)
|
||||
SELECT
|
||||
I.WarehouseCode AS Depo_Kodu,
|
||||
W.WarehouseDescription AS Depo_Adi,
|
||||
IT.ItemTypeDescription AS InventoryType,
|
||||
I.ItemCode AS Urun_Kodu,
|
||||
A.ProductDescription AS Madde_Aciklamasi,
|
||||
I.ColorCode AS Renk_Kodu,
|
||||
C.ColorDescription AS Renk_Aciklamasi,
|
||||
I.ItemDim1Code AS Beden,
|
||||
I.ItemDim2Code AS Yaka,
|
||||
ROUND(I.InventoryQty1 - I.PickingQty1 - I.ReserveQty1 - I.DispOrderQty1, U.RoundDigit) AS Kullanilabilir_Envanter,
|
||||
A.ProductAtt01Desc AS URUN_ANA_GRUBU,
|
||||
A.ProductAtt02Desc AS URUN_ALT_GRUBU,
|
||||
A.ProductAtt10Desc AS MARKA,
|
||||
A.ProductAtt11Desc AS DR,
|
||||
A.ProductAtt21Desc AS KALIP,
|
||||
A.ProductAtt22Desc AS IKINCI_PARCA_KALIP,
|
||||
A.ProductAtt23Desc AS PACA_GENISLIGI,
|
||||
A.ProductAtt24Desc AS UCUNCU_PARCA_KALIP,
|
||||
A.ProductAtt25Desc AS UCUNCU_PARCA_MODEL,
|
||||
A.ProductAtt26Desc AS BIRINCI_PARCA_KUMAS,
|
||||
A.ProductAtt27Desc AS IKINCI_PARCA_KUMAS,
|
||||
A.ProductAtt28Desc AS UCUNCU_PARCA_KUMAS,
|
||||
A.ProductAtt29Desc AS BIRINCI_PARCA_KARISIM,
|
||||
A.ProductAtt30Desc AS IKINCI_PARCA_KARISIM,
|
||||
A.ProductAtt31Desc AS UCUNCU_PARCA_KARISIM,
|
||||
A.ProductAtt32Desc AS YAKA_TIPI,
|
||||
A.ProductAtt33Desc AS DUGME,
|
||||
A.ProductAtt34Desc AS YIRTMAC,
|
||||
A.ProductAtt35Desc AS SEZON_YILI,
|
||||
A.ProductAtt36Desc AS MEVSIM,
|
||||
A.ProductAtt37Desc AS TABAN,
|
||||
A.ProductAtt38Desc AS BIRINCI_PARCA_FIT,
|
||||
A.ProductAtt39Desc AS IKINCI_PARCA_FIT,
|
||||
A.ProductAtt40Desc AS BOS2,
|
||||
A.ProductAtt41Desc AS KISA_KAR,
|
||||
A.ProductAtt42Desc AS SERI_FASON,
|
||||
A.ProductAtt43Desc AS STOK_GIRIS_YONTEMI,
|
||||
A.ProductAtt44Desc AS YETISKIN_GARSON,
|
||||
A.ProductAtt45Desc AS ASKILI_YAN,
|
||||
A.ProductAtt46Desc AS BOS3,
|
||||
P.Price AS Fiyat
|
||||
FROM INV I
|
||||
JOIN cdItem CI WITH (NOLOCK)
|
||||
ON CI.ItemTypeCode = I.ItemTypeCode
|
||||
AND CI.ItemCode = I.ItemCode
|
||||
LEFT JOIN cdUnitOfMeasure U WITH (NOLOCK)
|
||||
ON U.UnitOfMeasureCode = CI.UnitOfMeasureCode1
|
||||
LEFT JOIN cdWarehouseDesc W WITH (NOLOCK)
|
||||
ON W.WarehouseCode = I.WarehouseCode
|
||||
AND W.LangCode = 'TR'
|
||||
LEFT JOIN bsItemTypeDesc IT WITH (NOLOCK)
|
||||
ON IT.ItemTypeCode = I.ItemTypeCode
|
||||
AND IT.LangCode = 'TR'
|
||||
LEFT JOIN cdColorDesc C WITH (NOLOCK)
|
||||
ON C.ColorCode = I.ColorCode
|
||||
AND C.LangCode = 'TR'
|
||||
CROSS JOIN Attr A
|
||||
OUTER APPLY (SELECT TOP 1 Price FROM Price) P
|
||||
WHERE
|
||||
I.ItemTypeCode = 1
|
||||
AND I.ItemCode = @ProductCode
|
||||
AND LEN(I.ItemCode) = 13
|
||||
AND LEN(@ProductCode) = 13
|
||||
AND (I.InventoryQty1 - I.PickingQty1 - I.ReserveQty1 - I.DispOrderQty1) > 0
|
||||
AND CI.IsBlocked = 0
|
||||
AND I.WarehouseCode IN
|
||||
(
|
||||
'1-0-14','1-0-10','1-0-8','1-2-5','1-2-4','1-0-12','100','1-0-28',
|
||||
'1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3',
|
||||
'1-0-33','101','1-014','1-0-49','1-0-36'
|
||||
);
|
||||
`
|
||||
155
svc/routes/product_images.go
Normal file
155
svc/routes/product_images.go
Normal file
@@ -0,0 +1,155 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
type ProductImageItem struct {
|
||||
ID int64 `json:"id"`
|
||||
FileName string `json:"file_name"`
|
||||
FileSize int64 `json:"file_size"`
|
||||
Storage string `json:"storage_path"`
|
||||
ContentURL string `json:"content_url"`
|
||||
}
|
||||
|
||||
// GET /api/product-images?code=...&color=...
|
||||
func GetProductImagesHandler(pg *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
code := strings.TrimSpace(r.URL.Query().Get("code"))
|
||||
color := strings.TrimSpace(r.URL.Query().Get("color"))
|
||||
|
||||
if code == "" {
|
||||
http.Error(w, "Eksik parametre: code gerekli", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
query := `
|
||||
SELECT
|
||||
b.id,
|
||||
b.file_name,
|
||||
COALESCE(b.file_size, 0) AS file_size,
|
||||
COALESCE(b.storage_path, '') AS storage_path
|
||||
FROM dfblob b
|
||||
JOIN mmitem i
|
||||
ON i.id = b.src_id
|
||||
WHERE b.typ = 'img'
|
||||
AND b.src_table = 'mmitem'
|
||||
AND i.code = $1
|
||||
AND ($2 = '' OR b.file_name ILIKE '%' || '-' || $2 || '-%')
|
||||
ORDER BY COALESCE(b.sort_order, 999999), b.zlins_dttm DESC, b.id DESC
|
||||
`
|
||||
|
||||
rows, err := pg.Query(query, code, color)
|
||||
if err != nil {
|
||||
http.Error(w, "Görsel sorgu hatası: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
items := make([]ProductImageItem, 0, 16)
|
||||
for rows.Next() {
|
||||
var it ProductImageItem
|
||||
if err := rows.Scan(&it.ID, &it.FileName, &it.FileSize, &it.Storage); err != nil {
|
||||
continue
|
||||
}
|
||||
it.ContentURL = fmt.Sprintf("/api/product-images/%d/content", it.ID)
|
||||
items = append(items, it)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
_ = json.NewEncoder(w).Encode(items)
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/product-images/{id}/content
|
||||
func GetProductImageContentHandler(pg *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
idStr := mux.Vars(r)["id"]
|
||||
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||
if err != nil || id <= 0 {
|
||||
http.Error(w, "Geçersiz görsel id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
fileName string
|
||||
storagePath string
|
||||
storedInDB bool
|
||||
binData []byte
|
||||
)
|
||||
|
||||
err = pg.QueryRow(`
|
||||
SELECT
|
||||
COALESCE(file_name, ''),
|
||||
COALESCE(storage_path, ''),
|
||||
COALESCE(stored_in_db, false),
|
||||
bin
|
||||
FROM dfblob
|
||||
WHERE id = $1
|
||||
AND typ = 'img'
|
||||
`, id).Scan(&fileName, &storagePath, &storedInDB, &binData)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
http.Error(w, "Görsel okunamadı: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if storedInDB && len(binData) > 0 {
|
||||
w.Header().Set("Content-Type", http.DetectContentType(binData))
|
||||
w.Header().Set("Cache-Control", "public, max-age=3600")
|
||||
_, _ = w.Write(binData)
|
||||
return
|
||||
}
|
||||
|
||||
resolved := resolveStoragePath(storagePath)
|
||||
if resolved == "" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Cache-Control", "public, max-age=3600")
|
||||
http.ServeFile(w, r, resolved)
|
||||
}
|
||||
}
|
||||
|
||||
func resolveStoragePath(storagePath string) string {
|
||||
raw := strings.TrimSpace(storagePath)
|
||||
if raw == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
raw = strings.TrimPrefix(raw, "./")
|
||||
candidates := []string{
|
||||
filepath.Clean(storagePath),
|
||||
filepath.Clean(raw),
|
||||
filepath.Join(".", raw),
|
||||
filepath.Join("..", raw),
|
||||
}
|
||||
|
||||
if root := strings.TrimSpace(os.Getenv("BLOB_ROOT")); root != "" {
|
||||
candidates = append(candidates, filepath.Join(root, raw))
|
||||
}
|
||||
|
||||
for _, p := range candidates {
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
if st, err := os.Stat(p); err == nil && !st.IsDir() {
|
||||
return p
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
85
svc/routes/product_stock_query.go
Normal file
85
svc/routes/product_stock_query.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"bssapp-backend/db"
|
||||
"bssapp-backend/queries"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GetProductStockQueryHandler
|
||||
// GET /api/product-stock-query?code=...
|
||||
func GetProductStockQueryHandler(w http.ResponseWriter, r *http.Request) {
|
||||
code := strings.TrimSpace(r.URL.Query().Get("code"))
|
||||
if code == "" {
|
||||
http.Error(w, "Eksik parametre: code gerekli", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
|
||||
rows, err := db.MssqlDB.QueryContext(ctx, queries.GetProductStockQuery, code)
|
||||
if err != nil {
|
||||
log.Printf("❌ [PRODUCT-STOCK-QUERY] SQL hatası: %v", err)
|
||||
http.Error(w, "SQL hatası: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
columns, err := rows.Columns()
|
||||
if err != nil {
|
||||
http.Error(w, "Kolon bilgisi alınamadı", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
result := make([]map[string]interface{}, 0, 128)
|
||||
for rows.Next() {
|
||||
raw := make([]interface{}, len(columns))
|
||||
dest := make([]interface{}, len(columns))
|
||||
for i := range raw {
|
||||
dest[i] = &raw[i]
|
||||
}
|
||||
if err := rows.Scan(dest...); err != nil {
|
||||
log.Printf("⚠️ [PRODUCT-STOCK-QUERY] scan hatası: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
rowMap := make(map[string]interface{}, len(columns))
|
||||
for i, c := range columns {
|
||||
rowMap[c] = normalizeSQLValue(raw[i])
|
||||
}
|
||||
result = append(result, rowMap)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
http.Error(w, "Satır okuma hatası: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
_ = json.NewEncoder(w).Encode(result)
|
||||
}
|
||||
|
||||
func normalizeSQLValue(v interface{}) interface{} {
|
||||
switch val := v.(type) {
|
||||
case nil:
|
||||
return ""
|
||||
case []byte:
|
||||
s := strings.TrimSpace(string(val))
|
||||
if s == "" {
|
||||
return ""
|
||||
}
|
||||
if n, err := strconv.ParseFloat(strings.ReplaceAll(s, ",", "."), 64); err == nil {
|
||||
return n
|
||||
}
|
||||
return s
|
||||
default:
|
||||
return val
|
||||
}
|
||||
}
|
||||
@@ -261,6 +261,19 @@ const menuItems = [
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
label: 'Ürün',
|
||||
icon: 'inventory_2',
|
||||
|
||||
children: [
|
||||
{
|
||||
label: 'Ürün Kodundan Stok Sorgula',
|
||||
to: '/app/product-stock-query',
|
||||
permission: 'order:view'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
label: 'Sistem',
|
||||
icon: 'settings',
|
||||
|
||||
1078
ui/src/pages/ProductStockQuery.vue
Normal file
1078
ui/src/pages/ProductStockQuery.vue
Normal file
@@ -0,0 +1,1078 @@
|
||||
<template>
|
||||
<q-page
|
||||
v-if="canReadOrder"
|
||||
class="order-page q-pa-md"
|
||||
:style="{ '--grid-header-h': gridHeaderHeight }"
|
||||
>
|
||||
<div class="sticky-stack">
|
||||
<div class="filter-bar row q-col-gutter-md q-mb-sm">
|
||||
<div class="col-12 col-md-6">
|
||||
<q-select
|
||||
v-model="selectedProductCode"
|
||||
:options="filteredProductOptions"
|
||||
label="Ürün Kodu"
|
||||
filled
|
||||
dense
|
||||
clearable
|
||||
use-input
|
||||
input-debounce="250"
|
||||
emit-value
|
||||
map-options
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
:loading="loadingProducts"
|
||||
@filter="filterProducts"
|
||||
@keyup.enter="fetchStockByCode"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-auto">
|
||||
<q-btn
|
||||
color="primary"
|
||||
icon="search"
|
||||
label="Sorgula"
|
||||
:loading="loadingStock"
|
||||
:disable="!selectedProductCode"
|
||||
@click="fetchStockByCode"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-auto">
|
||||
<q-btn
|
||||
flat
|
||||
color="grey-8"
|
||||
icon="restart_alt"
|
||||
label="Sıfırla"
|
||||
@click="resetForm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-auto">
|
||||
<q-btn
|
||||
flat
|
||||
color="primary"
|
||||
icon="unfold_more"
|
||||
:label="allDetailsExpanded ? 'Tüm Depoları Kapat' : 'Tüm Depoları Göster'"
|
||||
:disable="!level1Groups.length"
|
||||
@click="toggleAllDetails"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="save-toolbar">
|
||||
<div class="text-subtitle2 text-weight-bold">Ürün Kodundan Stok Sorgula</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="showGridHeader"
|
||||
class="order-grid-header"
|
||||
>
|
||||
<div class="col-fixed model">MODEL</div>
|
||||
<div class="col-fixed renk">RENK</div>
|
||||
<div class="col-fixed ana">ÜRÜN ANA GRUBU</div>
|
||||
<div class="col-fixed alt">ÜRÜN ALT GRUBU</div>
|
||||
<div class="col-fixed aciklama-col">AÇIKLAMA</div>
|
||||
|
||||
<div class="beden-block">
|
||||
<div class="grp-row">
|
||||
<div class="grp-title">{{ activeSchema?.title || 'BEDEN' }}</div>
|
||||
<div class="grp-body">
|
||||
<div
|
||||
v-for="v in sizeLabels"
|
||||
:key="'hdr-' + activeGrpKey + '-' + v"
|
||||
class="grp-cell hdr"
|
||||
>
|
||||
{{ v }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="total-row">
|
||||
<div class="total-cell">ADET</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<q-banner
|
||||
v-if="errorMessage"
|
||||
class="bg-red-1 text-negative q-my-sm rounded-borders"
|
||||
dense
|
||||
>
|
||||
{{ errorMessage }}
|
||||
</q-banner>
|
||||
|
||||
<q-banner
|
||||
v-else-if="!loadingStock && !level1Groups.length"
|
||||
class="bg-blue-1 text-primary q-my-sm rounded-borders"
|
||||
dense
|
||||
>
|
||||
Ürün kodu seçip sorgulayın.
|
||||
</q-banner>
|
||||
|
||||
<div class="order-scroll-y">
|
||||
<div v-if="level1Groups.length" class="order-grid-body">
|
||||
<template v-for="grp1 in level1Groups" :key="grp1.key">
|
||||
<div class="summary-group open">
|
||||
<div class="order-sub-header level-1" @click="toggleOpen(grp1.key)">
|
||||
<div class="sub-col level1-merged">
|
||||
<div class="text-weight-bold">{{ grp1.productCode }}</div>
|
||||
<div class="text-caption">{{ grp1.productDesc || '-' }}</div>
|
||||
</div>
|
||||
|
||||
<div class="sub-center level1-center">
|
||||
<div class="beden-row values-top">
|
||||
<div v-for="sz in sizeLabels" :key="`v1-${grp1.key}-${sz}`" class="beden-cell">
|
||||
{{ Number(grp1.sizeTotals[sz] || 0) > 0 ? formatNumber(grp1.sizeTotals[sz]) : '' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="beden-row headers">
|
||||
<div v-for="sz in sizeLabels" :key="`h1-${grp1.key}-${sz}`" class="beden-cell">
|
||||
{{ sz }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sub-right level1-right">
|
||||
<div class="top-total">{{ formatNumber(grp1.totalQty) }}</div>
|
||||
<div class="bottom-row">
|
||||
<div class="bottom-label">ADET</div>
|
||||
</div>
|
||||
<div class="icon-row">
|
||||
<q-icon :name="isOpen(grp1.key) ? 'expand_less' : 'expand_more'" size="18px" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="isOpen(grp1.key)">
|
||||
<template v-for="grp2 in grp1.children" :key="grp2.key">
|
||||
<div class="order-sub-header level-2" @click="onLevel2Click(grp1.productCode, grp2)">
|
||||
<div class="sub-col model">{{ grp1.productCode || '-' }}</div>
|
||||
<div class="sub-col renk">
|
||||
<div class="renk-kodu">{{ grp2.colorCode || '-' }}{{ grp2.secondColor ? '-' + grp2.secondColor : '' }}</div>
|
||||
<div class="renk-aciklama">{{ grp2.colorDesc || '-' }}</div>
|
||||
</div>
|
||||
<div class="sub-col ana">{{ grp2.urunAnaGrubu || '-' }}</div>
|
||||
<div class="sub-col alt">{{ grp2.urunAltGrubu || '-' }}</div>
|
||||
<div class="sub-col aciklama">{{ grp2.aciklama || '-' }}</div>
|
||||
|
||||
<div class="sub-center level2-center">
|
||||
<div class="beden-row values-top">
|
||||
<div v-for="sz in sizeLabels" :key="`top2-${grp2.key}-${sz}`" class="beden-cell">
|
||||
{{ Number(grp2.sizeTotals[sz] || 0) > 0 ? formatNumber(grp2.sizeTotals[sz]) : '' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="beden-row headers">
|
||||
<div v-for="sz in sizeLabels" :key="`h2-${grp2.key}-${sz}`" class="beden-cell">
|
||||
{{ sz }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sub-right level2-right">
|
||||
<div class="top-total">{{ formatNumber(grp2.totalQty) }}</div>
|
||||
<div class="bottom-row">
|
||||
<div class="bottom-label">ADET</div>
|
||||
<q-icon :name="isOpen(grp2.key) ? 'expand_less' : 'expand_more'" size="18px" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="isOpen(grp2.key)">
|
||||
<div class="detail-table-wrap">
|
||||
<div
|
||||
v-for="row in buildLevel2Rows(grp2)"
|
||||
:key="row.rowKey"
|
||||
class="summary-row"
|
||||
>
|
||||
<div class="cell depo-merged">{{ row.depoAdi || '-' }}</div>
|
||||
|
||||
<div class="grp-area">
|
||||
<div class="grp-row">
|
||||
<div
|
||||
v-for="v in sizeLabels"
|
||||
:key="row.rowKey + '-sz-' + v"
|
||||
class="cell beden"
|
||||
>
|
||||
{{ resolveBedenValue(row.bedenMap, row.grpKey, v) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cell adet">{{ formatNumber(row.adet) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</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, ref } from 'vue'
|
||||
import { useQuasar } from 'quasar'
|
||||
import api from 'src/services/api'
|
||||
import { usePermission } from 'src/composables/usePermission'
|
||||
import {
|
||||
detectBedenGroup,
|
||||
normalizeBedenLabel,
|
||||
schemaByKey as storeSchemaByKey,
|
||||
useOrderEntryStore
|
||||
} from 'src/stores/orderentryStore'
|
||||
|
||||
const $q = useQuasar()
|
||||
const { canRead } = usePermission()
|
||||
const canReadOrder = canRead('order')
|
||||
const orderStore = useOrderEntryStore()
|
||||
|
||||
const loadingProducts = ref(false)
|
||||
const loadingStock = ref(false)
|
||||
const errorMessage = ref('')
|
||||
const selectedProductCode = ref('')
|
||||
const productOptions = ref([])
|
||||
const filteredProductOptions = ref([])
|
||||
const rawRows = ref([])
|
||||
const activeSchema = ref(storeSchemaByKey.tak)
|
||||
const activeGrpKey = ref('tak')
|
||||
const openState = ref({})
|
||||
|
||||
const sizeLabels = computed(() => activeSchema.value?.values || [])
|
||||
const showGridHeader = computed(() =>
|
||||
!loadingStock.value && level1Groups.value.length > 0
|
||||
)
|
||||
const allDetailsExpanded = computed(() => {
|
||||
const groups = level1Groups.value || []
|
||||
if (!groups.length) return false
|
||||
const detailKeys = []
|
||||
for (const g1 of groups) {
|
||||
for (const g2 of g1.children || []) {
|
||||
detailKeys.push(g2.key)
|
||||
for (const g3 of g2.children || []) detailKeys.push(g3.key)
|
||||
}
|
||||
}
|
||||
if (!detailKeys.length) return false
|
||||
return detailKeys.every((k) => openState.value[k] === true)
|
||||
})
|
||||
const gridHeaderHeight = computed(() =>
|
||||
showGridHeader.value ? '56px' : '0px'
|
||||
)
|
||||
|
||||
function emptySizeTotals() {
|
||||
const map = {}
|
||||
for (const s of sizeLabels.value) map[s] = 0
|
||||
return map
|
||||
}
|
||||
|
||||
function parseNumber(value) {
|
||||
if (typeof value === 'number') return Number.isFinite(value) ? value : 0
|
||||
const text = String(value ?? '').trim()
|
||||
if (!text) return 0
|
||||
const normalized = text.replace(/\./g, '').replace(',', '.')
|
||||
const n = Number.parseFloat(normalized)
|
||||
return Number.isFinite(n) ? n : 0
|
||||
}
|
||||
|
||||
function formatNumber(v) {
|
||||
return parseNumber(v).toLocaleString('tr-TR', { minimumFractionDigits: 0, maximumFractionDigits: 2 })
|
||||
}
|
||||
|
||||
function normalizeSize(v) {
|
||||
return normalizeBedenLabel(v)
|
||||
}
|
||||
|
||||
function resolveBedenValue(bedenMap, grpKey, bedenLabel) {
|
||||
const map = bedenMap?.[grpKey]
|
||||
if (!map || typeof map !== 'object') return 0
|
||||
return Number(map[bedenLabel] || 0)
|
||||
}
|
||||
|
||||
function isOpen(key) {
|
||||
return openState.value[key] !== false
|
||||
}
|
||||
|
||||
function toggleOpen(key) {
|
||||
openState.value[key] = !isOpen(key)
|
||||
}
|
||||
|
||||
|
||||
function initOpenState(groups, expandDetails = false) {
|
||||
const next = {}
|
||||
for (const g1 of groups) {
|
||||
next[g1.key] = true
|
||||
for (const g2 of g1.children) {
|
||||
next[g2.key] = expandDetails
|
||||
for (const g3 of g2.children) {
|
||||
next[g3.key] = expandDetails
|
||||
}
|
||||
}
|
||||
}
|
||||
openState.value = next
|
||||
}
|
||||
|
||||
function expandAllDetails() {
|
||||
initOpenState(level1Groups.value || [], true)
|
||||
}
|
||||
|
||||
function collapseAllDetails() {
|
||||
initOpenState(level1Groups.value || [], false)
|
||||
}
|
||||
|
||||
function toggleAllDetails() {
|
||||
if (allDetailsExpanded.value) {
|
||||
collapseAllDetails()
|
||||
return
|
||||
}
|
||||
expandAllDetails()
|
||||
}
|
||||
|
||||
function buildLevel3Rows(grp3) {
|
||||
const byKey = new Map()
|
||||
const gk = activeGrpKey.value || 'tak'
|
||||
|
||||
for (const item of grp3.items || []) {
|
||||
const model = String(item.Urun_Kodu || '').trim()
|
||||
const renk = String(item.Renk_Kodu || '').trim()
|
||||
const renk2 = String(item.Yaka || '').trim()
|
||||
const urunAnaGrubu = String(item.URUN_ANA_GRUBU || '').trim()
|
||||
const urunAltGrubu = String(item.URUN_ALT_GRUBU || '').trim()
|
||||
const aciklama = String(item.Madde_Aciklamasi || '').trim()
|
||||
const beden = normalizeSize(item.Beden || '')
|
||||
const qty = parseNumber(item.Kullanilabilir_Envanter)
|
||||
|
||||
const rowKey = [model, renk, renk2, grp3.depoKodu || '', grp3.depoAdi || ''].join('|')
|
||||
if (!byKey.has(rowKey)) {
|
||||
byKey.set(rowKey, {
|
||||
rowKey,
|
||||
model,
|
||||
renk,
|
||||
renk2,
|
||||
urunAnaGrubu,
|
||||
urunAltGrubu,
|
||||
aciklama,
|
||||
grpKey: gk,
|
||||
bedenMap: { [gk]: {} },
|
||||
adet: 0,
|
||||
depoAdi: grp3.depoAdi || '-'
|
||||
})
|
||||
}
|
||||
|
||||
const row = byKey.get(rowKey)
|
||||
row.bedenMap[gk][beden] = Number(row.bedenMap[gk][beden] || 0) + qty
|
||||
row.adet += qty
|
||||
}
|
||||
|
||||
return Array.from(byKey.values())
|
||||
}
|
||||
|
||||
function buildLevel2Rows(grp2) {
|
||||
const merged = []
|
||||
for (const grp3 of grp2.children || []) {
|
||||
merged.push(...buildLevel3Rows(grp3))
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
const level1Groups = computed(() => {
|
||||
const l1Map = new Map()
|
||||
|
||||
for (const item of rawRows.value) {
|
||||
const productCode = String(item.Urun_Kodu || '').trim()
|
||||
const productDesc = String(item.Madde_Aciklamasi || '').trim()
|
||||
const colorCode = String(item.Renk_Kodu || '').trim()
|
||||
const colorDesc = String(item.Renk_Aciklamasi || '').trim()
|
||||
const secondColor = String(item.Yaka || '').trim()
|
||||
const depoKodu = String(item.Depo_Kodu || '').trim()
|
||||
const depoAdi = String(item.Depo_Adi || '').trim()
|
||||
const urunAnaGrubu = String(item.URUN_ANA_GRUBU || '').trim()
|
||||
const urunAltGrubu = String(item.URUN_ALT_GRUBU || '').trim()
|
||||
const aciklama = String(item.Madde_Aciklamasi || '').trim()
|
||||
const beden = normalizeSize(item.Beden || '')
|
||||
const qty = parseNumber(item.Kullanilabilir_Envanter)
|
||||
|
||||
if (!l1Map.has(productCode)) {
|
||||
l1Map.set(productCode, {
|
||||
key: `L1|${productCode}`,
|
||||
productCode,
|
||||
productDesc,
|
||||
sizeTotals: emptySizeTotals(),
|
||||
totalQty: 0,
|
||||
childrenMap: new Map()
|
||||
})
|
||||
}
|
||||
const l1 = l1Map.get(productCode)
|
||||
if (Object.prototype.hasOwnProperty.call(l1.sizeTotals, beden)) {
|
||||
l1.sizeTotals[beden] += qty
|
||||
}
|
||||
l1.totalQty += qty
|
||||
|
||||
const l2Key = `${colorCode}|${secondColor}`
|
||||
if (!l1.childrenMap.has(l2Key)) {
|
||||
l1.childrenMap.set(l2Key, {
|
||||
key: `L2|${productCode}|${l2Key}`,
|
||||
colorCode,
|
||||
colorDesc,
|
||||
secondColor,
|
||||
urunAnaGrubu,
|
||||
urunAltGrubu,
|
||||
aciklama,
|
||||
sizeTotals: emptySizeTotals(),
|
||||
totalQty: 0,
|
||||
childrenMap: new Map()
|
||||
})
|
||||
}
|
||||
const l2 = l1.childrenMap.get(l2Key)
|
||||
if (Object.prototype.hasOwnProperty.call(l2.sizeTotals, beden)) {
|
||||
l2.sizeTotals[beden] += qty
|
||||
}
|
||||
l2.totalQty += qty
|
||||
|
||||
const l3Key = `${depoKodu}|${depoAdi}`
|
||||
if (!l2.childrenMap.has(l3Key)) {
|
||||
l2.childrenMap.set(l3Key, {
|
||||
key: `L3|${productCode}|${l2Key}|${l3Key}`,
|
||||
depoKodu,
|
||||
depoAdi,
|
||||
sizeTotals: emptySizeTotals(),
|
||||
totalQty: 0,
|
||||
items: []
|
||||
})
|
||||
}
|
||||
const l3 = l2.childrenMap.get(l3Key)
|
||||
if (Object.prototype.hasOwnProperty.call(l3.sizeTotals, beden)) {
|
||||
l3.sizeTotals[beden] += qty
|
||||
}
|
||||
l3.totalQty += qty
|
||||
l3.items.push({
|
||||
...item,
|
||||
_rowKey: `${productCode}|${colorCode}|${secondColor}|${depoKodu}|${beden}`
|
||||
})
|
||||
}
|
||||
|
||||
return Array.from(l1Map.values()).map((l1) => ({
|
||||
...l1,
|
||||
children: Array.from(l1.childrenMap.values()).map((l2) => ({
|
||||
...l2,
|
||||
children: Array.from(l2.childrenMap.values())
|
||||
}))
|
||||
}))
|
||||
})
|
||||
|
||||
function filterProducts(val, update) {
|
||||
if (!val) {
|
||||
update(() => {
|
||||
filteredProductOptions.value = [...productOptions.value]
|
||||
})
|
||||
return
|
||||
}
|
||||
const needle = String(val || '').toLocaleLowerCase('tr-TR')
|
||||
update(() => {
|
||||
filteredProductOptions.value = productOptions.value.filter(opt =>
|
||||
String(opt.label || '').toLocaleLowerCase('tr-TR').includes(needle)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
async function loadProductOptions() {
|
||||
loadingProducts.value = true
|
||||
try {
|
||||
const res = await api.get('/products')
|
||||
const arr = Array.isArray(res?.data) ? res.data : []
|
||||
productOptions.value = arr
|
||||
.map((x) => String(x?.ProductCode || '').trim())
|
||||
.filter((code) => code.length === 13)
|
||||
.sort((a, b) => a.localeCompare(b, 'tr'))
|
||||
.map((code) => ({ label: code, value: code }))
|
||||
filteredProductOptions.value = [...productOptions.value]
|
||||
} catch (err) {
|
||||
errorMessage.value = 'Ürün kodları alınamadı.'
|
||||
console.error('loadProductOptions error:', err)
|
||||
} finally {
|
||||
loadingProducts.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchStockByCode() {
|
||||
const code = String(selectedProductCode.value || '').trim()
|
||||
if (!code) return
|
||||
|
||||
loadingStock.value = true
|
||||
errorMessage.value = ''
|
||||
|
||||
try {
|
||||
if (!orderStore.schemaMap || !Object.keys(orderStore.schemaMap).length) {
|
||||
orderStore.initSchemaMap()
|
||||
}
|
||||
|
||||
const res = await api.get('/product-stock-query', { params: { code } })
|
||||
const list = Array.isArray(res?.data) ? res.data : []
|
||||
|
||||
if (!list.length) {
|
||||
rawRows.value = []
|
||||
openState.value = {}
|
||||
return
|
||||
}
|
||||
|
||||
const first = list[0] || {}
|
||||
const grpKey = detectBedenGroup(
|
||||
list.map((x) => x?.Beden || ''),
|
||||
first?.URUN_ANA_GRUBU || '',
|
||||
first?.YETISKIN_GARSON || ''
|
||||
)
|
||||
|
||||
const schemaMap = Object.keys(orderStore.schemaMap || {}).length
|
||||
? orderStore.schemaMap
|
||||
: storeSchemaByKey
|
||||
activeGrpKey.value = grpKey || 'tak'
|
||||
activeSchema.value = schemaMap?.[grpKey] || storeSchemaByKey.tak
|
||||
|
||||
rawRows.value = list
|
||||
initOpenState(level1Groups.value)
|
||||
} catch (err) {
|
||||
console.error('fetchStockByCode error:', err)
|
||||
rawRows.value = []
|
||||
openState.value = {}
|
||||
errorMessage.value = 'Stok sorgulama sırasında hata oluştu.'
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
position: 'top-right',
|
||||
message: 'Stok sorgusu başarısız.'
|
||||
})
|
||||
} finally {
|
||||
loadingStock.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function onLevel2Click(_productCode, grp2) {
|
||||
toggleOpen(grp2.key)
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
selectedProductCode.value = ''
|
||||
rawRows.value = []
|
||||
errorMessage.value = ''
|
||||
openState.value = {}
|
||||
activeSchema.value = storeSchemaByKey.tak
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadProductOptions()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.order-page {
|
||||
--psq-sticky-offset: 12px;
|
||||
--grp-title-w: 44px;
|
||||
--psq-header-h: 56px;
|
||||
--psq-col-adet: calc(var(--col-adet) + var(--beden-w));
|
||||
--psq-l1-lift: 42px;
|
||||
}
|
||||
|
||||
.order-grid-header {
|
||||
top: calc(var(--header-h) + var(--filter-h) + var(--save-h) + var(--psq-sticky-offset)) !important;
|
||||
grid-template-columns:
|
||||
var(--col-model)
|
||||
var(--col-renk)
|
||||
var(--col-ana)
|
||||
var(--col-alt)
|
||||
var(--col-aciklama)
|
||||
calc(var(--grp-title-w) + var(--grp-title-gap) + (var(--beden-w)*var(--beden-count)))
|
||||
var(--psq-col-adet) !important;
|
||||
}
|
||||
|
||||
.order-grid-header .col-fixed,
|
||||
.order-grid-header .total-cell {
|
||||
writing-mode: horizontal-tb !important;
|
||||
transform: none !important;
|
||||
height: var(--psq-header-h) !important;
|
||||
font-size: 10px !important;
|
||||
line-height: 1 !important;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding: 0 4px !important;
|
||||
}
|
||||
|
||||
.order-grid-header .beden-block {
|
||||
height: var(--psq-header-h) !important;
|
||||
}
|
||||
|
||||
.order-grid-header .grp-row {
|
||||
height: var(--psq-header-h) !important;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.order-grid-header .grp-title {
|
||||
width: var(--grp-title-w) !important;
|
||||
text-align: center !important;
|
||||
padding-right: 0 !important;
|
||||
font-size: 10px !important;
|
||||
}
|
||||
|
||||
.order-grid-header .grp-cell.hdr {
|
||||
height: var(--psq-header-h) !important;
|
||||
font-size: 10px !important;
|
||||
}
|
||||
|
||||
.order-grid-header .total-row {
|
||||
grid-column: 7 / -1 !important;
|
||||
}
|
||||
|
||||
.order-grid-header .total-cell {
|
||||
width: var(--psq-col-adet) !important;
|
||||
}
|
||||
|
||||
.order-grid-header .total-cell:last-child {
|
||||
width: var(--psq-col-adet) !important;
|
||||
}
|
||||
|
||||
.order-sub-header {
|
||||
grid-template-columns:
|
||||
var(--col-model)
|
||||
var(--col-renk)
|
||||
var(--col-ana)
|
||||
var(--col-alt)
|
||||
var(--col-aciklama)
|
||||
calc(var(--grp-title-w) + var(--grp-title-gap) + (var(--beden-w)*var(--beden-count)))
|
||||
var(--psq-col-adet) !important;
|
||||
top: calc(
|
||||
var(--header-h)
|
||||
+ var(--filter-h)
|
||||
+ var(--save-h)
|
||||
+ var(--grid-header-h)
|
||||
+ var(--psq-sticky-offset)
|
||||
) !important;
|
||||
}
|
||||
|
||||
.order-sub-header.level-2 {
|
||||
min-height: 82px !important;
|
||||
height: 82px !important;
|
||||
background: #fff9c4 !important;
|
||||
border-top: 1px solid #d4c79f !important;
|
||||
border-bottom: 1px solid #d4c79f !important;
|
||||
}
|
||||
|
||||
.order-sub-header.level-1 {
|
||||
min-height: 84px !important;
|
||||
height: 84px !important;
|
||||
top: calc(
|
||||
var(--header-h)
|
||||
+ var(--filter-h)
|
||||
+ var(--save-h)
|
||||
+ var(--grid-header-h)
|
||||
+ var(--psq-sticky-offset)
|
||||
- var(--psq-l1-lift)
|
||||
) !important;
|
||||
background: var(--q-primary, #1976d2) !important;
|
||||
border-top: 1px solid #145ea8 !important;
|
||||
border-bottom: 1px solid #145ea8 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.order-sub-header.level-1 .sub-col.level1-merged {
|
||||
grid-column: 1 / 6;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 2px;
|
||||
padding: 0 10px;
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.45);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.order-sub-header.level-1 .sub-col.level1-merged .text-caption {
|
||||
color: #fff !important;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.order-sub-header.level-1 .sub-center.level1-center {
|
||||
grid-column: 6;
|
||||
display: grid;
|
||||
grid-template-rows: 42px 42px;
|
||||
align-items: stretch;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
padding-left: calc(var(--grp-title-w) + var(--grp-title-gap));
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.order-sub-header.level-1 .beden-row {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: var(--beden-w);
|
||||
height: 42px;
|
||||
min-height: 42px;
|
||||
}
|
||||
|
||||
.order-sub-header.level-1 .beden-row .beden-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 42px;
|
||||
min-height: 42px;
|
||||
background: var(--q-primary, #1976d2) !important;
|
||||
color: #fff !important;
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.45);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.45);
|
||||
border-top: none;
|
||||
border-left: none;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.order-sub-header.level-1 .beden-row.values-top .beden-cell {
|
||||
background: #fff9c4 !important;
|
||||
color: var(--q-primary, #1976d2) !important;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.order-sub-header.level-1 .beden-row.headers .beden-cell {
|
||||
font-weight: 500;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.order-sub-header.level-1 .sub-right.level1-right {
|
||||
grid-column: 7 / 8;
|
||||
display: grid;
|
||||
grid-template-columns: var(--psq-col-adet);
|
||||
grid-template-rows: 30px 30px 24px;
|
||||
align-items: stretch;
|
||||
justify-items: stretch;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
padding: 0 8px 0 6px;
|
||||
border-left: 1px solid rgba(255, 255, 255, 0.45);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.order-sub-header.level-1 .sub-right .top-total {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
background: #fff9c4 !important;
|
||||
color: var(--q-primary, #1976d2) !important;
|
||||
padding: 0 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.order-sub-header.level-1 .sub-right .bottom-label {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.order-sub-header.level-1 .sub-right .bottom-row {
|
||||
grid-column: 1;
|
||||
grid-row: 2;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 4px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.order-sub-header.level-1 .sub-right .icon-row {
|
||||
grid-column: 1;
|
||||
grid-row: 3;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.order-sub-header.level-2 .sub-col {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: #111;
|
||||
min-width: 0;
|
||||
border-right: 1px solid #d4c79f;
|
||||
white-space: normal;
|
||||
overflow: visible;
|
||||
text-overflow: clip;
|
||||
line-height: 1.2;
|
||||
word-break: break-word;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.order-sub-header.level-2 .sub-col.model { grid-column: 1; }
|
||||
.order-sub-header.level-2 .sub-col.renk { grid-column: 2; }
|
||||
.order-sub-header.level-2 .sub-col.ana { grid-column: 3; }
|
||||
.order-sub-header.level-2 .sub-col.alt { grid-column: 4; }
|
||||
.order-sub-header.level-2 .sub-col.aciklama { grid-column: 5; }
|
||||
|
||||
.order-sub-header.level-2 .sub-col.model,
|
||||
.order-sub-header.level-2 .sub-col.renk,
|
||||
.order-sub-header.level-2 .sub-col.ana,
|
||||
.order-sub-header.level-2 .sub-col.alt {
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.order-sub-header.level-2 .sub-col.renk {
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.order-sub-header.level-2 .sub-col.renk .renk-kodu {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.order-sub-header.level-2 .sub-col.renk .renk-aciklama {
|
||||
font-size: 11px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.order-sub-header.level-2 .sub-col.aciklama {
|
||||
justify-content: flex-start;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.order-sub-header.level-2 .sub-center.level2-center {
|
||||
grid-column: 6;
|
||||
display: grid;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
align-items: stretch;
|
||||
height: 100%;
|
||||
padding-left: var(--grp-title-w);
|
||||
margin-left: var(--grp-title-gap);
|
||||
}
|
||||
|
||||
.order-sub-header.level-2 .beden-row {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: var(--beden-w);
|
||||
}
|
||||
|
||||
.order-sub-header.level-2 .beden-row.values-top .beden-cell {
|
||||
border-right: 1px solid #d4c79f;
|
||||
background: transparent;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #1f1f1f;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.order-sub-header.level-2 .beden-row.headers .beden-cell {
|
||||
border-top: 1px solid #d4c79f;
|
||||
border-right: 1px solid #d4c79f;
|
||||
border-bottom: none;
|
||||
background: var(--q-primary, #1976d2);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.order-sub-header.level-2 .sub-right.level2-right {
|
||||
grid-column: 7 / 8;
|
||||
display: grid;
|
||||
grid-template-columns: var(--psq-col-adet);
|
||||
grid-template-rows: 1fr 1fr;
|
||||
align-items: center;
|
||||
justify-items: start;
|
||||
padding-left: 8px;
|
||||
padding-right: 0;
|
||||
transform: none !important;
|
||||
border-left: 1px solid #d4c79f;
|
||||
}
|
||||
|
||||
.order-sub-header.level-2 .sub-right .top-total {
|
||||
grid-column: 1 / 2;
|
||||
grid-row: 1;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
justify-self: start;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.order-sub-header.level-2 .sub-right .bottom-label {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.order-sub-header.level-2 .sub-right .bottom-row {
|
||||
grid-column: 1;
|
||||
grid-row: 2;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.order-grid-body .summary-row {
|
||||
display: grid !important;
|
||||
grid-template-columns:
|
||||
var(--col-model)
|
||||
var(--col-renk)
|
||||
var(--col-ana)
|
||||
var(--col-alt)
|
||||
var(--col-aciklama)
|
||||
calc(var(--grp-title-w) + var(--grp-title-gap) + (var(--beden-w) * var(--beden-count)))
|
||||
var(--psq-col-adet) !important;
|
||||
min-height: 56px;
|
||||
height: 56px;
|
||||
background: #fff;
|
||||
border-top: 1px solid #d4c79f;
|
||||
border-bottom: 1px solid #d4c79f;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.order-grid-body .summary-row .cell {
|
||||
min-height: 56px;
|
||||
height: 56px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-right: 1px solid #d4c79f;
|
||||
font-size: 12px;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.order-grid-body .summary-row .cell:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.order-grid-body .summary-row .grp-area {
|
||||
display: grid !important;
|
||||
grid-template-columns: var(--grp-title-w) var(--grp-title-gap) 1fr;
|
||||
width: 100% !important;
|
||||
height: 56px;
|
||||
padding-left: 0 !important;
|
||||
transform: none !important;
|
||||
border-right: 1px solid #d4c79f;
|
||||
}
|
||||
|
||||
.order-grid-body .summary-row .grp-row {
|
||||
grid-column: 3;
|
||||
width: 100%;
|
||||
height: 56px;
|
||||
margin-left: 0 !important;
|
||||
justify-content: start !important;
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: var(--beden-w);
|
||||
}
|
||||
|
||||
.order-grid-body .summary-row .grp-row .cell.beden {
|
||||
width: var(--beden-w);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
border-right: 1px solid #d4c79f;
|
||||
border-left: none !important;
|
||||
border-top: none !important;
|
||||
border-bottom: none !important;
|
||||
min-height: 56px;
|
||||
height: 56px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.order-grid-body .summary-row .grp-row .cell.beden:first-child {
|
||||
border-left: 1px solid #d4c79f !important;
|
||||
}
|
||||
|
||||
.order-grid-body .summary-row .cell.model,
|
||||
.order-grid-body .summary-row .cell.renk,
|
||||
.order-grid-body .summary-row .cell.ana,
|
||||
.order-grid-body .summary-row .cell.alt {
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.order-grid-body .summary-row .cell.aciklama {
|
||||
grid-column: 5 / 6 !important;
|
||||
position: static !important;
|
||||
width: var(--col-aciklama) !important;
|
||||
margin-right: 0 !important;
|
||||
min-height: 56px !important;
|
||||
z-index: auto !important;
|
||||
background: #fff !important;
|
||||
box-sizing: border-box !important;
|
||||
border-right: 1px solid #d4c79f !important;
|
||||
justify-content: flex-start !important;
|
||||
text-align: left !important;
|
||||
white-space: nowrap !important;
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis !important;
|
||||
line-height: 1 !important;
|
||||
padding-left: 6px !important;
|
||||
padding-right: 6px !important;
|
||||
display: flex !important;
|
||||
flex-direction: row !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
|
||||
.order-grid-body .summary-row .cell.depo-merged {
|
||||
grid-column: 1 / 6 !important;
|
||||
justify-content: center !important;
|
||||
text-align: center !important;
|
||||
font-weight: 600;
|
||||
white-space: nowrap !important;
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis !important;
|
||||
}
|
||||
|
||||
.order-grid-body .summary-row .cell.adet,
|
||||
.order-grid-body .summary-row .cell.termin {
|
||||
justify-content: flex-end;
|
||||
text-align: right;
|
||||
padding-right: 10px !important;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.order-grid-body {
|
||||
margin-top: 0 !important;
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
.order-grid-body > .summary-group,
|
||||
.order-grid-body > .summary-group:first-child {
|
||||
margin-top: 0 !important;
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
|
||||
.detail-table-wrap {
|
||||
padding: 8px 0 12px 0;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.detail-table :deep(th),
|
||||
.detail-table :deep(td) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -290,6 +290,14 @@ const routes = [
|
||||
meta: { permission: 'order:export' }
|
||||
},
|
||||
|
||||
/* ================= PRODUCTS ================= */
|
||||
{
|
||||
path: 'product-stock-query',
|
||||
name: 'product-stock-query',
|
||||
component: () => import('pages/ProductStockQuery.vue'),
|
||||
meta: { permission: 'order:view' }
|
||||
},
|
||||
|
||||
|
||||
/* ================= PASSWORD ================= */
|
||||
|
||||
|
||||
Reference in New Issue
Block a user