Merge remote-tracking branch 'origin/master'

This commit is contained in:
M_Kececi
2026-05-06 11:07:55 +03:00
parent 05a2a03a6a
commit 77fe2b04b6
38 changed files with 12676 additions and 8 deletions

3
go.mod Normal file
View File

@@ -0,0 +1,3 @@
module bssapp
go 1.24.5

View File

@@ -0,0 +1,95 @@
/*
Full-text index for Production Product Costing item lookup.
Target: SQL Server / URETIM.dbo.tbStok
*/
IF SERVERPROPERTY('IsFullTextInstalled') <> 1
BEGIN
RAISERROR('SQL Server Full-Text Search yüklü değil.', 16, 1);
RETURN;
END;
GO
IF DATABASEPROPERTYEX(DB_NAME(), 'IsFullTextEnabled') <> 1
BEGIN
RAISERROR('Veritabanında Full-Text Search etkin değil.', 16, 1);
RETURN;
END;
GO
IF OBJECT_ID('dbo.tbStok') IS NULL
BEGIN
RAISERROR('dbo.tbStok bulunamadı.', 16, 1);
RETURN;
END;
GO
IF NOT EXISTS (
SELECT 1
FROM sys.fulltext_catalogs
WHERE name = 'FTC_ProductionProductCosting'
)
BEGIN
CREATE FULLTEXT CATALOG FTC_ProductionProductCosting;
END;
GO
IF EXISTS (
SELECT 1
FROM sys.fulltext_indexes
WHERE object_id = OBJECT_ID('dbo.tbStok')
)
BEGIN
IF NOT EXISTS (
SELECT 1
FROM sys.fulltext_index_columns fic
INNER JOIN sys.columns c
ON c.object_id = fic.object_id
AND c.column_id = fic.column_id
WHERE fic.object_id = OBJECT_ID('dbo.tbStok')
AND c.name = 'sAciklama'
)
BEGIN
ALTER FULLTEXT INDEX ON dbo.tbStok
ADD (sAciklama LANGUAGE 1055);
ALTER FULLTEXT INDEX ON dbo.tbStok
START FULL POPULATION;
END;
END;
ELSE
BEGIN
DECLARE @keyIndex sysname;
DECLARE @sql nvarchar(max);
SELECT TOP 1 @keyIndex = i.name
FROM sys.indexes i
WHERE i.object_id = OBJECT_ID('dbo.tbStok')
AND i.is_unique = 1
AND i.is_disabled = 0
AND i.type IN (1, 2)
ORDER BY
CASE WHEN i.is_primary_key = 1 THEN 0 ELSE 1 END,
i.index_id;
IF @keyIndex IS NULL
BEGIN
RAISERROR('dbo.tbStok için uygun unique key index bulunamadı.', 16, 1);
RETURN;
END;
SET @sql = N'
CREATE FULLTEXT INDEX ON dbo.tbStok
(
sAciklama LANGUAGE 1055
)
KEY INDEX ' + QUOTENAME(@keyIndex) + N'
ON [FTC_ProductionProductCosting]
WITH CHANGE_TRACKING AUTO;';
EXEC sp_executesql @sql;
ALTER FULLTEXT INDEX ON dbo.tbStok
START FULL POPULATION;
END;
GO

View File

@@ -23,6 +23,7 @@ UI_DIR=/opt/bssapp/ui/dist
# =============================== # ===============================
POSTGRES_CONN=host=46.224.33.150 port=5432 user=postgres password=tayitkan dbname=baggib2b sslmode=disable POSTGRES_CONN=host=46.224.33.150 port=5432 user=postgres password=tayitkan dbname=baggib2b sslmode=disable
MSSQL_CONN=sqlserver://sa:Gil_0150@10.0.0.9:1433?database=BAGGI_V3&encrypt=disable MSSQL_CONN=sqlserver://sa:Gil_0150@10.0.0.9:1433?database=BAGGI_V3&encrypt=disable
URETIM_MSSQL_CONN=sqlserver://sa:Gil_0150@10.0.0.9:1433?database=URETIM&encrypt=disable
# =============================== # ===============================
# PDF # PDF

0
svc/backend-dev.err.log Normal file
View File

365
svc/backend-dev.out.log Normal file
View File

@@ -0,0 +1,365 @@
time=2026-04-29T21:47:12.721+03:00 level=INFO msg="backend start" app=bssapp-backend scope=main
time=2026-04-29T21:47:12.767+03:00 level=INFO msg="🔥🔥🔥 BSSAPP BACKEND STARTED — LOGIN ROUTE SHOULD EXIST 🔥🔥🔥" app=bssapp-backend
time=2026-04-29T21:47:12.770+03:00 level=INFO msg="🔐 JWT_SECRET yüklendi" app=bssapp-backend
MSSQL baglantisi basarili (connection timeout=120s, dial timeout=120s)
URETIM MSSQL baglantisi basarili (connection timeout=120s, dial timeout=120s)
time=2026-04-29T21:47:13.271+03:00 level=INFO msg="PostgreSQL bağlantısı başarılı" app=bssapp-backend
time=2026-04-29T21:47:13.581+03:00 level=INFO msg="✅ Admin dept permissions seeded" app=bssapp-backend
time=2026-04-29T21:47:13.581+03:00 level=INFO msg="🟢 auditlog Init called, buffer: 1000" app=bssapp-backend
time=2026-04-29T21:47:13.587+03:00 level=INFO msg="🟢 auditlog worker STARTED" app=bssapp-backend
time=2026-04-29T21:47:13.588+03:00 level=INFO msg="🕵️ AuditLog sistemi başlatıldı (buffer=1000)" app=bssapp-backend
time=2026-04-29T21:47:13.678+03:00 level=INFO msg="[TranslationPerf] index_ready sql=\"CREATE EXTENSION IF NOT EXISTS pg_trgm\"" app=bssapp-backend
time=2026-04-29T21:47:13.765+03:00 level=INFO msg="[TranslationPerf] index_ready sql=\"CREATE INDEX IF NOT EXISTS idx_mk_translator_t_key_lang ON mk_translator (t_key, lang_code)\"" app=bssapp-backend
time=2026-04-29T21:47:13.854+03:00 level=INFO msg="[TranslationPerf] index_ready sql=\"CREATE INDEX IF NOT EXISTS idx_mk_translator_status_lang_updated ON mk_translator (status, lang_code...\"" app=bssapp-backend
time=2026-04-29T21:47:13.944+03:00 level=INFO msg="[TranslationPerf] index_ready sql=\"CREATE INDEX IF NOT EXISTS idx_mk_translator_manual_status ON mk_translator (is_manual, status)\"" app=bssapp-backend
time=2026-04-29T21:47:14.032+03:00 level=INFO msg="[TranslationPerf] index_ready sql=\"CREATE INDEX IF NOT EXISTS idx_mk_translator_source_type_expr ON mk_translator ((COALESCE(NULLIF(pro...\"" app=bssapp-backend
time=2026-04-29T21:47:14.133+03:00 level=INFO msg="[TranslationPerf] index_ready sql=\"CREATE INDEX IF NOT EXISTS idx_mk_translator_source_text_trgm ON mk_translator USING gin (source_tex...\"" app=bssapp-backend
time=2026-04-29T21:47:14.222+03:00 level=INFO msg="[TranslationPerf] index_ready sql=\"CREATE INDEX IF NOT EXISTS idx_mk_translator_translated_text_trgm ON mk_translator USING gin (transl...\"" app=bssapp-backend
time=2026-04-29T21:47:14.223+03:00 level=INFO msg="✉️ Graph Mailer hazır (App-only token) | from=baggiss@baggi.com.tr" app=bssapp-backend
time=2026-04-29T21:47:14.223+03:00 level=INFO msg="✉️ Graph Mailer hazır" app=bssapp-backend
📋 [DEBUG] İlk 10 kullanıcı:
- 1 : ctengiz
- 2 : ali.kale
- 5 : mehmet.keçeci
- 6 : mert.keçeci
- 7 : samet.keçeci
- 9 : orhan.caliskan
- 10 : nilgun.sara
- 14 : rustem.kurbanov
- 15 : caner.akyol
- 16 : kemal.matyakupov
time=2026-04-29T21:47:15.216+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/auth/login [auth:login]" app=bssapp-backend
time=2026-04-29T21:47:16.101+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/auth/refresh [auth:refresh]" app=bssapp-backend
time=2026-04-29T21:47:16.996+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/password/forgot [auth:update]" app=bssapp-backend
time=2026-04-29T21:47:17.904+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/password/reset/validate/{token} [auth:view]" app=bssapp-backend
time=2026-04-29T21:47:18.782+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/password/reset [auth:update]" app=bssapp-backend
time=2026-04-29T21:47:19.673+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/password/change [auth:update]" app=bssapp-backend
time=2026-04-29T21:47:20.563+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/activity-logs [system:read]" app=bssapp-backend
time=2026-04-29T21:47:21.444+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/test-mail [system:update]" app=bssapp-backend
time=2026-04-29T21:47:22.332+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/system/market-mail-mappings/lookups [system:update]" app=bssapp-backend
time=2026-04-29T21:47:23.224+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/system/market-mail-mappings [system:update]" app=bssapp-backend
time=2026-04-29T21:47:24.100+03:00 level=INFO msg="✅ Route+Perm registered → PUT /api/system/market-mail-mappings/{marketId} [system:update]" app=bssapp-backend
time=2026-04-29T21:47:24.978+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/language/translations [language:update]" app=bssapp-backend
time=2026-04-29T21:47:25.870+03:00 level=INFO msg="✅ Route+Perm registered → PUT /api/language/translations/{id} [language:update]" app=bssapp-backend
time=2026-04-29T21:47:26.752+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/language/translations/upsert-missing [language:update]" app=bssapp-backend
time=2026-04-29T21:47:27.638+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/language/translations/sync-sources [language:update]" app=bssapp-backend
time=2026-04-29T21:47:28.520+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/language/translations/translate-selected [language:update]" app=bssapp-backend
time=2026-04-29T21:47:29.449+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/language/translations/bulk-approve [language:update]" app=bssapp-backend
time=2026-04-29T21:47:30.328+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/language/translations/bulk-update [language:update]" app=bssapp-backend
time=2026-04-29T21:47:31.208+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/roles/{id}/permissions [system:update]" app=bssapp-backend
time=2026-04-29T21:47:32.090+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/roles/{id}/permissions [system:update]" app=bssapp-backend
time=2026-04-29T21:47:32.979+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/users/{id}/permissions [system:update]" app=bssapp-backend
time=2026-04-29T21:47:33.884+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/users/{id}/permissions [system:update]" app=bssapp-backend
time=2026-04-29T21:47:34.814+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/permissions/routes [system:view]" app=bssapp-backend
time=2026-04-29T21:47:35.718+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/permissions/effective [system:view]" app=bssapp-backend
time=2026-04-29T21:47:36.597+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/permissions/matrix [system:view]" app=bssapp-backend
time=2026-04-29T21:47:37.490+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/role-dept-permissions/list [system:update]" app=bssapp-backend
time=2026-04-29T21:47:38.395+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/roles/{roleId}/departments/{deptCode}/permissions [system:update]" app=bssapp-backend
time=2026-04-29T21:47:39.290+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/roles/{roleId}/departments/{deptCode}/permissions [system:update]" app=bssapp-backend
time=2026-04-29T21:47:40.177+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/users/list [user:view]" app=bssapp-backend
time=2026-04-29T21:47:41.060+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/users [user:insert]" app=bssapp-backend
time=2026-04-29T21:47:41.940+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/users/{id} [user:update]" app=bssapp-backend
time=2026-04-29T21:47:42.841+03:00 level=INFO msg="✅ Route+Perm registered → PUT /api/users/{id} [user:update]" app=bssapp-backend
time=2026-04-29T21:47:43.736+03:00 level=INFO msg="✅ Route+Perm registered → DELETE /api/users/{id} [user:delete]" app=bssapp-backend
time=2026-04-29T21:47:44.618+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/users/{id}/admin-reset-password [user:update]" app=bssapp-backend
time=2026-04-29T21:47:45.499+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/users/{id}/send-password-mail [user:update]" app=bssapp-backend
time=2026-04-29T21:47:46.389+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/users/create [user:insert]" app=bssapp-backend
time=2026-04-29T21:47:47.287+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/lookups/nebim-users [user:view]" app=bssapp-backend
time=2026-04-29T21:47:48.184+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/lookups/piyasalar [user:view]" app=bssapp-backend
time=2026-04-29T21:47:49.074+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/lookups/users-perm [user:view]" app=bssapp-backend
time=2026-04-29T21:47:49.968+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/lookups/roles-perm [user:view]" app=bssapp-backend
time=2026-04-29T21:47:50.851+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/lookups/departments-perm [user:view]" app=bssapp-backend
time=2026-04-29T21:47:52.990+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/lookups/modules [user:view]" app=bssapp-backend
time=2026-04-29T21:47:53.893+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/lookups/roles [user:view]" app=bssapp-backend
time=2026-04-29T21:47:54.789+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/lookups/departments [user:view]" app=bssapp-backend
time=2026-04-29T21:47:55.694+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/accounts [customer:view]" app=bssapp-backend
time=2026-04-29T21:47:56.587+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/customer-list [customer:view]" app=bssapp-backend
time=2026-04-29T21:47:57.474+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/today-currency [finance:view]" app=bssapp-backend
time=2026-04-29T21:47:58.358+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/export-pdf [finance:export]" app=bssapp-backend
time=2026-04-29T21:47:59.303+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/exportstamentheaderreport-pdf [finance:export]" app=bssapp-backend
time=2026-04-29T21:48:00.197+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/finance/customer-balances [finance:view]" app=bssapp-backend
time=2026-04-29T21:48:01.080+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/finance/customer-balances/export-pdf [finance:export]" app=bssapp-backend
time=2026-04-29T21:48:01.968+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/finance/customer-balances/export-excel [finance:export]" app=bssapp-backend
time=2026-04-29T21:48:02.850+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/finance/account-aging-statement [finance:view]" app=bssapp-backend
time=2026-04-29T21:48:03.759+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/finance/account-aging-statement/export-pdf [finance:export]" app=bssapp-backend
time=2026-04-29T21:48:04.664+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/finance/account-aging-statement/export-screen-pdf [finance:export]" app=bssapp-backend
time=2026-04-29T21:48:05.600+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/finance/account-aging-statement/export-excel [finance:export]" app=bssapp-backend
time=2026-04-29T21:48:06.513+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/finance/aged-customer-balance-list [finance:view]" app=bssapp-backend
time=2026-04-29T21:48:07.460+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/statements [finance:view]" app=bssapp-backend
time=2026-04-29T21:48:08.375+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/statements/{id}/details [finance:view]" app=bssapp-backend
time=2026-04-29T21:48:09.258+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/order/create [order:insert]" app=bssapp-backend
time=2026-04-29T21:48:10.154+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/order/update [order:update]" app=bssapp-backend
time=2026-04-29T21:48:11.034+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/order/{id}/bulk-due-date [order:update]" app=bssapp-backend
time=2026-04-29T21:48:11.924+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/order/get/{id} [order:view]" app=bssapp-backend
time=2026-04-29T21:48:12.811+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/orders/list [order:view]" app=bssapp-backend
time=2026-04-29T21:48:13.695+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/orders/production-list [order:update]" app=bssapp-backend
time=2026-04-29T21:48:14.624+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/orders/production-items/cditem-lookups [order:view]" app=bssapp-backend
time=2026-04-29T21:48:15.544+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/orders/production-items/{id} [order:view]" app=bssapp-backend
time=2026-04-29T21:48:17.930+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/orders/production-items/{id}/insert-missing [order:update]" app=bssapp-backend
time=2026-04-29T21:48:18.825+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/orders/production-items/{id}/validate [order:update]" app=bssapp-backend
time=2026-04-29T21:48:20.012+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/orders/production-items/{id}/apply [order:update]" app=bssapp-backend
time=2026-04-29T21:48:20.955+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/orders/close-ready [order:update]" app=bssapp-backend
time=2026-04-29T21:48:21.854+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/orders/bulk-close [order:update]" app=bssapp-backend
time=2026-04-29T21:48:23.037+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/orders/export [order:export]" app=bssapp-backend
time=2026-04-29T21:48:23.994+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/order/check/{id} [order:view]" app=bssapp-backend
time=2026-04-29T21:48:24.943+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/order/validate [order:insert]" app=bssapp-backend
time=2026-04-29T21:48:26.184+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/order/pdf/{id} [order:export]" app=bssapp-backend
time=2026-04-29T21:48:27.066+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/order/send-market-mail [order:read]" app=bssapp-backend
time=2026-04-29T21:48:27.952+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/order-inventory [order:view]" app=bssapp-backend
time=2026-04-29T21:48:28.844+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/orderpricelistb2b [order:view]" app=bssapp-backend
time=2026-04-29T21:48:29.747+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/min-price [order:view]" app=bssapp-backend
time=2026-04-29T21:48:30.627+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/products [order:view]" app=bssapp-backend
time=2026-04-29T21:48:31.512+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-detail [order:view]" app=bssapp-backend
time=2026-04-29T21:48:32.407+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-cditem [order:view]" app=bssapp-backend
time=2026-04-29T21:48:33.297+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-colors [order:view]" app=bssapp-backend
time=2026-04-29T21:48:34.190+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-newcolors [order:view]" app=bssapp-backend
time=2026-04-29T21:48:35.380+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-colorsize [order:view]" app=bssapp-backend
time=2026-04-29T21:48:36.265+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-secondcolor [order:view]" app=bssapp-backend
time=2026-04-29T21:48:37.162+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-newsecondcolor [order:view]" app=bssapp-backend
time=2026-04-29T21:48:38.051+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-attributes [order:view]" app=bssapp-backend
time=2026-04-29T21:48:38.944+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-item-attributes [order:view]" app=bssapp-backend
time=2026-04-29T21:48:39.858+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-stock-query [order:view]" app=bssapp-backend
time=2026-04-29T21:48:40.753+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-stock-attribute-options [order:view]" app=bssapp-backend
time=2026-04-29T21:48:41.640+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-stock-query-by-attributes [order:view]" app=bssapp-backend
time=2026-04-29T21:48:42.531+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-images [order:view]" app=bssapp-backend
time=2026-04-29T21:48:43.410+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-images/{id}/content [order:view]" app=bssapp-backend
time=2026-04-29T21:48:44.296+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-size-match/rules [order:view]" app=bssapp-backend
time=2026-04-29T21:48:45.193+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/products [order:view]" app=bssapp-backend
time=2026-04-29T21:48:46.071+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/no-cost-products [order:view]" app=bssapp-backend
time=2026-04-29T21:48:46.982+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/has-cost-products [order:view]" app=bssapp-backend
time=2026-04-29T21:48:48.168+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/has-cost-history [order:view]" app=bssapp-backend
time=2026-04-29T21:48:49.052+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/has-cost-detail-groups [order:view]" app=bssapp-backend
time=2026-04-29T21:48:49.942+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/has-cost-detail-header [order:view]" app=bssapp-backend
time=2026-04-29T21:48:50.846+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/production-types [order:view]" app=bssapp-backend
time=2026-04-29T21:48:51.740+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/detail-editor-options [order:view]" app=bssapp-backend
time=2026-04-29T21:48:52.639+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/has-cost-detail-exchange-rates [order:view]" app=bssapp-backend
time=2026-04-29T21:48:53.538+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/has-cost-detail-line-history [order:view]" app=bssapp-backend
time=2026-04-29T21:48:54.426+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/has-cost-detail-similar-history [order:view]" app=bssapp-backend
time=2026-04-29T21:48:55.308+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/production-product-costing/has-cost-detail-bulk-prices [order:view]" app=bssapp-backend
time=2026-04-29T21:48:56.205+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/roles [user:view]" app=bssapp-backend
time=2026-04-29T21:48:57.093+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/departments [user:view]" app=bssapp-backend
time=2026-04-29T21:48:57.980+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/piyasalar [user:view]" app=bssapp-backend
time=2026-04-29T21:49:00.122+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/roles/{id}/departments [user:update]" app=bssapp-backend
time=2026-04-29T21:49:01.048+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/roles/{id}/piyasalar [user:update]" app=bssapp-backend
time=2026-04-29T21:49:01.970+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/users/{id}/roles [user:update]" app=bssapp-backend
time=2026-04-29T21:49:03.170+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/admin/users/{id}/piyasa-sync [admin:user.update]" app=bssapp-backend
time=2026-04-29T21:49:03.172+03:00 level=INFO msg="🌍 CORS Allowed Origin: http://ss.baggi.com.tr/app" app=bssapp-backend
time=2026-04-29T21:49:03.172+03:00 level=INFO msg="🚀 Server running at: 0.0.0.0:8080" app=bssapp-backend
time=2026-04-29T21:49:03.172+03:00 level=INFO msg="🕓 Translation sync next run at 2026-04-30T04:00:00+03:00 (in 6h10m57s)" app=bssapp-backend
time=2026-04-29T21:57:23.596+03:00 level=INFO msg="➡️ POST /api/auth/refresh | auth=false" app=bssapp-backend
time=2026-04-29T21:57:24.523+03:00 level=INFO msg="⬅️ POST /api/auth/refresh | status=200 | 927.7384ms" app=bssapp-backend
time=2026-04-29T21:57:24.524+03:00 level=INFO msg="⚠️ LOGGER: claims is NIL" app=bssapp-backend
time=2026-04-29T21:57:24.524+03:00 level=INFO msg="🧾 auditlog INSERT | actor_dfusr=<nil> actor_user=<nil> role=public nav /api/auth/refresh target=<nil>" app=bssapp-backend
time=2026-04-29T21:57:24.544+03:00 level=INFO msg="🔐 GLOBAL AUTH user=5 role=admin" app=bssapp-backend
time=2026-04-29T21:57:24.545+03:00 level=INFO msg="➡️ GET /api/pricing/production-product-costing/has-cost-detail-header | auth=true" app=bssapp-backend
time=2026-04-29T21:57:24.546+03:00 level=INFO msg="🔐 GLOBAL AUTH user=5 role=admin" app=bssapp-backend
time=2026-04-29T21:57:24.546+03:00 level=INFO msg="➡️ GET /api/pricing/production-product-costing/has-cost-detail-groups | auth=true" app=bssapp-backend
time=2026-04-29T21:57:24.546+03:00 level=INFO msg="AUTH_MIDDLEWARE PASS user=5 role=admin method=GET path=/api/pricing/production-product-costing/has-cost-detail-header" app=bssapp-backend
time=2026-04-29T21:57:24.546+03:00 level=INFO msg="AUTH_MIDDLEWARE PASS user=5 role=admin method=GET path=/api/pricing/production-product-costing/has-cost-detail-groups" app=bssapp-backend
time=2026-04-29T21:57:24.850+03:00 level=INFO msg="🔐 GLOBAL AUTH user=5 role=admin" app=bssapp-backend
time=2026-04-29T21:57:24.850+03:00 level=INFO msg="➡️ GET /api/pricing/production-product-costing/production-types | auth=true" app=bssapp-backend
time=2026-04-29T21:57:24.850+03:00 level=INFO msg="AUTH_MIDDLEWARE PASS user=5 role=admin method=GET path=/api/pricing/production-product-costing/production-types" app=bssapp-backend
time=2026-04-29T21:57:24.964+03:00 level=INFO msg="🔐 PERM CHECK user=5 role=3 dept=[UST_YONETIM] order:view" app=bssapp-backend
time=2026-04-29T21:57:24.964+03:00 level=INFO msg="🔐 PERM CHECK user=5 role=3 dept=[UST_YONETIM] order:view" app=bssapp-backend
time=2026-04-29T21:57:25.021+03:00 level=INFO msg="🔐 PERM CHECK user=5 role=3 dept=[UST_YONETIM] order:view" app=bssapp-backend
time=2026-04-29T21:57:25.295+03:00 level=INFO msg=" ↳ ROLE+DEPT OVERRIDE = true" app=bssapp-backend
time=2026-04-29T21:57:25.297+03:00 level=INFO msg="request start" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.detail-groups detail_source=no-cost urun_kodu=S001-DMY26211 recete_kodu=S001-DMY26211
time=2026-04-29T21:57:25.297+03:00 level=INFO msg="query dispatch" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 query=production-product-costing.no-cost-detail-rows recete_kodu=S001-DMY26211 urun_kodu=S001-DMY26211
time=2026-04-29T21:57:25.300+03:00 level=INFO msg=" ↳ ROLE+DEPT OVERRIDE = true" app=bssapp-backend
time=2026-04-29T21:57:25.301+03:00 level=INFO msg="request start" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.detail-header detail_source=no-cost urun_kodu=S001-DMY26211 recete_kodu=S001-DMY26211
time=2026-04-29T21:57:25.302+03:00 level=INFO msg="query dispatch" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 query=production-product-costing.no-cost-detail-header recete_kodu=S001-DMY26211 urun_kodu=S001-DMY26211
time=2026-04-29T21:57:25.350+03:00 level=INFO msg=" ↳ ROLE+DEPT OVERRIDE = true" app=bssapp-backend
time=2026-04-29T21:57:25.351+03:00 level=INFO msg="request start" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.production-types
time=2026-04-29T21:57:25.389+03:00 level=INFO msg="request done" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.detail-header detail_source=no-cost urun_kodu=S001-DMY26211 recete_kodu=S001-DMY26211 n_urt_recete_id=20815 urun_kodu=S001-DMY26211
time=2026-04-29T21:57:25.391+03:00 level=INFO msg="⬅️ GET /api/pricing/production-product-costing/has-cost-detail-header | status=200 | 845.3861ms" app=bssapp-backend
time=2026-04-29T21:57:25.391+03:00 level=INFO msg="✅ LOGGER CLAIMS user=mehmet.keçeci role=admin id=5" app=bssapp-backend
time=2026-04-29T21:57:25.391+03:00 level=INFO msg="🧾 auditlog INSERT | actor_dfusr=5 actor_user=mehmet.keçeci role=admin nav /api/pricing/production-product-costing/has-cost-detail-header target=<nil>" app=bssapp-backend
time=2026-04-29T21:57:25.406+03:00 level=INFO msg="request done" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.production-types row_count=4
time=2026-04-29T21:57:25.406+03:00 level=INFO msg="request done" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.detail-groups detail_source=no-cost urun_kodu=S001-DMY26211 recete_kodu=S001-DMY26211 group_count=3 row_count=26 scan_errors=0
time=2026-04-29T21:57:25.407+03:00 level=INFO msg="⬅️ GET /api/pricing/production-product-costing/production-types | status=200 | 556.5518ms" app=bssapp-backend
time=2026-04-29T21:57:25.409+03:00 level=INFO msg="✅ LOGGER CLAIMS user=mehmet.keçeci role=admin id=5" app=bssapp-backend
time=2026-04-29T21:57:25.409+03:00 level=INFO msg="[ProductionNoCostDetailGroups] done recete_kodu=S001-DMY26211 groups=3" app=bssapp-backend
time=2026-04-29T21:57:25.411+03:00 level=INFO msg="⬅️ GET /api/pricing/production-product-costing/has-cost-detail-groups | status=200 | 864.8427ms" app=bssapp-backend
time=2026-04-29T21:57:25.412+03:00 level=INFO msg="✅ LOGGER CLAIMS user=mehmet.keçeci role=admin id=5" app=bssapp-backend
time=2026-04-29T21:57:25.567+03:00 level=INFO msg="🧾 auditlog INSERT | actor_dfusr=5 actor_user=mehmet.keçeci role=admin nav /api/pricing/production-product-costing/production-types target=<nil>" app=bssapp-backend
time=2026-04-29T21:57:25.733+03:00 level=INFO msg="🧾 auditlog INSERT | actor_dfusr=5 actor_user=mehmet.keçeci role=admin nav /api/pricing/production-product-costing/has-cost-detail-groups target=<nil>" app=bssapp-backend
time=2026-04-29T21:57:26.592+03:00 level=INFO msg="🔐 GLOBAL AUTH user=5 role=admin" app=bssapp-backend
time=2026-04-29T21:57:26.593+03:00 level=INFO msg="➡️ GET /api/pricing/production-product-costing/has-cost-detail-exchange-rates | auth=true" app=bssapp-backend
time=2026-04-29T21:57:26.593+03:00 level=INFO msg="AUTH_MIDDLEWARE PASS user=5 role=admin method=GET path=/api/pricing/production-product-costing/has-cost-detail-exchange-rates" app=bssapp-backend
time=2026-04-29T21:57:26.758+03:00 level=INFO msg="🔐 PERM CHECK user=5 role=3 dept=[UST_YONETIM] order:view" app=bssapp-backend
time=2026-04-29T21:57:27.095+03:00 level=INFO msg=" ↳ ROLE+DEPT OVERRIDE = true" app=bssapp-backend
time=2026-04-29T21:57:27.097+03:00 level=INFO msg="request start" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.exchange-rates maliyet_tarihi=2026-04-29
time=2026-04-29T21:57:27.097+03:00 level=INFO msg="[ProductionHasCostDetailExchangeRates] start maliyet_tarihi=2026-04-29" app=bssapp-backend
time=2026-04-29T21:57:27.191+03:00 level=INFO msg="request done" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.exchange-rates rate_date=2026-04-29 usd_rate=45.0515 eur_rate=52.6761 gbp_rate=60.8954
time=2026-04-29T21:57:27.191+03:00 level=INFO msg="[ProductionHasCostDetailExchangeRates] done maliyet_tarihi=2026-04-29 rate_date=2026-04-29 usd=45.0515 eur=52.6761" app=bssapp-backend
time=2026-04-29T21:57:27.191+03:00 level=INFO msg="⬅️ GET /api/pricing/production-product-costing/has-cost-detail-exchange-rates | status=200 | 598.5886ms" app=bssapp-backend
time=2026-04-29T21:57:27.191+03:00 level=INFO msg="✅ LOGGER CLAIMS user=mehmet.keçeci role=admin id=5" app=bssapp-backend
time=2026-04-29T21:57:27.191+03:00 level=INFO msg="🧾 auditlog INSERT | actor_dfusr=5 actor_user=mehmet.keçeci role=admin nav /api/pricing/production-product-costing/has-cost-detail-exchange-rates target=<nil>" app=bssapp-backend
time=2026-04-29T21:58:04.959+03:00 level=INFO msg="🔐 GLOBAL AUTH user=5 role=admin" app=bssapp-backend
time=2026-04-29T21:58:04.961+03:00 level=INFO msg="➡️ GET /api/pricing/production-product-costing/has-cost-detail-header | auth=true" app=bssapp-backend
time=2026-04-29T21:58:04.961+03:00 level=INFO msg="🔐 GLOBAL AUTH user=5 role=admin" app=bssapp-backend
time=2026-04-29T21:58:04.961+03:00 level=INFO msg="➡️ GET /api/pricing/production-product-costing/has-cost-detail-groups | auth=true" app=bssapp-backend
time=2026-04-29T21:58:04.962+03:00 level=INFO msg="AUTH_MIDDLEWARE PASS user=5 role=admin method=GET path=/api/pricing/production-product-costing/has-cost-detail-header" app=bssapp-backend
time=2026-04-29T21:58:04.962+03:00 level=INFO msg="🔐 PERM CHECK user=5 role=3 dept=[UST_YONETIM] order:view" app=bssapp-backend
time=2026-04-29T21:58:04.963+03:00 level=INFO msg="AUTH_MIDDLEWARE PASS user=5 role=admin method=GET path=/api/pricing/production-product-costing/has-cost-detail-groups" app=bssapp-backend
time=2026-04-29T21:58:04.963+03:00 level=INFO msg="🔐 PERM CHECK user=5 role=3 dept=[UST_YONETIM] order:view" app=bssapp-backend
time=2026-04-29T21:58:05.269+03:00 level=INFO msg="🔐 GLOBAL AUTH user=5 role=admin" app=bssapp-backend
time=2026-04-29T21:58:05.270+03:00 level=INFO msg="➡️ GET /api/pricing/production-product-costing/production-types | auth=true" app=bssapp-backend
time=2026-04-29T21:58:05.271+03:00 level=INFO msg="AUTH_MIDDLEWARE PASS user=5 role=admin method=GET path=/api/pricing/production-product-costing/production-types" app=bssapp-backend
time=2026-04-29T21:58:05.271+03:00 level=INFO msg="🔐 PERM CHECK user=5 role=3 dept=[UST_YONETIM] order:view" app=bssapp-backend
time=2026-04-29T21:58:05.353+03:00 level=INFO msg=" ↳ ROLE+DEPT OVERRIDE = true" app=bssapp-backend
time=2026-04-29T21:58:05.355+03:00 level=INFO msg="request start" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.detail-groups detail_source=no-cost urun_kodu=S001-DMY26211 recete_kodu=S001-DMY26211
time=2026-04-29T21:58:05.355+03:00 level=INFO msg="query dispatch" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 query=production-product-costing.no-cost-detail-rows recete_kodu=S001-DMY26211 urun_kodu=S001-DMY26211
time=2026-04-29T21:58:05.394+03:00 level=INFO msg="request done" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.detail-groups detail_source=no-cost urun_kodu=S001-DMY26211 recete_kodu=S001-DMY26211 group_count=3 row_count=26 scan_errors=0
time=2026-04-29T21:58:05.396+03:00 level=INFO msg="[ProductionNoCostDetailGroups] done recete_kodu=S001-DMY26211 groups=3" app=bssapp-backend
time=2026-04-29T21:58:05.397+03:00 level=INFO msg="⬅️ GET /api/pricing/production-product-costing/has-cost-detail-groups | status=200 | 435.4404ms" app=bssapp-backend
time=2026-04-29T21:58:05.397+03:00 level=INFO msg="✅ LOGGER CLAIMS user=mehmet.keçeci role=admin id=5" app=bssapp-backend
time=2026-04-29T21:58:05.397+03:00 level=INFO msg="🧾 auditlog INSERT | actor_dfusr=5 actor_user=mehmet.keçeci role=admin nav /api/pricing/production-product-costing/has-cost-detail-groups target=<nil>" app=bssapp-backend
time=2026-04-29T21:58:05.432+03:00 level=INFO msg="🔐 GLOBAL AUTH user=5 role=admin" app=bssapp-backend
time=2026-04-29T21:58:05.433+03:00 level=INFO msg="➡️ GET /api/pricing/production-product-costing/has-cost-detail-groups | auth=true" app=bssapp-backend
time=2026-04-29T21:58:05.433+03:00 level=INFO msg="AUTH_MIDDLEWARE PASS user=5 role=admin method=GET path=/api/pricing/production-product-costing/has-cost-detail-groups" app=bssapp-backend
time=2026-04-29T21:58:05.433+03:00 level=INFO msg="🔐 PERM CHECK user=5 role=3 dept=[UST_YONETIM] order:view" app=bssapp-backend
time=2026-04-29T21:58:05.652+03:00 level=INFO msg=" ↳ ROLE+DEPT OVERRIDE = true" app=bssapp-backend
time=2026-04-29T21:58:05.653+03:00 level=INFO msg="request start" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.production-types
time=2026-04-29T21:58:05.680+03:00 level=INFO msg="request done" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.production-types row_count=4
time=2026-04-29T21:58:05.681+03:00 level=INFO msg="⬅️ GET /api/pricing/production-product-costing/production-types | status=200 | 410.1372ms" app=bssapp-backend
time=2026-04-29T21:58:05.681+03:00 level=INFO msg="✅ LOGGER CLAIMS user=mehmet.keçeci role=admin id=5" app=bssapp-backend
time=2026-04-29T21:58:05.681+03:00 level=INFO msg="🧾 auditlog INSERT | actor_dfusr=5 actor_user=mehmet.keçeci role=admin nav /api/pricing/production-product-costing/production-types target=<nil>" app=bssapp-backend
time=2026-04-29T21:58:05.690+03:00 level=INFO msg="🔐 GLOBAL AUTH user=5 role=admin" app=bssapp-backend
time=2026-04-29T21:58:05.690+03:00 level=INFO msg="➡️ GET /api/pricing/production-product-costing/production-types | auth=true" app=bssapp-backend
time=2026-04-29T21:58:05.691+03:00 level=INFO msg="AUTH_MIDDLEWARE PASS user=5 role=admin method=GET path=/api/pricing/production-product-costing/production-types" app=bssapp-backend
time=2026-04-29T21:58:05.691+03:00 level=INFO msg="🔐 PERM CHECK user=5 role=3 dept=[UST_YONETIM] order:view" app=bssapp-backend
time=2026-04-29T21:58:05.943+03:00 level=INFO msg=" ↳ ROLE+DEPT OVERRIDE = true" app=bssapp-backend
time=2026-04-29T21:58:05.944+03:00 level=INFO msg="request start" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.detail-header detail_source=no-cost urun_kodu=S001-DMY26211 recete_kodu=S001-DMY26211
time=2026-04-29T21:58:05.944+03:00 level=INFO msg="query dispatch" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 query=production-product-costing.no-cost-detail-header recete_kodu=S001-DMY26211 urun_kodu=S001-DMY26211
time=2026-04-29T21:58:06.066+03:00 level=INFO msg=" ↳ ROLE+DEPT OVERRIDE = true" app=bssapp-backend
time=2026-04-29T21:58:06.067+03:00 level=INFO msg="request start" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.production-types
time=2026-04-29T21:58:06.494+03:00 level=INFO msg="request done" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.detail-header detail_source=no-cost urun_kodu=S001-DMY26211 recete_kodu=S001-DMY26211 n_urt_recete_id=20815 urun_kodu=S001-DMY26211
time=2026-04-29T21:58:06.494+03:00 level=INFO msg="⬅️ GET /api/pricing/production-product-costing/has-cost-detail-header | status=200 | 1.5333479s" app=bssapp-backend
time=2026-04-29T21:58:06.495+03:00 level=INFO msg="✅ LOGGER CLAIMS user=mehmet.keçeci role=admin id=5" app=bssapp-backend
time=2026-04-29T21:58:06.495+03:00 level=INFO msg="🧾 auditlog INSERT | actor_dfusr=5 actor_user=mehmet.keçeci role=admin nav /api/pricing/production-product-costing/has-cost-detail-header target=<nil>" app=bssapp-backend
time=2026-04-29T21:58:06.545+03:00 level=INFO msg="request done" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.production-types row_count=4
time=2026-04-29T21:58:06.545+03:00 level=INFO msg="⬅️ GET /api/pricing/production-product-costing/production-types | status=200 | 855.4172ms" app=bssapp-backend
time=2026-04-29T21:58:06.546+03:00 level=INFO msg="✅ LOGGER CLAIMS user=mehmet.keçeci role=admin id=5" app=bssapp-backend
time=2026-04-29T21:58:06.667+03:00 level=INFO msg="🧾 auditlog INSERT | actor_dfusr=5 actor_user=mehmet.keçeci role=admin nav /api/pricing/production-product-costing/production-types target=<nil>" app=bssapp-backend
time=2026-04-29T21:58:06.751+03:00 level=INFO msg=" ↳ ROLE+DEPT OVERRIDE = true" app=bssapp-backend
time=2026-04-29T21:58:06.752+03:00 level=INFO msg="request start" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.detail-groups detail_source=no-cost urun_kodu=S001-DMY26211 recete_kodu=S001-DMY26211
time=2026-04-29T21:58:06.753+03:00 level=INFO msg="query dispatch" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 query=production-product-costing.no-cost-detail-rows recete_kodu=S001-DMY26211 urun_kodu=S001-DMY26211
time=2026-04-29T21:58:06.817+03:00 level=INFO msg="🔐 GLOBAL AUTH user=5 role=admin" app=bssapp-backend
time=2026-04-29T21:58:06.818+03:00 level=INFO msg="➡️ GET /api/pricing/production-product-costing/has-cost-detail-header | auth=true" app=bssapp-backend
time=2026-04-29T21:58:06.818+03:00 level=INFO msg="AUTH_MIDDLEWARE PASS user=5 role=admin method=GET path=/api/pricing/production-product-costing/has-cost-detail-header" app=bssapp-backend
time=2026-04-29T21:58:06.819+03:00 level=INFO msg="🔐 PERM CHECK user=5 role=3 dept=[UST_YONETIM] order:view" app=bssapp-backend
time=2026-04-29T21:58:07.050+03:00 level=INFO msg="request done" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.detail-groups detail_source=no-cost urun_kodu=S001-DMY26211 recete_kodu=S001-DMY26211 group_count=3 row_count=26 scan_errors=0
time=2026-04-29T21:58:07.050+03:00 level=INFO msg="[ProductionNoCostDetailGroups] done recete_kodu=S001-DMY26211 groups=3" app=bssapp-backend
time=2026-04-29T21:58:07.050+03:00 level=INFO msg="⬅️ GET /api/pricing/production-product-costing/has-cost-detail-groups | status=200 | 1.617361s" app=bssapp-backend
time=2026-04-29T21:58:07.051+03:00 level=INFO msg="✅ LOGGER CLAIMS user=mehmet.keçeci role=admin id=5" app=bssapp-backend
time=2026-04-29T21:58:07.051+03:00 level=INFO msg="🧾 auditlog INSERT | actor_dfusr=5 actor_user=mehmet.keçeci role=admin nav /api/pricing/production-product-costing/has-cost-detail-groups target=<nil>" app=bssapp-backend
time=2026-04-29T21:58:07.183+03:00 level=INFO msg=" ↳ ROLE+DEPT OVERRIDE = true" app=bssapp-backend
time=2026-04-29T21:58:07.183+03:00 level=INFO msg="request start" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.detail-header detail_source=no-cost urun_kodu=S001-DMY26211 recete_kodu=S001-DMY26211
time=2026-04-29T21:58:07.184+03:00 level=INFO msg="query dispatch" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 query=production-product-costing.no-cost-detail-header recete_kodu=S001-DMY26211 urun_kodu=S001-DMY26211
time=2026-04-29T21:58:07.213+03:00 level=INFO msg="request done" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.detail-header detail_source=no-cost urun_kodu=S001-DMY26211 recete_kodu=S001-DMY26211 n_urt_recete_id=20815 urun_kodu=S001-DMY26211
time=2026-04-29T21:58:07.214+03:00 level=INFO msg="⬅️ GET /api/pricing/production-product-costing/has-cost-detail-header | status=200 | 396.483ms" app=bssapp-backend
time=2026-04-29T21:58:07.214+03:00 level=INFO msg="✅ LOGGER CLAIMS user=mehmet.keçeci role=admin id=5" app=bssapp-backend
time=2026-04-29T21:58:07.242+03:00 level=INFO msg="🧾 auditlog INSERT | actor_dfusr=5 actor_user=mehmet.keçeci role=admin nav /api/pricing/production-product-costing/has-cost-detail-header target=<nil>" app=bssapp-backend
time=2026-04-29T21:58:08.489+03:00 level=INFO msg="🔐 GLOBAL AUTH user=5 role=admin" app=bssapp-backend
time=2026-04-29T21:58:08.491+03:00 level=INFO msg="➡️ GET /api/pricing/production-product-costing/has-cost-detail-exchange-rates | auth=true" app=bssapp-backend
time=2026-04-29T21:58:08.491+03:00 level=INFO msg="AUTH_MIDDLEWARE PASS user=5 role=admin method=GET path=/api/pricing/production-product-costing/has-cost-detail-exchange-rates" app=bssapp-backend
time=2026-04-29T21:58:08.492+03:00 level=INFO msg="🔐 PERM CHECK user=5 role=3 dept=[UST_YONETIM] order:view" app=bssapp-backend
time=2026-04-29T21:58:08.823+03:00 level=INFO msg=" ↳ ROLE+DEPT OVERRIDE = true" app=bssapp-backend
time=2026-04-29T21:58:08.823+03:00 level=INFO msg="request start" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.exchange-rates maliyet_tarihi=2026-04-29
time=2026-04-29T21:58:08.823+03:00 level=INFO msg="[ProductionHasCostDetailExchangeRates] start maliyet_tarihi=2026-04-29" app=bssapp-backend
time=2026-04-29T21:58:08.851+03:00 level=INFO msg="request done" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.exchange-rates rate_date=2026-04-29 usd_rate=45.0515 eur_rate=52.6761 gbp_rate=60.8954
time=2026-04-29T21:58:08.853+03:00 level=INFO msg="[ProductionHasCostDetailExchangeRates] done maliyet_tarihi=2026-04-29 rate_date=2026-04-29 usd=45.0515 eur=52.6761" app=bssapp-backend
time=2026-04-29T21:58:08.853+03:00 level=INFO msg="⬅️ GET /api/pricing/production-product-costing/has-cost-detail-exchange-rates | status=200 | 361.9866ms" app=bssapp-backend
time=2026-04-29T21:58:08.853+03:00 level=INFO msg="✅ LOGGER CLAIMS user=mehmet.keçeci role=admin id=5" app=bssapp-backend
time=2026-04-29T21:58:08.853+03:00 level=INFO msg="🧾 auditlog INSERT | actor_dfusr=5 actor_user=mehmet.keçeci role=admin nav /api/pricing/production-product-costing/has-cost-detail-exchange-rates target=<nil>" app=bssapp-backend
time=2026-04-29T21:58:55.938+03:00 level=INFO msg="🔐 GLOBAL AUTH user=5 role=admin" app=bssapp-backend
time=2026-04-29T21:58:55.939+03:00 level=INFO msg="➡️ GET /api/pricing/production-product-costing/has-cost-detail-header | auth=true" app=bssapp-backend
time=2026-04-29T21:58:55.939+03:00 level=INFO msg="AUTH_MIDDLEWARE PASS user=5 role=admin method=GET path=/api/pricing/production-product-costing/has-cost-detail-header" app=bssapp-backend
time=2026-04-29T21:58:55.939+03:00 level=INFO msg="🔐 PERM CHECK user=5 role=3 dept=[UST_YONETIM] order:view" app=bssapp-backend
time=2026-04-29T21:58:56.076+03:00 level=INFO msg="🔐 GLOBAL AUTH user=5 role=admin" app=bssapp-backend
time=2026-04-29T21:58:56.078+03:00 level=INFO msg="➡️ GET /api/pricing/production-product-costing/has-cost-detail-groups | auth=true" app=bssapp-backend
time=2026-04-29T21:58:56.079+03:00 level=INFO msg="AUTH_MIDDLEWARE PASS user=5 role=admin method=GET path=/api/pricing/production-product-costing/has-cost-detail-groups" app=bssapp-backend
time=2026-04-29T21:58:56.080+03:00 level=INFO msg="🔐 PERM CHECK user=5 role=3 dept=[UST_YONETIM] order:view" app=bssapp-backend
time=2026-04-29T21:58:56.251+03:00 level=INFO msg="🔐 GLOBAL AUTH user=5 role=admin" app=bssapp-backend
time=2026-04-29T21:58:56.252+03:00 level=INFO msg="➡️ GET /api/pricing/production-product-costing/production-types | auth=true" app=bssapp-backend
time=2026-04-29T21:58:56.252+03:00 level=INFO msg="AUTH_MIDDLEWARE PASS user=5 role=admin method=GET path=/api/pricing/production-product-costing/production-types" app=bssapp-backend
time=2026-04-29T21:58:56.252+03:00 level=INFO msg="🔐 PERM CHECK user=5 role=3 dept=[UST_YONETIM] order:view" app=bssapp-backend
time=2026-04-29T21:58:56.269+03:00 level=INFO msg=" ↳ ROLE+DEPT OVERRIDE = true" app=bssapp-backend
time=2026-04-29T21:58:56.270+03:00 level=INFO msg="request start" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.detail-header detail_source=no-cost urun_kodu=S001-DMY26211 recete_kodu=S001-DMY26211
time=2026-04-29T21:58:56.270+03:00 level=INFO msg="query dispatch" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 query=production-product-costing.no-cost-detail-header recete_kodu=S001-DMY26211 urun_kodu=S001-DMY26211
time=2026-04-29T21:58:56.297+03:00 level=INFO msg="request done" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.detail-header detail_source=no-cost urun_kodu=S001-DMY26211 recete_kodu=S001-DMY26211 n_urt_recete_id=20815 urun_kodu=S001-DMY26211
time=2026-04-29T21:58:56.299+03:00 level=INFO msg="⬅️ GET /api/pricing/production-product-costing/has-cost-detail-header | status=200 | 359.9756ms" app=bssapp-backend
time=2026-04-29T21:58:56.299+03:00 level=INFO msg="✅ LOGGER CLAIMS user=mehmet.keçeci role=admin id=5" app=bssapp-backend
time=2026-04-29T21:58:56.299+03:00 level=INFO msg="🧾 auditlog INSERT | actor_dfusr=5 actor_user=mehmet.keçeci role=admin nav /api/pricing/production-product-costing/has-cost-detail-header target=<nil>" app=bssapp-backend
time=2026-04-29T21:58:56.418+03:00 level=INFO msg=" ↳ ROLE+DEPT OVERRIDE = true" app=bssapp-backend
time=2026-04-29T21:58:56.419+03:00 level=INFO msg="request start" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.detail-groups detail_source=no-cost urun_kodu=S001-DMY26211 recete_kodu=S001-DMY26211
time=2026-04-29T21:58:56.419+03:00 level=INFO msg="query dispatch" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 query=production-product-costing.no-cost-detail-rows recete_kodu=S001-DMY26211 urun_kodu=S001-DMY26211
time=2026-04-29T21:58:56.462+03:00 level=INFO msg="request done" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.detail-groups detail_source=no-cost urun_kodu=S001-DMY26211 recete_kodu=S001-DMY26211 group_count=3 row_count=26 scan_errors=0
time=2026-04-29T21:58:56.463+03:00 level=INFO msg="[ProductionNoCostDetailGroups] done recete_kodu=S001-DMY26211 groups=3" app=bssapp-backend
time=2026-04-29T21:58:56.463+03:00 level=INFO msg="⬅️ GET /api/pricing/production-product-costing/has-cost-detail-groups | status=200 | 385.19ms" app=bssapp-backend
time=2026-04-29T21:58:56.463+03:00 level=INFO msg="✅ LOGGER CLAIMS user=mehmet.keçeci role=admin id=5" app=bssapp-backend
time=2026-04-29T21:58:56.467+03:00 level=INFO msg="🧾 auditlog INSERT | actor_dfusr=5 actor_user=mehmet.keçeci role=admin nav /api/pricing/production-product-costing/has-cost-detail-groups target=<nil>" app=bssapp-backend
time=2026-04-29T21:58:56.589+03:00 level=INFO msg=" ↳ ROLE+DEPT OVERRIDE = true" app=bssapp-backend
time=2026-04-29T21:58:56.590+03:00 level=INFO msg="request start" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.production-types
time=2026-04-29T21:58:56.598+03:00 level=INFO msg="request done" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.production-types row_count=4
time=2026-04-29T21:58:56.598+03:00 level=INFO msg="⬅️ GET /api/pricing/production-product-costing/production-types | status=200 | 346.1189ms" app=bssapp-backend
time=2026-04-29T21:58:56.598+03:00 level=INFO msg="✅ LOGGER CLAIMS user=mehmet.keçeci role=admin id=5" app=bssapp-backend
time=2026-04-29T21:58:56.635+03:00 level=INFO msg="🧾 auditlog INSERT | actor_dfusr=5 actor_user=mehmet.keçeci role=admin nav /api/pricing/production-product-costing/production-types target=<nil>" app=bssapp-backend
time=2026-04-29T21:58:57.550+03:00 level=INFO msg="🔐 GLOBAL AUTH user=5 role=admin" app=bssapp-backend
time=2026-04-29T21:58:57.551+03:00 level=INFO msg="➡️ GET /api/pricing/production-product-costing/has-cost-detail-exchange-rates | auth=true" app=bssapp-backend
time=2026-04-29T21:58:57.552+03:00 level=INFO msg="AUTH_MIDDLEWARE PASS user=5 role=admin method=GET path=/api/pricing/production-product-costing/has-cost-detail-exchange-rates" app=bssapp-backend
time=2026-04-29T21:58:57.553+03:00 level=INFO msg="🔐 PERM CHECK user=5 role=3 dept=[UST_YONETIM] order:view" app=bssapp-backend
time=2026-04-29T21:58:57.891+03:00 level=INFO msg=" ↳ ROLE+DEPT OVERRIDE = true" app=bssapp-backend
time=2026-04-29T21:58:57.891+03:00 level=INFO msg="request start" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.exchange-rates maliyet_tarihi=2026-04-29
time=2026-04-29T21:58:57.891+03:00 level=INFO msg="[ProductionHasCostDetailExchangeRates] start maliyet_tarihi=2026-04-29" app=bssapp-backend
time=2026-04-29T21:58:57.923+03:00 level=INFO msg="request done" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.exchange-rates rate_date=2026-04-29 usd_rate=45.0515 eur_rate=52.6761 gbp_rate=60.8954
time=2026-04-29T21:58:57.924+03:00 level=INFO msg="[ProductionHasCostDetailExchangeRates] done maliyet_tarihi=2026-04-29 rate_date=2026-04-29 usd=45.0515 eur=52.6761" app=bssapp-backend
time=2026-04-29T21:58:57.925+03:00 level=INFO msg="⬅️ GET /api/pricing/production-product-costing/has-cost-detail-exchange-rates | status=200 | 373.817ms" app=bssapp-backend
time=2026-04-29T21:58:57.925+03:00 level=INFO msg="✅ LOGGER CLAIMS user=mehmet.keçeci role=admin id=5" app=bssapp-backend
time=2026-04-29T21:58:57.925+03:00 level=INFO msg="🧾 auditlog INSERT | actor_dfusr=5 actor_user=mehmet.keçeci role=admin nav /api/pricing/production-product-costing/has-cost-detail-exchange-rates target=<nil>" app=bssapp-backend
time=2026-04-29T21:59:35.728+03:00 level=INFO msg="🔐 GLOBAL AUTH user=5 role=admin" app=bssapp-backend
time=2026-04-29T21:59:35.729+03:00 level=INFO msg="➡️ GET /api/pricing/production-product-costing/has-cost-detail-header | auth=true" app=bssapp-backend
time=2026-04-29T21:59:35.729+03:00 level=INFO msg="🔐 GLOBAL AUTH user=5 role=admin" app=bssapp-backend
time=2026-04-29T21:59:35.730+03:00 level=INFO msg="➡️ GET /api/pricing/production-product-costing/has-cost-detail-groups | auth=true" app=bssapp-backend
time=2026-04-29T21:59:35.730+03:00 level=INFO msg="AUTH_MIDDLEWARE PASS user=5 role=admin method=GET path=/api/pricing/production-product-costing/has-cost-detail-groups" app=bssapp-backend
time=2026-04-29T21:59:35.730+03:00 level=INFO msg="🔐 PERM CHECK user=5 role=3 dept=[UST_YONETIM] order:view" app=bssapp-backend
time=2026-04-29T21:59:35.731+03:00 level=INFO msg="AUTH_MIDDLEWARE PASS user=5 role=admin method=GET path=/api/pricing/production-product-costing/has-cost-detail-header" app=bssapp-backend
time=2026-04-29T21:59:35.731+03:00 level=INFO msg="🔐 PERM CHECK user=5 role=3 dept=[UST_YONETIM] order:view" app=bssapp-backend
time=2026-04-29T21:59:35.820+03:00 level=INFO msg="🔐 GLOBAL AUTH user=5 role=admin" app=bssapp-backend
time=2026-04-29T21:59:35.820+03:00 level=INFO msg="➡️ GET /api/pricing/production-product-costing/production-types | auth=true" app=bssapp-backend
time=2026-04-29T21:59:35.821+03:00 level=INFO msg="AUTH_MIDDLEWARE PASS user=5 role=admin method=GET path=/api/pricing/production-product-costing/production-types" app=bssapp-backend
time=2026-04-29T21:59:35.821+03:00 level=INFO msg="🔐 PERM CHECK user=5 role=3 dept=[UST_YONETIM] order:view" app=bssapp-backend
time=2026-04-29T21:59:36.078+03:00 level=INFO msg=" ↳ ROLE+DEPT OVERRIDE = true" app=bssapp-backend
time=2026-04-29T21:59:36.079+03:00 level=INFO msg="request start" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.detail-header detail_source=no-cost urun_kodu=S001-DMY26211 recete_kodu=S001-DMY26211
time=2026-04-29T21:59:36.079+03:00 level=INFO msg="query dispatch" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 query=production-product-costing.no-cost-detail-header recete_kodu=S001-DMY26211 urun_kodu=S001-DMY26211
time=2026-04-29T21:59:36.079+03:00 level=INFO msg=" ↳ ROLE+DEPT OVERRIDE = true" app=bssapp-backend
time=2026-04-29T21:59:36.079+03:00 level=INFO msg="request start" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.detail-groups detail_source=no-cost urun_kodu=S001-DMY26211 recete_kodu=S001-DMY26211
time=2026-04-29T21:59:36.079+03:00 level=INFO msg="query dispatch" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 query=production-product-costing.no-cost-detail-rows recete_kodu=S001-DMY26211 urun_kodu=S001-DMY26211
time=2026-04-29T21:59:36.108+03:00 level=INFO msg="request done" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.detail-header detail_source=no-cost urun_kodu=S001-DMY26211 recete_kodu=S001-DMY26211 n_urt_recete_id=20815 urun_kodu=S001-DMY26211
time=2026-04-29T21:59:36.108+03:00 level=INFO msg="⬅️ GET /api/pricing/production-product-costing/has-cost-detail-header | status=200 | 379.6458ms" app=bssapp-backend
time=2026-04-29T21:59:36.108+03:00 level=INFO msg="✅ LOGGER CLAIMS user=mehmet.keçeci role=admin id=5" app=bssapp-backend
time=2026-04-29T21:59:36.108+03:00 level=INFO msg="🧾 auditlog INSERT | actor_dfusr=5 actor_user=mehmet.keçeci role=admin nav /api/pricing/production-product-costing/has-cost-detail-header target=<nil>" app=bssapp-backend
time=2026-04-29T21:59:36.161+03:00 level=INFO msg=" ↳ ROLE+DEPT OVERRIDE = true" app=bssapp-backend
time=2026-04-29T21:59:36.161+03:00 level=INFO msg="request start" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.production-types
time=2026-04-29T21:59:36.168+03:00 level=INFO msg="request done" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.production-types row_count=4
time=2026-04-29T21:59:36.168+03:00 level=INFO msg="⬅️ GET /api/pricing/production-product-costing/production-types | status=200 | 347.978ms" app=bssapp-backend
time=2026-04-29T21:59:36.169+03:00 level=INFO msg="✅ LOGGER CLAIMS user=mehmet.keçeci role=admin id=5" app=bssapp-backend
time=2026-04-29T21:59:36.172+03:00 level=INFO msg="request done" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.detail-groups detail_source=no-cost urun_kodu=S001-DMY26211 recete_kodu=S001-DMY26211 group_count=3 row_count=26 scan_errors=0
time=2026-04-29T21:59:36.173+03:00 level=INFO msg="[ProductionNoCostDetailGroups] done recete_kodu=S001-DMY26211 groups=3" app=bssapp-backend
time=2026-04-29T21:59:36.173+03:00 level=INFO msg="⬅️ GET /api/pricing/production-product-costing/has-cost-detail-groups | status=200 | 443.1026ms" app=bssapp-backend
time=2026-04-29T21:59:36.174+03:00 level=INFO msg="✅ LOGGER CLAIMS user=mehmet.keçeci role=admin id=5" app=bssapp-backend
time=2026-04-29T21:59:36.276+03:00 level=INFO msg="🧾 auditlog INSERT | actor_dfusr=5 actor_user=mehmet.keçeci role=admin nav /api/pricing/production-product-costing/production-types target=<nil>" app=bssapp-backend
time=2026-04-29T21:59:36.441+03:00 level=INFO msg="🧾 auditlog INSERT | actor_dfusr=5 actor_user=mehmet.keçeci role=admin nav /api/pricing/production-product-costing/has-cost-detail-groups target=<nil>" app=bssapp-backend
time=2026-04-29T21:59:37.181+03:00 level=INFO msg="🔐 GLOBAL AUTH user=5 role=admin" app=bssapp-backend
time=2026-04-29T21:59:37.182+03:00 level=INFO msg="➡️ GET /api/pricing/production-product-costing/has-cost-detail-exchange-rates | auth=true" app=bssapp-backend
time=2026-04-29T21:59:37.183+03:00 level=INFO msg="AUTH_MIDDLEWARE PASS user=5 role=admin method=GET path=/api/pricing/production-product-costing/has-cost-detail-exchange-rates" app=bssapp-backend
time=2026-04-29T21:59:37.184+03:00 level=INFO msg="🔐 PERM CHECK user=5 role=3 dept=[UST_YONETIM] order:view" app=bssapp-backend
time=2026-04-29T21:59:37.526+03:00 level=INFO msg=" ↳ ROLE+DEPT OVERRIDE = true" app=bssapp-backend
time=2026-04-29T21:59:37.527+03:00 level=INFO msg="request start" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.exchange-rates maliyet_tarihi=2026-04-29
time=2026-04-29T21:59:37.527+03:00 level=INFO msg="[ProductionHasCostDetailExchangeRates] start maliyet_tarihi=2026-04-29" app=bssapp-backend
time=2026-04-29T21:59:37.566+03:00 level=INFO msg="request done" app=bssapp-backend trace_id=pcd-no-cost-4d8aee0f-e28c-4f5a-b758-1aea1d646a62 handler=production-product-costing.exchange-rates rate_date=2026-04-29 usd_rate=45.0515 eur_rate=52.6761 gbp_rate=60.8954
time=2026-04-29T21:59:37.566+03:00 level=INFO msg="[ProductionHasCostDetailExchangeRates] done maliyet_tarihi=2026-04-29 rate_date=2026-04-29 usd=45.0515 eur=52.6761" app=bssapp-backend
time=2026-04-29T21:59:37.567+03:00 level=INFO msg="⬅️ GET /api/pricing/production-product-costing/has-cost-detail-exchange-rates | status=200 | 384.597ms" app=bssapp-backend
time=2026-04-29T21:59:37.567+03:00 level=INFO msg="✅ LOGGER CLAIMS user=mehmet.keçeci role=admin id=5" app=bssapp-backend
time=2026-04-29T21:59:37.568+03:00 level=INFO msg="🧾 auditlog INSERT | actor_dfusr=5 actor_user=mehmet.keçeci role=admin nav /api/pricing/production-product-costing/has-cost-detail-exchange-rates target=<nil>" app=bssapp-backend

View File

@@ -13,6 +13,7 @@ import (
) )
var MssqlDB *sql.DB var MssqlDB *sql.DB
var UretimDB *sql.DB
func envInt(name string, fallback int) int { func envInt(name string, fallback int) int {
raw := strings.TrimSpace(os.Getenv(name)) raw := strings.TrimSpace(os.Getenv(name))
@@ -123,3 +124,37 @@ func ConnectMSSQL() error {
func GetDB() *sql.DB { func GetDB() *sql.DB {
return MssqlDB return MssqlDB
} }
// ConnectMSSQLUretim initializes the URETIM MSSQL connection from environment.
func ConnectMSSQLUretim() error {
connString := strings.TrimSpace(os.Getenv("URETIM_MSSQL_CONN"))
if connString == "" {
return fmt.Errorf("URETIM_MSSQL_CONN tanimli degil")
}
connectionTimeoutSec := envInt("URETIM_MSSQL_CONNECTION_TIMEOUT_SEC", envInt("MSSQL_CONNECTION_TIMEOUT_SEC", 120))
dialTimeoutSec := envInt("URETIM_MSSQL_DIAL_TIMEOUT_SEC", connectionTimeoutSec)
connString = ensureMSSQLTimeouts(connString, connectionTimeoutSec, dialTimeoutSec)
var err error
UretimDB, err = sql.Open("sqlserver", connString)
if err != nil {
return fmt.Errorf("URETIM MSSQL baglanti hatasi: %w", err)
}
UretimDB.SetMaxOpenConns(envInt("URETIM_MSSQL_MAX_OPEN_CONNS", envInt("MSSQL_MAX_OPEN_CONNS", 40)))
UretimDB.SetMaxIdleConns(envInt("URETIM_MSSQL_MAX_IDLE_CONNS", envInt("MSSQL_MAX_IDLE_CONNS", 40)))
UretimDB.SetConnMaxLifetime(time.Duration(envInt("URETIM_MSSQL_CONN_MAX_LIFETIME_MIN", envInt("MSSQL_CONN_MAX_LIFETIME_MIN", 30))) * time.Minute)
UretimDB.SetConnMaxIdleTime(time.Duration(envInt("URETIM_MSSQL_CONN_MAX_IDLE_MIN", envInt("MSSQL_CONN_MAX_IDLE_MIN", 10))) * time.Minute)
if err = UretimDB.Ping(); err != nil {
return fmt.Errorf("URETIM MSSQL erisilemiyor: %w", err)
}
fmt.Printf("URETIM MSSQL baglantisi basarili (connection timeout=%ds, dial timeout=%ds)\n", connectionTimeoutSec, dialTimeoutSec)
return nil
}
func GetUretimDB() *sql.DB {
return UretimDB
}

View File

@@ -8,8 +8,10 @@ import (
"bssapp-backend/permissions" "bssapp-backend/permissions"
"bssapp-backend/repository" "bssapp-backend/repository"
"bssapp-backend/routes" "bssapp-backend/routes"
"bssapp-backend/utils"
"database/sql" "database/sql"
"log" "log"
"log/slog"
"net/http" "net/http"
"os" "os"
"path" "path"
@@ -738,6 +740,101 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router
"order", "view", "order", "view",
wrapV3(http.HandlerFunc(routes.GetProductPricingListHandler)), wrapV3(http.HandlerFunc(routes.GetProductPricingListHandler)),
) )
bindV3(r, pgDB,
"/api/pricing/production-product-costing/no-cost-products", "GET",
"order", "view",
wrapV3(http.HandlerFunc(routes.GetProductionNoCostProductsHandler)),
)
bindV3(r, pgDB,
"/api/pricing/production-product-costing/has-cost-products", "GET",
"order", "view",
wrapV3(http.HandlerFunc(routes.GetProductionHasCostProductsHandler)),
)
bindV3(r, pgDB,
"/api/pricing/production-product-costing/has-cost-history", "GET",
"order", "view",
wrapV3(http.HandlerFunc(routes.GetProductionHasCostHistoryHandler)),
)
bindV3(r, pgDB,
"/api/pricing/production-product-costing/has-cost-detail-groups", "GET",
"order", "view",
wrapV3(http.HandlerFunc(routes.GetProductionHasCostDetailGroupsHandler)),
)
bindV3(r, pgDB,
"/api/pricing/production-product-costing/has-cost-detail-header", "GET",
"order", "view",
wrapV3(http.HandlerFunc(routes.GetProductionHasCostDetailHeaderHandler)),
)
bindV3(r, pgDB,
"/api/pricing/production-product-costing/production-types", "GET",
"order", "view",
wrapV3(http.HandlerFunc(routes.GetProductionTypesHandler)),
)
bindV3(r, pgDB,
"/api/pricing/production-product-costing/detail-editor-options", "GET",
"order", "view",
wrapV3(http.HandlerFunc(routes.GetProductionHasCostDetailEditorOptionsHandler)),
)
bindV3(r, pgDB,
"/api/pricing/production-product-costing/has-cost-detail-exchange-rates", "GET",
"order", "view",
wrapV3(http.HandlerFunc(routes.GetProductionHasCostDetailExchangeRatesHandler)),
)
bindV3(r, pgDB,
"/api/pricing/production-product-costing/has-cost-detail-line-history", "GET",
"order", "view",
wrapV3(http.HandlerFunc(routes.GetProductionHasCostDetailLineHistoryHandler)),
)
bindV3(r, pgDB,
"/api/pricing/production-product-costing/has-cost-detail-similar-history", "GET",
"order", "view",
wrapV3(http.HandlerFunc(routes.GetProductionHasCostDetailSimilarHistoryHandler)),
)
bindV3(r, pgDB,
"/api/pricing/production-product-costing/has-cost-detail-bulk-prices", "POST",
"order", "view",
wrapV3(http.HandlerFunc(routes.PostProductionHasCostDetailBulkPricesHandler)),
)
bindV3(r, pgDB,
"/api/pricing/production-product-costing/options/urun-ana-grup", "GET",
"order", "view",
wrapV3(http.HandlerFunc(routes.GetProductionProductCostingUrunAnaGrupOptionsHandler)),
)
bindV3(r, pgDB,
"/api/pricing/production-product-costing/options/urun-alt-grup", "GET",
"order", "view",
wrapV3(http.HandlerFunc(routes.GetProductionProductCostingUrunAltGrupOptionsHandler)),
)
bindV3(r, pgDB,
"/api/pricing/production-product-costing/options/urun-ana-alt-combos", "GET",
"order", "view",
wrapV3(http.HandlerFunc(routes.GetProductionProductCostingUrunAnaAltCombosHandler)),
)
bindV3(r, pgDB,
"/api/pricing/production-product-costing/options/mtbolum", "GET",
"order", "view",
wrapV3(http.HandlerFunc(routes.GetProductionProductCostingMTBolumOptionsHandler)),
)
bindV3(r, pgDB,
"/api/pricing/production-product-costing/maliyet-parca-eslestirme", "GET",
"order", "view",
wrapV3(http.HandlerFunc(routes.GetProductionProductCostingParcaMappingsHandler)),
)
bindV3(r, pgDB,
"/api/pricing/production-product-costing/maliyet-parca-eslestirme", "DELETE",
"order", "view",
wrapV3(http.HandlerFunc(routes.DeleteProductionProductCostingParcaMappingHandler)),
)
bindV3(r, pgDB,
"/api/pricing/production-product-costing/maliyet-parca-eslestirme/upsert", "POST",
"order", "view",
wrapV3(http.HandlerFunc(routes.PostProductionProductCostingParcaMappingUpsertHandler)),
)
bindV3(r, pgDB,
"/api/pricing/production-product-costing/maliyet-parca-eslestirme/set-active", "POST",
"order", "view",
wrapV3(http.HandlerFunc(routes.PostProductionProductCostingParcaMappingSetActiveHandler)),
)
// ============================================================ // ============================================================
// ROLE MANAGEMENT // ROLE MANAGEMENT
@@ -797,6 +894,8 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router
} }
func main() { func main() {
utils.InitSlog()
slog.Info("backend start", "scope", "main")
log.Println("🔥🔥🔥 BSSAPP BACKEND STARTED — LOGIN ROUTE SHOULD EXIST 🔥🔥🔥") log.Println("🔥🔥🔥 BSSAPP BACKEND STARTED — LOGIN ROUTE SHOULD EXIST 🔥🔥🔥")
// ------------------------------------------------------- // -------------------------------------------------------
@@ -825,6 +924,13 @@ func main() {
log.Fatal(err3) log.Fatal(err3)
} }
} }
if err := db.ConnectMSSQLUretim(); err != nil {
if strings.Contains(err.Error(), "URETIM_MSSQL_CONN tanimli degil") {
log.Println("⚠️ URETIM DB baglantisi atlandi: URETIM_MSSQL_CONN tanimli degil")
} else {
log.Fatal(err)
}
}
pgDB, err := db.ConnectPostgres() pgDB, err := db.ConnectPostgres()
if err != nil { if err != nil {

View File

@@ -0,0 +1,282 @@
package models
type ProductionNoCostProductRow struct {
UretimSekli string `json:"UretimSekli"`
UrtSiparisNo string `json:"nUrtSiparisNo"`
IslemTarihi string `json:"dteIslemTarihi"`
FirmaKodu string `json:"FirmaKodu"`
FirmaAdi string `json:"FirmaAdi"`
SonIsEmriVeren string `json:"SonIsEmriVeren"`
ModelAdi string `json:"sAdi"`
Kodu string `json:"sKodu"`
SKullaniciAdi string `json:"sKullaniciAdi"`
SKullaniciGunc string `json:"sKullaniciAdiGunc"`
MMiktarG float64 `json:"lMMiktar_G"`
MModelKodu string `json:"sMModelKodu"`
}
type ProductionHasCostProductRow struct {
UretimSekli string `json:"UretimSekli"`
NOnMLNo string `json:"nOnMLNo"`
UrunKodu string `json:"UrunKodu"`
UrunAdi string `json:"UrunAdi"`
Tarihi string `json:"Tarihi"`
DteKayitTarihi string `json:"dteKayitTarihi"`
SKullaniciAdi string `json:"sKullaniciAdi"`
LTutarTL float64 `json:"lTutarTL"`
LTutarUSD float64 `json:"lTutarUSD"`
LTutarEURO float64 `json:"lTutarEURO"`
DteGuncellemeTarihi string `json:"dteGuncellemeTarihi"`
SGuncellemeKullaniciAdi string `json:"sGuncellemeKullaniciAdi"`
NUrtReceteID string `json:"nUrtReceteID"`
SAciklama string `json:"sAciklama"`
SonSiparisTarihi string `json:"SonSiparisTarihi"`
MaliyetDurumu string `json:"MaliyetDurumu"`
}
type ProductionHasCostHistoryRow struct {
NOnMLNo string `json:"nOnMLNo"`
UrunKodu string `json:"UrunKodu"`
UrunAdi string `json:"UrunAdi"`
Tarihi string `json:"Tarihi"`
SKullaniciAdi string `json:"sKullaniciAdi"`
LTutarUSD float64 `json:"lTutarUSD"`
LTutarTL float64 `json:"lTutarTL"`
LTutarEURO float64 `json:"lTutarEURO"`
LTutarGBP float64 `json:"lTutarGBP"`
SDovizCinsi string `json:"sDovizCinsi"`
LTutarDoviz float64 `json:"lTutarDoviz"`
DteGuncellemeTarihi string `json:"dteGuncellemeTarihi"`
SGuncellemeKullaniciAdi string `json:"sGuncellemeKullaniciAdi"`
NUrtReceteID string `json:"nUrtReceteID"`
SAciklama string `json:"sAciklama"`
}
type ProductionType struct {
ID string `json:"id"`
Aciklama string `json:"aciklama"`
}
type ProductionHasCostDetailGroupItem struct {
NOnMLNo string `json:"nOnMLNo"`
NOnMLDetNo string `json:"nOnMLDetNo"`
NHammaddeTuruNo string `json:"nHammaddeTuruNo"`
SKodu string `json:"sKodu"`
SAciklama string `json:"sAciklama"`
SRenk string `json:"sRenk"`
SBeden string `json:"sBeden"`
SAciklama2 string `json:"sAciklama2"`
LMiktar float64 `json:"lMiktar"`
LFiyat float64 `json:"lFiyat"`
LTutar float64 `json:"lTutar"`
SFiyatTipi string `json:"sFiyatTipi"`
SDovizCinsi string `json:"sDovizCinsi"`
LDovizKuru float64 `json:"lDovizKuru"`
LDovizFiyati float64 `json:"lDovizFiyati"`
FiyatGirilen *float64 `json:"fiyat_girilen"`
FiyatDoviz string `json:"fiyat_doviz"`
MaliyeteDahil bool `json:"maliyete_dahil"`
CMPriceTypeID *int `json:"cm_price_type_id"`
USDTutar float64 `json:"usdTutar"`
EURTutar float64 `json:"eurTutar"`
GBPTutar float64 `json:"gbpTutar"`
SBirim string `json:"sBirim"`
SHammaddeTuruAdi string `json:"sHammaddeTuruAdi"`
SParcaAdi string `json:"sParcaAdi"`
}
type ProductionHasCostDetailGroup struct {
SAciklama3 string `json:"sAciklama3"`
TotalTutar float64 `json:"totalTutar"`
TotalUSDTutar float64 `json:"totalUSDTutar"`
Items []ProductionHasCostDetailGroupItem `json:"items"`
}
type ProductionHasCostDetailHeader struct {
UretimiYapanFirma string `json:"UretimiYapanFirma"`
SonIsEmriVeren string `json:"SonIsEmriVeren"`
NOnMLNo string `json:"nOnMLNo"`
UrunKodu string `json:"UrunKodu"`
UrunAdi string `json:"UrunAdi"`
UrunAnaGrubu string `json:"UrunAnaGrubu"`
UrunAltGrubu string `json:"UrunAltGrubu"`
UretimSekliID string `json:"UretimSekliID"`
UretimSekli string `json:"UretimSekli"`
DteKayitTarihi string `json:"dteKayitTarihi"`
SKullaniciAdi string `json:"sKullaniciAdi"`
LTutarTL float64 `json:"lTutarTL"`
LTutarUSD float64 `json:"lTutarUSD"`
LTutarEURO float64 `json:"lTutarEURO"`
LTutarGBP float64 `json:"lTutarGBP"`
SDovizCinsi string `json:"sDovizCinsi"`
LTutarDoviz float64 `json:"lTutarDoviz"`
DteGuncellemeTarihi string `json:"dteGuncellemeTarihi"`
SGuncellemeKullaniciAdi string `json:"sGuncellemeKullaniciAdi"`
NUrtReceteID string `json:"nUrtReceteID"`
}
type ProductionHasCostDetailExchangeRates struct {
RateDate string `json:"rateDate"`
TRYRate float64 `json:"tryRate"`
USDRate float64 `json:"usdRate"`
EURRate float64 `json:"eurRate"`
GBPRate float64 `json:"gbpRate"`
}
type ProductionHasCostDetailEditorOption struct {
Kind string `json:"kind"`
Value string `json:"value"`
Label string `json:"label"`
NStokID string `json:"nStokID"`
NHammaddeTuruNo string `json:"nHammaddeTuruNo"`
SHammaddeTuruAdi string `json:"sHammaddeTuruAdi"`
SAciklama3 string `json:"sAciklama3"`
SKodu string `json:"sKodu"`
SAciklama string `json:"sAciklama"`
SModel string `json:"sModel"`
SBirim string `json:"sBirim"`
ColorCode string `json:"colorCode"`
ColorDescription string `json:"colorDescription"`
SParcaAdi string `json:"sParcaAdi"`
}
type ProductionHasCostDetailPriceLookupItem struct {
RowKey string `json:"__rowKey"`
NOnMLNo string `json:"n_onml_no"`
NOnMLDetNo string `json:"n_onml_det_no"`
NHammaddeTuruNo string `json:"n_hammadde_turu_no"`
SKodu string `json:"s_kodu"`
SAciklama string `json:"s_aciklama"`
SRenk string `json:"s_renk"`
ColorCode string `json:"color_code"`
ColorDescription string `json:"color_description"`
ItemDim1Code string `json:"item_dim1_code"`
ItemDim1Description string `json:"item_dim1_description"`
SBirim string `json:"s_birim"`
LMiktar float64 `json:"l_miktar"`
FiyatGirilen float64 `json:"fiyat_girilen"`
FiyatDoviz string `json:"fiyat_doviz"`
MaliyeteDahil int `json:"maliyete_dahil"`
CMPriceTypeID *int `json:"cm_price_type_id"`
}
type ProductionHasCostDetailBulkPriceRequest struct {
NOnMLNo string `json:"n_onml_no"`
UrunKodu string `json:"urun_kodu"`
NUrtReceteID string `json:"n_urt_recete_id"`
MaliyetTarihi string `json:"maliyet_tarihi"`
Items []ProductionHasCostDetailPriceLookupItem `json:"items"`
}
type ProductionHasCostDetailBulkPriceRow struct {
RowKey string `json:"__rowKey"`
NOnMLDetNo string `json:"nOnMLDetNo"`
NHammaddeTuruNo string `json:"nHammaddeTuruNo"`
SKodu string `json:"sKodu"`
ColorCode string `json:"ColorCode"`
ColorDescription string `json:"ColorDescription"`
ItemDim1Code string `json:"ItemDim1Code"`
ItemDim1Description string `json:"ItemDim1Description"`
FiyatGirilen float64 `json:"fiyat_girilen"`
FiyatDoviz string `json:"fiyat_doviz"`
PriceType string `json:"priceType"`
Tarih string `json:"Tarih"`
FaturaKodu string `json:"FaturaKodu"`
MasrafKodu string `json:"MasrafKodu"`
MasrafDetay string `json:"MasrafDetay"`
}
type ProductionHasCostDetailPurchaseHistoryRow struct {
SourceType string `json:"sourceType"`
Tarih string `json:"Tarih"`
FaturaKodu string `json:"FaturaKodu"`
FirmaKodu string `json:"FirmaKodu"`
FirmaAciklama string `json:"FirmaAciklama"`
MasrafKodu string `json:"MasrafKodu"`
MasrafDetay string `json:"MasrafDetay"`
ColorCode string `json:"ColorCode"`
ColorDescription string `json:"ColorDescription"`
ItemDim1Code string `json:"ItemDim1Code"`
ItemDim1Description string `json:"ItemDim1Description"`
Miktar float64 `json:"Miktar"`
BIRIM string `json:"BIRIM"`
EvrakFiyat float64 `json:"EvrakFiyat"`
EvrakTutar float64 `json:"EvrakTutar"`
EvrakDoviz string `json:"EvrakDoviz"`
PriceType string `json:"priceType"`
}
type ProductionHasCostDetailRecipeHistoryRow struct {
SourceType string `json:"sourceType"`
DteIslemTarihi string `json:"dteIslemTarihi"`
NOnMLNo string `json:"nOnMLNo"`
FirmaKodu string `json:"FirmaKodu"`
FirmaAciklama string `json:"FirmaAciklama"`
SKodu string `json:"sKodu"`
SAciklama string `json:"sAciklama"`
SRenk string `json:"sRenk"`
LMiktar float64 `json:"lMiktar"`
SBirim string `json:"sBirim"`
LDovizFiyati float64 `json:"lDovizFiyati"`
LDovizTutari float64 `json:"lDovizTutari"`
USD string `json:"USD"`
PriceType string `json:"priceType"`
DUMMY string `json:"DUMMY"`
}
type ProductionHasCostDetailLineHistoryResponse struct {
PurchaseRows []ProductionHasCostDetailPurchaseHistoryRow `json:"purchaseRows"`
RecipeRows []ProductionHasCostDetailRecipeHistoryRow `json:"recipeRows"`
}
type ProductionProductCostingMTBolumMappingRow struct {
ID int `json:"id"`
UrunAnaGrubu string `json:"urunAnaGrubu"`
UrunAltGrubu string `json:"urunAltGrubu"`
NUrtMTBolumID int `json:"nUrtMTBolumID"`
MTBolumAdi string `json:"mtBolumAdi"`
BAktif bool `json:"bAktif"`
DteIslem string `json:"dteIslemTarihi"`
SKullaniciAdi string `json:"sKullaniciAdi"`
}
type ProductionProductCostingMTBolumMappingUpsertRequest struct {
UrunAnaGrubu string `json:"urunAnaGrubu"`
UrunAltGrubu string `json:"urunAltGrubu"`
NUrtMTBolumID int `json:"nUrtMTBolumID"`
BAktif bool `json:"bAktif"`
}
// NEW: Maliyet Parca Eslestirme (Ana/Alt Grup + MTBolum + cok secmeli HammaddeTurleri)
type ProductionProductCostingParcaMappingRow struct {
ID int `json:"id"`
UrunIlkGrubu string `json:"urunIlkGrubu"`
UrunAnaGrubu string `json:"urunAnaGrubu"`
UrunAltGrubu string `json:"urunAltGrubu"`
NUrtMTBolumID int `json:"nUrtMTBolumID"`
ParcaBolumAdi string `json:"parcaBolumAdi"`
NHammaddeTurleri []string `json:"nHammaddeTurleri"`
BAktif bool `json:"bAktif"`
DteIslem string `json:"dteIslemTarihi"`
SKullaniciAdi string `json:"sKullaniciAdi"`
}
type ProductionProductCostingParcaMappingUpsertRequest struct {
UrunIlkGrubu string `json:"urunIlkGrubu"`
UrunAnaGrubu string `json:"urunAnaGrubu"`
UrunAltGrubu string `json:"urunAltGrubu"`
NUrtMTBolumID int `json:"nUrtMTBolumID"`
NHammaddeTurleri []int `json:"nHammaddeTurleri"`
BAktif bool `json:"bAktif"`
}
type ProductionProductCostingLookupOption struct {
Value string `json:"value"`
Label string `json:"label"`
}
type ProductionProductCostingAnaAltComboRow struct {
UrunIlkGrubu string `json:"urunIlkGrubu"`
UrunAnaGrubu string `json:"urunAnaGrubu"`
UrunAltGrubu string `json:"urunAltGrubu"`
}

View File

@@ -121,7 +121,14 @@ LEFT JOIN (
-- Paketlenen (OrderLine IsClosed=1) belge PB toplam -- Paketlenen (OrderLine IsClosed=1) belge PB toplam
SUM( SUM(
CASE CASE
WHEN ISNULL(l.IsClosed,0) = 1 WHEN (
ISNULL(l.IsClosed,0) = 1
OR EXISTS (
SELECT 1
FROM dbo.trInvoiceLine il WITH (NOLOCK)
WHERE il.OrderLineID = l.OrderLineID
)
)
AND c.CurrencyCode = h.DocCurrencyCode AND c.CurrencyCode = h.DocCurrencyCode
THEN c.NetAmount THEN c.NetAmount
ELSE 0 ELSE 0
@@ -131,7 +138,14 @@ LEFT JOIN (
-- Paketlenen TRY toplam -- Paketlenen TRY toplam
SUM( SUM(
CASE CASE
WHEN ISNULL(l.IsClosed,0) = 1 WHEN (
ISNULL(l.IsClosed,0) = 1
OR EXISTS (
SELECT 1
FROM dbo.trInvoiceLine il WITH (NOLOCK)
WHERE il.OrderLineID = l.OrderLineID
)
)
AND c.CurrencyCode = 'TRY' AND c.CurrencyCode = 'TRY'
THEN c.NetAmount THEN c.NetAmount
ELSE 0 ELSE 0

View File

@@ -216,7 +216,16 @@ SELECT
L.DeliveryDate, L.DeliveryDate,
L.PlannedDateOfLading, L.PlannedDateOfLading,
L.LineDescription, L.LineDescription,
L.IsClosed, CASE
WHEN ISNULL(L.IsClosed, 0) = 1
OR EXISTS (
SELECT 1
FROM BAGGI_V3.dbo.trInvoiceLine il WITH (NOLOCK)
WHERE il.OrderLineID = L.OrderLineID
)
THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END AS IsClosed,
L.CreatedUserName, L.CreatedUserName,
L.CreatedDate, L.CreatedDate,
L.LastUpdatedUserName, L.LastUpdatedUserName,

View File

@@ -143,14 +143,28 @@ JOIN (
) AS TotalTRY, ) AS TotalTRY,
SUM( SUM(
CASE CASE
-- "Paketlenen" = satir kapaliysa VEYA satir irsaliyeye/faturaya baglandiysa.
-- Not: IsClosed her zaman guncellenmiyor; trInvoiceLine.OrderLineID iliskiyi yakalar.
WHEN ISNULL(l.IsClosed,0) = 1 WHEN ISNULL(l.IsClosed,0) = 1
OR EXISTS (
SELECT 1
FROM dbo.trInvoiceLine il WITH (NOLOCK)
WHERE il.OrderLineID = l.OrderLineID
)
THEN ISNULL(c.NetAmount,0) THEN ISNULL(c.NetAmount,0)
ELSE 0 ELSE 0
END END
) AS PackedAmount, ) AS PackedAmount,
SUM( SUM(
CASE CASE
WHEN ISNULL(l.IsClosed,0) = 1 WHEN (
ISNULL(l.IsClosed,0) = 1
OR EXISTS (
SELECT 1
FROM dbo.trInvoiceLine il WITH (NOLOCK)
WHERE il.OrderLineID = l.OrderLineID
)
)
AND ISNULL(c.CurrencyCode,'') = 'TRY' AND ISNULL(c.CurrencyCode,'') = 'TRY'
THEN ISNULL(c.NetAmount,0) THEN ISNULL(c.NetAmount,0)
ELSE 0 ELSE 0

View File

@@ -0,0 +1,1702 @@
package queries
import (
"bssapp-backend/utils"
"context"
"database/sql"
"fmt"
"strconv"
"strings"
"unicode"
)
func GetProductAnaAltGrupByUrunKodu(ctx context.Context, mssqlDB *sql.DB, urunKodu string) (urunAnaGrubu string, urunAltGrubu string, err error) {
urunKodu = strings.TrimSpace(urunKodu)
if mssqlDB == nil || urunKodu == "" {
return "", "", nil
}
// Nebim V3: ProductFilterWithDescription exposes ProductAtt01Desc (ana grup) and ProductAtt02Desc (alt grup).
sqlText := `
SELECT TOP 1
ISNULL(ProductAtt01Desc, '') AS UrunAnaGrubu,
ISNULL(ProductAtt02Desc, '') AS UrunAltGrubu
FROM ProductFilterWithDescription('TR')
WHERE IsBlocked = 0
AND LTRIM(RTRIM(ProductCode)) = @p1
ORDER BY ProductCode;
`
row := mssqlDB.QueryRowContext(ctx, sqlText, urunKodu)
if err := row.Scan(&urunAnaGrubu, &urunAltGrubu); err != nil {
if err == sql.ErrNoRows {
return "", "", nil
}
return "", "", err
}
return urunAnaGrubu, urunAltGrubu, nil
}
func GetProductionProductCostingAnaGrupOptions(ctx context.Context, mssqlDB *sql.DB, search string, limit int) (*sql.Rows, error) {
search = strings.TrimSpace(search)
if limit <= 0 {
limit = 50
}
searchLike := "%" + search + "%"
sqlText := `
SELECT TOP (@p2)
ISNULL(NULLIF(LTRIM(RTRIM(P.ProductAtt01Desc)), ''), '') AS UrunAnaGrubu
FROM ProductFilterWithDescription('TR') AS P
WHERE P.IsBlocked = 0
AND NULLIF(LTRIM(RTRIM(P.ProductAtt01Desc)), '') IS NOT NULL
AND (@p1 = '' OR P.ProductAtt01Desc LIKE @p3)
GROUP BY P.ProductAtt01Desc
ORDER BY P.ProductAtt01Desc
`
return mssqlDB.QueryContext(ctx, sqlText, search, limit, searchLike)
}
func GetProductionProductCostingAltGrupOptions(ctx context.Context, mssqlDB *sql.DB, urunAnaGrubu string, search string, limit int) (*sql.Rows, error) {
urunAnaGrubu = strings.TrimSpace(urunAnaGrubu)
search = strings.TrimSpace(search)
if limit <= 0 {
limit = 50
}
searchLike := "%" + search + "%"
sqlText := `
SELECT TOP (@p3)
ISNULL(NULLIF(LTRIM(RTRIM(P.ProductAtt02Desc)), ''), '') AS UrunAltGrubu
FROM ProductFilterWithDescription('TR') AS P
WHERE P.IsBlocked = 0
AND NULLIF(LTRIM(RTRIM(P.ProductAtt02Desc)), '') IS NOT NULL
AND (@p1 = '' OR LTRIM(RTRIM(P.ProductAtt01Desc)) = @p1)
AND (@p2 = '' OR P.ProductAtt02Desc LIKE @p4)
GROUP BY P.ProductAtt02Desc
ORDER BY P.ProductAtt02Desc
`
return mssqlDB.QueryContext(ctx, sqlText, urunAnaGrubu, search, limit, searchLike)
}
func GetProductionProductCostingAnaAltComboRows(ctx context.Context, mssqlDB *sql.DB, search string, limit int) (*sql.Rows, error) {
search = strings.TrimSpace(search)
if limit <= 0 {
limit = 500
}
if limit > 5000 {
limit = 5000
}
// For very short searches, avoid leading-wildcard LIKE which can be extremely expensive on MSSQL side.
// Example: "ce" should match "CEKET%" cheaply vs "%ce%" scanning everything.
searchLike := "%" + search + "%"
if len([]rune(search)) > 0 && len([]rune(search)) < 3 {
searchLike = search + "%"
}
sqlText := `
SELECT TOP (@p2)
ISNULL(NULLIF(LTRIM(RTRIM(CONVERT(NVARCHAR(200), P.ProductAtt42Desc))), ''), '') AS UrunIlkGrubu,
ISNULL(NULLIF(LTRIM(RTRIM(CONVERT(NVARCHAR(200), P.ProductAtt01Desc))), ''), '') AS UrunAnaGrubu,
ISNULL(NULLIF(LTRIM(RTRIM(CONVERT(NVARCHAR(200), P.ProductAtt02Desc))), ''), '') AS UrunAltGrubu
FROM ProductFilterWithDescription('TR') AS P
WHERE P.IsBlocked = 0
AND NULLIF(LTRIM(RTRIM(CONVERT(NVARCHAR(200), P.ProductAtt42Desc))), '') IS NOT NULL
AND NULLIF(LTRIM(RTRIM(CONVERT(NVARCHAR(200), P.ProductAtt01Desc))), '') IS NOT NULL
AND NULLIF(LTRIM(RTRIM(CONVERT(NVARCHAR(200), P.ProductAtt02Desc))), '') IS NOT NULL
AND (
@p1 = ''
OR CONVERT(NVARCHAR(200), P.ProductAtt42Desc) LIKE @p3
OR CONVERT(NVARCHAR(200), P.ProductAtt01Desc) LIKE @p3
OR CONVERT(NVARCHAR(200), P.ProductAtt02Desc) LIKE @p3
)
GROUP BY
CONVERT(NVARCHAR(200), P.ProductAtt42Desc),
CONVERT(NVARCHAR(200), P.ProductAtt01Desc),
CONVERT(NVARCHAR(200), P.ProductAtt02Desc)
ORDER BY
CONVERT(NVARCHAR(200), P.ProductAtt42Desc),
CONVERT(NVARCHAR(200), P.ProductAtt02Desc),
CONVERT(NVARCHAR(200), P.ProductAtt01Desc)
`
return mssqlDB.QueryContext(ctx, sqlText, search, limit, searchLike)
}
func GetProductionProductCostingMTBolumOptions(ctx context.Context, uretimDB *sql.DB, search string, limit int) (*sql.Rows, error) {
search = strings.TrimSpace(search)
if limit <= 0 {
limit = 50
}
searchLike := "%" + search + "%"
sqlText := `
SELECT TOP (@p2)
ISNULL(B.nUrtMTBolumID, 0) AS nUrtMTBolumID,
ISNULL(B.sAdi, '') AS sAdi
FROM dbo.spUrtMTBolum B WITH (NOLOCK)
WHERE ISNULL(B.bAktif, 0) = 1
AND (@p1 = '' OR ISNULL(B.sAdi, '') LIKE @p3 OR CONVERT(VARCHAR(32), ISNULL(B.nUrtMTBolumID, 0)) LIKE @p3)
ORDER BY B.nUrtMTBolumID
`
return uretimDB.QueryContext(ctx, sqlText, search, limit, searchLike)
}
// ============================================================
// Maliyet Parca Eslestirme (URETIM DB)
// ============================================================
func ListProductionProductCostingParcaMappings(ctx context.Context, uretimDB *sql.DB, urunIlkGrubu string, urunAnaGrubu string, urunAltGrubu string, nUrtMTBolumID int, onlyActive *bool) (*sql.Rows, error) {
urunIlkGrubu = strings.TrimSpace(urunIlkGrubu)
urunAnaGrubu = strings.TrimSpace(urunAnaGrubu)
urunAltGrubu = strings.TrimSpace(urunAltGrubu)
sqlText := `
SELECT
M.id,
ISNULL(M.UrunIlkGrubu, '') AS UrunIlkGrubu,
ISNULL(M.UrunAnaGrubu, '') AS UrunAnaGrubu,
ISNULL(M.UrunAltGrubu, '') AS UrunAltGrubu,
ISNULL(M.nUrtMTBolumID, 0) AS nUrtMTBolumID,
ISNULL(B.sAdi, '') AS MTBolumAdi,
ISNULL(H.HammaddeTurleri, '') AS HammaddeTurleri,
CAST(CASE WHEN ISNULL(M.bAktif, 0) = 1 THEN 1 ELSE 0 END AS bit) AS bAktif,
CONVERT(VARCHAR(16), M.dteIslemTarihi, 120) AS dteIslemTarihi,
ISNULL(M.sKullaniciAdi, '') AS sKullaniciAdi
FROM dbo.mk_MaliyetParcaEslestirme M WITH (NOLOCK)
LEFT JOIN dbo.spUrtMTBolum B WITH (NOLOCK)
ON B.nUrtMTBolumID = M.nUrtMTBolumID
OUTER APPLY (
SELECT
STUFF((
SELECT ',' + LTRIM(RTRIM(CONVERT(VARCHAR(32), X.nHammaddeTuruNo)))
FROM dbo.mk_MaliyetParcaEslestirme_HammaddeTuru X WITH (NOLOCK)
WHERE X.mapping_id = M.id
ORDER BY X.nHammaddeTuruNo
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 1, '') AS HammaddeTurleri
) H
WHERE (@p1 = '' OR LTRIM(RTRIM(M.UrunIlkGrubu)) = @p1)
AND (@p2 = '' OR LTRIM(RTRIM(M.UrunAnaGrubu)) = @p2)
AND (@p3 = '' OR LTRIM(RTRIM(M.UrunAltGrubu)) = @p3)
AND (@p4 <= 0 OR M.nUrtMTBolumID = @p4)
AND (@p5 IS NULL OR ISNULL(M.bAktif, 0) = @p5)
ORDER BY M.UrunAltGrubu, M.UrunAnaGrubu, M.nUrtMTBolumID
`
var activeParam any = nil
if onlyActive != nil {
if *onlyActive {
activeParam = 1
} else {
activeParam = 0
}
}
return uretimDB.QueryContext(ctx, sqlText, urunIlkGrubu, urunAnaGrubu, urunAltGrubu, nUrtMTBolumID, activeParam)
}
func UpsertProductionProductCostingParcaMapping(ctx context.Context, uretimDB *sql.DB, urunIlkGrubu string, urunAnaGrubu string, urunAltGrubu string, nUrtMTBolumID int, nHammaddeTurleri []int, bAktif bool, user string) (mappingID int, err error) {
urunIlkGrubu = strings.TrimSpace(urunIlkGrubu)
urunAnaGrubu = strings.TrimSpace(urunAnaGrubu)
urunAltGrubu = strings.TrimSpace(urunAltGrubu)
user = strings.TrimSpace(user)
activeVal := 0
if bAktif {
activeVal = 1
}
tx, err := uretimDB.BeginTx(ctx, nil)
if err != nil {
return 0, err
}
defer func() {
if err != nil {
_ = tx.Rollback()
}
}()
// 1) Upsert header row and get mapping id
sqlText := `
DECLARE @id INT;
SELECT TOP 1 @id = id
FROM dbo.mk_MaliyetParcaEslestirme WITH (UPDLOCK, HOLDLOCK)
WHERE LTRIM(RTRIM(UrunIlkGrubu)) = @p1
AND LTRIM(RTRIM(UrunAnaGrubu)) = @p2
AND LTRIM(RTRIM(UrunAltGrubu)) = @p3
AND nUrtMTBolumID = @p4;
IF @id IS NULL
BEGIN
INSERT INTO dbo.mk_MaliyetParcaEslestirme
(UrunIlkGrubu, UrunAnaGrubu, UrunAltGrubu, nUrtMTBolumID, bAktif, sKullaniciAdi, dteIslemTarihi)
VALUES
(@p1, @p2, @p3, @p4, @p5, NULLIF(@p6, ''), GETDATE());
SET @id = SCOPE_IDENTITY();
END
ELSE
BEGIN
UPDATE dbo.mk_MaliyetParcaEslestirme
SET bAktif = @p5,
sKullaniciAdiDeg = NULLIF(@p6, ''),
dteIslemTarihiDeg = GETDATE()
WHERE id = @id;
END
SELECT @id;
`
if err = tx.QueryRowContext(ctx, sqlText, urunIlkGrubu, urunAnaGrubu, urunAltGrubu, nUrtMTBolumID, activeVal, user).Scan(&mappingID); err != nil {
return 0, err
}
// 2) Replace child rows
if _, err = tx.ExecContext(ctx, `DELETE FROM dbo.mk_MaliyetParcaEslestirme_HammaddeTuru WHERE mapping_id = @p1;`, mappingID); err != nil {
return 0, err
}
insertChild := `INSERT INTO dbo.mk_MaliyetParcaEslestirme_HammaddeTuru (mapping_id, nHammaddeTuruNo) VALUES (@p1, @p2);`
seen := make(map[int]bool, len(nHammaddeTurleri))
for _, n := range nHammaddeTurleri {
if n <= 0 {
continue
}
if seen[n] {
continue
}
seen[n] = true
if _, err = tx.ExecContext(ctx, insertChild, mappingID, n); err != nil {
return 0, err
}
}
if err = tx.Commit(); err != nil {
return 0, err
}
return mappingID, nil
}
func SetProductionProductCostingParcaMappingActive(ctx context.Context, uretimDB *sql.DB, id int, bAktif bool, user string) error {
user = strings.TrimSpace(user)
activeVal := 0
if bAktif {
activeVal = 1
}
sqlText := `
UPDATE dbo.mk_MaliyetParcaEslestirme
SET bAktif = @p2,
sKullaniciAdiDeg = NULLIF(@p3, ''),
dteIslemTarihiDeg = GETDATE()
WHERE id = @p1;
`
_, err := uretimDB.ExecContext(ctx, sqlText, id, activeVal, user)
return err
}
func DeleteProductionProductCostingParcaMapping(ctx context.Context, uretimDB *sql.DB, id int) error {
tx, err := uretimDB.BeginTx(ctx, nil)
if err != nil {
return err
}
defer func() {
if err != nil {
_ = tx.Rollback()
}
}()
if _, err = tx.ExecContext(ctx, `DELETE FROM dbo.mk_MaliyetParcaEslestirme_HammaddeTuru WHERE mapping_id = @p1;`, id); err != nil {
return err
}
if _, err = tx.ExecContext(ctx, `DELETE FROM dbo.mk_MaliyetParcaEslestirme WHERE id = @p1;`, id); err != nil {
return err
}
return tx.Commit()
}
func GetProductionNoCostProducts(
ctx context.Context,
uretimDB *sql.DB,
fromDate string,
search string,
) (*sql.Rows, error) {
fromDate = strings.TrimSpace(fromDate)
if fromDate == "" {
fromDate = "2025-06-01"
}
search = strings.TrimSpace(search)
sqlText := `
SELECT
CASE
WHEN LEFT(LTRIM(RTRIM(R.sMModelKodu)), 1) IN ('N','X','S','O','I','K')
THEN N'BAGGI URUN TARAFINDAN URETIME VERILEN'
WHEN LEFT(LTRIM(RTRIM(R.sMModelKodu)), 1) IN ('P','F','M')
THEN N'FASON ICIN URETILEN'
ELSE N'Tanimsiz'
END AS UretimSekli,
RTRIM(CONVERT(VARCHAR(32), ISNULL(SonIsEmri.nUrtSiparisNo, 0))) AS nUrtSiparisNo,
CONVERT(VARCHAR(10), SonIsEmri.dteIslemTarihi, 23) AS dteIslemTarihi,
ISNULL(SonIsEmri.FirmaKodu, '') AS FirmaKodu,
ISNULL(SonIsEmri.FirmaAdi, '') AS FirmaAdi,
ISNULL(SonIsEmri.sVeren, '') AS SonIsEmriVeren,
ISNULL(SonIsEmri.lMMiktar_G, 0) AS lMMiktar_G,
LTRIM(RTRIM(R.sMModelKodu)) AS sMModelKodu,
ISNULL(R.sKodu, '') AS sKodu,
ISNULL(R.sAdi, '') AS sAdi,
ISNULL(R.sKullaniciAdi, '') AS sKullaniciAdi,
ISNULL(R.sKullaniciAdiGunc, '') AS sKullaniciAdiGunc
FROM dbo.spUrtRecete R
OUTER APPLY (
SELECT TOP 1
SD.nUrtSiparisID,
SM.nUrtSiparisNo,
SD.dteIslemTarihi,
SD.lMMiktar_G,
SM.sVeren,
F.nFirmaID,
F.sKodu AS FirmaKodu,
F.sAciklama AS FirmaAdi
FROM dbo.spUrtSiparisDet SD
INNER JOIN dbo.spUrtSiparis SM
ON SM.nUrtSiparisID = SD.nUrtSiparisID
LEFT JOIN dbo.tbFirma F
ON F.nFirmaID = SM.nFirmaID
WHERE SD.nUrtReceteID = R.nUrtReceteID
ORDER BY SD.dteIslemTarihi DESC, SD.nUrtSiparisID DESC
) SonIsEmri
WHERE LTRIM(RTRIM(R.sMModelKodu)) <> ''
AND LEN(LTRIM(RTRIM(R.sMModelKodu))) = 13
AND R.dteIslemTarihi > @p1
AND (
@p2 = ''
OR LTRIM(RTRIM(R.sMModelKodu)) LIKE '%' + @p2 + '%'
OR ISNULL(SonIsEmri.FirmaKodu, '') LIKE '%' + @p2 + '%'
OR ISNULL(SonIsEmri.FirmaAdi, '') LIKE '%' + @p2 + '%'
OR ISNULL(SonIsEmri.sVeren, '') LIKE '%' + @p2 + '%'
)
AND NOT EXISTS (
SELECT 1
FROM dbo.spUrtOnMLMas M
WHERE LTRIM(RTRIM(M.UrunKodu)) = LTRIM(RTRIM(R.sMModelKodu))
)
ORDER BY SonIsEmri.dteIslemTarihi DESC, LTRIM(RTRIM(R.sMModelKodu)) ASC
`
return uretimDB.QueryContext(ctx, sqlText, fromDate, search)
}
func GetProductionHasCostProducts(
ctx context.Context,
uretimDB *sql.DB,
search string,
offset int,
limit int,
) (*sql.Rows, error) {
search = strings.TrimSpace(search)
sqlText := `
WITH SonOnMaliyet AS (
SELECT
M.*,
ROW_NUMBER() OVER (
PARTITION BY LTRIM(RTRIM(M.UrunKodu))
ORDER BY
M.Tarihi DESC,
M.dteGuncellemeTarihi DESC,
M.nOnMLNo DESC
) AS rn
FROM dbo.spUrtOnMLMas M
WHERE LTRIM(RTRIM(M.UrunKodu)) <> ''
),
SonSiparis AS (
SELECT
LTRIM(RTRIM(sMModelKodu)) AS UrunKodu,
MAX(dteIslemTarihi) AS SonSiparisTarihi
FROM dbo.spUrtSiparisDet
GROUP BY LTRIM(RTRIM(sMModelKodu))
)
SELECT
CASE
WHEN LEFT(LTRIM(RTRIM(OM.UrunKodu)), 1) IN ('N','X','S','O','I','K')
THEN N'BAGGI URUN TARAFINDAN URETIME VERILEN'
WHEN LEFT(LTRIM(RTRIM(OM.UrunKodu)), 1) IN ('P','F','M')
THEN N'FASON ICIN URETILEN'
ELSE N'Tanimsiz'
END AS UretimSekli,
RTRIM(CONVERT(VARCHAR(32), ISNULL(OM.nOnMLNo, 0))) AS nOnMLNo,
LTRIM(RTRIM(ISNULL(OM.UrunKodu, ''))) AS UrunKodu,
ISNULL(OM.UrunAdi, '') AS UrunAdi,
CONVERT(VARCHAR(10), OM.Tarihi, 23) AS Tarihi,
CONVERT(VARCHAR(10), OM.dteKayitTarihi, 23) AS dteKayitTarihi,
ISNULL(OM.sKullaniciAdi, '') AS sKullaniciAdi,
ISNULL(OM.lTutarTL, 0) AS lTutarTL,
ISNULL(OM.lTutarUSD, 0) AS lTutarUSD,
ISNULL(OM.lTutarEURO, 0) AS lTutarEURO,
CONVERT(VARCHAR(10), OM.dteGuncellemeTarihi, 23) AS dteGuncellemeTarihi,
ISNULL(OM.sGuncellemeKullaniciAdi, '') AS sGuncellemeKullaniciAdi,
RTRIM(CONVERT(VARCHAR(32), ISNULL(OM.nUrtReceteID, 0))) AS nUrtReceteID,
ISNULL(OM.sAciklama, '') AS sAciklama,
ISNULL(CONVERT(VARCHAR(10), SS.SonSiparisTarihi, 23), '') AS SonSiparisTarihi,
CASE
WHEN ISNULL(OM.lTutarUSD, 0) = 0 THEN N'GUNCELLEME GEREKIYOR'
WHEN SS.SonSiparisTarihi IS NULL THEN N'SIPARIS YOK'
WHEN SS.SonSiparisTarihi > OM.Tarihi THEN N'GUNCELLEME GEREKIYOR'
ELSE N'GUNCEL'
END AS MaliyetDurumu
FROM SonOnMaliyet OM
LEFT JOIN SonSiparis SS
ON SS.UrunKodu = LTRIM(RTRIM(OM.UrunKodu))
WHERE OM.rn = 1
AND (
@p1 = ''
OR RTRIM(CONVERT(VARCHAR(32), ISNULL(OM.nOnMLNo, 0))) LIKE '%' + @p1 + '%'
OR LTRIM(RTRIM(OM.UrunKodu)) LIKE '%' + @p1 + '%'
OR ISNULL(OM.UrunAdi, '') LIKE '%' + @p1 + '%'
OR ISNULL(OM.sAciklama, '') LIKE '%' + @p1 + '%'
)
ORDER BY
SS.SonSiparisTarihi DESC,
OM.nOnMLNo DESC
OFFSET @p2 ROWS
FETCH NEXT @p3 ROWS ONLY
`
return uretimDB.QueryContext(ctx, sqlText, search, offset, limit)
}
func GetProductionHasCostHistoryByProductCode(
ctx context.Context,
uretimDB *sql.DB,
productCode string,
) (*sql.Rows, error) {
productCode = strings.TrimSpace(productCode)
sqlText := `
SELECT
RTRIM(CONVERT(VARCHAR(32), ISNULL(M.nOnMLNo, 0))) AS nOnMLNo,
LTRIM(RTRIM(ISNULL(M.UrunKodu, ''))) AS UrunKodu,
ISNULL(M.UrunAdi, '') AS UrunAdi,
CONVERT(VARCHAR(16), M.Tarihi, 120) AS Tarihi,
ISNULL(M.sKullaniciAdi, '') AS sKullaniciAdi,
ISNULL(M.lTutarUSD, 0) AS lTutarUSD,
ISNULL(M.lTutarTL, 0) AS lTutarTL,
ISNULL(M.lTutarEURO, 0) AS lTutarEURO,
ISNULL(M.sDovizCinsi, '') AS sDovizCinsi,
ISNULL(M.lTutarDoviz, 0) AS lTutarDoviz,
CONVERT(VARCHAR(16), M.dteGuncellemeTarihi, 120) AS dteGuncellemeTarihi,
ISNULL(M.sGuncellemeKullaniciAdi, '') AS sGuncellemeKullaniciAdi,
RTRIM(CONVERT(VARCHAR(32), ISNULL(M.nUrtReceteID, 0))) AS nUrtReceteID,
ISNULL(M.sAciklama, '') AS sAciklama
FROM dbo.spUrtOnMLMas M
WHERE LTRIM(RTRIM(M.UrunKodu)) = @p1
ORDER BY
M.Tarihi DESC,
M.dteGuncellemeTarihi DESC,
M.nOnMLNo DESC
`
return uretimDB.QueryContext(ctx, sqlText, productCode)
}
func GetProductionHasCostDetailRowsByOnMLNo(
ctx context.Context,
uretimDB *sql.DB,
nOnMLNo int,
) (*sql.Rows, error) {
sqlText := `
SELECT
ISNULL(NULLIF(LTRIM(RTRIM(T.sAciklama3)), ''), N'TANIMSIZ') AS sAciklama3,
SUM(ISNULL(D.lTutar, 0)) OVER (
PARTITION BY ISNULL(NULLIF(LTRIM(RTRIM(T.sAciklama3)), ''), N'TANIMSIZ')
) AS GroupTotalTutar,
SUM(ISNULL(D.lMiktar, 0) * ISNULL(D.lDovizFiyati, 0)) OVER (
PARTITION BY ISNULL(NULLIF(LTRIM(RTRIM(T.sAciklama3)), ''), N'TANIMSIZ')
) AS GroupTotalUSDTutar,
RTRIM(CONVERT(VARCHAR(32), ISNULL(D.nOnMLNo, 0))) AS nOnMLNo,
RTRIM(CONVERT(VARCHAR(32), ISNULL(D.nOnMLDetNo, 0))) AS nOnMLDetNo,
RTRIM(CONVERT(VARCHAR(32), ISNULL(D.nHammaddeTuruNo, 0))) AS nHammaddeTuruNo,
ISNULL(D.sKodu, '') AS sKodu,
ISNULL(D.sAciklama, '') AS sAciklama,
ISNULL(D.sRenk, '') AS sRenk,
ISNULL(D.sBeden, '') AS sBeden,
ISNULL(D.sAciklama2, '') AS sAciklama2,
ISNULL(D.lMiktar, 0) AS lMiktar,
ISNULL(D.lFiyat, 0) AS lFiyat,
ISNULL(D.lTutar, 0) AS lTutar,
ISNULL(D.sFiyatTipi, '') AS sFiyatTipi,
ISNULL(D.sDovizCinsi, '') AS sDovizCinsi,
ISNULL(D.lDovizKuru, 0) AS lDovizKuru,
ISNULL(D.lDovizFiyati, 0) AS lDovizFiyati,
D.fiyat_girilen AS fiyat_girilen,
D.fiyat_doviz AS fiyat_doviz,
CAST(CASE WHEN ISNULL(D.Maliyete_dahil, 0) = 1 THEN 1 ELSE 0 END AS bit) AS maliyete_dahil,
D.cm_price_type_id AS cm_price_type_id,
ISNULL(D.lMiktar, 0) * ISNULL(D.lDovizFiyati, 0) AS usdTutar,
0.0 AS eurTutar,
0.0 AS gbpTutar,
ISNULL(D.sBirim, '') AS sBirim,
ISNULL(T.sAciklama, '') AS sHammaddeTuruAdi,
ISNULL(B.sAdi, '') AS sParcaAdi
FROM dbo.spUrtOnMLMasDet D
LEFT JOIN dbo.spUrtOnMLHammaddeTuru T
ON T.nHammaddeTuruNo = D.nHammaddeTuruNo
LEFT JOIN dbo.spUrtMTBolum B
ON B.nUrtMTBolumID = D.nUrtMTBolumID
WHERE D.nOnMLNo = @p1
ORDER BY
GroupTotalTutar DESC,
sAciklama3 ASC,
ISNULL(D.lTutar, 0) DESC,
D.nOnMLDetNo ASC
`
return uretimDB.QueryContext(ctx, sqlText, nOnMLNo)
}
func GetProductionHasCostDetailHeaderByOnMLNo(
ctx context.Context,
uretimDB *sql.DB,
nOnMLNo int,
) (*sql.Row, error) {
sqlText := `
SELECT TOP 1
ISNULL(UF.UretimiYapanFirma, '') AS UretimiYapanFirma,
ISNULL(UF.SonIsEmriVeren, '') AS SonIsEmriVeren,
RTRIM(CONVERT(VARCHAR(32), ISNULL(M.nOnMLNo, 0))) AS nOnMLNo,
LTRIM(RTRIM(ISNULL(M.UrunKodu, ''))) AS UrunKodu,
ISNULL(M.UrunAdi, '') AS UrunAdi,
RTRIM(CONVERT(VARCHAR(32), ISNULL(M.uretim_sekli_id, 0))) AS uretim_sekli_id,
ISNULL(US.aciklama, '') AS uretim_sekli,
CONVERT(VARCHAR(16), M.dteKayitTarihi, 120) AS dteKayitTarihi,
ISNULL(M.sKullaniciAdi, '') AS sKullaniciAdi,
ISNULL(M.lTutarTL, 0) AS lTutarTL,
ISNULL(M.lTutarUSD, 0) AS lTutarUSD,
ISNULL(M.lTutarEURO, 0) AS lTutarEURO,
0.0 AS lTutarGBP,
ISNULL(M.sDovizCinsi, '') AS sDovizCinsi,
ISNULL(M.lTutarDoviz, 0) AS lTutarDoviz,
CONVERT(VARCHAR(16), M.dteGuncellemeTarihi, 120) AS dteGuncellemeTarihi,
ISNULL(M.sGuncellemeKullaniciAdi, '') AS sGuncellemeKullaniciAdi,
RTRIM(CONVERT(VARCHAR(32), ISNULL(M.nUrtReceteID, 0))) AS nUrtReceteID
FROM dbo.spUrtOnMLMas M
LEFT JOIN dbo.mk_uretim_sekli US
ON US.id = M.uretim_sekli_id
OUTER APPLY (
SELECT TOP 1
ISNULL(F.sAciklama, '') AS UretimiYapanFirma,
ISNULL(SM.sVeren, '') AS SonIsEmriVeren
FROM dbo.spUrtSiparisDet SD
INNER JOIN dbo.spUrtSiparis SM
ON SM.nUrtSiparisID = SD.nUrtSiparisID
LEFT JOIN dbo.tbFirma F
ON F.nFirmaID = SM.nFirmaID
WHERE LTRIM(RTRIM(SD.sMModelKodu)) = LTRIM(RTRIM(M.UrunKodu))
ORDER BY SD.dteIslemTarihi DESC, SD.nUrtSiparisID DESC
) UF
WHERE M.nOnMLNo = @p1
ORDER BY M.nOnMLNo DESC
`
return uretimDB.QueryRowContext(ctx, sqlText, nOnMLNo), nil
}
func GetProductionNoCostDetailHeaderByRecipeCode(
ctx context.Context,
uretimDB *sql.DB,
recipeCode string,
productCode string,
) (*sql.Row, error) {
recipeCode = strings.TrimSpace(recipeCode)
productCode = strings.TrimSpace(productCode)
utils.SlogFromContext(ctx).With(
"query", "production-product-costing.no-cost-detail-header",
"recete_kodu", recipeCode,
"urun_kodu", productCode,
).Info("query dispatch")
sqlText := `
WITH RecipeMatch AS (
SELECT TOP 1
R.nUrtReceteID,
LTRIM(RTRIM(ISNULL(R.sMModelKodu, ''))) AS UrunKodu,
ISNULL(R.sAdi, '') AS UrunAdi,
CONVERT(VARCHAR(16), R.dteIslemTarihi, 120) AS dteKayitTarihi,
ISNULL(R.sKullaniciAdi, '') AS sKullaniciAdi,
ISNULL(R.sKullaniciAdiGunc, '') AS sGuncellemeKullaniciAdi,
CASE
WHEN LEFT(LTRIM(RTRIM(R.sMModelKodu)), 1) IN ('N','X','S','O','I','K')
THEN N'BAGGI URUN TARAFINDAN URETIME VERILEN'
WHEN LEFT(LTRIM(RTRIM(R.sMModelKodu)), 1) IN ('P','F','M')
THEN N'FASON ICIN URETILEN'
ELSE N'Tanimsiz'
END AS UretimSekli
FROM dbo.spUrtRecete R
WHERE LTRIM(RTRIM(ISNULL(R.sKodu, ''))) = @p1
AND (@p2 = '' OR LTRIM(RTRIM(ISNULL(R.sMModelKodu, ''))) = @p2)
ORDER BY
R.dteIslemTarihi DESC,
R.nUrtReceteID DESC
)
SELECT TOP 1
ISNULL(SonIsEmri.FirmaAdi, '') AS UretimiYapanFirma,
ISNULL(SonIsEmri.SonIsEmriVeren, '') AS SonIsEmriVeren,
'' AS nOnMLNo,
RM.UrunKodu,
RM.UrunAdi,
'' AS UretimSekliID,
RM.UretimSekli,
ISNULL(SonIsEmri.dteIslemTarihi, RM.dteKayitTarihi) AS dteKayitTarihi,
RM.sKullaniciAdi,
0.0 AS lTutarTL,
0.0 AS lTutarUSD,
0.0 AS lTutarEURO,
0.0 AS lTutarGBP,
'USD' AS sDovizCinsi,
0.0 AS lTutarDoviz,
'' AS dteGuncellemeTarihi,
RM.sGuncellemeKullaniciAdi,
RTRIM(CONVERT(VARCHAR(32), ISNULL(RM.nUrtReceteID, 0))) AS nUrtReceteID
FROM RecipeMatch RM
OUTER APPLY (
SELECT TOP 1
ISNULL(F.sAciklama, '') AS FirmaAdi,
ISNULL(SM.sVeren, '') AS SonIsEmriVeren,
CONVERT(VARCHAR(16), SD.dteIslemTarihi, 120) AS dteIslemTarihi
FROM dbo.spUrtSiparisDet SD
INNER JOIN dbo.spUrtSiparis SM
ON SM.nUrtSiparisID = SD.nUrtSiparisID
LEFT JOIN dbo.tbFirma F
ON F.nFirmaID = SM.nFirmaID
WHERE SD.nUrtReceteID = RM.nUrtReceteID
ORDER BY SD.dteIslemTarihi DESC, SD.nUrtSiparisID DESC
) SonIsEmri
`
return uretimDB.QueryRowContext(ctx, sqlText, recipeCode, productCode), nil
}
func GetProductionNoCostDetailRowsByRecipeCode(
ctx context.Context,
uretimDB *sql.DB,
recipeCode string,
productCode string,
) (*sql.Rows, error) {
recipeCode = strings.TrimSpace(recipeCode)
productCode = strings.TrimSpace(productCode)
logger := utils.SlogFromContext(ctx).With(
"query", "production-product-costing.no-cost-detail-rows",
"recete_kodu", recipeCode,
"urun_kodu", productCode,
)
logger.Info("query dispatch")
sqlText := `
WITH RecipeMatch AS (
SELECT TOP 1
R.nUrtReceteID
FROM dbo.spUrtRecete R
WHERE LTRIM(RTRIM(ISNULL(R.sKodu, ''))) = @p1
AND (@p2 = '' OR LTRIM(RTRIM(ISNULL(R.sMModelKodu, ''))) = @p2)
ORDER BY
R.dteIslemTarihi DESC,
R.nUrtReceteID DESC
),
HammaddeTekil AS (
SELECT
ISNULL(NULLIF(LTRIM(RTRIM(HT.sAciklama3)), ''), ISNULL(NULLIF(LTRIM(RTRIM(HT.sAciklama)), ''), N'TANIMSIZ')) AS sAciklama3,
ISNULL(HT.nHammaddeTuruNo, 0) AS nHammaddeTuruNoSort,
RTRIM(CONVERT(VARCHAR(32), ISNULL(HT.nHammaddeTuruNo, 0))) AS nHammaddeTuruNo,
-- Match URETIM's sp_pUrtOnMaliyetRecetedenKop behavior: use model code + color code instead of variant stock code.
ISNULL(S.sModel, ISNULL(S.sKodu, '')) AS sKodu,
ISNULL(S.sAciklama, '') AS sAciklama,
ISNULL(S.sRenk, '') AS sRenk,
ISNULL(HT.sAciklama, '') AS sHammaddeTuruAdi,
ISNULL(S.sBirimCinsi1, '') AS sBirim,
ISNULL(RMik.lHMiktar, 0) AS lMiktar,
ROW_NUMBER() OVER (
PARTITION BY HT.nHammaddeTuruNo
ORDER BY ISNULL(S.sModel, ISNULL(S.sKodu, ''))
) AS rn,
ROW_NUMBER() OVER (
ORDER BY HT.nHammaddeTuruNo, ISNULL(S.sModel, ISNULL(S.sKodu, ''))
) AS rowNo
FROM RecipeMatch R
INNER JOIN dbo.spUrtRecMBolumMik RMik
ON RMik.nUrtReceteID = R.nUrtReceteID
LEFT JOIN dbo.tbStok S
ON S.nStokID = RMik.nHStokID
OUTER APPLY (
SELECT TOP 1
H.nHammaddeTuruNo,
H.sAciklama,
H.sAciklama3
FROM dbo.spUrtOnMLHammaddeTuru H
WHERE H.nUrtMBolumID = RMik.nUrtMBolumID
ORDER BY
CASE WHEN H.nUrtMTBolumID = RMik.nUrtMTBolumID THEN 0 ELSE 1 END,
H.nHammaddeTuruNo
) HT
WHERE HT.nHammaddeTuruNo IS NOT NULL
)
SELECT
HT.sAciklama3,
0.0 AS GroupTotalTutar,
0.0 AS GroupTotalUSDTutar,
'' AS nOnMLNo,
RTRIM(CONVERT(VARCHAR(32), ISNULL(HT.rowNo, 0))) AS nOnMLDetNo,
HT.nHammaddeTuruNo,
HT.sKodu,
HT.sAciklama,
HT.sRenk AS sRenk,
'' AS sBeden,
'' AS sAciklama2,
HT.lMiktar,
0.0 AS lFiyat,
0.0 AS lTutar,
'' AS sFiyatTipi,
'USD' AS sDovizCinsi,
0.0 AS lDovizKuru,
0.0 AS lDovizFiyati,
NULL AS fiyat_girilen,
'' AS fiyat_doviz,
CAST(1 AS bit) AS maliyete_dahil,
NULL AS cm_price_type_id,
0.0 AS usdTutar,
0.0 AS eurTutar,
0.0 AS gbpTutar,
HT.sBirim,
HT.sHammaddeTuruAdi,
HT.sHammaddeTuruAdi AS sParcaAdi
FROM HammaddeTekil HT
WHERE HT.rn = 1
ORDER BY
HT.nHammaddeTuruNoSort,
HT.sKodu
`
rows, err := uretimDB.QueryContext(ctx, sqlText, recipeCode, productCode)
if err != nil {
logger.Error("query error", "err", err)
return nil, err
}
return rows, nil
}
func GetProductionTypes(ctx context.Context, uretimDB *sql.DB) (*sql.Rows, error) {
sqlText := `SELECT id, aciklama FROM dbo.mk_uretim_sekli ORDER BY id`
return uretimDB.QueryContext(ctx, sqlText)
}
func GetProductionHasCostDetailHammaddeTypeOptions(
ctx context.Context,
uretimDB *sql.DB,
search string,
limit int,
) (*sql.Rows, error) {
search = strings.TrimSpace(search)
if limit <= 0 {
limit = 50
}
searchLike := "%" + search + "%"
sqlText := `
SELECT TOP (@p2)
RTRIM(CONVERT(VARCHAR(32), ISNULL(T.nHammaddeTuruNo, 0))) AS nHammaddeTuruNo,
ISNULL(T.sAciklama, '') AS sHammaddeTuruAdi,
ISNULL(NULLIF(LTRIM(RTRIM(T.sAciklama3)), ''), N'TANIMSIZ') AS sAciklama3
FROM dbo.spUrtOnMLHammaddeTuru T
WHERE
ISNULL(T.bAktif, 0) = 1
AND (
@p1 = ''
OR RTRIM(CONVERT(VARCHAR(32), ISNULL(T.nHammaddeTuruNo, 0))) LIKE @p3
OR ISNULL(T.sAciklama, '') LIKE @p3
OR ISNULL(T.sAciklama3, '') LIKE @p3
)
ORDER BY
CASE
WHEN @p1 <> '' AND RTRIM(CONVERT(VARCHAR(32), ISNULL(T.nHammaddeTuruNo, 0))) = @p1 THEN 0
WHEN @p1 <> '' AND ISNULL(T.sAciklama, '') = @p1 THEN 1
ELSE 2
END,
T.nHammaddeTuruNo
`
return uretimDB.QueryContext(ctx, sqlText, search, limit, searchLike)
}
func buildSQLServerFullTextPrefixQuery(search string) string {
terms := strings.Fields(strings.TrimSpace(search))
parts := make([]string, 0, len(terms))
for _, term := range terms {
cleaned := strings.Map(func(r rune) rune {
if unicode.IsLetter(r) || unicode.IsDigit(r) {
return r
}
return -1
}, term)
if len([]rune(cleaned)) < 2 {
continue
}
parts = append(parts, fmt.Sprintf("\"%s*\"", cleaned))
}
return strings.Join(parts, " AND ")
}
func hasProductionHasCostDetailItemFullTextIndex(
ctx context.Context,
uretimDB *sql.DB,
) bool {
var exists int
err := uretimDB.QueryRowContext(ctx, `
SELECT CASE
WHEN EXISTS (
SELECT 1
FROM sys.fulltext_indexes fi
INNER JOIN sys.fulltext_index_columns fic
ON fic.object_id = fi.object_id
INNER JOIN sys.columns c
ON c.object_id = fic.object_id
AND c.column_id = fic.column_id
WHERE fi.object_id = OBJECT_ID('dbo.tbStok')
AND c.name = 'sAciklama'
) THEN 1 ELSE 0
END`).Scan(&exists)
return err == nil && exists == 1
}
func GetProductionHasCostDetailItemOptions(
ctx context.Context,
uretimDB *sql.DB,
search string,
limit int,
) (*sql.Rows, error) {
search = strings.TrimSpace(search)
if limit <= 0 {
limit = 50
}
if search == "" {
return uretimDB.QueryContext(ctx, `
SELECT TOP (0)
RTRIM(CONVERT(VARCHAR(32), ISNULL(S.nStokID, 0))) AS nStokID,
LTRIM(RTRIM(CONVERT(NVARCHAR(255), ISNULL(S.sKodu, '')))) AS sKodu,
LTRIM(RTRIM(CONVERT(NVARCHAR(255), ISNULL(S.sAciklama, '')))) AS sAciklama,
LTRIM(RTRIM(CONVERT(NVARCHAR(255), ISNULL(S.sModel, '')))) AS sModel,
LTRIM(RTRIM(CONVERT(NVARCHAR(64), ISNULL(S.sBirimCinsi1, '')))) AS sBirim
FROM dbo.tbStok S`)
}
searchExact := search
searchPrefix := search + "%"
searchLike := "%" + search + "%"
searchLen := len([]rune(search))
numericStokID, numericErr := strconv.Atoi(search)
hasNumericStokID := numericErr == nil
fullTextSearch := buildSQLServerFullTextPrefixQuery(search)
useFullText := searchLen >= 3 && fullTextSearch != "" && hasProductionHasCostDetailItemFullTextIndex(ctx, uretimDB)
baseSelect := `
SELECT TOP (@p2)
RTRIM(CONVERT(VARCHAR(32), ISNULL(S.nStokID, 0))) AS nStokID,
LTRIM(RTRIM(CONVERT(NVARCHAR(255), ISNULL(S.sKodu, '')))) AS sKodu,
LTRIM(RTRIM(CONVERT(NVARCHAR(255), ISNULL(S.sAciklama, '')))) AS sAciklama,
LTRIM(RTRIM(CONVERT(NVARCHAR(255), ISNULL(S.sModel, '')))) AS sModel,
LTRIM(RTRIM(CONVERT(NVARCHAR(64), ISNULL(S.sBirimCinsi1, '')))) AS sBirim
FROM dbo.tbStok S
WHERE
(ISNULL(S.IsBlocked, 0) = 0)
AND S.sModel LIKE '_.%%'
AND (
(@p5 = 1 AND S.nStokID = @p6)
OR S.sKodu = @p1
OR S.sKodu LIKE @p3
OR %s
)
ORDER BY
CASE
WHEN S.sKodu = @p1 THEN 0
WHEN (@p5 = 1 AND S.nStokID = @p6) THEN 1
WHEN S.sKodu LIKE @p3 THEN 2
ELSE 3
END,
S.sKodu
OPTION (RECOMPILE)
`
if useFullText {
sqlText := fmt.Sprintf(baseSelect, `CONTAINS(S.sAciklama, @p4)`)
return uretimDB.QueryContext(ctx, sqlText, searchExact, limit, searchPrefix, fullTextSearch, hasNumericStokID, numericStokID)
}
sqlText := fmt.Sprintf(baseSelect, `(@p4 >= 3 AND S.sAciklama LIKE @p7)`)
return uretimDB.QueryContext(ctx, sqlText, searchExact, limit, searchPrefix, searchLen, hasNumericStokID, numericStokID, searchLike)
}
func GetProductionHasCostDetailColorOptions(
ctx context.Context,
mssqlDB *sql.DB,
modelCode string,
search string,
limit int,
) (*sql.Rows, error) {
modelCode = strings.TrimSpace(modelCode)
search = strings.TrimSpace(search)
if limit <= 0 {
limit = 50
}
searchLike := "%" + search + "%"
sqlText := `
WITH ColorSource AS (
SELECT DISTINCT
LTRIM(RTRIM(CONVERT(NVARCHAR(64), ISNULL(T.sRenk, '')))) AS ColorCode
FROM dbo.tbStok T
WHERE ISNULL(T.IsBlocked, 0) = 0
AND LTRIM(RTRIM(CONVERT(NVARCHAR(255), ISNULL(T.sModel, '')))) = @p1
AND LTRIM(RTRIM(CONVERT(NVARCHAR(64), ISNULL(T.sRenk, '')))) <> ''
)
SELECT TOP (@p2)
LTRIM(RTRIM(ISNULL(S.ColorCode, ''))) AS colorCode,
ISNULL(C.ColorDescription, '') AS colorDescription
FROM (
SELECT ColorCode
FROM ColorSource
) S
OUTER APPLY (
SELECT TOP 1 ColorDescription
FROM dbo.cdColorDesc CD
WHERE CD.LangCode = 'TR'
AND LTRIM(RTRIM(ISNULL(CD.ColorCode, ''))) = S.ColorCode
) C
WHERE @p1 <> ''
AND (
@p3 = ''
OR LTRIM(RTRIM(ISNULL(S.ColorCode, ''))) LIKE @p4
OR ISNULL(C.ColorDescription, '') LIKE @p4
)
ORDER BY
CASE
WHEN @p3 <> '' AND LTRIM(RTRIM(ISNULL(S.ColorCode, ''))) = @p3 THEN 0
WHEN @p3 <> '' AND ISNULL(C.ColorDescription, '') = @p3 THEN 1
ELSE 2
END,
LTRIM(RTRIM(ISNULL(S.ColorCode, '')))
`
return mssqlDB.QueryContext(ctx, sqlText, modelCode, limit, search, searchLike)
}
func GetProductionHasCostDetailExchangeRatesByDate(
ctx context.Context,
mssqlDB *sql.DB,
costDate string,
) (*sql.Row, error) {
costDate = strings.TrimSpace(costDate)
sqlText := `
DECLARE @targetDate date = ISNULL(CONVERT(date, NULLIF(@p1, ''), 23), CONVERT(date, GETDATE()))
SELECT
CONVERT(VARCHAR(10), @targetDate, 23) AS rateDate,
ISNULL((
SELECT TOP 1 Rate
FROM dbo.AllExchangeRates
WHERE CurrencyCode = 'USD'
AND RelationCurrencyCode = 'TRY'
AND ExchangeTypeCode = 6
AND Rate > 0
AND CONVERT(date, [Date]) <= @targetDate
ORDER BY
CASE WHEN CONVERT(date, [Date]) = @targetDate THEN 0 ELSE 1 END,
[Date] DESC
), 0) AS usdRate,
ISNULL((
SELECT TOP 1 Rate
FROM dbo.AllExchangeRates
WHERE CurrencyCode = 'EUR'
AND RelationCurrencyCode = 'TRY'
AND ExchangeTypeCode = 6
AND Rate > 0
AND CONVERT(date, [Date]) <= @targetDate
ORDER BY
CASE WHEN CONVERT(date, [Date]) = @targetDate THEN 0 ELSE 1 END,
[Date] DESC
), 0) AS eurRate,
ISNULL((
SELECT TOP 1 Rate
FROM dbo.AllExchangeRates
WHERE CurrencyCode = 'GBP'
AND RelationCurrencyCode = 'TRY'
AND ExchangeTypeCode = 6
AND Rate > 0
AND CONVERT(date, [Date]) <= @targetDate
ORDER BY
CASE WHEN CONVERT(date, [Date]) = @targetDate THEN 0 ELSE 1 END,
[Date] DESC
), 0) AS gbpRate
`
return mssqlDB.QueryRowContext(ctx, sqlText, costDate), nil
}
func GetProductionHasCostLatestPurchasePriceForItem(
ctx context.Context,
mssqlDB *sql.DB,
sKodu string,
colorCode string,
itemDim1Code string,
costDate string,
) (*sql.Row, error) {
sKodu = strings.TrimSpace(sKodu)
colorCode = strings.TrimSpace(colorCode)
itemDim1Code = strings.TrimSpace(itemDim1Code)
costDate = strings.TrimSpace(costDate)
sqlText := `
WITH BASE AS (
SELECT
A.InvoiceDate,
A.InvoiceNumber,
A.ProcessCode,
A.CurrAccTypeCode,
A.CurrAccCode,
A.ItemTypeCode,
A.ItemCode,
A.ColorCode,
A.ItemDim1Code,
A.Qty1,
A.Doc_Price,
A.Doc_Amount,
A.Doc_CurrencyCode
FROM AllInvoicesWithAttributes A
WHERE A.ProcessCode IN ('BP')
AND A.ATAtt01 IN (1, 2)
AND A.CompanyCode IN (1, 2, 5)
AND A.IsCompleted = 1
AND YEAR(A.InvoiceDate) >= 2022
AND LTRIM(RTRIM(A.ItemCode)) = @p1
AND (NULLIF(@p2, '') IS NULL OR CONVERT(date, A.InvoiceDate) < CONVERT(date, NULLIF(@p2, ''), 23))
)
SELECT TOP 1
'MAN' AS priceType,
CONVERT(VARCHAR(16), B.InvoiceDate, 120) AS Tarih,
ISNULL(B.InvoiceNumber, '') AS FaturaKodu,
LTRIM(RTRIM(ISNULL(B.ItemCode, ''))) AS MasrafKodu,
ISNULL(ID.ItemDescription, '') AS MasrafDetay,
ISNULL(B.ColorCode, '') AS ColorCode,
ISNULL(COL.ColorDescription, '') AS ColorDescription,
ISNULL(B.ItemDim1Code, '') AS ItemDim1Code,
ISNULL(DIM1.ItemDim1Description, '') AS ItemDim1Description,
ISNULL(B.Doc_Price, 0) AS EvrakFiyat,
ISNULL(B.Doc_CurrencyCode, '') AS EvrakDoviz
FROM BASE B
LEFT JOIN cdItem CI
ON CI.ItemTypeCode = B.ItemTypeCode
AND CI.ItemCode = B.ItemCode
OUTER APPLY (
SELECT TOP 1 ItemDescription
FROM cdItemDesc
WHERE ItemTypeCode = B.ItemTypeCode
AND ItemCode = B.ItemCode
AND LangCode = 'TR'
) ID
OUTER APPLY (
SELECT TOP 1 ItemDim1Description
FROM cdItemDim1Desc
WHERE ItemDim1Code = B.ItemDim1Code
AND LangCode = 'TR'
) DIM1
OUTER APPLY (
SELECT TOP 1 ColorDescription
FROM cdColorDesc
WHERE ColorCode = B.ColorCode
AND LangCode = 'TR'
) COL
ORDER BY
CASE
WHEN @p3 <> '' AND LTRIM(RTRIM(ISNULL(B.ColorCode, ''))) = @p3 THEN 0
WHEN @p3 = '' THEN 0
ELSE 1
END,
CASE
WHEN @p4 <> '' AND LTRIM(RTRIM(ISNULL(B.ItemDim1Code, ''))) = @p4 THEN 0
WHEN @p4 = '' THEN 0
ELSE 1
END,
B.InvoiceDate DESC,
B.InvoiceNumber DESC
`
return mssqlDB.QueryRowContext(ctx, sqlText, sKodu, costDate, colorCode, itemDim1Code), nil
}
func GetProductionHasCostPurchaseHistoryByExpenseCode(
ctx context.Context,
mssqlDB *sql.DB,
sKodu string,
costDate string,
limit int,
) (*sql.Rows, error) {
sKodu = strings.TrimSpace(sKodu)
costDate = strings.TrimSpace(costDate)
if limit <= 0 {
limit = 250
}
sqlText := `
WITH BASE AS (
SELECT
A.InvoiceDate,
A.InvoiceNumber,
A.ProcessCode,
A.CurrAccTypeCode,
A.CurrAccCode,
A.ItemTypeCode,
A.ItemCode,
A.ColorCode,
A.ItemDim1Code,
A.Qty1,
A.Doc_Price,
A.Doc_Amount,
A.Doc_CurrencyCode
FROM AllInvoicesWithAttributes A
WHERE A.ProcessCode IN ('BP')
AND A.ATAtt01 IN (1, 2)
AND A.CompanyCode IN (1, 2, 5)
AND A.IsCompleted = 1
AND YEAR(A.InvoiceDate) >= 2022
AND LTRIM(RTRIM(A.ItemCode)) = @p1
AND (ISNULL(A.Doc_Price, 0) > 0 OR ISNULL(A.Doc_Amount, 0) > 0)
AND (NULLIF(@p2, '') IS NULL OR CONVERT(date, A.InvoiceDate) < CONVERT(date, NULLIF(@p2, ''), 23))
)
SELECT TOP (@p3)
'purchase' AS sourceType,
'MAN' AS priceType,
CONVERT(VARCHAR(16), B.InvoiceDate, 120) AS Tarih,
ISNULL(B.InvoiceNumber, '') AS FaturaKodu,
ISNULL(B.CurrAccCode, '') AS FirmaKodu,
ISNULL(CAD.CurrAccDescription, '') AS FirmaAciklama,
LTRIM(RTRIM(ISNULL(B.ItemCode, ''))) AS MasrafKodu,
ISNULL(ID.ItemDescription, '') AS MasrafDetay,
ISNULL(B.ColorCode, '') AS ColorCode,
ISNULL(COL.ColorDescription, '') AS ColorDescription,
ISNULL(B.ItemDim1Code, '') AS ItemDim1Code,
ISNULL(DIM1.ItemDim1Description, '') AS ItemDim1Description,
ISNULL(B.Qty1, 0) AS Miktar,
CASE
WHEN B.ProcessCode = 'EP' THEN ''
ELSE ISNULL(CI.UnitOfMeasureCode1, '')
END AS BIRIM,
ISNULL(B.Doc_Price, 0) AS EvrakFiyat,
ISNULL(B.Doc_Amount, 0) AS EvrakTutar,
ISNULL(B.Doc_CurrencyCode, '') AS EvrakDoviz
FROM BASE B
LEFT JOIN cdItem CI
ON CI.ItemTypeCode = B.ItemTypeCode
AND CI.ItemCode = B.ItemCode
OUTER APPLY (
SELECT TOP 1 ItemDescription
FROM cdItemDesc
WHERE ItemTypeCode = B.ItemTypeCode
AND ItemCode = B.ItemCode
AND LangCode = 'TR'
) ID
OUTER APPLY (
SELECT TOP 1 ItemDim1Description
FROM cdItemDim1Desc
WHERE ItemDim1Code = B.ItemDim1Code
AND LangCode = 'TR'
) DIM1
OUTER APPLY (
SELECT TOP 1 ColorDescription
FROM cdColorDesc
WHERE ColorCode = B.ColorCode
AND LangCode = 'TR'
) COL
OUTER APPLY (
SELECT TOP 1 CurrAccDescription
FROM cdCurrAccDesc
WHERE CurrAccTypeCode = B.CurrAccTypeCode
AND CurrAccCode = B.CurrAccCode
AND LangCode = 'TR'
) CAD
ORDER BY
B.InvoiceDate DESC,
B.InvoiceNumber DESC
`
return mssqlDB.QueryContext(ctx, sqlText, sKodu, costDate, limit)
}
func GetProductionHasCostRecipeHistoryByExpenseCode(
ctx context.Context,
uretimDB *sql.DB,
currentOnMLNo int,
sKodu string,
colorCode string,
costDate string,
limit int,
) (*sql.Rows, error) {
sKodu = strings.TrimSpace(sKodu)
colorCode = strings.TrimSpace(colorCode)
costDate = strings.TrimSpace(costDate)
if limit <= 0 {
limit = 250
}
sqlText := `
SELECT TOP (@p5)
'recipe' AS sourceType,
ISNULL(NULLIF(LTRIM(RTRIM(D.sFiyatTipi)), ''), 'SAF') AS priceType,
CONVERT(VARCHAR(16), COALESCE(D.dteIslemTarihiDeg, D.dteIslemTarihi, M.Tarihi, M.dteKayitTarihi), 120) AS dteIslemTarihi,
RTRIM(CONVERT(VARCHAR(32), ISNULL(D.nOnMLNo, 0))) AS nOnMLNo,
ISNULL(UF.FirmaKodu, '') AS FirmaKodu,
ISNULL(UF.FirmaAciklama, '') AS FirmaAciklama,
ISNULL(D.sKodu, '') AS sKodu,
ISNULL(D.sAciklama, '') AS sAciklama,
ISNULL(D.sRenk, '') AS sRenk,
ISNULL(D.lMiktar, 0) AS lMiktar,
ISNULL(D.sBirim, '') AS sBirim,
CASE
WHEN ISNULL(D.fiyat_girilen, 0) > 0 THEN ISNULL(D.fiyat_girilen, 0)
ELSE ISNULL(D.lDovizFiyati, 0)
END AS lDovizFiyati,
CASE
WHEN ISNULL(D.lDovizTutari, 0) > 0 THEN ISNULL(D.lDovizTutari, 0)
WHEN ISNULL(D.fiyat_girilen, 0) > 0 THEN ISNULL(D.fiyat_girilen, 0) * ISNULL(D.lMiktar, 0)
ELSE ISNULL(D.lDovizFiyati, 0) * ISNULL(D.lMiktar, 0)
END AS lDovizTutari,
CASE
WHEN LTRIM(RTRIM(ISNULL(D.fiyat_doviz, ''))) <> '' THEN LTRIM(RTRIM(D.fiyat_doviz))
WHEN LTRIM(RTRIM(ISNULL(D.sDovizCinsi, ''))) <> '' THEN LTRIM(RTRIM(D.sDovizCinsi))
WHEN LTRIM(RTRIM(ISNULL(M.sDovizCinsi, ''))) <> '' THEN LTRIM(RTRIM(M.sDovizCinsi))
ELSE 'USD'
END AS USD,
ISNULL(NULLIF(LTRIM(RTRIM(T.sAciklama3)), ''), N'DUMMY') AS DUMMY
FROM dbo.spUrtOnMLMasDet D
INNER JOIN dbo.spUrtOnMLMas M
ON M.nOnMLNo = D.nOnMLNo
LEFT JOIN dbo.spUrtOnMLHammaddeTuru T
ON T.nHammaddeTuruNo = D.nHammaddeTuruNo
OUTER APPLY (
SELECT TOP 1
ISNULL(F.sKodu, '') AS FirmaKodu,
ISNULL(F.sAciklama, '') AS FirmaAciklama
FROM dbo.spUrtSiparisDet SD
INNER JOIN dbo.spUrtSiparis SM
ON SM.nUrtSiparisID = SD.nUrtSiparisID
LEFT JOIN dbo.tbFirma F
ON F.nFirmaID = SM.nFirmaID
WHERE (
(ISNULL(M.nUrtReceteID, 0) > 0 AND SD.nUrtReceteID = M.nUrtReceteID)
OR (ISNULL(M.nUrtReceteID, 0) <= 0 AND LTRIM(RTRIM(ISNULL(SD.sMModelKodu, ''))) = LTRIM(RTRIM(ISNULL(M.UrunKodu, ''))))
)
AND SD.dteIslemTarihi <= COALESCE(D.dteIslemTarihiDeg, D.dteIslemTarihi, M.Tarihi, M.dteKayitTarihi)
ORDER BY SD.dteIslemTarihi DESC, SD.nUrtSiparisID DESC
) UF
WHERE LTRIM(RTRIM(D.sKodu)) = @p1
AND (@p2 <= 0 OR D.nOnMLNo <> @p2)
AND (
ISNULL(D.fiyat_girilen, 0) > 0
OR ISNULL(D.lDovizFiyati, 0) > 0
OR ISNULL(D.lDovizTutari, 0) > 0
)
AND (
NULLIF(@p3, '') IS NULL
OR CONVERT(date, COALESCE(D.dteIslemTarihiDeg, D.dteIslemTarihi, M.Tarihi, M.dteKayitTarihi)) < CONVERT(date, NULLIF(@p3, ''), 23)
)
ORDER BY
CASE
WHEN @p4 <> '' AND LTRIM(RTRIM(ISNULL(D.sRenk, ''))) = @p4 THEN 0
WHEN @p4 = '' THEN 0
ELSE 1
END,
COALESCE(D.dteIslemTarihiDeg, D.dteIslemTarihi, M.Tarihi, M.dteKayitTarihi) DESC,
D.nOnMLNo DESC,
D.nOnMLDetNo DESC
`
return uretimDB.QueryContext(ctx, sqlText, sKodu, currentOnMLNo, costDate, colorCode, limit)
}
func BuildProductionHasCostSimilarCodePrefix(sKodu string) string {
normalizedCode := strings.ToUpper(strings.TrimSpace(sKodu))
if normalizedCode == "" {
return ""
}
runes := []rune(normalizedCode)
if len(runes) <= 4 {
return normalizedCode
}
if len(runes) >= 5 && len(runes) > 1 && runes[1] == '.' {
return string(runes[:5])
}
return string(runes[:4])
}
func GetProductionHasCostPurchaseHistoryByCodePrefix(
ctx context.Context,
mssqlDB *sql.DB,
sKoduPrefix string,
costDate string,
limit int,
) (*sql.Rows, error) {
sKoduPrefix = strings.TrimSpace(sKoduPrefix)
costDate = strings.TrimSpace(costDate)
if limit <= 0 {
limit = 250
}
sqlText := `
WITH BASE AS (
SELECT
A.InvoiceDate,
A.InvoiceNumber,
A.ProcessCode,
A.CurrAccTypeCode,
A.CurrAccCode,
A.ItemTypeCode,
A.ItemCode,
A.ColorCode,
A.ItemDim1Code,
A.Qty1,
A.Doc_Price,
A.Doc_Amount,
A.Doc_CurrencyCode
FROM AllInvoicesWithAttributes A
WHERE A.ProcessCode IN ('BP')
AND A.ATAtt01 IN (1, 2)
AND A.CompanyCode IN (1, 2, 5)
AND A.IsCompleted = 1
AND YEAR(A.InvoiceDate) >= 2022
AND NULLIF(@p1, '') IS NOT NULL
AND LTRIM(RTRIM(A.ItemCode)) LIKE @p1 + '%'
AND (ISNULL(A.Doc_Price, 0) > 0 OR ISNULL(A.Doc_Amount, 0) > 0)
AND (NULLIF(@p2, '') IS NULL OR CONVERT(date, A.InvoiceDate) < CONVERT(date, NULLIF(@p2, ''), 23))
)
SELECT TOP (@p3)
'purchase' AS sourceType,
'BNZ' AS priceType,
CONVERT(VARCHAR(16), B.InvoiceDate, 120) AS Tarih,
ISNULL(B.InvoiceNumber, '') AS FaturaKodu,
ISNULL(B.CurrAccCode, '') AS FirmaKodu,
ISNULL(CAD.CurrAccDescription, '') AS FirmaAciklama,
LTRIM(RTRIM(ISNULL(B.ItemCode, ''))) AS MasrafKodu,
ISNULL(ID.ItemDescription, '') AS MasrafDetay,
ISNULL(B.ColorCode, '') AS ColorCode,
ISNULL(COL.ColorDescription, '') AS ColorDescription,
ISNULL(B.ItemDim1Code, '') AS ItemDim1Code,
ISNULL(DIM1.ItemDim1Description, '') AS ItemDim1Description,
ISNULL(B.Qty1, 0) AS Miktar,
CASE
WHEN B.ProcessCode = 'EP' THEN ''
ELSE ISNULL(CI.UnitOfMeasureCode1, '')
END AS BIRIM,
ISNULL(B.Doc_Price, 0) AS EvrakFiyat,
ISNULL(B.Doc_Amount, 0) AS EvrakTutar,
ISNULL(B.Doc_CurrencyCode, '') AS EvrakDoviz
FROM BASE B
LEFT JOIN cdItem CI
ON CI.ItemTypeCode = B.ItemTypeCode
AND CI.ItemCode = B.ItemCode
OUTER APPLY (
SELECT TOP 1 ItemDescription
FROM cdItemDesc
WHERE ItemTypeCode = B.ItemTypeCode
AND ItemCode = B.ItemCode
AND LangCode = 'TR'
) ID
OUTER APPLY (
SELECT TOP 1 ItemDim1Description
FROM cdItemDim1Desc
WHERE ItemDim1Code = B.ItemDim1Code
AND LangCode = 'TR'
) DIM1
OUTER APPLY (
SELECT TOP 1 ColorDescription
FROM cdColorDesc
WHERE ColorCode = B.ColorCode
AND LangCode = 'TR'
) COL
OUTER APPLY (
SELECT TOP 1 CurrAccDescription
FROM cdCurrAccDesc
WHERE CurrAccTypeCode = B.CurrAccTypeCode
AND CurrAccCode = B.CurrAccCode
AND LangCode = 'TR'
) CAD
ORDER BY
B.InvoiceDate DESC,
B.InvoiceNumber DESC
`
return mssqlDB.QueryContext(ctx, sqlText, sKoduPrefix, costDate, limit)
}
func GetProductionHasCostOnMLHistoryByCodePrefix(
ctx context.Context,
uretimDB *sql.DB,
sKoduPrefix string,
costDate string,
limit int,
) (*sql.Rows, error) {
sKoduPrefix = strings.TrimSpace(sKoduPrefix)
costDate = strings.TrimSpace(costDate)
if limit <= 0 {
limit = 250
}
sqlText := `
SELECT TOP (@p3)
'recipe' AS sourceType,
'BNZ' AS priceType,
CONVERT(VARCHAR(16), COALESCE(D.dteIslemTarihiDeg, D.dteIslemTarihi, M.Tarihi, M.dteKayitTarihi), 120) AS dteIslemTarihi,
RTRIM(CONVERT(VARCHAR(32), ISNULL(D.nOnMLNo, 0))) AS nOnMLNo,
ISNULL(UF.FirmaKodu, '') AS FirmaKodu,
ISNULL(UF.FirmaAciklama, '') AS FirmaAciklama,
ISNULL(D.sKodu, '') AS sKodu,
ISNULL(D.sAciklama, '') AS sAciklama,
ISNULL(D.sRenk, '') AS sRenk,
ISNULL(D.lMiktar, 0) AS lMiktar,
ISNULL(D.sBirim, '') AS sBirim,
CASE
WHEN ISNULL(D.fiyat_girilen, 0) > 0 THEN ISNULL(D.fiyat_girilen, 0)
ELSE ISNULL(D.lDovizFiyati, 0)
END AS lDovizFiyati,
CASE
WHEN ISNULL(D.lDovizTutari, 0) > 0 THEN ISNULL(D.lDovizTutari, 0)
WHEN ISNULL(D.fiyat_girilen, 0) > 0 THEN ISNULL(D.fiyat_girilen, 0) * ISNULL(D.lMiktar, 0)
ELSE ISNULL(D.lDovizFiyati, 0) * ISNULL(D.lMiktar, 0)
END AS lDovizTutari,
CASE
WHEN LTRIM(RTRIM(ISNULL(D.fiyat_doviz, ''))) <> '' THEN LTRIM(RTRIM(D.fiyat_doviz))
WHEN LTRIM(RTRIM(ISNULL(D.sDovizCinsi, ''))) <> '' THEN LTRIM(RTRIM(D.sDovizCinsi))
WHEN LTRIM(RTRIM(ISNULL(M.sDovizCinsi, ''))) <> '' THEN LTRIM(RTRIM(M.sDovizCinsi))
ELSE 'USD'
END AS USD,
ISNULL(NULLIF(LTRIM(RTRIM(D.sAciklama3)), ''), N'DUMMY') AS DUMMY
FROM dbo.spUrtOnMLMasDet D
INNER JOIN dbo.spUrtOnMLMas M
ON M.nOnMLNo = D.nOnMLNo
OUTER APPLY (
SELECT TOP 1
ISNULL(F.sKodu, '') AS FirmaKodu,
ISNULL(F.sAciklama, '') AS FirmaAciklama
FROM dbo.spUrtSiparisDet SD
INNER JOIN dbo.spUrtSiparis SM
ON SM.nUrtSiparisID = SD.nUrtSiparisID
LEFT JOIN dbo.tbFirma F
ON F.nFirmaID = SM.nFirmaID
WHERE (
(ISNULL(M.nUrtReceteID, 0) > 0 AND SD.nUrtReceteID = M.nUrtReceteID)
OR (ISNULL(M.nUrtReceteID, 0) <= 0 AND LTRIM(RTRIM(ISNULL(SD.sMModelKodu, ''))) = LTRIM(RTRIM(ISNULL(M.UrunKodu, ''))))
)
AND SD.dteIslemTarihi <= COALESCE(D.dteIslemTarihiDeg, D.dteIslemTarihi, M.Tarihi, M.dteKayitTarihi)
ORDER BY SD.dteIslemTarihi DESC, SD.nUrtSiparisID DESC
) UF
WHERE NULLIF(@p1, '') IS NOT NULL
AND LTRIM(RTRIM(ISNULL(D.sKodu, ''))) LIKE @p1 + '%'
AND (
ISNULL(D.fiyat_girilen, 0) > 0
OR ISNULL(D.lDovizFiyati, 0) > 0
OR ISNULL(D.lDovizTutari, 0) > 0
)
AND (
NULLIF(@p2, '') IS NULL
OR CONVERT(date, COALESCE(D.dteIslemTarihiDeg, D.dteIslemTarihi, M.Tarihi, M.dteKayitTarihi)) < CONVERT(date, NULLIF(@p2, ''), 23)
)
ORDER BY
COALESCE(D.dteIslemTarihiDeg, D.dteIslemTarihi, M.Tarihi, M.dteKayitTarihi) DESC,
D.nOnMLNo DESC,
D.nOnMLDetNo DESC
`
return uretimDB.QueryContext(ctx, sqlText, sKoduPrefix, costDate, limit)
}
func GetProductionHasCostOnMLHistoryByHammaddeTuruNo(
ctx context.Context,
uretimDB *sql.DB,
nHammaddeTuruNo string,
costDate string,
limit int,
) (*sql.Rows, error) {
nHammaddeTuruNo = strings.TrimSpace(nHammaddeTuruNo)
costDate = strings.TrimSpace(costDate)
if limit <= 0 {
limit = 250
}
sqlText := `
SELECT TOP (@p3)
'recipe' AS sourceType,
'BNZ' AS priceType,
CONVERT(VARCHAR(16), COALESCE(D.dteIslemTarihiDeg, D.dteIslemTarihi, M.Tarihi, M.dteKayitTarihi), 120) AS dteIslemTarihi,
RTRIM(CONVERT(VARCHAR(32), ISNULL(D.nOnMLNo, 0))) AS nOnMLNo,
ISNULL(UF.FirmaKodu, '') AS FirmaKodu,
ISNULL(UF.FirmaAciklama, '') AS FirmaAciklama,
ISNULL(D.sKodu, '') AS sKodu,
ISNULL(D.sAciklama, '') AS sAciklama,
ISNULL(D.sRenk, '') AS sRenk,
ISNULL(D.lMiktar, 0) AS lMiktar,
ISNULL(D.sBirim, '') AS sBirim,
CASE
WHEN ISNULL(D.fiyat_girilen, 0) > 0 THEN ISNULL(D.fiyat_girilen, 0)
ELSE ISNULL(D.lDovizFiyati, 0)
END AS lDovizFiyati,
CASE
WHEN ISNULL(D.lDovizTutari, 0) > 0 THEN ISNULL(D.lDovizTutari, 0)
WHEN ISNULL(D.fiyat_girilen, 0) > 0 THEN ISNULL(D.fiyat_girilen, 0) * ISNULL(D.lMiktar, 0)
ELSE ISNULL(D.lDovizFiyati, 0) * ISNULL(D.lMiktar, 0)
END AS lDovizTutari,
CASE
WHEN LTRIM(RTRIM(ISNULL(D.fiyat_doviz, ''))) <> '' THEN LTRIM(RTRIM(D.fiyat_doviz))
WHEN LTRIM(RTRIM(ISNULL(D.sDovizCinsi, ''))) <> '' THEN LTRIM(RTRIM(D.sDovizCinsi))
WHEN LTRIM(RTRIM(ISNULL(M.sDovizCinsi, ''))) <> '' THEN LTRIM(RTRIM(M.sDovizCinsi))
ELSE 'USD'
END AS USD,
ISNULL(NULLIF(LTRIM(RTRIM(D.sAciklama3)), ''), N'DUMMY') AS DUMMY
FROM dbo.spUrtOnMLMasDet D
INNER JOIN dbo.spUrtOnMLMas M
ON M.nOnMLNo = D.nOnMLNo
OUTER APPLY (
SELECT TOP 1
ISNULL(F.sKodu, '') AS FirmaKodu,
ISNULL(F.sAciklama, '') AS FirmaAciklama
FROM dbo.spUrtSiparisDet SD
INNER JOIN dbo.spUrtSiparis SM
ON SM.nUrtSiparisID = SD.nUrtSiparisID
LEFT JOIN dbo.tbFirma F
ON F.nFirmaID = SM.nFirmaID
WHERE (
(ISNULL(M.nUrtReceteID, 0) > 0 AND SD.nUrtReceteID = M.nUrtReceteID)
OR (ISNULL(M.nUrtReceteID, 0) <= 0 AND LTRIM(RTRIM(ISNULL(SD.sMModelKodu, ''))) = LTRIM(RTRIM(ISNULL(M.UrunKodu, ''))))
)
AND SD.dteIslemTarihi <= COALESCE(D.dteIslemTarihiDeg, D.dteIslemTarihi, M.Tarihi, M.dteKayitTarihi)
ORDER BY SD.dteIslemTarihi DESC, SD.nUrtSiparisID DESC
) UF
WHERE D.nHammaddeTuruNo = @p1
AND ISNULL(D.fiyat_girilen, 0) > 0
AND (
NULLIF(@p2, '') IS NULL
OR CONVERT(date, COALESCE(D.dteIslemTarihiDeg, D.dteIslemTarihi, M.Tarihi, M.dteKayitTarihi)) < CONVERT(date, NULLIF(@p2, ''), 23)
)
ORDER BY
COALESCE(D.dteIslemTarihiDeg, D.dteIslemTarihi, M.Tarihi, M.dteKayitTarihi) DESC,
D.nOnMLNo DESC,
D.nOnMLDetNo DESC
`
return uretimDB.QueryContext(ctx, sqlText, nHammaddeTuruNo, costDate, limit)
}
func GetProductionHasCostSimilarItemHistory(
ctx context.Context,
mssqlDB *sql.DB,
uretimDB *sql.DB,
nHammaddeTuruNo string,
costDate string,
limit int,
) ([]any, error) {
if limit <= 0 {
limit = 50
}
// 1. Satinalma (V3) tarafini sorgula - Simdilik V3 tarafi icin hammadde turu eslestirmesi belirsiz oldugundan pasif
/*
v3Sql := `
SELECT TOP (@p2)
'purchase' AS sourceType,
'BNZ' AS priceType,
CONVERT(VARCHAR(16), A.InvoiceDate, 120) AS Tarih,
ISNULL(A.InvoiceNumber, '') AS FaturaKodu,
LTRIM(RTRIM(ISNULL(A.ItemCode, ''))) AS MasrafKodu,
ISNULL(ID.ItemDescription, '') AS MasrafDetay,
ISNULL(A.ColorCode, '') AS ColorCode,
ISNULL(A.Qty1, 0) AS Miktar,
ISNULL(CI.UnitOfMeasureCode1, '') AS BIRIM,
ISNULL(A.Doc_Price, 0) AS EvrakFiyat,
ISNULL(A.Doc_CurrencyCode, '') AS EvrakDoviz
FROM AllInvoicesWithAttributes A
LEFT JOIN cdItem CI ON CI.ItemTypeCode = A.ItemTypeCode AND CI.ItemCode = A.ItemCode
OUTER APPLY (
SELECT TOP 1 ItemDescription FROM cdItemDesc
WHERE ItemTypeCode = A.ItemTypeCode AND ItemCode = A.ItemCode AND LangCode = 'TR'
) ID
WHERE A.ProcessCode IN ('BP')
AND A.ATAtt01 IN (1, 2)
AND A.CompanyCode IN (1, 2, 5)
AND A.IsCompleted = 1
AND YEAR(A.InvoiceDate) >= 2023
AND (NULLIF(@p1, '') IS NULL OR CONVERT(date, A.InvoiceDate) < CONVERT(date, NULLIF(@p1, ''), 23))
AND EXISTS (
-- Hammadde turu eslestirmesi (V3 tarafindaki karsiligi varsa)
SELECT 1 FROM cdItem WHERE ItemCode = A.ItemCode
)
ORDER BY A.InvoiceDate DESC
`
*/
// 2. Uretim (Recete) tarafini sorgula
uretimSql := `
SELECT TOP (@p3)
'recipe' AS sourceType,
'BNZ' AS priceType,
CONVERT(VARCHAR(16), ISNULL(M.Tarihi, M.dteKayitTarihi), 120) AS dteIslemTarihi,
RTRIM(CONVERT(VARCHAR(32), ISNULL(D.nOnMLNo, 0))) AS nOnMLNo,
ISNULL(D.sKodu, '') AS sKodu,
ISNULL(D.sAciklama, '') AS sAciklama,
ISNULL(D.sRenk, '') AS sRenk,
ISNULL(D.lMiktar, 0) AS lMiktar,
ISNULL(D.sBirim, '') AS sBirim,
ISNULL(D.lDovizFiyati, 0) AS lDovizFiyati,
CASE
WHEN LTRIM(RTRIM(ISNULL(D.fiyat_doviz, ''))) <> '' THEN LTRIM(RTRIM(D.fiyat_doviz))
WHEN LTRIM(RTRIM(ISNULL(M.sDovizCinsi, ''))) <> '' THEN LTRIM(RTRIM(M.sDovizCinsi))
ELSE 'USD'
END AS USD
FROM dbo.spUrtOnMLMasDet D
INNER JOIN dbo.spUrtOnMLMas M ON M.nOnMLNo = D.nOnMLNo
WHERE D.nHammaddeTuruNo = @p1
AND (NULLIF(@p2, '') IS NULL OR CONVERT(date, ISNULL(M.Tarihi, M.dteKayitTarihi)) < CONVERT(date, NULLIF(@p2, ''), 23))
ORDER BY ISNULL(M.Tarihi, M.dteKayitTarihi) DESC
`
// Not: nHammaddeTuruNo parametresine gore sadece Reçete tarafı şu an doğrudan filtrelenebiliyor.
// V3 tarafı için ItemTypeCode veya özel bir grup kodu gerekebilir.
// Kullanıcının talebi üzerine Reçete (URETIM) odaklı sorguyu önceliklendiriyoruz.
rows, err := uretimDB.QueryContext(ctx, uretimSql, nHammaddeTuruNo, costDate, limit)
if err != nil {
return nil, err
}
defer rows.Close()
var results []any
for rows.Next() {
// Basit bir map veya struct ile dondurebiliriz
var row struct {
SourceType string `json:"sourceType"`
PriceType string `json:"priceType"`
Tarih string `json:"Tarih"`
EvrakKodu string `json:"EvrakKodu"`
Kod string `json:"Kod"`
Aciklama string `json:"Aciklama"`
Renk string `json:"Renk"`
Miktar float64 `json:"Miktar"`
Birim string `json:"Birim"`
Fiyat float64 `json:"Fiyat"`
Doviz string `json:"Doviz"`
}
if err := rows.Scan(
&row.SourceType, &row.PriceType, &row.Tarih, &row.EvrakKodu,
&row.Kod, &row.Aciklama, &row.Renk, &row.Miktar, &row.Birim,
&row.Fiyat, &row.Doviz,
); err == nil {
results = append(results, row)
}
}
return results, nil
}

View File

@@ -760,7 +760,16 @@ func getOrderLinesFromDB(db *sql.DB, orderID string) ([]OrderLineRaw, error) {
P.ProductAtt01Desc, P.ProductAtt01Desc,
P.ProductAtt02Desc, P.ProductAtt02Desc,
P.ProductAtt44Desc, P.ProductAtt44Desc,
L.IsClosed, CASE
WHEN ISNULL(L.IsClosed, 0) = 1
OR EXISTS (
SELECT 1
FROM BAGGI_V3.dbo.trInvoiceLine il WITH (NOLOCK)
WHERE il.OrderLineID = L.OrderLineID
)
THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END AS IsClosed,
L.WithHoldingTaxTypeCode, L.WithHoldingTaxTypeCode,
L.DOVCode, L.DOVCode,
L.PlannedDateOfLading, L.PlannedDateOfLading,

View File

@@ -0,0 +1,2048 @@
package routes
import (
"bssapp-backend/auth"
"bssapp-backend/db"
"bssapp-backend/models"
"bssapp-backend/queries"
"bssapp-backend/utils"
"context"
"database/sql"
"encoding/json"
"log"
"net/http"
"strconv"
"strings"
"time"
)
func logProductionHasCostDetailEditorOptionItemDiagnostics(ctx context.Context, uretimDB *sql.DB, search string, limit int) {
logger := utils.SlogFromContext(ctx).With(
"handler", "production-product-costing.detail-editor-options.diagnostics",
"search", search,
"limit", limit,
)
expectedColumns := []string{
"nStokID",
"sKodu",
"sAciklama",
"sModel",
"sBirimCinsi1",
"IsBlocked",
}
probes := []struct {
name string
sqlText string
}{
{
name: "projection:nStokID",
sqlText: `SELECT TOP 1 RTRIM(CONVERT(VARCHAR(32), ISNULL(S.nStokID, 0))) FROM dbo.tbStok S`,
},
{
name: "projection:sKodu",
sqlText: `SELECT TOP 1 LTRIM(RTRIM(CONVERT(NVARCHAR(255), ISNULL(S.sKodu, '')))) FROM dbo.tbStok S`,
},
{
name: "projection:sAciklama",
sqlText: `SELECT TOP 1 LTRIM(RTRIM(CONVERT(NVARCHAR(255), ISNULL(S.sAciklama, '')))) FROM dbo.tbStok S`,
},
{
name: "projection:sModel",
sqlText: `SELECT TOP 1 LTRIM(RTRIM(CONVERT(NVARCHAR(255), ISNULL(S.sModel, '')))) FROM dbo.tbStok S`,
},
{
name: "projection:sBirimCinsi1",
sqlText: `SELECT TOP 1 LTRIM(RTRIM(CONVERT(NVARCHAR(64), ISNULL(S.sBirimCinsi1, '')))) FROM dbo.tbStok S`,
},
{
name: "projection:IsBlocked",
sqlText: `SELECT TOP 1 RTRIM(CONVERT(VARCHAR(32), ISNULL(S.IsBlocked, 0))) FROM dbo.tbStok S`,
},
{
name: "filter:model_like_count",
sqlText: `SELECT RTRIM(CONVERT(VARCHAR(32), COUNT(1))) FROM dbo.tbStok S WHERE LTRIM(RTRIM(CONVERT(NVARCHAR(255), ISNULL(S.sModel, '')))) LIKE '_.%'`,
},
{
name: "filter:isblocked_count",
sqlText: `SELECT RTRIM(CONVERT(VARCHAR(32), COUNT(1))) FROM dbo.tbStok S WHERE ISNULL(S.IsBlocked, 0) = 0`,
},
{
name: "filter:final_count",
sqlText: `SELECT RTRIM(CONVERT(VARCHAR(32), COUNT(1))) FROM dbo.tbStok S WHERE ISNULL(S.IsBlocked, 0) = 0 AND LTRIM(RTRIM(CONVERT(NVARCHAR(255), ISNULL(S.sModel, '')))) LIKE '_.%'`,
},
}
log.Printf("[ProductionHasCostDetailEditorOptions] item diagnostics start search=%q limit=%d", search, limit)
for _, columnName := range expectedColumns {
var exists int
err := uretimDB.QueryRowContext(
ctx,
`SELECT COUNT(1)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'dbo'
AND TABLE_NAME = 'tbStok'
AND COLUMN_NAME = @p1`,
columnName,
).Scan(&exists)
if err != nil {
logger.Error("query error", "err", err)
log.Printf("⚠️ [ProductionHasCostDetailEditorOptions] item diagnostics column check error column=%s err=%v", columnName, err)
continue
}
log.Printf("[ProductionHasCostDetailEditorOptions] item diagnostics column=%s exists=%t", columnName, exists > 0)
}
for _, probe := range probes {
var sample sql.NullString
err := uretimDB.QueryRowContext(ctx, probe.sqlText).Scan(&sample)
if err != nil {
log.Printf("⚠️ [ProductionHasCostDetailEditorOptions] item diagnostics probe=%s err=%v", probe.name, err)
continue
}
log.Printf("[ProductionHasCostDetailEditorOptions] item diagnostics probe=%s sample=%q", probe.name, strings.TrimSpace(sample.String))
}
log.Printf("[ProductionHasCostDetailEditorOptions] item diagnostics end search=%q limit=%d", search, limit)
}
// GET /api/pricing/production-product-costing/no-cost-products
func GetProductionNoCostProductsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
uretimDB := db.GetUretimDB()
if uretimDB == nil {
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
return
}
search := strings.TrimSpace(r.URL.Query().Get("search"))
fromDate := strings.TrimSpace(r.URL.Query().Get("from_date"))
rows, err := queries.GetProductionNoCostProducts(r.Context(), uretimDB, fromDate, search)
if err != nil {
log.Printf("❌ [ProductionNoCost] query error: %v", err)
http.Error(w, "Veritabanı hatası", http.StatusInternalServerError)
return
}
defer rows.Close()
list := make([]models.ProductionNoCostProductRow, 0, 200)
for rows.Next() {
var item models.ProductionNoCostProductRow
if err := rows.Scan(
&item.UretimSekli,
&item.UrtSiparisNo,
&item.IslemTarihi,
&item.FirmaKodu,
&item.FirmaAdi,
&item.SonIsEmriVeren,
&item.MMiktarG,
&item.MModelKodu,
&item.Kodu,
&item.ModelAdi,
&item.SKullaniciAdi,
&item.SKullaniciGunc,
); err != nil {
log.Printf("⚠️ [ProductionNoCost] scan error: %v", err)
continue
}
list = append(list, item)
}
if err := rows.Err(); err != nil {
log.Printf("⚠️ [ProductionNoCost] rows error: %v", err)
http.Error(w, "Veritabanı satır hatası", http.StatusInternalServerError)
return
}
_ = json.NewEncoder(w).Encode(list)
}
// GET /api/pricing/production-product-costing/has-cost-products
func GetProductionHasCostProductsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
uretimDB := db.GetUretimDB()
if uretimDB == nil {
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
return
}
search := strings.TrimSpace(r.URL.Query().Get("search"))
offset := parsePositiveIntOrDefault(r.URL.Query().Get("offset"), 0)
limit := parsePositiveIntOrDefault(r.URL.Query().Get("limit"), 300)
if limit > 1000 {
limit = 1000
}
rows, err := queries.GetProductionHasCostProducts(r.Context(), uretimDB, search, offset, limit)
if err != nil {
log.Printf("❌ [ProductionHasCost] query error: %v", err)
http.Error(w, "Veritabanı hatası", http.StatusInternalServerError)
return
}
defer rows.Close()
list := make([]models.ProductionHasCostProductRow, 0, 200)
for rows.Next() {
var item models.ProductionHasCostProductRow
if err := rows.Scan(
&item.UretimSekli,
&item.NOnMLNo,
&item.UrunKodu,
&item.UrunAdi,
&item.Tarihi,
&item.DteKayitTarihi,
&item.SKullaniciAdi,
&item.LTutarTL,
&item.LTutarUSD,
&item.LTutarEURO,
&item.DteGuncellemeTarihi,
&item.SGuncellemeKullaniciAdi,
&item.NUrtReceteID,
&item.SAciklama,
&item.SonSiparisTarihi,
&item.MaliyetDurumu,
); err != nil {
log.Printf("⚠️ [ProductionHasCost] scan error: %v", err)
continue
}
list = append(list, item)
}
if err := rows.Err(); err != nil {
log.Printf("⚠️ [ProductionHasCost] rows error: %v", err)
http.Error(w, "Veritabanı satır hatası", http.StatusInternalServerError)
return
}
_ = json.NewEncoder(w).Encode(list)
}
// GET /api/pricing/production-product-costing/has-cost-history
func GetProductionHasCostHistoryHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
uretimDB := db.GetUretimDB()
if uretimDB == nil {
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
return
}
productCode := strings.TrimSpace(r.URL.Query().Get("urun_kodu"))
if productCode == "" {
http.Error(w, "urun_kodu zorunlu", http.StatusBadRequest)
return
}
rows, err := queries.GetProductionHasCostHistoryByProductCode(r.Context(), uretimDB, productCode)
if err != nil {
log.Printf("❌ [ProductionHasCostHistory] query error: %v", err)
http.Error(w, "Veritabanı hatası", http.StatusInternalServerError)
return
}
defer rows.Close()
list := make([]models.ProductionHasCostHistoryRow, 0, 100)
for rows.Next() {
var item models.ProductionHasCostHistoryRow
if err := rows.Scan(
&item.NOnMLNo,
&item.UrunKodu,
&item.UrunAdi,
&item.Tarihi,
&item.SKullaniciAdi,
&item.LTutarUSD,
&item.LTutarTL,
&item.LTutarEURO,
&item.SDovizCinsi,
&item.LTutarDoviz,
&item.DteGuncellemeTarihi,
&item.SGuncellemeKullaniciAdi,
&item.NUrtReceteID,
&item.SAciklama,
); err != nil {
log.Printf("⚠️ [ProductionHasCostHistory] scan error: %v", err)
continue
}
list = append(list, item)
}
if err := rows.Err(); err != nil {
log.Printf("⚠️ [ProductionHasCostHistory] rows error: %v", err)
http.Error(w, "Veritabanı satır hatası", http.StatusInternalServerError)
return
}
_ = json.NewEncoder(w).Encode(list)
}
// GET /api/pricing/production-product-costing/has-cost-detail-groups
func GetProductionHasCostDetailGroupsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
uretimDB := db.GetUretimDB()
if uretimDB == nil {
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
return
}
detailSource := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("detail_source")))
recipeCode := strings.TrimSpace(r.URL.Query().Get("recete_kodu"))
productCode := strings.TrimSpace(r.URL.Query().Get("urun_kodu"))
traceID := utils.TraceIDFromRequest(r)
ctx := utils.ContextWithTraceID(r.Context(), traceID)
logger := utils.SlogFromContext(ctx).With(
"handler", "production-product-costing.detail-groups",
"detail_source", detailSource,
"urun_kodu", productCode,
"recete_kodu", recipeCode,
)
if detailSource == "no-cost" || recipeCode != "" {
if recipeCode == "" {
logger.Warn("request invalid", "reason", "missing recete_kodu")
http.Error(w, "recete_kodu zorunlu", http.StatusBadRequest)
return
}
logger.Info("request start")
rows, err := queries.GetProductionNoCostDetailRowsByRecipeCode(ctx, uretimDB, recipeCode, productCode)
if err != nil {
logger.Error("query error", "err", err)
log.Printf("❌ [ProductionNoCostDetailGroups] query error: %v", err)
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
return
}
defer rows.Close()
groups := make([]models.ProductionHasCostDetailGroup, 0, 16)
groupIndexByName := map[string]int{}
scannedRows := 0
scanErrors := 0
for rows.Next() {
var (
groupName string
groupTotal float64
groupTotalUSD float64
fiyatGirilen sql.NullFloat64
fiyatDoviz sql.NullString
maliyeteDahil sql.NullBool
cmPriceTypeID sql.NullInt64
item models.ProductionHasCostDetailGroupItem
)
if err := rows.Scan(
&groupName,
&groupTotal,
&groupTotalUSD,
&item.NOnMLNo,
&item.NOnMLDetNo,
&item.NHammaddeTuruNo,
&item.SKodu,
&item.SAciklama,
&item.SRenk,
&item.SBeden,
&item.SAciklama2,
&item.LMiktar,
&item.LFiyat,
&item.LTutar,
&item.SFiyatTipi,
&item.SDovizCinsi,
&item.LDovizKuru,
&item.LDovizFiyati,
&fiyatGirilen,
&fiyatDoviz,
&maliyeteDahil,
&cmPriceTypeID,
&item.USDTutar,
&item.EURTutar,
&item.GBPTutar,
&item.SBirim,
&item.SHammaddeTuruAdi,
&item.SParcaAdi,
); err != nil {
scanErrors += 1
logger.Warn("scan error", "scan_index", scannedRows+scanErrors+1, "err", err)
log.Printf("⚠️ [ProductionNoCostDetailGroups] scan error: %v", err)
continue
}
scannedRows += 1
if fiyatGirilen.Valid {
item.FiyatGirilen = new(float64)
*item.FiyatGirilen = fiyatGirilen.Float64
}
if fiyatDoviz.Valid {
item.FiyatDoviz = strings.TrimSpace(fiyatDoviz.String)
}
item.MaliyeteDahil = !maliyeteDahil.Valid || maliyeteDahil.Bool
if cmPriceTypeID.Valid {
value := int(cmPriceTypeID.Int64)
item.CMPriceTypeID = &value
}
idx, ok := groupIndexByName[groupName]
if !ok {
groups = append(groups, models.ProductionHasCostDetailGroup{
SAciklama3: groupName,
TotalTutar: groupTotal,
TotalUSDTutar: groupTotalUSD,
Items: make([]models.ProductionHasCostDetailGroupItem, 0, 8),
})
idx = len(groups) - 1
groupIndexByName[groupName] = idx
}
groups[idx].Items = append(groups[idx].Items, item)
}
if err := rows.Err(); err != nil {
logger.Error("rows error", "err", err)
log.Printf("⚠️ [ProductionNoCostDetailGroups] rows error: %v", err)
http.Error(w, "Veritabani satir hatasi", http.StatusInternalServerError)
return
}
logger.Info("request done", "group_count", len(groups), "row_count", scannedRows, "scan_errors", scanErrors)
log.Printf("[ProductionNoCostDetailGroups] done recete_kodu=%s groups=%d", recipeCode, len(groups))
_ = json.NewEncoder(w).Encode(groups)
return
}
rawOnMLNo := strings.TrimSpace(r.URL.Query().Get("n_onml_no"))
nOnMLNo, err := strconv.Atoi(rawOnMLNo)
if err != nil || nOnMLNo <= 0 {
logger.Warn("request invalid", "reason", "invalid n_onml_no", "raw_n_onml_no", rawOnMLNo)
http.Error(w, "n_onml_no zorunlu ve pozitif sayi olmali", http.StatusBadRequest)
return
}
logger = logger.With("n_onml_no", nOnMLNo)
logger.Info("request start")
log.Printf("[ProductionHasCostDetailGroups] start n_onml_no=%d", nOnMLNo)
rows, err := queries.GetProductionHasCostDetailRowsByOnMLNo(ctx, uretimDB, nOnMLNo)
if err != nil {
logger.Error("query error", "err", err)
log.Printf("❌ [ProductionHasCostDetailGroups] query error: %v", err)
http.Error(w, "Veritabanı hatası", http.StatusInternalServerError)
return
}
defer rows.Close()
groups := make([]models.ProductionHasCostDetailGroup, 0, 16)
groupIndexByName := map[string]int{}
scannedRows := 0
scanErrors := 0
for rows.Next() {
var (
groupName string
groupTotal float64
groupTotalUSD float64
fiyatGirilen sql.NullFloat64
fiyatDoviz sql.NullString
maliyeteDahil sql.NullBool
cmPriceTypeID sql.NullInt64
item models.ProductionHasCostDetailGroupItem
)
if err := rows.Scan(
&groupName,
&groupTotal,
&groupTotalUSD,
&item.NOnMLNo,
&item.NOnMLDetNo,
&item.NHammaddeTuruNo,
&item.SKodu,
&item.SAciklama,
&item.SRenk,
&item.SBeden,
&item.SAciklama2,
&item.LMiktar,
&item.LFiyat,
&item.LTutar,
&item.SFiyatTipi,
&item.SDovizCinsi,
&item.LDovizKuru,
&item.LDovizFiyati,
&fiyatGirilen,
&fiyatDoviz,
&maliyeteDahil,
&cmPriceTypeID,
&item.USDTutar,
&item.EURTutar,
&item.GBPTutar,
&item.SBirim,
&item.SHammaddeTuruAdi,
&item.SParcaAdi,
); err != nil {
log.Printf("⚠️ [ProductionHasCostDetailGroups] scan error: %v", err)
continue
}
scannedRows += 1
if fiyatGirilen.Valid {
item.FiyatGirilen = new(float64)
*item.FiyatGirilen = fiyatGirilen.Float64
}
if fiyatDoviz.Valid {
item.FiyatDoviz = strings.TrimSpace(fiyatDoviz.String)
}
item.MaliyeteDahil = maliyeteDahil.Valid && maliyeteDahil.Bool
if cmPriceTypeID.Valid {
value := int(cmPriceTypeID.Int64)
item.CMPriceTypeID = &value
}
idx, ok := groupIndexByName[groupName]
if !ok {
groups = append(groups, models.ProductionHasCostDetailGroup{
SAciklama3: groupName,
TotalTutar: groupTotal,
TotalUSDTutar: groupTotalUSD,
Items: make([]models.ProductionHasCostDetailGroupItem, 0, 24),
})
idx = len(groups) - 1
groupIndexByName[groupName] = idx
}
groups[idx].Items = append(groups[idx].Items, item)
}
if err := rows.Err(); err != nil {
log.Printf("⚠️ [ProductionHasCostDetailGroups] rows error: %v", err)
http.Error(w, "Veritabanı satır hatası", http.StatusInternalServerError)
return
}
logger.Info("request done", "group_count", len(groups), "row_count", scannedRows, "scan_errors", scanErrors)
log.Printf("[ProductionHasCostDetailGroups] done n_onml_no=%d groups=%d rows=%d scan_errors=%d", nOnMLNo, len(groups), scannedRows, scanErrors)
_ = json.NewEncoder(w).Encode(groups)
}
// GET /api/pricing/production-product-costing/has-cost-detail-header
func GetProductionHasCostDetailHeaderHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
uretimDB := db.GetUretimDB()
if uretimDB == nil {
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
return
}
mssqlDB := db.GetDB()
detailSource := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("detail_source")))
recipeCode := strings.TrimSpace(r.URL.Query().Get("recete_kodu"))
productCode := strings.TrimSpace(r.URL.Query().Get("urun_kodu"))
traceID := utils.TraceIDFromRequest(r)
ctx := utils.ContextWithTraceID(r.Context(), traceID)
logger := utils.SlogFromContext(ctx).With(
"handler", "production-product-costing.detail-header",
"detail_source", detailSource,
"urun_kodu", productCode,
"recete_kodu", recipeCode,
)
if detailSource == "no-cost" || recipeCode != "" {
if recipeCode == "" {
logger.Warn("request invalid", "reason", "missing recete_kodu")
http.Error(w, "recete_kodu zorunlu", http.StatusBadRequest)
return
}
logger.Info("request start")
row, err := queries.GetProductionNoCostDetailHeaderByRecipeCode(ctx, uretimDB, recipeCode, productCode)
if err != nil {
logger.Error("query prepare error", "err", err)
log.Printf("❌ [ProductionNoCostDetailHeader] query prepare error: %v", err)
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
return
}
var item models.ProductionHasCostDetailHeader
if err := row.Scan(
&item.UretimiYapanFirma,
&item.SonIsEmriVeren,
&item.NOnMLNo,
&item.UrunKodu,
&item.UrunAdi,
&item.UretimSekliID,
&item.UretimSekli,
&item.DteKayitTarihi,
&item.SKullaniciAdi,
&item.LTutarTL,
&item.LTutarUSD,
&item.LTutarEURO,
&item.LTutarGBP,
&item.SDovizCinsi,
&item.LTutarDoviz,
&item.DteGuncellemeTarihi,
&item.SGuncellemeKullaniciAdi,
&item.NUrtReceteID,
); err != nil {
logger.Warn("scan or not found", "err", err)
if err == sql.ErrNoRows {
logger.Warn("row not found")
http.Error(w, "Kayit bulunamadi", http.StatusNotFound)
return
}
log.Printf("❌ [ProductionNoCostDetailHeader] scan error: %v", err)
http.Error(w, "Veritabani satir hatasi", http.StatusInternalServerError)
return
}
logger.Info("request done", "n_urt_recete_id", item.NUrtReceteID, "urun_kodu", item.UrunKodu)
if mssqlDB != nil {
ana, alt, err := queries.GetProductAnaAltGrupByUrunKodu(ctx, mssqlDB, item.UrunKodu)
if err != nil {
logger.Warn("product group query error", "err", err)
} else {
item.UrunAnaGrubu = ana
item.UrunAltGrubu = alt
}
}
_ = json.NewEncoder(w).Encode(item)
return
}
rawOnMLNo := strings.TrimSpace(r.URL.Query().Get("n_onml_no"))
nOnMLNo, err := strconv.Atoi(rawOnMLNo)
if err != nil || nOnMLNo <= 0 {
logger.Warn("request invalid", "reason", "invalid n_onml_no", "raw_n_onml_no", rawOnMLNo)
http.Error(w, "n_onml_no zorunlu ve pozitif sayi olmali", http.StatusBadRequest)
return
}
logger = logger.With("n_onml_no", nOnMLNo)
logger.Info("request start")
row, err := queries.GetProductionHasCostDetailHeaderByOnMLNo(ctx, uretimDB, nOnMLNo)
if err != nil {
logger.Error("query prepare error", "err", err)
log.Printf("❌ [ProductionHasCostDetailHeader] query prepare error: %v", err)
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
return
}
var item models.ProductionHasCostDetailHeader
if err := row.Scan(
&item.UretimiYapanFirma,
&item.SonIsEmriVeren,
&item.NOnMLNo,
&item.UrunKodu,
&item.UrunAdi,
&item.UretimSekliID,
&item.UretimSekli,
&item.DteKayitTarihi,
&item.SKullaniciAdi,
&item.LTutarTL,
&item.LTutarUSD,
&item.LTutarEURO,
&item.LTutarGBP,
&item.SDovizCinsi,
&item.LTutarDoviz,
&item.DteGuncellemeTarihi,
&item.SGuncellemeKullaniciAdi,
&item.NUrtReceteID,
); err != nil {
logger.Warn("scan or not found", "err", err)
if err == sql.ErrNoRows {
http.Error(w, "Kayit bulunamadi", http.StatusNotFound)
return
}
log.Printf("❌ [ProductionHasCostDetailHeader] scan error: %v", err)
http.Error(w, "Veritabani satir hatasi", http.StatusInternalServerError)
return
}
logger.Info("request done", "n_onml_no", item.NOnMLNo, "urun_kodu", item.UrunKodu, "n_urt_recete_id", item.NUrtReceteID)
if mssqlDB != nil {
ana, alt, err := queries.GetProductAnaAltGrupByUrunKodu(ctx, mssqlDB, item.UrunKodu)
if err != nil {
logger.Warn("product group query error", "err", err)
} else {
item.UrunAnaGrubu = ana
item.UrunAltGrubu = alt
}
}
_ = json.NewEncoder(w).Encode(item)
}
// GET /api/pricing/production-product-costing/production-types
func GetProductionTypesHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
uretimDB := db.GetUretimDB()
if uretimDB == nil {
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
return
}
traceID := utils.TraceIDFromRequest(r)
ctx := utils.ContextWithTraceID(r.Context(), traceID)
logger := utils.SlogFromContext(ctx).With("handler", "production-product-costing.production-types")
logger.Info("request start")
rows, err := queries.GetProductionTypes(ctx, uretimDB)
if err != nil {
logger.Error("query error", "err", err)
log.Printf("❌ [ProductionTypes] query error: %v", err)
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
return
}
defer rows.Close()
var types []models.ProductionType
for rows.Next() {
var t models.ProductionType
if err := rows.Scan(&t.ID, &t.Aciklama); err != nil {
logger.Warn("scan error", "err", err)
log.Printf("⚠️ [ProductionTypes] scan error: %v", err)
continue
}
types = append(types, t)
}
if err := rows.Err(); err != nil {
logger.Error("rows error", "err", err)
http.Error(w, "Veritabani satir hatasi", http.StatusInternalServerError)
return
}
logger.Info("request done", "row_count", len(types))
_ = json.NewEncoder(w).Encode(types)
}
// GET /api/pricing/production-product-costing/detail-editor-options
func GetProductionHasCostDetailEditorOptionsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
traceID := utils.TraceIDFromRequest(r)
ctx := utils.ContextWithTraceID(r.Context(), traceID)
kind := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("kind")))
search := strings.TrimSpace(r.URL.Query().Get("search"))
limit := parsePositiveIntOrDefault(r.URL.Query().Get("limit"), 100)
if limit > 200 {
limit = 200
}
logger := utils.SlogFromContext(ctx).With(
"handler", "production-product-costing.detail-editor-options",
"kind", kind,
"search", search,
"limit", limit,
)
logger.Info("request start")
switch kind {
case "hammadde":
uretimDB := db.GetUretimDB()
if uretimDB == nil {
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
return
}
rows, err := queries.GetProductionHasCostDetailHammaddeTypeOptions(ctx, uretimDB, search, limit)
if err != nil {
logger.Error("hammadde query error", "err", err)
log.Printf("⚠️ [ProductionHasCostDetailEditorOptions] hammadde query error: %v", err)
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
return
}
defer rows.Close()
if columns, columnErr := rows.Columns(); columnErr != nil {
log.Printf("⚠️ [ProductionHasCostDetailEditorOptions] item columns read error search=%q limit=%d err=%v", search, limit, columnErr)
} else {
log.Printf("[ProductionHasCostDetailEditorOptions] item columns=%v", columns)
}
list := make([]models.ProductionHasCostDetailEditorOption, 0, limit)
for rows.Next() {
var item models.ProductionHasCostDetailEditorOption
if err := rows.Scan(&item.NHammaddeTuruNo, &item.SHammaddeTuruAdi, &item.SAciklama3); err != nil {
logger.Warn("hammadde scan error", "err", err)
log.Printf("⚠️ [ProductionHasCostDetailEditorOptions] hammadde scan error: %v", err)
continue
}
item.Kind = "hammadde"
item.Value = item.NHammaddeTuruNo
item.Label = strings.TrimSpace(item.NHammaddeTuruNo + " - " + item.SHammaddeTuruAdi)
item.SParcaAdi = item.SAciklama3
list = append(list, item)
}
if err := rows.Err(); err != nil {
logger.Error("hammadde rows error", "err", err)
http.Error(w, "Veritabani satir hatasi", http.StatusInternalServerError)
return
}
logger.Info("request done", "kind", "hammadde", "row_count", len(list))
_ = json.NewEncoder(w).Encode(list)
return
case "item":
uretimDB := db.GetUretimDB()
if uretimDB == nil {
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
return
}
log.Printf("[ProductionHasCostDetailEditorOptions] item start search=%q limit=%d", search, limit)
rows, err := queries.GetProductionHasCostDetailItemOptions(ctx, uretimDB, search, limit)
if err != nil {
logger.Error("item query error", "err", err)
log.Printf("⚠️ [ProductionHasCostDetailEditorOptions] item query error search=%q limit=%d err=%v", search, limit, err)
logProductionHasCostDetailEditorOptionItemDiagnostics(ctx, uretimDB, search, limit)
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
return
}
defer rows.Close()
if columns, columnErr := rows.Columns(); columnErr != nil {
log.Printf("⚠️ [ProductionHasCostDetailEditorOptions] item columns read error search=%q limit=%d err=%v", search, limit, columnErr)
} else {
log.Printf("[ProductionHasCostDetailEditorOptions] item columns=%v", columns)
}
list := make([]models.ProductionHasCostDetailEditorOption, 0, limit)
scanErrors := 0
for rows.Next() {
var item models.ProductionHasCostDetailEditorOption
if err := rows.Scan(&item.NStokID, &item.SKodu, &item.SAciklama, &item.SModel, &item.SBirim); err != nil {
scanErrors += 1
logger.Warn("item scan error", "scan_index", len(list)+scanErrors, "err", err)
log.Printf("⚠️ [ProductionHasCostDetailEditorOptions] item scan error scan_index=%d err=%v", len(list)+scanErrors, err)
continue
}
item.Kind = "item"
item.Value = item.SKodu
item.Label = strings.TrimSpace(item.SKodu + " - " + item.SAciklama)
list = append(list, item)
}
if err := rows.Err(); err != nil {
logger.Error("item rows error", "err", err)
log.Printf("⚠️ [ProductionHasCostDetailEditorOptions] item rows error search=%q limit=%d err=%v", search, limit, err)
http.Error(w, "Veritabani satir hatasi", http.StatusInternalServerError)
return
}
logger.Info("request done", "kind", "item", "row_count", len(list), "scan_errors", scanErrors)
log.Printf("[ProductionHasCostDetailEditorOptions] item done search=%q limit=%d row_count=%d scan_errors=%d", search, limit, len(list), scanErrors)
_ = json.NewEncoder(w).Encode(list)
return
case "color":
uretimDB := db.GetUretimDB()
if uretimDB == nil {
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
return
}
modelCode := strings.TrimSpace(r.URL.Query().Get("model_code"))
if modelCode == "" {
_ = json.NewEncoder(w).Encode([]models.ProductionHasCostDetailEditorOption{})
return
}
rows, err := queries.GetProductionHasCostDetailColorOptions(ctx, uretimDB, modelCode, search, limit)
if err != nil {
logger.Error("color query error", "model_code", modelCode, "err", err)
log.Printf("⚠️ [ProductionHasCostDetailEditorOptions] color query error: %v", err)
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
return
}
defer rows.Close()
list := make([]models.ProductionHasCostDetailEditorOption, 0, limit)
for rows.Next() {
var item models.ProductionHasCostDetailEditorOption
if err := rows.Scan(&item.ColorCode, &item.ColorDescription); err != nil {
logger.Warn("color scan error", "model_code", modelCode, "err", err)
log.Printf("⚠️ [ProductionHasCostDetailEditorOptions] color scan error: %v", err)
continue
}
item.Kind = "color"
item.Value = item.ColorCode
item.Label = strings.TrimSpace(item.ColorCode + " - " + item.ColorDescription)
list = append(list, item)
}
if err := rows.Err(); err != nil {
logger.Error("color rows error", "model_code", modelCode, "err", err)
http.Error(w, "Veritabani satir hatasi", http.StatusInternalServerError)
return
}
logger.Info("request done", "kind", "color", "model_code", modelCode, "row_count", len(list))
_ = json.NewEncoder(w).Encode(list)
return
}
logger.Warn("request invalid", "reason", "invalid kind")
http.Error(w, "kind hammadde, item veya color olmali", http.StatusBadRequest)
}
// GET /api/pricing/production-product-costing/has-cost-detail-exchange-rates
func GetProductionHasCostDetailExchangeRatesHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
mssqlDB := db.GetDB()
if mssqlDB == nil {
http.Error(w, "MSSQL veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
return
}
traceID := utils.TraceIDFromRequest(r)
ctx := utils.ContextWithTraceID(r.Context(), traceID)
logger := utils.SlogFromContext(ctx).With("handler", "production-product-costing.exchange-rates")
rawCostDate := strings.TrimSpace(r.URL.Query().Get("maliyet_tarihi"))
costDate := ""
if rawCostDate != "" {
parsedDate, err := time.Parse("2006-01-02", rawCostDate)
if err != nil {
logger.Warn("request invalid", "reason", "invalid maliyet_tarihi", "maliyet_tarihi", rawCostDate)
http.Error(w, "maliyet_tarihi YYYY-MM-DD formatinda olmali", http.StatusBadRequest)
return
}
costDate = parsedDate.Format("2006-01-02")
}
logger.Info("request start", "maliyet_tarihi", costDate)
log.Printf("[ProductionHasCostDetailExchangeRates] start maliyet_tarihi=%s", costDate)
row, err := queries.GetProductionHasCostDetailExchangeRatesByDate(ctx, mssqlDB, costDate)
if err != nil {
logger.Error("query prepare error", "err", err)
log.Printf("⚠️ [ProductionHasCostDetailExchangeRates] query prepare error: %v", err)
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
return
}
item := models.ProductionHasCostDetailExchangeRates{
TRYRate: 1,
}
if err := row.Scan(
&item.RateDate,
&item.USDRate,
&item.EURRate,
&item.GBPRate,
); err != nil {
if err == sql.ErrNoRows {
item.RateDate = costDate
logger.Info("request done", "rate_date", item.RateDate, "fallback", true)
_ = json.NewEncoder(w).Encode(item)
return
}
log.Printf("⚠️ [ProductionHasCostDetailExchangeRates] scan error: %v", err)
http.Error(w, "Veritabani satir hatasi", http.StatusInternalServerError)
return
}
logger.Info("request done", "rate_date", item.RateDate, "usd_rate", item.USDRate, "eur_rate", item.EURRate, "gbp_rate", item.GBPRate)
log.Printf("[ProductionHasCostDetailExchangeRates] done maliyet_tarihi=%s rate_date=%s usd=%.4f eur=%.4f", costDate, item.RateDate, item.USDRate, item.EURRate)
_ = json.NewEncoder(w).Encode(item)
}
// POST /api/pricing/production-product-costing/has-cost-detail-bulk-prices
func PostProductionHasCostDetailBulkPricesHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
mssqlDB := db.GetDB()
if mssqlDB == nil {
http.Error(w, "MSSQL veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
return
}
traceID := utils.TraceIDFromRequest(r)
ctx := utils.ContextWithTraceID(r.Context(), traceID)
logger := utils.SlogFromContext(ctx).With("handler", "production-product-costing.bulk-prices")
var req models.ProductionHasCostDetailBulkPriceRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
logger.Warn("request invalid", "reason", "invalid request body", "err", err)
http.Error(w, "Gecersiz istek govdesi", http.StatusBadRequest)
return
}
costDate := strings.TrimSpace(req.MaliyetTarihi)
itemsCount := len(req.Items)
responseChan := make(chan *models.ProductionHasCostDetailBulkPriceRow, itemsCount)
logger.Info("request start",
"n_onml_no", strings.TrimSpace(req.NOnMLNo),
"urun_kodu", strings.TrimSpace(req.UrunKodu),
"maliyet_tarihi", costDate,
"item_count", itemsCount,
)
log.Printf("[ProductionHasCostDetailBulkPrices] start n_onml_no=%s urun_kodu=%s maliyet_tarihi=%s item_count=%d", strings.TrimSpace(req.NOnMLNo), strings.TrimSpace(req.UrunKodu), costDate, itemsCount)
for _, item := range req.Items {
go func(item models.ProductionHasCostDetailPriceLookupItem) {
sKodu := normalizeLookupValue(item.SKodu)
if sKodu == "" {
responseChan <- nil
return
}
colorCode := firstNonEmptyString(
normalizeLookupValue(item.ColorCode),
normalizeLookupValue(item.SRenk),
)
itemDim1Code := firstNonEmptyString(
normalizeLookupValue(item.ItemDim1Code),
)
row, err := queries.GetProductionHasCostLatestPurchasePriceForItem(
ctx,
mssqlDB,
sKodu,
colorCode,
itemDim1Code,
costDate,
)
if err != nil {
logger.Warn("item lookup error", "s_kodu", sKodu, "color_code", colorCode, "item_dim1_code", itemDim1Code, "err", err)
responseChan <- nil
return
}
var result models.ProductionHasCostDetailBulkPriceRow
if err := row.Scan(
&result.PriceType,
&result.Tarih,
&result.FaturaKodu,
&result.MasrafKodu,
&result.MasrafDetay,
&result.ColorCode,
&result.ColorDescription,
&result.ItemDim1Code,
&result.ItemDim1Description,
&result.FiyatGirilen,
&result.FiyatDoviz,
); err != nil {
logger.Warn("item scan error", "s_kodu", sKodu, "color_code", colorCode, "item_dim1_code", itemDim1Code, "err", err)
responseChan <- nil
return
}
result.RowKey = strings.TrimSpace(item.RowKey)
result.NOnMLDetNo = strings.TrimSpace(item.NOnMLDetNo)
result.NHammaddeTuruNo = strings.TrimSpace(item.NHammaddeTuruNo)
result.SKodu = sKodu
if strings.TrimSpace(result.ColorCode) == "" {
result.ColorCode = colorCode
}
if strings.TrimSpace(result.ItemDim1Code) == "" {
result.ItemDim1Code = itemDim1Code
}
responseChan <- &result
}(item)
}
response := make([]models.ProductionHasCostDetailBulkPriceRow, 0, itemsCount)
for i := 0; i < itemsCount; i++ {
res := <-responseChan
if res != nil {
response = append(response, *res)
}
}
logger.Info("request done", "item_count", itemsCount, "matched_count", len(response))
log.Printf("[ProductionHasCostDetailBulkPrices] done item_count=%d matched=%d", itemsCount, len(response))
_ = json.NewEncoder(w).Encode(map[string]any{
"items": response,
})
}
// GET /api/pricing/production-product-costing/has-cost-detail-line-history
func GetProductionHasCostDetailLineHistoryHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
uretimDB := db.GetUretimDB()
mssqlDB := db.GetDB()
if uretimDB == nil && mssqlDB == nil {
http.Error(w, "Veritabani baglantilari aktif degil", http.StatusServiceUnavailable)
return
}
traceID := utils.TraceIDFromRequest(r)
ctx := utils.ContextWithTraceID(r.Context(), traceID)
logger := utils.SlogFromContext(ctx).With("handler", "production-product-costing.line-history")
rawOnMLNo := strings.TrimSpace(r.URL.Query().Get("n_onml_no"))
currentOnMLNo, _ := strconv.Atoi(rawOnMLNo)
nHammaddeTuruNo := strings.TrimSpace(r.URL.Query().Get("n_hammadde_turu_no"))
sKodu := normalizeLookupValue(r.URL.Query().Get("s_kodu"))
colorCode := firstNonEmptyString(
normalizeLookupValue(r.URL.Query().Get("color_code")),
normalizeLookupValue(r.URL.Query().Get("s_renk")),
)
costDate := strings.TrimSpace(r.URL.Query().Get("maliyet_tarihi"))
if sKodu == "" {
logger.Warn("request invalid", "reason", "missing s_kodu")
http.Error(w, "s_kodu zorunlu", http.StatusBadRequest)
return
}
logger.Info("request start", "n_onml_no", currentOnMLNo, "n_hammadde_turu_no", nHammaddeTuruNo, "s_kodu", sKodu, "color_code", colorCode, "maliyet_tarihi", costDate)
log.Printf("[ProductionHasCostDetailLineHistory] start n_onml_no=%d n_hammadde_turu_no=%s s_kodu=%s color=%s maliyet_tarihi=%s", currentOnMLNo, nHammaddeTuruNo, sKodu, colorCode, costDate)
const LINE_HISTORY_ROW_LIMIT = 500
response := models.ProductionHasCostDetailLineHistoryResponse{
PurchaseRows: make([]models.ProductionHasCostDetailPurchaseHistoryRow, 0, LINE_HISTORY_ROW_LIMIT),
RecipeRows: make([]models.ProductionHasCostDetailRecipeHistoryRow, 0, LINE_HISTORY_ROW_LIMIT),
}
allowLegacyAutoFallback := true
if mssqlDB != nil {
rows, err := queries.GetProductionHasCostPurchaseHistoryByExpenseCode(
ctx,
mssqlDB,
sKodu,
costDate,
LINE_HISTORY_ROW_LIMIT,
)
if err != nil {
logger.Warn("purchase query error", "err", err)
log.Printf("⚠️ [ProductionHasCostDetailLineHistory] purchase query error: %v", err)
} else {
defer rows.Close()
for rows.Next() {
var item models.ProductionHasCostDetailPurchaseHistoryRow
if err := rows.Scan(
&item.SourceType,
&item.PriceType,
&item.Tarih,
&item.FaturaKodu,
&item.FirmaKodu,
&item.FirmaAciklama,
&item.MasrafKodu,
&item.MasrafDetay,
&item.ColorCode,
&item.ColorDescription,
&item.ItemDim1Code,
&item.ItemDim1Description,
&item.Miktar,
&item.BIRIM,
&item.EvrakFiyat,
&item.EvrakTutar,
&item.EvrakDoviz,
); err != nil {
logger.Warn("purchase scan error", "err", err)
log.Printf("⚠️ [ProductionHasCostDetailLineHistory] purchase scan error: %v", err)
continue
}
response.PurchaseRows = append(response.PurchaseRows, item)
}
if err := rows.Err(); err != nil {
logger.Warn("purchase rows error", "err", err)
log.Printf("⚠️ [ProductionHasCostDetailLineHistory] purchase rows error: %v", err)
}
}
}
if allowLegacyAutoFallback && mssqlDB != nil && len(response.PurchaseRows) == 0 {
similarPrefix := queries.BuildProductionHasCostSimilarCodePrefix(sKodu)
if similarPrefix != "" {
logger.Info("purchase fallback start", "s_kodu", sKodu, "similar_prefix", similarPrefix, "maliyet_tarihi", costDate)
rows, err := queries.GetProductionHasCostPurchaseHistoryByCodePrefix(
ctx,
mssqlDB,
similarPrefix,
costDate,
LINE_HISTORY_ROW_LIMIT,
)
if err != nil {
logger.Warn("purchase fallback query error", "err", err)
log.Printf("⚠️ [ProductionHasCostDetailLineHistory] purchase fallback query error: %v", err)
} else {
defer rows.Close()
for rows.Next() {
var item models.ProductionHasCostDetailPurchaseHistoryRow
if err := rows.Scan(
&item.SourceType,
&item.PriceType,
&item.Tarih,
&item.FaturaKodu,
&item.FirmaKodu,
&item.FirmaAciklama,
&item.MasrafKodu,
&item.MasrafDetay,
&item.ColorCode,
&item.ColorDescription,
&item.ItemDim1Code,
&item.ItemDim1Description,
&item.Miktar,
&item.BIRIM,
&item.EvrakFiyat,
&item.EvrakTutar,
&item.EvrakDoviz,
); err != nil {
logger.Warn("purchase fallback scan error", "err", err)
log.Printf("⚠️ [ProductionHasCostDetailLineHistory] purchase fallback scan error: %v", err)
continue
}
response.PurchaseRows = append(response.PurchaseRows, item)
}
if err := rows.Err(); err != nil {
logger.Warn("purchase fallback rows error", "err", err)
log.Printf("⚠️ [ProductionHasCostDetailLineHistory] purchase fallback rows error: %v", err)
}
}
}
}
if uretimDB != nil {
rows, err := queries.GetProductionHasCostRecipeHistoryByExpenseCode(
ctx,
uretimDB,
currentOnMLNo,
sKodu,
colorCode,
costDate,
LINE_HISTORY_ROW_LIMIT,
)
if err != nil {
logger.Warn("recipe query error", "err", err)
log.Printf("⚠️ [ProductionHasCostDetailLineHistory] recipe query error: %v", err)
} else {
defer rows.Close()
for rows.Next() {
var item models.ProductionHasCostDetailRecipeHistoryRow
if err := rows.Scan(
&item.SourceType,
&item.PriceType,
&item.DteIslemTarihi,
&item.NOnMLNo,
&item.FirmaKodu,
&item.FirmaAciklama,
&item.SKodu,
&item.SAciklama,
&item.SRenk,
&item.LMiktar,
&item.SBirim,
&item.LDovizFiyati,
&item.LDovizTutari,
&item.USD,
&item.DUMMY,
); err != nil {
logger.Warn("recipe scan error", "err", err)
log.Printf("⚠️ [ProductionHasCostDetailLineHistory] recipe scan error: %v", err)
continue
}
response.RecipeRows = append(response.RecipeRows, item)
}
if err := rows.Err(); err != nil {
logger.Warn("recipe rows error", "err", err)
log.Printf("⚠️ [ProductionHasCostDetailLineHistory] recipe rows error: %v", err)
}
}
}
if allowLegacyAutoFallback && uretimDB != nil && len(response.RecipeRows) == 0 {
similarPrefix := queries.BuildProductionHasCostSimilarCodePrefix(sKodu)
if similarPrefix != "" {
logger.Info("recipe fallback prefix start", "s_kodu", sKodu, "similar_prefix", similarPrefix, "maliyet_tarihi", costDate)
rows, err := queries.GetProductionHasCostOnMLHistoryByCodePrefix(
ctx,
uretimDB,
similarPrefix,
costDate,
LINE_HISTORY_ROW_LIMIT,
)
if err != nil {
logger.Warn("recipe fallback prefix query error", "err", err)
log.Printf("⚠️ [ProductionHasCostDetailLineHistory] recipe fallback prefix query error: %v", err)
} else {
defer rows.Close()
for rows.Next() {
var item models.ProductionHasCostDetailRecipeHistoryRow
if err := rows.Scan(
&item.SourceType,
&item.PriceType,
&item.DteIslemTarihi,
&item.NOnMLNo,
&item.FirmaKodu,
&item.FirmaAciklama,
&item.SKodu,
&item.SAciklama,
&item.SRenk,
&item.LMiktar,
&item.SBirim,
&item.LDovizFiyati,
&item.LDovizTutari,
&item.USD,
&item.DUMMY,
); err != nil {
logger.Warn("recipe fallback prefix scan error", "err", err)
log.Printf("⚠️ [ProductionHasCostDetailLineHistory] recipe fallback prefix scan error: %v", err)
continue
}
response.RecipeRows = append(response.RecipeRows, item)
}
if err := rows.Err(); err != nil {
logger.Warn("recipe fallback prefix rows error", "err", err)
log.Printf("⚠️ [ProductionHasCostDetailLineHistory] recipe fallback prefix rows error: %v", err)
}
}
}
}
if allowLegacyAutoFallback && uretimDB != nil && len(response.RecipeRows) == 0 && nHammaddeTuruNo != "" {
logger.Info("recipe fallback hammadde-turu start", "n_hammadde_turu_no", nHammaddeTuruNo, "maliyet_tarihi", costDate)
rows, err := queries.GetProductionHasCostOnMLHistoryByHammaddeTuruNo(
ctx,
uretimDB,
nHammaddeTuruNo,
costDate,
LINE_HISTORY_ROW_LIMIT,
)
if err != nil {
logger.Warn("recipe fallback hammadde-turu query error", "err", err)
log.Printf("⚠️ [ProductionHasCostDetailLineHistory] recipe fallback hammadde-turu query error: %v", err)
} else {
defer rows.Close()
for rows.Next() {
var item models.ProductionHasCostDetailRecipeHistoryRow
if err := rows.Scan(
&item.SourceType,
&item.PriceType,
&item.DteIslemTarihi,
&item.NOnMLNo,
&item.FirmaKodu,
&item.FirmaAciklama,
&item.SKodu,
&item.SAciklama,
&item.SRenk,
&item.LMiktar,
&item.SBirim,
&item.LDovizFiyati,
&item.LDovizTutari,
&item.USD,
&item.DUMMY,
); err != nil {
logger.Warn("recipe fallback hammadde-turu scan error", "err", err)
log.Printf("⚠️ [ProductionHasCostDetailLineHistory] recipe fallback hammadde-turu scan error: %v", err)
continue
}
response.RecipeRows = append(response.RecipeRows, item)
}
if err := rows.Err(); err != nil {
logger.Warn("recipe fallback hammadde-turu rows error", "err", err)
log.Printf("⚠️ [ProductionHasCostDetailLineHistory] recipe fallback hammadde-turu rows error: %v", err)
}
}
}
logger.Info("request done", "s_kodu", sKodu, "purchase_count", len(response.PurchaseRows), "recipe_count", len(response.RecipeRows))
log.Printf("[ProductionHasCostDetailLineHistory] done s_kodu=%s purchase_rows=%d recipe_rows=%d", sKodu, len(response.PurchaseRows), len(response.RecipeRows))
_ = json.NewEncoder(w).Encode(response)
}
// GET /api/pricing/production-product-costing/has-cost-detail-similar-history
func GetProductionHasCostDetailSimilarHistoryHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
mssqlDB := db.GetDB()
uretimDB := db.GetUretimDB()
if mssqlDB == nil || uretimDB == nil {
http.Error(w, "Veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
return
}
traceID := utils.TraceIDFromRequest(r)
ctx := utils.ContextWithTraceID(r.Context(), traceID)
logger := utils.SlogFromContext(ctx).With("handler", "production-product-costing.similar-history")
q := r.URL.Query()
nHammaddeTuruNo := strings.TrimSpace(q.Get("n_hammadde_turu_no"))
sKodu := normalizeLookupValue(q.Get("s_kodu"))
costDate := strings.TrimSpace(q.Get("maliyet_tarihi"))
limit := parsePositiveIntOrDefault(q.Get("limit"), 500)
searchMode := strings.ToLower(strings.TrimSpace(q.Get("search_mode")))
if searchMode == "" || searchMode == "exact" {
searchMode = "prefix"
}
similarPrefix := queries.BuildProductionHasCostSimilarCodePrefix(sKodu)
if nHammaddeTuruNo == "" && sKodu == "" {
http.Error(w, "n_hammadde_turu_no veya s_kodu parametresi zorunludur", http.StatusBadRequest)
return
}
logger.Info("request start", "search_mode", searchMode, "n_hammadde_turu_no", nHammaddeTuruNo, "s_kodu", sKodu, "similar_prefix", similarPrefix, "maliyet_tarihi", costDate, "limit", limit)
response := models.ProductionHasCostDetailLineHistoryResponse{
PurchaseRows: make([]models.ProductionHasCostDetailPurchaseHistoryRow, 0, limit),
RecipeRows: make([]models.ProductionHasCostDetailRecipeHistoryRow, 0, limit),
}
purchaseMatchStage := searchMode
recipeMatchStage := searchMode
allowRecipeAutoFallback := false
if searchMode == "alternative" {
purchaseMatchStage = "skipped"
if nHammaddeTuruNo != "" && uretimDB != nil {
rows, err := queries.GetProductionHasCostOnMLHistoryByHammaddeTuruNo(ctx, uretimDB, nHammaddeTuruNo, costDate, limit)
if err != nil {
logger.Warn("alternative onml query error", "err", err)
http.Error(w, "Sorgu calistirilirken hata olustu", http.StatusInternalServerError)
return
}
defer rows.Close()
for rows.Next() {
var item models.ProductionHasCostDetailRecipeHistoryRow
if err := rows.Scan(
&item.SourceType,
&item.PriceType,
&item.DteIslemTarihi,
&item.NOnMLNo,
&item.FirmaKodu,
&item.FirmaAciklama,
&item.SKodu,
&item.SAciklama,
&item.SRenk,
&item.LMiktar,
&item.SBirim,
&item.LDovizFiyati,
&item.LDovizTutari,
&item.USD,
&item.DUMMY,
); err != nil {
logger.Warn("alternative onml scan error", "err", err)
continue
}
response.RecipeRows = append(response.RecipeRows, item)
}
if err := rows.Err(); err != nil {
logger.Warn("alternative onml rows error", "err", err)
}
}
if len(response.PurchaseRows) == 0 {
purchaseMatchStage = "empty"
}
if len(response.RecipeRows) == 0 {
recipeMatchStage = "empty"
}
logger.Info("request done",
"search_mode", searchMode,
"purchase_match_stage", purchaseMatchStage,
"recipe_match_stage", recipeMatchStage,
"similar_prefix", similarPrefix,
"purchase_count", len(response.PurchaseRows),
"recipe_count", len(response.RecipeRows),
)
_ = json.NewEncoder(w).Encode(map[string]any{
"purchaseRows": response.PurchaseRows,
"recipeRows": response.RecipeRows,
"purchase_match_stage": purchaseMatchStage,
"recipe_match_stage": recipeMatchStage,
"similar_code_prefix": similarPrefix,
"search_mode": searchMode,
})
return
}
if similarPrefix != "" {
if mssqlDB != nil {
rows, err := queries.GetProductionHasCostPurchaseHistoryByCodePrefix(ctx, mssqlDB, similarPrefix, costDate, limit)
if err != nil {
logger.Warn("prefix purchase query error", "err", err)
} else {
defer rows.Close()
for rows.Next() {
var item models.ProductionHasCostDetailPurchaseHistoryRow
if err := rows.Scan(
&item.SourceType,
&item.PriceType,
&item.Tarih,
&item.FaturaKodu,
&item.FirmaKodu,
&item.FirmaAciklama,
&item.MasrafKodu,
&item.MasrafDetay,
&item.ColorCode,
&item.ColorDescription,
&item.ItemDim1Code,
&item.ItemDim1Description,
&item.Miktar,
&item.BIRIM,
&item.EvrakFiyat,
&item.EvrakTutar,
&item.EvrakDoviz,
); err != nil {
logger.Warn("prefix purchase scan error", "err", err)
continue
}
response.PurchaseRows = append(response.PurchaseRows, item)
}
if err := rows.Err(); err != nil {
logger.Warn("prefix purchase rows error", "err", err)
}
}
}
if uretimDB != nil {
rows, err := queries.GetProductionHasCostOnMLHistoryByCodePrefix(ctx, uretimDB, similarPrefix, costDate, limit)
if err != nil {
logger.Warn("prefix onml query error", "err", err)
} else {
defer rows.Close()
for rows.Next() {
var item models.ProductionHasCostDetailRecipeHistoryRow
if err := rows.Scan(
&item.SourceType,
&item.PriceType,
&item.DteIslemTarihi,
&item.NOnMLNo,
&item.FirmaKodu,
&item.FirmaAciklama,
&item.SKodu,
&item.SAciklama,
&item.SRenk,
&item.LMiktar,
&item.SBirim,
&item.LDovizFiyati,
&item.LDovizTutari,
&item.USD,
&item.DUMMY,
); err != nil {
logger.Warn("prefix onml scan error", "err", err)
continue
}
response.RecipeRows = append(response.RecipeRows, item)
}
if err := rows.Err(); err != nil {
logger.Warn("prefix onml rows error", "err", err)
}
}
}
}
if allowRecipeAutoFallback && len(response.RecipeRows) == 0 && nHammaddeTuruNo != "" && uretimDB != nil {
recipeMatchStage = "hammadde-turu-fallback"
rows, err := queries.GetProductionHasCostOnMLHistoryByHammaddeTuruNo(ctx, uretimDB, nHammaddeTuruNo, costDate, limit)
if err != nil {
logger.Warn("fallback onml query error", "err", err)
http.Error(w, "Sorgu calistirilirken hata olustu", http.StatusInternalServerError)
return
}
defer rows.Close()
for rows.Next() {
var item models.ProductionHasCostDetailRecipeHistoryRow
if err := rows.Scan(
&item.SourceType,
&item.PriceType,
&item.DteIslemTarihi,
&item.NOnMLNo,
&item.FirmaKodu,
&item.FirmaAciklama,
&item.SKodu,
&item.SAciklama,
&item.SRenk,
&item.LMiktar,
&item.SBirim,
&item.LDovizFiyati,
&item.LDovizTutari,
&item.USD,
&item.DUMMY,
); err != nil {
logger.Warn("fallback onml scan error", "err", err)
continue
}
response.RecipeRows = append(response.RecipeRows, item)
}
if err := rows.Err(); err != nil {
logger.Warn("fallback onml rows error", "err", err)
}
}
if len(response.PurchaseRows) == 0 {
purchaseMatchStage = "empty"
}
if len(response.RecipeRows) == 0 {
recipeMatchStage = "empty"
}
logger.Info("request done",
"search_mode", searchMode,
"purchase_match_stage", purchaseMatchStage,
"recipe_match_stage", recipeMatchStage,
"similar_prefix", similarPrefix,
"purchase_count", len(response.PurchaseRows),
"recipe_count", len(response.RecipeRows),
)
_ = json.NewEncoder(w).Encode(map[string]any{
"purchaseRows": response.PurchaseRows,
"recipeRows": response.RecipeRows,
"purchase_match_stage": purchaseMatchStage,
"recipe_match_stage": recipeMatchStage,
"similar_code_prefix": similarPrefix,
"search_mode": searchMode,
})
}
func parsePositiveIntOrDefault(raw string, fallback int) int {
v, err := strconv.Atoi(strings.TrimSpace(raw))
if err != nil || v < 0 {
return fallback
}
return v
}
func normalizeLookupValue(raw string) string {
return strings.ToUpper(strings.TrimSpace(raw))
}
func firstNonEmptyString(values ...string) string {
for _, value := range values {
value = strings.TrimSpace(value)
if value != "" {
return value
}
}
return ""
}
// ============================================================
// MT BOLUM MAPPING (URETIM DB)
// ============================================================
// GET /api/pricing/production-product-costing/options/urun-ana-grup
func GetProductionProductCostingUrunAnaGrupOptionsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
mssqlDB := db.GetDB()
if mssqlDB == nil {
http.Error(w, "MSSQL veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
return
}
traceID := utils.TraceIDFromRequest(r)
ctx := utils.ContextWithTraceID(r.Context(), traceID)
search := strings.TrimSpace(r.URL.Query().Get("search"))
limit := parsePositiveIntOrDefault(r.URL.Query().Get("limit"), 50)
if limit > 500 {
limit = 500
}
logger := utils.SlogFromContext(ctx).With(
"handler", "production-product-costing.options.urun-ana-grup",
"search", search,
"limit", limit,
)
logger.Info("request start")
rows, err := queries.GetProductionProductCostingAnaGrupOptions(ctx, mssqlDB, search, limit)
if err != nil {
logger.Error("query error", "err", err)
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
return
}
defer rows.Close()
out := make([]models.ProductionProductCostingLookupOption, 0, limit)
for rows.Next() {
var v string
if err := rows.Scan(&v); err != nil {
logger.Warn("scan error", "err", err)
continue
}
v = strings.TrimSpace(v)
if v == "" {
continue
}
out = append(out, models.ProductionProductCostingLookupOption{Value: v, Label: v})
}
if err := rows.Err(); err != nil {
logger.Error("rows error", "err", err)
http.Error(w, "Veritabani satir hatasi", http.StatusInternalServerError)
return
}
logger.Info("request done", "row_count", len(out))
_ = json.NewEncoder(w).Encode(out)
}
// GET /api/pricing/production-product-costing/options/urun-alt-grup
func GetProductionProductCostingUrunAltGrupOptionsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
mssqlDB := db.GetDB()
if mssqlDB == nil {
http.Error(w, "MSSQL veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
return
}
traceID := utils.TraceIDFromRequest(r)
ctx := utils.ContextWithTraceID(r.Context(), traceID)
urunAnaGrubu := strings.TrimSpace(r.URL.Query().Get("urun_ana_grubu"))
search := strings.TrimSpace(r.URL.Query().Get("search"))
limit := parsePositiveIntOrDefault(r.URL.Query().Get("limit"), 50)
if limit > 500 {
limit = 500
}
logger := utils.SlogFromContext(ctx).With(
"handler", "production-product-costing.options.urun-alt-grup",
"urun_ana_grubu", urunAnaGrubu,
"search", search,
"limit", limit,
)
logger.Info("request start")
rows, err := queries.GetProductionProductCostingAltGrupOptions(ctx, mssqlDB, urunAnaGrubu, search, limit)
if err != nil {
logger.Error("query error", "err", err)
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
return
}
defer rows.Close()
out := make([]models.ProductionProductCostingLookupOption, 0, limit)
for rows.Next() {
var v string
if err := rows.Scan(&v); err != nil {
logger.Warn("scan error", "err", err)
continue
}
v = strings.TrimSpace(v)
if v == "" {
continue
}
out = append(out, models.ProductionProductCostingLookupOption{Value: v, Label: v})
}
if err := rows.Err(); err != nil {
logger.Error("rows error", "err", err)
http.Error(w, "Veritabani satir hatasi", http.StatusInternalServerError)
return
}
logger.Info("request done", "row_count", len(out))
_ = json.NewEncoder(w).Encode(out)
}
// GET /api/pricing/production-product-costing/options/urun-ana-alt-combos
func GetProductionProductCostingUrunAnaAltCombosHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
mssqlDB := db.GetDB()
if mssqlDB == nil {
http.Error(w, "MSSQL veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
return
}
traceID := utils.TraceIDFromRequest(r)
ctx := utils.ContextWithTraceID(r.Context(), traceID)
search := strings.TrimSpace(r.URL.Query().Get("search"))
limit := parsePositiveIntOrDefault(r.URL.Query().Get("limit"), 2000)
if limit > 5000 {
limit = 5000
}
logger := utils.SlogFromContext(ctx).With(
"handler", "production-product-costing.options.urun-ana-alt-combos",
"search", search,
"limit", limit,
)
logger.Info("request start")
rows, err := queries.GetProductionProductCostingAnaAltComboRows(ctx, mssqlDB, search, limit)
if err != nil {
// Keep the response generic, but log the underlying SQL driver error for diagnostics.
logger.Error("query error", "err", err, "search", search, "limit", limit, "trace_id", traceID)
log.Printf("❌ [ProductionProductCostingAnaAltCombos] query error trace_id=%s search=%q limit=%d err=%v", traceID, search, limit, err)
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
return
}
defer rows.Close()
out := make([]models.ProductionProductCostingAnaAltComboRow, 0, 1024)
for rows.Next() {
var item models.ProductionProductCostingAnaAltComboRow
if err := rows.Scan(&item.UrunIlkGrubu, &item.UrunAnaGrubu, &item.UrunAltGrubu); err != nil {
logger.Warn("scan error", "err", err)
continue
}
item.UrunIlkGrubu = strings.TrimSpace(item.UrunIlkGrubu)
item.UrunAnaGrubu = strings.TrimSpace(item.UrunAnaGrubu)
item.UrunAltGrubu = strings.TrimSpace(item.UrunAltGrubu)
if item.UrunIlkGrubu == "" || item.UrunAnaGrubu == "" || item.UrunAltGrubu == "" {
continue
}
out = append(out, item)
}
if err := rows.Err(); err != nil {
logger.Error("rows error", "err", err, "trace_id", traceID)
log.Printf("⚠️ [ProductionProductCostingAnaAltCombos] rows error trace_id=%s err=%v", traceID, err)
http.Error(w, "Veritabani satir hatasi", http.StatusInternalServerError)
return
}
logger.Info("request done", "row_count", len(out))
_ = json.NewEncoder(w).Encode(out)
}
// GET /api/pricing/production-product-costing/options/mtbolum
func GetProductionProductCostingMTBolumOptionsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
uretimDB := db.GetUretimDB()
if uretimDB == nil {
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
return
}
traceID := utils.TraceIDFromRequest(r)
ctx := utils.ContextWithTraceID(r.Context(), traceID)
search := strings.TrimSpace(r.URL.Query().Get("search"))
limit := parsePositiveIntOrDefault(r.URL.Query().Get("limit"), 50)
if limit > 500 {
limit = 500
}
logger := utils.SlogFromContext(ctx).With(
"handler", "production-product-costing.options.mtbolum",
"search", search,
"limit", limit,
)
logger.Info("request start")
rows, err := queries.GetProductionProductCostingMTBolumOptions(ctx, uretimDB, search, limit)
if err != nil {
logger.Error("query error", "err", err)
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
return
}
defer rows.Close()
out := make([]models.ProductionProductCostingLookupOption, 0, limit)
for rows.Next() {
var id int
var name string
if err := rows.Scan(&id, &name); err != nil {
logger.Warn("scan error", "err", err)
continue
}
label := strings.TrimSpace(strconv.Itoa(id) + " - " + strings.TrimSpace(name))
out = append(out, models.ProductionProductCostingLookupOption{
Value: strconv.Itoa(id),
Label: label,
})
}
if err := rows.Err(); err != nil {
logger.Error("rows error", "err", err)
http.Error(w, "Veritabani satir hatasi", http.StatusInternalServerError)
return
}
logger.Info("request done", "row_count", len(out))
_ = json.NewEncoder(w).Encode(out)
}
// GET /api/pricing/production-product-costing/maliyet-parca-eslestirme
func GetProductionProductCostingParcaMappingsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
uretimDB := db.GetUretimDB()
if uretimDB == nil {
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
return
}
traceID := utils.TraceIDFromRequest(r)
ctx := utils.ContextWithTraceID(r.Context(), traceID)
urunIlkGrubu := strings.TrimSpace(r.URL.Query().Get("urun_ilk_grubu"))
urunAnaGrubu := strings.TrimSpace(r.URL.Query().Get("urun_ana_grubu"))
urunAltGrubu := strings.TrimSpace(r.URL.Query().Get("urun_alt_grubu"))
nUrtMTBolumID := parsePositiveIntOrDefault(r.URL.Query().Get("n_urt_mt_bolum_id"), 0)
rawOnlyActive := strings.TrimSpace(r.URL.Query().Get("only_active"))
var onlyActive *bool = nil
if rawOnlyActive != "" {
v := rawOnlyActive == "1" || strings.EqualFold(rawOnlyActive, "true")
onlyActive = &v
}
logger := utils.SlogFromContext(ctx).With(
"handler", "production-product-costing.maliyet-parca-eslestirme.list",
"urun_ilk_grubu", urunIlkGrubu,
"urun_ana_grubu", urunAnaGrubu,
"urun_alt_grubu", urunAltGrubu,
"n_urt_mt_bolum_id", nUrtMTBolumID,
"only_active", rawOnlyActive,
)
logger.Info("request start")
rows, err := queries.ListProductionProductCostingParcaMappings(ctx, uretimDB, urunIlkGrubu, urunAnaGrubu, urunAltGrubu, nUrtMTBolumID, onlyActive)
if err != nil {
logger.Error("query error", "err", err)
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
return
}
defer rows.Close()
out := make([]models.ProductionProductCostingParcaMappingRow, 0, 200)
for rows.Next() {
var row models.ProductionProductCostingParcaMappingRow
var bAktif sql.NullBool
var hammaddeCsv sql.NullString
if err := rows.Scan(
&row.ID,
&row.UrunIlkGrubu,
&row.UrunAnaGrubu,
&row.UrunAltGrubu,
&row.NUrtMTBolumID,
&row.ParcaBolumAdi,
&hammaddeCsv,
&bAktif,
&row.DteIslem,
&row.SKullaniciAdi,
); err != nil {
logger.Warn("scan error", "err", err)
continue
}
row.BAktif = bAktif.Valid && bAktif.Bool
row.NHammaddeTurleri = make([]string, 0, 8)
if hammaddeCsv.Valid {
for _, part := range strings.Split(hammaddeCsv.String, ",") {
part = strings.TrimSpace(part)
if part != "" {
row.NHammaddeTurleri = append(row.NHammaddeTurleri, part)
}
}
}
out = append(out, row)
}
if err := rows.Err(); err != nil {
logger.Error("rows error", "err", err)
http.Error(w, "Veritabani satir hatasi", http.StatusInternalServerError)
return
}
logger.Info("request done", "row_count", len(out))
_ = json.NewEncoder(w).Encode(out)
}
// POST /api/pricing/production-product-costing/maliyet-parca-eslestirme/upsert
func PostProductionProductCostingParcaMappingUpsertHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
uretimDB := db.GetUretimDB()
if uretimDB == nil {
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
return
}
claims, _ := auth.GetClaimsFromContext(r.Context())
user := ""
if claims != nil {
user = strings.TrimSpace(claims.Username)
}
traceID := utils.TraceIDFromRequest(r)
ctx := utils.ContextWithTraceID(r.Context(), traceID)
logger := utils.SlogFromContext(ctx).With("handler", "production-product-costing.maliyet-parca-eslestirme.upsert")
logger.Info("request start")
var req models.ProductionProductCostingParcaMappingUpsertRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
logger.Warn("invalid json", "err", err)
http.Error(w, "Gecersiz JSON", http.StatusBadRequest)
return
}
req.UrunAnaGrubu = strings.TrimSpace(req.UrunAnaGrubu)
req.UrunAltGrubu = strings.TrimSpace(req.UrunAltGrubu)
req.UrunIlkGrubu = strings.TrimSpace(req.UrunIlkGrubu)
if req.UrunIlkGrubu == "" || req.UrunAnaGrubu == "" || req.UrunAltGrubu == "" || req.NUrtMTBolumID <= 0 {
http.Error(w, "urunIlkGrubu, urunAnaGrubu, urunAltGrubu ve nUrtMTBolumID zorunlu", http.StatusBadRequest)
return
}
id, err := queries.UpsertProductionProductCostingParcaMapping(ctx, uretimDB, req.UrunIlkGrubu, req.UrunAnaGrubu, req.UrunAltGrubu, req.NUrtMTBolumID, req.NHammaddeTurleri, req.BAktif, user)
if err != nil {
logger.Error("exec error", "err", err)
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
return
}
logger.Info("request done", "id", id, "user", user, "bAktif", req.BAktif)
_ = json.NewEncoder(w).Encode(map[string]any{"ok": true, "id": id})
}
type productionProductCostingSetActiveRequest struct {
ID int `json:"id"`
BAktif bool `json:"bAktif"`
}
// POST /api/pricing/production-product-costing/maliyet-parca-eslestirme/set-active
func PostProductionProductCostingParcaMappingSetActiveHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
uretimDB := db.GetUretimDB()
if uretimDB == nil {
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
return
}
claims, _ := auth.GetClaimsFromContext(r.Context())
user := ""
if claims != nil {
user = strings.TrimSpace(claims.Username)
}
traceID := utils.TraceIDFromRequest(r)
ctx := utils.ContextWithTraceID(r.Context(), traceID)
logger := utils.SlogFromContext(ctx).With("handler", "production-product-costing.maliyet-parca-eslestirme.set-active")
logger.Info("request start")
var req productionProductCostingSetActiveRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
logger.Warn("invalid json", "err", err)
http.Error(w, "Gecersiz JSON", http.StatusBadRequest)
return
}
if req.ID <= 0 {
http.Error(w, "id zorunlu", http.StatusBadRequest)
return
}
if err := queries.SetProductionProductCostingParcaMappingActive(ctx, uretimDB, req.ID, req.BAktif, user); err != nil {
logger.Error("exec error", "err", err)
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
return
}
logger.Info("request done", "id", req.ID, "bAktif", req.BAktif, "user", user)
_ = json.NewEncoder(w).Encode(map[string]any{"ok": true})
}
// DELETE /api/pricing/production-product-costing/maliyet-parca-eslestirme?id=123
func DeleteProductionProductCostingParcaMappingHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
uretimDB := db.GetUretimDB()
if uretimDB == nil {
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
return
}
traceID := utils.TraceIDFromRequest(r)
ctx := utils.ContextWithTraceID(r.Context(), traceID)
logger := utils.SlogFromContext(ctx).With("handler", "production-product-costing.maliyet-parca-eslestirme.delete")
logger.Info("request start")
id := parsePositiveIntOrDefault(r.URL.Query().Get("id"), 0)
if id <= 0 {
http.Error(w, "id zorunlu", http.StatusBadRequest)
return
}
if err := queries.DeleteProductionProductCostingParcaMapping(ctx, uretimDB, id); err != nil {
logger.Error("exec error", "err", err)
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
return
}
logger.Info("request done", "id", id)
_ = json.NewEncoder(w).Encode(map[string]any{"ok": true})
}

1
svc/tmp_backend.err.log Normal file
View File

@@ -0,0 +1 @@
exit status 1

157
svc/tmp_backend.out.log Normal file
View File

@@ -0,0 +1,157 @@
time=2026-05-03T21:39:36.490+03:00 level=INFO msg="backend start" app=bssapp-backend scope=main
time=2026-05-03T21:39:36.508+03:00 level=INFO msg="🔥🔥🔥 BSSAPP BACKEND STARTED — LOGIN ROUTE SHOULD EXIST 🔥🔥🔥" app=bssapp-backend
time=2026-05-03T21:39:36.509+03:00 level=INFO msg="🔐 JWT_SECRET yüklendi" app=bssapp-backend
MSSQL baglantisi basarili (connection timeout=120s, dial timeout=120s)
URETIM MSSQL baglantisi basarili (connection timeout=120s, dial timeout=120s)
time=2026-05-03T21:39:36.926+03:00 level=INFO msg="PostgreSQL bağlantısı başarılı" app=bssapp-backend
time=2026-05-03T21:39:37.222+03:00 level=INFO msg="✅ Admin dept permissions seeded" app=bssapp-backend
time=2026-05-03T21:39:37.223+03:00 level=INFO msg="🟢 auditlog Init called, buffer: 1000" app=bssapp-backend
time=2026-05-03T21:39:37.224+03:00 level=INFO msg="🕵️ AuditLog sistemi başlatıldı (buffer=1000)" app=bssapp-backend
time=2026-05-03T21:39:37.224+03:00 level=INFO msg="🟢 auditlog worker STARTED" app=bssapp-backend
time=2026-05-03T21:39:37.306+03:00 level=INFO msg="[TranslationPerf] index_ready sql=\"CREATE EXTENSION IF NOT EXISTS pg_trgm\"" app=bssapp-backend
time=2026-05-03T21:39:37.386+03:00 level=INFO msg="[TranslationPerf] index_ready sql=\"CREATE INDEX IF NOT EXISTS idx_mk_translator_t_key_lang ON mk_translator (t_key, lang_code)\"" app=bssapp-backend
time=2026-05-03T21:39:37.471+03:00 level=INFO msg="[TranslationPerf] index_ready sql=\"CREATE INDEX IF NOT EXISTS idx_mk_translator_status_lang_updated ON mk_translator (status, lang_code...\"" app=bssapp-backend
time=2026-05-03T21:39:37.553+03:00 level=INFO msg="[TranslationPerf] index_ready sql=\"CREATE INDEX IF NOT EXISTS idx_mk_translator_manual_status ON mk_translator (is_manual, status)\"" app=bssapp-backend
time=2026-05-03T21:39:37.635+03:00 level=INFO msg="[TranslationPerf] index_ready sql=\"CREATE INDEX IF NOT EXISTS idx_mk_translator_source_type_expr ON mk_translator ((COALESCE(NULLIF(pro...\"" app=bssapp-backend
time=2026-05-03T21:39:37.717+03:00 level=INFO msg="[TranslationPerf] index_ready sql=\"CREATE INDEX IF NOT EXISTS idx_mk_translator_source_text_trgm ON mk_translator USING gin (source_tex...\"" app=bssapp-backend
time=2026-05-03T21:39:37.799+03:00 level=INFO msg="[TranslationPerf] index_ready sql=\"CREATE INDEX IF NOT EXISTS idx_mk_translator_translated_text_trgm ON mk_translator USING gin (transl...\"" app=bssapp-backend
time=2026-05-03T21:39:37.799+03:00 level=INFO msg="✉️ Graph Mailer hazır (App-only token) | from=baggiss@baggi.com.tr" app=bssapp-backend
time=2026-05-03T21:39:37.799+03:00 level=INFO msg="✉️ Graph Mailer hazır" app=bssapp-backend
📋 [DEBUG] İlk 10 kullanıcı:
- 1 : ctengiz
- 2 : ali.kale
- 5 : mehmet.keçeci
- 6 : mert.keçeci
- 7 : samet.keçeci
- 9 : orhan.caliskan
- 10 : nilgun.sara
- 14 : rustem.kurbanov
- 15 : caner.akyol
- 16 : kemal.matyakupov
time=2026-05-03T21:39:38.702+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/auth/login [auth:login]" app=bssapp-backend
time=2026-05-03T21:39:39.525+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/auth/refresh [auth:refresh]" app=bssapp-backend
time=2026-05-03T21:39:40.343+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/password/forgot [auth:update]" app=bssapp-backend
time=2026-05-03T21:39:41.180+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/password/reset/validate/{token} [auth:view]" app=bssapp-backend
time=2026-05-03T21:39:41.998+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/password/reset [auth:update]" app=bssapp-backend
time=2026-05-03T21:39:42.815+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/password/change [auth:update]" app=bssapp-backend
time=2026-05-03T21:39:43.636+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/activity-logs [system:read]" app=bssapp-backend
time=2026-05-03T21:39:44.451+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/test-mail [system:update]" app=bssapp-backend
time=2026-05-03T21:39:45.268+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/system/market-mail-mappings/lookups [system:update]" app=bssapp-backend
time=2026-05-03T21:39:46.088+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/system/market-mail-mappings [system:update]" app=bssapp-backend
time=2026-05-03T21:39:46.905+03:00 level=INFO msg="✅ Route+Perm registered → PUT /api/system/market-mail-mappings/{marketId} [system:update]" app=bssapp-backend
time=2026-05-03T21:39:47.722+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/language/translations [language:update]" app=bssapp-backend
time=2026-05-03T21:39:48.540+03:00 level=INFO msg="✅ Route+Perm registered → PUT /api/language/translations/{id} [language:update]" app=bssapp-backend
time=2026-05-03T21:39:49.366+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/language/translations/upsert-missing [language:update]" app=bssapp-backend
time=2026-05-03T21:39:50.191+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/language/translations/sync-sources [language:update]" app=bssapp-backend
time=2026-05-03T21:39:51.011+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/language/translations/translate-selected [language:update]" app=bssapp-backend
time=2026-05-03T21:39:51.827+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/language/translations/bulk-approve [language:update]" app=bssapp-backend
time=2026-05-03T21:39:52.647+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/language/translations/bulk-update [language:update]" app=bssapp-backend
time=2026-05-03T21:39:53.473+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/roles/{id}/permissions [system:update]" app=bssapp-backend
time=2026-05-03T21:39:54.310+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/roles/{id}/permissions [system:update]" app=bssapp-backend
time=2026-05-03T21:39:55.165+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/users/{id}/permissions [system:update]" app=bssapp-backend
time=2026-05-03T21:39:56.030+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/users/{id}/permissions [system:update]" app=bssapp-backend
time=2026-05-03T21:39:56.877+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/permissions/routes [system:view]" app=bssapp-backend
time=2026-05-03T21:39:57.718+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/permissions/effective [system:view]" app=bssapp-backend
time=2026-05-03T21:39:58.552+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/permissions/matrix [system:view]" app=bssapp-backend
time=2026-05-03T21:39:59.385+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/role-dept-permissions/list [system:update]" app=bssapp-backend
time=2026-05-03T21:40:00.208+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/roles/{roleId}/departments/{deptCode}/permissions [system:update]" app=bssapp-backend
time=2026-05-03T21:40:01.043+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/roles/{roleId}/departments/{deptCode}/permissions [system:update]" app=bssapp-backend
time=2026-05-03T21:40:01.861+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/users/list [user:view]" app=bssapp-backend
time=2026-05-03T21:40:02.685+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/users [user:insert]" app=bssapp-backend
time=2026-05-03T21:40:03.509+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/users/{id} [user:update]" app=bssapp-backend
time=2026-05-03T21:40:04.357+03:00 level=INFO msg="✅ Route+Perm registered → PUT /api/users/{id} [user:update]" app=bssapp-backend
time=2026-05-03T21:40:05.218+03:00 level=INFO msg="✅ Route+Perm registered → DELETE /api/users/{id} [user:delete]" app=bssapp-backend
time=2026-05-03T21:40:06.102+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/users/{id}/admin-reset-password [user:update]" app=bssapp-backend
time=2026-05-03T21:40:06.946+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/users/{id}/send-password-mail [user:update]" app=bssapp-backend
time=2026-05-03T21:40:07.760+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/users/create [user:insert]" app=bssapp-backend
time=2026-05-03T21:40:08.577+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/lookups/modules [user:view]" app=bssapp-backend
time=2026-05-03T21:40:09.402+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/lookups/roles [user:view]" app=bssapp-backend
time=2026-05-03T21:40:10.216+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/lookups/departments [user:view]" app=bssapp-backend
time=2026-05-03T21:40:11.051+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/lookups/nebim-users [user:view]" app=bssapp-backend
time=2026-05-03T21:40:11.932+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/lookups/piyasalar [user:view]" app=bssapp-backend
time=2026-05-03T21:40:12.770+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/lookups/users-perm [user:view]" app=bssapp-backend
time=2026-05-03T21:40:13.610+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/lookups/roles-perm [user:view]" app=bssapp-backend
time=2026-05-03T21:40:14.445+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/lookups/departments-perm [user:view]" app=bssapp-backend
time=2026-05-03T21:40:15.268+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/accounts [customer:view]" app=bssapp-backend
time=2026-05-03T21:40:16.090+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/customer-list [customer:view]" app=bssapp-backend
time=2026-05-03T21:40:16.921+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/today-currency [finance:view]" app=bssapp-backend
time=2026-05-03T21:40:17.735+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/export-pdf [finance:export]" app=bssapp-backend
time=2026-05-03T21:40:18.573+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/exportstamentheaderreport-pdf [finance:export]" app=bssapp-backend
time=2026-05-03T21:40:19.416+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/finance/customer-balances [finance:view]" app=bssapp-backend
time=2026-05-03T21:40:20.262+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/finance/customer-balances/export-pdf [finance:export]" app=bssapp-backend
time=2026-05-03T21:40:21.096+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/finance/customer-balances/export-excel [finance:export]" app=bssapp-backend
time=2026-05-03T21:40:21.915+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/finance/account-aging-statement [finance:view]" app=bssapp-backend
time=2026-05-03T21:40:22.770+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/finance/account-aging-statement/export-pdf [finance:export]" app=bssapp-backend
time=2026-05-03T21:40:23.607+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/finance/account-aging-statement/export-screen-pdf [finance:export]" app=bssapp-backend
time=2026-05-03T21:40:24.427+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/finance/account-aging-statement/export-excel [finance:export]" app=bssapp-backend
time=2026-05-03T21:40:25.254+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/finance/aged-customer-balance-list [finance:view]" app=bssapp-backend
time=2026-05-03T21:40:26.082+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/statements [finance:view]" app=bssapp-backend
time=2026-05-03T21:40:26.915+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/statements/{id}/details [finance:view]" app=bssapp-backend
time=2026-05-03T21:40:27.733+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/order/create [order:insert]" app=bssapp-backend
time=2026-05-03T21:40:28.560+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/order/update [order:update]" app=bssapp-backend
time=2026-05-03T21:40:29.446+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/order/{id}/bulk-due-date [order:update]" app=bssapp-backend
time=2026-05-03T21:40:30.278+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/order/get/{id} [order:view]" app=bssapp-backend
time=2026-05-03T21:40:31.103+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/orders/list [order:view]" app=bssapp-backend
time=2026-05-03T21:40:31.929+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/orders/production-list [order:update]" app=bssapp-backend
time=2026-05-03T21:40:32.753+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/orders/production-items/cditem-lookups [order:view]" app=bssapp-backend
time=2026-05-03T21:40:33.571+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/orders/production-items/{id} [order:view]" app=bssapp-backend
time=2026-05-03T21:40:34.385+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/orders/production-items/{id}/insert-missing [order:update]" app=bssapp-backend
time=2026-05-03T21:40:35.206+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/orders/production-items/{id}/validate [order:update]" app=bssapp-backend
time=2026-05-03T21:40:36.027+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/orders/production-items/{id}/apply [order:update]" app=bssapp-backend
time=2026-05-03T21:40:36.841+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/orders/close-ready [order:update]" app=bssapp-backend
time=2026-05-03T21:40:37.670+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/orders/bulk-close [order:update]" app=bssapp-backend
time=2026-05-03T21:40:38.506+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/orders/export [order:export]" app=bssapp-backend
time=2026-05-03T21:40:39.341+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/order/check/{id} [order:view]" app=bssapp-backend
time=2026-05-03T21:40:40.201+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/order/validate [order:insert]" app=bssapp-backend
time=2026-05-03T21:40:41.036+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/order/pdf/{id} [order:export]" app=bssapp-backend
time=2026-05-03T21:40:41.857+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/order/send-market-mail [order:read]" app=bssapp-backend
time=2026-05-03T21:40:42.673+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/order-inventory [order:view]" app=bssapp-backend
time=2026-05-03T21:40:43.498+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/orderpricelistb2b [order:view]" app=bssapp-backend
time=2026-05-03T21:40:44.313+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/min-price [order:view]" app=bssapp-backend
time=2026-05-03T21:40:45.140+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/products [order:view]" app=bssapp-backend
time=2026-05-03T21:40:45.993+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-detail [order:view]" app=bssapp-backend
time=2026-05-03T21:40:46.836+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-cditem [order:view]" app=bssapp-backend
time=2026-05-03T21:40:47.696+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-colors [order:view]" app=bssapp-backend
time=2026-05-03T21:40:48.557+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-newcolors [order:view]" app=bssapp-backend
time=2026-05-03T21:40:49.393+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-colorsize [order:view]" app=bssapp-backend
time=2026-05-03T21:40:50.212+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-secondcolor [order:view]" app=bssapp-backend
time=2026-05-03T21:40:51.042+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-newsecondcolor [order:view]" app=bssapp-backend
time=2026-05-03T21:40:51.871+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-attributes [order:view]" app=bssapp-backend
time=2026-05-03T21:40:52.704+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-item-attributes [order:view]" app=bssapp-backend
time=2026-05-03T21:40:53.518+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-stock-query [order:view]" app=bssapp-backend
time=2026-05-03T21:40:54.344+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-stock-attribute-options [order:view]" app=bssapp-backend
time=2026-05-03T21:40:55.177+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-stock-query-by-attributes [order:view]" app=bssapp-backend
time=2026-05-03T21:40:56.013+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-images [order:view]" app=bssapp-backend
time=2026-05-03T21:40:56.845+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-images/{id}/content [order:view]" app=bssapp-backend
time=2026-05-03T21:40:57.677+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-size-match/rules [order:view]" app=bssapp-backend
time=2026-05-03T21:40:58.502+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/products [order:view]" app=bssapp-backend
time=2026-05-03T21:40:59.343+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/no-cost-products [order:view]" app=bssapp-backend
time=2026-05-03T21:41:00.155+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/has-cost-products [order:view]" app=bssapp-backend
time=2026-05-03T21:41:00.974+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/has-cost-history [order:view]" app=bssapp-backend
time=2026-05-03T21:41:01.791+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/has-cost-detail-groups [order:view]" app=bssapp-backend
time=2026-05-03T21:41:02.612+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/has-cost-detail-header [order:view]" app=bssapp-backend
time=2026-05-03T21:41:03.482+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/production-types [order:view]" app=bssapp-backend
time=2026-05-03T21:41:04.331+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/detail-editor-options [order:view]" app=bssapp-backend
time=2026-05-03T21:41:05.183+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/has-cost-detail-exchange-rates [order:view]" app=bssapp-backend
time=2026-05-03T21:41:06.013+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/has-cost-detail-line-history [order:view]" app=bssapp-backend
time=2026-05-03T21:41:06.839+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/has-cost-detail-similar-history [order:view]" app=bssapp-backend
time=2026-05-03T21:41:07.662+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/production-product-costing/has-cost-detail-bulk-prices [order:view]" app=bssapp-backend
time=2026-05-03T21:41:08.497+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/options/urun-ana-grup [order:view]" app=bssapp-backend
time=2026-05-03T21:41:09.318+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/options/urun-alt-grup [order:view]" app=bssapp-backend
time=2026-05-03T21:41:10.135+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/options/urun-ana-alt-combos [order:view]" app=bssapp-backend
time=2026-05-03T21:41:10.952+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/options/mtbolum [order:view]" app=bssapp-backend
time=2026-05-03T21:41:11.795+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/maliyet-parca-eslestirme [order:view]" app=bssapp-backend
time=2026-05-03T21:41:12.633+03:00 level=INFO msg="✅ Route+Perm registered → DELETE /api/pricing/production-product-costing/maliyet-parca-eslestirme [order:view]" app=bssapp-backend
time=2026-05-03T21:41:13.473+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/production-product-costing/maliyet-parca-eslestirme/upsert [order:view]" app=bssapp-backend
time=2026-05-03T21:41:14.327+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/production-product-costing/maliyet-parca-eslestirme/set-active [order:view]" app=bssapp-backend
time=2026-05-03T21:41:15.176+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/roles [user:view]" app=bssapp-backend
time=2026-05-03T21:41:16.018+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/departments [user:view]" app=bssapp-backend
time=2026-05-03T21:41:16.855+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/piyasalar [user:view]" app=bssapp-backend
time=2026-05-03T21:41:17.674+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/roles/{id}/departments [user:update]" app=bssapp-backend
time=2026-05-03T21:41:18.487+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/roles/{id}/piyasalar [user:update]" app=bssapp-backend
time=2026-05-03T21:41:19.308+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/users/{id}/roles [user:update]" app=bssapp-backend
time=2026-05-03T21:41:20.120+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/admin/users/{id}/piyasa-sync [admin:user.update]" app=bssapp-backend
time=2026-05-03T21:41:20.120+03:00 level=INFO msg="🌍 CORS Allowed Origin: http://ss.baggi.com.tr/app" app=bssapp-backend
time=2026-05-03T21:41:20.121+03:00 level=INFO msg="🚀 Server running at: 0.0.0.0:8080" app=bssapp-backend
time=2026-05-03T21:41:20.121+03:00 level=INFO msg="🕓 Translation sync next run at 2026-05-04T04:00:00+03:00 (in 6h18m40s)" app=bssapp-backend
time=2026-05-03T21:41:20.122+03:00 level=INFO msg="listen tcp 0.0.0.0:8080: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted." app=bssapp-backend

78
svc/utils/slog.go Normal file
View File

@@ -0,0 +1,78 @@
package utils
import (
"context"
"log/slog"
"net/http"
"os"
"strings"
)
type traceIDContextKey string
const traceIDKey traceIDContextKey = "trace_id"
func InitSlog() {
level := new(slog.LevelVar)
switch strings.ToLower(strings.TrimSpace(os.Getenv("SLOG_LEVEL"))) {
case "debug":
level.Set(slog.LevelDebug)
case "warn":
level.Set(slog.LevelWarn)
case "error":
level.Set(slog.LevelError)
default:
level.Set(slog.LevelInfo)
}
handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: level,
AddSource: false,
})
slog.SetDefault(slog.New(handler).With("app", "bssapp-backend"))
}
func TraceIDFromRequest(r *http.Request) string {
if r == nil {
return ""
}
candidates := []string{
strings.TrimSpace(r.Header.Get("X-Trace-ID")),
strings.TrimSpace(r.URL.Query().Get("trace_id")),
strings.TrimSpace(r.Header.Get("X-Request-ID")),
}
for _, candidate := range candidates {
if candidate != "" {
return candidate
}
}
return ""
}
func ContextWithTraceID(ctx context.Context, traceID string) context.Context {
traceID = strings.TrimSpace(traceID)
if ctx == nil || traceID == "" {
return ctx
}
return context.WithValue(ctx, traceIDKey, traceID)
}
func TraceIDFromContext(ctx context.Context) string {
if ctx == nil {
return ""
}
value, _ := ctx.Value(traceIDKey).(string)
return strings.TrimSpace(value)
}
func SlogFromContext(ctx context.Context) *slog.Logger {
traceID := TraceIDFromContext(ctx)
if traceID == "" {
return slog.Default()
}
return slog.Default().With("trace_id", traceID)
}

View File

@@ -0,0 +1,75 @@
/* eslint-disable */
/**
* THIS FILE IS GENERATED AUTOMATICALLY.
* DO NOT EDIT.
*
* You are probably looking on adding startup/initialization code.
* Use "quasar new boot <name>" and add it there.
* One boot file per concern. Then reference the file(s) in quasar.config file > boot:
* boot: ['file', ...] // do not add ".js" extension to it.
*
* Boot files are your "main.js"
**/
import { Quasar } from 'quasar'
import { markRaw } from 'vue'
import RootComponent from 'app/src/App.vue'
import createStore from 'app/src/stores/index'
import createRouter from 'app/src/router/index'
export default async function (createAppFn, quasarUserOptions) {
// Create the app instance.
// Here we inject into it the Quasar UI, the router & possibly the store.
const app = createAppFn(RootComponent)
app.use(Quasar, quasarUserOptions)
const store = typeof createStore === 'function'
? await createStore({})
: createStore
app.use(store)
const router = markRaw(
typeof createRouter === 'function'
? await createRouter({store})
: createRouter
)
// make router instance available in store
store.use(({ store }) => { store.router = router })
// Expose the app, the router and the store.
// Note that we are not mounting the app here, since bootstrapping will be
// different depending on whether we are in a browser or on the server.
return {
app,
store,
router
}
}

View File

@@ -0,0 +1,158 @@
/* eslint-disable */
/**
* THIS FILE IS GENERATED AUTOMATICALLY.
* DO NOT EDIT.
*
* You are probably looking on adding startup/initialization code.
* Use "quasar new boot <name>" and add it there.
* One boot file per concern. Then reference the file(s) in quasar.config file > boot:
* boot: ['file', ...] // do not add ".js" extension to it.
*
* Boot files are your "main.js"
**/
import { createApp } from 'vue'
import '@quasar/extras/roboto-font/roboto-font.css'
import '@quasar/extras/material-icons/material-icons.css'
// We load Quasar stylesheet file
import 'quasar/dist/quasar.sass'
import 'src/css/app.css'
import createQuasarApp from './app.js'
import quasarUserOptions from './quasar-user-options.js'
const publicPath = `/`
async function start ({
app,
router
, store
}, bootFiles) {
let hasRedirected = false
const getRedirectUrl = url => {
try { return router.resolve(url).href }
catch (err) {}
return Object(url) === url
? null
: url
}
const redirect = url => {
hasRedirected = true
if (typeof url === 'string' && /^https?:\/\//.test(url)) {
window.location.href = url
return
}
const href = getRedirectUrl(url)
// continue if we didn't fail to resolve the url
if (href !== null) {
window.location.href = href
window.location.reload()
}
}
const urlPath = window.location.href.replace(window.location.origin, '')
for (let i = 0; hasRedirected === false && i < bootFiles.length; i++) {
try {
await bootFiles[i]({
app,
router,
store,
ssrContext: null,
redirect,
urlPath,
publicPath
})
}
catch (err) {
if (err && err.url) {
redirect(err.url)
return
}
console.error('[Quasar] boot error:', err)
return
}
}
if (hasRedirected === true) return
app.use(router)
app.mount('#q-app')
}
createQuasarApp(createApp, quasarUserOptions)
.then(app => {
// eventually remove this when Cordova/Capacitor/Electron support becomes old
const [ method, mapFn ] = Promise.allSettled !== void 0
? [
'allSettled',
bootFiles => bootFiles.map(result => {
if (result.status === 'rejected') {
console.error('[Quasar] boot error:', result.reason)
return
}
return result.value.default
})
]
: [
'all',
bootFiles => bootFiles.map(entry => entry.default)
]
return Promise[ method ]([
import(/* webpackMode: "eager" */ 'boot/dayjs'),
import(/* webpackMode: "eager" */ 'boot/locale'),
import(/* webpackMode: "eager" */ 'boot/resizeObserverGuard')
]).then(bootFiles => {
const boot = mapFn(bootFiles).filter(entry => typeof entry === 'function')
start(app, boot)
})
})

View File

@@ -0,0 +1,116 @@
/* eslint-disable */
/**
* THIS FILE IS GENERATED AUTOMATICALLY.
* DO NOT EDIT.
*
* You are probably looking on adding startup/initialization code.
* Use "quasar new boot <name>" and add it there.
* One boot file per concern. Then reference the file(s) in quasar.config file > boot:
* boot: ['file', ...] // do not add ".js" extension to it.
*
* Boot files are your "main.js"
**/
import App from 'app/src/App.vue'
let appPrefetch = typeof App.preFetch === 'function'
? App.preFetch
: (
// Class components return the component options (and the preFetch hook) inside __c property
App.__c !== void 0 && typeof App.__c.preFetch === 'function'
? App.__c.preFetch
: false
)
function getMatchedComponents (to, router) {
const route = to
? (to.matched ? to : router.resolve(to).route)
: router.currentRoute.value
if (!route) { return [] }
const matched = route.matched.filter(m => m.components !== void 0)
if (matched.length === 0) { return [] }
return Array.prototype.concat.apply([], matched.map(m => {
return Object.keys(m.components).map(key => {
const comp = m.components[key]
return {
path: m.path,
c: comp
}
})
}))
}
export function addPreFetchHooks ({ router, store, publicPath }) {
// Add router hook for handling preFetch.
// Doing it after initial route is resolved so that we don't double-fetch
// the data that we already have. Using router.beforeResolve() so that all
// async components are resolved.
router.beforeResolve((to, from, next) => {
const
urlPath = window.location.href.replace(window.location.origin, ''),
matched = getMatchedComponents(to, router),
prevMatched = getMatchedComponents(from, router)
let diffed = false
const preFetchList = matched
.filter((m, i) => {
return diffed || (diffed = (
!prevMatched[i] ||
prevMatched[i].c !== m.c ||
m.path.indexOf('/:') > -1 // does it has params?
))
})
.filter(m => m.c !== void 0 && (
typeof m.c.preFetch === 'function'
// Class components return the component options (and the preFetch hook) inside __c property
|| (m.c.__c !== void 0 && typeof m.c.__c.preFetch === 'function')
))
.map(m => m.c.__c !== void 0 ? m.c.__c.preFetch : m.c.preFetch)
if (appPrefetch !== false) {
preFetchList.unshift(appPrefetch)
appPrefetch = false
}
if (preFetchList.length === 0) {
return next()
}
let hasRedirected = false
const redirect = url => {
hasRedirected = true
next(url)
}
const proceed = () => {
if (hasRedirected === false) { next() }
}
preFetchList.reduce(
(promise, preFetch) => promise.then(() => hasRedirected === false && preFetch({
store,
currentRoute: to,
previousRoute: from,
redirect,
urlPath,
publicPath
})),
Promise.resolve()
)
.then(proceed)
.catch(e => {
console.error(e)
proceed()
})
})
}

View File

@@ -0,0 +1,23 @@
/* eslint-disable */
/**
* THIS FILE IS GENERATED AUTOMATICALLY.
* DO NOT EDIT.
*
* You are probably looking on adding startup/initialization code.
* Use "quasar new boot <name>" and add it there.
* One boot file per concern. Then reference the file(s) in quasar.config file > boot:
* boot: ['file', ...] // do not add ".js" extension to it.
*
* Boot files are your "main.js"
**/
import lang from 'quasar/lang/tr.js'
import {Loading,Dialog,Notify} from 'quasar'
export default { config: {"notify":{"position":"top","timeout":2500}},lang,plugins: {Loading,Dialog,Notify} }

View File

@@ -0,0 +1,97 @@
npm warn Unknown project config "shamefully-hoist". This will stop working in the next major version of npm. See `npm help npmrc` for supported config options.
npm warn Unknown project config "strict-peer-dependencies". This will stop working in the next major version of npm. See `npm help npmrc` for supported config options.
npm warn Unknown project config "resolution-mode". This will stop working in the next major version of npm. See `npm help npmrc` for supported config options.
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue?vue&type=script&setup=true&lang=js
Syntax Error: TypeError: Cannot read properties of null (reading 'content')
at selectBlock (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\select.js:23:45)
at Object.loader (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\index.js:93:41)
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue
Module Error (from ./node_modules/vue-loader/dist/index.js):
VueCompilerError: Element is missing end tag.
at D:\baggitekstilas\software projects\bssapp\bssapp\ui\src\pages\ProductionProductCostingHasCostDetail.vue:2617:1
2614|
2615| </script>
2616|
| ^
2617| <style scoped>
2618| .pcd-page {
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?

296
ui/quasar-dev-9102.out.log Normal file
View File

@@ -0,0 +1,296 @@
> baggisowtfaresystem@0.0.1 dev
> quasar dev --port 9102 --hostname 127.0.0.1
.d88888b.
d88P" "Y88b
888 888
888 888 888 888 8888b. .d8888b 8888b. 888d888
888 888 888 888 "88b 88K "88b 888P"
888 Y8b 888 888 888 .d888888 "Y8888b. .d888888 888
Y88b.Y8b88P Y88b 888 888 888 X88 888 888 888
"Y888888" "Y88888 "Y888888 88888P' "Y888888 888
Y8b
App • Using quasar.config.js in "esm" format
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • DONE • "SPA UI" compiled by Webpack with errors • 16663ms
App • COMPILATION FAILED • Please check the log above for details.
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 4708ms
» Reported at............... 29.04.2026 19:59:28
» App dir................... D:\baggitekstilas\software projects\bssapp\bssapp\ui
» App URL................... http://127.0.0.1:9102/
» Dev mode.................. spa
» Pkg quasar................ v2.18.6
» Pkg @quasar/app-webpack... v4.3.2
» Webpack transpiled JS..... yes (Babel)
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2252ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 4291ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 60ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 713ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 8605ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 2753ms
App • COMPILATION FAILED • Please check the log above for details.
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3927ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1249ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1265ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1128ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 4600ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2824ms
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 54ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 617ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3332ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2947ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 80ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 611ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 4786ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 4916ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 59ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 733ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 32ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 539ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 26ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 566ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 47ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 840ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2536ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2803ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3245ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1962ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3271ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2830ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 39ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 605ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 22ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 253ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3930ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1727ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • DONE • "SPA UI" compiled by Webpack with errors • 94ms
App • COMPILATION FAILED • Please check the log above for details.
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 296ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 4152ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2614ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 30ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 289ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • Applying quasar.config file changes...
App • DONE • "SPA UI" compiled with success by Webpack • 205ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3249ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2064ms
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 23ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 238ms
App • Applying quasar.config file changes...

View File

@@ -0,0 +1,75 @@
npm warn Unknown project config "shamefully-hoist". This will stop working in the next major version of npm. See `npm help npmrc` for supported config options.
npm warn Unknown project config "strict-peer-dependencies". This will stop working in the next major version of npm. See `npm help npmrc` for supported config options.
npm warn Unknown project config "resolution-mode". This will stop working in the next major version of npm. See `npm help npmrc` for supported config options.
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?

282
ui/quasar-dev-9103.out.log Normal file
View File

@@ -0,0 +1,282 @@
> baggisowtfaresystem@0.0.1 dev
> quasar dev --port 9103 --hostname 127.0.0.1
.d88888b.
d88P" "Y88b
888 888
888 888 888 888 8888b. .d8888b 8888b. 888d888
888 888 888 888 "88b 88K "88b 888P"
888 Y8b 888 888 888 .d888888 "Y8888b. .d888888 888
Y88b.Y8b88P Y88b 888 888 888 X88 888 888 888
"Y888888" "Y88888 "Y888888 88888P' "Y888888 888
Y8b
App • Using quasar.config.js in "esm" format
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 30114ms
» Reported at............... 29.04.2026 20:02:54
» App dir................... D:\baggitekstilas\software projects\bssapp\bssapp\ui
» App URL................... http://127.0.0.1:9103/
» Dev mode.................. spa
» Pkg quasar................ v2.18.6
» Pkg @quasar/app-webpack... v4.3.2
» Webpack transpiled JS..... yes (Babel)
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 4662ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 44ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 667ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 9844ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 4855ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1320ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1362ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1175ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3993ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2864ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • DONE • "SPA UI" compiled by Webpack with errors • 74ms
App • COMPILATION FAILED • Please check the log above for details.
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 667ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3306ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2984ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 87ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 617ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 4879ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 5001ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 74ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 666ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 56ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 525ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 27ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 587ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 97ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 898ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2810ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2801ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3683ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1934ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3294ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2827ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 37ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 636ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 530ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 27ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 237ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3974ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1801ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • DONE • "SPA UI" compiled by Webpack with errors • 57ms
App • COMPILATION FAILED • Please check the log above for details.
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 286ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3989ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2476ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 29ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 301ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3558ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1850ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 20ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 225ms
App • Applying quasar.config file changes...

310
ui/quasar-dev.err.log Normal file
View File

@@ -0,0 +1,310 @@
npm warn Unknown project config "shamefully-hoist". This will stop working in the next major version of npm. See `npm help npmrc` for supported config options.
npm warn Unknown project config "strict-peer-dependencies". This will stop working in the next major version of npm. See `npm help npmrc` for supported config options.
npm warn Unknown project config "resolution-mode". This will stop working in the next major version of npm. See `npm help npmrc` for supported config options.
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue
Module Error (from ./node_modules/vue-loader/dist/index.js):
VueCompilerError: Element is missing end tag.
at D:\baggitekstilas\software projects\bssapp\bssapp\ui\src\pages\ProductionProductCostingHasCostDetail.vue:629:9
627| </div>
628|
629| <div v-else>
| ^
630| <div class="row items-center justify-end q-mb-sm" v-if="lineHistoryTargetHammaddeTuruNo">
631| <q-btn
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue
Module Error (from ./node_modules/vue-loader/dist/index.js):
VueCompilerError: Element is missing end tag.
at D:\baggitekstilas\software projects\bssapp\bssapp\ui\src\pages\ProductionProductCostingHasCostDetail.vue:629:9
627| </div>
628|
629| <div v-else>
| ^
630| <div class="row items-center justify-end q-mb-sm" v-if="lineHistoryTargetHammaddeTuruNo">
631| <q-btn
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue
Module Error (from ./node_modules/vue-loader/dist/index.js):
VueCompilerError: Element is missing end tag.
at D:\baggitekstilas\software projects\bssapp\bssapp\ui\src\pages\ProductionProductCostingHasCostDetail.vue:629:9
627| </div>
628|
629| <div v-else>
| ^
630| <div class="row items-center justify-end q-mb-sm" v-if="lineHistoryTargetHammaddeTuruNo">
631| <q-btn
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue
Module Error (from ./node_modules/vue-loader/dist/index.js):
VueCompilerError: Element is missing end tag.
at D:\baggitekstilas\software projects\bssapp\bssapp\ui\src\pages\ProductionProductCostingHasCostDetail.vue:629:9
627| </div>
628|
629| <div v-else>
| ^
630| <div class="row items-center justify-end q-mb-sm" v-if="lineHistoryTargetHammaddeTuruNo">
631| <q-btn
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue?vue&type=script&setup=true&lang=js
Syntax Error: TypeError: Cannot read properties of null (reading 'content')
at selectBlock (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\select.js:23:45)
at Object.loader (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\index.js:93:41)
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue?vue&type=script&setup=true&lang=js
Syntax Error: TypeError: Cannot read properties of null (reading 'content')
at selectBlock (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\select.js:23:45)
at Object.loader (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\index.js:93:41)
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue?vue&type=script&setup=true&lang=js
Syntax Error: TypeError: Cannot read properties of null (reading 'content')
at selectBlock (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\select.js:23:45)
at Object.loader (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\index.js:93:41)
Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'D:\baggitekstilas\software projects\bssapp\bssapp\ui\quasar.config.js.temporary.compiled.1777459195929.mjs' imported from D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\@quasar\app-webpack\lib\quasar-config-file.js
at finalizeResolution (node:internal/modules/esm/resolve:274:11)
at moduleResolve (node:internal/modules/esm/resolve:859:10)
at defaultResolve (node:internal/modules/esm/resolve:983:11)
at #cachedDefaultResolve (node:internal/modules/esm/loader:717:20)
at ModuleLoader.resolve (node:internal/modules/esm/loader:694:38)
at ModuleLoader.getModuleJobForImport (node:internal/modules/esm/loader:308:38)
at onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:650:36)
at TracingChannel.tracePromise (node:diagnostics_channel:344:14)
at ModuleLoader.import (node:internal/modules/esm/loader:649:21)
at defaultImportModuleDynamicallyForScript (node:internal/modules/esm/utils:235:31)
at importModuleDynamicallyCallback (node:internal/modules/esm/utils:257:12)
at D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\@quasar\app-webpack\lib\quasar-config-file.js:377:62
at D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\esbuild\lib\main.js:1317:33
at runOnEndCallbacks (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\esbuild\lib\main.js:1346:9)
at buildResponseToResult (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\esbuild\lib\main.js:924:7)
at D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\esbuild\lib\main.js:936:9
at new Promise (<anonymous>)
at requestCallbacks.on-end (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\esbuild\lib\main.js:935:54)
at handleRequest (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\esbuild\lib\main.js:628:17)
at handleIncomingPacket (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\esbuild\lib\main.js:653:7)
at Socket.readFromStdout (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\esbuild\lib\main.js:581:7)
at Socket.emit (node:events:519:28)
at addChunk (node:internal/streams/readable:561:12)
at readableAddChunkPushByteMode (node:internal/streams/readable:512:3)
at Readable.push (node:internal/streams/readable:392:5)
at Pipe.onStreamRead (node:internal/stream_base_commons:189:23) {
code: 'ERR_MODULE_NOT_FOUND',
url: 'file:///D:/baggitekstilas/software%20projects/bssapp/bssapp/ui/quasar.config.js.temporary.compiled.1777459195929.mjs?t=1777474212686'
}
App • ⚠️ Importing quasar.config file results in error. Please check the Node.js stack above against the temporarily created quasar.config.js.temporary.compiled.1777459195929.mjs file and fix the original file then DELETE the temporary one ("quasar clean --qconf" can be used).
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue?vue&type=script&setup=true&lang=js
Syntax Error: TypeError: Cannot read properties of null (reading 'content')
at selectBlock (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\select.js:23:45)
at Object.loader (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\index.js:93:41)
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue?vue&type=script&setup=true&lang=js
Syntax Error: TypeError: Cannot read properties of null (reading 'content')
at selectBlock (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\select.js:23:45)
at Object.loader (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\index.js:93:41)
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue?vue&type=script&setup=true&lang=js
Syntax Error: TypeError: Cannot read properties of null (reading 'content')
at selectBlock (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\select.js:23:45)
at Object.loader (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\index.js:93:41)
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue?vue&type=script&setup=true&lang=js
Syntax Error: TypeError: Cannot read properties of null (reading 'content')
at selectBlock (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\select.js:23:45)
at Object.loader (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\index.js:93:41)
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue?vue&type=script&setup=true&lang=js
Syntax Error: TypeError: Cannot read properties of null (reading 'content')
at selectBlock (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\select.js:23:45)
at Object.loader (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\index.js:93:41)
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue?vue&type=script&setup=true&lang=js
Syntax Error: TypeError: Cannot read properties of null (reading 'content')
at selectBlock (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\select.js:23:45)
at Object.loader (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\index.js:93:41)
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue?vue&type=script&setup=true&lang=js
Syntax Error: TypeError: Cannot read properties of null (reading 'content')
at selectBlock (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\select.js:23:45)
at Object.loader (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\index.js:93:41)
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI in ./src/pages/ProductionProductCostingHasCostDetail.vue?vue&type=script&setup=true&lang=js
Syntax Error: TypeError: Cannot read properties of null (reading 'content')
at selectBlock (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\select.js:23:45)
at Object.loader (D:\baggitekstilas\software projects\bssapp\bssapp\ui\node_modules\vue-loader\dist\index.js:93:41)
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?
App • ERROR • SPA UI
Module not found: Can't resolve imported dependency "D:\baggitekstilas\software projects\bssapp\bssapp\ui\.quasar\dev-spa\client-entry.js"
Did you forget to install it?

578
ui/quasar-dev.out.log Normal file
View File

@@ -0,0 +1,578 @@
> baggisowtfaresystem@0.0.1 dev
> quasar dev --port 9100 --hostname 127.0.0.1
.d88888b.
d88P" "Y88b
888 888
888 888 888 888 8888b. .d8888b 8888b. 888d888
888 888 888 888 "88b 88K "88b 888P"
888 Y8b 888 888 888 .d888888 "Y8888b. .d888888 888
Y88b.Y8b88P Y88b 888 888 888 X88 888 888 888
"Y888888" "Y88888 "Y888888 88888P' "Y888888 888
Y8b
App • Using quasar.config.js in "esm" format
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 9782ms
» Reported at............... 29.04.2026 13:40:07
» App dir................... D:\baggitekstilas\software projects\bssapp\bssapp\ui
» App URL................... http://127.0.0.1:9100/
» Dev mode.................. spa
» Pkg quasar................ v2.18.6
» Pkg @quasar/app-webpack... v4.3.2
» Webpack transpiled JS..... yes (Babel)
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 252ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 32ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 219ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 907ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 32ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 212ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 459ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 711ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 578ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 987ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 618ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 433ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 431ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1103ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2147ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 340ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 453ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 721ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 392ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 685ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1348ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1650ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 22ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 196ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 111ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 52ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 224ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2054ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 381ms
App • COMPILATION FAILED • Please check the log above for details.
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 656ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 61ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 420ms
App • COMPILATION FAILED • Please check the log above for details.
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • Applying quasar.config file changes...
App • DONE • "SPA UI" compiled by Webpack with errors • 355ms
App • COMPILATION FAILED • Please check the log above for details.
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 2573ms
App • COMPILATION FAILED • Please check the log above for details.
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • DONE • "SPA UI" compiled by Webpack with errors • 35ms
App • COMPILATION FAILED • Please check the log above for details.
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 355ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 2168ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 58ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 445ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 1914ms
App • COMPILATION FAILED • Please check the log above for details.
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 55ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 443ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 2288ms
App • COMPILATION FAILED • Please check the log above for details.
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 56ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 348ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 38ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 339ms
App • COMPILATION FAILED • Please check the log above for details.
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • Applying quasar.config file changes...
App • DONE • "SPA UI" compiled by Webpack with errors • 277ms
App • COMPILATION FAILED • Please check the log above for details.
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 47ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 489ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3903ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2020ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 4095ms
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 42ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 661ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 8717ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 5079ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2476ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1230ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1203ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1129ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 5848ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2848ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 115ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 612ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3207ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2894ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled by Webpack with errors • 73ms
App • COMPILATION FAILED • Please check the log above for details.
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 451ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 256ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 5443ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 4432ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 56ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 774ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • DONE • "SPA UI" compiled by Webpack with errors • 35ms
App • COMPILATION FAILED • Please check the log above for details.
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 509ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 31ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 576ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • DONE • "SPA UI" compiled by Webpack with errors • 50ms
App • COMPILATION FAILED • Please check the log above for details.
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 833ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2630ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2299ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3191ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1905ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3218ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2310ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 37ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 624ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 647ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 26ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 234ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 4120ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1714ms
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 26ms
App • COMPILATION FAILED • Please check the log above for details.
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 243ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 219ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 4317ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 2456ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 18ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • DONE • "SPA UI" compiled with success by Webpack • 251ms
App • Applying quasar.config file changes...
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 3959ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 1742ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled by Webpack with errors • 19ms
App • COMPILATION FAILED • Please check the log above for details.
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • Applying quasar.config file changes...
App • The quasar.config file (or its dependencies) changed. Reading it again...
App • TIP • 🚀 You are using the --port parameter. It is recommended to use a different devServer port for each Quasar mode to avoid browser cache issues
App • Scheduled to apply quasar.config changes in 550ms
App • WAIT • Compiling of "SPA UI" by Webpack in progress...
App • DONE • "SPA UI" compiled with success by Webpack • 226ms
App • Applying quasar.config file changes...

View File

@@ -325,7 +325,7 @@ const menuItems = [
}, },
{ {
label: 'Fiyatlandırma', label: 'Fiyatlandırma/Maliyetlendirme',
icon: 'request_quote', icon: 'request_quote',
children: [ children: [
@@ -333,6 +333,16 @@ const menuItems = [
label: 'Ürün Fiyatlandırma', label: 'Ürün Fiyatlandırma',
to: '/app/pricing/product-pricing', to: '/app/pricing/product-pricing',
permission: 'order:view' permission: 'order:view'
},
{
label: "Üretim'den Ürün Maliyetlendirme",
to: '/app/pricing/production-product-costing',
permission: 'order:view'
},
{
label: 'Maliyet Parça Eşleştirme',
to: '/app/pricing/production-product-costing/maliyet-parca-eslestirme',
permission: 'order:view'
} }
] ]
}, },

View File

@@ -0,0 +1,110 @@
<template>
<q-page v-if="canReadOrder" class="costing-gateway-page flex flex-center">
<div class="gateway-container">
<div class="gateway-header">
<div class="text-h5">Üretim'den Ürün Maliyetlendirme</div>
<div class="text-subtitle2 text-grey-7">
İşlem seçerek maliyetlendirme ekranına geçin
</div>
</div>
<div class="gateway-actions row q-col-gutter-lg q-mt-lg">
<q-card
class="gateway-card cursor-pointer"
flat
bordered
@click="goHasCostProducts"
>
<q-card-section class="text-center">
<q-icon name="fact_check" size="48px" color="primary" />
<div class="text-h6 q-mt-sm">Mevcut Maliyeti Olan Ürünleri Göster</div>
</q-card-section>
</q-card>
<q-card
class="gateway-card cursor-pointer"
flat
bordered
@click="goNoCostProducts"
>
<q-card-section class="text-center">
<q-icon name="price_change" size="48px" color="primary" />
<div class="text-h6 q-mt-sm">Maliyeti Olmayan Ürünler İçin Maliyet Oluştur</div>
</q-card-section>
</q-card>
<q-card
class="gateway-card cursor-pointer"
flat
bordered
@click="goMTBolumMapping"
>
<q-card-section class="text-center">
<q-icon name="hub" size="48px" color="primary" />
<div class="text-h6 q-mt-sm">Maliyet Parça Eşleştirme</div>
</q-card-section>
</q-card>
</div>
</div>
</q-page>
<q-page v-else class="q-pa-md flex flex-center">
<div class="text-negative text-subtitle1">
Bu module erisim yetkiniz yok.
</div>
</q-page>
</template>
<script setup>
import { useRouter } from 'vue-router'
import { usePermission } from 'src/composables/usePermission'
const { canRead } = usePermission()
const canReadOrder = canRead('order')
const router = useRouter()
function goHasCostProducts () {
if (!canReadOrder.value) return
router.push({ name: 'production-product-costing-has-cost' })
}
function goNoCostProducts () {
if (!canReadOrder.value) return
router.push({ name: 'production-product-costing-no-cost' })
}
function goMTBolumMapping () {
if (!canReadOrder.value) return
router.push({ name: 'production-product-costing-maliyet-parca-eslestirme' })
}
</script>
<style scoped>
.costing-gateway-page {
background: #fafafa;
}
.gateway-container {
width: 100%;
max-width: 1000px;
padding: 24px;
}
.gateway-header {
text-align: center;
}
.gateway-actions {
justify-content: center;
}
.gateway-card {
width: 360px;
transition: all 0.2s ease;
}
.gateway-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
}
</style>

View File

@@ -0,0 +1,478 @@
<template>
<q-page v-if="canReadOrder" class="npc-page">
<div class="ol-filter-bar npc-filter-bar">
<div class="npc-filter-row">
<q-input
v-model="filters.search"
class="npc-filter-input npc-search"
dense
filled
clearable
debounce="300"
label="Arama (Maliyet No / Urun Kodu / Urun Adi / Aciklama)"
>
<template #append>
<q-icon name="search" />
</template>
</q-input>
<div class="ol-filter-actions npc-filter-actions">
<q-btn
label="Temizle"
icon="clear"
color="grey-7"
flat
:disable="loading"
@click="clearFilters"
/>
<q-btn
label="Kolon Filtreleri"
icon="filter_alt_off"
color="grey-7"
flat
:disable="loading"
@click="clearAllColumnFilters"
/>
<q-btn
label="Yenile"
icon="refresh"
color="primary"
:loading="loading"
@click="fetchRows"
/>
</div>
</div>
</div>
<q-table
title="Mevcut Maliyeti Olan Urunler"
class="ol-table npc-table"
flat
bordered
dense
virtual-scroll
:virtual-scroll-item-size="34"
table-style="max-height: calc(100vh - 190px)"
separator="cell"
row-key="__rowKey"
:rows="rows"
:columns="columns"
:loading="loading"
no-data-label="Kayit bulunamadi"
:rows-per-page-options="[0]"
v-model:pagination="tablePagination"
hide-bottom
>
<template #header-cell="props">
<q-th :props="props">
<div class="npc-header-cell">
<div class="npc-head-wrap-3">{{ props.col.label }}</div>
<q-btn
v-if="props.col.name !== 'open'"
dense
flat
round
size="sm"
icon="filter_alt"
:color="isColumnFilterActive(props.col.name) ? 'primary' : 'grey-6'"
>
<q-menu class="npc-filter-menu" fit>
<div class="npc-filter-menu-content">
<div class="text-caption text-weight-bold q-mb-sm">{{ props.col.label }}</div>
<q-input
v-model="getColumnFilter(props.col.name).text"
dense
outlined
clearable
label="Icerir"
/>
<q-select
v-model="getColumnFilter(props.col.name).selected"
class="q-mt-sm"
dense
outlined
multiple
use-chips
use-input
emit-value
map-options
:options="columnDistinctOptions[props.col.name] || []"
label="Deger Sec"
/>
<div class="row justify-end q-gutter-sm q-mt-sm">
<q-btn dense flat color="grey-7" label="Temizle" @click="clearColumnFilter(props.col.name)" />
</div>
</div>
</q-menu>
</q-btn>
</div>
</q-th>
</template>
<template #body-cell="props">
<q-td v-if="props.col.name === 'open'" :props="props" class="text-center">
<q-btn
icon="open_in_new"
color="primary"
flat
round
dense
@click="openRow(props.row)"
>
<q-tooltip>Ac</q-tooltip>
</q-btn>
</q-td>
<q-td v-else :props="props" class="npc-wrap-col">
<div class="npc-wrap-3" :title="String(props.value || '')">{{ props.value }}</div>
</q-td>
</template>
</q-table>
<q-banner v-if="error" class="bg-red text-white q-mt-sm">
Hata: {{ error }}
</q-banner>
</q-page>
<q-page v-else class="q-pa-md flex flex-center">
<div class="text-negative text-subtitle1">
Bu module erisim yetkiniz yok.
</div>
</q-page>
</template>
<script setup>
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import { usePermission } from 'src/composables/usePermission'
import { get, extractApiErrorDetail } from 'src/services/api'
const { canRead } = usePermission()
const canReadOrder = canRead('order')
const router = useRouter()
const loading = ref(false)
const error = ref('')
const allRows = ref([])
const tablePagination = ref({
sortBy: 'Tarihi',
descending: true,
page: 1,
rowsPerPage: 0
})
const filters = reactive({
search: ''
})
const dateColumns = new Set(['Tarihi', 'dteKayitTarihi', 'dteGuncellemeTarihi', 'SonSiparisTarihi'])
const columns = [
{ name: 'open', label: '', field: 'open', align: 'center', sortable: false, style: 'width:3%', headerStyle: 'width:3%' },
{ name: 'UretimSekli', label: 'Uretim Sekli', field: 'UretimSekli', align: 'left', sortable: true, style: 'width:9%', headerStyle: 'width:9%' },
{ name: 'nOnMLNo', label: 'nOnMLNo', field: 'nOnMLNo', align: 'left', sortable: true, style: 'width:6%', headerStyle: 'width:6%' },
{ name: 'UrunKodu', label: 'UrunKodu', field: 'UrunKodu', align: 'left', sortable: true, style: 'width:7%', headerStyle: 'width:7%' },
{ name: 'UrunAdi', label: 'UrunAdi', field: 'UrunAdi', align: 'left', sortable: true, style: 'width:8%', headerStyle: 'width:8%' },
{ name: 'Tarihi', label: 'Tarihi', field: 'Tarihi', align: 'center', sortable: true, format: val => formatDateTR(val), style: 'width:6%', headerStyle: 'width:6%' },
{ name: 'dteKayitTarihi', label: 'dteKayitTarihi', field: 'dteKayitTarihi', align: 'center', sortable: true, format: val => formatDateTR(val), style: 'width:6%', headerStyle: 'width:6%' },
{ name: 'sKullaniciAdi', label: 'sKullaniciAdi', field: 'sKullaniciAdi', align: 'left', sortable: true, style: 'width:7%', headerStyle: 'width:7%' },
{ name: 'lTutarTL', label: 'lTutarTL', field: 'lTutarTL', align: 'right', sortable: true, format: val => formatMoney(val), style: 'width:6%', headerStyle: 'width:6%' },
{ name: 'lTutarUSD', label: 'lTutarUSD', field: 'lTutarUSD', align: 'right', sortable: true, format: val => formatMoney(val), style: 'width:6%', headerStyle: 'width:6%' },
{ name: 'lTutarEURO', label: 'lTutarEURO', field: 'lTutarEURO', align: 'right', sortable: true, format: val => formatMoney(val), style: 'width:6%', headerStyle: 'width:6%' },
{ name: 'dteGuncellemeTarihi', label: 'dteGuncellemeTarihi', field: 'dteGuncellemeTarihi', align: 'center', sortable: true, format: val => formatDateTR(val), style: 'width:7%', headerStyle: 'width:7%' },
{ name: 'sGuncellemeKullaniciAdi', label: 'sGuncellemeKullaniciAdi', field: 'sGuncellemeKullaniciAdi', align: 'left', sortable: true, style: 'width:7%', headerStyle: 'width:7%' },
{ name: 'nUrtReceteID', label: 'nUrtReceteID', field: 'nUrtReceteID', align: 'left', sortable: true, style: 'width:6%', headerStyle: 'width:6%' },
{ name: 'sAciklama', label: 'sAciklama', field: 'sAciklama', align: 'left', sortable: true, style: 'width:8%', headerStyle: 'width:8%' },
{ name: 'SonSiparisTarihi', label: 'SonSiparisTarihi', field: 'SonSiparisTarihi', align: 'center', sortable: true, format: val => formatDateTR(val), style: 'width:7%', headerStyle: 'width:7%' },
{ name: 'MaliyetDurumu', label: 'MaliyetDurumu', field: 'MaliyetDurumu', align: 'left', sortable: true, style: 'width:8%', headerStyle: 'width:8%' }
]
const columnFilters = reactive({})
function getColumnFilter (name) {
if (!columnFilters[name]) {
columnFilters[name] = {
text: '',
selected: []
}
}
return columnFilters[name]
}
function formatDateTR (value) {
const s = String(value || '').trim()
if (!s) return ''
const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(s)
if (!m) return s
return `${m[3]}.${m[2]}.${m[1]}`
}
function formatMoney (value) {
return Number(value || 0).toLocaleString('tr-TR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})
}
const columnDistinctOptions = computed(() => {
const optionsByColumn = {}
for (const col of columns) {
if (col.name === 'open') continue
const set = new Set()
for (const row of allRows.value) {
const val = getColumnComparableValue(row, col.name)
if (val) set.add(val)
}
optionsByColumn[col.name] = Array.from(set)
.sort((a, b) => a.localeCompare(b, 'tr'))
.map(v => ({ label: v, value: v }))
}
return optionsByColumn
})
const rows = computed(() => {
let result = allRows.value
for (const col of columns) {
if (col.name === 'open') continue
const cf = getColumnFilter(col.name)
const text = String(cf.text || '').trim().toLowerCase()
const selected = Array.isArray(cf.selected) ? cf.selected : []
if (!text && selected.length === 0) continue
result = result.filter((row) => {
const value = getColumnComparableValue(row, col.name)
const valueLC = value.toLowerCase()
if (text && !valueLC.includes(text)) return false
if (selected.length > 0 && !selected.includes(value)) return false
return true
})
}
return result
})
function getColumnComparableValue (row, colName) {
if (row?.__cmp?.[colName] !== undefined) return row.__cmp[colName]
if (dateColumns.has(colName)) return formatDateTR(row?.[colName])
return String(row?.[colName] ?? '').trim()
}
function buildComparableMap (row) {
const cmp = {}
for (const col of columns) {
if (col.name === 'open') continue
cmp[col.name] = dateColumns.has(col.name)
? formatDateTR(row?.[col.name])
: String(row?.[col.name] ?? '').trim()
}
return cmp
}
function isColumnFilterActive (name) {
const cf = getColumnFilter(name)
return !!String(cf.text || '').trim() || (Array.isArray(cf.selected) && cf.selected.length > 0)
}
function clearColumnFilter (name) {
const cf = getColumnFilter(name)
cf.text = ''
cf.selected = []
}
function clearAllColumnFilters () {
for (const col of columns) {
if (col.name === 'open') continue
clearColumnFilter(col.name)
}
}
let searchTimer = null
watch(
() => filters.search,
() => {
clearTimeout(searchTimer)
searchTimer = setTimeout(() => {
fetchRows()
}, 400)
}
)
async function fetchRows () {
loading.value = true
error.value = ''
try {
const data = await get('/pricing/production-product-costing/has-cost-products', {
search: filters.search || '',
offset: 0,
limit: 300
})
const list = Array.isArray(data) ? data : []
const sortedList = list.slice().sort((a, b) => {
const aDate = Date.parse(String(a?.Tarihi || ''))
const bDate = Date.parse(String(b?.Tarihi || ''))
const aTs = Number.isNaN(aDate) ? 0 : aDate
const bTs = Number.isNaN(bDate) ? 0 : bDate
return bTs - aTs
})
allRows.value = sortedList.map((x, i) => ({
__rowKey: `${x?.nOnMLNo || ''}-${x?.UrunKodu || ''}-${i}`,
__cmp: buildComparableMap(x),
...x
}))
tablePagination.value = {
...tablePagination.value,
sortBy: 'Tarihi',
descending: true,
page: 1
}
} catch (err) {
error.value = await extractApiErrorDetail(err)
allRows.value = []
} finally {
loading.value = false
}
}
function clearFilters () {
filters.search = ''
clearAllColumnFilters()
fetchRows()
}
function openRow (row) {
const urunKodu = String(row?.UrunKodu || '').trim()
if (!urunKodu) return
router.push({
name: 'production-product-costing-has-cost-history',
query: { urun_kodu: urunKodu }
})
}
onMounted(() => {
if (!canReadOrder.value) return
fetchRows()
})
</script>
<style scoped>
.npc-page {
padding: 10px;
}
.npc-filter-bar {
margin-bottom: 8px;
}
.npc-filter-row {
display: flex;
flex-wrap: nowrap;
gap: 10px;
align-items: center;
}
.npc-filter-input {
min-width: 118px;
width: 136px;
flex: 0 0 136px;
}
.npc-search {
min-width: 240px;
max-width: 420px;
flex: 1 1 360px;
}
.npc-filter-actions {
display: flex;
gap: 8px;
flex-wrap: nowrap;
flex: 0 0 auto;
}
.npc-filter-menu {
min-width: 300px;
}
.npc-filter-menu-content {
padding: 10px;
}
.npc-table :deep(.q-table thead th) {
font-size: 11px;
padding: 3px 4px;
white-space: normal !important;
vertical-align: top !important;
line-height: 1.15;
}
.npc-table :deep(.q-table tbody td) {
font-size: 11px;
padding: 2px 4px;
white-space: normal !important;
vertical-align: top !important;
line-height: 1.15;
}
.npc-table :deep(.q-table) {
width: 100%;
table-layout: fixed;
}
.npc-wrap-col {
white-space: normal !important;
}
.npc-header-cell {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 4px;
width: 100%;
}
.npc-head-wrap-3 {
min-width: 0;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
line-height: 1.15;
white-space: normal;
word-break: break-word;
}
.npc-wrap-3 {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
line-height: 1.15;
white-space: normal;
word-break: break-word;
}
@media (max-width: 1440px) {
.npc-filter-row {
flex-wrap: wrap;
align-items: flex-start;
}
.npc-filter-actions {
flex-wrap: wrap;
}
.npc-filter-input {
flex: 1 1 140px;
}
}
</style>

View File

@@ -0,0 +1,3474 @@
<template>
<q-page v-if="canReadOrder" class="pcd-page" :style="pageVars">
<div ref="stickyStackRef" class="sticky-stack pcd-sticky-stack">
<div ref="saveToolbarRef" class="save-toolbar pcd-save-toolbar q-px-md">
<div class="pcd-toolbar-row">
<div class="pcd-toolbar-left">
<div class="pcd-toolbar-title">Maliyet Detay Sayfasi</div>
<div v-if="detailHeader && !detailLoading" class="pcd-toolbar-summary">
<div class="pcd-toolbar-pill pcd-toolbar-pill-emphasis">
<span class="pcd-toolbar-pill-label">USD</span>
<span class="pcd-toolbar-pill-value">{{ formatMoney(toolbarSummary.usdTotal) }}</span>
</div>
<div class="pcd-toolbar-pill pcd-toolbar-pill-emphasis">
<span class="pcd-toolbar-pill-label">EUR</span>
<span class="pcd-toolbar-pill-value">{{ formatMoney(toolbarSummary.eurTotal) }}</span>
</div>
<div class="pcd-toolbar-pill pcd-toolbar-pill-emphasis">
<span class="pcd-toolbar-pill-label">GBP</span>
<span class="pcd-toolbar-pill-value">{{ formatMoney(toolbarSummary.gbpTotal) }}</span>
</div>
<div class="pcd-toolbar-pill pcd-toolbar-pill-neutral">
<span class="pcd-toolbar-pill-label">USD Kur</span>
<span class="pcd-toolbar-pill-value">{{ formatMoney(exchangeRates.usdRate) }}</span>
</div>
<div class="pcd-toolbar-pill pcd-toolbar-pill-neutral">
<span class="pcd-toolbar-pill-label">EUR Kur</span>
<span class="pcd-toolbar-pill-value">{{ formatMoney(exchangeRates.eurRate) }}</span>
</div>
<div class="pcd-toolbar-pill pcd-toolbar-pill-neutral">
<span class="pcd-toolbar-pill-label">GBP Kur</span>
<span class="pcd-toolbar-pill-value">{{ formatMoney(exchangeRates.gbpRate) }}</span>
</div>
</div>
</div>
<div class="pcd-toolbar-actions">
<q-btn
flat
dense
color="grey-7"
class="pcd-toolbar-btn"
:label="headerInfoCollapsed ? 'HEADER GOSTER' : 'HEADER DARALT'"
:icon="headerInfoCollapsed ? 'expand_more' : 'expand_less'"
@click="toggleHeaderInfo"
/>
<q-btn icon="arrow_back" label="Geri" dense flat color="grey-8" class="pcd-toolbar-btn" @click="goBack" />
<q-btn label="Yenile" icon="refresh" dense color="primary" class="pcd-toolbar-btn" :loading="detailLoading" @click="fetchDetail" />
<q-btn
label="Toplu Fiyat Cagir"
icon="playlist_add_check"
dense
color="secondary"
outline
class="pcd-toolbar-btn"
:loading="bulkPriceLoading"
:disable="!detailHeader || detailLoading || saveLoading || bulkPriceLoading"
@click="fetchBulkItemPrices"
/>
<q-btn
label="SATIR EKLE"
dense
color="secondary"
icon="add"
class="pcd-toolbar-btn"
:disable="!detailHeader || detailLoading || saveLoading || bulkPriceLoading"
@click="openNewRowDialog"
/>
<q-btn
label="Kaydet"
icon="save"
dense
color="primary"
outline
class="pcd-toolbar-btn"
:loading="saveLoading"
:disable="!detailHeader || detailLoading || saveLoading || bulkPriceLoading"
@click="saveChanges"
/>
</div>
</div>
</div>
<q-banner v-if="detailError" class="bg-red text-white q-mb-md">
Hata: {{ detailError }}
</q-banner>
<div v-if="detailHeader && !detailLoading && !headerInfoCollapsed" class="filter-bar pcd-detail-header-bar q-mx-md q-mb-md">
<div class="row q-col-gutter-sm">
<div class="col-12 col-md-3">
<q-input
dense
filled
readonly
label="Maliyet Tarihi"
:model-value="formatDateTR(costDate)"
class="pcd-emphasis-field-alt"
>
<template #append>
<q-icon name="event" class="cursor-pointer">
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
<q-date v-model="costDate" mask="YYYY-MM-DD" locale="tr-TR" />
</q-popup-proxy>
</q-icon>
</template>
</q-input>
</div>
<div class="col-12 col-md-3">
<q-select
v-if="!isNoCostDetail"
v-model="detailHeader.UretimSekliID"
:options="productionTypes"
option-value="id"
option-label="aciklama"
emit-value
map-options
dense
filled
label="Uretim Sekli"
class="pcd-emphasis-field-alt"
@update:model-value="onUretimSekliChange"
/>
<q-input
v-else
dense
filled
readonly
label="Uretim Sekli"
:model-value="detailHeader.UretimSekli || '-'"
class="pcd-emphasis-field-alt"
/>
</div>
<div class="col-12 col-md-6">
<div class="row q-col-gutter-xs">
<div class="col-4">
<q-input dense filled readonly label="USD Kuru" :model-value="formatMoney(exchangeRates.usdRate)" class="pcd-emphasis-field-alt" />
</div>
<div class="col-4">
<q-input dense filled readonly label="EUR Kuru" :model-value="formatMoney(exchangeRates.eurRate)" class="pcd-emphasis-field-alt" />
</div>
<div class="col-4">
<q-input dense filled readonly label="GBP Kuru" :model-value="formatMoney(exchangeRates.gbpRate)" class="pcd-emphasis-field-alt" />
</div>
</div>
</div>
<div class="col-12 col-md-4">
<q-input dense filled readonly label="Uretimi Yapan Firma" :model-value="detailHeader.UretimiYapanFirma || '-'" />
</div>
<div class="col-12 col-md-4">
<q-input dense filled readonly label="2.Firma" :model-value="detailHeader.SonIsEmriVeren || '-'" />
</div>
<div class="col-6 col-md-2">
<q-input dense filled readonly label="nOnMLNo" :model-value="detailHeader.nOnMLNo || '-'" />
</div>
<div class="col-6 col-md-2">
<q-input dense filled readonly label="UrunKodu" :model-value="detailHeader.UrunKodu || '-'" />
</div>
<div class="col-12 col-md-3">
<q-input dense filled readonly label="UrunAdi" :model-value="detailHeader.UrunAdi || '-'" />
</div>
<div class="col-12 col-md-3">
<q-input dense filled readonly label="Urun Ana Grubu" :model-value="detailHeader.UrunAnaGrubu || '-'" />
</div>
<div class="col-12 col-md-3">
<q-input dense filled readonly label="Urun Alt Grubu" :model-value="detailHeader.UrunAltGrubu || '-'" />
</div>
<div class="col-6 col-md-2">
<q-input dense filled readonly label="sKullaniciAdi" :model-value="detailHeader.sKullaniciAdi || '-'" />
</div>
<div class="col-12 col-md-3">
<q-input dense filled readonly label="Son Guncelleme Tarihi" :model-value="formatDateTR(detailHeader.dteGuncellemeTarihi)" />
</div>
<div class="col-12 col-md-2">
<q-input dense filled readonly label="sGuncellemeKullaniciAdi" :model-value="detailHeader.sGuncellemeKullaniciAdi || '-'" />
</div>
<div class="col-6 col-md-2">
<q-input dense filled readonly label="nUrtReceteID" :model-value="detailHeader.nUrtReceteID || '-'" />
</div>
<div v-if="!isNoCostDetail && partSummary && partSummary.length > 0" class="col-12">
<div class="pcd-part-summary-card">
<div class="pcd-part-summary-title">Parça Bazlı Maliyet Özetleri</div>
<q-markup-table dense flat bordered separator="cell" class="pcd-part-summary-table">
<thead>
<tr>
<th class="text-left">Parça</th>
<th class="text-right">TRY</th>
<th class="text-right">USD</th>
<th class="text-right">EUR</th>
</tr>
</thead>
<tbody>
<tr v-for="ps in partSummary" :key="ps.name">
<td class="text-left text-weight-bold">{{ ps.name }}</td>
<td class="text-right">{{ formatMoney(ps.try) }}</td>
<td class="text-right">{{ formatMoney(ps.usd) }}</td>
<td class="text-right">{{ formatMoney(ps.eur) }}</td>
</tr>
</tbody>
</q-markup-table>
</div>
</div>
</div>
</div>
<div v-if="detailHeader && !detailLoading && headerInfoCollapsed" class="filter-bar pcd-detail-header-bar q-mx-md q-mb-md">
<div class="row q-col-gutter-sm">
<div class="col-6 col-md-2">
<q-input dense filled readonly label="nOnMLNo" :model-value="detailHeader.nOnMLNo || '-'" />
</div>
<div class="col-12 col-md-3">
<q-input dense filled readonly label="Uretimi Yapan Firma" :model-value="detailHeader.UretimiYapanFirma || '-'" />
</div>
<div class="col-12 col-md-3">
<q-input dense filled readonly label="2.Firma" :model-value="detailHeader.SonIsEmriVeren || '-'" />
</div>
<div class="col-6 col-md-2">
<q-input dense filled readonly label="UrunKodu" :model-value="detailHeader.UrunKodu || '-'" />
</div>
<div class="col-12 col-md-2">
<q-input dense filled readonly label="Uretim Sekli" :model-value="formatUretimSekli(detailHeader)" />
</div>
</div>
</div>
</div>
<div v-if="detailLoading" class="row justify-center q-pa-lg">
<q-spinner color="primary" size="36px" />
</div>
<div v-else-if="detailGroups.length === 0" class="text-grey-7 q-pa-md">
Kayit bulunamadi.
</div>
<div v-else class="column q-gutter-md pcd-content-body">
<div
v-for="(grp, gi) in detailGroups"
:key="groupKey(grp, gi)"
class="pcd-group-card"
>
<div class="order-sub-header pcd-sub-header">
<div class="sub-left">
{{ grp.sAciklama3 || 'TANIMSIZ' }}
</div>
<div class="sub-right pcd-sub-right-clickable" @click="toggleGroup(grp, gi)">
Grup Toplami TRY: {{ formatBarMoney(resolveGroupTRYTutar(grp)) }} | USD: {{ formatBarMoney(resolveGroupUSDTutar(grp)) }}
<q-icon
:name="isGroupOpen(grp, gi) ? 'expand_less' : 'expand_more'"
size="18px"
class="q-ml-sm"
/>
</div>
</div>
<q-table
v-if="isGroupOpen(grp, gi)"
class="pcd-detail-table"
dense
flat
bordered
separator="cell"
row-key="__rowKey"
:rows="grp.items"
:columns="detailColumns"
:table-row-class-fn="resolveDetailRowClass"
@row-click="onDetailRowClick"
hide-bottom
:rows-per-page-options="[0]"
>
<template #header-cell-lMiktar="props">
<q-th :props="props" class="pcd-entry-header">
{{ props.col.label }}
</q-th>
</template>
<template #header-cell-inputPrice="props">
<q-th :props="props" class="pcd-entry-header">
{{ props.col.label }}
</q-th>
</template>
<template #header-cell-inputPricePrBr="props">
<q-th :props="props" class="pcd-entry-header">
{{ props.col.label }}
</q-th>
</template>
<template #header-cell-maliyeteDahil="props">
<q-th :props="props" class="pcd-secondary-header">
{{ props.col.label }}
</q-th>
</template>
<template #header-cell-cmPriceType="props">
<q-th :props="props" class="pcd-secondary-header">
{{ props.col.label }}
</q-th>
</template>
<template #body-cell-actions="props">
<q-td :props="props" class="q-gutter-xs no-wrap">
<q-btn
flat
round
dense
size="sm"
color="primary"
icon="history"
@click.stop="openLineHistory(props.row)"
>
<q-tooltip>Eski Fiyatlar / Geçmiş</q-tooltip>
</q-btn>
<q-icon
name="edit"
size="xs"
color="grey-7"
class="cursor-pointer"
@click.stop="openRowEditorForEdit(props.row)"
>
<q-tooltip>Satırı Düzenle</q-tooltip>
</q-icon>
</q-td>
</template>
<template #body-cell-maliyeteDahil="props">
<q-td :props="props" class="text-center pcd-secondary-cell">
<q-checkbox
v-model="props.row.maliyeteDahil"
color="primary"
keep-color
dense
@update:model-value="value => onRowMaliyeteDahilChange(props.row, value)"
/>
</q-td>
</template>
<template #body-cell-cmPriceType="props">
<q-td :props="props" class="text-center pcd-secondary-cell">
<q-checkbox
v-if="isCMGroupName(props.row.sAciklama3)"
:model-value="resolveCMPriceTypeChecked(props.row)"
color="secondary"
keep-color
dense
@update:model-value="value => onRowCMPriceTypeChange(props.row, value)"
/>
<span v-else class="text-grey-6">-</span>
</q-td>
</template>
<template #body-cell-nOnMLDetNo="props">
<q-td :props="props">
<div v-if="props.row.isNew" class="text-primary text-weight-bold">YENI</div>
<div>{{ props.value }}</div>
</q-td>
</template>
<template #body-cell-sParcaAdi="props">
<q-td :props="props">
{{ props.value || props.row.sAciklama3 || '-' }}
</q-td>
</template>
<template #body-cell-nHammaddeTuruNo="props">
<q-td :props="props">
<div class="text-weight-medium" style="font-size: 1.1em;">
{{ props.value }}<span v-if="props.row.sHammaddeTuruAdi"> - {{ props.row.sHammaddeTuruAdi }}</span>
</div>
</q-td>
</template>
<template #body-cell-sKodu="props">
<q-td :props="props">
<span>{{ props.value }}</span>
</q-td>
</template>
<template #body-cell-sAciklama="props">
<q-td :props="props">
<span>{{ props.value }}</span>
</q-td>
</template>
<template #body-cell-sRenk="props">
<q-td :props="props">
<span>{{ props.value }}</span>
</q-td>
</template>
<template #body-cell-lMiktar="props">
<q-td :props="props">
<q-input
v-model="props.row.miktarInput"
class="pcd-inline-input pcd-entry-input"
color="primary"
dense
filled
@update:model-value="value => onRowQuantityInput(props.row, value)"
@blur="normalizeRowQuantityDisplay(props.row)"
input-class="text-right"
inputmode="decimal"
placeholder="0,0000"
/>
</q-td>
</template>
<template #body-cell-inputPrice="props">
<q-td :props="props">
<q-input
v-model="props.row.inputPrice"
class="pcd-inline-input pcd-entry-input"
color="primary"
dense
filled
@update:model-value="value => onRowInputPriceChange(props.row, value)"
@blur="normalizeRowPriceDisplay(props.row)"
input-class="text-right"
inputmode="decimal"
placeholder="0,00"
/>
</q-td>
</template>
<template #body-cell-inputPricePrBr="props">
<q-td :props="props">
<q-select
v-model="props.row.inputPricePrBr"
class="pcd-inline-input pcd-entry-input"
:options="priceCurrencyOptions"
color="primary"
dense
filled
@update:model-value="value => onRowInputPriceCurrencyChange(props.row, value)"
emit-value
map-options
options-dense
/>
</q-td>
</template>
<template #body-cell-sBirim="props">
<q-td :props="props">
<span>{{ props.value }}</span>
</q-td>
</template>
</q-table>
</div>
</div>
</q-page>
<q-page v-else class="q-pa-md flex flex-center">
<div class="text-negative text-subtitle1">
Bu module erisim yetkiniz yok.
</div>
</q-page>
<q-dialog v-model="rowEditorDialogOpen" persistent>
<q-card class="pcd-row-editor-dialog">
<q-card-section class="row items-center justify-between q-pb-sm">
<div class="text-subtitle1 text-weight-bold">
{{ rowEditorMode === 'edit' ? 'Satir Duzenle' : 'Yeni Satir Ekle' }}
</div>
<q-btn flat round dense icon="close" v-close-popup />
</q-card-section>
<q-separator />
<q-card-section class="q-pa-md">
<div class="row q-col-gutter-md">
<div class="col-12 col-md-2">
<q-input dense filled readonly label="No" v-model="rowEditorForm.nOnMLDetNo" />
</div>
<div class="col-12 col-md-3">
<q-input dense filled readonly label="Parca Adi" v-model="rowEditorForm.sParcaAdi" />
</div>
<div class="col-12 col-md-3">
<q-input dense filled readonly label="Parca Grubu" v-model="rowEditorForm.sAciklama3" />
</div>
<div class="col-12 col-md-4">
<q-select
v-model="rowEditorForm.nHammaddeTuruNo"
class="pcd-row-editor-entry"
:options="rowEditorHammaddeOptions"
:loading="rowEditorHammaddeLoading"
option-value="value"
option-label="label"
emit-value
map-options
use-input
fill-input
hide-selected
input-debounce="250"
dense
filled
label="Hammadde Turu"
placeholder="Aramak icin yazin"
@filter="filterRowEditorHammaddeOptions"
@update:model-value="onRowEditorHammaddeChange"
/>
</div>
<div class="col-12 col-md-6">
<q-select
v-model="rowEditorForm.sKodu"
class="pcd-row-editor-entry"
:options="rowEditorItemOptions"
:loading="rowEditorItemLoading"
option-value="value"
option-label="label"
emit-value
map-options
use-input
fill-input
hide-selected
input-debounce="350"
dense
filled
label="Kod / Aciklama"
placeholder="En az 2 karakter yazin"
@filter="filterRowEditorItemOptions"
@update:model-value="onRowEditorItemChange"
/>
</div>
<div class="col-12 col-md-6">
<q-select
v-model="rowEditorForm.ColorCode"
class="pcd-row-editor-entry"
:options="rowEditorColorOptions"
:loading="rowEditorColorLoading"
option-value="value"
option-label="label"
emit-value
map-options
use-input
fill-input
hide-selected
input-debounce="250"
dense
filled
label="Renk"
placeholder="Aramak icin yazin"
@filter="filterRowEditorColorOptions"
@update:model-value="onRowEditorColorChange"
/>
</div>
<div class="col-12 col-md-2">
<q-input v-model="rowEditorForm.miktarInput" class="pcd-row-editor-entry" dense filled label="Miktar" input-class="text-right" inputmode="decimal" />
</div>
<div class="col-12 col-md-2">
<q-input v-model="rowEditorForm.inputPrice" class="pcd-row-editor-entry" dense filled label="Fiyat Giris" input-class="text-right" inputmode="decimal" />
</div>
<div class="col-12 col-md-2">
<q-select
v-model="rowEditorForm.inputPricePrBr"
class="pcd-row-editor-entry"
:options="priceCurrencyOptions"
emit-value
map-options
options-dense
dense
filled
label="Pr.Br."
/>
</div>
<div class="col-12 col-md-3">
<q-input dense filled readonly label="Birim" v-model="rowEditorForm.sBirim" />
</div>
<div class="col-12 col-md-2 pcd-row-editor-flag" style="display:flex;align-items:center;">
<q-checkbox v-model="rowEditorForm.maliyeteDahil" label="Maliyete Dahil" color="primary" keep-color />
</div>
<div v-if="isCMGroupName(rowEditorForm.sAciklama3)" class="col-12 col-md-3 pcd-row-editor-flag" style="display:flex;align-items:center;">
<q-checkbox v-model="rowEditorForm.cmPriceTypeChecked" label="CMT Malzeme Dahil" color="secondary" keep-color />
</div>
</div>
</q-card-section>
<q-card-actions align="right" class="q-pa-md">
<q-btn
v-if="rowEditorMode === 'edit'"
flat
color="negative"
icon="delete"
label="Satiri Sil"
class="q-mr-auto"
@click="deleteRowEditor"
/>
<q-btn flat label="Vazgec" color="grey-7" v-close-popup />
<q-btn color="secondary" icon="save" label="Satira Uygula" @click="saveRowEditor" />
</q-card-actions>
</q-card>
</q-dialog>
<q-dialog v-model="lineHistoryDialogOpen" maximized>
<q-card class="pcd-history-dialog">
<q-card-section class="row items-center justify-between q-gutter-sm">
<div>
<div class="text-subtitle1 text-weight-bold">Eski Satinalma ve Recete Fiyatlari</div>
<div class="text-caption text-grey-7">
{{ lineHistoryTargetSummary }}
</div>
</div>
<q-btn flat round dense icon="close" v-close-popup />
</q-card-section>
<q-separator />
<q-card-section class="q-pa-md">
<q-banner v-if="lineHistoryError" class="bg-red text-white q-mb-md">
Hata: {{ lineHistoryError }}
</q-banner>
<q-banner v-if="lineHistoryShowingFallback" class="bg-amber-1 text-amber-9 q-mb-md rounded-borders border-amber">
<template v-slot:avatar>
<q-icon name="info" color="amber-9" />
</template>
Tam eşleşme bulunamadı. Benzer kodlara (prefix) ait geçmiş fiyatlar gösteriliyor.
</q-banner>
<div v-if="lineHistoryCanFetchSimilar || lineHistoryCanFetchAlternative" class="row q-col-gutter-sm q-mb-md">
<div v-if="lineHistorySearchMode !== 'exact'" class="col-auto">
<q-btn
label="Geri (Tam Eslesme)"
icon="arrow_back"
color="grey-7"
outline
no-caps
@click="fetchSimilarItemHistory('exact')"
/>
</div>
<div v-if="lineHistoryCanFetchSimilar" class="col-auto">
<q-btn
label="Benzer Kodları Göster"
icon="travel_explore"
color="secondary"
outline
no-caps
:loading="lineHistoryLoading && lineHistorySearchMode === 'prefix'"
@click="fetchSimilarItemHistory('prefix')"
/>
</div>
<div v-if="lineHistoryCanFetchAlternative" class="col-auto">
<q-btn
label="Diğer Alternatifleri Göster"
icon="alt_route"
color="primary"
outline
no-caps
:loading="lineHistoryLoading && lineHistorySearchMode === 'alternative'"
@click="fetchSimilarItemHistory('alternative')"
/>
</div>
</div>
<div v-if="lineHistoryLoading" class="row justify-center q-pa-lg">
<q-spinner color="primary" size="32px" />
</div>
<div v-else-if="lineHistoryRows.length === 0" class="column items-center q-pa-xl">
<q-icon name="history" size="64px" color="grey-4" />
<div class="text-grey-7 q-mt-md text-center">
Bu kalem için geçmiş hareket bulunamadı.
<div v-if="lineHistoryTargetHammaddeTuruNo" class="q-mt-sm">
<q-btn
label="Hammadde Türü Bazında Benzer Ürünleri Göster"
icon="travel_explore"
color="secondary"
outline
no-caps
:loading="lineHistoryLoading"
@click="fetchSimilarItemHistory('prefix')"
/>
</div>
</div>
</div>
<div v-else>
<q-table
class="pcd-history-table"
dense
flat
bordered
row-key="__historyKey"
:rows="lineHistoryRows"
:columns="lineHistoryColumns"
:table-row-class-fn="resolveLineHistoryRowClass"
:rows-per-page-options="[0]"
hide-bottom
>
<template v-slot:body-cell-sourceLabel="props">
<q-td :props="props">
<span class="pcd-history-source-chip" :class="`pcd-history-source-chip--${String(props.row.sourceType || '').toLowerCase()}`">
{{ props.row.sourceLabel }}
</span>
</q-td>
</template>
<template v-slot:body-cell-price="props">
<q-td :props="props" class="text-right">
{{ formatMoney(props.row.price) }}
</q-td>
</template>
<template v-slot:body-cell-quantity="props">
<q-td :props="props" class="text-right">
{{ formatMoney(props.row.quantity) }}
</q-td>
</template>
<template v-slot:body-cell-amount="props">
<q-td :props="props" class="text-right">
{{ formatMoney(props.row.amount) }}
</q-td>
</template>
<template v-slot:body-cell-select="props">
<q-td :props="props" class="text-right">
<q-btn
label="Sec"
color="primary"
dense
unelevated
@click="applyLineHistorySelection(props.row)"
/>
</q-td>
</template>
</q-table>
</div>
</q-card-section>
</q-card>
</q-dialog>
</template>
<script setup>
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { useQuasar } from 'quasar'
import { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router'
import { usePermission } from 'src/composables/usePermission'
import { get, post, extractApiErrorDetail } from 'src/services/api'
import { createTraceId, slog } from 'src/utils/slog'
const route = useRoute()
const router = useRouter()
const $q = useQuasar()
const { canRead } = usePermission()
const canReadOrder = canRead('order')
const detailLoading = ref(false)
const detailError = ref('')
const detailGroups = ref([])
const detailHeader = ref(null)
const costDate = ref('')
const exchangeRates = ref(createEmptyExchangeRates())
const deletedDetailRows = ref([])
const localDraftTimer = ref(0)
const initialHeaderSnapshot = ref('')
const saveLoading = ref(false)
const bulkPriceLoading = ref(false)
const newRowSequence = ref(0)
const ROW_EDITOR_OPTIONS_LIMIT = 100
const ROW_EDITOR_ITEM_MIN_SEARCH_LENGTH = 2
const LINE_HISTORY_ROW_LIMIT = 500
const LINE_HISTORY_COMBINED_ROW_LIMIT = LINE_HISTORY_ROW_LIMIT * 2
const rowEditorDialogOpen = ref(false)
const rowEditorMode = ref('new')
const rowEditorTargetRowKey = ref('')
const rowEditorForm = ref(createRowEditorForm())
const rowEditorHammaddeOptions = ref([])
const rowEditorHammaddeAllOptions = ref([])
const rowEditorHammaddeLoading = ref(false)
const rowEditorItemOptions = ref([])
const rowEditorItemAllOptions = ref([])
const rowEditorItemLoading = ref(false)
const rowEditorColorOptions = ref([])
const rowEditorColorAllOptions = ref([])
const rowEditorColorLoading = ref(false)
const lineHistoryDialogOpen = ref(false)
const lineHistoryLoading = ref(false)
const lineHistoryError = ref('')
const lineHistoryRows = ref([])
const lineHistoryTargetRowKey = ref('')
const lineHistoryTargetHammaddeTuruNo = ref('')
const lineHistoryTargetItemCode = ref('')
const lineHistoryTargetSummary = ref('')
const lineHistorySearchMode = ref('exact')
const lineHistoryLastPurchaseMatchStage = ref('')
const lineHistoryLastRecipeMatchStage = ref('')
const headerInfoCollapsed = ref(false)
const subHeaderTop = ref(140)
const stickyStackRef = ref(null)
const saveToolbarRef = ref(null)
const groupOpenState = ref({})
const productionTypes = ref([])
const onMLNo = computed(() => String(route.query?.n_onml_no || '').trim())
const productCode = computed(() => String(route.query?.urun_kodu || '').trim())
const recipeCode = computed(() => String(route.query?.recete_kodu || '').trim())
const detailSource = computed(() => String(route.query?.detail_source || '').trim().toLowerCase())
const isNoCostDetail = computed(() => detailSource.value === 'no-cost')
const generatedTraceId = ref(createTraceId('pcd-detail'))
const traceId = computed(() => String(route.query?.trace_id || generatedTraceId.value).trim())
const pageMode = computed(() => (isNoCostDetail.value ? 'new' : 'edit'))
const pageVars = computed(() => ({
'--pcd-subheader-top': `${subHeaderTop.value}px`
}))
const lineHistoryCanFetchSimilar = computed(() => (
Boolean(lineHistoryTargetItemCode.value) &&
lineHistorySearchMode.value !== 'prefix'
))
const lineHistoryCanFetchAlternative = computed(() => (
Boolean(lineHistoryTargetHammaddeTuruNo.value) &&
lineHistorySearchMode.value !== 'alternative'
))
const lineHistoryShowingFallback = computed(() => (
lineHistorySearchMode.value === 'exact' &&
lineHistoryRows.value.some(r => r.priceType === 'BNZ')
))
const priceCurrencyOptions = [
{ label: 'USD', value: 'USD' },
{ label: 'TRY', value: 'TRY' },
{ label: 'EUR', value: 'EUR' },
{ label: 'GBP', value: 'GBP' }
]
const flatDetailRows = computed(() => detailGroups.value.flatMap(grp => Array.isArray(grp?.items) ? grp.items : []))
// no-cost: required parca slots (from Maliyet Parca Eslestirme)
const requiredParcaMappings = ref([])
const requiredAttentionRowKeys = ref({})
const draftStorageKey = computed(() => {
if (isNoCostDetail.value) {
if (!recipeCode.value) return ''
return `pcd-costing:no-cost:${String(productCode.value || '').trim()}:${String(recipeCode.value || '').trim()}`
}
if (!onMLNo.value) return ''
return `pcd-costing:has-cost:${String(onMLNo.value).trim()}`
})
const currentHeaderSnapshot = computed(() => JSON.stringify({
UretimSekliID: String(detailHeader.value?.UretimSekliID || '').trim(),
UretimSekli: String(detailHeader.value?.UretimSekli || '').trim()
}))
const headerHasUnsavedChanges = computed(() => {
if (!initialHeaderSnapshot.value) return false
return currentHeaderSnapshot.value !== initialHeaderSnapshot.value
})
const hasUnsavedChanges = computed(() => {
if (deletedDetailRows.value.length > 0) return true
if (headerHasUnsavedChanges.value) return true
return flatDetailRows.value.some(row => Boolean(row?.draftChanged))
})
function persistLocalDraftNow () {
const key = draftStorageKey.value
if (!key) return
try {
const payload = {
version: 1,
savedAt: new Date().toISOString(),
mode: pageMode.value,
detail_source: detailSource.value || 'has-cost',
n_onml_no: onMLNo.value,
urun_kodu: productCode.value,
recete_kodu: recipeCode.value,
header: detailHeader.value ? {
UretimSekliID: String(detailHeader.value?.UretimSekliID || '').trim(),
UretimSekli: String(detailHeader.value?.UretimSekli || '').trim()
} : null,
deletedDetailRows: Array.isArray(deletedDetailRows.value) ? deletedDetailRows.value : [],
detailGroups: Array.isArray(detailGroups.value) ? detailGroups.value : []
}
localStorage.setItem(key, JSON.stringify(payload))
} catch (err) {
slog.error('production-product-costing.detail', 'draft:persist:error', {
trace_id: traceId.value,
key: draftStorageKey.value,
error: String(err?.message || err)
})
}
}
function schedulePersistLocalDraft () {
try {
if (localDraftTimer.value) window.clearTimeout(localDraftTimer.value)
localDraftTimer.value = window.setTimeout(() => {
localDraftTimer.value = 0
persistLocalDraftNow()
}, 400)
} catch {
persistLocalDraftNow()
}
}
function clearLocalDraft () {
const key = draftStorageKey.value
if (!key) return
try {
localStorage.removeItem(key)
} catch {
// ignore
}
}
function tryHydrateFromLocalDraft () {
const key = draftStorageKey.value
if (!key) return false
try {
const raw = localStorage.getItem(key)
if (!raw) return false
const payload = JSON.parse(raw)
if (!payload || typeof payload !== 'object') return false
if (Array.isArray(payload.deletedDetailRows)) deletedDetailRows.value = payload.deletedDetailRows
if (Array.isArray(payload.detailGroups)) detailGroups.value = normalizeDetailGroups(payload.detailGroups)
if (payload.header && detailHeader.value) {
detailHeader.value.UretimSekliID = String(payload.header.UretimSekliID || '').trim()
detailHeader.value.UretimSekli = String(payload.header.UretimSekli || '').trim()
}
return true
} catch {
return false
}
}
function ensureBeforeUnloadGuard (enabled) {
if (!enabled) {
window.onbeforeunload = null
return
}
window.onbeforeunload = (e) => {
e.preventDefault()
e.returnValue = ''
return ''
}
}
const toolbarSummary = computed(() => flatDetailRows.value.reduce((acc, row) => {
if (!row?.maliyeteDahil) return acc
acc.tryTotal += resolveRowTRYTutar(row)
acc.usdTotal += resolveRowUSDTutar(row)
acc.eurTotal += resolveRowEURTutar(row)
acc.gbpTotal += resolveRowGBPTutar(row)
return acc
}, {
tryTotal: 0,
usdTotal: 0,
eurTotal: 0,
gbpTotal: 0
}))
const partSummary = computed(() => {
const summary = {}
flatDetailRows.value.forEach(row => {
if (!row?.maliyeteDahil) return
const part = String(row?.sParcaAdi || 'TANIMSIZ').trim() || 'TANIMSIZ'
if (!summary[part]) {
summary[part] = { try: 0, usd: 0, eur: 0, gbp: 0 }
}
summary[part].try += resolveRowTRYTutar(row)
summary[part].usd += resolveRowUSDTutar(row)
summary[part].eur += resolveRowEURTutar(row)
summary[part].gbp += resolveRowGBPTutar(row)
})
return Object.entries(summary).map(([name, totals]) => ({ name, ...totals }))
})
const lineHistoryColumns = [
{ name: 'sourceLabel', label: 'Kaynak', field: 'sourceLabel', align: 'left', sortable: false, style: 'width:6%', headerStyle: 'width:6%' },
{ name: 'dateLabel', label: 'Tarih', field: 'dateLabel', align: 'left', sortable: true, style: 'width:7%', headerStyle: 'width:7%' },
{ name: 'invoiceCode', label: 'Fatura/OnML', field: 'invoiceCode', align: 'left', sortable: true, style: 'width:8%', headerStyle: 'width:8%' },
{ name: 'companyCode', label: 'Firma Kodu', field: 'companyCode', align: 'left', sortable: true, style: 'width:8%', headerStyle: 'width:8%' },
{ name: 'companyDescription', label: 'Firma Aciklama', field: 'companyDescription', align: 'left', sortable: true, style: 'width:12%', headerStyle: 'width:12%' },
{ name: 'itemCode', label: 'Masraf/sKodu', field: 'itemCode', align: 'left', sortable: true, style: 'width:8%', headerStyle: 'width:8%' },
{ name: 'itemDescription', label: 'Masraf Detay', field: 'itemDescription', align: 'left', sortable: true, style: 'width:11%', headerStyle: 'width:11%' },
{ name: 'colorCode', label: 'Renk', field: 'colorCode', align: 'left', sortable: true, style: 'width:5%', headerStyle: 'width:5%' },
{ name: 'colorDescription', label: 'Renk Aciklama', field: 'colorDescription', align: 'left', sortable: true, style: 'width:8%', headerStyle: 'width:8%' },
{ name: 'itemDim1Code', label: 'Dim1', field: 'itemDim1Code', align: 'left', sortable: true, style: 'width:5%', headerStyle: 'width:5%' },
{ name: 'itemDim1Description', label: 'Dim1 Aciklama', field: 'itemDim1Description', align: 'left', sortable: true, style: 'width:8%', headerStyle: 'width:8%' },
{ name: 'quantity', label: 'Miktar', field: 'quantity', align: 'right', sortable: true, style: 'width:6%', headerStyle: 'width:6%' },
{ name: 'unit', label: 'Birim', field: 'unit', align: 'left', sortable: true, style: 'width:4%', headerStyle: 'width:4%' },
{ name: 'price', label: 'Fiyat', field: 'price', align: 'right', sortable: true, style: 'width:6%', headerStyle: 'width:6%' },
{ name: 'amount', label: 'Tutar', field: 'amount', align: 'right', sortable: true, style: 'width:6%', headerStyle: 'width:6%' },
{ name: 'currency', label: 'Pr. Br.', field: 'currency', align: 'left', sortable: true, style: 'width:5%', headerStyle: 'width:5%' },
{ name: 'select', label: '', field: 'select', align: 'right', sortable: false, style: 'width:6%', headerStyle: 'width:6%' }
]
function resolveLineHistoryRowClass (row) {
if (row?.priceType === 'BNZ') return 'pcd-history-row-similar'
if (row?.sourceType === 'recipe') return 'pcd-history-row-recipe'
if (row?.sourceType === 'purchase') return 'pcd-history-row-purchase'
return ''
}
const detailColumns = [
{ name: 'actions', label: '', field: 'actions', align: 'left', style: 'width:60px', headerStyle: 'width:60px' },
{ name: 'nOnMLDetNo', label: 'No', field: 'nOnMLDetNo', align: 'left', sortable: true },
{ name: 'sParcaAdi', label: 'Parça Adı', field: 'sParcaAdi', align: 'left', sortable: true },
{ name: 'nHammaddeTuruNo', label: 'Hammadde Türü', field: 'nHammaddeTuruNo', align: 'left', sortable: true },
{ name: 'sKodu', label: 'Kod', field: 'sKodu', align: 'left', sortable: true },
{ name: 'sAciklama', label: 'Açıklama', field: 'sAciklama', align: 'left', sortable: true },
{ name: 'sRenk', label: 'Renk', field: 'sRenk', align: 'left', sortable: true },
{ name: 'lMiktar', label: 'Miktar', field: 'lMiktar', align: 'right', sortable: true, format: val => formatQuantity(val), style: 'width: 80px', headerStyle: 'width: 80px' },
{ name: 'inputPrice', label: 'Fiyat Giriş', field: 'inputPrice', align: 'right', sortable: false, style: 'width: 80px', headerStyle: 'width: 80px' },
{ name: 'inputPricePrBr', label: 'Fiyat Giriş Pr.Br.', field: 'inputPricePrBr', align: 'left', sortable: false, style: 'width: 80px', headerStyle: 'width: 80px' },
{ name: 'maliyeteDahil', label: 'Maliyete Dahil', field: 'maliyeteDahil', align: 'center', sortable: false },
{ name: 'cmPriceType', label: 'CMT', field: 'cm_price_type_id', align: 'center', sortable: false, style: 'width: 72px', headerStyle: 'width: 72px' },
{ name: 'lFiyat', label: 'lFiyat', field: 'lFiyat', align: 'right', sortable: true, format: val => formatMoney(val) },
{ name: 'lTutar', label: 'lTutar', field: 'lTutar', align: 'right', sortable: true, format: val => formatMoney(val) },
{ name: 'sFiyatTipi', label: 'sFiyatTipi', field: 'sFiyatTipi', align: 'left', sortable: true },
{ name: 'sDovizCinsi', label: 'Döviz', field: 'sDovizCinsi', align: 'left', sortable: true },
{ name: 'lDovizKuru', label: 'Kur', field: 'lDovizKuru', align: 'right', sortable: true, format: val => formatMoney(val) },
{ name: 'lDovizFiyati', label: 'Döviz Fiyatı', field: 'lDovizFiyati', align: 'right', sortable: true, format: val => formatMoney(val) },
{ name: 'priceUpdateState', label: 'Fiyat Tipi', field: 'priceUpdateState', align: 'left', sortable: true },
{
name: 'usdTutar',
label: 'USD TUTAR',
field: row => resolveRowUSDTutar(row),
align: 'right',
sortable: true,
format: val => formatMoney(val)
},
{ name: 'sBirim', label: 'Birim', field: 'sBirim', align: 'left', sortable: true }
]
function formatDateTR (value) {
const s = String(value || '').trim()
if (!s) return ''
const m = /^(\d{4})-(\d{2})-(\d{2})(?:\s+(\d{2}):(\d{2}))?/.exec(s)
if (!m) return s
const datePart = `${m[3]}.${m[2]}.${m[1]}`
if (m[4] && m[5]) return `${datePart} ${m[4]}:${m[5]}`
return datePart
}
function formatUretimSekli (header) {
const id = String(header?.UretimSekliID || '').trim()
const aciklama = String(header?.UretimSekli || '').trim()
if (id && aciklama) return `${id}-${aciklama}`
return id || aciklama || '-'
}
function normalizeDateInput (value) {
const s = String(value || '').trim()
const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(s)
if (!m) return ''
return `${m[1]}-${m[2]}-${m[3]}`
}
function isCMGroupName (value) {
const normalizedValue = String(value || '').trim().toUpperCase()
return normalizedValue.includes('CM1') || normalizedValue.includes('CM2')
}
function createEmptyExchangeRates () {
return {
rateDate: '',
tryRate: 1,
usdRate: 0,
eurRate: 0,
gbpRate: 0
}
}
function createRowEditorForm (seed = {}) {
const defaultCurrency = normalizePriceCurrency(seed?.inputPricePrBr || seed?.fiyat_doviz || detailHeader.value?.sDovizCinsi) || 'USD'
const cmPriceTypeId = normalizeCMPriceTypeId(seed?.cmPriceTypeId ?? seed?.cm_price_type_id, seed?.sAciklama3 ?? seed?.sParcaAdi)
return {
__rowKey: String(seed?.__rowKey || '').trim(),
isNew: Boolean(seed?.isNew),
nStokID: String(seed?.nStokID || '').trim(),
sModel: String(seed?.sModel || '').trim(),
nOnMLDetNo: String(seed?.nOnMLDetNo || '').trim(),
sParcaAdi: String(seed?.sParcaAdi || seed?.sAciklama3 || '').trim(),
nHammaddeTuruNo: String(seed?.nHammaddeTuruNo || '').trim(),
sHammaddeTuruAdi: String(seed?.sHammaddeTuruAdi || '').trim(),
sAciklama3: String(seed?.sAciklama3 || seed?.sParcaAdi || '').trim(),
sKodu: String(seed?.sKodu || '').trim(),
sAciklama: String(seed?.sAciklama || '').trim(),
sRenk: String(seed?.sRenk || seed?.ColorCode || '').trim(),
ColorCode: String(seed?.ColorCode || seed?.sRenk || '').trim(),
ColorDescription: String(seed?.ColorDescription || '').trim(),
miktarInput: normalizeQuantityInput(seed?.miktarInput ?? seed?.lMiktar ?? 1),
inputPrice: normalizeInputPrice(seed?.inputPrice ?? seed?.fiyat_girilen ?? 0),
inputPricePrBr: defaultCurrency,
maliyeteDahil: seed?.maliyeteDahil ?? normalizeBooleanFlag(seed?.maliyete_dahil ?? true),
cmPriceTypeChecked: cmPriceTypeId === 2,
sBirim: extractPrimaryUnitValue(seed?.sBirim || 'AD') || 'AD'
}
}
function formatMoney (value) {
return parseMoneyInput(value).toLocaleString('tr-TR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})
}
function formatQuantity (value) {
return parseMoneyInput(value).toLocaleString('tr-TR', {
minimumFractionDigits: 4,
maximumFractionDigits: 4
})
}
function formatBarMoney (value) {
const roundedValue = Math.round((Number(value || 0) + Number.EPSILON) * 100) / 100
return formatMoney(roundedValue)
}
function normalizePriceCurrency (value) {
const normalizedValue = String(value || '').trim().toUpperCase()
return ['USD', 'TRY', 'EUR', 'GBP'].includes(normalizedValue) ? normalizedValue : ''
}
function normalizeCodeValue (value) {
return String(value ?? '').trim().toUpperCase()
}
function extractPrimaryUnitValue (value) {
const normalizedValue = String(value || '').trim()
if (!normalizedValue) return ''
return normalizedValue.split(/[\s,;|/]+/).filter(Boolean)[0] || normalizedValue
}
function normalizeInputPrice (value) {
const numericValue = parseMoneyInput(value)
if (value === null || value === undefined || value === '' || !Number.isFinite(numericValue)) {
return formatMoney(0)
}
return formatMoney(numericValue)
}
function normalizeQuantityInput (value) {
const numericValue = parseMoneyInput(value)
if (value === null || value === undefined || value === '' || !Number.isFinite(numericValue)) {
return formatQuantity(0)
}
return formatQuantity(numericValue)
}
function normalizeSingleSeparatorNumber (rawValue, separator) {
const parts = String(rawValue || '').split(separator)
if (parts.length <= 1) return String(rawValue || '')
if (parts.length === 2) {
return separator === ',' ? `${parts[0]}.${parts[1]}` : `${parts[0]}.${parts[1]}`
}
const headParts = parts.slice(0, -1)
const lastPart = parts[parts.length - 1]
const looksLikeGroupedInteger = lastPart.length === 3 && headParts.every((part, index) => {
if (index === 0) return part.length >= 1 && part.length <= 3
return part.length === 3
})
if (looksLikeGroupedInteger) {
return parts.join('')
}
return `${headParts.join('')}.${lastPart}`
}
function parseMoneyInput (value) {
const rawValue = String(value ?? '').trim()
if (!rawValue) return 0
const compactValue = rawValue.replace(/\s+/g, '')
const lastDotIndex = compactValue.lastIndexOf('.')
const lastCommaIndex = compactValue.lastIndexOf(',')
const hasDot = lastDotIndex >= 0
const hasComma = lastCommaIndex >= 0
let normalizedValue = compactValue
if (hasDot && hasComma) {
const decimalSeparator = lastDotIndex > lastCommaIndex ? '.' : ','
const thousandSeparator = decimalSeparator === '.' ? ',' : '.'
normalizedValue = compactValue.replace(new RegExp(`\\${thousandSeparator}`, 'g'), '')
if (decimalSeparator === ',') {
normalizedValue = normalizedValue.replace(',', '.')
}
} else if (hasComma) {
normalizedValue = normalizeSingleSeparatorNumber(compactValue, ',')
} else if (hasDot) {
normalizedValue = normalizeSingleSeparatorNumber(compactValue, '.')
}
const numericValue = Number(normalizedValue)
return Number.isFinite(numericValue) ? numericValue : 0
}
function normalizeBooleanFlag (value) {
if (typeof value === 'boolean') return value
if (typeof value === 'number') return value === 1
const normalizedValue = String(value ?? '').trim().toLowerCase()
return normalizedValue === '1' || normalizedValue === 'true' || normalizedValue === 'evet'
}
function normalizeCMPriceTypeId (value, groupName) {
if (!isCMGroupName(groupName)) return null
if (value === null || value === undefined || value === '') return 1
const numericValue = Number(value)
if (numericValue === 2) return 2
return 1
}
function resolveCMPriceTypeChecked (row) {
return normalizeCMPriceTypeId(row?.cmPriceTypeId ?? row?.cm_price_type_id, row?.sAciklama3) === 2
}
function firstDefinedValue (source, keys) {
for (const key of keys) {
const value = source?.[key]
if (value !== undefined && value !== null && String(value).trim() !== '') {
return value
}
}
return ''
}
function normalizeExchangeRatesPayload (payload, fallbackDate = '') {
return {
rateDate: normalizeDateInput(firstDefinedValue(payload, ['rateDate', 'RateDate', 'date', 'Date'])) || fallbackDate,
tryRate: 1,
usdRate: parseMoneyInput(firstDefinedValue(payload, ['usdRate', 'USDRate', 'usd', 'USD'])),
eurRate: parseMoneyInput(firstDefinedValue(payload, ['eurRate', 'EURRate', 'eur', 'EUR'])),
gbpRate: parseMoneyInput(firstDefinedValue(payload, ['gbpRate', 'GBPRate', 'gbp', 'GBP']))
}
}
function buildRowEditorHammaddeOption (source) {
const value = String(source?.value || source?.nHammaddeTuruNo || '').trim()
const name = String(source?.sHammaddeTuruAdi || '').trim()
const groupName = String(source?.sAciklama3 || '').trim()
return {
kind: 'hammadde',
value,
label: String(source?.label || `${value}${name ? ` - ${name}` : ''}`).trim(),
nHammaddeTuruNo: value,
sHammaddeTuruAdi: name,
sAciklama3: groupName,
sParcaAdi: String(source?.sParcaAdi || groupName).trim()
}
}
function buildRowEditorItemOption (source) {
const value = String(source?.value || source?.sKodu || '').trim()
const desc = String(source?.sAciklama || '').trim()
return {
kind: 'item',
value,
nStokID: String(source?.nStokID || '').trim(),
sModel: String(source?.sModel || '').trim(),
label: String(source?.label || `${value}${desc ? ` - ${desc}` : ''}`).trim(),
sKodu: value,
sAciklama: desc,
sBirim: extractPrimaryUnitValue(source?.sBirim)
}
}
function buildRowEditorColorOption (source) {
const value = String(source?.value || source?.colorCode || source?.ColorCode || source?.sRenk || '').trim()
const desc = String(source?.colorDescription || source?.ColorDescription || '').trim()
return {
kind: 'color',
value,
label: String(source?.label || `${value}${desc ? ` - ${desc}` : ''}`).trim(),
colorCode: value,
colorDescription: desc
}
}
function upsertEditorOption (optionsRef, option) {
if (!option?.value) return
const nextOptions = [...optionsRef.value]
const idx = nextOptions.findIndex(x => String(x?.value || '').trim() === option.value)
if (idx >= 0) {
nextOptions[idx] = { ...nextOptions[idx], ...option }
} else {
nextOptions.unshift(option)
}
optionsRef.value = nextOptions
}
async function fetchRowEditorOptions (kind, search = '', extraParams = {}) {
const response = await get('/pricing/production-product-costing/detail-editor-options', {
kind,
search,
limit: ROW_EDITOR_OPTIONS_LIMIT,
trace_id: traceId.value,
...extraParams
})
return Array.isArray(response) ? response : []
}
function setRowEditorOptionsByKind (kind, rows) {
if (kind === 'hammadde') {
rowEditorHammaddeAllOptions.value = rows
rowEditorHammaddeOptions.value = rows
return
}
if (kind === 'item') {
rowEditorItemAllOptions.value = rows
rowEditorItemOptions.value = rows
return
}
rowEditorColorAllOptions.value = rows
rowEditorColorOptions.value = rows
}
function setRowEditorLookupLoading (kind, isLoading) {
if (kind === 'hammadde') {
rowEditorHammaddeLoading.value = isLoading
return
}
if (kind === 'item') {
rowEditorItemLoading.value = isLoading
return
}
rowEditorColorLoading.value = isLoading
}
async function refreshRowEditorOptions (kind, search = '') {
const extraParams = kind === 'color'
? { model_code: String(rowEditorForm.value.sModel || '').trim() }
: {}
if (kind === 'color' && !extraParams.model_code) {
setRowEditorOptionsByKind(kind, [])
primeRowEditorOptionsFromForm()
return []
}
setRowEditorLookupLoading(kind, true)
try {
const rows = await fetchRowEditorOptions(kind, search, extraParams)
setRowEditorOptionsByKind(kind, rows)
primeRowEditorOptionsFromForm()
return rows
} finally {
setRowEditorLookupLoading(kind, false)
}
}
async function filterRowEditorOptions (kind, val, update, abort) {
const normalizedSearch = String(val || '').trim()
if (kind === 'item' && normalizedSearch.length < ROW_EDITOR_ITEM_MIN_SEARCH_LENGTH) {
update(() => {
rowEditorItemAllOptions.value = []
rowEditorItemOptions.value = []
primeRowEditorOptionsFromForm()
})
return
}
try {
const rows = await refreshRowEditorOptions(kind, normalizedSearch)
update(() => {
setRowEditorOptionsByKind(kind, rows)
primeRowEditorOptionsFromForm()
})
} catch (err) {
abort()
$q.notify({
type: 'negative',
message: `Lookup getirilemedi: ${await extractApiErrorDetail(err)}`,
position: 'top-right'
})
}
}
function filterRowEditorHammaddeOptions (val, update, abort) {
return filterRowEditorOptions('hammadde', val, update, abort)
}
function filterRowEditorItemOptions (val, update, abort) {
return filterRowEditorOptions('item', val, update, abort)
}
function filterRowEditorColorOptions (val, update, abort) {
const modelCode = String(rowEditorForm.value.sModel || '').trim()
if (!modelCode) {
update(() => {
rowEditorColorAllOptions.value = []
rowEditorColorOptions.value = []
})
return
}
return filterRowEditorOptions('color', val, update, abort)
}
async function loadRowEditorColorOptions () {
const modelCode = String(rowEditorForm.value.sModel || '').trim()
if (!modelCode) return []
try {
return await fetchRowEditorOptions('color', '', { model_code: modelCode })
} catch (err) {
$q.notify({
type: 'negative',
message: `Renk lookup getirilemedi: ${await extractApiErrorDetail(err)}`,
position: 'top-right'
})
return []
}
}
function primeRowEditorOptionsFromForm () {
upsertEditorOption(rowEditorHammaddeAllOptions, buildRowEditorHammaddeOption(rowEditorForm.value))
upsertEditorOption(rowEditorHammaddeOptions, buildRowEditorHammaddeOption(rowEditorForm.value))
upsertEditorOption(rowEditorItemAllOptions, buildRowEditorItemOption(rowEditorForm.value))
upsertEditorOption(rowEditorItemOptions, buildRowEditorItemOption(rowEditorForm.value))
upsertEditorOption(rowEditorColorAllOptions, buildRowEditorColorOption(rowEditorForm.value))
upsertEditorOption(rowEditorColorOptions, buildRowEditorColorOption(rowEditorForm.value))
}
function getNextDetailNo () {
const maxNo = flatDetailRows.value.reduce((acc, row) => {
const rowNo = parseInt(String(row?.nOnMLDetNo || '').trim(), 10)
return Number.isFinite(rowNo) ? Math.max(acc, rowNo) : acc
}, 0)
return String(maxNo + 1)
}
function resolveRowColorCode (row) {
return normalizeCodeValue(firstDefinedValue(row, [
'ColorCode',
'colorCode',
'sRenk',
's_renk',
'renk'
]))
}
function resolveRowItemDim1Code (row) {
return normalizeCodeValue(firstDefinedValue(row, [
'ItemDim1Code',
'itemDim1Code',
'sBeden',
's_beden'
]))
}
function extractNamedArray (payload, keys) {
for (const key of keys) {
if (Array.isArray(payload?.[key])) return payload[key]
if (Array.isArray(payload?.data?.[key])) return payload.data[key]
}
return []
}
function parseDateSortValue (value) {
const rawValue = String(value || '').trim()
if (!rawValue) return 0
const normalizedValue = rawValue.replace(' ', 'T')
const timeValue = Date.parse(normalizedValue)
return Number.isFinite(timeValue) ? timeValue : 0
}
function sortHistoryRowsByDateDesc (rows) {
return [...rows].sort((a, b) => parseDateSortValue(b?.dateValue) - parseDateSortValue(a?.dateValue))
}
function resolveHistorySourceType (item, forcedType = '') {
const normalizedType = String(forcedType || firstDefinedValue(item, [
'sourceType',
'source',
'kaynak',
'historyType',
'tip'
])).trim().toLowerCase()
if (['recipe', 'recete', 'uretim'].includes(normalizedType)) return 'recipe'
if (['purchase', 'invoice', 'satinalma', 'baggi_v3', 'baggi-v3'].includes(normalizedType)) return 'purchase'
if (firstDefinedValue(item, ['MasrafKodu', 'FaturaKodu', 'EvrakFiyat', 'ItemDim1Code']) !== '') return 'purchase'
if (firstDefinedValue(item, ['nOnMLNo', 'dteIslemTarihi', 'lDovizFiyati', 'lDovizTutari']) !== '') return 'recipe'
return normalizedType || 'history'
}
function resolveHistorySourceLabel (sourceType) {
if (sourceType === 'purchase') return 'BAGGI_V3'
if (sourceType === 'recipe') return 'URETIM'
return 'HISTORY'
}
function normalizeHistoryRowFromItem (item, index, forcedType = '') {
const sourceType = resolveHistorySourceType(item, forcedType)
const price = parseMoneyInput(firstDefinedValue(item, [
'EvrakFiyat',
'evrakFiyat',
'fiyat_girilen',
'fiyatGirilen',
'price',
'fiyat',
'birimFiyat',
'unitPrice',
'lDovizFiyati',
'lFiyat'
]))
const quantity = parseMoneyInput(firstDefinedValue(item, [
'Miktar',
'miktar',
'quantity',
'qty',
'adet',
'lMiktar'
]))
const amountValue = firstDefinedValue(item, [
'EvrakTutar',
'evrakTutar',
'tutar',
'amount',
'toplamTutar',
'lTutar',
'lDovizTutari'
])
const currency = normalizePriceCurrency(firstDefinedValue(item, [
'EvrakDoviz',
'evrakDoviz',
'fiyat_doviz',
'fiyatDoviz',
'currency',
'priceCurrency',
'prBr',
'pb',
'sDovizCinsi',
'USD'
]))
const dateValue = String(firstDefinedValue(item, [
'Tarih',
'tarih',
'InvoiceDate',
'invoiceDate',
'dteIslemTarihi',
'islemTarihi',
'dteKayitTarihi',
'date'
])).trim()
const amount = amountValue === ''
? price * quantity
: parseMoneyInput(amountValue)
return {
__historyKey: String(firstDefinedValue(item, ['id', '__historyKey', 'historyKey'])).trim() || `${sourceType}-${index}`,
raw: item,
sourceType,
sourceLabel: resolveHistorySourceLabel(sourceType),
dateValue,
dateLabel: formatDateTR(dateValue),
invoiceCode: String(firstDefinedValue(item, [
'FaturaKodu',
'faturaKodu',
'InvoiceNumber',
'invoiceCode',
'FaturaNo',
'nOnMLNo',
'n_onml_no'
])).trim(),
companyCode: String(firstDefinedValue(item, [
'FirmaKodu',
'firmaKodu',
'CurrAccCode',
'currAccCode',
'accountCode',
'vendorCode'
])).trim(),
companyDescription: String(firstDefinedValue(item, [
'FirmaAciklama',
'firmaAciklama',
'CurrAccDescription',
'currAccDescription',
'companyDescription',
'vendorDescription'
])).trim(),
itemCode: String(firstDefinedValue(item, [
'MasrafKodu',
'masrafKodu',
'sKodu',
's_kodu',
'ItemCode',
'itemCode'
])).trim(),
itemDescription: String(firstDefinedValue(item, [
'MasrafDetay',
'masrafDetay',
'sAciklama',
's_aciklama',
'ItemDescription',
'itemDescription',
'description'
])).trim(),
colorCode: String(firstDefinedValue(item, [
'ColorCode',
'colorCode',
'sRenk',
's_renk'
])).trim(),
colorDescription: String(firstDefinedValue(item, [
'ColorDescription',
'colorDescription',
'sRenkAdi',
's_renk_adi'
])).trim(),
itemDim1Code: String(firstDefinedValue(item, [
'ItemDim1Code',
'itemDim1Code',
'sBeden',
's_beden'
])).trim(),
itemDim1Description: String(firstDefinedValue(item, [
'ItemDim1Description',
'itemDim1Description',
'sAciklama2',
's_aciklama2'
])).trim(),
priceType: String(item?.priceType || '').trim(),
quantity,
unit: String(firstDefinedValue(item, [
'BIRIM',
'birim',
'unit',
'Unit',
'sBirim',
's_birim'
])).trim(),
price,
amount,
currency: currency || 'USD',
recipeInfo: String(firstDefinedValue(item, [
'recipeInfo',
'receteBilgisi',
'recete',
'DUMMY',
'nUrtReceteID',
'sAciklama3',
'Description',
'Aciklama',
'Aciklama1',
'LineDescription',
'note'
])).trim()
}
}
function normalizeBulkPriceItems (payload) {
const list = Array.isArray(payload)
? payload
: extractNamedArray(payload, ['items', 'data', 'updates', 'matchedItems', 'rows'])
return list.map((item, index) => {
const price = parseMoneyInput(firstDefinedValue(item, [
'EvrakFiyat',
'evrakFiyat',
'fiyat_girilen',
'fiyatGirilen',
'price',
'fiyat',
'unitPrice',
'birimFiyat',
'lDovizFiyati',
'lFiyat'
]))
const currency = normalizePriceCurrency(firstDefinedValue(item, [
'EvrakDoviz',
'evrakDoviz',
'fiyat_doviz',
'fiyatDoviz',
'currency',
'priceCurrency',
'prBr',
'pb',
'sDovizCinsi'
]))
return {
__updateKey: `upd-${index}`,
__rowKey: String(firstDefinedValue(item, ['__rowKey', 'rowKey'])).trim(),
nOnMLDetNo: String(firstDefinedValue(item, ['nOnMLDetNo', 'n_onml_det_no'])).trim(),
nHammaddeTuruNo: String(firstDefinedValue(item, ['nHammaddeTuruNo', 'n_hammadde_turu_no'])).trim(),
sKodu: normalizeCodeValue(firstDefinedValue(item, ['sKodu', 's_kodu', 'stokKodu', 'MasrafKodu', 'masrafKodu', 'ItemCode', 'itemCode'])),
colorCode: normalizeCodeValue(firstDefinedValue(item, ['ColorCode', 'colorCode', 'sRenk', 's_renk', 'renk'])),
itemDim1Code: normalizeCodeValue(firstDefinedValue(item, ['ItemDim1Code', 'itemDim1Code', 'item_dim1_code', 'sBeden', 's_beden'])),
inputPrice: normalizeInputPrice(price),
fiyat_girilen: price,
inputPricePrBr: currency || 'USD',
fiyat_doviz: currency || 'USD'
}
})
}
function normalizeLineHistoryRows (payload) {
const purchaseRows = extractNamedArray(payload, [
'purchaseRows',
'purchase_items',
'purchaseItems',
'invoiceRows',
'invoice_items',
'invoiceItems',
'baggiV3Rows',
'baggi_v3_rows',
'baggiRows'
])
const recipeRows = extractNamedArray(payload, [
'recipeRows',
'recipe_items',
'recipeItems',
'uretimRows',
'uretim_rows',
'productionRows'
])
if (purchaseRows.length > 0 || recipeRows.length > 0) {
const normalizedPurchaseRows = sortHistoryRowsByDateDesc(
purchaseRows.map((item, index) => normalizeHistoryRowFromItem(item, index, 'purchase'))
)
const normalizedRecipeRows = sortHistoryRowsByDateDesc(
recipeRows.map((item, index) => normalizeHistoryRowFromItem(item, index, 'recipe'))
)
return sortHistoryRowsByDateDesc([
...normalizedPurchaseRows,
...normalizedRecipeRows
]).slice(0, LINE_HISTORY_COMBINED_ROW_LIMIT)
}
const flatList = Array.isArray(payload)
? payload
: extractNamedArray(payload, ['items', 'data', 'rows'])
const normalizedRows = flatList.map((item, index) => normalizeHistoryRowFromItem(item, index))
const purchaseHistoryRows = sortHistoryRowsByDateDesc(normalizedRows.filter(row => row.sourceType === 'purchase'))
const recipeHistoryRows = sortHistoryRowsByDateDesc(normalizedRows.filter(row => row.sourceType === 'recipe'))
const otherHistoryRows = sortHistoryRowsByDateDesc(normalizedRows.filter(row => !['purchase', 'recipe'].includes(row.sourceType)))
return sortHistoryRowsByDateDesc([
...purchaseHistoryRows,
...recipeHistoryRows,
...otherHistoryRows
]).slice(0, LINE_HISTORY_COMBINED_ROW_LIMIT)
}
function rowMatchesBulkUpdate (row, update) {
if (update.__rowKey && update.__rowKey === row.__rowKey) return true
if (update.nOnMLDetNo && String(row?.nOnMLDetNo || '').trim() === update.nOnMLDetNo) return true
const rowKodu = normalizeCodeValue(row?.sKodu)
const rowRenk = resolveRowColorCode(row)
const rowTur = String(row?.nHammaddeTuruNo || '').trim()
const rowItemDim1Code = resolveRowItemDim1Code(row)
if (update.nHammaddeTuruNo && update.sKodu) {
if (rowTur === update.nHammaddeTuruNo && rowKodu === update.sKodu) return true
}
if (update.sKodu && update.colorCode && update.itemDim1Code) {
if (rowKodu === update.sKodu && rowRenk === update.colorCode && rowItemDim1Code === update.itemDim1Code) return true
}
if (update.sKodu && update.colorCode) {
if (rowKodu === update.sKodu && rowRenk === update.colorCode) return true
}
if (update.sKodu && update.itemDim1Code) {
if (rowKodu === update.sKodu && rowItemDim1Code === update.itemDim1Code) return true
}
if (update.sKodu) {
return rowKodu === update.sKodu
}
return false
}
function resolveExchangeRateValue (currency) {
const normalizedCurrency = normalizePriceCurrency(currency)
if (normalizedCurrency === 'TRY') return 1
if (normalizedCurrency === 'USD') return parseMoneyInput(exchangeRates.value?.usdRate)
if (normalizedCurrency === 'EUR') return parseMoneyInput(exchangeRates.value?.eurRate)
if (normalizedCurrency === 'GBP') return parseMoneyInput(exchangeRates.value?.gbpRate)
return 0
}
function resolveInputCurrency (row) {
return normalizePriceCurrency(row?.inputPricePrBr || row?.fiyat_doviz) || 'USD'
}
function resolveNumericRowQuantity (row) {
return parseMoneyInput(row?.miktarInput ?? row?.lMiktar)
}
function resolveNumericRowInputPrice (row) {
return parseMoneyInput(row?.inputPrice ?? row?.fiyat_girilen)
}
function resolveTRYUnitPriceByInput (inputPrice, inputCurrency, usdRate, eurRate, gbpRate) {
if (inputPrice <= 0) return 0
if (inputCurrency === 'TRY') return inputPrice
if (inputCurrency === 'USD') return usdRate > 0 ? inputPrice * usdRate : 0
if (inputCurrency === 'EUR') return eurRate > 0 ? inputPrice * eurRate : 0
if (inputCurrency === 'GBP') return gbpRate > 0 ? inputPrice * gbpRate : 0
return 0
}
function resolveUSDUnitPriceByInput (inputPrice, inputCurrency, usdRate, eurRate, gbpRate) {
if (inputPrice <= 0) return 0
if (inputCurrency === 'USD') return inputPrice
if (inputCurrency === 'TRY') return usdRate > 0 ? inputPrice / usdRate : 0
if (inputCurrency === 'EUR' || inputCurrency === 'GBP') {
const tryEquivalent = resolveTRYUnitPriceByInput(inputPrice, inputCurrency, usdRate, eurRate, gbpRate)
return usdRate > 0 ? tryEquivalent / usdRate : 0
}
return 0
}
function normalizeDetailRows (items, groupName = '') {
const list = Array.isArray(items) ? items : []
return list.map((x, i) => ({
...x,
__rowKey: x?.__rowKey || `${x?.nOnMLNo || ''}-${x?.nOnMLDetNo || ''}-${i}`,
miktarInput: x?.miktarInput ?? normalizeQuantityInput(x?.lMiktar),
inputPrice: x?.inputPrice ?? normalizeInputPrice(x?.fiyat_girilen),
inputPricePrBr: normalizePriceCurrency(x?.inputPricePrBr || x?.fiyat_doviz || x?.sDovizCinsi) || 'USD',
maliyeteDahil: x?.maliyeteDahil ?? normalizeBooleanFlag(x?.maliyete_dahil ?? x?.Maliyete_dahil),
cmPriceTypeId: normalizeCMPriceTypeId(x?.cmPriceTypeId ?? x?.cm_price_type_id, groupName || x?.sAciklama3),
draftChanged: Boolean(x?.draftChanged),
priceUpdateState: String(x?.priceUpdateState || '').trim()
}))
}
function normalizeDetailGroups (groups) {
const list = Array.isArray(groups) ? groups : []
return list.map(grp => {
const groupName = String(grp?.sAciklama3 || '').trim()
const items = normalizeDetailRows(grp?.items, groupName).map(row => ({
...row,
sAciklama3: String(grp?.sAciklama3 || row?.sAciklama3 || '').trim(),
cmPriceTypeId: normalizeCMPriceTypeId(row?.cmPriceTypeId ?? row?.cm_price_type_id, groupName || row?.sAciklama3)
}))
// USD TUTAR (DESC) sıralama
items.sort((a, b) => {
const valA = resolveRowUSDTutar(a)
const valB = resolveRowUSDTutar(b)
return valB - valA
})
return {
...grp,
items
}
})
}
function recalculateDetailRow (row, options = {}) {
const quantity = resolveNumericRowQuantity(row)
const inputPrice = resolveNumericRowInputPrice(row)
const inputCurrency = resolveInputCurrency(row)
const usdRate = resolveExchangeRateValue('USD')
const eurRate = resolveExchangeRateValue('EUR')
const gbpRate = resolveExchangeRateValue('GBP')
const unitTRY = resolveTRYUnitPriceByInput(inputPrice, inputCurrency, usdRate, eurRate, gbpRate)
const unitUSD = resolveUSDUnitPriceByInput(inputPrice, inputCurrency, usdRate, eurRate, gbpRate)
const unitEUR = eurRate > 0 ? unitTRY / eurRate : 0
const unitGBP = gbpRate > 0 ? unitTRY / gbpRate : 0
const maliyeteDahil = normalizeBooleanFlag(row?.maliyeteDahil ?? row?.maliyete_dahil ?? row?.Maliyete_dahil)
const cmPriceTypeId = normalizeCMPriceTypeId(row?.cmPriceTypeId ?? row?.cm_price_type_id, row?.sAciklama3)
row.lMiktar = quantity
row.fiyat_girilen = inputPrice
row.fiyat_doviz = inputCurrency
row.inputPricePrBr = inputCurrency
row.maliyeteDahil = maliyeteDahil
row.maliyete_dahil = maliyeteDahil ? 1 : 0
row.Maliyete_dahil = maliyeteDahil ? 1 : 0
row.cmPriceTypeId = cmPriceTypeId
row.cm_price_type_id = cmPriceTypeId
row.sDovizCinsi = 'USD'
row.lDovizKuru = usdRate
row.lDovizFiyati = unitUSD
row.lFiyat = unitTRY
row.lTutar = unitTRY * quantity
row.usdTutar = unitUSD * quantity
row.eurTutar = unitEUR * quantity
row.gbpTutar = unitGBP * quantity
row.lTutarUSD = row.usdTutar
row.lTutarEURO = row.eurTutar
row.lTutarGBP = row.gbpTutar
row.lTutarTL = row.lTutar
if (!options.preserveInputs) {
row.miktarInput = normalizeQuantityInput(quantity)
row.inputPrice = normalizeInputPrice(inputPrice)
}
if (options.markChanged) {
row.draftChanged = true
}
if (options.priceType !== undefined) {
row.sFiyatTipi = options.priceType
}
if (options.updateState !== undefined) {
row.priceUpdateState = options.updateState
}
return row
}
function recalculateAllDetailRows () {
detailGroups.value = detailGroups.value.map(grp => ({
...grp,
items: (Array.isArray(grp?.items) ? grp.items : []).map(row => recalculateDetailRow({ ...row }, { preserveInputs: true }))
}))
}
function resolveRowTRYTutar (row) {
const tryAmount = Number(row?.lTutar || 0)
return Number.isFinite(tryAmount) ? tryAmount : 0
}
function resolveRowEURTutar (row) {
const eurAmount = Number(row?.eurTutar || 0)
return Number.isFinite(eurAmount) ? eurAmount : 0
}
function resolveRowGBPTutar (row) {
const gbpAmount = Number(row?.gbpTutar || 0)
return Number.isFinite(gbpAmount) ? gbpAmount : 0
}
function resolveRowUSDTutar (row) {
const usdAmount = Number(row?.usdTutar || 0)
if (Number.isFinite(usdAmount)) return usdAmount
const miktar = resolveNumericRowQuantity(row)
const dovizFiyati = Number(row?.lDovizFiyati || 0)
const calc = miktar * dovizFiyati
return Number.isFinite(calc) ? calc : 0
}
function shouldIgnoreGroupMaliyeteDahil (grp) {
return isCMGroupName(grp?.sAciklama3)
}
function shouldIncludeRowInGroupTotal (grp, row) {
return shouldIgnoreGroupMaliyeteDahil(grp) || normalizeBooleanFlag(row?.maliyeteDahil)
}
function resolveGroupTRYTutar (grp) {
const items = Array.isArray(grp?.items) ? grp.items : []
return items.reduce((acc, row) => acc + (shouldIncludeRowInGroupTotal(grp, row) ? resolveRowTRYTutar(row) : 0), 0)
}
function resolveGroupUSDTutar (grp) {
const items = Array.isArray(grp?.items) ? grp.items : []
return items.reduce((acc, row) => acc + (shouldIncludeRowInGroupTotal(grp, row) ? resolveRowUSDTutar(row) : 0), 0)
}
function groupKey (grp, gi) {
return `${String(grp?.sAciklama3 || 'TANIMSIZ')}-${gi}`
}
function isGroupOpen (grp, gi) {
return groupOpenState.value[groupKey(grp, gi)] !== false
}
function toggleGroup (grp, gi) {
const key = groupKey(grp, gi)
groupOpenState.value = {
...groupOpenState.value,
[key]: !isGroupOpen(grp, gi)
}
}
function resolveElHeight (refVal) {
const el = refVal?.$el || refVal
return Number(el?.offsetHeight || 0)
}
function updateStickyTop () {
const stackH = resolveElHeight(stickyStackRef.value)
// Quasar default header height is usually around 50px
// If we are in a sub-layout or context where top header is not 50px, this might need adjustment
const layoutHeader = document.querySelector('.q-header')
const layoutHeaderH = layoutHeader ? layoutHeader.offsetHeight : 50
subHeaderTop.value = (stackH || 0) + layoutHeaderH
}
function toggleHeaderInfo () {
headerInfoCollapsed.value = !headerInfoCollapsed.value
nextTick(() => {
updateStickyTop()
})
}
function onUretimSekliChange (newId) {
if (!detailHeader.value) return
const found = productionTypes.value.find(t => String(t.id) === String(newId))
if (found) {
detailHeader.value.UretimSekliID = found.id
detailHeader.value.UretimSekli = found.aciklama
schedulePersistLocalDraft()
}
}
function buildDetailFetchParams () {
if (isNoCostDetail.value) {
return {
detail_source: 'no-cost',
urun_kodu: productCode.value,
recete_kodu: recipeCode.value,
trace_id: traceId.value
}
}
return {
n_onml_no: onMLNo.value,
urun_kodu: productCode.value,
trace_id: traceId.value
}
}
async function fetchExchangeRatesForCostDate (targetDate = costDate.value) {
const normalizedDate = normalizeDateInput(targetDate)
if (!normalizedDate) {
exchangeRates.value = createEmptyExchangeRates()
recalculateAllDetailRows()
return
}
try {
const response = await get('/pricing/production-product-costing/has-cost-detail-exchange-rates', {
maliyet_tarihi: normalizedDate,
trace_id: traceId.value
})
exchangeRates.value = normalizeExchangeRatesPayload(response, normalizedDate)
} catch (err) {
exchangeRates.value = {
...createEmptyExchangeRates(),
rateDate: normalizedDate
}
slog.error('production-product-costing.detail', 'exchange-rates:error', {
trace_id: traceId.value,
maliyet_tarihi: normalizedDate,
detail: await extractApiErrorDetail(err)
})
$q.notify({
type: 'negative',
message: await extractApiErrorDetail(err),
position: 'top-right'
})
} finally {
recalculateAllDetailRows()
}
}
async function fetchDetail () {
if (isNoCostDetail.value && !recipeCode.value) {
detailError.value = 'Recete kodu bulunamadi'
detailGroups.value = []
detailHeader.value = null
exchangeRates.value = createEmptyExchangeRates()
return
}
if (!isNoCostDetail.value && !onMLNo.value) {
detailError.value = 'nOnMLNo bulunamadi'
detailGroups.value = []
detailHeader.value = null
exchangeRates.value = createEmptyExchangeRates()
return
}
detailLoading.value = true
detailError.value = ''
detailGroups.value = []
detailHeader.value = null
costDate.value = ''
exchangeRates.value = createEmptyExchangeRates()
newRowSequence.value = 0
rowEditorDialogOpen.value = false
rowEditorMode.value = 'new'
rowEditorTargetRowKey.value = ''
rowEditorForm.value = createRowEditorForm()
rowEditorHammaddeAllOptions.value = []
rowEditorHammaddeOptions.value = []
rowEditorHammaddeLoading.value = false
rowEditorItemAllOptions.value = []
rowEditorItemOptions.value = []
rowEditorItemLoading.value = false
rowEditorColorAllOptions.value = []
rowEditorColorOptions.value = []
rowEditorColorLoading.value = false
lineHistoryDialogOpen.value = false
lineHistoryLoading.value = false
lineHistoryError.value = ''
lineHistoryRows.value = []
lineHistoryTargetRowKey.value = ''
lineHistoryTargetHammaddeTuruNo.value = ''
lineHistoryTargetItemCode.value = ''
lineHistoryTargetSummary.value = ''
lineHistorySearchMode.value = 'exact'
lineHistoryLastPurchaseMatchStage.value = ''
lineHistoryLastRecipeMatchStage.value = ''
try {
const detailParams = buildDetailFetchParams()
slog.info('production-product-costing.detail', 'fetch-detail:start', {
trace_id: traceId.value,
detail_source: detailSource.value || 'has-cost',
n_onml_no: onMLNo.value,
urun_kodu: productCode.value,
recete_kodu: recipeCode.value
})
const [headerData, groupsData, typesData] = await Promise.all([
get('/pricing/production-product-costing/has-cost-detail-header', detailParams),
get('/pricing/production-product-costing/has-cost-detail-groups', detailParams),
get('/pricing/production-product-costing/production-types', {
trace_id: traceId.value
})
])
detailHeader.value = headerData && typeof headerData === 'object' ? headerData : null
productionTypes.value = Array.isArray(typesData) ? typesData : []
costDate.value = normalizeDateInput(detailHeader.value?.dteKayitTarihi)
detailGroups.value = normalizeDetailGroups(groupsData)
initialHeaderSnapshot.value = currentHeaderSnapshot.value
// Optional: hydrate local draft after base data load.
tryHydrateFromLocalDraft()
// ensure required placeholder rows exist (based on mapping screen)
try {
const mappings = await fetchRequiredParcaMappings()
ensureNoCostRequiredRowsFromMappings(mappings)
} catch (err) {
slog.error('production-product-costing.detail', 'required-mapping:error', {
trace_id: traceId.value,
detail: await extractApiErrorDetail(err)
})
}
const initialOpen = {}
detailGroups.value.forEach((grp, gi) => {
initialOpen[groupKey(grp, gi)] = true
})
groupOpenState.value = initialOpen
slog.info('production-product-costing.detail', 'fetch-detail:ok', {
trace_id: traceId.value,
detail_source: detailSource.value || 'has-cost',
group_count: detailGroups.value.length,
item_count: flatDetailRows.value.length,
urun_kodu: detailHeader.value?.UrunKodu || productCode.value,
n_urt_recete_id: detailHeader.value?.nUrtReceteID || ''
})
} catch (err) {
detailError.value = await extractApiErrorDetail(err)
slog.error('production-product-costing.detail', 'fetch-detail:error', {
trace_id: traceId.value,
detail_source: detailSource.value || 'has-cost',
n_onml_no: onMLNo.value,
urun_kodu: productCode.value,
recete_kodu: recipeCode.value,
detail: detailError.value
})
} finally {
detailLoading.value = false
}
}
function goBack () {
if (isNoCostDetail.value) {
router.push({ name: 'production-product-costing-no-cost' })
return
}
const urunKodu = productCode.value
if (!urunKodu) {
router.push({ name: 'production-product-costing-has-cost' })
return
}
router.push({
name: 'production-product-costing-has-cost-history',
query: { urun_kodu: urunKodu }
})
}
function buildDetailItemRequestPayload (row) {
return {
__rowKey: row.__rowKey,
n_onml_no: String(row?.nOnMLNo || detailHeader.value?.nOnMLNo || onMLNo.value || '').trim(),
n_onml_det_no: String(row?.nOnMLDetNo || '').trim(),
n_hammadde_turu_no: String(row?.nHammaddeTuruNo || '').trim(),
s_kodu: String(row?.sKodu || '').trim(),
s_aciklama: String(row?.sAciklama || '').trim(),
s_renk: String(row?.sRenk || '').trim(),
color_code: String(firstDefinedValue(row, ['ColorCode', 'colorCode', 'sRenk', 's_renk'])).trim(),
color_description: String(firstDefinedValue(row, ['ColorDescription', 'colorDescription', 'sRenkAdi', 's_renk_adi'])).trim(),
item_dim1_code: String(firstDefinedValue(row, ['ItemDim1Code', 'itemDim1Code', 'sBeden', 's_beden'])).trim(),
item_dim1_description: String(firstDefinedValue(row, ['ItemDim1Description', 'itemDim1Description', 'sAciklama2', 's_aciklama2'])).trim(),
s_birim: String(row?.sBirim || '').trim(),
l_miktar: resolveNumericRowQuantity(row),
fiyat_girilen: resolveNumericRowInputPrice(row),
fiyat_doviz: resolveInputCurrency(row),
maliyete_dahil: row?.maliyeteDahil ? 1 : 0,
cm_price_type_id: normalizeCMPriceTypeId(row?.cmPriceTypeId ?? row?.cm_price_type_id, row?.sAciklama3)
}
}
function applyPriceSelectionToRow (targetRowKey, price, currency, priceType) {
const normalizedCurrency = normalizePriceCurrency(currency) || 'USD'
const normalizedPrice = parseMoneyInput(price)
const finalPriceType = priceType || 'SAF'
detailGroups.value = detailGroups.value.map(grp => ({
...grp,
items: (Array.isArray(grp?.items) ? grp.items : []).map(row => {
if (row.__rowKey !== targetRowKey) return row
return recalculateDetailRow({
...row,
inputPrice: normalizeInputPrice(normalizedPrice),
fiyat_girilen: normalizedPrice,
inputPricePrBr: normalizedCurrency,
fiyat_doviz: normalizedCurrency
}, {
priceType: finalPriceType,
updateState: 'selection',
markChanged: true
})
})
}))
}
async function fetchSimilarItemHistory (mode = 'prefix') {
if (mode === 'exact') {
const row = flatDetailRows.value.find(r => r.__rowKey === lineHistoryTargetRowKey.value)
if (row) {
await openLineHistory(row)
}
return
}
if (!lineHistoryTargetHammaddeTuruNo.value && !lineHistoryTargetItemCode.value) return
const normalizedMode = mode === 'alternative' ? 'alternative' : 'prefix'
lineHistorySearchMode.value = normalizedMode
lineHistoryLoading.value = true
lineHistoryError.value = ''
try {
slog.info('production-product-costing.detail', 'line-history-similar:start', {
trace_id: traceId.value,
search_mode: normalizedMode,
n_hammadde_turu_no: lineHistoryTargetHammaddeTuruNo.value,
s_kodu: lineHistoryTargetItemCode.value,
maliyet_tarihi: costDate.value,
limit: LINE_HISTORY_ROW_LIMIT
})
const response = await get('/pricing/production-product-costing/has-cost-detail-similar-history', {
n_hammadde_turu_no: lineHistoryTargetHammaddeTuruNo.value,
s_kodu: lineHistoryTargetItemCode.value,
maliyet_tarihi: costDate.value,
limit: LINE_HISTORY_ROW_LIMIT,
search_mode: normalizedMode,
trace_id: traceId.value
})
lineHistoryRows.value = normalizeLineHistoryRows(response)
lineHistoryLastPurchaseMatchStage.value = String(response?.purchase_match_stage || '').trim()
lineHistoryLastRecipeMatchStage.value = String(response?.recipe_match_stage || '').trim()
slog.info('production-product-costing.detail', 'line-history-similar:ok', {
trace_id: traceId.value,
search_mode: normalizedMode,
purchase_match_stage: response?.purchase_match_stage || '',
recipe_match_stage: response?.recipe_match_stage || '',
similar_code_prefix: response?.similar_code_prefix || '',
row_count: lineHistoryRows.value.length
})
if (lineHistoryRows.value.length === 0) {
$q.notify({
type: 'warning',
message: 'Bu hammadde türü için de benzer ürün kaydı bulunamadı.',
position: 'top-right'
})
}
} catch (err) {
lineHistoryError.value = await extractApiErrorDetail(err)
slog.error('production-product-costing.detail', 'line-history-similar:error', {
trace_id: traceId.value,
search_mode: normalizedMode,
n_hammadde_turu_no: lineHistoryTargetHammaddeTuruNo.value,
s_kodu: lineHistoryTargetItemCode.value,
error: lineHistoryError.value
})
} finally {
lineHistoryLoading.value = false
}
}
function onRowQuantityInput (row, value) {
row.miktarInput = value
recalculateDetailRow(row, {
preserveInputs: true,
markChanged: true
})
schedulePersistLocalDraft()
triggerUIUpdate()
}
function triggerUIUpdate () {
detailGroups.value = [...detailGroups.value]
}
function normalizeRowQuantityDisplay (row) {
row.miktarInput = normalizeQuantityInput(row?.miktarInput)
recalculateDetailRow(row, {
preserveInputs: true
})
schedulePersistLocalDraft()
triggerUIUpdate()
}
function onRowInputPriceChange (row, value) {
row.inputPrice = value
recalculateDetailRow(row, {
preserveInputs: true,
priceType: 'MAN',
updateState: 'manual',
markChanged: true
})
schedulePersistLocalDraft()
triggerUIUpdate()
}
function normalizeRowPriceDisplay (row) {
row.inputPrice = normalizeInputPrice(row?.inputPrice)
recalculateDetailRow(row, {
preserveInputs: true
})
schedulePersistLocalDraft()
triggerUIUpdate()
}
function onRowInputPriceCurrencyChange (row, value) {
row.inputPricePrBr = normalizePriceCurrency(value) || 'USD'
recalculateDetailRow(row, {
preserveInputs: true,
priceType: 'MAN',
updateState: 'manual',
markChanged: true
})
schedulePersistLocalDraft()
triggerUIUpdate()
}
function onRowMaliyeteDahilChange (row, value) {
row.maliyeteDahil = Boolean(value)
row.maliyete_dahil = value ? 1 : 0
row.Maliyete_dahil = value ? 1 : 0
row.draftChanged = true
schedulePersistLocalDraft()
triggerUIUpdate()
}
function onRowCMPriceTypeChange (row, value) {
row.cmPriceTypeId = isCMGroupName(row?.sAciklama3) ? (value ? 2 : 1) : null
row.cm_price_type_id = row.cmPriceTypeId
row.draftChanged = true
schedulePersistLocalDraft()
triggerUIUpdate()
}
async function fetchBulkItemPrices () {
const flatRows = detailGroups.value.flatMap(grp => Array.isArray(grp?.items) ? grp.items : [])
if (flatRows.length === 0) {
$q.notify({
type: 'warning',
message: 'Toplu fiyat cagrisi icin satir bulunamadi.',
position: 'top-right'
})
return
}
bulkPriceLoading.value = true
try {
const response = await post('/pricing/production-product-costing/has-cost-detail-bulk-prices', {
n_onml_no: onMLNo.value,
urun_kodu: detailHeader.value?.UrunKodu || productCode.value,
n_urt_recete_id: detailHeader.value?.nUrtReceteID || '',
maliyet_tarihi: costDate.value,
items: flatRows.map(buildDetailItemRequestPayload)
}, {
params: {
trace_id: traceId.value
}
})
const updates = normalizeBulkPriceItems(response)
if (updates.length === 0) {
$q.notify({
type: 'warning',
message: 'Toplu fiyat cagrisindan uygulanacak veri donmedi.',
position: 'top-right'
})
return
}
let appliedCount = 0
detailGroups.value = detailGroups.value.map(grp => ({
...grp,
items: (Array.isArray(grp?.items) ? grp.items : []).map(row => {
const match = updates.find(update => rowMatchesBulkUpdate(row, update))
if (!match) return row
appliedCount += 1
return recalculateDetailRow({
...row,
inputPrice: match.inputPrice,
fiyat_girilen: match.fiyat_girilen,
inputPricePrBr: match.inputPricePrBr,
fiyat_doviz: match.fiyat_doviz
}, {
priceType: match.priceType || 'SAF',
updateState: 'bulk',
markChanged: true
})
})
}))
$q.notify({
type: appliedCount > 0 ? 'positive' : 'warning',
message: appliedCount > 0
? `${appliedCount} kalemin fiyat ve pr. br. bilgisi guncellendi.`
: 'Donen veriler satirlarla eslestirilemedi.',
position: 'top-right'
})
} catch (err) {
$q.notify({
type: 'negative',
message: await extractApiErrorDetail(err),
position: 'top-right'
})
} finally {
bulkPriceLoading.value = false
}
}
async function openLineHistory (row) {
const rowCode = String(row?.sKodu || '').trim()
if (!rowCode) {
$q.notify({
type: 'warning',
message: 'History acmak icin satirda kod bilgisi olmali.',
position: 'top-right'
})
return
}
lineHistoryDialogOpen.value = true
lineHistoryLoading.value = true
lineHistoryError.value = ''
lineHistoryRows.value = []
lineHistoryTargetRowKey.value = row.__rowKey
lineHistoryTargetHammaddeTuruNo.value = String(row?.nHammaddeTuruNo || '').trim()
lineHistoryTargetItemCode.value = rowCode
lineHistoryTargetSummary.value = `${rowCode} | ${String(row?.sAciklama || '').trim() || 'TANIMSIZ'}`
lineHistorySearchMode.value = 'exact'
lineHistoryLastPurchaseMatchStage.value = ''
lineHistoryLastRecipeMatchStage.value = ''
try {
const response = await get('/pricing/production-product-costing/has-cost-detail-line-history', {
n_onml_no: onMLNo.value,
urun_kodu: detailHeader.value?.UrunKodu || productCode.value,
n_urt_recete_id: detailHeader.value?.nUrtReceteID || '',
n_onml_det_no: String(row?.nOnMLDetNo || '').trim(),
n_hammadde_turu_no: String(row?.nHammaddeTuruNo || '').trim(),
s_kodu: rowCode,
s_renk: String(row?.sRenk || '').trim(),
color_code: String(firstDefinedValue(row, ['ColorCode', 'colorCode', 'sRenk', 's_renk'])).trim(),
item_dim1_code: String(firstDefinedValue(row, ['ItemDim1Code', 'itemDim1Code', 'sBeden', 's_beden'])).trim(),
maliyet_tarihi: costDate.value,
trace_id: traceId.value
})
lineHistoryRows.value = normalizeLineHistoryRows(response)
} catch (err) {
lineHistoryError.value = await extractApiErrorDetail(err)
} finally {
lineHistoryLoading.value = false
}
}
function onDetailRowClick (evt, row) {
// Grid satırı tıklanabilir değil, artık ikonlar ve butonlar kullanılıyor.
}
function applyLineHistorySelection (historyRow) {
applyPriceSelectionToRow(lineHistoryTargetRowKey.value, historyRow?.price, historyRow?.currency, historyRow?.priceType)
lineHistoryDialogOpen.value = false
$q.notify({
type: 'positive',
message: 'Secilen history fiyati satira uygulandi.',
position: 'top-right'
})
}
function resolveDetailRowClass (row) {
const key = String(row?.__rowKey || '').trim()
if (key && requiredAttentionRowKeys.value?.[key]) return 'pcd-detail-row-required'
if (row?.requiredPlaceholder) return 'pcd-detail-row-required'
return row?.draftChanged ? 'pcd-detail-row-secondary' : ''
}
async function bootstrapRowEditorOptions () {
try {
const hammaddeSearch = String(rowEditorForm.value.nHammaddeTuruNo || '').trim()
const itemSearch = String(rowEditorForm.value.sKodu || '').trim()
const colorSearch = String(rowEditorForm.value.ColorCode || '').trim()
const [hammaddeRows, itemRows] = await Promise.all([
refreshRowEditorOptions('hammadde', hammaddeSearch),
itemSearch.length >= ROW_EDITOR_ITEM_MIN_SEARCH_LENGTH
? refreshRowEditorOptions('item', itemSearch)
: Promise.resolve([])
])
if (itemSearch.length < ROW_EDITOR_ITEM_MIN_SEARCH_LENGTH) {
rowEditorItemAllOptions.value = []
rowEditorItemOptions.value = []
primeRowEditorOptionsFromForm()
}
const selectedHammadde = hammaddeRows.find(opt => String(opt?.value || '') === hammaddeSearch)
if (selectedHammadde) {
rowEditorForm.value.sHammaddeTuruAdi = String(selectedHammadde.sHammaddeTuruAdi || rowEditorForm.value.sHammaddeTuruAdi || '').trim()
rowEditorForm.value.sAciklama3 = String(selectedHammadde.sAciklama3 || rowEditorForm.value.sAciklama3 || 'TANIMSIZ').trim() || 'TANIMSIZ'
rowEditorForm.value.sParcaAdi = String(selectedHammadde.sParcaAdi || selectedHammadde.sAciklama3 || rowEditorForm.value.sParcaAdi || '').trim()
}
const selectedItem = itemRows.find(opt => String(opt?.value || '') === itemSearch)
if (selectedItem) {
rowEditorForm.value.nStokID = String(selectedItem.nStokID || rowEditorForm.value.nStokID || '').trim()
rowEditorForm.value.sModel = String(selectedItem.sModel || rowEditorForm.value.sModel || '').trim()
rowEditorForm.value.sAciklama = String(selectedItem.sAciklama || rowEditorForm.value.sAciklama || '').trim()
rowEditorForm.value.sBirim = extractPrimaryUnitValue(selectedItem.sBirim || rowEditorForm.value.sBirim || 'AD') || 'AD'
}
await refreshRowEditorOptions('color', colorSearch)
primeRowEditorOptionsFromForm()
} catch (err) {
rowEditorHammaddeAllOptions.value = []
rowEditorHammaddeOptions.value = []
rowEditorItemAllOptions.value = []
rowEditorItemOptions.value = []
rowEditorColorAllOptions.value = []
rowEditorColorOptions.value = []
$q.notify({
type: 'negative',
message: await extractApiErrorDetail(err),
position: 'top-right'
})
}
}
function onRowEditorHammaddeChange (value) {
const selected = rowEditorHammaddeOptions.value.find(opt => String(opt?.value || '') === String(value || ''))
rowEditorForm.value.nHammaddeTuruNo = String(value || '').trim()
if (!selected) return
rowEditorForm.value.sHammaddeTuruAdi = String(selected.sHammaddeTuruAdi || '').trim()
rowEditorForm.value.sAciklama3 = String(selected.sAciklama3 || 'TANIMSIZ').trim() || 'TANIMSIZ'
rowEditorForm.value.sParcaAdi = String(selected.sParcaAdi || selected.sAciklama3 || '').trim()
if (!isCMGroupName(rowEditorForm.value.sAciklama3)) {
rowEditorForm.value.cmPriceTypeChecked = false
}
}
async function onRowEditorItemChange (value) {
const selected = rowEditorItemOptions.value.find(opt => String(opt?.value || '') === String(value || ''))
rowEditorForm.value.sKodu = String(value || '').trim()
if (!selected) return
const previousColorCode = String(rowEditorForm.value.ColorCode || '').trim()
rowEditorForm.value.nStokID = String(selected.nStokID || '').trim()
rowEditorForm.value.sModel = String(selected.sModel || '').trim()
rowEditorForm.value.sAciklama = String(selected.sAciklama || '').trim()
rowEditorForm.value.sBirim = extractPrimaryUnitValue(selected.sBirim || rowEditorForm.value.sBirim || 'AD') || 'AD'
rowEditorForm.value.ColorCode = ''
rowEditorForm.value.sRenk = ''
rowEditorForm.value.ColorDescription = ''
try {
await refreshRowEditorOptions('color', previousColorCode)
} catch (err) {
$q.notify({
type: 'negative',
message: `Renk lookup getirilemedi: ${await extractApiErrorDetail(err)}`,
position: 'top-right'
})
}
const matchedColor = rowEditorColorOptions.value.find(opt => String(opt?.value || '') === previousColorCode)
if (matchedColor) {
rowEditorForm.value.ColorCode = previousColorCode
rowEditorForm.value.sRenk = previousColorCode
rowEditorForm.value.ColorDescription = String(matchedColor.colorDescription || '').trim()
}
}
function onRowEditorColorChange (value) {
const selected = rowEditorColorOptions.value.find(opt => String(opt?.value || '') === String(value || ''))
const normalizedValue = String(value || '').trim()
rowEditorForm.value.ColorCode = normalizedValue
rowEditorForm.value.sRenk = normalizedValue
if (!selected) return
rowEditorForm.value.ColorDescription = String(selected.colorDescription || '').trim()
}
function buildRowFromEditorForm () {
const form = rowEditorForm.value
const existingRow = flatDetailRows.value.find(row => row.__rowKey === rowEditorTargetRowKey.value)
const cmPriceTypeId = normalizeCMPriceTypeId(form.cmPriceTypeChecked ? 2 : 1, form.sAciklama3)
if (!existingRow) {
newRowSequence.value += 1
}
return recalculateDetailRow({
...(existingRow || {}),
__rowKey: existingRow?.__rowKey || `new-editor-row-${newRowSequence.value}`,
isNew: Boolean(existingRow?.isNew) || rowEditorMode.value === 'new',
nOnMLNo: detailHeader.value?.nOnMLNo || onMLNo.value || '',
nStokID: String(form.nStokID || '').trim(),
sModel: String(form.sModel || '').trim(),
nOnMLDetNo: String(form.nOnMLDetNo || '').trim(),
sParcaAdi: String(form.sParcaAdi || form.sAciklama3 || '').trim(),
sAciklama3: String(form.sAciklama3 || form.sParcaAdi || 'TANIMSIZ').trim() || 'TANIMSIZ',
nHammaddeTuruNo: String(form.nHammaddeTuruNo || '').trim(),
sHammaddeTuruAdi: String(form.sHammaddeTuruAdi || '').trim(),
sKodu: String(form.sKodu || '').trim(),
sAciklama: String(form.sAciklama || '').trim(),
sRenk: String(form.sRenk || form.ColorCode || '').trim(),
ColorCode: String(form.ColorCode || form.sRenk || '').trim(),
ColorDescription: String(form.ColorDescription || '').trim(),
lMiktar: parseMoneyInput(form.miktarInput),
miktarInput: normalizeQuantityInput(form.miktarInput),
inputPrice: normalizeInputPrice(form.inputPrice),
inputPricePrBr: normalizePriceCurrency(form.inputPricePrBr) || 'USD',
fiyat_girilen: parseMoneyInput(form.inputPrice),
fiyat_doviz: normalizePriceCurrency(form.inputPricePrBr) || 'USD',
maliyeteDahil: Boolean(form.maliyeteDahil),
maliyete_dahil: form.maliyeteDahil ? 1 : 0,
Maliyete_dahil: form.maliyeteDahil ? 1 : 0,
cmPriceTypeId,
cm_price_type_id: cmPriceTypeId,
sBirim: String(form.sBirim || 'AD').trim() || 'AD',
draftChanged: true
}, {
preserveInputs: true,
priceType: 'MAN',
updateState: 'editor',
markChanged: true
})
}
function applyEditorRowToGroups (nextRow) {
const targetGroupName = String(nextRow?.sAciklama3 || 'TANIMSIZ').trim() || 'TANIMSIZ'
const nextGroups = detailGroups.value
.map(grp => ({
...grp,
items: (Array.isArray(grp?.items) ? grp.items : []).filter(row => row.__rowKey !== nextRow.__rowKey)
}))
.filter(grp => (Array.isArray(grp?.items) ? grp.items.length : 0) > 0 || String(grp?.sAciklama3 || '').trim() === targetGroupName)
const targetIndex = nextGroups.findIndex(grp => String(grp?.sAciklama3 || '').trim() === targetGroupName)
if (targetIndex >= 0) {
nextGroups[targetIndex] = {
...nextGroups[targetIndex],
items: [...(Array.isArray(nextGroups[targetIndex].items) ? nextGroups[targetIndex].items : []), nextRow]
.sort((left, right) => parseInt(String(left?.nOnMLDetNo || '0'), 10) - parseInt(String(right?.nOnMLDetNo || '0'), 10))
}
} else {
nextGroups.push({
sAciklama3: targetGroupName,
totalTutar: 0,
totalUSDTutar: 0,
items: [nextRow]
})
}
detailGroups.value = nextGroups
syncAllGroupsOpen()
schedulePersistLocalDraft()
}
function syncAllGroupsOpen () {
const openState = {}
detailGroups.value.forEach((grp, gi) => {
openState[groupKey(grp, gi)] = true
})
groupOpenState.value = openState
}
function normalizeHammaddeNo (value) {
return String(value || '').trim()
}
function normalizeGroupName (value) {
return String(value || 'TANIMSIZ').trim() || 'TANIMSIZ'
}
async function fetchRequiredParcaMappings () {
const ana = String(detailHeader.value?.UrunAnaGrubu || '').trim()
const alt = String(detailHeader.value?.UrunAltGrubu || '').trim()
if (!ana || !alt) return []
const data = await get('/pricing/production-product-costing/maliyet-parca-eslestirme', {
trace_id: traceId.value,
only_active: 1,
urun_ana_grubu: ana,
urun_alt_grubu: alt
})
return Array.isArray(data) ? data : []
}
function ensureNoCostRequiredRowsFromMappings (mappings) {
const list = Array.isArray(mappings) ? mappings : []
requiredParcaMappings.value = list
if (list.length === 0) return
// Add missing placeholder rows (qty=1, price=0) to remind user
list.forEach(mapping => {
const groupName = normalizeGroupName(mapping?.parcaBolumAdi || mapping?.mtBolumAdi || mapping?.sAciklama3)
const hList = Array.isArray(mapping?.nHammaddeTurleri) ? mapping.nHammaddeTurleri : []
hList.forEach(hNoRaw => {
const hNo = normalizeHammaddeNo(hNoRaw)
if (!hNo) return
const exists = flatDetailRows.value.some(r =>
normalizeGroupName(r?.sAciklama3) === groupName &&
normalizeHammaddeNo(r?.nHammaddeTuruNo) === hNo
)
if (exists) return
newRowSequence.value += 1
const rowKey = `req-auto-row-${newRowSequence.value}`
const placeholder = recalculateDetailRow({
__rowKey: rowKey,
isNew: true,
nOnMLNo: detailHeader.value?.nOnMLNo || onMLNo.value || '',
nOnMLDetNo: '',
sParcaAdi: groupName,
sAciklama3: groupName,
nHammaddeTuruNo: hNo,
sHammaddeTuruAdi: '',
sKodu: '',
sAciklama: '',
sRenk: '',
ColorCode: '',
ColorDescription: '',
lMiktar: 1,
miktarInput: '1',
inputPrice: '0',
inputPricePrBr: 'USD',
fiyat_girilen: 0,
fiyat_doviz: 'USD',
maliyeteDahil: true,
maliyete_dahil: 1,
Maliyete_dahil: 1,
cmPriceTypeId: normalizeCMPriceTypeId(1, groupName),
cm_price_type_id: normalizeCMPriceTypeId(1, groupName),
sBirim: 'AD',
draftChanged: true,
requiredPlaceholder: true
}, {
preserveInputs: true,
priceType: 'REQ',
updateState: 'required',
markChanged: true
})
applyEditorRowToGroups(placeholder)
})
})
}
function computeMissingRequiredSlots () {
const list = Array.isArray(requiredParcaMappings.value) ? requiredParcaMappings.value : []
const missing = []
if (list.length === 0) return missing
list.forEach(mapping => {
const groupName = normalizeGroupName(mapping?.parcaBolumAdi || mapping?.mtBolumAdi || mapping?.sAciklama3)
const hList = Array.isArray(mapping?.nHammaddeTurleri) ? mapping.nHammaddeTurleri : []
hList.forEach(hNoRaw => {
const hNo = normalizeHammaddeNo(hNoRaw)
if (!hNo) return
const match = flatDetailRows.value.find(r =>
normalizeGroupName(r?.sAciklama3) === groupName &&
normalizeHammaddeNo(r?.nHammaddeTuruNo) === hNo
)
const price = resolveNumericRowInputPrice(match)
if (!match || !(price > 0)) {
missing.push({ groupName, nHammaddeTuruNo: hNo, rowKey: match?.__rowKey || '' })
}
})
})
return missing
}
function removeDetailRowByKey (rowKey) {
const normalizedRowKey = String(rowKey || '').trim()
if (!normalizedRowKey) return false
const existingRow = flatDetailRows.value.find(r => String(r?.__rowKey || '').trim() === normalizedRowKey)
const nextGroups = detailGroups.value
.map(grp => ({
...grp,
items: (Array.isArray(grp?.items) ? grp.items : []).filter(row => row.__rowKey !== normalizedRowKey)
}))
.filter(grp => (Array.isArray(grp?.items) ? grp.items.length : 0) > 0)
const hasChanged = nextGroups.length !== detailGroups.value.length ||
nextGroups.some((grp, index) => (grp?.items?.length || 0) !== (detailGroups.value[index]?.items?.length || 0))
if (!hasChanged) return false
detailGroups.value = nextGroups
syncAllGroupsOpen()
if (existingRow && !existingRow?.isNew) {
deletedDetailRows.value = [
...(Array.isArray(deletedDetailRows.value) ? deletedDetailRows.value : []),
{
__rowKey: normalizedRowKey,
nOnMLNo: String(existingRow?.nOnMLNo || '').trim(),
nOnMLDetNo: String(existingRow?.nOnMLDetNo || '').trim(),
nHammaddeTuruNo: String(existingRow?.nHammaddeTuruNo || '').trim(),
sKodu: String(existingRow?.sKodu || '').trim(),
sRenk: String(existingRow?.sRenk || '').trim()
}
]
}
schedulePersistLocalDraft()
return true
}
function openNewRowDialog () {
if (!detailHeader.value) {
$q.notify({
type: 'warning',
message: 'Yeni satir eklemek icin once detay verisi olmali.',
position: 'top-right'
})
return
}
rowEditorMode.value = 'new'
rowEditorTargetRowKey.value = ''
rowEditorForm.value = createRowEditorForm({
isNew: true,
nOnMLDetNo: getNextDetailNo(),
inputPricePrBr: normalizePriceCurrency(detailHeader.value?.sDovizCinsi) || 'USD',
maliyeteDahil: true,
sBirim: 'AD'
})
primeRowEditorOptionsFromForm()
rowEditorDialogOpen.value = true
void bootstrapRowEditorOptions()
}
function openRowEditorForEdit (row) {
rowEditorMode.value = 'edit'
rowEditorTargetRowKey.value = String(row?.__rowKey || '').trim()
rowEditorForm.value = createRowEditorForm({
...row,
sAciklama3: row?.sAciklama3 || ''
})
primeRowEditorOptionsFromForm()
rowEditorDialogOpen.value = true
void bootstrapRowEditorOptions()
}
function deleteRowEditor () {
const targetRowKey = String(rowEditorTargetRowKey.value || rowEditorForm.value.__rowKey || '').trim()
if (!targetRowKey) return
$q.dialog({
title: 'Satiri Sil',
message: 'Bu satir gridden kaldirilacak. Devam edilsin mi?',
cancel: {
flat: true,
color: 'grey-7',
label: 'Vazgec'
},
ok: {
unelevated: true,
color: 'negative',
label: 'Sil'
},
persistent: true
}).onOk(() => {
if (!removeDetailRowByKey(targetRowKey)) {
$q.notify({
type: 'warning',
message: 'Silinecek satir bulunamadi.',
position: 'top-right'
})
return
}
rowEditorDialogOpen.value = false
$q.notify({
type: 'positive',
message: 'Satir gridden kaldirildi.',
position: 'top-right'
})
})
}
function saveRowEditor () {
if (!String(rowEditorForm.value.nOnMLDetNo || '').trim()) {
rowEditorForm.value.nOnMLDetNo = getNextDetailNo()
}
if (!String(rowEditorForm.value.nHammaddeTuruNo || '').trim()) {
$q.notify({
type: 'warning',
message: 'Hammadde Turu secilmeden satir kaydedilemez.',
position: 'top-right'
})
return
}
if (!String(rowEditorForm.value.sKodu || '').trim()) {
$q.notify({
type: 'warning',
message: 'Kod secilmeden satir kaydedilemez.',
position: 'top-right'
})
return
}
const nextRow = buildRowFromEditorForm()
applyEditorRowToGroups(nextRow)
rowEditorDialogOpen.value = false
}
async function saveChanges () {
saveLoading.value = true
try {
requiredAttentionRowKeys.value = {}
if (isNoCostDetail.value) {
const missing = computeMissingRequiredSlots()
if (missing.length > 0) {
const ok = await new Promise(resolve => {
$q.dialog({
title: 'Eksik Maliyet Parcalari',
message: `Eslestirilen parcalarda (fiyat > 0) girilmemis satirlar var. Devam etmek istiyor musunuz? (Eksik: ${missing.length})`,
cancel: true,
persistent: true
}).onOk(() => resolve(true)).onCancel(() => resolve(false))
})
if (!ok) {
const next = {}
missing.forEach(x => {
if (x.rowKey) next[String(x.rowKey)] = true
})
requiredAttentionRowKeys.value = next
$q.notify({
type: 'warning',
message: 'Eksik parcalar isaretlendi. Fiyat girip tekrar deneyin.',
position: 'top-right'
})
return
}
}
}
$q.notify({
type: 'warning',
message: 'Kaydetme endpointi henuz eklenmedi. Buton hazir, backend baglantisi bir sonraki adimda eklenebilir.',
position: 'top-right'
})
} finally {
saveLoading.value = false
}
}
watch(
() => [onMLNo.value, productCode.value, recipeCode.value, detailSource.value],
() => {
fetchDetail()
}
)
watch(
() => costDate.value,
value => {
const normalizedDate = normalizeDateInput(value)
if (detailHeader.value) {
detailHeader.value.dteKayitTarihi = normalizedDate
}
fetchExchangeRatesForCostDate(normalizedDate)
}
)
onMounted(() => {
if (!canReadOrder.value) return
fetchDetail()
nextTick(() => {
updateStickyTop()
})
window.addEventListener('resize', updateStickyTop)
})
onBeforeUnmount(() => {
window.removeEventListener('resize', updateStickyTop)
ensureBeforeUnloadGuard(false)
if (localDraftTimer.value) window.clearTimeout(localDraftTimer.value)
})
watch(
() => hasUnsavedChanges.value,
(value) => {
ensureBeforeUnloadGuard(Boolean(value))
}
)
onBeforeRouteLeave((to, from, next) => {
if (!hasUnsavedChanges.value) {
next()
return
}
$q.dialog({
title: 'Sayfadan ayriliyorsunuz',
message: pageMode.value === 'edit'
? 'Yaptiginiz degisiklikler kaybolacak. Devam edilsin mi?'
: 'Taslak korunacak. Sayfadan cikmak istiyor musunuz?',
ok: { label: 'Evet', color: 'negative' },
cancel: { label: 'Hayir' },
persistent: true
})
.onOk(() => {
// NEW (no-cost): always persist draft so it can be resumed
if (pageMode.value === 'new') {
persistLocalDraftNow()
next()
return
}
// EDIT (has-cost): allow exit, keep draft for now (later we can clear it after successful save)
next()
})
.onCancel(() => next(false))
})
watch(
() => [detailLoading.value, detailError.value, !!detailHeader.value, headerInfoCollapsed.value],
() => {
nextTick(() => {
updateStickyTop()
})
}
)
</script>
<style scoped>
.pcd-page {
padding: 0;
}
.pcd-content-body {
margin-top: 4px;
}
.pcd-sticky-stack {
position: sticky !important;
top: 50px !important;
z-index: 1000 !important;
background: #fff;
margin-bottom: 0;
border-bottom: 1px solid #ddd;
width: 100%;
}
.pcd-save-toolbar {
border-top: 0;
}
.pcd-toolbar-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
min-width: 0;
}
.pcd-toolbar-left {
display: flex;
align-items: center;
gap: 10px;
flex: 1 1 auto;
min-width: 0;
}
.pcd-toolbar-title {
flex: 0 0 auto;
font-size: 15px;
font-weight: 800;
color: #223048;
white-space: nowrap;
}
.pcd-toolbar-summary {
display: flex;
flex-wrap: nowrap;
align-items: center;
gap: 6px;
min-width: 0;
flex: 1 1 auto;
}
.pcd-toolbar-pill {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
padding: 5px 8px;
border-radius: 8px;
border: 1px solid #d7e0ea;
white-space: nowrap;
}
.pcd-toolbar-pill-emphasis {
background: var(--q-primary);
border-color: #145ea8;
color: #ffffff;
}
.pcd-toolbar-pill-neutral {
background: #f5f7fa;
color: #2b3c54;
}
.pcd-toolbar-pill-label {
font-size: 10px;
font-weight: 800;
letter-spacing: 0.02em;
opacity: 0.88;
}
.pcd-toolbar-pill-value {
font-size: 12px;
font-weight: 800;
min-width: 0;
}
.pcd-toolbar-actions {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 6px;
flex: 0 0 auto;
margin-left: auto;
}
.pcd-toolbar-btn {
font-size: 12px;
}
.pcd-toolbar-btn :deep(.q-btn__content) {
min-height: 34px;
padding: 0 4px;
}
.pcd-add-row-dialog {
min-width: 360px;
}
.pcd-row-editor-dialog {
width: min(960px, 96vw);
max-width: 96vw;
}
.pcd-row-editor-dialog :deep(.pcd-row-editor-entry .q-field__control) {
background: color-mix(in srgb, var(--q-secondary) 18%, white) !important;
border: 1px solid var(--q-secondary) !important;
border-radius: 6px;
}
.pcd-row-editor-dialog :deep(.pcd-row-editor-entry .q-field__label),
.pcd-row-editor-dialog :deep(.pcd-row-editor-entry .q-field__native),
.pcd-row-editor-dialog :deep(.pcd-row-editor-entry .q-field__input),
.pcd-row-editor-dialog :deep(.pcd-row-editor-entry .q-field__marginal),
.pcd-row-editor-dialog :deep(.pcd-row-editor-entry .q-select__dropdown-icon),
.pcd-row-editor-dialog :deep(.pcd-row-editor-entry .q-field__selection) {
color: var(--q-secondary) !important;
font-weight: 700;
}
.pcd-row-editor-dialog :deep(.pcd-row-editor-entry.q-field--focused .q-field__control) {
box-shadow: 0 0 0 1px var(--q-secondary), 0 0 0 3px color-mix(in srgb, var(--q-secondary) 24%, white);
}
.pcd-row-editor-dialog :deep(.pcd-row-editor-entry .q-placeholder) {
color: var(--q-secondary) !important;
opacity: 0.76;
}
.pcd-row-editor-flag {
background: color-mix(in srgb, var(--q-secondary) 10%, white);
border: 1px solid color-mix(in srgb, var(--q-secondary) 35%, white);
border-radius: 8px;
padding: 6px 10px;
}
.pcd-history-dialog {
background: #fff;
}
.pcd-detail-header-bar {
border: 1px solid #ddd;
border-radius: 6px;
}
.pcd-emphasis-field :deep(.q-field__control) {
background: var(--q-primary) !important;
border: 1px solid #145ea8;
}
.pcd-emphasis-field :deep(.q-field__label) {
color: #eaf3ff !important;
font-weight: 700;
}
.pcd-emphasis-field :deep(.q-field__native),
.pcd-emphasis-field :deep(.q-field__input) {
color: #ffffff !important;
font-weight: 800;
}
.pcd-emphasis-field-alt :deep(.q-field__control) {
background: color-mix(in srgb, var(--q-secondary) 25%, white) !important;
border: 1px solid var(--q-secondary);
}
.pcd-emphasis-field-alt :deep(.q-field__label) {
color: var(--q-primary) !important;
font-weight: 700;
}
.pcd-emphasis-field-alt :deep(.q-field__native),
.pcd-emphasis-field-alt :deep(.q-field__input),
.pcd-emphasis-field-alt :deep(.q-field__selection) {
color: var(--q-primary) !important;
font-weight: 800;
}
.pcd-readonly-header-input :deep(.q-field__control) {
background: #f8f9fa !important;
}
.pcd-group-card {
border: 1px solid #dfe3e8;
border-radius: 6px;
overflow: visible;
position: relative;
margin: 0 10px 15px;
}
.pcd-sub-header {
position: sticky !important;
top: var(--pcd-subheader-top) !important;
z-index: 990 !important;
display: flex !important;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 8px 10px;
min-height: 42px;
height: 42px;
border-top: 1px solid #d6c06a;
border-bottom: 1px solid #d6c06a;
background: linear-gradient(90deg, #fffbe9 0%, #fff4c4 50%, #fff1b0 100%);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
}
.pcd-sub-header .sub-left {
font-weight: 800;
color: #2b1f05;
text-transform: uppercase;
}
.pcd-sub-header .sub-right {
font-weight: 900;
color: #3b2f09;
font-size: 12px;
text-transform: uppercase;
text-align: right;
}
.pcd-sub-right-clickable {
cursor: pointer;
user-select: none;
display: inline-flex;
align-items: center;
gap: 8px;
}
.pcd-detail-table :deep(.q-table__middle) {
overflow: visible !important;
}
.pcd-detail-table :deep(.q-table thead) {
display: table-header-group !important;
}
.pcd-detail-table :deep(.q-table thead tr:first-child th) {
position: sticky !important;
top: calc(var(--pcd-subheader-top) + 42px) !important;
z-index: 980 !important;
background: #f8f9fa !important;
opacity: 1 !important;
}
.pcd-detail-table :deep(.q-table thead th) {
font-size: 11px;
padding: 3px 4px;
white-space: normal;
line-height: 1.2;
vertical-align: bottom;
border-bottom: 1px solid rgba(0,0,0,0.12);
}
.pcd-detail-table :deep(.q-table tbody td) {
font-size: 11px;
padding: 2px 4px;
}
.pcd-detail-table :deep(.q-table tbody tr) {
cursor: pointer;
}
.pcd-detail-table :deep(.pcd-detail-row-secondary td) {
background: color-mix(in srgb, var(--q-secondary) 14%, white) !important;
}
.pcd-detail-table :deep(.pcd-detail-row-required td) {
background: color-mix(in srgb, #ff9800 18%, white) !important;
}
.pcd-detail-table :deep(.pcd-entry-header) {
background: color-mix(in srgb, var(--q-primary) 16%, #f8f9fa) !important;
color: var(--q-primary) !important;
font-weight: 800;
}
.pcd-detail-table :deep(.pcd-secondary-header) {
background: color-mix(in srgb, var(--q-secondary) 18%, #f8f9fa) !important;
color: var(--q-secondary) !important;
font-weight: 800;
}
.pcd-detail-table :deep(.pcd-secondary-cell) {
background: color-mix(in srgb, var(--q-secondary) 10%, white) !important;
}
.pcd-detail-table :deep(.pcd-entry-input .q-field__control) {
background: color-mix(in srgb, var(--q-primary) 12%, white) !important;
border: 1px solid var(--q-primary) !important;
border-radius: 6px;
}
.pcd-detail-table :deep(.pcd-entry-input .q-field__native),
.pcd-detail-table :deep(.pcd-entry-input .q-field__input),
.pcd-detail-table :deep(.pcd-entry-input .q-field__marginal),
.pcd-detail-table :deep(.pcd-entry-input .q-select__dropdown-icon) {
color: var(--q-primary) !important;
font-weight: 700;
}
.pcd-detail-table :deep(.pcd-entry-input.q-field--focused .q-field__control) {
box-shadow: 0 0 0 1px var(--q-primary), 0 0 0 3px color-mix(in srgb, var(--q-primary) 28%, white);
}
.pcd-detail-table :deep(.pcd-entry-input .q-placeholder) {
color: var(--q-primary) !important;
opacity: 0.72;
}
.pcd-detail-table :deep(.pcd-inline-input .q-field__control) {
min-height: 30px;
}
.pcd-detail-table :deep(.pcd-inline-input .q-field__native),
.pcd-detail-table :deep(.pcd-inline-input .q-field__input) {
padding-top: 0;
padding-bottom: 0;
}
.pcd-detail-table {
position: relative;
z-index: 1;
}
.pcd-part-summary-card {
margin-top: 8px;
border: 1px solid #d7e0ea;
border-radius: 8px;
background: #fcfdfe;
padding: 8px;
}
.pcd-part-summary-title {
font-size: 13px;
font-weight: 800;
color: #2b3c54;
margin-bottom: 6px;
padding-left: 4px;
border-left: 3px solid var(--q-primary);
}
.pcd-part-summary-table {
background: transparent !important;
}
.pcd-part-summary-table thead th {
background: #f5f7fa;
font-weight: 800;
color: #44556c;
}
.pcd-part-summary-table tbody td {
font-size: 12px;
}
.pcd-history-table :deep(.q-table thead th) {
font-size: 12px;
}
.pcd-history-table :deep(.q-table tbody td) {
font-size: 12px;
vertical-align: top;
}
.pcd-history-table :deep(.pcd-history-row-purchase td) {
background: #ffffff;
}
.pcd-history-table :deep(.pcd-history-row-recipe td) {
background: color-mix(in srgb, var(--q-secondary) 10%, white);
}
.pcd-history-table :deep(.pcd-history-row-similar td) {
background: #fff8e1 !important;
border-bottom: 1px solid #ffe082 !important;
}
.pcd-history-source-chip {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 72px;
padding: 2px 8px;
border-radius: 999px;
font-size: 11px;
font-weight: 800;
letter-spacing: 0.02em;
}
.pcd-history-source-chip--purchase {
background: color-mix(in srgb, var(--q-primary) 12%, white);
color: var(--q-primary);
}
.pcd-history-source-chip--bnz-v3,
.pcd-history-source-chip--bnz-rec {
background: #f3e5f5;
color: #7b1fa2;
font-weight: bold;
}
.pcd-history-source-chip--recipe {
background: color-mix(in srgb, var(--q-secondary) 18%, white);
color: color-mix(in srgb, var(--q-secondary) 82%, black);
}
.pcd-history-source-chip--history {
background: #eceff3;
color: #44556c;
}
</style>

View File

@@ -0,0 +1,259 @@
<template>
<q-page v-if="canReadOrder" class="pch-page">
<div class="sticky-stack pch-sticky-stack">
<div class="ol-filter-bar filter-bar pch-filter-bar">
<div class="row items-center q-col-gutter-md">
<div class="col-12 col-md-4">
<q-input
:model-value="productCode || '-'"
label="Urun Kodu"
dense
filled
readonly
/>
</div>
<div class="col-12 col-md-8">
<q-input
:model-value="rows.length"
label="Toplam Maliyet Kaydi"
dense
filled
readonly
/>
</div>
</div>
</div>
<div class="save-toolbar pch-save-toolbar">
<div class="text-subtitle2 text-weight-bold">Secili Urunun Tum Maliyetleri</div>
<div class="row items-center q-gutter-sm">
<q-btn
icon="arrow_back"
label="Geri"
flat
color="grey-8"
@click="goBack"
/>
<q-btn
label="Yenile"
icon="refresh"
color="primary"
:loading="loading"
@click="fetchRows"
/>
</div>
</div>
</div>
<q-table
title=""
class="ol-table pch-table"
flat
bordered
dense
separator="cell"
row-key="__rowKey"
:rows="rows"
:columns="columns"
:loading="loading"
no-data-label="Kayit bulunamadi"
:rows-per-page-options="[0]"
hide-bottom
>
<template #body-cell="props">
<q-td v-if="props.col.name === 'open'" :props="props" class="text-center">
<q-btn
icon="open_in_new"
color="primary"
flat
round
dense
@click="openRow(props.row)"
>
<q-tooltip>Ac</q-tooltip>
</q-btn>
</q-td>
<q-td v-else :props="props" class="pch-wrap-col">
<div class="pch-wrap-2" :title="String(props.value ?? '')">{{ props.value }}</div>
</q-td>
</template>
</q-table>
<q-banner v-if="error" class="bg-red text-white q-mt-sm">
Hata: {{ error }}
</q-banner>
</q-page>
<q-page v-else class="q-pa-md flex flex-center">
<div class="text-negative text-subtitle1">
Bu module erisim yetkiniz yok.
</div>
</q-page>
</template>
<script setup>
import { computed, onMounted, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { usePermission } from 'src/composables/usePermission'
import { get, extractApiErrorDetail } from 'src/services/api'
const route = useRoute()
const router = useRouter()
const { canRead } = usePermission()
const canReadOrder = canRead('order')
const loading = ref(false)
const error = ref('')
const allRows = ref([])
const productCode = computed(() => String(route.query?.urun_kodu || '').trim())
const columns = [
{ name: 'open', label: '', field: 'open', align: 'center', sortable: false, style: 'width:3%', headerStyle: 'width:3%' },
{ name: 'nOnMLNo', label: 'nOnMLNo', field: 'nOnMLNo', align: 'left', sortable: true, style: 'width:7%', headerStyle: 'width:7%' },
{ name: 'UrunKodu', label: 'UrunKodu', field: 'UrunKodu', align: 'left', sortable: true, style: 'width:8%', headerStyle: 'width:8%' },
{ name: 'UrunAdi', label: 'UrunAdi', field: 'UrunAdi', align: 'left', sortable: true, style: 'width:12%', headerStyle: 'width:12%' },
{ name: 'Tarihi', label: 'Tarihi', field: 'Tarihi', align: 'center', sortable: true, format: val => formatDateTR(val), style: 'width:8%', headerStyle: 'width:8%' },
{ name: 'sKullaniciAdi', label: 'sKullaniciAdi', field: 'sKullaniciAdi', align: 'left', sortable: true, style: 'width:8%', headerStyle: 'width:8%' },
{ name: 'lTutarUSD', label: 'lTutarUSD', field: 'lTutarUSD', align: 'right', sortable: true, format: val => formatMoney(val), style: 'width:7%', headerStyle: 'width:7%' },
{ name: 'lTutarTL', label: 'lTutarTL', field: 'lTutarTL', align: 'right', sortable: true, format: val => formatMoney(val), style: 'width:7%', headerStyle: 'width:7%' },
{ name: 'lTutarEURO', label: 'lTutarEURO', field: 'lTutarEURO', align: 'right', sortable: true, format: val => formatMoney(val), style: 'width:7%', headerStyle: 'width:7%' },
{ name: 'sDovizCinsi', label: 'sDovizCinsi', field: 'sDovizCinsi', align: 'left', sortable: true, style: 'width:7%', headerStyle: 'width:7%' },
{ name: 'lTutarDoviz', label: 'lTutarDoviz', field: 'lTutarDoviz', align: 'right', sortable: true, format: val => formatMoney(val), style: 'width:7%', headerStyle: 'width:7%' },
{ name: 'dteGuncellemeTarihi', label: 'dteGuncellemeTarihi', field: 'dteGuncellemeTarihi', align: 'center', sortable: true, format: val => formatDateTR(val), style: 'width:9%', headerStyle: 'width:9%' },
{ name: 'sGuncellemeKullaniciAdi', label: 'sGuncellemeKullaniciAdi', field: 'sGuncellemeKullaniciAdi', align: 'left', sortable: true, style: 'width:9%', headerStyle: 'width:9%' },
{ name: 'nUrtReceteID', label: 'nUrtReceteID', field: 'nUrtReceteID', align: 'left', sortable: true, style: 'width:7%', headerStyle: 'width:7%' },
{ name: 'sAciklama', label: 'sAciklama', field: 'sAciklama', align: 'left', sortable: true, style: 'width:10%', headerStyle: 'width:10%' }
]
const rows = computed(() => allRows.value)
function formatDateTR (value) {
const s = String(value || '').trim()
if (!s) return ''
const m = /^(\d{4})-(\d{2})-(\d{2})(?:\s+(\d{2}):(\d{2}))?/.exec(s)
if (!m) return s
const datePart = `${m[3]}.${m[2]}.${m[1]}`
if (m[4] && m[5]) return `${datePart} ${m[4]}:${m[5]}`
return datePart
}
function formatMoney (value) {
return Number(value || 0).toLocaleString('tr-TR', {
minimumFractionDigits: 2,
maximumFractionDigits: 4
})
}
async function fetchRows () {
const urunKodu = productCode.value
if (!urunKodu) {
error.value = 'Urun kodu bulunamadi'
allRows.value = []
return
}
loading.value = true
error.value = ''
try {
const data = await get('/pricing/production-product-costing/has-cost-history', {
urun_kodu: urunKodu
})
const list = Array.isArray(data) ? data : []
allRows.value = list.map((x, i) => ({
__rowKey: `${x?.nOnMLNo || ''}-${x?.UrunKodu || ''}-${i}`,
...x
}))
} catch (err) {
error.value = await extractApiErrorDetail(err)
allRows.value = []
} finally {
loading.value = false
}
}
function goBack () {
router.push({ name: 'production-product-costing-has-cost' })
}
function openRow (row) {
const onMLNo = String(row?.nOnMLNo || '').trim()
if (!onMLNo) return
router.push({
name: 'production-product-costing-has-cost-detail',
query: {
n_onml_no: onMLNo,
urun_kodu: productCode.value || String(row?.UrunKodu || '').trim()
}
})
}
watch(
() => productCode.value,
() => {
fetchRows()
}
)
onMounted(() => {
if (!canReadOrder.value) return
fetchRows()
})
</script>
<style scoped>
.pch-page {
padding: 10px;
}
.pch-sticky-stack {
margin-bottom: 8px;
}
.pch-filter-bar {
min-height: auto !important;
padding-top: 8px !important;
padding-bottom: 8px !important;
}
.pch-save-toolbar {
border-top: 0;
}
.pch-table :deep(.q-table thead th) {
font-size: 11px;
padding: 3px 4px;
white-space: normal !important;
vertical-align: top !important;
line-height: 1.15;
}
.pch-table :deep(.q-table tbody td) {
font-size: 11px;
padding: 2px 4px;
white-space: normal !important;
vertical-align: top !important;
line-height: 1.15;
}
.pch-table :deep(.q-table) {
width: 100%;
table-layout: fixed;
}
.pch-wrap-col {
white-space: normal !important;
}
.pch-wrap-2 {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
line-height: 1.15;
white-space: normal;
word-break: break-word;
}
</style>

View File

@@ -0,0 +1,744 @@
<template>
<q-page v-if="canReadOrder" class="pcmm-page q-pa-md">
<div class="pcmm-header row items-center q-col-gutter-md">
<div class="col">
<div class="text-h6">Maliyet Parca Eslestirme</div>
<div class="text-caption text-grey-7">
V3 Urun Ilk Grubu (42. ozellik) + Urun Ana/Alt Grup + URETIM Parca Bolum + Hammadde Turleri eslestirmesi (URETIM mk_ tablolarinda tutulur)
</div>
</div>
<div class="col-auto">
<q-btn
color="primary"
icon="refresh"
label="Yenile"
:loading="loading"
@click="refreshAll"
/>
</div>
</div>
<q-separator class="q-my-md" />
<q-table
class="ol-table pcmm-table"
flat
bordered
dense
separator="cell"
row-key="__key"
:rows="mappings"
:columns="columns"
:loading="loading"
no-data-label="Kayit bulunamadi"
:rows-per-page-options="[0]"
hide-bottom
>
<template #top-right>
<div class="row items-center q-gutter-sm">
<q-input
v-model="filters.search"
dense
filled
clearable
debounce="250"
label="Ara (Ana/Alt)"
style="min-width: 220px"
@update:model-value="fetchSheet"
@clear="onSearchCleared"
/>
<q-btn
color="secondary"
icon="content_copy"
label="Kopyala"
:disable="loading || saving || !canCopySelected"
@click="copySelectedToSelected"
/>
<q-btn
color="secondary"
icon="save"
label="Secilenleri Kaydet"
:loading="saving"
:disable="loading || saving || !canSaveSelected"
@click="saveSelected"
/>
<q-btn
color="primary"
icon="save"
label="Degisenleri Kaydet"
:loading="saving"
:disable="loading || saving || dirtyCount === 0"
@click="saveAll"
/>
<div class="text-caption text-grey-7 q-pl-sm">
Satir: {{ mappings.length }} | Degisen: {{ dirtyCount }} | Kopya: {{ copySelectedCount }} | Secili: {{ saveSelectedCount }}
</div>
</div>
</template>
<template #body-cell-copy_select="props">
<q-td :props="props" style="width: 54px">
<div class="row items-center no-wrap">
<q-checkbox
:model-value="isCopySelected(props.row.__key)"
@update:model-value="(v) => toggleCopySelected(props.row.__key, v)"
/>
<q-badge
v-if="copyRoleLabel(props.row.__key)"
color="grey-8"
class="q-ml-xs"
style="max-width: 110px"
>
{{ copyRoleLabel(props.row.__key) }}
<q-tooltip anchor="center left" self="center right">
{{ copyRoleLabel(props.row.__key) }}
</q-tooltip>
</q-badge>
</div>
</q-td>
</template>
<template #body-cell-save_select="props">
<q-td :props="props" style="width: 54px">
<q-checkbox
:model-value="isSaveSelected(props.row.__key)"
@update:model-value="(v) => toggleSaveSelected(props.row.__key, v)"
/>
</q-td>
</template>
<template #body-cell-parcaBolumAdi="props">
<q-td :props="props">
<q-select
:model-value="bolumByKey[props.row.__key] || []"
dense
filled
multiple
use-chips
clearable
use-input
input-debounce="0"
:options="mtBolumOptions"
option-label="label"
option-value="value"
emit-value
map-options
class="pcmm-multi-select"
behavior="menu"
@filter="onFilterMTBolum"
@update:model-value="(val) => { updateBolumSelection(props.row.__key, val); markDirty(props.row) }"
style="min-width: 260px"
>
<template #before-options>
<q-item clickable @click="selectAllMTBolum(props.row.__key)">
<q-item-section>Tumunu Sec</q-item-section>
</q-item>
<q-item clickable @click="clearMTBolum(props.row.__key)">
<q-item-section>Temizle</q-item-section>
</q-item>
<q-separator />
</template>
<template #selected-item="scope">
<q-chip
class="q-mr-xs"
dense
removable
@remove="scope.removeAtIndex(scope.index)"
>
{{ scope.opt.label }}
</q-chip>
</template>
<template #option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section avatar>
<q-checkbox
:model-value="scope.selected"
tabindex="-1"
@update:model-value="() => scope.toggleOption(scope.opt)"
@click.stop
/>
</q-item-section>
<q-item-section>
<q-item-label>{{ scope.opt.label }}</q-item-label>
</q-item-section>
</q-item>
</template>
</q-select>
</q-td>
</template>
<template #body-cell-nHammaddeTurleri="props">
<q-td :props="props">
<q-select
:model-value="hammaddeByKey[props.row.__key] || []"
dense
filled
multiple
use-chips
clearable
use-input
input-debounce="0"
:options="hammaddeOptions"
option-label="label"
option-value="value"
emit-value
map-options
class="pcmm-multi-select"
behavior="menu"
@filter="onFilterHammadde"
@update:model-value="(val) => { updateHammaddeSelection(props.row.__key, val); markDirty(props.row) }"
style="min-width: 320px"
>
<template #before-options>
<q-item clickable @click="selectAllHammadde(props.row.__key)">
<q-item-section>Tumunu Sec</q-item-section>
</q-item>
<q-item clickable @click="clearHammadde(props.row.__key)">
<q-item-section>Temizle</q-item-section>
</q-item>
<q-separator />
</template>
<template #selected-item="scope">
<q-chip
class="q-mr-xs"
dense
removable
@remove="scope.removeAtIndex(scope.index)"
>
{{ scope.opt.label }}
</q-chip>
</template>
<template #option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section avatar>
<q-checkbox
:model-value="scope.selected"
tabindex="-1"
@update:model-value="() => scope.toggleOption(scope.opt)"
@click.stop
/>
</q-item-section>
<q-item-section>
<q-item-label>{{ scope.opt.label }}</q-item-label>
</q-item-section>
</q-item>
</template>
</q-select>
</q-td>
</template>
</q-table>
</q-page>
<q-page v-else class="q-pa-md flex flex-center">
<div class="text-negative text-subtitle1">
Bu module erisim yetkiniz yok.
</div>
</q-page>
</template>
<script setup>
import { computed, onMounted, ref } from 'vue'
import { useQuasar } from 'quasar'
import { get, post, del, extractApiErrorDetail } from 'src/services/api'
import { usePermission } from 'src/composables/usePermission'
import { slog } from 'src/utils/slog'
const $q = useQuasar()
const { canRead } = usePermission()
const canReadOrder = canRead('order')
const traceId = `pcd-mtbolum-map-${crypto?.randomUUID?.() || String(Date.now())}`
const loading = ref(false)
const saving = ref(false)
const mappings = ref([])
const copySelectedKeys = ref([]) // ordered
const saveSelectedKeyMap = ref({}) // key -> true
const filters = ref({
search: ''
})
const mtBolumOptions = ref([])
const hammaddeOptions = ref([])
const mtBolumLoading = ref(false)
const hammaddeLoading = ref(false)
const bolumByKey = ref({})
const hammaddeByKey = ref({})
const columns = [
{ name: 'copy_select', label: 'Kopya', field: 'copy_select', align: 'center' },
{ name: 'save_select', label: 'Sec', field: 'save_select', align: 'center' },
{ name: 'urunIlkGrubu', label: 'Urun Ilk Grubu', field: 'urunIlkGrubu', align: 'left', sortable: true },
{ name: 'urunAnaGrubu', label: 'Urun Ana Grubu', field: 'urunAnaGrubu', align: 'left', sortable: true },
{ name: 'urunAltGrubu', label: 'Urun Alt Grubu', field: 'urunAltGrubu', align: 'left', sortable: true },
{ name: 'parcaBolumAdi', label: 'Parca Bolum', field: row => (Array.isArray(row?.nUrtMTBolumIDs) ? row.nUrtMTBolumIDs.join(', ') : ''), align: 'left' },
{ name: 'nHammaddeTurleri', label: 'Hammadde Turleri', field: row => (Array.isArray(row?.nHammaddeTurleri) ? row.nHammaddeTurleri.join(', ') : ''), align: 'left' }
]
const dirtyMap = ref({})
const dirtyCount = computed(() => Object.keys(dirtyMap.value || {}).length)
const copySelectedCount = computed(() => (Array.isArray(copySelectedKeys.value) ? copySelectedKeys.value.length : 0))
const canCopySelected = computed(() => copySelectedCount.value >= 2)
const saveSelectedCount = computed(() => Object.keys(saveSelectedKeyMap.value || {}).length)
const canSaveSelected = computed(() => saveSelectedCount.value > 0)
function markDirty (row) {
const key = String(row?.__key || '').trim()
if (!key) return
dirtyMap.value = { ...(dirtyMap.value || {}), [key]: true }
}
function clearDirty () {
dirtyMap.value = {}
}
function isCopySelected (rowKey) {
const key = String(rowKey || '').trim()
if (!key) return false
return (Array.isArray(copySelectedKeys.value) ? copySelectedKeys.value : []).includes(key)
}
function toggleCopySelected (rowKey, enabled) {
const key = String(rowKey || '').trim()
if (!key) return
const current = Array.isArray(copySelectedKeys.value) ? [...copySelectedKeys.value] : []
const idx = current.indexOf(key)
if (enabled) {
if (idx === -1) current.push(key)
} else {
if (idx >= 0) current.splice(idx, 1)
}
copySelectedKeys.value = current
}
function copyRoleLabel (rowKey) {
const key = String(rowKey || '').trim()
if (!key) return ''
const keys = Array.isArray(copySelectedKeys.value) ? copySelectedKeys.value : []
const idx = keys.indexOf(key)
if (idx === 0) return 'Kopyalanacak'
if (idx > 0) return 'Yapistirilacak'
return ''
}
function isSaveSelected (rowKey) {
const key = String(rowKey || '').trim()
if (!key) return false
return !!saveSelectedKeyMap.value?.[key]
}
function toggleSaveSelected (rowKey, enabled) {
const key = String(rowKey || '').trim()
if (!key) return
const next = { ...(saveSelectedKeyMap.value || {}) }
if (enabled) next[key] = true
else delete next[key]
saveSelectedKeyMap.value = next
}
function copySelectedToSelected () {
const keys = Array.isArray(copySelectedKeys.value) ? copySelectedKeys.value : []
if (keys.length < 2) return
const srcKey = String(keys[0] || '').trim()
if (!srcKey) return
const srcBolum = bolumByKey.value?.[srcKey] || []
const srcHam = hammaddeByKey.value?.[srcKey] || []
for (let i = 1; i < keys.length; i++) {
const key = String(keys[i] || '').trim()
if (!key) continue
updateBolumSelection(key, srcBolum)
updateHammaddeSelection(key, srcHam)
const row = (Array.isArray(mappings.value) ? mappings.value : []).find(r => String(r?.__key || '') === key)
if (row) markDirty(row)
}
$q.notify({ type: 'positive', message: 'Kopyala / Yapistir tamamlandi' })
}
async function saveSelected () {
const keys = Object.keys(saveSelectedKeyMap.value || {})
await saveKeys(keys)
}
function selectAllMTBolum (rowKey) {
const key = String(rowKey || '').trim()
if (!key) return
const all = (Array.isArray(mtBolumOptions.value) ? mtBolumOptions.value : [])
.map(o => Number(o?.value))
.filter(n => Number.isFinite(n) && n > 0)
updateBolumSelection(key, all)
const row = (Array.isArray(mappings.value) ? mappings.value : []).find(r => String(r?.__key || '') === key)
if (row) markDirty(row)
}
function clearMTBolum (rowKey) {
const key = String(rowKey || '').trim()
if (!key) return
updateBolumSelection(key, [])
const row = (Array.isArray(mappings.value) ? mappings.value : []).find(r => String(r?.__key || '') === key)
if (row) markDirty(row)
}
function selectAllHammadde (rowKey) {
const key = String(rowKey || '').trim()
if (!key) return
const all = (Array.isArray(hammaddeOptions.value) ? hammaddeOptions.value : [])
.map(o => Number(o?.value))
.filter(n => Number.isFinite(n) && n > 0)
updateHammaddeSelection(key, all)
const row = (Array.isArray(mappings.value) ? mappings.value : []).find(r => String(r?.__key || '') === key)
if (row) markDirty(row)
}
function clearHammadde (rowKey) {
const key = String(rowKey || '').trim()
if (!key) return
updateHammaddeSelection(key, [])
const row = (Array.isArray(mappings.value) ? mappings.value : []).find(r => String(r?.__key || '') === key)
if (row) markDirty(row)
}
function normalizeIntList (list) {
const arr = Array.isArray(list) ? list : []
const nums = arr
.map(x => Number(String(x).trim()))
.filter(n => Number.isFinite(n) && n > 0)
return Array.from(new Set(nums))
}
function initEditableStateFromRows (rows) {
const bolum = {}
const ham = {}
;(Array.isArray(rows) ? rows : []).forEach(r => {
const key = String(r?.__key || '').trim()
if (!key) return
bolum[key] = normalizeIntList(r?.nUrtMTBolumIDs || [])
ham[key] = normalizeIntList(r?.nHammaddeTurleri || [])
})
bolumByKey.value = bolum
hammaddeByKey.value = ham
}
function updateBolumSelection (key, newValue) {
const k = String(key || '').trim()
if (!k) return
bolumByKey.value = {
...(bolumByKey.value || {}),
[k]: normalizeIntList(newValue)
}
}
function updateHammaddeSelection (key, newValue) {
const k = String(key || '').trim()
if (!k) return
hammaddeByKey.value = {
...(hammaddeByKey.value || {}),
[k]: normalizeIntList(newValue)
}
}
// label resolution now handled by options' `label` field + selected-item slot (see UserDetail.vue "Piyasalar").
async function fetchMappings () {
loading.value = true
try {
const data = await get('/pricing/production-product-costing/maliyet-parca-eslestirme', {
trace_id: traceId,
only_active: 1
})
return Array.isArray(data) ? data : []
} catch (e) {
const detail = await extractApiErrorDetail(e)
$q.notify({ type: 'negative', message: detail || 'Liste okunamadi' })
return []
} finally {
loading.value = false
}
}
async function fetchSheet () {
loading.value = true
try {
// Search changes should not carry over row-level selections from a previous sheet.
copySelectedKeys.value = []
saveSelectedKeyMap.value = {}
const [combos, existing] = await Promise.all([
get('/pricing/production-product-costing/options/urun-ana-alt-combos', {
trace_id: traceId,
search: String(filters.value.search || '').trim(),
limit: 5000
}),
fetchMappings()
])
const existingByKey = new Map()
;(Array.isArray(existing) ? existing : []).forEach(x => {
const k = `${String(x?.urunIlkGrubu || '').trim()}|${String(x?.urunAltGrubu || '').trim()}|${String(x?.urunAnaGrubu || '').trim()}`
const prev = existingByKey.get(k)
const nextList = Array.isArray(prev) ? prev : (prev ? [prev] : [])
nextList.push(x)
existingByKey.set(k, nextList)
})
const rows = (Array.isArray(combos) ? combos : []).map((c, idx) => {
const ilk = String(c?.urunIlkGrubu || '').trim()
const ana = String(c?.urunAnaGrubu || '').trim()
const alt = String(c?.urunAltGrubu || '').trim()
const k = `${ilk}|${alt}|${ana}`
const hitList = existingByKey.get(k)
const list = Array.isArray(hitList) ? hitList : []
const mtIds = list
.map(x => String(x?.nUrtMTBolumID || '').trim())
.filter(Boolean)
const uniqMt = Array.from(new Set(mtIds))
// If multiple mappings exist, we merge hammadde types (union) for editing convenience.
const hSet = new Set()
list.forEach(x => {
if (Array.isArray(x?.nHammaddeTurleri)) {
x.nHammaddeTurleri.forEach(v => {
v = String(v || '').trim()
if (v) hSet.add(v)
})
}
})
return {
__key: k,
__rowIndex: idx,
__existingIDs: list.map(x => Number(x?.id || 0)).filter(n => n > 0),
urunIlkGrubu: ilk,
urunAnaGrubu: ana,
urunAltGrubu: alt,
nUrtMTBolumIDs: uniqMt,
nHammaddeTurleri: Array.from(hSet)
}
})
mappings.value = rows
initEditableStateFromRows(rows)
clearDirty()
} catch (e) {
const detail = await extractApiErrorDetail(e)
$q.notify({ type: 'negative', message: detail || 'Carsaf yuklenemedi' })
} finally {
loading.value = false
}
}
function onSearchCleared () {
filters.value.search = ''
copySelectedKeys.value = []
saveSelectedKeyMap.value = {}
clearDirty()
fetchSheet()
}
async function refreshAll () {
await Promise.all([
fetchMTBolumOptions(''),
fetchHammaddeOptions('')
])
await fetchSheet()
}
async function fetchMTBolumOptions (search) {
mtBolumLoading.value = true
try {
const data = await get('/pricing/production-product-costing/options/mtbolum', {
trace_id: traceId,
search: search || '',
limit: 200
})
mtBolumOptions.value = Array.isArray(data)
? data.map(x => ({
value: Number(x?.value ?? x?.nUrtMTBolumID ?? x?.id ?? 0),
label: (() => {
const v = Number(x?.value ?? x?.nUrtMTBolumID ?? x?.id ?? 0)
const name = String(x?.label || x?.sAdi || '').trim()
if (Number.isFinite(v) && v > 0 && name) return `${v} - ${name}`
if (Number.isFinite(v) && v > 0) return String(v)
return name
})()
}))
.filter(x => Number.isFinite(x.value) && x.value > 0)
.sort((a, b) => a.value - b.value)
: []
} finally {
mtBolumLoading.value = false
}
}
async function fetchHammaddeOptions (search) {
hammaddeLoading.value = true
try {
const data = await get('/pricing/production-product-costing/detail-editor-options', {
trace_id: traceId,
kind: 'hammadde',
search: search || '',
limit: 200
})
hammaddeOptions.value = Array.isArray(data)
? data.map(x => ({
value: Number(String(x?.nHammaddeTuruNo ?? x?.value ?? '').trim()),
label: (() => {
const v = Number(String(x?.nHammaddeTuruNo ?? x?.value ?? '').trim())
const name = String(x?.sHammaddeTuruAdi || x?.label || '').trim()
if (Number.isFinite(v) && v > 0 && name) return `${v} - ${name}`
if (Number.isFinite(v) && v > 0) return String(v)
return name
})()
}))
.filter(x => Number.isFinite(x.value) && x.value > 0)
.sort((a, b) => a.value - b.value)
: []
} finally {
hammaddeLoading.value = false
}
}
function onFilterMTBolum (val, update) {
update(async () => {
await fetchMTBolumOptions(val)
})
}
function onFilterHammadde (val, update) {
update(async () => {
await fetchHammaddeOptions(val)
})
}
async function saveAll () {
const dirtyKeys = Object.keys(dirtyMap.value || {})
await saveKeys(dirtyKeys)
}
async function saveKeys (keys) {
const list = Array.isArray(keys) ? keys.map(k => String(k || '').trim()).filter(Boolean) : []
if (list.length === 0) return
saving.value = true
try {
const rowsToSave = mappings.value.filter(r => list.includes(String(r.__key || '').trim()))
for (const row of rowsToSave) {
const key = String(row.__key || '').trim()
const mtIds = normalizeIntList(bolumByKey.value?.[key] || [])
const hList = normalizeIntList(hammaddeByKey.value?.[key] || [])
// If hammadde cleared OR no bölüm selected: delete all existing mappings for this combo
const existingIDs = Array.isArray(row.__existingIDs) ? row.__existingIDs : []
if (existingIDs.length > 0 && (mtIds.length === 0 || hList.length === 0)) {
for (const id of existingIDs) {
await del('/pricing/production-product-costing/maliyet-parca-eslestirme', { trace_id: traceId, id })
}
row.__existingIDs = []
bolumByKey.value = { ...(bolumByKey.value || {}), [key]: [] }
hammaddeByKey.value = { ...(hammaddeByKey.value || {}), [key]: [] }
continue
}
if (mtIds.length > 0 && hList.length > 0) {
const existing = await fetchMappings()
const ilk = String(row.urunIlkGrubu || '').trim()
const ana = String(row.urunAnaGrubu || '').trim()
const alt = String(row.urunAltGrubu || '').trim()
const existingForCombo = (Array.isArray(existing) ? existing : []).filter(x =>
String(x?.urunIlkGrubu || '').trim() === ilk &&
String(x?.urunAnaGrubu || '').trim() === ana &&
String(x?.urunAltGrubu || '').trim() === alt
)
const selectedSet = new Set(mtIds.map(String))
for (const ex of existingForCombo) {
const exMt = String(ex?.nUrtMTBolumID || '').trim()
if (exMt && !selectedSet.has(exMt) && Number(ex?.id || 0) > 0) {
await del('/pricing/production-product-costing/maliyet-parca-eslestirme', { trace_id: traceId, id: Number(ex.id) })
}
}
const idsAfter = []
for (const mtId of mtIds) {
const payload = {
urunIlkGrubu: ilk,
urunAnaGrubu: ana,
urunAltGrubu: alt,
nUrtMTBolumID: mtId,
nHammaddeTurleri: hList,
bAktif: true
}
const resp = await post('/pricing/production-product-costing/maliyet-parca-eslestirme/upsert', payload, { trace_id: traceId })
if (resp?.id) idsAfter.push(Number(resp.id))
}
row.__existingIDs = idsAfter
}
}
$q.notify({ type: 'positive', message: 'Degisiklikler kaydedildi' })
clearDirty()
// after saving, clear save selection to avoid accidental re-save
saveSelectedKeyMap.value = {}
await refreshAll()
} catch (e) {
const detail = await extractApiErrorDetail(e)
$q.notify({ type: 'negative', message: detail || 'Kaydetme basarisiz' })
} finally {
saving.value = false
}
}
onMounted(async () => {
await Promise.all([
fetchMTBolumOptions(''),
fetchHammaddeOptions(''),
fetchSheet()
])
})
</script>
<style scoped>
.pcmm-page {
background: #fafafa;
}
.pcmm-header {
max-width: 1200px;
margin: 0 auto;
}
.pcmm-form {
max-width: 1200px;
margin: 0 auto;
}
.pcmm-table {
max-width: 1200px;
margin: 0 auto;
}
/* Allow multi-select chips to wrap and grow vertically (PowerBI-like) */
.pcmm-table :deep(.pcmm-multi-select .q-field__control) {
height: auto !important;
min-height: 38px;
}
.pcmm-table :deep(.pcmm-multi-select .q-field__native) {
align-items: flex-start;
}
.pcmm-table :deep(.pcmm-multi-select .q-select__chips) {
flex-wrap: wrap;
}
.pcmm-table :deep(.pcmm-multi-select .q-chip) {
max-width: 100%;
}
</style>

View File

@@ -0,0 +1,516 @@
<template>
<q-page v-if="canReadOrder" class="npc-page">
<div class="ol-filter-bar npc-filter-bar">
<div class="npc-filter-row">
<q-input
v-model="filters.search"
class="npc-filter-input npc-search"
dense
filled
clearable
debounce="300"
label="Arama (Model Kodu / Firma / Veren)"
>
<template #append>
<q-icon name="search" />
</template>
</q-input>
<q-input
v-model="filters.fromDate"
class="npc-filter-input"
dense
filled
type="date"
label="Baslangic Tarihi"
/>
<div class="ol-filter-actions npc-filter-actions">
<q-btn
label="Temizle"
icon="clear"
color="grey-7"
flat
:disable="loading"
@click="clearFilters"
/>
<q-btn
label="Kolon Filtreleri"
icon="filter_alt_off"
color="grey-7"
flat
:disable="loading"
@click="clearAllColumnFilters"
/>
<q-btn
label="Yenile"
icon="refresh"
color="primary"
:loading="loading"
@click="fetchRows"
/>
</div>
</div>
</div>
<q-table
title="Maliyeti Olmayan Urunler"
class="ol-table npc-table"
flat
bordered
dense
separator="cell"
row-key="__rowKey"
:rows="rows"
:columns="columns"
:loading="loading"
no-data-label="Kayit bulunamadi"
:rows-per-page-options="[0]"
hide-bottom
>
<template #header-cell="props">
<q-th :props="props">
<div class="npc-header-cell">
<div class="npc-head-wrap-3">{{ props.col.label }}</div>
<q-btn
v-if="props.col.name !== 'open'"
dense
flat
round
size="sm"
icon="filter_alt"
:color="isColumnFilterActive(props.col.name) ? 'primary' : 'grey-6'"
>
<q-menu class="npc-filter-menu" fit>
<div class="npc-filter-menu-content">
<div class="text-caption text-weight-bold q-mb-sm">{{ props.col.label }}</div>
<q-input
v-model="getColumnFilter(props.col.name).text"
dense
outlined
clearable
label="Icerir"
/>
<q-select
v-model="getColumnFilter(props.col.name).selected"
class="q-mt-sm"
dense
outlined
multiple
use-chips
use-input
emit-value
map-options
:options="getColumnDistinctOptions(props.col.name)"
label="Deger Sec"
/>
<div class="row justify-end q-gutter-sm q-mt-sm">
<q-btn dense flat color="grey-7" label="Temizle" @click="clearColumnFilter(props.col.name)" />
</div>
</div>
</q-menu>
</q-btn>
</div>
</q-th>
</template>
<template #body-cell="props">
<q-td v-if="props.col.name === 'open'" :props="props" class="text-center">
<q-btn
icon="open_in_new"
color="primary"
flat
round
dense
@click="openRow(props.row)"
>
<q-tooltip>Ac</q-tooltip>
</q-btn>
</q-td>
<q-td v-else :props="props" class="npc-wrap-col">
<div class="npc-wrap-3">{{ props.value }}</div>
<q-tooltip v-if="props.value">{{ props.value }}</q-tooltip>
</q-td>
</template>
</q-table>
<q-banner v-if="error" class="bg-red text-white q-mt-sm">
Hata: {{ error }}
</q-banner>
</q-page>
<q-page v-else class="q-pa-md flex flex-center">
<div class="text-negative text-subtitle1">
Bu module erisim yetkiniz yok.
</div>
</q-page>
</template>
<script setup>
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { useQuasar } from 'quasar'
import { useRouter } from 'vue-router'
import { usePermission } from 'src/composables/usePermission'
import { get, extractApiErrorDetail } from 'src/services/api'
import { createTraceId, slog } from 'src/utils/slog'
const { canRead } = usePermission()
const canReadOrder = canRead('order')
const $q = useQuasar()
const router = useRouter()
const loading = ref(false)
const error = ref('')
const allRows = ref([])
const filters = reactive({
search: '',
fromDate: '2025-06-01'
})
const columns = [
{ name: 'open', label: '', field: 'open', align: 'center', sortable: false, style: 'width:3%', headerStyle: 'width:3%' },
{
name: 'UretimSekli',
label: 'Uretim Sekli',
field: 'UretimSekli',
align: 'left',
sortable: true,
classes: 'npc-wrap-col',
headerClasses: 'npc-wrap-col',
style: 'width:12%',
headerStyle: 'width:12%'
},
{ name: 'nUrtSiparisNo', label: 'Uretim Siparis No', field: 'nUrtSiparisNo', align: 'left', sortable: true, style: 'width:7%', headerStyle: 'width:7%' },
{
name: 'dteIslemTarihi',
label: 'Islem Tarihi',
field: 'dteIslemTarihi',
align: 'center',
sortable: true,
format: val => formatDateTR(val),
style: 'width:7%',
headerStyle: 'width:7%'
},
{
name: 'FirmaKodu',
label: 'Firma Kodu',
field: 'FirmaKodu',
align: 'left',
sortable: true,
classes: 'npc-wrap-col',
headerClasses: 'npc-wrap-col',
style: 'width:8%',
headerStyle: 'width:8%'
},
{ name: 'FirmaAdi', label: 'Firma Adi', field: 'FirmaAdi', align: 'left', sortable: true, style: 'width:10%', headerStyle: 'width:10%' },
{ name: 'SonIsEmriVeren', label: '2.Firma', field: 'SonIsEmriVeren', align: 'left', sortable: true, style: 'width:9%', headerStyle: 'width:9%' },
{
name: 'lMMiktar_G',
label: 'Miktar (G)',
field: 'lMMiktar_G',
align: 'right',
sortable: true,
format: val => Number(val || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }),
style: 'width:7%',
headerStyle: 'width:7%'
},
{
name: 'sMModelKodu',
label: 'Model Kodu',
field: 'sMModelKodu',
align: 'left',
sortable: true,
classes: 'npc-wrap-col',
headerClasses: 'npc-wrap-col',
style: 'width:8%',
headerStyle: 'width:8%'
},
{ name: 'sAdi', label: 'Model Adi', field: 'sAdi', align: 'left', sortable: true, style: 'width:9%', headerStyle: 'width:9%' },
{ name: 'sKodu', label: 'Recete Kodu', field: 'sKodu', align: 'left', sortable: true, style: 'width:7%', headerStyle: 'width:7%' },
{ name: 'sKullaniciAdi', label: 'Receteyi Acan Kullanici', field: 'sKullaniciAdi', align: 'left', sortable: true, style: 'width:7%', headerStyle: 'width:7%' },
{ name: 'sKullaniciAdiGunc', label: 'Receteyi Son Guncelleyen Kullanici', field: 'sKullaniciAdiGunc', align: 'left', sortable: true, style: 'width:6%', headerStyle: 'width:6%' }
]
const columnFilters = reactive({})
function getColumnFilter (name) {
if (!columnFilters[name]) {
columnFilters[name] = {
text: '',
selected: []
}
}
return columnFilters[name]
}
function formatDateTR (value) {
const s = String(value || '').trim()
if (!s) return ''
const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(s)
if (!m) return s
return `${m[3]}.${m[2]}.${m[1]}`
}
const rows = computed(() => {
let result = allRows.value
for (const col of columns) {
if (col.name === 'open') continue
const cf = getColumnFilter(col.name)
const text = String(cf.text || '').trim().toLowerCase()
const selected = Array.isArray(cf.selected) ? cf.selected : []
if (!text && selected.length === 0) continue
result = result.filter((row) => {
const value = getColumnComparableValue(row, col.name)
const valueLC = value.toLowerCase()
if (text && !valueLC.includes(text)) {
return false
}
if (selected.length > 0 && !selected.includes(value)) {
return false
}
return true
})
}
return result
})
function getColumnComparableValue (row, colName) {
if (colName === 'dteIslemTarihi') {
return formatDateTR(row?.dteIslemTarihi)
}
return String(row?.[colName] ?? '').trim()
}
function getColumnDistinctOptions (colName) {
const set = new Set()
for (const row of allRows.value) {
const val = getColumnComparableValue(row, colName)
if (val) set.add(val)
}
return Array.from(set)
.sort((a, b) => a.localeCompare(b, 'tr'))
.map(v => ({ label: v, value: v }))
}
function isColumnFilterActive (name) {
const cf = getColumnFilter(name)
return !!String(cf.text || '').trim() || (Array.isArray(cf.selected) && cf.selected.length > 0)
}
function clearColumnFilter (name) {
const cf = getColumnFilter(name)
cf.text = ''
cf.selected = []
}
function clearAllColumnFilters () {
for (const col of columns) {
if (col.name === 'open') continue
clearColumnFilter(col.name)
}
}
let searchTimer = null
watch(
() => filters.search,
() => {
clearTimeout(searchTimer)
searchTimer = setTimeout(() => {
fetchRows()
}, 400)
}
)
watch(
() => filters.fromDate,
() => {
fetchRows()
}
)
async function fetchRows () {
loading.value = true
error.value = ''
try {
const data = await get('/pricing/production-product-costing/no-cost-products', {
search: filters.search || '',
from_date: filters.fromDate || ''
})
const list = Array.isArray(data) ? data : []
allRows.value = list.map((x, i) => ({
__rowKey: `${x?.sMModelKodu || ''}-${x?.nUrtSiparisNo || 0}-${i}`,
...x
}))
} catch (err) {
error.value = await extractApiErrorDetail(err)
allRows.value = []
} finally {
loading.value = false
}
}
function clearFilters () {
filters.search = ''
filters.fromDate = '2025-06-01'
clearAllColumnFilters()
fetchRows()
}
function openRow (row) {
const productCode = String(row?.sMModelKodu || '').trim()
const recipeCode = String(row?.sKodu || '').trim()
const traceId = createTraceId('pcd-no-cost')
if (!productCode || !recipeCode) {
$q.notify({
type: 'warning',
message: 'Detay acmak icin urun ve recete kodu gerekli.',
position: 'top-right'
})
return
}
slog.info('production-product-costing.no-cost', 'navigate:detail', {
trace_id: traceId,
product_code: productCode,
recipe_code: recipeCode
})
router.push({
name: 'production-product-costing-has-cost-detail',
query: {
detail_source: 'no-cost',
urun_kodu: productCode,
recete_kodu: recipeCode,
trace_id: traceId
}
})
}
onMounted(() => {
if (!canReadOrder.value) return
fetchRows()
})
</script>
<style scoped>
.npc-page {
padding: 10px;
}
.npc-filter-bar {
margin-bottom: 8px;
}
.npc-filter-row {
display: flex;
flex-wrap: nowrap;
gap: 10px;
align-items: center;
}
.npc-filter-input {
min-width: 118px;
width: 136px;
flex: 0 0 136px;
}
.npc-search {
min-width: 240px;
max-width: 420px;
flex: 1 1 360px;
}
.npc-filter-actions {
display: flex;
gap: 8px;
flex-wrap: nowrap;
flex: 0 0 auto;
}
.npc-filter-menu {
min-width: 300px;
}
.npc-filter-menu-content {
padding: 10px;
}
.npc-table :deep(.q-table thead th) {
font-size: 11px;
padding: 3px 4px;
white-space: normal !important;
vertical-align: top !important;
line-height: 1.15;
}
.npc-table :deep(.q-table tbody td) {
font-size: 11px;
padding: 2px 4px;
white-space: normal !important;
vertical-align: top !important;
line-height: 1.15;
}
.npc-table :deep(.q-table) {
width: 100%;
table-layout: fixed;
}
.npc-wrap-col {
white-space: normal !important;
}
.npc-header-cell {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 4px;
width: 100%;
}
.npc-head-wrap-3 {
min-width: 0;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
line-height: 1.15;
white-space: normal;
word-break: break-word;
}
.npc-wrap-3 {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
line-height: 1.15;
white-space: normal;
word-break: break-word;
}
@media (max-width: 1440px) {
.npc-filter-row {
flex-wrap: wrap;
align-items: flex-start;
}
.npc-filter-actions {
flex-wrap: wrap;
}
.npc-filter-input {
flex: 1 1 140px;
}
}
</style>

View File

@@ -324,6 +324,42 @@ const routes = [
component: () => import('pages/ProductPricing.vue'), component: () => import('pages/ProductPricing.vue'),
meta: { permission: 'order:view' } meta: { permission: 'order:view' }
}, },
{
path: 'pricing/production-product-costing',
name: 'production-product-costing',
component: () => import('pages/ProductionProductCosting.vue'),
meta: { permission: 'order:view' }
},
{
path: 'pricing/production-product-costing/has-cost',
name: 'production-product-costing-has-cost',
component: () => import('pages/ProductionProductCostingHasCost.vue'),
meta: { permission: 'order:view' }
},
{
path: 'pricing/production-product-costing/has-cost/history',
name: 'production-product-costing-has-cost-history',
component: () => import('pages/ProductionProductCostingHasCostHistory.vue'),
meta: { permission: 'order:view' }
},
{
path: 'pricing/production-product-costing/has-cost/detail',
name: 'production-product-costing-has-cost-detail',
component: () => import('pages/ProductionProductCostingHasCostDetail.vue'),
meta: { permission: 'order:view' }
},
{
path: 'pricing/production-product-costing/no-cost',
name: 'production-product-costing-no-cost',
component: () => import('pages/ProductionProductCostingNoCost.vue'),
meta: { permission: 'order:view' }
},
{
path: 'pricing/production-product-costing/maliyet-parca-eslestirme',
name: 'production-product-costing-maliyet-parca-eslestirme',
component: () => import('pages/ProductionProductCostingMTBolumMapping.vue'),
meta: { permission: 'order:view' }
},
/* ================= PASSWORD ================= */ /* ================= PASSWORD ================= */

View File

@@ -2,6 +2,7 @@ import axios from 'axios'
import qs from 'qs' import qs from 'qs'
import { useAuthStore } from 'stores/authStore' import { useAuthStore } from 'stores/authStore'
import { DEFAULT_LOCALE, normalizeLocale } from 'src/i18n/languages' import { DEFAULT_LOCALE, normalizeLocale } from 'src/i18n/languages'
import { slog } from 'src/utils/slog'
const rawBaseUrl = const rawBaseUrl =
(typeof process !== 'undefined' && process.env?.VITE_API_BASE_URL) || '/api' (typeof process !== 'undefined' && process.env?.VITE_API_BASE_URL) || '/api'
@@ -81,6 +82,20 @@ function getRequestLocale() {
return normalizeLocale(window.localStorage.getItem(LOCALE_STORAGE_KEY)) return normalizeLocale(window.localStorage.getItem(LOCALE_STORAGE_KEY))
} }
function extractTraceIdFromConfig(config) {
const rawTraceId =
config?.params?.trace_id ||
config?.headers?.['X-Trace-ID'] ||
config?.headers?.['x-trace-id'] ||
''
return String(rawTraceId || '').trim()
}
function shouldStructuredLogRequest(config) {
return extractTraceIdFromConfig(config) !== ''
}
api.interceptors.request.use((config) => { api.interceptors.request.use((config) => {
const auth = useAuthStore() const auth = useAuthStore()
const url = config.url || '' const url = config.url || ''
@@ -92,6 +107,22 @@ api.interceptors.request.use((config) => {
config.headers ||= {} config.headers ||= {}
config.headers['Accept-Language'] = getRequestLocale() config.headers['Accept-Language'] = getRequestLocale()
const traceId = extractTraceIdFromConfig(config)
config.__traceId = traceId
config.__startedAt = Date.now()
if (traceId) {
config.headers['X-Trace-ID'] = traceId
}
if (shouldStructuredLogRequest(config)) {
slog.info('production-product-costing.api', 'request:start', {
trace_id: traceId,
method: String(config.method || 'GET').toUpperCase(),
url,
params: config.params || {}
})
}
return config return config
}) })
@@ -148,7 +179,19 @@ function clearSessionAndRedirect() {
} }
api.interceptors.response.use( api.interceptors.response.use(
r => r, (response) => {
const requestConfig = response?.config || {}
if (shouldStructuredLogRequest(requestConfig)) {
slog.info('production-product-costing.api', 'request:ok', {
trace_id: requestConfig.__traceId || extractTraceIdFromConfig(requestConfig),
method: String(requestConfig.method || 'GET').toUpperCase(),
url: String(requestConfig.url || ''),
status: response?.status || 200,
duration_ms: Math.max(0, Date.now() - Number(requestConfig.__startedAt || Date.now()))
})
}
return response
},
async (error) => { async (error) => {
const requestConfig = error?.config || {} const requestConfig = error?.config || {}
const status = error?.response?.status const status = error?.response?.status
@@ -169,6 +212,22 @@ api.interceptors.response.use(
console.error(`API ${status || '-'} ${method} ${requestUrl}: ${detail}`) console.error(`API ${status || '-'} ${method} ${requestUrl}: ${detail}`)
} }
if (shouldStructuredLogRequest(requestConfig)) {
const detail = sanitizeApiErrorDetail(
await extractApiErrorDetail(error),
status
)
error.parsedMessage ||= detail
slog.error('production-product-costing.api', 'request:error', {
trace_id: requestConfig.__traceId || extractTraceIdFromConfig(requestConfig),
method: String(requestConfig.method || 'GET').toUpperCase(),
url: requestUrl,
status: status || 0,
duration_ms: Math.max(0, Date.now() - Number(requestConfig.__startedAt || Date.now())),
detail
})
}
const shouldTryRefresh = const shouldTryRefresh =
status === 401 && status === 401 &&
!requestConfig._retry && !requestConfig._retry &&

53
ui/src/utils/slog.js Normal file
View File

@@ -0,0 +1,53 @@
const isDev =
typeof process !== 'undefined' &&
Boolean(process.env?.DEV)
function emit(level, scope, message, data = {}) {
const payload = {
time: new Date().toISOString(),
level,
scope,
message,
...data
}
const text = `[${scope}] ${message}`
if (level === 'error') {
console.error(text, payload)
return
}
if (level === 'warn') {
console.warn(text, payload)
return
}
if (level === 'debug' && !isDev) {
return
}
console.info(text, payload)
}
export function createTraceId(prefix = 'trace') {
const rawId =
typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
? crypto.randomUUID()
: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`
return `${prefix}-${rawId}`
}
export const slog = {
debug(scope, message, data = {}) {
emit('debug', scope, message, data)
},
info(scope, message, data = {}) {
emit('info', scope, message, data)
},
warn(scope, message, data = {}) {
emit('warn', scope, message, data)
},
error(scope, message, data = {}) {
emit('error', scope, message, data)
}
}