Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
package queries
|
package queries
|
||||||
|
|
||||||
// GetProductVariantDimsForPricing:
|
// GetProductVariantDimsForPricing:
|
||||||
// Pull variant dimension combos from Nebim stock tables (same source as product-stock-query UI).
|
// Pull variant dimension combos from Nebim's variant master table.
|
||||||
// We intentionally keep it small: only the keys we need to write dim-aware prices into PG sdprc.
|
// We intentionally keep it small: only the keys we need to write dim-aware prices into PG sdprc.
|
||||||
//
|
//
|
||||||
// Note: Column semantics depend on your Nebim setup. We treat ItemDim1Code/ItemDim3Code as the
|
// Note: Column semantics depend on your Nebim setup. We treat ItemDim1Code/ItemDim3Code as the
|
||||||
@@ -10,16 +10,16 @@ const GetProductVariantDimsForPricing = `
|
|||||||
DECLARE @ProductCode NVARCHAR(50) = @p1;
|
DECLARE @ProductCode NVARCHAR(50) = @p1;
|
||||||
|
|
||||||
SELECT DISTINCT
|
SELECT DISTINCT
|
||||||
LTRIM(RTRIM(ISNULL(S.ColorCode,''))) AS ColorCode,
|
LTRIM(RTRIM(ISNULL(V.ColorCode,''))) AS ColorCode,
|
||||||
LTRIM(RTRIM(ISNULL(S.ItemDim1Code,''))) AS ItemDim1Code,
|
LTRIM(RTRIM(ISNULL(V.ItemDim1Code,''))) AS ItemDim1Code,
|
||||||
LTRIM(RTRIM(ISNULL(S.ItemDim3Code,''))) AS ItemDim3Code
|
LTRIM(RTRIM(ISNULL(V.ItemDim3Code,''))) AS ItemDim3Code
|
||||||
FROM trStock S WITH(NOLOCK)
|
FROM prItemVariant V WITH(NOLOCK)
|
||||||
WHERE S.ItemTypeCode = 1
|
WHERE V.ItemTypeCode = 1
|
||||||
AND S.ItemCode = @ProductCode
|
AND V.ItemCode = @ProductCode
|
||||||
AND LEN(S.ItemCode) = 13
|
AND LEN(V.ItemCode) = 13
|
||||||
AND LEN(@ProductCode) = 13
|
AND LEN(@ProductCode) = 13
|
||||||
ORDER BY
|
ORDER BY
|
||||||
LTRIM(RTRIM(ISNULL(S.ColorCode,''))),
|
LTRIM(RTRIM(ISNULL(V.ColorCode,''))),
|
||||||
LTRIM(RTRIM(ISNULL(S.ItemDim1Code,''))),
|
LTRIM(RTRIM(ISNULL(V.ItemDim1Code,''))),
|
||||||
LTRIM(RTRIM(ISNULL(S.ItemDim3Code,'')));
|
LTRIM(RTRIM(ISNULL(V.ItemDim3Code,'')));
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -16,6 +16,45 @@ DECLARE @Codes NVARCHAR(MAX) = @p1;
|
|||||||
CROSS APPLY D.XmlData.nodes('/i') AS X(C)
|
CROSS APPLY D.XmlData.nodes('/i') AS X(C)
|
||||||
WHERE LTRIM(RTRIM(X.C.value('.', 'NVARCHAR(50)'))) <> ''
|
WHERE LTRIM(RTRIM(X.C.value('.', 'NVARCHAR(50)'))) <> ''
|
||||||
),
|
),
|
||||||
|
VARIANT_MASTER AS (
|
||||||
|
SELECT
|
||||||
|
V.ItemCode,
|
||||||
|
LTRIM(RTRIM(ISNULL(V.ColorCode,''))) AS ColorCode,
|
||||||
|
LTRIM(RTRIM(ISNULL(V.ItemDim3Code,''))) AS ItemDim3Code,
|
||||||
|
MAX(LTRIM(RTRIM(ISNULL(V.ItemDim1Code,'')))) AS ItemDim1Code
|
||||||
|
FROM prItemVariant V WITH(NOLOCK)
|
||||||
|
JOIN INP ON INP.ItemCode = V.ItemCode
|
||||||
|
WHERE V.ItemTypeCode = 1
|
||||||
|
AND LEN(V.ItemCode) = 13
|
||||||
|
GROUP BY
|
||||||
|
V.ItemCode, V.ColorCode, V.ItemDim3Code
|
||||||
|
),
|
||||||
|
VARIANT_STOCK AS (
|
||||||
|
SELECT
|
||||||
|
S.ItemCode,
|
||||||
|
LTRIM(RTRIM(ISNULL(S.ColorCode,''))) AS ColorCode,
|
||||||
|
LTRIM(RTRIM(ISNULL(S.ItemDim3Code,''))) AS ItemDim3Code,
|
||||||
|
MAX(LTRIM(RTRIM(ISNULL(S.ItemDim1Code,'')))) AS ItemDim1Code
|
||||||
|
FROM trStock S WITH(NOLOCK)
|
||||||
|
JOIN INP ON INP.ItemCode = S.ItemCode
|
||||||
|
WHERE S.ItemTypeCode = 1
|
||||||
|
AND LEN(S.ItemCode) = 13
|
||||||
|
GROUP BY
|
||||||
|
S.ItemCode, S.ColorCode, S.ItemDim3Code
|
||||||
|
),
|
||||||
|
VARIANT AS (
|
||||||
|
SELECT
|
||||||
|
X.ItemCode,
|
||||||
|
X.ColorCode,
|
||||||
|
X.ItemDim3Code,
|
||||||
|
MAX(X.ItemDim1Code) AS ItemDim1Code
|
||||||
|
FROM (
|
||||||
|
SELECT ItemCode, ColorCode, ItemDim3Code, ItemDim1Code FROM VARIANT_MASTER
|
||||||
|
UNION ALL
|
||||||
|
SELECT ItemCode, ColorCode, ItemDim3Code, ItemDim1Code FROM VARIANT_STOCK
|
||||||
|
) X
|
||||||
|
GROUP BY X.ItemCode, X.ColorCode, X.ItemDim3Code
|
||||||
|
),
|
||||||
STOCK AS (
|
STOCK AS (
|
||||||
SELECT
|
SELECT
|
||||||
S.ItemCode,
|
S.ItemCode,
|
||||||
@@ -73,27 +112,25 @@ DISP AS (
|
|||||||
D.ItemCode, D.ColorCode, D.ItemDim3Code
|
D.ItemCode, D.ColorCode, D.ItemDim3Code
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
S.ItemCode AS ItemCode,
|
V.ItemCode AS ItemCode,
|
||||||
S.ColorCode AS ColorCode,
|
V.ColorCode AS ColorCode,
|
||||||
S.ItemDim1Code AS ItemDim1Code,
|
V.ItemDim1Code AS ItemDim1Code,
|
||||||
S.ItemDim3Code AS ItemDim3Code,
|
V.ItemDim3Code AS ItemDim3Code,
|
||||||
CAST(ROUND(
|
CAST(ROUND(
|
||||||
S.InventoryQty1
|
ISNULL(S.InventoryQty1,0)
|
||||||
- ISNULL(PK.PickingQty1,0)
|
- ISNULL(PK.PickingQty1,0)
|
||||||
- ISNULL(RS.ReserveQty1,0)
|
- ISNULL(RS.ReserveQty1,0)
|
||||||
- ISNULL(DP.DispOrderQty1,0),
|
- ISNULL(DP.DispOrderQty1,0),
|
||||||
2
|
2
|
||||||
) AS FLOAT) AS StockQty
|
) AS FLOAT) AS StockQty
|
||||||
FROM STOCK S
|
FROM VARIANT V
|
||||||
|
LEFT JOIN STOCK S
|
||||||
|
ON S.ItemCode=V.ItemCode AND S.ColorCode=V.ColorCode AND S.ItemDim3Code=V.ItemDim3Code
|
||||||
LEFT JOIN PICK PK
|
LEFT JOIN PICK PK
|
||||||
ON PK.ItemCode=S.ItemCode AND PK.ColorCode=S.ColorCode AND PK.ItemDim3Code=S.ItemDim3Code
|
ON PK.ItemCode=V.ItemCode AND PK.ColorCode=V.ColorCode AND PK.ItemDim3Code=V.ItemDim3Code
|
||||||
LEFT JOIN RESERVE RS
|
LEFT JOIN RESERVE RS
|
||||||
ON RS.ItemCode=S.ItemCode AND RS.ColorCode=S.ColorCode AND RS.ItemDim3Code=S.ItemDim3Code
|
ON RS.ItemCode=V.ItemCode AND RS.ColorCode=V.ColorCode AND RS.ItemDim3Code=V.ItemDim3Code
|
||||||
LEFT JOIN DISP DP
|
LEFT JOIN DISP DP
|
||||||
ON DP.ItemCode=S.ItemCode AND DP.ColorCode=S.ColorCode AND DP.ItemDim3Code=S.ItemDim3Code
|
ON DP.ItemCode=V.ItemCode AND DP.ColorCode=V.ColorCode AND DP.ItemDim3Code=V.ItemDim3Code
|
||||||
WHERE (S.InventoryQty1
|
ORDER BY V.ItemCode, V.ColorCode, V.ItemDim3Code;
|
||||||
- ISNULL(PK.PickingQty1,0)
|
|
||||||
- ISNULL(RS.ReserveQty1,0)
|
|
||||||
- ISNULL(DP.DispOrderQty1,0)) <> 0
|
|
||||||
ORDER BY S.ItemCode, S.ColorCode, S.ItemDim3Code;
|
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -53,9 +53,7 @@ CREATE TABLE IF NOT EXISTS mk_order_price_list_first_group_mail (
|
|||||||
urun_ilk_grubu TEXT NOT NULL,
|
urun_ilk_grubu TEXT NOT NULL,
|
||||||
mail_id UUID NOT NULL,
|
mail_id UUID NOT NULL,
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
PRIMARY KEY (urun_ilk_grubu, mail_id),
|
PRIMARY KEY (urun_ilk_grubu, mail_id)
|
||||||
CONSTRAINT fk_order_price_list_first_group_mail_mail
|
|
||||||
FOREIGN KEY (mail_id) REFERENCES mk_mail(id) ON DELETE CASCADE
|
|
||||||
)
|
)
|
||||||
`,
|
`,
|
||||||
`CREATE INDEX IF NOT EXISTS ix_order_price_list_first_group_mail_group ON mk_order_price_list_first_group_mail (urun_ilk_grubu)`,
|
`CREATE INDEX IF NOT EXISTS ix_order_price_list_first_group_mail_group ON mk_order_price_list_first_group_mail (urun_ilk_grubu)`,
|
||||||
@@ -79,7 +77,7 @@ func GetCostingFirstGroupMailMappingLookupsHandler(pg *sql.DB) http.HandlerFunc
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := ensureFirstGroupMailMappingTables(pg); err != nil {
|
if err := ensureFirstGroupMailMappingTables(pg); err != nil {
|
||||||
http.Error(w, "mapping table bootstrap error", http.StatusInternalServerError)
|
http.Error(w, "mapping table bootstrap error: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +154,7 @@ func GetCostingFirstGroupMailMappingsHandler(pg *sql.DB) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := ensureFirstGroupMailMappingTables(pg); err != nil {
|
if err := ensureFirstGroupMailMappingTables(pg); err != nil {
|
||||||
http.Error(w, "mapping table bootstrap error", http.StatusInternalServerError)
|
http.Error(w, "mapping table bootstrap error: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,7 +277,7 @@ func GetPricingFirstGroupMailMappingsHandler(pg *sql.DB) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := ensureFirstGroupMailMappingTables(pg); err != nil {
|
if err := ensureFirstGroupMailMappingTables(pg); err != nil {
|
||||||
http.Error(w, "mapping table bootstrap error", http.StatusInternalServerError)
|
http.Error(w, "mapping table bootstrap error: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,7 +529,7 @@ func getFirstGroupMailMappingsByQuery(pg *sql.DB, mappingQuery string) http.Hand
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := ensureFirstGroupMailMappingTables(pg); err != nil {
|
if err := ensureFirstGroupMailMappingTables(pg); err != nil {
|
||||||
http.Error(w, "mapping table bootstrap error", http.StatusInternalServerError)
|
http.Error(w, "mapping table bootstrap error: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -619,16 +619,13 @@ DO UPDATE SET dim_id = EXCLUDED.dim_id, updated_at = EXCLUDED.updated_at
|
|||||||
if c.Dim1 <= 0 {
|
if c.Dim1 <= 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
v2 := int64(0)
|
|
||||||
var v2any any = nil
|
var v2any any = nil
|
||||||
if c.Dim3.Valid && c.Dim3.Int64 > 0 {
|
// Active key: val1=color, val3=itemdim3. val2 is size and is not part of price/campaign key.
|
||||||
v2 = c.Dim3.Int64
|
|
||||||
v2any = v2
|
|
||||||
}
|
|
||||||
// If we managed to resolve an "ItemDim3Code" id too, store it in val3 and mark mmdim_id=3.
|
|
||||||
v3 := int64(0)
|
v3 := int64(0)
|
||||||
if extraVal3 != nil {
|
if c.Dim3.Valid && c.Dim3.Int64 > 0 {
|
||||||
if vv, ok := extraVal3[fmt.Sprintf("%d|%d", c.Dim1, v2)]; ok && vv > 0 {
|
v3 = c.Dim3.Int64
|
||||||
|
} else if extraVal3 != nil {
|
||||||
|
if vv, ok := extraVal3[fmt.Sprintf("%d|0", c.Dim1)]; ok && vv > 0 {
|
||||||
v3 = vv
|
v3 = vv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1071,18 +1068,38 @@ VALUES (
|
|||||||
_ = upsertDimCombosCache(code, dims) // best-effort cache fill
|
_ = upsertDimCombosCache(code, dims) // best-effort cache fill
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) Last resort: MSSQL stock tokens, then seed mmitem_dim. Do not use
|
// 2) Merge MSSQL variant master combos. PG may be partially populated; missing
|
||||||
// mk_mmitem_dim_combo as a write source; stale cache rows can create wrong keys.
|
// colors/dim3 combos still need to be seeded before sdprc/zbggcampaign writes.
|
||||||
if len(dims) == 0 {
|
if d, err := loadDimsFromMssqlStock(code); err != nil {
|
||||||
d, err := loadDimsFromMssqlStock(code)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("save:pg:dims:mssql:error", "product_code", code, "err", err)
|
logger.Error("save:pg:dims:mssql:error", "product_code", code, "err", err)
|
||||||
} else {
|
} else if len(d) > 0 {
|
||||||
dims = d
|
seenDims := make(map[string]struct{}, len(dims)+len(d))
|
||||||
_ = upsertDimCombosCache(code, dims)
|
merged := make([]dimCombo, 0, len(dims)+len(d))
|
||||||
// If PG doesn't have mmitem_dim rows for this product yet, try to seed them.
|
dim3Value := func(v sql.NullInt64) int64 {
|
||||||
ensureMMItemDimRows(mmItemID, dims, nil)
|
if v.Valid {
|
||||||
|
return v.Int64
|
||||||
}
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
for _, c := range dims {
|
||||||
|
k := fmt.Sprintf("%d|%d", c.Dim1, dim3Value(c.Dim3))
|
||||||
|
if _, ok := seenDims[k]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seenDims[k] = struct{}{}
|
||||||
|
merged = append(merged, c)
|
||||||
|
}
|
||||||
|
for _, c := range d {
|
||||||
|
k := fmt.Sprintf("%d|%d", c.Dim1, dim3Value(c.Dim3))
|
||||||
|
if _, ok := seenDims[k]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seenDims[k] = struct{}{}
|
||||||
|
merged = append(merged, c)
|
||||||
|
}
|
||||||
|
dims = merged
|
||||||
|
_ = upsertDimCombosCache(code, dims)
|
||||||
|
ensureMMItemDimRows(mmItemID, d, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -420,6 +421,18 @@ type wholesaleVariantRow struct {
|
|||||||
CampaignLast string `json:"campaign_last_dttm"`
|
CampaignLast string `json:"campaign_last_dttm"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildNebimVariantDisplayCode(colorCode string, dim3Code string) string {
|
||||||
|
colorCode = strings.TrimSpace(colorCode)
|
||||||
|
dim3Code = strings.TrimSpace(dim3Code)
|
||||||
|
if colorCode != "" && dim3Code != "" {
|
||||||
|
return colorCode + "-" + dim3Code
|
||||||
|
}
|
||||||
|
if colorCode != "" {
|
||||||
|
return colorCode
|
||||||
|
}
|
||||||
|
return dim3Code
|
||||||
|
}
|
||||||
|
|
||||||
type wholesaleCampaignHistoryRow struct {
|
type wholesaleCampaignHistoryRow struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
CampaignID *int64 `json:"campaign_id"`
|
CampaignID *int64 `json:"campaign_id"`
|
||||||
@@ -736,6 +749,7 @@ DO UPDATE SET dim_id = EXCLUDED.dim_id, updated_at = EXCLUDED.updated_at
|
|||||||
ItemID int64
|
ItemID int64
|
||||||
Dim1 int64
|
Dim1 int64
|
||||||
Dim3Key int64
|
Dim3Key int64
|
||||||
|
HasMSSQL bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build base variant keys from PG's authoritative table (mmitem_dim).
|
// Build base variant keys from PG's authoritative table (mmitem_dim).
|
||||||
@@ -805,7 +819,8 @@ WHERE mmitem_id = ANY($1::bigint[])
|
|||||||
rows.Close()
|
rows.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve dim ids -> tokens for a readable VariantCode.
|
// Resolve dim ids -> tokens for a fallback readable VariantCode.
|
||||||
|
// MSSQL/Nebim tokens override this below; PG ids are only storage keys.
|
||||||
idToToken := map[int64]string{}
|
idToToken := map[int64]string{}
|
||||||
if len(dimIDs) > 0 {
|
if len(dimIDs) > 0 {
|
||||||
// uniq
|
// uniq
|
||||||
@@ -900,11 +915,11 @@ ORDER BY dim_id, updated_at DESC;
|
|||||||
key := fmt.Sprintf("%d|%d|%d", itemID, d1, d3k)
|
key := fmt.Sprintf("%d|%d|%d", itemID, d1, d3k)
|
||||||
prev, ok := tmpMap[key]
|
prev, ok := tmpMap[key]
|
||||||
if !ok {
|
if !ok {
|
||||||
// If PG does not have mmitem_dim rows for this item yet, seed it from MSSQL and include it.
|
// Seed missing MSSQL combos even when the product already has some mmitem_dim rows.
|
||||||
if !hasMMItemDim[itemID] {
|
// PG remains the storage key source, but MSSQL may reveal new/missing color or dim3 combos.
|
||||||
var v2 any = nil
|
var v2 any = nil
|
||||||
if d3k > 0 {
|
if sizeID, ok := resolveDimID("dimval1", dim1Code); ok && sizeID > 0 {
|
||||||
v2 = d3k
|
v2 = sizeID
|
||||||
}
|
}
|
||||||
v3 := int64(0)
|
v3 := int64(0)
|
||||||
if id, ok := resolveDimID("dimval1", dim3Code); ok {
|
if id, ok := resolveDimID("dimval1", dim3Code); ok {
|
||||||
@@ -950,7 +965,6 @@ WHERE NOT EXISTS (
|
|||||||
prev = tmpMap[key]
|
prev = tmpMap[key]
|
||||||
ok = true
|
ok = true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -960,6 +974,8 @@ WHERE NOT EXISTS (
|
|||||||
q = qty.Float64
|
q = qty.Float64
|
||||||
}
|
}
|
||||||
prev.StockQty += q
|
prev.StockQty += q
|
||||||
|
prev.VariantCode = buildNebimVariantDisplayCode(colorCode, dim3Code)
|
||||||
|
prev.HasMSSQL = true
|
||||||
tmpMap[key] = prev
|
tmpMap[key] = prev
|
||||||
_ = colorCode // display-only
|
_ = colorCode // display-only
|
||||||
}
|
}
|
||||||
@@ -968,6 +984,15 @@ WHERE NOT EXISTS (
|
|||||||
for _, v := range tmpMap {
|
for _, v := range tmpMap {
|
||||||
tmp = append(tmp, v)
|
tmp = append(tmp, v)
|
||||||
}
|
}
|
||||||
|
sort.SliceStable(tmp, func(i, j int) bool {
|
||||||
|
if tmp[i].ProductCode != tmp[j].ProductCode {
|
||||||
|
return tmp[i].ProductCode < tmp[j].ProductCode
|
||||||
|
}
|
||||||
|
if tmp[i].HasMSSQL != tmp[j].HasMSSQL {
|
||||||
|
return tmp[i].HasMSSQL
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(tmp[i].VariantCode) < strings.TrimSpace(tmp[j].VariantCode)
|
||||||
|
})
|
||||||
|
|
||||||
// Bulk load campaign assignment for each (mmitem_id, dim1, dim3_key)
|
// Bulk load campaign assignment for each (mmitem_id, dim1, dim3_key)
|
||||||
type keyRec struct {
|
type keyRec struct {
|
||||||
|
|||||||
@@ -321,6 +321,137 @@
|
|||||||
</div>
|
</div>
|
||||||
</q-menu>
|
</q-menu>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
|
<q-btn
|
||||||
|
v-else-if="col.name === 'variantCodes'"
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
round
|
||||||
|
size="8px"
|
||||||
|
icon="filter_alt"
|
||||||
|
:color="selectedVariantCodes.length > 0 ? 'primary' : 'grey-7'"
|
||||||
|
:disable="pageBusy"
|
||||||
|
class="header-filter-btn"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<q-badge v-if="selectedVariantCodes.length > 0" color="primary" floating rounded>
|
||||||
|
{{ selectedVariantCodes.length }}
|
||||||
|
</q-badge>
|
||||||
|
<q-menu anchor="bottom right" self="top right" :offset="[0, 4]">
|
||||||
|
<div class="excel-filter-menu">
|
||||||
|
<q-input
|
||||||
|
v-model="variantFilterSearch"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
clearable
|
||||||
|
class="excel-filter-select"
|
||||||
|
placeholder="Varyant ara"
|
||||||
|
/>
|
||||||
|
<div class="excel-filter-actions row items-center justify-between q-pt-xs">
|
||||||
|
<q-btn flat dense size="sm" label="Tumunu Sec" :disable="pageBusy || filteredVariantOptions.length === 0" @click="selectAllVariantOptions" />
|
||||||
|
<q-btn flat dense size="sm" label="Temizle" :disable="pageBusy || selectedVariantCodes.length === 0" @click="clearVariantOptions" />
|
||||||
|
</div>
|
||||||
|
<q-virtual-scroll
|
||||||
|
v-if="filteredVariantOptions.length > 0"
|
||||||
|
class="excel-filter-options"
|
||||||
|
:items="filteredVariantOptions"
|
||||||
|
:virtual-scroll-item-size="32"
|
||||||
|
separator
|
||||||
|
>
|
||||||
|
<template #default="{ item: option }">
|
||||||
|
<q-item
|
||||||
|
:key="`variant-${option.value}`"
|
||||||
|
dense
|
||||||
|
clickable
|
||||||
|
:disable="pageBusy"
|
||||||
|
class="excel-filter-option"
|
||||||
|
@click="toggleVariantValue(option.value)"
|
||||||
|
>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-checkbox
|
||||||
|
dense
|
||||||
|
size="sm"
|
||||||
|
:model-value="selectedVariantCodeSet.has(option.value)"
|
||||||
|
:disable="pageBusy"
|
||||||
|
@update:model-value="() => toggleVariantValue(option.value)"
|
||||||
|
@click.stop
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>{{ option.label }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
</q-virtual-scroll>
|
||||||
|
<div v-else class="excel-filter-empty">Sonuc yok</div>
|
||||||
|
</div>
|
||||||
|
</q-menu>
|
||||||
|
</q-btn>
|
||||||
|
<q-btn
|
||||||
|
v-else-if="isLocalFilterableColumn(col.name)"
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
round
|
||||||
|
size="8px"
|
||||||
|
icon="filter_alt"
|
||||||
|
:color="getColumnFilterValues(col.name).length > 0 ? 'primary' : 'grey-7'"
|
||||||
|
:disable="pageBusy"
|
||||||
|
class="header-filter-btn"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<q-badge v-if="getColumnFilterValues(col.name).length > 0" color="primary" floating rounded>
|
||||||
|
{{ getColumnFilterValues(col.name).length }}
|
||||||
|
</q-badge>
|
||||||
|
<q-menu anchor="bottom right" self="top right" :offset="[0, 4]">
|
||||||
|
<div class="excel-filter-menu">
|
||||||
|
<q-input
|
||||||
|
:model-value="columnFilterSearch[col.name] || ''"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
clearable
|
||||||
|
class="excel-filter-select"
|
||||||
|
placeholder="Filtre ara"
|
||||||
|
@update:model-value="(val) => setColumnFilterSearch(col.name, val)"
|
||||||
|
/>
|
||||||
|
<div class="excel-filter-actions row items-center justify-between q-pt-xs">
|
||||||
|
<q-btn flat dense size="sm" label="Tumunu Sec" :disable="pageBusy || getFilteredColumnOptions(col).length === 0" @click="selectAllColumnOptions(col)" />
|
||||||
|
<q-btn flat dense size="sm" label="Temizle" :disable="pageBusy || getColumnFilterValues(col.name).length === 0" @click="clearColumnOptions(col.name)" />
|
||||||
|
</div>
|
||||||
|
<q-virtual-scroll
|
||||||
|
v-if="getFilteredColumnOptions(col).length > 0"
|
||||||
|
class="excel-filter-options"
|
||||||
|
:items="getFilteredColumnOptions(col)"
|
||||||
|
:virtual-scroll-item-size="32"
|
||||||
|
separator
|
||||||
|
>
|
||||||
|
<template #default="{ item: option }">
|
||||||
|
<q-item
|
||||||
|
:key="`${col.name}-${option.value}`"
|
||||||
|
dense
|
||||||
|
clickable
|
||||||
|
:disable="pageBusy"
|
||||||
|
class="excel-filter-option"
|
||||||
|
@click="toggleColumnFilterValue(col.name, option.value)"
|
||||||
|
>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-checkbox
|
||||||
|
dense
|
||||||
|
size="sm"
|
||||||
|
:model-value="getColumnFilterSet(col.name).has(option.value)"
|
||||||
|
:disable="pageBusy"
|
||||||
|
@update:model-value="() => toggleColumnFilterValue(col.name, option.value)"
|
||||||
|
@click.stop
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>{{ option.label || '-' }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
</q-virtual-scroll>
|
||||||
|
<div v-else class="excel-filter-empty">Sonuc yok</div>
|
||||||
|
</div>
|
||||||
|
</q-menu>
|
||||||
|
</q-btn>
|
||||||
<span v-else class="header-filter-ghost"></span>
|
<span v-else class="header-filter-ghost"></span>
|
||||||
</div>
|
</div>
|
||||||
</q-th>
|
</q-th>
|
||||||
@@ -503,7 +634,11 @@ const topUrunIlkGrubu = ref(null)
|
|||||||
const topUrunAnaGrubu = ref(null)
|
const topUrunAnaGrubu = ref(null)
|
||||||
const selectedProductCodes = ref([])
|
const selectedProductCodes = ref([])
|
||||||
const selectedCampaignLabels = ref([])
|
const selectedCampaignLabels = ref([])
|
||||||
|
const selectedVariantCodes = ref([])
|
||||||
const campaignFilterSearch = ref('')
|
const campaignFilterSearch = ref('')
|
||||||
|
const variantFilterSearch = ref('')
|
||||||
|
const columnFilters = ref({})
|
||||||
|
const columnFilterSearch = ref({})
|
||||||
const selectedPriceOptions = ref(['usd5', 'try5'])
|
const selectedPriceOptions = ref(['usd5', 'try5'])
|
||||||
const leftDetailsExpanded = ref(true)
|
const leftDetailsExpanded = ref(true)
|
||||||
|
|
||||||
@@ -537,6 +672,7 @@ const fullscreenImages = computed(() => productCardImages.value || [])
|
|||||||
const selectedPriceSet = computed(() => new Set(selectedPriceOptions.value || []))
|
const selectedPriceSet = computed(() => new Set(selectedPriceOptions.value || []))
|
||||||
const selectedProductCodeSet = computed(() => new Set(selectedProductCodes.value || []))
|
const selectedProductCodeSet = computed(() => new Set(selectedProductCodes.value || []))
|
||||||
const selectedCampaignLabelSet = computed(() => new Set(selectedCampaignLabels.value || []))
|
const selectedCampaignLabelSet = computed(() => new Set(selectedCampaignLabels.value || []))
|
||||||
|
const selectedVariantCodeSet = computed(() => new Set(selectedVariantCodes.value || []))
|
||||||
const pageBusy = computed(() => loading.value || renderPending.value)
|
const pageBusy = computed(() => loading.value || renderPending.value)
|
||||||
const canFetch = computed(() => Boolean(topUrunIlkGrubu.value || topUrunAnaGrubu.value || selectedProductCodes.value.length > 0))
|
const canFetch = computed(() => Boolean(topUrunIlkGrubu.value || topUrunAnaGrubu.value || selectedProductCodes.value.length > 0))
|
||||||
const showGuidanceOverlay = computed(() => !loading.value && rows.value.length === 0 && error.value === GUIDANCE_MSG)
|
const showGuidanceOverlay = computed(() => !loading.value && rows.value.length === 0 && error.value === GUIDANCE_MSG)
|
||||||
@@ -559,6 +695,21 @@ const filteredCampaignOptions = computed(() => {
|
|||||||
const list = campaignOptions.value
|
const list = campaignOptions.value
|
||||||
return q ? list.filter((x) => x.label.toLocaleLowerCase('tr').includes(q)) : list
|
return q ? list.filter((x) => x.label.toLocaleLowerCase('tr').includes(q)) : list
|
||||||
})
|
})
|
||||||
|
const variantOptions = computed(() => {
|
||||||
|
const uniq = new Set()
|
||||||
|
for (const row of rows.value || []) {
|
||||||
|
const val = toText(row?.variantCodes)
|
||||||
|
if (val) uniq.add(val)
|
||||||
|
}
|
||||||
|
return Array.from(uniq)
|
||||||
|
.sort((a, b) => variantCodeCollator.compare(a, b))
|
||||||
|
.map((value) => ({ label: value, value }))
|
||||||
|
})
|
||||||
|
const filteredVariantOptions = computed(() => {
|
||||||
|
const q = toText(variantFilterSearch.value).toLocaleLowerCase('tr')
|
||||||
|
const list = variantOptions.value
|
||||||
|
return q ? list.filter((x) => x.label.toLocaleLowerCase('tr').includes(q)) : list
|
||||||
|
})
|
||||||
|
|
||||||
function toText (value) {
|
function toText (value) {
|
||||||
return String(value ?? '').trim()
|
return String(value ?? '').trim()
|
||||||
@@ -763,6 +914,79 @@ function clearCampaignOptions () {
|
|||||||
selectedCampaignLabels.value = []
|
selectedCampaignLabels.value = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleVariantValue (value) {
|
||||||
|
const v = toText(value)
|
||||||
|
if (!v) return
|
||||||
|
const set = new Set(selectedVariantCodes.value || [])
|
||||||
|
if (set.has(v)) set.delete(v)
|
||||||
|
else set.add(v)
|
||||||
|
selectedVariantCodes.value = Array.from(set).sort((a, b) => variantCodeCollator.compare(a, b))
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectAllVariantOptions () {
|
||||||
|
const set = new Set(selectedVariantCodes.value || [])
|
||||||
|
for (const option of filteredVariantOptions.value) {
|
||||||
|
const v = toText(option.value)
|
||||||
|
if (v) set.add(v)
|
||||||
|
}
|
||||||
|
selectedVariantCodes.value = Array.from(set).sort((a, b) => variantCodeCollator.compare(a, b))
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearVariantOptions () {
|
||||||
|
selectedVariantCodes.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLocalFilterableColumn (name) {
|
||||||
|
if (!name || ['image', 'productCode', 'variantCodes', 'campaignLabel'].includes(name)) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function getColumnFilterValues (name) {
|
||||||
|
const list = columnFilters.value?.[name]
|
||||||
|
return Array.isArray(list) ? list : []
|
||||||
|
}
|
||||||
|
|
||||||
|
function getColumnFilterSet (name) {
|
||||||
|
return new Set(getColumnFilterValues(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
function setColumnFilterSearch (name, value) {
|
||||||
|
columnFilterSearch.value = { ...columnFilterSearch.value, [name]: toText(value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
function getColumnOptions (col) {
|
||||||
|
const uniq = new Set()
|
||||||
|
for (const row of rows.value || []) {
|
||||||
|
uniq.add(exportCell(row, col))
|
||||||
|
}
|
||||||
|
return Array.from(uniq)
|
||||||
|
.sort((a, b) => String(a).localeCompare(String(b), 'tr', { numeric: true, sensitivity: 'base' }))
|
||||||
|
.map((value) => ({ label: value || '-', value }))
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFilteredColumnOptions (col) {
|
||||||
|
const q = toText(columnFilterSearch.value?.[col.name]).toLocaleLowerCase('tr')
|
||||||
|
const list = getColumnOptions(col)
|
||||||
|
return q ? list.filter((x) => String(x.label || '').toLocaleLowerCase('tr').includes(q)) : list
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleColumnFilterValue (name, value) {
|
||||||
|
const set = new Set(getColumnFilterValues(name))
|
||||||
|
if (set.has(value)) set.delete(value)
|
||||||
|
else set.add(value)
|
||||||
|
columnFilters.value = { ...columnFilters.value, [name]: Array.from(set) }
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectAllColumnOptions (col) {
|
||||||
|
const set = new Set(getColumnFilterValues(col.name))
|
||||||
|
for (const option of getFilteredColumnOptions(col)) set.add(option.value)
|
||||||
|
columnFilters.value = { ...columnFilters.value, [col.name]: Array.from(set) }
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearColumnOptions (name) {
|
||||||
|
columnFilters.value = { ...columnFilters.value, [name]: [] }
|
||||||
|
}
|
||||||
|
|
||||||
function onTopUrunIlkGrubuChange () {
|
function onTopUrunIlkGrubuChange () {
|
||||||
topUrunAnaGrubu.value = null
|
topUrunAnaGrubu.value = null
|
||||||
void fetchServerFilterOptions('urunAnaGrubu', '')
|
void fetchServerFilterOptions('urunAnaGrubu', '')
|
||||||
@@ -947,6 +1171,10 @@ function resetSelections () {
|
|||||||
topUrunIlkGrubu.value = null
|
topUrunIlkGrubu.value = null
|
||||||
topUrunAnaGrubu.value = null
|
topUrunAnaGrubu.value = null
|
||||||
selectedProductCodes.value = []
|
selectedProductCodes.value = []
|
||||||
|
selectedCampaignLabels.value = []
|
||||||
|
selectedVariantCodes.value = []
|
||||||
|
columnFilters.value = {}
|
||||||
|
columnFilterSearch.value = {}
|
||||||
rows.value = []
|
rows.value = []
|
||||||
error.value = GUIDANCE_MSG
|
error.value = GUIDANCE_MSG
|
||||||
currentPage.value = 1
|
currentPage.value = 1
|
||||||
@@ -1031,8 +1259,25 @@ const visibleColumns = computed(() => allColumns.filter((c) => {
|
|||||||
|
|
||||||
const filteredRows = computed(() => {
|
const filteredRows = computed(() => {
|
||||||
const campaignSet = selectedCampaignLabelSet.value
|
const campaignSet = selectedCampaignLabelSet.value
|
||||||
if (campaignSet.size === 0) return rows.value || []
|
const variantSet = selectedVariantCodeSet.value
|
||||||
return (rows.value || []).filter((row) => campaignSet.has(toText(row?.campaignLabel)))
|
const localFilters = columnFilters.value || {}
|
||||||
|
let list = rows.value || []
|
||||||
|
if (campaignSet.size > 0) {
|
||||||
|
list = list.filter((row) => campaignSet.has(toText(row?.campaignLabel)))
|
||||||
|
}
|
||||||
|
if (variantSet.size > 0) {
|
||||||
|
list = list.filter((row) => variantSet.has(toText(row?.variantCodes)))
|
||||||
|
}
|
||||||
|
const active = Object.entries(localFilters).filter(([, values]) => Array.isArray(values) && values.length > 0)
|
||||||
|
if (active.length > 0) {
|
||||||
|
const byName = new Map(allColumns.map((c) => [c.name, c]))
|
||||||
|
list = list.filter((row) => active.every(([name, values]) => {
|
||||||
|
const col = byName.get(name)
|
||||||
|
if (!col) return true
|
||||||
|
return new Set(values).has(exportCell(row, col))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
return list
|
||||||
})
|
})
|
||||||
const tableMinWidth = computed(() => visibleColumns.value.reduce((sum, c) => sum + extractWidth(c.style), 0))
|
const tableMinWidth = computed(() => visibleColumns.value.reduce((sum, c) => sum + extractWidth(c.style), 0))
|
||||||
const tableStyle = computed(() => ({
|
const tableStyle = computed(() => ({
|
||||||
|
|||||||
@@ -884,6 +884,7 @@ const currentPage = ref(1)
|
|||||||
let reloadTimer = null
|
let reloadTimer = null
|
||||||
const variantRows = ref([])
|
const variantRows = ref([])
|
||||||
const variantRowsCache = new Map()
|
const variantRowsCache = new Map()
|
||||||
|
const variantCodeCollator = new Intl.Collator('tr', { numeric: true, sensitivity: 'base' })
|
||||||
const VARIANT_ROWS_CACHE_LIMIT = 16
|
const VARIANT_ROWS_CACHE_LIMIT = 16
|
||||||
|
|
||||||
const GUIDANCE_MSG = "Calismak icin once Urun Ilk Grubu veya Urun Ana Grubu Secin ve GRUPLARI GETIR'e Basin."
|
const GUIDANCE_MSG = "Calismak icin once Urun Ilk Grubu veya Urun Ana Grubu Secin ve GRUPLARI GETIR'e Basin."
|
||||||
@@ -2678,7 +2679,7 @@ async function buildVariantRowsForProductPage (baseProductRows = []) {
|
|||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
vs.sort((a, b) => String(a?.variant_code || '').localeCompare(String(b?.variant_code || ''), 'tr'))
|
vs.sort((a, b) => variantCodeCollator.compare(String(a?.variant_code || ''), String(b?.variant_code || '')))
|
||||||
for (const v of vs) {
|
for (const v of vs) {
|
||||||
const d1 = Number(v?.dim1 || 0)
|
const d1 = Number(v?.dim1 || 0)
|
||||||
const d3 = v?.dim3 == null ? null : Number(v?.dim3 || 0)
|
const d3 = v?.dim3 == null ? null : Number(v?.dim3 || 0)
|
||||||
|
|||||||
Reference in New Issue
Block a user