From 77fe2b04b67675b16ef8d946df92a423584d926f Mon Sep 17 00:00:00 2001 From: M_Kececi Date: Wed, 6 May 2026 11:07:55 +0300 Subject: [PATCH] Merge remote-tracking branch 'origin/master' --- go.mod | 3 + ...uction_product_costing_tbStok_fulltext.sql | 95 + svc/.env.local | 1 + svc/backend-dev.err.log | 0 svc/backend-dev.out.log | 365 ++ svc/db/mssql.go | 35 + svc/main.go | 106 + svc/models/production_product_costing.go | 282 ++ svc/queries/get_order_list_excel.go | 18 +- svc/queries/order_get.go | 11 +- svc/queries/orderlist.go | 18 +- svc/queries/production_product_costing.go | 1702 ++++++++ svc/routes/order_pdf.go | 11 +- svc/routes/production_product_costing.go | 2048 ++++++++++ svc/tmp_backend.err.log | 1 + svc/tmp_backend.out.log | 157 + svc/utils/slog.go | 78 + ui/.quasar/prod-spa/app.js | 75 + ui/.quasar/prod-spa/client-entry.js | 158 + ui/.quasar/prod-spa/client-prefetch.js | 116 + ui/.quasar/prod-spa/quasar-user-options.js | 23 + ui/quasar-dev-9102.err.log | 97 + ui/quasar-dev-9102.out.log | 296 ++ ui/quasar-dev-9103.err.log | 75 + ui/quasar-dev-9103.out.log | 282 ++ ui/quasar-dev.err.log | 310 ++ ui/quasar-dev.out.log | 578 +++ ...g.js.temporary.compiled.1777992888293.mjs} | 0 ui/src/layouts/MainLayout.vue | 12 +- ui/src/pages/ProductionProductCosting.vue | 110 + .../pages/ProductionProductCostingHasCost.vue | 478 +++ .../ProductionProductCostingHasCostDetail.vue | 3474 +++++++++++++++++ ...ProductionProductCostingHasCostHistory.vue | 259 ++ ...ProductionProductCostingMTBolumMapping.vue | 744 ++++ .../pages/ProductionProductCostingNoCost.vue | 516 +++ ui/src/router/routes.js | 36 + ui/src/services/api.js | 61 +- ui/src/utils/slog.js | 53 + 38 files changed, 12676 insertions(+), 8 deletions(-) create mode 100644 go.mod create mode 100644 scripts/sql/production_product_costing_tbStok_fulltext.sql create mode 100644 svc/backend-dev.err.log create mode 100644 svc/backend-dev.out.log create mode 100644 svc/models/production_product_costing.go create mode 100644 svc/queries/production_product_costing.go create mode 100644 svc/routes/production_product_costing.go create mode 100644 svc/tmp_backend.err.log create mode 100644 svc/tmp_backend.out.log create mode 100644 svc/utils/slog.go create mode 100644 ui/.quasar/prod-spa/app.js create mode 100644 ui/.quasar/prod-spa/client-entry.js create mode 100644 ui/.quasar/prod-spa/client-prefetch.js create mode 100644 ui/.quasar/prod-spa/quasar-user-options.js create mode 100644 ui/quasar-dev-9102.err.log create mode 100644 ui/quasar-dev-9102.out.log create mode 100644 ui/quasar-dev-9103.err.log create mode 100644 ui/quasar-dev-9103.out.log create mode 100644 ui/quasar-dev.err.log create mode 100644 ui/quasar-dev.out.log rename ui/{quasar.config.js.temporary.compiled.1776695212119.mjs => quasar.config.js.temporary.compiled.1777992888293.mjs} (100%) create mode 100644 ui/src/pages/ProductionProductCosting.vue create mode 100644 ui/src/pages/ProductionProductCostingHasCost.vue create mode 100644 ui/src/pages/ProductionProductCostingHasCostDetail.vue create mode 100644 ui/src/pages/ProductionProductCostingHasCostHistory.vue create mode 100644 ui/src/pages/ProductionProductCostingMTBolumMapping.vue create mode 100644 ui/src/pages/ProductionProductCostingNoCost.vue create mode 100644 ui/src/utils/slog.js diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..15f6eae --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module bssapp + +go 1.24.5 diff --git a/scripts/sql/production_product_costing_tbStok_fulltext.sql b/scripts/sql/production_product_costing_tbStok_fulltext.sql new file mode 100644 index 0000000..7d9f159 --- /dev/null +++ b/scripts/sql/production_product_costing_tbStok_fulltext.sql @@ -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 diff --git a/svc/.env.local b/svc/.env.local index e6e9b30..08055f7 100644 --- a/svc/.env.local +++ b/svc/.env.local @@ -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 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 diff --git a/svc/backend-dev.err.log b/svc/backend-dev.err.log new file mode 100644 index 0000000..e69de29 diff --git a/svc/backend-dev.out.log b/svc/backend-dev.out.log new file mode 100644 index 0000000..252bd01 --- /dev/null +++ b/svc/backend-dev.out.log @@ -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= actor_user= role=public nav /api/auth/refresh target=" 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=" 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=" 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=" 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=" 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=" 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=" 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=" 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=" 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=" 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=" 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=" 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=" 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=" 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=" 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=" 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=" 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=" 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=" 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=" app=bssapp-backend diff --git a/svc/db/mssql.go b/svc/db/mssql.go index 55b7a90..494b208 100644 --- a/svc/db/mssql.go +++ b/svc/db/mssql.go @@ -13,6 +13,7 @@ import ( ) var MssqlDB *sql.DB +var UretimDB *sql.DB func envInt(name string, fallback int) int { raw := strings.TrimSpace(os.Getenv(name)) @@ -123,3 +124,37 @@ func ConnectMSSQL() error { func GetDB() *sql.DB { 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 +} diff --git a/svc/main.go b/svc/main.go index 1788764..e0193e2 100644 --- a/svc/main.go +++ b/svc/main.go @@ -8,8 +8,10 @@ import ( "bssapp-backend/permissions" "bssapp-backend/repository" "bssapp-backend/routes" + "bssapp-backend/utils" "database/sql" "log" + "log/slog" "net/http" "os" "path" @@ -738,6 +740,101 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router "order", "view", 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 @@ -797,6 +894,8 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router } func main() { + utils.InitSlog() + slog.Info("backend start", "scope", "main") log.Println("🔥🔥🔥 BSSAPP BACKEND STARTED — LOGIN ROUTE SHOULD EXIST 🔥🔥🔥") // ------------------------------------------------------- @@ -825,6 +924,13 @@ func main() { 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() if err != nil { diff --git a/svc/models/production_product_costing.go b/svc/models/production_product_costing.go new file mode 100644 index 0000000..06f2ce3 --- /dev/null +++ b/svc/models/production_product_costing.go @@ -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"` +} diff --git a/svc/queries/get_order_list_excel.go b/svc/queries/get_order_list_excel.go index 285d2bb..e060b17 100644 --- a/svc/queries/get_order_list_excel.go +++ b/svc/queries/get_order_list_excel.go @@ -121,7 +121,14 @@ LEFT JOIN ( -- Paketlenen (OrderLine IsClosed=1) belge PB toplam SUM( 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 THEN c.NetAmount ELSE 0 @@ -131,7 +138,14 @@ LEFT JOIN ( -- Paketlenen TRY toplam SUM( 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' THEN c.NetAmount ELSE 0 diff --git a/svc/queries/order_get.go b/svc/queries/order_get.go index 4e8c8da..dcc9738 100644 --- a/svc/queries/order_get.go +++ b/svc/queries/order_get.go @@ -216,7 +216,16 @@ SELECT L.DeliveryDate, L.PlannedDateOfLading, 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.CreatedDate, L.LastUpdatedUserName, diff --git a/svc/queries/orderlist.go b/svc/queries/orderlist.go index b6e27fb..4d7a515 100644 --- a/svc/queries/orderlist.go +++ b/svc/queries/orderlist.go @@ -143,15 +143,29 @@ JOIN ( ) AS TotalTRY, SUM( CASE + -- "Paketlenen" = satir kapaliysa VEYA satir irsaliyeye/faturaya baglandiysa. + -- Not: IsClosed her zaman guncellenmiyor; trInvoiceLine.OrderLineID iliskiyi yakalar. 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) ELSE 0 END ) AS PackedAmount, SUM( CASE - WHEN ISNULL(l.IsClosed,0) = 1 - AND ISNULL(c.CurrencyCode,'') = 'TRY' + 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' THEN ISNULL(c.NetAmount,0) ELSE 0 END diff --git a/svc/queries/production_product_costing.go b/svc/queries/production_product_costing.go new file mode 100644 index 0000000..704b40a --- /dev/null +++ b/svc/queries/production_product_costing.go @@ -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 +} diff --git a/svc/routes/order_pdf.go b/svc/routes/order_pdf.go index fa29ed7..d6b1bc0 100644 --- a/svc/routes/order_pdf.go +++ b/svc/routes/order_pdf.go @@ -760,7 +760,16 @@ func getOrderLinesFromDB(db *sql.DB, orderID string) ([]OrderLineRaw, error) { P.ProductAtt01Desc, P.ProductAtt02Desc, 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.DOVCode, L.PlannedDateOfLading, diff --git a/svc/routes/production_product_costing.go b/svc/routes/production_product_costing.go new file mode 100644 index 0000000..e0caa5f --- /dev/null +++ b/svc/routes/production_product_costing.go @@ -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}) +} diff --git a/svc/tmp_backend.err.log b/svc/tmp_backend.err.log new file mode 100644 index 0000000..c260a1f --- /dev/null +++ b/svc/tmp_backend.err.log @@ -0,0 +1 @@ +exit status 1 diff --git a/svc/tmp_backend.out.log b/svc/tmp_backend.out.log new file mode 100644 index 0000000..ad89e70 --- /dev/null +++ b/svc/tmp_backend.out.log @@ -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 diff --git a/svc/utils/slog.go b/svc/utils/slog.go new file mode 100644 index 0000000..f3ff28d --- /dev/null +++ b/svc/utils/slog.go @@ -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) +} diff --git a/ui/.quasar/prod-spa/app.js b/ui/.quasar/prod-spa/app.js new file mode 100644 index 0000000..caeaac1 --- /dev/null +++ b/ui/.quasar/prod-spa/app.js @@ -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 " 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 + } +} diff --git a/ui/.quasar/prod-spa/client-entry.js b/ui/.quasar/prod-spa/client-entry.js new file mode 100644 index 0000000..5223e2b --- /dev/null +++ b/ui/.quasar/prod-spa/client-entry.js @@ -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 " 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) + }) + }) + diff --git a/ui/.quasar/prod-spa/client-prefetch.js b/ui/.quasar/prod-spa/client-prefetch.js new file mode 100644 index 0000000..9bbe3c5 --- /dev/null +++ b/ui/.quasar/prod-spa/client-prefetch.js @@ -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 " 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() + }) + }) +} diff --git a/ui/.quasar/prod-spa/quasar-user-options.js b/ui/.quasar/prod-spa/quasar-user-options.js new file mode 100644 index 0000000..ac1dae3 --- /dev/null +++ b/ui/.quasar/prod-spa/quasar-user-options.js @@ -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 " 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} } + diff --git a/ui/quasar-dev-9102.err.log b/ui/quasar-dev-9102.err.log new file mode 100644 index 0000000..37b0064 --- /dev/null +++ b/ui/quasar-dev-9102.err.log @@ -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| +2616| + | ^ +2617| diff --git a/ui/src/pages/ProductionProductCostingHasCost.vue b/ui/src/pages/ProductionProductCostingHasCost.vue new file mode 100644 index 0000000..1994b5e --- /dev/null +++ b/ui/src/pages/ProductionProductCostingHasCost.vue @@ -0,0 +1,478 @@ + + + + + diff --git a/ui/src/pages/ProductionProductCostingHasCostDetail.vue b/ui/src/pages/ProductionProductCostingHasCostDetail.vue new file mode 100644 index 0000000..8c0ba0f --- /dev/null +++ b/ui/src/pages/ProductionProductCostingHasCostDetail.vue @@ -0,0 +1,3474 @@ + + + + + diff --git a/ui/src/pages/ProductionProductCostingHasCostHistory.vue b/ui/src/pages/ProductionProductCostingHasCostHistory.vue new file mode 100644 index 0000000..5366932 --- /dev/null +++ b/ui/src/pages/ProductionProductCostingHasCostHistory.vue @@ -0,0 +1,259 @@ + + + + + diff --git a/ui/src/pages/ProductionProductCostingMTBolumMapping.vue b/ui/src/pages/ProductionProductCostingMTBolumMapping.vue new file mode 100644 index 0000000..49e3fb1 --- /dev/null +++ b/ui/src/pages/ProductionProductCostingMTBolumMapping.vue @@ -0,0 +1,744 @@ + + + + + diff --git a/ui/src/pages/ProductionProductCostingNoCost.vue b/ui/src/pages/ProductionProductCostingNoCost.vue new file mode 100644 index 0000000..7ced4d3 --- /dev/null +++ b/ui/src/pages/ProductionProductCostingNoCost.vue @@ -0,0 +1,516 @@ + + + + + diff --git a/ui/src/router/routes.js b/ui/src/router/routes.js index 1aa9844..be54b72 100644 --- a/ui/src/router/routes.js +++ b/ui/src/router/routes.js @@ -324,6 +324,42 @@ const routes = [ component: () => import('pages/ProductPricing.vue'), 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 ================= */ diff --git a/ui/src/services/api.js b/ui/src/services/api.js index 399bc43..aa8c123 100644 --- a/ui/src/services/api.js +++ b/ui/src/services/api.js @@ -2,6 +2,7 @@ import axios from 'axios' import qs from 'qs' import { useAuthStore } from 'stores/authStore' import { DEFAULT_LOCALE, normalizeLocale } from 'src/i18n/languages' +import { slog } from 'src/utils/slog' const rawBaseUrl = (typeof process !== 'undefined' && process.env?.VITE_API_BASE_URL) || '/api' @@ -81,6 +82,20 @@ function getRequestLocale() { 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) => { const auth = useAuthStore() const url = config.url || '' @@ -92,6 +107,22 @@ api.interceptors.request.use((config) => { config.headers ||= {} 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 }) @@ -148,7 +179,19 @@ function clearSessionAndRedirect() { } 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) => { const requestConfig = error?.config || {} const status = error?.response?.status @@ -169,6 +212,22 @@ api.interceptors.response.use( 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 = status === 401 && !requestConfig._retry && diff --git a/ui/src/utils/slog.js b/ui/src/utils/slog.js new file mode 100644 index 0000000..a95757e --- /dev/null +++ b/ui/src/utils/slog.js @@ -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) + } +}